diff --git a/annotations/src/main/java/mindustry/annotations/Annotations.java b/annotations/src/main/java/mindustry/annotations/Annotations.java index 9960f673eb..24b00c5f76 100644 --- a/annotations/src/main/java/mindustry/annotations/Annotations.java +++ b/annotations/src/main/java/mindustry/annotations/Annotations.java @@ -34,6 +34,14 @@ public class Annotations{ } + /** Indicates that a field should not be synced to clients (but may still be non-transient) */ + @Target({ElementType.FIELD}) + @Retention(RetentionPolicy.SOURCE) + public @interface NoSync{ + + } + + /** Indicates that a component field is imported from other components. This means it doesn't actually exist. */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.SOURCE) diff --git a/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java b/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java index 7f817fe562..1d6e6a0aec 100644 --- a/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java +++ b/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java @@ -118,7 +118,7 @@ public class EntityIO{ } } - void writeSync(MethodSpec.Builder method, boolean write, Seq syncFields, Seq allFields) throws Exception{ + void writeSync(MethodSpec.Builder method, boolean write, Seq allFields) throws Exception{ this.method = method; this.write = write; @@ -138,6 +138,7 @@ public class EntityIO{ //add code for reading revision for(RevisionField field : rev.fields){ Svar var = allFields.find(s -> s.name().equals(field.name)); + if(var == null || var.has(NoSync.class)) continue; boolean sf = var.has(SyncField.class), sl = var.has(SyncLocal.class); if(sl) cont("if(!islocal)"); diff --git a/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java b/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java index ba44aa5b9a..3fa62d8446 100644 --- a/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java +++ b/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java @@ -490,7 +490,7 @@ public class EntityProcess extends BaseProcessor{ //SPECIAL CASE: sync I/O code if((first.name().equals("readSync") || first.name().equals("writeSync"))){ - io.writeSync(mbuilder, first.name().equals("writeSync"), syncedFields, allFields); + io.writeSync(mbuilder, first.name().equals("writeSync"), allFields); } //SPECIAL CASE: sync I/O code for writing to/from a manual buffer diff --git a/annotations/src/main/resources/revisions/PlayerComp/1.json b/annotations/src/main/resources/revisions/PlayerComp/1.json new file mode 100644 index 0000000000..52d4a67cae --- /dev/null +++ b/annotations/src/main/resources/revisions/PlayerComp/1.json @@ -0,0 +1 @@ +{version:1,fields:[{name:admin,type:boolean},{name:boosting,type:boolean},{name:color,type:arc.graphics.Color},{name:lastCommand,type:mindustry.ai.UnitCommand},{name:mouseX,type:float},{name:mouseY,type:float},{name:name,type:java.lang.String},{name:shooting,type:boolean},{name:team,type:mindustry.game.Team},{name:typing,type:boolean},{name:unit,type:Unit},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/core/src/mindustry/ai/UnitCommand.java b/core/src/mindustry/ai/UnitCommand.java index 79bf619a77..0cc864fd83 100644 --- a/core/src/mindustry/ai/UnitCommand.java +++ b/core/src/mindustry/ai/UnitCommand.java @@ -2,6 +2,7 @@ package mindustry.ai; import arc.*; import arc.func.*; +import arc.scene.style.*; import arc.struct.*; import mindustry.ai.types.*; import mindustry.entities.units.*; @@ -60,6 +61,10 @@ public class UnitCommand{ return Core.bundle.get("command." + name); } + public TextureRegionDrawable getIcon(){ + return Icon.icons.get(icon, Icon.cancel); + } + @Override public String toString(){ return "UnitCommand:" + name; diff --git a/core/src/mindustry/entities/comp/PlayerComp.java b/core/src/mindustry/entities/comp/PlayerComp.java index ec3c5749f3..a28a293c16 100644 --- a/core/src/mindustry/entities/comp/PlayerComp.java +++ b/core/src/mindustry/entities/comp/PlayerComp.java @@ -8,6 +8,8 @@ import arc.scene.ui.layout.*; import arc.util.*; import arc.util.pooling.*; import mindustry.*; +import mindustry.ai.*; +import mindustry.ai.types.*; import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.entities.units.*; @@ -36,6 +38,8 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra @ReadOnly Team team = Team.sharded; @SyncLocal boolean typing, shooting, boosting; @SyncLocal float mouseX, mouseY; + /** command the unit had before it was controlled. */ + @Nullable @NoSync UnitCommand lastCommand; boolean admin; String name = "frog"; Color color = new Color(); @@ -203,9 +207,18 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra if(unit == null) throw new IllegalArgumentException("Unit cannot be null. Use clearUnit() instead."); if(this.unit == unit) return; + //save last command this unit had + if(unit.controller() instanceof CommandAI ai){ + lastCommand = ai.command; + } + if(this.unit != Nulls.unit){ //un-control the old unit this.unit.resetController(); + //restore last command issued before it was controlled + if(lastCommand != null && this.unit.controller() instanceof CommandAI ai){ + ai.command(lastCommand); + } } this.unit = unit; if(unit != Nulls.unit){ diff --git a/core/src/mindustry/io/TypeIO.java b/core/src/mindustry/io/TypeIO.java index a12c2f5f7d..8be6b0cf90 100644 --- a/core/src/mindustry/io/TypeIO.java +++ b/core/src/mindustry/io/TypeIO.java @@ -301,12 +301,13 @@ public class TypeIO{ return Nulls.unit; } - public static void writeCommand(Writes write, UnitCommand command){ - write.b(command.id); + public static void writeCommand(Writes write, @Nullable UnitCommand command){ + write.b(command == null ? 255 : command.id); } - public static UnitCommand readCommand(Reads read){ - return UnitCommand.all.get(read.ub()); + public static @Nullable UnitCommand readCommand(Reads read){ + int val = read.ub(); + return val == 255 ? null : UnitCommand.all.get(val); } public static void writeEntity(Writes write, Entityc entity){ diff --git a/core/src/mindustry/world/blocks/units/Reconstructor.java b/core/src/mindustry/world/blocks/units/Reconstructor.java index 52b2270b47..8ff7554706 100644 --- a/core/src/mindustry/world/blocks/units/Reconstructor.java +++ b/core/src/mindustry/world/blocks/units/Reconstructor.java @@ -1,13 +1,18 @@ package mindustry.world.blocks.units; import arc.*; +import arc.Graphics.*; +import arc.Graphics.Cursor.*; import arc.graphics.g2d.*; import arc.math.*; import arc.math.geom.*; +import arc.scene.ui.*; +import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; import arc.util.io.*; import mindustry.*; +import mindustry.ai.*; import mindustry.content.*; import mindustry.entities.*; import mindustry.entities.units.*; @@ -35,6 +40,7 @@ public class Reconstructor extends UnitBlock{ regionRotated2 = 2; commandable = true; ambientSound = Sounds.respawning; + configurable = true; } @Override @@ -132,6 +138,7 @@ public class Reconstructor extends UnitBlock{ public class ReconstructorBuild extends UnitBuild{ public @Nullable Vec2 commandPos; + public @Nullable UnitCommand command; public float fraction(){ return progress / constructTime; @@ -157,6 +164,51 @@ public class Reconstructor extends UnitBlock{ return hasUpgrade(unit.type) && !upgrade(unit.type).isBanned(); } + public boolean canSetCommand(){ + var output = unit(); + return output != null && output.commands.length > 1; + } + + @Override + public Cursor getCursor(){ + return canSetCommand() ? super.getCursor() : SystemCursor.arrow; + } + + @Override + public boolean shouldShowConfigure(Player player){ + return canSetCommand(); + } + + @Override + public void buildConfiguration(Table table){ + var unit = unit(); + + if(unit == null){ + deselect(); + return; + } + + var group = new ButtonGroup(); + group.setMinCheckCount(0); + int i = 0, columns = 4; + + table.background(Styles.black6); + + var list = unit().commands; + for(var item : list){ + ImageButton button = table.button(item.getIcon(), Styles.clearNoneTogglei, 40f, () -> { + command = (command == item ? null : item); + deselect(); + }).tooltip(item.localized()).group(group).get(); + + button.update(() -> button.setChecked(command == item)); + + if(++i % columns == 0){ + table.row(); + } + } + } + @Override public boolean acceptPayload(Building source, Payload payload){ if(!(this.payload == null @@ -252,9 +304,17 @@ public class Reconstructor extends UnitBlock{ //upgrade the unit if(progress >= constructTime){ payload.unit = upgrade(payload.unit.type).create(payload.unit.team()); - if(commandPos != null && payload.unit.isCommandable()){ - payload.unit.command().commandPosition(commandPos); + + if(payload.unit.isCommandable()){ + if(commandPos != null){ + payload.unit.command().commandPosition(commandPos); + } + if(command != null){ + //this already checks if it is a valid command for the unit type + payload.unit.command().command(command); + } } + progress %= 1f; Effect.shake(2f, 3f, this); Fx.producesmoke.at(this); @@ -303,7 +363,7 @@ public class Reconstructor extends UnitBlock{ @Override public byte version(){ - return 2; + return 3; } @Override @@ -312,6 +372,7 @@ public class Reconstructor extends UnitBlock{ write.f(progress); TypeIO.writeVecNullable(write, commandPos); + TypeIO.writeCommand(write, command); } @Override @@ -325,6 +386,10 @@ public class Reconstructor extends UnitBlock{ if(revision >= 2){ commandPos = TypeIO.readVecNullable(read); } + + if(revision >= 3){ + command = TypeIO.readCommand(read); + } } }