From 3051598b929ff8b9fa556b3986f5d45c480c6ceb Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 24 May 2020 17:01:17 -0400 Subject: [PATCH] Various sync fixes --- .../mindustry/annotations/Annotations.java | 9 +++ .../annotations/entity/EntityIO.java | 21 +++++-- .../annotations/entity/EntityProcess.java | 5 +- .../resources/revisions/PlayerEntity/1.json | 1 + core/src/mindustry/content/UnitTypes.java | 4 +- core/src/mindustry/core/NetServer.java | 13 ++-- .../mindustry/entities/comp/FlyingComp.java | 2 +- .../src/mindustry/entities/comp/LegsComp.java | 2 +- .../mindustry/entities/comp/PlayerComp.java | 15 ++++- core/src/mindustry/entities/comp/PosComp.java | 2 +- core/src/mindustry/entities/comp/RotComp.java | 2 +- core/src/mindustry/entities/comp/VelComp.java | 3 +- core/src/mindustry/input/DesktopInput.java | 12 +--- .../world/blocks/storage/CoreBlock.java | 59 +++---------------- 14 files changed, 71 insertions(+), 79 deletions(-) create mode 100644 annotations/src/main/resources/revisions/PlayerEntity/1.json diff --git a/annotations/src/main/java/mindustry/annotations/Annotations.java b/annotations/src/main/java/mindustry/annotations/Annotations.java index fbc5845a18..99766716f1 100644 --- a/annotations/src/main/java/mindustry/annotations/Annotations.java +++ b/annotations/src/main/java/mindustry/annotations/Annotations.java @@ -23,6 +23,15 @@ public class Annotations{ public @interface SyncField{ /** If true, the field will be linearly interpolated. If false, it will be interpolated as an angle. */ boolean value(); + /** If true, the field is clamped to 0-1. */ + boolean clamped() default false; + } + + /** Indicates that a field will not be read from the server when syncing. */ + @Target({ElementType.FIELD}) + @Retention(RetentionPolicy.SOURCE) + public @interface SyncLocal{ + } /** Indicates that a component field is imported from other components. This means it doesn't actually exist. */ diff --git a/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java b/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java index 853a96b162..def31f42f3 100644 --- a/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java +++ b/annotations/src/main/java/mindustry/annotations/entity/EntityIO.java @@ -61,7 +61,9 @@ public class EntityIO{ //add new revision if it doesn't match or there are no revisions if(revisions.isEmpty() || !revisions.peek().equal(fields)){ - revisions.add(new Revision(nextRevision, fields.map(f -> new RevisionField(f.name, f.type.toString(), f.type.isPrimitive() ? BaseProcessor.typeSize(f.type.toString()) : -1)))); + revisions.add(new Revision(nextRevision, + fields.map(f -> new RevisionField(f.name, f.type.toString(), + f.type.isPrimitive() ? BaseProcessor.typeSize(f.type.toString()) : -1)))); //write revision directory.child(nextRevision + ".json").writeString(json.toJson(revisions.peek())); } @@ -108,7 +110,7 @@ public class EntityIO{ } } - void writeSync(MethodSpec.Builder method, boolean write, Array syncFields) throws Exception{ + void writeSync(MethodSpec.Builder method, boolean write, Array syncFields, Array allFields) throws Exception{ this.method = method; this.write = write; @@ -123,15 +125,22 @@ public class EntityIO{ //base read code st("if(lastUpdated != 0) updateSpacing = $T.timeSinceMillis(lastUpdated)", Time.class); st("lastUpdated = $T.millis()", Time.class); + st("boolean islocal = isLocal()"); //add code for reading revision for(RevisionField field : rev.fields){ - Svar sf = syncFields.find(s -> s.name().equals(field.name)); - if(sf != null){ + Svar var = allFields.find(s -> s.name().equals(field.name)); + boolean sf = var.has(SyncField.class), sl = var.has(SyncLocal.class); + + if(sl) cont("if(!islocal)"); + + if(sf){ st(field.name + lastSuf + " = this." + field.name); } - io(field.type, "this." + (sf != null ? field.name + targetSuf : field.name) + " = "); + io(field.type, "this." + (sf ? field.name + targetSuf : field.name) + " = "); + + if(sl) econt(); } st("afterSync()"); @@ -173,7 +182,7 @@ public class EntityIO{ //write interpolated data, using slerp / lerp for(Svar field : fields){ String name = field.name(), targetName = name + targetSuf, lastName = name + lastSuf; - st("$L = $T.$L($L, $L, alpha)", name, Mathf.class, field.annotation(SyncField.class).value() ? "lerp" : "slerp", lastName, targetName); + st("$L = $L($T.$L($L, $L, alpha))", name, field.annotation(SyncField.class).clamped() ? "arc.math.Mathf.clamp" : "", Mathf.class, field.annotation(SyncField.class).value() ? "lerp" : "slerp", lastName, targetName); } ncont("else"); //no meaningful data has arrived yet diff --git a/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java b/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java index 1800b3c311..fe8fae4437 100644 --- a/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java +++ b/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java @@ -220,6 +220,7 @@ public class EntityProcess extends BaseProcessor{ //all SyncField fields Array syncedFields = new Array<>(); + Array allFields = new Array<>(); //add all components for(Stype comp : components){ @@ -253,6 +254,8 @@ public class EntityProcess extends BaseProcessor{ builder.addField(fbuilder.build()); specVariables.put(builder.fieldSpecs.get(builder.fieldSpecs.size() - 1), f); + allFields.add(f); + //add extra sync fields if(f.has(SyncField.class)){ if(!f.tname().toString().equals("float")) err("All SyncFields must be of type float", f); @@ -357,7 +360,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); + io.writeSync(mbuilder, first.name().equals("writeSync"), syncedFields, allFields); } //SPECIAL CASE: sync I/O code for writing to/from a manual buffer diff --git a/annotations/src/main/resources/revisions/PlayerEntity/1.json b/annotations/src/main/resources/revisions/PlayerEntity/1.json new file mode 100644 index 0000000000..ef3fe88681 --- /dev/null +++ b/annotations/src/main/resources/revisions/PlayerEntity/1.json @@ -0,0 +1 @@ +{version:1,fields:[{name:admin,type:boolean,size:1},{name:boosting,type:boolean,size:1},{name:color,type:arc.graphics.Color,size:-1},{name:mouseX,type:float,size:4},{name:mouseY,type:float,size:4},{name:name,type:java.lang.String,size:-1},{name:shooting,type:boolean,size:1},{name:team,type:mindustry.game.Team,size:-1},{name:typing,type:boolean,size:1},{name:unit,type:Unitc,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]} \ No newline at end of file diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 07e1c45e6d..ac789fde21 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -334,12 +334,12 @@ public class UnitTypes implements ContentList{ accel = 0.1f; range = 70f; itemCapacity = 30; - health = 400; + health = 80f; engineOffset = 6f; hitsize = 8f; weapons.add(new Weapon("small-basic-weapon"){{ - reload = 20f; + reload = 25f; x = -1f; y = -1f; shootX = 3.5f; diff --git a/core/src/mindustry/core/NetServer.java b/core/src/mindustry/core/NetServer.java index 10ded99a92..6ea2056176 100644 --- a/core/src/mindustry/core/NetServer.java +++ b/core/src/mindustry/core/NetServer.java @@ -548,10 +548,16 @@ public class NetServer implements ApplicationListener{ connection.viewWidth = viewWidth; connection.viewHeight = viewHeight; + //disable shooting when a mech flies + if(!player.dead() && player.unit().isFlying() && !player.unit().type().flying){ + shooting = false; + } + player.mouseX(pointerX); player.mouseY(pointerY); player.typing(chatting); player.shooting(shooting); + player.boosting(boosting); player.unit().controlWeapons(shooting, shooting); player.unit().aim(pointerX, pointerY); @@ -593,8 +599,6 @@ public class NetServer implements ApplicationListener{ connection.rejectedRequests.clear(); if(!player.dead()){ - player.unit().elevation(!player.unit().type().flying && boosting && player.unit().type().canBoost ? 1f : 0f); - Unitc unit = player.unit(); long elapsed = Time.timeSinceMillis(connection.lastRecievedClientTime); float maxSpeed = player.dead() ? Float.MAX_VALUE : player.unit().type().speed; @@ -645,8 +649,9 @@ public class NetServer implements ApplicationListener{ //read sync data so it can be used for interpolation for the server unit.readSyncManual(fbuffer); - //TODO fix - unit.vel().set(xVelocity, yVelocity); //only for visual calculation purposes, doesn't actually update the player (TODO or does it?) + //TODO clients shouldn't care about velocities, so should it always just get set to 0? why even save it? + //[[ignore sent velocity values, set it to the delta movement vector instead]] + //unit.vel().set(vector); }else{ player.x(x); player.y(y); diff --git a/core/src/mindustry/entities/comp/FlyingComp.java b/core/src/mindustry/entities/comp/FlyingComp.java index c23e2e241e..c0b4865ed7 100644 --- a/core/src/mindustry/entities/comp/FlyingComp.java +++ b/core/src/mindustry/entities/comp/FlyingComp.java @@ -17,7 +17,7 @@ abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{ @Import float x, y; @Import Vec2 vel; - float elevation; + @SyncField(value = true, clamped = true) @SyncLocal float elevation; private transient boolean wasFlying; transient float drownTime; transient float splashTimer; diff --git a/core/src/mindustry/entities/comp/LegsComp.java b/core/src/mindustry/entities/comp/LegsComp.java index d8afd9a2aa..37cf33a244 100644 --- a/core/src/mindustry/entities/comp/LegsComp.java +++ b/core/src/mindustry/entities/comp/LegsComp.java @@ -7,7 +7,7 @@ import mindustry.gen.*; @Component abstract class LegsComp implements Posc, Flyingc, Hitboxc, Unitc, Legsc, ElevationMovec{ - @SyncField(false) float baseRotation; + @SyncField(false) @SyncLocal float baseRotation; transient float walkTime; @Override diff --git a/core/src/mindustry/entities/comp/PlayerComp.java b/core/src/mindustry/entities/comp/PlayerComp.java index 7f956ded9b..d6944f6192 100644 --- a/core/src/mindustry/entities/comp/PlayerComp.java +++ b/core/src/mindustry/entities/comp/PlayerComp.java @@ -18,6 +18,7 @@ import mindustry.net.Administration.*; import mindustry.net.*; import mindustry.net.Packets.*; import mindustry.ui.*; +import mindustry.world.*; import mindustry.world.blocks.storage.CoreBlock.*; import static mindustry.Vars.*; @@ -32,7 +33,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra @ReadOnly Team team = Team.sharded; String name = "noname"; - boolean admin, typing, shooting; + boolean admin, typing, shooting, boosting; Color color = new Color(); float mouseX, mouseY; @@ -71,7 +72,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra public void afterSync(){ unit.aim(mouseX, mouseY); //this is only necessary when the thing being controlled isn't synced - unit.isShooting(shooting); + unit.controlWeapons(shooting, shooting); //extra precaution, necessary for non-synced things unit.controller(this); } @@ -89,14 +90,24 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra y(unit.y()); unit.team(team); deathTimer = 0; + + //update some basic state to sync things + if(unit.type().canBoost){ + Tile tile = unit.tileOn(); + unit.elevation(Mathf.approachDelta(unit.elevation(), (tile != null && tile.solid()) || boosting ? 1f : 0f, 0.08f)); + } }else if(core != null){ + //have a small delay before death to prevent the camera from jumping around too quickly + //(this is not for balance) deathTimer += Time.delta(); if(deathTimer >= deathDelay){ core.requestSpawn((Playerc)this); + deathTimer = 0; } } textFadeTime -= Time.delta() / (60 * 5); + } public void team(Team team){ diff --git a/core/src/mindustry/entities/comp/PosComp.java b/core/src/mindustry/entities/comp/PosComp.java index 6fb2b08d98..fc4fd92594 100644 --- a/core/src/mindustry/entities/comp/PosComp.java +++ b/core/src/mindustry/entities/comp/PosComp.java @@ -12,7 +12,7 @@ import static mindustry.Vars.world; @Component abstract class PosComp implements Position{ - @SyncField(true) float x, y; + @SyncField(true) @SyncLocal float x, y; void set(float x, float y){ this.x = x; diff --git a/core/src/mindustry/entities/comp/RotComp.java b/core/src/mindustry/entities/comp/RotComp.java index 427bee4c95..77651b0c26 100644 --- a/core/src/mindustry/entities/comp/RotComp.java +++ b/core/src/mindustry/entities/comp/RotComp.java @@ -5,5 +5,5 @@ import mindustry.gen.*; @Component abstract class RotComp implements Entityc{ - @SyncField(false) float rotation; + @SyncField(false) @SyncLocal float rotation; } diff --git a/core/src/mindustry/entities/comp/VelComp.java b/core/src/mindustry/entities/comp/VelComp.java index 583f2331f5..c6869f3204 100644 --- a/core/src/mindustry/entities/comp/VelComp.java +++ b/core/src/mindustry/entities/comp/VelComp.java @@ -9,7 +9,8 @@ import mindustry.gen.*; abstract class VelComp implements Posc{ @Import float x, y; - final Vec2 vel = new Vec2(); + //TODO @SyncLocal this? does it even need to be sent? + transient final Vec2 vel = new Vec2(); transient float drag = 0f; //velocity needs to be called first, as it affects delta and lastPosition diff --git a/core/src/mindustry/input/DesktopInput.java b/core/src/mindustry/input/DesktopInput.java index e49fa806a9..4d7849e908 100644 --- a/core/src/mindustry/input/DesktopInput.java +++ b/core/src/mindustry/input/DesktopInput.java @@ -588,15 +588,9 @@ public class DesktopInput extends InputHandler{ } unit.aim(unit.type().faceTarget ? Core.input.mouseWorld() : Tmp.v1.trns(unit.rotation(), Core.input.mouseWorld().dst(unit)).add(unit.x(), unit.y())); + unit.controlWeapons(true, isShooting && !(!unit.type().flying && unit.isFlying())); - unit.controlWeapons(true, isShooting); - - isBoosting = Core.input.keyDown(Binding.boost); - - if(unit.type().canBoost){ - Tile tile = unit.tileOn(); - - unit.elevation(Mathf.approachDelta(unit.elevation(), (tile != null && tile.solid()) || (isBoosting && !movement.isZero()) ? 1f : 0f, 0.08f)); - } + isBoosting = Core.input.keyDown(Binding.boost) && !movement.isZero(); + player.boosting(isBoosting); } } diff --git a/core/src/mindustry/world/blocks/storage/CoreBlock.java b/core/src/mindustry/world/blocks/storage/CoreBlock.java index 405d2449a6..e505ca32d8 100644 --- a/core/src/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/mindustry/world/blocks/storage/CoreBlock.java @@ -43,14 +43,15 @@ public class CoreBlock extends StorageBlock{ CoreEntity entity = tile.ent(); CoreBlock block = (CoreBlock)tile.block(); Fx.spawn.at(entity); - entity.progress = 0; - Unitc unit = block.unitType.create(tile.team()); - unit.set(entity); - unit.impulse(0f, 8f); - unit.controller(player); - unit.spawnedByCore(true); - unit.add(); + if(!net.client()){ + Unitc unit = block.unitType.create(tile.team()); + unit.set(entity); + unit.impulse(0f, 2f); + unit.controller(player); + unit.spawnedByCore(true); + unit.add(); + } } @Override @@ -78,20 +79,10 @@ public class CoreBlock extends StorageBlock{ } public class CoreEntity extends TileEntity{ - protected float time, heat, progress; protected int storageCapacity; - protected boolean shouldBuild; - protected Playerc lastRequested; public void requestSpawn(Playerc player){ - shouldBuild = true; - if(lastRequested == null){ - lastRequested = player; - } - - if(progress >= 1f){ - Call.onPlayerSpawn(tile, player); - } + Call.onPlayerSpawn(tile, player); } @Override @@ -210,17 +201,6 @@ public class CoreBlock extends StorageBlock{ state.teams.registerCore(this); } - @Override - public void draw(){ - super.draw(); - - if(heat > 0.001f){ - Draw.draw(Layer.blockOver, () -> { - Drawf.respawn(this, heat, progress, time, unitType, lastRequested); - }); - } - } - @Override public void handleItem(Tilec source, Item item){ if(net.server() || !net.active()){ @@ -230,26 +210,5 @@ public class CoreBlock extends StorageBlock{ } } } - - @Override - public void updateTile(){ - - if(shouldBuild){ - heat = Mathf.lerpDelta(heat, 1f, 0.1f); - time += delta(); - progress += 1f / state.rules.respawnTime * delta(); - }else{ - progress = 0f; - heat = Mathf.lerpDelta(heat, 0f, 0.1f); - } - - shouldBuild = false; - lastRequested = null; - } - - @Override - public boolean shouldActiveSound(){ - return shouldBuild; - } } }