From 597a8832756e76663e039f3d003732458b4732ba Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 12 Jan 2018 14:01:57 -0500 Subject: [PATCH] Netcode changes, new pathfinding, fixed enemies jittering when stuck --- build.gradle | 2 +- core/src/io/anuke/mindustry/Mindustry.java | 13 +++- .../io/anuke/mindustry/ai/HeuristicImpl.java | 36 --------- .../src/io/anuke/mindustry/ai/Heuristics.java | 65 ++++++++++++++++ core/src/io/anuke/mindustry/ai/Pathfind.java | 36 ++++++--- .../io/anuke/mindustry/ai/TileConnection.java | 29 ------- core/src/io/anuke/mindustry/ai/TileGraph.java | 17 +---- core/src/io/anuke/mindustry/core/Control.java | 26 ++++--- .../io/anuke/mindustry/core/NetClient.java | 44 +++++------ .../io/anuke/mindustry/core/NetServer.java | 33 ++++---- core/src/io/anuke/mindustry/core/World.java | 4 +- .../io/anuke/mindustry/entities/Player.java | 2 +- .../mindustry/entities/enemies/Enemy.java | 1 + .../mindustry/entities/enemies/EnemyType.java | 22 +++++- core/src/io/anuke/mindustry/io/NetworkIO.java | 76 +++++++------------ .../anuke/mindustry/io/PlatformFunction.java | 1 + .../anuke/mindustry/io/versions/Save13.java | 2 +- .../anuke/mindustry/io/versions/Save14.java | 4 +- core/src/io/anuke/mindustry/net/Packets.java | 10 +-- .../io/anuke/mindustry/net/Registrator.java | 2 +- .../{Generator.java => WorldGenerator.java} | 2 +- .../world/blocks/types/defense/CoreBlock.java | 2 +- .../mindustry/desktop/DesktopLauncher.java | 5 ++ 23 files changed, 219 insertions(+), 215 deletions(-) delete mode 100644 core/src/io/anuke/mindustry/ai/HeuristicImpl.java create mode 100644 core/src/io/anuke/mindustry/ai/Heuristics.java delete mode 100644 core/src/io/anuke/mindustry/ai/TileConnection.java rename core/src/io/anuke/mindustry/world/{Generator.java => WorldGenerator.java} (99%) diff --git a/build.gradle b/build.gradle index c67fcbf227..a09e297bc4 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ allprojects { appName = "Mindustry" gdxVersion = '1.9.8' aiVersion = '1.8.1' - uCoreVersion = '9d4c546'; + uCoreVersion = '459e8ae'; } repositories { diff --git a/core/src/io/anuke/mindustry/Mindustry.java b/core/src/io/anuke/mindustry/Mindustry.java index c561775d58..c6dbd7f055 100644 --- a/core/src/io/anuke/mindustry/Mindustry.java +++ b/core/src/io/anuke/mindustry/Mindustry.java @@ -45,10 +45,17 @@ public class Mindustry extends ModuleCore { I18NBundle.setExceptionOnMissingKey(false); if(externalBundle){ - FileHandle handle = Gdx.files.local("bundle"); + try { + FileHandle handle = Gdx.files.local("bundle"); - Locale locale = Locale.ENGLISH; - Core.bundle = I18NBundle.createBundle(handle, locale); + Locale locale = Locale.ENGLISH; + Core.bundle = I18NBundle.createBundle(handle, locale); + }catch (Exception e){ + e.printStackTrace(); + platforms.showError("Failed to find bundle!\nMake sure you have bundle.properties in the same directory\nas the jar file.\n\nIf the problem persists, try running it through the command prompt:\n" + + "Hold left-shift, then right click and select 'open command prompt here'.\nThen, type in 'java -jar mindustry.jar' without quotes."); + Gdx.app.exit(); + } }else{ FileHandle handle = Gdx.files.internal("bundles/bundle"); diff --git a/core/src/io/anuke/mindustry/ai/HeuristicImpl.java b/core/src/io/anuke/mindustry/ai/HeuristicImpl.java deleted file mode 100644 index bfbbf761cc..0000000000 --- a/core/src/io/anuke/mindustry/ai/HeuristicImpl.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.anuke.mindustry.ai; - -import com.badlogic.gdx.ai.pfa.Heuristic; - -import io.anuke.mindustry.Vars; -import io.anuke.mindustry.world.Tile; - -public class HeuristicImpl implements Heuristic{ - /**How many times more it costs to go through a destructible block than an empty block.*/ - static final float solidMultiplier = 5f; - /**How many times more it costs to go through a tile that touches a solid block.*/ - static final float occludedMultiplier = 5f; - - @Override - public float estimate(Tile node, Tile other){ - return estimateStatic(node, other); - } - - /**Estimate the cost of walking between two tiles.*/ - public static float estimateStatic(Tile node, Tile other){ - //Get Manhattan distance cost - float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy()); - - //If either one of the tiles is a breakable solid block (that is, it's player-made), - //increase the cost by the tilesize times the solid block multiplier - //Also add the block health, so blocks with more health cost more to traverse - if(node.breakable() && node.block().solid) cost += Vars.tilesize* solidMultiplier + node.block().health; - if(other.breakable() && other.block().solid) cost += Vars.tilesize* solidMultiplier + other.block().health; - - //if this block has solid blocks near it, increase the cost, as we don't want enemies hugging walls - if(node.occluded) cost += Vars.tilesize*occludedMultiplier; - - return cost; - } - -} diff --git a/core/src/io/anuke/mindustry/ai/Heuristics.java b/core/src/io/anuke/mindustry/ai/Heuristics.java new file mode 100644 index 0000000000..db28f29ef1 --- /dev/null +++ b/core/src/io/anuke/mindustry/ai/Heuristics.java @@ -0,0 +1,65 @@ +package io.anuke.mindustry.ai; + +import com.badlogic.gdx.ai.pfa.Heuristic; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.types.defense.Turret; +import io.anuke.mindustry.world.blocks.types.production.Drill; +import io.anuke.mindustry.world.blocks.types.production.Generator; +import io.anuke.mindustry.world.blocks.types.production.Pump; +import io.anuke.mindustry.world.blocks.types.production.Smelter; + +public class Heuristics { + /**How many times more it costs to go through a destructible block than an empty block.*/ + static final float solidMultiplier = 5f; + /**How many times more it costs to go through a tile that touches a solid block.*/ + static final float occludedMultiplier = 5f; + + public static class FastestHeuristic implements Heuristic { + + @Override + public float estimate(Tile node, Tile other){ + //Get Manhattan distance cost + float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy()); + + //If either one of the tiles is a breakable solid block (that is, it's player-made), + //increase the cost by the tilesize times the solid block multiplier + //Also add the block health, so blocks with more health cost more to traverse + if(node.breakable() && node.block().solid) cost += Vars.tilesize* solidMultiplier + node.block().health; + if(other.breakable() && other.block().solid) cost += Vars.tilesize* solidMultiplier + other.block().health; + + //if this block has solid blocks near it, increase the cost, as we don't want enemies hugging walls + if(node.occluded) cost += Vars.tilesize*occludedMultiplier; + + return cost; + } + } + + public static class DestrutiveHeuristic implements Heuristic { + + @Override + public float estimate(Tile node, Tile other){ + //Get Manhattan distance cost + float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy()); + + //If either one of the tiles is a breakable solid block (that is, it's player-made), + //increase the cost by the tilesize times the solid block multiplier + //Also add the block health, so blocks with more health cost more to traverse + if(node.breakable() && node.block().solid) cost += Vars.tilesize* solidMultiplier + node.block().health; + if(other.breakable() && other.block().solid) cost += Vars.tilesize* solidMultiplier + other.block().health; + + //if this block has solid blocks near it, increase the cost, as we don't want enemies hugging walls + if(node.occluded) cost += Vars.tilesize*occludedMultiplier; + + //generators are free! + if(generator(other) || generator(node)) cost = 0; + + return cost; + } + + private boolean generator(Tile tile){ + return tile.block() instanceof Generator || tile.block() instanceof Turret + || tile.block() instanceof Pump || tile.block() instanceof Drill || tile.block() instanceof Smelter; + } + } +} diff --git a/core/src/io/anuke/mindustry/ai/Pathfind.java b/core/src/io/anuke/mindustry/ai/Pathfind.java index a80319a874..55481fc7b5 100644 --- a/core/src/io/anuke/mindustry/ai/Pathfind.java +++ b/core/src/io/anuke/mindustry/ai/Pathfind.java @@ -1,12 +1,13 @@ package io.anuke.mindustry.ai; +import com.badlogic.gdx.ai.pfa.Heuristic; import com.badlogic.gdx.ai.pfa.PathFinderRequest; import com.badlogic.gdx.ai.pfa.PathSmoother; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import io.anuke.mindustry.Vars; +import io.anuke.mindustry.ai.Heuristics.DestrutiveHeuristic; import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; import io.anuke.mindustry.game.SpawnPoint; import io.anuke.mindustry.world.Tile; import io.anuke.ucore.UCore; @@ -20,7 +21,7 @@ public class Pathfind{ private static final long maxTime = 1000000 * 5; /**Heuristic for determining cost between two tiles*/ - HeuristicImpl heuristic = new HeuristicImpl(); + Heuristic heuristic = new DestrutiveHeuristic(); /**Tile graph, for determining conenctions between two tiles*/ TileGraph graph = new TileGraph(); /**Smoother that removes extra nodes from a path.*/ @@ -54,17 +55,17 @@ public class Pathfind{ } //if an enemy is idle for a while, it's probably stuck - if(enemy.idletime > EnemyType.maxIdle){ + //if(enemy.idletime > EnemyType.maxIdle){ - Tile target = path[enemy.node]; - if(Vars.world.raycastWorld(enemy.x, enemy.y, target.worldx(), target.worldy()) != null) { - if (enemy.node > 1) - enemy.node = enemy.node - 1; - enemy.idletime = 0; - } + //Tile target = path[enemy.node]; + //if(Vars.world.raycastWorld(enemy.x, enemy.y, target.worldx(), target.worldy()) != null) { + // if (enemy.node > 1) + // enemy.node = enemy.node - 1; + // enemy.idletime = 0; + //} //else, must be blocked by a playermade block, do nothing - } + //} //-1 is only possible here if both pathfindings failed, which should NOT happen //check graph code @@ -82,6 +83,12 @@ public class Pathfind{ Tile prev = path[enemy.node - 1]; Tile target = path[enemy.node]; + + //a bridge has broken + if(!Vars.world.passable(target.x, target.y)){ + remakePath(); + return vector.set(enemy.x, enemy.y); + } float projectLen = Vector2.dst(prev.worldx(), prev.worldy(), target.worldx(), target.worldy()) / 6f; @@ -124,6 +131,15 @@ public class Pathfind{ } + private void remakePath(){ + for(int i = 0; i < Vars.control.enemyGroup.amount(); i ++){ + Enemy enemy = Vars.control.enemyGroup.all().get(i); + enemy.node = -1; + } + + resetPaths(); + } + /**Update the pathfinders and continue calculating the path if it hasn't been calculated yet. * This method is run each frame.*/ public void update(){ diff --git a/core/src/io/anuke/mindustry/ai/TileConnection.java b/core/src/io/anuke/mindustry/ai/TileConnection.java deleted file mode 100644 index 7f1441c4c7..0000000000 --- a/core/src/io/anuke/mindustry/ai/TileConnection.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.anuke.mindustry.ai; - -import com.badlogic.gdx.ai.pfa.Connection; -import io.anuke.mindustry.world.Tile; - -public class TileConnection implements Connection{ - Tile a, b; - - public TileConnection(Tile a, Tile b){ - this.a = a; - this.b = b; - } - - @Override - public float getCost(){ - return HeuristicImpl.estimateStatic(a, b); - } - - @Override - public Tile getFromNode(){ - return a; - } - - @Override - public Tile getToNode(){ - return b; - } - -} diff --git a/core/src/io/anuke/mindustry/ai/TileGraph.java b/core/src/io/anuke/mindustry/ai/TileGraph.java index 16847be27d..1bc156e85b 100644 --- a/core/src/io/anuke/mindustry/ai/TileGraph.java +++ b/core/src/io/anuke/mindustry/ai/TileGraph.java @@ -7,23 +7,10 @@ import io.anuke.mindustry.world.Tile; /**Tilegraph that ignores player-made tiles.*/ public class TileGraph implements OptimizedGraph { - private Array> tempConnections = new Array>(4); - /**Used for the default Graph implementation. Returns a result similar to connectionsOf()*/ + /**return nothing, as this isn't used*/ @Override - public Array> getConnections(Tile fromNode){ - tempConnections.clear(); - - if(!fromNode.passable()) - return tempConnections; - - for(Tile tile : fromNode.getNearby()){ - if(tile != null && (tile.passable())) - tempConnections.add(new TileConnection(fromNode, tile)); - } - - return tempConnections; - } + public Array> getConnections(Tile fromNode){ return null; } /**Used for the OptimizedPathFinder implementation.*/ @Override diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index cf2b98eab5..8a1469f298 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -8,14 +8,13 @@ import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.Mindustry; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState.State; -import io.anuke.mindustry.entities.*; +import io.anuke.mindustry.entities.Bullet; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.effect.Shield; import io.anuke.mindustry.entities.enemies.Enemy; import io.anuke.mindustry.entities.enemies.EnemyTypes; -import io.anuke.mindustry.game.Difficulty; -import io.anuke.mindustry.game.EnemySpawn; -import io.anuke.mindustry.game.SpawnPoint; -import io.anuke.mindustry.game.WaveCreator; +import io.anuke.mindustry.game.*; import io.anuke.mindustry.graphics.Fx; import io.anuke.mindustry.input.AndroidInput; import io.anuke.mindustry.input.DesktopInput; @@ -25,7 +24,9 @@ import io.anuke.mindustry.net.Net; import io.anuke.mindustry.resource.Item; import io.anuke.mindustry.resource.ItemStack; import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.*; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Map; +import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.ProductionBlocks; import io.anuke.ucore.UCore; import io.anuke.ucore.core.*; @@ -63,7 +64,7 @@ public class Control extends Module{ float wavetime; float extrawavetime; int enemies = 0; - io.anuke.mindustry.game.GameMode mode = io.anuke.mindustry.game.GameMode.waves; + GameMode mode = GameMode.waves; Tile core; Array spawnpoints = new Array<>(); @@ -180,6 +181,7 @@ public class Control extends Module{ "rotate_alt", new Axis(Input.CONTROLLER_DPAD_RIGHT, Input.CONTROLLER_DPAD_LEFT), "rotate", new Axis(Input.CONTROLLER_A, Input.CONTROLLER_B), "player_list", Input.CONTROLLER_START, + "chat", Input.ENTER, "weapon_1", Input.NUM_1, "weapon_2", Input.NUM_2, "weapon_3", Input.NUM_3, @@ -279,7 +281,7 @@ public class Control extends Module{ return core; } - public Array getSpawnPoints(){ + public Array getSpawnPoints(){ return spawnpoints; } @@ -292,7 +294,7 @@ public class Control extends Module{ } public void addSpawnPoint(Tile tile){ - io.anuke.mindustry.game.SpawnPoint point = new SpawnPoint(); + SpawnPoint point = new SpawnPoint(); point.start = tile; spawnpoints.add(point); } @@ -310,11 +312,11 @@ public class Control extends Module{ Timers.run(18, ()-> ui.loadfrag.hide()); } - public io.anuke.mindustry.game.GameMode getMode(){ + public GameMode getMode(){ return mode; } - public void setMode(io.anuke.mindustry.game.GameMode mode){ + public void setMode(GameMode mode){ this.mode = mode; } @@ -597,7 +599,7 @@ public class Control extends Module{ } if(Inputs.keyTap(Keys.U)){ - Vars.showUI = !Vars.showUI; + Vars.showPaths = !Vars.showPaths; } if(Inputs.keyTap(Keys.O)){ diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index af3d53cc61..27c967af4e 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -26,6 +26,7 @@ import io.anuke.ucore.UCore; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.BaseBulletType; +import io.anuke.ucore.entities.Entities; import io.anuke.ucore.entities.Entity; import io.anuke.ucore.modules.Module; @@ -37,7 +38,7 @@ public class NetClient extends Module { Color.GOLD, Color.PINK, Color.SKY, Color.GOLD, Color.VIOLET, Color.GREEN, Color.CORAL, Color.CYAN, Color.CHARTREUSE}; boolean connecting = false; - boolean gotEntities = false, gotData = false; + boolean gotData = false; boolean kicked = false; IntSet requests = new IntSet(); float playerSyncTime = 2; @@ -48,22 +49,23 @@ public class NetClient extends Module { Net.handle(Connect.class, packet -> { requests.clear(); connecting = true; - gotEntities = false; gotData = false; kicked = false; Gdx.app.postRunnable(() -> { Vars.ui.loadfrag.hide(); Vars.ui.loadfrag.show("$text.connecting.data"); + + Entities.clear(); + + ConnectPacket c = new ConnectPacket(); + c.name = Vars.player.name; + c.android = Vars.android; + Net.send(c, SendMode.tcp); }); - ConnectPacket c = new ConnectPacket(); - c.name = Vars.player.name; - c.android = Vars.android; - Net.send(c, SendMode.tcp); - Timers.runTask(dataTimeout, () -> { - if(!gotEntities){ + if(!gotData){ Gdx.app.error("Mindustry", "Failed to load data!"); Vars.ui.loadfrag.hide(); Net.disconnect(); @@ -90,32 +92,18 @@ public class NetClient extends Module { NetworkIO.load(data.stream); Vars.player.set(Vars.control.core.worldx(), Vars.control.core.worldy() - Vars.tilesize*2); - GameState.set(State.playing); connecting = false; Vars.ui.loadfrag.hide(); Vars.ui.join.hide(); gotData = true; - }); - }); - Net.handle(EntityDataPacket.class, data -> { - - Gdx.app.postRunnable(() -> { - Timers.run(10f, () -> { //TODO hack. should only run once world data is recieved - Vars.control.playerGroup.remap(Vars.player, data.playerid); - - for(int i = 0; i < data.weapons.length; i ++){ - Vars.control.addWeapon((Weapon) Upgrade.getByID(data.weapons[i])); - } - Vars.player.weaponLeft = Vars.player.weaponRight = Vars.control.getWeapons().peek(); - Vars.ui.hudfrag.updateWeapons(); - gotEntities = true; - }); + Net.send(new ConnectConfirmPacket(), SendMode.tcp); + GameState.set(State.playing); }); }); Net.handle(SyncPacket.class, packet -> { - if(!gotEntities) return; + if(!gotData) return; //TODO awful code for(int i = 0; i < packet.ids.length; i ++){ @@ -177,11 +165,13 @@ public class NetClient extends Module { Gdx.app.postRunnable(() -> { //duplicates. if(Vars.control.enemyGroup.getByID(spawn.id) != null) return; + Enemy enemy = new Enemy(EnemyType.getByID(spawn.type)); enemy.set(spawn.x, spawn.y); enemy.tier = spawn.tier; enemy.lane = spawn.lane; enemy.id = spawn.id; + enemy.health = spawn.health; enemy.add(); Effects.effect(Fx.spawn, enemy); @@ -221,7 +211,7 @@ public class NetClient extends Module { }); Net.handle(BlockSyncPacket.class, packet -> { - if(!gotEntities) return; + if(!gotData) return; DataInputStream stream = new DataInputStream(packet.stream); @@ -306,7 +296,7 @@ public class NetClient extends Module { if(!Net.client() || !Net.active()) return; if(!GameState.is(State.menu) && Net.active()){ - if(gotEntities && gotData) sync(); + if(gotData) sync(); }else if(!connecting){ Net.disconnect(); } diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 832e2485ad..7cafb47474 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -43,18 +43,7 @@ public class NetServer extends Module{ UCore.log("Sending world data to client (ID="+id+")"); - WorldData data = new WorldData(); - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - NetworkIO.write(stream); - - UCore.log("Packed " + stream.size() + " uncompressed bytes of data."); - data.stream = new ByteArrayInputStream(stream.toByteArray()); - - Net.sendStream(id, data); - Gdx.app.postRunnable(() -> { - EntityDataPacket dp = new EntityDataPacket(); - Player player = new Player(); player.clientid = id; player.name = packet.name; @@ -62,18 +51,29 @@ public class NetServer extends Module{ player.set(Vars.control.core.worldx(), Vars.control.core.worldy() - Vars.tilesize*2); player.getInterpolator().last.set(player.x, player.y); player.getInterpolator().target.set(player.x, player.y); - player.add(); connections.put(id, player); - dp.playerid = player.id; - dp.weapons = weapons.get(packet.name, new ByteArray()).toArray(); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + NetworkIO.write(player.id, weapons.get(packet.name, new ByteArray()), stream); - Net.sendTo(id, dp, SendMode.tcp); + UCore.log("Packed " + stream.size() + " uncompressed bytes of data."); - sendMessage("[accent]"+Bundles.format("text.server.connected", packet.name)); + WorldData data = new WorldData(); + data.stream = new ByteArrayInputStream(stream.toByteArray()); + + Net.sendStream(id, data); }); }); + Net.handleServer(ConnectConfirmPacket.class, packet -> { + Player player = connections.get(Net.getLastConnection()); + + if(player == null) return; + + Gdx.app.postRunnable(player::add); + sendMessage("[accent]"+Bundles.format("text.server.connected", player.name)); + }); + Net.handleServer(Disconnect.class, packet -> { Player player = connections.get(packet.id); @@ -197,6 +197,7 @@ public class NetServer extends Module{ e.tier = (byte)enemy.tier; e.lane = (byte)enemy.lane; e.type = enemy.type.id; + e.health = (short)enemy.health; Net.sendTo(dest, e, SendMode.tcp); Gdx.app.error("Mindustry", "Replying to entity request("+Net.getLastConnection()+"): enemy, " + id); }else{ diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 9056cec29a..ec58dc94fa 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -10,7 +10,7 @@ import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.io.Maps; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Generator; +import io.anuke.mindustry.world.WorldGenerator; import io.anuke.mindustry.world.Map; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.Blocks; @@ -180,7 +180,7 @@ public class World extends Module{ Entities.resizeTree(0, 0, map.getWidth() * tilesize, map.getHeight() * tilesize); this.seed = seed; - Generator.generate(map.pixmap, tiles); + WorldGenerator.generate(map.pixmap, tiles); if(control.getCore() == null) return; diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 79fde2af1e..485c7d5507 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -123,7 +123,7 @@ public class Player extends DestructibleEntity implements Syncable{ dashing = Inputs.keyDown("dash"); - float speed = dashing ? Player.dashSpeed : Player.speed; + float speed = dashing ? (debug ? Player.dashSpeed * 5f : Player.dashSpeed) : Player.speed; if(health < maxhealth && Timers.get(this, "regen", 20)) health ++; diff --git a/core/src/io/anuke/mindustry/entities/enemies/Enemy.java b/core/src/io/anuke/mindustry/entities/enemies/Enemy.java index 8414a1b69c..9c66474c32 100644 --- a/core/src/io/anuke/mindustry/entities/enemies/Enemy.java +++ b/core/src/io/anuke/mindustry/entities/enemies/Enemy.java @@ -30,6 +30,7 @@ public class Enemy extends DestructibleEntity implements Syncable{ public Entity target; public float hitTime; public int tier = 1; + public Vector2 totalMove = new Vector2(); public Enemy(EnemyType type){ this.type = type; diff --git a/core/src/io/anuke/mindustry/entities/enemies/EnemyType.java b/core/src/io/anuke/mindustry/entities/enemies/EnemyType.java index 33f07eb899..a94d2a1328 100644 --- a/core/src/io/anuke/mindustry/entities/enemies/EnemyType.java +++ b/core/src/io/anuke/mindustry/entities/enemies/EnemyType.java @@ -18,6 +18,7 @@ import io.anuke.ucore.core.Graphics; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.Entities; import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Strings; import io.anuke.ucore.util.Tmp; import static io.anuke.mindustry.Vars.world; @@ -30,8 +31,7 @@ public class EnemyType { public final static Color[] tierColors = { Color.valueOf("ffe451"), Color.valueOf("f48e20"), Color.valueOf("ff6757"), Color.valueOf("ff2d86") }; public final static int maxtier = 4; - public final static float maxIdle = 60*1.5f; - public final static float maxIdleLife = 60f*13f; //13 seconds idle = death + public final static float maxIdleLife = 60f*2f; //2 seconds idle = death public final static float hitDuration = 5f; public final String name; @@ -56,6 +56,7 @@ public class EnemyType { protected final int timerTarget = timeid ++; protected final int timerReload = timeid ++; + protected final int timerReset = timeid ++; public EnemyType(String name){ this.id = lastid++; @@ -76,6 +77,14 @@ public class EnemyType { Draw.color(); Graphics.flush(); + + if(Vars.showPaths){ + Draw.tscl(0.25f); + Draw.text((int)enemy.idletime + "\n" + Strings.toFixed(enemy.totalMove.x, 2) + ", " + + Strings.toFixed(enemy.totalMove.x, 2), enemy.x, enemy.y); + Draw.tscl(Vars.fontscale); + } + Shaders.outline.lighten = 0f; } @@ -90,15 +99,24 @@ public class EnemyType { move(enemy); enemy.velocity.set(enemy.x - lastx, enemy.y - lasty).scl(1f / Timers.delta()); + enemy.totalMove.add(enemy.velocity); float minv = 0.07f; + if(enemy.timer.get(timerReset, 60)){ + enemy.totalMove.setZero(); + } + if(enemy.velocity.len() < minv && enemy.node > 0 && enemy.target == null){ enemy.idletime += Timers.delta(); }else{ enemy.idletime = 0; } + if(enemy.timer.getTime(timerReset) > 40 && enemy.totalMove.len() < 0.3f && enemy.node > 0 && enemy.target == null){ + enemy.idletime = 999999f; + } + Tile tile = world.tileWorld(enemy.x, enemy.y); if(tile != null && tile.floor().liquid && tile.block() == Blocks.air){ enemy.damage(enemy.health+1); //drown diff --git a/core/src/io/anuke/mindustry/io/NetworkIO.java b/core/src/io/anuke/mindustry/io/NetworkIO.java index 18f936c9ba..f2e097370a 100644 --- a/core/src/io/anuke/mindustry/io/NetworkIO.java +++ b/core/src/io/anuke/mindustry/io/NetworkIO.java @@ -1,15 +1,15 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.files.FileHandle; -import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ByteArray; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.mindustry.Vars; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; +import io.anuke.mindustry.game.GameMode; +import io.anuke.mindustry.resource.Upgrade; import io.anuke.mindustry.resource.Weapon; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.game.GameMode; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.WorldGenerator; import io.anuke.mindustry.world.blocks.Blocks; import io.anuke.mindustry.world.blocks.types.BlockPart; import io.anuke.mindustry.world.blocks.types.Rock; @@ -19,9 +19,9 @@ import io.anuke.ucore.entities.Entities; import java.io.*; public class NetworkIO { - private static final int fileVersionID = 13; + private static final int fileVersionID = 14; - public static void write(OutputStream os){ + public static void write(int playerID, ByteArray upgrades, OutputStream os){ try(DataOutputStream stream = new DataOutputStream(os)){ @@ -36,28 +36,20 @@ public class NetworkIO { stream.writeInt(Vars.control.getWave()); //wave stream.writeFloat(Vars.control.getWaveCountdown()); //wave countdown + stream.writeInt(Vars.control.enemyGroup.amount()); //enemy amount + + stream.writeInt(playerID); //player remap ID //--INVENTORY-- - for(int i = 0; i < Vars.control.getItems().length; i ++){ + for(int i = 0; i < Vars.control.getItems().length; i ++){ //items stream.writeInt(Vars.control.getItems()[i]); } - //--ENEMIES-- - Array enemies = Vars.control.enemyGroup.all(); + stream.writeByte(upgrades.size); //upgrade data - stream.writeInt(enemies.size); //enemy amount - - for(int i = 0; i < enemies.size; i ++){ - Enemy enemy = enemies.get(i); - stream.writeInt(enemy.id); - stream.writeByte(enemy.type.id); //type - stream.writeByte(enemy.lane); //lane - stream.writeFloat(enemy.x); //x - stream.writeFloat(enemy.y); //y - stream.writeByte(enemy.tier); //tier - stream.writeShort(enemy.health); //health - stream.writeShort(enemy.node); //current node + for(int i = 0; i < upgrades.size; i ++){ + stream.writeByte(upgrades.get(i)); } //--MAP DATA-- @@ -166,7 +158,7 @@ public class NetworkIO { Timers.resetTime(timerTime + (TimeUtils.timeSinceMillis(timestamp) / 1000f) * 60f); if(version != fileVersionID){ - throw new RuntimeException("Save file version mismatch!"); + throw new RuntimeException("Netcode version mismatch!"); } //general state @@ -175,9 +167,13 @@ public class NetworkIO { int wave = stream.readInt(); float wavetime = stream.readFloat(); + int enemies = stream.readInt(); + Vars.control.setWaveData(enemies, wave, wavetime); Vars.control.setMode(GameMode.values()[mode]); + int pid = stream.readInt(); + //inventory for(int i = 0; i < Vars.control.getItems().length; i ++){ Vars.control.getItems()[i] = stream.readInt(); @@ -187,38 +183,18 @@ public class NetworkIO { Vars.control.getWeapons().clear(); Vars.control.getWeapons().add(Weapon.blaster); - Vars.player.weaponLeft = Vars.player.weaponRight = Weapon.blaster; - Vars.ui.hudfrag.updateWeapons(); - //enemies + byte weapons = stream.readByte(); - Entities.clear(); - - int enemies = stream.readInt(); - - for(int i = 0; i < enemies; i ++){ - int id = stream.readInt(); - byte type = stream.readByte(); - int lane = stream.readByte(); - float x = stream.readFloat(); - float y = stream.readFloat(); - byte tier = stream.readByte(); - short health = stream.readShort(); - short node = stream.readShort(); - - Enemy enemy = new Enemy(EnemyType.getByID(type)); - enemy.id = id; - enemy.lane = lane; - enemy.health = health; - enemy.x = x; - enemy.y = y; - enemy.tier = tier; - enemy.node = node; - enemy.add(Vars.control.enemyGroup); + for(int i = 0; i < weapons; i ++){ + Vars.control.getWeapons().add((Weapon) Upgrade.getByID(stream.readByte())); } - Vars.control.setWaveData(enemies, wave, wavetime); + Vars.player.weaponLeft = Vars.player.weaponRight = Vars.control.getWeapons().peek(); + Vars.ui.hudfrag.updateWeapons(); + Entities.clear(); + Vars.player.id = pid; Vars.player.add(); //map @@ -246,7 +222,7 @@ public class NetworkIO { for(int i = 0; i < rocks; i ++){ int pos = stream.readInt(); Tile tile = Vars.world.tile(pos % Vars.world.width(), pos / Vars.world.width()); - Block result = io.anuke.mindustry.world.Generator.rocks.get(tile.floor()); + Block result = WorldGenerator.rocks.get(tile.floor()); if(result != null) tile.setBlock(result); } diff --git a/core/src/io/anuke/mindustry/io/PlatformFunction.java b/core/src/io/anuke/mindustry/io/PlatformFunction.java index e7e08555f1..55f97594eb 100644 --- a/core/src/io/anuke/mindustry/io/PlatformFunction.java +++ b/core/src/io/anuke/mindustry/io/PlatformFunction.java @@ -8,6 +8,7 @@ public abstract class PlatformFunction{ public String format(Date date){return "invalid";} public String format(int number){return "invalid";} public void openLink(String link){} + public void showError(String text){} public void addDialog(TextField field){ addDialog(field, 16); } diff --git a/core/src/io/anuke/mindustry/io/versions/Save13.java b/core/src/io/anuke/mindustry/io/versions/Save13.java index 595f9a7efa..bc3d94ccbd 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save13.java +++ b/core/src/io/anuke/mindustry/io/versions/Save13.java @@ -148,7 +148,7 @@ public class Save13 extends SaveFileVersion { for(int i = 0; i < rocks; i ++){ int pos = stream.readInt(); Tile tile = Vars.world.tile(pos % Vars.world.width(), pos / Vars.world.width()); - Block result = Generator.rocks.get(tile.floor()); + Block result = WorldGenerator.rocks.get(tile.floor()); if(result != null) tile.setBlock(result); } diff --git a/core/src/io/anuke/mindustry/io/versions/Save14.java b/core/src/io/anuke/mindustry/io/versions/Save14.java index f48dbe0f2e..fd8504601c 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save14.java +++ b/core/src/io/anuke/mindustry/io/versions/Save14.java @@ -12,7 +12,7 @@ import io.anuke.mindustry.resource.Upgrade; import io.anuke.mindustry.resource.Weapon; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.game.GameMode; -import io.anuke.mindustry.world.Generator; +import io.anuke.mindustry.world.WorldGenerator; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.Blocks; import io.anuke.mindustry.world.blocks.types.BlockPart; @@ -165,7 +165,7 @@ public class Save14 extends SaveFileVersion{ for(int i = 0; i < rocks; i ++){ int pos = stream.readInt(); Tile tile = Vars.world.tile(pos % Vars.world.width(), pos / Vars.world.width()); - Block result = Generator.rocks.get(tile.floor()); + Block result = WorldGenerator.rocks.get(tile.floor()); if(result != null) tile.setBlock(result); } diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index c4a5a790de..30e17f6d08 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -17,11 +17,6 @@ public class Packets { } - public static class EntityDataPacket{ - public int playerid; - public byte[] weapons; - } - public static class SyncPacket{ public int[] ids; public float[][] data; @@ -37,6 +32,10 @@ public class Packets { public boolean android; } + public static class ConnectConfirmPacket{ + + } + public static class DisconnectPacket{ public int playerid; } @@ -85,6 +84,7 @@ public class Packets { public static class EnemySpawnPacket{ public byte type, lane, tier; public float x, y; + public short health; public int id; } diff --git a/core/src/io/anuke/mindustry/net/Registrator.java b/core/src/io/anuke/mindustry/net/Registrator.java index fe742acfc0..8068889b9c 100644 --- a/core/src/io/anuke/mindustry/net/Registrator.java +++ b/core/src/io/anuke/mindustry/net/Registrator.java @@ -18,7 +18,6 @@ public class Registrator { StreamChunk.class, WorldData.class, SyncPacket.class, - EntityDataPacket.class, PositionPacket.class, ShootPacket.class, PlacePacket.class, @@ -39,6 +38,7 @@ public class Registrator { BlockTapPacket.class, BlockConfigPacket.class, EntityRequestPacket.class, + ConnectConfirmPacket.class, Class.class, byte[].class, diff --git a/core/src/io/anuke/mindustry/world/Generator.java b/core/src/io/anuke/mindustry/world/WorldGenerator.java similarity index 99% rename from core/src/io/anuke/mindustry/world/Generator.java rename to core/src/io/anuke/mindustry/world/WorldGenerator.java index ab7dc3b3c5..048519d6b4 100644 --- a/core/src/io/anuke/mindustry/world/Generator.java +++ b/core/src/io/anuke/mindustry/world/WorldGenerator.java @@ -16,7 +16,7 @@ import io.anuke.ucore.graphics.Hue; import io.anuke.ucore.noise.Noise; import io.anuke.ucore.util.Mathf; -public class Generator{ +public class WorldGenerator { public static final ObjectMap rocks = new ObjectMap(){{ put(Blocks.stone, Blocks.rock); put(Blocks.snow, Blocks.icerock); diff --git a/core/src/io/anuke/mindustry/world/blocks/types/defense/CoreBlock.java b/core/src/io/anuke/mindustry/world/blocks/types/defense/CoreBlock.java index 12c983a4e7..5216e7a517 100644 --- a/core/src/io/anuke/mindustry/world/blocks/types/defense/CoreBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/types/defense/CoreBlock.java @@ -19,7 +19,7 @@ public class CoreBlock extends Block { @Override public int handleDamage(Tile tile, int amount){ - return Vars.debug ? amount : amount; + return Vars.debug ? 0 : amount; } @Override diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java index f32359d451..4cf614c21f 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java @@ -81,6 +81,11 @@ public class DesktopLauncher { } } + @Override + public void showError(String text){ + JOptionPane.showMessageDialog(null, text); + } + @Override public void updateRPC() { DiscordRichPresence presence = new DiscordRichPresence();