diff --git a/annotations/src/main/java/io/anuke/annotations/Annotations.java b/annotations/src/main/java/io/anuke/annotations/Annotations.java index 7961a57b89..1d5b62ed46 100644 --- a/annotations/src/main/java/io/anuke/annotations/Annotations.java +++ b/annotations/src/main/java/io/anuke/annotations/Annotations.java @@ -4,12 +4,27 @@ import java.lang.annotation.*; public class Annotations{ - @Target({ElementType.METHOD, ElementType.FIELD}) + /** Indicates that a method should always call its super version. */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.SOURCE) + public @interface CallSuper{ + + } + + /** Annotation that allows overriding CallSuper annotation. To be used on method that overrides method with CallSuper annotation from parent class.*/ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.SOURCE) + public @interface OverrideCallSuper { + } + + /** Indicates that a method return or field can be null.*/ + @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface Nullable{ } + /** Indicates that a method return or field cannot be null.*/ @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.SOURCE) public @interface NonNull{ diff --git a/annotations/src/main/java/io/anuke/annotations/CallSuperAnnotationProcessor.java b/annotations/src/main/java/io/anuke/annotations/CallSuperAnnotationProcessor.java new file mode 100644 index 0000000000..1bdc75c786 --- /dev/null +++ b/annotations/src/main/java/io/anuke/annotations/CallSuperAnnotationProcessor.java @@ -0,0 +1,65 @@ +package io.anuke.annotations; + +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; +import io.anuke.annotations.Annotations.OverrideCallSuper; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic.Kind; +import java.util.List; +import java.util.Set; + +@SupportedAnnotationTypes("java.lang.Override") +public class CallSuperAnnotationProcessor extends AbstractProcessor{ + private Trees trees; + + @Override + public void init (ProcessingEnvironment pe) { + super.init(pe); + trees = Trees.instance(pe); + } + + public boolean process (Set annotations, RoundEnvironment roundEnv) { + for (Element e : roundEnv.getElementsAnnotatedWith(Override.class)) { + if (e.getAnnotation(OverrideCallSuper.class) != null) return false; + + CodeAnalyzerTreeScanner codeScanner = new CodeAnalyzerTreeScanner(); + codeScanner.setMethodName(e.getSimpleName().toString()); + + TreePath tp = trees.getPath(e.getEnclosingElement()); + codeScanner.scan(tp, trees); + + if (codeScanner.isCallSuperUsed()) { + List list = codeScanner.getMethod().getBody().getStatements(); + + if (!doesCallSuper(list, codeScanner.getMethodName())) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Overriding method '" + codeScanner.getMethodName() + "' must explicitly call super method from its parent class.", e); + } + } + } + + return false; + } + + private boolean doesCallSuper (List list, String methodName) { + for (Object object : list) { + if (object instanceof JCTree.JCExpressionStatement) { + JCTree.JCExpressionStatement expr = (JCExpressionStatement) object; + String exprString = expr.toString(); + if (exprString.startsWith("super." + methodName) && exprString.endsWith(");")) return true; + } + } + + return false; + } + + @Override + public SourceVersion getSupportedSourceVersion () { + return SourceVersion.RELEASE_8; + } +} diff --git a/annotations/src/main/java/io/anuke/annotations/CodeAnalyzerTreeScanner.java b/annotations/src/main/java/io/anuke/annotations/CodeAnalyzerTreeScanner.java new file mode 100644 index 0000000000..c2d543e3be --- /dev/null +++ b/annotations/src/main/java/io/anuke/annotations/CodeAnalyzerTreeScanner.java @@ -0,0 +1,98 @@ +package io.anuke.annotations; + +import com.sun.source.tree.*; +import com.sun.source.util.TreePathScanner; +import com.sun.source.util.Trees; +import com.sun.tools.javac.code.Scope; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Type.ClassType; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCTypeApply; +import io.anuke.annotations.Annotations.CallSuper; + +import java.lang.annotation.Annotation; + +class CodeAnalyzerTreeScanner extends TreePathScanner { + private String methodName; + private MethodTree method; + private boolean callSuperUsed; + + @Override + public Object visitClass (ClassTree classTree, Trees trees) { + Tree extendTree = classTree.getExtendsClause(); + + if (extendTree instanceof JCTypeApply) { //generic classes case + JCTypeApply generic = (JCTypeApply) extendTree; + extendTree = generic.clazz; + } + + if (extendTree instanceof JCIdent) { + JCIdent tree = (JCIdent) extendTree; + Scope members = tree.sym.members(); + + if (checkScope(members)) + return super.visitClass(classTree, trees); + + if (checkSuperTypes((ClassType) tree.type)) + return super.visitClass(classTree, trees); + + } + callSuperUsed = false; + + return super.visitClass(classTree, trees); + } + + public boolean checkSuperTypes (ClassType type) { + if (type.supertype_field != null && type.supertype_field.tsym != null) { + if (checkScope(type.supertype_field.tsym.members())) + return true; + else + return checkSuperTypes((ClassType) type.supertype_field); + } + + return false; + } + + public boolean checkScope (Scope members) { + for (Symbol s : members.getElements()) { + if (s instanceof MethodSymbol) { + MethodSymbol ms = (MethodSymbol) s; + + if (ms.getSimpleName().toString().equals(methodName)) { + Annotation annotation = ms.getAnnotation(CallSuper.class); + if (annotation != null) { + callSuperUsed = true; + return true; + } + } + } + } + + return false; + } + + @Override + public Object visitMethod (MethodTree methodTree, Trees trees) { + if (methodTree.getName().toString().equals(methodName)) + method = methodTree; + + return super.visitMethod(methodTree, trees); + } + + public void setMethodName (String methodName) { + this.methodName = methodName; + } + + public String getMethodName () { + return methodName; + } + + public MethodTree getMethod () { + return method; + } + + public boolean isCallSuperUsed () { + return callSuperUsed; + } +} \ No newline at end of file diff --git a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java index 358a52bbbf..1fba8bc972 100644 --- a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java +++ b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java @@ -13,9 +13,7 @@ import java.util.List; import java.util.Set; @SupportedSourceVersion(SourceVersion.RELEASE_8) -@SupportedAnnotationTypes({ -"io.anuke.annotations.Annotations.Serialize" -}) +@SupportedAnnotationTypes("io.anuke.annotations.Annotations.Serialize") public class SerializeAnnotationProcessor extends AbstractProcessor{ /** Target class name. */ private static final String className = "Serialization"; @@ -44,20 +42,10 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC); classBuilder.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "\"unchecked\"").build()); classBuilder.addJavadoc(RemoteMethodAnnotationProcessor.autogenWarning); + + MethodSpec.Builder method = MethodSpec.methodBuilder("init").addModifiers(Modifier.PUBLIC, Modifier.STATIC); - TypeName jsonType = ClassName.bestGuess("io.anuke.arc.util.serialization.Json"); - TypeName jsonValueType = ClassName.bestGuess("io.anuke.arc.util.serialization.JsonValue"); - TypeName ubJsonWriterType = ClassName.bestGuess("io.anuke.arc.util.serialization.UBJsonWriter"); - TypeName ubJsonReaderType = ClassName.bestGuess("io.anuke.arc.util.serialization.UBJsonReader"); - - classBuilder.addField(jsonType, "bjson", Modifier.STATIC, Modifier.PRIVATE); - classBuilder.addField(ubJsonReaderType, "bjsonReader", Modifier.STATIC, Modifier.PRIVATE); - classBuilder.addStaticBlock(CodeBlock.builder() - .addStatement("bjson = new " + jsonType + "()") - .addStatement("bjsonReader = new " + ubJsonReaderType + "()") - .build()); - for(TypeElement elem : elements){ TypeName type = TypeName.get(elem.asType()); String simpleTypeName = type.toString().substring(type.toString().lastIndexOf('.') + 1); @@ -79,19 +67,7 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ .addException(IOException.class) .addModifiers(Modifier.PUBLIC); - MethodSpec.Builder jsonWriteMethod = MethodSpec.methodBuilder("write" + simpleTypeName + "Json") - .returns(void.class) - .addParameter(jsonType, "json") - .addParameter(type, "object") - .addModifiers(Modifier.PUBLIC, Modifier.STATIC); - - MethodSpec.Builder jsonReadMethod = MethodSpec.methodBuilder("read" + simpleTypeName + "Json") - .returns(type) - .addParameter(jsonValueType, "value") - .addModifiers(Modifier.PUBLIC, Modifier.STATIC); - readMethod.addStatement("$L object = new $L()", type, type); - jsonReadMethod.addStatement("$L object = new $L()", type, type); List fields = ElementFilter.fieldsIn(Utils.elementUtils.getAllMembers(elem)); for(VariableElement field : fields){ @@ -105,9 +81,6 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ if(field.asType().getKind().isPrimitive()){ writeMethod.addStatement("stream.write" + capName + "(object." + name + ")"); readMethod.addStatement("object." + name + "= stream.read" + capName + "()"); - - jsonWriteMethod.addStatement("json.writeValue(\"" + name + "\", object." + name + ")"); - jsonReadMethod.addStatement("if(value.has(\"" + name + "\")) object." + name + "= value.get" + capName + "(\"" + name + "\")"); }else{ writeMethod.addStatement("io.anuke.arc.Core.settings.getSerializer(" + typeName + ".class).write(stream, object." + name + ")"); readMethod.addStatement("object." + name + " = (" + typeName + ")io.anuke.arc.Core.settings.getSerializer(" + typeName + ".class).read(stream)"); @@ -115,7 +88,6 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ } readMethod.addStatement("return object"); - jsonReadMethod.addStatement("return object"); serializer.addMethod(writeMethod.build()); serializer.addMethod(readMethod.build()); @@ -130,32 +102,6 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ classBuilder.addMethod(writeMethod.build()); classBuilder.addMethod(readMethod.build()); - - classBuilder.addMethod(jsonWriteMethod.build()); - classBuilder.addMethod(jsonReadMethod.build()); - - MethodSpec.Builder binaryJsonWriteMethod = MethodSpec.methodBuilder("write" + simpleTypeName + "StreamJson") - .returns(void.class) - .addParameter(DataOutput.class, "stream") - .addParameter(type, "object") - .addException(IOException.class) - .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addStatement("java.io.StringWriter output = new java.io.StringWriter()") - .addStatement("bjson.setWriter(output)") - .addStatement("bjson.writeObjectStart(" + type + ".class, " + type + ".class)") - .addStatement("write" + simpleTypeName + "Json(bjson, object)") - .addStatement("bjson.writeObjectEnd()") - .addStatement("stream.writeUTF(output.toString())"); - - MethodSpec.Builder binaryJsonReadMethod = MethodSpec.methodBuilder("read" + simpleTypeName + "StreamJson") - .returns(type) - .addParameter(DataInput.class, "stream") - .addException(IOException.class) - .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addStatement("return read" + simpleTypeName + "Json(bjson.fromJson(null, stream.readUTF()))"); - - classBuilder.addMethod(binaryJsonWriteMethod.build()); - classBuilder.addMethod(binaryJsonReadMethod.build()); } classBuilder.addMethod(method.build()); diff --git a/annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor index af76756139..5954bfccb9 100644 --- a/annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1,3 +1,4 @@ io.anuke.annotations.RemoteMethodAnnotationProcessor io.anuke.annotations.SerializeAnnotationProcessor io.anuke.annotations.StructAnnotationProcessor +io.anuke.annotations.CallSuperAnnotationProcessor \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9f587e2791..bcf90a444b 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ allprojects{ arcHash = null debugged = { - return new File(projectDir.parent, '../debug').exists() && !project.hasProperty("release") + return new File(projectDir.parent, '../debug').exists() && !project.hasProperty("release") && project.hasProperty("args") } localArc = { @@ -177,6 +177,31 @@ project(":core"){ } dependencies{ + if(System.properties["user.name"] == "anuke"){ + task cleanGen{ + doFirst{ + delete{ + delete "../core/src/io/anuke/mindustry/gen/" + } + } + } + + task copyGen{ + doLast{ + copy{ + from("../core/build/generated/sources/annotationProcessor/java/main/io/anuke/mindustry/gen"){ + include "**/*.java" + } + + into "../core/src/io/anuke/mindustry/gen" + } + } + } + + compileJava.dependsOn(cleanGen) + compileJava.finalizedBy(copyGen) + } + compile arcModule("arc-core") compile arcModule("extensions:freetype") compile arcModule("extensions:arcnet") @@ -227,6 +252,7 @@ project(":annotations"){ dependencies{ compile 'com.squareup:javapoet:1.11.0' + compile files("${System.getProperty('java.home')}/../lib/tools.jar") } } diff --git a/core/assets-raw/sprites/ui/icons/icon-diagonal.png b/core/assets-raw/sprites/ui/icons/icon-diagonal.png new file mode 100644 index 0000000000..262be1cebc Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-diagonal.png differ diff --git a/core/assets-raw/sprites_replacement/items/item-copper.png b/core/assets-raw/sprites_replacement/items/item-copper.png index 50742d5643..664c9cc6f3 100644 Binary files a/core/assets-raw/sprites_replacement/items/item-copper.png and b/core/assets-raw/sprites_replacement/items/item-copper.png differ diff --git a/core/assets-raw/sprites_replacement/items/item-lead.png b/core/assets-raw/sprites_replacement/items/item-lead.png index 040918a394..ce09ac6185 100644 Binary files a/core/assets-raw/sprites_replacement/items/item-lead.png and b/core/assets-raw/sprites_replacement/items/item-lead.png differ diff --git a/core/assets-raw/sprites_replacement/items/item-plastanium.png b/core/assets-raw/sprites_replacement/items/item-plastanium.png index 454afb274a..6ce75034f3 100644 Binary files a/core/assets-raw/sprites_replacement/items/item-plastanium.png and b/core/assets-raw/sprites_replacement/items/item-plastanium.png differ diff --git a/core/assets-raw/sprites_replacement/items/item-sand.png b/core/assets-raw/sprites_replacement/items/item-sand.png index 0acb132b0d..ac94898030 100644 Binary files a/core/assets-raw/sprites_replacement/items/item-sand.png and b/core/assets-raw/sprites_replacement/items/item-sand.png differ diff --git a/core/assets-raw/sprites_replacement/items/item-scrap.png b/core/assets-raw/sprites_replacement/items/item-scrap.png index e0a70317e0..0de9f03656 100644 Binary files a/core/assets-raw/sprites_replacement/items/item-scrap.png and b/core/assets-raw/sprites_replacement/items/item-scrap.png differ diff --git a/core/assets-raw/sprites_replacement/items/item-thorium.png b/core/assets-raw/sprites_replacement/items/item-thorium.png index 709a4a1046..2cd39c8f49 100644 Binary files a/core/assets-raw/sprites_replacement/items/item-thorium.png and b/core/assets-raw/sprites_replacement/items/item-thorium.png differ diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 37be0efdb7..305733dca0 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -89,13 +89,8 @@ trace = Trace Player trace.playername = Player name: [accent]{0} trace.ip = IP: [accent]{0} trace.id = Unique ID: [accent]{0} -trace.android = Android Client: [accent]{0} +trace.mobile = Mobile Client: [accent]{0} trace.modclient = Custom Client: [accent]{0} -trace.totalblocksbroken = Total blocks broken: [accent]{0} -trace.structureblocksbroken = Structure blocks broken: [accent]{0} -trace.lastblockbroken = Last block broken: [accent]{0} -trace.totalblocksplaced = Total blocks placed: [accent]{0} -trace.lastblockplaced = Last block placed: [accent]{0} invalidid = Invalid client ID! Submit a bug report. server.bans = Bans server.bans.none = No banned players found! @@ -199,6 +194,8 @@ editor.mapinfo = Map Info editor.author = Author: editor.description = Description: editor.waves = Waves: +editor.rules = Rules: +editor.ingame = Edit In-Game waves.title = Waves waves.remove = Remove waves.never = @@ -216,6 +213,8 @@ waves.copied = Waves copied. editor.default = [LIGHT_GRAY] edit = Edit... editor.name = Name: +editor.spawn = Spawn Unit +editor.removeunit = Remove Unit editor.teams = Teams editor.elevation = Elevation editor.errorload = Error loading file:\n[accent]{0} @@ -436,11 +435,11 @@ setting.fpscap.name = Max FPS setting.fpscap.none = None setting.fpscap.text = {0} FPS setting.swapdiagonal.name = Always Diagonal Placement -setting.difficulty.training = training -setting.difficulty.easy = easy -setting.difficulty.normal = normal -setting.difficulty.hard = hard -setting.difficulty.insane = insane +setting.difficulty.training = Training +setting.difficulty.easy = Easy +setting.difficulty.normal = Normal +setting.difficulty.hard = Hard +setting.difficulty.insane = Insane setting.difficulty.name = Difficulty: setting.screenshake.name = Screen Shake setting.effects.name = Display Effects diff --git a/core/assets/bundles/bundle_it.properties b/core/assets/bundles/bundle_it.properties index 07bdaafdbd..17b87b404e 100644 --- a/core/assets/bundles/bundle_it.properties +++ b/core/assets/bundles/bundle_it.properties @@ -1,4 +1,4 @@ -credits.text = Creato da [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\n\n[GRAY](Nel caso non te ne sia accorto, la traduzione del gioco non è completa.\n Chi di dovere sta lavorando più velocemente possibile per completarla!) +credits.text = Creato da [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\n\n[GRAY](Nel caso non te ne sia accorto, la traduzione del gioco non è completa.\n Chi di dovere sta lavorando più velocemente possibile per completarla! Un aiutino non sarebbe male!) credits = Crediti contributors = Translators and Contributors discord = Unisciti sul server discord di mindustry! @@ -10,38 +10,38 @@ link.itch.io.description = pagina di itch.io con download per PC e versione web link.google-play.description = Elenco di Google Play Store link.wiki.description = wiki ufficiale di Mindustry linkfail = Impossibile aprire il link! L'URL è stato copiato nella tua bacheca. -screenshot = Screenshot saved to {0} -screenshot.invalid = Map too large, potentially not enough memory for screenshot. +screenshot = Screenshot salvato a {0} +screenshot.invalid = Mappa troppo grossa, probabilmente non c'è abbastanza memoria libera. gameover = Il nucleo è stato distrutto. -gameover.pvp = The[accent] {0}[] team is victorious! +gameover.pvp = La squadra [accent] {0}[] ha vinto! highscore = [YELLOW]Nuovo record! -stat.wave = Waves Defeated:[accent] {0} -stat.enemiesDestroyed = Enemies Destroyed:[accent] {0} -stat.built = Buildings Built:[accent] {0} -stat.destroyed = Buildings Destroyed:[accent] {0} -stat.deconstructed = Buildings Deconstructed:[accent] {0} -stat.delivered = Resources Launched: -stat.rank = Final Rank: [accent]{0} -placeline = You have selected a block.\nYou can[accent] place in a line[] by[accent] holding down your finger for a few seconds[] and dragging in a direction.\nTry it. -removearea = You have selected removal mode.\nYou can[accent] remove blocks in a rectangle[] by[accent] holding down your finger for a few seconds[] and dragging.\nTry it. -launcheditems = [accent]Launched Items +stat.wave = Ondate sconfitte:[accent] {0} +stat.enemiesDestroyed = Nemici distrutti:[accent] {0} +stat.built = Costruzioni erette:[accent] {0} +stat.destroyed = Costruzioni distrutte:[accent] {0} +stat.deconstructed = Costruzioni smontate:[accent] {0} +stat.delivered = Riorse lanciate: +stat.rank = Livello finale: [accent]{0} +placeline = Hai appena selezionato un blocco.\nOra puoi[accent] piazzarne una linea[] eseguendo[accent] una lunga pressione[] e poi trascinando in ogni direzione.\nProva! +removearea = Hai appena selezionato la modalità distruzione.\nOra puoi[accent] rimuovere blocchi in una certa zona [] eseguendo[accent] una lunga pressione[] e poi trascinando in ogni direzione.\nProva! +launcheditems = [accent]Oggetti lanciati map.delete = Sei sicuro di voler eliminare questa mappa"[accent]{0}[]"? level.highscore = Miglior punteggio: [accent]{0} level.select = Selezione del livello level.mode = Modalità di gioco: showagain = non mostrare più coreattack = < Il nucleo è sotto attacco! > -nearpoint = [[ [scarlet]LEAVE DROP POINT IMMEDIATELY[] ]\nannihilation imminent -outofbounds = [[ OUT OF BOUNDS ]\n[]self-destruct in {0} -database = Core Database +nearpoint = [[ [scarlet]LACIA LA ZONA NEMICA IMMEDIATAMENTE[] ]\nautodistruzione imminente +outofbounds = [[ SEI FUORI DAL MONDO ]\n[]Auto-distruzione in {0} +database = Database nucleo savegame = Salva loadgame = Carica joingame = Unisciti al gioco addplayers = Aggiungi/rimuovi giocatori customgame = Gioco personalizzato -newgame = New Game +newgame = Nuova partita none = -minimap = Minimap +minimap = Minimapa close = Chiuso quit = Esci maps = Mappe @@ -49,14 +49,14 @@ continue = Continua maps.none = [LIGHT_GRAY]Nessuna mappa trovata! about.button = Info name = Nome: -noname = Pick a[accent] player name[] first. -filename = File Name: +noname = Scegli un [accent] nome[] prima di unirti. +filename = Nome file: unlocked = Nuovo blocco scoperto! -completed = [accent]Completed -techtree = Tech Tree -research.list = [LIGHT_GRAY]Research: -research = Research -researched = [LIGHT_GRAY]{0} researched. +completed = [accent]Completo +techtree = Albero scoperta +research.list = [LIGHT_GRAY]Ricerca: +research = Ricerca +researched = [LIGHT_GRAY]{0} cercati. players = {0} giocatori online players.single = {0} giocatori online server.closing = [accent]Chiusura server ... @@ -74,7 +74,7 @@ server.kicked.gameover = Game over! host.info = Il pulsante [accent]hos [] ospita un server sulle porte [scarlet]6567[] e [scarlet]656.[] Chiunque sulla stessa [LIGHT_GRAY]connessione wifi o rete locale[] dovrebbe essere in grado di vedere il proprio server nel proprio elenco server.\n\n Se vuoi che le persone siano in grado di connettersi ovunque tramite IP, è richiesto il [accent]port forwarding[]. \n\n[LIGHT_GRAY]Nota: se qualcuno sta riscontrando problemi durante la connessione al gioco LAN, assicurati di aver consentito a Mindustry di accedere alla rete locale nelle impostazioni del firewall. join.info = Qui è possibile inserire un [accent]IP del server[] a cui connettersi, o scoprire [accento]un server sulla rete locale[] disponibile.\n Sono supportati sia il multiplayer LAN che WAN. \n\n[LIGHT_GRAY]Nota: non esiste un elenco di server globali automatici; se si desidera connettersi a qualcuno tramite IP, è necessario chiedere all'host il proprio IP. hostserver = Host Server -hostserver.mobile = Host\nGame +hostserver.mobile = Host\nServer host = Host hosting = [accent] Apertura del server ... hosts.refresh = Aggiorna @@ -82,7 +82,7 @@ hosts.discovering = Ricerca partite LAN server.refreshing = Aggiornamento del server hosts.none = [lightgray]Nessuna partita LAN trovata! host.invalid = [scarlet]Impossibile connettersi all'host. -trace = Trace Player +trace = Traccia giocatore trace.playername = Nome del giocatore: [accent]{0} trace.ip = IP: [accent]{0} trace.id = ID univoco: [accent]{0} @@ -185,8 +185,8 @@ builtin = Incluso map.delete.confirm = Sei sicuro di voler eliminare questa mappa? Non potrai tornare indietro! map.random = [accent]Mappa casuale map.nospawn = Questa mappa non possiede un nucleo dove spawnare! Aggiungine uno nell'editor. -map.nospawn.pvp = This map does not have any enemy cores for player to spawn into! Add[SCARLET] red[] cores to this map in the editor. -map.nospawn.attack = This map does not have any enemy cores for player to attack! Add[SCARLET] red[] cores to this map in the editor. +map.nospawn.pvp = Questa mappa non ha un nucleo nemico! Aggiungi un [SCARLET]nucleo rosso[] nell'editor per poter giocare. +map.nospawn.attack = Questa mappa non ha un nucleo nemico! Aggiungi un [SCARLET]nucleo rosso[] nell'editor per poter giocare. map.invalid = Errore nel caricamento della mappa: file mappa corrotto o non valido. editor.brush = Pennello editor.openin = Apri nell'editor @@ -195,32 +195,32 @@ editor.oregen.info = Generazione dei minerali: editor.mapinfo = Informazioni mappa editor.author = Autore: editor.description = Descrizione: -editor.waves = Waves: -waves.title = Waves -waves.remove = Remove -waves.never = -waves.every = every -waves.waves = wave(s) +editor.waves = Ondate: +waves.title = Ondate +waves.remove = Rimuovi +waves.never = +waves.every = sempre +waves.waves = ondata/e waves.perspawn = per spawn -waves.to = to +waves.to = a waves.boss = Boss -waves.preview = Preview -waves.edit = Edit... -waves.copy = Copy to Clipboard -waves.load = Load from Clipboard -waves.invalid = Invalid waves in clipboard. -waves.copied = Waves copied. -editor.default = [LIGHT_GRAY] -edit = Edit... +waves.preview = Anteprima +waves.edit = Modifica... +waves.copy = Copia negli appunti +waves.load = Caica dagli appunti +waves.invalid = Onde dagli appunti non valide. +waves.copied = Onde copiate. +editor.default = [LIGHT_GRAY] +edit = Modifica... editor.name = Nome: editor.teams = Squadre editor.elevation = Elevazione -editor.errorload = Error loading file:\n[accent]{0} -editor.errorsave = Error saving file:\n[accent]{0} -editor.errorname = Map has no name defined. -editor.update = Update -editor.randomize = Randomize -editor.apply = Apply +editor.errorload = Errore nel caricamento di:\n[accent]{0} +editor.errorsave = Errore nel salvataggio di:\n[accent]{0} +editor.errorname = Questa mappa è senza nome. +editor.update = Aggiorna +editor.randomize = Casualizza +editor.apply = Applica editor.generate = Genera editor.resize = Ridimensiona editor.loadmap = Carica\nmappa @@ -250,15 +250,15 @@ editor.overwrite = [Accent]Attenzione!\nQuesto sovrascrive una mappa esistente. editor.overwrite.confirm = [scarlet]Attenzione![] Una mappa con questo nome esiste già. Sei sicuro di volerla sovrascrivere? editor.selectmap = Seleziona una mappa da caricare: filters.empty = [LIGHT_GRAY]No filters! Add one with the button below. -filter.distort = Distort -filter.noise = Noise -filter.ore = Ore -filter.rivernoise = River Noise -filter.scatter = Scatter -filter.terrain = Terrain -filter.option.scale = Scale -filter.option.chance = Chance -filter.option.mag = Magnitude +filter.distort = Modifica +filter.noise = Interferenza +filter.ore = Minerali +filter.rivernoise = Interferenze a fiume +filter.scatter = Dispersione +filter.terrain = Terreno +filter.option.scale = Scala +filter.option.chance = Probabilità +filter.option.mag = Magnitudine filter.option.threshold = Threshold filter.option.circle-scale = Circle Scale filter.option.octaves = Octaves diff --git a/core/assets/maps/craters.mmap b/core/assets/maps/craters.mmap deleted file mode 100644 index 6134ce272a..0000000000 Binary files a/core/assets/maps/craters.mmap and /dev/null differ diff --git a/core/assets/maps/craters.msav b/core/assets/maps/craters.msav new file mode 100644 index 0000000000..91066fd661 Binary files /dev/null and b/core/assets/maps/craters.msav differ diff --git a/core/assets/maps/desolateRift.mmap b/core/assets/maps/desolateRift.mmap deleted file mode 100644 index eebcb97310..0000000000 Binary files a/core/assets/maps/desolateRift.mmap and /dev/null differ diff --git a/core/assets/maps/desolateRift.msav b/core/assets/maps/desolateRift.msav new file mode 100644 index 0000000000..67a0528236 Binary files /dev/null and b/core/assets/maps/desolateRift.msav differ diff --git a/core/assets/maps/fortress.mmap b/core/assets/maps/fortress.mmap deleted file mode 100644 index 2b395107e5..0000000000 Binary files a/core/assets/maps/fortress.mmap and /dev/null differ diff --git a/core/assets/maps/fortress.msav b/core/assets/maps/fortress.msav new file mode 100644 index 0000000000..24caf622f6 Binary files /dev/null and b/core/assets/maps/fortress.msav differ diff --git a/core/assets/maps/frozenForest.mmap b/core/assets/maps/frozenForest.mmap deleted file mode 100644 index 06354bf306..0000000000 Binary files a/core/assets/maps/frozenForest.mmap and /dev/null differ diff --git a/core/assets/maps/frozenForest.msav b/core/assets/maps/frozenForest.msav new file mode 100644 index 0000000000..3984aeebc8 Binary files /dev/null and b/core/assets/maps/frozenForest.msav differ diff --git a/core/assets/maps/groundZero.mmap b/core/assets/maps/groundZero.mmap deleted file mode 100644 index e4044fd6e4..0000000000 Binary files a/core/assets/maps/groundZero.mmap and /dev/null differ diff --git a/core/assets/maps/groundZero.msav b/core/assets/maps/groundZero.msav new file mode 100644 index 0000000000..dbecd18706 Binary files /dev/null and b/core/assets/maps/groundZero.msav differ diff --git a/core/assets/maps/map_9.mmap b/core/assets/maps/map_9.mmap deleted file mode 100644 index 93d78bbb15..0000000000 Binary files a/core/assets/maps/map_9.mmap and /dev/null differ diff --git a/core/assets/maps/map_9.msav b/core/assets/maps/map_9.msav new file mode 100644 index 0000000000..d45c5d618b Binary files /dev/null and b/core/assets/maps/map_9.msav differ diff --git a/core/assets/maps/nuclearProductionComplex.mmap b/core/assets/maps/nuclearProductionComplex.mmap deleted file mode 100644 index 2668fec58a..0000000000 Binary files a/core/assets/maps/nuclearProductionComplex.mmap and /dev/null differ diff --git a/core/assets/maps/nuclearProductionComplex.msav b/core/assets/maps/nuclearProductionComplex.msav new file mode 100644 index 0000000000..e20fc7c73b Binary files /dev/null and b/core/assets/maps/nuclearProductionComplex.msav differ diff --git a/core/assets/maps/overgrowth.mmap b/core/assets/maps/overgrowth.mmap deleted file mode 100644 index 1bbb742e2f..0000000000 Binary files a/core/assets/maps/overgrowth.mmap and /dev/null differ diff --git a/core/assets/maps/overgrowth.msav b/core/assets/maps/overgrowth.msav new file mode 100644 index 0000000000..4d58d57fda Binary files /dev/null and b/core/assets/maps/overgrowth.msav differ diff --git a/core/assets/maps/ruinousShores.mmap b/core/assets/maps/ruinousShores.mmap deleted file mode 100644 index c8b4855a75..0000000000 Binary files a/core/assets/maps/ruinousShores.mmap and /dev/null differ diff --git a/core/assets/maps/ruinousShores.msav b/core/assets/maps/ruinousShores.msav new file mode 100644 index 0000000000..32f0e7cd6c Binary files /dev/null and b/core/assets/maps/ruinousShores.msav differ diff --git a/core/assets/maps/stainedMountains.mmap b/core/assets/maps/stainedMountains.mmap deleted file mode 100644 index f1119c0e80..0000000000 Binary files a/core/assets/maps/stainedMountains.mmap and /dev/null differ diff --git a/core/assets/maps/stainedMountains.msav b/core/assets/maps/stainedMountains.msav new file mode 100644 index 0000000000..6312b102cb Binary files /dev/null and b/core/assets/maps/stainedMountains.msav differ diff --git a/core/assets/maps/tarFields.mmap b/core/assets/maps/tarFields.mmap deleted file mode 100644 index 525ee369aa..0000000000 Binary files a/core/assets/maps/tarFields.mmap and /dev/null differ diff --git a/core/assets/maps/tarFields.msav b/core/assets/maps/tarFields.msav new file mode 100644 index 0000000000..8c19d68047 Binary files /dev/null and b/core/assets/maps/tarFields.msav differ diff --git a/core/assets/sprites/block_colors.png b/core/assets/sprites/block_colors.png index faaa9fa238..ff20fd35b2 100644 Binary files a/core/assets/sprites/block_colors.png and b/core/assets/sprites/block_colors.png differ diff --git a/core/assets/sprites/sprites.atlas b/core/assets/sprites/sprites.atlas index 201d46df73..4e03cb8190 100644 --- a/core/assets/sprites/sprites.atlas +++ b/core/assets/sprites/sprites.atlas @@ -3688,7 +3688,7 @@ item-blast-compound-medium index: -1 item-blast-compound-small rotate: false - xy: 1469, 226 + xy: 2029, 1167 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3709,7 +3709,7 @@ item-coal-medium index: -1 item-coal-small rotate: false - xy: 2029, 1167 + xy: 1519, 673 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3730,7 +3730,7 @@ item-copper-medium index: -1 item-copper-small rotate: false - xy: 1519, 673 + xy: 1469, 208 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3751,7 +3751,7 @@ item-graphite-medium index: -1 item-graphite-small rotate: false - xy: 1469, 208 + xy: 1537, 673 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3772,7 +3772,7 @@ item-lead-medium index: -1 item-lead-small rotate: false - xy: 1537, 673 + xy: 1469, 190 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3793,7 +3793,7 @@ item-metaglass-medium index: -1 item-metaglass-small rotate: false - xy: 1469, 190 + xy: 1555, 673 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3814,7 +3814,7 @@ item-phase-fabric-medium index: -1 item-phase-fabric-small rotate: false - xy: 1555, 673 + xy: 1469, 172 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3835,7 +3835,7 @@ item-plastanium-medium index: -1 item-plastanium-small rotate: false - xy: 1469, 172 + xy: 1573, 673 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3856,7 +3856,7 @@ item-pyratite-medium index: -1 item-pyratite-small rotate: false - xy: 1573, 673 + xy: 1469, 154 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3877,7 +3877,7 @@ item-sand-medium index: -1 item-sand-small rotate: false - xy: 1469, 154 + xy: 1469, 136 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3898,7 +3898,7 @@ item-scrap-medium index: -1 item-scrap-small rotate: false - xy: 1469, 136 + xy: 1469, 118 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3919,7 +3919,7 @@ item-silicon-medium index: -1 item-silicon-small rotate: false - xy: 1469, 118 + xy: 1469, 100 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3954,7 +3954,7 @@ item-spore-pod-medium index: -1 item-spore-pod-small rotate: false - xy: 1469, 100 + xy: 1469, 82 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3975,7 +3975,7 @@ item-surge-alloy-medium index: -1 item-surge-alloy-small rotate: false - xy: 1469, 82 + xy: 1469, 64 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -3996,7 +3996,7 @@ item-thorium-medium index: -1 item-thorium-small rotate: false - xy: 1469, 64 + xy: 1469, 46 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -4017,7 +4017,7 @@ item-titanium-medium index: -1 item-titanium-small rotate: false - xy: 1469, 46 + xy: 1469, 28 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7150,56 +7150,56 @@ icon-defense index: -1 icon-distribution rotate: false - xy: 2031, 787 + xy: 2031, 769 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-effect rotate: false - xy: 2031, 769 + xy: 2031, 751 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-liquid rotate: false - xy: 761, 535 + xy: 533, 27 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-power rotate: false - xy: 489, 1168 + xy: 783, 1101 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-production rotate: false - xy: 783, 1101 + xy: 759, 905 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-turret rotate: false - xy: 489, 1150 + xy: 2029, 1203 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-units rotate: false - xy: 1483, 673 + xy: 1469, 244 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-upgrade rotate: false - xy: 2029, 1185 + xy: 1501, 673 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7379,6 +7379,13 @@ icon-dev-builds orig: 14, 14 offset: 0, 0 index: -1 +icon-diagonal + rotate: false + xy: 2031, 787 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 icon-discord rotate: false xy: 605, 21 @@ -7416,14 +7423,14 @@ icon-egg index: -1 icon-elevation rotate: false - xy: 2031, 751 + xy: 489, 1186 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-eraser rotate: false - xy: 489, 1186 + xy: 783, 1119 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7437,14 +7444,14 @@ icon-exit index: -1 icon-file rotate: false - xy: 783, 1119 + xy: 849, 1081 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-file-image rotate: false - xy: 849, 1081 + xy: 759, 923 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7458,7 +7465,7 @@ icon-file-text index: -1 icon-fill rotate: false - xy: 759, 923 + xy: 825, 883 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7472,14 +7479,14 @@ icon-floppy index: -1 icon-floppy-16 rotate: false - xy: 825, 883 + xy: 587, 653 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-folder rotate: false - xy: 1469, 30 + xy: 1469, 12 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -7514,7 +7521,7 @@ icon-google-play index: -1 icon-grid rotate: false - xy: 587, 653 + xy: 1245, 875 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7535,28 +7542,28 @@ icon-holdDelete index: -1 icon-home rotate: false - xy: 1469, 14 + xy: 1229, 10 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-host rotate: false - xy: 1229, 10 + xy: 195, 8 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-info rotate: false - xy: 195, 8 + xy: 211, 8 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-itch.io rotate: false - xy: 211, 8 + xy: 227, 8 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -7577,14 +7584,14 @@ icon-items-none index: -1 icon-line rotate: false - xy: 1245, 875 + xy: 761, 535 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-link rotate: false - xy: 227, 8 + xy: 243, 8 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -7598,35 +7605,35 @@ icon-liquid-small index: -1 icon-load rotate: false - xy: 243, 8 + xy: 259, 8 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-load-image rotate: false - xy: 533, 27 + xy: 727, 709 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-load-map rotate: false - xy: 727, 709 + xy: 1345, 898 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-loading rotate: false - xy: 1345, 898 + xy: 2029, 1239 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-locked rotate: false - xy: 2029, 1239 + xy: 1447, 673 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7640,7 +7647,7 @@ icon-logic index: -1 icon-map rotate: false - xy: 259, 8 + xy: 275, 8 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -7654,7 +7661,7 @@ icon-menu index: -1 icon-menu-large rotate: false - xy: 1447, 673 + xy: 1469, 280 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7703,7 +7710,7 @@ icon-none index: -1 icon-paste rotate: false - xy: 1469, 280 + xy: 546, 1889 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7717,21 +7724,21 @@ icon-pause index: -1 icon-pencil rotate: false - xy: 546, 1889 + xy: 564, 1889 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-pencil-small rotate: false - xy: 275, 8 + xy: 721, 5 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-pick rotate: false - xy: 564, 1889 + xy: 489, 1168 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7745,14 +7752,14 @@ icon-play index: -1 icon-play-2 rotate: false - xy: 721, 5 + xy: 737, 5 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-play-custom rotate: false - xy: 737, 5 + xy: 753, 5 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -7780,84 +7787,84 @@ icon-power-small index: -1 icon-quit rotate: false - xy: 753, 5 + xy: 769, 5 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-redo rotate: false - xy: 759, 905 + xy: 605, 653 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-refresh rotate: false - xy: 769, 5 + xy: 785, 5 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rename rotate: false - xy: 785, 5 + xy: 801, 5 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-resize rotate: false - xy: 605, 653 + xy: 1263, 875 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-rotate rotate: false - xy: 801, 5 + xy: 2027, 1393 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rotate-arrow rotate: false - xy: 2027, 1393 + xy: 2027, 1377 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rotate-left rotate: false - xy: 2027, 1377 + xy: 2027, 1361 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rotate-right rotate: false - xy: 2027, 1361 + xy: 1463, 657 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-save rotate: false - xy: 1463, 657 + xy: 1463, 641 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-save-image rotate: false - xy: 1263, 875 + xy: 779, 535 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-save-map rotate: false - xy: 779, 535 + xy: 533, 9 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7871,56 +7878,56 @@ icon-settings index: -1 icon-spray rotate: false - xy: 533, 9 + xy: 2029, 1221 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-terrain rotate: false - xy: 2029, 1221 + xy: 1465, 673 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-tools rotate: false - xy: 1463, 641 + xy: 1479, 657 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-touch rotate: false - xy: 1463, 613 + xy: 1479, 629 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-touchDelete rotate: false - xy: 1479, 629 + xy: 1511, 645 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-trash rotate: false - xy: 1479, 657 + xy: 1495, 657 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-trash-16 rotate: false - xy: 1465, 673 + xy: 1469, 262 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-tree rotate: false - xy: 1469, 262 + xy: 489, 1150 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7934,42 +7941,42 @@ icon-tree-locked index: -1 icon-trello rotate: false - xy: 1495, 657 + xy: 1463, 625 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-tutorial rotate: false - xy: 1463, 625 + xy: 1479, 641 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-undo rotate: false - xy: 2029, 1203 + xy: 1483, 673 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-unlocked rotate: false - xy: 1469, 244 + xy: 2029, 1185 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-unlocks rotate: false - xy: 1479, 641 + xy: 1495, 641 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-wiki rotate: false - xy: 1495, 641 + xy: 1511, 657 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -7990,14 +7997,14 @@ icon-zone-locked index: -1 icon-zoom rotate: false - xy: 1501, 673 + xy: 1469, 226 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-zoom-small rotate: false - xy: 1511, 657 + xy: 1463, 609 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -10443,7 +10450,7 @@ char2 index: -1 char3 rotate: false - xy: 911, 401 + xy: 1201, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10471,7 +10478,7 @@ coal2 index: -1 coal3 rotate: false - xy: 945, 401 + xy: 1235, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10499,7 +10506,7 @@ copper3 index: -1 craters1 rotate: false - xy: 979, 401 + xy: 1269, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10513,7 +10520,7 @@ craters2 index: -1 craters3 rotate: false - xy: 1013, 401 + xy: 1303, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10527,7 +10534,7 @@ craters4 index: -1 craters5 rotate: false - xy: 1047, 401 + xy: 1337, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10555,21 +10562,21 @@ dark-metal-large index: -1 dark-metal1 rotate: false - xy: 1081, 401 + xy: 1371, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 dark-metal2 rotate: false - xy: 1115, 401 + xy: 1405, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 dark-panel-1 rotate: false - xy: 1149, 401 + xy: 1439, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10583,7 +10590,7 @@ dark-panel-1-edge index: -1 dark-panel-2 rotate: false - xy: 1183, 401 + xy: 1473, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10597,7 +10604,7 @@ dark-panel-2-edge index: -1 dark-panel-3 rotate: false - xy: 1217, 401 + xy: 1507, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10611,7 +10618,7 @@ dark-panel-3-edge index: -1 dark-panel-4 rotate: false - xy: 1251, 401 + xy: 1541, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10625,7 +10632,7 @@ dark-panel-4-edge index: -1 dark-panel-5 rotate: false - xy: 1285, 401 + xy: 1575, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10639,7 +10646,7 @@ dark-panel-5-edge index: -1 dark-panel-6 rotate: false - xy: 1319, 401 + xy: 1609, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10660,7 +10667,7 @@ darksand-edge index: -1 darksand-tainted-water rotate: false - xy: 1455, 401 + xy: 903, 335 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10674,7 +10681,7 @@ darksand-tainted-water-edge index: -1 darksand-water rotate: false - xy: 1489, 401 + xy: 937, 335 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10688,28 +10695,28 @@ darksand-water-edge index: -1 darksand1 rotate: false - xy: 1353, 401 + xy: 1643, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 darksand2 rotate: false - xy: 1387, 401 + xy: 1677, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 darksand3 rotate: false - xy: 1421, 401 + xy: 1711, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 deepwater rotate: false - xy: 1523, 401 + xy: 971, 335 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10723,21 +10730,21 @@ deepwater-edge index: -1 dunerocks-large rotate: false - xy: 613, 369 + xy: 903, 369 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 dunerocks1 rotate: false - xy: 1557, 401 + xy: 1005, 335 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 dunerocks2 rotate: false - xy: 1591, 401 + xy: 1039, 335 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10758,21 +10765,21 @@ grass-edge index: -1 grass1 rotate: false - xy: 1625, 401 + xy: 1745, 401 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 grass2 rotate: false - xy: 1659, 401 + xy: 1073, 335 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 grass3 rotate: false - xy: 1693, 401 + xy: 1839, 769 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10786,42 +10793,42 @@ holostone-edge index: -1 holostone1 rotate: false - xy: 1727, 401 + xy: 1107, 351 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 holostone2 rotate: false - xy: 613, 335 + xy: 1141, 351 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 holostone3 rotate: false - xy: 647, 335 + xy: 1175, 351 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 hotrock1 rotate: false - xy: 681, 335 + xy: 1823, 735 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 hotrock2 rotate: false - xy: 715, 335 + xy: 1823, 701 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 hotrock3 rotate: false - xy: 749, 335 + xy: 1823, 667 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10842,42 +10849,42 @@ ice-snow-edge index: -1 ice-snow1 rotate: false - xy: 817, 351 + xy: 1277, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice-snow2 rotate: false - xy: 851, 351 + xy: 1311, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice-snow3 rotate: false - xy: 885, 351 + xy: 1345, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice1 rotate: false - xy: 783, 335 + xy: 1823, 633 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice2 rotate: false - xy: 1773, 427 + xy: 1209, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice3 rotate: false - xy: 1839, 769 + xy: 1243, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10891,14 +10898,14 @@ icerocks-large index: -1 icerocks1 rotate: false - xy: 1823, 735 + xy: 1379, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 icerocks2 rotate: false - xy: 1823, 701 + xy: 1413, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10912,77 +10919,77 @@ ignarock-edge index: -1 ignarock1 rotate: false - xy: 1823, 667 + xy: 1447, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ignarock2 rotate: false - xy: 1823, 633 + xy: 1481, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ignarock3 rotate: false - xy: 919, 367 + xy: 1515, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 lead1 rotate: false - xy: 953, 367 + xy: 1549, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 lead2 rotate: false - xy: 987, 367 + xy: 1583, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 lead3 rotate: false - xy: 1021, 367 + xy: 1617, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 magmarock1 rotate: false - xy: 1055, 367 + xy: 1651, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 magmarock2 rotate: false - xy: 1089, 367 + xy: 1685, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 magmarock3 rotate: false - xy: 1123, 367 + xy: 1719, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor rotate: false - xy: 1157, 367 + xy: 1753, 367 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor-2 rotate: false - xy: 1191, 367 + xy: 199, 1 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10996,7 +11003,7 @@ metal-floor-2-edge index: -1 metal-floor-3 rotate: false - xy: 1225, 367 + xy: 1807, 597 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11010,7 +11017,7 @@ metal-floor-3-edge index: -1 metal-floor-5 rotate: false - xy: 1259, 367 + xy: 1807, 563 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11031,21 +11038,21 @@ metal-floor-damaged-edge index: -1 metal-floor-damaged1 rotate: false - xy: 1293, 367 + xy: 1807, 529 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor-damaged2 rotate: false - xy: 1327, 367 + xy: 1807, 495 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor-damaged3 rotate: false - xy: 1361, 367 + xy: 1807, 461 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11066,147 +11073,147 @@ moss-edge index: -1 moss1 rotate: false - xy: 1395, 367 + xy: 1107, 317 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 moss2 rotate: false - xy: 1429, 367 + xy: 1141, 317 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 moss3 rotate: false - xy: 1463, 367 + xy: 1175, 317 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-coal1 rotate: false - xy: 1497, 367 + xy: 1209, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-coal2 rotate: false - xy: 1531, 367 + xy: 1243, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-coal3 rotate: false - xy: 1565, 367 + xy: 1277, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-copper1 rotate: false - xy: 1599, 367 + xy: 1311, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-copper2 rotate: false - xy: 1633, 367 + xy: 1345, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-copper3 rotate: false - xy: 1667, 367 + xy: 1379, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-lead1 rotate: false - xy: 1701, 367 + xy: 1413, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-lead2 rotate: false - xy: 1735, 367 + xy: 1447, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-lead3 rotate: false - xy: 199, 1 + xy: 1481, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-scrap1 rotate: false - xy: 1807, 597 + xy: 1515, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-scrap2 rotate: false - xy: 1807, 563 + xy: 1549, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-scrap3 rotate: false - xy: 1807, 529 + xy: 1583, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-thorium1 rotate: false - xy: 1807, 495 + xy: 1617, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-thorium2 rotate: false - xy: 1807, 461 + xy: 1651, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-thorium3 rotate: false - xy: 1807, 427 + xy: 1685, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-titanium1 rotate: false - xy: 817, 317 + xy: 1719, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-titanium2 rotate: false - xy: 851, 317 + xy: 1753, 333 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-titanium3 rotate: false - xy: 885, 317 + xy: 1857, 735 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11220,21 +11227,21 @@ pebbles-edge index: -1 pebbles1 rotate: false - xy: 919, 333 + xy: 1857, 701 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 pebbles2 rotate: false - xy: 953, 333 + xy: 1857, 667 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 pebbles3 rotate: false - xy: 987, 333 + xy: 1857, 633 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11255,7 +11262,7 @@ rock1 index: -1 rock2 rotate: false - xy: 811, 385 + xy: 1101, 385 size: 48, 48 orig: 48, 48 offset: 0, 0 @@ -11269,21 +11276,21 @@ rocks-large index: -1 rocks1 rotate: false - xy: 1021, 333 + xy: 1841, 599 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 rocks2 rotate: false - xy: 1055, 333 + xy: 1841, 565 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 salt rotate: false - xy: 1089, 333 + xy: 1841, 531 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11304,14 +11311,14 @@ saltrocks-large index: -1 saltrocks1 rotate: false - xy: 1123, 333 + xy: 1841, 497 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 saltrocks2 rotate: false - xy: 1157, 333 + xy: 1841, 463 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11325,7 +11332,7 @@ sand-edge index: -1 sand-water rotate: false - xy: 1293, 333 + xy: 1277, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11339,77 +11346,77 @@ sand-water-edge index: -1 sand1 rotate: false - xy: 1191, 333 + xy: 233, 1 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 sand2 rotate: false - xy: 1225, 333 + xy: 1209, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 sand3 rotate: false - xy: 1259, 333 + xy: 1243, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 sandrocks-large rotate: false - xy: 679, 369 + xy: 969, 369 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 sandrocks1 rotate: false - xy: 1327, 333 + xy: 1311, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 sandrocks2 rotate: false - xy: 1361, 333 + xy: 1345, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 scrap1 rotate: false - xy: 1395, 333 + xy: 1379, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 scrap2 rotate: false - xy: 1429, 333 + xy: 1413, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 scrap3 rotate: false - xy: 1463, 333 + xy: 1447, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shale-boulder1 rotate: false - xy: 1599, 333 + xy: 1583, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shale-boulder2 rotate: false - xy: 1633, 333 + xy: 1617, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11423,21 +11430,21 @@ shale-edge index: -1 shale1 rotate: false - xy: 1497, 333 + xy: 1481, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shale2 rotate: false - xy: 1531, 333 + xy: 1515, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shale3 rotate: false - xy: 1565, 333 + xy: 1549, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11451,14 +11458,14 @@ shalerocks-large index: -1 shalerocks1 rotate: false - xy: 1667, 333 + xy: 1651, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shalerocks2 rotate: false - xy: 1701, 333 + xy: 1685, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11472,14 +11479,14 @@ shrubs-large index: -1 shrubs1 rotate: false - xy: 1735, 333 + xy: 1719, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shrubs2 rotate: false - xy: 1857, 735 + xy: 1753, 299 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11493,21 +11500,21 @@ snow-edge index: -1 snow1 rotate: false - xy: 1857, 701 + xy: 1875, 599 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 snow2 rotate: false - xy: 1857, 667 + xy: 1875, 565 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 snow3 rotate: false - xy: 1857, 633 + xy: 1875, 531 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11521,7 +11528,7 @@ snowrock1 index: -1 snowrock2 rotate: false - xy: 861, 385 + xy: 1151, 385 size: 48, 48 orig: 48, 48 offset: 0, 0 @@ -11535,18 +11542,25 @@ snowrocks-large index: -1 snowrocks1 rotate: false - xy: 1841, 599 + xy: 1875, 497 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 snowrocks2 rotate: false - xy: 1841, 565 + xy: 1875, 463 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 +spawn-edge + rotate: false + xy: 1193, 533 + size: 288, 96 + orig: 288, 96 + offset: 0, 0 + index: -1 spore-cluster1 rotate: false xy: 1839, 871 @@ -11570,28 +11584,28 @@ spore-cluster3 index: -1 spore-moss-edge rotate: false - xy: 1193, 533 + xy: 1483, 631 size: 288, 96 orig: 288, 96 offset: 0, 0 index: -1 spore-moss1 rotate: false - xy: 1841, 531 + xy: 267, 9 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 spore-moss2 rotate: false - xy: 1841, 497 + xy: 1993, 921 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 spore-moss3 rotate: false - xy: 1841, 463 + xy: 1779, 427 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11605,161 +11619,161 @@ spore-pine index: -1 sporerocks-large rotate: false - xy: 745, 369 + xy: 1035, 369 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 sporerocks1 rotate: false - xy: 1841, 429 + xy: 1813, 427 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 sporerocks2 rotate: false - xy: 233, 1 + xy: 1847, 429 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 stone-edge rotate: false - xy: 1483, 631 + xy: 1193, 435 size: 288, 96 orig: 288, 96 offset: 0, 0 index: -1 stone1 rotate: false - xy: 919, 299 + xy: 1881, 429 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 stone2 rotate: false - xy: 953, 299 + xy: 1873, 837 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 stone3 rotate: false - xy: 987, 299 + xy: 1873, 803 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 tainted-water rotate: false - xy: 1021, 299 + xy: 1873, 769 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 tainted-water-edge rotate: false - xy: 1193, 435 + xy: 1483, 533 size: 288, 96 orig: 288, 96 offset: 0, 0 index: -1 tar rotate: false - xy: 1055, 299 + xy: 1891, 735 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 tar-edge rotate: false - xy: 1483, 533 + xy: 1483, 435 size: 288, 96 orig: 288, 96 offset: 0, 0 index: -1 tendrils-edge rotate: false - xy: 1483, 435 + xy: 323, 337 size: 288, 96 orig: 288, 96 offset: 0, 0 index: -1 tendrils1 rotate: false - xy: 1089, 299 + xy: 1891, 701 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 tendrils2 rotate: false - xy: 1123, 299 + xy: 1891, 667 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 tendrils3 rotate: false - xy: 1157, 299 + xy: 1891, 633 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 thorium1 rotate: false - xy: 1191, 299 + xy: 1909, 599 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 thorium2 rotate: false - xy: 1225, 299 + xy: 1909, 565 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 thorium3 rotate: false - xy: 1259, 299 + xy: 1909, 531 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 titanium1 rotate: false - xy: 1293, 299 + xy: 1909, 497 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 titanium2 rotate: false - xy: 1327, 299 + xy: 1909, 463 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 titanium3 rotate: false - xy: 1361, 299 + xy: 1915, 429 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 water rotate: false - xy: 1395, 299 + xy: 1787, 393 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 water-edge rotate: false - xy: 323, 337 + xy: 613, 337 size: 288, 96 orig: 288, 96 offset: 0, 0 diff --git a/core/assets/sprites/sprites.png b/core/assets/sprites/sprites.png index 81a49984a7..71da20b99c 100644 Binary files a/core/assets/sprites/sprites.png and b/core/assets/sprites/sprites.png differ diff --git a/core/assets/sprites/sprites3.png b/core/assets/sprites/sprites3.png index 843b93d182..192d3985b9 100644 Binary files a/core/assets/sprites/sprites3.png and b/core/assets/sprites/sprites3.png differ diff --git a/core/assets/sprites/sprites_fallback.atlas b/core/assets/sprites/sprites_fallback.atlas index 7583316f05..8f3916def5 100644 --- a/core/assets/sprites/sprites_fallback.atlas +++ b/core/assets/sprites/sprites_fallback.atlas @@ -7627,7 +7627,7 @@ item-blast-compound-medium index: -1 item-blast-compound-small rotate: false - xy: 977, 69 + xy: 995, 69 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7641,7 +7641,7 @@ item-coal-medium index: -1 item-coal-small rotate: false - xy: 995, 69 + xy: 698, 83 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7655,7 +7655,7 @@ item-copper-medium index: -1 item-copper-small rotate: false - xy: 698, 83 + xy: 698, 65 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7669,7 +7669,7 @@ item-graphite-medium index: -1 item-graphite-small rotate: false - xy: 698, 65 + xy: 716, 83 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7683,7 +7683,7 @@ item-lead-medium index: -1 item-lead-small rotate: false - xy: 716, 83 + xy: 716, 65 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7697,7 +7697,7 @@ item-metaglass-medium index: -1 item-metaglass-small rotate: false - xy: 716, 65 + xy: 713, 47 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7711,7 +7711,7 @@ item-phase-fabric-medium index: -1 item-phase-fabric-small rotate: false - xy: 713, 47 + xy: 713, 29 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7725,7 +7725,7 @@ item-plastanium-medium index: -1 item-plastanium-small rotate: false - xy: 713, 29 + xy: 713, 11 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7739,7 +7739,7 @@ item-pyratite-medium index: -1 item-pyratite-small rotate: false - xy: 713, 11 + xy: 731, 47 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7753,7 +7753,7 @@ item-sand-medium index: -1 item-sand-small rotate: false - xy: 731, 47 + xy: 731, 29 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7767,7 +7767,7 @@ item-scrap-medium index: -1 item-scrap-small rotate: false - xy: 731, 29 + xy: 731, 11 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7781,7 +7781,7 @@ item-silicon-medium index: -1 item-silicon-small rotate: false - xy: 731, 11 + xy: 749, 91 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7802,7 +7802,7 @@ item-spore-pod-medium index: -1 item-spore-pod-small rotate: false - xy: 749, 91 + xy: 767, 83 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7816,7 +7816,7 @@ item-surge-alloy-medium index: -1 item-surge-alloy-small rotate: false - xy: 767, 83 + xy: 734, 65 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7830,7 +7830,7 @@ item-thorium-medium index: -1 item-thorium-small rotate: false - xy: 734, 65 + xy: 749, 47 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -7844,7 +7844,7 @@ item-titanium-medium index: -1 item-titanium-small rotate: false - xy: 749, 47 + xy: 749, 29 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -9513,56 +9513,56 @@ icon-defense index: -1 icon-distribution rotate: false - xy: 815, 115 + xy: 815, 97 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-effect rotate: false - xy: 815, 97 + xy: 833, 120 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-liquid rotate: false - xy: 851, 105 + xy: 851, 87 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-power rotate: false - xy: 923, 87 + xy: 941, 105 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-production rotate: false - xy: 941, 105 + xy: 941, 87 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-turret rotate: false - xy: 869, 69 + xy: 887, 69 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-units rotate: false - xy: 905, 69 + xy: 923, 69 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-upgrade rotate: false - xy: 941, 69 + xy: 959, 69 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -9742,6 +9742,13 @@ icon-dev-builds orig: 14, 14 offset: 0, 0 index: -1 +icon-diagonal + rotate: false + xy: 815, 115 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 icon-discord rotate: false xy: 1009, 341 @@ -9779,14 +9786,14 @@ icon-egg index: -1 icon-elevation rotate: false - xy: 833, 120 + xy: 833, 102 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-eraser rotate: false - xy: 833, 102 + xy: 693, 101 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -9800,14 +9807,14 @@ icon-exit index: -1 icon-file rotate: false - xy: 693, 101 + xy: 711, 101 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-file-image rotate: false - xy: 711, 101 + xy: 1005, 267 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -9821,56 +9828,56 @@ icon-file-text index: -1 icon-fill rotate: false - xy: 1005, 267 + xy: 1001, 249 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-floppy rotate: false - xy: 749, 31 + xy: 749, 13 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-floppy-16 rotate: false - xy: 1001, 249 + xy: 1001, 231 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-folder rotate: false - xy: 749, 15 + xy: 785, 81 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-folder-parent rotate: false - xy: 785, 81 + xy: 801, 81 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-github rotate: false - xy: 801, 81 + xy: 817, 81 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-google-play rotate: false - xy: 817, 81 + xy: 752, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-grid rotate: false - xy: 1001, 231 + xy: 833, 84 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -9891,28 +9898,28 @@ icon-holdDelete index: -1 icon-home rotate: false - xy: 752, 65 + xy: 768, 67 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-host rotate: false - xy: 768, 67 + xy: 784, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-info rotate: false - xy: 784, 65 + xy: 800, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-itch.io rotate: false - xy: 800, 65 + xy: 816, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -9933,14 +9940,14 @@ icon-items-none index: -1 icon-line rotate: false - xy: 833, 84 + xy: 851, 105 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-link rotate: false - xy: 816, 65 + xy: 768, 51 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -9954,35 +9961,35 @@ icon-liquid-small index: -1 icon-load rotate: false - xy: 768, 51 + xy: 767, 35 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-load-image rotate: false - xy: 851, 87 + xy: 869, 105 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-load-map rotate: false - xy: 869, 105 + xy: 869, 87 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-loading rotate: false - xy: 869, 87 + xy: 887, 105 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-locked rotate: false - xy: 887, 105 + xy: 887, 87 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -10010,14 +10017,14 @@ icon-menu index: -1 icon-menu-large rotate: false - xy: 887, 87 + xy: 905, 105 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-missing rotate: false - xy: 851, 126 + xy: 749, 1 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -10031,14 +10038,14 @@ icon-mission-background index: -1 icon-mission-battle rotate: false - xy: 569, 5 + xy: 851, 126 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-mission-defense rotate: false - xy: 829, 5 + xy: 569, 5 size: 10, 10 orig: 10, 10 offset: 0, 0 @@ -10059,7 +10066,7 @@ icon-none index: -1 icon-paste rotate: false - xy: 905, 105 + xy: 905, 87 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -10073,7 +10080,7 @@ icon-pause index: -1 icon-pencil rotate: false - xy: 905, 87 + xy: 923, 105 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -10087,7 +10094,7 @@ icon-pencil-small index: -1 icon-pick rotate: false - xy: 923, 105 + xy: 923, 87 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -10108,7 +10115,7 @@ icon-play-2 index: -1 icon-play-custom rotate: false - xy: 833, 68 + xy: 783, 33 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -10129,119 +10136,119 @@ icon-power-requirement index: -1 icon-power-small rotate: false - xy: 749, 3 + xy: 847, 24 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-quit rotate: false - xy: 832, 52 + xy: 799, 33 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-redo rotate: false - xy: 941, 87 + xy: 959, 105 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-refresh rotate: false - xy: 767, 35 + xy: 815, 33 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rename rotate: false - xy: 765, 19 + xy: 767, 19 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-resize rotate: false - xy: 959, 105 + xy: 959, 87 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-rotate rotate: false - xy: 783, 33 + xy: 783, 17 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rotate-arrow rotate: false - xy: 799, 33 + xy: 799, 17 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rotate-left rotate: false - xy: 815, 33 + xy: 815, 17 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rotate-right rotate: false - xy: 781, 17 + xy: 765, 1 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-save rotate: false - xy: 797, 17 + xy: 781, 1 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-save-image rotate: false - xy: 959, 87 + xy: 977, 105 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-save-map rotate: false - xy: 977, 105 + xy: 977, 87 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-settings rotate: false - xy: 841, 5 + xy: 847, 12 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-spray rotate: false - xy: 977, 87 + xy: 995, 105 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-terrain rotate: false - xy: 995, 105 + xy: 995, 87 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-tools rotate: false - xy: 813, 17 + xy: 797, 1 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -10262,21 +10269,21 @@ icon-touchDelete index: -1 icon-trash rotate: false - xy: 765, 3 + xy: 813, 1 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-trash-16 rotate: false - xy: 995, 87 + xy: 851, 69 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-tree rotate: false - xy: 851, 69 + xy: 869, 69 size: 16, 16 orig: 16, 16 offset: 0, 0 @@ -10290,35 +10297,35 @@ icon-tree-locked index: -1 icon-trello rotate: false - xy: 781, 1 + xy: 829, 1 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-tutorial rotate: false - xy: 797, 1 + xy: 833, 68 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-undo rotate: false - xy: 887, 69 + xy: 905, 69 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-unlocked rotate: false - xy: 923, 69 + xy: 941, 69 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-unlocks rotate: false - xy: 813, 1 + xy: 832, 52 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -10339,14 +10346,14 @@ icon-zone index: -1 icon-zoom rotate: false - xy: 959, 69 + xy: 977, 69 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-zoom-small rotate: false - xy: 829, 17 + xy: 831, 20 size: 14, 14 orig: 14, 14 offset: 0, 0 @@ -10635,35 +10642,35 @@ filter: Nearest,Nearest repeat: none char1 rotate: false - xy: 423, 637 + xy: 713, 735 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 char2 rotate: false - xy: 473, 687 + xy: 763, 785 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 char3 rotate: false - xy: 763, 785 + xy: 1, 309 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 cliffs1 rotate: false - xy: 1, 309 + xy: 183, 491 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 coal1 rotate: false - xy: 183, 491 + xy: 473, 589 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10677,42 +10684,42 @@ coal2 index: -1 coal3 rotate: false - xy: 383, 555 + xy: 673, 653 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 copper1 rotate: false - xy: 423, 603 + xy: 713, 701 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 copper2 rotate: false - xy: 507, 687 + xy: 797, 785 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 copper3 rotate: false - xy: 797, 785 + xy: 1, 275 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 craters1 rotate: false - xy: 1, 275 + xy: 217, 491 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 craters2 rotate: false - xy: 217, 491 + xy: 507, 589 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10733,14 +10740,14 @@ craters4 index: -1 craters5 rotate: false - xy: 541, 687 + xy: 831, 785 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 craters6 rotate: false - xy: 831, 785 + xy: 1, 241 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10754,21 +10761,21 @@ creeptree index: -1 dark-metal-large rotate: false - xy: 291, 655 + xy: 581, 753 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 dark-metal1 rotate: false - xy: 1, 241 + xy: 251, 491 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 dark-metal2 rotate: false - xy: 251, 491 + xy: 541, 589 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10817,14 +10824,14 @@ dark-panel-6 index: -1 darksand-tainted-water rotate: false - xy: 649, 719 + xy: 359, 523 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 darksand-water rotate: false - xy: 683, 719 + xy: 393, 523 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10838,49 +10845,49 @@ darksand1 index: -1 darksand2 rotate: false - xy: 581, 719 + xy: 291, 523 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 darksand3 rotate: false - xy: 615, 719 + xy: 325, 523 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 deepwater rotate: false - xy: 575, 685 + xy: 285, 489 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 dunerocks-large rotate: false - xy: 581, 753 + xy: 1, 459 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 dunerocks1 rotate: false - xy: 609, 685 + xy: 319, 489 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 dunerocks2 rotate: false - xy: 643, 685 + xy: 353, 489 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 grass1 rotate: false - xy: 677, 685 + xy: 387, 489 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10901,7 +10908,7 @@ grass3 index: -1 holostone1 rotate: false - xy: 717, 735 + xy: 427, 539 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -10922,385 +10929,385 @@ holostone3 index: -1 hotrock1 rotate: false - xy: 457, 637 + xy: 747, 735 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 hotrock2 rotate: false - xy: 457, 603 + xy: 747, 701 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 hotrock3 rotate: false - xy: 491, 653 + xy: 781, 751 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice-snow1 rotate: false - xy: 559, 651 + xy: 35, 309 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice-snow2 rotate: false - xy: 593, 651 + xy: 35, 275 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice-snow3 rotate: false - xy: 627, 651 + xy: 35, 241 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice1 rotate: false - xy: 491, 619 + xy: 781, 717 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice2 rotate: false - xy: 525, 653 + xy: 815, 751 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ice3 rotate: false - xy: 525, 619 + xy: 815, 717 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 icerocks-large rotate: false - xy: 1, 459 + xy: 291, 557 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 icerocks1 rotate: false - xy: 661, 651 + xy: 35, 207 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 icerocks2 rotate: false - xy: 559, 617 + xy: 35, 173 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ignarock1 rotate: false - xy: 593, 617 + xy: 35, 139 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ignarock2 rotate: false - xy: 627, 617 + xy: 35, 105 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ignarock3 rotate: false - xy: 661, 617 + xy: 35, 71 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 lead1 rotate: false - xy: 763, 751 + xy: 35, 37 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 lead2 rotate: false - xy: 797, 751 + xy: 35, 3 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 lead3 rotate: false - xy: 831, 751 + xy: 473, 555 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 magmarock1 rotate: false - xy: 865, 771 + xy: 507, 555 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 magmarock2 rotate: false - xy: 865, 737 + xy: 541, 555 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 magmarock3 rotate: false - xy: 35, 309 + xy: 183, 457 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor rotate: false - xy: 35, 275 + xy: 217, 457 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor-2 rotate: false - xy: 35, 241 + xy: 251, 457 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor-3 rotate: false - xy: 35, 207 + xy: 285, 455 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor-5 rotate: false - xy: 35, 173 + xy: 319, 455 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor-damaged1 rotate: false - xy: 35, 139 + xy: 353, 455 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor-damaged2 rotate: false - xy: 35, 105 + xy: 387, 455 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 metal-floor-damaged3 rotate: false - xy: 35, 71 + xy: 427, 505 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 moss1 rotate: false - xy: 35, 37 + xy: 421, 471 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 moss2 rotate: false - xy: 35, 3 + xy: 461, 521 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 moss3 rotate: false - xy: 183, 457 + xy: 495, 521 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-coal1 rotate: false - xy: 217, 457 + xy: 529, 521 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-coal2 rotate: false - xy: 251, 457 + xy: 85, 375 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-coal3 rotate: false - xy: 717, 701 + xy: 781, 683 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-copper1 rotate: false - xy: 751, 717 + xy: 815, 683 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-copper2 rotate: false - xy: 785, 717 + xy: 849, 751 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-copper3 rotate: false - xy: 819, 717 + xy: 849, 717 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-lead1 rotate: false - xy: 85, 375 + xy: 849, 683 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-lead2 rotate: false - xy: 695, 651 + xy: 421, 437 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-lead3 rotate: false - xy: 695, 617 + xy: 461, 487 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-scrap1 rotate: false - xy: 491, 585 + xy: 495, 487 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-scrap2 rotate: false - xy: 525, 585 + xy: 529, 487 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-scrap3 rotate: false - xy: 559, 583 + xy: 455, 453 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-thorium1 rotate: false - xy: 593, 583 + xy: 489, 453 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-thorium2 rotate: false - xy: 627, 583 + xy: 523, 453 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-thorium3 rotate: false - xy: 661, 583 + xy: 85, 341 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-titanium1 rotate: false - xy: 695, 583 + xy: 69, 307 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-titanium2 rotate: false - xy: 751, 683 + xy: 69, 273 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 ore-titanium3 rotate: false - xy: 785, 683 + xy: 69, 239 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 pebbles1 rotate: false - xy: 819, 683 + xy: 69, 205 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 pebbles2 rotate: false - xy: 853, 703 + xy: 69, 171 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 pebbles3 rotate: false - xy: 853, 669 + xy: 69, 137 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 pine rotate: false - xy: 291, 539 + xy: 581, 637 size: 48, 48 orig: 48, 48 offset: 0, 0 index: -1 rock1 rotate: false - xy: 423, 671 + xy: 713, 769 size: 48, 48 orig: 48, 48 offset: 0, 0 index: -1 rock2 rotate: false - xy: 713, 769 + xy: 1, 343 size: 48, 48 orig: 48, 48 offset: 0, 0 @@ -11314,42 +11321,42 @@ rocks-large index: -1 rocks1 rotate: false - xy: 85, 341 + xy: 69, 103 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 rocks2 rotate: false - xy: 69, 307 + xy: 69, 69 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 salt rotate: false - xy: 69, 273 + xy: 69, 35 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 saltrocks-large rotate: false - xy: 291, 589 + xy: 581, 687 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 saltrocks1 rotate: false - xy: 69, 239 + xy: 69, 1 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 saltrocks2 rotate: false - xy: 69, 205 + xy: 119, 391 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11363,7 +11370,7 @@ sand-edge index: -1 sand-water rotate: false - xy: 69, 69 + xy: 523, 419 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11377,77 +11384,77 @@ sand-water-edge index: -1 sand1 rotate: false - xy: 69, 171 + xy: 119, 357 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 sand2 rotate: false - xy: 69, 137 + xy: 455, 419 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 sand3 rotate: false - xy: 69, 103 + xy: 489, 419 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 sandrocks-large rotate: false - xy: 357, 655 + xy: 647, 753 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 sandrocks1 rotate: false - xy: 69, 35 + xy: 103, 307 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 sandrocks2 rotate: false - xy: 69, 1 + xy: 103, 273 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 scrap1 rotate: false - xy: 119, 391 + xy: 103, 239 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 scrap2 rotate: false - xy: 119, 357 + xy: 103, 205 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 scrap3 rotate: false - xy: 103, 307 + xy: 103, 171 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shale-boulder1 rotate: false - xy: 103, 171 + xy: 103, 35 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shale-boulder2 rotate: false - xy: 103, 137 + xy: 103, 1 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11461,63 +11468,63 @@ shale-edge index: -1 shale1 rotate: false - xy: 103, 273 + xy: 103, 137 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shale2 rotate: false - xy: 103, 239 + xy: 103, 103 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shale3 rotate: false - xy: 103, 205 + xy: 103, 69 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shalerocks-large rotate: false - xy: 647, 753 + xy: 1, 393 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 shalerocks1 rotate: false - xy: 103, 103 + xy: 581, 603 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shalerocks2 rotate: false - xy: 103, 69 + xy: 575, 569 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shrubs-large rotate: false - xy: 1, 393 + xy: 67, 459 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 shrubs1 rotate: false - xy: 103, 35 + xy: 615, 603 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 shrubs2 rotate: false - xy: 103, 1 + xy: 609, 569 size: 32, 32 orig: 32, 32 offset: 0, 0 @@ -11531,60 +11538,67 @@ snow-edge index: -1 snow1 rotate: false - xy: 291, 505 + xy: 649, 611 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 snow2 rotate: false - xy: 285, 471 + xy: 683, 619 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 snow3 rotate: false - xy: 325, 505 + xy: 707, 653 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 snowrock1 rotate: false - xy: 1, 343 + xy: 67, 409 size: 48, 48 orig: 48, 48 offset: 0, 0 index: -1 snowrock2 rotate: false - xy: 67, 409 + xy: 133, 475 size: 48, 48 orig: 48, 48 offset: 0, 0 index: -1 snowrocks-large rotate: false - xy: 67, 459 + xy: 357, 557 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 snowrocks1 rotate: false - xy: 319, 471 + xy: 717, 619 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 snowrocks2 rotate: false - xy: 359, 513 + xy: 741, 667 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 +spawn-edge + rotate: false + xy: 291, 819 + size: 288, 96 + orig: 288, 96 + offset: 0, 0 + index: -1 spore-cluster1 rotate: false xy: 981, 973 @@ -11601,203 +11615,203 @@ spore-cluster2 index: -1 spore-cluster3 rotate: false - xy: 341, 547 + xy: 631, 645 size: 40, 40 orig: 40, 40 offset: 0, 0 index: -1 spore-moss-edge rotate: false - xy: 291, 819 + xy: 581, 917 size: 288, 96 orig: 288, 96 offset: 0, 0 index: -1 spore-moss1 rotate: false - xy: 393, 521 + xy: 575, 535 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 spore-moss2 rotate: false - xy: 417, 555 + xy: 609, 535 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 spore-moss3 rotate: false - xy: 427, 521 + xy: 563, 501 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 spore-pine rotate: false - xy: 133, 475 + xy: 423, 573 size: 48, 48 orig: 48, 48 offset: 0, 0 index: -1 sporerocks-large rotate: false - xy: 357, 589 + xy: 647, 687 size: 64, 64 orig: 64, 64 offset: 0, 0 index: -1 sporerocks1 rotate: false - xy: 451, 569 + xy: 597, 501 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 sporerocks2 rotate: false - xy: 285, 437 + xy: 563, 467 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 stone-edge rotate: false - xy: 581, 917 + xy: 1, 623 size: 288, 96 orig: 288, 96 offset: 0, 0 index: -1 stone1 rotate: false - xy: 319, 437 + xy: 597, 467 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 stone2 rotate: false - xy: 887, 703 + xy: 557, 433 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 stone3 rotate: false - xy: 887, 669 + xy: 591, 433 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 tainted-water rotate: false - xy: 729, 649 + xy: 557, 399 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 tainted-water-edge rotate: false - xy: 1, 623 + xy: 291, 721 size: 288, 96 orig: 288, 96 offset: 0, 0 index: -1 tar rotate: false - xy: 729, 615 + xy: 591, 399 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 tar-edge rotate: false - xy: 291, 721 + xy: 581, 819 size: 288, 96 orig: 288, 96 offset: 0, 0 index: -1 tendrils-edge rotate: false - xy: 581, 819 + xy: 1, 525 size: 288, 96 orig: 288, 96 offset: 0, 0 index: -1 tendrils1 rotate: false - xy: 763, 649 + xy: 631, 501 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 tendrils2 rotate: false - xy: 763, 615 + xy: 631, 467 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 tendrils3 rotate: false - xy: 797, 649 + xy: 625, 433 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 thorium1 rotate: false - xy: 797, 615 + xy: 625, 399 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 thorium2 rotate: false - xy: 729, 581 + xy: 751, 633 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 thorium3 rotate: false - xy: 763, 581 + xy: 785, 649 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 titanium1 rotate: false - xy: 797, 581 + xy: 819, 649 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 titanium2 rotate: false - xy: 831, 635 + xy: 853, 649 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 titanium3 rotate: false - xy: 831, 601 + xy: 785, 615 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 water rotate: false - xy: 865, 635 + xy: 819, 615 size: 32, 32 orig: 32, 32 offset: 0, 0 index: -1 water-edge rotate: false - xy: 1, 525 + xy: 291, 623 size: 288, 96 orig: 288, 96 offset: 0, 0 diff --git a/core/assets/sprites/sprites_fallback4.png b/core/assets/sprites/sprites_fallback4.png index 8f535d5f0d..5bbf265a48 100644 Binary files a/core/assets/sprites/sprites_fallback4.png and b/core/assets/sprites/sprites_fallback4.png differ diff --git a/core/assets/sprites/sprites_fallback5.png b/core/assets/sprites/sprites_fallback5.png index 446c926ff3..46781d2e93 100644 Binary files a/core/assets/sprites/sprites_fallback5.png and b/core/assets/sprites/sprites_fallback5.png differ diff --git a/core/assets/sprites/sprites_fallback7.png b/core/assets/sprites/sprites_fallback7.png index f2fae997ba..fdc9f89c42 100644 Binary files a/core/assets/sprites/sprites_fallback7.png and b/core/assets/sprites/sprites_fallback7.png differ diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 191d5a65a5..297c78f82d 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -25,6 +25,8 @@ import java.util.Locale; @SuppressWarnings("unchecked") public class Vars{ + /** Whether to load locales.*/ + public static boolean loadLocales = true; /** IO buffer size. */ public static final int bufferSize = 8192; /** global charset */ @@ -38,9 +40,10 @@ public class Vars{ /** URL for Github API for releases */ public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases"; /** URL for Github API for contributors */ + //TODO remove and replace with a manually updated list public static final String contributorsURL = "https://api.github.com/repos/Anuken/Mindustry/contributors"; /** URL for sending crash reports to */ - public static final String crashReportURL = "http://mindustry.us.to/report"; + public static final String crashReportURL = "http://mins.us.to/report"; /** maximum distance between mine and core that supports automatic transferring */ public static final float mineTransferRange = 220f; /** team of the player by default */ @@ -104,8 +107,10 @@ public class Vars{ public static FileHandle customMapDirectory; /** data subdirectory used for saves */ public static FileHandle saveDirectory; + /** old map file extension, for conversion */ + public static final String oldMapExtension = "mmap"; /** map file extension */ - public static final String mapExtension = "mmap"; + public static final String mapExtension = "msav"; /** save file extension */ public static final String saveExtension = "msav"; @@ -141,19 +146,22 @@ public class Vars{ public static void init(){ Serialization.init(); - //load locales - String[] stra = Core.files.internal("locales").readString().split("\n"); - locales = new Locale[stra.length]; - for(int i = 0; i < locales.length; i++){ - String code = stra[i]; - if(code.contains("_")){ - locales[i] = new Locale(code.split("_")[0], code.split("_")[1]); - }else{ - locales[i] = new Locale(code); + if(loadLocales){ + //load locales + String[] stra = Core.files.internal("locales").readString().split("\n"); + locales = new Locale[stra.length]; + for(int i = 0; i < locales.length; i++){ + String code = stra[i]; + if(code.contains("_")){ + locales[i] = new Locale(code.split("_")[0], code.split("_")[1]); + }else{ + locales[i] = new Locale(code); + } } + + Arrays.sort(locales, Structs.comparing(l -> l.getDisplayName(l), String.CASE_INSENSITIVE_ORDER)); } - Arrays.sort(locales, Structs.comparing(l -> l.getDisplayName(l), String.CASE_INSENSITIVE_ORDER)); Version.init(); content = new ContentLoader(); diff --git a/core/src/io/anuke/mindustry/ai/BlockIndexer.java b/core/src/io/anuke/mindustry/ai/BlockIndexer.java index f3cba3a173..bcaca39b7f 100644 --- a/core/src/io/anuke/mindustry/ai/BlockIndexer.java +++ b/core/src/io/anuke/mindustry/ai/BlockIndexer.java @@ -171,12 +171,10 @@ public class BlockIndexer{ for(int tx = rx * structQuadrantSize; tx < (rx + 1) * structQuadrantSize && tx < world.width(); tx++){ for(int ty = ry * structQuadrantSize; ty < (ry + 1) * structQuadrantSize && ty < world.height(); ty++){ - Tile other = world.tile(tx, ty); + Tile other = world.ltile(tx, ty); if(other == null) continue; - other = other.target(); - if(other.entity == null || other.getTeam() != team || !pred.test(other) || !other.block().targetable) continue; @@ -293,7 +291,7 @@ public class BlockIndexer{ outer: for(int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++){ for(int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++){ - Tile result = world.tile(x, y).target(); + Tile result = world.ltile(x, y); //when a targetable block is found, mark this quadrant as occupied and stop searching if(result.entity != null && result.getTeam() == data.team){ structQuadrants[data.team.ordinal()].set(index); diff --git a/core/src/io/anuke/mindustry/ai/Pathfinder.java b/core/src/io/anuke/mindustry/ai/Pathfinder.java index 8ee044e32b..85890bba56 100644 --- a/core/src/io/anuke/mindustry/ai/Pathfinder.java +++ b/core/src/io/anuke/mindustry/ai/Pathfinder.java @@ -83,7 +83,7 @@ public class Pathfinder{ } private boolean passable(Tile tile, Team team){ - return (!tile.solid()) || (tile.breakable() && (tile.target().getTeam() != team)); + return (!tile.solid()) || (tile.breakable() && (tile.getTeam() != team)); } /** diff --git a/core/src/io/anuke/mindustry/ai/WaveSpawner.java b/core/src/io/anuke/mindustry/ai/WaveSpawner.java index 3a1eb65fce..1be663b807 100644 --- a/core/src/io/anuke/mindustry/ai/WaveSpawner.java +++ b/core/src/io/anuke/mindustry/ai/WaveSpawner.java @@ -2,10 +2,10 @@ package io.anuke.mindustry.ai; import io.anuke.arc.Events; import io.anuke.arc.collection.Array; -import io.anuke.arc.collection.IntArray; import io.anuke.arc.math.Angles; import io.anuke.arc.math.Mathf; -import io.anuke.arc.util.*; +import io.anuke.arc.util.Time; +import io.anuke.arc.util.Tmp; import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.content.Fx; import io.anuke.mindustry.entities.Damage; @@ -14,48 +14,25 @@ import io.anuke.mindustry.entities.type.BaseUnit; import io.anuke.mindustry.game.EventType.WorldLoadEvent; import io.anuke.mindustry.game.SpawnGroup; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.world.Pos; - -import java.io.*; import static io.anuke.mindustry.Vars.*; public class WaveSpawner{ private Array flySpawns = new Array<>(); private Array groundSpawns = new Array<>(); - private IntArray loadedSpawns = new IntArray(); private boolean spawning = false; public WaveSpawner(){ Events.on(WorldLoadEvent.class, e -> reset()); } - public void write(DataOutput stream) throws IOException{ - stream.writeInt(groundSpawns.size); - for(GroundSpawn spawn : groundSpawns){ - stream.writeInt(Pos.get(spawn.x, spawn.y)); - } - } - - public void read(DataInput stream) throws IOException{ - flySpawns.clear(); - groundSpawns.clear(); - loadedSpawns.clear(); - - int amount = stream.readInt(); - - for(int i = 0; i < amount; i++){ - loadedSpawns.add(stream.readInt()); - } - } - public int countSpawns(){ return groundSpawns.size; } /** @return true if the player is near a ground spawn point. */ public boolean playerNear(){ - return groundSpawns.count(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius) > 0; + return groundSpawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius); } public void spawnEnemies(){ @@ -117,21 +94,11 @@ public class WaveSpawner{ for(int x = 0; x < world.width(); x++){ for(int y = 0; y < world.height(); y++){ - if(world.tile(x, y).block() == Blocks.spawn){ + if(world.tile(x, y).overlay() == Blocks.spawn){ addSpawns(x, y); - - //hide spawnpoints, they have served their purpose - world.tile(x, y).setBlock(Blocks.air); } } } - - for(int i = 0; i < loadedSpawns.size; i++){ - int pos = loadedSpawns.get(i); - addSpawns(Pos.x(pos), Pos.y(pos)); - } - - loadedSpawns.clear(); } private void addSpawns(int x, int y){ diff --git a/core/src/io/anuke/mindustry/content/Blocks.java b/core/src/io/anuke/mindustry/content/Blocks.java index d95fa55176..db737c9608 100644 --- a/core/src/io/anuke/mindustry/content/Blocks.java +++ b/core/src/io/anuke/mindustry/content/Blocks.java @@ -30,7 +30,7 @@ public class Blocks implements ContentList{ public static Block //environment - air, part, spawn, deepwater, water, taintedWater, tar, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater, + air, spawn, deepwater, water, taintedWater, tar, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater, holostone, rocks, sporerocks, icerocks, cliffs, sporePine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster, iceSnow, sandWater, darksandWater, duneRocks, sandRocks, moss, sporeMoss, shale, shaleRocks, shaleBoulder, grass, salt, metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks, rock, snowrock, saltRocks, @@ -88,15 +88,9 @@ public class Blocks implements ContentList{ hasShadow = false; } - public void draw(Tile tile){ - } - - public void load(){ - } - - public void init(){ - } - + public void draw(Tile tile){} + public void load(){} + public void init(){} public boolean isHidden(){ return true; } @@ -109,14 +103,24 @@ public class Blocks implements ContentList{ } }; - part = new BlockPart(); + //create special blockpart variants + for(int dx = 0; dx < BlockPart.maxSize; dx++){ + for(int dy = 0; dy < BlockPart.maxSize; dy++){ + int fx = dx - BlockPart.maxSize/2, fy = dy - BlockPart.maxSize/2; + if(fx != 0 || fy != 0){ + new BlockPart(fx, fy); + } + } + } - spawn = new Block("spawn"); + spawn = new OverlayFloor("spawn"){ + public void draw(Tile tile){} + }; - //Registers build blocks from size 1-6 + //Registers build blocks //no reference is needed here since they can be looked up by name later - for(int i = 1; i <= 6; i++){ - new BuildBlock("build" + i); + for(int i = 1; i <= BuildBlock.maxSize; i++){ + new BuildBlock(i); } deepwater = new Floor("deepwater"){{ @@ -556,7 +560,7 @@ public class Blocks implements ContentList{ drawer = tile -> { LiquidModule mod = tile.entity.liquids; - int rotation = rotate ? tile.getRotation() * 90 : 0; + int rotation = rotate ? tile.rotation() * 90 : 0; Draw.rect(reg(bottomRegion), tile.drawx(), tile.drawy(), rotation); @@ -1105,11 +1109,11 @@ public class Blocks implements ContentList{ requirements(Category.power, ItemStack.with(Items.lead, 1000, Items.silicon, 600, Items.graphite, 800, Items.thorium, 200, Items.surgealloy, 500, Items.metaglass, 500)); size = 4; health = 900; - powerProduction = 110f; - itemDuration = 60f; + powerProduction = 130f; + itemDuration = 90f; consumes.power(25f); consumes.item(Items.blastCompound); - consumes.liquid(Liquids.cryofluid, 0.26f); + consumes.liquid(Liquids.cryofluid, 0.25f); }}; //endregion power @@ -1147,7 +1151,7 @@ public class Blocks implements ContentList{ }}; blastDrill = new Drill("blast-drill"){{ - requirements(Category.production, ItemStack.with(Items.copper, 130, Items.silicon, 120, Items.titanium, 100, Items.thorium, 60)); + requirements(Category.production, ItemStack.with(Items.copper, 130, Items.silicon, 120, Items.titanium, 100, Items.thorium, 100)); drillTime = 200; size = 4; drawRim = true; diff --git a/core/src/io/anuke/mindustry/content/Bullets.java b/core/src/io/anuke/mindustry/content/Bullets.java index 864e49460c..9119ed9575 100644 --- a/core/src/io/anuke/mindustry/content/Bullets.java +++ b/core/src/io/anuke/mindustry/content/Bullets.java @@ -398,9 +398,9 @@ public class Bullets implements ContentList{ @Override public void hitTile(Bullet b, Tile tile){ super.hit(b); - tile = tile.target(); + tile = tile.link(); - if(tile != null && tile.getTeam() == b.getTeam() && !(tile.block() instanceof BuildBlock)){ + if(tile.getTeam() == b.getTeam() && !(tile.block() instanceof BuildBlock)){ Effects.effect(Fx.healBlockFull, Pal.heal, tile.drawx(), tile.drawy(), tile.block().size); tile.entity.healBy(healPercent / 100f * tile.entity.maxHealth()); } @@ -424,7 +424,6 @@ public class Bullets implements ContentList{ @Override public void draw(Bullet b){ - //TODO add color to the bullet depending on the color of the flame it came from Draw.color(Pal.lightFlame, Pal.darkFlame, Color.GRAY, b.fin()); Fill.circle(b.x, b.y, 3f * b.fout()); Draw.reset(); diff --git a/core/src/io/anuke/mindustry/content/Mechs.java b/core/src/io/anuke/mindustry/content/Mechs.java index 050ff47c16..d19aaae610 100644 --- a/core/src/io/anuke/mindustry/content/Mechs.java +++ b/core/src/io/anuke/mindustry/content/Mechs.java @@ -9,6 +9,7 @@ import io.anuke.arc.math.Mathf; import io.anuke.arc.util.Time; import io.anuke.mindustry.entities.Effects; import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.entities.bullet.BombBulletType; import io.anuke.mindustry.entities.effect.Lightning; import io.anuke.mindustry.entities.type.Player; import io.anuke.mindustry.game.ContentList; @@ -307,11 +308,11 @@ public class Mechs implements ContentList{ trident = new Mech("trident-ship", true){ { drillPower = 2; - speed = 0.14f; + speed = 0.15f; drag = 0.034f; mass = 2.5f; turnCursor = false; - health = 220f; + health = 250f; itemCapacity = 30; engineColor = Color.valueOf("84f491"); cellTrnsY = 1f; @@ -319,14 +320,22 @@ public class Mechs implements ContentList{ weapon = new Weapon("bomber"){{ length = 0f; width = 2f; - reload = 8f; + reload = 25f; shots = 2; + shotDelay = 1f; + shots = 8; roundrobin = true; ejectEffect = Fx.none; velocityRnd = 1f; - inaccuracy = 40f; + inaccuracy = 20f; ignoreRotation = true; - bullet = Bullets.bombExplosive; + bullet = new BombBulletType(14f, 25f, "shell"){{ + bulletWidth = 10f; + bulletHeight = 14f; + hitEffect = Fx.flakExplosion; + shootEffect = Fx.none; + smokeEffect = Fx.none; + }}; }}; } diff --git a/core/src/io/anuke/mindustry/content/StatusEffects.java b/core/src/io/anuke/mindustry/content/StatusEffects.java index f10340df75..a869b0cbe9 100644 --- a/core/src/io/anuke/mindustry/content/StatusEffects.java +++ b/core/src/io/anuke/mindustry/content/StatusEffects.java @@ -75,8 +75,6 @@ public class StatusEffects implements ContentList{ armorMultiplier = 3f; damageMultiplier = 3f; speedMultiplier = 1.1f; - //TODO custom effect - //effect = Fx.overdriven; }}; shocked = new StatusEffect(); diff --git a/core/src/io/anuke/mindustry/content/Zones.java b/core/src/io/anuke/mindustry/content/Zones.java index a85fcd2e07..e8e3933150 100644 --- a/core/src/io/anuke/mindustry/content/Zones.java +++ b/core/src/io/anuke/mindustry/content/Zones.java @@ -154,20 +154,6 @@ public class Zones implements ContentList{ }}; }}; - /* - crags = new Zone("crags", new MapGenerator("groundZero", 1)){{ //TODO implement - baseLaunchCost = ItemStack.with(Items.copper, 300); - startingItems = ItemStack.with(Items.copper, 200); - conditionWave = 15; - zoneRequirements = new Zone[]{frozenForest}; - blockRequirements = new Block[]{Blocks.copperWall}; - rules = () -> new Rules(){{ - waves = true;] - waveTimer = true; - waveSpacing = 60 * 80; - }}; - }};*/ - stainedMountains = new Zone("stainedMountains", new MapGenerator("stainedMountains", 2) .dist(0f, false) .decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{ diff --git a/core/src/io/anuke/mindustry/core/ContentLoader.java b/core/src/io/anuke/mindustry/core/ContentLoader.java index 72df701ddc..f84e770ca3 100644 --- a/core/src/io/anuke/mindustry/core/ContentLoader.java +++ b/core/src/io/anuke/mindustry/core/ContentLoader.java @@ -90,17 +90,12 @@ public class ContentLoader{ for(Array arr : contentMap){ for(int i = 0; i < arr.size; i++){ int id = arr.get(i).id; - if(id < 0) id += 256; if(id != i){ throw new IllegalArgumentException("Out-of-order IDs for content '" + arr.get(i) + "' (expected " + i + " but got " + id + ")"); } } } - if(blocks().size >= 256){ - throw new ImpendingDoomException("THE TIME HAS COME. More than 256 blocks have been created."); - } - if(verbose){ Log.info("--- CONTENT INFO ---"); for(int k = 0; k < contentMap.length; k++){ @@ -129,7 +124,7 @@ public class ContentLoader{ /** Loads block colors. */ public void loadColors(){ Pixmap pixmap = new Pixmap(files.internal("sprites/block_colors.png")); - for(int i = 0; i < 256; i++){ + for(int i = 0; i < pixmap.getWidth(); i++){ if(blocks().size > i){ int color = pixmap.getPixel(i, 0); @@ -170,10 +165,12 @@ public class ContentLoader{ } public T getByID(ContentType type, int id){ - //offset negative values by 256, as they are probably a product of byte overflow - if(id < 0) id += 256; if(temporaryMapper != null && temporaryMapper[type.ordinal()] != null && temporaryMapper[type.ordinal()].length != 0){ + //-1 = invalid content + if(id < 0){ + return null; + } if(temporaryMapper[type.ordinal()].length <= id || temporaryMapper[type.ordinal()][id] == null){ return getByID(type, 0); //default value is always ID 0 } @@ -243,10 +240,4 @@ public class ContentLoader{ TypeTrait.registerType(Bullet.class, Bullet::new); TypeTrait.registerType(Lightning.class, Lightning::new); } - - private class ImpendingDoomException extends RuntimeException{ - ImpendingDoomException(String s){ - super(s); - } - } } diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index c4ef5ee932..7bfb76aac8 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -18,7 +18,7 @@ import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.input.*; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.*; import io.anuke.mindustry.ui.dialogs.FloatingDialog; import io.anuke.mindustry.world.Tile; @@ -120,7 +120,7 @@ public class Control implements ApplicationListener{ Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y); //the restart dialog can show info for any number of scenarios Call.onGameOver(event.winner); - if(state.rules.zone != -1){ + if(state.rules.zone != null){ //remove zone save on game over if(saves.getZoneSlot() != null){ saves.getZoneSlot().delete(); @@ -201,8 +201,25 @@ public class Control implements ApplicationListener{ public void playMap(Map map, Rules rules){ ui.loadAnd(() -> { logic.reset(); - state.rules = rules; world.loadMap(map); + state.rules = rules; + logic.play(); + }); + } + + public void playZone(Zone zone){ + ui.loadAnd(() -> { + logic.reset(); + world.loadGenerator(zone.generator); + state.rules = zone.rules.get(); + state.rules.zone = zone; + for(Tile core : state.teams.get(defaultTeam).cores){ + for(ItemStack stack : zone.getStartingItems()){ + core.entity.items.add(stack.item, stack.amount); + } + } + state.set(State.playing); + control.saves.zoneSave(); logic.play(); }); } diff --git a/core/src/io/anuke/mindustry/core/GameState.java b/core/src/io/anuke/mindustry/core/GameState.java index 2cedcda4bc..fcf4d6646c 100644 --- a/core/src/io/anuke/mindustry/core/GameState.java +++ b/core/src/io/anuke/mindustry/core/GameState.java @@ -40,6 +40,10 @@ public class GameState{ state = astate; } + public boolean isEditor(){ + return rules.editor; + } + public boolean isPaused(){ return (is(State.paused) && !Net.active()) || (gameOver && !Net.active()); } diff --git a/core/src/io/anuke/mindustry/core/Logic.java b/core/src/io/anuke/mindustry/core/Logic.java index a81d3c2702..191abb61b3 100644 --- a/core/src/io/anuke/mindustry/core/Logic.java +++ b/core/src/io/anuke/mindustry/core/Logic.java @@ -58,12 +58,6 @@ public class Logic implements ApplicationListener{ public void play(){ state.set(State.playing); state.wavetime = state.rules.waveSpacing * 2; //grace period of 2x wave time before game starts - - //sometimes a map has no waves defined, they're defined in the zone rules - if(world.getMap().getWaves() != DefaultWaves.get() || !world.isZone()){ - state.rules.spawns = world.getMap().getWaves(); - } - Events.fire(new PlayEvent()); } @@ -92,7 +86,7 @@ public class Logic implements ApplicationListener{ } private void checkGameOver(){ - if(state.rules.waves && state.teams.get(defaultTeam).cores.size == 0 && !state.gameOver){ + if(!state.rules.attackMode && state.teams.get(defaultTeam).cores.size == 0 && !state.gameOver){ state.gameOver = true; Events.fire(new GameOverEvent(waveTeam)); }else if(state.rules.attackMode){ @@ -165,15 +159,17 @@ public class Logic implements ApplicationListener{ Entities.update(groundEffectGroup); } - for(EntityGroup group : unitGroups){ - Entities.update(group); - } + if(!state.isEditor()){ + for(EntityGroup group : unitGroups){ + Entities.update(group); + } - Entities.update(puddleGroup); - Entities.update(shieldGroup); - Entities.update(bulletGroup); - Entities.update(tileGroup); - Entities.update(fireGroup); + Entities.update(puddleGroup); + Entities.update(shieldGroup); + Entities.update(bulletGroup); + Entities.update(tileGroup); + Entities.update(fireGroup); + } Entities.update(playerGroup); //effect group only contains item transfers in the headless version, update it! @@ -181,19 +177,21 @@ public class Logic implements ApplicationListener{ Entities.update(effectGroup); } - for(EntityGroup group : unitGroups){ - if(group.isEmpty()) continue; + if(!state.isEditor()){ - collisions.collideGroups(bulletGroup, group); + for(EntityGroup group : unitGroups){ + if(group.isEmpty()) continue; + collisions.collideGroups(bulletGroup, group); + } + + collisions.collideGroups(bulletGroup, playerGroup); + collisions.collideGroups(playerGroup, playerGroup); } - collisions.collideGroups(bulletGroup, playerGroup); - collisions.collideGroups(playerGroup, playerGroup); - world.pathfinder.update(); } - if(!Net.client() && !world.isInvalidMap()){ + if(!Net.client() && !world.isInvalidMap() && !state.isEditor()){ checkGameOver(); } } diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 2cdd52ad3b..bf196e37e1 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -20,6 +20,7 @@ import io.anuke.mindustry.entities.type.Player; import io.anuke.mindustry.game.Version; import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.gen.RemoteReadClient; +import io.anuke.mindustry.net.Administration.TraceInfo; import io.anuke.mindustry.net.*; import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.Packets.*; @@ -117,7 +118,7 @@ public class NetClient implements ApplicationListener{ } //called on all clients - @Remote(called = Loc.server, targets = Loc.server) + @Remote(called = Loc.server, targets = Loc.server, variants = Variant.both) public static void sendMessage(String message, String sender, Player playersender){ if(Vars.ui != null){ Vars.ui.chatfrag.addMessage(message, sender); @@ -158,6 +159,13 @@ public class NetClient implements ApplicationListener{ return "[#" + player.color.toString().toUpperCase() + "]" + name; } + @Remote(variants = Variant.one) + public static void onTraceInfo(Player player, TraceInfo info){ + if(player != null){ + ui.traces.show(player, info); + } + } + @Remote(variants = Variant.one, priority = PacketPriority.high) public static void onKick(KickReason reason){ netClient.disconnectQuietly(); diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 605dce9128..461a7a65c9 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -12,8 +12,7 @@ import io.anuke.arc.math.Mathf; import io.anuke.arc.math.geom.Rectangle; import io.anuke.arc.math.geom.Vector2; import io.anuke.arc.util.*; -import io.anuke.arc.util.io.ByteBufferOutput; -import io.anuke.arc.util.io.ReusableByteOutStream; +import io.anuke.arc.util.io.*; import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Entities; @@ -29,6 +28,7 @@ import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.gen.RemoteReadServer; import io.anuke.mindustry.net.*; import io.anuke.mindustry.net.Administration.PlayerInfo; +import io.anuke.mindustry.net.Administration.TraceInfo; import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.world.Tile; @@ -212,7 +212,7 @@ public class NetServer implements ApplicationListener{ public void sendWorldData(Player player, int clientID){ ByteArrayOutputStream stream = new ByteArrayOutputStream(); - DeflaterOutputStream def = new DeflaterOutputStream(stream); + DeflaterOutputStream def = new FastDeflaterOutputStream(stream); NetworkIO.writeWorld(player, def); WorldStream data = new WorldStream(); data.stream = new ByteArrayInputStream(stream.toByteArray()); @@ -289,7 +289,7 @@ public class NetServer implements ApplicationListener{ //auto-skip done requests if(req.breaking && tile.block() == Blocks.air){ continue; - }else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || tile.getRotation() == req.rotation)){ + }else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || tile.rotation() == req.rotation)){ continue; } player.getPlaceQueue().addLast(req); @@ -355,11 +355,11 @@ public class NetServer implements ApplicationListener{ netServer.kick(other.con.id, KickReason.kick); Log.info("&lc{0} has kicked {1}.", player.name, other.name); }else if(action == AdminAction.trace){ - //TODO implement + TraceInfo info = new TraceInfo(other.con.address, other.uuid, other.con.modclient, other.con.mobile); if(player.con != null){ - //Call.onTraceInfo(player.con.id, other.con.trace); + Call.onTraceInfo(player.con.id, other, info); }else{ - //NetClient.onTraceInfo(other.con.trace); + NetClient.onTraceInfo(other, info); } Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name); } @@ -478,7 +478,7 @@ public class NetServer implements ApplicationListener{ sent++; - if(syncStream.position() > maxSnapshotSize){ + if(syncStream.size() > maxSnapshotSize){ dataStream.close(); byte[] syncBytes = syncStream.toByteArray(); Call.onEntitySnapshot(player.con.id, (byte)group.getID(), (short)sent, (short)syncBytes.length, Net.compressSnapshot(syncBytes)); diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 1645d4b70b..90762e9997 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -2,7 +2,6 @@ package io.anuke.mindustry.core; import io.anuke.annotations.Annotations.Nullable; import io.anuke.arc.*; -import io.anuke.arc.collection.Array; import io.anuke.arc.collection.IntArray; import io.anuke.arc.math.Mathf; import io.anuke.arc.math.geom.Geometry; @@ -18,8 +17,9 @@ import io.anuke.mindustry.game.Team; import io.anuke.mindustry.io.MapIO; import io.anuke.mindustry.maps.*; import io.anuke.mindustry.maps.generators.Generator; -import io.anuke.mindustry.type.*; +import io.anuke.mindustry.type.Zone; import io.anuke.mindustry.world.*; +import io.anuke.mindustry.world.blocks.BlockPart; import static io.anuke.mindustry.Vars.*; @@ -28,17 +28,22 @@ public class World implements ApplicationListener{ public final BlockIndexer indexer = new BlockIndexer(); public final WaveSpawner spawner = new WaveSpawner(); public final Pathfinder pathfinder = new Pathfinder(); + public final Context context = new Context(); private Map currentMap; private Tile[][] tiles; - private Array tempTiles = new Array<>(); private boolean generating, invalidMap; public World(){ maps.load(); } + @Override + public void init(){ + maps.loadLegacyMaps(); + } + @Override public void dispose(){ maps.dispose(); @@ -97,6 +102,12 @@ public class World implements ApplicationListener{ return tiles[x][y]; } + public @Nullable Tile ltile(int x, int y){ + Tile tile = tile(x, y); + if(tile == null) return null; + return tile.block().linked(tile); + } + public Tile rawTile(int x, int y){ return tiles[x][y]; } @@ -105,6 +116,10 @@ public class World implements ApplicationListener{ return tile(Math.round(x / tilesize), Math.round(y / tilesize)); } + public @Nullable Tile ltileWorld(float x, float y){ + return ltile(Math.round(x / tilesize), Math.round(y / tilesize)); + } + public int toTile(float coord){ return Math.round(coord / tilesize); } @@ -160,6 +175,8 @@ public class World implements ApplicationListener{ * A WorldLoadEvent will be fire. */ public void endMapLoad(){ + prepareTiles(tiles); + for(int x = 0; x < tiles.length; x++){ for(int y = 0; y < tiles[0].length; y++){ Tile tile = tiles[x][y]; @@ -188,24 +205,7 @@ public class World implements ApplicationListener{ } public Zone getZone(){ - return content.getByID(ContentType.zone, state.rules.zone); - } - - public void playZone(Zone zone){ - ui.loadAnd(() -> { - logic.reset(); - state.rules = zone.rules.get(); - state.rules.zone = zone.id; - loadGenerator(zone.generator); - for(Tile core : state.teams.get(defaultTeam).cores){ - for(ItemStack stack : zone.getStartingItems()){ - core.entity.items.add(stack.item, stack.amount); - } - } - state.set(State.playing); - control.saves.zoneSave(); - logic.play(); - }); + return state.rules.zone; } public void loadGenerator(Generator generator){ @@ -213,24 +213,14 @@ public class World implements ApplicationListener{ createTiles(generator.width, generator.height); generator.generate(tiles); - prepareTiles(tiles); endMapLoad(); } public void loadMap(Map map){ - beginMapLoad(); - this.currentMap = map; try{ - createTiles(map.width, map.height); - for(int x = 0; x < map.width; x++){ - for(int y = 0; y < map.height; y++){ - tiles[x][y] = new Tile(x, y); - } - } - MapIO.readTiles(map, tiles); - prepareTiles(tiles); + MapIO.loadMap(map); }catch(Exception e){ Log.err(e); if(!headless){ @@ -242,7 +232,7 @@ public class World implements ApplicationListener{ return; } - endMapLoad(); + this.currentMap = map; invalidMap = false; @@ -289,16 +279,7 @@ public class World implements ApplicationListener{ } public void removeBlock(Tile tile){ - if(!tile.block().isMultiblock() && !tile.isLinked()){ - tile.setBlock(Blocks.air); - }else{ - Tile target = tile.target(); - Array removals = target.getLinkedTiles(tempTiles); - for(Tile toremove : removals){ - //note that setting a new block automatically unlinks it - if(toremove != null) toremove.setBlock(Blocks.air); - } - } + tile.link().getLinkedTiles(other -> other.setBlock(Blocks.air)); } public void setBlock(Tile tile, Block block, Team team){ @@ -318,8 +299,7 @@ public class World implements ApplicationListener{ if(!(worldx == tile.x && worldy == tile.y)){ Tile toplace = world.tile(worldx, worldy); if(toplace != null){ - toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety)); - toplace.setTeam(team); + toplace.setBlock(BlockPart.get(dx + offsetx, dy + offsety), team); } } } @@ -327,15 +307,6 @@ public class World implements ApplicationListener{ } } - public int transform(int packed, int oldWidth, int oldHeight, int newWidth, int shiftX, int shiftY){ - int x = packed % oldWidth; - int y = packed / oldWidth; - if(!Structs.inBounds(x, y, oldWidth, oldHeight)) return -1; - x += shiftX; - y += shiftY; - return y * newWidth + x; - } - /** * Raycast, but with world coordinates. */ @@ -413,11 +384,6 @@ public class World implements ApplicationListener{ } } - /** Loads raw map tile data into a Tile[][] array, setting up multiblocks, cliffs and ores. */ - void loadTileData(Tile[][] tiles){ - prepareTiles(tiles); - } - public void addDarkness(Tile[][] tiles){ byte[][] dark = new byte[tiles.length][tiles[0].length]; byte[][] writeBuffer = new byte[tiles.length][tiles[0].length]; @@ -456,7 +422,7 @@ public class World implements ApplicationListener{ for(int y = 0; y < tiles[0].length; y++){ Tile tile = tiles[x][y]; if(tile.block().solid && !tile.block().synthetic()){ - tiles[x][y].setRotation(dark[x][y]); + tiles[x][y].rotation(dark[x][y]); } } } @@ -503,25 +469,47 @@ public class World implements ApplicationListener{ if(!(worldx == x && worldy == y)){ Tile toplace = world.tile(worldx, worldy); if(toplace != null){ - toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety)); - toplace.setTeam(team); + toplace.setBlock(BlockPart.get(dx + offsetx, dy + offsety), team); } } } } } - - //update cliffs, occlusion data - for(int x = 0; x < tiles.length; x++){ - for(int y = 0; y < tiles[0].length; y++){ - Tile tile = tiles[x][y]; - - tile.updateOcclusion(); - } - } } public interface Raycaster{ boolean accept(int x, int y); } + + class Context implements WorldContext{ + @Override + public Tile tile(int x, int y){ + return tiles[x][y]; + } + + @Override + public void resize(int width, int height){ + createTiles(width, height); + } + + @Override + public Tile create(int x, int y, int floorID, int overlayID, int wallID){ + return (tiles[x][y] = new Tile(x, y, floorID, overlayID, wallID)); + } + + @Override + public boolean isGenerating(){ + return World.this.isGenerating(); + } + + @Override + public void begin(){ + beginMapLoad(); + } + + @Override + public void end(){ + endMapLoad(); + } + } } diff --git a/core/src/io/anuke/mindustry/editor/DrawOperation.java b/core/src/io/anuke/mindustry/editor/DrawOperation.java index ea26df6ea4..ee71b7e2b9 100755 --- a/core/src/io/anuke/mindustry/editor/DrawOperation.java +++ b/core/src/io/anuke/mindustry/editor/DrawOperation.java @@ -9,6 +9,7 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.Floor; import static io.anuke.mindustry.Vars.content; +import static io.anuke.mindustry.Vars.world; public class DrawOperation{ private LongArray array = new LongArray(); @@ -24,32 +25,46 @@ public class DrawOperation{ public void undo(MapEditor editor){ for(int i = array.size - 1; i >= 0; i--){ long l = array.get(i); - set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.from(l)); + array.set(i, TileOp.get(TileOp.x(l), TileOp.y(l), TileOp.type(l), get(editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l)))); + set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.value(l)); } } public void redo(MapEditor editor){ for(int i = 0; i < array.size; i++){ long l = array.get(i); - set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.to(l)); + array.set(i, TileOp.get(TileOp.x(l), TileOp.y(l), TileOp.type(l), get(editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l)))); + set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.value(l)); } } - void set(MapEditor editor, Tile tile, byte type, byte to){ + short get(Tile tile, byte type){ + if(type == OpType.floor.ordinal()){ + return tile.floorID(); + }else if(type == OpType.block.ordinal()){ + return tile.blockID(); + }else if(type == OpType.rotation.ordinal()){ + return tile.rotation(); + }else if(type == OpType.team.ordinal()){ + return tile.getTeamID(); + }else if(type == OpType.overlay.ordinal()){ + return tile.overlayID(); + } + throw new IllegalArgumentException("Invalid type."); + } + + void set(MapEditor editor, Tile tile, byte type, short to){ editor.load(() -> { if(type == OpType.floor.ordinal()){ tile.setFloor((Floor)content.block(to)); }else if(type == OpType.block.ordinal()){ Block block = content.block(to); - tile.setBlock(block); - if(block.isMultiblock()){ - editor.updateLinks(block, tile.x, tile.y); - } + world.setBlock(tile, block, tile.getTeam(), tile.rotation()); }else if(type == OpType.rotation.ordinal()){ - tile.setRotation(to); + tile.rotation(to); }else if(type == OpType.team.ordinal()){ tile.setTeam(Team.all[to]); - }else if(type == OpType.ore.ordinal()){ + }else if(type == OpType.overlay.ordinal()){ tile.setOverlayID(to); } }); @@ -61,8 +76,7 @@ public class DrawOperation{ short x; short y; byte type; - byte from; - byte to; + short value; } public enum OpType{ @@ -70,6 +84,6 @@ public class DrawOperation{ block, rotation, team, - ore + overlay } } diff --git a/core/src/io/anuke/mindustry/editor/EditorTile.java b/core/src/io/anuke/mindustry/editor/EditorTile.java index 1af8d2ffe8..e7e694d75b 100644 --- a/core/src/io/anuke/mindustry/editor/EditorTile.java +++ b/core/src/io/anuke/mindustry/editor/EditorTile.java @@ -1,6 +1,7 @@ package io.anuke.mindustry.editor; import io.anuke.mindustry.content.Blocks; +import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.editor.DrawOperation.OpType; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.gen.TileOp; @@ -9,21 +10,23 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.modules.*; +import static io.anuke.mindustry.Vars.state; import static io.anuke.mindustry.Vars.ui; +//TODO somehow remove or replace this class with a more flexible solution public class EditorTile extends Tile{ - public EditorTile(int x, int y, byte floor, byte wall){ - super(x, y, floor, wall); - } - - @Override - public Team getTeam(){ - return Team.all[getTeamID()]; + public EditorTile(int x, int y, int floor, int overlay, int wall){ + super(x, y, floor, overlay, wall); } @Override public void setFloor(Floor type){ + if(state.is(State.playing)){ + super.setFloor(type); + return; + } + if(type instanceof OverlayFloor){ //don't place on liquids if(!floor.isLiquid){ @@ -32,63 +35,91 @@ public class EditorTile extends Tile{ return; } - Block previous = floor(); - Block ore = overlay(); - if(previous == type && ore == Blocks.air) return; + if(floor == type && overlayID() == 0) return; + if(overlayID() != 0) op(OpType.overlay, overlayID()); + if(floor != type) op(OpType.floor, floor.id); super.setFloor(type); - //ore may get nullified so make sure to save edits - if(overlay() != ore){ - op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), ore.id, overlay().id)); - } - if(previous != type){ - op(TileOp.get(x, y, (byte)OpType.floor.ordinal(), previous.id, type.id)); - } } @Override public void setBlock(Block type){ - Block previous = block; - byte pteam = getTeamID(); - if(previous == type) return; - super.setBlock(type); - if(pteam != getTeamID()){ - op(TileOp.get(x, y, (byte)OpType.team.ordinal(), pteam, getTeamID())); + if(state.is(State.playing)){ + super.setBlock(type); + return; } - op(TileOp.get(x, y, (byte)OpType.block.ordinal(), previous.id, type.id)); + if(block == type) return; + op(OpType.block, block.id); + if(rotation != 0) op(OpType.rotation, rotation); + if(team != 0) op(OpType.team, team); + super.setBlock(type); + } + + @Override + public void setBlock(Block type, Team team, int rotation){ + if(state.is(State.playing)){ + super.setBlock(type, team, rotation); + return; + } + + setBlock(type); + setTeam(team); + rotation(rotation); } @Override public void setTeam(Team team){ - byte previous = getTeamID(); - if(previous == team.ordinal()) return; + if(state.is(State.playing)){ + super.setTeam(team); + return; + } + + if(getTeamID() == team.ordinal()) return; + op(OpType.team, getTeamID()); super.setTeam(team); - op(TileOp.get(x, y, (byte)OpType.team.ordinal(), previous, (byte)team.ordinal())); } @Override - public void setRotation(byte rotation){ - byte previous = getRotation(); - if(previous == rotation) return; - super.setRotation(rotation); - op(TileOp.get(x, y, (byte)OpType.rotation.ordinal(), previous, rotation)); + public void rotation(int rotation){ + if(state.is(State.playing)){ + super.rotation(rotation); + return; + } + + if(rotation == rotation()) return; + op(OpType.rotation, rotation()); + super.rotation(rotation); } @Override - public void setOverlayID(byte ore){ - byte previous = getOverlayID(); - if(previous == ore) return; - super.setOverlayID(ore); - op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), previous, ore)); + public void setOverlayID(short overlay){ + if(state.is(State.playing)){ + super.setOverlayID(overlay); + return; + } + + if(overlayID() == overlay) return; + op(OpType.overlay, overlay); + super.setOverlayID(overlay); } @Override protected void preChanged(){ + if(state.is(State.playing)){ + super.preChanged(); + return; + } + super.setTeam(Team.none); } @Override protected void changed(){ + if(state.is(State.playing)){ + super.changed(); + return; + } + entity = null; if(block == null){ @@ -102,8 +133,7 @@ public class EditorTile extends Tile{ Block block = block(); if(block.hasEntity()){ - entity = block.newEntity(); - entity.health = block.health; + entity = block.newEntity().init(this, false); entity.cons = new ConsumeModule(entity); if(block.hasItems) entity.items = new ItemModule(); if(block.hasLiquids) entity.liquids = new LiquidModule(); @@ -111,7 +141,7 @@ public class EditorTile extends Tile{ } } - private static void op(long op){ - ui.editor.editor.addTileOp(op); + private void op(OpType type, short value){ + ui.editor.editor.addTileOp(TileOp.get(x, y, (byte)type.ordinal(), value)); } } diff --git a/core/src/io/anuke/mindustry/editor/EditorTool.java b/core/src/io/anuke/mindustry/editor/EditorTool.java index b4cb882ce2..41cab43717 100644 --- a/core/src/io/anuke/mindustry/editor/EditorTool.java +++ b/core/src/io/anuke/mindustry/editor/EditorTool.java @@ -15,21 +15,7 @@ public enum EditorTool{ public void touched(MapEditor editor, int x, int y){ if(!Structs.inBounds(x, y, editor.width(), editor.height())) return; - Tile tile = editor.tile(x, y); - - byte link = tile.getLinkByte(); - - if(tile.isLinked()){ - x -= (Pack.leftByte(link) - 8); - y -= (Pack.rightByte(link) - 8); - - tile = editor.tile(x, y); - } - - //do not. - if(tile.isLinked()){ - return; - } + Tile tile = editor.tile(x, y).link(); editor.drawBlock = tile.block() == Blocks.air ? tile.overlay() == Blocks.air ? tile.floor() : tile.overlay() : tile.block(); } @@ -87,7 +73,7 @@ public enum EditorTool{ Block draw = editor.drawBlock; dest = draw instanceof OverlayFloor ? tile.overlay() : isfloor ? floor : block; - if(dest == draw || block == Blocks.part || block.isMultiblock()){ + if(dest == draw || block instanceof BlockPart || block.isMultiblock()){ return; } @@ -116,7 +102,7 @@ public enum EditorTool{ } if(draw.rotate){ - write.setRotation((byte)editor.rotation); + write.rotation((byte)editor.rotation); } }; diff --git a/core/src/io/anuke/mindustry/editor/MapEditor.java b/core/src/io/anuke/mindustry/editor/MapEditor.java index e74128be2b..73cbae12a1 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditor.java +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -1,27 +1,28 @@ package io.anuke.mindustry.editor; -import io.anuke.arc.collection.ObjectMap; +import io.anuke.arc.collection.StringMap; import io.anuke.arc.files.FileHandle; +import io.anuke.arc.graphics.Pixmap; import io.anuke.arc.math.Mathf; -import io.anuke.arc.util.Pack; import io.anuke.arc.util.Structs; import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.gen.TileOp; +import io.anuke.mindustry.io.LegacyMapIO; import io.anuke.mindustry.io.MapIO; import io.anuke.mindustry.maps.Map; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.*; +import io.anuke.mindustry.world.blocks.BlockPart; import io.anuke.mindustry.world.blocks.Floor; -import java.io.IOException; +import static io.anuke.mindustry.Vars.world; public class MapEditor{ public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15, 20}; - private ObjectMap tags = new ObjectMap<>(); + private final Context context = new Context(); + private StringMap tags = new StringMap(); private MapRenderer renderer = new MapRenderer(this); - private Tile[][] tiles; private OperationStack stack = new OperationStack(); private DrawOperation currentOp; @@ -32,7 +33,7 @@ public class MapEditor{ public Block drawBlock = Blocks.stone; public Team drawTeam = Team.blue; - public ObjectMap getTags(){ + public StringMap getTags(){ return tags; } @@ -40,39 +41,39 @@ public class MapEditor{ reset(); loading = true; - tiles = createTiles(width, height); + createTiles(width, height); renderer.resize(width(), height()); loading = false; } - public void beginEdit(Map map) throws IOException{ + public void beginEdit(Map map){ reset(); loading = true; - tiles = createTiles(map.width, map.height); tags.putAll(map.tags); - MapIO.readTiles(map, tiles); + MapIO.loadMap(map, context); checkLinkedTiles(); renderer.resize(width(), height()); loading = false; } - public void beginEdit(Tile[][] tiles){ + public void beginEdit(Pixmap pixmap){ reset(); - this.tiles = tiles; - checkLinkedTiles(); + createTiles(pixmap.getWidth(), pixmap.getHeight()); + load(() -> LegacyMapIO.readPixmap(pixmap, tiles())); renderer.resize(width(), height()); } //adds missing blockparts public void checkLinkedTiles(){ + Tile[][] tiles = world.getTiles(); + //clear block parts first for(int x = 0; x < width(); x++){ for(int y = 0; y < height(); y++){ - if(tiles[x][y].block() == Blocks.part){ + if(tiles[x][y].block() instanceof BlockPart){ tiles[x][y].setBlock(Blocks.air); - tiles[x][y].setLinkByte((byte)0); } } } @@ -80,22 +81,8 @@ public class MapEditor{ //set up missing blockparts for(int x = 0; x < width(); x++){ for(int y = 0; y < height(); y++){ - Block drawBlock = tiles[x][y].block(); - if(drawBlock.isMultiblock()){ - int offsetx = -(drawBlock.size - 1) / 2; - int offsety = -(drawBlock.size - 1) / 2; - for(int dx = 0; dx < drawBlock.size; dx++){ - for(int dy = 0; dy < drawBlock.size; dy++){ - int worldx = dx + offsetx + x; - int worldy = dy + offsety + y; - - if(Structs.inBounds(worldx, worldy, width(), height()) && !(dx + offsetx == 0 && dy + offsety == 0)){ - Tile tile = tiles[worldx][worldy]; - tile.setBlock(Blocks.part); - tile.setLinkByte(Pack.byteByte((byte)(dx + offsetx + 8), (byte)(dy + offsety + 8))); - } - } - } + if(tiles[x][y].block().isMultiblock()){ + world.setBlock(tiles[x][y], tiles[x][y].block(), tiles[x][y].getTeam()); } } } @@ -108,63 +95,41 @@ public class MapEditor{ } /** Creates a 2-D array of EditorTiles with stone as the floor block. */ - public Tile[][] createTiles(int width, int height){ - tiles = new Tile[width][height]; + private void createTiles(int width, int height){ + Tile[][] tiles = world.createTiles(width, height); for(int x = 0; x < width; x++){ for(int y = 0; y < height; y++){ - tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (byte)0); + tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0); } } - return tiles; } public Map createMap(FileHandle file){ - return new Map(file, width(), height(), new ObjectMap<>(tags), true); + return new Map(file, width(), height(), new StringMap(tags), true); } private void reset(){ clearOp(); brushSize = 1; drawBlock = Blocks.stone; - tags = new ObjectMap<>(); + tags = new StringMap(); } public Tile[][] tiles(){ - return tiles; + return world.getTiles(); } public Tile tile(int x, int y){ - return tiles[x][y]; + return world.rawTile(x, y); } public int width(){ - return tiles.length; + return world.width(); } public int height(){ - return tiles[0].length; - } - - public void updateLinks(Block block, int x, int y){ - int offsetx = -(block.size - 1) / 2; - int offsety = -(block.size - 1) / 2; - - for(int dx = 0; dx < block.size; dx++){ - for(int dy = 0; dy < block.size; dy++){ - int worldx = dx + offsetx + x; - int worldy = dy + offsety + y; - - if(Structs.inBounds(worldx, worldy, width(), height())){ - Tile tile = tiles[worldx][worldy]; - - if(!(worldx == x && worldy == y)){ - tile.setBlock(Blocks.part); - tile.setLinkByte(Pack.byteByte((byte)(dx + offsetx + 8), (byte)(dy + offsety + 8))); - } - } - } - } + return world.height(); } public void draw(int x, int y, boolean paint){ @@ -177,45 +142,36 @@ public class MapEditor{ public void draw(int x, int y, boolean paint, Block drawBlock, double chance){ boolean isfloor = drawBlock instanceof Floor && drawBlock != Blocks.air; + Tile[][] tiles = world.getTiles(); if(drawBlock.isMultiblock()){ - x = Mathf.clamp(x, (drawBlock.size - 1) / 2, width() - drawBlock.size / 2 - 1); y = Mathf.clamp(y, (drawBlock.size - 1) / 2, height() - drawBlock.size / 2 - 1); int offsetx = -(drawBlock.size - 1) / 2; int offsety = -(drawBlock.size - 1) / 2; - for(int i = 0; i < 2; i++){ - for(int dx = 0; dx < drawBlock.size; dx++){ - for(int dy = 0; dy < drawBlock.size; dy++){ - int worldx = dx + offsetx + x; - int worldy = dy + offsety + y; + for(int dx = 0; dx < drawBlock.size; dx++){ + for(int dy = 0; dy < drawBlock.size; dy++){ + int worldx = dx + offsetx + x; + int worldy = dy + offsety + y; - if(Structs.inBounds(worldx, worldy, width(), height())){ - Tile tile = tiles[worldx][worldy]; + if(Structs.inBounds(worldx, worldy, width(), height())){ + Tile tile = tiles[worldx][worldy]; - if(i == 1){ - tile.setBlock(Blocks.part); - tile.setLinkByte(Pack.byteByte((byte)(dx + offsetx + 8), (byte)(dy + offsety + 8))); - }else{ - byte link = tile.getLinkByte(); - Block block = tile.block(); + Block block = tile.block(); - if(link != 0){ - removeLinked(worldx - (Pack.leftByte(link) - 8), worldy - (Pack.rightByte(link) - 8)); - }else if(block.isMultiblock()){ - removeLinked(worldx, worldy); - } - } + //bail out if there's anything blocking the way + if(block.isMultiblock() || block instanceof BlockPart){ + return; } + + renderer.updatePoint(worldx, worldy); } } } - Tile tile = tiles[x][y]; - tile.setBlock(drawBlock); - tile.setTeam(drawTeam); + world.setBlock(tiles[x][y], drawBlock, drawTeam); }else{ for(int rx = -brushSize; rx <= brushSize; rx++){ for(int ry = -brushSize; ry <= brushSize; ry++){ @@ -228,14 +184,8 @@ public class MapEditor{ Tile tile = tiles[wx][wy]; - if(!isfloor){ - byte link = tile.getLinkByte(); - - if(tile.block().isMultiblock()){ - removeLinked(wx, wy); - }else if(link != 0 && tiles[wx][wy].block() == Blocks.part){ - removeLinked(wx - (Pack.leftByte(link) - 8), wy - (Pack.rightByte(link) - 8)); - } + if(!isfloor && (tile.isLinked() || tile.block().isMultiblock())){ + world.removeBlock(tile.link()); } if(isfloor){ @@ -246,7 +196,7 @@ public class MapEditor{ tile.setTeam(drawTeam); } if(drawBlock.rotate){ - tile.setRotation((byte)rotation); + tile.rotation((byte)rotation); } } } @@ -255,22 +205,6 @@ public class MapEditor{ } } - private void removeLinked(int x, int y){ - Block block = tiles[x][y].block(); - - int offsetx = -(block.size - 1) / 2; - int offsety = -(block.size - 1) / 2; - for(int dx = 0; dx < block.size; dx++){ - for(int dy = 0; dy < block.size; dy++){ - int worldx = x + dx + offsetx, worldy = y + dy + offsety; - if(Structs.inBounds(worldx, worldy, width(), height())){ - tiles[worldx][worldy].setTeam(Team.none); - tiles[worldx][worldy].setBlock(Blocks.air); - } - } - } - } - public MapRenderer renderer(){ return renderer; } @@ -278,11 +212,11 @@ public class MapEditor{ public void resize(int width, int height){ clearOp(); - Tile[][] previous = tiles; + Tile[][] previous = world.getTiles(); int offsetX = -(width - width()) / 2, offsetY = -(height - height()) / 2; loading = true; - tiles = new Tile[width][height]; + Tile[][] tiles = world.createTiles(width, height); for(int x = 0; x < width; x++){ for(int y = 0; y < height; y++){ int px = offsetX + x, py = offsetY + y; @@ -291,7 +225,7 @@ public class MapEditor{ tiles[x][y].x = (short)x; tiles[x][y].y = (short)y; }else{ - tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (byte)0); + tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0); } } } @@ -338,4 +272,36 @@ public class MapEditor{ renderer.updatePoint(TileOp.x(data), TileOp.y(data)); } + + class Context implements WorldContext{ + @Override + public Tile tile(int x, int y){ + return world.tile(x, y); + } + + @Override + public void resize(int width, int height){ + world.createTiles(width, height); + } + + @Override + public Tile create(int x, int y, int floorID, int overlayID, int wallID){ + return (tiles()[x][y] = new EditorTile(x, y, floorID, overlayID, wallID)); + } + + @Override + public boolean isGenerating(){ + return world.isGenerating(); + } + + @Override + public void begin(){ + world.beginMapLoad(); + } + + @Override + public void end(){ + world.endMapLoad(); + } + } } \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index ef7785e102..00cec9c417 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.editor; import io.anuke.arc.Core; import io.anuke.arc.collection.Array; +import io.anuke.arc.collection.StringMap; import io.anuke.arc.files.FileHandle; import io.anuke.arc.function.Consumer; import io.anuke.arc.graphics.Color; @@ -17,8 +18,10 @@ import io.anuke.arc.scene.ui.layout.Table; import io.anuke.arc.scene.ui.layout.Unit; import io.anuke.arc.util.*; import io.anuke.mindustry.Vars; +import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.*; +import io.anuke.mindustry.io.JsonIO; import io.anuke.mindustry.io.MapIO; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.ui.dialogs.FloatingDialog; @@ -40,6 +43,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ private MapGenerateDialog generateDialog; private ScrollPane pane; private FloatingDialog menu; + private Rules lastSavedRules; private boolean saved = false; private boolean shownWithMap = false; private Array blocksOut = new Array<>(); @@ -91,7 +95,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ Platform.instance.showFileChooser("$editor.loadmap", "Map Files", file -> ui.loadAnd(() -> { try{ //TODO what if it's an image? users should be warned for their stupidity - editor.beginEdit(MapIO.readMap(file, true)); + editor.beginEdit(MapIO.createMap(file, true)); }catch(Exception e){ ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, false))); Log.err(e); @@ -103,9 +107,8 @@ public class MapEditorDialog extends Dialog implements Disposable{ ui.loadAnd(() -> { try{ Pixmap pixmap = new Pixmap(file); - Tile[][] tiles = editor.createTiles(pixmap.getWidth(), pixmap.getHeight()); - editor.load(() -> MapIO.readLegacyPixmap(pixmap, tiles)); - editor.beginEdit(tiles); + editor.beginEdit(pixmap); + pixmap.dispose(); }catch(Exception e){ ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, false))); Log.err(e); @@ -122,7 +125,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ if(!editor.getTags().containsKey("name")){ editor.getTags().put("name", result.nameWithoutExtension()); } - MapIO.writeMap(result, editor.createMap(result), editor.tiles()); + MapIO.writeMap(result, editor.createMap(result)); }catch(Exception e){ ui.showError(Core.bundle.format("editor.errorsave", Strings.parseException(e, false))); Log.err(e); @@ -133,10 +136,14 @@ public class MapEditorDialog extends Dialog implements Disposable{ menu.cont.row(); + menu.cont.addImageTextButton("$editor.ingame", "icon-arrow", isize, this::playtest).padTop(-5).size(swidth * 2f + 10, 60f); + + menu.cont.row(); + menu.cont.addImageTextButton("$quit", "icon-back", isize, () -> { tryExit(); menu.hide(); - }).padTop(-5).size(swidth * 2f + 10, 60f); + }).size(swidth * 2f + 10, 60f); resizeDialog = new MapResizeDialog(editor, (x, y) -> { if(!(editor.width() == x && editor.height() == y)){ @@ -181,11 +188,14 @@ public class MapEditorDialog extends Dialog implements Disposable{ }); shown(() -> { + //clear units, rules and other unnecessary stuff + logic.reset(); saved = true; if(!Core.settings.getBool("landscape")) Platform.instance.beginForceLandscape(); editor.clearOp(); Core.scene.setScrollFocus(view); if(!shownWithMap){ + state.rules = new Rules(); editor.beginEdit(200, 200); } shownWithMap = false; @@ -205,8 +215,47 @@ public class MapEditorDialog extends Dialog implements Disposable{ drawDefaultBackground(x, y); } + public void resumeEditing(){ + state.set(State.menu); + shownWithMap = true; + show(); + state.rules = (lastSavedRules == null ? new Rules() : lastSavedRules); + lastSavedRules = null; + editor.renderer().updateAll(); + } + + private void playtest(){ + menu.hide(); + ui.loadAnd(() -> { + lastSavedRules = state.rules; + hide(); + //only reset the player; logic.reset() will clear entities, which we do not want + player.reset(); + state.rules = Gamemode.editor.apply(new Rules()); + world.setMap(new Map(StringMap.of( + "name", "Editor Playtesting", + "width", editor.width(), + "height", editor.height() + ))); + world.endMapLoad(); + //add entities so they update. is this really needed? + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ + Tile tile = world.rawTile(x, y); + if(tile.entity != null){ + tile.entity.add(); + } + } + } + player.set(world.width() * tilesize/2f, world.height() * tilesize/2f); + player.setDead(false); + logic.play(); + }); + } + private void save(){ String name = editor.getTags().get("name", "").trim(); + editor.getTags().put("rules", JsonIO.write(state.rules)); if(name.isEmpty()){ infoDialog.show(); @@ -216,7 +265,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ if(map != null && !map.custom){ ui.showError("$editor.save.overwrite"); }else{ - world.maps.saveMap(editor.getTags(), editor.tiles()); + world.maps.saveMap(editor.getTags()); ui.showInfoFade("$editor.saved"); } } @@ -281,9 +330,8 @@ public class MapEditorDialog extends Dialog implements Disposable{ public void beginEditMap(FileHandle file){ ui.loadAnd(() -> { try{ - Map map = MapIO.readMap(file, true); shownWithMap = true; - editor.beginEdit(map); + editor.beginEdit(MapIO.createMap(file, true)); show(); }catch(Exception e){ Log.err(e); diff --git a/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java b/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java index e34a139e9e..02d5453b22 100644 --- a/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java @@ -224,7 +224,7 @@ public class MapGenerateDialog extends FloatingDialog{ Tile tile = editor.tile(x, y); input.begin(editor, x, y, tile.floor(), tile.block(), tile.overlay()); filter.apply(input); - writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.getRotation()); + writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.rotation()); } } @@ -235,7 +235,7 @@ public class MapGenerateDialog extends FloatingDialog{ Tile tile = editor.tile(x, y); DummyTile write = writeTiles[x][y]; - tile.setRotation(write.rotation); + tile.rotation(write.rotation); tile.setFloor((Floor)content.block(write.floor)); tile.setBlock(content.block(write.block)); tile.setTeam(Team.all[write.team]); @@ -322,7 +322,8 @@ public class MapGenerateDialog extends FloatingDialog{ } public static class DummyTile{ - public byte block, floor, ore, team, rotation; + public byte team, rotation; + public short block, floor, ore; void set(Block floor, Block wall, Block ore, Team team, int rotation){ this.floor = floor.id; @@ -341,7 +342,7 @@ public class MapGenerateDialog extends FloatingDialog{ } void set(Tile other){ - set(other.floor(), other.block(), other.overlay(), other.getTeam(), other.getRotation()); + set(other.floor(), other.block(), other.overlay(), other.getTeam(), other.rotation()); } } diff --git a/core/src/io/anuke/mindustry/editor/MapInfoDialog.java b/core/src/io/anuke/mindustry/editor/MapInfoDialog.java index a422552312..efd049d11f 100644 --- a/core/src/io/anuke/mindustry/editor/MapInfoDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapInfoDialog.java @@ -4,12 +4,16 @@ import io.anuke.arc.Core; import io.anuke.arc.collection.ObjectMap; import io.anuke.arc.scene.ui.TextArea; import io.anuke.arc.scene.ui.TextField; +import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.Platform; +import io.anuke.mindustry.game.Rules; +import io.anuke.mindustry.ui.dialogs.CustomRulesDialog; import io.anuke.mindustry.ui.dialogs.FloatingDialog; public class MapInfoDialog extends FloatingDialog{ private final MapEditor editor; private final WaveInfoDialog waveinfo; + private final CustomRulesDialog ruleinfo = new CustomRulesDialog(); public MapInfoDialog(MapEditor editor){ super("$editor.mapinfo"); @@ -36,7 +40,6 @@ public class MapInfoDialog extends FloatingDialog{ name.setMessageText("$unknown"); cont.row(); - cont.add("$editor.description").padRight(8).left(); TextArea description = cont.addArea(tags.get("description", ""), "textarea", text -> { @@ -44,7 +47,6 @@ public class MapInfoDialog extends FloatingDialog{ }).size(400f, 140f).get(); cont.row(); - cont.add("$editor.author").padRight(8).left(); TextField author = cont.addField(tags.get("author", Core.settings.getString("mapAuthor", "")), text -> { @@ -54,14 +56,13 @@ public class MapInfoDialog extends FloatingDialog{ }).size(400, 55f).get(); author.setMessageText("$unknown"); + cont.row(); + cont.add("$editor.rules").padRight(8).left(); + cont.addButton("$edit", () -> ruleinfo.show(Vars.state.rules, () -> Vars.state.rules = new Rules())).left().width(200f);; + cont.row(); cont.add("$editor.waves").padRight(8).left(); - cont.table(t -> { - t.add().growX(); - t.label(() -> tags.containsKey("waves") ? "" : Core.bundle.get("editor.default")).left(); - t.add().growX(); - t.addButton("$edit", waveinfo::show).growY().width(200f); - }).size(400, 55f); + cont.addButton("$edit", waveinfo::show).left().width(200f); name.change(); description.change(); diff --git a/core/src/io/anuke/mindustry/editor/MapRenderer.java b/core/src/io/anuke/mindustry/editor/MapRenderer.java index 1488f8e0c6..decc0c18d3 100644 --- a/core/src/io/anuke/mindustry/editor/MapRenderer.java +++ b/core/src/io/anuke/mindustry/editor/MapRenderer.java @@ -14,6 +14,7 @@ import io.anuke.mindustry.game.Team; import io.anuke.mindustry.graphics.IndexedRenderer; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.BlockPart; import static io.anuke.mindustry.Vars.tilesize; @@ -84,7 +85,6 @@ public class MapRenderer implements Disposable{ } public void updatePoint(int x, int y){ - //TODO spread out over multiple frames? updates.add(x + y * width); } @@ -110,13 +110,13 @@ public class MapRenderer implements Disposable{ int idxWall = (wx % chunksize) + (wy % chunksize) * chunksize; int idxDecal = (wx % chunksize) + (wy % chunksize) * chunksize + chunksize * chunksize; - if(wall != Blocks.air && (wall.synthetic() || wall == Blocks.part)){ + if(wall != Blocks.air && (wall.synthetic() || wall instanceof BlockPart)){ region = !Core.atlas.isFound(wall.editorIcon()) ? Core.atlas.find("clear-editor") : wall.editorIcon(); if(wall.rotate){ mesh.draw(idxWall, region, wx * tilesize + wall.offset(), wy * tilesize + wall.offset(), - region.getWidth() * Draw.scl, region.getHeight() * Draw.scl, tile.getRotation() * 90 - 90); + region.getWidth() * Draw.scl, region.getHeight() * Draw.scl, tile.rotation() * 90 - 90); }else{ mesh.draw(idxWall, region, wx * tilesize + wall.offset() + (tilesize - region.getWidth() * Draw.scl) / 2f, diff --git a/core/src/io/anuke/mindustry/entities/Damage.java b/core/src/io/anuke/mindustry/entities/Damage.java index 9f7ea0ca44..7acfae0783 100644 --- a/core/src/io/anuke/mindustry/entities/Damage.java +++ b/core/src/io/anuke/mindustry/entities/Damage.java @@ -85,9 +85,8 @@ public class Damage{ public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length){ tr.trns(angle, length); world.raycastEachWorld(x, y, x + tr.x, y + tr.y, (cx, cy) -> { - Tile tile = world.tile(cx, cy); - if(tile != null) tile = tile.target(); - if(tile != null && tile.entity != null && tile.target().getTeamID() != team.ordinal() && tile.entity.collide(hitter)){ + Tile tile = world.ltile(cx, cy); + if(tile != null && tile.entity != null && tile.getTeamID() != team.ordinal() && tile.entity.collide(hitter)){ tile.entity.collision(hitter); hitter.getBulletType().hit(hitter, tile.worldx(), tile.worldy()); } @@ -216,12 +215,10 @@ public class Damage{ int scaledDamage = (int)(damage * (1f - (float)dst / radius)); bits.set(bitOffset + x, bitOffset + y); - Tile tile = world.tile(startx + x, starty + y); + Tile tile = world.ltile(startx + x, starty + y); if(scaledDamage <= 0 || tile == null) continue; - tile = tile.target(); - //apply damage to entity if needed if(tile.entity != null && tile.getTeam() != team){ int health = (int)tile.entity.health; diff --git a/core/src/io/anuke/mindustry/entities/Entities.java b/core/src/io/anuke/mindustry/entities/Entities.java index f98ee41587..d0d5d16dea 100755 --- a/core/src/io/anuke/mindustry/entities/Entities.java +++ b/core/src/io/anuke/mindustry/entities/Entities.java @@ -7,10 +7,11 @@ import io.anuke.arc.function.Consumer; import io.anuke.arc.function.Predicate; import io.anuke.arc.graphics.Camera; import io.anuke.arc.math.geom.Rectangle; -import io.anuke.mindustry.Vars; import io.anuke.mindustry.entities.traits.DrawTrait; import io.anuke.mindustry.entities.traits.Entity; +import static io.anuke.mindustry.Vars.collisions; + public class Entities{ public static final int maxLeafObjects = 4; private static final Array> groupArray = new Array<>(); @@ -48,7 +49,7 @@ public class Entities{ group.updateEvents(); if(group.useTree()){ - Vars.collisions.updatePhysics(group); + collisions.updatePhysics(group); } for(Entity e : group.all()){ diff --git a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java index 590e3b1635..2ff39fcc6f 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java +++ b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java @@ -225,9 +225,8 @@ public class Bullet extends SolidEntity implements DamageTrait, ScaleTrait, Pool if(type.hitTiles && collidesTiles() && !supressCollision && initialized){ world.raycastEach(world.toTile(lastPosition().x), world.toTile(lastPosition().y), world.toTile(x), world.toTile(y), (x, y) -> { - Tile tile = world.tile(x, y); + Tile tile = world.ltile(x, y); if(tile == null) return false; - tile = tile.target(); if(tile.entity != null && tile.entity.collide(this) && type.collides(this, tile) && !tile.entity.isDead() && (type.collidesTeam || tile.getTeam() != team)){ if(tile.getTeam() != team){ diff --git a/core/src/io/anuke/mindustry/entities/effect/Fire.java b/core/src/io/anuke/mindustry/entities/effect/Fire.java index fe447c1dcd..fa1b74b106 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Fire.java +++ b/core/src/io/anuke/mindustry/entities/effect/Fire.java @@ -72,6 +72,11 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{ } } + @Override + public byte version(){ + return 0; + } + @Override public float lifetime(){ return lifetime; @@ -98,7 +103,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{ return; } - TileEntity entity = tile.target().entity; + TileEntity entity = tile.link().entity; boolean damage = entity != null; float flammability = baseFlammability + puddleFlammability; @@ -151,7 +156,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{ } @Override - public void readSave(DataInput stream) throws IOException{ + public void readSave(DataInput stream, byte version) throws IOException{ this.loadedPosition = stream.readInt(); this.lifetime = stream.readFloat(); this.time = stream.readFloat(); diff --git a/core/src/io/anuke/mindustry/entities/effect/Puddle.java b/core/src/io/anuke/mindustry/entities/effect/Puddle.java index 76816d077e..4b633ac41d 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Puddle.java +++ b/core/src/io/anuke/mindustry/entities/effect/Puddle.java @@ -143,6 +143,11 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai return liquid.flammability * amount; } + @Override + public byte version(){ + return 0; + } + @Override public void hitbox(Rectangle rectangle){ rectangle.setCenter(x, y).setSize(tilesize); @@ -201,7 +206,7 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai } }); - if(liquid.temperature > 0.7f && (tile.target().entity != null) && Mathf.chance(0.3 * Time.delta())){ + if(liquid.temperature > 0.7f && (tile.link().entity != null) && Mathf.chance(0.3 * Time.delta())){ Fire.create(tile); } @@ -245,7 +250,7 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai } @Override - public void readSave(DataInput stream) throws IOException{ + public void readSave(DataInput stream, byte version) throws IOException{ this.loadedPosition = stream.readInt(); this.x = stream.readFloat(); this.y = stream.readFloat(); diff --git a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java index ec745b67c3..7787b834c1 100644 --- a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java @@ -40,10 +40,98 @@ public interface BuilderTrait extends Entity, TeamTrait{ float placeDistance = 220f; float mineDistance = 70f; - //due to iOS wierdness - class BuildDataStatic{ - static Array removal = new Array<>(); - static Vector2[] tmptr = new Vector2[]{new Vector2(), new Vector2(), new Vector2(), new Vector2()}; + /** + * Update building mechanism for this unit. + * This includes mining. + */ + default void updateBuilding(){ + float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : placeDistance; + Unit unit = (Unit)this; + //remove already completed build requests + removal.clear(); + for(BuildRequest req : getPlaceQueue()){ + removal.add(req); + } + + getPlaceQueue().clear(); + + for(BuildRequest request : removal){ + if(!((request.breaking && world.tile(request.x, request.y).block() == Blocks.air) || + (!request.breaking && (world.tile(request.x, request.y).rotation() == request.rotation || !request.block.rotate) + && world.tile(request.x, request.y).block() == request.block))){ + getPlaceQueue().addLast(request); + } + } + + BuildRequest current = getCurrentRequest(); + + //update mining here + if(current == null){ + if(getMineTile() != null){ + updateMining(); + } + return; + }else{ + setMineTile(null); + } + + Tile tile = world.tile(current.x, current.y); + + if(dst(tile) > finalPlaceDst){ + if(getPlaceQueue().size > 1){ + getPlaceQueue().removeFirst(); + getPlaceQueue().addLast(current); + } + return; + } + + if(!(tile.block() instanceof BuildBlock)){ + if(canCreateBlocks() && !current.breaking && Build.validPlace(getTeam(), current.x, current.y, current.block, current.rotation)){ + Call.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation); + }else if(canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){ + Call.beginBreak(getTeam(), current.x, current.y); + }else{ + getPlaceQueue().removeFirst(); + return; + } + } + + TileEntity core = unit.getClosestCore(); + + //if there is no core to build with or no build entity, stop building! + if((core == null && !state.rules.infiniteResources) || !(tile.entity instanceof BuildEntity)){ + return; + } + + //otherwise, update it. + BuildEntity entity = tile.entity(); + + if(entity == null){ + return; + } + + if(unit.dst(tile) <= finalPlaceDst){ + unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f); + } + + //progress is synced, thus not updated clientside + if(!Net.client()){ + //deconstructing is 2x as fast + if(current.breaking){ + entity.deconstruct(unit, core, 2f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); + }else{ + entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); + } + + current.progress = entity.progress(); + }else{ + entity.progress = current.progress; + } + + if(!current.initialized){ + Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, unit.getTeam(), this, current.breaking))); + current.initialized = true; + } } /** Returns the queue for storing build requests. */ @@ -148,97 +236,10 @@ public interface BuilderTrait extends Entity, TeamTrait{ return getPlaceQueue().size == 0 ? null : getPlaceQueue().first(); } - /** - * Update building mechanism for this unit. - * This includes mining. - */ - default void updateBuilding(){ - Unit unit = (Unit)this; - //remove already completed build requests - removal.clear(); - for(BuildRequest req : getPlaceQueue()){ - removal.add(req); - } - - getPlaceQueue().clear(); - - for(BuildRequest request : removal){ - if(!((request.breaking && world.tile(request.x, request.y).block() == Blocks.air) || - (!request.breaking && (world.tile(request.x, request.y).getRotation() == request.rotation || !request.block.rotate) - && world.tile(request.x, request.y).block() == request.block))){ - getPlaceQueue().addLast(request); - } - } - - BuildRequest current = getCurrentRequest(); - - //update mining here - if(current == null){ - if(getMineTile() != null){ - updateMining(); - } - return; - }else{ - setMineTile(null); - } - - Tile tile = world.tile(current.x, current.y); - - if(dst(tile) > placeDistance){ - if(getPlaceQueue().size > 1){ - getPlaceQueue().removeFirst(); - getPlaceQueue().addLast(current); - } - return; - } - - if(!(tile.block() instanceof BuildBlock)){ - if(canCreateBlocks() && !current.breaking && Build.validPlace(getTeam(), current.x, current.y, current.block, current.rotation)){ - Call.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation); - }else if(canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){ - Call.beginBreak(getTeam(), current.x, current.y); - }else{ - getPlaceQueue().removeFirst(); - return; - } - } - - TileEntity core = unit.getClosestCore(); - - //if there is no core to build with or no build entity, stop building! - if(core == null || !(tile.entity instanceof BuildEntity)){ - return; - } - - //otherwise, update it. - BuildEntity entity = tile.entity(); - - if(entity == null){ - return; - } - - if(unit.dst(tile) <= placeDistance){ - unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f); - } - - //progress is synced, thus not updated clientside - if(!Net.client()){ - //deconstructing is 2x as fast - if(current.breaking){ - entity.deconstruct(unit, core, 2f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); - }else{ - entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier); - } - - current.progress = entity.progress(); - }else{ - entity.progress = current.progress; - } - - if(!current.initialized){ - Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, unit.getTeam(), this, current.breaking))); - current.initialized = true; - } + //due to iOS wierdness, this is apparently required + class BuildDataStatic{ + static Array removal = new Array<>(); + static Vector2[] tmptr = new Vector2[]{new Vector2(), new Vector2(), new Vector2(), new Vector2()}; } /** Do not call directly. */ @@ -291,7 +292,7 @@ public interface BuilderTrait extends Entity, TeamTrait{ Tile tile = world.tile(request.x, request.y); - if(dst(tile) > placeDistance){ + if(dst(tile) > placeDistance && !state.isEditor()){ return; } diff --git a/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java b/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java index dfaf8011aa..d992de766a 100644 --- a/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java @@ -4,4 +4,5 @@ package io.anuke.mindustry.entities.traits; * Marks an entity as serializable. */ public interface SaveTrait extends Entity, TypeTrait, Saveable{ + byte version(); } diff --git a/core/src/io/anuke/mindustry/entities/traits/Saveable.java b/core/src/io/anuke/mindustry/entities/traits/Saveable.java index b3b3bc5914..b0d22e6a34 100644 --- a/core/src/io/anuke/mindustry/entities/traits/Saveable.java +++ b/core/src/io/anuke/mindustry/entities/traits/Saveable.java @@ -4,6 +4,5 @@ import java.io.*; public interface Saveable{ void writeSave(DataOutput stream) throws IOException; - - void readSave(DataInput stream) throws IOException; + void readSave(DataInput stream, byte version) throws IOException; } diff --git a/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java b/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java index f41bc0381b..7a25c9c1ed 100644 --- a/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java @@ -41,15 +41,6 @@ public interface SyncTrait extends Entity, TypeTrait{ return true; } - /** Whether this entity is clipped and not synced when out of viewport. */ - default boolean isClipped(){ - return true; - } - - default float clipSize(){ - return (this instanceof DrawTrait ? ((DrawTrait)this).drawSize() : 8f); - } - //Read and write sync data, usually position void write(DataOutput data) throws IOException; diff --git a/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java b/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java index 663575bb5f..69d1794e96 100644 --- a/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java @@ -23,9 +23,7 @@ public interface TypeTrait{ lastRegisteredID[0]++; } - /** - * Registers a syncable type by ID. - */ + /**Gets a syncable type by ID.*/ static Supplier getTypeByID(int id){ if(id == -1){ throw new IllegalArgumentException("Attempt to retrieve invalid entity type ID! Did you forget to set it in ContentLoader.registerTypes()?"); diff --git a/core/src/io/anuke/mindustry/entities/type/BaseUnit.java b/core/src/io/anuke/mindustry/entities/type/BaseUnit.java index 6909d470e5..f54ef58bcd 100644 --- a/core/src/io/anuke/mindustry/entities/type/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/type/BaseUnit.java @@ -244,12 +244,14 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{ @Override public void update(){ - hitTime -= Time.delta(); - if(isDead()){ + //dead enemies should get immediately removed + remove(); return; } + hitTime -= Time.delta(); + if(Net.client()){ interpolate(); status.update(this); @@ -304,11 +306,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{ return type.hitsize * 10; } - @Override - public float clipSize(){ - return isBoss() ? 10000000000f : super.clipSize(); - } - @Override public void onDeath(){ Call.onUnitDeath(this); @@ -336,6 +333,11 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{ return unitGroups[team.ordinal()]; } + @Override + public byte version(){ + return 0; + } + @Override public void writeSave(DataOutput stream) throws IOException{ super.writeSave(stream); @@ -344,8 +346,8 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{ } @Override - public void readSave(DataInput stream) throws IOException{ - super.readSave(stream); + public void readSave(DataInput stream, byte version) throws IOException{ + super.readSave(stream, version); byte type = stream.readByte(); this.spawner = stream.readInt(); @@ -363,7 +365,9 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{ @Override public void read(DataInput data) throws IOException{ float lastx = x, lasty = y, lastrot = rotation; - super.readSave(data); + + super.readSave(data, version()); + this.type = content.getByID(ContentType.unit, data.readByte()); this.spawner = data.readInt(); diff --git a/core/src/io/anuke/mindustry/entities/type/Player.java b/core/src/io/anuke/mindustry/entities/type/Player.java index 6a4e40125b..c68727da15 100644 --- a/core/src/io/anuke/mindustry/entities/type/Player.java +++ b/core/src/io/anuke/mindustry/entities/type/Player.java @@ -432,7 +432,7 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{ if(getCurrentRequest() == request && request.progress > 0.001f) continue; if(request.breaking){ - Block block = world.tile(request.x, request.y).target().block(); + Block block = world.ltile(request.x, request.y).block(); //draw removal request Lines.stroke(2f, Pal.removeBack); @@ -636,13 +636,22 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{ } protected void updateShooting(){ - if(isShooting() && mech.canShoot(this)){ - mech.weapon.update(this, pointerX, pointerY); + if(!state.isEditor() && isShooting() && mech.canShoot(this)){ + if(!mech.turnCursor){ + //shoot forward ignoring cursor + mech.weapon.update(this, x + Angles.trnsx(rotation, 1f), y + Angles.trnsy(rotation, 1f)); + }else{ + mech.weapon.update(this, pointerX, pointerY); + } } } protected void updateFlying(){ - if(Units.invalidateTarget(target, this) && !(target instanceof TileEntity && ((TileEntity)target).damaged() && target.isValid() && ((TileEntity)target).isAdded() && target.getTeam() == team && mech.canHeal && dst(target) < getWeapon().bullet.range())){ + if(Units.invalidateTarget(target, this) && !(target instanceof TileEntity && ((TileEntity)target).damaged() && target.isValid() && target.getTeam() == team && mech.canHeal && dst(target) < getWeapon().bullet.range())){ + target = null; + } + + if(state.isEditor()){ target = null; } @@ -778,6 +787,7 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{ moveTarget = null; spawner = lastSpawner = null; health = maxHealth(); + mining = null; boostHeat = drownTime = hitTime = 0f; mech = Mechs.starter; placeQueue.clear(); @@ -790,7 +800,11 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{ public void updateRespawning(){ - if(spawner != null && spawner.isValid()){ + if(state.isEditor()){ + //instant respawn at center of map. + set(world.width() * tilesize/2f, world.height() * tilesize/2f); + setDead(false); + }else if(spawner != null && spawner.isValid()){ spawner.updateSpawning(this); }else if(!netServer.isWaitingForPlayers()){ if(!Net.client()){ @@ -810,6 +824,7 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{ this.lastSpawner = spawner; this.dead = true; setNet(spawner.getX(), spawner.getY()); + spawner.updateSpawning(this); } //endregion @@ -817,8 +832,8 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{ //region read and write methods @Override - public boolean isClipped(){ - return false; + public byte version(){ + return 0; } @Override @@ -833,7 +848,7 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{ } @Override - public void readSave(DataInput stream) throws IOException{ + public void readSave(DataInput stream, byte version) throws IOException{ boolean local = stream.readBoolean(); if(local){ @@ -844,14 +859,14 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{ lastSpawner = (SpawnerTrait)stile.entity; } Player player = headless ? this : Vars.player; - player.readSaveSuper(stream); + player.readSaveSuper(stream, version); player.mech = content.getByID(ContentType.mech, mechid); player.dead = false; } } - private void readSaveSuper(DataInput stream) throws IOException{ - super.readSave(stream); + private void readSaveSuper(DataInput stream, byte version) throws IOException{ + super.readSave(stream, version); add(); } @@ -859,7 +874,7 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{ @Override public void write(DataOutput buffer) throws IOException{ super.writeSave(buffer, !isLocal); - TypeIO.writeStringData(buffer, name); //TODO writing strings is very inefficient + TypeIO.writeStringData(buffer, name); buffer.writeByte(Pack.byteValue(isAdmin) | (Pack.byteValue(dead) << 1) | (Pack.byteValue(isBoosting) << 2) | (Pack.byteValue(isTyping) << 3)); buffer.writeInt(Color.rgba8888(color)); buffer.writeByte(mech.id); @@ -873,7 +888,9 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{ @Override public void read(DataInput buffer) throws IOException{ float lastx = x, lasty = y, lastrot = rotation, lastvx = velocity.x, lastvy = velocity.y; - super.readSave(buffer); + + super.readSave(buffer, version()); + name = TypeIO.readStringData(buffer); byte bools = buffer.readByte(); isAdmin = (bools & 1) != 0; diff --git a/core/src/io/anuke/mindustry/entities/type/TileEntity.java b/core/src/io/anuke/mindustry/entities/type/TileEntity.java index 51f84ebefc..c7793c5895 100644 --- a/core/src/io/anuke/mindustry/entities/type/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/type/TileEntity.java @@ -1,14 +1,12 @@ package io.anuke.mindustry.entities.type; -import io.anuke.annotations.Annotations.Loc; -import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.Annotations.*; import io.anuke.arc.Events; import io.anuke.arc.collection.Array; import io.anuke.arc.collection.ObjectSet; import io.anuke.arc.math.geom.Point2; import io.anuke.arc.math.geom.Vector2; -import io.anuke.arc.util.Interval; -import io.anuke.arc.util.Time; +import io.anuke.arc.util.*; import io.anuke.mindustry.entities.EntityGroup; import io.anuke.mindustry.entities.bullet.Bullet; import io.anuke.mindustry.entities.impl.BaseEntity; @@ -115,16 +113,35 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ return dead || tile.entity != this; } + @CallSuper public void write(DataOutput stream) throws IOException{ + stream.writeShort((short)health); + stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.rotation())); //team + rotation + if(items != null) items.write(stream); + if(power != null) power.write(stream); + if(liquids != null) liquids.write(stream); + if(cons != null) cons.write(stream); } - public void writeConfig(DataOutput stream) throws IOException{ + @CallSuper + public void read(DataInput stream, byte revision) throws IOException{ + health = stream.readUnsignedShort(); + byte tr = stream.readByte(); + byte team = Pack.leftByte(tr); + byte rotation = Pack.rightByte(tr); + + tile.setTeam(Team.all[team]); + tile.rotation(rotation); + + if(items != null) items.read(stream); + if(power != null) power.read(stream); + if(liquids != null) liquids.read(stream); + if(cons != null) cons.read(stream); } - public void read(DataInput stream) throws IOException{ - } - - public void readConfig(DataInput stream) throws IOException{ + /** Returns the version of this TileEntity IO code.*/ + public byte version(){ + return 0; } public boolean collide(Bullet other){ @@ -168,14 +185,14 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ Point2[] nearby = Edges.getEdges(block.size); for(Point2 point : nearby){ - Tile other = world.tile(tile.x + point.x, tile.y + point.y); + Tile other = world.ltile(tile.x + point.x, tile.y + point.y); //remove this tile from all nearby tile's proximities if(other != null){ - other = other.target(); other.block().onProximityUpdate(other); - } - if(other != null && other.entity != null){ - other.entity.proximity.removeValue(tile, true); + + if(other.entity != null){ + other.entity.proximity.removeValue(tile, true); + } } } } @@ -186,10 +203,9 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ Point2[] nearby = Edges.getEdges(block.size); for(Point2 point : nearby){ - Tile other = world.tile(tile.x + point.x, tile.y + point.y); + Tile other = world.ltile(tile.x + point.x, tile.y + point.y); if(other == null) continue; - other = other.target(); if(other.entity == null || !(other.interactable(tile.getTeam()))) continue; other.block().onProximityUpdate(other); @@ -280,6 +296,11 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ } } + @Override + public boolean isValid(){ + return !isDead() && tile.entity == this; + } + @Override public EntityGroup targetGroup(){ return tileGroup; diff --git a/core/src/io/anuke/mindustry/entities/type/Unit.java b/core/src/io/anuke/mindustry/entities/type/Unit.java index 8bb69e72e1..6579af6c97 100644 --- a/core/src/io/anuke/mindustry/entities/type/Unit.java +++ b/core/src/io/anuke/mindustry/entities/type/Unit.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.entities.type; +import io.anuke.annotations.Annotations.Nullable; import io.anuke.arc.Core; import io.anuke.arc.Events; import io.anuke.arc.graphics.Color; @@ -8,7 +9,8 @@ import io.anuke.arc.graphics.g2d.TextureRegion; import io.anuke.arc.math.Mathf; import io.anuke.arc.math.geom.Geometry; import io.anuke.arc.math.geom.Vector2; -import io.anuke.arc.util.*; +import io.anuke.arc.util.Time; +import io.anuke.arc.util.Tmp; import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.content.Fx; import io.anuke.mindustry.entities.*; @@ -138,7 +140,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } @Override - public void readSave(DataInput stream) throws IOException{ + public void readSave(DataInput stream, byte version) throws IOException{ byte team = stream.readByte(); boolean dead = stream.readBoolean(); float x = stream.readFloat(); @@ -150,7 +152,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ byte itemID = stream.readByte(); short itemAmount = stream.readShort(); - this.status.readSave(stream); + this.status.readSave(stream, version); this.item.amount = itemAmount; this.item.item = content.item(itemID); this.dead = dead; @@ -221,7 +223,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ velocity.add(moveVector.x / mass() * Time.delta(), moveVector.y / mass() * Time.delta()); } - public TileEntity getClosestCore(){ + public @Nullable TileEntity getClosestCore(){ TeamData data = state.teams.get(team); Tile tile = Geometry.findClosest(x, y, data.cores); diff --git a/core/src/io/anuke/mindustry/entities/type/base/Drone.java b/core/src/io/anuke/mindustry/entities/type/base/Drone.java index 5d6fe4c1e1..6fd10ba022 100644 --- a/core/src/io/anuke/mindustry/entities/type/base/Drone.java +++ b/core/src/io/anuke/mindustry/entities/type/base/Drone.java @@ -58,7 +58,7 @@ public class Drone extends FlyingUnit implements BuilderTrait{ if(isBreaking){ getPlaceQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y)); }else{ - getPlaceQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y, entity.tile.getRotation(), entity.cblock)); + getPlaceQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y, entity.tile.rotation(), entity.cblock)); } } @@ -211,7 +211,7 @@ public class Drone extends FlyingUnit implements BuilderTrait{ Events.on(BuildSelectEvent.class, event -> { EntityGroup group = unitGroups[event.team.ordinal()]; - if(!(event.builder instanceof Player) || !(event.tile.entity instanceof BuildEntity)) return; + if(!(event.tile.entity instanceof BuildEntity)) return; for(BaseUnit unit : group.all()){ if(unit instanceof Drone){ diff --git a/core/src/io/anuke/mindustry/entities/units/Statuses.java b/core/src/io/anuke/mindustry/entities/units/Statuses.java index b8d0116ce3..75551f692b 100644 --- a/core/src/io/anuke/mindustry/entities/units/Statuses.java +++ b/core/src/io/anuke/mindustry/entities/units/Statuses.java @@ -130,7 +130,7 @@ public class Statuses implements Saveable{ } @Override - public void readSave(DataInput stream) throws IOException{ + public void readSave(DataInput stream, byte version) throws IOException{ for(StatusEntry effect : statuses){ Pools.free(effect); } diff --git a/core/src/io/anuke/mindustry/game/Content.java b/core/src/io/anuke/mindustry/game/Content.java index 4da406e960..b03177e9c3 100644 --- a/core/src/io/anuke/mindustry/game/Content.java +++ b/core/src/io/anuke/mindustry/game/Content.java @@ -6,10 +6,10 @@ import io.anuke.mindustry.type.ContentType; /** Base class for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. */ public abstract class Content{ - public final byte id; + public final short id; public Content(){ - this.id = (byte)Vars.content.getBy(getContentType()).size; + this.id = (short)Vars.content.getBy(getContentType()).size; Vars.content.handleContent(this); } diff --git a/core/src/io/anuke/mindustry/game/Gamemode.java b/core/src/io/anuke/mindustry/game/Gamemode.java index 4211e1fcf9..41a77c1949 100644 --- a/core/src/io/anuke/mindustry/game/Gamemode.java +++ b/core/src/io/anuke/mindustry/game/Gamemode.java @@ -1,49 +1,64 @@ package io.anuke.mindustry.game; import io.anuke.arc.Core; -import io.anuke.arc.function.Supplier; +import io.anuke.arc.function.Consumer; /** Defines preset rule sets.. */ public enum Gamemode{ - survival(() -> new Rules(){{ - waveTimer = true; - waves = true; - unitDrops = true; - spawns = DefaultWaves.get(); - }}), - sandbox(() -> new Rules(){{ - infiniteResources = true; - waves = true; - waveTimer = false; - respawnTime = 0f; - }}), - attack(() -> new Rules(){{ - enemyCheat = true; - unitDrops = true; - waves = false; - attackMode = true; - }}), - pvp(() -> new Rules(){{ - pvp = true; - enemyCoreBuildRadius = 600f; - respawnTime = 60 * 10; - buildCostMultiplier = 0.5f; - buildSpeedMultiplier = 2f; - playerDamageMultiplier = 0.45f; - playerHealthMultiplier = 0.8f; - unitBuildSpeedMultiplier = 3f; - unitHealthMultiplier = 2f; - attackMode = true; - }}); + survival(rules -> { + rules.waveTimer = true; + rules.waves = true; + rules.unitDrops = true; + }), + sandbox(rules -> { + rules.infiniteResources = true; + rules.waves = true; + rules.waveTimer = false; + rules.respawnTime = 0f; + }), + attack(rules -> { + rules.enemyCheat = true; + rules.unitDrops = true; + rules.waves = false; + rules.attackMode = true; + }), + pvp(rules -> { + rules.pvp = true; + rules.enemyCoreBuildRadius = 600f; + rules.respawnTime = 60 * 10; + rules.buildCostMultiplier = 0.5f; + rules.buildSpeedMultiplier = 2f; + rules.playerDamageMultiplier = 0.45f; + rules.playerHealthMultiplier = 0.8f; + rules.unitBuildSpeedMultiplier = 3f; + rules.unitHealthMultiplier = 2f; + rules.attackMode = true; + }), + editor(true, rules -> { + rules.infiniteResources = true; + rules.editor = true; + rules.waves = false; + rules.enemyCoreBuildRadius = 0f; + rules.waveTimer = false; + rules.respawnTime = 0f; + }); - private final Supplier rules; + private final Consumer rules; + public final boolean hidden; - Gamemode(Supplier rules){ - this.rules = rules; + Gamemode(Consumer rules){ + this(false, rules); } - public Rules get(){ - return rules.get(); + Gamemode(boolean hidden, Consumer rules){ + this.rules = rules; + this.hidden = hidden; + } + + /** Applies this preset to this ruleset. */ + public Rules apply(Rules in){ + rules.accept(in); + return in; } public String description(){ diff --git a/core/src/io/anuke/mindustry/game/Rules.java b/core/src/io/anuke/mindustry/game/Rules.java index 95faf1c90a..63babc963b 100644 --- a/core/src/io/anuke/mindustry/game/Rules.java +++ b/core/src/io/anuke/mindustry/game/Rules.java @@ -2,6 +2,8 @@ package io.anuke.mindustry.game; import io.anuke.annotations.Annotations.Serialize; import io.anuke.arc.collection.Array; +import io.anuke.mindustry.io.JsonIO; +import io.anuke.mindustry.type.Zone; /** * Defines current rules on how the game should function. @@ -47,10 +49,10 @@ public class Rules{ public float bossWaveMultiplier = 3f; /** How many times longer a launch wave takes. */ public float launchWaveMultiplier = 2f; - /** Zone ID, -1 for invalid zone. */ - public byte zone = -1; + /** Zone for saves that have them.*/ + public Zone zone; /** Spawn layout. Should be assigned on save load based on map or zone. */ - public transient Array spawns = DefaultWaves.get(); + public Array spawns = DefaultWaves.get(); /** Determines if there should be limited respawns. */ public boolean limitedRespawns = false; /** How many times player can respawn during one wave. */ @@ -59,4 +61,11 @@ public class Rules{ public boolean waitForWaveToEnd = false; /** Determinates if gamemode is attack mode */ public boolean attackMode = false; + /** Whether this is the editor gamemode. */ + public boolean editor = false; + + /** Copies this ruleset exactly. Not very efficient at all, do not use often. */ + public Rules copy(){ + return JsonIO.read(Rules.class, JsonIO.write(this)); + } } diff --git a/core/src/io/anuke/mindustry/game/Saves.java b/core/src/io/anuke/mindustry/game/Saves.java index 718d3a7df4..a307a315ec 100644 --- a/core/src/io/anuke/mindustry/game/Saves.java +++ b/core/src/io/anuke/mindustry/game/Saves.java @@ -4,22 +4,21 @@ import io.anuke.arc.Core; import io.anuke.arc.Events; import io.anuke.arc.collection.*; import io.anuke.arc.files.FileHandle; -import io.anuke.arc.util.Strings; -import io.anuke.arc.util.Time; +import io.anuke.arc.util.*; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.game.EventType.StateChangeEvent; import io.anuke.mindustry.io.SaveIO; import io.anuke.mindustry.io.SaveIO.SaveException; import io.anuke.mindustry.io.SaveMeta; import io.anuke.mindustry.maps.Map; -import io.anuke.mindustry.type.ContentType; import io.anuke.mindustry.type.Zone; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; -import static io.anuke.mindustry.Vars.*; +import static io.anuke.mindustry.Vars.saveExtension; +import static io.anuke.mindustry.Vars.state; public class Saves{ private int nextSlot; @@ -52,7 +51,7 @@ public class Saves{ SaveSlot slot = new SaveSlot(index); saves.add(slot); saveMap.put(slot.index, slot); - slot.meta = SaveIO.getData(index); + slot.meta = SaveIO.getMeta(index); nextSlot = Math.max(index + 1, nextSlot); } } @@ -134,7 +133,7 @@ public class Saves{ slot.setName(file.nameWithoutExtension()); saves.add(slot); saveMap.put(slot.index, slot); - slot.meta = SaveIO.getData(slot.index); + slot.meta = SaveIO.getMeta(slot.index); current = slot; saveSlots(); return slot; @@ -172,7 +171,7 @@ public class Saves{ public void load() throws SaveException{ try{ SaveIO.loadFromSlot(index); - meta = SaveIO.getData(index); + meta = SaveIO.getMeta(index); current = this; totalPlaytime = meta.timePlayed; }catch(Exception e){ @@ -186,7 +185,7 @@ public class Saves{ totalPlaytime = time; SaveIO.saveToSlot(index); - meta = SaveIO.getData(index); + meta = SaveIO.getMeta(index); if(!state.is(State.menu)){ current = this; } @@ -224,7 +223,7 @@ public class Saves{ } public Zone getZone(){ - return meta == null || meta.rules == null ? null : content.getByID(ContentType.zone, meta.rules.zone); + return meta == null || meta.rules == null ? null : meta.rules.zone; } public int getBuild(){ diff --git a/core/src/io/anuke/mindustry/game/Stats.java b/core/src/io/anuke/mindustry/game/Stats.java index 7e00d05d44..ebaa62dfc6 100644 --- a/core/src/io/anuke/mindustry/game/Stats.java +++ b/core/src/io/anuke/mindustry/game/Stats.java @@ -9,7 +9,7 @@ import io.anuke.mindustry.type.*; @Serialize public class Stats{ /** Items delivered to global resoure counter. Zones only. */ - public ObjectIntMap itemsDelivered = new ObjectIntMap<>(); + public transient ObjectIntMap itemsDelivered = new ObjectIntMap<>(); /** Enemy (red team) units destroyed. */ public int enemyUnitsDestroyed; /** Total waves lasted. */ diff --git a/core/src/io/anuke/mindustry/graphics/BlockRenderer.java b/core/src/io/anuke/mindustry/graphics/BlockRenderer.java index 6d62314e23..50d904682d 100644 --- a/core/src/io/anuke/mindustry/graphics/BlockRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/BlockRenderer.java @@ -78,7 +78,7 @@ public class BlockRenderer implements Disposable{ for(int y = 0; y < world.height(); y++){ Tile tile = world.rawTile(x, y); int edgeBlend = 2; - float rot = tile.getRotation(); + float rot = tile.rotation(); boolean fillable = (tile.block().solid && tile.block().fillsTile && !tile.block().synthetic()); int edgeDst = Math.min(x, Math.min(y, Math.min(Math.abs(x - (world.width() - 1)), Math.abs(y - (world.height() - 1))))); if(edgeDst <= edgeBlend){ diff --git a/core/src/io/anuke/mindustry/graphics/FloorRenderer.java b/core/src/io/anuke/mindustry/graphics/FloorRenderer.java index df6a420be4..06dba70468 100644 --- a/core/src/io/anuke/mindustry/graphics/FloorRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/FloorRenderer.java @@ -62,7 +62,7 @@ public class FloorRenderer implements Disposable{ //loop through all layers, and add layer index if it exists for(int i = 0; i < layers; i++){ - if(chunk.caches[i] != -1){ + if(chunk.caches[i] != -1 && i != CacheLayer.walls.ordinal()){ drawnLayerSet.add(i); } } diff --git a/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java b/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java index 612af596eb..9777f4e833 100644 --- a/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java @@ -80,7 +80,7 @@ public class MinimapRenderer implements Disposable{ for(Unit unit : units){ float rx = (unit.x - rect.x) / rect.width * w, ry = (unit.y - rect.y) / rect.width * h; Draw.color(unit.getTeam().color); - Fill.rect(x + rx, y + ry, baseSize / 2f, baseSize / 2f); + Fill.rect(x + rx, y + ry, io.anuke.arc.scene.ui.layout.Unit.dp.scl(baseSize / 2f), io.anuke.arc.scene.ui.layout.Unit.dp.scl(baseSize / 2f)); } Draw.color(); @@ -129,7 +129,7 @@ public class MinimapRenderer implements Disposable{ } private int colorFor(Tile tile){ - tile = tile.target(); + tile = tile.link(); return MapIO.colorFor(tile.floor(), tile.block(), tile.overlay(), tile.getTeam()); } diff --git a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java index bea762d8be..494099befb 100644 --- a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java @@ -97,11 +97,10 @@ public class OverlayRenderer{ //draw selected block bars and info if(input.block == null && !Core.scene.hasMouse()){ Vector2 vec = Core.input.mouseWorld(input.getMouseX(), input.getMouseY()); - Tile tile = world.tileWorld(vec.x, vec.y); + Tile tile = world.ltileWorld(vec.x, vec.y); - if(tile != null && tile.block() != Blocks.air && tile.target().getTeam() == player.getTeam()){ - Tile target = tile.target(); - target.block().drawSelect(target); + if(tile != null && tile.block() != Blocks.air && tile.getTeam() == player.getTeam()){ + tile.block().drawSelect(tile); } } @@ -113,8 +112,7 @@ public class OverlayRenderer{ Lines.circle(v.x, v.y, 6 + Mathf.absin(Time.time(), 5f, 1f)); Draw.reset(); - Tile tile = world.tileWorld(v.x, v.y); - if(tile != null) tile = tile.target(); + Tile tile = world.ltileWorld(v.x, v.y); if(tile != null && tile.interactable(player.getTeam()) && tile.block().acceptStack(player.item().item, player.item().amount, tile, player) > 0){ Draw.color(Pal.place); Lines.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize / 2f + 1 + Mathf.absin(Time.time(), 5f, 1f)); diff --git a/core/src/io/anuke/mindustry/graphics/Shapes.java b/core/src/io/anuke/mindustry/graphics/Shapes.java index 4918d5dd1b..7e629d347b 100644 --- a/core/src/io/anuke/mindustry/graphics/Shapes.java +++ b/core/src/io/anuke/mindustry/graphics/Shapes.java @@ -5,7 +5,6 @@ import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.math.Mathf; import io.anuke.arc.util.Tmp; -//TODO remove public class Shapes{ public static void laser(String line, String edge, float x, float y, float x2, float y2, float scale){ diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index 0327108f1d..2198f002ef 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -96,9 +96,8 @@ public class DesktopInput extends InputHandler{ for(int x = dresult.x; x <= dresult.x2; x++){ for(int y = dresult.y; y <= dresult.y2; y++){ - Tile tile = world.tile(x, y); + Tile tile = world.ltile(x, y); if(tile == null || !validBreak(tile.x, tile.y)) continue; - tile = tile.target(); Draw.color(Pal.removeBack); Lines.square(tile.drawx(), tile.drawy() - 1, tile.block().size * tilesize / 2f - 1); @@ -175,7 +174,7 @@ public class DesktopInput extends InputHandler{ Tile cursor = tileAt(Core.input.mouseX(), Core.input.mouseY()); if(cursor != null){ - cursor = cursor.target(); + cursor = cursor.link(); cursorType = cursor.block().getCursor(cursor); @@ -257,7 +256,7 @@ public class DesktopInput extends InputHandler{ } if(selected != null){ - tryDropItems(selected.target(), Core.input.mouseWorld().x, Core.input.mouseWorld().y); + tryDropItems(selected.link(), Core.input.mouseWorld().x, Core.input.mouseWorld().y); } mode = none; diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index 8a073e1f4b..f69c9331a6 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -154,7 +154,7 @@ public abstract class InputHandler implements InputProcessor{ /** Handles tile tap events that are not platform specific. */ boolean tileTapped(Tile tile){ - tile = tile.target(); + tile = tile.link(); boolean consumed = false, showedInventory = false; @@ -195,16 +195,17 @@ public abstract class InputHandler implements InputProcessor{ } } - if(!showedInventory){ - frag.inv.hide(); - } - - if(!consumed && player.isBuilding()){ + //clear when the player taps on something else + if(!consumed && !mobile && player.isBuilding() && block == null){ player.clearBuilding(); block = null; return true; } + if(!showedInventory){ + frag.inv.hide(); + } + return consumed; } @@ -331,7 +332,7 @@ public abstract class InputHandler implements InputProcessor{ } public void breakBlock(int x, int y){ - Tile tile = world.tile(x, y).target(); + Tile tile = world.ltile(x, y); player.addBuildRequest(new BuildRequest(tile.x, tile.y)); } diff --git a/core/src/io/anuke/mindustry/input/MobileInput.java b/core/src/io/anuke/mindustry/input/MobileInput.java index 7b7f53b098..16dd78802b 100644 --- a/core/src/io/anuke/mindustry/input/MobileInput.java +++ b/core/src/io/anuke/mindustry/input/MobileInput.java @@ -87,8 +87,7 @@ public class MobileInput extends InputHandler implements GestureListener{ player.setMineTile(null); player.target = unit; }else{ - Tile tile = world.tileWorld(x, y); - if(tile != null) tile = tile.target(); + Tile tile = world.ltileWorld(x, y); if(tile != null && tile.synthetic() && state.teams.areEnemies(player.getTeam(), tile.getTeam())){ TileEntity entity = tile.entity; @@ -276,12 +275,11 @@ public class MobileInput extends InputHandler implements GestureListener{ } }).update(l -> l.setChecked(mode == breaking)); - //cancel button - table.addImageButton("icon-cancel", "clear-partial", 16 * 2f, () -> { - player.clearBuilding(); - mode = none; - block = null; - }).visible(() -> player.isBuilding() || block != null || mode == breaking); + //diagonal swap button + table.addImageButton("icon-diagonal", "clear-toggle-partial", 16 * 2f, () -> { + Core.settings.put("swapdiagonal", !Core.settings.getBool("swapdiagonal")); + Core.settings.save(); + }).update(l -> l.setChecked(Core.settings.getBool("swapdiagonal"))); //rotate button table.addImageButton("icon-arrow", "clear-partial", 16 * 2f, () -> rotation = Mathf.mod(rotation + 1, 4)) @@ -311,6 +309,15 @@ public class MobileInput extends InputHandler implements GestureListener{ selection.clear(); selecting = false; }).visible(() -> !selection.isEmpty()); + + Core.scene.table(t -> { + t.bottom().left().visible(() -> player.isBuilding() || block != null || mode == breaking); + t.addImageTextButton("$cancel", "icon-cancel", 16*2, () -> { + player.clearBuilding(); + mode = none; + block = null; + }).width(155f); + }); } @Override @@ -408,9 +415,8 @@ public class MobileInput extends InputHandler implements GestureListener{ for(int x = dresult.x; x <= dresult.x2; x++){ for(int y = dresult.y; y <= dresult.y2; y++){ - Tile other = world.tile(x, y); + Tile other = world.ltile(x, y); if(other == null || !validBreak(other.x, other.y)) continue; - other = other.target(); Draw.color(Pal.removeBack); Lines.square(other.drawx(), other.drawy() - 1, other.block().size * tilesize / 2f - 1); @@ -506,12 +512,10 @@ public class MobileInput extends InputHandler implements GestureListener{ int wx = lineStartX + x * Mathf.sign(tileX - lineStartX); int wy = lineStartY + y * Mathf.sign(tileY - lineStartY); - Tile tar = world.tile(wx, wy); + Tile tar = world.ltile(wx, wy); if(tar == null) continue; - tar = tar.target(); - if(!hasRequest(world.tile(tar.x, tar.y)) && validBreak(tar.x, tar.y)){ PlaceRequest request = new PlaceRequest(tar.x, tar.y); request.scale = 1f; @@ -527,7 +531,7 @@ public class MobileInput extends InputHandler implements GestureListener{ if(tile == null) return false; - tryDropItems(tile.target(), Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y); + tryDropItems(tile.link(), Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y); } return false; } @@ -577,11 +581,11 @@ public class MobileInput extends InputHandler implements GestureListener{ }else if(mode == placing && isPlacing() && validPlace(cursor.x, cursor.y, block, rotation) && !checkOverlapPlacement(cursor.x, cursor.y, block)){ //add to selection queue if it's a valid place position selection.add(lastPlaced = new PlaceRequest(cursor.x, cursor.y, block, rotation)); - }else if(mode == breaking && validBreak(cursor.target().x, cursor.target().y) && !hasRequest(cursor.target())){ + }else if(mode == breaking && validBreak(cursor.link().x, cursor.link().y) && !hasRequest(cursor.link())){ //add to selection queue if it's a valid BREAK position - cursor = cursor.target(); + cursor = cursor.link(); selection.add(new PlaceRequest(cursor.x, cursor.y)); - }else if(!canTapPlayer(worldx, worldy) && !tileTapped(cursor.target())){ + }else if(!canTapPlayer(worldx, worldy) && !tileTapped(cursor.link())){ tryBeginMine(cursor); } diff --git a/core/src/io/anuke/mindustry/io/JsonIO.java b/core/src/io/anuke/mindustry/io/JsonIO.java new file mode 100644 index 0000000000..da5182fc32 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/JsonIO.java @@ -0,0 +1,40 @@ +package io.anuke.mindustry.io; + +import io.anuke.arc.util.serialization.Json; +import io.anuke.arc.util.serialization.JsonValue; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.game.Rules; +import io.anuke.mindustry.game.SpawnGroup; +import io.anuke.mindustry.type.ContentType; +import io.anuke.mindustry.type.Zone; + +public class JsonIO{ + private static Json json = new Json(){{ + setIgnoreUnknownFields(true); + setElementType(Rules.class, "spawns", SpawnGroup.class); + + setSerializer(Zone.class, new Serializer(){ + @Override + public void write(Json json, Zone object, Class knownType){ + json.writeValue(object.name); + } + + @Override + public Zone read(Json json, JsonValue jsonData, Class type){ + return Vars.content.getByName(ContentType.zone, jsonData.asString()); + } + }); + }}; + + public static String write(Object object){ + return json.toJson(object); + } + + public static T read(Class type, String string){ + return json.fromJson(type, string); + } + + public static String print(String in){ + return json.prettyPrint(in); + } +} diff --git a/core/src/io/anuke/mindustry/io/LegacyMapIO.java b/core/src/io/anuke/mindustry/io/LegacyMapIO.java new file mode 100644 index 0000000000..0773534617 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/LegacyMapIO.java @@ -0,0 +1,227 @@ +package io.anuke.mindustry.io; + +import io.anuke.arc.collection.*; +import io.anuke.arc.files.FileHandle; +import io.anuke.arc.graphics.Color; +import io.anuke.arc.graphics.Pixmap; +import io.anuke.arc.util.Pack; +import io.anuke.arc.util.Structs; +import io.anuke.arc.util.serialization.Json; +import io.anuke.mindustry.content.Blocks; +import io.anuke.mindustry.game.SpawnGroup; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.MapIO.TileProvider; +import io.anuke.mindustry.maps.Map; +import io.anuke.mindustry.type.ContentType; +import io.anuke.mindustry.world.*; +import io.anuke.mindustry.world.LegacyColorMapper.LegacyBlock; +import io.anuke.mindustry.world.blocks.BlockPart; +import io.anuke.mindustry.world.blocks.Floor; + +import java.io.*; +import java.util.zip.InflaterInputStream; + +import static io.anuke.mindustry.Vars.*; + +/** Map IO for the "old" .mmap format. + * Differentiate between legacy maps and new maps by checking the extension (or the header).*/ +public class LegacyMapIO{ + private static final ObjectMap fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad"); + private static final Json json = new Json(); + + /* Convert a map from the old format to the new format. */ + public static void convertMap(FileHandle in, FileHandle out) throws IOException{ + Map map = readMap(in, true); + + String waves = map.tags.get("waves", "[]"); + Array groups = new Array<>(json.fromJson(SpawnGroup[].class, waves)); + + Tile[][] tiles = world.createTiles(map.width, map.height); + for(int x = 0; x < map.width; x++){ + for(int y = 0; y < map.height; y++){ + tiles[x][y] = new CachedTile(); + } + } + state.rules.spawns = groups; + readTiles(map, tiles); + MapIO.writeMap(out, map); + } + + public static Map readMap(FileHandle file, boolean custom) throws IOException{ + try(DataInputStream stream = new DataInputStream(file.read(1024))){ + StringMap tags = new StringMap(); + + //meta is uncompressed + int version = stream.readInt(); + int build = stream.readInt(); + short width = stream.readShort(), height = stream.readShort(); + byte tagAmount = stream.readByte(); + + for(int i = 0; i < tagAmount; i++){ + String name = stream.readUTF(); + String value = stream.readUTF(); + tags.put(name, value); + } + + return new Map(file, width, height, tags, custom, version, build); + } + } + + public static void readTiles(Map map, Tile[][] tiles) throws IOException{ + readTiles(map, (x, y) -> tiles[x][y]); + } + + public static void readTiles(Map map, TileProvider tiles) throws IOException{ + readTiles(map.file, map.width, map.height, tiles); + } + + private static void readTiles(FileHandle file, int width, int height, Tile[][] tiles) throws IOException{ + readTiles(file, width, height, (x, y) -> tiles[x][y]); + } + + private static void readTiles(FileHandle file, int width, int height, TileProvider tiles) throws IOException{ + try(BufferedInputStream input = file.read(bufferSize)){ + + //read map + { + DataInputStream stream = new DataInputStream(input); + + stream.readInt(); //version + stream.readInt(); //build + stream.readInt(); //width + height + byte tagAmount = stream.readByte(); + + for(int i = 0; i < tagAmount; i++){ + stream.readUTF(); //key + stream.readUTF(); //val + } + } + + try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){ + + try{ + byte mapped = stream.readByte(); + IntMap idmap = new IntMap<>(); + IntMap namemap = new IntMap<>(); + + for(int i = 0; i < mapped; i++){ + byte type = stream.readByte(); + short total = stream.readShort(); + + for(int j = 0; j < total; j++){ + String name = stream.readUTF(); + if(type == 1){ + Block res = content.getByName(ContentType.block, fallback.get(name, name)); + idmap.put(j, res == null ? Blocks.air : res); + namemap.put(j, fallback.get(name, name)); + } + } + } + + //read floor and create tiles first + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; + int floorid = stream.readUnsignedByte(); + int oreid = stream.readUnsignedByte(); + int consecutives = stream.readUnsignedByte(); + + Tile tile = tiles.get(x, y); + tile.setFloor((Floor)idmap.get(floorid)); + tile.setOverlay(idmap.get(oreid)); + + for(int j = i + 1; j < i + 1 + consecutives; j++){ + int newx = j % width, newy = j / width; + Tile newTile = tiles.get(newx, newy); + newTile.setFloor((Floor)idmap.get(floorid)); + newTile.setOverlay(idmap.get(oreid)); + } + + i += consecutives; + } + + //read blocks + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; + int id = stream.readUnsignedByte(); + Block block = idmap.get(id); + if(block == null) block = Blocks.air; + + Tile tile = tiles.get(x, y); + //the spawn block is saved in the block tile layer in older maps, shift it to the overlay + if(block != Blocks.spawn){ + tile.setBlock(block); + }else{ + tile.setOverlay(block); + } + + if(namemap.get(id, "").equals("part")){ + stream.readByte(); //link + }else if(tile.entity != null){ + byte tr = stream.readByte(); + stream.readShort(); //read health (which is actually irrelevant) + + byte team = Pack.leftByte(tr); + byte rotation = Pack.rightByte(tr); + + tile.setTeam(Team.all[team]); + tile.entity.health = tile.block().health; + tile.rotation(rotation); + + if(tile.block() == Blocks.liquidSource || tile.block() == Blocks.unloader || tile.block() == Blocks.sorter){ + stream.readByte(); //these blocks have an extra config byte, read it + } + }else{ //no entity/part, read consecutives + int consecutives = stream.readUnsignedByte(); + + for(int j = i + 1; j < i + 1 + consecutives; j++){ + int newx = j % width, newy = j / width; + tiles.get(newx, newy).setBlock(block); + } + + i += consecutives; + } + } + + }finally{ + content.setTemporaryMapper(null); + } + } + } + } + + /** Reads a pixmap in the 3.5 pixmap format. */ + public static void readPixmap(Pixmap pixmap, Tile[][] tiles){ + for(int x = 0; x < pixmap.getWidth(); x++){ + for(int y = 0; y < pixmap.getHeight(); y++){ + int color = pixmap.getPixel(x, pixmap.getHeight() - 1 - y); + LegacyBlock block = LegacyColorMapper.get(color); + Tile tile = tiles[x][y]; + + tile.setFloor(block.floor); + tile.setBlock(block.wall); + if(block.ore != null) tile.setOverlay(block.ore); + + //place core + if(color == Color.rgba8888(Color.GREEN)){ + for(int dx = 0; dx < 3; dx++){ + for(int dy = 0; dy < 3; dy++){ + int worldx = dx - 1 + x; + int worldy = dy - 1 + y; + + //multiblock parts + if(Structs.inBounds(worldx, worldy, pixmap.getWidth(), pixmap.getHeight())){ + Tile write = tiles[worldx][worldy]; + write.setBlock(BlockPart.get(dx - 1, dy - 1)); + write.setTeam(Team.blue); + } + } + } + + //actual core parts + tile.setBlock(Blocks.coreShard); + tile.setTeam(Team.blue); + } + } + } + } +} diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index 407438e698..8daf1a7e2a 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -1,53 +1,27 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.IntIntMap; -import io.anuke.arc.collection.ObjectMap; -import io.anuke.arc.collection.ObjectMap.Entry; +import io.anuke.arc.collection.StringMap; import io.anuke.arc.files.FileHandle; import io.anuke.arc.graphics.Color; import io.anuke.arc.graphics.Pixmap; import io.anuke.arc.graphics.Pixmap.Format; -import io.anuke.arc.math.Mathf; -import io.anuke.arc.util.*; +import io.anuke.arc.util.io.CounterInputStream; import io.anuke.mindustry.content.Blocks; -import io.anuke.mindustry.game.*; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.Version; import io.anuke.mindustry.maps.Map; -import io.anuke.mindustry.type.ContentType; -import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.*; -import io.anuke.mindustry.world.LegacyColorMapper.LegacyBlock; -import io.anuke.mindustry.world.blocks.BlockPart; -import io.anuke.mindustry.world.blocks.Floor; +import io.anuke.mindustry.world.blocks.storage.CoreBlock; import java.io.*; -import java.util.Arrays; -import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; -import static io.anuke.mindustry.Vars.bufferSize; -import static io.anuke.mindustry.Vars.content; +import static io.anuke.mindustry.Vars.*; /** Reads and writes map files. */ +//TODO does this class even need to exist??? move to Maps? public class MapIO{ - public static final int version = 1; - private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; - private static ObjectMap missingBlocks; - - private static void initBlocks(){ - if(missingBlocks != null) return; - - //only for legacy maps - missingBlocks = ObjectMap.of( - "stained-stone", Blocks.shale, - "stained-stone-red", Blocks.shale, - "stained-stone-yellow", Blocks.shale, - "stained-rocks", Blocks.shaleRocks, - "stained-boulder", Blocks.shaleBoulder, - "stained-rocks-red", Blocks.shaleRocks, - "stained-rocks-yellow", Blocks.shaleRocks - ); - } public static boolean isImage(FileHandle file){ try(InputStream stream = file.read(32)){ @@ -62,42 +36,107 @@ public class MapIO{ } } + public static Map createMap(FileHandle file, boolean custom) throws IOException{ + try(InputStream is = new InflaterInputStream(file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){ + SaveIO.readHeader(stream); + int version = stream.readInt(); + SaveVersion ver = SaveIO.getSaveWriter(version); + StringMap tags = new StringMap(); + ver.region("meta", stream, counter, in -> tags.putAll(ver.readStringMap(in))); + return new Map(file, tags.getInt("width"), tags.getInt("height"), tags, custom, version, Version.build); + } + } + + public static void writeMap(FileHandle file, Map map) throws IOException{ + try{ + SaveIO.write(file, map.tags); + }catch(Exception e){ + throw new IOException(e); + } + } + + public static void loadMap(Map map){ + SaveIO.load(map.file); + } + + public static void loadMap(Map map, WorldContext cons){ + SaveIO.load(map.file, cons); + } + public static Pixmap generatePreview(Map map) throws IOException{ - Time.mark(); - Pixmap floors = new Pixmap(map.width, map.height, Format.RGBA8888); - Pixmap walls = new Pixmap(map.width, map.height, Format.RGBA8888); - int black = Color.rgba8888(Color.BLACK); - int shade = Color.rgba8888(0f, 0f, 0f, 0.5f); - CachedTile tile = new CachedTile(){ - @Override - public void setFloor(Floor type){ - floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(type, Blocks.air, Blocks.air, getTeam())); - } + //by default, it does not have an enemy core or any other cores + map.tags.put("enemycore", "false"); + map.tags.put("othercore", "false"); - @Override - public void setOverlayID(byte b){ - if(b != 0) - floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(floor(), Blocks.air, content.block(b), getTeam())); - } + try(InputStream is = new InflaterInputStream(map.file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){ + SaveIO.readHeader(stream); + int version = stream.readInt(); + SaveVersion ver = SaveIO.getSaveWriter(version); + ver.region("meta", stream, counter, ver::readStringMap); - @Override - protected void changed(){ - super.changed(); - int c = colorFor(Blocks.air, block(), Blocks.air, getTeam()); - if(c != black){ - walls.drawPixel(x, floors.getHeight() - 1 - y, c); - floors.drawPixel(x, floors.getHeight() - 1 - y + 1, shade); + Pixmap floors = new Pixmap(map.width, map.height, Format.RGBA8888); + Pixmap walls = new Pixmap(map.width, map.height, Format.RGBA8888); + int black = Color.rgba8888(Color.BLACK); + int shade = Color.rgba8888(0f, 0f, 0f, 0.5f); + CachedTile tile = new CachedTile(){ + @Override + public void setBlock(Block type){ + super.setBlock(type); + int c = colorFor(Blocks.air, block(), Blocks.air, getTeam()); + if(c != black){ + walls.drawPixel(x, floors.getHeight() - 1 - y, c); + floors.drawPixel(x, floors.getHeight() - 1 - y + 1, shade); + } } - } - }; - readTiles(map, (x, y) -> { - tile.x = (short)x; - tile.y = (short)y; - return tile; - }); - floors.drawPixmap(walls, 0, 0); - walls.dispose(); - return floors; + + @Override + public void setTeam(Team team){ + super.setTeam(team); + if(block instanceof CoreBlock){ + if(team != defaultTeam){ + //map must have other team's cores + map.tags.put("othercore", "true"); + } + + if(team == waveTeam){ + //map must have default enemy team's core + map.tags.put("enemycore", "true"); + } + } + } + }; + + ver.region("content", stream, counter, ver::readContentHeader); + ver.region("preview_map", stream, counter, in -> ver.readMap(in, new WorldContext(){ + @Override public void resize(int width, int height){} + @Override public boolean isGenerating(){return false;} + @Override public void begin(){} + @Override public void end(){} + + @Override + public Tile tile(int x, int y){ + tile.x = (short)x; + tile.y = (short)y; + return tile; + } + + @Override + public Tile create(int x, int y, int floorID, int overlayID, int wallID){ + if(overlayID != 0){ + floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(Blocks.air, Blocks.air, content.block(overlayID), Team.none)); + }else{ + floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(content.block(floorID), Blocks.air, Blocks.air, Team.none)); + } + return tile; + } + })); + + floors.drawPixmap(walls, 0, 0); + walls.dispose(); + return floors; + }finally{ + content.setTemporaryMapper(null); + } } public static Pixmap generatePreview(Tile[][] tiles){ @@ -118,360 +157,6 @@ public class MapIO{ return Color.rgba8888(wall.solid ? wall.color : ore == Blocks.air ? floor.color : ore.color); } - public static void writeMap(FileHandle file, Map map, Tile[][] tiles) throws IOException{ - OutputStream output = file.write(false, bufferSize); - - { - DataOutputStream stream = new DataOutputStream(output); - stream.writeInt(version); - stream.writeInt(Version.build); - stream.writeShort(tiles.length); - stream.writeShort(tiles[0].length); - stream.writeByte((byte)map.tags.size); - - for(Entry entry : map.tags.entries()){ - stream.writeUTF(entry.key); - stream.writeUTF(entry.value); - } - } - - try(DataOutputStream stream = new DataOutputStream(new DeflaterOutputStream(output))){ - int width = map.width, height = map.height; - - SaveIO.getSaveWriter().writeContentHeader(stream); - - //floor first - for(int i = 0; i < tiles.length * tiles[0].length; i++){ - Tile tile = tiles[i % width][i / width]; - stream.writeByte(tile.getFloorID()); - stream.writeByte(tile.getOverlayID()); - int consecutives = 0; - - for(int j = i + 1; j < width * height && consecutives < 255; j++){ - Tile nextTile = tiles[j % width][j / width]; - - if(nextTile.getFloorID() != tile.getFloorID() || nextTile.block() != Blocks.air || nextTile.getOverlayID() != tile.getOverlayID()){ - break; - } - - consecutives++; - } - - stream.writeByte(consecutives); - i += consecutives; - } - - //then blocks - for(int i = 0; i < tiles.length * tiles[0].length; i++){ - Tile tile = tiles[i % width][i / width]; - stream.writeByte(tile.getBlockID()); - - if(tile.block() instanceof BlockPart){ - stream.writeByte(tile.getLinkByte()); - }else if(tile.entity != null){ - stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation - stream.writeShort(/*(short)tile.entity.health*/tile.block().health); //health - tile.entity.writeConfig(stream); - }else{ - //write consecutive non-entity blocks - int consecutives = 0; - - for(int j = i + 1; j < width * height && consecutives < 255; j++){ - Tile nextTile = tiles[j % width][j / width]; - - if(nextTile.block() != tile.block()){ - break; - } - - consecutives++; - } - - stream.writeByte(consecutives); - i += consecutives; - } - } - } - } - - public static Map readMap(FileHandle file, boolean custom) throws IOException{ - try(DataInputStream stream = new DataInputStream(file.read(1024))){ - ObjectMap tags = new ObjectMap<>(); - - //meta is uncompressed - int version = stream.readInt(); - if(version == 0){ - return readLegacyMap(file, custom); - } - int build = stream.readInt(); - short width = stream.readShort(), height = stream.readShort(); - byte tagAmount = stream.readByte(); - - for(int i = 0; i < tagAmount; i++){ - String name = stream.readUTF(); - String value = stream.readUTF(); - tags.put(name, value); - } - - return new Map(file, width, height, tags, custom, version, build); - } - } - - /** Reads tiles from a map, version-agnostic. */ - public static void readTiles(Map map, Tile[][] tiles) throws IOException{ - readTiles(map, (x, y) -> tiles[x][y]); - } - - /** Reads tiles from a map, version-agnostic. */ - public static void readTiles(Map map, TileProvider tiles) throws IOException{ - if(map.version == 0){ - readLegacyMmapTiles(map.file, tiles); - }else if(map.version == version){ - readTiles(map.file, map.width, map.height, tiles); - }else{ - throw new IOException("Unknown map version. What?"); - } - } - - /** Reads tiles from a map in the new build-65 format. */ - private static void readTiles(FileHandle file, int width, int height, Tile[][] tiles) throws IOException{ - readTiles(file, width, height, (x, y) -> tiles[x][y]); - } - - /** Reads tiles from a map in the new build-65 format. */ - private static void readTiles(FileHandle file, int width, int height, TileProvider tiles) throws IOException{ - try(BufferedInputStream input = file.read(bufferSize)){ - - //read map - { - DataInputStream stream = new DataInputStream(input); - - stream.readInt(); //version - stream.readInt(); //build - stream.readInt(); //width + height - byte tagAmount = stream.readByte(); - - for(int i = 0; i < tagAmount; i++){ - stream.readUTF(); //key - stream.readUTF(); //val - } - } - - try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){ - - MappableContent[][] c = SaveIO.getSaveWriter().readContentHeader(stream); - - try{ - content.setTemporaryMapper(c); - - //read floor and create tiles first - for(int i = 0; i < width * height; i++){ - int x = i % width, y = i / width; - byte floorid = stream.readByte(); - byte oreid = stream.readByte(); - int consecutives = stream.readUnsignedByte(); - - Tile tile = tiles.get(x, y); - tile.setFloor((Floor)content.block(floorid)); - tile.setOverlay(content.block(oreid)); - - for(int j = i + 1; j < i + 1 + consecutives; j++){ - int newx = j % width, newy = j / width; - Tile newTile = tiles.get(newx, newy); - newTile.setFloor((Floor)content.block(floorid)); - newTile.setOverlay(content.block(oreid)); - } - - i += consecutives; - } - - //read blocks - for(int i = 0; i < width * height; i++){ - int x = i % width, y = i / width; - Block block = content.block(stream.readByte()); - - Tile tile = tiles.get(x, y); - tile.setBlock(block); - - if(block == Blocks.part){ - tile.setLinkByte(stream.readByte()); - }else if(tile.entity != null){ - byte tr = stream.readByte(); - short health = stream.readShort(); - - byte team = Pack.leftByte(tr); - byte rotation = Pack.rightByte(tr); - - tile.setTeam(Team.all[team]); - tile.entity.health = /*health*/tile.block().health; - tile.setRotation(rotation); - - tile.entity.readConfig(stream); - }else{ //no entity/part, read consecutives - int consecutives = stream.readUnsignedByte(); - - for(int j = i + 1; j < i + 1 + consecutives; j++){ - int newx = j % width, newy = j / width; - tiles.get(newx, newy).setBlock(block); - } - - i += consecutives; - } - } - - }finally{ - content.setTemporaryMapper(null); - } - } - } - } - - //region legacy IO - - /** Reads a pixmap in the 3.5 pixmap format. */ - public static void readLegacyPixmap(Pixmap pixmap, Tile[][] tiles){ - for(int x = 0; x < pixmap.getWidth(); x++){ - for(int y = 0; y < pixmap.getHeight(); y++){ - int color = pixmap.getPixel(x, pixmap.getHeight() - 1 - y); - LegacyBlock block = LegacyColorMapper.get(color); - Tile tile = tiles[x][y]; - - tile.setFloor(block.floor); - tile.setBlock(block.wall); - if(block.ore != null) tile.setOverlay(block.ore); - - //place core - if(color == Color.rgba8888(Color.GREEN)){ - for(int dx = 0; dx < 3; dx++){ - for(int dy = 0; dy < 3; dy++){ - int worldx = dx - 1 + x; - int worldy = dy - 1 + y; - - //multiblock parts - if(Structs.inBounds(worldx, worldy, pixmap.getWidth(), pixmap.getHeight())){ - Tile write = tiles[worldx][worldy]; - write.setBlock(Blocks.part); - write.setTeam(Team.blue); - write.setLinkByte(Pack.byteByte((byte)(dx - 1 + 8), (byte)(dy - 1 + 8))); - } - } - } - - //actual core parts - tile.setBlock(Blocks.coreShard); - tile.setTeam(Team.blue); - } - } - } - } - - /** Reads a pixmap in the old 4.0 .mmap format. */ - private static void readLegacyMmapTiles(FileHandle file, Tile[][] tiles) throws IOException{ - readLegacyMmapTiles(file, (x, y) -> tiles[x][y]); - } - - /** Reads a mmap in the old 4.0 .mmap format. */ - private static void readLegacyMmapTiles(FileHandle file, TileProvider tiles) throws IOException{ - try(DataInputStream stream = new DataInputStream(file.read(bufferSize))){ - stream.readInt(); //version - byte tagAmount = stream.readByte(); - - for(int i = 0; i < tagAmount; i++){ - stream.readUTF(); //key - stream.readUTF(); //val - } - - initBlocks(); - - //block id -> real id map - IntIntMap map = new IntIntMap(); - IntIntMap oreMap = new IntIntMap(); - - short blocks = stream.readShort(); - for(int i = 0; i < blocks; i++){ - short id = stream.readShort(); - String name = stream.readUTF(); - Block block = content.getByName(ContentType.block, name); - if(block == null){ - //substitute for replacement in missingBlocks if possible - if(missingBlocks.containsKey(name)){ - block = missingBlocks.get(name); - }else if(name.startsWith("ore-")){ //an ore floor combination - String[] split = name.split("-"); - String itemName = split[1], floorName = Strings.join("-", Arrays.copyOfRange(split, 2, split.length)); - Item item = content.getByName(ContentType.item, itemName); - Block oreBlock = item == null ? null : content.getByName(ContentType.block, "ore-" + item.name); - Block floor = missingBlocks.get(floorName, content.getByName(ContentType.block, floorName)); - if(oreBlock != null && floor != null){ - oreMap.put(id, oreBlock.id); - block = floor; - }else{ - block = Blocks.air; - } - }else{ - block = Blocks.air; - } - - } - map.put(id, block.id); - } - short width = stream.readShort(), height = stream.readShort(); - - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - Tile tile = tiles.get(x, y); - byte floorb = stream.readByte(); - byte blockb = stream.readByte(); - byte link = stream.readByte(); - byte rotTeamb = stream.readByte(); - stream.readByte();//unused stuff - - tile.setFloor((Floor)content.block(map.get(floorb, 0))); - tile.setBlock(content.block(map.get(blockb, 0))); - tile.setRotation(Pack.leftByte(rotTeamb)); - if(tile.block().synthetic()){ - tile.setTeam(Team.all[Mathf.clamp(Pack.rightByte(rotTeamb), 0, Team.all.length)]); - } - - if(tile.block() == Blocks.part){ - tile.setLinkByte(link); - } - - if(oreMap.containsKey(floorb)){ - tile.setOverlay(content.block(oreMap.get(floorb, 0))); - } - } - } - } - } - - private static Map readLegacyMap(FileHandle file, boolean custom) throws IOException{ - try(DataInputStream stream = new DataInputStream(file.read(bufferSize))){ - ObjectMap tags = new ObjectMap<>(); - - int version = stream.readInt(); - if(version != 0) throw new IOException("Attempted to read non-legacy map in legacy method!"); - byte tagAmount = stream.readByte(); - - for(int i = 0; i < tagAmount; i++){ - String name = stream.readUTF(); - String value = stream.readUTF(); - tags.put(name, value); - } - - short blocks = stream.readShort(); - for(int i = 0; i < blocks; i++){ - stream.readShort(); - stream.readUTF(); - } - short width = stream.readShort(), height = stream.readShort(); - - //note that build 64 is the default build of all maps <65; while this can be inaccurate it's better than nothing - return new Map(file, width, height, tags, custom, 0, 64); - } - } - - //endregion - interface TileProvider{ Tile get(int x, int y); } diff --git a/core/src/io/anuke/mindustry/io/SaveFileReader.java b/core/src/io/anuke/mindustry/io/SaveFileReader.java new file mode 100644 index 0000000000..8384aca50e --- /dev/null +++ b/core/src/io/anuke/mindustry/io/SaveFileReader.java @@ -0,0 +1,113 @@ +package io.anuke.mindustry.io; + +import io.anuke.arc.collection.ObjectMap; +import io.anuke.arc.collection.ObjectMap.Entry; +import io.anuke.arc.collection.StringMap; +import io.anuke.arc.util.io.CounterInputStream; +import io.anuke.arc.util.io.ReusableByteOutStream; +import io.anuke.mindustry.world.WorldContext; + +import java.io.*; + +public abstract class SaveFileReader{ + protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream(); + protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput); + protected final ReusableByteOutStream byteOutputSmall = new ReusableByteOutStream(); + protected final DataOutputStream dataBytesSmall = new DataOutputStream(byteOutputSmall); + protected final ObjectMap fallback = ObjectMap.of(); + + protected void region(String name, DataInput stream, CounterInputStream counter, IORunner cons) throws IOException{ + counter.resetCount(); + int length; + try{ + length = readChunk(stream, cons); + }catch(Throwable e){ + throw new IOException("Error reading region \"" + name + "\".", e); + } + + if(length != counter.count() - 4){ + throw new IOException("Error reading region \"" + name + "\": read length mismatch. Expected: " + length + "; Actual: " + (counter.count() - 4)); + } + } + + protected void region(String name, DataOutput stream, IORunner cons) throws IOException{ + try{ + writeChunk(stream, cons); + }catch(Throwable e){ + throw new IOException("Error writing region \"" + name + "\".", e); + } + } + + public void writeChunk(DataOutput output, IORunner runner) throws IOException{ + writeChunk(output, false, runner); + } + + /** Write a chunk of input to the stream. An integer of some length is written first, followed by the data. */ + public void writeChunk(DataOutput output, boolean isByte, IORunner runner) throws IOException{ + ReusableByteOutStream dout = isByte ? byteOutputSmall : byteOutput; + //reset output position + dout.reset(); + //write the needed info + runner.accept(isByte ? dataBytesSmall : dataBytes); + int length = dout.size(); + //write length (either int or byte) followed by the output bytes + if(!isByte){ + output.writeInt(length); + }else{ + if(length > Short.MAX_VALUE){ + throw new IOException("Byte write length exceeded: " + length + " > " + Short.MAX_VALUE); + } + output.writeShort(length); + } + output.write(dout.getBytes(), 0, length); + } + + public int readChunk(DataInput input, IORunner runner) throws IOException{ + return readChunk(input, false, runner); + } + + /** Reads a chunk of some length. Use the runner for reading to catch more descriptive errors. */ + public int readChunk(DataInput input, boolean isByte, IORunner runner) throws IOException{ + int length = isByte ? input.readUnsignedShort() : input.readInt(); + runner.accept(input); + return length; + } + + public void skipRegion(DataInput input) throws IOException{ + skipRegion(input, false); + } + + /** Skip a region completely. */ + public void skipRegion(DataInput input, boolean isByte) throws IOException{ + int length = readChunk(input, isByte, t -> {}); + int skipped = input.skipBytes(length); + if(length != skipped){ + throw new IOException("Could not skip bytes. Expected length: " + length + "; Actual length: " + skipped); + } + } + + public void writeStringMap(DataOutput stream, ObjectMap map) throws IOException{ + stream.writeShort(map.size); + for(Entry entry : map.entries()){ + stream.writeUTF(entry.key); + stream.writeUTF(entry.value); + } + } + + public StringMap readStringMap(DataInput stream) throws IOException{ + StringMap map = new StringMap(); + short size = stream.readShort(); + for(int i = 0; i < size; i++){ + map.put(stream.readUTF(), stream.readUTF()); + } + return map; + } + + public abstract void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException; + + public abstract void write(DataOutputStream stream) throws IOException; + + protected interface IORunner{ + void accept(T stream) throws IOException; + } +} diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileVersion.java deleted file mode 100644 index 48e7cba779..0000000000 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ /dev/null @@ -1,258 +0,0 @@ -package io.anuke.mindustry.io; - -import io.anuke.arc.collection.Array; -import io.anuke.arc.collection.ObjectMap; -import io.anuke.arc.util.Pack; -import io.anuke.mindustry.content.Blocks; -import io.anuke.mindustry.entities.Entities; -import io.anuke.mindustry.entities.EntityGroup; -import io.anuke.mindustry.entities.traits.*; -import io.anuke.mindustry.game.*; -import io.anuke.mindustry.gen.Serialization; -import io.anuke.mindustry.type.ContentType; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; - -import java.io.*; - -import static io.anuke.mindustry.Vars.content; -import static io.anuke.mindustry.Vars.world; - -public abstract class SaveFileVersion{ - public final int version; - private final ObjectMap fallback = ObjectMap.of( - "alpha-dart-mech-pad", "dart-mech-pad" - ); - - public SaveFileVersion(int version){ - this.version = version; - } - - public SaveMeta getData(DataInputStream stream) throws IOException{ - long time = stream.readLong(); - long playtime = stream.readLong(); - int build = stream.readInt(); - - Rules rules = Serialization.readRulesStreamJson(stream); - String map = stream.readUTF(); - int wave = stream.readInt(); - return new SaveMeta(version, time, playtime, build, map, wave, rules); - } - - public void writeMap(DataOutputStream stream) throws IOException{ - //write world size - stream.writeShort(world.width()); - stream.writeShort(world.height()); - - //floor first - for(int i = 0; i < world.width() * world.height(); i++){ - Tile tile = world.tile(i % world.width(), i / world.width()); - stream.writeByte(tile.getFloorID()); - stream.writeByte(tile.getOverlayID()); - int consecutives = 0; - - for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){ - Tile nextTile = world.tile(j % world.width(), j / world.width()); - - if(nextTile.getFloorID() != tile.getFloorID() || nextTile.getOverlayID() != tile.getOverlayID()){ - break; - } - - consecutives++; - } - - stream.writeByte(consecutives); - i += consecutives; - } - - //blocks - for(int i = 0; i < world.width() * world.height(); i++){ - Tile tile = world.tile(i % world.width(), i / world.width()); - stream.writeByte(tile.getBlockID()); - - if(tile.block() == Blocks.part){ - stream.writeByte(tile.getLinkByte()); - }else if(tile.entity != null){ - stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation - stream.writeShort((short)tile.entity.health); //health - - if(tile.entity.items != null) tile.entity.items.write(stream); - if(tile.entity.power != null) tile.entity.power.write(stream); - if(tile.entity.liquids != null) tile.entity.liquids.write(stream); - if(tile.entity.cons != null) tile.entity.cons.write(stream); - - tile.entity.writeConfig(stream); - tile.entity.write(stream); - }else{ - //write consecutive non-entity blocks - int consecutives = 0; - - for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){ - Tile nextTile = world.tile(j % world.width(), j / world.width()); - - if(nextTile.block() != tile.block()){ - break; - } - - consecutives++; - } - - stream.writeByte(consecutives); - i += consecutives; - } - } - } - - public void readMap(DataInputStream stream) throws IOException{ - short width = stream.readShort(); - short height = stream.readShort(); - - world.beginMapLoad(); - - Tile[][] tiles = world.createTiles(width, height); - - //read floor and create tiles first - for(int i = 0; i < width * height; i++){ - int x = i % width, y = i / width; - byte floorid = stream.readByte(); - byte oreid = stream.readByte(); - int consecutives = stream.readUnsignedByte(); - Block ore = content.block(oreid); - - tiles[x][y] = new Tile(x, y, floorid, (byte)0); - tiles[x][y].setOverlay(ore); - - for(int j = i + 1; j < i + 1 + consecutives; j++){ - int newx = j % width, newy = j / width; - Tile newTile = new Tile(newx, newy, floorid, (byte)0); - newTile.setOverlay(ore); - tiles[newx][newy] = newTile; - } - - i += consecutives; - } - - //read blocks - for(int i = 0; i < width * height; i++){ - int x = i % width, y = i / width; - Block block = content.block(stream.readByte()); - Tile tile = tiles[x][y]; - tile.setBlock(block); - - if(block == Blocks.part){ - tile.setLinkByte(stream.readByte()); - }else if(tile.entity != null){ - byte tr = stream.readByte(); - short health = stream.readShort(); - - byte team = Pack.leftByte(tr); - byte rotation = Pack.rightByte(tr); - - tile.setTeam(Team.all[team]); - tile.entity.health = health; - tile.setRotation(rotation); - - if(tile.entity.items != null) tile.entity.items.read(stream); - if(tile.entity.power != null) tile.entity.power.read(stream); - if(tile.entity.liquids != null) tile.entity.liquids.read(stream); - if(tile.entity.cons != null) tile.entity.cons.read(stream); - - tile.entity.readConfig(stream); - tile.entity.read(stream); - }else{ - int consecutives = stream.readUnsignedByte(); - - for(int j = i + 1; j < i + 1 + consecutives; j++){ - int newx = j % width, newy = j / width; - tiles[newx][newy].setBlock(block); - } - - i += consecutives; - } - } - - content.setTemporaryMapper(null); - world.endMapLoad(); - } - - public void writeEntities(DataOutputStream stream) throws IOException{ - int groups = 0; - - for(EntityGroup group : Entities.getAllGroups()){ - if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ - groups++; - } - } - - stream.writeByte(groups); - - for(EntityGroup group : Entities.getAllGroups()){ - if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ - stream.writeInt(group.size()); - for(Entity entity : group.all()){ - stream.writeByte(((SaveTrait)entity).getTypeID()); - ((SaveTrait)entity).writeSave(stream); - } - } - } - } - - public void readEntities(DataInputStream stream) throws IOException{ - byte groups = stream.readByte(); - - for(int i = 0; i < groups; i++){ - int amount = stream.readInt(); - for(int j = 0; j < amount; j++){ - byte typeid = stream.readByte(); - SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get(); - trait.readSave(stream); - } - } - } - - public MappableContent[][] readContentHeader(DataInputStream stream) throws IOException{ - - byte mapped = stream.readByte(); - - MappableContent[][] map = new MappableContent[ContentType.values().length][0]; - - for(int i = 0; i < mapped; i++){ - ContentType type = ContentType.values()[stream.readByte()]; - short total = stream.readShort(); - map[type.ordinal()] = new MappableContent[total]; - - for(int j = 0; j < total; j++){ - String name = stream.readUTF(); - map[type.ordinal()][j] = content.getByName(type, fallback.get(name, name)); - } - } - - return map; - } - - public void writeContentHeader(DataOutputStream stream) throws IOException{ - Array[] map = content.getContentMap(); - - int mappable = 0; - for(Array arr : map){ - if(arr.size > 0 && arr.first() instanceof MappableContent){ - mappable++; - } - } - - stream.writeByte(mappable); - for(Array arr : map){ - if(arr.size > 0 && arr.first() instanceof MappableContent){ - stream.writeByte(arr.first().getContentType().ordinal()); - stream.writeShort(arr.size); - for(Content c : arr){ - stream.writeUTF(((MappableContent)c).name); - } - } - } - } - - public abstract void read(DataInputStream stream) throws IOException; - - public abstract void write(DataOutputStream stream) throws IOException; -} diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index ad6231d118..916c4ad414 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -2,39 +2,46 @@ package io.anuke.mindustry.io; import io.anuke.arc.collection.*; import io.anuke.arc.files.FileHandle; +import io.anuke.arc.util.io.CounterInputStream; +import io.anuke.arc.util.io.FastDeflaterOutputStream; import io.anuke.mindustry.Vars; import io.anuke.mindustry.io.versions.Save1; +import io.anuke.mindustry.world.WorldContext; import java.io.*; -import java.util.zip.DeflaterOutputStream; +import java.util.Arrays; import java.util.zip.InflaterInputStream; import static io.anuke.mindustry.Vars.*; -//TODO load backup meta if possible public class SaveIO{ - public static final IntArray breakingVersions = IntArray.with(47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 63); - public static final IntMap versions = new IntMap<>(); - public static final Array versionArray = Array.with(new Save1()); + /** Format header. This is the string 'MSAV' in ASCII. */ + public static final byte[] header = {77, 83, 65, 86}; + public static final IntMap versions = new IntMap<>(); + public static final Array versionArray = Array.with(new Save1()); static{ - for(SaveFileVersion version : versionArray){ + for(SaveVersion version : versionArray){ versions.put(version.version, version); } } - public static SaveFileVersion getSaveWriter(){ + public static SaveVersion getSaveWriter(){ return versionArray.peek(); } + public static SaveVersion getSaveWriter(int version){ + return versions.get(version); + } + public static void saveToSlot(int slot){ FileHandle file = fileFor(slot); boolean exists = file.exists(); - if(exists) file.moveTo(file.sibling(file.name() + "-backup." + file.extension())); + if(exists) file.moveTo(backupFileFor(file)); try{ write(fileFor(slot)); }catch(Exception e){ - if(exists) file.sibling(file.name() + "-backup." + file.extension()).moveTo(file); + if(exists) backupFileFor(file).moveTo(file); throw new RuntimeException(e); } } @@ -47,22 +54,30 @@ public class SaveIO{ return new DataInputStream(new InflaterInputStream(fileFor(slot).read(bufferSize))); } + public static DataInputStream getBackupSlotStream(int slot){ + return new DataInputStream(new InflaterInputStream(backupFileFor(fileFor(slot)).read(bufferSize))); + } + public static boolean isSaveValid(int slot){ try{ - return isSaveValid(getSlotStream(slot)); + getMeta(slot); + return true; }catch(Exception e){ return false; } } public static boolean isSaveValid(FileHandle file){ - return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize)))); + try{ + return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize)))); + }catch(Exception e){ + return false; + } } public static boolean isSaveValid(DataInputStream stream){ - try{ - getData(stream); + getMeta(stream); return true; }catch(Exception e){ e.printStackTrace(); @@ -70,15 +85,20 @@ public class SaveIO{ } } - public static SaveMeta getData(int slot){ - return getData(getSlotStream(slot)); + public static SaveMeta getMeta(int slot){ + try{ + return getMeta(getSlotStream(slot)); + }catch(Exception e){ + return getMeta(getBackupSlotStream(slot)); + } } - public static SaveMeta getData(DataInputStream stream){ + public static SaveMeta getMeta(DataInputStream stream){ try{ + readHeader(stream); int version = stream.readInt(); - SaveMeta meta = versions.get(version).getData(stream); + SaveMeta meta = versions.get(version).getMeta(stream); stream.close(); return meta; }catch(IOException e){ @@ -90,61 +110,79 @@ public class SaveIO{ return saveDirectory.child(slot + "." + Vars.saveExtension); } - public static void write(FileHandle file){ - write(new DeflaterOutputStream(file.write(false, bufferSize)){ - byte[] tmp = {0}; - - public void write(int var1) throws IOException{ - tmp[0] = (byte)(var1 & 255); - this.write(tmp, 0, 1); - } - }); + public static FileHandle backupFileFor(FileHandle file){ + return file.sibling(file.name() + "-backup." + file.extension()); } - public static void write(OutputStream os){ - DataOutputStream stream; + public static void write(FileHandle file, StringMap tags){ + write(new FastDeflaterOutputStream(file.write(false, bufferSize)), tags); + } - try{ - stream = new DataOutputStream(os); - getVersion().write(stream); - stream.close(); + public static void write(FileHandle file){ + write(file, null); + } + + public static void write(OutputStream os, StringMap tags){ + try(DataOutputStream stream = new DataOutputStream(os)){ + stream.write(header); + stream.writeInt(getVersion().version); + if(tags == null){ + getVersion().write(stream); + }else{ + getVersion().write(stream, tags); + } }catch(Exception e){ throw new RuntimeException(e); } } public static void load(FileHandle file) throws SaveException{ + load(file, world.context); + } + + public static void load(FileHandle file, WorldContext context) throws SaveException{ try{ //try and load; if any exception at all occurs - load(new InflaterInputStream(file.read(bufferSize))); + load(new InflaterInputStream(file.read(bufferSize)), context); }catch(SaveException e){ e.printStackTrace(); FileHandle backup = file.sibling(file.name() + "-backup." + file.extension()); if(backup.exists()){ - load(new InflaterInputStream(backup.read(bufferSize))); + load(new InflaterInputStream(backup.read(bufferSize)), context); }else{ throw new SaveException(e.getCause()); } } } - public static void load(InputStream is) throws SaveException{ - try(DataInputStream stream = new DataInputStream(is)){ + /** Loads from a deflated (!) input stream.*/ + public static void load(InputStream is, WorldContext context) throws SaveException{ + try(CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){ logic.reset(); + readHeader(stream); int version = stream.readInt(); - SaveFileVersion ver = versions.get(version); + SaveVersion ver = versions.get(version); - ver.read(stream); + ver.read(stream, counter, context); }catch(Exception e){ - content.setTemporaryMapper(null); throw new SaveException(e); + }finally{ + content.setTemporaryMapper(null); } } - public static SaveFileVersion getVersion(){ + public static SaveVersion getVersion(){ return versionArray.peek(); } + public static void readHeader(DataInput input) throws IOException{ + byte[] bytes = new byte[header.length]; + input.readFully(bytes); + if(!Arrays.equals(bytes, header)){ + throw new IOException("Incorrect header! Expecting: " + Arrays.toString(header) + "; Actual: " + Arrays.toString(bytes)); + } + } + public static class SaveException extends RuntimeException{ public SaveException(Throwable throwable){ super(throwable); diff --git a/core/src/io/anuke/mindustry/io/SaveVersion.java b/core/src/io/anuke/mindustry/io/SaveVersion.java new file mode 100644 index 0000000000..f46853dcf5 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/SaveVersion.java @@ -0,0 +1,279 @@ +package io.anuke.mindustry.io; + +import io.anuke.arc.collection.Array; +import io.anuke.arc.collection.StringMap; +import io.anuke.arc.util.Time; +import io.anuke.arc.util.io.CounterInputStream; +import io.anuke.mindustry.entities.Entities; +import io.anuke.mindustry.entities.EntityGroup; +import io.anuke.mindustry.entities.traits.*; +import io.anuke.mindustry.game.*; +import io.anuke.mindustry.maps.Map; +import io.anuke.mindustry.type.ContentType; +import io.anuke.mindustry.world.*; + +import java.io.*; + +import static io.anuke.mindustry.Vars.*; + +public abstract class SaveVersion extends SaveFileReader{ + public final int version; + + public SaveVersion(int version){ + this.version = version; + } + + public SaveMeta getMeta(DataInput stream) throws IOException{ + stream.readInt(); //length of data, doesn't matter here + StringMap map = readStringMap(stream); + return new SaveMeta(map.getInt("version"), map.getLong("saved"), map.getLong("playtime"), map.getInt("build"), map.get("mapname"), map.getInt("wave"), JsonIO.read(Rules.class, map.get("rules", "{}"))); + } + + @Override + public final void write(DataOutputStream stream) throws IOException{ + write(stream, new StringMap()); + } + + @Override + public final void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{ + region("meta", stream, counter, this::readMeta); + region("content", stream, counter, this::readContentHeader); + region("map", stream, counter, in -> readMap(in, context)); + region("entities", stream, counter, this::readEntities); + } + + public final void write(DataOutputStream stream, StringMap extraTags) throws IOException{ + region("meta", stream, out -> writeMeta(out, extraTags)); + region("content", stream, this::writeContentHeader); + region("map", stream, this::writeMap); + region("entities", stream, this::writeEntities); + } + + public void writeMeta(DataOutput stream, StringMap tags) throws IOException{ + writeStringMap(stream, StringMap.of( + "saved", Time.millis(), + "playtime", headless ? 0 : control.saves.getTotalPlaytime(), + "build", Version.build, + "mapname", world.getMap() == null ? "unknown" : world.getMap().name(), + "wave", state.wave, + "wavetime", state.wavetime, + "stats", JsonIO.write(state.stats), + "rules", JsonIO.write(state.rules), + "width", world.width(), + "height", world.height() + ).merge(tags)); + } + + public void readMeta(DataInput stream) throws IOException{ + StringMap map = readStringMap(stream); + + state.wave = map.getInt("wave"); + state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing); + state.stats = JsonIO.read(Stats.class, map.get("stats", "{}")); + state.rules = JsonIO.read(Rules.class, map.get("rules", "{}")); + Map worldmap = world.maps.byName(map.get("mapname", "\\\\\\")); + world.setMap(worldmap == null ? new Map(StringMap.of( + "name", map.get("mapname", "Unknown"), + "width", 1, + "height", 1 + )) : worldmap); + } + + public void writeMap(DataOutput stream) throws IOException{ + //write world size + stream.writeShort(world.width()); + stream.writeShort(world.height()); + + //floor + overlay + for(int i = 0; i < world.width() * world.height(); i++){ + Tile tile = world.tile(i % world.width(), i / world.width()); + stream.writeShort(tile.floorID()); + stream.writeShort(tile.overlayID()); + int consecutives = 0; + + for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){ + Tile nextTile = world.tile(j % world.width(), j / world.width()); + + if(nextTile.floorID() != tile.floorID() || nextTile.overlayID() != tile.overlayID()){ + break; + } + + consecutives++; + } + + stream.writeByte(consecutives); + i += consecutives; + } + + //blocks + for(int i = 0; i < world.width() * world.height(); i++){ + Tile tile = world.tile(i % world.width(), i / world.width()); + stream.writeShort(tile.blockID()); + + if(tile.entity != null){ + writeChunk(stream, true, out -> { + out.writeByte(tile.entity.version()); + tile.entity.write(out); + }); + }else{ + //write consecutive non-entity blocks + int consecutives = 0; + + for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){ + Tile nextTile = world.tile(j % world.width(), j / world.width()); + + if(nextTile.blockID() != tile.blockID()){ + break; + } + + consecutives++; + } + + stream.writeByte(consecutives); + i += consecutives; + } + } + } + + public void readMap(DataInput stream, WorldContext context) throws IOException{ + int width = stream.readUnsignedShort(); + int height = stream.readUnsignedShort(); + + boolean generating = context.isGenerating(); + + if(!generating) context.begin(); + + context.resize(width, height); + + //read floor and create tiles first + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; + short floorid = stream.readShort(); + short oreid = stream.readShort(); + int consecutives = stream.readUnsignedByte(); + + context.create(x, y, floorid, oreid, (short)0); + + for(int j = i + 1; j < i + 1 + consecutives; j++){ + int newx = j % width, newy = j / width; + context.create(newx, newy, floorid, oreid, (short)0); + } + + i += consecutives; + } + + //read blocks + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; + Block block = content.block(stream.readShort()); + Tile tile = context.tile(x, y); + tile.setBlock(block); + + if(tile.entity != null){ + readChunk(stream, true, in -> { + byte version = in.readByte(); + tile.entity.read(in, version); + }); + }else{ + int consecutives = stream.readUnsignedByte(); + + for(int j = i + 1; j < i + 1 + consecutives; j++){ + int newx = j % width, newy = j / width; + context.tile(newx, newy).setBlock(block); + } + + i += consecutives; + } + } + + content.setTemporaryMapper(null); + if(!generating) context.end(); + } + + public void writeEntities(DataOutput stream) throws IOException{ + //write entity chunk + int groups = 0; + + for(EntityGroup group : Entities.getAllGroups()){ + if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ + groups++; + } + } + + stream.writeByte(groups); + + for(EntityGroup group : Entities.getAllGroups()){ + if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ + stream.writeInt(group.size()); + for(Entity entity : group.all()){ + SaveTrait save = (SaveTrait)entity; + //each entity is a separate chunk. + writeChunk(stream, true, out -> { + out.writeByte(save.getTypeID()); + out.writeByte(save.version()); + save.writeSave(out); + }); + } + } + } + } + + public void readEntities(DataInput stream) throws IOException{ + byte groups = stream.readByte(); + + for(int i = 0; i < groups; i++){ + int amount = stream.readInt(); + for(int j = 0; j < amount; j++){ + //TODO throw exception on read fail + readChunk(stream, true, in -> { + byte typeid = in.readByte(); + byte version = in.readByte(); + SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get(); + trait.readSave(in, version); + }); + } + } + } + + public void readContentHeader(DataInput stream) throws IOException{ + + byte mapped = stream.readByte(); + + MappableContent[][] map = new MappableContent[ContentType.values().length][0]; + + for(int i = 0; i < mapped; i++){ + ContentType type = ContentType.values()[stream.readByte()]; + short total = stream.readShort(); + map[type.ordinal()] = new MappableContent[total]; + + for(int j = 0; j < total; j++){ + String name = stream.readUTF(); + map[type.ordinal()][j] = content.getByName(type, fallback.get(name, name)); + } + } + + content.setTemporaryMapper(map); + } + + public void writeContentHeader(DataOutput stream) throws IOException{ + Array[] map = content.getContentMap(); + + int mappable = 0; + for(Array arr : map){ + if(arr.size > 0 && arr.first() instanceof MappableContent){ + mappable++; + } + } + + stream.writeByte(mappable); + for(Array arr : map){ + if(arr.size > 0 && arr.first() instanceof MappableContent){ + stream.writeByte(arr.first().getContentType().ordinal()); + stream.writeShort(arr.size); + for(Content c : arr){ + stream.writeUTF(((MappableContent)c).name); + } + } + } + } +} diff --git a/core/src/io/anuke/mindustry/io/TypeIO.java b/core/src/io/anuke/mindustry/io/TypeIO.java index d785cbb1b3..c3dc0be2d3 100644 --- a/core/src/io/anuke/mindustry/io/TypeIO.java +++ b/core/src/io/anuke/mindustry/io/TypeIO.java @@ -12,6 +12,7 @@ import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest; import io.anuke.mindustry.entities.traits.ShooterTrait; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.net.Administration.TraceInfo; import io.anuke.mindustry.net.Packets.AdminAction; import io.anuke.mindustry.net.Packets.KickReason; import io.anuke.mindustry.type.*; @@ -108,12 +109,12 @@ public class TypeIO{ @WriteClass(Block.class) public static void writeBlock(ByteBuffer buffer, Block block){ - buffer.put(block.id); + buffer.putShort(block.id); } @ReadClass(Block.class) public static Block readBlock(ByteBuffer buffer){ - return content.block(buffer.get()); + return content.block(buffer.getShort()); } @WriteClass(BuildRequest[].class) @@ -123,7 +124,7 @@ public class TypeIO{ buffer.put(request.breaking ? (byte)1 : 0); buffer.putInt(Pos.get(request.x, request.y)); if(!request.breaking){ - buffer.put(request.block.id); + buffer.putShort(request.block.id); buffer.put((byte)request.rotation); } } @@ -141,7 +142,7 @@ public class TypeIO{ if(type == 1){ //remove currentRequest = new BuildRequest(Pos.x(position), Pos.y(position)); }else{ //place - byte block = buffer.get(); + short block = buffer.getShort(); byte rotation = buffer.get(); currentRequest = new BuildRequest(Pos.x(position), Pos.y(position), rotation, content.block(block)); } @@ -204,7 +205,7 @@ public class TypeIO{ @WriteClass(Mech.class) public static void writeMech(ByteBuffer buffer, Mech mech){ - buffer.put(mech.id); + buffer.put((byte)mech.id); } @ReadClass(Mech.class) @@ -214,33 +215,33 @@ public class TypeIO{ @WriteClass(Liquid.class) public static void writeLiquid(ByteBuffer buffer, Liquid liquid){ - buffer.put(liquid == null ? -1 : liquid.id); + buffer.putShort(liquid == null ? -1 : liquid.id); } @ReadClass(Liquid.class) public static Liquid readLiquid(ByteBuffer buffer){ - byte id = buffer.get(); - return id == -1 ? null : content.liquid(buffer.get()); + short id = buffer.getShort(); + return id == -1 ? null : content.liquid(id); } @WriteClass(BulletType.class) public static void writeBulletType(ByteBuffer buffer, BulletType type){ - buffer.put(type.id); + buffer.putShort(type.id); } @ReadClass(BulletType.class) public static BulletType readBulletType(ByteBuffer buffer){ - return content.getByID(ContentType.bullet, buffer.get()); + return content.getByID(ContentType.bullet, buffer.getShort()); } @WriteClass(Item.class) public static void writeItem(ByteBuffer buffer, Item item){ - buffer.put(item == null ? -1 : item.id); + buffer.putShort(item == null ? -1 : item.id); } @ReadClass(Item.class) public static Item readItem(ByteBuffer buffer){ - byte id = buffer.get(); + short id = buffer.getShort(); return id == -1 ? null : content.item(id); } @@ -281,6 +282,19 @@ public class TypeIO{ return bytes; } + @WriteClass(TraceInfo.class) + public static void writeTraceInfo(ByteBuffer buffer, TraceInfo trace){ + writeString(buffer, trace.ip); + writeString(buffer, trace.uuid); + buffer.put(trace.modded ? (byte)1 : 0); + buffer.put(trace.mobile ? (byte)1 : 0); + } + + @ReadClass(TraceInfo.class) + public static TraceInfo readTraceInfo(ByteBuffer buffer){ + return new TraceInfo(readString(buffer), readString(buffer), buffer.get() == 1, buffer.get() == 1); + } + public static void writeStringData(DataOutput buffer, String string) throws IOException{ if(string != null){ byte[] bytes = string.getBytes(charset); diff --git a/core/src/io/anuke/mindustry/io/versions/Save1.java b/core/src/io/anuke/mindustry/io/versions/Save1.java index 41563a20c9..f9ea40b8ce 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save1.java +++ b/core/src/io/anuke/mindustry/io/versions/Save1.java @@ -1,82 +1,10 @@ package io.anuke.mindustry.io.versions; -import io.anuke.arc.collection.ObjectMap; -import io.anuke.arc.util.Time; -import io.anuke.mindustry.game.*; -import io.anuke.mindustry.gen.Serialization; -import io.anuke.mindustry.io.SaveFileVersion; -import io.anuke.mindustry.maps.Map; -import io.anuke.mindustry.type.ContentType; -import io.anuke.mindustry.type.Zone; +import io.anuke.mindustry.io.SaveVersion; -import java.io.*; - -import static io.anuke.mindustry.Vars.*; - -public class Save1 extends SaveFileVersion{ +public class Save1 extends SaveVersion{ public Save1(){ super(1); } - - @Override - public void read(DataInputStream stream) throws IOException{ - stream.readLong(); //time - stream.readLong(); //total playtime - stream.readInt(); //build - - //general state - state.rules = Serialization.readRulesStreamJson(stream); - String mapname = stream.readUTF(); - Map map = world.maps.all().find(m -> m.name().equals(mapname)); - if(map == null) map = new Map(customMapDirectory.child(mapname), 1, 1, new ObjectMap<>(), true); - world.setMap(map); - state.rules.spawns = map.getWaves(); - if(content.getByID(ContentType.zone, state.rules.zone) != null){ - Rules rules = content.getByID(ContentType.zone, state.rules.zone).rules.get(); - if(rules.spawns != DefaultWaves.get()){ - state.rules.spawns = rules.spawns; - } - } - - int wave = stream.readInt(); - float wavetime = stream.readFloat(); - - state.wave = wave; - state.wavetime = wavetime; - state.stats = Serialization.readStats(stream); - world.spawner.read(stream); - - content.setTemporaryMapper(readContentHeader(stream)); - - readEntities(stream); - readMap(stream); - } - - @Override - public void write(DataOutputStream stream) throws IOException{ - //--META-- - stream.writeInt(version); //version id - stream.writeLong(Time.millis()); //last saved - stream.writeLong(headless ? 0 : control.saves.getTotalPlaytime()); //playtime - stream.writeInt(Version.build); //build - - //--GENERAL STATE-- - Serialization.writeRulesStreamJson(stream, state.rules); - stream.writeUTF(world.getMap().name()); //map name - - stream.writeInt(state.wave); //wave - stream.writeFloat(state.wavetime); //wave countdown - - Serialization.writeStats(stream, state.stats); - world.spawner.write(stream); - - writeContentHeader(stream); - - //--ENTITIES-- - - writeEntities(stream); - - writeMap(stream); - } } diff --git a/core/src/io/anuke/mindustry/maps/Map.java b/core/src/io/anuke/mindustry/maps/Map.java index 83f4d5f02d..2833ddb782 100644 --- a/core/src/io/anuke/mindustry/maps/Map.java +++ b/core/src/io/anuke/mindustry/maps/Map.java @@ -1,23 +1,18 @@ package io.anuke.mindustry.maps; import io.anuke.arc.Core; -import io.anuke.arc.collection.Array; -import io.anuke.arc.collection.ObjectMap; +import io.anuke.arc.collection.StringMap; import io.anuke.arc.files.FileHandle; import io.anuke.arc.graphics.Texture; -import io.anuke.arc.util.Log; import io.anuke.mindustry.Vars; -import io.anuke.mindustry.game.DefaultWaves; -import io.anuke.mindustry.game.SpawnGroup; -import io.anuke.mindustry.io.MapIO; - -import static io.anuke.mindustry.Vars.world; +import io.anuke.mindustry.game.Rules; +import io.anuke.mindustry.io.JsonIO; public class Map implements Comparable{ /** Whether this is a custom map. */ public final boolean custom; /** Metadata. Author description, display name, etc. */ - public final ObjectMap tags; + public final StringMap tags; /** Base file of this map. File can be named anything at all. */ public final FileHandle file; /** Format version. */ @@ -29,7 +24,7 @@ public class Map implements Comparable{ /** Build that this map was created in. -1 = unknown or custom build. */ public int build; - public Map(FileHandle file, int width, int height, ObjectMap tags, boolean custom, int version, int build){ + public Map(FileHandle file, int width, int height, StringMap tags, boolean custom, int version, int build){ this.custom = custom; this.tags = tags; this.file = file; @@ -39,26 +34,16 @@ public class Map implements Comparable{ this.build = build; } - public Map(FileHandle file, int width, int height, ObjectMap tags, boolean custom, int version){ + public Map(FileHandle file, int width, int height, StringMap tags, boolean custom, int version){ this(file, width, height, tags, custom, version, -1); } - public Map(FileHandle file, int width, int height, ObjectMap tags, boolean custom){ - this(file, width, height, tags, custom, MapIO.version); + public Map(FileHandle file, int width, int height, StringMap tags, boolean custom){ + this(file, width, height, tags, custom, -1); } - public Array getWaves(){ - if(tags.containsKey("waves")){ - try{ - return world.maps.readWaves(tags.get("waves")); - }catch(Exception e){ - Log.err("Malformed waves: {0}", tags.get("waves")); - e.printStackTrace(); - return DefaultWaves.get(); - } - }else{ - return DefaultWaves.get(); - } + public Map(StringMap tags){ + this(Vars.customMapDirectory.child(tags.get("name", "unknown")), 0, 0, tags, true); } public int getHightScore(){ @@ -70,6 +55,23 @@ public class Map implements Comparable{ Vars.data.modified(); } + /** This creates a new instance.*/ + public Rules rules(){ + return JsonIO.read(Rules.class, tags.get("rules", "{}")); + } + + /** Whether this map has a core of the enemy 'wave' team. Default: true. + * Used for checking Attack mode validity.*/ + public boolean hasEnemyCore(){ + return tags.get("enemycore", "true").equals("true"); + } + + /** Whether this map has a core of any team except the default player team. Default: true. + * Used for checking PvP mode validity.*/ + public boolean hasOtherCores(){ + return tags.get("othercore", "true").equals("true"); + } + public String author(){ return tag("author"); } diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index 015b994174..ba37fe1ad5 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -1,16 +1,14 @@ package io.anuke.mindustry.maps; import io.anuke.arc.Core; -import io.anuke.arc.collection.Array; -import io.anuke.arc.collection.ObjectMap; +import io.anuke.arc.collection.*; import io.anuke.arc.files.FileHandle; import io.anuke.arc.graphics.Texture; import io.anuke.arc.util.Disposable; import io.anuke.arc.util.Log; import io.anuke.arc.util.serialization.Json; import io.anuke.mindustry.game.SpawnGroup; -import io.anuke.mindustry.io.MapIO; -import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.io.*; import java.io.IOException; import java.io.StringWriter; @@ -52,7 +50,7 @@ public class Maps implements Disposable{ FileHandle file = Core.files.internal("maps/" + name + "." + mapExtension); try{ - return MapIO.readMap(file, false); + return MapIO.createMap(file, false); }catch(IOException e){ throw new RuntimeException(e); } @@ -81,13 +79,12 @@ public class Maps implements Disposable{ * Save a custom map to the directory. This updates all values and stored data necessary. * The tags are copied to prevent mutation later. */ - public void saveMap(ObjectMap baseTags, Tile[][] data){ + public void saveMap(ObjectMap baseTags){ try{ - ObjectMap tags = new ObjectMap<>(baseTags); + StringMap tags = new StringMap(baseTags); String name = tags.get("name"); if(name == null) throw new IllegalArgumentException("Can't save a map with no name. How did this happen?"); - //FileHandle file = customMapDirectory.child(name + "." + mapExtension); FileHandle file; //find map with the same exact display name @@ -106,11 +103,11 @@ public class Maps implements Disposable{ } //create map, write it, etc etc etc - Map map = new Map(file, data.length, data[0].length, tags, true); - MapIO.writeMap(file, map, data); + Map map = new Map(file, world.width(), world.height(), tags, true); + MapIO.writeMap(file, map); if(!headless){ - map.texture = new Texture(MapIO.generatePreview(data)); + map.texture = new Texture(MapIO.generatePreview(world.getTiles())); } maps.add(map); maps.sort(); @@ -160,6 +157,34 @@ public class Maps implements Disposable{ return str == null ? null : str.equals("[]") ? new Array<>() : Array.with(json.fromJson(SpawnGroup[].class, str)); } + public void loadLegacyMaps(){ + boolean convertedAny = false; + for(FileHandle file : customMapDirectory.list()){ + if(file.extension().equalsIgnoreCase(oldMapExtension)){ + try{ + convertedAny = true; + LegacyMapIO.convertMap(file, file.sibling(file.nameWithoutExtension() + "." + mapExtension)); + //delete old, converted file; it is no longer useful + file.delete(); + Log.info("Converted file {0}", file); + }catch(Exception e){ + //rename the file to a 'mmap_conversion_failed' extension to keep it there just in case + //but don't delete it + file.copyTo(file.sibling(file.name() + "_conversion_failed")); + file.delete(); + Log.err(e); + } + } + } + + //free up any potential memory that was used up during conversion + if(convertedAny){ + world.createTiles(1, 1); + //reload maps to load the converted ones + reload(); + } + } + /** Find a new filename to put a map to. */ private FileHandle findFile(){ //find a map name that isn't used. @@ -171,7 +196,7 @@ public class Maps implements Disposable{ } private void loadMap(FileHandle file, boolean custom) throws IOException{ - Map map = MapIO.readMap(file, custom); + Map map = MapIO.createMap(file, custom); if(map.name() == null){ throw new IOException("Map name cannot be empty! File: " + file); diff --git a/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java b/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java index 920596c020..375af9543a 100644 --- a/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java @@ -156,8 +156,7 @@ public abstract class BasicGenerator extends RandomGenerator{ block = tiles[x][y].block(); ore = tiles[x][y].overlay(); r.accept(x, y); - tiles[x][y] = new Tile(x, y, floor.id, block.id); - tiles[x][y].setOverlay(ore); + tiles[x][y] = new Tile(x, y, floor.id, ore.id, block.id); } } } @@ -200,7 +199,7 @@ public abstract class BasicGenerator extends RandomGenerator{ Tile child = tiles[newx][newy]; if(!closed.get(child.x, child.y)){ closed.set(child.x, child.y); - child.setRotation(child.relativeTo(next.x, next.y)); + child.rotation(child.relativeTo(next.x, next.y)); costs.put(child.pos(), th.cost(child) + baseCost); queue.add(child); } @@ -215,7 +214,7 @@ public abstract class BasicGenerator extends RandomGenerator{ Tile current = end; while(current != start){ out.add(current); - Point2 p = Geometry.d4(current.getRotation()); + Point2 p = Geometry.d4(current.rotation()); current = tiles[current.x + p.x][current.y + p.y]; } diff --git a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java index 6d860232c9..643f4c8b12 100644 --- a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java @@ -12,13 +12,10 @@ import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Loadout; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.Floor; -import io.anuke.mindustry.world.blocks.StaticWall; +import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.storage.CoreBlock; import io.anuke.mindustry.world.blocks.storage.StorageBlock; -import java.io.IOException; - import static io.anuke.mindustry.Vars.world; public class MapGenerator extends Generator{ @@ -70,115 +67,115 @@ public class MapGenerator extends Generator{ @Override public void generate(Tile[][] tiles){ - try{ - for(int x = 0; x < width; x++){ - for(int y = 0; y < height; y++){ - tiles[x][y] = new Tile(x, y); + for(int x = 0; x < width; x++){ + for(int y = 0; y < height; y++){ + tiles[x][y] = new Tile(x, y); + } + } + + MapIO.loadMap(map); + Array players = new Array<>(); + Array enemies = new Array<>(); + + for(int x = 0; x < width; x++){ + for(int y = 0; y < height; y++){ + if(tiles[x][y].block() instanceof CoreBlock){ + players.add(new Point2(x, y)); + tiles[x][y].setBlock(Blocks.air); + } + + if(tiles[x][y].overlay() == Blocks.spawn && enemySpawns != -1){ + enemies.add(new Point2(x, y)); + tiles[x][y].setOverlay(Blocks.air); + } + + if(tiles[x][y].block() instanceof BlockPart){ + tiles[x][y].setBlock(Blocks.air); } } + } - MapIO.readTiles(map, tiles); - Array players = new Array<>(); - Array enemies = new Array<>(); + Simplex simplex = new Simplex(Mathf.random(99999)); - for(int x = 0; x < width; x++){ - for(int y = 0; y < height; y++){ - if(tiles[x][y].block() instanceof CoreBlock){ - players.add(new Point2(x, y)); - tiles[x][y].setBlock(Blocks.air); - } + for(int x = 0; x < width; x++){ + for(int y = 0; y < height; y++){ + final double scl = 10; + Tile tile = tiles[x][y]; + int newX = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x, y) * distortion + x), 0, width - 1); + int newY = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x + 9999, y + 9999) * distortion + y), 0, height - 1); - if(tiles[x][y].block() == Blocks.spawn && enemySpawns != -1){ - enemies.add(new Point2(x, y)); - tiles[x][y].setBlock(Blocks.air); - } - - if(tiles[x][y].block() == Blocks.part){ - tiles[x][y].setBlock(Blocks.air); - } + if(((tile.block() instanceof StaticWall + && tiles[newX][newY].block() instanceof StaticWall) + || (tile.block() == Blocks.air && !tiles[newX][newY].block().synthetic()) + || (tiles[newX][newY].block() == Blocks.air && tile.block() instanceof StaticWall))){ + tile.setBlock(tiles[newX][newY].block()); } - } - Simplex simplex = new Simplex(Mathf.random(99999)); - - for(int x = 0; x < width; x++){ - for(int y = 0; y < height; y++){ - final double scl = 10; - Tile tile = tiles[x][y]; - int newX = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x, y) * distortion + x), 0, width - 1); - int newY = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x + 9999, y + 9999) * distortion + y), 0, height - 1); - - if(((tile.block() instanceof StaticWall - && tiles[newX][newY].block() instanceof StaticWall) - || (tile.block() == Blocks.air && !tiles[newX][newY].block().synthetic()) - || (tiles[newX][newY].block() == Blocks.air && tile.block() instanceof StaticWall)) && tiles[newX][newY].block() != Blocks.spawn && tile.block() != Blocks.spawn){ - tile.setBlock(tiles[newX][newY].block()); - } - - if(distortFloor){ - tile.setFloor(tiles[newX][newY].floor()); + if(distortFloor){ + tile.setFloor(tiles[newX][newY].floor()); + if(tiles[newX][newY].overlay() != Blocks.spawn && tile.overlay() != Blocks.spawn){ tile.setOverlay(tiles[newX][newY].overlay()); } + } - for(Decoration decor : decorations){ - if(x > 0 && y > 0 && (tiles[x - 1][y].block() == decor.wall || tiles[x][y - 1].block() == decor.wall)){ - continue; - } - - if(tile.block() == Blocks.air && !(decor.wall instanceof Floor) && tile.floor() == decor.floor && Mathf.chance(decor.chance)){ - tile.setBlock(decor.wall); - }else if(tile.floor() == decor.floor && decor.wall instanceof Floor && Mathf.chance(decor.chance)){ - tile.setFloor((Floor)decor.wall); - } + for(Decoration decor : decorations){ + if(x > 0 && y > 0 && (tiles[x - 1][y].block() == decor.wall || tiles[x][y - 1].block() == decor.wall)){ + continue; } - if(tile.block() instanceof StorageBlock && !(tile.block() instanceof CoreBlock) && world.getZone() != null){ - for(Item item : world.getZone().resources){ - if(Mathf.chance(0.3)){ - tile.entity.items.add(item, Math.min(Mathf.random(500), tile.block().itemCapacity)); - } + if(tile.block() == Blocks.air && !(decor.wall instanceof Floor) && tile.floor() == decor.floor && Mathf.chance(decor.chance)){ + tile.setBlock(decor.wall); + }else if(tile.floor() == decor.floor && decor.wall instanceof Floor && Mathf.chance(decor.chance)){ + tile.setFloor((Floor)decor.wall); + } + } + + if(tile.block() instanceof StorageBlock && !(tile.block() instanceof CoreBlock) && world.getZone() != null){ + for(Item item : world.getZone().resources){ + if(Mathf.chance(0.3)){ + tile.entity.items.add(item, Math.min(Mathf.random(500), tile.block().itemCapacity)); } } } } + } - if(enemySpawns != -1){ - if(enemySpawns > enemies.size){ - throw new IllegalArgumentException("Enemy spawn pool greater than map spawn number for map: " + mapName); - } + if(enemySpawns != -1){ + if(enemySpawns > enemies.size){ + throw new IllegalArgumentException("Enemy spawn pool greater than map spawn number for map: " + mapName); + } - enemies.shuffle(); - for(int i = 0; i < enemySpawns; i++){ - Point2 point = enemies.get(i); - tiles[point.x][point.y].setBlock(Blocks.spawn); + enemies.shuffle(); + for(int i = 0; i < enemySpawns; i++){ + Point2 point = enemies.get(i); + tiles[point.x][point.y].setOverlay(Blocks.spawn); - int rad = 10, frad = 12; + int rad = 10, frad = 12; - for(int x = -rad; x <= rad; x++){ - for(int y = -rad; y <= rad; y++){ - int wx = x + point.x, wy = y + point.y; - double dst = Mathf.dst(x, y); - if(dst < frad && Structs.inBounds(wx, wy, tiles) && (dst <= rad || Mathf.chance(0.5))){ - Tile tile = tiles[wx][wy]; + for(int x = -rad; x <= rad; x++){ + for(int y = -rad; y <= rad; y++){ + int wx = x + point.x, wy = y + point.y; + double dst = Mathf.dst(x, y); + if(dst < frad && Structs.inBounds(wx, wy, tiles) && (dst <= rad || Mathf.chance(0.5))){ + Tile tile = tiles[wx][wy]; + if(tile.overlay() != Blocks.spawn){ tile.clearOverlay(); } } } } } - - Point2 core = players.random(); - if(core == null){ - throw new IllegalArgumentException("All zone maps must have a core."); - } - - loadout.setup(core.x, core.y); - - world.prepareTiles(tiles); - world.setMap(map); - }catch(IOException e){ - throw new RuntimeException(e); } + + Point2 core = players.random(); + if(core == null){ + throw new IllegalArgumentException("All zone maps must have a core."); + } + + loadout.setup(core.x, core.y); + + world.prepareTiles(tiles); + world.setMap(map); } public static class Decoration{ diff --git a/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java b/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java index bacb5cbaea..23591e86df 100644 --- a/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java @@ -1,12 +1,11 @@ package io.anuke.mindustry.maps.generators; -import io.anuke.arc.collection.ObjectMap; +import io.anuke.arc.collection.StringMap; import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import static io.anuke.mindustry.Vars.customMapDirectory; import static io.anuke.mindustry.Vars.world; public abstract class RandomGenerator extends Generator{ @@ -26,14 +25,13 @@ public abstract class RandomGenerator extends Generator{ block = Blocks.air; ore = Blocks.air; generate(x, y); - tiles[x][y] = new Tile(x, y, floor.id, block.id); - tiles[x][y].setOverlay(ore); + tiles[x][y] = new Tile(x, y, floor.id, ore.id, block.id); } } decorate(tiles); - world.setMap(new Map(customMapDirectory.child("generated"), 0, 0, new ObjectMap<>(), true)); + world.setMap(new Map(new StringMap())); } public abstract void decorate(Tile[][] tiles); diff --git a/core/src/io/anuke/mindustry/maps/zonegen/DesertWastesGenerator.java b/core/src/io/anuke/mindustry/maps/zonegen/DesertWastesGenerator.java index 95ee943df3..31ed7981dd 100644 --- a/core/src/io/anuke/mindustry/maps/zonegen/DesertWastesGenerator.java +++ b/core/src/io/anuke/mindustry/maps/zonegen/DesertWastesGenerator.java @@ -41,7 +41,7 @@ public class DesertWastesGenerator extends BasicGenerator{ overlay(tiles, Blocks.sand, Blocks.pebbles, 0.15f, 5, 0.8f, 30f, 0.62f); //scatter(tiles, Blocks.sandRocks, Blocks.creeptree, 1f); - tiles[endX][endY].setBlock(Blocks.spawn); + tiles[endX][endY].setOverlay(Blocks.spawn); loadout.setup(spawnX, spawnY); } } diff --git a/core/src/io/anuke/mindustry/maps/zonegen/OvergrowthGenerator.java b/core/src/io/anuke/mindustry/maps/zonegen/OvergrowthGenerator.java index c5317ac011..0fec4b4879 100644 --- a/core/src/io/anuke/mindustry/maps/zonegen/OvergrowthGenerator.java +++ b/core/src/io/anuke/mindustry/maps/zonegen/OvergrowthGenerator.java @@ -37,7 +37,7 @@ public class OvergrowthGenerator extends BasicGenerator{ noise(tiles, Blocks.darksandTaintedWater, Blocks.duneRocks, 4, 0.7f, 120f, 0.64f); //scatter(tiles, Blocks.sporePine, Blocks.whiteTreeDead, 1f); - tiles[endX][endY].setBlock(Blocks.spawn); + tiles[endX][endY].setOverlay(Blocks.spawn); loadout.setup(spawnX, spawnY); } } diff --git a/core/src/io/anuke/mindustry/net/Administration.java b/core/src/io/anuke/mindustry/net/Administration.java index 81f729fd67..c1edec3ecb 100644 --- a/core/src/io/anuke/mindustry/net/Administration.java +++ b/core/src/io/anuke/mindustry/net/Administration.java @@ -14,7 +14,8 @@ public class Administration{ public Administration(){ Core.settings.defaults( - "strict", true + "strict", true, + "servername", "Server" ); load(); @@ -285,4 +286,16 @@ public class Administration{ } } + public static class TraceInfo{ + public String ip, uuid; + public boolean modded, mobile; + + public TraceInfo(String ip, String uuid, boolean modded, boolean mobile){ + this.ip = ip; + this.uuid = uuid; + this.modded = modded; + this.mobile = mobile; + } + } + } diff --git a/core/src/io/anuke/mindustry/net/CrashSender.java b/core/src/io/anuke/mindustry/net/CrashSender.java index b6e37c0dec..89a38ed5a5 100644 --- a/core/src/io/anuke/mindustry/net/CrashSender.java +++ b/core/src/io/anuke/mindustry/net/CrashSender.java @@ -26,7 +26,6 @@ public class CrashSender{ exception.printStackTrace(); //don't create crash logs for me (anuke) or custom builds, as it's expected - //TODO maybe custom builds such as bleeding edge in certain cases if(System.getProperty("user.name").equals("anuke") || Version.build == -1) return; //attempt to load version regardless diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index 2239a20f05..bda5a91628 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -1,19 +1,18 @@ package io.anuke.mindustry.net; -import io.anuke.arc.collection.ObjectMap; -import io.anuke.arc.collection.ObjectMap.Entry; +import io.anuke.arc.Core; import io.anuke.arc.util.Time; import io.anuke.mindustry.entities.Entities; import io.anuke.mindustry.entities.type.Player; -import io.anuke.mindustry.game.*; -import io.anuke.mindustry.game.Teams.TeamData; -import io.anuke.mindustry.gen.Serialization; +import io.anuke.mindustry.game.Rules; +import io.anuke.mindustry.game.Version; +import io.anuke.mindustry.io.JsonIO; import io.anuke.mindustry.io.SaveIO; import io.anuke.mindustry.maps.Map; -import io.anuke.mindustry.world.Tile; import java.io.*; import java.nio.ByteBuffer; +import java.util.Arrays; import static io.anuke.mindustry.Vars.*; @@ -22,45 +21,16 @@ public class NetworkIO{ public static void writeWorld(Player player, OutputStream os){ try(DataOutputStream stream = new DataOutputStream(os)){ - //--GENERAL STATE-- - Serialization.writeRules(stream, state.rules); - stream.writeUTF(world.getMap().name()); //map name + stream.writeUTF(JsonIO.write(state.rules)); + SaveIO.getSaveWriter().writeStringMap(stream, world.getMap().tags); - //write tags - ObjectMap tags = world.getMap().tags; - stream.writeByte(tags.size); - for(Entry entry : tags.entries()){ - stream.writeUTF(entry.key); - stream.writeUTF(entry.value); - } - - stream.writeInt(state.wave); //wave - stream.writeFloat(state.wavetime); //wave countdown + stream.writeInt(state.wave); + stream.writeFloat(state.wavetime); stream.writeInt(player.id); player.write(stream); - world.spawner.write(stream); SaveIO.getSaveWriter().writeMap(stream); - - stream.write(Team.all.length); - - //write team data - for(Team team : Team.all){ - TeamData data = state.teams.get(team); - stream.writeByte(team.ordinal()); - - stream.writeByte(data.enemies.size()); - for(Team enemy : data.enemies){ - stream.writeByte(enemy.ordinal()); - } - - stream.writeByte(data.cores.size); - for(Tile tile : data.cores){ - stream.writeInt(tile.pos()); - } - } - }catch(IOException e){ throw new RuntimeException(e); } @@ -70,25 +40,11 @@ public class NetworkIO{ try(DataInputStream stream = new DataInputStream(is)){ Time.clear(); + state.rules = JsonIO.read(Rules.class, stream.readUTF()); + world.setMap(new Map(SaveIO.getSaveWriter().readStringMap(stream))); - //general state - state.rules = Serialization.readRules(stream); - String map = stream.readUTF(); - - ObjectMap tags = new ObjectMap<>(); - - byte tagSize = stream.readByte(); - for(int i = 0; i < tagSize; i++){ - String key = stream.readUTF(); - String value = stream.readUTF(); - tags.put(key, value); - } - - int wave = stream.readInt(); - float wavetime = stream.readFloat(); - - state.wave = wave; - state.wavetime = wavetime; + state.wave = stream.readInt(); + state.wavetime = stream.readFloat(); Entities.clear(); int id = stream.readInt(); @@ -97,82 +53,60 @@ public class NetworkIO{ player.resetID(id); player.add(); - //map - world.spawner.read(stream); - SaveIO.getSaveWriter().readMap(stream); - world.setMap(new Map(customMapDirectory.child(map), 0, 0, new ObjectMap<>(), true)); - - state.teams = new Teams(); - - byte teams = stream.readByte(); - for(int i = 0; i < teams; i++){ - Team team = Team.all[stream.readByte()]; - - byte enemies = stream.readByte(); - Team[] enemyArr = new Team[enemies]; - for(int j = 0; j < enemies; j++){ - enemyArr[j] = Team.all[stream.readByte()]; - } - - state.teams.add(team, enemyArr); - - byte cores = stream.readByte(); - - for(int j = 0; j < cores; j++){ - state.teams.get(team).cores.add(world.tile(stream.readInt())); - } - } - + SaveIO.getSaveWriter().readMap(stream, world.context); }catch(IOException e){ throw new RuntimeException(e); } } public static ByteBuffer writeServerData(){ - int maxlen = 32; - - String host = (headless ? "Server" : player.name); + String name = (headless ? Core.settings.getString("servername") : player.name); String map = world.getMap() == null ? "None" : world.getMap().name(); - host = host.substring(0, Math.min(host.length(), maxlen)); - map = map.substring(0, Math.min(map.length(), maxlen)); + ByteBuffer buffer = ByteBuffer.allocate(256); - ByteBuffer buffer = ByteBuffer.allocate(128); - - buffer.put((byte)host.getBytes(charset).length); - buffer.put(host.getBytes(charset)); - - buffer.put((byte)map.getBytes(charset).length); - buffer.put(map.getBytes(charset)); + writeString(buffer, name, 100); + writeString(buffer, map); buffer.putInt(playerGroup.size()); buffer.putInt(state.wave); buffer.putInt(Version.build); - buffer.put((byte)Version.type.getBytes(charset).length); - buffer.put(Version.type.getBytes(charset)); + writeString(buffer, Version.type); + //TODO additional information: + // - gamemode ID/name (just pick the closest one?) return buffer; } public static Host readServerData(String hostAddress, ByteBuffer buffer){ - byte hlength = buffer.get(); - byte[] hb = new byte[hlength]; - buffer.get(hb); - - byte mlength = buffer.get(); - byte[] mb = new byte[mlength]; - buffer.get(mb); - - String host = new String(hb, charset); - String map = new String(mb, charset); - + String host = readString(buffer); + String map = readString(buffer); int players = buffer.getInt(); int wave = buffer.getInt(); int version = buffer.getInt(); - byte tlength = buffer.get(); - byte[] tb = new byte[tlength]; - buffer.get(tb); - String vertype = new String(tb, charset); + String vertype = readString(buffer); return new Host(host, hostAddress, map, wave, players, version, vertype); } + + private static void writeString(ByteBuffer buffer, String string, int maxlen){ + byte[] bytes = string.getBytes(charset); + //truncating this way may lead to wierd encoding errors at the ends of strings... + if(bytes.length > maxlen){ + bytes = Arrays.copyOfRange(bytes, 0, maxlen); + } + + buffer.put((byte)bytes.length); + buffer.put(bytes); + } + + private static void writeString(ByteBuffer buffer, String string){ + writeString(buffer, string, 32); + } + + private static String readString(ByteBuffer buffer){ + short length = (short)(buffer.get() & 0xff); + byte[] bytes = new byte[length]; + buffer.get(bytes); + return new String(bytes, charset); + } } diff --git a/core/src/io/anuke/mindustry/type/Loadout.java b/core/src/io/anuke/mindustry/type/Loadout.java index c322488fc4..aebdaf03bc 100644 --- a/core/src/io/anuke/mindustry/type/Loadout.java +++ b/core/src/io/anuke/mindustry/type/Loadout.java @@ -71,7 +71,7 @@ public class Loadout extends Content{ int ry = Pos.y(entry.key); Tile tile = world.tile(x + rx, y + ry); world.setBlock(tile, entry.value.block, defaultTeam); - tile.setRotation((byte)entry.value.rotation); + tile.rotation((byte)entry.value.rotation); if(entry.value.ore != null){ for(Tile t : tile.getLinkedTiles(outArray)){ t.setOverlay(entry.value.ore); diff --git a/core/src/io/anuke/mindustry/type/Weapon.java b/core/src/io/anuke/mindustry/type/Weapon.java index fceb8d22fc..7d2a22c98f 100644 --- a/core/src/io/anuke/mindustry/type/Weapon.java +++ b/core/src/io/anuke/mindustry/type/Weapon.java @@ -88,13 +88,14 @@ public class Weapon{ public static void shootDirect(ShooterTrait shooter, float offsetX, float offsetY, float rotation, boolean left){ float x = shooter.getX() + offsetX; float y = shooter.getY() + offsetY; + float baseX = shooter.getX(), baseY = shooter.getY(); Weapon weapon = shooter.getWeapon(); sequenceNum = 0; if(weapon.shotDelay > 0.01f){ Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> { - Time.run(sequenceNum * weapon.shotDelay, () -> weapon.bullet(shooter, x, y, f + Mathf.range(weapon.inaccuracy))); + Time.run(sequenceNum * weapon.shotDelay, () -> weapon.bullet(shooter, x + shooter.getX() - baseX, y + shooter.getY() - baseY, f + Mathf.range(weapon.inaccuracy))); sequenceNum++; }); }else{ diff --git a/core/src/io/anuke/mindustry/type/WeatherEvent.java b/core/src/io/anuke/mindustry/type/WeatherEvent.java index e2aaa5f778..385124a1b9 100644 --- a/core/src/io/anuke/mindustry/type/WeatherEvent.java +++ b/core/src/io/anuke/mindustry/type/WeatherEvent.java @@ -2,7 +2,7 @@ package io.anuke.mindustry.type; import io.anuke.mindustry.game.Content; -//TODO implement-- should it even be content? +//currently unimplemented, see trello for implementation plans public class WeatherEvent extends Content{ public final String name; diff --git a/core/src/io/anuke/mindustry/ui/BorderImage.java b/core/src/io/anuke/mindustry/ui/BorderImage.java index 0e14960df0..6c7a8e54d9 100644 --- a/core/src/io/anuke/mindustry/ui/BorderImage.java +++ b/core/src/io/anuke/mindustry/ui/BorderImage.java @@ -35,6 +35,7 @@ public class BorderImage extends Image{ float scaleY = getScaleY(); Draw.color(Pal.accent); + Draw.alpha(parentAlpha); Lines.stroke(Unit.dp.scl(thickness)); Lines.rect(x + imageX, y + imageY, imageWidth * scaleX, imageHeight * scaleY); Draw.reset(); diff --git a/core/src/io/anuke/mindustry/ui/ItemsDisplay.java b/core/src/io/anuke/mindustry/ui/ItemsDisplay.java index a4ca3d6d07..684cc07aa1 100644 --- a/core/src/io/anuke/mindustry/ui/ItemsDisplay.java +++ b/core/src/io/anuke/mindustry/ui/ItemsDisplay.java @@ -13,6 +13,7 @@ import java.util.Locale; import static io.anuke.mindustry.Vars.content; import static io.anuke.mindustry.Vars.data; +/** Displays a list of items, e.g. launched items.*/ public class ItemsDisplay extends Table{ private static final NumberFormat format = NumberFormat.getNumberInstance(Locale.getDefault()); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/AboutDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/AboutDialog.java index d243e3220c..6ac0fab053 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/AboutDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/AboutDialog.java @@ -7,9 +7,7 @@ import io.anuke.arc.graphics.Color; import io.anuke.arc.scene.ui.ScrollPane; import io.anuke.arc.scene.ui.layout.Cell; import io.anuke.arc.scene.ui.layout.Table; -import io.anuke.arc.util.OS; -import io.anuke.arc.util.Strings; -import io.anuke.arc.util.Time; +import io.anuke.arc.util.*; import io.anuke.mindustry.graphics.Pal; import io.anuke.mindustry.io.Contributors; import io.anuke.mindustry.io.Contributors.Contributor; @@ -27,7 +25,10 @@ public class AboutDialog extends FloatingDialog{ super("$about.button"); if(!ios){ - Contributors.getContributors(out -> contributors = out, Throwable::printStackTrace); + shown(() -> Contributors.getContributors(out -> { + contributors = out; + Core.app.post(this::setup); + }, Throwable::printStackTrace)); } shown(this::setup); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ChangelogDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ChangelogDialog.java index 4087992daa..9bea180562 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ChangelogDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ChangelogDialog.java @@ -24,15 +24,17 @@ public class ChangelogDialog extends FloatingDialog{ cont.add("$changelog.loading"); - if(!ios && !OS.isMac){ - Changelogs.getChangelog(result -> { - versions = result; - Core.app.post(this::setup); - }, t -> { - Log.err(t); - Core.app.post(this::setup); - }); - } + shown(() -> { + if(!ios && !OS.isMac){ + Changelogs.getChangelog(result -> { + versions = result; + Core.app.post(this::setup); + }, t -> { + Log.err(t); + Core.app.post(this::setup); + }); + } + }); } void setup(){ diff --git a/core/src/io/anuke/mindustry/ui/dialogs/CustomGameDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/CustomGameDialog.java index b0cd47ee60..02444c8b06 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/CustomGameDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/CustomGameDialog.java @@ -2,28 +2,18 @@ package io.anuke.mindustry.ui.dialogs; import io.anuke.arc.Core; import io.anuke.arc.graphics.g2d.TextureRegion; -import io.anuke.arc.math.Mathf; -import io.anuke.arc.scene.event.Touchable; -import io.anuke.arc.scene.ui.ButtonGroup; import io.anuke.arc.scene.ui.ImageButton; import io.anuke.arc.scene.ui.ScrollPane; -import io.anuke.arc.scene.ui.TextButton; import io.anuke.arc.scene.ui.layout.Table; import io.anuke.arc.util.Align; import io.anuke.arc.util.Scaling; -import io.anuke.mindustry.game.Difficulty; -import io.anuke.mindustry.game.Gamemode; -import io.anuke.mindustry.game.Rules; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.ui.BorderImage; -import static io.anuke.mindustry.Vars.*; +import static io.anuke.mindustry.Vars.world; public class CustomGameDialog extends FloatingDialog{ - Difficulty difficulty = Difficulty.normal; - CustomRulesDialog dialog = new CustomRulesDialog(); - Rules rules; - Gamemode selectedGamemode; + private MapPlayDialog dialog = new MapPlayDialog(); public CustomGameDialog(){ super("$customgame"); @@ -33,9 +23,11 @@ public class CustomGameDialog extends FloatingDialog{ } void setup(){ - selectedGamemode = Gamemode.survival; - rules = selectedGamemode.get(); - + clearChildren(); + add(titleTable); + row(); + stack(cont, buttons).grow(); + buttons.bottom(); cont.clear(); Table maps = new Table(); @@ -44,63 +36,9 @@ public class CustomGameDialog extends FloatingDialog{ pane.setFadeScrollBars(false); int maxwidth = (Core.graphics.isPortrait() ? 2 : 4); - - Table selmode = new Table(); - ButtonGroup group = new ButtonGroup<>(); - selmode.add("$level.mode").colspan(4); - selmode.row(); - int i = 0; - - Table modes = new Table(); - - for(Gamemode mode : Gamemode.values()){ - modes.addButton(mode.toString(), "toggle", () -> { - selectedGamemode = mode; - rules = mode.get(); - dialog.selectedGamemode = null; - dialog.rules = null; - }).update(b -> b.setChecked(selectedGamemode == mode)).group(group).size(140f, 54f); - if(i++ % 2 == 1) modes.row(); - } - selmode.add(modes); - selmode.addButton("?", this::displayGameModeHelp).width(50f).fillY().padLeft(18f); - - cont.add(selmode); - cont.row(); - - Difficulty[] ds = Difficulty.values(); - - float s = 50f; - - Table sdif = new Table(); - - sdif.add("$setting.difficulty.name").colspan(3); - sdif.row(); - sdif.defaults().height(s + 4); - sdif.addImageButton("icon-arrow-left", 10 * 3, () -> { - difficulty = (ds[Mathf.mod(difficulty.ordinal() - 1, ds.length)]); - state.wavetime = difficulty.waveTime; - }).width(s); - - sdif.addButton("", () -> { - }) - .update(t -> { - t.setText(difficulty.toString()); - t.touchable(Touchable.disabled); - }).width(180f); - - sdif.addImageButton("icon-arrow-right", 10 * 3, () -> { - difficulty = (ds[Mathf.mod(difficulty.ordinal() + 1, ds.length)]); - state.wavetime = difficulty.waveTime; - }).width(s); - sdif.addButton("$customize", () -> dialog.show(rules, selectedGamemode)).width(140).padLeft(10); - - cont.add(sdif); - cont.row(); - float images = 146f; - i = 0; + int i = 0; maps.defaults().width(170).fillY().top().pad(4f); for(Map map : world.maps.all()){ @@ -121,10 +59,7 @@ public class CustomGameDialog extends FloatingDialog{ border.setScaling(Scaling.fit); image.replaceImage(border); - image.clicked(() -> { - hide(); - control.playMap(map, (dialog.rules == null) ? rules : dialog.rules); - }); + image.clicked(() -> dialog.show(map)); maps.add(image); @@ -137,22 +72,4 @@ public class CustomGameDialog extends FloatingDialog{ cont.add(pane).uniformX(); } - - private void displayGameModeHelp(){ - FloatingDialog d = new FloatingDialog(Core.bundle.get("mode.help.title")); - d.setFillParent(false); - Table table = new Table(); - table.defaults().pad(1f); - ScrollPane pane = new ScrollPane(table); - pane.setFadeScrollBars(false); - table.row(); - for(Gamemode mode : Gamemode.values()){ - table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(400f); - table.row(); - } - - d.cont.add(pane); - d.buttons.addButton("$ok", d::hide).size(110, 50).pad(10f); - d.show(); - } } \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java index 0fb415686c..062b85b567 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java @@ -5,7 +5,6 @@ import io.anuke.arc.graphics.Color; import io.anuke.arc.scene.ui.layout.Table; import io.anuke.arc.util.Strings; import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.game.Gamemode; import io.anuke.mindustry.game.Rules; import io.anuke.mindustry.graphics.Pal; @@ -13,8 +12,8 @@ import static io.anuke.mindustry.Vars.tilesize; public class CustomRulesDialog extends FloatingDialog{ private Table main; - public Rules rules; - public Gamemode selectedGamemode; + private Rules rules; + private Supplier resetter; public CustomRulesDialog(){ super("$mode.custom"); @@ -24,9 +23,9 @@ public class CustomRulesDialog extends FloatingDialog{ addCloseButton(); } - public void show(Rules rules, Gamemode gamemode){ + public void show(Rules rules, Supplier resetter){ this.rules = rules; - this.selectedGamemode = gamemode; + this.resetter = resetter; show(); } @@ -35,12 +34,12 @@ public class CustomRulesDialog extends FloatingDialog{ cont.pane(m -> main = m); main.margin(10f); main.addButton("$settings.reset", () -> { - rules = selectedGamemode.get(); + rules = resetter.get(); setup(); }).size(300f, 50f); main.left().defaults().fillX().left().pad(5); main.row(); - + title("$rules.title.waves"); check("$rules.waves", b -> rules.waves = b, () -> rules.waves); check("$rules.wavetimer", b -> rules.waveTimer = b, () -> rules.waveTimer, () -> rules.waves); @@ -49,8 +48,8 @@ public class CustomRulesDialog extends FloatingDialog{ number("$rules.dropzoneradius", false, f -> rules.dropZoneRadius = f * tilesize, () -> rules.dropZoneRadius / tilesize, () -> rules.waves); title("$rules.title.respawns"); - check("$rules.limitedRespawns", b -> rules.limitedRespawns= b, () -> rules.limitedRespawns); - number("$rules.respawns", true, f -> rules.respawns = (int) f, () -> rules.respawns, () -> rules.limitedRespawns); + check("$rules.limitedRespawns", b -> rules.limitedRespawns = b, () -> rules.limitedRespawns); + number("$rules.respawns", true, f -> rules.respawns = (int)f, () -> rules.respawns, () -> rules.limitedRespawns); number("$rules.respawntime", f -> rules.respawnTime = f * 60f, () -> rules.respawnTime / 60f); title("$rules.title.resourcesbuilding"); @@ -63,7 +62,7 @@ public class CustomRulesDialog extends FloatingDialog{ number("$rules.playerhealthmultiplier", f -> rules.playerHealthMultiplier = f, () -> rules.playerHealthMultiplier); title("$rules.title.unit"); - check("$rules.unitdrops", b -> rules.unitDrops = b, () -> rules.unitDrops, ()->true); + check("$rules.unitdrops", b -> rules.unitDrops = b, () -> rules.unitDrops, () -> true); number("$rules.unitbuildspeedmultiplier", f -> rules.unitBuildSpeedMultiplier = f, () -> rules.unitBuildSpeedMultiplier); number("$rules.unithealthmultiplier", f -> rules.unitHealthMultiplier = f, () -> rules.unitHealthMultiplier); number("$rules.unitdamagemultiplier", f -> rules.unitDamageMultiplier = f, () -> rules.unitDamageMultiplier); @@ -80,11 +79,11 @@ public class CustomRulesDialog extends FloatingDialog{ main.table(t -> { t.left(); t.add(text).left().padRight(5) - .update(a->a.setColor(condition.get() ? Color.WHITE : Color.GRAY)); + .update(a -> a.setColor(condition.get() ? Color.WHITE : Color.GRAY)); Platform.instance.addDialog(t.addField((integer ? (int)prov.get() : prov.get()) + "", s -> cons.accept(Strings.parseFloat(s))) - .padRight(100f) - .update(a -> a.setDisabled(!condition.get())) - .valid(Strings::canParsePositiveFloat).width(120f) .left().get()); + .padRight(100f) + .update(a -> a.setDisabled(!condition.get())) + .valid(Strings::canParsePositiveFloat).width(120f).left().get()); }).padTop(0); main.row(); } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/DiscordDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/DiscordDialog.java index 7ea5b5b683..a38ffa4af7 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/DiscordDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/DiscordDialog.java @@ -34,9 +34,9 @@ public class DiscordDialog extends Dialog{ }).size(h).left(); t.add("$discord").color(Pal.accent).growX().padLeft(10f); - }).size(470f, h).pad(10f); + }).size(440f, h).pad(10f); - buttons.defaults().size(170f, 50); + buttons.defaults().size(150f, 50); buttons.addButton("$back", this::hide); buttons.addButton("$copylink", () -> { diff --git a/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java index 96c495687e..65c7e22137 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java @@ -164,9 +164,9 @@ public class JoinDialog extends FloatingDialog{ server.content.clear(); server.content.table(t -> { - t.add(versionString).left(); + t.add("[lightgray]" + host.name).width(targetWidth() - 10f).left().get().setEllipsis(true); t.row(); - t.add("[lightgray]" + Core.bundle.format("server.hostname", host.name)).width(targetWidth() - 10f).left().get().setEllipsis(true); + t.add(versionString).left(); t.row(); t.add("[lightgray]" + (host.players != 1 ? Core.bundle.format("players", host.players) : Core.bundle.format("players.single", host.players))).left(); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/LanguageDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/LanguageDialog.java index 006b9c25d9..25040a0a03 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/LanguageDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/LanguageDialog.java @@ -4,6 +4,7 @@ import io.anuke.arc.Core; import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.layout.Table; import io.anuke.arc.util.Log; +import io.anuke.arc.util.Strings; import java.util.Locale; @@ -28,7 +29,7 @@ public class LanguageDialog extends FloatingDialog{ ButtonGroup group = new ButtonGroup<>(); for(Locale loc : locales){ - TextButton button = new TextButton(loc.getDisplayName(loc), "toggle"); + TextButton button = new TextButton(Strings.capitalize(loc.getDisplayName(loc)), "toggle"); button.clicked(() -> { if(getLocale().equals(loc)) return; Core.settings.put("locale", loc.toString()); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java index 01e1126217..efdaf0adbd 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/LoadDialog.java @@ -185,12 +185,7 @@ public class LoadDialog extends FloatingDialog{ button.clicked(() -> { if(!button.childrenPressed()){ int build = slot.getBuild(); - if(SaveIO.breakingVersions.contains(build)){ - ui.showInfo("$save.old"); - slot.delete(); - }else{ - runLoadSave(slot); - } + runLoadSave(slot); } }); } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java new file mode 100644 index 0000000000..ab22b52549 --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java @@ -0,0 +1,116 @@ +package io.anuke.mindustry.ui.dialogs; + +import io.anuke.arc.Core; +import io.anuke.arc.math.Mathf; +import io.anuke.arc.scene.event.Touchable; +import io.anuke.arc.scene.ui.ScrollPane; +import io.anuke.arc.scene.ui.layout.Table; +import io.anuke.arc.util.Scaling; +import io.anuke.mindustry.game.*; +import io.anuke.mindustry.maps.Map; +import io.anuke.mindustry.ui.BorderImage; + +import static io.anuke.mindustry.Vars.*; + +public class MapPlayDialog extends FloatingDialog{ + Difficulty difficulty = Difficulty.normal; + CustomRulesDialog dialog = new CustomRulesDialog(); + Rules rules; + Gamemode selectedGamemode; + + public MapPlayDialog(){ + super(""); + setFillParent(false); + } + + public void show(Map map){ + title.setText(map.name()); + cont.clearChildren(); + rules = map.rules(); + + Table selmode = new Table(); + selmode.add("$level.mode").colspan(4); + selmode.row(); + int i = 0; + + Table modes = new Table(); + + for(Gamemode mode : Gamemode.values()){ + if(mode.hidden) continue; + + if((mode == Gamemode.attack && !map.hasEnemyCore()) || (mode == Gamemode.pvp && !map.hasOtherCores())){ + continue; + } + + modes.addButton(mode.toString(), "toggle", () -> { + selectedGamemode = selectedGamemode == mode ? null : mode; + rules = selectedGamemode == null ? map.rules() : mode.apply(map.rules()); + }).update(b -> b.setChecked(selectedGamemode == mode)).size(140f, 54f); + if(i++ % 2 == 1) modes.row(); + } + selmode.add(modes); + selmode.addButton("?", this::displayGameModeHelp).width(50f).fillY().padLeft(18f); + + cont.add(selmode); + cont.row(); + + Difficulty[] ds = Difficulty.values(); + + float s = 50f; + + Table sdif = new Table(); + + sdif.add("$setting.difficulty.name").colspan(3); + sdif.row(); + sdif.defaults().height(s + 4); + sdif.addImageButton("icon-arrow-left", 10 * 3, () -> { + difficulty = (ds[Mathf.mod(difficulty.ordinal() - 1, ds.length)]); + state.wavetime = difficulty.waveTime; + }).width(s); + + sdif.addButton("", () -> {}).update(t -> { + t.setText(difficulty.toString()); + t.touchable(Touchable.disabled); + }).width(180f); + + sdif.addImageButton("icon-arrow-right", 10 * 3, () -> { + difficulty = (ds[Mathf.mod(difficulty.ordinal() + 1, ds.length)]); + state.wavetime = difficulty.waveTime; + }).width(s); + sdif.addButton("$customize", () -> dialog.show(rules, () -> rules = (selectedGamemode == null ? map.rules() : selectedGamemode.apply(map.rules())))).width(140).padLeft(10); + + cont.add(sdif); + cont.row(); + cont.add(new BorderImage(map.texture, 3f)).size(250f).get().setScaling(Scaling.fit); + + buttons.clearChildren(); + addCloseButton(); + + buttons.addImageTextButton("$play", "icon-play", 8*3, () -> { + control.playMap(map, rules); + hide(); + ui.custom.hide(); + }).size(210f, 64f); + + show(); + } + + private void displayGameModeHelp(){ + FloatingDialog d = new FloatingDialog(Core.bundle.get("mode.help.title")); + d.setFillParent(false); + Table table = new Table(); + table.defaults().pad(1f); + ScrollPane pane = new ScrollPane(table); + pane.setFadeScrollBars(false); + table.row(); + for(Gamemode mode : Gamemode.values()){ + if(mode.hidden) continue; + table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(400f); + table.row(); + } + + d.cont.add(pane); + d.buttons.addButton("$ok", d::hide).size(110, 50).pad(10f); + d.show(); + } +} diff --git a/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java index 7e4015f0fd..1205146ccd 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java @@ -24,7 +24,7 @@ public class MapsDialog extends FloatingDialog{ buttons.addImageTextButton("$editor.importmap", "icon-add", 14 * 2, () -> { Platform.instance.showFileChooser("$editor.importmap", "Map File", file -> { try{ - Map map = MapIO.readMap(file, true); + Map map = MapIO.createMap(file, true); String name = map.tags.get("name"); if(name == null){ ui.showError("$editor.errorname"); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java index 452c4e2627..5b72cba724 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/PausedDialog.java @@ -47,7 +47,7 @@ public class PausedDialog extends FloatingDialog{ } cont.addButton("$settings", ui.settings::show); - if(!world.isZone()){ + if(!world.isZone() && !state.isEditor()){ cont.row(); cont.addButton("$savegame", save::show); cont.addButton("$loadgame", load::show).disabled(b -> Net.active()); @@ -55,9 +55,10 @@ public class PausedDialog extends FloatingDialog{ cont.row(); - cont.addButton("$hostserver", ui.host::show).disabled(b -> Net.active()).colspan(2).width(dw * 2 + 20f); - - cont.row(); + if(!state.isEditor()){ + cont.addButton("$hostserver", ui.host::show).disabled(b -> Net.active()).colspan(2).width(dw * 2 + 20f); + cont.row(); + } cont.addButton("$quit", () -> { ui.showConfirm("$confirm", "$quit.confirm", () -> { @@ -74,7 +75,7 @@ public class PausedDialog extends FloatingDialog{ cont.addRowImageTextButton("$back", "icon-play-2", isize, this::hide); cont.addRowImageTextButton("$settings", "icon-tools", isize, ui.settings::show); - if(!world.isZone()){ + if(!world.isZone() && !state.isEditor()){ cont.addRowImageTextButton("$save", "icon-save", isize, save::show); cont.row(); @@ -84,7 +85,9 @@ public class PausedDialog extends FloatingDialog{ cont.row(); } - cont.addRowImageTextButton("$hostserver.mobile", "icon-host", isize, ui.host::show).disabled(b -> Net.active()); + if(!state.isEditor()){ + cont.addRowImageTextButton("$hostserver.mobile", "icon-host", isize, ui.host::show).disabled(b -> Net.active()); + } cont.addRowImageTextButton("$quit", "icon-quit", isize, () -> { ui.showConfirm("$confirm", "$quit.confirm", () -> { if(Net.client()) netClient.disconnectQuietly(); @@ -96,8 +99,12 @@ public class PausedDialog extends FloatingDialog{ } public void runExitSave(){ - if(control.saves.getCurrent() == null || - !control.saves.getCurrent().isAutosave()){ + if(state.isEditor()){ + ui.editor.resumeEditing(); + return; + } + + if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave()){ state.set(State.menu); return; } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java index 919b4dfe31..cb870470fe 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java @@ -124,7 +124,6 @@ public class SettingsMenuDialog extends SettingsDialog{ game.screenshakePref(); game.checkPref("effects", true); - game.checkPref("swapdiagonal", false); if(mobile){ game.checkPref("autotarget", true); } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/TraceDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/TraceDialog.java index 5dd8e13142..354156e954 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/TraceDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/TraceDialog.java @@ -1,21 +1,26 @@ package io.anuke.mindustry.ui.dialogs; +import io.anuke.arc.Core; +import io.anuke.arc.scene.ui.layout.Table; +import io.anuke.mindustry.entities.type.Player; +import io.anuke.mindustry.net.Administration.TraceInfo; + public class TraceDialog extends FloatingDialog{ public TraceDialog(){ super("$trace"); addCloseButton(); + setFillParent(false); } -/* - public void show(Player player, SessionInfo info){ + + public void show(Player player, TraceInfo info){ cont.clear(); Table table = new Table("clear"); table.margin(14); table.defaults().pad(1); - /* table.defaults().left(); table.add(Core.bundle.format("trace.playername", player.name)); table.row(); @@ -23,33 +28,16 @@ public class TraceDialog extends FloatingDialog{ table.row(); table.add(Core.bundle.format("trace.id", info.uuid)); table.row(); - table.add(Core.bundle.format("trace.modclient", info.modclient)); + table.add(Core.bundle.format("trace.modclient", info.modded)); table.row(); - table.add(Core.bundle.format("trace.android", info.android)); + table.add(Core.bundle.format("trace.mobile", info.mobile)); table.row(); table.add().pad(5); table.row(); - //disabled until further notice -/* - table.add(Core.bundle.format("trace.totalblocksbroken", info.totalBlocksBroken)); - table.row(); - table.add(Core.bundle.format("trace.structureblocksbroken", info.structureBlocksBroken)); - table.row(); - table.add(Core.bundle.format("trace.lastblockbroken", info.lastBlockBroken.localizedName)); - table.row(); - - table.add().pad(5); - table.row(); - - table.add(Core.bundle.format("trace.totalblocksplaced", info.totalBlocksPlaced)); - table.row(); - table.add(Core.bundle.format("trace.lastblockplaced", info.lastBlockPlaced.localizedName)); - table.row(); - cont.add(table); show(); - }*/ + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java index e36824afc1..99b03b803d 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java @@ -133,7 +133,7 @@ public class ZoneInfoDialog extends FloatingDialog{ ui.deploy.hide(); data.removeItems(zone.getLaunchCost()); hide(); - world.playZone(zone); + control.playZone(zone); } }).minWidth(150f).margin(13f).padTop(5).disabled(b -> zone.locked() ? !canUnlock(zone) : !data.hasItems(zone.getLaunchCost())).uniformY().get(); diff --git a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java index 71a633707b..d0a86d0f3c 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/BlockInventoryFragment.java @@ -59,11 +59,11 @@ public class BlockInventoryFragment extends Fragment{ } public void showFor(Tile t){ - if(this.tile == t.target()){ + if(this.tile == t){ hide(); return; } - this.tile = t.target(); + this.tile = t; if(tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.total() == 0) return; rebuild(true); diff --git a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java index fa294a642a..fbee21cea9 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java @@ -4,30 +4,39 @@ import io.anuke.arc.Core; import io.anuke.arc.Events; import io.anuke.arc.collection.Array; import io.anuke.arc.graphics.Color; +import io.anuke.arc.graphics.g2d.Draw; +import io.anuke.arc.graphics.g2d.Lines; import io.anuke.arc.input.KeyCode; import io.anuke.arc.math.Interpolation; import io.anuke.arc.math.Mathf; +import io.anuke.arc.math.geom.Vector2; import io.anuke.arc.scene.Element; import io.anuke.arc.scene.Group; import io.anuke.arc.scene.actions.Actions; import io.anuke.arc.scene.event.Touchable; +import io.anuke.arc.scene.style.TextureRegionDrawable; import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.utils.Elements; import io.anuke.arc.util.*; +import io.anuke.mindustry.content.Fx; import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.entities.Effects; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.entities.type.BaseUnit; import io.anuke.mindustry.game.EventType.StateChangeEvent; +import io.anuke.mindustry.game.Team; import io.anuke.mindustry.game.UnlockableContent; import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.graphics.Pal; import io.anuke.mindustry.input.Binding; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Packets.AdminAction; +import io.anuke.mindustry.type.ContentType; +import io.anuke.mindustry.type.UnitType; import io.anuke.mindustry.ui.*; import io.anuke.mindustry.ui.dialogs.FloatingDialog; -import java.lang.StringBuilder; - import static io.anuke.mindustry.Vars.*; public class HudFragment extends Fragment{ @@ -131,8 +140,13 @@ public class HudFragment extends Fragment{ } }); - cont.table(stuff -> { - stuff.left(); + Table wavesMain, editorMain; + + cont.stack(wavesMain = new Table(), editorMain = new Table()).height(e -> wavesMain.isVisible() ? wavesMain.getPrefHeight() : editorMain.getPrefHeight()); + + { + wavesMain.visible(() -> shown && !state.isEditor()); + wavesMain.left(); Stack stack = new Stack(); TextButton waves = new TextButton("", "wave"); Table btable = new Table().margin(0); @@ -142,17 +156,103 @@ public class HudFragment extends Fragment{ addWaveTable(waves); addPlayButton(btable); - stuff.add(stack).width(dsize * 4 + 3f); - stuff.row(); - stuff.table("button", t -> t.margin(10f).add(new Bar("boss.health", Pal.health, () -> state.boss() == null ? 0f : state.boss().healthf()).blink(Color.WHITE)) + wavesMain.add(stack).width(dsize * 4 + 3f); + wavesMain.row(); + wavesMain.table("button", t -> t.margin(10f).add(new Bar("boss.health", Pal.health, () -> state.boss() == null ? 0f : state.boss().healthf()).blink(Color.WHITE)) .grow()).fillX().visible(() -> state.rules.waves && state.boss() != null).height(60f).get(); - stuff.row(); - }).visible(() -> shown); + wavesMain.row(); + } + + { + editorMain.table("button-edge-4", t -> { + //t.margin(0f); + t.add("$editor.teams").growX().left(); + t.row(); + t.table(teams -> { + teams.left(); + int i = 0; + for(Team team : Team.all){ + ImageButton button = teams.addImageButton("white", "clear-toggle-partial", 40f, () -> player.setTeam(team)) + .size(50f).margin(6f).get(); + button.getImageCell().grow(); + button.getStyle().imageUpColor = team.color; + button.update(() -> button.setChecked(player.getTeam() == team)); + + if(++i % 3 == 0){ + teams.row(); + } + } + }).left(); + + t.row(); + t.addImageTextButton("$editor.spawn", "icon-add", 8*3, () -> { + FloatingDialog dialog = new FloatingDialog("$editor.spawn"); + int i = 0; + for(UnitType type : content.getBy(ContentType.unit)){ + dialog.cont.addImageButton("white", 48, () -> { + BaseUnit unit = type.create(player.getTeam()); + unit.set(player.x, player.y); + unit.rotation = player.rotation; + unit.add(); + //trigger the entity to become visible + unitGroups[player.getTeam().ordinal()].updateEvents(); + collisions.updatePhysics( unitGroups[player.getTeam().ordinal()]); + dialog.hide(); + }).get().getStyle().imageUp = new TextureRegionDrawable(type.iconRegion); + if(++i % 4 == 0) dialog.cont.row(); + } + dialog.addCloseButton(); + dialog.setFillParent(false); + dialog.show(); + }).fillX(); + + float[] size = {0}; + float[] position = {0, 0}; + + t.row(); + t.addImageTextButton("$editor.removeunit", "icon-quit", "toggle", 8*3, () -> { + + }).fillX().update(b -> { + boolean[] found = {false}; + if(b.isChecked()){ + Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true); + if(e == null){ + Vector2 world = Core.input.mouseWorld(); + Units.nearby(world.x, world.y, 1f, 1f, unit -> { + if(!found[0] && unit instanceof BaseUnit){ + if(Core.input.keyTap(KeyCode.MOUSE_LEFT)){ + Effects.effect(Fx.spawn, unit); + unit.remove(); + unitGroups[unit.getTeam().ordinal()].updateEvents(); + collisions.updatePhysics(unitGroups[unit.getTeam().ordinal()]); + } + found[0] = true; + unit.hitbox(Tmp.r1); + size[0] = Mathf.lerpDelta(size[0], Tmp.r1.width*2f + Mathf.absin(Time.time(), 10f, 5f), 0.1f); + position[0] = unit.x; + position[1] = unit.y; + } + }); + //TODO check for unit removal, remove unit if needed + } + } + + Draw.color(Pal.accent, Color.WHITE, Mathf.absin(Time.time(), 8f, 1f)); + Lines.poly(position[0], position[1], 4, size[0]/2f); + Draw.reset(); + + if(!found[0]){ + size[0] = Mathf.lerpDelta(size[0], 0f, 0.2f); + } + }); + }).width(dsize * 4 + 3f); + editorMain.visible(() -> shown && state.isEditor()); + } //fps display cont.table(info -> { info.top().left().margin(4).visible(() -> Core.settings.getBool("fps")); - info.update(() -> info.setTranslation(state.rules.waves ? 0f : -Unit.dp.scl(dsize * 4 + 3), 0)); + info.update(() -> info.setTranslation(state.rules.waves || state.isEditor() ? 0f : -Unit.dp.scl(dsize * 4 + 3), 0)); IntFormat fps = new IntFormat("fps"); IntFormat ping = new IntFormat("ping"); diff --git a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java index 6a01e36a88..29e5d9cd90 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java @@ -38,7 +38,7 @@ public class PlacementFragment extends Fragment{ Table blockTable, toggler, topTable; boolean lastGround; - //TODO make this configurable + //not configurable, no plans to make it configurable final KeyCode[] inputGrid = { KeyCode.NUM_1, KeyCode.NUM_2, KeyCode.NUM_3, KeyCode.NUM_4, KeyCode.Q, KeyCode.W, KeyCode.E, KeyCode.R, @@ -78,7 +78,7 @@ public class PlacementFragment extends Fragment{ Tile tile = world.tileWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y); if(tile != null){ - tile = tile.target(); + tile = tile.link(); Block tryRecipe = tile.block(); if(tryRecipe.isVisible() && unlocked(tryRecipe)){ input.block = tryRecipe; @@ -142,7 +142,7 @@ public class PlacementFragment extends Fragment{ ImageButton button = blockTable.addImageButton("icon-locked", "select", 8 * 4, () -> { if(unlocked(block)){ - input.block = input.block == block ? null : block; + input.block = block; } }).size(46f).group(group).get(); @@ -150,7 +150,7 @@ public class PlacementFragment extends Fragment{ button.update(() -> { //color unplacable things gray TileEntity core = player.getClosestCore(); - Color color = core != null && (core.items.has(block.buildRequirements, state.rules.buildCostMultiplier) || state.rules.infiniteResources) ? Color.WHITE : Color.GRAY; + Color color = state.rules.infiniteResources || (core != null && (core.items.has(block.buildRequirements, state.rules.buildCostMultiplier) || state.rules.infiniteResources)) ? Color.WHITE : Color.GRAY; button.forEach(elem -> elem.setColor(color)); button.setChecked(input.block == block); }); @@ -313,7 +313,7 @@ public class PlacementFragment extends Fragment{ if(!Core.scene.hasMouse() && topTable.hit(v.x, v.y, false) == null){ Tile tile = world.tileWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y); if(tile != null){ - hoverTile = tile.target(); + hoverTile = tile.link(); }else{ hoverTile = null; } diff --git a/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java b/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java index fefe7fdc4a..09b19a889c 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java @@ -128,7 +128,7 @@ public class PlayerListFragment extends Fragment{ .touchable(() -> Net.client() ? Touchable.disabled : Touchable.enabled) .checked(user.isAdmin); - t.addImageButton("icon-zoom-small", "clear-partial", 14 * 2, () -> ui.showError("Currently unimplemented.")/*Call.onAdminRequest(user, AdminAction.trace)*/); + t.addImageButton("icon-zoom-small", "clear-partial", 14 * 2, () -> Call.onAdminRequest(user, AdminAction.trace)); }).padRight(12).size(bs + 10f, bs); } diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index c3aa1ec35e..3c77052b8c 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.world; +import io.anuke.annotations.Annotations.CallSuper; import io.anuke.arc.Core; import io.anuke.arc.Graphics.Cursor; import io.anuke.arc.Graphics.Cursor.SystemCursor; @@ -240,7 +241,7 @@ public class Block extends BlockStorage{ } public void draw(Tile tile){ - Draw.rect(region, tile.drawx(), tile.drawy(), rotate ? tile.getRotation() * 90 : 0); + Draw.rect(region, tile.drawx(), tile.drawy(), rotate ? tile.rotation() * 90 : 0); } public void drawTeam(Tile tile){ @@ -313,6 +314,7 @@ public class Block extends BlockStorage{ /** Called after all blocks are created. */ @Override + @CallSuper public void init(){ //initialize default health based on size if(health == -1){ @@ -451,6 +453,10 @@ public class Block extends BlockStorage{ } } + public Tile linked(Tile tile){ + return tile; + } + public boolean isSolidFor(Tile tile){ return false; } diff --git a/core/src/io/anuke/mindustry/world/BlockStorage.java b/core/src/io/anuke/mindustry/world/BlockStorage.java index 0484676931..358e2d6ddc 100644 --- a/core/src/io/anuke/mindustry/world/BlockStorage.java +++ b/core/src/io/anuke/mindustry/world/BlockStorage.java @@ -105,7 +105,7 @@ public abstract class BlockStorage extends UnlockableContent{ public void tryDumpLiquid(Tile tile, Liquid liquid){ Array proximity = tile.entity.proximity(); - int dump = tile.getDump(); + int dump = tile.rotation(); for(int i = 0; i < proximity.size; i++){ incrementDump(tile, proximity.size); @@ -138,7 +138,7 @@ public abstract class BlockStorage extends UnlockableContent{ public float tryMoveLiquid(Tile tile, Tile next, boolean leak, Liquid liquid){ if(next == null) return 0; - next = next.target(); + next = next.link(); if(next.getTeam() == tile.getTeam() && next.block().hasLiquids && tile.entity.liquids.get(liquid) > 0f){ @@ -182,7 +182,7 @@ public abstract class BlockStorage extends UnlockableContent{ */ public void offloadNear(Tile tile, Item item){ Array proximity = tile.entity.proximity(); - int dump = tile.getDump(); + int dump = tile.rotation(); for(int i = 0; i < proximity.size; i++){ incrementDump(tile, proximity.size); @@ -212,7 +212,7 @@ public abstract class BlockStorage extends UnlockableContent{ return false; Array proximity = entity.proximity(); - int dump = tile.getDump(); + int dump = tile.rotation(); if(proximity.size == 0) return false; @@ -249,7 +249,7 @@ public abstract class BlockStorage extends UnlockableContent{ } protected void incrementDump(Tile tile, int prox){ - tile.setDump((byte)((tile.getDump() + 1) % prox)); + tile.rotation((byte)((tile.rotation() + 1) % prox)); } /** Used for dumping items. */ @@ -259,8 +259,9 @@ public abstract class BlockStorage extends UnlockableContent{ /** Try offloading an item to a nearby container in its facing direction. Returns true if success. */ public boolean offloadDir(Tile tile, Item item){ - Tile other = tile.getNearby(tile.getRotation()); - if(other != null && other.target().getTeamID() == tile.getTeamID() && other.block().acceptItem(item, other, tile)){ + Tile other = tile.getNearby(tile.rotation()); + if(other != null) other = other.link(); + if(other != null && other.getTeam() == tile.getTeam() && other.block().acceptItem(item, other, tile)){ other.block().handleItem(item, other, tile); return true; } diff --git a/core/src/io/anuke/mindustry/world/Build.java b/core/src/io/anuke/mindustry/world/Build.java index f301dd3e42..a97a310922 100644 --- a/core/src/io/anuke/mindustry/world/Build.java +++ b/core/src/io/anuke/mindustry/world/Build.java @@ -10,7 +10,7 @@ import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.entities.Units; import io.anuke.mindustry.game.EventType.BlockBuildBeginEvent; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.type.ContentType; +import io.anuke.mindustry.world.blocks.BuildBlock; import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity; import static io.anuke.mindustry.Vars.*; @@ -25,7 +25,7 @@ public class Build{ return; } - Tile tile = world.tile(x, y); + Tile tile = world.ltile(x, y); float prevPercent = 1f; //just in case @@ -35,38 +35,15 @@ public class Build{ prevPercent = tile.entity.healthf(); } - tile = tile.target(); - + int rotation = tile.rotation(); Block previous = tile.block(); + Block sub = BuildBlock.get(previous.size); - Block sub = content.getByName(ContentType.block, "build" + previous.size); - - tile.setBlock(sub); + world.setBlock(tile, sub, team, rotation); tile.entity().setDeconstruct(previous); - tile.setTeam(team); tile.entity.health = tile.entity.maxHealth() * prevPercent; - if(previous.isMultiblock()){ - int offsetx = -(previous.size - 1) / 2; - int offsety = -(previous.size - 1) / 2; - - for(int dx = 0; dx < previous.size; dx++){ - for(int dy = 0; dy < previous.size; dy++){ - int worldx = dx + offsetx + tile.x; - int worldy = dy + offsety + tile.y; - if(!(worldx == tile.x && worldy == tile.y)){ - Tile toplace = world.tile(worldx, worldy); - if(toplace != null){ - toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety)); - toplace.setTeam(team); - } - } - } - } - } - - Tile ftile = tile; - Core.app.post(() -> Events.fire(new BlockBuildBeginEvent(ftile, team, true))); + Core.app.post(() -> Events.fire(new BlockBuildBeginEvent(tile, team, true))); } /** Places a BuildBlock at this location. */ @@ -82,31 +59,10 @@ public class Build{ if(tile == null) return; Block previous = tile.block(); + Block sub = BuildBlock.get(result.size); - Block sub = content.getByName(ContentType.block, "build" + result.size); - - tile.setBlock(sub, rotation); + world.setBlock(tile, sub, team, rotation); tile.entity().setConstruct(previous, result); - tile.setTeam(team); - - if(result.isMultiblock()){ - int offsetx = -(result.size - 1) / 2; - int offsety = -(result.size - 1) / 2; - - for(int dx = 0; dx < result.size; dx++){ - for(int dy = 0; dy < result.size; dy++){ - int worldx = dx + offsetx + x; - int worldy = dy + offsety + y; - if(!(worldx == x && worldy == y)){ - Tile toplace = world.tile(worldx, worldy); - if(toplace != null){ - toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety)); - toplace.setTeam(team); - } - } - } - } - } Core.app.post(() -> Events.fire(new BlockBuildBeginEvent(tile, team, false))); } @@ -130,6 +86,7 @@ public class Build{ } } + Tile tile = world.tile(x, y); if(tile == null) return false; @@ -166,7 +123,7 @@ public class Build{ && (!tile.floor().isDeep() || type.floating) && tile.floor().placeableOn && ((type.canReplace(tile.block()) - && !(type == tile.block() && rotation == tile.getRotation() && type.rotate)) || tile.block().alwaysReplace || tile.block() == Blocks.air) + && !(type == tile.block() && rotation == tile.rotation() && type.rotate)) || tile.block().alwaysReplace || tile.block() == Blocks.air) && tile.block().isMultiblock() == type.isMultiblock() && type.canPlaceOn(tile); } } @@ -195,9 +152,7 @@ public class Build{ /** Returns whether the tile at this position is breakable by this team */ public static boolean validBreak(Team team, int x, int y){ - Tile tile = world.tile(x, y); - if(tile != null) tile = tile.target(); - + Tile tile = world.ltile(x, y); return tile != null && tile.block().canBreak(tile) && tile.breakable() && tile.interactable(team); } } diff --git a/core/src/io/anuke/mindustry/world/CachedTile.java b/core/src/io/anuke/mindustry/world/CachedTile.java index 75dded63db..05f3b16144 100644 --- a/core/src/io/anuke/mindustry/world/CachedTile.java +++ b/core/src/io/anuke/mindustry/world/CachedTile.java @@ -37,6 +37,7 @@ public class CachedTile extends Tile{ if(!entities.containsKey(block.id)){ TileEntity n = block.newEntity(); n.cons = new ConsumeModule(entity); + n.tile = this; if(block.hasItems) n.items = new ItemModule(); if(block.hasLiquids) n.liquids = new LiquidModule(); if(block.hasPower) n.power = new PowerModule(); diff --git a/core/src/io/anuke/mindustry/world/ItemBuffer.java b/core/src/io/anuke/mindustry/world/ItemBuffer.java index 251517b379..3c80a87f54 100644 --- a/core/src/io/anuke/mindustry/world/ItemBuffer.java +++ b/core/src/io/anuke/mindustry/world/ItemBuffer.java @@ -1,8 +1,11 @@ package io.anuke.mindustry.world; -import io.anuke.arc.util.*; +import io.anuke.arc.util.Pack; +import io.anuke.arc.util.Time; import io.anuke.mindustry.type.Item; +import java.io.*; + import static io.anuke.mindustry.Vars.content; public class ItemBuffer{ @@ -57,4 +60,23 @@ public class ItemBuffer{ System.arraycopy(buffer, 1, buffer, 0, index - 1); index--; } + + public void write(DataOutput stream) throws IOException{ + stream.writeByte((byte)index); + stream.writeByte((byte)buffer.length); + for(long l : buffer){ + stream.writeLong(l); + } + } + + public void read(DataInput stream) throws IOException{ + index = stream.readByte(); + byte length = stream.readByte(); + for(int i = 0; i < length; i++){ + long l = stream.readLong(); + if(i < buffer.length){ + buffer[i] = l; + } + } + } } diff --git a/core/src/io/anuke/mindustry/world/LegacyColorMapper.java b/core/src/io/anuke/mindustry/world/LegacyColorMapper.java index 1c9e3f2ec1..17c7e19417 100644 --- a/core/src/io/anuke/mindustry/world/LegacyColorMapper.java +++ b/core/src/io/anuke/mindustry/world/LegacyColorMapper.java @@ -18,7 +18,7 @@ public class LegacyColorMapper implements ContentList{ public void load(){ defaultValue = new LegacyBlock(Blocks.stone, Blocks.air); - map("ff0000", Blocks.stone, Blocks.spawn); + map("ff0000", Blocks.stone, Blocks.air, Blocks.spawn); map("00ff00", Blocks.stone); map("323232", Blocks.stone); map("646464", Blocks.stone, Blocks.rocks); @@ -60,9 +60,7 @@ public class LegacyColorMapper implements ContentList{ public final Block ore; public LegacyBlock(Block floor, Block wall){ - this.floor = (Floor)floor; - this.wall = wall; - this.ore = null; + this(floor, wall, Blocks.air); } public LegacyBlock(Block floor, Block wall, Block ore){ diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index 3bb1503058..1e458fc34e 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -4,7 +4,6 @@ import io.anuke.arc.collection.Array; import io.anuke.arc.function.Consumer; import io.anuke.arc.math.Mathf; import io.anuke.arc.math.geom.*; -import io.anuke.arc.util.Pack; import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.entities.type.TileEntity; @@ -24,12 +23,12 @@ public class Tile implements Position, TargetTrait{ public short x, y; protected Block block; protected Floor floor; - /** Rotation, 0-3. Also used to store offload location, in which case it can be any number. */ - private byte rotation; + /** Rotation, 0-3. Also used to store offload location, in which case it can be any number.*/ + protected byte rotation; /** Team ordinal. */ - private byte team; + protected byte team; /** Ore that is on top of this (floor) block. */ - private byte overlay = 0; + protected short overlay = 0; public Tile(int x, int y){ this.x = (short)x; @@ -37,20 +36,15 @@ public class Tile implements Position, TargetTrait{ block = floor = (Floor)Blocks.air; } - public Tile(int x, int y, byte floor, byte block){ - this(x, y); + public Tile(int x, int y, int floor, int overlay, int wall){ + this.x = (short)x; + this.y = (short)y; this.floor = (Floor)content.block(floor); - this.block = content.block(block); - changed(); - } + this.block = content.block(wall); + this.overlay = (short)overlay; - public Tile(int x, int y, byte floor, byte block, byte rotation, byte team){ - this(x, y); - this.floor = (Floor)content.block(floor); - this.block = content.block(block); - this.rotation = rotation; + //update entity and create it if needed changed(); - this.team = team; } /** Returns this tile's position as a {@link Pos}. */ @@ -58,14 +52,6 @@ public class Tile implements Position, TargetTrait{ return Pos.get(x, y); } - public byte getBlockID(){ - return block.id; - } - - public byte getFloorID(){ - return floor.id; - } - /** Return relative rotation to a coordinate. Returns -1 if the coordinate is not near this tile. */ public byte relativeTo(int cx, int cy){ if(x == cx && y == cy - 1) return 1; @@ -139,7 +125,7 @@ public class Tile implements Position, TargetTrait{ @Override public Team getTeam(){ - return Team.all[target().team]; + return Team.all[link().team]; } public void setTeam(Team team){ @@ -154,25 +140,12 @@ public class Tile implements Position, TargetTrait{ preChanged(); this.block = type; this.team = (byte)team.ordinal(); - this.rotation = 0; - this.rotation = (byte)Mathf.mod(rotation, 4); - changed(); - } - - public void setBlock(Block type, int rotation){ - preChanged(); - this.block = type; - this.rotation = 0; this.rotation = (byte)Mathf.mod(rotation, 4); changed(); } public void setBlock(Block type, Team team){ - preChanged(); - this.block = type; - this.team = (byte)team.ordinal(); - this.rotation = 0; - changed(); + setBlock(type, team, 0); } public void setBlock(Block type){ @@ -188,27 +161,27 @@ public class Tile implements Position, TargetTrait{ this.overlay = 0; } - public byte getRotation(){ + public byte rotation(){ return rotation; } - public void setRotation(byte rotation){ - this.rotation = rotation; + public void rotation(int rotation){ + this.rotation = (byte)rotation; } - public byte getDump(){ - return rotation; - } - - public void setDump(byte dump){ - this.rotation = dump; - } - - public byte getOverlayID(){ + public short overlayID(){ return overlay; } - public void setOverlayID(byte ore){ + public short blockID(){ + return block.id; + } + + public short floorID(){ + return floor.id; + } + + public void setOverlayID(short ore){ this.overlay = ore; } @@ -221,31 +194,24 @@ public class Tile implements Position, TargetTrait{ } public boolean passable(){ - Block block = block(); - Block floor = floor(); return isLinked() || !((floor.solid && (block == Blocks.air || block.solidifes)) || (block.solid && (!block.destructible && !block.update))); } /** Whether this block was placed by a player/unit. */ public boolean synthetic(){ - Block block = block(); return block.update || block.destructible; } public boolean solid(){ - Block block = block(); - Block floor = floor(); - return block.solid || (floor.solid && (block == Blocks.air || block.solidifes)) || block.isSolidFor(this) - || (isLinked() && getLinked().block().isSolidFor(getLinked())); + return block.solid || block.isSolidFor(this) || (isLinked() && link().solid()); } public boolean breakable(){ - Block block = block(); - if(!isLinked()){ - return (block.destructible || block.breakable || block.update); - }else{ - return getLinked() != this && getLinked().getLinked() == null && getLinked().breakable(); - } + return !isLinked() ? (block.destructible || block.breakable || block.update) : link().breakable(); + } + + public Tile link(){ + return block.linked(this); } public boolean isEnemyCheat(){ @@ -253,21 +219,27 @@ public class Tile implements Position, TargetTrait{ } public boolean isLinked(){ - return block == Blocks.part; + return block instanceof BlockPart; } - public byte getLinkByte(){ - return rotation; - } - - public void setLinkByte(byte b){ - this.rotation = b; - } - - /** Sets this to a linked tile, which sets the block to a part. dx and dy can only be -8-7. */ - public void setLinked(byte dx, byte dy){ - setBlock(Blocks.part); - rotation = Pack.byteByte((byte)(dx + 8), (byte)(dy + 8)); + /** + * Returns the list of all tiles linked to this multiblock, or an empty array if it's not a multiblock. + * This array contains all linked tiles, including this tile itself. + */ + public void getLinkedTiles(Consumer cons){ + if(block.isMultiblock()){ + int size = block.size; + int offsetx = -(size - 1) / 2; + int offsety = -(size - 1) / 2; + for(int dx = 0; dx < size; dx++){ + for(int dy = 0; dy < size; dy++){ + Tile other = world.tile(x + dx + offsetx, y + dy + offsety); + if(other != null) cons.accept(other); + } + } + }else{ + cons.accept(this); + } } /** @@ -275,20 +247,8 @@ public class Tile implements Position, TargetTrait{ * This array contains all linked tiles, including this tile itself. */ public Array getLinkedTiles(Array tmpArray){ - Block block = block(); tmpArray.clear(); - if(block.isMultiblock()){ - int offsetx = -(block.size - 1) / 2; - int offsety = -(block.size - 1) / 2; - for(int dx = 0; dx < block.size; dx++){ - for(int dy = 0; dy < block.size; dy++){ - Tile other = world.tile(x + dx + offsetx, y + dy + offsety); - if(other != null) tmpArray.add(other); - } - } - }else{ - tmpArray.add(this); - } + getLinkedTiles(tmpArray::add); return tmpArray; } @@ -313,7 +273,7 @@ public class Tile implements Position, TargetTrait{ return tmpArray; } - /** Returns the block the multiblock is linked to, or null if it is not linked to any block. */ + /** Returns the block the multiblock is linked to, or null if it is not linked to any block. public Tile getLinked(){ if(!isLinked()){ return null; @@ -322,28 +282,10 @@ public class Tile implements Position, TargetTrait{ } } - public void allNearby(Consumer cons){ - for(Point2 point : Edges.getEdges(block().size)){ - Tile tile = world.tile(x + point.x, y + point.y); - if(tile != null){ - cons.accept(tile.target()); - } - } - } - - public void allInside(Consumer cons){ - for(Point2 point : Edges.getInsideEdges(block().size)){ - Tile tile = world.tile(x + point.x, y + point.y); - if(tile != null){ - cons.accept(tile); - } - } - } - public Tile target(){ Tile link = getLinked(); return link == null ? this : link; - } + }*/ public Rectangle getHitbox(Rectangle rect){ return rect.setSize(block().size * tilesize).setCenter(drawx(), drawy()); @@ -398,8 +340,8 @@ public class Tile implements Position, TargetTrait{ //+26 - if(target().synthetic()){ - cost += Mathf.clamp(target().block().health / 10f, 0, 20); + if(link().synthetic()){ + cost += Mathf.clamp(link().block.health / 10f, 0, 20); } //+46 @@ -453,9 +395,8 @@ public class Tile implements Position, TargetTrait{ }else if(!(block instanceof BlockPart) && !world.isGenerating()){ //since the entity won't update proximity for us, update proximity for all nearby tiles manually for(Point2 p : Geometry.d4){ - Tile tile = world.tile(x + p.x, y + p.y); + Tile tile = world.ltile(x + p.x, y + p.y); if(tile != null){ - tile = tile.target(); tile.block().onProximityUpdate(tile); } } @@ -498,20 +439,6 @@ public class Tile implements Position, TargetTrait{ @Override public String toString(){ - Block block = block(); - Block floor = floor(); - - return floor.name + ":" + block.name + ":" + content.block(overlay) + "[" + x + "," + y + "] " + "entity=" + (entity == null ? "null" : (entity.getClass())) + - (isLinked() ? " link=[" + linkX(rotation) + ", " + linkY(rotation) + "]" : ""); - } - - /**Returns the relative X from a link byte.*/ - public static int linkX(byte value){ - return -((byte)((value >> 4) & (byte)0x0F) - 8); - } - - /**Returns the relative Y from a link byte.*/ - public static int linkY(byte value){ - return -((byte)(value & 0x0F) - 8); + return floor.name + ":" + block.name + ":" + content.block(overlay) + "[" + x + "," + y + "] " + "entity=" + (entity == null ? "null" : (entity.getClass())); } } \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/world/WorldContext.java b/core/src/io/anuke/mindustry/world/WorldContext.java new file mode 100644 index 0000000000..12a3d190b3 --- /dev/null +++ b/core/src/io/anuke/mindustry/world/WorldContext.java @@ -0,0 +1,22 @@ +package io.anuke.mindustry.world; + +public interface WorldContext{ + + /** Return a tile in the tile array.*/ + Tile tile(int x, int y); + + /** Create the tile array.*/ + void resize(int width, int height); + + /** This should create a tile and put it into the tile array, then return it. */ + Tile create(int x, int y, int floorID, int overlayID, int wallID); + + /** Returns whether the world is already generating.*/ + boolean isGenerating(); + + /** Begins generating.*/ + void begin(); + + /** End generating, prepares tiles.*/ + void end(); +} diff --git a/core/src/io/anuke/mindustry/world/blocks/BlockPart.java b/core/src/io/anuke/mindustry/world/blocks/BlockPart.java index bc82001109..e177217ffa 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BlockPart.java +++ b/core/src/io/anuke/mindustry/world/blocks/BlockPart.java @@ -1,7 +1,5 @@ package io.anuke.mindustry.world.blocks; -import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.type.Liquid; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; @@ -11,18 +9,36 @@ import io.anuke.mindustry.world.Tile; * They are made to share all properties from the linked tile/block. */ public class BlockPart extends Block{ + public final static int maxSize = 9; + private final static BlockPart[][] parts = new BlockPart[maxSize][maxSize]; - public BlockPart(){ - super("part"); + private final int dx, dy; + + public BlockPart(int dx, int dy){ + super("part_" + dx + "_" + dy); + this.dx = dx; + this.dy = dy; solid = false; hasPower = hasItems = hasLiquids = true; + parts[dx + maxSize/2][dy + maxSize/2] = this; + } + + public static BlockPart get(int dx, int dy){ + if(dx == -maxSize/2 && dy == -maxSize/2) throw new IllegalArgumentException("Why are you getting a [0,0] blockpart? Stop it."); + return parts[dx + maxSize/2][dy + maxSize/2]; } @Override - public void drawTeam(Tile tile){ - + public Tile linked(Tile tile){ + return tile.getNearby(-dx, -dy); } + @Override + public void drawTeam(Tile tile){} + + @Override + public void draw(Tile tile){} + @Override public boolean synthetic(){ return true; @@ -34,42 +50,9 @@ public class BlockPart extends Block{ } @Override - public void draw(Tile tile){ - //do nothing + public String toString(){ + return "BlockPart[" + dx + ", " + dy + "]"; } - @Override - public boolean isSolidFor(Tile tile){ - return tile.getLinked() == null - || (tile.getLinked().block() instanceof BlockPart || tile.getLinked().solid() - || tile.getLinked().block().isSolidFor(tile.getLinked())); - } - - @Override - public void handleItem(Item item, Tile tile, Tile source){ - tile.getLinked().block().handleItem(item, tile.getLinked(), source); - } - - @Override - public boolean acceptItem(Item item, Tile tile, Tile source){ - return tile.getLinked().block().acceptItem(item, tile.getLinked(), source); - } - - @Override - public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - Block block = linked(tile); - return block.hasLiquids - && block.acceptLiquid(tile.getLinked(), source, liquid, amount); - } - - @Override - public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - Block block = linked(tile); - block.handleLiquid(tile.getLinked(), source, liquid, amount); - } - - private Block linked(Tile tile){ - return tile.getLinked().block(); - } } diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index f1fb7c7929..e1205b465e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -1,7 +1,6 @@ package io.anuke.mindustry.world.blocks; -import io.anuke.annotations.Annotations.Loc; -import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.Annotations.*; import io.anuke.arc.Core; import io.anuke.arc.Events; import io.anuke.arc.Graphics.Cursor; @@ -28,15 +27,25 @@ import java.io.*; import static io.anuke.mindustry.Vars.*; public class BuildBlock extends Block{ + public static final int maxSize = 9; + private static final BuildBlock[] buildBlocks = new BuildBlock[maxSize]; - public BuildBlock(String name){ - super(name); + public BuildBlock(int size){ + super("build" + size); + this.size = size; update = true; - size = Integer.parseInt(name.charAt(name.length() - 1) + ""); health = 20; layer = Layer.placement; consumesTap = true; solidifes = true; + + buildBlocks[size - 1] = this; + } + + /** Returns a BuildBlock by size. */ + public static BuildBlock get(int size){ + if(size > maxSize) throw new IllegalArgumentException("No. Don't place BuildBlocks of size greater than " + maxSize); + return buildBlocks[size - 1]; } @Remote(called = Loc.server) @@ -102,7 +111,7 @@ public class BuildBlock extends Block{ //if the target is constructible, begin constructing if(entity.cblock != null){ player.clearBuilding(); - player.addBuildRequest(new BuildRequest(tile.x, tile.y, tile.getRotation(), entity.cblock)); + player.addBuildRequest(new BuildRequest(tile.x, tile.y, tile.rotation(), entity.cblock)); } } @@ -127,7 +136,7 @@ public class BuildBlock extends Block{ if(entity.previous == null) return; if(Core.atlas.isFound(entity.previous.icon(Icon.full))){ - Draw.rect(entity.previous.icon(Icon.full), tile.drawx(), tile.drawy(), entity.previous.rotate ? tile.getRotation() * 90 : 0); + Draw.rect(entity.previous.icon(Icon.full), tile.drawx(), tile.drawy(), entity.previous.rotate ? tile.rotation() * 90 : 0); } } @@ -146,7 +155,7 @@ public class BuildBlock extends Block{ Shaders.blockbuild.region = region; Shaders.blockbuild.progress = entity.progress; - Draw.rect(region, tile.drawx(), tile.drawy(), target.rotate ? tile.getRotation() * 90 : 0); + Draw.rect(region, tile.drawx(), tile.drawy(), target.rotate ? tile.rotation() * 90 : 0); Draw.flush(); } } @@ -175,13 +184,13 @@ public class BuildBlock extends Block{ private float[] accumulator; private float[] totalAccumulator; - public void construct(Unit builder, TileEntity core, float amount){ + public void construct(Unit builder, @Nullable TileEntity core, float amount){ if(cblock == null){ kill(); return; } - float maxProgress = checkRequired(core.items, amount, false); + float maxProgress = core == null ? amount : checkRequired(core.items, amount, false); for(int i = 0; i < cblock.buildRequirements.length; i++){ int reqamount = Math.round(state.rules.buildCostMultiplier * cblock.buildRequirements[i].amount); @@ -189,7 +198,7 @@ public class BuildBlock extends Block{ totalAccumulator[i] = Math.min(totalAccumulator[i] + reqamount * maxProgress, reqamount); } - maxProgress = checkRequired(core.items, maxProgress, true); + maxProgress = core == null ? maxProgress : checkRequired(core.items, maxProgress, true); progress = Mathf.clamp(progress + maxProgress); @@ -198,11 +207,11 @@ public class BuildBlock extends Block{ } if(progress >= 1f || state.rules.infiniteResources){ - Call.onConstructFinish(tile, cblock, builderID, tile.getRotation(), builder.getTeam()); + Call.onConstructFinish(tile, cblock, builderID, tile.rotation(), builder.getTeam()); } } - public void deconstruct(Unit builder, TileEntity core, float amount){ + public void deconstruct(Unit builder, @Nullable TileEntity core, float amount){ float deconstructMultiplier = 0.5f; if(cblock != null){ @@ -219,10 +228,13 @@ public class BuildBlock extends Block{ int accumulated = (int)(accumulator[i]); //get amount if(amount > 0 && accumulated > 0){ //if it's positive, add it to the core - int accepting = core.tile.block().acceptStack(requirements[i].item, accumulated, core.tile, builder); - core.tile.block().handleStack(requirements[i].item, accepting, core.tile, builder); - - accumulator[i] -= accepting; + if(core != null){ + int accepting = core.tile.block().acceptStack(requirements[i].item, accumulated, core.tile, builder); + core.tile.block().handleStack(requirements[i].item, accepting, core.tile, builder); + accumulator[i] -= accepting; + }else{ + accumulator[i] -= accumulated; + } } } } @@ -292,6 +304,7 @@ public class BuildBlock extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeFloat(progress); stream.writeShort(previous == null ? -1 : previous.id); stream.writeShort(cblock == null ? -1 : cblock.id); @@ -308,7 +321,8 @@ public class BuildBlock extends Block{ } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); progress = stream.readFloat(); short pid = stream.readShort(); short rid = stream.readShort(); diff --git a/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java b/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java index f20db17790..f7b4b9566c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/LiquidBlock.java @@ -38,7 +38,7 @@ public class LiquidBlock extends Block{ public void draw(Tile tile){ LiquidModule mod = tile.entity.liquids; - int rotation = rotate ? tile.getRotation() * 90 : 0; + int rotation = rotate ? tile.rotation() * 90 : 0; Draw.rect(bottomRegion, tile.drawx(), tile.drawy(), rotation); diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/Door.java b/core/src/io/anuke/mindustry/world/blocks/defense/Door.java index 5676592267..07454f6849 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/Door.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/Door.java @@ -85,11 +85,13 @@ public class Door extends Wall{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeBoolean(open); } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); open = stream.readBoolean(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/ForceProjector.java b/core/src/io/anuke/mindustry/world/blocks/defense/ForceProjector.java index 6b9e611893..1db6033d79 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/ForceProjector.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/ForceProjector.java @@ -203,6 +203,7 @@ public class ForceProjector extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeBoolean(broken); stream.writeFloat(buildup); stream.writeFloat(radscl); @@ -211,7 +212,8 @@ public class ForceProjector extends Block{ } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); broken = stream.readBoolean(); buildup = stream.readFloat(); radscl = stream.readFloat(); diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/MendProjector.java b/core/src/io/anuke/mindustry/world/blocks/defense/MendProjector.java index df97089bda..dc8075a680 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/MendProjector.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/MendProjector.java @@ -85,10 +85,9 @@ public class MendProjector extends Block{ for(int y = -tileRange + tile.y; y <= tileRange + tile.y; y++){ if(Mathf.dst(x, y, tile.x, tile.y) > tileRange) continue; - Tile other = world.tile(x, y); + Tile other = world.ltile(x, y); if(other == null) continue; - other = other.target(); if(other.getTeamID() == tile.getTeamID() && !healed.contains(other.pos()) && other.entity != null && other.entity.health < other.entity.maxHealth()){ other.entity.healBy(other.entity.maxHealth() * (healPercent + entity.phaseHeat * phaseBoost) / 100f * entity.power.satisfaction); @@ -149,12 +148,14 @@ public class MendProjector extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeFloat(heat); stream.writeFloat(phaseHeat); } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); heat = stream.readFloat(); phaseHeat = stream.readFloat(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/OverdriveProjector.java b/core/src/io/anuke/mindustry/world/blocks/defense/OverdriveProjector.java index 2de2cd6b8f..98293c8066 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/OverdriveProjector.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/OverdriveProjector.java @@ -92,10 +92,9 @@ public class OverdriveProjector extends Block{ for(int y = -tileRange + tile.y; y <= tileRange + tile.y; y++){ if(Mathf.dst(x, y, tile.x, tile.y) > tileRange) continue; - Tile other = world.tile(x, y); + Tile other = world.ltile(x, y); if(other == null) continue; - other = other.target(); if(other.getTeamID() == tile.getTeamID() && !healed.contains(other.pos()) && other.entity != null){ other.entity.timeScaleDuration = Math.max(other.entity.timeScaleDuration, reload + 1f); @@ -148,12 +147,14 @@ public class OverdriveProjector extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeFloat(heat); stream.writeFloat(phaseHeat); } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); heat = stream.readFloat(); phaseHeat = stream.readFloat(); } 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 c89ef0c4cf..86ea2257a7 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 @@ -121,6 +121,7 @@ public class ItemTurret extends CooledTurret{ public class ItemTurretEntity extends TurretEntity{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeByte(ammo.size); for(AmmoEntry entry : ammo){ ItemEntry i = (ItemEntry)entry; @@ -130,7 +131,8 @@ public class ItemTurret extends CooledTurret{ } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); byte amount = stream.readByte(); for(int i = 0; i < amount; i++){ Item item = Vars.content.item(stream.readByte()); diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LaserTurret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LaserTurret.java index fabfa0349d..f619d1727f 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LaserTurret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/LaserTurret.java @@ -60,7 +60,7 @@ public class LaserTurret extends PowerTurret{ return; } - if(entity.reload >= reload && entity.cons.valid()){ + if(entity.reload >= reload && (entity.cons.valid() || tile.isEnemyCheat())){ BulletType type = peekAmmo(tile); shoot(tile, type); @@ -70,7 +70,7 @@ public class LaserTurret extends PowerTurret{ Liquid liquid = entity.liquids.current(); float maxUsed = consumes.get(ConsumeType.liquid).amount; - float used = Math.min(entity.liquids.get(liquid), maxUsed * Time.delta()); + float used = tile.isEnemyCheat() ? maxUsed : Math.min(entity.liquids.get(liquid), maxUsed * Time.delta()); entity.reload += used; entity.liquids.remove(liquid, used); diff --git a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java index 1780ba40c9..52d0cba905 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/turrets/Turret.java @@ -146,8 +146,7 @@ public abstract class Turret extends Block{ public void update(Tile tile){ TurretEntity entity = tile.entity(); - if(entity.target != null && entity.target.isDead()) - entity.target = null; + if(!validateTarget(tile)) entity.target = null; entity.recoil = Mathf.lerpDelta(entity.recoil, 0f, restitution); entity.heat = Mathf.lerpDelta(entity.heat, 0f, cooldown); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java index 709c84300e..74d47e2c6d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java @@ -6,6 +6,8 @@ import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.ItemBuffer; import io.anuke.mindustry.world.Tile; +import java.io.*; + public class BufferedItemBridge extends ExtendingItemBridge{ protected int timerAccept = timers++; @@ -43,5 +45,17 @@ public class BufferedItemBridge extends ExtendingItemBridge{ class BufferedItemBridgeEntity extends ItemBridgeEntity{ ItemBuffer buffer = new ItemBuffer(bufferCapacity, speed); + + @Override + public void write(DataOutput stream) throws IOException{ + super.write(stream); + buffer.write(stream); + } + + @Override + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); + buffer.read(stream); + } } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java index 44c54540ce..e4b4bc1950 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java @@ -10,8 +10,6 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.LiquidBlock; import io.anuke.mindustry.world.modules.LiquidModule; -import java.io.*; - public class Conduit extends LiquidBlock{ protected final int timerFlow = timers++; @@ -62,17 +60,17 @@ public class Conduit extends LiquidBlock{ } private boolean blends(Tile tile, int direction){ - Tile other = tile.getNearby(Mathf.mod(tile.getRotation() - direction, 4)); - if(other != null) other = other.target(); + Tile other = tile.getNearby(Mathf.mod(tile.rotation() - direction, 4)); + if(other != null) other = other.link(); - return other != null && other.block().hasLiquids && other.block().outputsLiquid && ((tile.getNearby(tile.getRotation()) == other) || (!other.block().rotate || other.getNearby(other.getRotation()) == tile)); + return other != null && other.block().hasLiquids && other.block().outputsLiquid && ((tile.getNearby(tile.rotation()) == other) || (!other.block().rotate || other.getNearby(other.rotation()) == tile)); } @Override public void draw(Tile tile){ ConduitEntity entity = tile.entity(); LiquidModule mod = tile.entity.liquids; - int rotation = tile.getRotation() * 90; + int rotation = tile.rotation() * 90; Draw.colorl(0.34f); Draw.rect(botRegions[entity.blendbits], tile.drawx(), tile.drawy(), rotation); @@ -91,7 +89,7 @@ public class Conduit extends LiquidBlock{ entity.smoothLiquid = Mathf.lerpDelta(entity.smoothLiquid, entity.liquids.total() / liquidCapacity, 0.05f); if(tile.entity.liquids.total() > 0.001f && tile.entity.timer.get(timerFlow, 1)){ - tryMoveLiquid(tile, tile.getNearby(tile.getRotation()), true, tile.entity.liquids.current()); + tryMoveLiquid(tile, tile.getNearby(tile.rotation()), true, tile.entity.liquids.current()); entity.noSleep(); }else{ entity.sleep(); @@ -106,7 +104,7 @@ public class Conduit extends LiquidBlock{ @Override public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ tile.entity.noSleep(); - return tile.entity.liquids.get(liquid) + amount < liquidCapacity && (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.2f) && ((2 + source.relativeTo(tile.x, tile.y)) % 4 != tile.getRotation()); + return tile.entity.liquids.get(liquid) + amount < liquidCapacity && (tile.entity.liquids.current() == liquid || tile.entity.liquids.get(tile.entity.liquids.current()) < 0.2f) && ((2 + source.relativeTo(tile.x, tile.y)) % 4 != tile.rotation()); } @Override @@ -119,15 +117,5 @@ public class Conduit extends LiquidBlock{ byte blendbits; int blendshadowrot; - - @Override - public void write(DataOutput stream) throws IOException{ - stream.writeFloat(smoothLiquid); - } - - @Override - public void read(DataInput stream) throws IOException{ - smoothLiquid = stream.readFloat(); - } } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java index 1e602a1ff5..a2cbf90315 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java @@ -69,7 +69,7 @@ public class Conveyor extends Block{ @Override public void draw(Tile tile){ ConveyorEntity entity = tile.entity(); - byte rotation = tile.getRotation(); + byte rotation = tile.rotation(); int frame = entity.clogHeat <= 0.5f ? (int)(((Time.time() * speed * 8f * entity.timeScale)) % 4) : 0; Draw.rect(regions[Mathf.clamp(entity.blendbits, 0, regions.length - 1)][Mathf.clamp(frame, 0, regions[0].length - 1)], tile.drawx(), tile.drawy(), @@ -125,11 +125,11 @@ public class Conveyor extends Block{ } private boolean blends(Tile tile, int direction){ - Tile other = tile.getNearby(Mathf.mod(tile.getRotation() - direction, 4)); - if(other != null) other = other.target(); + Tile other = tile.getNearby(Mathf.mod(tile.rotation() - direction, 4)); + if(other != null) other = other.link(); return other != null && other.block().outputsItems() - && ((tile.getNearby(tile.getRotation()) == other) || (!other.block().rotate || other.getNearby(other.getRotation()) == tile)); + && ((tile.getNearby(tile.rotation()) == other) || (!other.block().rotate || other.getNearby(other.rotation()) == tile)); } @Override @@ -141,7 +141,7 @@ public class Conveyor extends Block{ public void drawLayer(Tile tile){ ConveyorEntity entity = tile.entity(); - byte rotation = tile.getRotation(); + byte rotation = tile.rotation(); try{ @@ -176,7 +176,7 @@ public class Conveyor extends Block{ float speed = this.speed * tilesize / 2.4f; float centerSpeed = 0.1f; float centerDstScl = 3f; - float tx = Geometry.d4[tile.getRotation()].x, ty = Geometry.d4[tile.getRotation()].y; + float tx = Geometry.d4[tile.rotation()].x, ty = Geometry.d4[tile.rotation()].y; float centerx = 0f, centery = 0f; @@ -197,7 +197,8 @@ public class Conveyor extends Block{ public void update(Tile tile){ ConveyorEntity entity = tile.entity(); entity.minitem = 1f; - Tile next = tile.getNearby(tile.getRotation()); + Tile next = tile.getNearby(tile.rotation()); + if(next != null) next = next.link(); float nextMax = next != null && next.block() instanceof Conveyor ? 1f - Math.max(itemSpace - next.entity().minitem, 0) : 1f; int minremove = Integer.MAX_VALUE; @@ -231,7 +232,7 @@ public class Conveyor extends Block{ ItemPos ni = pos2.set(othere.convey.get(othere.lastInserted), ItemPos.updateShorts); - if(next.getRotation() == tile.getRotation()){ + if(next.rotation() == tile.rotation()){ ni.x = pos.x; } othere.convey.set(othere.lastInserted, ni.pack()); @@ -290,7 +291,7 @@ public class Conveyor extends Block{ @Override public void getStackOffset(Item item, Tile tile, Vector2 trns){ - trns.trns(tile.getRotation() * 90 + 180f, tilesize / 2f); + trns.trns(tile.rotation() * 90 + 180f, tilesize / 2f); } @Override @@ -314,15 +315,15 @@ public class Conveyor extends Block{ @Override public boolean acceptItem(Item item, Tile tile, Tile source){ - int direction = source == null ? 0 : Math.abs(source.relativeTo(tile.x, tile.y) - tile.getRotation()); + int direction = source == null ? 0 : Math.abs(source.relativeTo(tile.x, tile.y) - tile.rotation()); float minitem = tile.entity().minitem; return (((direction == 0) && minitem > itemSpace) || - ((direction % 2 == 1) && minitem > 0.52f)) && (source == null || !(source.block().rotate && (source.getRotation() + 2) % 4 == tile.getRotation())); + ((direction % 2 == 1) && minitem > 0.52f)) && (source == null || !(source.block().rotate && (source.rotation() + 2) % 4 == tile.rotation())); } @Override public void handleItem(Item item, Tile tile, Tile source){ - byte rotation = tile.getRotation(); + byte rotation = tile.rotation(); int ch = Math.abs(source.relativeTo(tile.x, tile.y) - rotation); int ang = ((source.relativeTo(tile.x, tile.y) - rotation)); @@ -367,6 +368,7 @@ public class Conveyor extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeInt(convey.size); for(int i = 0; i < convey.size; i++){ @@ -375,7 +377,8 @@ public class Conveyor extends Block{ } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); convey.clear(); int amount = stream.readInt(); convey.ensureCapacity(Math.min(amount, 10)); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java index 2218991af5..22ca19a48a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java @@ -252,7 +252,7 @@ public class ItemBridge extends Block{ @Override public boolean acceptItem(Item item, Tile tile, Tile source){ - if(tile.getTeam() != source.target().getTeam()) return false; + if(tile.getTeam() != source.getTeam()) return false; ItemBridgeEntity entity = tile.entity(); Tile other = world.tile(entity.link); @@ -327,6 +327,7 @@ public class ItemBridge extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeInt(link); stream.writeFloat(uptime); stream.writeByte(incoming.size); @@ -339,7 +340,8 @@ public class ItemBridge extends Block{ } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); link = stream.readInt(); uptime = stream.readFloat(); byte links = stream.readByte(); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java index b3f0227266..3760197ac8 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java @@ -7,6 +7,8 @@ import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.meta.BlockGroup; +import java.io.*; + import static io.anuke.mindustry.Vars.content; public class Junction extends Block{ @@ -72,7 +74,7 @@ public class Junction extends Block{ if(entity == null || relative == -1 || entity.buffers[relative].full()) return false; Tile to = tile.getNearby(relative); - return to != null && to.target().entity != null; + return to != null && to.link().entity != null; } @Override @@ -82,6 +84,22 @@ public class Junction extends Block{ class JunctionEntity extends TileEntity{ Buffer[] buffers = {new Buffer(), new Buffer(), new Buffer(), new Buffer()}; + + @Override + public void write(DataOutput stream) throws IOException{ + super.write(stream); + for(Buffer b : buffers){ + b.write(stream); + } + } + + @Override + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); + for(Buffer b : buffers){ + b.read(stream); + } + } } class Buffer{ @@ -96,5 +114,24 @@ public class Junction extends Block{ boolean full(){ return index >= items.length - 1; } + + void write(DataOutput stream) throws IOException{ + stream.writeByte((byte)index); + stream.writeByte((byte)items.length); + for(long l : items){ + stream.writeLong(l); + } + } + + void read(DataInput stream) throws IOException{ + index = stream.readByte(); + byte length = stream.readByte(); + for(int i = 0; i < length; i++){ + long l = stream.readLong(); + if(i < items.length){ + items[i] = l; + } + } + } } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java index 1999136d89..cf3c96cdab 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidBridge.java @@ -59,7 +59,7 @@ public class LiquidBridge extends ItemBridge{ @Override public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){ - if(tile.getTeam() != source.target().getTeam()) return false; + if(tile.getTeam() != source.getTeam()) return false; ItemBridgeEntity entity = tile.entity(); Tile other = world.tile(entity.link); diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java index a160348107..c2942cb797 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/LiquidJunction.java @@ -41,7 +41,7 @@ public class LiquidJunction extends LiquidBlock{ public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){ int dir = source.relativeTo(tile.x, tile.y); dir = (dir + 4) % 4; - Tile to = tile.getNearby(dir).target(); + Tile to = tile.getNearby(dir).link(); if(to.block().hasLiquids && to.block().acceptLiquid(to, tile, liquid, amount)){ to.block().handleLiquid(to, tile, liquid, amount); @@ -54,7 +54,7 @@ public class LiquidJunction extends LiquidBlock{ dir = (dir + 4) % 4; Tile to = dest.getNearby(dir); if(to == null) return false; - to = to.target(); + to = to.link(); return to != null && to.entity != null && to.block().hasLiquids && to.block().acceptLiquid(to, dest, liquid, amount); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java index 03feb4e4ef..ee83e508ae 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java @@ -23,8 +23,6 @@ import io.anuke.mindustry.graphics.Pal; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.meta.BlockStat; -import io.anuke.mindustry.world.meta.StatUnit; import java.io.*; @@ -323,13 +321,15 @@ public class MassDriver extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeInt(link); stream.writeFloat(rotation); stream.writeByte((byte)state.ordinal()); } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); link = stream.readInt(); rotation = stream.readFloat(); state = DriverState.values()[stream.readByte()]; diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java index 6ba2d1496b..de9532a56c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/OverflowGate.java @@ -58,12 +58,12 @@ public class OverflowGate extends Router{ }else if(bc && !ac){ to = b; }else{ - if(tile.getDump() == 0){ + if(tile.rotation() == 0){ to = a; - if(flip) tile.setDump((byte)1); + if(flip) tile.rotation((byte)1); }else{ to = b; - if(flip) tile.setDump((byte)0); + if(flip) tile.rotation((byte)0); } } } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java index a272f1011e..21a7baf18c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Router.java @@ -58,11 +58,11 @@ public class Router extends Block{ Tile getTileTarget(Tile tile, Item item, Tile from, boolean set){ Array proximity = tile.entity.proximity(); - int counter = tile.getDump(); + int counter = tile.rotation(); for(int i = 0; i < proximity.size; i++){ Tile other = proximity.get((i + counter) % proximity.size); if(tile == from) continue; - if(set) tile.setDump((byte)((tile.getDump() + 1) % proximity.size)); + if(set) tile.rotation((byte)((tile.rotation() + 1) % proximity.size)); if(other.block().acceptItem(item, other, Edges.getFacingEdge(tile, other))){ return other; } diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java index e62e0da1a8..ff5888dfbf 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java @@ -99,14 +99,14 @@ public class Sorter extends Block{ }else if(!bc){ return null; }else{ - if(dest.getDump() == 0){ + if(dest.rotation() == 0){ to = a; if(flip) - dest.setDump((byte)1); + dest.rotation((byte)1); }else{ to = b; if(flip) - dest.setDump((byte)0); + dest.rotation((byte)0); } } } @@ -132,14 +132,15 @@ public class Sorter extends Block{ public Item sortItem; @Override - public void writeConfig(DataOutput stream) throws IOException{ - stream.writeByte(sortItem == null ? -1 : sortItem.id); + public void write(DataOutput stream) throws IOException{ + super.write(stream); + stream.writeShort(sortItem == null ? -1 : sortItem.id); } @Override - public void readConfig(DataInput stream) throws IOException{ - byte b = stream.readByte(); - sortItem = b == -1 ? null : content.items().get(b); + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); + sortItem = content.item(stream.readShort()); } } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ImpactReactor.java b/core/src/io/anuke/mindustry/world/blocks/power/ImpactReactor.java index 71e018bb56..e3d04259c6 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ImpactReactor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ImpactReactor.java @@ -172,8 +172,8 @@ public class ImpactReactor extends PowerGenerator{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); warmup = stream.readFloat(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java index 6af6b8f13f..9088a19e3d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -25,7 +25,6 @@ import static io.anuke.mindustry.Vars.tilesize; * Liquids will take priority over items. */ public class ItemLiquidGenerator extends PowerGenerator{ - protected float minItemEfficiency = 0.2f; /** The time in number of ticks during which a single item will produce power. */ protected float itemDuration = 70f; @@ -71,6 +70,12 @@ public class ItemLiquidGenerator extends PowerGenerator{ } } + @Override + public boolean shouldConsume(Tile tile){ + ItemLiquidGeneratorEntity entity = tile.entity(); + return entity.generateTime > 0; + } + @Override public void update(Tile tile){ ItemLiquidGeneratorEntity entity = tile.entity(); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java index 6468094447..bde3b43176 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java @@ -183,11 +183,13 @@ public class NuclearReactor extends PowerGenerator{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeFloat(heat); } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); heat = stream.readFloat(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java index fa0043dbbc..f4e1597445 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java @@ -63,11 +63,13 @@ public class PowerGenerator extends PowerDistributor{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeFloat(productionEfficiency); } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); productionEfficiency = stream.readFloat(); } } 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 3915f563eb..af73a01302 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerNode.java @@ -101,7 +101,7 @@ public class PowerNode extends PowerBlock{ Tile before = world.tile(lastPlaced); if(linkValid(tile, before) && before.block() instanceof PowerNode){ for(Tile near : before.entity.proximity()){ - if(near.target() == tile){ + if(near == tile){ lastPlaced = tile.pos(); return; } @@ -127,7 +127,7 @@ public class PowerNode extends PowerBlock{ @Override public boolean onConfigureTileTapped(Tile tile, Tile other){ TileEntity entity = tile.entity(); - other = other.target(); + other = other.link(); Tile result = other; @@ -167,8 +167,7 @@ public class PowerNode extends PowerBlock{ for(int x = (int)(tile.x - laserRange - 1); x <= tile.x + laserRange + 1; x++){ for(int y = (int)(tile.y - laserRange - 1); y <= tile.y + laserRange + 1; y++){ - Tile link = world.tile(x, y); - if(link != null) link = link.target(); + Tile link = world.ltile(x, y); if(link != tile && linkValid(tile, link, false)){ boolean linked = linked(tile, link); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/SingleTypeGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/SingleTypeGenerator.java index 9caf39dc5c..72c0ec9e4b 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/SingleTypeGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/SingleTypeGenerator.java @@ -4,6 +4,7 @@ import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Liquid; public class SingleTypeGenerator extends ItemLiquidGenerator{ + public SingleTypeGenerator(boolean hasItems, boolean hasLiquids, String name){ super(hasItems, hasLiquids, name); } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java b/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java index 955c786520..07a7242bb2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java @@ -118,11 +118,13 @@ public class Cultivator extends GenericCrafter{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeFloat(warmup); } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); warmup = stream.readFloat(); } } diff --git a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java index 3b1f50544e..661b007331 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java @@ -58,6 +58,12 @@ public class GenericCrafter extends Block{ } } + @Override + public void init(){ + outputsLiquid = outputLiquid != null; + super.init(); + } + @Override public void draw(Tile tile){ if(drawer == null){ @@ -150,12 +156,14 @@ public class GenericCrafter extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeFloat(progress); stream.writeFloat(warmup); } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); progress = stream.readFloat(); warmup = stream.readFloat(); } diff --git a/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java b/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java index 35723aa00d..52f506d35a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java +++ b/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java @@ -9,7 +9,6 @@ import io.anuke.arc.scene.style.TextureRegionDrawable; import io.anuke.arc.scene.ui.ButtonGroup; import io.anuke.arc.scene.ui.ImageButton; import io.anuke.arc.scene.ui.layout.Table; -import io.anuke.mindustry.content.Liquids; import io.anuke.mindustry.entities.type.Player; import io.anuke.mindustry.entities.type.TileEntity; import io.anuke.mindustry.gen.Call; @@ -109,19 +108,21 @@ public class LiquidSource extends Block{ @Remote(targets = Loc.both, called = Loc.both, forward = true) public static void setLiquidSourceLiquid(Player player, Tile tile, Liquid liquid){ LiquidSourceEntity entity = tile.entity(); - entity.source = liquid; + if(entity != null) entity.source = liquid; } class LiquidSourceEntity extends TileEntity{ public Liquid source = null; @Override - public void writeConfig(DataOutput stream) throws IOException{ + public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeByte(source == null ? -1 : source.id); } @Override - public void readConfig(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); byte id = stream.readByte(); source = id == -1 ? null : content.liquid(id); } diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java index f755175992..ce784a3b7a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java @@ -35,8 +35,7 @@ public class Unloader extends Block{ @Override public boolean canDump(Tile tile, Tile to, Item item){ - Block block = to.target().block(); - return !(block instanceof StorageBlock); + return !(to.block() instanceof StorageBlock); } @Override @@ -104,12 +103,14 @@ public class Unloader extends Block{ public Item sortItem = null; @Override - public void writeConfig(DataOutput stream) throws IOException{ + public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeByte(sortItem == null ? -1 : sortItem.id); } @Override - public void readConfig(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); byte id = stream.readByte(); sortItem = id == -1 ? null : content.items().get(id); } diff --git a/core/src/io/anuke/mindustry/world/blocks/units/MechPad.java b/core/src/io/anuke/mindustry/world/blocks/units/MechPad.java index 85e361bddc..b298f11773 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/MechPad.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/MechPad.java @@ -59,6 +59,7 @@ public class MechPad extends Block{ if(!entity.cons.valid()) return; player.beginRespawning(entity); + entity.sameMech = false; } @Remote(called = Loc.server) @@ -71,7 +72,7 @@ public class MechPad extends Block{ if(entity.player == null) return; Mech mech = ((MechPad)tile.block()).mech; - entity.player.mech = entity.player.mech == mech ? Mechs.starter : mech; + entity.player.mech = !entity.sameMech && entity.player.mech == mech ? Mechs.starter : mech; entity.progress = 0; entity.player.onRespawn(tile); @@ -113,7 +114,7 @@ public class MechPad extends Block{ Draw.rect(Core.atlas.find(name), tile.drawx(), tile.drawy()); if(entity.player != null){ - TextureRegion region = (entity.player.mech == mech ? Mechs.starter.iconRegion : mech.iconRegion); + TextureRegion region = (!entity.sameMech && entity.player.mech == mech ? Mechs.starter.iconRegion : mech.iconRegion); Shaders.build.region = region; Shaders.build.progress = entity.progress; @@ -162,6 +163,7 @@ public class MechPad extends Block{ public class MechFactoryEntity extends TileEntity implements SpawnerTrait{ Player player; + boolean sameMech; float progress; float time; float heat; @@ -171,6 +173,7 @@ public class MechPad extends Block{ if(player == null){ progress = 0f; player = unit; + sameMech = true; player.beginRespawning(this); } @@ -178,13 +181,15 @@ public class MechPad extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeFloat(progress); stream.writeFloat(time); stream.writeFloat(heat); } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); progress = stream.readFloat(); time = stream.readFloat(); heat = stream.readFloat(); diff --git a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java index 7d7077a371..7e4a6d483d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java @@ -196,15 +196,15 @@ public class UnitFactory extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeFloat(buildTime); - stream.writeFloat(0f); stream.writeInt(spawned); } @Override - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); buildTime = stream.readFloat(); - stream.readFloat(); //unneeded information, will remove later spawned = stream.readInt(); } } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java b/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java index f2c9edac07..fe9e0a17d4 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumeItemFilter.java @@ -1,6 +1,5 @@ package io.anuke.mindustry.world.consumers; -import io.anuke.arc.collection.Array; import io.anuke.arc.function.Predicate; import io.anuke.arc.scene.ui.layout.Table; import io.anuke.mindustry.entities.type.TileEntity; @@ -33,9 +32,8 @@ public class ConsumeItemFilter extends Consume{ @Override public void build(Tile tile, Table table){ - Array list = content.items().select(filter); MultiReqImage image = new MultiReqImage(); - list.each(item -> image.add(new ReqImage(new ItemImage(item.icon(Icon.large), 1), () -> tile.entity != null && tile.entity.items != null && tile.entity.items.has(item)))); + content.items().each(filter, item -> image.add(new ReqImage(new ItemImage(item.icon(Icon.large), 1), () -> tile.entity != null && tile.entity.items != null && tile.entity.items.has(item)))); table.add(image).size(8 * 4); } diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java index a8499aa995..4cccab941f 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java @@ -1,17 +1,12 @@ package io.anuke.mindustry.desktop; -import io.anuke.arc.ApplicationListener; import io.anuke.arc.backends.lwjgl3.Lwjgl3Application; import io.anuke.arc.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import io.anuke.mindustry.Mindustry; import io.anuke.mindustry.core.Platform; import io.anuke.mindustry.net.*; -public class DesktopLauncher extends Lwjgl3Application{ - - public DesktopLauncher(ApplicationListener listener, Lwjgl3ApplicationConfiguration config){ - super(listener, config); - } +public class DesktopLauncher{ public static void main(String[] arg){ try{ @@ -26,7 +21,7 @@ public class DesktopLauncher extends Lwjgl3Application{ Net.setClientProvider(new ArcNetClient()); Net.setServerProvider(new ArcNetServer()); - new DesktopLauncher(new Mindustry(), config); + new Lwjgl3Application(new Mindustry(), config); }catch(Throwable e){ DesktopPlatform.handleCrash(e); } diff --git a/gradle.properties b/gradle.properties index 28fb6f7a7c..7e937afc9e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -android.enableD8=true \ No newline at end of file +#TODO set to true on release; see if it fixes things: +#android.enableD8.desugaring=false \ No newline at end of file diff --git a/net/src/io/anuke/mindustry/net/ArcNetClient.java b/net/src/io/anuke/mindustry/net/ArcNetClient.java index 3e267b8703..068b47cbb6 100644 --- a/net/src/io/anuke/mindustry/net/ArcNetClient.java +++ b/net/src/io/anuke/mindustry/net/ArcNetClient.java @@ -31,7 +31,7 @@ public class ArcNetClient implements ClientProvider{ handler = new ClientDiscoveryHandler(){ @Override public DatagramPacket newDatagramPacket(){ - return new DatagramPacket(new byte[128], 128); + return new DatagramPacket(new byte[256], 256); } @Override diff --git a/server/src/io/anuke/mindustry/server/MindustryServer.java b/server/src/io/anuke/mindustry/server/MindustryServer.java index 0f61363859..37f13187b6 100644 --- a/server/src/io/anuke/mindustry/server/MindustryServer.java +++ b/server/src/io/anuke/mindustry/server/MindustryServer.java @@ -19,6 +19,7 @@ public class MindustryServer implements ApplicationListener{ @Override public void init(){ Core.settings.setDataDirectory(Core.files.local("config")); + loadLocales = false; Vars.init(); headless = true; diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index 3d1fa9f8f2..03214a847d 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -235,7 +235,7 @@ public class ServerControl implements ApplicationListener{ info("Loading map..."); logic.reset(); - state.rules = preset.get(); + state.rules = preset.apply(result.rules()); try{ world.loadMap(result); logic.play(); @@ -357,6 +357,16 @@ public class ServerControl implements ApplicationListener{ } }); + handler.register("name", "[name...]", "Change the server display name.", arg -> { + if(arg.length == 0){ + info("Server name is currently &lc'{0}'.", Core.settings.getString("servername")); + return; + } + Core.settings.put("servername", arg[0]); + Core.settings.save(); + info("Server name is now &lc'{0}'.", arg[0]); + }); + handler.register("crashreport", "", "Disables or enables automatic crash reporting", arg -> { boolean value = arg[0].equalsIgnoreCase("on"); Core.settings.put("crashreport", value); @@ -775,6 +785,8 @@ public class ServerControl implements ApplicationListener{ socketOutput = null; } } + }catch(BindException b){ + err("Command input socket already in use. Is another instance of the server running?"); }catch(IOException e){ err("Terminating socket server."); e.printStackTrace(); diff --git a/server/src/io/anuke/mindustry/server/ServerLauncher.java b/server/src/io/anuke/mindustry/server/ServerLauncher.java index a6ed80333c..06f842c418 100644 --- a/server/src/io/anuke/mindustry/server/ServerLauncher.java +++ b/server/src/io/anuke/mindustry/server/ServerLauncher.java @@ -1,38 +1,18 @@ package io.anuke.mindustry.server; -import io.anuke.arc.ApplicationListener; import io.anuke.arc.backends.headless.HeadlessApplication; -import io.anuke.arc.backends.headless.HeadlessApplicationConfiguration; -import io.anuke.mindustry.net.CrashSender; -import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.ArcNetClient; -import io.anuke.mindustry.net.ArcNetServer; +import io.anuke.mindustry.net.*; -public class ServerLauncher extends HeadlessApplication{ - - public ServerLauncher(ApplicationListener listener, HeadlessApplicationConfiguration config){ - super(listener, config); - } +public class ServerLauncher{ public static void main(String[] args){ try{ - Net.setClientProvider(new ArcNetClient()); Net.setServerProvider(new ArcNetServer()); - - HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration(); - new ServerLauncher(new MindustryServer(args), config); + new HeadlessApplication(new MindustryServer(args), null, throwable -> CrashSender.send(throwable, f -> {})); }catch(Throwable t){ CrashSender.send(t, f -> {}); } - - //find and handle uncaught exceptions in libGDX thread - for(Thread thread : Thread.getAllStackTraces().keySet()){ - if(thread.getName().equals("HeadlessApplication")){ - thread.setUncaughtExceptionHandler((t, throwable) -> CrashSender.send(throwable, f -> {})); - break; - } - } } } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 384df3c359..40cab3a003 100644 --- a/settings.gradle +++ b/settings.gradle @@ -27,6 +27,7 @@ if(!hasProperty("release")){ use(':Arc:extensions:freetype', '../Arc/extensions/freetype') use(':Arc:extensions:recorder', '../Arc/extensions/recorder') use(':Arc:extensions:arcnet', '../Arc/extensions/arcnet') + use(':Arc:extensions:packer', '../Arc/extensions/packer') use(':Arc:backends', '../Arc/backends') use(':Arc:backends:backend-lwjgl3', '../Arc/backends/backend-lwjgl3') use(':Arc:backends:backend-android', '../Arc/backends/backend-android') diff --git a/tests/src/test/java/ApplicationTests.java b/tests/src/test/java/ApplicationTests.java index ef40719bf4..875ae50e6c 100644 --- a/tests/src/test/java/ApplicationTests.java +++ b/tests/src/test/java/ApplicationTests.java @@ -1,6 +1,5 @@ import io.anuke.arc.ApplicationCore; import io.anuke.arc.backends.headless.HeadlessApplication; -import io.anuke.arc.backends.headless.HeadlessApplicationConfiguration; import io.anuke.arc.collection.Array; import io.anuke.arc.math.geom.Point2; import io.anuke.arc.util.Log; @@ -18,6 +17,7 @@ import io.anuke.mindustry.io.SaveIO; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.*; +import io.anuke.mindustry.world.blocks.BlockPart; import org.junit.jupiter.api.*; import static io.anuke.mindustry.Vars.*; @@ -63,16 +63,7 @@ public class ApplicationTests{ } }; - HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration(); - - new HeadlessApplication(core, config); - - for(Thread thread : Thread.getAllStackTraces().keySet()){ - if(thread.getName().equals("HeadlessApplication")){ - thread.setUncaughtExceptionHandler((t, throwable) -> exceptionThrown[0] = throwable); - break; - } - } + new HeadlessApplication(core, null, throwable -> exceptionThrown[0] = throwable); while(!begins[0]){ if(exceptionThrown[0] != null){ @@ -125,7 +116,7 @@ public class ApplicationTests{ world.beginMapLoad(); for(int x = 0; x < tiles.length; x++){ for(int y = 0; y < tiles[0].length; y++){ - tiles[x][y] = new Tile(x, y, (byte)0, (byte)0); + tiles[x][y] = new Tile(x, y); } } world.endMapLoad(); @@ -143,7 +134,7 @@ public class ApplicationTests{ if(x == bx && by == y){ assertEquals(world.tile(x, y).block(), Blocks.coreShard); }else{ - assertTrue(world.tile(x, y).block() == Blocks.part && world.tile(x, y).getLinked() == world.tile(bx, by)); + assertTrue(world.tile(x, y).block() instanceof BlockPart && world.tile(x, y).link() == world.tile(bx, by)); } } } @@ -262,7 +253,7 @@ public class ApplicationTests{ assertEquals(Blocks.copperWallLarge, world.tile(0, 0).block()); assertEquals(Blocks.air, world.tile(2, 2).block()); - assertEquals(Blocks.part, world.tile(1, 1).block()); + assertTrue(world.tile(1, 1).block() instanceof BlockPart); } @Test @@ -335,7 +326,7 @@ public class ApplicationTests{ world.beginMapLoad(); for(int x = 0; x < tiles.length; x++){ for(int y = 0; y < tiles[0].length; y++){ - tiles[x][y] = new Tile(x, y, Blocks.stone.id, (byte)0); + tiles[x][y] = new Tile(x, y, Blocks.stone.id, (byte)0, (byte)0); } } int i = 0; @@ -379,9 +370,11 @@ public class ApplicationTests{ void depositTest(Block block, Item item){ BaseUnit unit = UnitTypes.spirit.create(Team.none); - Tile tile = new Tile(0, 0, Blocks.air.id, block.id); + Tile tile = new Tile(0, 0, Blocks.air.id, (byte)0, block.id); int capacity = tile.block().itemCapacity; + assertNotNull(tile.entity, "Tile should have an entity, but does not: " + tile); + int deposited = tile.block().acceptStack(item, capacity - 1, tile, unit); assertEquals(capacity - 1, deposited); diff --git a/tests/src/test/java/WorldTests.java b/tests/src/test/java/WorldTests.java index 2b4f480bfa..5809b24c20 100644 --- a/tests/src/test/java/WorldTests.java +++ b/tests/src/test/java/WorldTests.java @@ -31,7 +31,7 @@ public class WorldTests{ for(int x = 0; x < tiles.length; x++){ for(int y = 0; y < tiles[0].length; y++){ - assertEquals(4, tiles[x][y].getRotation()); + assertEquals(4, tiles[x][y].rotation()); } } } @@ -43,7 +43,7 @@ public class WorldTests{ for(int x = 0; x < tiles.length; x++){ for(int y = 0; y < tiles[0].length; y++){ - assertEquals(0, tiles[x][y].getRotation()); + assertEquals(0, tiles[x][y].rotation()); } } } @@ -55,7 +55,7 @@ public class WorldTests{ for(int x = 0; x < tiles.length; x++){ for(int y = 0; y < tiles[0].length; y++){ - assertEquals(0, tiles[x][y].getRotation()); + assertEquals(0, tiles[x][y].rotation()); } } } @@ -67,7 +67,15 @@ public class WorldTests{ for(int x = 0; x < tiles.length; x++){ for(int y = 0; y < tiles[0].length; y++){ - assertEquals(0, tiles[x][y].getRotation()); + assertEquals(0, tiles[x][y].rotation()); + } + } + } + + private static void fillWith(short tileID){ + for(int x = 0; x < tiles.length; x++){ + for(int y = 0; y < tiles[0].length; y++){ + tiles[x][y] = new Tile(x, y, (short)0, (short)0, tileID); } } } @@ -75,12 +83,12 @@ public class WorldTests{ @Test void addDarknessOneNotSolidMiddleNoDarkness(){ fillWith(Blocks.rocks.id); - tiles[5][5] = new Tile(5, 5, (byte)0, Blocks.copperWall.id, (byte)0, (byte)0); + tiles[5][5] = new Tile(5, 5, (byte)0, (byte)0, Blocks.copperWall.id); world.addDarkness(tiles); for(int x = 0; x < tiles.length; x++){ for(int y = 0; y < tiles[0].length; y++){ - byte darkness = tiles[x][y].getRotation(); + byte darkness = tiles[x][y].rotation(); int distance = Math.abs(x - 5) + Math.abs(y - 5); assertEquals(Math.min(Math.max(distance - 1, 0), 4), darkness); } @@ -90,23 +98,15 @@ public class WorldTests{ @Test void addDarknessOneNotSolidCornerNoDarkness(){ fillWith(Blocks.rocks.id); - tiles[7][7] = new Tile(5, 5, (byte)0, Blocks.copperWall.id, (byte)0, (byte)0); + tiles[7][7] = new Tile(5, 5, (byte)0, (byte)0, Blocks.copperWall.id); world.addDarkness(tiles); for(int x = 0; x < tiles.length; x++){ for(int y = 0; y < tiles[0].length; y++){ - byte darkness = tiles[x][y].getRotation(); + byte darkness = tiles[x][y].rotation(); int distance = Math.abs(x - 7) + Math.abs(y - 7); assertEquals(Math.min(Math.max(distance - 1, 0), 4), darkness); } } } - - private static void fillWith(byte tileID){ - for(int x = 0; x < tiles.length; x++){ - for(int y = 0; y < tiles[0].length; y++){ - tiles[x][y] = new Tile(x, y, (byte)0, tileID, (byte)0, (byte)0); - } - } - } } diff --git a/tools/build.gradle b/tools/build.gradle index 16781d2917..23dfbcf530 100644 --- a/tools/build.gradle +++ b/tools/build.gradle @@ -168,7 +168,7 @@ task swapColors(){ task scaleSprites4x(){ doLast{ fileTree(dir: '../core/assets-raw/sprites_out/', include: "**/*.png").visit{ file -> - if(file.isDirectory() || file.toString().contains("/ui/")) return + if(file.isDirectory() || file.toString().replace("\\", "/").contains("/ui/")) return scaleImage(file.file) antialias(file.file) @@ -205,7 +205,7 @@ task pack(){ doLast{ fileTree(dir: '../core/assets-raw/sprites_out/', include: "**/*.png").visit{ file -> - if(file.isDirectory() || file.toString().contains("/ui/")) return + if(file.isDirectory() || file.toString().replace("\\", "/").contains("/ui/")) return antialias(file.file) } diff --git a/tools/src/io/anuke/mindustry/Generators.java b/tools/src/io/anuke/mindustry/Generators.java index c164860d52..6b476619c3 100644 --- a/tools/src/io/anuke/mindustry/Generators.java +++ b/tools/src/io/anuke/mindustry/Generators.java @@ -70,7 +70,7 @@ public class Generators{ }); ImagePacker.generate("block-icons", () -> { - Image colors = new Image(256, 1); + Image colors = new Image(content.blocks().size, 1); Color outlineColor = new Color(0, 0, 0, 0.3f); for(Block block : content.blocks()){