Less respawn rubberbanding
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user