diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index ad5ffd6797..ccbcf0a603 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -676,7 +676,6 @@ requirement.capture = Capture {0} requirement.onplanet = Control Sector On {0} requirement.onsector = Land On Sector: {0} launch.text = Launch -research.multiplayer = Only the host can research items. map.multiplayer = Only the host can view sectors. uncover = Uncover configure = Configure Loadout diff --git a/core/src/mindustry/core/ContentLoader.java b/core/src/mindustry/core/ContentLoader.java index 377549c4ec..4e2f86410a 100644 --- a/core/src/mindustry/core/ContentLoader.java +++ b/core/src/mindustry/core/ContentLoader.java @@ -27,6 +27,7 @@ import static mindustry.Vars.*; public class ContentLoader{ private ObjectMap[] contentNameMap = new ObjectMap[ContentType.all.length]; private Seq[] contentMap = new Seq[ContentType.all.length]; + private ObjectMap nameMap = new ObjectMap<>(); private MappableContent[][] temporaryMapper; private @Nullable LoadedMod currentMod; private @Nullable Content lastAdded; @@ -188,12 +189,18 @@ public class ContentLoader{ } } contentNameMap[content.getContentType().ordinal()].put(content.name, content); + nameMap.put(content.name, content); } public void setTemporaryMapper(MappableContent[][] temporaryMapper){ this.temporaryMapper = temporaryMapper; } + /** @return the last registered content with the specified name. Note that the content loader makes no attempt to resolve name conflicts. This method can be unreliable. */ + public @Nullable MappableContent byName(String name){ + return nameMap.get(name); + } + public Seq[] getContentMap(){ return contentMap; } diff --git a/core/src/mindustry/core/Logic.java b/core/src/mindustry/core/Logic.java index c6e39cf0ff..af704fcd94 100644 --- a/core/src/mindustry/core/Logic.java +++ b/core/src/mindustry/core/Logic.java @@ -394,7 +394,7 @@ public class Logic implements ApplicationListener{ public static void researched(Content content){ if(!(content instanceof UnlockableContent u)) return; - boolean was = u.unlockedNow(); + boolean was = u.unlockedNowHost(); state.rules.researched.add(u); if(!was){ diff --git a/core/src/mindustry/game/Objectives.java b/core/src/mindustry/game/Objectives.java index f50c8c0ca1..fb947c4bd0 100644 --- a/core/src/mindustry/game/Objectives.java +++ b/core/src/mindustry/game/Objectives.java @@ -19,14 +19,14 @@ public class Objectives{ @Override public boolean complete(){ - return content.unlocked(); + return content.unlockedHost(); } @Override public String display(){ return Core.bundle.format("requirement.research", //TODO broken for multi tech nodes. - (content.techNode == null || content.techNode.parent == null || content.techNode.parent.content.unlocked()) ? + (content.techNode == null || content.techNode.parent == null || content.techNode.parent.content.unlockedHost()) ? (content.emoji() + " " + content.localizedName) : "???"); } } @@ -42,13 +42,13 @@ public class Objectives{ @Override public boolean complete(){ - return content.unlocked(); + return content.unlockedHost(); } @Override public String display(){ return Core.bundle.format("requirement.produce", - content.unlocked() ? (content.emoji() + " " + content.localizedName) : "???"); + content.unlockedHost() ? (content.emoji() + " " + content.localizedName) : "???"); } } diff --git a/core/src/mindustry/io/JsonIO.java b/core/src/mindustry/io/JsonIO.java index e301f5b499..34957d1443 100644 --- a/core/src/mindustry/io/JsonIO.java +++ b/core/src/mindustry/io/JsonIO.java @@ -261,15 +261,8 @@ public class JsonIO{ public UnlockableContent read(Json json, JsonValue jsonData, Class type){ if(jsonData.isNull()) return null; String str = jsonData.asString(); - Item item = Vars.content.item(str); - Liquid liquid = Vars.content.liquid(str); - Block block = Vars.content.block(str); - UnitType unit = Vars.content.unit(str); - return - item != null ? item : - liquid != null ? liquid : - block != null ? block : - unit; + var map = Vars.content.byName(str); + return map instanceof UnlockableContent u ? u : null; } }); diff --git a/core/src/mindustry/ui/dialogs/ResearchDialog.java b/core/src/mindustry/ui/dialogs/ResearchDialog.java index e601e02b5d..03fecf7563 100644 --- a/core/src/mindustry/ui/dialogs/ResearchDialog.java +++ b/core/src/mindustry/ui/dialogs/ResearchDialog.java @@ -47,10 +47,30 @@ public class ResearchDialog extends BaseDialog{ public ItemSeq items; private boolean showTechSelect; + private boolean needsRebuild; public ResearchDialog(){ super(""); + Events.on(ResetEvent.class, e -> { + hide(); + }); + + Events.on(UnlockEvent.class, e -> { + if(net.client() && !needsRebuild){ + needsRebuild = true; + Core.app.post(() -> { + needsRebuild = false; + + checkNodes(root); + view.hoverNode = null; + treeLayout(); + view.rebuild(); + Core.scene.act(); + }); + } + }); + titleTable.remove(); titleTable.clear(); titleTable.top(); @@ -67,7 +87,7 @@ public class ResearchDialog extends BaseDialog{ t.table(Tex.button, in -> { in.defaults().width(300f).height(60f); for(TechNode node : TechTree.roots){ - if(node.requiresUnlock && !node.content.unlocked() && node != getPrefRoot()) continue; + if(node.requiresUnlock && !node.content.unlockedHost() && node != getPrefRoot()) continue; //TODO toggle in.button(node.localizedName(), node.icon(), Styles.flatTogglet, iconMed, () -> { @@ -84,10 +104,11 @@ public class ResearchDialog extends BaseDialog{ addCloseButton(); }}.show(); - }).visible(() -> showTechSelect = TechTree.roots.count(node -> !(node.requiresUnlock && !node.content.unlocked())) > 1).minWidth(300f); + }).visible(() -> showTechSelect = TechTree.roots.count(node -> !(node.requiresUnlock && !node.content.unlockedHost())) > 1).minWidth(300f); margin(0f).marginBottom(8); cont.stack(titleTable, view = new View(), itemDisplay = new ItemsDisplay()).grow(); + itemDisplay.visible(() -> !net.client()); titleTable.toFront(); @@ -177,15 +198,6 @@ public class ResearchDialog extends BaseDialog{ }); } - @Override - public Dialog show(){ - if(net.client()){ - ui.showInfo("@research.multiplayer"); - return this; - } - return show(Core.scene); - } - void checkMargin(){ if(Core.graphics.isPortrait() && showTechSelect){ itemDisplay.marginTop(60f); @@ -361,11 +373,12 @@ public class ResearchDialog extends BaseDialog{ } boolean selectable(TechNode node){ - return node.content.unlocked() || !node.objectives.contains(i -> !i.complete()); + //there's a desync here as far as sectors go, since the client doesn't know about that, but I'm not too concerned + return node.content.unlockedHost() || !node.objectives.contains(i -> !i.complete()); } boolean locked(TechNode node){ - return node.content.locked(); + return !node.content.unlockedHost(); } class LayoutNode extends TreeNode{ @@ -418,31 +431,34 @@ public class ResearchDialog extends BaseDialog{ button.resizeImage(32f); button.getImage().setScaling(Scaling.fit); button.visible(() -> node.visible); - button.clicked(() -> { - if(moved) return; + if(!net.client()){ + button.clicked(() -> { + if(moved) return; - if(mobile){ - hoverNode = button; - rebuild(); - float right = infoTable.getRight(); - if(right > Core.graphics.getWidth()){ - float moveBy = right - Core.graphics.getWidth(); - addAction(new RelativeTemporalAction(){ - { - setDuration(0.1f); - setInterpolation(Interp.fade); - } + if(mobile){ + hoverNode = button; + rebuild(); + float right = infoTable.getRight(); + if(right > Core.graphics.getWidth()){ + float moveBy = right - Core.graphics.getWidth(); + addAction(new RelativeTemporalAction(){ + { + setDuration(0.1f); + setInterpolation(Interp.fade); + } - @Override - protected void updateRelative(float percentDelta){ - panX -= moveBy * percentDelta; - } - }); + @Override + protected void updateRelative(float percentDelta){ + panX -= moveBy * percentDelta; + } + }); + } + }else if(canSpend(node.node) && locked(node.node)){ + spend(node.node); } - }else if(canSpend(node.node) && locked(node.node)){ - spend(node.node); - } - }); + }); + } + button.hovered(() -> { if(!mobile && hoverNode != button && node.visible){ hoverNode = button; @@ -459,6 +475,7 @@ public class ResearchDialog extends BaseDialog{ button.userObject = node.node; button.setSize(nodeSize); button.update(() -> { + button.setDisabled(net.client() && !mobile); float offset = (Core.graphics.getHeight() % 2) / 2f; button.setPosition(node.x + panX + width / 2f, node.y + panY + height / 2f + offset, Align.center); button.getStyle().up = !locked(node.node) ? Tex.buttonOver : !selectable(node.node) || !canSpend(node.node) ? Tex.buttonRed : Tex.button; @@ -498,7 +515,7 @@ public class ResearchDialog extends BaseDialog{ } boolean canSpend(TechNode node){ - if(!selectable(node)) return false; + if(!selectable(node) || net.client()) return false; if(node.requirements.length == 0) return true; @@ -514,6 +531,8 @@ public class ResearchDialog extends BaseDialog{ } void spend(TechNode node){ + if(net.client()) return; + boolean complete = true; boolean[] shine = new boolean[node.requirements.length]; @@ -611,86 +630,90 @@ public class ResearchDialog extends BaseDialog{ desc.left().defaults().left(); desc.add(selectable ? node.content.localizedName : "[accent]???"); desc.row(); - if(locked(node) || debugShowRequirements){ + if(locked(node) || (debugShowRequirements && !net.client())){ - desc.table(t -> { - t.left(); - if(selectable){ + if(net.client()){ + desc.add("@locked").color(Pal.remove); + }else{ + desc.table(t -> { + t.left(); + if(selectable){ - //check if there is any progress, add research progress text - if(Structs.contains(node.finishedRequirements, s -> s.amount > 0)){ - float sum = 0f, used = 0f; - boolean shiny = false; + //check if there is any progress, add research progress text + if(Structs.contains(node.finishedRequirements, s -> s.amount > 0)){ + float sum = 0f, used = 0f; + boolean shiny = false; - for(int i = 0; i < node.requirements.length; i++){ - sum += node.requirements[i].item.cost * node.requirements[i].amount; - used += node.finishedRequirements[i].item.cost * node.finishedRequirements[i].amount; - if(shine != null) shiny |= shine[i]; - } + for(int i = 0; i < node.requirements.length; i++){ + sum += node.requirements[i].item.cost * node.requirements[i].amount; + used += node.finishedRequirements[i].item.cost * node.finishedRequirements[i].amount; + if(shine != null) shiny |= shine[i]; + } - Label label = t.add(Core.bundle.format("research.progress", Math.min((int)(used / sum * 100), 99))).left().get(); - - if(shiny){ - label.setColor(Pal.accent); - label.actions(Actions.color(Color.lightGray, 0.75f, Interp.fade)); - }else{ - label.setColor(Color.lightGray); - } - - t.row(); - } - - for(int i = 0; i < node.requirements.length; i++){ - ItemStack req = node.requirements[i]; - ItemStack completed = node.finishedRequirements[i]; - - //skip finished stacks - if(req.amount <= completed.amount && !debugShowRequirements) continue; - boolean shiny = shine != null && shine[i]; - - t.table(list -> { - int reqAmount = debugShowRequirements ? req.amount : req.amount - completed.amount; - - list.left(); - list.image(req.item.uiIcon).size(8 * 3).padRight(3); - list.add(req.item.localizedName).color(Color.lightGray); - Label label = list.label(() -> " " + - UI.formatAmount(Math.min(items.get(req.item), reqAmount)) + " / " - + UI.formatAmount(reqAmount)).get(); - - Color targetColor = items.has(req.item) ? Color.lightGray : Color.scarlet; + Label label = t.add(Core.bundle.format("research.progress", Math.min((int)(used / sum * 100), 99))).left().get(); if(shiny){ label.setColor(Pal.accent); - label.actions(Actions.color(targetColor, 0.75f, Interp.fade)); + label.actions(Actions.color(Color.lightGray, 0.75f, Interp.fade)); }else{ - label.setColor(targetColor); + label.setColor(Color.lightGray); } - }).fillX().left(); + t.row(); + } + + for(int i = 0; i < node.requirements.length; i++){ + ItemStack req = node.requirements[i]; + ItemStack completed = node.finishedRequirements[i]; + + //skip finished stacks + if(req.amount <= completed.amount && !debugShowRequirements) continue; + boolean shiny = shine != null && shine[i]; + + t.table(list -> { + int reqAmount = debugShowRequirements ? req.amount : req.amount - completed.amount; + + list.left(); + list.image(req.item.uiIcon).size(8 * 3).padRight(3); + list.add(req.item.localizedName).color(Color.lightGray); + Label label = list.label(() -> " " + + UI.formatAmount(Math.min(items.get(req.item), reqAmount)) + " / " + + UI.formatAmount(reqAmount)).get(); + + Color targetColor = items.has(req.item) ? Color.lightGray : Color.scarlet; + + if(shiny){ + label.setColor(Pal.accent); + label.actions(Actions.color(targetColor, 0.75f, Interp.fade)); + }else{ + label.setColor(targetColor); + } + + }).fillX().left(); + t.row(); + } + }else if(node.objectives.size > 0){ + t.table(r -> { + r.add("@complete").colspan(2).left(); + r.row(); + for(Objective o : node.objectives){ + if(o.complete()) continue; + + r.add("> " + o.display()).color(Color.lightGray).left(); + r.image(o.complete() ? Icon.ok : Icon.cancel, o.complete() ? Color.lightGray : Color.scarlet).padLeft(3); + r.row(); + } + }); t.row(); } - }else if(node.objectives.size > 0){ - t.table(r -> { - r.add("@complete").colspan(2).left(); - r.row(); - for(Objective o : node.objectives){ - if(o.complete()) continue; - - r.add("> " + o.display()).color(Color.lightGray).left(); - r.image(o.complete() ? Icon.ok : Icon.cancel, o.complete() ? Color.lightGray : Color.scarlet).padLeft(3); - r.row(); - } - }); - t.row(); - } - }); + }); + } }else{ desc.add("@completed"); } }).pad(9); - if(mobile && locked(node)){ + if(mobile && locked(node) && !net.client()){ b.row(); b.button("@research", Icon.ok, new TextButtonStyle(){{ disabled = Tex.button;