Less respawn rubberbanding

This commit is contained in:
Anuken
2021-06-03 15:59:22 -04:00
parent 1cbd58820e
commit 1c4184db4b
7 changed files with 61 additions and 16 deletions

View File

@@ -2005,7 +2005,7 @@ public class Blocks implements ContentList{
requirements(Category.units, with(Items.silicon, 70, Items.thorium, 60, Items.plastanium, 60)); requirements(Category.units, with(Items.silicon, 70, Items.thorium, 60, Items.plastanium, 60));
size = 2; size = 2;
length = 6f; length = 6f;
repairSpeed = 5f; repairSpeed = 4f;
repairRadius = 140f; repairRadius = 140f;
powerUse = 5f; powerUse = 5f;
beamWidth = 1.1f; beamWidth = 1.1f;

View File

@@ -1936,7 +1936,6 @@ public class UnitTypes implements ContentList{
ejectEffect = Fx.none; ejectEffect = Fx.none;
bullet = new FlakBulletType(2.5f, 25){{ bullet = new FlakBulletType(2.5f, 25){{
sprite = "missile-large"; sprite = "missile-large";
collides = false;
//for targeting //for targeting
collidesGround = collidesAir = true; collidesGround = collidesAir = true;
explodeRange = 40f; explodeRange = 40f;
@@ -1950,9 +1949,9 @@ public class UnitTypes implements ContentList{
lightColor = Pal.heal; lightColor = Pal.heal;
splashDamageRadius = 30f; splashDamageRadius = 30f;
splashDamage = 28f; splashDamage = 25f;
lifetime = 90f; lifetime = 80f;
backColor = Pal.heal; backColor = Pal.heal;
frontColor = Color.white; frontColor = Color.white;
@@ -1982,11 +1981,11 @@ public class UnitTypes implements ContentList{
fragBullets = 7; fragBullets = 7;
fragVelocityMin = 0.3f; fragVelocityMin = 0.3f;
fragBullet = new MissileBulletType(3.9f, 12){{ fragBullet = new MissileBulletType(3.9f, 11){{
homingPower = 0.2f; homingPower = 0.2f;
weaveMag = 4; weaveMag = 4;
weaveScale = 4; weaveScale = 4;
lifetime = 70f; lifetime = 60f;
shootEffect = Fx.shootHeal; shootEffect = Fx.shootHeal;
smokeEffect = Fx.hitLaser; smokeEffect = Fx.hitLaser;
splashDamage = 13f; splashDamage = 13f;
@@ -2112,8 +2111,8 @@ public class UnitTypes implements ContentList{
bullet = new ContinuousLaserBulletType(){{ bullet = new ContinuousLaserBulletType(){{
maxRange = 90f; maxRange = 90f;
damage = 25f; damage = 26f;
length = 90f; length = 95f;
hitEffect = Fx.hitMeltHeal; hitEffect = Fx.hitMeltHeal;
drawSize = 200f; drawSize = 200f;
lifetime = 155f; lifetime = 155f;
@@ -2160,7 +2159,7 @@ public class UnitTypes implements ContentList{
timeIncrease = 3f; timeIncrease = 3f;
timeDuration = 60f * 20f; timeDuration = 60f * 20f;
powerDamageScl = 3f; powerDamageScl = 3f;
damage = 40; damage = 50;
hitColor = lightColor = Pal.heal; hitColor = lightColor = Pal.heal;
lightRadius = 70f; lightRadius = 70f;
clipSize = 250f; clipSize = 250f;
@@ -2176,7 +2175,7 @@ public class UnitTypes implements ContentList{
trailWidth = 6f; trailWidth = 6f;
trailColor = Pal.heal; trailColor = Pal.heal;
trailInterval = 3f; trailInterval = 3f;
splashDamage = 40f; splashDamage = 60f;
splashDamageRadius = rad; splashDamageRadius = rad;
hitShake = 4f; hitShake = 4f;
trailRotation = true; trailRotation = true;

View File

@@ -45,6 +45,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
transient String lastText = ""; transient String lastText = "";
transient float textFadeTime; transient float textFadeTime;
transient private Unit lastReadUnit = Nulls.unit; transient private Unit lastReadUnit = Nulls.unit;
transient @Nullable Unit justSwitchFrom, justSwitchTo;
public boolean isBuilder(){ public boolean isBuilder(){
return unit.canBuild(); return unit.canBuild();
@@ -100,6 +101,16 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Override @Override
public void afterSync(){ public void afterSync(){
//fix rubberbanding:
//when the player recs a unit that they JUST transitioned away from, use the new unit instead
//reason: we know the server is lying here, essentially skip the unit snapshot because we know the client's information is more recent
if(isLocal() && unit == justSwitchFrom && justSwitchFrom != null && justSwitchTo != null){
unit = justSwitchTo;
}else{
justSwitchFrom = null;
justSwitchTo = null;
}
//simulate a unit change after sync //simulate a unit change after sync
Unit set = unit; Unit set = unit;
unit = lastReadUnit; unit = lastReadUnit;
@@ -149,6 +160,13 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
} }
public void checkSpawn(){
CoreBuild core = bestCore();
if(core != null){
core.requestSpawn(self());
}
}
@Override @Override
public void remove(){ public void remove(){
//clear unit upon removal //clear unit upon removal
@@ -171,6 +189,11 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
} }
public void unit(Unit unit){ public void unit(Unit unit){
//refuse to switch when the unit was just transitioned from
if(isLocal() && unit == justSwitchFrom && justSwitchFrom != null && justSwitchTo != null){
return;
}
if(unit == null) throw new IllegalArgumentException("Unit cannot be null. Use clearUnit() instead."); if(unit == null) throw new IllegalArgumentException("Unit cannot be null. Use clearUnit() instead.");
if(this.unit == unit) return; if(this.unit == unit) return;

View File

@@ -229,8 +229,10 @@ public class DesktopInput extends InputHandler{
if(on != null){ if(on != null){
Call.unitControl(player, on); Call.unitControl(player, on);
shouldShoot = false; shouldShoot = false;
recentRespawnTimer = 1f;
}else if(build != null){ }else if(build != null){
Call.buildingControlSelect(player, build); Call.buildingControlSelect(player, build);
recentRespawnTimer = 1f;
} }
} }
} }
@@ -239,8 +241,9 @@ public class DesktopInput extends InputHandler{
updateMovement(player.unit()); updateMovement(player.unit());
if(Core.input.keyTap(Binding.respawn)){ if(Core.input.keyTap(Binding.respawn)){
Call.unitClear(player);
controlledType = null; controlledType = null;
recentRespawnTimer = 1f;
Call.unitClear(player);
} }
} }

View File

@@ -58,6 +58,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public Group uiGroup; public Group uiGroup;
public boolean isBuilding = true, buildWasAutoPaused = false, wasShooting = false; public boolean isBuilding = true, buildWasAutoPaused = false, wasShooting = false;
public @Nullable UnitType controlledType; public @Nullable UnitType controlledType;
public float recentRespawnTimer;
public @Nullable Schematic lastSchematic; public @Nullable Schematic lastSchematic;
public GestureDetector detector; public GestureDetector detector;
@@ -375,15 +376,27 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
throw new ValidateException(player, "Player cannot control a unit."); throw new ValidateException(player, "Player cannot control a unit.");
} }
//TODO problem:
//1. server send snapshot
//2. client requests to control unit, becomes unit locally
//3. snapshot arrives, client now thinks they're in the old unit (!!!)
//4. server gets packet that player is in the right unit
//5. server sends snapshot
//6. client gets snapshot, realizes that they are actually in the unit they selected
//7. client gets switched to new unit -> rubberbanding (!!!)
//clear player unit when they possess a core //clear player unit when they possess a core
if(unit == null){ //just clear the unit (is this used?) if(unit == null){ //just clear the unit (is this used?)
player.clearUnit(); player.clearUnit();
//make sure it's AI controlled, so players can't overwrite each other //make sure it's AI controlled, so players can't overwrite each other
}else if(unit.isAI() && unit.team == player.team() && !unit.dead){ }else if(unit.isAI() && unit.team == player.team() && !unit.dead){
if(!net.client()){ if(net.client()){
player.unit(unit); player.justSwitchFrom = player.unit();
player.justSwitchTo = unit;
} }
player.unit(unit);
Time.run(Fx.unitSpirit.lifetime, () -> Fx.unitControl.at(unit.x, unit.y, 0f, unit)); Time.run(Fx.unitSpirit.lifetime, () -> Fx.unitControl.at(unit.x, unit.y, 0f, unit));
if(!player.dead()){ if(!player.dead()){
Fx.unitSpirit.at(player.x, player.y, 0f, unit); Fx.unitSpirit.at(player.x, player.y, 0f, unit);
@@ -393,12 +406,14 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Events.fire(new UnitControlEvent(player, unit)); Events.fire(new UnitControlEvent(player, unit));
} }
@Remote(targets = Loc.both, called = Loc.both, forward = true) @Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void unitClear(Player player){ public static void unitClear(Player player){
if(player == null) return; if(player == null) return;
//problem: this gets called on both ends. it shouldn't be.
Fx.spawn.at(player); Fx.spawn.at(player);
player.clearUnit(); player.clearUnit();
player.checkSpawn();
player.deathTimer = Player.deathDelay + 1f; //for instant respawn player.deathTimer = Player.deathDelay + 1f; //for instant respawn
} }
@@ -419,7 +434,6 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
player.unit().commandNearby(new CircleFormation()); player.unit().commandNearby(new CircleFormation());
Fx.commandSend.at(player, player.unit().type.commandRadius); Fx.commandSend.at(player, player.unit().type.commandRadius);
} }
} }
public Eachable<BuildPlan> allRequests(){ public Eachable<BuildPlan> allRequests(){
@@ -451,7 +465,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
wasShooting = player.shooting; wasShooting = player.shooting;
if(!player.dead()){ //only reset the controlled type and control a unit after the timer runs out
//essentially, this means the client waits for 1 second after controlling something before trying to control something else automatically
if(!player.dead() && (recentRespawnTimer -= Time.delta / 60f) <= 0f){
controlledType = player.unit().type; controlledType = player.unit().type;
} }
@@ -461,6 +477,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(unit != null){ if(unit != null){
//only trying controlling once a second to prevent packet spam //only trying controlling once a second to prevent packet spam
if(!net.client() || controlInterval.get(0, 70f)){ if(!net.client() || controlInterval.get(0, 70f)){
recentRespawnTimer = 1f;
Call.unitControl(player, unit); Call.unitControl(player, unit);
} }
} }

View File

@@ -614,8 +614,10 @@ public class MobileInput extends InputHandler implements GestureListener{
//control a unit/block detected on first tap of double-tap //control a unit/block detected on first tap of double-tap
if(unitTapped != null){ if(unitTapped != null){
Call.unitControl(player, unitTapped); Call.unitControl(player, unitTapped);
recentRespawnTimer = 1f;
}else if(buildingTapped != null){ }else if(buildingTapped != null){
Call.buildingControlSelect(player, buildingTapped); Call.buildingControlSelect(player, buildingTapped);
recentRespawnTimer = 1f;
}else if(!tryBeginMine(cursor)){ }else if(!tryBeginMine(cursor)){
tileTapped(linked.build); tileTapped(linked.build);
} }

View File

@@ -721,6 +721,7 @@ public class HudFragment extends Fragment{
t.clicked(() -> { t.clicked(() -> {
if(!player.dead() && mobile){ if(!player.dead() && mobile){
Call.unitClear(player); Call.unitClear(player);
control.input.recentRespawnTimer = 1f;
control.input.controlledType = null; control.input.controlledType = null;
} }
}); });