Various sync fixes
This commit is contained in:
@@ -23,6 +23,15 @@ public class Annotations{
|
|||||||
public @interface SyncField{
|
public @interface SyncField{
|
||||||
/** If true, the field will be linearly interpolated. If false, it will be interpolated as an angle. */
|
/** If true, the field will be linearly interpolated. If false, it will be interpolated as an angle. */
|
||||||
boolean value();
|
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. */
|
/** Indicates that a component field is imported from other components. This means it doesn't actually exist. */
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ public class EntityIO{
|
|||||||
|
|
||||||
//add new revision if it doesn't match or there are no revisions
|
//add new revision if it doesn't match or there are no revisions
|
||||||
if(revisions.isEmpty() || !revisions.peek().equal(fields)){
|
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
|
//write revision
|
||||||
directory.child(nextRevision + ".json").writeString(json.toJson(revisions.peek()));
|
directory.child(nextRevision + ".json").writeString(json.toJson(revisions.peek()));
|
||||||
}
|
}
|
||||||
@@ -108,7 +110,7 @@ public class EntityIO{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeSync(MethodSpec.Builder method, boolean write, Array<Svar> syncFields) throws Exception{
|
void writeSync(MethodSpec.Builder method, boolean write, Array<Svar> syncFields, Array<Svar> allFields) throws Exception{
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.write = write;
|
this.write = write;
|
||||||
|
|
||||||
@@ -123,15 +125,22 @@ public class EntityIO{
|
|||||||
//base read code
|
//base read code
|
||||||
st("if(lastUpdated != 0) updateSpacing = $T.timeSinceMillis(lastUpdated)", Time.class);
|
st("if(lastUpdated != 0) updateSpacing = $T.timeSinceMillis(lastUpdated)", Time.class);
|
||||||
st("lastUpdated = $T.millis()", Time.class);
|
st("lastUpdated = $T.millis()", Time.class);
|
||||||
|
st("boolean islocal = isLocal()");
|
||||||
|
|
||||||
//add code for reading revision
|
//add code for reading revision
|
||||||
for(RevisionField field : rev.fields){
|
for(RevisionField field : rev.fields){
|
||||||
Svar sf = syncFields.find(s -> s.name().equals(field.name));
|
Svar var = allFields.find(s -> s.name().equals(field.name));
|
||||||
if(sf != null){
|
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);
|
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()");
|
st("afterSync()");
|
||||||
@@ -173,7 +182,7 @@ public class EntityIO{
|
|||||||
//write interpolated data, using slerp / lerp
|
//write interpolated data, using slerp / lerp
|
||||||
for(Svar field : fields){
|
for(Svar field : fields){
|
||||||
String name = field.name(), targetName = name + targetSuf, lastName = name + lastSuf;
|
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
|
ncont("else"); //no meaningful data has arrived yet
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ public class EntityProcess extends BaseProcessor{
|
|||||||
|
|
||||||
//all SyncField fields
|
//all SyncField fields
|
||||||
Array<Svar> syncedFields = new Array<>();
|
Array<Svar> syncedFields = new Array<>();
|
||||||
|
Array<Svar> allFields = new Array<>();
|
||||||
|
|
||||||
//add all components
|
//add all components
|
||||||
for(Stype comp : components){
|
for(Stype comp : components){
|
||||||
@@ -253,6 +254,8 @@ public class EntityProcess extends BaseProcessor{
|
|||||||
builder.addField(fbuilder.build());
|
builder.addField(fbuilder.build());
|
||||||
specVariables.put(builder.fieldSpecs.get(builder.fieldSpecs.size() - 1), f);
|
specVariables.put(builder.fieldSpecs.get(builder.fieldSpecs.size() - 1), f);
|
||||||
|
|
||||||
|
allFields.add(f);
|
||||||
|
|
||||||
//add extra sync fields
|
//add extra sync fields
|
||||||
if(f.has(SyncField.class)){
|
if(f.has(SyncField.class)){
|
||||||
if(!f.tname().toString().equals("float")) err("All SyncFields must be of type float", f);
|
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
|
//SPECIAL CASE: sync I/O code
|
||||||
if((first.name().equals("readSync") || first.name().equals("writeSync"))){
|
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
|
//SPECIAL CASE: sync I/O code for writing to/from a manual buffer
|
||||||
|
|||||||
@@ -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}]}
|
||||||
@@ -334,12 +334,12 @@ public class UnitTypes implements ContentList{
|
|||||||
accel = 0.1f;
|
accel = 0.1f;
|
||||||
range = 70f;
|
range = 70f;
|
||||||
itemCapacity = 30;
|
itemCapacity = 30;
|
||||||
health = 400;
|
health = 80f;
|
||||||
engineOffset = 6f;
|
engineOffset = 6f;
|
||||||
hitsize = 8f;
|
hitsize = 8f;
|
||||||
|
|
||||||
weapons.add(new Weapon("small-basic-weapon"){{
|
weapons.add(new Weapon("small-basic-weapon"){{
|
||||||
reload = 20f;
|
reload = 25f;
|
||||||
x = -1f;
|
x = -1f;
|
||||||
y = -1f;
|
y = -1f;
|
||||||
shootX = 3.5f;
|
shootX = 3.5f;
|
||||||
|
|||||||
@@ -548,10 +548,16 @@ public class NetServer implements ApplicationListener{
|
|||||||
connection.viewWidth = viewWidth;
|
connection.viewWidth = viewWidth;
|
||||||
connection.viewHeight = viewHeight;
|
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.mouseX(pointerX);
|
||||||
player.mouseY(pointerY);
|
player.mouseY(pointerY);
|
||||||
player.typing(chatting);
|
player.typing(chatting);
|
||||||
player.shooting(shooting);
|
player.shooting(shooting);
|
||||||
|
player.boosting(boosting);
|
||||||
|
|
||||||
player.unit().controlWeapons(shooting, shooting);
|
player.unit().controlWeapons(shooting, shooting);
|
||||||
player.unit().aim(pointerX, pointerY);
|
player.unit().aim(pointerX, pointerY);
|
||||||
@@ -593,8 +599,6 @@ public class NetServer implements ApplicationListener{
|
|||||||
connection.rejectedRequests.clear();
|
connection.rejectedRequests.clear();
|
||||||
|
|
||||||
if(!player.dead()){
|
if(!player.dead()){
|
||||||
player.unit().elevation(!player.unit().type().flying && boosting && player.unit().type().canBoost ? 1f : 0f);
|
|
||||||
|
|
||||||
Unitc unit = player.unit();
|
Unitc unit = player.unit();
|
||||||
long elapsed = Time.timeSinceMillis(connection.lastRecievedClientTime);
|
long elapsed = Time.timeSinceMillis(connection.lastRecievedClientTime);
|
||||||
float maxSpeed = player.dead() ? Float.MAX_VALUE : player.unit().type().speed;
|
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
|
//read sync data so it can be used for interpolation for the server
|
||||||
unit.readSyncManual(fbuffer);
|
unit.readSyncManual(fbuffer);
|
||||||
|
|
||||||
//TODO fix
|
//TODO clients shouldn't care about velocities, so should it always just get set to 0? why even save it?
|
||||||
unit.vel().set(xVelocity, yVelocity); //only for visual calculation purposes, doesn't actually update the player (TODO or does it?)
|
//[[ignore sent velocity values, set it to the delta movement vector instead]]
|
||||||
|
//unit.vel().set(vector);
|
||||||
}else{
|
}else{
|
||||||
player.x(x);
|
player.x(x);
|
||||||
player.y(y);
|
player.y(y);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
|
|||||||
@Import float x, y;
|
@Import float x, y;
|
||||||
@Import Vec2 vel;
|
@Import Vec2 vel;
|
||||||
|
|
||||||
float elevation;
|
@SyncField(value = true, clamped = true) @SyncLocal float elevation;
|
||||||
private transient boolean wasFlying;
|
private transient boolean wasFlying;
|
||||||
transient float drownTime;
|
transient float drownTime;
|
||||||
transient float splashTimer;
|
transient float splashTimer;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import mindustry.gen.*;
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
abstract class LegsComp implements Posc, Flyingc, Hitboxc, Unitc, Legsc, ElevationMovec{
|
abstract class LegsComp implements Posc, Flyingc, Hitboxc, Unitc, Legsc, ElevationMovec{
|
||||||
@SyncField(false) float baseRotation;
|
@SyncField(false) @SyncLocal float baseRotation;
|
||||||
transient float walkTime;
|
transient float walkTime;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import mindustry.net.Administration.*;
|
|||||||
import mindustry.net.*;
|
import mindustry.net.*;
|
||||||
import mindustry.net.Packets.*;
|
import mindustry.net.Packets.*;
|
||||||
import mindustry.ui.*;
|
import mindustry.ui.*;
|
||||||
|
import mindustry.world.*;
|
||||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||||
|
|
||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
@@ -32,7 +33,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
|||||||
|
|
||||||
@ReadOnly Team team = Team.sharded;
|
@ReadOnly Team team = Team.sharded;
|
||||||
String name = "noname";
|
String name = "noname";
|
||||||
boolean admin, typing, shooting;
|
boolean admin, typing, shooting, boosting;
|
||||||
Color color = new Color();
|
Color color = new Color();
|
||||||
float mouseX, mouseY;
|
float mouseX, mouseY;
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
|||||||
public void afterSync(){
|
public void afterSync(){
|
||||||
unit.aim(mouseX, mouseY);
|
unit.aim(mouseX, mouseY);
|
||||||
//this is only necessary when the thing being controlled isn't synced
|
//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
|
//extra precaution, necessary for non-synced things
|
||||||
unit.controller(this);
|
unit.controller(this);
|
||||||
}
|
}
|
||||||
@@ -89,14 +90,24 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
|||||||
y(unit.y());
|
y(unit.y());
|
||||||
unit.team(team);
|
unit.team(team);
|
||||||
deathTimer = 0;
|
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){
|
}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();
|
deathTimer += Time.delta();
|
||||||
if(deathTimer >= deathDelay){
|
if(deathTimer >= deathDelay){
|
||||||
core.requestSpawn((Playerc)this);
|
core.requestSpawn((Playerc)this);
|
||||||
|
deathTimer = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textFadeTime -= Time.delta() / (60 * 5);
|
textFadeTime -= Time.delta() / (60 * 5);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void team(Team team){
|
public void team(Team team){
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import static mindustry.Vars.world;
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
abstract class PosComp implements Position{
|
abstract class PosComp implements Position{
|
||||||
@SyncField(true) float x, y;
|
@SyncField(true) @SyncLocal float x, y;
|
||||||
|
|
||||||
void set(float x, float y){
|
void set(float x, float y){
|
||||||
this.x = x;
|
this.x = x;
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ import mindustry.gen.*;
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
abstract class RotComp implements Entityc{
|
abstract class RotComp implements Entityc{
|
||||||
@SyncField(false) float rotation;
|
@SyncField(false) @SyncLocal float rotation;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import mindustry.gen.*;
|
|||||||
abstract class VelComp implements Posc{
|
abstract class VelComp implements Posc{
|
||||||
@Import float x, y;
|
@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;
|
transient float drag = 0f;
|
||||||
|
|
||||||
//velocity needs to be called first, as it affects delta and lastPosition
|
//velocity needs to be called first, as it affects delta and lastPosition
|
||||||
|
|||||||
@@ -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.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) && !movement.isZero();
|
||||||
|
player.boosting(isBoosting);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,14 +43,15 @@ public class CoreBlock extends StorageBlock{
|
|||||||
CoreEntity entity = tile.ent();
|
CoreEntity entity = tile.ent();
|
||||||
CoreBlock block = (CoreBlock)tile.block();
|
CoreBlock block = (CoreBlock)tile.block();
|
||||||
Fx.spawn.at(entity);
|
Fx.spawn.at(entity);
|
||||||
entity.progress = 0;
|
|
||||||
|
|
||||||
Unitc unit = block.unitType.create(tile.team());
|
if(!net.client()){
|
||||||
unit.set(entity);
|
Unitc unit = block.unitType.create(tile.team());
|
||||||
unit.impulse(0f, 8f);
|
unit.set(entity);
|
||||||
unit.controller(player);
|
unit.impulse(0f, 2f);
|
||||||
unit.spawnedByCore(true);
|
unit.controller(player);
|
||||||
unit.add();
|
unit.spawnedByCore(true);
|
||||||
|
unit.add();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -78,20 +79,10 @@ public class CoreBlock extends StorageBlock{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class CoreEntity extends TileEntity{
|
public class CoreEntity extends TileEntity{
|
||||||
protected float time, heat, progress;
|
|
||||||
protected int storageCapacity;
|
protected int storageCapacity;
|
||||||
protected boolean shouldBuild;
|
|
||||||
protected Playerc lastRequested;
|
|
||||||
|
|
||||||
public void requestSpawn(Playerc player){
|
public void requestSpawn(Playerc player){
|
||||||
shouldBuild = true;
|
Call.onPlayerSpawn(tile, player);
|
||||||
if(lastRequested == null){
|
|
||||||
lastRequested = player;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(progress >= 1f){
|
|
||||||
Call.onPlayerSpawn(tile, player);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -210,17 +201,6 @@ public class CoreBlock extends StorageBlock{
|
|||||||
state.teams.registerCore(this);
|
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
|
@Override
|
||||||
public void handleItem(Tilec source, Item item){
|
public void handleItem(Tilec source, Item item){
|
||||||
if(net.server() || !net.active()){
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user