diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 6b1429cf6f..e07fedf620 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -1,7 +1,6 @@ text.about=Created by [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[] text.credits=Credits text.discord=Join the mindustry discord! -text.changes=[SCARLET]Attention!\n[]Some important game mechanics have been changed.\n\n- [accent]Teleporters[] now use power.\n- [accent]Smelteries[] and [accent]crucibles[] now have a maximum item capacity.\n- [accent]Crucibles[] now require coal as fuel. text.link.discord.description=the official Mindustry discord chatroom text.link.github.description=Game source code text.link.dev-builds.description=Unstable development builds @@ -271,15 +270,6 @@ text.info.title=[accent]Info text.error.title=[crimson]An error has occured text.error.crashmessage=[SCARLET]An unexpected error has occured, which would have caused a crash.\n[]Please report the exact circumstances under which this error occured to the developer: \n[ORANGE]anukendev@gmail.com[] text.error.crashtitle=An error has occured -text.mode.break=Break mode: {0} -text.mode.place=Place mode: {0} -placemode.hold.name=line -placemode.areadelete.name=area -placemode.touchdelete.name=touch -placemode.holddelete.name=hold -placemode.none.name=none -placemode.touch.name=touch -placemode.cursor.name=cursor text.blocks.blockinfo=Block Info text.blocks.powercapacity=Power Capacity text.blocks.powershot=Power/Shot diff --git a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java index e2334271fc..a42faae567 100644 --- a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java @@ -13,7 +13,7 @@ public class PowerBlocks extends BlockList implements ContentList { @Override public void load() { combustionGenerator = new BurnerGenerator("combustion-generator") {{ - powerOutput = 0.06f; + powerOutput = 0.09f; powerCapacity = 40f; itemDuration = 40f; }}; @@ -27,7 +27,7 @@ public class PowerBlocks extends BlockList implements ContentList { }}; turbineGenerator = new TurbineGenerator("turbine-generator") {{ - powerOutput = 0.15f; + powerOutput = 0.25f; powerCapacity = 40f; itemDuration = 30f; size = 2; diff --git a/core/src/io/anuke/mindustry/core/ContentLoader.java b/core/src/io/anuke/mindustry/core/ContentLoader.java index 829236302e..d60fd098a3 100644 --- a/core/src/io/anuke/mindustry/core/ContentLoader.java +++ b/core/src/io/anuke/mindustry/core/ContentLoader.java @@ -1,6 +1,9 @@ package io.anuke.mindustry.core; -import com.badlogic.gdx.utils.*; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectSet; +import com.badlogic.gdx.utils.OrderedMap; +import com.badlogic.gdx.utils.OrderedSet; import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.blocks.*; import io.anuke.mindustry.content.bullets.*; @@ -28,6 +31,7 @@ public class ContentLoader { private static boolean loaded = false; private static ObjectSet> contentSet = new OrderedSet<>(); private static OrderedMap> contentMap = new OrderedMap<>(); + private static ObjectSet> initialization = new ObjectSet<>(); private static ContentList[] content = { //effects new BlockFx(), @@ -132,11 +136,15 @@ public class ContentLoader { /**Initializes all content with the specified function.*/ public static void initialize(Consumer callable){ + if(initialization.contains(callable)) return; + for(Array arr : contentSet){ for(Content content : arr){ callable.accept(content); } } + + initialization.add(callable); } public static void dispose(){ diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index d340b98c15..eff4715163 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -13,10 +13,10 @@ import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.game.Content; import io.anuke.mindustry.game.ContentDatabase; import io.anuke.mindustry.game.EventType.*; -import io.anuke.mindustry.input.MobileInput; import io.anuke.mindustry.input.DefaultKeybinds; import io.anuke.mindustry.input.DesktopInput; import io.anuke.mindustry.input.InputHandler; +import io.anuke.mindustry.input.MobileInput; import io.anuke.mindustry.io.Map; import io.anuke.mindustry.io.Saves; import io.anuke.mindustry.net.Net; @@ -285,6 +285,7 @@ public class Control extends Module{ @Override public void dispose(){ Platform.instance.onGameExit(); + ContentLoader.dispose(); Net.dispose(); ui.editor.dispose(); } @@ -321,6 +322,18 @@ public class Control extends Module{ }); } + + if(!Settings.has("4.0-no-sound")){ + Settings.putBool("4.0-no-sound", true); + + Timers.runTask(4f, () -> { + FloatingDialog dialog = new FloatingDialog("[orange]Attention![]"); + dialog.buttons().addButton("$text.ok", dialog::hide).size(100f, 60f); + dialog.content().add("You might have noticed that 4.0 does not have any sound.\nThis is [orange]intentional![] Sound will be added in a later update.\n\n[LIGHT_GRAY](now stop reporting this as a bug)").wrap().width(500f); + dialog.show(); + + }); + } } /**Called from main logic thread.*/ @@ -355,7 +368,7 @@ public class Control extends Module{ } //check unlocks every 2 seconds - if(!state.mode.infiniteResources && !state.mode.disableWaveTimer && Timers.get("timerCheckUnlock", 120)){ + if(!state.mode.infiniteResources && Timers.get("timerCheckUnlock", 120)){ checkUnlockableBlocks(); //save if the db changed, but don't save unlocks diff --git a/core/src/io/anuke/mindustry/core/GameState.java b/core/src/io/anuke/mindustry/core/GameState.java index 9262c848d1..b368a9867e 100644 --- a/core/src/io/anuke/mindustry/core/GameState.java +++ b/core/src/io/anuke/mindustry/core/GameState.java @@ -12,8 +12,6 @@ public class GameState{ public int wave = 1; public float wavetime; - public float extrawavetime; - public int enemies = 0; public boolean gameOver = false; public GameMode mode = GameMode.waves; public Difficulty difficulty = Difficulty.normal; diff --git a/core/src/io/anuke/mindustry/core/Logic.java b/core/src/io/anuke/mindustry/core/Logic.java index dc97f4b24b..b137d380f0 100644 --- a/core/src/io/anuke/mindustry/core/Logic.java +++ b/core/src/io/anuke/mindustry/core/Logic.java @@ -70,9 +70,7 @@ public class Logic extends Module { public void reset(){ state.wave = 1; - state.extrawavetime = maxwavespace * state.difficulty.maxTimeScaling; state.wavetime = wavespace * state.difficulty.timeScaling; - state.enemies = 0; state.gameOver = false; state.teams = new TeamInfo(); state.teams.add(Team.blue, true); @@ -89,7 +87,6 @@ public class Logic extends Module { state.spawner.spawnEnemies(); state.wave ++; state.wavetime = wavespace * state.difficulty.timeScaling; - state.extrawavetime = maxwavespace * state.difficulty.maxTimeScaling; Events.fire(WaveEvent.class); } @@ -133,15 +130,10 @@ public class Logic extends Module { if(!state.is(State.paused) || Net.active()){ if(!state.mode.disableWaveTimer){ - - if(state.enemies <= 0){ - if(!world.getMap().name.equals("tutorial")) state.wavetime -= Timers.delta(); - }else{ - state.extrawavetime -= Timers.delta(); - } + state.wavetime -= Timers.delta(); } - if(!Net.client() && (state.wavetime <= 0 || state.extrawavetime <= 0)){ + if(!Net.client() && state.wavetime <= 0){ runWave(); } diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index 596d787636..be54f94247 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -92,8 +92,6 @@ public class UI extends SceneModule{ Settings.setErrorHandler(()-> Timers.run(1f, ()-> showError("[crimson]Failed to access local storage.\nSettings will not be saved."))); - Settings.defaults("pixelate", true); - Dialog.closePadR = -1; Dialog.closePadT = 5; diff --git a/core/src/io/anuke/mindustry/editor/MapEditor.java b/core/src/io/anuke/mindustry/editor/MapEditor.java index 3966e1d819..441c077f09 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditor.java +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -268,6 +268,11 @@ public class MapEditor{ public void resize(int width, int height){ map = new MapTileData(width, height); + for (int x = 0; x < map.width(); x++) { + for (int y = 0; y < map.height(); y++) { + map.write(x, y, DataPosition.floor, (byte)Blocks.stone.id); + } + } renderer.resize(width, height); } } diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index 33e9fe3a81..d8624f7ef2 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -369,8 +369,8 @@ public class MapEditorDialog extends Dialog implements Disposable{ public void updateSelectedBlock(){ Block block = editor.getDrawBlock(); int i = 0; - for(Block test : Block.all()){ - if(block == test){ + for(int j = 0; j < Block.all().size; j ++){ + if(block.id == j){ blockgroup.getButtons().get(i).setChecked(true); break; } diff --git a/core/src/io/anuke/mindustry/editor/MapRenderer.java b/core/src/io/anuke/mindustry/editor/MapRenderer.java index e16d4339f6..16fc993438 100644 --- a/core/src/io/anuke/mindustry/editor/MapRenderer.java +++ b/core/src/io/anuke/mindustry/editor/MapRenderer.java @@ -61,7 +61,7 @@ public class MapRenderer implements Disposable{ while(it.hasNext){ int i = it.next(); int x = i % width; - int y = i / height; + int y = i / width; render(x, y); } updates.clear(); diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index a115bda9a9..f4d0cc5161 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -610,7 +610,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra if (target == null) { isShooting = false; target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); - } else { + } else if(target.isValid()){ //rotate toward and shoot the target rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f); @@ -697,12 +697,18 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra public void readSave(DataInput stream) throws IOException { boolean local = stream.readBoolean(); - if(local){ + if(local && !headless){ byte mechid = stream.readByte(); int index = stream.readByte(); players[index].readSaveSuper(stream); players[index].mech = Upgrade.getByID(mechid); players[index].dead = false; + }else if(local){ + byte mechid = stream.readByte(); + stream.readByte(); + readSaveSuper(stream); + mech = Upgrade.getByID(mechid); + dead = false; } } diff --git a/core/src/io/anuke/mindustry/entities/effect/Fire.java b/core/src/io/anuke/mindustry/entities/effect/Fire.java index 73ec1c7d74..078bb4b1a0 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Fire.java +++ b/core/src/io/anuke/mindustry/entities/effect/Fire.java @@ -43,7 +43,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable /**Start a fire on the tile. If there already is a file there, refreshes its lifetime.*/ public static void create(Tile tile){ - if(Net.client()) return; //not clientside. + if(Net.client() || tile == null) return; //not clientside. Fire fire = map.get(tile.packedPosition()); @@ -62,7 +62,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable /**Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.*/ public static void extinguish(Tile tile, float intensity) { - if (map.containsKey(tile.packedPosition())) { + if (tile != null && map.containsKey(tile.packedPosition())) { map.get(tile.packedPosition()).time += intensity * Timers.delta(); } } diff --git a/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java b/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java index 732b57afb3..eb96808569 100644 --- a/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java +++ b/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java @@ -69,6 +69,7 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT Effects.effect(UnitFx.pickup, drop); } itemGroup.removeByID(itemid); + netClient.addRemovedEntity(itemid); } /**Internal use only!*/ diff --git a/core/src/io/anuke/mindustry/game/GameMode.java b/core/src/io/anuke/mindustry/game/GameMode.java index 8481f1a97f..cbc7f7349d 100644 --- a/core/src/io/anuke/mindustry/game/GameMode.java +++ b/core/src/io/anuke/mindustry/game/GameMode.java @@ -4,12 +4,13 @@ import io.anuke.ucore.util.Bundles; public enum GameMode{ waves, - sandbox{ + //disabled for technical reasons + /*sandbox{ { infiniteResources = true; disableWaveTimer = true; } - }, + },*/ freebuild{ { disableWaveTimer = true; diff --git a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java index 4872127590..c9c4dcb58d 100644 --- a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java @@ -123,7 +123,7 @@ public class OverlayRenderer { drawbars.run(); if(values[0] > 0){ - drawEncloser(target.drawx(), target.drawy() + block.size * tilesize/2f + 2f + values[0]/2f - 0.5f + (values[0] > 2 ? 0.5f : 0), values[0]); + drawEncloser(target.drawx(), target.drawy() + block.size * tilesize/2f + 2f + values[0]/2f - 0.5f + (values[0] > 1 ? 0.75f : 0), values[0]); } if(values[1] > 0){ diff --git a/core/src/io/anuke/mindustry/input/MobileInput.java b/core/src/io/anuke/mindustry/input/MobileInput.java index d5c7064b21..9a260cf94d 100644 --- a/core/src/io/anuke/mindustry/input/MobileInput.java +++ b/core/src/io/anuke/mindustry/input/MobileInput.java @@ -353,7 +353,7 @@ public class MobileInput extends InputHandler implements GestureListener{ if(tile != null){ //draw placing - if(mode == placing) { + if(mode == placing && recipe != null) { NormalizeDrawResult dresult = PlaceUtils.normalizeDrawArea(recipe.result, lineStartX, lineStartY, tile.x, tile.y, true, maxLength, lineScale); Lines.rect(dresult.x, dresult.y, dresult.x2 - dresult.x, dresult.y2 - dresult.y); @@ -465,7 +465,7 @@ public class MobileInput extends InputHandler implements GestureListener{ if (tile == null) return false; - if(mode == placing) { + if(mode == placing && recipe != null) { //normalize area NormalizeResult result = PlaceUtils.normalizeArea(lineStartX, lineStartY, tile.x, tile.y, rotation, true, 100); @@ -599,6 +599,10 @@ public class MobileInput extends InputHandler implements GestureListener{ selection.clear(); } + if(lineMode && mode == placing && recipe == null){ + lineMode = false; + } + //if there is no mode and there's a recipe, switch to placing if(recipe != null && mode == none){ mode = placing; diff --git a/core/src/io/anuke/mindustry/io/versions/Save16.java b/core/src/io/anuke/mindustry/io/versions/Save16.java index fc7891ddd7..263ff28e06 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save16.java +++ b/core/src/io/anuke/mindustry/io/versions/Save16.java @@ -19,7 +19,6 @@ import io.anuke.ucore.entities.Entities; import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.entities.trait.Entity; import io.anuke.ucore.util.Bits; -import io.anuke.ucore.util.Log; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -51,7 +50,6 @@ public class Save16 extends SaveFileVersion { state.difficulty = Difficulty.values()[difficulty]; state.mode = GameMode.values()[mode]; - state.enemies = 0; //TODO display enemies correctly! state.wave = wave; state.wavetime = wavetime; diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java index a87ab23856..41d5abc494 100644 --- a/core/src/io/anuke/mindustry/net/Net.java +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -31,7 +31,6 @@ public class Net{ private static boolean active; private static boolean clientLoaded; private static Array packetQueue = new Array<>(); - private static ObjectMap, Consumer> listeners = new ObjectMap<>(); private static ObjectMap, Consumer> clientListeners = new ObjectMap<>(); private static ObjectMap, BiConsumer> serverListeners = new ObjectMap<>(); private static ClientProvider clientProvider; @@ -146,11 +145,6 @@ public class Net{ Net.serverProvider = provider; } - /**Registers a common listener for when an object is recieved. Fired on both client and serve.r*/ - public static void handle(Class type, Consumer listener){ - listeners.put(type, listener); - } - /**Registers a client listener for when an object is recieved.*/ public static void handleClient(Class type, Consumer listener){ clientListeners.put(type, listener); @@ -178,12 +172,10 @@ public class Net{ streams.remove(builder.id); handleClientReceived(builder.build()); } - }else if(clientListeners.get(object.getClass()) != null || - listeners.get(object.getClass()) != null){ + }else if(clientListeners.get(object.getClass()) != null){ if(clientLoaded || ((object instanceof Packet) && ((Packet) object).isImportant())){ if(clientListeners.get(object.getClass()) != null) clientListeners.get(object.getClass()).accept(object); - if(listeners.get(object.getClass()) != null) listeners.get(object.getClass()).accept(object); synchronized (packetPoolLock) { Pooling.free(object); } @@ -203,9 +195,8 @@ public class Net{ /**Call to handle a packet being recieved for the server.*/ public static void handleServerReceived(int connection, Object object){ - if(serverListeners.get(object.getClass()) != null || listeners.get(object.getClass()) != null){ + if(serverListeners.get(object.getClass()) != null){ if(serverListeners.get(object.getClass()) != null) serverListeners.get(object.getClass()).accept(connection, object); - if(listeners.get(object.getClass()) != null) listeners.get(object.getClass()).accept(object); synchronized (packetPoolLock) { Pooling.free(object); } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java index 1aaf99c3dd..457856f17c 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/LevelDialog.java @@ -4,19 +4,20 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.Align; import io.anuke.mindustry.game.Difficulty; -import io.anuke.mindustry.game.EventType.ResizeEvent; import io.anuke.mindustry.game.GameMode; import io.anuke.mindustry.io.Map; import io.anuke.mindustry.ui.BorderImage; -import io.anuke.ucore.core.Events; import io.anuke.ucore.core.Settings; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.scene.event.Touchable; +import io.anuke.ucore.scene.ui.ButtonGroup; import io.anuke.ucore.scene.ui.ImageButton; import io.anuke.ucore.scene.ui.ScrollPane; +import io.anuke.ucore.scene.ui.TextButton; import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.scene.utils.Cursors; +import io.anuke.ucore.scene.utils.Elements; import io.anuke.ucore.util.Bundles; import io.anuke.ucore.util.Mathf; @@ -42,7 +43,7 @@ public class LevelDialog extends FloatingDialog{ int maxwidth = (Gdx.graphics.getHeight() > Gdx.graphics.getHeight() ? 2 : 4); - /*Table selmode = new Table(); + Table selmode = new Table(); ButtonGroup group = new ButtonGroup<>(); selmode.add("$text.level.mode").padRight(15f); @@ -50,14 +51,13 @@ public class LevelDialog extends FloatingDialog{ TextButton[] b = {null}; b[0] = Elements.newButton("$mode." + mode.name() + ".name", "toggle", () -> state.mode = mode); b[0].update(() -> b[0].setChecked(state.mode == mode)); - b[0].setDisabled(true); group.add(b[0]); selmode.add(b[0]).size(130f, 54f); } selmode.addButton("?", this::displayGameModeHelp).size(50f, 54f).padLeft(18f); content().add(selmode); - content().row();*/ + content().row(); Difficulty[] ds = Difficulty.values(); diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java index ba266bc99d..4ac97e9f02 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java @@ -181,6 +181,8 @@ public class BlockInventoryFragment extends Fragment { @Remote(called = Loc.server, targets = Loc.both, in = In.blocks, forward = true) public static void requestItem(Player player, Tile tile, Item item, int amount){ + if(player == null) return; + int removed = tile.block().removeStack(tile, item, amount); player.inventory.addItem(item, removed); diff --git a/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java b/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java index 40d6598675..1d187e9057 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java @@ -185,7 +185,7 @@ public class DebugFragment extends Fragment { result.append(player.id); result.append("\n"); result.append(" cid: "); - result.append(player.con.id); + result.append(player.con == null ? -1 : player.con.id); result.append("\n"); result.append(" dead: "); result.append(player.isDead()); diff --git a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java index 9222e2b930..f61acbe745 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Scaling; import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.game.Team; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.ui.IntFormat; @@ -288,10 +289,11 @@ public class HudFragment extends Fragment{ } private String getEnemiesRemaining() { - if(state.enemies == 1) { - return Bundles.format("text.enemies.single", state.enemies); + int enemies = unitGroups[Team.red.ordinal()].size(); + if(enemies == 1) { + return Bundles.format("text.enemies.single", enemies); } else { - return Bundles.format("text.enemies", state.enemies); + return Bundles.format("text.enemies", enemies); } } @@ -310,7 +312,7 @@ public class HudFragment extends Fragment{ row(); - new label(() -> state.enemies > 0 ? + new label(() -> unitGroups[Team.red.ordinal()].size() > 0 && state.mode.disableWaveTimer ? getEnemiesRemaining() : (state.mode.disableWaveTimer) ? "$text.waiting" : timef.get((int) (state.wavetime / 60f))) @@ -333,10 +335,9 @@ public class HudFragment extends Fragment{ }).height(uheight).fillX().right().padTop(-8f).padBottom(-12f).padLeft(-15).padRight(-10).width(40f).update(l->{ boolean vis = state.mode.disableWaveTimer && (Net.server() || !Net.active()); boolean paused = state.is(State.paused) || !vis; - - l.setVisible(vis); + l.getStyle().imageUp = Core.skin.getDrawable(vis ? "icon-play" : "clear"); l.setTouchable(!paused ? Touchable.enabled : Touchable.disabled); - }); + }).visible(() -> state.mode.disableWaveTimer && (Net.server() || !Net.active()) && unitGroups[Team.red.ordinal()].size() == 0); } } diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index 483e058ba0..987041b034 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -432,7 +432,7 @@ public class Block extends BaseBlock implements Content{ if(shadowRegions != null) { Draw.rect(shadowRegions[(Mathf.randomSeed(tile.id(), 0, variants - 1))], tile.worldx(), tile.worldy()); - }else{ + }else if(shadowRegion != null){ Draw.rect(shadowRegion, tile.drawx(), tile.drawy()); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/BreakBlock.java b/core/src/io/anuke/mindustry/world/blocks/BreakBlock.java index 2be166ba70..d273aeb554 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BreakBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BreakBlock.java @@ -76,7 +76,7 @@ public class BreakBlock extends Block { public void afterDestroyed(Tile tile, TileEntity e){ BreakEntity entity = (BreakEntity)e; - if(entity.previous.synthetic()){ + if(entity != null && entity.previous != null && entity.previous.synthetic()){ tile.setBlock(entity.previous); } } @@ -145,7 +145,9 @@ public class BreakBlock extends Block { if(tile.entity instanceof BreakEntity){ BreakEntity entity = tile.entity(); - Effects.effect(Fx.breakBlock, tile.drawx(), tile.drawy(), entity.previous.size); + if(entity.previous != null){ + Effects.effect(Fx.breakBlock, tile.drawx(), tile.drawy(), entity.previous.size); + } } world.removeBlock(tile); @@ -186,6 +188,10 @@ public class BreakBlock extends Block { } progress += add; + + if(progress > 1.0001f){ + progress = 1.0001f; + } } public float progress(){ diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index 2465861d7b..4e81f02eb9 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -48,7 +48,7 @@ public class BuildBlock extends Block { @Override public boolean isSolidFor(Tile tile) { BuildEntity entity = tile.entity(); - return entity.recipe.result.solid || entity.previous.solid; + return entity == null || entity.recipe ==null || entity.recipe.result.solid || entity.previous.solid; } @Override @@ -88,7 +88,7 @@ public class BuildBlock extends Block { public void draw(Tile tile){ BuildEntity entity = tile.entity(); - if(entity.previous.synthetic()) { + if(entity.previous != null && entity.previous.synthetic()) { for (TextureRegion region : entity.previous.getBlockIcon()) { Draw.rect(region, tile.drawx(), tile.drawy(), entity.recipe.result.rotate ? tile.getRotation() * 90 : 0); } @@ -131,7 +131,7 @@ public class BuildBlock extends Block { CallBlocks.onBuildDeath(tile); } - if(!entity.updated){ + if(!entity.updated && entity.recipe != null){ entity.progress -= 1f/entity.recipe.cost/decaySpeedScl; } @@ -208,6 +208,10 @@ public class BuildBlock extends Block { lastProgress = maxProgress; updated = true; + + if(progress > 1.0001f){ + progress = 1.0001f; + } } public double checkRequired(InventoryModule inventory, double amount){ diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java index 3a12751d13..1fdfbdc39d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/ItemTurret.java @@ -40,6 +40,13 @@ public class ItemTurret extends CooledTurret { return Math.min((int)((maxAmmo - entity.totalAmmo) / ammoMap.get(item).quantityMultiplier), amount); } + + @Override + public void handleStack(Item item, int amount, Tile tile, Unit source){ + for (int i = 0; i < amount; i++) { + handleItem(item, tile, null); + } + } //currently can't remove items from turrets. @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerDistributor.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerDistributor.java index c55744b4de..07b84e8ba1 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerDistributor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerDistributor.java @@ -51,7 +51,9 @@ public class PowerDistributor extends PowerBlock { } protected boolean shouldDistribute(Tile tile, Tile other) { - return !(other.block() instanceof PowerGenerator) || other.entity.power.amount / other.block().powerCapacity < tile.entity.power.amount / powerCapacity; + //only generators can distribute to other generators + return (!(other.block() instanceof PowerGenerator) || tile.block() instanceof PowerGenerator) + && other.entity.power.amount / other.block().powerCapacity < tile.entity.power.amount / powerCapacity; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java index 1aa0c60969..331f2d4546 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java @@ -129,14 +129,14 @@ public class PowerNode extends PowerBlock{ Tile link = world.tile(x, y); if(link != null) link = link.target(); - if(link != tile && linkValid(tile, link)){ + if(link != tile && linkValid(tile, link, false)){ boolean linked = linked(tile, link); Draw.color(linked ? Palette.place : Palette.breakInvalid); Lines.square(link.drawx(), link.drawy(), link.block().size * tilesize / 2f + 1f + (linked ? 0f : Mathf.absin(Timers.time(), 4f, 1f))); - if(entity.links.size >= maxNodes && !linked){ + if((entity.links.size >= maxNodes || (link.block() instanceof PowerNode && ((DistributorEntity)link.entity).links.size >= ((PowerNode)link.block()).maxNodes)) && !linked){ Draw.color(); Draw.rect("cross-" + link.block().size, link.drawx(), link.drawy()); } @@ -190,11 +190,13 @@ public class PowerNode extends PowerBlock{ } protected boolean shouldDistribute(Tile tile, Tile other) { - return other.entity.power.amount / other.block().powerCapacity <= tile.entity.power.amount / powerCapacity; + return other.entity.power.amount / other.block().powerCapacity <= tile.entity.power.amount / powerCapacity && + !(other.block() instanceof PowerGenerator); //do not distribute to power generators } protected boolean shouldLeechPower(Tile tile, Tile other){ return !(other.block() instanceof PowerNode) + && other.block() instanceof PowerDistributor //only suck power from batteries and power generators && other.entity.power.amount / other.block().powerCapacity > tile.entity.power.amount / powerCapacity; } @@ -242,6 +244,10 @@ public class PowerNode extends PowerBlock{ } protected boolean linkValid(Tile tile, Tile link){ + return linkValid(tile, link, true); + } + + protected boolean linkValid(Tile tile, Tile link, boolean checkMaxNodes){ if(!(tile != link && link != null && link.block().hasPower)) return false; if(link.block() instanceof PowerNode){ @@ -250,7 +256,7 @@ public class PowerNode extends PowerBlock{ return Vector2.dst(tile.drawx(), tile.drawy(), link.drawx(), link.drawy()) <= Math.max(laserRange * tilesize, ((PowerNode)link.block()).laserRange * tilesize) - tilesize/2f + (link.block().size-1)*tilesize/2f + (tile.block().size-1)*tilesize/2f && - (oe.links.size < ((PowerNode)link.block()).maxNodes || oe.links.contains(tile.packedPosition())); + (!checkMaxNodes || (oe.links.size < ((PowerNode)link.block()).maxNodes || oe.links.contains(tile.packedPosition()))); }else{ return Vector2.dst(tile.drawx(), tile.drawy(), link.drawx(), link.drawy()) <= laserRange * tilesize - tilesize/2f + (link.block().size-1)*tilesize; @@ -276,8 +282,9 @@ public class PowerNode extends PowerBlock{ return new DistributorEntity(); } - @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) + @Remote(targets = Loc.both, called = Loc.server, in = In.blocks, forward = true) public static void linkPowerDistributors(Player player, Tile tile, Tile other){ + DistributorEntity entity = tile.entity(); if(!entity.links.contains(other.packedPosition())){ diff --git a/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java b/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java index 8484526dca..4d3a0a0967 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/MechFactory.java @@ -154,6 +154,9 @@ public class MechFactory extends Block{ MechFactoryEntity entity = tile.entity(); Effects.effect(Fx.spawn, entity); + + if(entity.player == null) return; + Mech result = ((MechFactory)tile.block()).mech; if(entity.player.mech == result){ diff --git a/kryonet/src/io/anuke/kryonet/ByteSerializer.java b/kryonet/src/io/anuke/kryonet/ByteSerializer.java index 5ce0e5a7b3..df278e09aa 100644 --- a/kryonet/src/io/anuke/kryonet/ByteSerializer.java +++ b/kryonet/src/io/anuke/kryonet/ByteSerializer.java @@ -26,9 +26,6 @@ public class ByteSerializer implements Serialization { throw new RuntimeException("Unregistered class: " + ClassReflection.getSimpleName(o.getClass())); byteBuffer.put(id); ((Packet) o).write(byteBuffer); - synchronized (packetPoolLock) { - Pooling.free(o); - } } } diff --git a/kryonet/src/io/anuke/kryonet/KryoClient.java b/kryonet/src/io/anuke/kryonet/KryoClient.java index dca0cbdab6..aa77a00360 100644 --- a/kryonet/src/io/anuke/kryonet/KryoClient.java +++ b/kryonet/src/io/anuke/kryonet/KryoClient.java @@ -15,6 +15,7 @@ import io.anuke.mindustry.net.NetworkIO; import io.anuke.mindustry.net.Packets.Connect; import io.anuke.mindustry.net.Packets.Disconnect; import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.util.Pooling; import io.anuke.ucore.util.Strings; import java.io.IOException; @@ -26,6 +27,7 @@ import java.nio.channels.ClosedSelectorException; import java.util.List; import static io.anuke.mindustry.Vars.*; +import static io.anuke.mindustry.net.Net.packetPoolLock; public class KryoClient implements ClientProvider{ Client client; @@ -134,6 +136,10 @@ public class KryoClient implements ClientProvider{ }else{ client.sendUDP(object); } + + synchronized (packetPoolLock) { + Pooling.free(object); + } } @Override diff --git a/kryonet/src/io/anuke/kryonet/KryoCore.java b/kryonet/src/io/anuke/kryonet/KryoCore.java index 8b4d9bc8e4..412fc1000b 100644 --- a/kryonet/src/io/anuke/kryonet/KryoCore.java +++ b/kryonet/src/io/anuke/kryonet/KryoCore.java @@ -6,9 +6,7 @@ import io.anuke.ucore.util.ColorCodes; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import static io.anuke.mindustry.Vars.headless; @@ -65,25 +63,4 @@ public class KryoCore { private static int calculateLag() { return fakeLagMin + (int)(Math.random() * (fakeLagMax - fakeLagMin)); } - - /**Executes something in a potentially unreliable way. Used to simulate lag and packet errors with UDP.*/ - public static void recieveUnreliable(Runnable run){ - if(fakeLag && threadPool == null){ - threadPool = Executors.newScheduledThreadPool(1, r -> { - Thread t = Executors.defaultThreadFactory().newThread(r); - t.setDaemon(true); - return t; - }); - } - - if(fakeLag){ - do { - if (Math.random() >= fakeLagDrop) { - threadPool.schedule(run, calculateLag(), TimeUnit.MILLISECONDS); - } - } while (Math.random() < fakeLagDuplicate); - }else{ - run.run(); - } - } } diff --git a/kryonet/src/io/anuke/kryonet/KryoServer.java b/kryonet/src/io/anuke/kryonet/KryoServer.java index af8f06dbe5..bd3f488fd7 100644 --- a/kryonet/src/io/anuke/kryonet/KryoServer.java +++ b/kryonet/src/io/anuke/kryonet/KryoServer.java @@ -15,10 +15,7 @@ import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.Net.ServerProvider; import io.anuke.mindustry.net.NetConnection; import io.anuke.mindustry.net.NetworkIO; -import io.anuke.mindustry.net.Packets.Connect; -import io.anuke.mindustry.net.Packets.Disconnect; -import io.anuke.mindustry.net.Packets.StreamBegin; -import io.anuke.mindustry.net.Packets.StreamChunk; +import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.net.Streamable; import io.anuke.ucore.UCore; import io.anuke.ucore.core.Timers; diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index 8a217d5bc1..b9f87cd798 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -221,7 +221,7 @@ public class ServerControl extends Module { if(playerGroup.size() > 0) { info("&lyPlayers: {0}", playerGroup.size()); for (Player p : playerGroup.all()) { - print(" &y{0} / Connection {1} / IP: {2}", p.name, p.con, p.con.address); + print(" &y{0} / Connection {1} / IP: {2}", p.name, p.con.id, p.con.address); } }else{ info("&lyNo players connected.");