From af67690e7527e0fb5cce99e28ffa2fb2cf2b6671 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 5 May 2019 11:36:38 -0400 Subject: [PATCH 01/33] WIP save refactoring --- .../io/anuke/annotations/Annotations.java | 15 +++ .../CallSuperAnnotationProcessor.java | 65 ++++++++++++ .../annotations/CodeAnalyzerTreeScanner.java | 98 +++++++++++++++++++ .../javax.annotation.processing.Processor | 1 + build.gradle | 26 +++++ .../io/anuke/mindustry/core/NetClient.java | 4 +- .../io/anuke/mindustry/core/NetServer.java | 4 +- .../mindustry/entities/type/TileEntity.java | 11 +-- core/src/io/anuke/mindustry/io/MapIO.java | 8 +- .../anuke/mindustry/io/SaveFileVersion.java | 77 ++++++++++++++- core/src/io/anuke/mindustry/io/SaveIO.java | 21 ++-- .../mindustry/ui/dialogs/LoadDialog.java | 7 +- .../io/anuke/mindustry/world/ItemBuffer.java | 24 ++++- core/src/io/anuke/mindustry/world/Tile.java | 6 +- .../distribution/BufferedItemBridge.java | 14 +++ .../world/blocks/distribution/Junction.java | 35 +++++++ .../world/blocks/distribution/Sorter.java | 4 +- .../world/blocks/sandbox/LiquidSource.java | 4 +- .../world/blocks/storage/Unloader.java | 4 +- .../world/blocks/units/UnitFactory.java | 3 +- 20 files changed, 387 insertions(+), 44 deletions(-) create mode 100644 annotations/src/main/java/io/anuke/annotations/CallSuperAnnotationProcessor.java create mode 100644 annotations/src/main/java/io/anuke/annotations/CodeAnalyzerTreeScanner.java diff --git a/annotations/src/main/java/io/anuke/annotations/Annotations.java b/annotations/src/main/java/io/anuke/annotations/Annotations.java index 7961a57b89..06bde34e2e 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{ + /** 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}) @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/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..415b6fe183 100644 --- a/build.gradle +++ b/build.gradle @@ -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/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 534d98093c..2cdd52ad3b 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -7,7 +7,7 @@ import io.anuke.arc.collection.IntSet; import io.anuke.arc.graphics.Color; import io.anuke.arc.math.RandomXS128; import io.anuke.arc.util.*; -import io.anuke.arc.util.io.ReusableByteArrayInputStream; +import io.anuke.arc.util.io.ReusableByteInStream; import io.anuke.arc.util.serialization.Base64Coder; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState.State; @@ -50,7 +50,7 @@ public class NetClient implements ApplicationListener{ /** List of entities that were removed, and need not be added while syncing. */ private IntSet removed = new IntSet(); /** Byte stream for reading in snapshots. */ - private ReusableByteArrayInputStream byteStream = new ReusableByteArrayInputStream(); + private ReusableByteInStream byteStream = new ReusableByteInStream(); private DataInputStream dataStream = new DataInputStream(byteStream); public NetClient(){ diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 9c5f6ed830..605dce9128 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -13,7 +13,7 @@ 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.CountableByteArrayOutputStream; +import io.anuke.arc.util.io.ReusableByteOutStream; import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Entities; @@ -56,7 +56,7 @@ public class NetServer implements ApplicationListener{ private ByteBufferOutput outputBuffer = new ByteBufferOutput(writeBuffer); /** Stream for writing player sync data to. */ - private CountableByteArrayOutputStream syncStream = new CountableByteArrayOutputStream(); + private ReusableByteOutStream syncStream = new ReusableByteOutStream(); /** Data stream for writing player sync data to. */ private DataOutputStream dataStream = new DataOutputStream(syncStream); diff --git a/core/src/io/anuke/mindustry/entities/type/TileEntity.java b/core/src/io/anuke/mindustry/entities/type/TileEntity.java index 51f84ebefc..1f77fd475b 100644 --- a/core/src/io/anuke/mindustry/entities/type/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/type/TileEntity.java @@ -1,7 +1,6 @@ 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; @@ -115,18 +114,14 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ return dead || tile.entity != this; } + @CallSuper public void write(DataOutput stream) throws IOException{ } - public void writeConfig(DataOutput stream) throws IOException{ - } - + @CallSuper public void read(DataInput stream) throws IOException{ } - public void readConfig(DataInput stream) throws IOException{ - } - public boolean collide(Bullet other){ return true; } diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index 407438e698..f14525abd3 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -171,7 +171,9 @@ public class MapIO{ }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); + if(tile.block() == Blocks.liquidSource || tile.block() == Blocks.unloader || tile.block() == Blocks.sorter){ + stream.writeByte(-1); //write an meaningless byte here, just a fallback thing + } }else{ //write consecutive non-entity blocks int consecutives = 0; @@ -305,7 +307,9 @@ public class MapIO{ tile.entity.health = /*health*/tile.block().health; tile.setRotation(rotation); - tile.entity.readConfig(stream); + 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(); diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileVersion.java index 48e7cba779..49a14cd24b 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveFileVersion.java @@ -1,8 +1,9 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.Array; -import io.anuke.arc.collection.ObjectMap; +import io.anuke.arc.collection.*; +import io.anuke.arc.collection.ObjectMap.Entry; import io.anuke.arc.util.Pack; +import io.anuke.arc.util.io.ReusableByteOutStream; import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.entities.Entities; import io.anuke.mindustry.entities.EntityGroup; @@ -18,8 +19,21 @@ import java.io.*; import static io.anuke.mindustry.Vars.content; import static io.anuke.mindustry.Vars.world; +/** + * Format: + * + * Everything is compressed. Use a DeflaterStream to begin reading. + * + * 1. version of format / int + * 2. meta tags + * - length / short + * - continues with (string, string) pairs indicating key, value + */ public abstract class SaveFileVersion{ public final int version; + + private final ReusableByteOutStream byteOutput = new ReusableByteOutStream(); + private final DataOutputStream dataBytes = new DataOutputStream(byteOutput); private final ObjectMap fallback = ObjectMap.of( "alpha-dart-mech-pad", "dart-mech-pad" ); @@ -28,6 +42,42 @@ public abstract class SaveFileVersion{ this.version = version; } + /** 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{ + //reset output position + byteOutput.position(0); + //writer the needed info + runner.accept(dataBytes); + int length = byteOutput.position(); + //write length (either int or byte) followed by the output bytes + if(!isByte){ + output.writeInt(length); + }else{ + if(length > 255){ + throw new IOException("Byte write length exceeded: " + length + " > 255"); + } + output.writeByte(length); + } + output.write(byteOutput.getBytes(), 0, length); + } + + /** 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.readUnsignedByte() : input.readInt(); + //TODO descriptive error with chunk name + runner.accept(input); + return length; + } + + /** Skip a chunk completely. */ + public void skipChunk(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 SaveMeta getData(DataInputStream stream) throws IOException{ long time = stream.readLong(); long playtime = stream.readLong(); @@ -39,6 +89,23 @@ public abstract class SaveFileVersion{ return new SaveMeta(version, time, playtime, build, map, wave, rules); } + public void writeMeta(DataOutputStream stream, ObjectMap map) throws IOException{ + stream.writeShort(map.size); + for(Entry entry : map.entries()){ + stream.writeUTF(entry.key); + stream.writeUTF(entry.value); + } + } + + public StringMap readMeta(DataInputStream 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 void writeMap(DataOutputStream stream) throws IOException{ //write world size stream.writeShort(world.width()); @@ -81,7 +148,6 @@ public abstract class SaveFileVersion{ 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 @@ -157,7 +223,6 @@ public abstract class SaveFileVersion{ 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(); @@ -255,4 +320,8 @@ public abstract class SaveFileVersion{ public abstract void read(DataInputStream stream) throws IOException; public abstract void write(DataOutputStream stream) throws IOException; + + public interface IORunner{ + void accept(T 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..8be9291446 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -11,9 +11,7 @@ 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()); @@ -30,11 +28,11 @@ public class SaveIO{ 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,12 +45,12 @@ 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)); - }catch(Exception e){ - return false; - } + return isSaveValid(getSlotStream(slot)) || isSaveValid(getBackupSlotStream(slot)); } public static boolean isSaveValid(FileHandle file){ @@ -60,7 +58,6 @@ public class SaveIO{ } public static boolean isSaveValid(DataInputStream stream){ - try{ getData(stream); return true; @@ -90,6 +87,10 @@ public class SaveIO{ return saveDirectory.child(slot + "." + Vars.saveExtension); } + public static FileHandle backupFileFor(FileHandle file){ + return file.sibling(file.name() + "-backup." + file.extension()); + } + public static void write(FileHandle file){ write(new DeflaterOutputStream(file.write(false, bufferSize)){ byte[] tmp = {0}; 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/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/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index 8b418769dd..0427d6da09 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -24,9 +24,11 @@ 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. */ + /** Rotation, 0-3. Also used to store offload location and link, in which case it can be any number. + * When saved in non-link form, this data is truncated to 4 bits = max 16.*/ private byte rotation; - /** Team ordinal. */ + /** Team ordinal. Keep in mind that this is written as 4 bits, which means that there are only 2^4 = 16 possible teams. + * Complications may arise from using signed bytes as well. Be careful.*/ private byte team; /** Ore that is on top of this (floor) block. */ private byte overlay = 0; 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..8c692f9b5d 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) throws IOException{ + super.read(stream); + buffer.read(stream); + } } } 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..f2c652e0f9 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{ @@ -82,6 +84,20 @@ 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{ + for(Buffer b : buffers){ + b.write(stream); + } + } + + @Override + public void read(DataInput stream) throws IOException{ + for(Buffer b : buffers){ + b.read(stream); + } + } } class Buffer{ @@ -96,5 +112,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/Sorter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java index e62e0da1a8..2f17b814ec 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java @@ -132,12 +132,12 @@ public class Sorter extends Block{ public Item sortItem; @Override - public void writeConfig(DataOutput stream) throws IOException{ + public void write(DataOutput stream) throws IOException{ stream.writeByte(sortItem == null ? -1 : sortItem.id); } @Override - public void readConfig(DataInput stream) throws IOException{ + public void read(DataInput stream) throws IOException{ byte b = stream.readByte(); sortItem = b == -1 ? null : content.items().get(b); } 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..970e64245e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java +++ b/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java @@ -116,12 +116,12 @@ public class LiquidSource extends Block{ public Liquid source = null; @Override - public void writeConfig(DataOutput stream) throws IOException{ + public void write(DataOutput stream) throws IOException{ stream.writeByte(source == null ? -1 : source.id); } @Override - public void readConfig(DataInput stream) throws IOException{ + public void read(DataInput stream) throws IOException{ 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..ba01ffdc2a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java @@ -104,12 +104,12 @@ public class Unloader extends Block{ public Item sortItem = null; @Override - public void writeConfig(DataOutput stream) throws IOException{ + public void write(DataOutput stream) throws IOException{ stream.writeByte(sortItem == null ? -1 : sortItem.id); } @Override - public void readConfig(DataInput stream) throws IOException{ + public void read(DataInput stream) throws IOException{ byte id = stream.readByte(); sortItem = id == -1 ? null : content.items().get(id); } 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 6a17d50abb..a90cb448b7 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java @@ -197,14 +197,15 @@ public class UnitFactory extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeFloat(buildTime); stream.writeInt(spawned); } @Override public void read(DataInput stream) throws IOException{ + super.read(stream); buildTime = stream.readFloat(); - stream.readFloat(); //unneeded information, will remove later spawned = stream.readInt(); } } From 35b158dba7ca1dfc3d47d76aa87abebc70e7bd0f Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 5 May 2019 14:16:34 -0400 Subject: [PATCH 02/33] everything is borked --- .../SerializeAnnotationProcessor.java | 4 - .../io/anuke/mindustry/content/Blocks.java | 9 +- .../anuke/mindustry/core/ContentLoader.java | 15 +--- core/src/io/anuke/mindustry/core/World.java | 6 ++ .../io/anuke/mindustry/editor/EditorTile.java | 2 +- .../mindustry/entities/type/TileEntity.java | 21 ++++- core/src/io/anuke/mindustry/game/Content.java | 4 +- core/src/io/anuke/mindustry/game/Rules.java | 7 +- core/src/io/anuke/mindustry/io/MapIO.java | 4 +- .../anuke/mindustry/io/SaveFileVersion.java | 57 +++--------- core/src/io/anuke/mindustry/world/Block.java | 4 + core/src/io/anuke/mindustry/world/Tile.java | 87 +++++-------------- .../mindustry/world/blocks/BlockPart.java | 66 +++++--------- .../mindustry/world/blocks/BuildBlock.java | 2 + .../mindustry/world/blocks/defense/Door.java | 2 + .../world/blocks/defense/ForceProjector.java | 2 + .../world/blocks/defense/MendProjector.java | 2 + .../blocks/defense/OverdriveProjector.java | 2 + .../blocks/defense/turrets/ItemTurret.java | 2 + .../world/blocks/distribution/Conduit.java | 12 --- .../world/blocks/distribution/Conveyor.java | 2 + .../world/blocks/distribution/ItemBridge.java | 2 + .../world/blocks/distribution/Junction.java | 2 + .../world/blocks/distribution/MassDriver.java | 4 +- .../world/blocks/distribution/Sorter.java | 2 + .../world/blocks/power/NuclearReactor.java | 2 + .../world/blocks/power/PowerGenerator.java | 2 + .../world/blocks/production/Cultivator.java | 2 + .../blocks/production/GenericCrafter.java | 2 + .../world/blocks/sandbox/LiquidSource.java | 3 +- .../world/blocks/storage/Unloader.java | 2 + .../mindustry/world/blocks/units/MechPad.java | 2 + tools/src/io/anuke/mindustry/Generators.java | 2 +- 33 files changed, 138 insertions(+), 201 deletions(-) diff --git a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java index 358a52bbbf..4f5934fdfd 100644 --- a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java +++ b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java @@ -48,14 +48,10 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ 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){ diff --git a/core/src/io/anuke/mindustry/content/Blocks.java b/core/src/io/anuke/mindustry/content/Blocks.java index d95fa55176..dc287849be 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, @@ -109,7 +109,12 @@ 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++){ + new BlockPart(dx - BlockPart.maxSize/2, dy - BlockPart.maxSize/2); + } + } spawn = new Block("spawn"); diff --git a/core/src/io/anuke/mindustry/core/ContentLoader.java b/core/src/io/anuke/mindustry/core/ContentLoader.java index 72df701ddc..c7af6e9154 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,8 +165,6 @@ 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){ if(temporaryMapper[type.ordinal()].length <= id || temporaryMapper[type.ordinal()][id] == null){ @@ -243,10 +236,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/World.java b/core/src/io/anuke/mindustry/core/World.java index 1645d4b70b..d309942f80 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -97,6 +97,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]; } diff --git a/core/src/io/anuke/mindustry/editor/EditorTile.java b/core/src/io/anuke/mindustry/editor/EditorTile.java index 1af8d2ffe8..021664ce06 100644 --- a/core/src/io/anuke/mindustry/editor/EditorTile.java +++ b/core/src/io/anuke/mindustry/editor/EditorTile.java @@ -76,7 +76,7 @@ public class EditorTile extends Tile{ @Override public void setOverlayID(byte ore){ - byte previous = getOverlayID(); + byte previous = overlayID(); if(previous == ore) return; super.setOverlayID(ore); op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), previous, ore)); diff --git a/core/src/io/anuke/mindustry/entities/type/TileEntity.java b/core/src/io/anuke/mindustry/entities/type/TileEntity.java index 1f77fd475b..313307c777 100644 --- a/core/src/io/anuke/mindustry/entities/type/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/type/TileEntity.java @@ -6,8 +6,7 @@ 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; @@ -116,10 +115,28 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ @CallSuper public void write(DataOutput stream) throws IOException{ + stream.writeShort((short)health); + stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //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); } @CallSuper public void read(DataInput stream) 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.setRotation(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 boolean collide(Bullet other){ 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/Rules.java b/core/src/io/anuke/mindustry/game/Rules.java index 95faf1c90a..857c563529 100644 --- a/core/src/io/anuke/mindustry/game/Rules.java +++ b/core/src/io/anuke/mindustry/game/Rules.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.game; import io.anuke.annotations.Annotations.Serialize; import io.anuke.arc.collection.Array; +import io.anuke.mindustry.type.Zone; /** * Defines current rules on how the game should function. @@ -47,10 +48,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. */ diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index f14525abd3..ccd7381c8d 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -144,13 +144,13 @@ public class MapIO{ 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()); + stream.writeByte(tile.overlayID()); 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()){ + if(nextTile.getFloorID() != tile.getFloorID() || nextTile.block() != Blocks.air || nextTile.overlayID() != tile.overlayID()){ break; } diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileVersion.java index 49a14cd24b..1ccb52ba17 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveFileVersion.java @@ -2,9 +2,7 @@ package io.anuke.mindustry.io; import io.anuke.arc.collection.*; import io.anuke.arc.collection.ObjectMap.Entry; -import io.anuke.arc.util.Pack; import io.anuke.arc.util.io.ReusableByteOutStream; -import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.entities.Entities; import io.anuke.mindustry.entities.EntityGroup; import io.anuke.mindustry.entities.traits.*; @@ -111,17 +109,17 @@ public abstract class SaveFileVersion{ stream.writeShort(world.width()); stream.writeShort(world.height()); - //floor first + //floor + overlay 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()); + 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.getFloorID() != tile.getFloorID() || nextTile.getOverlayID() != tile.getOverlayID()){ + if(nextTile.floorID() != tile.floorID() || nextTile.overlayID() != tile.overlayID()){ break; } @@ -135,19 +133,9 @@ public abstract class SaveFileVersion{ //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); + stream.writeShort(tile.blockID()); + if(tile.entity != null){ tile.entity.write(stream); }else{ //write consecutive non-entity blocks @@ -156,7 +144,7 @@ public abstract class SaveFileVersion{ 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()){ + if(nextTile.blockID() != tile.blockID()){ break; } @@ -180,19 +168,15 @@ public abstract class SaveFileVersion{ //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(); + short floorid = stream.readShort(); + short oreid = stream.readShort(); int consecutives = stream.readUnsignedByte(); - Block ore = content.block(oreid); - tiles[x][y] = new Tile(x, y, floorid, (byte)0); - tiles[x][y].setOverlay(ore); + tiles[x][y] = new Tile(false, x, y, floorid, oreid); 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; + tiles[newx][newy] = new Tile(false, newx, newy, floorid, oreid); } i += consecutives; @@ -205,24 +189,7 @@ public abstract class SaveFileVersion{ 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); - + if(tile.entity != null){ tile.entity.read(stream); }else{ int consecutives = stream.readUnsignedByte(); diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index c3aa1ec35e..2a39935fd2 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -451,6 +451,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/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index 94400da648..b1f8bc861e 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -1,10 +1,8 @@ package io.anuke.mindustry.world; 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,14 +22,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 and link, in which case it can be any number. - * When saved in non-link form, this data is truncated to 4 bits = max 16.*/ + /** Rotation, 0-3. Also used to store offload location, in which case it can be any number.*/ private byte rotation; - /** Team ordinal. Keep in mind that this is written as 4 bits, which means that there are only 2^4 = 16 possible teams. - * Complications may arise from using signed bytes as well. Be careful.*/ + /** Team ordinal. */ private byte team; /** Ore that is on top of this (floor) block. */ - private byte overlay = 0; + private short overlay = 0; public Tile(int x, int y){ this.x = (short)x; @@ -39,20 +35,11 @@ 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(boolean __removeThisLater, int x, int y, short floor, short overlay){ + this.x = (short)x; + this.y = (short)y; this.floor = (Floor)content.block(floor); - this.block = content.block(block); - changed(); - } - - 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; - changed(); - this.team = team; + this.overlay = overlay; } /** Returns this tile's position as a {@link Pos}. */ @@ -60,14 +47,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; @@ -206,11 +185,19 @@ public class Tile implements Position, TargetTrait{ 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; } @@ -255,21 +242,7 @@ public class Tile implements Position, TargetTrait{ } public boolean isLinked(){ - return block == Blocks.part; - } - - 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)); + return block instanceof BlockPart; } /** @@ -315,7 +288,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; @@ -324,28 +297,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()); @@ -507,6 +462,8 @@ public class Tile implements Position, TargetTrait{ (isLinked() ? " link=[" + linkX(rotation) + ", " + linkY(rotation) + "]" : ""); } + //TODO remove these! + /**Returns the relative X from a link byte.*/ public static int linkX(byte value){ return -((byte)((value >> 4) & (byte)0x0F) - 8); diff --git a/core/src/io/anuke/mindustry/world/blocks/BlockPart.java b/core/src/io/anuke/mindustry/world/blocks/BlockPart.java index bc82001109..325f451175 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,35 @@ 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){ + 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; @@ -33,43 +48,4 @@ public class BlockPart extends Block{ return true; } - @Override - public void draw(Tile tile){ - //do nothing - } - - @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..f9e373e2f1 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -292,6 +292,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); @@ -309,6 +310,7 @@ public class BuildBlock extends Block{ @Override public void read(DataInput stream) throws IOException{ + super.read(stream); progress = stream.readFloat(); short pid = stream.readShort(); short rid = stream.readShort(); 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..62dcb98d6e 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{ + super.read(stream); 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..503081a610 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); @@ -212,6 +213,7 @@ public class ForceProjector extends Block{ @Override public void read(DataInput stream) throws IOException{ + super.read(stream); 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..defe6d698e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/MendProjector.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/MendProjector.java @@ -149,12 +149,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{ + super.read(stream); 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..7f5cd03c5c 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/OverdriveProjector.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/OverdriveProjector.java @@ -148,12 +148,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{ + super.read(stream); 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..f23ae75df3 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; @@ -131,6 +132,7 @@ public class ItemTurret extends CooledTurret{ @Override public void read(DataInput stream) throws IOException{ + super.read(stream); 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/distribution/Conduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java index 44c54540ce..e1b2055236 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++; @@ -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..023eb8fb72 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java @@ -367,6 +367,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++){ @@ -376,6 +377,7 @@ public class Conveyor extends Block{ @Override public void read(DataInput stream) throws IOException{ + super.read(stream); 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..213418b72e 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java @@ -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); @@ -340,6 +341,7 @@ public class ItemBridge extends Block{ @Override public void read(DataInput stream) throws IOException{ + super.read(stream); 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 f2c652e0f9..6330409029 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java @@ -87,6 +87,7 @@ public class Junction extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); for(Buffer b : buffers){ b.write(stream); } @@ -94,6 +95,7 @@ public class Junction extends Block{ @Override public void read(DataInput stream) throws IOException{ + super.read(stream); for(Buffer b : buffers){ b.read(stream); } 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..ef1f5cb256 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,6 +321,7 @@ 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()); @@ -330,6 +329,7 @@ public class MassDriver extends Block{ @Override public void read(DataInput stream) throws IOException{ + super.read(stream); link = stream.readInt(); rotation = stream.readFloat(); state = DriverState.values()[stream.readByte()]; 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 2f17b814ec..89068a1a06 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java @@ -133,11 +133,13 @@ public class Sorter extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeByte(sortItem == null ? -1 : sortItem.id); } @Override public void read(DataInput stream) throws IOException{ + super.read(stream); byte b = stream.readByte(); sortItem = b == -1 ? null : content.items().get(b); } 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..9e9b33cfde 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{ + super.read(stream); 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..7277f0e23c 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{ + super.read(stream); productionEfficiency = stream.readFloat(); } } 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..ae4667deef 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{ + super.read(stream); 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..794b97b8f4 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java @@ -150,12 +150,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{ + super.read(stream); 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 970e64245e..52132a69ae 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; @@ -117,11 +116,13 @@ public class LiquidSource extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeByte(source == null ? -1 : source.id); } @Override public void read(DataInput stream) throws IOException{ + super.read(stream); 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 ba01ffdc2a..12026eef43 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java @@ -105,11 +105,13 @@ public class Unloader extends Block{ @Override public void write(DataOutput stream) throws IOException{ + super.write(stream); stream.writeByte(sortItem == null ? -1 : sortItem.id); } @Override public void read(DataInput stream) throws IOException{ + super.read(stream); 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..720cb2c358 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/MechPad.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/MechPad.java @@ -178,6 +178,7 @@ 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); @@ -185,6 +186,7 @@ public class MechPad extends Block{ @Override public void read(DataInput stream) throws IOException{ + super.read(stream); progress = stream.readFloat(); time = stream.readFloat(); heat = stream.readFloat(); 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()){ From bf073a84c86c63f194a45e8ed936d1c0dbc123cf Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 5 May 2019 19:05:46 -0400 Subject: [PATCH 03/33] a bit less broken but still broken --- .../io/anuke/mindustry/ai/BlockIndexer.java | 6 +- .../src/io/anuke/mindustry/ai/Pathfinder.java | 2 +- .../io/anuke/mindustry/content/Blocks.java | 13 +- .../io/anuke/mindustry/content/Bullets.java | 4 +- core/src/io/anuke/mindustry/core/Control.java | 2 +- .../io/anuke/mindustry/core/NetServer.java | 2 +- core/src/io/anuke/mindustry/core/World.java | 37 ++--- .../anuke/mindustry/editor/DrawOperation.java | 2 +- .../io/anuke/mindustry/editor/EditorTile.java | 6 +- .../io/anuke/mindustry/editor/EditorTool.java | 2 +- .../io/anuke/mindustry/editor/MapEditor.java | 2 +- .../mindustry/editor/MapGenerateDialog.java | 6 +- .../anuke/mindustry/editor/MapRenderer.java | 2 +- .../io/anuke/mindustry/entities/Damage.java | 9 +- .../mindustry/entities/bullet/Bullet.java | 3 +- .../anuke/mindustry/entities/effect/Fire.java | 2 +- .../mindustry/entities/effect/Puddle.java | 2 +- .../entities/traits/BuilderTrait.java | 2 +- .../anuke/mindustry/entities/type/Player.java | 2 +- .../mindustry/entities/type/TileEntity.java | 17 +-- .../mindustry/entities/type/base/Drone.java | 2 +- core/src/io/anuke/mindustry/game/Saves.java | 6 +- .../mindustry/graphics/BlockRenderer.java | 2 +- .../mindustry/graphics/MinimapRenderer.java | 2 +- .../mindustry/graphics/OverlayRenderer.java | 10 +- .../anuke/mindustry/input/DesktopInput.java | 7 +- .../anuke/mindustry/input/InputHandler.java | 4 +- .../io/anuke/mindustry/input/MobileInput.java | 18 +-- core/src/io/anuke/mindustry/io/MapIO.java | 139 +----------------- .../anuke/mindustry/io/SaveFileVersion.java | 96 ++++++++---- core/src/io/anuke/mindustry/io/SaveIO.java | 10 +- core/src/io/anuke/mindustry/io/TypeIO.java | 24 +-- .../maps/generators/BasicGenerator.java | 4 +- core/src/io/anuke/mindustry/type/Loadout.java | 2 +- .../ui/fragments/BlockInventoryFragment.java | 4 +- .../ui/fragments/PlacementFragment.java | 4 +- core/src/io/anuke/mindustry/world/Block.java | 2 +- .../anuke/mindustry/world/BlockStorage.java | 14 +- core/src/io/anuke/mindustry/world/Build.java | 66 ++------- core/src/io/anuke/mindustry/world/Tile.java | 63 ++++---- .../mindustry/world/blocks/BlockPart.java | 1 + .../mindustry/world/blocks/BuildBlock.java | 24 ++- .../mindustry/world/blocks/LiquidBlock.java | 2 +- .../world/blocks/defense/MendProjector.java | 3 +- .../blocks/defense/OverdriveProjector.java | 3 +- .../world/blocks/distribution/Conduit.java | 12 +- .../world/blocks/distribution/Conveyor.java | 24 +-- .../world/blocks/distribution/ItemBridge.java | 2 +- .../world/blocks/distribution/Junction.java | 2 +- .../blocks/distribution/LiquidBridge.java | 2 +- .../blocks/distribution/LiquidJunction.java | 4 +- .../blocks/distribution/OverflowGate.java | 6 +- .../world/blocks/distribution/Router.java | 4 +- .../world/blocks/distribution/Sorter.java | 6 +- .../world/blocks/power/PowerNode.java | 7 +- .../world/blocks/storage/Unloader.java | 3 +- tests/src/test/java/WorldTests.java | 12 +- 57 files changed, 274 insertions(+), 445 deletions(-) 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/content/Blocks.java b/core/src/io/anuke/mindustry/content/Blocks.java index dc287849be..bb200f9c6f 100644 --- a/core/src/io/anuke/mindustry/content/Blocks.java +++ b/core/src/io/anuke/mindustry/content/Blocks.java @@ -112,16 +112,19 @@ public class Blocks implements ContentList{ //create special blockpart variants for(int dx = 0; dx < BlockPart.maxSize; dx++){ for(int dy = 0; dy < BlockPart.maxSize; dy++){ - new BlockPart(dx - BlockPart.maxSize/2, dy - BlockPart.maxSize/2); + int fx = dx - BlockPart.maxSize/2, fy = dy - BlockPart.maxSize/2; + if(fx != 0 || fy != 0){ + new BlockPart(fx, fy); + } } } spawn = new Block("spawn"); - //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"){{ @@ -561,7 +564,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); diff --git a/core/src/io/anuke/mindustry/content/Bullets.java b/core/src/io/anuke/mindustry/content/Bullets.java index 864e49460c..fb6ca467fe 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()); } diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index c4ef5ee932..b641b1dcb2 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -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(); diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 605dce9128..ea02ae0c01 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -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); diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index d309942f80..ef2cdeef30 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -20,6 +20,7 @@ import io.anuke.mindustry.maps.*; import io.anuke.mindustry.maps.generators.Generator; import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.*; +import io.anuke.mindustry.world.blocks.BlockPart; import static io.anuke.mindustry.Vars.*; @@ -111,6 +112,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); } @@ -194,14 +199,14 @@ public class World implements ApplicationListener{ } public Zone getZone(){ - return content.getByID(ContentType.zone, state.rules.zone); + return state.rules.zone; } public void playZone(Zone zone){ ui.loadAnd(() -> { logic.reset(); state.rules = zone.rules.get(); - state.rules.zone = zone.id; + state.rules.zone = zone; loadGenerator(zone.generator); for(Tile core : state.teams.get(defaultTeam).cores){ for(ItemStack stack : zone.getStartingItems()){ @@ -295,16 +300,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){ @@ -324,8 +320,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); } } } @@ -333,15 +328,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. */ @@ -462,7 +448,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]); } } } @@ -509,8 +495,7 @@ 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); } } } diff --git a/core/src/io/anuke/mindustry/editor/DrawOperation.java b/core/src/io/anuke/mindustry/editor/DrawOperation.java index ea26df6ea4..62dd831fa1 100755 --- a/core/src/io/anuke/mindustry/editor/DrawOperation.java +++ b/core/src/io/anuke/mindustry/editor/DrawOperation.java @@ -46,7 +46,7 @@ public class DrawOperation{ editor.updateLinks(block, tile.x, tile.y); } }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()){ diff --git a/core/src/io/anuke/mindustry/editor/EditorTile.java b/core/src/io/anuke/mindustry/editor/EditorTile.java index 021664ce06..75a35f8e8a 100644 --- a/core/src/io/anuke/mindustry/editor/EditorTile.java +++ b/core/src/io/anuke/mindustry/editor/EditorTile.java @@ -67,10 +67,10 @@ public class EditorTile extends Tile{ } @Override - public void setRotation(byte rotation){ - byte previous = getRotation(); + public void rotation(byte rotation){ + byte previous = rotation(); if(previous == rotation) return; - super.setRotation(rotation); + super.rotation(rotation); op(TileOp.get(x, y, (byte)OpType.rotation.ordinal(), previous, rotation)); } diff --git a/core/src/io/anuke/mindustry/editor/EditorTool.java b/core/src/io/anuke/mindustry/editor/EditorTool.java index b4cb882ce2..b92d1ac3d0 100644 --- a/core/src/io/anuke/mindustry/editor/EditorTool.java +++ b/core/src/io/anuke/mindustry/editor/EditorTool.java @@ -116,7 +116,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..c1253509aa 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditor.java +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -246,7 +246,7 @@ public class MapEditor{ tile.setTeam(drawTeam); } if(drawBlock.rotate){ - tile.setRotation((byte)rotation); + tile.rotation((byte)rotation); } } } diff --git a/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java b/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java index e34a139e9e..0e1f81affb 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]); @@ -341,7 +341,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/MapRenderer.java b/core/src/io/anuke/mindustry/editor/MapRenderer.java index 1488f8e0c6..f26b7f83f5 100644 --- a/core/src/io/anuke/mindustry/editor/MapRenderer.java +++ b/core/src/io/anuke/mindustry/editor/MapRenderer.java @@ -116,7 +116,7 @@ public class MapRenderer implements Disposable{ 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/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..99dcc91a93 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Fire.java +++ b/core/src/io/anuke/mindustry/entities/effect/Fire.java @@ -98,7 +98,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; diff --git a/core/src/io/anuke/mindustry/entities/effect/Puddle.java b/core/src/io/anuke/mindustry/entities/effect/Puddle.java index 76816d077e..09eaad2494 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Puddle.java +++ b/core/src/io/anuke/mindustry/entities/effect/Puddle.java @@ -201,7 +201,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); } diff --git a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java index ec745b67c3..c8cf5c9164 100644 --- a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java +++ b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java @@ -164,7 +164,7 @@ public interface BuilderTrait extends Entity, TeamTrait{ 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) + (!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); } diff --git a/core/src/io/anuke/mindustry/entities/type/Player.java b/core/src/io/anuke/mindustry/entities/type/Player.java index 6a4e40125b..35a8b058bf 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); diff --git a/core/src/io/anuke/mindustry/entities/type/TileEntity.java b/core/src/io/anuke/mindustry/entities/type/TileEntity.java index 313307c777..36636d9468 100644 --- a/core/src/io/anuke/mindustry/entities/type/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/type/TileEntity.java @@ -116,7 +116,7 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ @CallSuper public void write(DataOutput stream) throws IOException{ stream.writeShort((short)health); - stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation + 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); @@ -131,7 +131,7 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ byte rotation = Pack.rightByte(tr); tile.setTeam(Team.all[team]); - tile.setRotation(rotation); + tile.rotation(rotation); if(items != null) items.read(stream); if(power != null) power.read(stream); @@ -180,14 +180,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); + } } } } @@ -198,10 +198,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); 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..3531e22d39 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)); } } diff --git a/core/src/io/anuke/mindustry/game/Saves.java b/core/src/io/anuke/mindustry/game/Saves.java index 718d3a7df4..46d66c5d0a 100644 --- a/core/src/io/anuke/mindustry/game/Saves.java +++ b/core/src/io/anuke/mindustry/game/Saves.java @@ -12,14 +12,14 @@ 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; @@ -224,7 +224,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/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/MinimapRenderer.java b/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java index 612af596eb..12390c6558 100644 --- a/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java @@ -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/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..9d553bdbfc 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; @@ -331,7 +331,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..1a6ef3a1d4 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; @@ -408,9 +407,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 +504,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 +523,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 +573,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/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index ccd7381c8d..78cbb4200b 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -1,26 +1,21 @@ 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.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.mindustry.content.Blocks; import io.anuke.mindustry.game.*; 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 java.io.*; -import java.util.Arrays; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; @@ -30,24 +25,7 @@ import static io.anuke.mindustry.Vars.content; /** Reads and writes map files. */ 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)){ @@ -169,7 +147,7 @@ public class MapIO{ 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.writeByte(Pack.byteByte(tile.getTeamID(), tile.rotation())); //team + rotation stream.writeShort(/*(short)tile.entity.health*/tile.block().health); //health if(tile.block() == Blocks.liquidSource || tile.block() == Blocks.unloader || tile.block() == Blocks.sorter){ stream.writeByte(-1); //write an meaningless byte here, just a fallback thing @@ -294,9 +272,7 @@ public class MapIO{ Tile tile = tiles.get(x, y); tile.setBlock(block); - if(block == Blocks.part){ - tile.setLinkByte(stream.readByte()); - }else if(tile.entity != null){ + if(tile.entity != null){ byte tr = stream.readByte(); short health = stream.readShort(); @@ -305,7 +281,7 @@ public class MapIO{ tile.setTeam(Team.all[team]); tile.entity.health = /*health*/tile.block().health; - tile.setRotation(rotation); + 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 @@ -353,9 +329,8 @@ public class MapIO{ //multiblock parts if(Structs.inBounds(worldx, worldy, pixmap.getWidth(), pixmap.getHeight())){ Tile write = tiles[worldx][worldy]; - write.setBlock(Blocks.part); + write.setBlock(BlockPart.get(dx - 1, dy - 1)); write.setTeam(Team.blue); - write.setLinkByte(Pack.byteByte((byte)(dx - 1 + 8), (byte)(dy - 1 + 8))); } } } @@ -368,112 +343,6 @@ public class MapIO{ } } - /** 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{ diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileVersion.java index 1ccb52ba17..aedbb453bc 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveFileVersion.java @@ -2,12 +2,13 @@ package io.anuke.mindustry.io; import io.anuke.arc.collection.*; import io.anuke.arc.collection.ObjectMap.Entry; +import io.anuke.arc.util.io.CounterInputStream; import io.anuke.arc.util.io.ReusableByteOutStream; 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.game.Content; +import io.anuke.mindustry.game.MappableContent; import io.anuke.mindustry.type.ContentType; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; @@ -19,25 +20,25 @@ import static io.anuke.mindustry.Vars.world; /** * Format: - * - * Everything is compressed. Use a DeflaterStream to begin reading. - * * 1. version of format / int - * 2. meta tags - * - length / short - * - continues with (string, string) pairs indicating key, value + * (begin deflating) + * 2. regions */ public abstract class SaveFileVersion{ public final int version; private final ReusableByteOutStream byteOutput = new ReusableByteOutStream(); private final DataOutputStream dataBytes = new DataOutputStream(byteOutput); - private final ObjectMap fallback = ObjectMap.of( - "alpha-dart-mech-pad", "dart-mech-pad" - ); + private final Region[] regions; + private final ObjectMap fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad"); - public SaveFileVersion(int version){ + public SaveFileVersion(int version, Region... regions){ this.version = version; + this.regions = regions; + } + + 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. */ @@ -59,6 +60,10 @@ public abstract class SaveFileVersion{ output.write(byteOutput.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.readUnsignedByte() : input.readInt(); @@ -76,18 +81,7 @@ public abstract class SaveFileVersion{ } } - 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 writeMeta(DataOutputStream stream, ObjectMap map) throws IOException{ + public void writeMeta(DataOutput stream, ObjectMap map) throws IOException{ stream.writeShort(map.size); for(Entry entry : map.entries()){ stream.writeUTF(entry.key); @@ -95,7 +89,7 @@ public abstract class SaveFileVersion{ } } - public StringMap readMeta(DataInputStream stream) throws IOException{ + public StringMap readMeta(DataInput stream) throws IOException{ StringMap map = new StringMap(); short size = stream.readShort(); for(int i = 0; i < size; i++){ @@ -104,7 +98,7 @@ public abstract class SaveFileVersion{ return map; } - public void writeMap(DataOutputStream stream) throws IOException{ + public void writeMap(DataOutput stream) throws IOException{ //write world size stream.writeShort(world.width()); stream.writeShort(world.height()); @@ -185,7 +179,7 @@ public abstract class SaveFileVersion{ //read blocks for(int i = 0; i < width * height; i++){ int x = i % width, y = i / width; - Block block = content.block(stream.readByte()); + Block block = content.block(stream.readShort()); Tile tile = tiles[x][y]; tile.setBlock(block); @@ -208,6 +202,7 @@ public abstract class SaveFileVersion{ } public void writeEntities(DataOutputStream stream) throws IOException{ + //write entity chunk int groups = 0; for(EntityGroup group : Entities.getAllGroups()){ @@ -222,8 +217,11 @@ public abstract class SaveFileVersion{ 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); + //each entity is a separate chunk. + writeChunk(stream, true, out -> { + stream.writeByte(((SaveTrait)entity).getTypeID()); + ((SaveTrait)entity).writeSave(out); + }); } } } @@ -284,11 +282,45 @@ public abstract class SaveFileVersion{ } } - public abstract void read(DataInputStream stream) throws IOException; + public final void read(DataInputStream stream, CounterInputStream counter) throws IOException{ + for(Region region : regions){ + counter.resetCount(); + try{ + int length = readChunk(stream, region.reader); + if(length != counter.count() + 4){ + throw new IOException("Error reading region \"" + region.name + "\": read length mismatch. Expected: " + length + "; Actual: " + (counter.count() + 4)); + } + }catch(Throwable e){ + throw new IOException("Error reading region \"" + region.name + "\".", e); + } + } + } - public abstract void write(DataOutputStream stream) throws IOException; + public final void write(DataOutputStream stream) throws IOException{ + for(Region region : regions){ + try{ + writeChunk(stream, region.writer); + }catch(Throwable e){ + throw new IOException("Error writing region \"" + region.name + "\".", e); + } + } + } - public interface IORunner{ + /** A region of a save file that holds a specific category of information. + * Uses: simplify code reuse, provide better error messages, skip unnecessary data.*/ + protected final class Region{ + final IORunner writer; + final IORunner reader; + final String name; + + public Region(IORunner writer, IORunner reader, String name){ + this.writer = writer; + this.reader = reader; + this.name = name; + } + } + + protected interface IORunner{ void accept(T stream) throws IOException; } } diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 8be9291446..fbba038e5a 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -2,6 +2,7 @@ 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.mindustry.Vars; import io.anuke.mindustry.io.versions.Save1; @@ -12,6 +13,8 @@ import java.util.zip.InflaterInputStream; import static io.anuke.mindustry.Vars.*; public class SaveIO{ + /** 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()); @@ -130,15 +133,16 @@ public class SaveIO{ } public static void load(InputStream is) throws SaveException{ - try(DataInputStream stream = new DataInputStream(is)){ + try(CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){ logic.reset(); int version = stream.readInt(); SaveFileVersion ver = versions.get(version); - ver.read(stream); + ver.read(stream, counter); }catch(Exception e){ - content.setTemporaryMapper(null); throw new SaveException(e); + }finally{ + content.setTemporaryMapper(null); } } diff --git a/core/src/io/anuke/mindustry/io/TypeIO.java b/core/src/io/anuke/mindustry/io/TypeIO.java index d785cbb1b3..41a097bc51 100644 --- a/core/src/io/anuke/mindustry/io/TypeIO.java +++ b/core/src/io/anuke/mindustry/io/TypeIO.java @@ -108,12 +108,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 +123,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 +141,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 +204,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 +214,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(buffer.getShort()); } @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); } diff --git a/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java b/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java index 920596c020..cf6f3cc750 100644 --- a/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java @@ -200,7 +200,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 +215,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/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/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/PlacementFragment.java b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java index 6a01e36a88..c8c0013a23 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java @@ -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; @@ -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/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index 2a39935fd2..672c50d8b0 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -240,7 +240,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){ diff --git a/core/src/io/anuke/mindustry/world/BlockStorage.java b/core/src/io/anuke/mindustry/world/BlockStorage.java index 0484676931..1ce213f382 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,8 @@ 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.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..bb08372000 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))); } @@ -166,7 +122,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 +151,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/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index b1f8bc861e..147bfffd03 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -1,6 +1,7 @@ package io.anuke.mindustry.world; 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.mindustry.content.Blocks; @@ -120,7 +121,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){ @@ -169,20 +170,12 @@ 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 byte getDump(){ - return rotation; - } - - public void setDump(byte dump){ - this.rotation = dump; + public void rotation(int rotation){ + this.rotation = (byte)rotation; } public short overlayID(){ @@ -210,31 +203,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(){ @@ -249,21 +235,28 @@ public class Tile implements Position, TargetTrait{ * 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 Array getLinkedTiles(Array tmpArray){ - Block block = block(); - tmpArray.clear(); + public void getLinkedTiles(Consumer cons){ 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); + if(other != null) cons.accept(other); } } }else{ - tmpArray.add(this); + cons.accept(this); } + } + + /** + * 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 Array getLinkedTiles(Array tmpArray){ + tmpArray.clear(); + getLinkedTiles(tmpArray::add); return tmpArray; } @@ -355,8 +348,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 @@ -410,9 +403,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); } } @@ -455,9 +447,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) + "]" : ""); } diff --git a/core/src/io/anuke/mindustry/world/blocks/BlockPart.java b/core/src/io/anuke/mindustry/world/blocks/BlockPart.java index 325f451175..ab163259d4 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BlockPart.java +++ b/core/src/io/anuke/mindustry/world/blocks/BlockPart.java @@ -24,6 +24,7 @@ public class BlockPart extends Block{ } 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]; } diff --git a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index f9e373e2f1..bfe29bed54 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -28,15 +28,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 +112,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 +137,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 +156,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(); } } @@ -198,7 +208,7 @@ 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()); } } 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/MendProjector.java b/core/src/io/anuke/mindustry/world/blocks/defense/MendProjector.java index defe6d698e..22e45e65cb 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); 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 7f5cd03c5c..6b0efc6450 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); 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 e1b2055236..e4b4bc1950 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java @@ -60,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); @@ -89,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(); @@ -104,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 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 023eb8fb72..25fb795702 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,7 @@ 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()); float nextMax = next != null && next.block() instanceof Conveyor ? 1f - Math.max(itemSpace - next.entity().minitem, 0) : 1f; int minremove = Integer.MAX_VALUE; @@ -231,7 +231,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 +290,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 +314,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)); 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 213418b72e..d49f665f16 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); 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 6330409029..87c8c71aa7 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java @@ -74,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 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/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 89068a1a06..e9bd6df167 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); } } } 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/storage/Unloader.java b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java index 12026eef43..33494a8db5 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 diff --git a/tests/src/test/java/WorldTests.java b/tests/src/test/java/WorldTests.java index 2b4f480bfa..bd1cf2af54 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,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()); } } } @@ -80,7 +80,7 @@ public class WorldTests{ 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); } @@ -95,7 +95,7 @@ public class WorldTests{ 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); } From 20fbe2fbbeb6e9f865b7e129bb28f2368dc17ca7 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 5 May 2019 22:09:02 -0400 Subject: [PATCH 04/33] even less broken --- .../SerializeAnnotationProcessor.java | 14 + .../io/anuke/mindustry/ai/WaveSpawner.java | 41 +-- .../io/anuke/mindustry/content/Blocks.java | 16 +- .../anuke/mindustry/editor/MapRenderer.java | 3 +- .../anuke/mindustry/io/BaseSaveVersion.java | 248 ++++++++++++++++ .../anuke/mindustry/io/SaveFileVersion.java | 277 +++--------------- core/src/io/anuke/mindustry/io/SaveIO.java | 15 +- .../io/anuke/mindustry/io/versions/Save1.java | 1 + .../maps/generators/BasicGenerator.java | 3 +- .../maps/generators/MapGenerator.java | 21 +- .../maps/generators/RandomGenerator.java | 3 +- .../maps/zonegen/DesertWastesGenerator.java | 2 +- .../maps/zonegen/OvergrowthGenerator.java | 2 +- .../src/io/anuke/mindustry/net/NetworkIO.java | 2 - .../mindustry/world/LegacyColorMapper.java | 6 +- core/src/io/anuke/mindustry/world/Tile.java | 3 +- 16 files changed, 343 insertions(+), 314 deletions(-) create mode 100644 core/src/io/anuke/mindustry/io/BaseSaveVersion.java diff --git a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java index 4f5934fdfd..7ef43f64d8 100644 --- a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java +++ b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java @@ -143,6 +143,19 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ .addStatement("bjson.writeObjectEnd()") .addStatement("stream.writeUTF(output.toString())"); + MethodSpec.Builder binaryJsonWriteStringMethod = MethodSpec.methodBuilder("write" + simpleTypeName + "StringJson") + .returns(String.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("return output.toString()"); + MethodSpec.Builder binaryJsonReadMethod = MethodSpec.methodBuilder("read" + simpleTypeName + "StreamJson") .returns(type) .addParameter(DataInput.class, "stream") @@ -151,6 +164,7 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ .addStatement("return read" + simpleTypeName + "Json(bjson.fromJson(null, stream.readUTF()))"); classBuilder.addMethod(binaryJsonWriteMethod.build()); + classBuilder.addMethod(binaryJsonWriteStringMethod.build()); classBuilder.addMethod(binaryJsonReadMethod.build()); } 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 bb200f9c6f..8b9b166877 100644 --- a/core/src/io/anuke/mindustry/content/Blocks.java +++ b/core/src/io/anuke/mindustry/content/Blocks.java @@ -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; } @@ -119,7 +113,9 @@ public class Blocks implements ContentList{ } } - spawn = new Block("spawn"); + spawn = new OverlayFloor("spawn"){ + public void draw(Tile tile){} + }; //Registers build blocks //no reference is needed here since they can be looked up by name later diff --git a/core/src/io/anuke/mindustry/editor/MapRenderer.java b/core/src/io/anuke/mindustry/editor/MapRenderer.java index f26b7f83f5..8be3489f1d 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; @@ -110,7 +111,7 @@ 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){ diff --git a/core/src/io/anuke/mindustry/io/BaseSaveVersion.java b/core/src/io/anuke/mindustry/io/BaseSaveVersion.java new file mode 100644 index 0000000000..244b970593 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/BaseSaveVersion.java @@ -0,0 +1,248 @@ +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.type.ContentType; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; + +import java.io.*; + +import static io.anuke.mindustry.Vars.*; + +public abstract class BaseSaveVersion extends SaveFileVersion{ + + public BaseSaveVersion(int version){ + super(version); + } + + public void write(DataOutputStream stream) throws IOException{ + region("meta", stream, this::writeMeta); + region("content", stream, this::writeContentHeader); + region("map", stream, this::writeMap); + region("entities", stream, this::writeEntities); + } + + public void read(DataInputStream stream, CounterInputStream counter) throws IOException{ + region("meta", stream, counter, this::readMeta); + region("content", stream, counter, this::readContentHeader); + region("map", stream, counter, this::readMap); + region("entities", stream, counter, this::readEntities); + } + + 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){ + //TODO chunks, backward compat + 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.blockID() != tile.blockID()){ + break; + } + + consecutives++; + } + + stream.writeByte(consecutives); + i += consecutives; + } + } + } + + public void readMap(DataInput 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; + short floorid = stream.readShort(); + short oreid = stream.readShort(); + int consecutives = stream.readUnsignedByte(); + + tiles[x][y] = new Tile(x, y, floorid, oreid, (short)0); + + for(int j = i + 1; j < i + 1 + consecutives; j++){ + int newx = j % width, newy = j / width; + tiles[newx][newy] = new Tile(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 = tiles[x][y]; + tile.setBlock(block); + + if(tile.entity != null){ + //TODO chunks, backward compat + 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(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()){ + //TODO chunks, backward compat + //each entity is a separate chunk. + writeChunk(stream, true, out -> { + stream.writeByte(((SaveTrait)entity).getTypeID()); + ((SaveTrait)entity).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 chunks, backwards compat + byte typeid = stream.readByte(); + SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get(); + trait.readSave(stream); + } + } + } + + 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); + } + } + } + } + + public void writeMeta(DataOutput stream) throws IOException{ + writeStringMap(stream, StringMap.of( + "saved", Time.millis(), + "playtime", headless ? 0 : control.saves.getTotalPlaytime(), + "build", Version.build, + "mapname", world.getMap().name(), + "wave", state.wave, + "wavetime", state.wavetime//, + //"stats", Serialization.writeStatsStreamJson(state.stats), + //"rules", Serialization.writeRulesStreamJson(state.rules), + )); + } + + public void readMeta(DataInput stream) throws IOException{ + StringMap map = readStringMap(stream); + + //TODO read rules, stats + + state.wave = map.getInt("wave"); + state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing); + } +} diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileVersion.java index aedbb453bc..99f7847699 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveFileVersion.java @@ -1,40 +1,50 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.*; +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.entities.Entities; -import io.anuke.mindustry.entities.EntityGroup; -import io.anuke.mindustry.entities.traits.*; -import io.anuke.mindustry.game.Content; -import io.anuke.mindustry.game.MappableContent; -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; - /** * Format: - * 1. version of format / int + * - version of format: int * (begin deflating) - * 2. regions + * - regions begin + * 1. meta tags (short length, key-val UTF pairs) + * 2. map data */ public abstract class SaveFileVersion{ public final int version; - private final ReusableByteOutStream byteOutput = new ReusableByteOutStream(); - private final DataOutputStream dataBytes = new DataOutputStream(byteOutput); - private final Region[] regions; - private final ObjectMap fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad"); + protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream(); + protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput); + protected final ObjectMap fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad"); - public SaveFileVersion(int version, Region... regions){ + public SaveFileVersion(int version){ this.version = version; - this.regions = regions; + } + + protected void region(String name, DataInput stream, CounterInputStream counter, IORunner cons) throws IOException{ + 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{ @@ -67,7 +77,6 @@ public abstract class SaveFileVersion{ /** 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.readUnsignedByte() : input.readInt(); - //TODO descriptive error with chunk name runner.accept(input); return length; } @@ -81,7 +90,7 @@ public abstract class SaveFileVersion{ } } - public void writeMeta(DataOutput stream, ObjectMap map) throws IOException{ + public void writeStringMap(DataOutput stream, ObjectMap map) throws IOException{ stream.writeShort(map.size); for(Entry entry : map.entries()){ stream.writeUTF(entry.key); @@ -89,7 +98,7 @@ public abstract class SaveFileVersion{ } } - public StringMap readMeta(DataInput stream) throws IOException{ + public StringMap readStringMap(DataInput stream) throws IOException{ StringMap map = new StringMap(); short size = stream.readShort(); for(int i = 0; i < size; i++){ @@ -98,227 +107,9 @@ public abstract class SaveFileVersion{ return map; } - public void writeMap(DataOutput stream) throws IOException{ - //write world size - stream.writeShort(world.width()); - stream.writeShort(world.height()); + public abstract void read(DataInputStream stream, CounterInputStream counter) throws IOException; - //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){ - 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.blockID() != tile.blockID()){ - 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; - short floorid = stream.readShort(); - short oreid = stream.readShort(); - int consecutives = stream.readUnsignedByte(); - - tiles[x][y] = new Tile(false, x, y, floorid, oreid); - - for(int j = i + 1; j < i + 1 + consecutives; j++){ - int newx = j % width, newy = j / width; - tiles[newx][newy] = new Tile(false, newx, newy, floorid, 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.readShort()); - Tile tile = tiles[x][y]; - tile.setBlock(block); - - if(tile.entity != null){ - 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{ - //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()){ - //each entity is a separate chunk. - writeChunk(stream, true, out -> { - stream.writeByte(((SaveTrait)entity).getTypeID()); - ((SaveTrait)entity).writeSave(out); - }); - } - } - } - } - - 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 final void read(DataInputStream stream, CounterInputStream counter) throws IOException{ - for(Region region : regions){ - counter.resetCount(); - try{ - int length = readChunk(stream, region.reader); - if(length != counter.count() + 4){ - throw new IOException("Error reading region \"" + region.name + "\": read length mismatch. Expected: " + length + "; Actual: " + (counter.count() + 4)); - } - }catch(Throwable e){ - throw new IOException("Error reading region \"" + region.name + "\".", e); - } - } - } - - public final void write(DataOutputStream stream) throws IOException{ - for(Region region : regions){ - try{ - writeChunk(stream, region.writer); - }catch(Throwable e){ - throw new IOException("Error writing region \"" + region.name + "\".", e); - } - } - } - - /** A region of a save file that holds a specific category of information. - * Uses: simplify code reuse, provide better error messages, skip unnecessary data.*/ - protected final class Region{ - final IORunner writer; - final IORunner reader; - final String name; - - public Region(IORunner writer, IORunner reader, String name){ - this.writer = writer; - this.reader = reader; - this.name = name; - } - } + 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/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index fbba038e5a..929143b82a 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -1,12 +1,14 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.*; +import io.anuke.arc.collection.Array; +import io.anuke.arc.collection.IntMap; import io.anuke.arc.files.FileHandle; import io.anuke.arc.util.io.CounterInputStream; import io.anuke.mindustry.Vars; import io.anuke.mindustry.io.versions.Save1; import java.io.*; +import java.util.Arrays; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; @@ -110,6 +112,8 @@ public class SaveIO{ try{ stream = new DataOutputStream(os); + stream.write(header); + stream.writeInt(getVersion().version); getVersion().write(stream); stream.close(); }catch(Exception e){ @@ -135,6 +139,7 @@ public class SaveIO{ public static void load(InputStream is) 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); @@ -150,6 +155,14 @@ public class SaveIO{ 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/versions/Save1.java b/core/src/io/anuke/mindustry/io/versions/Save1.java index 41563a20c9..5b6e872e49 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save1.java +++ b/core/src/io/anuke/mindustry/io/versions/Save1.java @@ -69,6 +69,7 @@ public class Save1 extends SaveFileVersion{ stream.writeFloat(state.wavetime); //wave countdown Serialization.writeStats(stream, state.stats); + world.spawner.write(stream); writeContentHeader(stream); diff --git a/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java b/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java index cf6f3cc750..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); } } } diff --git a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java index 6d860232c9..8f325fd8c4 100644 --- a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java @@ -12,8 +12,7 @@ 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; @@ -88,12 +87,12 @@ public class MapGenerator extends Generator{ tiles[x][y].setBlock(Blocks.air); } - if(tiles[x][y].block() == Blocks.spawn && enemySpawns != -1){ + if(tiles[x][y].overlay() == Blocks.spawn && enemySpawns != -1){ enemies.add(new Point2(x, y)); - tiles[x][y].setBlock(Blocks.air); + tiles[x][y].setOverlay(Blocks.air); } - if(tiles[x][y].block() == Blocks.part){ + if(tiles[x][y].block() instanceof BlockPart){ tiles[x][y].setBlock(Blocks.air); } } @@ -111,13 +110,15 @@ public class MapGenerator extends Generator{ 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){ + || (tiles[newX][newY].block() == Blocks.air && tile.block() instanceof StaticWall))){ tile.setBlock(tiles[newX][newY].block()); } if(distortFloor){ tile.setFloor(tiles[newX][newY].floor()); - tile.setOverlay(tiles[newX][newY].overlay()); + if(tiles[newX][newY].overlay() != Blocks.spawn && tile.overlay() != Blocks.spawn){ + tile.setOverlay(tiles[newX][newY].overlay()); + } } for(Decoration decor : decorations){ @@ -150,7 +151,7 @@ public class MapGenerator extends Generator{ enemies.shuffle(); for(int i = 0; i < enemySpawns; i++){ Point2 point = enemies.get(i); - tiles[point.x][point.y].setBlock(Blocks.spawn); + tiles[point.x][point.y].setOverlay(Blocks.spawn); int rad = 10, frad = 12; @@ -160,7 +161,9 @@ public class MapGenerator extends Generator{ 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]; - tile.clearOverlay(); + if(tile.overlay() != Blocks.spawn){ + tile.clearOverlay(); + } } } } diff --git a/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java b/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java index bacb5cbaea..1b29ddf9b8 100644 --- a/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java @@ -26,8 +26,7 @@ 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); } } 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/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index 2239a20f05..30a11d8adc 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -40,7 +40,6 @@ public class NetworkIO{ stream.writeInt(player.id); player.write(stream); - world.spawner.write(stream); SaveIO.getSaveWriter().writeMap(stream); stream.write(Team.all.length); @@ -98,7 +97,6 @@ public class NetworkIO{ player.add(); //map - world.spawner.read(stream); SaveIO.getSaveWriter().readMap(stream); world.setMap(new Map(customMapDirectory.child(map), 0, 0, new ObjectMap<>(), true)); 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 147bfffd03..1ee70f24bc 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -36,10 +36,11 @@ public class Tile implements Position, TargetTrait{ block = floor = (Floor)Blocks.air; } - public Tile(boolean __removeThisLater, int x, int y, short floor, short overlay){ + public Tile(int x, int y, short floor, short overlay, short wall){ this.x = (short)x; this.y = (short)y; this.floor = (Floor)content.block(floor); + this.block = content.block(wall); this.overlay = overlay; } From 51f9ad5a2cd798c6413ccd2b1754723058d15e9b Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 6 May 2019 14:34:21 -0400 Subject: [PATCH 05/33] many things --- .../SerializeAnnotationProcessor.java | 11 +- .../io/anuke/mindustry/core/NetClient.java | 2 +- core/src/io/anuke/mindustry/core/World.java | 1 + .../anuke/mindustry/entities/effect/Fire.java | 7 +- .../mindustry/entities/effect/Puddle.java | 7 +- .../mindustry/entities/traits/SaveTrait.java | 1 + .../mindustry/entities/traits/Saveable.java | 3 +- .../mindustry/entities/traits/SyncTrait.java | 9 - .../mindustry/entities/traits/TypeTrait.java | 4 +- .../mindustry/entities/type/BaseUnit.java | 18 +- .../anuke/mindustry/entities/type/Player.java | 16 +- .../mindustry/entities/type/TileEntity.java | 7 +- .../anuke/mindustry/entities/type/Unit.java | 4 +- .../mindustry/entities/units/Statuses.java | 2 +- core/src/io/anuke/mindustry/game/Saves.java | 8 +- .../io/anuke/mindustry/io/LegacyMapIO.java | 180 +++++++++++ core/src/io/anuke/mindustry/io/MapIO.java | 279 +----------------- ...veFileVersion.java => SaveFileReader.java} | 24 +- core/src/io/anuke/mindustry/io/SaveIO.java | 22 +- core/src/io/anuke/mindustry/io/SaveMeta.java | 1 + ...{BaseSaveVersion.java => SaveVersion.java} | 88 +++--- .../io/anuke/mindustry/io/versions/Save1.java | 77 +---- core/src/io/anuke/mindustry/maps/Map.java | 7 +- .../maps/generators/RandomGenerator.java | 3 +- .../anuke/mindustry/net/Administration.java | 3 +- .../src/io/anuke/mindustry/net/NetworkIO.java | 147 +++------ .../mindustry/ui/dialogs/JoinDialog.java | 4 +- .../mindustry/world/blocks/BuildBlock.java | 4 +- .../mindustry/world/blocks/defense/Door.java | 4 +- .../world/blocks/defense/ForceProjector.java | 4 +- .../world/blocks/defense/MendProjector.java | 4 +- .../blocks/defense/OverdriveProjector.java | 4 +- .../blocks/defense/turrets/ItemTurret.java | 4 +- .../distribution/BufferedItemBridge.java | 4 +- .../world/blocks/distribution/Conveyor.java | 4 +- .../world/blocks/distribution/ItemBridge.java | 4 +- .../world/blocks/distribution/Junction.java | 4 +- .../world/blocks/distribution/MassDriver.java | 4 +- .../world/blocks/distribution/Sorter.java | 9 +- .../world/blocks/power/ImpactReactor.java | 4 +- .../world/blocks/power/NuclearReactor.java | 4 +- .../world/blocks/power/PowerGenerator.java | 4 +- .../world/blocks/production/Cultivator.java | 4 +- .../blocks/production/GenericCrafter.java | 4 +- .../world/blocks/sandbox/LiquidSource.java | 4 +- .../world/blocks/storage/Unloader.java | 4 +- .../mindustry/world/blocks/units/MechPad.java | 4 +- .../world/blocks/units/UnitFactory.java | 5 +- .../io/anuke/mindustry/net/ArcNetClient.java | 2 +- .../anuke/mindustry/server/ServerControl.java | 10 + 50 files changed, 424 insertions(+), 613 deletions(-) create mode 100644 core/src/io/anuke/mindustry/io/LegacyMapIO.java rename core/src/io/anuke/mindustry/io/{SaveFileVersion.java => SaveFileReader.java} (89%) rename core/src/io/anuke/mindustry/io/{BaseSaveVersion.java => SaveVersion.java} (77%) diff --git a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java index 7ef43f64d8..43f366c3f9 100644 --- a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java +++ b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java @@ -143,9 +143,8 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ .addStatement("bjson.writeObjectEnd()") .addStatement("stream.writeUTF(output.toString())"); - MethodSpec.Builder binaryJsonWriteStringMethod = MethodSpec.methodBuilder("write" + simpleTypeName + "StringJson") + MethodSpec.Builder binaryJsonWriteStringMethod = MethodSpec.methodBuilder("write" + simpleTypeName + "Json") .returns(String.class) - .addParameter(DataOutput.class, "stream") .addParameter(type, "object") .addException(IOException.class) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) @@ -163,9 +162,17 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addStatement("return read" + simpleTypeName + "Json(bjson.fromJson(null, stream.readUTF()))"); + MethodSpec.Builder binaryJsonReadStringMethod = MethodSpec.methodBuilder("read" + simpleTypeName + "StringJson") + .returns(type) + .addParameter(String.class, "str") + .addException(IOException.class) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addStatement("return read" + simpleTypeName + "Json(bjson.fromJson(null, str))"); + classBuilder.addMethod(binaryJsonWriteMethod.build()); classBuilder.addMethod(binaryJsonWriteStringMethod.build()); classBuilder.addMethod(binaryJsonReadMethod.build()); + classBuilder.addMethod(binaryJsonReadStringMethod.build()); } classBuilder.addMethod(method.build()); diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 2cdd52ad3b..2558d45335 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -117,7 +117,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); diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index ef2cdeef30..d393e8c0f6 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -202,6 +202,7 @@ public class World implements ApplicationListener{ return state.rules.zone; } + //TODO move to Control public void playZone(Zone zone){ ui.loadAnd(() -> { logic.reset(); diff --git a/core/src/io/anuke/mindustry/entities/effect/Fire.java b/core/src/io/anuke/mindustry/entities/effect/Fire.java index 99dcc91a93..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; @@ -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 09eaad2494..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); @@ -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/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..22a4c2564e 100644 --- a/core/src/io/anuke/mindustry/entities/type/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/type/BaseUnit.java @@ -304,11 +304,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 +331,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 +344,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 +363,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 35a8b058bf..033a51b1b8 100644 --- a/core/src/io/anuke/mindustry/entities/type/Player.java +++ b/core/src/io/anuke/mindustry/entities/type/Player.java @@ -817,8 +817,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 +833,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 +844,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(); } @@ -873,7 +873,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 36636d9468..e8d706ee1f 100644 --- a/core/src/io/anuke/mindustry/entities/type/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/type/TileEntity.java @@ -124,7 +124,7 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ } @CallSuper - public void read(DataInput stream) throws IOException{ + public void read(DataInput stream, byte revision) throws IOException{ health = stream.readUnsignedShort(); byte tr = stream.readByte(); byte team = Pack.leftByte(tr); @@ -139,6 +139,11 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{ if(cons != null) cons.read(stream); } + /** Returns the version of this TileEntity IO code.*/ + public byte version(){ + return 0; + } + public boolean collide(Bullet other){ return true; } diff --git a/core/src/io/anuke/mindustry/entities/type/Unit.java b/core/src/io/anuke/mindustry/entities/type/Unit.java index 8bb69e72e1..039c9d8c81 100644 --- a/core/src/io/anuke/mindustry/entities/type/Unit.java +++ b/core/src/io/anuke/mindustry/entities/type/Unit.java @@ -138,7 +138,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 +150,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; 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/Saves.java b/core/src/io/anuke/mindustry/game/Saves.java index 46d66c5d0a..2f394978f6 100644 --- a/core/src/io/anuke/mindustry/game/Saves.java +++ b/core/src/io/anuke/mindustry/game/Saves.java @@ -52,7 +52,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 +134,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 +172,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 +186,7 @@ public class Saves{ totalPlaytime = time; SaveIO.saveToSlot(index); - meta = SaveIO.getData(index); + meta = SaveIO.getMeta(index); if(!state.is(State.menu)){ current = this; } 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..adf555eaf9 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/LegacyMapIO.java @@ -0,0 +1,180 @@ +package io.anuke.mindustry.io; + +import io.anuke.arc.collection.ObjectMap; +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.mindustry.content.Blocks; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.MapIO.TileProvider; +import io.anuke.mindustry.maps.Map; +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.bufferSize; +import static io.anuke.mindustry.Vars.content; + +/** Map IO for the "old" .mmap format. + * Differentiate between legacy maps and new maps by checking the extension (or the header).*/ +public class LegacyMapIO{ + + 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(); + 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{ + SaveIO.getSaveWriter().readContentHeader(stream); + + //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(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 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(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 78cbb4200b..2ba9e911dc 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -1,30 +1,21 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.ObjectMap; -import io.anuke.arc.collection.ObjectMap.Entry; 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.util.*; +import io.anuke.arc.util.Time; import io.anuke.mindustry.content.Blocks; -import io.anuke.mindustry.game.*; +import io.anuke.mindustry.game.Team; import io.anuke.mindustry.maps.Map; 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.DeflaterOutputStream; -import java.util.zip.InflaterInputStream; - -import static io.anuke.mindustry.Vars.bufferSize; -import static io.anuke.mindustry.Vars.content; +import java.io.IOException; +import java.io.InputStream; /** Reads and writes map files. */ public class MapIO{ - public static final int version = 1; private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; public static boolean isImage(FileHandle file){ @@ -53,9 +44,9 @@ public class MapIO{ } @Override - public void setOverlayID(byte b){ - if(b != 0) - floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(floor(), Blocks.air, content.block(b), getTeam())); + public void setOverlay(Block type){ + if(type != Blocks.air) + floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(floor(), Blocks.air, type, getTeam())); } @Override @@ -68,13 +59,10 @@ public class MapIO{ } } }; - readTiles(map, (x, y) -> { - tile.x = (short)x; - tile.y = (short)y; - return tile; - }); + floors.drawPixmap(walls, 0, 0); walls.dispose(); + //TODO actually generate the preview return floors; } @@ -96,255 +84,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.overlayID()); - 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.overlayID() != tile.overlayID()){ - 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.rotation())); //team + rotation - stream.writeShort(/*(short)tile.entity.health*/tile.block().health); //health - if(tile.block() == Blocks.liquidSource || tile.block() == Blocks.unloader || tile.block() == Blocks.sorter){ - stream.writeByte(-1); //write an meaningless byte here, just a fallback thing - } - }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(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.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); - } - } - } - } - - //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(BlockPart.get(dx - 1, dy - 1)); - write.setTeam(Team.blue); - } - } - } - - //actual core parts - tile.setBlock(Blocks.coreShard); - tile.setTeam(Team.blue); - } - } - } - } - - //endregion - interface TileProvider{ Tile get(int x, int y); } diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileReader.java similarity index 89% rename from core/src/io/anuke/mindustry/io/SaveFileVersion.java rename to core/src/io/anuke/mindustry/io/SaveFileReader.java index 99f7847699..dd1dc6ffd1 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveFileReader.java @@ -8,25 +8,11 @@ import io.anuke.arc.util.io.ReusableByteOutStream; import java.io.*; -/** - * Format: - * - version of format: int - * (begin deflating) - * - regions begin - * 1. meta tags (short length, key-val UTF pairs) - * 2. map data - */ -public abstract class SaveFileVersion{ - public final int version; - +public abstract class SaveFileReader{ protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream(); protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput); protected final ObjectMap fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad"); - public SaveFileVersion(int version){ - this.version = version; - } - protected void region(String name, DataInput stream, CounterInputStream counter, IORunner cons) throws IOException{ int length; try{ @@ -62,10 +48,10 @@ public abstract class SaveFileVersion{ if(!isByte){ output.writeInt(length); }else{ - if(length > 255){ - throw new IOException("Byte write length exceeded: " + length + " > 255"); + if(length > Short.MAX_VALUE){ + throw new IOException("Byte write length exceeded: " + length + " > " + Short.MAX_VALUE); } - output.writeByte(length); + output.writeShort(length); } output.write(byteOutput.getBytes(), 0, length); } @@ -76,7 +62,7 @@ public abstract class SaveFileVersion{ /** 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.readUnsignedByte() : input.readInt(); + int length = isByte ? input.readUnsignedShort() : input.readInt(); runner.accept(input); return length; } diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 929143b82a..48f82cdb40 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -17,16 +17,16 @@ import static io.anuke.mindustry.Vars.*; public class SaveIO{ /** 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()); + 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(); } @@ -64,7 +64,7 @@ public class SaveIO{ public static boolean isSaveValid(DataInputStream stream){ try{ - getData(stream); + getMeta(stream); return true; }catch(Exception e){ e.printStackTrace(); @@ -72,15 +72,15 @@ public class SaveIO{ } } - public static SaveMeta getData(int slot){ - return getData(getSlotStream(slot)); + public static SaveMeta getMeta(int slot){ + return getMeta(getSlotStream(slot)); } - public static SaveMeta getData(DataInputStream stream){ + public static SaveMeta getMeta(DataInputStream stream){ try{ 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){ @@ -141,7 +141,7 @@ public class SaveIO{ logic.reset(); readHeader(stream); int version = stream.readInt(); - SaveFileVersion ver = versions.get(version); + SaveVersion ver = versions.get(version); ver.read(stream, counter); }catch(Exception e){ @@ -151,7 +151,7 @@ public class SaveIO{ } } - public static SaveFileVersion getVersion(){ + public static SaveVersion getVersion(){ return versionArray.peek(); } diff --git a/core/src/io/anuke/mindustry/io/SaveMeta.java b/core/src/io/anuke/mindustry/io/SaveMeta.java index f88746605f..6deaddb249 100644 --- a/core/src/io/anuke/mindustry/io/SaveMeta.java +++ b/core/src/io/anuke/mindustry/io/SaveMeta.java @@ -5,6 +5,7 @@ import io.anuke.mindustry.maps.Map; import static io.anuke.mindustry.Vars.world; +//TODO consider removing and replacing with a raw StringMap public class SaveMeta{ public int version; public int build; diff --git a/core/src/io/anuke/mindustry/io/BaseSaveVersion.java b/core/src/io/anuke/mindustry/io/SaveVersion.java similarity index 77% rename from core/src/io/anuke/mindustry/io/BaseSaveVersion.java rename to core/src/io/anuke/mindustry/io/SaveVersion.java index 244b970593..782b228fcc 100644 --- a/core/src/io/anuke/mindustry/io/BaseSaveVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveVersion.java @@ -8,6 +8,7 @@ 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; @@ -16,10 +17,17 @@ import java.io.*; import static io.anuke.mindustry.Vars.*; -public abstract class BaseSaveVersion extends SaveFileVersion{ +public abstract class SaveVersion extends SaveFileReader{ + public final int version; - public BaseSaveVersion(int version){ - super(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"), Serialization.readRulesStringJson(map.get("rules", "{}"))); } public void write(DataOutputStream stream) throws IOException{ @@ -36,6 +44,28 @@ public abstract class BaseSaveVersion extends SaveFileVersion{ region("entities", stream, counter, this::readEntities); } + public void writeMeta(DataOutput stream) throws IOException{ + writeStringMap(stream, StringMap.of( + "saved", Time.millis(), + "playtime", headless ? 0 : control.saves.getTotalPlaytime(), + "build", Version.build, + "mapname", world.getMap().name(), + "wave", state.wave, + "wavetime", state.wavetime, + "stats", Serialization.writeStatsJson(state.stats), + "rules", Serialization.writeRulesJson(state.rules) + )); + } + + 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 = Serialization.readStatsStringJson(map.get("stats", "{}")); + state.rules = Serialization.readRulesStringJson(map.get("rules", "{}")); + } + public void writeMap(DataOutput stream) throws IOException{ //write world size stream.writeShort(world.width()); @@ -68,8 +98,10 @@ public abstract class BaseSaveVersion extends SaveFileVersion{ stream.writeShort(tile.blockID()); if(tile.entity != null){ - //TODO chunks, backward compat - tile.entity.write(stream); + writeChunk(stream, true, out -> { + out.writeByte(tile.entity.version()); + tile.entity.write(stream); + }); }else{ //write consecutive non-entity blocks int consecutives = 0; @@ -123,8 +155,10 @@ public abstract class BaseSaveVersion extends SaveFileVersion{ tile.setBlock(block); if(tile.entity != null){ - //TODO chunks, backward compat - tile.entity.read(stream); + readChunk(stream, true, in -> { + byte version = in.readByte(); + tile.entity.read(stream, version); + }); }else{ int consecutives = stream.readUnsignedByte(); @@ -157,11 +191,12 @@ public abstract class BaseSaveVersion extends SaveFileVersion{ if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ stream.writeInt(group.size()); for(Entity entity : group.all()){ - //TODO chunks, backward compat + SaveTrait save = (SaveTrait)entity; //each entity is a separate chunk. writeChunk(stream, true, out -> { - stream.writeByte(((SaveTrait)entity).getTypeID()); - ((SaveTrait)entity).writeSave(out); + stream.writeByte(save.getTypeID()); + stream.writeByte(save.version()); + save.writeSave(out); }); } } @@ -174,10 +209,13 @@ public abstract class BaseSaveVersion extends SaveFileVersion{ for(int i = 0; i < groups; i++){ int amount = stream.readInt(); for(int j = 0; j < amount; j++){ - //TODO chunks, backwards compat - byte typeid = stream.readByte(); - SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get(); - trait.readSave(stream); + //TODO throw exception on read fail + readChunk(stream, true, in -> { + byte typeid = stream.readByte(); + byte version = stream.readByte(); + SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get(); + trait.readSave(stream, version); + }); } } } @@ -223,26 +261,4 @@ public abstract class BaseSaveVersion extends SaveFileVersion{ } } } - - public void writeMeta(DataOutput stream) throws IOException{ - writeStringMap(stream, StringMap.of( - "saved", Time.millis(), - "playtime", headless ? 0 : control.saves.getTotalPlaytime(), - "build", Version.build, - "mapname", world.getMap().name(), - "wave", state.wave, - "wavetime", state.wavetime//, - //"stats", Serialization.writeStatsStreamJson(state.stats), - //"rules", Serialization.writeRulesStreamJson(state.rules), - )); - } - - public void readMeta(DataInput stream) throws IOException{ - StringMap map = readStringMap(stream); - - //TODO read rules, stats - - state.wave = map.getInt("wave"); - state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing); - } } diff --git a/core/src/io/anuke/mindustry/io/versions/Save1.java b/core/src/io/anuke/mindustry/io/versions/Save1.java index 5b6e872e49..f9ea40b8ce 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save1.java +++ b/core/src/io/anuke/mindustry/io/versions/Save1.java @@ -1,83 +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..93f291e5aa 100644 --- a/core/src/io/anuke/mindustry/maps/Map.java +++ b/core/src/io/anuke/mindustry/maps/Map.java @@ -9,7 +9,6 @@ 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; @@ -44,7 +43,11 @@ public class Map implements Comparable{ } public Map(FileHandle file, int width, int height, ObjectMap tags, boolean custom){ - this(file, width, height, tags, custom, MapIO.version); + this(file, width, height, tags, custom, -1); + } + + public Map(ObjectMap tags){ + this(Vars.customMapDirectory.child(tags.get("name", "unknown")), 0, 0, tags, true); } public Array getWaves(){ diff --git a/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java b/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java index 1b29ddf9b8..3704d192b6 100644 --- a/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java @@ -6,7 +6,6 @@ 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{ @@ -32,7 +31,7 @@ public abstract class RandomGenerator extends Generator{ decorate(tiles); - world.setMap(new Map(customMapDirectory.child("generated"), 0, 0, new ObjectMap<>(), true)); + world.setMap(new Map(new ObjectMap<>())); } public abstract void decorate(Tile[][] tiles); diff --git a/core/src/io/anuke/mindustry/net/Administration.java b/core/src/io/anuke/mindustry/net/Administration.java index 81f729fd67..6f0d83a04e 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(); diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index 30a11d8adc..c022363676 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -1,19 +1,17 @@ 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.game.Version; import io.anuke.mindustry.gen.Serialization; 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,44 +20,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 + 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); 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); } @@ -69,25 +39,11 @@ public class NetworkIO{ try(DataInputStream stream = new DataInputStream(is)){ Time.clear(); - - //general state state.rules = Serialization.readRules(stream); - String map = stream.readUTF(); + world.setMap(new Map(SaveIO.getSaveWriter().readStringMap(stream))); - 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(); @@ -96,81 +52,60 @@ public class NetworkIO{ player.resetID(id); player.add(); - //map 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())); - } - } - }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/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/world/blocks/BuildBlock.java b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java index bfe29bed54..5be964ca80 100644 --- a/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/BuildBlock.java @@ -319,8 +319,8 @@ public class BuildBlock extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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/defense/Door.java b/core/src/io/anuke/mindustry/world/blocks/defense/Door.java index 62dcb98d6e..07454f6849 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/Door.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/Door.java @@ -90,8 +90,8 @@ public class Door extends Wall{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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 503081a610..1db6033d79 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/ForceProjector.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/ForceProjector.java @@ -212,8 +212,8 @@ public class ForceProjector extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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 22e45e65cb..dc8075a680 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/MendProjector.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/MendProjector.java @@ -154,8 +154,8 @@ public class MendProjector extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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 6b0efc6450..98293c8066 100644 --- a/core/src/io/anuke/mindustry/world/blocks/defense/OverdriveProjector.java +++ b/core/src/io/anuke/mindustry/world/blocks/defense/OverdriveProjector.java @@ -153,8 +153,8 @@ public class OverdriveProjector extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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 f23ae75df3..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 @@ -131,8 +131,8 @@ public class ItemTurret extends CooledTurret{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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/distribution/BufferedItemBridge.java b/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java index 8c692f9b5d..74d47e2c6d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/BufferedItemBridge.java @@ -53,8 +53,8 @@ public class BufferedItemBridge extends ExtendingItemBridge{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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/Conveyor.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java index 25fb795702..6f788c3eec 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java @@ -376,8 +376,8 @@ public class Conveyor extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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 d49f665f16..22ca19a48a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/ItemBridge.java @@ -340,8 +340,8 @@ public class ItemBridge extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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 87c8c71aa7..3760197ac8 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Junction.java @@ -94,8 +94,8 @@ public class Junction extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); for(Buffer b : buffers){ b.read(stream); } 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 ef1f5cb256..ee83e508ae 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/MassDriver.java @@ -328,8 +328,8 @@ public class MassDriver extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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/Sorter.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java index e9bd6df167..ff5888dfbf 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Sorter.java @@ -134,14 +134,13 @@ public class Sorter extends Block{ @Override public void write(DataOutput stream) throws IOException{ super.write(stream); - stream.writeByte(sortItem == null ? -1 : sortItem.id); + stream.writeShort(sortItem == null ? -1 : sortItem.id); } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); - 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/NuclearReactor.java b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java index 9e9b33cfde..bde3b43176 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/NuclearReactor.java @@ -188,8 +188,8 @@ public class NuclearReactor 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); 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 7277f0e23c..f4e1597445 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java @@ -68,8 +68,8 @@ public class PowerGenerator extends PowerDistributor{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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/production/Cultivator.java b/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java index ae4667deef..07a7242bb2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/Cultivator.java @@ -123,8 +123,8 @@ public class Cultivator extends GenericCrafter{ } @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/production/GenericCrafter.java b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java index 794b97b8f4..e237dd1228 100644 --- a/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java +++ b/core/src/io/anuke/mindustry/world/blocks/production/GenericCrafter.java @@ -156,8 +156,8 @@ public class GenericCrafter extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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 52132a69ae..54675a0bdc 100644 --- a/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java +++ b/core/src/io/anuke/mindustry/world/blocks/sandbox/LiquidSource.java @@ -121,8 +121,8 @@ public class LiquidSource extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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 33494a8db5..ce784a3b7a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/Unloader.java @@ -109,8 +109,8 @@ public class Unloader extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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 720cb2c358..448d9725f0 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/MechPad.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/MechPad.java @@ -185,8 +185,8 @@ public class MechPad extends Block{ } @Override - public void read(DataInput stream) throws IOException{ - super.read(stream); + 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 3069d844a2..7e4a6d483d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/UnitFactory.java @@ -198,13 +198,12 @@ public class UnitFactory extends Block{ 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{ - super.read(stream); + public void read(DataInput stream, byte revision) throws IOException{ + super.read(stream, revision); buildTime = stream.readFloat(); spawned = stream.readInt(); } 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/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index 3d1fa9f8f2..0a077704ab 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -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); From 3035d569cca6039febc7901eb66f302e845ef8cf Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 6 May 2019 17:03:53 -0400 Subject: [PATCH 06/33] Editor<->world data merging --- core/src/io/anuke/mindustry/core/Control.java | 19 ++- core/src/io/anuke/mindustry/core/World.java | 33 +--- .../anuke/mindustry/editor/DrawOperation.java | 3 +- .../io/anuke/mindustry/editor/EditorTile.java | 5 +- .../io/anuke/mindustry/editor/MapEditor.java | 152 +++++------------- .../mindustry/editor/MapEditorDialog.java | 4 +- core/src/io/anuke/mindustry/io/MapIO.java | 24 ++- .../io/anuke/mindustry/io/SaveFileReader.java | 8 +- core/src/io/anuke/mindustry/io/SaveIO.java | 4 + .../io/anuke/mindustry/io/SaveVersion.java | 4 +- core/src/io/anuke/mindustry/maps/Maps.java | 9 +- .../mindustry/ui/dialogs/MapsDialog.java | 2 +- settings.gradle | 1 + 13 files changed, 108 insertions(+), 160 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index b641b1dcb2..58b345c8f7 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; @@ -207,6 +207,23 @@ public class Control implements ApplicationListener{ }); } + public void playZone(Zone zone){ + ui.loadAnd(() -> { + logic.reset(); + state.rules = zone.rules.get(); + state.rules.zone = zone; + world.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(); + }); + } + public boolean isHighScore(){ return hiscore; } diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index d393e8c0f6..91244682c3 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -202,24 +202,6 @@ public class World implements ApplicationListener{ return state.rules.zone; } - //TODO move to Control - public void playZone(Zone zone){ - ui.loadAnd(() -> { - logic.reset(); - state.rules = zone.rules.get(); - state.rules.zone = zone; - 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(); - }); - } - public void loadGenerator(Generator generator){ beginMapLoad(); @@ -231,18 +213,9 @@ public class World implements ApplicationListener{ } 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){ @@ -254,7 +227,7 @@ public class World implements ApplicationListener{ return; } - endMapLoad(); + this.currentMap = map; invalidMap = false; @@ -503,7 +476,7 @@ public class World implements ApplicationListener{ } } - //update cliffs, occlusion data + //update occlusion data for(int x = 0; x < tiles.length; x++){ for(int y = 0; y < tiles[0].length; y++){ Tile tile = tiles[x][y]; diff --git a/core/src/io/anuke/mindustry/editor/DrawOperation.java b/core/src/io/anuke/mindustry/editor/DrawOperation.java index 62dd831fa1..df4e0374b9 100755 --- a/core/src/io/anuke/mindustry/editor/DrawOperation.java +++ b/core/src/io/anuke/mindustry/editor/DrawOperation.java @@ -60,9 +60,8 @@ public class DrawOperation{ class TileOpStruct{ short x; short y; + short value; byte type; - byte from; - byte to; } public enum OpType{ diff --git a/core/src/io/anuke/mindustry/editor/EditorTile.java b/core/src/io/anuke/mindustry/editor/EditorTile.java index 75a35f8e8a..23f1604c0d 100644 --- a/core/src/io/anuke/mindustry/editor/EditorTile.java +++ b/core/src/io/anuke/mindustry/editor/EditorTile.java @@ -11,10 +11,11 @@ import io.anuke.mindustry.world.modules.*; 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); + public EditorTile(int x, int y, short floor, short overlay, short wall){ + super(x, y, floor, overlay, wall); } @Override diff --git a/core/src/io/anuke/mindustry/editor/MapEditor.java b/core/src/io/anuke/mindustry/editor/MapEditor.java index c1253509aa..0390e74de3 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditor.java +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -1,9 +1,9 @@ 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.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; @@ -12,16 +12,18 @@ 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.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 StringMap tags = new StringMap(); private MapRenderer renderer = new MapRenderer(this); - private Tile[][] tiles; private OperationStack stack = new OperationStack(); private DrawOperation currentOp; @@ -32,7 +34,7 @@ public class MapEditor{ public Block drawBlock = Blocks.stone; public Team drawTeam = Team.blue; - public ObjectMap getTags(){ + public StringMap getTags(){ return tags; } @@ -40,7 +42,7 @@ public class MapEditor{ reset(); loading = true; - tiles = createTiles(width, height); + createTiles(width, height); renderer.resize(width(), height()); loading = false; } @@ -49,30 +51,25 @@ public class MapEditor{ reset(); loading = true; - tiles = createTiles(map.width, map.height); + //TODO redundant and does nothing since tiles are overwritten + createTiles(map.width, map.height); tags.putAll(map.tags); - MapIO.readTiles(map, tiles); + //TODO this actually creates the tiles, which are not editor tiles + MapIO.loadMap(map); checkLinkedTiles(); renderer.resize(width(), height()); loading = false; } - public void beginEdit(Tile[][] tiles){ - reset(); - - this.tiles = tiles; - checkLinkedTiles(); - 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 +77,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,15 +91,14 @@ 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){ @@ -131,40 +113,19 @@ public class MapEditor{ } 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 +138,34 @@ 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; } } } } - 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 +178,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){ @@ -255,22 +199,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 +206,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 +219,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); } } } diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index ef7785e102..905390631c 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -91,7 +91,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); @@ -216,7 +216,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"); } } diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index 2ba9e911dc..59c9aa3f25 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -1,18 +1,23 @@ package io.anuke.mindustry.io; +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.util.Time; +import io.anuke.arc.util.io.CounterInputStream; import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.Version; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.blocks.Floor; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; +import java.util.zip.InflaterInputStream; + +import static io.anuke.mindustry.Vars.bufferSize; /** Reads and writes map files. */ public class MapIO{ @@ -31,6 +36,21 @@ 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 loadMap(Map map){ + SaveIO.load(map.file); + } + public static Pixmap generatePreview(Map map) throws IOException{ Time.mark(); Pixmap floors = new Pixmap(map.width, map.height, Format.RGBA8888); diff --git a/core/src/io/anuke/mindustry/io/SaveFileReader.java b/core/src/io/anuke/mindustry/io/SaveFileReader.java index dd1dc6ffd1..437227b16a 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileReader.java +++ b/core/src/io/anuke/mindustry/io/SaveFileReader.java @@ -67,8 +67,12 @@ public abstract class SaveFileReader{ return length; } - /** Skip a chunk completely. */ - public void skipChunk(DataInput input, boolean isByte) throws IOException{ + 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){ diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 48f82cdb40..1dd209b6e0 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -30,6 +30,10 @@ public class SaveIO{ 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(); diff --git a/core/src/io/anuke/mindustry/io/SaveVersion.java b/core/src/io/anuke/mindustry/io/SaveVersion.java index 782b228fcc..3ea724ddf9 100644 --- a/core/src/io/anuke/mindustry/io/SaveVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveVersion.java @@ -53,7 +53,9 @@ public abstract class SaveVersion extends SaveFileReader{ "wave", state.wave, "wavetime", state.wavetime, "stats", Serialization.writeStatsJson(state.stats), - "rules", Serialization.writeRulesJson(state.rules) + "rules", Serialization.writeRulesJson(state.rules), + "width", world.width(), + "height", world.height() )); } diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index 015b994174..1db54badb2 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -52,7 +52,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 +81,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); 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,7 +105,7 @@ public class Maps implements Disposable{ } //create map, write it, etc etc etc - Map map = new Map(file, data.length, data[0].length, tags, true); + Map map = new Map(file, world.width(), world.height(), tags, true); MapIO.writeMap(file, map, data); if(!headless){ @@ -171,7 +170,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/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/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') From 88de54ec902c309a7bb37d61c9b600cce2fd4b13 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 6 May 2019 18:32:18 -0400 Subject: [PATCH 07/33] Further unfinished map tweaks --- core/src/io/anuke/mindustry/core/NetServer.java | 5 ++--- .../src/io/anuke/mindustry/editor/MapEditor.java | 16 ++++++++++++---- .../anuke/mindustry/editor/MapEditorDialog.java | 9 ++++----- core/src/io/anuke/mindustry/io/LegacyMapIO.java | 2 +- core/src/io/anuke/mindustry/io/MapIO.java | 8 ++++++++ core/src/io/anuke/mindustry/io/SaveIO.java | 11 ++--------- core/src/io/anuke/mindustry/io/SaveVersion.java | 4 ++-- core/src/io/anuke/mindustry/maps/Maps.java | 5 ++--- 8 files changed, 33 insertions(+), 27 deletions(-) diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index ea02ae0c01..491eab38ed 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; @@ -212,7 +211,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()); diff --git a/core/src/io/anuke/mindustry/editor/MapEditor.java b/core/src/io/anuke/mindustry/editor/MapEditor.java index 0390e74de3..523f0fc2f3 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditor.java +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -3,11 +3,13 @@ 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.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; @@ -15,8 +17,6 @@ import io.anuke.mindustry.world.Tile; 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{ @@ -47,7 +47,7 @@ public class MapEditor{ loading = false; } - public void beginEdit(Map map) throws IOException{ + public void beginEdit(Map map){ reset(); loading = true; @@ -61,6 +61,14 @@ public class MapEditor{ loading = false; } + public void beginEdit(Pixmap pixmap){ + reset(); + + createTiles(pixmap.getWidth(), pixmap.getHeight()); + load(() -> LegacyMapIO.readPixmap(pixmap, tiles())); + renderer.resize(width(), height()); + } + //adds missing blockparts public void checkLinkedTiles(){ Tile[][] tiles = world.getTiles(); @@ -109,7 +117,7 @@ public class MapEditor{ clearOp(); brushSize = 1; drawBlock = Blocks.stone; - tags = new ObjectMap<>(); + tags = new StringMap(); } public Tile[][] tiles(){ diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index 905390631c..2d664bcfda 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -19,6 +19,7 @@ import io.anuke.arc.util.*; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.Platform; import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.LegacyMapIO; import io.anuke.mindustry.io.MapIO; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.ui.dialogs.FloatingDialog; @@ -103,9 +104,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); @@ -281,9 +281,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/io/LegacyMapIO.java b/core/src/io/anuke/mindustry/io/LegacyMapIO.java index adf555eaf9..789e9d4c62 100644 --- a/core/src/io/anuke/mindustry/io/LegacyMapIO.java +++ b/core/src/io/anuke/mindustry/io/LegacyMapIO.java @@ -143,7 +143,7 @@ public class LegacyMapIO{ } /** Reads a pixmap in the 3.5 pixmap format. */ - public static void readLegacyPixmap(Pixmap pixmap, Tile[][] tiles){ + 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); diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index 59c9aa3f25..7f07fbd989 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -20,6 +20,7 @@ import java.util.zip.InflaterInputStream; import static io.anuke.mindustry.Vars.bufferSize; /** Reads and writes map files. */ +//TODO does this class even need to exist??? move to Maps public class MapIO{ private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; @@ -47,6 +48,13 @@ public class MapIO{ } } + public static void writeMap(FileHandle file, Map map) throws IOException{ + SaveIO.write(file); + try(DataOutputStream out = new DataOutputStream(file.write(false, bufferSize))){ + + } + } + public static void loadMap(Map map){ SaveIO.load(map.file); } diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 1dd209b6e0..d01c38c04c 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -4,12 +4,12 @@ import io.anuke.arc.collection.Array; import io.anuke.arc.collection.IntMap; 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 java.io.*; import java.util.Arrays; -import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import static io.anuke.mindustry.Vars.*; @@ -101,14 +101,7 @@ public class SaveIO{ } 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); - } - }); + write(new FastDeflaterOutputStream(file.write(false, bufferSize))); } public static void write(OutputStream os){ diff --git a/core/src/io/anuke/mindustry/io/SaveVersion.java b/core/src/io/anuke/mindustry/io/SaveVersion.java index 3ea724ddf9..5acf7e180f 100644 --- a/core/src/io/anuke/mindustry/io/SaveVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveVersion.java @@ -30,14 +30,14 @@ public abstract class SaveVersion extends SaveFileReader{ return new SaveMeta(map.getInt("version"), map.getLong("saved"), map.getLong("playtime"), map.getInt("build"), map.get("mapname"), map.getInt("wave"), Serialization.readRulesStringJson(map.get("rules", "{}"))); } - public void write(DataOutputStream stream) throws IOException{ + public final void write(DataOutputStream stream) throws IOException{ region("meta", stream, this::writeMeta); region("content", stream, this::writeContentHeader); region("map", stream, this::writeMap); region("entities", stream, this::writeEntities); } - public void read(DataInputStream stream, CounterInputStream counter) throws IOException{ + public final void read(DataInputStream stream, CounterInputStream counter) throws IOException{ region("meta", stream, counter, this::readMeta); region("content", stream, counter, this::readContentHeader); region("map", stream, counter, this::readMap); diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index 1db54badb2..ec05d339c7 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -10,7 +10,6 @@ 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 java.io.IOException; import java.io.StringWriter; @@ -106,10 +105,10 @@ public class Maps implements Disposable{ //create map, write it, etc etc etc Map map = new Map(file, world.width(), world.height(), tags, true); - MapIO.writeMap(file, map, data); + 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(); From 1b77247c405888ee7646924e94471f64bf708da0 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 7 May 2019 11:56:17 -0400 Subject: [PATCH 08/33] wow it builds --- .../anuke/mindustry/editor/DrawOperation.java | 35 ++- .../io/anuke/mindustry/editor/EditorTile.java | 51 ++-- .../io/anuke/mindustry/editor/EditorTool.java | 18 +- .../io/anuke/mindustry/editor/MapEditor.java | 3 +- .../mindustry/editor/MapEditorDialog.java | 4 +- .../mindustry/editor/MapGenerateDialog.java | 3 +- .../io/anuke/mindustry/io/LegacyMapIO.java | 11 +- core/src/io/anuke/mindustry/io/MapIO.java | 7 +- core/src/io/anuke/mindustry/io/SaveIO.java | 19 +- .../io/anuke/mindustry/io/SaveVersion.java | 19 +- core/src/io/anuke/mindustry/maps/Map.java | 13 +- core/src/io/anuke/mindustry/maps/Maps.java | 5 +- .../maps/generators/MapGenerator.java | 221 +++++++++--------- .../maps/generators/RandomGenerator.java | 4 +- .../mindustry/ui/dialogs/ZoneInfoDialog.java | 2 +- 15 files changed, 206 insertions(+), 209 deletions(-) diff --git a/core/src/io/anuke/mindustry/editor/DrawOperation.java b/core/src/io/anuke/mindustry/editor/DrawOperation.java index df4e0374b9..1a638c74d0 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.value(l, get(editor, 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.value(l, get(editor, 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(MapEditor editor, 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.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); } }); @@ -60,8 +75,8 @@ public class DrawOperation{ class TileOpStruct{ short x; short y; - short value; byte type; + short value; } public enum OpType{ @@ -69,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 23f1604c0d..63fa0a3a33 100644 --- a/core/src/io/anuke/mindustry/editor/EditorTile.java +++ b/core/src/io/anuke/mindustry/editor/EditorTile.java @@ -33,54 +33,43 @@ 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; + if(block == type) return; + op(OpType.block, block.id); super.setBlock(type); - if(pteam != getTeamID()){ - op(TileOp.get(x, y, (byte)OpType.team.ordinal(), pteam, getTeamID())); - } - op(TileOp.get(x, y, (byte)OpType.block.ordinal(), previous.id, type.id)); + //TODO check if this line is necessary + //if(pteam != getTeamID()){ + // op((byte)OpType.team.ordinal(), pteam, getTeamID()); + //} } @Override public void setTeam(Team team){ - byte previous = getTeamID(); - if(previous == team.ordinal()) 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 rotation(byte rotation){ - byte previous = rotation(); - if(previous == rotation) return; + public void rotation(int rotation){ + if(rotation == rotation()) return; + op(OpType.rotation, rotation()); super.rotation(rotation); - op(TileOp.get(x, y, (byte)OpType.rotation.ordinal(), previous, rotation)); } @Override - public void setOverlayID(byte ore){ - byte previous = overlayID(); - if(previous == ore) return; - super.setOverlayID(ore); - op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), previous, ore)); + public void setOverlayID(short overlay){ + if(overlayID() == overlay) return; + op(OpType.overlay, overlay); + super.setOverlayID(overlay); } @Override @@ -112,7 +101,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 b92d1ac3d0..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; } diff --git a/core/src/io/anuke/mindustry/editor/MapEditor.java b/core/src/io/anuke/mindustry/editor/MapEditor.java index 523f0fc2f3..f6f7f7c584 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditor.java +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -1,6 +1,5 @@ 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; @@ -110,7 +109,7 @@ public class MapEditor{ } 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(){ diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index 2d664bcfda..7a1c4add99 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -19,13 +19,11 @@ import io.anuke.arc.util.*; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.Platform; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.io.LegacyMapIO; import io.anuke.mindustry.io.MapIO; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.ui.dialogs.FloatingDialog; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Block.Icon; -import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.OverlayFloor; import io.anuke.mindustry.world.blocks.storage.CoreBlock; @@ -122,7 +120,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); diff --git a/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java b/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java index 0e1f81affb..02d5453b22 100644 --- a/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java @@ -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; diff --git a/core/src/io/anuke/mindustry/io/LegacyMapIO.java b/core/src/io/anuke/mindustry/io/LegacyMapIO.java index 789e9d4c62..63381bbebb 100644 --- a/core/src/io/anuke/mindustry/io/LegacyMapIO.java +++ b/core/src/io/anuke/mindustry/io/LegacyMapIO.java @@ -1,6 +1,6 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.ObjectMap; +import io.anuke.arc.collection.StringMap; import io.anuke.arc.files.FileHandle; import io.anuke.arc.graphics.Color; import io.anuke.arc.graphics.Pixmap; @@ -27,7 +27,7 @@ public class LegacyMapIO{ public static Map readMap(FileHandle file, boolean custom) throws IOException{ try(DataInputStream stream = new DataInputStream(file.read(1024))){ - ObjectMap tags = new ObjectMap<>(); + StringMap tags = new StringMap(); //meta is uncompressed int version = stream.readInt(); @@ -107,7 +107,12 @@ public class LegacyMapIO{ Block block = content.block(stream.readByte()); Tile tile = tiles.get(x, y); - tile.setBlock(block); + //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(tile.entity != null){ byte tr = stream.readByte(); diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index 7f07fbd989..760b7b6ae3 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -49,9 +49,10 @@ public class MapIO{ } public static void writeMap(FileHandle file, Map map) throws IOException{ - SaveIO.write(file); - try(DataOutputStream out = new DataOutputStream(file.write(false, bufferSize))){ - + try{ + SaveIO.write(file, map.tags); + }catch(Exception e){ + throw new IOException(e); } } diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index d01c38c04c..7c4be59f79 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -1,7 +1,6 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.Array; -import io.anuke.arc.collection.IntMap; +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; @@ -100,18 +99,26 @@ public class SaveIO{ return file.sibling(file.name() + "-backup." + file.extension()); } - public static void write(FileHandle file){ - write(new FastDeflaterOutputStream(file.write(false, bufferSize))); + public static void write(FileHandle file, StringMap tags){ + write(new FastDeflaterOutputStream(file.write(false, bufferSize)), tags); } - public static void write(OutputStream os){ + public static void write(FileHandle file){ + write(file, null); + } + + public static void write(OutputStream os, StringMap tags){ DataOutputStream stream; try{ stream = new DataOutputStream(os); stream.write(header); stream.writeInt(getVersion().version); - getVersion().write(stream); + if(tags == null){ + getVersion().write(stream); + }else{ + getVersion().write(stream, tags); + } stream.close(); }catch(Exception e){ throw new RuntimeException(e); diff --git a/core/src/io/anuke/mindustry/io/SaveVersion.java b/core/src/io/anuke/mindustry/io/SaveVersion.java index 5acf7e180f..f63f528520 100644 --- a/core/src/io/anuke/mindustry/io/SaveVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveVersion.java @@ -1,7 +1,6 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.Array; -import io.anuke.arc.collection.StringMap; +import io.anuke.arc.collection.*; import io.anuke.arc.util.Time; import io.anuke.arc.util.io.CounterInputStream; import io.anuke.mindustry.entities.Entities; @@ -31,10 +30,7 @@ public abstract class SaveVersion extends SaveFileReader{ } public final void write(DataOutputStream stream) throws IOException{ - region("meta", stream, this::writeMeta); - region("content", stream, this::writeContentHeader); - region("map", stream, this::writeMap); - region("entities", stream, this::writeEntities); + write(stream, new StringMap()); } public final void read(DataInputStream stream, CounterInputStream counter) throws IOException{ @@ -44,7 +40,14 @@ public abstract class SaveVersion extends SaveFileReader{ region("entities", stream, counter, this::readEntities); } - public void writeMeta(DataOutput stream) throws IOException{ + public 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(), @@ -56,7 +59,7 @@ public abstract class SaveVersion extends SaveFileReader{ "rules", Serialization.writeRulesJson(state.rules), "width", world.width(), "height", world.height() - )); + ).merge(tags)); } public void readMeta(DataInput stream) throws IOException{ diff --git a/core/src/io/anuke/mindustry/maps/Map.java b/core/src/io/anuke/mindustry/maps/Map.java index 93f291e5aa..6765528ec0 100644 --- a/core/src/io/anuke/mindustry/maps/Map.java +++ b/core/src/io/anuke/mindustry/maps/Map.java @@ -1,8 +1,7 @@ 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.Log; @@ -16,7 +15,7 @@ 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. */ @@ -28,7 +27,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; @@ -38,15 +37,15 @@ 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){ + public Map(FileHandle file, int width, int height, StringMap tags, boolean custom){ this(file, width, height, tags, custom, -1); } - public Map(ObjectMap tags){ + public Map(StringMap tags){ this(Vars.customMapDirectory.child(tags.get("name", "unknown")), 0, 0, tags, true); } diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index ec05d339c7..7f00fae5e0 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -1,8 +1,7 @@ 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; @@ -83,7 +82,7 @@ public class Maps implements Disposable{ 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; diff --git a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java index 8f325fd8c4..aa318bf072 100644 --- a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java @@ -16,8 +16,6 @@ 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{ @@ -69,119 +67,116 @@ 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.readTiles(map, tiles); - 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); - } - } - } - - 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))){ - tile.setBlock(tiles[newX][newY].block()); - } - - 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); - } - } - - 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); - } - - 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; - - 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); } + + //TODO this will probably not get the desired effect + 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); + } + } + } + + 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))){ + tile.setBlock(tiles[newX][newY].block()); + } + + 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); + } + } + + 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); + } + + 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; + + 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); } 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 3704d192b6..23591e86df 100644 --- a/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java @@ -1,6 +1,6 @@ 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; @@ -31,7 +31,7 @@ public abstract class RandomGenerator extends Generator{ decorate(tiles); - world.setMap(new Map(new ObjectMap<>())); + world.setMap(new Map(new StringMap())); } public abstract void decorate(Tile[][] tiles); 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(); From 53167a3b52f10c2e90cdc08eb48fd699354c79a7 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 7 May 2019 17:30:37 -0400 Subject: [PATCH 09/33] Map conversion code --- core/src/io/anuke/mindustry/Vars.java | 4 +- .../io/anuke/mindustry/core/NetServer.java | 2 +- core/src/io/anuke/mindustry/core/World.java | 6 +- .../io/anuke/mindustry/io/LegacyMapIO.java | 57 +++++++++++++++---- core/src/io/anuke/mindustry/io/MapIO.java | 2 +- .../io/anuke/mindustry/io/SaveFileReader.java | 13 +++-- core/src/io/anuke/mindustry/io/SaveIO.java | 12 +++- .../io/anuke/mindustry/io/SaveVersion.java | 5 +- core/src/io/anuke/mindustry/maps/Maps.java | 30 ++++++++++ .../maps/generators/MapGenerator.java | 7 ++- 10 files changed, 106 insertions(+), 32 deletions(-) diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 191d5a65a5..f38ac346fd 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -104,8 +104,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"; diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 491eab38ed..9772b0e876 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -477,7 +477,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 91244682c3..c303fd4c75 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,7 +17,7 @@ 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; @@ -33,11 +32,10 @@ public class World implements ApplicationListener{ private Map currentMap; private Tile[][] tiles; - private Array tempTiles = new Array<>(); private boolean generating, invalidMap; public World(){ - maps.load(); + Core.app.post(maps::load); } @Override diff --git a/core/src/io/anuke/mindustry/io/LegacyMapIO.java b/core/src/io/anuke/mindustry/io/LegacyMapIO.java index 63381bbebb..d002a4d0f8 100644 --- a/core/src/io/anuke/mindustry/io/LegacyMapIO.java +++ b/core/src/io/anuke/mindustry/io/LegacyMapIO.java @@ -1,6 +1,6 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.StringMap; +import io.anuke.arc.collection.*; import io.anuke.arc.files.FileHandle; import io.anuke.arc.graphics.Color; import io.anuke.arc.graphics.Pixmap; @@ -10,6 +10,7 @@ import io.anuke.mindustry.content.Blocks; 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; @@ -18,12 +19,25 @@ import io.anuke.mindustry.world.blocks.Floor; import java.io.*; 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.*; /** 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"); + + /* 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); + 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 Tile(x, y); + } + } + 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))){ @@ -78,24 +92,40 @@ public class LegacyMapIO{ try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){ try{ - SaveIO.getSaveWriter().readContentHeader(stream); + 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; - byte floorid = stream.readByte(); - byte oreid = stream.readByte(); + int floorid = stream.readUnsignedByte(); + int oreid = stream.readUnsignedByte(); int consecutives = stream.readUnsignedByte(); Tile tile = tiles.get(x, y); - tile.setFloor((Floor)content.block(floorid)); - tile.setOverlay(content.block(oreid)); + 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)content.block(floorid)); - newTile.setOverlay(content.block(oreid)); + newTile.setFloor((Floor)idmap.get(floorid)); + newTile.setOverlay(idmap.get(oreid)); } i += consecutives; @@ -104,7 +134,8 @@ public class LegacyMapIO{ //read blocks for(int i = 0; i < width * height; i++){ int x = i % width, y = i / width; - Block block = content.block(stream.readByte()); + int id = stream.readUnsignedByte(); + Block block = idmap.get(id); Tile tile = tiles.get(x, y); //the spawn block is saved in the block tile layer in older maps, shift it to the overlay @@ -114,7 +145,9 @@ public class LegacyMapIO{ tile.setOverlay(block); } - if(tile.entity != null){ + 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) diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index 760b7b6ae3..bbeb6d87ff 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -20,7 +20,7 @@ import java.util.zip.InflaterInputStream; import static io.anuke.mindustry.Vars.bufferSize; /** Reads and writes map files. */ -//TODO does this class even need to exist??? move to Maps +//TODO does this class even need to exist??? move to Maps? public class MapIO{ private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; diff --git a/core/src/io/anuke/mindustry/io/SaveFileReader.java b/core/src/io/anuke/mindustry/io/SaveFileReader.java index 437227b16a..e761a0d00e 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileReader.java +++ b/core/src/io/anuke/mindustry/io/SaveFileReader.java @@ -11,17 +11,18 @@ import java.io.*; public abstract class SaveFileReader{ protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream(); protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput); - protected final ObjectMap fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad"); + 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)); + if(length != counter.count() - 4){ + throw new IOException("Error reading region \"" + name + "\": read length mismatch. Expected: " + length + "; Actual: " + (counter.count() - 4)); } } @@ -40,10 +41,10 @@ public abstract class SaveFileReader{ /** 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{ //reset output position - byteOutput.position(0); - //writer the needed info + byteOutput.reset(); + //write the needed info runner.accept(dataBytes); - int length = byteOutput.position(); + int length = byteOutput.size(); //write length (either int or byte) followed by the output bytes if(!isByte){ output.writeInt(length); diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 7c4be59f79..628217cc98 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -58,11 +58,19 @@ public class SaveIO{ } public static boolean isSaveValid(int slot){ - return isSaveValid(getSlotStream(slot)) || isSaveValid(getBackupSlotStream(slot)); + try{ //file may not even exist at all, so catch that too + return isSaveValid(getSlotStream(slot)) || isSaveValid(getBackupSlotStream(slot)); + }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){ diff --git a/core/src/io/anuke/mindustry/io/SaveVersion.java b/core/src/io/anuke/mindustry/io/SaveVersion.java index f63f528520..5d513971a0 100644 --- a/core/src/io/anuke/mindustry/io/SaveVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveVersion.java @@ -1,6 +1,7 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.*; +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; @@ -52,7 +53,7 @@ public abstract class SaveVersion extends SaveFileReader{ "saved", Time.millis(), "playtime", headless ? 0 : control.saves.getTotalPlaytime(), "build", Version.build, - "mapname", world.getMap().name(), + "mapname", world.getMap() == null ? "unknown" : world.getMap().name(), "wave", state.wave, "wavetime", state.wavetime, "stats", Serialization.writeStatsJson(state.stats), diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index 7f00fae5e0..c5b03e6d2c 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -8,6 +8,7 @@ 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.LegacyMapIO; import io.anuke.mindustry.io.MapIO; import java.io.IOException; @@ -59,6 +60,15 @@ public class Maps implements Disposable{ /** Load all maps. Should be called at application start. */ public void load(){ try{ + //TODO remove, this is only for testing + for(FileHandle in : Core.files.absolute("/home/anuke/Projects/Mindustry/core/assets/maps").list()){ + if(in.extension().equalsIgnoreCase(oldMapExtension)){ + Log.info("Converting {0}...", in); + LegacyMapIO.convertMap(in, in.sibling(in.nameWithoutExtension() + "." + mapExtension)); + Log.info("Converted {0}", in); + } + } + for(String name : defaultMapNames){ FileHandle file = Core.files.internal("maps/" + name + "." + mapExtension); loadMap(file, false); @@ -183,6 +193,26 @@ public class Maps implements Disposable{ } private void loadCustomMaps(){ + boolean convertedAny = false; + for(FileHandle file : customMapDirectory.list()){ + if(file.extension().equalsIgnoreCase(oldMapExtension)){ + convertedAny = true; + try{ + LegacyMapIO.convertMap(file, file.sibling(file.nameWithoutExtension() + "." + mapExtension)); + //TODO delete so conversion doesn't happen again + //file.delete(); + }catch(IOException e){ + //don't convert + Log.err(e); + } + } + } + + //free up any potential memory that was used up during conversion + if(convertedAny){ + world.createTiles(0, 0); + } + for(FileHandle file : customMapDirectory.list()){ try{ if(file.extension().equalsIgnoreCase(mapExtension)){ diff --git a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java index aa318bf072..f0fab14dea 100644 --- a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java @@ -60,9 +60,10 @@ public class MapGenerator extends Generator{ @Override public void init(Loadout loadout){ this.loadout = loadout; - map = world.maps.loadInternalMap(mapName); - width = map.width; - height = map.height; + //TODO uncomment once conversion works + //map = world.maps.loadInternalMap(mapName); + //width = map.width; + //height = map.height; } @Override From 8127e5a66f08544f119d1610627b42b5d1e24e85 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 8 May 2019 23:16:08 -0400 Subject: [PATCH 10/33] Made save format actually functional, many things still broken --- .../SerializeAnnotationProcessor.java | 52 +++++++++++++++++-- core/src/io/anuke/mindustry/core/Logic.java | 7 +-- core/src/io/anuke/mindustry/core/World.java | 2 + core/src/io/anuke/mindustry/game/Stats.java | 2 +- core/src/io/anuke/mindustry/io/JsonIO.java | 24 +++++++++ .../io/anuke/mindustry/io/JsonTypeReader.java | 7 +++ .../io/anuke/mindustry/io/JsonTypeWriter.java | 7 +++ .../io/anuke/mindustry/io/LegacyMapIO.java | 10 +++- .../io/anuke/mindustry/io/SaveFileReader.java | 12 +++-- core/src/io/anuke/mindustry/io/SaveIO.java | 7 +-- .../io/anuke/mindustry/io/SaveVersion.java | 36 +++++++------ core/src/io/anuke/mindustry/maps/Map.java | 21 +------- core/src/io/anuke/mindustry/maps/Maps.java | 4 +- .../maps/generators/MapGenerator.java | 7 ++- core/src/io/anuke/mindustry/type/Zone.java | 7 +++ .../io/anuke/mindustry/world/CachedTile.java | 1 + 16 files changed, 146 insertions(+), 60 deletions(-) create mode 100644 core/src/io/anuke/mindustry/io/JsonIO.java create mode 100644 core/src/io/anuke/mindustry/io/JsonTypeReader.java create mode 100644 core/src/io/anuke/mindustry/io/JsonTypeWriter.java diff --git a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java index 43f366c3f9..3b38b842ee 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,6 +42,28 @@ 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); + + + classBuilder.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.bestGuess("io.anuke.arc.collection.ObjectMap"), + ClassName.get(Class.class), + ClassName.bestGuess("io.anuke.mindustry.io.JsonTypeWriter")), + "writers", Modifier.PRIVATE, Modifier.STATIC).initializer("new io.anuke.arc.collection.ObjectMap<>()").build()); + + classBuilder.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.bestGuess("io.anuke.arc.collection.ObjectMap"), + ClassName.get(Class.class), + ClassName.bestGuess("io.anuke.mindustry.io.JsonTypeReader")), + "readers", Modifier.PRIVATE, Modifier.STATIC).initializer("new io.anuke.arc.collection.ObjectMap<>()").build()); + + classBuilder.addMethod(MethodSpec.methodBuilder("setSerializer") + .addModifiers(Modifier.STATIC, Modifier.PUBLIC) + .addTypeVariable(TypeVariableName.get("T")) + .addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), TypeVariableName.get("T")), "type") + .addParameter(ParameterizedTypeName.get(ClassName.bestGuess("io.anuke.mindustry.io.JsonTypeWriter"), TypeVariableName.get("T")), "writer") + .addParameter(ParameterizedTypeName.get(ClassName.bestGuess("io.anuke.mindustry.io.JsonTypeReader"), TypeVariableName.get("T")), "reader") + .addStatement("writers.put(type, writer)") + .addStatement("readers.put(type, reader)").build()); + + MethodSpec.Builder method = MethodSpec.methodBuilder("init").addModifiers(Modifier.PUBLIC, Modifier.STATIC); TypeName jsonType = ClassName.bestGuess("io.anuke.arc.util.serialization.Json"); @@ -107,6 +127,32 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ }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)"); + + if(field.asType().toString().equalsIgnoreCase("java.lang.String")){ + jsonWriteMethod.addStatement("json.writeValue(\"" + name + "\", object." + name + ")"); + jsonReadMethod.addStatement("if(value.has(\"" + name + "\")) object." + name + "= value.getString(\"" + name + "\")"); + }else if(field.asType().toString().startsWith("io.anuke.arc.collection.Array")){ //oh boy here it begins + String genericType = field.asType().toString().substring(field.asType().toString().indexOf('<') + 1, field.asType().toString().indexOf('>')); + { + jsonWriteMethod.addStatement("json.writeArrayStart($S)", name) + .beginControlFlow("for(" + genericType + " item : object." + name + ")") + .addStatement("json.writeValue(item)") + .endControlFlow() + .addStatement("json.writeArrayEnd()"); + } + + { + //jsonWriteMethod.beginControlFlow("if(value.has($S))", name); + //jsonWriteMethod.addStatement("io.anuke.arc.util.serialization.JsonValue list = value.get($S)", name); + //jsonWriteMethod.endControlFlow(); + } + //jsonWriteMethod.addStatement("for( ") + }else{ + jsonWriteMethod.addStatement("if(object."+name+" != null) writers.getThrow("+typeName+".class, () -> new IllegalArgumentException(\"Class '" + + typeName + "' does not have a serializer!\")).write(bjson, object."+name+", \"" + name+"\")"); + jsonReadMethod.addStatement("if(value.has(\"" + name + "\")) object." + name + " = ("+typeName+")readers.getThrow("+typeName+".class, () -> new IllegalArgumentException(\"Class '" + + typeName + "' does not have a serializer!\")).read(value, \""+name+"\")"); + } } } diff --git a/core/src/io/anuke/mindustry/core/Logic.java b/core/src/io/anuke/mindustry/core/Logic.java index a81d3c2702..76f57f9ad9 100644 --- a/core/src/io/anuke/mindustry/core/Logic.java +++ b/core/src/io/anuke/mindustry/core/Logic.java @@ -60,9 +60,10 @@ public class Logic implements ApplicationListener{ 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(); - } + //TODO ???? how does this even work now + //if(world.getMap().getWaves() != DefaultWaves.get() || !world.isZone()){ + // state.rules.spawns = world.getMap().getWaves(); + //} Events.fire(new PlayEvent()); } diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index c303fd4c75..30c1a1c1ef 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -35,7 +35,9 @@ public class World implements ApplicationListener{ private boolean generating, invalidMap; public World(){ + //TODO swap Core.app.post(maps::load); + //maps.load(); } @Override 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/io/JsonIO.java b/core/src/io/anuke/mindustry/io/JsonIO.java new file mode 100644 index 0000000000..ff5c736a13 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/JsonIO.java @@ -0,0 +1,24 @@ +package io.anuke.mindustry.io; + +import io.anuke.arc.util.serialization.Json; +import io.anuke.mindustry.game.Rules; +import io.anuke.mindustry.game.SpawnGroup; + +public class JsonIO{ + private static Json json = new Json(){{ + setIgnoreUnknownFields(true); + setElementType(Rules.class, "spawns", SpawnGroup.class); + }}; + + 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/JsonTypeReader.java b/core/src/io/anuke/mindustry/io/JsonTypeReader.java new file mode 100644 index 0000000000..5d91e1a896 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/JsonTypeReader.java @@ -0,0 +1,7 @@ +package io.anuke.mindustry.io; + +import io.anuke.arc.util.serialization.JsonValue; + +public interface JsonTypeReader{ + T read(JsonValue json, String name); +} diff --git a/core/src/io/anuke/mindustry/io/JsonTypeWriter.java b/core/src/io/anuke/mindustry/io/JsonTypeWriter.java new file mode 100644 index 0000000000..116a9a7857 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/JsonTypeWriter.java @@ -0,0 +1,7 @@ +package io.anuke.mindustry.io; + +import io.anuke.arc.util.serialization.Json; + +public interface JsonTypeWriter{ + void write(Json json, T object, String name); +} diff --git a/core/src/io/anuke/mindustry/io/LegacyMapIO.java b/core/src/io/anuke/mindustry/io/LegacyMapIO.java index d002a4d0f8..ec86df1773 100644 --- a/core/src/io/anuke/mindustry/io/LegacyMapIO.java +++ b/core/src/io/anuke/mindustry/io/LegacyMapIO.java @@ -6,7 +6,9 @@ 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; @@ -25,16 +27,22 @@ import static io.anuke.mindustry.Vars.*; * 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 Tile(x, y); + tiles[x][y] = new CachedTile(); } } + state.rules.spawns = groups; readTiles(map, tiles); MapIO.writeMap(out, map); } diff --git a/core/src/io/anuke/mindustry/io/SaveFileReader.java b/core/src/io/anuke/mindustry/io/SaveFileReader.java index e761a0d00e..a559430afd 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileReader.java +++ b/core/src/io/anuke/mindustry/io/SaveFileReader.java @@ -11,6 +11,8 @@ 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{ @@ -21,6 +23,7 @@ public abstract class SaveFileReader{ }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)); } @@ -40,11 +43,12 @@ public abstract class SaveFileReader{ /** 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 - byteOutput.reset(); + dout.reset(); //write the needed info - runner.accept(dataBytes); - int length = byteOutput.size(); + 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); @@ -54,7 +58,7 @@ public abstract class SaveFileReader{ } output.writeShort(length); } - output.write(byteOutput.getBytes(), 0, length); + output.write(dout.getBytes(), 0, length); } public int readChunk(DataInput input, IORunner runner) throws IOException{ diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 628217cc98..05914d4299 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -90,6 +90,7 @@ public class SaveIO{ public static SaveMeta getMeta(DataInputStream stream){ try{ + readHeader(stream); int version = stream.readInt(); SaveMeta meta = versions.get(version).getMeta(stream); stream.close(); @@ -116,10 +117,7 @@ public class SaveIO{ } public static void write(OutputStream os, StringMap tags){ - DataOutputStream stream; - - try{ - stream = new DataOutputStream(os); + try(DataOutputStream stream = new DataOutputStream(os)){ stream.write(header); stream.writeInt(getVersion().version); if(tags == null){ @@ -127,7 +125,6 @@ public class SaveIO{ }else{ getVersion().write(stream, tags); } - stream.close(); }catch(Exception e){ throw new RuntimeException(e); } diff --git a/core/src/io/anuke/mindustry/io/SaveVersion.java b/core/src/io/anuke/mindustry/io/SaveVersion.java index 5d513971a0..7026bdd549 100644 --- a/core/src/io/anuke/mindustry/io/SaveVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveVersion.java @@ -8,7 +8,6 @@ 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; @@ -27,7 +26,7 @@ public abstract class SaveVersion extends SaveFileReader{ 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"), Serialization.readRulesStringJson(map.get("rules", "{}"))); + 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", "{}"))); } public final void write(DataOutputStream stream) throws IOException{ @@ -56,8 +55,8 @@ public abstract class SaveVersion extends SaveFileReader{ "mapname", world.getMap() == null ? "unknown" : world.getMap().name(), "wave", state.wave, "wavetime", state.wavetime, - "stats", Serialization.writeStatsJson(state.stats), - "rules", Serialization.writeRulesJson(state.rules), + "stats", JsonIO.write(state.stats), + "rules", JsonIO.write(state.rules), "width", world.width(), "height", world.height() ).merge(tags)); @@ -68,11 +67,12 @@ public abstract class SaveVersion extends SaveFileReader{ state.wave = map.getInt("wave"); state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing); - state.stats = Serialization.readStatsStringJson(map.get("stats", "{}")); - state.rules = Serialization.readRulesStringJson(map.get("rules", "{}")); + state.stats = JsonIO.read(Stats.class, map.get("stats", "{}")); + state.rules = JsonIO.read(Rules.class, map.get("rules", "{}")); } public void writeMap(DataOutput stream) throws IOException{ + //TODO something here messes up everything //write world size stream.writeShort(world.width()); stream.writeShort(world.height()); @@ -106,7 +106,7 @@ public abstract class SaveVersion extends SaveFileReader{ if(tile.entity != null){ writeChunk(stream, true, out -> { out.writeByte(tile.entity.version()); - tile.entity.write(stream); + tile.entity.write(out); }); }else{ //write consecutive non-entity blocks @@ -129,10 +129,12 @@ public abstract class SaveVersion extends SaveFileReader{ } public void readMap(DataInput stream) throws IOException{ - short width = stream.readShort(); - short height = stream.readShort(); + int width = stream.readUnsignedShort(); + int height = stream.readUnsignedShort(); - world.beginMapLoad(); + boolean generating = world.isGenerating(); + + if(!generating) world.beginMapLoad(); Tile[][] tiles = world.createTiles(width, height); @@ -163,7 +165,7 @@ public abstract class SaveVersion extends SaveFileReader{ if(tile.entity != null){ readChunk(stream, true, in -> { byte version = in.readByte(); - tile.entity.read(stream, version); + tile.entity.read(in, version); }); }else{ int consecutives = stream.readUnsignedByte(); @@ -178,7 +180,7 @@ public abstract class SaveVersion extends SaveFileReader{ } content.setTemporaryMapper(null); - world.endMapLoad(); + if(!generating) world.endMapLoad(); } public void writeEntities(DataOutput stream) throws IOException{ @@ -200,8 +202,8 @@ public abstract class SaveVersion extends SaveFileReader{ SaveTrait save = (SaveTrait)entity; //each entity is a separate chunk. writeChunk(stream, true, out -> { - stream.writeByte(save.getTypeID()); - stream.writeByte(save.version()); + out.writeByte(save.getTypeID()); + out.writeByte(save.version()); save.writeSave(out); }); } @@ -217,10 +219,10 @@ public abstract class SaveVersion extends SaveFileReader{ for(int j = 0; j < amount; j++){ //TODO throw exception on read fail readChunk(stream, true, in -> { - byte typeid = stream.readByte(); - byte version = stream.readByte(); + byte typeid = in.readByte(); + byte version = in.readByte(); SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get(); - trait.readSave(stream, version); + trait.readSave(in, version); }); } } diff --git a/core/src/io/anuke/mindustry/maps/Map.java b/core/src/io/anuke/mindustry/maps/Map.java index 6765528ec0..920adc89ca 100644 --- a/core/src/io/anuke/mindustry/maps/Map.java +++ b/core/src/io/anuke/mindustry/maps/Map.java @@ -1,15 +1,10 @@ package io.anuke.mindustry.maps; import io.anuke.arc.Core; -import io.anuke.arc.collection.*; +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 static io.anuke.mindustry.Vars.world; public class Map implements Comparable{ /** Whether this is a custom map. */ @@ -49,20 +44,6 @@ public class Map implements Comparable{ this(Vars.customMapDirectory.child(tags.get("name", "unknown")), 0, 0, tags, true); } - 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 int getHightScore(){ return Core.settings.getInt("hiscore" + file.nameWithoutExtension(), 0); } diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index c5b03e6d2c..587182c871 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -8,8 +8,7 @@ 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.LegacyMapIO; -import io.anuke.mindustry.io.MapIO; +import io.anuke.mindustry.io.*; import java.io.IOException; import java.io.StringWriter; @@ -61,6 +60,7 @@ public class Maps implements Disposable{ public void load(){ try{ //TODO remove, this is only for testing + for(FileHandle in : Core.files.absolute("/home/anuke/Projects/Mindustry/core/assets/maps").list()){ if(in.extension().equalsIgnoreCase(oldMapExtension)){ Log.info("Converting {0}...", in); diff --git a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java index f0fab14dea..aa318bf072 100644 --- a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java @@ -60,10 +60,9 @@ public class MapGenerator extends Generator{ @Override public void init(Loadout loadout){ this.loadout = loadout; - //TODO uncomment once conversion works - //map = world.maps.loadInternalMap(mapName); - //width = map.width; - //height = map.height; + map = world.maps.loadInternalMap(mapName); + width = map.width; + height = map.height; } @Override diff --git a/core/src/io/anuke/mindustry/type/Zone.java b/core/src/io/anuke/mindustry/type/Zone.java index 74ec3d9183..9951c1939b 100644 --- a/core/src/io/anuke/mindustry/type/Zone.java +++ b/core/src/io/anuke/mindustry/type/Zone.java @@ -13,6 +13,7 @@ import io.anuke.mindustry.game.EventType.ZoneConfigureCompleteEvent; import io.anuke.mindustry.game.EventType.ZoneRequireCompleteEvent; import io.anuke.mindustry.game.Rules; import io.anuke.mindustry.game.UnlockableContent; +import io.anuke.mindustry.gen.Serialization; import io.anuke.mindustry.maps.generators.Generator; import io.anuke.mindustry.world.Block; @@ -38,6 +39,12 @@ public class Zone extends UnlockableContent{ private Array defaultStartingItems = new Array<>(); + static{ + Serialization.setSerializer(Zone.class, + (json, zone, name) -> json.writeValue(name, zone.name), + (json, name) -> content.getByName(ContentType.zone, json.getString(name))); + } + public Zone(String name, Generator generator){ super(name); this.generator = generator; 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(); From 486e3ffc0ad1941443878c214ceedf6e3121064e Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 9 May 2019 10:53:52 -0400 Subject: [PATCH 11/33] Actual map conversion, loading of saves --- .../SerializeAnnotationProcessor.java | 117 ------------------ core/assets/maps/craters.mmap | Bin 13265 -> 0 bytes core/assets/maps/craters.msav | Bin 0 -> 11559 bytes core/assets/maps/desolateRift.mmap | Bin 9246 -> 0 bytes core/assets/maps/desolateRift.msav | Bin 0 -> 8026 bytes core/assets/maps/fortress.mmap | Bin 11720 -> 0 bytes core/assets/maps/fortress.msav | Bin 0 -> 9427 bytes core/assets/maps/frozenForest.mmap | Bin 8654 -> 0 bytes core/assets/maps/frozenForest.msav | Bin 0 -> 6864 bytes core/assets/maps/groundZero.mmap | Bin 11860 -> 0 bytes core/assets/maps/groundZero.msav | Bin 0 -> 9747 bytes core/assets/maps/map_9.mmap | Bin 10518 -> 0 bytes core/assets/maps/map_9.msav | Bin 0 -> 9897 bytes .../assets/maps/nuclearProductionComplex.mmap | Bin 14701 -> 0 bytes .../assets/maps/nuclearProductionComplex.msav | Bin 0 -> 12404 bytes core/assets/maps/overgrowth.mmap | Bin 37614 -> 0 bytes core/assets/maps/overgrowth.msav | Bin 0 -> 33525 bytes core/assets/maps/ruinousShores.mmap | Bin 18596 -> 0 bytes core/assets/maps/ruinousShores.msav | Bin 0 -> 15465 bytes core/assets/maps/stainedMountains.mmap | Bin 20823 -> 0 bytes core/assets/maps/stainedMountains.msav | Bin 0 -> 16172 bytes core/assets/maps/tarFields.mmap | Bin 16893 -> 0 bytes core/assets/maps/tarFields.msav | Bin 0 -> 14189 bytes core/src/io/anuke/mindustry/core/Control.java | 2 +- core/src/io/anuke/mindustry/core/World.java | 9 +- core/src/io/anuke/mindustry/io/JsonIO.java | 16 +++ .../io/anuke/mindustry/io/JsonTypeReader.java | 7 -- .../io/anuke/mindustry/io/JsonTypeWriter.java | 7 -- core/src/io/anuke/mindustry/maps/Maps.java | 58 +++++---- core/src/io/anuke/mindustry/type/Zone.java | 7 -- 30 files changed, 51 insertions(+), 172 deletions(-) delete mode 100644 core/assets/maps/craters.mmap create mode 100644 core/assets/maps/craters.msav delete mode 100644 core/assets/maps/desolateRift.mmap create mode 100644 core/assets/maps/desolateRift.msav delete mode 100644 core/assets/maps/fortress.mmap create mode 100644 core/assets/maps/fortress.msav delete mode 100644 core/assets/maps/frozenForest.mmap create mode 100644 core/assets/maps/frozenForest.msav delete mode 100644 core/assets/maps/groundZero.mmap create mode 100644 core/assets/maps/groundZero.msav delete mode 100644 core/assets/maps/map_9.mmap create mode 100644 core/assets/maps/map_9.msav delete mode 100644 core/assets/maps/nuclearProductionComplex.mmap create mode 100644 core/assets/maps/nuclearProductionComplex.msav delete mode 100644 core/assets/maps/overgrowth.mmap create mode 100644 core/assets/maps/overgrowth.msav delete mode 100644 core/assets/maps/ruinousShores.mmap create mode 100644 core/assets/maps/ruinousShores.msav delete mode 100644 core/assets/maps/stainedMountains.mmap create mode 100644 core/assets/maps/stainedMountains.msav delete mode 100644 core/assets/maps/tarFields.mmap create mode 100644 core/assets/maps/tarFields.msav delete mode 100644 core/src/io/anuke/mindustry/io/JsonTypeReader.java delete mode 100644 core/src/io/anuke/mindustry/io/JsonTypeWriter.java diff --git a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java index 3b38b842ee..1fba8bc972 100644 --- a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java +++ b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java @@ -44,36 +44,8 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ classBuilder.addJavadoc(RemoteMethodAnnotationProcessor.autogenWarning); - classBuilder.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.bestGuess("io.anuke.arc.collection.ObjectMap"), - ClassName.get(Class.class), - ClassName.bestGuess("io.anuke.mindustry.io.JsonTypeWriter")), - "writers", Modifier.PRIVATE, Modifier.STATIC).initializer("new io.anuke.arc.collection.ObjectMap<>()").build()); - - classBuilder.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.bestGuess("io.anuke.arc.collection.ObjectMap"), - ClassName.get(Class.class), - ClassName.bestGuess("io.anuke.mindustry.io.JsonTypeReader")), - "readers", Modifier.PRIVATE, Modifier.STATIC).initializer("new io.anuke.arc.collection.ObjectMap<>()").build()); - - classBuilder.addMethod(MethodSpec.methodBuilder("setSerializer") - .addModifiers(Modifier.STATIC, Modifier.PUBLIC) - .addTypeVariable(TypeVariableName.get("T")) - .addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), TypeVariableName.get("T")), "type") - .addParameter(ParameterizedTypeName.get(ClassName.bestGuess("io.anuke.mindustry.io.JsonTypeWriter"), TypeVariableName.get("T")), "writer") - .addParameter(ParameterizedTypeName.get(ClassName.bestGuess("io.anuke.mindustry.io.JsonTypeReader"), TypeVariableName.get("T")), "reader") - .addStatement("writers.put(type, writer)") - .addStatement("readers.put(type, reader)").build()); - - 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"); - - classBuilder.addField(jsonType, "bjson", Modifier.STATIC, Modifier.PRIVATE); - classBuilder.addStaticBlock(CodeBlock.builder() - .addStatement("bjson = new " + jsonType + "()") - .build()); - for(TypeElement elem : elements){ TypeName type = TypeName.get(elem.asType()); String simpleTypeName = type.toString().substring(type.toString().lastIndexOf('.') + 1); @@ -95,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){ @@ -121,43 +81,13 @@ 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)"); - - if(field.asType().toString().equalsIgnoreCase("java.lang.String")){ - jsonWriteMethod.addStatement("json.writeValue(\"" + name + "\", object." + name + ")"); - jsonReadMethod.addStatement("if(value.has(\"" + name + "\")) object." + name + "= value.getString(\"" + name + "\")"); - }else if(field.asType().toString().startsWith("io.anuke.arc.collection.Array")){ //oh boy here it begins - String genericType = field.asType().toString().substring(field.asType().toString().indexOf('<') + 1, field.asType().toString().indexOf('>')); - { - jsonWriteMethod.addStatement("json.writeArrayStart($S)", name) - .beginControlFlow("for(" + genericType + " item : object." + name + ")") - .addStatement("json.writeValue(item)") - .endControlFlow() - .addStatement("json.writeArrayEnd()"); - } - - { - //jsonWriteMethod.beginControlFlow("if(value.has($S))", name); - //jsonWriteMethod.addStatement("io.anuke.arc.util.serialization.JsonValue list = value.get($S)", name); - //jsonWriteMethod.endControlFlow(); - } - //jsonWriteMethod.addStatement("for( ") - }else{ - jsonWriteMethod.addStatement("if(object."+name+" != null) writers.getThrow("+typeName+".class, () -> new IllegalArgumentException(\"Class '" - + typeName + "' does not have a serializer!\")).write(bjson, object."+name+", \"" + name+"\")"); - jsonReadMethod.addStatement("if(value.has(\"" + name + "\")) object." + name + " = ("+typeName+")readers.getThrow("+typeName+".class, () -> new IllegalArgumentException(\"Class '" - + typeName + "' does not have a serializer!\")).read(value, \""+name+"\")"); - } } } readMethod.addStatement("return object"); - jsonReadMethod.addStatement("return object"); serializer.addMethod(writeMethod.build()); serializer.addMethod(readMethod.build()); @@ -172,53 +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 binaryJsonWriteStringMethod = MethodSpec.methodBuilder("write" + simpleTypeName + "Json") - .returns(String.class) - .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("return 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()))"); - - MethodSpec.Builder binaryJsonReadStringMethod = MethodSpec.methodBuilder("read" + simpleTypeName + "StringJson") - .returns(type) - .addParameter(String.class, "str") - .addException(IOException.class) - .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addStatement("return read" + simpleTypeName + "Json(bjson.fromJson(null, str))"); - - classBuilder.addMethod(binaryJsonWriteMethod.build()); - classBuilder.addMethod(binaryJsonWriteStringMethod.build()); - classBuilder.addMethod(binaryJsonReadMethod.build()); - classBuilder.addMethod(binaryJsonReadStringMethod.build()); } classBuilder.addMethod(method.build()); diff --git a/core/assets/maps/craters.mmap b/core/assets/maps/craters.mmap deleted file mode 100644 index 6134ce272a05db88b7d425cc3e188040fdd341d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13265 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 Z=GTAe3m@tiuuT4~{{PK?X7MnEYyhwZj^qFU diff --git a/core/assets/maps/craters.msav b/core/assets/maps/craters.msav new file mode 100644 index 0000000000000000000000000000000000000000..91066fd661a8e6e6e35cc1d9fa1ae7a32212800c GIT binary patch literal 11559 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 sz91s{Puk-{x?Rl;(evjI&iQXW(^b literal 0 HcmV?d00001 diff --git a/core/assets/maps/desolateRift.mmap b/core/assets/maps/desolateRift.mmap deleted file mode 100644 index eebcb97310b34a1b09c12feddec37935839fe62a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9246 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 diff --git a/core/assets/maps/desolateRift.msav b/core/assets/maps/desolateRift.msav new file mode 100644 index 0000000000000000000000000000000000000000..67a05282366771b2274b8cde041665d116e781ca GIT binary patch literal 8026 zcmb=J^ENs&f6mFmcjfoW-*taq*t}##^c9asJWHoO+4^n5-i_CH%L}n{PEd$EU(oiX zqFttluQ&DZeDOx}gG`sYerpRoVQ1l(a9{XXj|Wtg)qHsx zSYNc{(%Qo9*TYuc+7?!}-PS7Lxl^%5*IeTJ7SXz2)_X5L$@uZ- z|Jsk!qaT0QQk)-lahq1w^82zS{qA@FP5f3G`d4T1m7Y^?zV-K8Gf(|ub#~h+`7*b& zf8&ZRLU#Uj{QP5q^{W*rH*B(2r)(3;$J@2M9i1JbHs{K%ezRdU>*YP% zR$A?8fiHh7uzYjn+pM*Y<>8|3j}O)@zxL+qul1pI6?f!IvX1}VwrWeqqHVYBZLZDA ze7&R2Hse*VRp6EdkJ8E)RGP6b*3Yv3^Xx?^dDio8_dVbC*fS{l#KTuirYxIu zUiVesEnIK?uJrg`y*sbtEn;F9v^h*mJzSV?@s9WS-iJHPTdE&(x3A{DB5-2Uox(fi zGT&v7*B&ptFZj+Hs@mFWn&$IWJeR&~`?)>uU+#?h@-zGU&+hj>v;Y0f`uVf!=dY~U zec5u}3n%LZx4iyOaXofCdBzj+X5aZGk5}rX-(2~}asTynyUe+7O3q!@u2TLR zeqBqnyxe-$!@T8fiD}!f-@PsU>eZ4+-mMK&_Dc92uysEzammGI@^%Z}ZO7f4uI}c( zA^lS(`*^IbU(^1X4-Y2Xky`btx=rXC2wEu&n#M?cv|qx(H;do zKg+lUE4^7f1+|k>xFU~+?YZ@F0rTFT>c#6MLwqY&^YUBUUMl&Ny1jgfSnNjUzl*j; zbuWA8Yqje~)^hiw!lzEXdr(@x-u&G*{@wd8K31A;FBg*Y*7$Lc;wgS>E3etRc3()H zV#``2+IQS`ulx7yRlZaGB5uEx>1DgS_hX&A=x+C2mM;tknom%LMX-zKG9l__w}vzN_Qq>uML){aE!_adma=T2uao%|Yx-wyo}aJEv@E z_}uGK*4pgCA@8m~UL5-+GxV za#NYvy}9kj?BCo=Thae{>8e?cGD{!zB~{#{8&kFYR^9Pkf7jS{^-J+b-QtqVTDf*}b@~G5<1*B`fe3M#l35!nmsYQk{xY#A-DYd>yH_h;eGE*mS>arFU7^cL`o-nhjL+FN zi?D9E_VaAUfjeBgr!0)yxa4o1{tTyYmqR!DRyrQ8z7lPo*exXB{`hWO`Wo+BtCyuM z{&Y(uq*b%MufOWAT4mM3>sQ!pF0|*YmSms(_}JURc=La|4Vu5Xo-O-$ArCoMbO!MtM`@NdiSxgf7OrIcjcx&Ov$&oa`@p!z8Q^tLZ@nT zcbSK^9lz^XzC>TBWXJWW%&5-A{}w7})g(?Xto-kA-CFY8joXWrGt9q#-)hvlc&DNj zUx)W`NB7SiM(Yy1nCDL1ed}NC-JL5otqZVfFYj5Wm8)BIxz~H~vm3MD%9nlHdvj)d zcI?a3v$utRpJ8a#ar>6E?6OtsQ>P^8C@n2ob>U5>YeWCJ;7yCqZAvqbl}uH?+ccrW zWxdzGwkV5yU;EVUACtmz>n05Hly#@bopS!4j>t(yu<$$m&F2Z~LD-GVseLa3SUhcx2 zb&MPh-=3E^UZ0S6p~UaclG`u0wx`GSYIV$e_F$>ruf6B;r~GZqEyCbCgX}%jPbaa>nV`o)b-zm)ow4dgU9XaV;f`GuJb1 zV_#c-5zEm{(MlqzO=6L|t7LP3wjHUMEUG81$hxs=`XpgpL%+#KeXgmx&ZxbA^iXQ2 z>egxorDgdCo>qA@-qpL96Zu0l<=V^CTf1s@=n1|k+Hvk>NUKQ7F>}{vIsP-n|6cqc zJ8^Gtk(trs*?R&Q!UZ@)x-{=hi~Uyi!r|;n+vd|98GVjk8gr~V-{>x2yXbakr$ay> z%kht!&$tF3k+EQO?OY(Kf8fooSsqI{iZ+QHW$;qzJ#66HSrhHF+NtiHi)!KRAdQ2u z(@Nq`eB;e65uCp1#Pc0@zU_$Nh&|AHW52JHp5go5@8zGd8|+Zsy_CbN_4yA+o;56< z8B-#ZB2UbCq^8^HnzsDpmVl5PpZ&c~n>7;urXm-VBF{z1 ze;1iuvpzFj#e1cuTW4$8EZYg2ryrGdoH+f$fj9mUOwx75Iw|3`(l_r)>^i^uW{tt| zE{px|=B;tg&?)F(f9=DxL9FHT`P2I}3+pBu>4XT?-MGHf)hlPG#JPI{xd&2~a728# ztup25tT%@4ucx(mw=FQ8IagfILXan3yvx<{Y2S%Y+%y0BEctKKvfmi?i%#TXRaViEX2~$m4eutcDV}Ms+|HP%x+Frd@N?o{k4~Z3XUnrn zXLZav;r9O0qcpv(J08_rE>Ym#*X>#r&TQtoN@;gS_adbO3)e1?duK9j)^op3v!#0H zCJ5@iFBaIo!rx^=#tTiyXvHTfE8|;McfM1ecK)1`*p}(#uB##nq@5T&;{~=H%Up8w zRNd@J3O{EE^%p0cIhLLt{fFJ>zFKgaaZcur^65rfk3Cf>kP<3M^(&s1)YyDkZOW<( z%Tm83&X^`-H~Z*8{xH?%D=k;I98DFx^mO&_NsbPJr>$fRv|qpTkn`f)@9|B8^QduT z_|!E{Dhd)f)Fdr&?Z08VMU;GkkpE*@nP8uD7#to@CWTX?f~= zXHai^9K@%~tf9>~yTW|cw4EL+GMAs|CJNz{Q5Dt<*M*Z-*zkC&X5W3Th9cYwPr2x&yarfef#n)=R(;1g%@^M zSgf7(jw{{j(C&`V^sR#3;_=*5U&t-*`4U?t{qj@xm6e6Yug^JNm5rI z{YZ@ppAlPJ^Q=-`?3`v{>hYhu))=o`n!4p-Rh!d^qmrxCL^hTD0F9>-4JLbO5xxL0mWElT@?O6XbC-&>yMGIvf?(Ud2quqQq!>2=Al)lA2D*dnK z#Lh(`?fbI( z)nxyy{%`c~-q#otJx&+T(DJ5lGR1N0YZzHeGw(1iS=w3V9ki{u`tiBy*e^nz;>Wke zKQ5cf_xYM-)v4?sYt$0mCcXEK(g=F=!fT%W)yTuQ^*N@uC{=Hp!mc~nTRFiY*U0|J zDGO-<&f2=EdBjaXcqu{7UY>)VsYUQ`%WwJ}C5qZlWiPQ~BhFI;p+S*YSH7#g(LF zMov#Up|--+(ADin?x{DEuFTvbBQ;l0ref|j$#cuDKKQoy!0R`a-G?fD@BK?(`+DxI z<*{E>uLVfH_I_}K>+DpYL@&(uS?w+-``DyB1+sNsH86TCp zTr8Hqd9(f4(wHtA|A*fSL{4vNU#!s)68-9ra)QzY(S;g*HFcF2J%XwK7PmZoer{pP4i%w00#V^s3CE5+PMUHeYnO6>*_3;&^Mx-( zzFwLW!mBND zV0~zNP;uwPc|UViRF6fA&sjYuJ>&eE;`m;(lvtTZ^IFQMGihI&_kC}7+Ved(7nmi@ z-K`YvtRI)ed9Hp-t5VS^wF^S}W#ZPa*-ytAXKyYKo3v$d>=ctVNA7S3@3K_=({)EX z`l(2(w@6XsBxmeIp^AHYxkVS$Z7l|E^AWtn{*R{p)qr1;?4*{;AZ9b60!yWyg<~rZ3igoIQ7s zL5I`ad6o5@dkXG-KP{{J!<_YI%s%e9ySM|x6#h@DyytrUL-Yrg4V-dsom$@t{#co5 z@;1L!Y|U5yyV2`@KM?ff7jNBSY3Uwyvw!*KHE$N^7N<<%`jLD@a+78MnUuZeFAHRw zGUpvWzf#3Q@Wi9{s?61YytF1xPIu|4wA`{QBUb6rF~$8;-gK9;cK+qQ{?MG|gh}c& zeFLMvVjp|6vp*KM%-Md!=&yr}=RS`)yI#-Pl`%J4I=<`pEdP&XkJc{CgOQ zJ8#eZz4XS+$T#0SUcH(WU~(k0q1fKhvbp-q!OZ*SMGxNy>+$LSd#H5&pRPK?Z(;A| z*G1nR?$#`Lx7_l3O5wb_$EE(wW0`#V#rBpr`_8B(*?eEUdX`JWZV!uF>_Yh>H{So@ z&YEujV!xsO({KO6Cfz*Wx~NF-+!pz=GW8u=dyKF1T%Q-+b$rvgyKA*#uI#RBZsZqP zr|W#LR&VpW-raAW-klb|_|Dsx?us*iCr*3m5dClaWBKaz;;7B0|Cry$9<9qPu=n`m zV0MW2`rhk`r}uwYmswr+>;Hk^xo;~By611+tDoXJ)7NuWwcNbxwt>Iny>pwtotb=Z z(9Onxqt3d?bN9}RsOs-x%RWJ+J-}WB5v*1csE;I>$>^b?&otNpUds~{LpIi z@qO{>ci!{eJ^%Q_?a&!Heg%@83#Ah`_U-vx=Ib_p`i7?NIPrtwu5%~uI2t2F*&U!QVvM*iKYhRSoy4@G{DmpsFj=lR9y@b-7h-#+_zO{{b~-}~jCl$UQ`r?|AD zx#-@k{LKe-{+bA`nQ?#FnP;xcZ)(0?QC+KjfBF4g;k;d4`@`m}(`{XOM5^fj)#FPG zrrA8o4c)TwoZ0mqo94)DtI=4lQ)$N46j9f5;M2~0uML4wX?OVz?)s<5q|_xOMn{w@ zmfK&eSGq8B{;D-aXCm|_)VXSGYKj(}^K<5`*zXY;vg;IIn(FRWHQKE^Y1e(W9p{7J z6`y6+T=GtMB&f2upFHNT=dalNS&1Fw@4@h#qo*KDk4=110xfPyW*D^lO%~)xs z;C$-bvbSs6g0nXD?blT9RGazaO7p!cuTe(hS*0diYJ#t(A3rtwJa&prw zk=}0{^AnVNueYyan%wqbPtd~*);Uqj#IF2*HFwhaG!OY-EvlxPg6^AqlR8Z|`5a+% zcRY4%(XS8o9FmNYcI`D>f~NZ(Dc*06Hej2VXb_r^)IHOX-K;(3iOvN1$Oq1)M(GSu z$3C#XN?|;}t+2H;eZ^Jv86Um=?!MOgOIh=U`vHBX|1y1Bd2Y%bE}YIW?P%YszR0~r z({}uAKX&}T?+^BZR9{p7lXDWzR(3kgj&XdvK0w`t?ee5M3*NQ#>~(Nlx(n45!GnbuBblc9=H*ERkqgeK}C|q&e>QFnCbEiy>3A?58 zn#40YOXIp2C9eslRa)mva1T5(ZQ+yY4`NsYRMP|OSpycCbA7rl(HG~UQlO%KV^PQ5 z%LIXhU@7 z>ZZg^xr>WVt~fU_-R8%(?aKrnC0D!`+WD?NbJh##6O)7LmmF-AtT6U5YF~U*Zgu+y zrMY@xPJbi~FNGMWt=@7>R!df9LePe9PrO^ox117A>e}?K!DsiI)Gqd>?vBqkes$au zZ)?I6(?qOf7yWmBQr@wrfwgi~#SKRIx z)TA%vh^5CL%{h7adBKO%GjtC%OV&$2?>eB~{2=8I?~ZV$2*);&bGtb$lTRGjESR?5 zQQfd)#<5Cs8=mZhyW6xXSF;~m@@AIyg+DeGMrpkBwy{;neh8gY$eg~jtJtuHwQN7z z-8i;Aoi;lhu8T{>xgYGyy?=yPD*L_Ak2?zSswoFoDY>UDs+x8*Qt2{_Ws2zWz<`ws zJHOp=T6xYXu=}1tzl`Ex9iNkjWoq)T@>wb>ZN97-^h&yD* zDZOE$^YDt%nMD>S4U)C*%ku0za_&ZA(r+QTWd#SkEPNE1mlRCuW@oauCBY8bBsbzD&=L82c-!@gt1 zN3S-K;&5DaRy~E2u}-d1_8+tT^l9_AL>-E=32o!7c{0Z|xNlDH95X}RiB9L;#ip<1 znNS=tLHYB^&&a7)7jqn!R_erxWbWun?BP1zT5~KW!$-x)$jK(` zVw9qv@21$cIz45vKYKqd3td#VqN`vBlcjfqcE`TZ=o34{bQ+`iW^nDvmOQS*5WMYb zx#E#EPQ9uLUSiC@XPK({uj1(J&}ZA@t$HLRH}{#?eo@5aqX#W@4^*1S|F!?M`uc*q zS2p@IEnOfGchWML`(}gFk4@5#75obh7(L**^wL(M>FUv+DZ1GV$GMY>bA9rO>q{iW zdqQ`IpW-Xt(XyuL73-yy{C^}Or#ET`T;%2oT2v*kZ)5jOMBb2%9u_lu6pA1B`1<2N&r@nflY0q4Y0aUZQ0TWfyE zZCl55ujyObhPu=DQp{eQwsd{u{`_BEPh^|@kv{oHS>K!HUHIYUxv%iHREYd%zP_34 z^=;idvhN(7CA8{f?u@N0|99pxzf$X|yKMGtyTFV~bMLjiH9INRvcK*5B#uXWU5tNn z&N@6_aZ7!l%D0t!na^L~clx=sH|dY@l8#Fz4}x19IxRi~3*P&sku~OPdXK|kn<4&e$75+<)udzBO$GAVa`#RTJ`_A6_ zO9j$>6K|-e)V-U#JUAfl%xrb1RWZ@q(!;K~CMRq=pw8;kbZHwa;|%HKcPl5KUeKFx lQ*zDDtbD7;D+jB^gc*&U-4?Qc)p=Fav+n7ChN9Lt&Hx?G$B+O3 literal 0 HcmV?d00001 diff --git a/core/assets/maps/fortress.mmap b/core/assets/maps/fortress.mmap deleted file mode 100644 index 2b395107e586e8eb9661ab00e05dfc2d441cb956..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11720 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 diff --git a/core/assets/maps/fortress.msav b/core/assets/maps/fortress.msav new file mode 100644 index 0000000000000000000000000000000000000000..24caf622f66d3f7fced2490eda8d5d2209c446a2 GIT binary patch literal 9427 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 PhRTV3|M}0i%<=~SmiDS8 literal 0 HcmV?d00001 diff --git a/core/assets/maps/frozenForest.mmap b/core/assets/maps/frozenForest.mmap deleted file mode 100644 index 06354bf306c963e1b937cd05a238b142d1013402..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8654 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 literal 0 HcmV?d00001 diff --git a/core/assets/maps/groundZero.mmap b/core/assets/maps/groundZero.mmap deleted file mode 100644 index e4044fd6e4e96d9715c582d2be1b5e3ae588a380..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11860 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 KpP%K=fnWgCggf~F diff --git a/core/assets/maps/groundZero.msav b/core/assets/maps/groundZero.msav new file mode 100644 index 0000000000000000000000000000000000000000..dbecd187061f8f4374b8f03de39cb80926e1bffa GIT binary patch literal 9747 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 literal 0 HcmV?d00001 diff --git a/core/assets/maps/nuclearProductionComplex.mmap b/core/assets/maps/nuclearProductionComplex.mmap deleted file mode 100644 index 2668fec58abf2f37c4536f4c5a35f8ecf8651461..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14701 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πY5b-5X;w8tI1>&Jg(~sp(4?BKk&5|%< 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 Pzx7zr`;R})?7BMur-lk9 literal 0 HcmV?d00001 diff --git a/core/assets/maps/overgrowth.mmap b/core/assets/maps/overgrowth.mmap deleted file mode 100644 index 1bbb742e2f09eb793016db15f8bdf987d60af998..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37614 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