1
0
mirror of https://github.com/Anuken/Mindustry.git synced 2024-11-13 07:15:28 +03:00

Multiplayer bugfixes

This commit is contained in:
Anuken 2020-07-09 13:13:57 -04:00
parent 29e9d064df
commit 2584197a02
24 changed files with 139 additions and 70 deletions

View File

@ -193,7 +193,7 @@ public class EntityIO{
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 if(lastUpdated != 0)"); //check if no meaningful data has arrived yet
//write values directly to targets
for(Svar field : fields){

View File

@ -566,6 +566,14 @@ public class EntityProcess extends BaseProcessor{
//write the groups
groupsBuilder.addMethod(groupInit.build());
MethodSpec.Builder groupClear = MethodSpec.methodBuilder("clear").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
for(GroupDefinition group : groupDefs){
groupClear.addStatement("$L.clear()", group.name);
}
//write clear
groupsBuilder.addMethod(groupClear.build());
//add method for resizing all necessary groups
MethodSpec.Builder groupResize = MethodSpec.methodBuilder("resize")
.addParameter(TypeName.FLOAT, "x").addParameter(TypeName.FLOAT, "y").addParameter(TypeName.FLOAT, "w").addParameter(TypeName.FLOAT, "h")

View File

@ -163,7 +163,7 @@ public class Logic implements ApplicationListener{
//fire change event, since it was technically changed
Events.fire(new StateChangeEvent(prev, State.menu));
Groups.all.clear();
Groups.clear();
Time.clear();
Events.fire(new ResetEvent());

View File

@ -343,7 +343,7 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.both)
public static void worldDataBegin(){
Groups.all.clear();
Groups.clear();
netClient.removed.clear();
logic.reset();
@ -361,6 +361,7 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.one)
public static void setPosition(float x, float y){
player.unit().set(x, y);
player.set(x, y);
}
@ -517,7 +518,7 @@ public class NetClient implements ApplicationListener{
quiet = false;
lastSent = 0;
Groups.all.clear();
Groups.clear();
ui.chatfrag.clearMessages();
}
@ -566,13 +567,14 @@ public class NetClient implements ApplicationListener{
Unit unit = player.dead() ? Nulls.unit : player.unit();
Call.clientShapshot(lastSent++,
player.dead(),
unit.x, unit.y,
player.unit().aimX(), player.unit().aimY(),
unit.rotation,
unit instanceof Mechc ? ((Mechc)unit).baseRotation() : 0,
unit.vel.x, unit.vel.y,
player.miner().mineTile(),
control.input.isBoosting, control.input.isShooting, ui.chatfrag.shown(),
player.boosting, player.shooting, ui.chatfrag.shown(),
requests,
Core.camera.position.x, Core.camera.position.y,
Core.camera.width * viewScale, Core.camera.height * viewScale);

View File

@ -529,6 +529,7 @@ public class NetServer implements ApplicationListener{
public static void clientShapshot(
Player player,
int snapshotID,
boolean dead,
float x, float y,
float pointerX, float pointerY,
float rotation, float baseRotation,
@ -538,23 +539,24 @@ public class NetServer implements ApplicationListener{
@Nullable BuildPlan[] requests,
float viewX, float viewY, float viewWidth, float viewHeight
){
NetConnection connection = player.con;
if(connection == null || snapshotID < connection.lastRecievedClientSnapshot) return;
NetConnection con = player.con;
if(con == null || snapshotID < con.lastRecievedClientSnapshot) return;
boolean verifyPosition = !player.dead() && netServer.admins.getStrict() && headless;
if(connection.lastRecievedClientTime == 0) connection.lastRecievedClientTime = Time.millis() - 16;
if(con.lastRecievedClientTime == 0) con.lastRecievedClientTime = Time.millis() - 16;
connection.viewX = viewX;
connection.viewY = viewY;
connection.viewWidth = viewWidth;
connection.viewHeight = viewHeight;
con.viewX = viewX;
con.viewY = viewY;
con.viewWidth = viewWidth;
con.viewHeight = viewHeight;
//disable shooting when a mech flies
if(!player.dead() && player.unit().isFlying() && player.unit() instanceof Mechc){
shooting = false;
}
//TODO these need to be assigned elsewhere
player.mouseX = pointerX;
player.mouseY = pointerY;
player.typing = chatting;
@ -582,7 +584,7 @@ public class NetServer implements ApplicationListener{
continue;
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || tile.rotation() == req.rotation)){
continue;
}else if(connection.rejectedRequests.contains(r -> r.breaking == req.breaking && r.x == req.x && r.y == req.y)){ //check if request was recently rejected, and skip it if so
}else if(con.rejectedRequests.contains(r -> r.breaking == req.breaking && r.x == req.x && r.y == req.y)){ //check if request was recently rejected, and skip it if so
continue;
}else if(!netServer.admins.allowAction(player, req.breaking ? ActionType.breakBlock : ActionType.placeBlock, tile, action -> { //make sure request is allowed by the server
action.block = req.block;
@ -591,33 +593,39 @@ public class NetServer implements ApplicationListener{
})){
//force the player to remove this request if that's not the case
Call.removeQueueBlock(player.con, req.x, req.y, req.breaking);
connection.rejectedRequests.add(req);
con.rejectedRequests.add(req);
continue;
}
player.builder().plans().addLast(req);
}
}
connection.rejectedRequests.clear();
con.rejectedRequests.clear();
if(!player.dead()){
Unit unit = player.unit();
unit.vel().set(xVelocity, yVelocity).limit(unit.type().speed);
long elapsed = Time.timeSinceMillis(connection.lastRecievedClientTime);
float maxSpeed = player.dead() ? Float.MAX_VALUE : player.unit().type().speed;
unit.vel.set(xVelocity, yVelocity).limit(unit.type().speed);
long elapsed = Time.timeSinceMillis(con.lastRecievedClientTime);
float maxSpeed = player.unit().type().speed;
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
if(connection.lastUnit != unit && !player.dead()){
connection.lastUnit = unit;
connection.lastPosition.set(unit);
if(con.lastUnit != unit){
con.lastUnit = unit;
con.lastPosition.set(unit);
}
vector.set(x, y).sub(connection.lastPosition);
//if the player think they're dead their position should be ignored
if(dead){
x = unit.x;
y = unit.y;
}
vector.set(x, y).sub(con.lastPosition);
vector.limit(maxMove);
float prevx = unit.x(), prevy = unit.y();
unit.set(connection.lastPosition);
float prevx = unit.x, prevy = unit.y;
unit.set(con.lastPosition);
if(!unit.isFlying()){
unit.move(vector.x, vector.y);
}else{
@ -625,30 +633,24 @@ public class NetServer implements ApplicationListener{
}
//set last position after movement
connection.lastPosition.set(unit);
float newx = unit.x(), newy = unit.y();
con.lastPosition.set(unit);
float newx = unit.x, newy = unit.y;
if(!verifyPosition){
unit.set(prevx, prevy);
newx = x;
newy = y;
}else if(!Mathf.within(x, y, newx, newy, correctDist)){
}else if(!Mathf.within(x, y, newx, newy, correctDist) && !dead){
Call.setPosition(player.con, newx, newy); //teleport and correct position when necessary
}
//reset player to previous synced position so it gets interpolated
//the server does not interpolate
if(!headless){
unit.set(prevx, prevy);
}
//write sync data to the buffer
fbuffer.limit(20);
fbuffer.position(0);
//now, put the new position, rotation and baserotation into the buffer so it can be read
//TODO this is terrible
if(unit instanceof Mechc) fbuffer.put(baseRotation); //base rotation is optional
fbuffer.put(unit.elevation());
fbuffer.put(rotation); //rotation is always there
fbuffer.put(newx);
fbuffer.put(newy);
@ -661,8 +663,8 @@ public class NetServer implements ApplicationListener{
player.y = y;
}
connection.lastRecievedClientSnapshot = snapshotID;
connection.lastRecievedClientTime = Time.millis();
con.lastRecievedClientSnapshot = snapshotID;
con.lastRecievedClientTime = Time.millis();
}
@Remote(targets = Loc.client, called = Loc.server)

View File

@ -166,6 +166,9 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
int idx = array.indexOf(type, true);
if(idx != -1){
array.remove(idx);
if(map != null){
map.remove(type.id());
}
//fix iteration index when removing
if(index >= idx){

View File

@ -4,7 +4,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
class GroupDefs<G>{
@GroupDef(value = Entityc.class, mapping = true) G all;
@GroupDef(value = Entityc.class) G all;
@GroupDef(value = Playerc.class, mapping = true) G player;
@GroupDef(value = Bulletc.class, spatial = true, collide = true) G bullet;
@GroupDef(value = Unitc.class, spatial = true, mapping = true) G unit;

View File

@ -3,6 +3,7 @@ package mindustry.entities;
import arc.func.*;
import arc.math.geom.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
@ -21,6 +22,12 @@ public class Units{
unit.killed();
}
@Remote(called = Loc.server)
public static void unitDespawn(Unit unit){
Fx.unitDespawn.at(unit.x, unit.y, 0, unit);
unit.remove();
}
/** @return whether a new instance of a unit of this team can be created. */
public static boolean canCreate(Team team){
return teamIndex.count(team) < getCap(team);

View File

@ -1,10 +1,12 @@
package mindustry.entities.comp;
import arc.graphics.g2d.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.ui.*;
import static mindustry.Vars.tilesize;
import static mindustry.Vars.*;
@Component
abstract class BlockUnitComp implements Unitc{
@ -29,6 +31,12 @@ abstract class BlockUnitComp implements Unitc{
}
}
@Replace
@Override
public TextureRegion icon(){
return tile.block.icon(Cicon.full);
}
@Override
public void killed(){
tile.kill();

View File

@ -28,7 +28,7 @@ abstract class BuilderComp implements Unitc{
@Import float x, y, rotation;
Queue<BuildPlan> plans = new Queue<>();
@SyncLocal Queue<BuildPlan> plans = new Queue<>();
transient boolean building = true;
@Override

View File

@ -19,7 +19,7 @@ abstract class DecalComp implements Drawc, Timedc, Rotc, Posc{
public void draw(){
Draw.z(Layer.scorch);
Draw.mixcol(color, 1f);
Draw.mixcol(color, color.a);
Draw.alpha(1f - Mathf.curve(fin(), 0.98f));
Draw.rect(region, x, y, rotation);
Draw.reset();

View File

@ -32,6 +32,10 @@ abstract class EntityComp{
return ((Object)this) == player || ((Object)this) instanceof Unitc && ((Unitc)((Object)this)).controller() == player;
}
boolean isRemote(){
return ((Object)this) instanceof Unitc && ((Unitc)((Object)this)).isPlayer() && !isLocal();
}
boolean isNull(){
return false;
}

View File

@ -17,7 +17,7 @@ abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
@Import float x, y;
@Import Vec2 vel;
@SyncField(value = true, clamped = true) @SyncLocal float elevation;
@SyncLocal float elevation;
private transient boolean wasFlying;
transient float drownTime;
transient float splashTimer;

View File

@ -22,7 +22,7 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc, Unitc{
@Import UnitType type;
transient float mineTimer;
@Nullable Tile mineTile;
@Nullable @SyncLocal Tile mineTile;
public boolean canMine(Item item){
return type.mineTier >= item.hardness;

View File

@ -12,8 +12,8 @@ import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.net.Administration.*;
@ -37,10 +37,10 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
transient @Nullable NetConnection con;
@ReadOnly Team team = Team.sharded;
@SyncLocal boolean admin, typing, shooting, boosting;
@SyncLocal float mouseX, mouseY;
String name = "noname";
boolean admin, typing, shooting, boosting;
Color color = new Color();
float mouseX, mouseY;
transient float deathTimer;
transient String lastText = "";
@ -63,9 +63,10 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
}
public TextureRegion icon(){
//display default icon for dead players
if(dead()) return core() == null ? UnitTypes.alpha.icon(Cicon.full) : ((CoreBlock)core().block).unitType.icon(Cicon.full);
return unit.type().icon(Cicon.full);
return unit.icon();
}
public void reset(){
@ -78,6 +79,11 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
}
}
@Override
public boolean isValidController(){
return isAdded();
}
@Replace
public float clipSize(){
return unit.isNull() ? 20 : unit.type().hitsize * 2f;
@ -125,6 +131,14 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
}
@Override
public void remove(){
//clear unit upon removal
if(!unit.isNull()){
clearUnit();
}
}
public void team(Team team){
this.team = team;
unit.team(team);

View File

@ -22,8 +22,19 @@ abstract class SyncComp implements Entityc{
@Override
public void update(){
if(Vars.net.client() && !isLocal()){
//interpolate the player if:
//- this is a client and the entity is everything except the local player
//- this is a server and the entity is a remote player
if((Vars.net.client() && !isLocal()) || isRemote()){
interpolate();
}
}
@Override
public void remove(){
//notify client of removal
if(Vars.net.client()){
Vars.netClient.addRemovedEntity(id());
}
}
}

View File

@ -1,6 +1,7 @@
package mindustry.entities.comp;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
@ -233,11 +234,15 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
//remove units spawned by the core
if(spawnedByCore && !isPlayer()){
Fx.unitDespawn.at(x, y, 0, this);
remove();
Call.unitDespawn(base());
}
}
/** @return a preview icon for this unit. */
public TextureRegion icon(){
return type.icon(Cicon.full);
}
/** Actually destroys the unit, removing it and creating explosions. **/
public void destroy(){
float explosiveness = 2f + item().explosiveness * stack().amount;

View File

@ -6,6 +6,10 @@ public interface UnitController{
void unit(Unit unit);
Unit unit();
default boolean isValidController(){
return true;
}
default void command(UnitCommand command){
}

View File

@ -221,7 +221,7 @@ public class DesktopInput extends InputHandler{
}
if(Core.input.keyRelease(Binding.select)){
isShooting = false;
player.shooting = false;
}
if(state.isGame() && Core.input.keyTap(Binding.minimap) && !scene.hasDialog() && !(scene.getKeyboardFocus() instanceof TextField)){
@ -248,8 +248,8 @@ public class DesktopInput extends InputHandler{
mode = none;
}
if(isShooting && !canShoot()){
isShooting = false;
if(player.shooting && !canShoot()){
player.shooting = false;
}
if(isPlacing() && player.isBuilder()){
@ -464,10 +464,10 @@ public class DesktopInput extends InputHandler{
//only begin shooting if there's no cursor event
if(!tileTapped(selected.build) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && (player.builder().plans().size == 0 || !player.builder().isBuilding()) && !droppingItem &&
!tryBeginMine(selected) && player.miner().mineTile() == null && !Core.scene.hasKeyboard()){
isShooting = shouldShoot;
player.shooting = shouldShoot;
}
}else if(!Core.scene.hasKeyboard()){ //if it's out of bounds, shooting is just fine
isShooting = shouldShoot;
player.shooting = shouldShoot;
}
}else if(Core.input.keyTap(Binding.deselect) && isPlacing()){
block = null;
@ -575,7 +575,7 @@ public class DesktopInput extends InputHandler{
movement.set(xa, ya).nor().scl(speed);
float mouseAngle = Angles.mouseAngle(unit.x(), unit.y());
boolean aimCursor = omni && isShooting && unit.type().hasWeapons() && unit.type().faceTarget && !boosted && unit.type().rotateShooting;
boolean aimCursor = omni && player.shooting && unit.type().hasWeapons() && unit.type().faceTarget && !boosted && unit.type().rotateShooting;
if(aimCursor){
unit.lookAt(mouseAngle);
@ -595,10 +595,11 @@ 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 && !boosted);
unit.controlWeapons(true, player.shooting && !boosted);
isBoosting = Core.input.keyDown(Binding.boost) && !movement.isZero();
player.boosting(isBoosting);
player.boosting = Core.input.keyDown(Binding.boost) && !movement.isZero();
player.mouseX = unit.aimX();
player.mouseY = unit.aimY();
//TODO netsync this
if(unit instanceof Payloadc){

View File

@ -58,7 +58,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public int rotation;
public boolean droppingItem;
public Group uiGroup;
public boolean isShooting, isBuilding = true, buildWasAutoPaused = false, isBoosting = false;
public boolean isBuilding = true, buildWasAutoPaused = false;
public @Nullable UnitType controlledType;
protected @Nullable Schematic lastSchematic;
@ -249,7 +249,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
public void update(){
player.typing(ui.chatfrag.shown());
player.typing = ui.chatfrag.shown();
if(player.isBuilder()){
player.builder().building(isBuilding);

View File

@ -454,7 +454,7 @@ public class MobileInput extends InputHandler implements GestureListener{
lastLineY = tileY;
}else if(!tryTapPlayer(worldx, worldy) && Core.settings.getBool("keyboard")){
//shoot on touch down when in keyboard mode
isShooting = true;
player.shooting = true;
}
}
@ -589,11 +589,11 @@ public class MobileInput extends InputHandler implements GestureListener{
if(Core.settings.getBool("keyboard")){
if(Core.input.keyRelease(Binding.select)){
isShooting = false;
player.shooting = false;
}
if(isShooting && !canShoot()){
isShooting = false;
if(player.shooting && !canShoot()){
player.shooting = false;
}
}

View File

@ -170,7 +170,7 @@ public class TypeIO{
}
public static <T extends Entityc> T readEntity(Reads read){
return (T)Groups.all.getByID(read.i());
return (T)Groups.sync.getByID(read.i());
}
public static void writeBuilding(Writes write, Building tile){

View File

@ -27,7 +27,7 @@ public class NetworkIO{
stream.writeInt(state.wave);
stream.writeFloat(state.wavetime);
stream.writeInt(player.id());
stream.writeInt(player.id);
player.write(Writes.get(stream));
SaveIO.getSaveWriter().writeContentHeader(stream);
@ -47,11 +47,11 @@ public class NetworkIO{
state.wave = stream.readInt();
state.wavetime = stream.readFloat();
Groups.all.clear();
Groups.clear();
int id = stream.readInt();
player.reset();
player.read(Reads.get(stream));
player.id(id);
player.id = id;
player.add();
SaveIO.getSaveWriter().readContentHeader(stream);

View File

@ -59,9 +59,9 @@ public class BuildBlock extends Block{
@Remote(called = Loc.server)
public static void constructFinish(Tile tile, Block block, int builderID, byte rotation, Team team, boolean skipConfig){
if(tile == null) return;
float healthf = tile.build.healthf();
float healthf = tile.build == null ? 1f : tile.build.healthf();
tile.setBlock(block, team, rotation);
tile.build.health(block.health * healthf);
tile.build.health = block.health * healthf;
//last builder was this local client player, call placed()
if(!headless && builderID == player.unit().id()){
if(!skipConfig){