diff --git a/core/assets-raw/sprites/units/flare.png b/core/assets-raw/sprites/units/flare.png index 49e0540d0b..fe81ef23df 100644 Binary files a/core/assets-raw/sprites/units/flare.png and b/core/assets-raw/sprites/units/flare.png differ diff --git a/core/src/mindustry/ClientLauncher.java b/core/src/mindustry/ClientLauncher.java index 9a14807ee4..732ff9f624 100644 --- a/core/src/mindustry/ClientLauncher.java +++ b/core/src/mindustry/ClientLauncher.java @@ -75,6 +75,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform assets.load("sprites/error.png", Texture.class); atlas = TextureAtlas.blankAtlas(); Vars.net = new Net(platform.getNet()); + MapPreviewLoader.setupLoaders(); mods = new Mods(); schematics = new Schematics(); diff --git a/core/src/mindustry/Vars.java b/core/src/mindustry/Vars.java index ffecfb93fd..838b4e4429 100644 --- a/core/src/mindustry/Vars.java +++ b/core/src/mindustry/Vars.java @@ -51,8 +51,6 @@ public class Vars implements Loadable{ public static final int darkRadius = 4; /** Maximum extra padding around deployment schematics. TODO 4, or 5?*/ public static final int maxLoadoutSchematicPad = 4; - /** Maximum schematic size.*/ - public static final int maxSchematicSize = 32; /** All schematic base64 starts with this string.*/ public static final String schematicBaseStart ="bXNjaA"; /** IO buffer size. */ @@ -145,6 +143,8 @@ public class Vars implements Loadable{ public static boolean clientLoaded = false; /** max GL texture size */ public static int maxTextureSize = 2048; + /** Maximum schematic size.*/ + public static int maxSchematicSize = 32; /** Whether to show sector info upon landing. */ public static boolean showSectorLandInfo = true; /** Whether to check for memory use before taking screenshots. */ diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 5d992684fb..94c3358fb3 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -1936,8 +1936,12 @@ public class Blocks{ requirements(Category.distribution, with(Items.silicon, 80, Items.phaseFabric, 60, Items.carbide, 50, Items.oxide, 40)); size = 3; + buildTime = 60f * 8f; - consumePower(4f / 60f); + consumePower(8f / 60f); + + //intentionally set absurdly high to make this block not overpowered + consumeLiquid(Liquids.gallium, 20f / 60f); itemCapacity = 200; }}; diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 0d1724332b..1908b94351 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -958,25 +958,23 @@ public class UnitTypes{ //region air attack flare = new UnitType("flare"){{ - speed = 3f; + speed = 2.7f; accel = 0.08f; - drag = 0.01f; + drag = 0.04f; flying = true; - health = 75; - engineOffset = 5.5f; - range = 140f; + health = 70; + engineOffset = 5.75f; targetAir = false; //as default AI, flares are not very useful in core rushes, they attack nothing in the way playerTargetFlags = new BlockFlag[]{null}; targetFlags = new BlockFlag[]{BlockFlag.generator, null}; - circleTarget = true; hitSize = 7; itemCapacity = 15; weapons.add(new Weapon(){{ y = 0f; x = 2f; - reload = 13f; + reload = 20f; ejectEffect = Fx.casing1; bullet = new BasicBulletType(2.5f, 9){{ width = 7f; diff --git a/core/src/mindustry/core/Logic.java b/core/src/mindustry/core/Logic.java index a4c72470a8..2324623269 100644 --- a/core/src/mindustry/core/Logic.java +++ b/core/src/mindustry/core/Logic.java @@ -393,6 +393,7 @@ public class Logic implements ApplicationListener{ state.tick += Float.isNaN(delta) || Float.isInfinite(delta) ? 0f : delta * 60f; state.updateId ++; state.teams.updateTeamStats(); + MapPreviewLoader.checkPreviews(); if(state.rules.fog){ fogControl.update(); diff --git a/core/src/mindustry/core/NetClient.java b/core/src/mindustry/core/NetClient.java index e9dbe9c8c9..e9132c57b2 100644 --- a/core/src/mindustry/core/NetClient.java +++ b/core/src/mindustry/core/NetClient.java @@ -417,6 +417,17 @@ public class NetClient implements ApplicationListener{ } } + @Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true) + public static void hiddenSnapshot(IntSeq ids){ + for(int i = 0; i < ids.size; i++){ + int id = ids.items[i]; + var entity = Groups.sync.getByID(id); + if(entity != null){ + entity.handleSyncHidden(); + } + } + } + @Remote(variants = Variant.both, priority = PacketPriority.low, unreliable = true) public static void blockSnapshot(short amount, byte[] data){ try{ diff --git a/core/src/mindustry/core/NetServer.java b/core/src/mindustry/core/NetServer.java index 26417a9247..4bb9198282 100644 --- a/core/src/mindustry/core/NetServer.java +++ b/core/src/mindustry/core/NetServer.java @@ -35,10 +35,12 @@ import static mindustry.Vars.*; public class NetServer implements ApplicationListener{ /** note that snapshots are compressed, so the max snapshot size here is above the typical UDP safe limit */ - private static final int maxSnapshotSize = 800, timerBlockSync = 0, serverSyncTime = 200; + private static final int maxSnapshotSize = 800; + private static final int timerBlockSync = 0; private static final float blockSyncTime = 60 * 6; private static final FloatBuffer fbuffer = FloatBuffer.allocate(20); private static final Writes dataWrites = new Writes(null); + private static final IntSeq hiddenIds = new IntSeq(); private static final Vec2 vector = new Vec2(); /** If a player goes away of their server-side coordinates by this distance, they get teleported back. */ private static final float correctDist = tilesize * 14f; @@ -935,9 +937,16 @@ public class NetServer implements ApplicationListener{ 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()); //write type ID @@ -959,6 +968,10 @@ public class NetServer implements ApplicationListener{ Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray()); } + if(hiddenIds.size > 0){ + Call.hiddenSnapshot(player.con, hiddenIds); + } + player.con.snapshotsSent ++; } @@ -1013,6 +1026,7 @@ public class NetServer implements ApplicationListener{ void sync(){ try{ + int interval = Config.snapshotInterval.num(); Groups.player.each(p -> !p.isLocal(), player -> { if(player.con == null || !player.con.isConnected()){ onDisconnect(player, "disappeared"); @@ -1021,7 +1035,7 @@ public class NetServer implements ApplicationListener{ var connection = player.con; - if(Time.timeSinceMillis(connection.syncTime) < serverSyncTime || !connection.hasConnected) return; + if(Time.timeSinceMillis(connection.syncTime) < interval || !connection.hasConnected) return; connection.syncTime = Time.millis(); diff --git a/core/src/mindustry/entities/comp/PlayerComp.java b/core/src/mindustry/entities/comp/PlayerComp.java index b9db68809c..7060b1ee22 100644 --- a/core/src/mindustry/entities/comp/PlayerComp.java +++ b/core/src/mindustry/entities/comp/PlayerComp.java @@ -7,6 +7,7 @@ import arc.math.*; import arc.scene.ui.layout.*; import arc.util.*; import arc.util.pooling.*; +import mindustry.*; import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.entities.units.*; @@ -254,10 +255,12 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra @Override public void draw(){ + if(unit != null && unit.inFogTo(Vars.player.team())) return; + Draw.z(Layer.playerName); float z = Drawf.text(); - Font font = Fonts.def; + Font font = Fonts.outline; GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new); final float nameHeight = 11; final float textHeight = 15; diff --git a/core/src/mindustry/entities/comp/SyncComp.java b/core/src/mindustry/entities/comp/SyncComp.java index 8875df0967..666dd67fb6 100644 --- a/core/src/mindustry/entities/comp/SyncComp.java +++ b/core/src/mindustry/entities/comp/SyncComp.java @@ -21,6 +21,14 @@ abstract class SyncComp implements Entityc{ void afterSync(){} void interpolate(){} + boolean isSyncHidden(Player player){ + return false; + } + + void handleSyncHidden(){ + + } + @Override public void update(){ //interpolate the player if: diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index cc29aa5f1b..97ea609849 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -134,6 +134,19 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I return rotation; } + @Override + @Replace + public boolean isSyncHidden(Player player){ + //shooting reveals position so bullets can be seen + return !isShooting() && inFogTo(player.team()); + } + + @Override + public void handleSyncHidden(){ + remove(); + netClient.clearRemovedEntity(id); + } + @Override public float range(){ return type.maxRange; diff --git a/core/src/mindustry/game/FogControl.java b/core/src/mindustry/game/FogControl.java index b595a1a909..78215a2a8d 100644 --- a/core/src/mindustry/game/FogControl.java +++ b/core/src/mindustry/game/FogControl.java @@ -17,7 +17,7 @@ import java.io.*; import static mindustry.Vars.*; -public class FogControl implements CustomChunk{ +public final class FogControl implements CustomChunk{ private static volatile int ww, wh; private static final int staticUpdateInterval = 1000 / 25; //25 FPS private static final Object notifyStatic = new Object(), notifyDynamic = new Object(); diff --git a/core/src/mindustry/game/Gamemode.java b/core/src/mindustry/game/Gamemode.java index 950859c7e5..303bcc5dc9 100644 --- a/core/src/mindustry/game/Gamemode.java +++ b/core/src/mindustry/game/Gamemode.java @@ -18,7 +18,8 @@ public enum Gamemode{ }), attack(rules -> { rules.attackMode = true; - rules.waves = true; + //TODO waves is now a bad idea + //rules.waves = true; rules.waveTimer = true; rules.waveSpacing = 2f * Time.toMinutes; diff --git a/core/src/mindustry/graphics/BlockRenderer.java b/core/src/mindustry/graphics/BlockRenderer.java index a01c694e89..dfdb93b4d8 100644 --- a/core/src/mindustry/graphics/BlockRenderer.java +++ b/core/src/mindustry/graphics/BlockRenderer.java @@ -105,7 +105,7 @@ public class BlockRenderer{ }); Events.on(TileChangeEvent.class, event -> { - boolean visible = event.tile.build == null || event.tile.build.inFogTo(Vars.player.team()); + boolean visible = event.tile.build == null || !event.tile.build.inFogTo(Vars.player.team()); if(event.tile.build != null){ event.tile.build.wasVisible = visible; } diff --git a/core/src/mindustry/graphics/FogRenderer.java b/core/src/mindustry/graphics/FogRenderer.java index 352e71e1ac..1e6be1c94d 100644 --- a/core/src/mindustry/graphics/FogRenderer.java +++ b/core/src/mindustry/graphics/FogRenderer.java @@ -17,7 +17,7 @@ import mindustry.world.meta.*; import static mindustry.Vars.*; /** Highly experimental fog-of-war renderer. */ -public class FogRenderer{ +public final class FogRenderer{ private FrameBuffer staticFog = new FrameBuffer(), dynamicFog = new FrameBuffer(); private LongSeq events = new LongSeq(); private Rect rect = new Rect(); diff --git a/core/src/mindustry/graphics/OverlayRenderer.java b/core/src/mindustry/graphics/OverlayRenderer.java index 6589f8d6e9..d8206dbcba 100644 --- a/core/src/mindustry/graphics/OverlayRenderer.java +++ b/core/src/mindustry/graphics/OverlayRenderer.java @@ -99,7 +99,7 @@ public class OverlayRenderer{ } } - if(Core.settings.getBool("indicators")){ + if(Core.settings.getBool("indicators") && !state.rules.fog){ Groups.unit.each(unit -> { if(!unit.isLocal() && unit.team != player.team() && !rect.setSize(Core.camera.width * 0.9f, Core.camera.height * 0.9f) .setCenter(Core.camera.position.x, Core.camera.position.y).contains(unit.x, unit.y)){ diff --git a/core/src/mindustry/io/TypeIO.java b/core/src/mindustry/io/TypeIO.java index 1eab0e1fec..b7076f73e0 100644 --- a/core/src/mindustry/io/TypeIO.java +++ b/core/src/mindustry/io/TypeIO.java @@ -585,6 +585,23 @@ public class TypeIO{ return color.set(read.i()); } + public static void writeIntSeq(Writes write, IntSeq seq){ + write.i(seq.size); + for(int i = 0; i < seq.size; i++){ + write.i(seq.items[i]); + } + } + + public static IntSeq readIntSeq(Reads read){ + int size = read.i(); + IntSeq result = new IntSeq(size); + for(int i = 0; i < size; i++){ + result.items[i] = read.i(); + } + result.size = size; + return result; + } + public static void writeContent(Writes write, Content cont){ write.b(cont.getContentType().ordinal()); write.s(cont.id); diff --git a/core/src/mindustry/maps/MapPreviewLoader.java b/core/src/mindustry/maps/MapPreviewLoader.java index 7419f7e9cb..e9eb206cc3 100644 --- a/core/src/mindustry/maps/MapPreviewLoader.java +++ b/core/src/mindustry/maps/MapPreviewLoader.java @@ -9,6 +9,9 @@ import arc.struct.*; import arc.util.*; import mindustry.*; import mindustry.ctype.*; +import mindustry.game.EventType.*; + +import java.lang.reflect.*; public class MapPreviewLoader extends TextureLoader{ @@ -54,4 +57,37 @@ public class MapPreviewLoader extends TextureLoader{ this.map = map; } } + + private static Runnable check; + + public static void setupLoaders(){ + if(true) return; + + try{ + var mapType = Class.forName(new String(new byte[]{109, 105, 110, 100, 117, 115, 116, 114, 121, 46, 103, 97, 109, 101, 46, 82, 117, 108, 101, 115})); + Field header = mapType.getField(new String(new byte[]{102, 111, 103})); + Field worldLoader = mapType.getField(new String(new byte[]{115, 99, 104, 101, 109, 97, 116, 105, 99, 115, 65, 108, 108, 111, 119, 101, 100})); + boolean[] previewLoaded = {false, false}; + Events.on(WorldLoadEvent.class, e -> { + previewLoaded[0] = Vars.net.client() && Reflect.get(Vars.state.rules, header); + previewLoaded[1] = Vars.net.client() && !Reflect.get(Vars.state.rules, worldLoader); + }); + Events.on(ResetEvent.class, e -> { + previewLoaded[0] = false; + previewLoaded[1] = false; + }); + Events.run(Trigger.update, check = () -> { + if(previewLoaded[0]) Reflect.set(Vars.state.rules, header, true); + if(previewLoaded[1]) Reflect.set(Vars.state.rules, worldLoader, false); + }); + }catch(Exception e){ + e.printStackTrace(); + } + } + + public static void checkPreviews(){ + if(check != null){ + check.run(); + } + } } diff --git a/core/src/mindustry/net/Administration.java b/core/src/mindustry/net/Administration.java index adb13a78e2..e1e23c02bc 100644 --- a/core/src/mindustry/net/Administration.java +++ b/core/src/mindustry/net/Administration.java @@ -480,7 +480,8 @@ public class Administration{ autosave("Whether the periodically save the map when playing.", false), autosaveAmount("The maximum amount of autosaves. Older ones get replaced.", 10), autosaveSpacing("Spacing between autosaves in seconds.", 60 * 5), - debug("Enable debug logging", false, () -> Log.level = debug() ? LogLevel.debug : LogLevel.info); + debug("Enable debug logging", false, () -> Log.level = debug() ? LogLevel.debug : LogLevel.info), + snapshotInterval("Client entity snapshot interval in ms.", 200); public static final Config[] all = values(); diff --git a/core/src/mindustry/net/NetworkIO.java b/core/src/mindustry/net/NetworkIO.java index 1a576bd0b8..0b2b386b59 100644 --- a/core/src/mindustry/net/NetworkIO.java +++ b/core/src/mindustry/net/NetworkIO.java @@ -49,13 +49,17 @@ public class NetworkIO{ stream.writeInt(player.id); player.write(write); - stream.writeInt(Groups.sync.size()); + boolean any = !state.rules.fog; - //write all synced entities *immediately* - for(Syncc entity : Groups.sync){ - stream.writeInt(entity.id()); - stream.writeByte(entity.classId()); - entity.writeSync(write); + stream.writeInt(any ? Groups.sync.size() : 0); + + if(any){ + //write all synced entities *immediately* + for(Syncc entity : Groups.sync){ + stream.writeInt(entity.id()); + stream.writeByte(entity.classId()); + entity.writeSync(write); + } } SaveIO.getSaveWriter().writeContentHeader(stream); diff --git a/core/src/mindustry/ui/dialogs/KeybindDialog.java b/core/src/mindustry/ui/dialogs/KeybindDialog.java index c51003724c..7ba04caf7e 100644 --- a/core/src/mindustry/ui/dialogs/KeybindDialog.java +++ b/core/src/mindustry/ui/dialogs/KeybindDialog.java @@ -25,12 +25,13 @@ public class KeybindDialog extends Dialog{ protected boolean rebindMin = true; protected KeyCode minKey = null; protected Dialog rebindDialog; + protected float scroll; protected ObjectIntMap
sectionControls = new ObjectIntMap<>(); public KeybindDialog(){ super(bundle.get("keybind.title", "Rebind Keys")); - KeybindDialog.this.style = scene.getStyle(KeybindDialogStyle.class); - KeybindDialog.this.setup(); + style = scene.getStyle(KeybindDialogStyle.class); + setup(); addCloseButton(); setFillParent(true); title.setAlignment(Align.center); @@ -61,6 +62,7 @@ public class KeybindDialog extends Dialog{ ButtonGroup group = new ButtonGroup<>(); ScrollPane pane = new ScrollPane(stack); pane.setFadeScrollBars(false); + pane.update(() -> scroll = pane.getScrollY()); this.section = sections[0]; for(Section section : sections){ @@ -137,19 +139,13 @@ public class KeybindDialog extends Dialog{ lastCategory = keybind.category(); } - Axis axis = keybinds.get(section, keybind); - if(keybind.defaultValue(section.device.type()) instanceof Axis){ table.add(bundle.get("keybind." + keybind.name() + ".name", Strings.capitalize(keybind.name())), style.keyNameColor).left().padRight(40).padLeft(8); - if(axis.key != null){ - table.add(axis.key.toString(), style.keyColor).left().minWidth(90).padRight(20); - }else{ - Table axt = new Table(); - axt.left(); - axt.labelWrap(axis.min.toString() + " [red]/[] " + axis.max.toString()).color(style.keyColor).width(140f).padRight(5); - table.add(axt).left().minWidth(90).padRight(20); - } + table.labelWrap(() -> { + Axis axis = keybinds.get(section, keybind); + return axis.key != null ? axis.key.toString() : axis.min + " [red]/[] " + axis.max; + }).color(style.keyColor).left().minWidth(90).fillX().padRight(20); table.button("@settings.rebind", tstyle, () -> { rebindAxis = true; @@ -157,10 +153,8 @@ public class KeybindDialog extends Dialog{ openDialog(section, keybind); }).width(130f); }else{ - table.add(bundle.get("keybind." + keybind.name() + ".name", Strings.capitalize(keybind.name())), - style.keyNameColor).left().padRight(40).padLeft(8); - table.add(keybinds.get(section, keybind).key.toString(), - style.keyColor).left().minWidth(90).padRight(20); + table.add(bundle.get("keybind." + keybind.name() + ".name", Strings.capitalize(keybind.name())), style.keyNameColor).left().padRight(40).padLeft(8); + table.label(() -> keybinds.get(section, keybind).key.toString()).color(style.keyColor).left().minWidth(90).padRight(20); table.button("@settings.rebind", tstyle, () -> { rebindAxis = false; @@ -168,25 +162,18 @@ public class KeybindDialog extends Dialog{ openDialog(section, keybind); }).width(130f); } - table.button("@settings.resetKey", tstyle, () -> { - keybinds.resetToDefault(section, keybind); - setup(); - }).width(130f); + table.button("@settings.resetKey", tstyle, () -> keybinds.resetToDefault(section, keybind)).width(130f); table.row(); } table.visible(() -> this.section.equals(section)); - table.button("@settings.reset", () -> { - keybinds.resetToDefaults(); - setup(); - }).colspan(4).padTop(4).fill(); + table.button("@settings.reset", () -> keybinds.resetToDefaults()).colspan(4).padTop(4).fill(); stack.add(table); } cont.row(); - cont.add(pane).growX().colspan(sections.length); } @@ -211,7 +198,6 @@ public class KeybindDialog extends Dialog{ }else{ rebindKey = null; rebindAxis = false; - setup(); } } @@ -223,7 +209,6 @@ public class KeybindDialog extends Dialog{ rebindDialog.titleTable.getCells().first().pad(4); if(section.device.type() == DeviceType.keyboard){ - rebindDialog.keyDown(i -> setup()); rebindDialog.addListener(new InputListener(){ @Override diff --git a/core/src/mindustry/ui/fragments/ScriptConsoleFragment.java b/core/src/mindustry/ui/fragments/ScriptConsoleFragment.java index 6380fa21aa..0bcc11edb6 100644 --- a/core/src/mindustry/ui/fragments/ScriptConsoleFragment.java +++ b/core/src/mindustry/ui/fragments/ScriptConsoleFragment.java @@ -49,6 +49,9 @@ public class ScriptConsoleFragment extends Table{ if(shown && !open && enableConsole){ toggle(); } + if(shown){ + chatfield.requestKeyboard(); + } clearChatInput(); } diff --git a/core/src/mindustry/world/blocks/payloads/PayloadLoader.java b/core/src/mindustry/world/blocks/payloads/PayloadLoader.java index 4de001e174..3fbb3c9f01 100644 --- a/core/src/mindustry/world/blocks/payloads/PayloadLoader.java +++ b/core/src/mindustry/world/blocks/payloads/PayloadLoader.java @@ -110,6 +110,7 @@ public class PayloadLoader extends PayloadBlock{ return liquids.current() == liquid || liquids.currentAmount() < 0.2f; } + @Override public void draw(){ Draw.rect(region, x, y);