diff --git a/core/src/mindustry/core/NetServer.java b/core/src/mindustry/core/NetServer.java index 21ecc21a83..29973972e9 100644 --- a/core/src/mindustry/core/NetServer.java +++ b/core/src/mindustry/core/NetServer.java @@ -114,11 +114,6 @@ public class NetServer implements ApplicationListener{ public static float voteDuration = 0.5f * 60; /** Cooldown between votes in seconds. */ public static int voteCooldown = 60 * 5; - /** - * If this is true, isSyncHidden will called only once per team if FoW is enabled, or not at all. - * Set this to false if a mod uses isSyncHidden to hide entities. - */ - public boolean skipHiddenEntitiesCheck = true; private ReusableByteOutStream writeBuffer = new ReusableByteOutStream(127); private Writes outputBuffer = new Writes(new DataOutputStream(writeBuffer)); @@ -1116,48 +1111,6 @@ public class NetServer implements ApplicationListener{ universe.seconds(), tps, GlobalVars.rand.seed0, GlobalVars.rand.seed1, syncStream.toByteArray()); } - public void writeEntitySnapshot(Player player) throws IOException{ - syncStream.reset(); - - hiddenIds.clear(); - int sent = 0; - - for(Syncc entity : Groups.sync){ - //TODO write to special list - if(entity.isSyncHidden(player)){ - hiddenIds.add(entity.id()); - continue; - } - - //write all entities now - dataStream.writeInt(entity.id()); //write id - dataStream.writeByte(entity.classId() & 0xFF); //write type ID - entity.beforeWrite(); - entity.writeSync(dataStreamWrites); //write entity itself - - sent++; - - if(syncStream.size() > maxSnapshotSize){ - dataStream.close(); - Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray()); - sent = 0; - syncStream.reset(); - } - } - - if(sent > 0){ - dataStream.close(); - - Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray()); - } - - if(hiddenIds.size > 0){ - Call.hiddenSnapshot(player.con, hiddenIds); - } - - player.con.snapshotsSent++; - } - /** Does not check isSyncHidden. Call this if no entities are hidden. */ public void writeEntitySnapshotsAll() throws IOException{ syncStream.reset(); @@ -1165,11 +1118,7 @@ public class NetServer implements ApplicationListener{ int sent = 0; for(Syncc entity : Groups.sync){ - //write all entities now - dataStream.writeInt(entity.id()); //write id - dataStream.writeByte(entity.classId() & 0xFF); //write type ID - entity.beforeWrite(); - entity.writeSync(dataStreamWrites); //write entity itself + writeEntity(entity, dataStream); sent++; @@ -1191,30 +1140,25 @@ public class NetServer implements ApplicationListener{ } /** Checks isSyncHidden for only one player per team. Called if FoW is enabled but there is no custom syncHidden. */ - public void writeEntitySnapshotsTeam(Seq players) throws IOException{ + public void writeEntitySnapshotsTeam(Team team, Seq players) throws IOException{ syncStream.reset(); hiddenIds.clear(); int sent = 0; for(Syncc entity : Groups.sync){ - //TODO write to special list - if(entity.isSyncHidden(players.first())){ + if(entity.isSyncHidden(team)){ hiddenIds.add(entity.id()); continue; } - //write all entities now - dataStream.writeInt(entity.id()); //write id - dataStream.writeByte(entity.classId() & 0xFF); //write type ID - entity.beforeWrite(); - entity.writeSync(dataStreamWrites); //write entity itself + writeEntity(entity, dataStream); sent++; if(syncStream.size() > maxSnapshotSize){ dataStream.close(); - final var ssent = (short)sent; + short ssent = (short)sent; var bytes = syncStream.toByteArray(); players.each(player -> Call.entitySnapshot(player.con, ssent, bytes)); sent = 0; @@ -1224,7 +1168,7 @@ public class NetServer implements ApplicationListener{ if(sent > 0){ dataStream.close(); - final var ssent = (short)sent; + short ssent = (short)sent; var bytes = syncStream.toByteArray(); players.each(player -> Call.entitySnapshot(player.con, ssent, bytes)); } @@ -1236,6 +1180,40 @@ public class NetServer implements ApplicationListener{ players.each(player -> player.con.snapshotsSent++); } + + /** Writes a custom snapshot containing player-local entities; this is for entities other players don't see. */ + public void writeCustomEntitySnapshot(Player player, Iterable entities) throws IOException{ + syncStream.reset(); + + int sent = 0; + + for(Syncc entity : entities){ + writeEntity(entity, dataStream); + + sent++; + + if(syncStream.size() > maxSnapshotSize){ + dataStream.close(); + Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray()); + sent = 0; + syncStream.reset(); + } + } + + if(sent > 0){ + dataStream.close(); + + Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray()); + } + } + + protected void writeEntity(Syncc entity, DataOutputStream dataStream) throws IOException{ + dataStream.writeInt(entity.id()); //write id + dataStream.writeByte(entity.classId() & 0xFF); //write type ID + entity.beforeWrite(); + entity.writeSync(dataStreamWrites); //write entity itself + } + public String fixName(String name){ name = name.trim().replace("\n", "").replace("\t", ""); if(name.equals("[") || name.equals("]")){ @@ -1291,7 +1269,6 @@ public class NetServer implements ApplicationListener{ Groups.player.each(p -> !p.isLocal(), player -> { if(player.con == null || !player.con.isConnected()){ onDisconnect(player, "disappeared"); - return; } }); @@ -1300,29 +1277,25 @@ public class NetServer implements ApplicationListener{ writeStateSnapshot(); - if(skipHiddenEntitiesCheck){ - if(Vars.state.rules.fog){ - //Serialize by teams - for(Team team : Team.all){ //Not Teams.active, because players can be on inactive teams - var tdata = team.data(); - playersToSend.selectFrom(tdata.players, p -> !p.isLocal() && p.con.hasConnected); - if(!playersToSend.isEmpty()){ - writeEntitySnapshotsTeam(playersToSend); - } + if(Vars.state.rules.fog){ + //Serialize by teams + for(Team team : Team.all){ //Not Teams.active, because players can be on inactive teams + var tdata = team.data(); + playersToSend.selectFrom(tdata.players, p -> !p.isLocal() && p.con.hasConnected); + if(!playersToSend.isEmpty()){ + writeEntitySnapshotsTeam(team, playersToSend); } - }else{ - //Serialize once for all players - writeEntitySnapshotsAll(); } }else{ - //Serialize for each player - Groups.player.each(p -> !p.isLocal() && p.con.hasConnected, player -> { - try{ - writeEntitySnapshot(player); - }catch(IOException e){ - Log.err(e); - } - }); + //Serialize once for all players + writeEntitySnapshotsAll(); + } + + //write custom player-specific entities (usually labels) + for(Player player : Groups.player){ + if(player.con != null && player.con.hasConnected && player.con.localEntities.size > 0){ + writeCustomEntitySnapshot(player, player.con.localEntities); + } } } diff --git a/core/src/mindustry/entities/comp/SyncComp.java b/core/src/mindustry/entities/comp/SyncComp.java index 666dd67fb6..2352a1bef2 100644 --- a/core/src/mindustry/entities/comp/SyncComp.java +++ b/core/src/mindustry/entities/comp/SyncComp.java @@ -3,6 +3,7 @@ package mindustry.entities.comp; import arc.util.io.*; import mindustry.*; import mindustry.annotations.Annotations.*; +import mindustry.game.*; import mindustry.gen.*; import java.nio.*; @@ -21,7 +22,7 @@ abstract class SyncComp implements Entityc{ void afterSync(){} void interpolate(){} - boolean isSyncHidden(Player player){ + boolean isSyncHidden(Team team){ return false; } diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index c8778bd566..3edda762f0 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -213,9 +213,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I @Override @Replace - public boolean isSyncHidden(Player player){ + public boolean isSyncHidden(Team team){ //shooting reveals position so bullets can be seen - return !isShooting() && inFogTo(player.team()); + return !isShooting() && inFogTo(team); } @Override diff --git a/core/src/mindustry/entities/comp/WorldLabelComp.java b/core/src/mindustry/entities/comp/WorldLabelComp.java index 2942f2757d..f0e9c09b4a 100644 --- a/core/src/mindustry/entities/comp/WorldLabelComp.java +++ b/core/src/mindustry/entities/comp/WorldLabelComp.java @@ -17,14 +17,14 @@ import mindustry.ui.*; public abstract class WorldLabelComp implements Posc, Drawc, Syncc{ @Import int id; @Import float x, y; + @Import boolean added; public static final byte - flagBackground = 1, - flagOutline = 2, - flagAlignLeft = 4, - flagAlignRight = 8, - flagAutoscale = 16, - flagOnlyParentVisible = 32; + flagBackground = 1 << 0, + flagOutline = 1 << 1, + flagAlignLeft = 1 << 2, + flagAlignRight = 1 << 3, + flagAutoscale = 1 << 4; public String text = "sample text"; public float fontSize = 1f, z = Layer.playerName + 1; @@ -37,6 +37,7 @@ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{ @Replace public float clipSize(){ + if(parent != null) return Float.MAX_VALUE; return text.length() * 10f * fontSize; } @@ -54,9 +55,6 @@ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{ if(parent != null){ x += parent.x(); y += parent.y(); - if((flags & flagOnlyParentVisible) != 0 && !parent.isLocal()){ - return; - } } drawAt(text, x, y, z, flags, fontSize, Align.center, (flags & flagAlignLeft) != 0 ? Align.left : (flags & flagAlignRight) != 0 ? Align.right : Align.center); } @@ -111,6 +109,21 @@ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{ Draw.z(z); } + /** Makes this label visible only to the specific player. This must be called instead of add(). */ + public void show(Player player){ + if(added || player.con == null) return; + player.con.localEntities.add(this); + added = true; + } + + /** Hides this player-specific label. If you used {@link #show(Player)} previously, you must call this method instead of {@link #hide()}! */ + public void hide(Player player){ + if(!added || player.con == null) return; + player.con.localEntities.remove(this); + Call.removeWorldLabel(player.con, id); + added = false; + } + /** This MUST be called instead of remove()! */ public void hide(){ remove(); diff --git a/core/src/mindustry/net/NetConnection.java b/core/src/mindustry/net/NetConnection.java index 5401150131..d622e5fa1a 100644 --- a/core/src/mindustry/net/NetConnection.java +++ b/core/src/mindustry/net/NetConnection.java @@ -31,6 +31,8 @@ public abstract class NetConnection{ public Ratekeeper chatRate = new Ratekeeper(); /** Handles packet spam rate limits. */ public Ratekeeper packetRate = new Ratekeeper(); + /** Entities that only this player will get synced to them. */ + public Seq localEntities = new Seq<>(false); //TODO: refactor to state enum public boolean hasConnected, hasBegunConnecting, determiningAssets, receivingAssets, hasDisconnected; diff --git a/core/src/mindustry/ui/Menus.java b/core/src/mindustry/ui/Menus.java index 4881b63be6..a268aa7f96 100644 --- a/core/src/mindustry/ui/Menus.java +++ b/core/src/mindustry/ui/Menus.java @@ -200,7 +200,7 @@ public class Menus{ } //internal use only - @Remote + @Remote(variants = Variant.both) public static void removeWorldLabel(int id){ var label = Groups.label.getByID(id); if(label != null){