From c077f6e1e82976126cd5ba49828e711c5e15c7b5 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 13 Jun 2018 17:06:46 -0400 Subject: [PATCH] Implemented authoritative movement/shooting, fixed netbugs --- .../src/io/anuke/mindustry/content/Mechs.java | 4 + .../io/anuke/mindustry/core/NetClient.java | 8 +- .../io/anuke/mindustry/core/NetServer.java | 42 ++++++++- core/src/io/anuke/mindustry/core/World.java | 10 ++- .../io/anuke/mindustry/entities/Player.java | 87 +++++++++++-------- .../src/io/anuke/mindustry/entities/Unit.java | 24 ++--- .../mindustry/entities/traits/CarryTrait.java | 34 ++++++-- .../anuke/mindustry/input/DesktopInput.java | 9 +- .../anuke/mindustry/input/InputHandler.java | 10 +-- core/src/io/anuke/mindustry/io/TypeIO.java | 35 ++++++++ .../anuke/mindustry/io/versions/Save16.java | 7 +- .../io/anuke/mindustry/net/NetConnection.java | 2 + .../src/io/anuke/mindustry/net/NetworkIO.java | 7 +- core/src/io/anuke/mindustry/net/Packets.java | 6 +- core/src/io/anuke/mindustry/type/Mech.java | 2 + core/src/io/anuke/mindustry/type/Weapon.java | 24 ++++- .../world/blocks/storage/CoreBlock.java | 44 ++++++---- 17 files changed, 268 insertions(+), 87 deletions(-) diff --git a/core/src/io/anuke/mindustry/content/Mechs.java b/core/src/io/anuke/mindustry/content/Mechs.java index b922c59efa..934281d8ce 100644 --- a/core/src/io/anuke/mindustry/content/Mechs.java +++ b/core/src/io/anuke/mindustry/content/Mechs.java @@ -14,10 +14,14 @@ public class Mechs implements ContentList { standard = new Mech("standard-mech", false){{ drillPower = 1; + speed = 1.1f; + maxSpeed = 1.1f; }}; standardShip = new Mech("standard-ship", true){{ drillPower = 1; + speed = 0.4f; + maxSpeed = 3f; }}; } diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 3d146fa207..024ed5d9dd 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -33,7 +33,7 @@ public class NetClient extends Module { private final static float playerSyncTime = 2; private Timer timer = new Timer(5); - /**Whether the client is currently conencting.*/ + /**Whether the client is currently connecting.*/ private boolean connecting = false; /**If true, no message will be shown on disconnect.*/ private boolean quiet = false; @@ -178,6 +178,12 @@ public class NetClient extends Module { ui.loadfrag.hide(); } + @Remote(variants = Variant.one) + public static void onPositionSet(float x, float y){ + players[0].x = x; + players[0].y = y; + } + @Remote(variants = Variant.one, unreliable = true) public static void onSnapshot(byte[] snapshot, int snapshotID){ //skip snapshot IDs that have already been recieved diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 62600f0697..8eabcfaa3e 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.core; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Colors; +import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Base64Coder; import com.badlogic.gdx.utils.IntMap; @@ -40,6 +41,9 @@ import static io.anuke.mindustry.Vars.*; public class NetServer extends Module{ private final static float serverSyncTime = 4, kickDuration = 30 * 1000; private final static boolean preventDuplicatNames = false; + private final static Vector2 vector = new Vector2(); + /**If a play goes away of their server-side coordinates by this distance, they get teleported back.*/ + private final static float correctDist = 16f; public final Administration admins = new Administration(); @@ -153,10 +157,46 @@ public class NetServer extends Module{ NetConnection connection = Net.getConnection(id); if(player == null || connection == null || packet.snapid < connection.lastRecievedSnapshot) return; - player.getInterpolator().read(player.x, player.y, packet.x, packet.y, packet.timeSent, packet.rotation, packet.baseRotation); + boolean verifyPosition = !player.isDead() && !debug && !headless; + + if(connection.lastRecievedTime == 0) connection.lastRecievedTime = TimeUtils.millis() - 16; + + long elapsed = TimeUtils.timeSinceMillis(connection.lastRecievedTime); + + //extra 1.1x multiplicaton is added just in case + float maxMove = elapsed / 1000f * 60f * player.mech.maxSpeed * 1.1f; + + player.pointerX = packet.pointerX; + player.pointerY = packet.pointerY; + + vector.set(packet.x - player.getInterpolator().target.x, packet.y - player.getInterpolator().target.y); + + vector.limit(maxMove); + + float prevx = player.x, prevy = player.y; + player.set(player.getInterpolator().target.x, player.getInterpolator().target.y); + player.move(vector.x, vector.y); + float newx = player.x, newy = player.y; + + if(!verifyPosition){ + player.x = prevx; + player.y = prevy; + newx = packet.x; + newy = packet.y; + }else if(Vector2.dst(packet.x, packet.y, newx, newy) > correctDist){ + Call.onPositionSet(id, newx, newy); //teleport and correct position when necessary + } + //reset player to previous synced position so it gets interpolated + player.x = prevx; + player.y = prevy; + + //set interpolator target to *new* position so it moves toward it + player.getInterpolator().read(player.x, player.y, newx, newy, packet.timeSent, packet.rotation, packet.baseRotation); player.getVelocity().set(packet.xv, packet.yv); //only for visual calculation purposes, doesn't actually update the player + connection.lastSnapshotID = packet.lastSnapshot; connection.lastRecievedSnapshot = packet.snapid; + connection.lastRecievedTime = TimeUtils.millis(); }); Net.handleServer(InvokePacket.class, (id, packet) -> RemoteReadServer.readPacket(packet.writeBuffer, packet.type, connections.get(id))); diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index c945ad8a81..952b255289 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -156,9 +156,17 @@ public class World extends Module{ generating = true; } - /**Call to signify the end of map loading. + /**Call to signify the end of map loading. Updates tile occlusions and sets up physics for the world. * A WorldLoadEvent will be fire.*/ public void endMapLoad(){ + for(int x = 0; x < tiles.length; x ++) { + for (int y = 0; y < tiles[0].length; y++) { + tiles[x][y].updateOcclusion(); + } + } + + EntityPhysics.resizeTree(0, 0, tiles.length * tilesize, tiles[0].length * tilesize); + generating = false; Events.fire(WorldLoadEvent.class); } diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 785203c2a6..2ea25df266 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -18,6 +18,7 @@ import io.anuke.mindustry.gen.CallEntity; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.graphics.Trail; import io.anuke.mindustry.net.In; +import io.anuke.mindustry.net.Net; import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; @@ -28,10 +29,7 @@ import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.entities.trait.SolidTrait; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Lines; -import io.anuke.ucore.util.Angles; -import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.ThreadQueue; -import io.anuke.ucore.util.Timer; +import io.anuke.ucore.util.*; import java.io.DataInput; import java.io.DataOutput; @@ -40,10 +38,7 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; public class Player extends Unit implements BuilderTrait, CarryTrait { - private static final float walkSpeed = 1.1f; - private static final float flySpeed = 0.4f; - private static final float flyMaxSpeed = 3f; - private static final float dashSpeed = 1.8f; + private static final float debugSpeed = 1.8f; private static final Vector2 movement = new Vector2(); public static int typeID = -1; @@ -56,9 +51,10 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { public float baseRotation; + public float pointerX, pointerY; public String name = "name"; public String uuid; - public boolean isAdmin, isTransferring; + public boolean isAdmin, isTransferring, isShooting; public Color color = new Color(); public Array upgrades = new Array<>(); @@ -224,7 +220,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { @Override public void removed() { - dropCarry(); + dropCarryLocal(); } @Override @@ -388,8 +384,8 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { if(isDead()){ CoreEntity entity = (CoreEntity)getClosestCore(); - if(!respawning && entity != null && entity.trySetPlayer(this)){ - respawning = true; + if (!respawning && entity != null) { + entity.trySetPlayer(this); } return; } @@ -398,6 +394,10 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { interpolate(); updateBuilding(this); //building happens even with non-locals status.update(this); //status effect updating also happens with non locals for effect purposes + + if(Net.server()){ + updateShooting(); //server simulates player shooting + } return; } @@ -424,13 +424,13 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { Tile tile = world.tileWorld(x, y); //if player is in solid block - if(tile != null && tile.solid()) { + if(tile != null && tile.solid() && !noclip) { damage(health + 1); //die instantly } if(ui.chatfrag.chatOpen()) return; - float speed = Inputs.keyDown("dash") ? (debug ? Player.dashSpeed * 5f : Player.dashSpeed) : Player.walkSpeed; + float speed = Inputs.keyDown("dash") && debug ? 5f : mech.speed; float carrySlowdown = 0.3f; speed *= ((1f-carrySlowdown) + (inventory.hasItem() ? (float)inventory.getItem().amount/inventory.capacity(): 1f) * carrySlowdown); @@ -452,16 +452,11 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { movement.y += ya*speed; movement.x += xa*speed; - boolean shooting = isShooting(); - - if(shooting){ - Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), - Vars.control.input(playerIndex).getMouseY()); - float vx = vec.x, vy = vec.y; - - weapon.update(this, true, vx, vy); - weapon.update(this, false, vx, vy); - } + Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), + Vars.control.input(playerIndex).getMouseY()); + pointerX = vec.x; + pointerY = vec.y; + updateShooting(); movement.limit(speed); @@ -474,7 +469,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { baseRotation = Mathf.slerpDelta(baseRotation, movement.angle(), 0.13f); } - if(!shooting){ + if(!isShooting()){ if(!movement.isZero()) { rotation = Mathf.slerpDelta(rotation, movement.angle(), 0.13f); } @@ -484,6 +479,13 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { } } + protected void updateShooting(){ + if(isShooting()){ + weapon.update(this, true, pointerX, pointerY); + weapon.update(this, false, pointerX, pointerY); + } + } + protected void updateFlying(){ if(Units.invalidateTarget(target, this)){ target = null; @@ -506,7 +508,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { pickupTarget = null; } - movement.set(targetX - x, targetY - y).limit(flySpeed); + movement.set(targetX - x, targetY - y).limit(mech.speed); movement.setAngle(Mathf.slerpDelta(movement.angle(), velocity.angle(), 0.05f)); if(distanceTo(targetX, targetY) < attractDst){ @@ -514,7 +516,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { } velocity.add(movement); - updateVelocityStatus(0.1f, flyMaxSpeed); + updateVelocityStatus(0.1f, mech.maxSpeed); //hovering effect x += Mathf.sin(Timers.time() + id * 999, 25f, 0.08f); @@ -531,7 +533,10 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { //autofire: mobile only! if(mobile) { + boolean lastShooting = isShooting; + if (target == null) { + isShooting = false; target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); } else { //rotate toward and shoot the target @@ -540,16 +545,24 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { Vector2 intercept = Predict.intercept(x, y, target.getX(), target.getY(), target.getVelocity().x - velocity.x, target.getVelocity().y - velocity.y, inventory.getAmmo().bullet.speed); - weapon.update(this, true, intercept.x, intercept.y); - weapon.update(this, false, intercept.x, intercept.y); + pointerX = intercept.x; + pointerY = intercept.y; + + updateShooting(); + isShooting = true; + } + + //update status of shooting to server + if(lastShooting != isShooting){ + CallEntity.setShooting(isShooting); } }else if(isShooting()){ //desktop shooting, TODO Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), Vars.control.input(playerIndex).getMouseY()); - float vx = vec.x, vy = vec.y; + pointerX = vec.x; + pointerY = vec.y; - weapon.update(this, true, vx, vy); - weapon.update(this, false, vx, vy); + updateShooting(); } } } @@ -578,7 +591,11 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { } public boolean isShooting(){ - return control.input(playerIndex).canShoot() && control.input(playerIndex).isShooting() && inventory.hasAmmo(); + return isShooting && inventory.hasAmmo(); + } + + public void setRespawning(){ + respawning = true; } @Override @@ -601,7 +618,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { if(isLocal){ stream.writeInt(playerIndex); - super.writeSave(stream); + super.writeSave(stream, false); stream.writeByte(upgrades.size); for(Upgrade u : upgrades){ @@ -632,7 +649,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { @Override public void write(DataOutput buffer) throws IOException { - super.writeSave(buffer); + super.writeSave(buffer, !isLocal); buffer.writeUTF(name); buffer.writeInt(Color.rgba8888(color)); buffer.writeBoolean(dead); diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index f7baa1fcb4..fdc25d9313 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -105,16 +105,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ @Override public void writeSave(DataOutput stream) throws IOException { - stream.writeByte(team.ordinal()); - stream.writeFloat(x); - stream.writeFloat(y); - stream.writeByte((byte)(Mathf.clamp(velocity.x, -maxAbsVelocity, maxAbsVelocity) * velocityPercision)); - stream.writeByte((byte)(Mathf.clamp(velocity.y, -maxAbsVelocity, maxAbsVelocity) * velocityPercision)); - stream.writeShort((short)(rotation*2)); - stream.writeShort((short)health); - stream.writeByte(status.current().id); - stream.writeShort((short)(status.getTime()*2)); - inventory.write(stream); + writeSave(stream, false); } @Override @@ -139,6 +130,19 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ this.status.set(StatusEffect.getByID(effect), etime); } + public void writeSave(DataOutput stream, boolean net) throws IOException { + stream.writeByte(team.ordinal()); + stream.writeFloat(net ? interpolator.target.x : x); + stream.writeFloat(net ? interpolator.target.y : y); + stream.writeByte((byte)(Mathf.clamp(velocity.x, -maxAbsVelocity, maxAbsVelocity) * velocityPercision)); + stream.writeByte((byte)(Mathf.clamp(velocity.y, -maxAbsVelocity, maxAbsVelocity) * velocityPercision)); + stream.writeShort((short)(rotation*2)); + stream.writeShort((short)health); + stream.writeByte(status.current().id); + stream.writeShort((short)(status.getTime()*2)); + inventory.write(stream); + } + public StatusEffect getStatus(){ return status.current(); } diff --git a/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java b/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java index fecfc64f44..6413c817b4 100644 --- a/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java @@ -1,6 +1,11 @@ package io.anuke.mindustry.entities.traits; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.content.fx.UnitFx; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.net.In; import io.anuke.ucore.core.Effects; import io.anuke.ucore.entities.trait.SolidTrait; @@ -17,23 +22,36 @@ public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{ carry(null); } + default void dropCarryLocal(){ + setCarryOf(null, this, null); + } + /**Do not override unless absolutely necessary. * Carries a unit. To drop a unit, call with {@code null}.*/ default void carry(CarriableTrait unit){ - if(getCarry() != null){ //already carrying something, drop it + CallEntity.setCarryOf(this instanceof Player ? (Player)this : null, this, unit); + } + + @Remote(called = Loc.server, targets = Loc.both, forward = true, in = In.entities) + static void setCarryOf(Player player, CarryTrait trait, CarriableTrait unit){ + if(player != null){ //when a server recieves this called from a player, set the carrier to the player. + trait = player; + } + + if(trait.getCarry() != null){ //already carrying something, drop it //drop current - Effects.effect(UnitFx.unitDrop, getCarry()); - getCarry().setCarrier(null); - setCarry(null); + Effects.effect(UnitFx.unitDrop, trait.getCarry()); + trait.getCarry().setCarrier(null); + trait.setCarry(null); if(unit != null){ - carry(unit); //now carry this new thing + trait.carry(unit); //now carry this new thing } }else if(unit != null){ //not currently carrying anything, make sure it's not null - setCarry(unit); - unit.setCarrier(this); + trait.setCarry(unit); + unit.setCarrier(trait); - Effects.effect(UnitFx.unitPickup, this); + Effects.effect(UnitFx.unitPickup, trait); } } } diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index 745d089c82..17502c86fe 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -6,6 +6,7 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.gen.CallEntity; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.input.PlaceUtils.NormalizeDrawResult; import io.anuke.mindustry.input.PlaceUtils.NormalizeResult; @@ -130,6 +131,10 @@ public class DesktopInput extends InputHandler{ mode = none; } + if(player.isShooting && !canShoot()){ + CallEntity.setShooting(false); + } + if(isPlacing()){ cursorType = hand; selectScale = Mathf.lerpDelta(selectScale, 1f, 0.2f); @@ -188,7 +193,7 @@ public class DesktopInput extends InputHandler{ //only begin shooting if there's no cursor event if(!tileTapped(cursor) && player.getPlaceQueue().size == 0 && !tryTapPlayer(worldx, worldy) && !droppingItem && !tryBeginMine(cursor) && player.getMineTile() == null){ - shooting = true; + CallEntity.setShooting(true); } } }else if(button == Buttons.RIGHT){ //right = begin breaking @@ -206,7 +211,7 @@ public class DesktopInput extends InputHandler{ @Override public boolean touchUp (int screenX, int screenY, int pointer, int button) { if(button == Buttons.LEFT){ - shooting = false; + CallEntity.setShooting(false); } if(player.isDead() || state.is(State.menu) || ui.hasDialog()) return false; diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index e010a699fc..baad65d1eb 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -46,7 +46,6 @@ public abstract class InputHandler extends InputAdapter{ public Recipe recipe; public int rotation; public boolean droppingItem; - public boolean shooting; public InputHandler(Player player){ this.player = player; @@ -189,10 +188,6 @@ public abstract class InputHandler extends InputAdapter{ public boolean canShoot(){ return recipe == null && !ui.hasMouse() && !onConfigurable() && !isDroppingItem(); } - - public boolean isShooting(){ - return shooting; - } public boolean onConfigurable(){ return false; @@ -262,6 +257,11 @@ public abstract class InputHandler extends InputAdapter{ player.addBuildRequest(new BuildRequest(tile.x, tile.y)); } + @Remote(targets = Loc.client, called = Loc.both, in = In.entities) + public static void setShooting(Player player, boolean on){ + player.isShooting = on; + } + @Remote(targets = Loc.both, called = Loc.server, in = In.entities) public static void dropItem(Player player, float angle){ if(Net.server() && !player.inventory.hasItem()){ diff --git a/core/src/io/anuke/mindustry/io/TypeIO.java b/core/src/io/anuke/mindustry/io/TypeIO.java index 12a068ff2d..85fe11510c 100644 --- a/core/src/io/anuke/mindustry/io/TypeIO.java +++ b/core/src/io/anuke/mindustry/io/TypeIO.java @@ -6,6 +6,8 @@ import io.anuke.annotations.Annotations.WriteClass; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.entities.traits.CarriableTrait; +import io.anuke.mindustry.entities.traits.CarryTrait; import io.anuke.mindustry.entities.units.BaseUnit; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.net.Packets.KickReason; @@ -51,6 +53,39 @@ public class TypeIO { return (Unit)Entities.getGroup(gid).getByID(id); } + @WriteClass(CarriableTrait.class) + public static void writeCarriable(ByteBuffer buffer, CarriableTrait unit){ + if(unit == null){ + buffer.put((byte)-1); + return; + } + buffer.put((byte)unit.getGroup().getID()); + buffer.putInt(unit.getID()); + } + + @ReadClass(CarriableTrait.class) + public static CarriableTrait readCarriable(ByteBuffer buffer){ + byte gid = buffer.get(); + if(gid == -1){ + return null; + } + int id = buffer.getInt(); + return (CarriableTrait)Entities.getGroup(gid).getByID(id); + } + + @WriteClass(CarryTrait.class) + public static void writeCarry(ByteBuffer buffer, CarryTrait unit){ + buffer.put((byte)unit.getGroup().getID()); + buffer.putInt(unit.getID()); + } + + @ReadClass(CarryTrait.class) + public static CarryTrait readCarry(ByteBuffer buffer){ + byte gid = buffer.get(); + int id = buffer.getInt(); + return (CarryTrait)Entities.getGroup(gid).getByID(id); + } + @WriteClass(BaseUnit.class) public static void writeBaseUnit(ByteBuffer buffer, BaseUnit unit){ buffer.put((byte)unit.getGroup().getID()); diff --git a/core/src/io/anuke/mindustry/io/versions/Save16.java b/core/src/io/anuke/mindustry/io/versions/Save16.java index 547fded6aa..4703700755 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save16.java +++ b/core/src/io/anuke/mindustry/io/versions/Save16.java @@ -92,9 +92,11 @@ public class Save16 extends SaveFileVersion { for(int y = 0; y < height; y ++) { byte floorid = stream.readByte(); byte wallid = stream.readByte(); + byte elevation = stream.readByte(); Tile tile = new Tile(x, y, floorid, wallid); + tile.elevation = elevation; if (wallid == Blocks.blockpart.id) { tile.link = stream.readByte(); @@ -190,8 +192,9 @@ public class Save16 extends SaveFileVersion { for(int y = 0; y < world.height(); y ++){ Tile tile = world.tile(x, y); - stream.writeByte(tile.floor().id); //floor ID - stream.writeByte(tile.block().id); //wall ID + stream.writeByte(tile.floor().id); + stream.writeByte(tile.block().id); + stream.writeByte(tile.elevation); if(tile.block() instanceof BlockPart){ stream.writeByte(tile.link); diff --git a/core/src/io/anuke/mindustry/net/NetConnection.java b/core/src/io/anuke/mindustry/net/NetConnection.java index fa29b2e6b6..e6750f20fb 100644 --- a/core/src/io/anuke/mindustry/net/NetConnection.java +++ b/core/src/io/anuke/mindustry/net/NetConnection.java @@ -18,6 +18,8 @@ public abstract class NetConnection { /**ID of last recieved client snapshot.*/ public int lastRecievedSnapshot = -1; + /**Timestamp of last recieved snapshot.*/ + public long lastRecievedTime; public NetConnection(int id, String address){ this.id = id; diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index e7645bac63..3113f6d39e 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -63,9 +63,9 @@ public class NetworkIO { for(int y = 0; y < world.height(); y ++){ Tile tile = world.tile(x, y); - //TODO will break if block number gets over BYTE_MAX stream.writeByte(tile.floor().id); //floor ID stream.writeByte(tile.block().id); //block ID + stream.writeByte(tile.elevation); if(tile.block() instanceof BlockPart){ stream.writeByte(tile.link); @@ -162,9 +162,12 @@ public class NetworkIO { for(int y = 0; y < height; y ++){ byte floorid = stream.readByte(); byte blockid = stream.readByte(); + byte elevation = stream.readByte(); Tile tile = new Tile(x, y, floorid, blockid); + tile.elevation = elevation; + if(tile.block() == Blocks.blockpart){ tile.link = stream.readByte(); } @@ -189,8 +192,6 @@ public class NetworkIO { } } - EntityPhysics.resizeTree(0, 0, width * tilesize, height * tilesize); - player.reset(); state.teams = new TeamInfo(); diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index 4088610683..d4df4742bc 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -101,7 +101,7 @@ public class Packets { public int snapid; public long timeSent; //player snapshot data - public float x, y, rotation, baseRotation, xv, yv; + public float x, y, pointerX, pointerY, rotation, baseRotation, xv, yv; @Override public void write(ByteBuffer buffer) { @@ -113,6 +113,8 @@ public class Packets { buffer.putFloat(player.x); buffer.putFloat(player.y); + buffer.putFloat(player.pointerX); + buffer.putFloat(player.pointerY); buffer.put((byte)(Mathf.clamp(player.getVelocity().x, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision)); buffer.put((byte)(Mathf.clamp(player.getVelocity().y, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision)); @@ -129,6 +131,8 @@ public class Packets { x = buffer.getFloat(); y = buffer.getFloat(); + pointerX = buffer.getFloat(); + pointerY = buffer.getFloat(); xv = buffer.get() / Unit.velocityPercision; yv = buffer.get() / Unit.velocityPercision; rotation = buffer.getShort()/2f; diff --git a/core/src/io/anuke/mindustry/type/Mech.java b/core/src/io/anuke/mindustry/type/Mech.java index c88671bcf3..a9871078d9 100644 --- a/core/src/io/anuke/mindustry/type/Mech.java +++ b/core/src/io/anuke/mindustry/type/Mech.java @@ -5,6 +5,8 @@ import io.anuke.ucore.graphics.Draw; public class Mech extends Upgrade { public boolean flying; + public float speed = 1.1f; + public float maxSpeed = 1.1f; public float mass = 1f; public int drillPower = -1; public float carryWeight = 1f; diff --git a/core/src/io/anuke/mindustry/type/Weapon.java b/core/src/io/anuke/mindustry/type/Weapon.java index 07cd2b7d9e..1115740e87 100644 --- a/core/src/io/anuke/mindustry/type/Weapon.java +++ b/core/src/io/anuke/mindustry/type/Weapon.java @@ -2,14 +2,16 @@ package io.anuke.mindustry.type; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.ObjectMap; -import io.anuke.annotations.Annotations.Remote; import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.Vars; import io.anuke.mindustry.content.fx.Fx; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.entities.bullet.Bullet; import io.anuke.mindustry.gen.CallEntity; import io.anuke.mindustry.net.In; +import io.anuke.mindustry.net.Net; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.graphics.Draw; @@ -82,7 +84,13 @@ public class Weapon extends Upgrade { } public void shoot(Player p, float x, float y, float angle, boolean left){ - CallEntity.onShootWeapon(p, this, x, y, angle, left); + if(Net.client()){ + //call it directly, don't invoke on server + shootDirect(p, this, x, y, angle, left); + }else{ + CallEntity.onShootWeapon(p, this, x, y, angle, left); + } + p.inventory.useAmmo(); } @@ -101,8 +109,18 @@ public class Weapon extends Upgrade { Bullet.create(owner.inventory.getAmmo().bullet, owner, x + tr.x, y + tr.y, angle); } - @Remote(targets = Loc.both, called = Loc.both, in = In.entities, unreliable = true, forward = true) + @Remote(targets = Loc.server, called = Loc.both, in = In.entities, unreliable = true) public static void onShootWeapon(Player player, Weapon weapon, float x, float y, float rotation, boolean left){ + //clients do not see their own shoot events: they are simulated completely clientside to prevent laggy visuals + //messing with the firerate or any other stats does not affect the server (take that, script kiddies!) + if(Net.client() && player == Vars.players[0]){ + return; + } + + shootDirect(player, weapon, x, y, rotation, left); + } + + public static void shootDirect(Player player, Weapon weapon, float x, float y, float rotation, boolean left){ Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> weapon.bullet(player, x, y, f + Mathf.range(weapon.inaccuracy))); AmmoType type = player.inventory.getAmmo(); diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java index cde291d0d4..1c0dc5826e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java @@ -155,15 +155,7 @@ public class CoreBlock extends StorageBlock { } if(entity.progress >= 1f){ - Effects.effect(Fx.spawn, entity); - CallBlocks.setCoreSolid(tile, false); - entity.progress = 0; - entity.currentPlayer.heal(); - entity.currentPlayer.rotation = 90f; - entity.currentPlayer.baseRotation = 90f; - entity.currentPlayer.set(tile.drawx(), tile.drawy()); - entity.currentPlayer.add(); - entity.currentPlayer = null; + CallBlocks.onPlayerRespawn(tile, entity.currentPlayer); } }else{ entity.heat = Mathf.lerpDelta(entity.heat, 0f, 0.1f); @@ -193,6 +185,30 @@ public class CoreBlock extends StorageBlock { return new CoreEntity(); } + @Remote(called = Loc.server, in = In.blocks) + public static void onPlayerRespawn(Tile tile, Player player){ + CoreEntity entity = tile.entity(); + Effects.effect(Fx.spawn, entity); + entity.solid = false; + entity.progress = 0; + entity.currentPlayer = player; + entity.currentPlayer.heal(); + entity.currentPlayer.rotation = 90f; + entity.currentPlayer.baseRotation = 90f; + entity.currentPlayer.setNet(tile.drawx(), tile.drawy()); + entity.currentPlayer.add(); + entity.currentPlayer = null; + } + + @Remote(called = Loc.server, in = In.blocks) + public static void onCorePlayerSet(Tile tile, Player player){ + CoreEntity entity = tile.entity(); + entity.currentPlayer = player; + entity.progress = 0f; + player.set(tile.drawx(), tile.drawy()); + player.setRespawning(); + } + @Remote(called = Loc.server, in = In.blocks) public static void setCoreSolid(Tile tile, boolean solid){ CoreEntity entity = tile.entity(); @@ -206,12 +222,10 @@ public class CoreBlock extends StorageBlock { float time; float heat; - public boolean trySetPlayer(Player player){ - if(currentPlayer != null) return false; - player.set(tile.drawx(), tile.drawy()); - currentPlayer = player; - progress = 0f; - return true; + public void trySetPlayer(Player player){ + if(currentPlayer == null){ + CallBlocks.onCorePlayerSet(tile, player); + } } @Override