Merging of map saves
This commit is contained in:
@@ -4,12 +4,27 @@ import java.lang.annotation.*;
|
||||
|
||||
public class Annotations{
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
/** Indicates that a method should always call its super version. */
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface CallSuper{
|
||||
|
||||
}
|
||||
|
||||
/** Annotation that allows overriding CallSuper annotation. To be used on method that overrides method with CallSuper annotation from parent class.*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OverrideCallSuper {
|
||||
}
|
||||
|
||||
/** Indicates that a method return or field can be null.*/
|
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Nullable{
|
||||
|
||||
}
|
||||
|
||||
/** Indicates that a method return or field cannot be null.*/
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface NonNull{
|
||||
|
||||
@@ -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<? extends TypeElement> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Object, Trees> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||
@SupportedAnnotationTypes({
|
||||
"io.anuke.annotations.Annotations.Serialize"
|
||||
})
|
||||
@SupportedAnnotationTypes("io.anuke.annotations.Annotations.Serialize")
|
||||
public class SerializeAnnotationProcessor extends AbstractProcessor{
|
||||
/** Target class name. */
|
||||
private static final String className = "Serialization";
|
||||
@@ -44,20 +42,10 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{
|
||||
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC);
|
||||
classBuilder.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "\"unchecked\"").build());
|
||||
classBuilder.addJavadoc(RemoteMethodAnnotationProcessor.autogenWarning);
|
||||
|
||||
|
||||
MethodSpec.Builder method = MethodSpec.methodBuilder("init").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
|
||||
TypeName jsonType = ClassName.bestGuess("io.anuke.arc.util.serialization.Json");
|
||||
TypeName jsonValueType = ClassName.bestGuess("io.anuke.arc.util.serialization.JsonValue");
|
||||
TypeName ubJsonWriterType = ClassName.bestGuess("io.anuke.arc.util.serialization.UBJsonWriter");
|
||||
TypeName ubJsonReaderType = ClassName.bestGuess("io.anuke.arc.util.serialization.UBJsonReader");
|
||||
|
||||
classBuilder.addField(jsonType, "bjson", Modifier.STATIC, Modifier.PRIVATE);
|
||||
classBuilder.addField(ubJsonReaderType, "bjsonReader", Modifier.STATIC, Modifier.PRIVATE);
|
||||
classBuilder.addStaticBlock(CodeBlock.builder()
|
||||
.addStatement("bjson = new " + jsonType + "()")
|
||||
.addStatement("bjsonReader = new " + ubJsonReaderType + "()")
|
||||
.build());
|
||||
|
||||
for(TypeElement elem : elements){
|
||||
TypeName type = TypeName.get(elem.asType());
|
||||
String simpleTypeName = type.toString().substring(type.toString().lastIndexOf('.') + 1);
|
||||
@@ -79,19 +67,7 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{
|
||||
.addException(IOException.class)
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
|
||||
MethodSpec.Builder jsonWriteMethod = MethodSpec.methodBuilder("write" + simpleTypeName + "Json")
|
||||
.returns(void.class)
|
||||
.addParameter(jsonType, "json")
|
||||
.addParameter(type, "object")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
|
||||
MethodSpec.Builder jsonReadMethod = MethodSpec.methodBuilder("read" + simpleTypeName + "Json")
|
||||
.returns(type)
|
||||
.addParameter(jsonValueType, "value")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
|
||||
readMethod.addStatement("$L object = new $L()", type, type);
|
||||
jsonReadMethod.addStatement("$L object = new $L()", type, type);
|
||||
|
||||
List<VariableElement> fields = ElementFilter.fieldsIn(Utils.elementUtils.getAllMembers(elem));
|
||||
for(VariableElement field : fields){
|
||||
@@ -105,9 +81,6 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{
|
||||
if(field.asType().getKind().isPrimitive()){
|
||||
writeMethod.addStatement("stream.write" + capName + "(object." + name + ")");
|
||||
readMethod.addStatement("object." + name + "= stream.read" + capName + "()");
|
||||
|
||||
jsonWriteMethod.addStatement("json.writeValue(\"" + name + "\", object." + name + ")");
|
||||
jsonReadMethod.addStatement("if(value.has(\"" + name + "\")) object." + name + "= value.get" + capName + "(\"" + name + "\")");
|
||||
}else{
|
||||
writeMethod.addStatement("io.anuke.arc.Core.settings.getSerializer(" + typeName + ".class).write(stream, object." + name + ")");
|
||||
readMethod.addStatement("object." + name + " = (" + typeName + ")io.anuke.arc.Core.settings.getSerializer(" + typeName + ".class).read(stream)");
|
||||
@@ -115,7 +88,6 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{
|
||||
}
|
||||
|
||||
readMethod.addStatement("return object");
|
||||
jsonReadMethod.addStatement("return object");
|
||||
|
||||
serializer.addMethod(writeMethod.build());
|
||||
serializer.addMethod(readMethod.build());
|
||||
@@ -130,32 +102,6 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{
|
||||
|
||||
classBuilder.addMethod(writeMethod.build());
|
||||
classBuilder.addMethod(readMethod.build());
|
||||
|
||||
classBuilder.addMethod(jsonWriteMethod.build());
|
||||
classBuilder.addMethod(jsonReadMethod.build());
|
||||
|
||||
MethodSpec.Builder binaryJsonWriteMethod = MethodSpec.methodBuilder("write" + simpleTypeName + "StreamJson")
|
||||
.returns(void.class)
|
||||
.addParameter(DataOutput.class, "stream")
|
||||
.addParameter(type, "object")
|
||||
.addException(IOException.class)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addStatement("java.io.StringWriter output = new java.io.StringWriter()")
|
||||
.addStatement("bjson.setWriter(output)")
|
||||
.addStatement("bjson.writeObjectStart(" + type + ".class, " + type + ".class)")
|
||||
.addStatement("write" + simpleTypeName + "Json(bjson, object)")
|
||||
.addStatement("bjson.writeObjectEnd()")
|
||||
.addStatement("stream.writeUTF(output.toString())");
|
||||
|
||||
MethodSpec.Builder binaryJsonReadMethod = MethodSpec.methodBuilder("read" + simpleTypeName + "StreamJson")
|
||||
.returns(type)
|
||||
.addParameter(DataInput.class, "stream")
|
||||
.addException(IOException.class)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addStatement("return read" + simpleTypeName + "Json(bjson.fromJson(null, stream.readUTF()))");
|
||||
|
||||
classBuilder.addMethod(binaryJsonWriteMethod.build());
|
||||
classBuilder.addMethod(binaryJsonReadMethod.build());
|
||||
}
|
||||
|
||||
classBuilder.addMethod(method.build());
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
io.anuke.annotations.RemoteMethodAnnotationProcessor
|
||||
io.anuke.annotations.SerializeAnnotationProcessor
|
||||
io.anuke.annotations.StructAnnotationProcessor
|
||||
io.anuke.annotations.CallSuperAnnotationProcessor
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,6 +194,8 @@ editor.mapinfo = Map Info
|
||||
editor.author = Author:
|
||||
editor.description = Description:
|
||||
editor.waves = Waves:
|
||||
editor.rules = Rules:
|
||||
editor.ingame = Edit In-Game
|
||||
waves.title = Waves
|
||||
waves.remove = Remove
|
||||
waves.never = <never>
|
||||
@@ -211,6 +213,8 @@ waves.copied = Waves copied.
|
||||
editor.default = [LIGHT_GRAY]<Default>
|
||||
edit = Edit...
|
||||
editor.name = Name:
|
||||
editor.spawn = Spawn Unit
|
||||
editor.removeunit = Remove Unit
|
||||
editor.teams = Teams
|
||||
editor.elevation = Elevation
|
||||
editor.errorload = Error loading file:\n[accent]{0}
|
||||
@@ -431,11 +435,11 @@ setting.fpscap.name = Max FPS
|
||||
setting.fpscap.none = None
|
||||
setting.fpscap.text = {0} FPS
|
||||
setting.swapdiagonal.name = Always Diagonal Placement
|
||||
setting.difficulty.training = training
|
||||
setting.difficulty.easy = easy
|
||||
setting.difficulty.normal = normal
|
||||
setting.difficulty.hard = hard
|
||||
setting.difficulty.insane = insane
|
||||
setting.difficulty.training = Training
|
||||
setting.difficulty.easy = Easy
|
||||
setting.difficulty.normal = Normal
|
||||
setting.difficulty.hard = Hard
|
||||
setting.difficulty.insane = Insane
|
||||
setting.difficulty.name = Difficulty:
|
||||
setting.screenshake.name = Screen Shake
|
||||
setting.effects.name = Display Effects
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 502 B After Width: | Height: | Size: 702 B |
+144
-137
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 417 KiB |
@@ -25,6 +25,8 @@ import java.util.Locale;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Vars{
|
||||
/** Whether to load locales.*/
|
||||
public static boolean loadLocales = true;
|
||||
/** IO buffer size. */
|
||||
public static final int bufferSize = 8192;
|
||||
/** global charset */
|
||||
@@ -104,8 +106,10 @@ public class Vars{
|
||||
public static FileHandle customMapDirectory;
|
||||
/** data subdirectory used for saves */
|
||||
public static FileHandle saveDirectory;
|
||||
/** old map file extension, for conversion */
|
||||
public static final String oldMapExtension = "mmap";
|
||||
/** map file extension */
|
||||
public static final String mapExtension = "mmap";
|
||||
public static final String mapExtension = "msav";
|
||||
/** save file extension */
|
||||
public static final String saveExtension = "msav";
|
||||
|
||||
@@ -141,19 +145,22 @@ public class Vars{
|
||||
public static void init(){
|
||||
Serialization.init();
|
||||
|
||||
//load locales
|
||||
String[] stra = Core.files.internal("locales").readString().split("\n");
|
||||
locales = new Locale[stra.length];
|
||||
for(int i = 0; i < locales.length; i++){
|
||||
String code = stra[i];
|
||||
if(code.contains("_")){
|
||||
locales[i] = new Locale(code.split("_")[0], code.split("_")[1]);
|
||||
}else{
|
||||
locales[i] = new Locale(code);
|
||||
if(loadLocales){
|
||||
//load locales
|
||||
String[] stra = Core.files.internal("locales").readString().split("\n");
|
||||
locales = new Locale[stra.length];
|
||||
for(int i = 0; i < locales.length; i++){
|
||||
String code = stra[i];
|
||||
if(code.contains("_")){
|
||||
locales[i] = new Locale(code.split("_")[0], code.split("_")[1]);
|
||||
}else{
|
||||
locales[i] = new Locale(code);
|
||||
}
|
||||
}
|
||||
|
||||
Arrays.sort(locales, Structs.comparing(l -> l.getDisplayName(l), String.CASE_INSENSITIVE_ORDER));
|
||||
}
|
||||
|
||||
Arrays.sort(locales, Structs.comparing(l -> l.getDisplayName(l), String.CASE_INSENSITIVE_ORDER));
|
||||
Version.init();
|
||||
|
||||
content = new ContentLoader();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<FlyerSpawn> flySpawns = new Array<>();
|
||||
private Array<GroundSpawn> 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){
|
||||
|
||||
@@ -30,7 +30,7 @@ public class Blocks implements ContentList{
|
||||
public static Block
|
||||
|
||||
//environment
|
||||
air, part, spawn, deepwater, water, taintedWater, tar, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
|
||||
air, spawn, deepwater, water, taintedWater, tar, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
|
||||
holostone, rocks, sporerocks, icerocks, cliffs, sporePine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
|
||||
iceSnow, sandWater, darksandWater, duneRocks, sandRocks, moss, sporeMoss, shale, shaleRocks, shaleBoulder, grass, salt,
|
||||
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks, rock, snowrock, saltRocks,
|
||||
@@ -88,15 +88,9 @@ public class Blocks implements ContentList{
|
||||
hasShadow = false;
|
||||
}
|
||||
|
||||
public void draw(Tile tile){
|
||||
}
|
||||
|
||||
public void load(){
|
||||
}
|
||||
|
||||
public void init(){
|
||||
}
|
||||
|
||||
public void draw(Tile tile){}
|
||||
public void load(){}
|
||||
public void init(){}
|
||||
public boolean isHidden(){
|
||||
return true;
|
||||
}
|
||||
@@ -109,14 +103,24 @@ public class Blocks implements ContentList{
|
||||
}
|
||||
};
|
||||
|
||||
part = new BlockPart();
|
||||
//create special blockpart variants
|
||||
for(int dx = 0; dx < BlockPart.maxSize; dx++){
|
||||
for(int dy = 0; dy < BlockPart.maxSize; dy++){
|
||||
int fx = dx - BlockPart.maxSize/2, fy = dy - BlockPart.maxSize/2;
|
||||
if(fx != 0 || fy != 0){
|
||||
new BlockPart(fx, fy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spawn = new Block("spawn");
|
||||
spawn = new OverlayFloor("spawn"){
|
||||
public void draw(Tile tile){}
|
||||
};
|
||||
|
||||
//Registers build blocks from size 1-6
|
||||
//Registers build blocks
|
||||
//no reference is needed here since they can be looked up by name later
|
||||
for(int i = 1; i <= 6; i++){
|
||||
new BuildBlock("build" + i);
|
||||
for(int i = 1; i <= BuildBlock.maxSize; i++){
|
||||
new BuildBlock(i);
|
||||
}
|
||||
|
||||
deepwater = new Floor("deepwater"){{
|
||||
@@ -556,7 +560,7 @@ public class Blocks implements ContentList{
|
||||
drawer = tile -> {
|
||||
LiquidModule mod = tile.entity.liquids;
|
||||
|
||||
int rotation = rotate ? tile.getRotation() * 90 : 0;
|
||||
int rotation = rotate ? tile.rotation() * 90 : 0;
|
||||
|
||||
Draw.rect(reg(bottomRegion), tile.drawx(), tile.drawy(), rotation);
|
||||
|
||||
|
||||
@@ -398,9 +398,9 @@ public class Bullets implements ContentList{
|
||||
@Override
|
||||
public void hitTile(Bullet b, Tile tile){
|
||||
super.hit(b);
|
||||
tile = tile.target();
|
||||
tile = tile.link();
|
||||
|
||||
if(tile != null && tile.getTeam() == b.getTeam() && !(tile.block() instanceof BuildBlock)){
|
||||
if(tile.getTeam() == b.getTeam() && !(tile.block() instanceof BuildBlock)){
|
||||
Effects.effect(Fx.healBlockFull, Pal.heal, tile.drawx(), tile.drawy(), tile.block().size);
|
||||
tile.entity.healBy(healPercent / 100f * tile.entity.maxHealth());
|
||||
}
|
||||
@@ -424,7 +424,6 @@ public class Bullets implements ContentList{
|
||||
|
||||
@Override
|
||||
public void draw(Bullet b){
|
||||
//TODO add color to the bullet depending on the color of the flame it came from
|
||||
Draw.color(Pal.lightFlame, Pal.darkFlame, Color.GRAY, b.fin());
|
||||
Fill.circle(b.x, b.y, 3f * b.fout());
|
||||
Draw.reset();
|
||||
|
||||
@@ -75,8 +75,6 @@ public class StatusEffects implements ContentList{
|
||||
armorMultiplier = 3f;
|
||||
damageMultiplier = 3f;
|
||||
speedMultiplier = 1.1f;
|
||||
//TODO custom effect
|
||||
//effect = Fx.overdriven;
|
||||
}};
|
||||
|
||||
shocked = new StatusEffect();
|
||||
|
||||
@@ -154,20 +154,6 @@ public class Zones implements ContentList{
|
||||
}};
|
||||
}};
|
||||
|
||||
/*
|
||||
crags = new Zone("crags", new MapGenerator("groundZero", 1)){{ //TODO implement
|
||||
baseLaunchCost = ItemStack.with(Items.copper, 300);
|
||||
startingItems = ItemStack.with(Items.copper, 200);
|
||||
conditionWave = 15;
|
||||
zoneRequirements = new Zone[]{frozenForest};
|
||||
blockRequirements = new Block[]{Blocks.copperWall};
|
||||
rules = () -> new Rules(){{
|
||||
waves = true;]
|
||||
waveTimer = true;
|
||||
waveSpacing = 60 * 80;
|
||||
}};
|
||||
}};*/
|
||||
|
||||
stainedMountains = new Zone("stainedMountains", new MapGenerator("stainedMountains", 2)
|
||||
.dist(0f, false)
|
||||
.decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{
|
||||
|
||||
@@ -90,17 +90,12 @@ public class ContentLoader{
|
||||
for(Array<Content> 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 extends Content> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import io.anuke.mindustry.gen.Call;
|
||||
import io.anuke.mindustry.input.*;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.net.Net;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.type.*;
|
||||
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
@@ -120,7 +120,7 @@ public class Control implements ApplicationListener{
|
||||
Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
|
||||
//the restart dialog can show info for any number of scenarios
|
||||
Call.onGameOver(event.winner);
|
||||
if(state.rules.zone != -1){
|
||||
if(state.rules.zone != null){
|
||||
//remove zone save on game over
|
||||
if(saves.getZoneSlot() != null){
|
||||
saves.getZoneSlot().delete();
|
||||
@@ -207,6 +207,23 @@ public class Control implements ApplicationListener{
|
||||
});
|
||||
}
|
||||
|
||||
public void playZone(Zone zone){
|
||||
ui.loadAnd(() -> {
|
||||
logic.reset();
|
||||
world.loadGenerator(zone.generator);
|
||||
state.rules = zone.rules.get();
|
||||
state.rules.zone = zone;
|
||||
for(Tile core : state.teams.get(defaultTeam).cores){
|
||||
for(ItemStack stack : zone.getStartingItems()){
|
||||
core.entity.items.add(stack.item, stack.amount);
|
||||
}
|
||||
}
|
||||
state.set(State.playing);
|
||||
control.saves.zoneSave();
|
||||
logic.play();
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isHighScore(){
|
||||
return hiscore;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ public class GameState{
|
||||
state = astate;
|
||||
}
|
||||
|
||||
public boolean isEditor(){
|
||||
return rules.editor;
|
||||
}
|
||||
|
||||
public boolean isPaused(){
|
||||
return (is(State.paused) && !Net.active()) || (gameOver && !Net.active());
|
||||
}
|
||||
|
||||
@@ -58,12 +58,6 @@ public class Logic implements ApplicationListener{
|
||||
public void play(){
|
||||
state.set(State.playing);
|
||||
state.wavetime = state.rules.waveSpacing * 2; //grace period of 2x wave time before game starts
|
||||
|
||||
//sometimes a map has no waves defined, they're defined in the zone rules
|
||||
if(world.getMap().getWaves() != DefaultWaves.get() || !world.isZone()){
|
||||
state.rules.spawns = world.getMap().getWaves();
|
||||
}
|
||||
|
||||
Events.fire(new PlayEvent());
|
||||
}
|
||||
|
||||
@@ -165,15 +159,17 @@ public class Logic implements ApplicationListener{
|
||||
Entities.update(groundEffectGroup);
|
||||
}
|
||||
|
||||
for(EntityGroup group : unitGroups){
|
||||
Entities.update(group);
|
||||
}
|
||||
if(!state.isEditor()){
|
||||
for(EntityGroup group : unitGroups){
|
||||
Entities.update(group);
|
||||
}
|
||||
|
||||
Entities.update(puddleGroup);
|
||||
Entities.update(shieldGroup);
|
||||
Entities.update(bulletGroup);
|
||||
Entities.update(tileGroup);
|
||||
Entities.update(fireGroup);
|
||||
Entities.update(puddleGroup);
|
||||
Entities.update(shieldGroup);
|
||||
Entities.update(bulletGroup);
|
||||
Entities.update(tileGroup);
|
||||
Entities.update(fireGroup);
|
||||
}
|
||||
Entities.update(playerGroup);
|
||||
|
||||
//effect group only contains item transfers in the headless version, update it!
|
||||
@@ -181,19 +177,21 @@ public class Logic implements ApplicationListener{
|
||||
Entities.update(effectGroup);
|
||||
}
|
||||
|
||||
for(EntityGroup group : unitGroups){
|
||||
if(group.isEmpty()) continue;
|
||||
if(!state.isEditor()){
|
||||
|
||||
collisions.collideGroups(bulletGroup, group);
|
||||
for(EntityGroup group : unitGroups){
|
||||
if(group.isEmpty()) continue;
|
||||
collisions.collideGroups(bulletGroup, group);
|
||||
}
|
||||
|
||||
collisions.collideGroups(bulletGroup, playerGroup);
|
||||
collisions.collideGroups(playerGroup, playerGroup);
|
||||
}
|
||||
|
||||
collisions.collideGroups(bulletGroup, playerGroup);
|
||||
collisions.collideGroups(playerGroup, playerGroup);
|
||||
|
||||
world.pathfinder.update();
|
||||
}
|
||||
|
||||
if(!Net.client() && !world.isInvalidMap()){
|
||||
if(!Net.client() && !world.isInvalidMap() && !state.isEditor()){
|
||||
checkGameOver();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ public class NetClient implements ApplicationListener{
|
||||
}
|
||||
|
||||
//called on all clients
|
||||
@Remote(called = Loc.server, targets = Loc.server)
|
||||
@Remote(called = Loc.server, targets = Loc.server, variants = Variant.both)
|
||||
public static void sendMessage(String message, String sender, Player playersender){
|
||||
if(Vars.ui != null){
|
||||
Vars.ui.chatfrag.addMessage(message, sender);
|
||||
|
||||
@@ -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;
|
||||
@@ -213,7 +212,7 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
public void sendWorldData(Player player, int clientID){
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
DeflaterOutputStream def = new DeflaterOutputStream(stream);
|
||||
DeflaterOutputStream def = new FastDeflaterOutputStream(stream);
|
||||
NetworkIO.writeWorld(player, def);
|
||||
WorldStream data = new WorldStream();
|
||||
data.stream = new ByteArrayInputStream(stream.toByteArray());
|
||||
@@ -290,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);
|
||||
|
||||
@@ -2,7 +2,6 @@ package io.anuke.mindustry.core;
|
||||
|
||||
import io.anuke.annotations.Annotations.Nullable;
|
||||
import io.anuke.arc.*;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.IntArray;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.math.geom.Geometry;
|
||||
@@ -18,8 +17,9 @@ import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.io.MapIO;
|
||||
import io.anuke.mindustry.maps.*;
|
||||
import io.anuke.mindustry.maps.generators.Generator;
|
||||
import io.anuke.mindustry.type.*;
|
||||
import io.anuke.mindustry.type.Zone;
|
||||
import io.anuke.mindustry.world.*;
|
||||
import io.anuke.mindustry.world.blocks.BlockPart;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
@@ -28,17 +28,22 @@ public class World implements ApplicationListener{
|
||||
public final BlockIndexer indexer = new BlockIndexer();
|
||||
public final WaveSpawner spawner = new WaveSpawner();
|
||||
public final Pathfinder pathfinder = new Pathfinder();
|
||||
public final Context context = new Context();
|
||||
|
||||
private Map currentMap;
|
||||
private Tile[][] tiles;
|
||||
|
||||
private Array<Tile> tempTiles = new Array<>();
|
||||
private boolean generating, invalidMap;
|
||||
|
||||
public World(){
|
||||
maps.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
maps.loadLegacyMaps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(){
|
||||
maps.dispose();
|
||||
@@ -97,6 +102,12 @@ public class World implements ApplicationListener{
|
||||
return tiles[x][y];
|
||||
}
|
||||
|
||||
public @Nullable Tile ltile(int x, int y){
|
||||
Tile tile = tile(x, y);
|
||||
if(tile == null) return null;
|
||||
return tile.block().linked(tile);
|
||||
}
|
||||
|
||||
public Tile rawTile(int x, int y){
|
||||
return tiles[x][y];
|
||||
}
|
||||
@@ -105,6 +116,10 @@ public class World implements ApplicationListener{
|
||||
return tile(Math.round(x / tilesize), Math.round(y / tilesize));
|
||||
}
|
||||
|
||||
public @Nullable Tile ltileWorld(float x, float y){
|
||||
return ltile(Math.round(x / tilesize), Math.round(y / tilesize));
|
||||
}
|
||||
|
||||
public int toTile(float coord){
|
||||
return Math.round(coord / tilesize);
|
||||
}
|
||||
@@ -160,6 +175,8 @@ public class World implements ApplicationListener{
|
||||
* A WorldLoadEvent will be fire.
|
||||
*/
|
||||
public void endMapLoad(){
|
||||
prepareTiles(tiles);
|
||||
|
||||
for(int x = 0; x < tiles.length; x++){
|
||||
for(int y = 0; y < tiles[0].length; y++){
|
||||
Tile tile = tiles[x][y];
|
||||
@@ -188,24 +205,7 @@ public class World implements ApplicationListener{
|
||||
}
|
||||
|
||||
public Zone getZone(){
|
||||
return content.getByID(ContentType.zone, state.rules.zone);
|
||||
}
|
||||
|
||||
public void playZone(Zone zone){
|
||||
ui.loadAnd(() -> {
|
||||
logic.reset();
|
||||
state.rules = zone.rules.get();
|
||||
state.rules.zone = zone.id;
|
||||
loadGenerator(zone.generator);
|
||||
for(Tile core : state.teams.get(defaultTeam).cores){
|
||||
for(ItemStack stack : zone.getStartingItems()){
|
||||
core.entity.items.add(stack.item, stack.amount);
|
||||
}
|
||||
}
|
||||
state.set(State.playing);
|
||||
control.saves.zoneSave();
|
||||
logic.play();
|
||||
});
|
||||
return state.rules.zone;
|
||||
}
|
||||
|
||||
public void loadGenerator(Generator generator){
|
||||
@@ -213,24 +213,14 @@ public class World implements ApplicationListener{
|
||||
|
||||
createTiles(generator.width, generator.height);
|
||||
generator.generate(tiles);
|
||||
prepareTiles(tiles);
|
||||
|
||||
endMapLoad();
|
||||
}
|
||||
|
||||
public void loadMap(Map map){
|
||||
beginMapLoad();
|
||||
this.currentMap = map;
|
||||
|
||||
try{
|
||||
createTiles(map.width, map.height);
|
||||
for(int x = 0; x < map.width; x++){
|
||||
for(int y = 0; y < map.height; y++){
|
||||
tiles[x][y] = new Tile(x, y);
|
||||
}
|
||||
}
|
||||
MapIO.readTiles(map, tiles);
|
||||
prepareTiles(tiles);
|
||||
MapIO.loadMap(map);
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
if(!headless){
|
||||
@@ -242,7 +232,7 @@ public class World implements ApplicationListener{
|
||||
return;
|
||||
}
|
||||
|
||||
endMapLoad();
|
||||
this.currentMap = map;
|
||||
|
||||
invalidMap = false;
|
||||
|
||||
@@ -289,16 +279,7 @@ public class World implements ApplicationListener{
|
||||
}
|
||||
|
||||
public void removeBlock(Tile tile){
|
||||
if(!tile.block().isMultiblock() && !tile.isLinked()){
|
||||
tile.setBlock(Blocks.air);
|
||||
}else{
|
||||
Tile target = tile.target();
|
||||
Array<Tile> removals = target.getLinkedTiles(tempTiles);
|
||||
for(Tile toremove : removals){
|
||||
//note that setting a new block automatically unlinks it
|
||||
if(toremove != null) toremove.setBlock(Blocks.air);
|
||||
}
|
||||
}
|
||||
tile.link().getLinkedTiles(other -> other.setBlock(Blocks.air));
|
||||
}
|
||||
|
||||
public void setBlock(Tile tile, Block block, Team team){
|
||||
@@ -318,8 +299,7 @@ public class World implements ApplicationListener{
|
||||
if(!(worldx == tile.x && worldy == tile.y)){
|
||||
Tile toplace = world.tile(worldx, worldy);
|
||||
if(toplace != null){
|
||||
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
|
||||
toplace.setTeam(team);
|
||||
toplace.setBlock(BlockPart.get(dx + offsetx, dy + offsety), team);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,15 +307,6 @@ public class World implements ApplicationListener{
|
||||
}
|
||||
}
|
||||
|
||||
public int transform(int packed, int oldWidth, int oldHeight, int newWidth, int shiftX, int shiftY){
|
||||
int x = packed % oldWidth;
|
||||
int y = packed / oldWidth;
|
||||
if(!Structs.inBounds(x, y, oldWidth, oldHeight)) return -1;
|
||||
x += shiftX;
|
||||
y += shiftY;
|
||||
return y * newWidth + x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raycast, but with world coordinates.
|
||||
*/
|
||||
@@ -413,11 +384,6 @@ public class World implements ApplicationListener{
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads raw map tile data into a Tile[][] array, setting up multiblocks, cliffs and ores. */
|
||||
void loadTileData(Tile[][] tiles){
|
||||
prepareTiles(tiles);
|
||||
}
|
||||
|
||||
public void addDarkness(Tile[][] tiles){
|
||||
byte[][] dark = new byte[tiles.length][tiles[0].length];
|
||||
byte[][] writeBuffer = new byte[tiles.length][tiles[0].length];
|
||||
@@ -456,7 +422,7 @@ public class World implements ApplicationListener{
|
||||
for(int y = 0; y < tiles[0].length; y++){
|
||||
Tile tile = tiles[x][y];
|
||||
if(tile.block().solid && !tile.block().synthetic()){
|
||||
tiles[x][y].setRotation(dark[x][y]);
|
||||
tiles[x][y].rotation(dark[x][y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,25 +469,47 @@ public class World implements ApplicationListener{
|
||||
if(!(worldx == x && worldy == y)){
|
||||
Tile toplace = world.tile(worldx, worldy);
|
||||
if(toplace != null){
|
||||
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
|
||||
toplace.setTeam(team);
|
||||
toplace.setBlock(BlockPart.get(dx + offsetx, dy + offsety), team);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//update cliffs, occlusion data
|
||||
for(int x = 0; x < tiles.length; x++){
|
||||
for(int y = 0; y < tiles[0].length; y++){
|
||||
Tile tile = tiles[x][y];
|
||||
|
||||
tile.updateOcclusion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Raycaster{
|
||||
boolean accept(int x, int y);
|
||||
}
|
||||
|
||||
class Context implements WorldContext{
|
||||
@Override
|
||||
public Tile tile(int x, int y){
|
||||
return tiles[x][y];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height){
|
||||
createTiles(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
|
||||
return (tiles[x][y] = new Tile(x, y, floorID, overlayID, wallID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerating(){
|
||||
return World.this.isGenerating();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin(){
|
||||
beginMapLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(){
|
||||
endMapLoad();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
|
||||
import static io.anuke.mindustry.Vars.content;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public class DrawOperation{
|
||||
private LongArray array = new LongArray();
|
||||
@@ -24,32 +25,46 @@ public class DrawOperation{
|
||||
public void undo(MapEditor editor){
|
||||
for(int i = array.size - 1; i >= 0; i--){
|
||||
long l = array.get(i);
|
||||
set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.from(l));
|
||||
array.set(i, TileOp.get(TileOp.x(l), TileOp.y(l), TileOp.type(l), get(editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l))));
|
||||
set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.value(l));
|
||||
}
|
||||
}
|
||||
|
||||
public void redo(MapEditor editor){
|
||||
for(int i = 0; i < array.size; i++){
|
||||
long l = array.get(i);
|
||||
set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.to(l));
|
||||
array.set(i, TileOp.get(TileOp.x(l), TileOp.y(l), TileOp.type(l), get(editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l))));
|
||||
set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.value(l));
|
||||
}
|
||||
}
|
||||
|
||||
void set(MapEditor editor, Tile tile, byte type, byte to){
|
||||
short get(Tile tile, byte type){
|
||||
if(type == OpType.floor.ordinal()){
|
||||
return tile.floorID();
|
||||
}else if(type == OpType.block.ordinal()){
|
||||
return tile.blockID();
|
||||
}else if(type == OpType.rotation.ordinal()){
|
||||
return tile.rotation();
|
||||
}else if(type == OpType.team.ordinal()){
|
||||
return tile.getTeamID();
|
||||
}else if(type == OpType.overlay.ordinal()){
|
||||
return tile.overlayID();
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid type.");
|
||||
}
|
||||
|
||||
void set(MapEditor editor, Tile tile, byte type, short to){
|
||||
editor.load(() -> {
|
||||
if(type == OpType.floor.ordinal()){
|
||||
tile.setFloor((Floor)content.block(to));
|
||||
}else if(type == OpType.block.ordinal()){
|
||||
Block block = content.block(to);
|
||||
tile.setBlock(block);
|
||||
if(block.isMultiblock()){
|
||||
editor.updateLinks(block, tile.x, tile.y);
|
||||
}
|
||||
world.setBlock(tile, block, tile.getTeam(), tile.rotation());
|
||||
}else if(type == OpType.rotation.ordinal()){
|
||||
tile.setRotation(to);
|
||||
tile.rotation(to);
|
||||
}else if(type == OpType.team.ordinal()){
|
||||
tile.setTeam(Team.all[to]);
|
||||
}else if(type == OpType.ore.ordinal()){
|
||||
}else if(type == OpType.overlay.ordinal()){
|
||||
tile.setOverlayID(to);
|
||||
}
|
||||
});
|
||||
@@ -61,8 +76,7 @@ public class DrawOperation{
|
||||
short x;
|
||||
short y;
|
||||
byte type;
|
||||
byte from;
|
||||
byte to;
|
||||
short value;
|
||||
}
|
||||
|
||||
public enum OpType{
|
||||
@@ -70,6 +84,6 @@ public class DrawOperation{
|
||||
block,
|
||||
rotation,
|
||||
team,
|
||||
ore
|
||||
overlay
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.anuke.mindustry.editor;
|
||||
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.editor.DrawOperation.OpType;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.gen.TileOp;
|
||||
@@ -9,21 +10,23 @@ import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.*;
|
||||
import io.anuke.mindustry.world.modules.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.state;
|
||||
import static io.anuke.mindustry.Vars.ui;
|
||||
|
||||
//TODO somehow remove or replace this class with a more flexible solution
|
||||
public class EditorTile extends Tile{
|
||||
|
||||
public EditorTile(int x, int y, byte floor, byte wall){
|
||||
super(x, y, floor, wall);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Team getTeam(){
|
||||
return Team.all[getTeamID()];
|
||||
public EditorTile(int x, int y, int floor, int overlay, int wall){
|
||||
super(x, y, floor, overlay, wall);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloor(Floor type){
|
||||
if(state.is(State.playing)){
|
||||
super.setFloor(type);
|
||||
return;
|
||||
}
|
||||
|
||||
if(type instanceof OverlayFloor){
|
||||
//don't place on liquids
|
||||
if(!floor.isLiquid){
|
||||
@@ -32,63 +35,91 @@ public class EditorTile extends Tile{
|
||||
return;
|
||||
}
|
||||
|
||||
Block previous = floor();
|
||||
Block ore = overlay();
|
||||
if(previous == type && ore == Blocks.air) return;
|
||||
if(floor == type && overlayID() == 0) return;
|
||||
if(overlayID() != 0) op(OpType.overlay, overlayID());
|
||||
if(floor != type) op(OpType.floor, floor.id);
|
||||
super.setFloor(type);
|
||||
//ore may get nullified so make sure to save edits
|
||||
if(overlay() != ore){
|
||||
op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), ore.id, overlay().id));
|
||||
}
|
||||
if(previous != type){
|
||||
op(TileOp.get(x, y, (byte)OpType.floor.ordinal(), previous.id, type.id));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(Block type){
|
||||
Block previous = block;
|
||||
byte pteam = getTeamID();
|
||||
if(previous == type) return;
|
||||
super.setBlock(type);
|
||||
if(pteam != getTeamID()){
|
||||
op(TileOp.get(x, y, (byte)OpType.team.ordinal(), pteam, getTeamID()));
|
||||
if(state.is(State.playing)){
|
||||
super.setBlock(type);
|
||||
return;
|
||||
}
|
||||
op(TileOp.get(x, y, (byte)OpType.block.ordinal(), previous.id, type.id));
|
||||
|
||||
if(block == type) return;
|
||||
op(OpType.block, block.id);
|
||||
if(rotation != 0) op(OpType.rotation, rotation);
|
||||
if(team != 0) op(OpType.team, team);
|
||||
super.setBlock(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(Block type, Team team, int rotation){
|
||||
if(state.is(State.playing)){
|
||||
super.setBlock(type, team, rotation);
|
||||
return;
|
||||
}
|
||||
|
||||
setBlock(type);
|
||||
setTeam(team);
|
||||
rotation(rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTeam(Team team){
|
||||
byte previous = getTeamID();
|
||||
if(previous == team.ordinal()) return;
|
||||
if(state.is(State.playing)){
|
||||
super.setTeam(team);
|
||||
return;
|
||||
}
|
||||
|
||||
if(getTeamID() == team.ordinal()) return;
|
||||
op(OpType.team, getTeamID());
|
||||
super.setTeam(team);
|
||||
op(TileOp.get(x, y, (byte)OpType.team.ordinal(), previous, (byte)team.ordinal()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRotation(byte rotation){
|
||||
byte previous = getRotation();
|
||||
if(previous == rotation) return;
|
||||
super.setRotation(rotation);
|
||||
op(TileOp.get(x, y, (byte)OpType.rotation.ordinal(), previous, rotation));
|
||||
public void rotation(int rotation){
|
||||
if(state.is(State.playing)){
|
||||
super.rotation(rotation);
|
||||
return;
|
||||
}
|
||||
|
||||
if(rotation == rotation()) return;
|
||||
op(OpType.rotation, rotation());
|
||||
super.rotation(rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOverlayID(byte ore){
|
||||
byte previous = getOverlayID();
|
||||
if(previous == ore) return;
|
||||
super.setOverlayID(ore);
|
||||
op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), previous, ore));
|
||||
public void setOverlayID(short overlay){
|
||||
if(state.is(State.playing)){
|
||||
super.setOverlayID(overlay);
|
||||
return;
|
||||
}
|
||||
|
||||
if(overlayID() == overlay) return;
|
||||
op(OpType.overlay, overlay);
|
||||
super.setOverlayID(overlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preChanged(){
|
||||
if(state.is(State.playing)){
|
||||
super.preChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
super.setTeam(Team.none);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void changed(){
|
||||
if(state.is(State.playing)){
|
||||
super.changed();
|
||||
return;
|
||||
}
|
||||
|
||||
entity = null;
|
||||
|
||||
if(block == null){
|
||||
@@ -102,8 +133,7 @@ public class EditorTile extends Tile{
|
||||
Block block = block();
|
||||
|
||||
if(block.hasEntity()){
|
||||
entity = block.newEntity();
|
||||
entity.health = block.health;
|
||||
entity = block.newEntity().init(this, false);
|
||||
entity.cons = new ConsumeModule(entity);
|
||||
if(block.hasItems) entity.items = new ItemModule();
|
||||
if(block.hasLiquids) entity.liquids = new LiquidModule();
|
||||
@@ -111,7 +141,7 @@ public class EditorTile extends Tile{
|
||||
}
|
||||
}
|
||||
|
||||
private static void op(long op){
|
||||
ui.editor.editor.addTileOp(op);
|
||||
private void op(OpType type, short value){
|
||||
ui.editor.editor.addTileOp(TileOp.get(x, y, (byte)type.ordinal(), value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,7 @@ public enum EditorTool{
|
||||
public void touched(MapEditor editor, int x, int y){
|
||||
if(!Structs.inBounds(x, y, editor.width(), editor.height())) return;
|
||||
|
||||
Tile tile = editor.tile(x, y);
|
||||
|
||||
byte link = tile.getLinkByte();
|
||||
|
||||
if(tile.isLinked()){
|
||||
x -= (Pack.leftByte(link) - 8);
|
||||
y -= (Pack.rightByte(link) - 8);
|
||||
|
||||
tile = editor.tile(x, y);
|
||||
}
|
||||
|
||||
//do not.
|
||||
if(tile.isLinked()){
|
||||
return;
|
||||
}
|
||||
Tile tile = editor.tile(x, y).link();
|
||||
|
||||
editor.drawBlock = tile.block() == Blocks.air ? tile.overlay() == Blocks.air ? tile.floor() : tile.overlay() : tile.block();
|
||||
}
|
||||
@@ -87,7 +73,7 @@ public enum EditorTool{
|
||||
Block draw = editor.drawBlock;
|
||||
dest = draw instanceof OverlayFloor ? tile.overlay() : isfloor ? floor : block;
|
||||
|
||||
if(dest == draw || block == Blocks.part || block.isMultiblock()){
|
||||
if(dest == draw || block instanceof BlockPart || block.isMultiblock()){
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,7 +102,7 @@ public enum EditorTool{
|
||||
}
|
||||
|
||||
if(draw.rotate){
|
||||
write.setRotation((byte)editor.rotation);
|
||||
write.rotation((byte)editor.rotation);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
package io.anuke.mindustry.editor;
|
||||
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.graphics.Pixmap;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.Pack;
|
||||
import io.anuke.arc.util.Structs;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.gen.TileOp;
|
||||
import io.anuke.mindustry.io.LegacyMapIO;
|
||||
import io.anuke.mindustry.io.MapIO;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.*;
|
||||
import io.anuke.mindustry.world.blocks.BlockPart;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
|
||||
import java.io.IOException;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public class MapEditor{
|
||||
public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15, 20};
|
||||
|
||||
private ObjectMap<String, String> tags = new ObjectMap<>();
|
||||
private final Context context = new Context();
|
||||
private StringMap tags = new StringMap();
|
||||
private MapRenderer renderer = new MapRenderer(this);
|
||||
private Tile[][] tiles;
|
||||
|
||||
private OperationStack stack = new OperationStack();
|
||||
private DrawOperation currentOp;
|
||||
@@ -32,7 +33,7 @@ public class MapEditor{
|
||||
public Block drawBlock = Blocks.stone;
|
||||
public Team drawTeam = Team.blue;
|
||||
|
||||
public ObjectMap<String, String> getTags(){
|
||||
public StringMap getTags(){
|
||||
return tags;
|
||||
}
|
||||
|
||||
@@ -40,39 +41,39 @@ public class MapEditor{
|
||||
reset();
|
||||
|
||||
loading = true;
|
||||
tiles = createTiles(width, height);
|
||||
createTiles(width, height);
|
||||
renderer.resize(width(), height());
|
||||
loading = false;
|
||||
}
|
||||
|
||||
public void beginEdit(Map map) throws IOException{
|
||||
public void beginEdit(Map map){
|
||||
reset();
|
||||
|
||||
loading = true;
|
||||
tiles = createTiles(map.width, map.height);
|
||||
tags.putAll(map.tags);
|
||||
MapIO.readTiles(map, tiles);
|
||||
MapIO.loadMap(map, context);
|
||||
checkLinkedTiles();
|
||||
renderer.resize(width(), height());
|
||||
loading = false;
|
||||
}
|
||||
|
||||
public void beginEdit(Tile[][] tiles){
|
||||
public void beginEdit(Pixmap pixmap){
|
||||
reset();
|
||||
|
||||
this.tiles = tiles;
|
||||
checkLinkedTiles();
|
||||
createTiles(pixmap.getWidth(), pixmap.getHeight());
|
||||
load(() -> LegacyMapIO.readPixmap(pixmap, tiles()));
|
||||
renderer.resize(width(), height());
|
||||
}
|
||||
|
||||
//adds missing blockparts
|
||||
public void checkLinkedTiles(){
|
||||
Tile[][] tiles = world.getTiles();
|
||||
|
||||
//clear block parts first
|
||||
for(int x = 0; x < width(); x++){
|
||||
for(int y = 0; y < height(); y++){
|
||||
if(tiles[x][y].block() == Blocks.part){
|
||||
if(tiles[x][y].block() instanceof BlockPart){
|
||||
tiles[x][y].setBlock(Blocks.air);
|
||||
tiles[x][y].setLinkByte((byte)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,22 +81,8 @@ public class MapEditor{
|
||||
//set up missing blockparts
|
||||
for(int x = 0; x < width(); x++){
|
||||
for(int y = 0; y < height(); y++){
|
||||
Block drawBlock = tiles[x][y].block();
|
||||
if(drawBlock.isMultiblock()){
|
||||
int offsetx = -(drawBlock.size - 1) / 2;
|
||||
int offsety = -(drawBlock.size - 1) / 2;
|
||||
for(int dx = 0; dx < drawBlock.size; dx++){
|
||||
for(int dy = 0; dy < drawBlock.size; dy++){
|
||||
int worldx = dx + offsetx + x;
|
||||
int worldy = dy + offsety + y;
|
||||
|
||||
if(Structs.inBounds(worldx, worldy, width(), height()) && !(dx + offsetx == 0 && dy + offsety == 0)){
|
||||
Tile tile = tiles[worldx][worldy];
|
||||
tile.setBlock(Blocks.part);
|
||||
tile.setLinkByte(Pack.byteByte((byte)(dx + offsetx + 8), (byte)(dy + offsety + 8)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(tiles[x][y].block().isMultiblock()){
|
||||
world.setBlock(tiles[x][y], tiles[x][y].block(), tiles[x][y].getTeam());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,63 +95,41 @@ public class MapEditor{
|
||||
}
|
||||
|
||||
/** Creates a 2-D array of EditorTiles with stone as the floor block. */
|
||||
public Tile[][] createTiles(int width, int height){
|
||||
tiles = new Tile[width][height];
|
||||
private void createTiles(int width, int height){
|
||||
Tile[][] tiles = world.createTiles(width, height);
|
||||
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (byte)0);
|
||||
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0);
|
||||
}
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
public Map createMap(FileHandle file){
|
||||
return new Map(file, width(), height(), new ObjectMap<>(tags), true);
|
||||
return new Map(file, width(), height(), new StringMap(tags), true);
|
||||
}
|
||||
|
||||
private void reset(){
|
||||
clearOp();
|
||||
brushSize = 1;
|
||||
drawBlock = Blocks.stone;
|
||||
tags = new ObjectMap<>();
|
||||
tags = new StringMap();
|
||||
}
|
||||
|
||||
public Tile[][] tiles(){
|
||||
return tiles;
|
||||
return world.getTiles();
|
||||
}
|
||||
|
||||
public Tile tile(int x, int y){
|
||||
return tiles[x][y];
|
||||
return world.rawTile(x, y);
|
||||
}
|
||||
|
||||
public int width(){
|
||||
return tiles.length;
|
||||
return world.width();
|
||||
}
|
||||
|
||||
public int height(){
|
||||
return tiles[0].length;
|
||||
}
|
||||
|
||||
public void updateLinks(Block block, int x, int y){
|
||||
int offsetx = -(block.size - 1) / 2;
|
||||
int offsety = -(block.size - 1) / 2;
|
||||
|
||||
for(int dx = 0; dx < block.size; dx++){
|
||||
for(int dy = 0; dy < block.size; dy++){
|
||||
int worldx = dx + offsetx + x;
|
||||
int worldy = dy + offsety + y;
|
||||
|
||||
if(Structs.inBounds(worldx, worldy, width(), height())){
|
||||
Tile tile = tiles[worldx][worldy];
|
||||
|
||||
if(!(worldx == x && worldy == y)){
|
||||
tile.setBlock(Blocks.part);
|
||||
tile.setLinkByte(Pack.byteByte((byte)(dx + offsetx + 8), (byte)(dy + offsety + 8)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return world.height();
|
||||
}
|
||||
|
||||
public void draw(int x, int y, boolean paint){
|
||||
@@ -177,45 +142,36 @@ public class MapEditor{
|
||||
|
||||
public void draw(int x, int y, boolean paint, Block drawBlock, double chance){
|
||||
boolean isfloor = drawBlock instanceof Floor && drawBlock != Blocks.air;
|
||||
Tile[][] tiles = world.getTiles();
|
||||
|
||||
if(drawBlock.isMultiblock()){
|
||||
|
||||
x = Mathf.clamp(x, (drawBlock.size - 1) / 2, width() - drawBlock.size / 2 - 1);
|
||||
y = Mathf.clamp(y, (drawBlock.size - 1) / 2, height() - drawBlock.size / 2 - 1);
|
||||
|
||||
int offsetx = -(drawBlock.size - 1) / 2;
|
||||
int offsety = -(drawBlock.size - 1) / 2;
|
||||
|
||||
for(int i = 0; i < 2; i++){
|
||||
for(int dx = 0; dx < drawBlock.size; dx++){
|
||||
for(int dy = 0; dy < drawBlock.size; dy++){
|
||||
int worldx = dx + offsetx + x;
|
||||
int worldy = dy + offsety + y;
|
||||
for(int dx = 0; dx < drawBlock.size; dx++){
|
||||
for(int dy = 0; dy < drawBlock.size; dy++){
|
||||
int worldx = dx + offsetx + x;
|
||||
int worldy = dy + offsety + y;
|
||||
|
||||
if(Structs.inBounds(worldx, worldy, width(), height())){
|
||||
Tile tile = tiles[worldx][worldy];
|
||||
if(Structs.inBounds(worldx, worldy, width(), height())){
|
||||
Tile tile = tiles[worldx][worldy];
|
||||
|
||||
if(i == 1){
|
||||
tile.setBlock(Blocks.part);
|
||||
tile.setLinkByte(Pack.byteByte((byte)(dx + offsetx + 8), (byte)(dy + offsety + 8)));
|
||||
}else{
|
||||
byte link = tile.getLinkByte();
|
||||
Block block = tile.block();
|
||||
Block block = tile.block();
|
||||
|
||||
if(link != 0){
|
||||
removeLinked(worldx - (Pack.leftByte(link) - 8), worldy - (Pack.rightByte(link) - 8));
|
||||
}else if(block.isMultiblock()){
|
||||
removeLinked(worldx, worldy);
|
||||
}
|
||||
}
|
||||
//bail out if there's anything blocking the way
|
||||
if(block.isMultiblock() || block instanceof BlockPart){
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.updatePoint(worldx, worldy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tile tile = tiles[x][y];
|
||||
tile.setBlock(drawBlock);
|
||||
tile.setTeam(drawTeam);
|
||||
world.setBlock(tiles[x][y], drawBlock, drawTeam);
|
||||
}else{
|
||||
for(int rx = -brushSize; rx <= brushSize; rx++){
|
||||
for(int ry = -brushSize; ry <= brushSize; ry++){
|
||||
@@ -228,14 +184,8 @@ public class MapEditor{
|
||||
|
||||
Tile tile = tiles[wx][wy];
|
||||
|
||||
if(!isfloor){
|
||||
byte link = tile.getLinkByte();
|
||||
|
||||
if(tile.block().isMultiblock()){
|
||||
removeLinked(wx, wy);
|
||||
}else if(link != 0 && tiles[wx][wy].block() == Blocks.part){
|
||||
removeLinked(wx - (Pack.leftByte(link) - 8), wy - (Pack.rightByte(link) - 8));
|
||||
}
|
||||
if(!isfloor && (tile.isLinked() || tile.block().isMultiblock())){
|
||||
world.removeBlock(tile.link());
|
||||
}
|
||||
|
||||
if(isfloor){
|
||||
@@ -246,7 +196,7 @@ public class MapEditor{
|
||||
tile.setTeam(drawTeam);
|
||||
}
|
||||
if(drawBlock.rotate){
|
||||
tile.setRotation((byte)rotation);
|
||||
tile.rotation((byte)rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,22 +205,6 @@ public class MapEditor{
|
||||
}
|
||||
}
|
||||
|
||||
private void removeLinked(int x, int y){
|
||||
Block block = tiles[x][y].block();
|
||||
|
||||
int offsetx = -(block.size - 1) / 2;
|
||||
int offsety = -(block.size - 1) / 2;
|
||||
for(int dx = 0; dx < block.size; dx++){
|
||||
for(int dy = 0; dy < block.size; dy++){
|
||||
int worldx = x + dx + offsetx, worldy = y + dy + offsety;
|
||||
if(Structs.inBounds(worldx, worldy, width(), height())){
|
||||
tiles[worldx][worldy].setTeam(Team.none);
|
||||
tiles[worldx][worldy].setBlock(Blocks.air);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MapRenderer renderer(){
|
||||
return renderer;
|
||||
}
|
||||
@@ -278,11 +212,11 @@ public class MapEditor{
|
||||
public void resize(int width, int height){
|
||||
clearOp();
|
||||
|
||||
Tile[][] previous = tiles;
|
||||
Tile[][] previous = world.getTiles();
|
||||
int offsetX = -(width - width()) / 2, offsetY = -(height - height()) / 2;
|
||||
loading = true;
|
||||
|
||||
tiles = new Tile[width][height];
|
||||
Tile[][] tiles = world.createTiles(width, height);
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
int px = offsetX + x, py = offsetY + y;
|
||||
@@ -291,7 +225,7 @@ public class MapEditor{
|
||||
tiles[x][y].x = (short)x;
|
||||
tiles[x][y].y = (short)y;
|
||||
}else{
|
||||
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (byte)0);
|
||||
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,4 +272,36 @@ public class MapEditor{
|
||||
|
||||
renderer.updatePoint(TileOp.x(data), TileOp.y(data));
|
||||
}
|
||||
|
||||
class Context implements WorldContext{
|
||||
@Override
|
||||
public Tile tile(int x, int y){
|
||||
return world.tile(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height){
|
||||
world.createTiles(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
|
||||
return (tiles()[x][y] = new EditorTile(x, y, floorID, overlayID, wallID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerating(){
|
||||
return world.isGenerating();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin(){
|
||||
world.beginMapLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(){
|
||||
world.endMapLoad();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.anuke.mindustry.editor;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.function.Consumer;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
@@ -17,8 +18,10 @@ import io.anuke.arc.scene.ui.layout.Table;
|
||||
import io.anuke.arc.scene.ui.layout.Unit;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.core.Platform;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.io.JsonIO;
|
||||
import io.anuke.mindustry.io.MapIO;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||
@@ -40,6 +43,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
private MapGenerateDialog generateDialog;
|
||||
private ScrollPane pane;
|
||||
private FloatingDialog menu;
|
||||
private Rules lastSavedRules;
|
||||
private boolean saved = false;
|
||||
private boolean shownWithMap = false;
|
||||
private Array<Block> blocksOut = new Array<>();
|
||||
@@ -91,7 +95,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
Platform.instance.showFileChooser("$editor.loadmap", "Map Files", file -> ui.loadAnd(() -> {
|
||||
try{
|
||||
//TODO what if it's an image? users should be warned for their stupidity
|
||||
editor.beginEdit(MapIO.readMap(file, true));
|
||||
editor.beginEdit(MapIO.createMap(file, true));
|
||||
}catch(Exception e){
|
||||
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, false)));
|
||||
Log.err(e);
|
||||
@@ -103,9 +107,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
ui.loadAnd(() -> {
|
||||
try{
|
||||
Pixmap pixmap = new Pixmap(file);
|
||||
Tile[][] tiles = editor.createTiles(pixmap.getWidth(), pixmap.getHeight());
|
||||
editor.load(() -> MapIO.readLegacyPixmap(pixmap, tiles));
|
||||
editor.beginEdit(tiles);
|
||||
editor.beginEdit(pixmap);
|
||||
pixmap.dispose();
|
||||
}catch(Exception e){
|
||||
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, false)));
|
||||
Log.err(e);
|
||||
@@ -122,7 +125,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
if(!editor.getTags().containsKey("name")){
|
||||
editor.getTags().put("name", result.nameWithoutExtension());
|
||||
}
|
||||
MapIO.writeMap(result, editor.createMap(result), editor.tiles());
|
||||
MapIO.writeMap(result, editor.createMap(result));
|
||||
}catch(Exception e){
|
||||
ui.showError(Core.bundle.format("editor.errorsave", Strings.parseException(e, false)));
|
||||
Log.err(e);
|
||||
@@ -133,10 +136,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
menu.cont.row();
|
||||
|
||||
menu.cont.addImageTextButton("$editor.ingame", "icon-arrow", isize, this::playtest).padTop(-5).size(swidth * 2f + 10, 60f);
|
||||
|
||||
menu.cont.row();
|
||||
|
||||
menu.cont.addImageTextButton("$quit", "icon-back", isize, () -> {
|
||||
tryExit();
|
||||
menu.hide();
|
||||
}).padTop(-5).size(swidth * 2f + 10, 60f);
|
||||
}).size(swidth * 2f + 10, 60f);
|
||||
|
||||
resizeDialog = new MapResizeDialog(editor, (x, y) -> {
|
||||
if(!(editor.width() == x && editor.height() == y)){
|
||||
@@ -181,11 +188,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
});
|
||||
|
||||
shown(() -> {
|
||||
//clear units, rules and other unnecessary stuff
|
||||
logic.reset();
|
||||
saved = true;
|
||||
if(!Core.settings.getBool("landscape")) Platform.instance.beginForceLandscape();
|
||||
editor.clearOp();
|
||||
Core.scene.setScrollFocus(view);
|
||||
if(!shownWithMap){
|
||||
state.rules = new Rules();
|
||||
editor.beginEdit(200, 200);
|
||||
}
|
||||
shownWithMap = false;
|
||||
@@ -205,8 +215,47 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
drawDefaultBackground(x, y);
|
||||
}
|
||||
|
||||
public void resumeEditing(){
|
||||
state.set(State.menu);
|
||||
shownWithMap = true;
|
||||
show();
|
||||
state.rules = (lastSavedRules == null ? new Rules() : lastSavedRules);
|
||||
lastSavedRules = null;
|
||||
editor.renderer().updateAll();
|
||||
}
|
||||
|
||||
private void playtest(){
|
||||
menu.hide();
|
||||
ui.loadAnd(() -> {
|
||||
lastSavedRules = state.rules;
|
||||
hide();
|
||||
//only reset the player; logic.reset() will clear entities, which we do not want
|
||||
player.reset();
|
||||
state.rules = Gamemode.editor.apply(new Rules());
|
||||
world.setMap(new Map(StringMap.of(
|
||||
"name", "Editor Playtesting",
|
||||
"width", editor.width(),
|
||||
"height", editor.height()
|
||||
)));
|
||||
world.endMapLoad();
|
||||
//add entities so they update. is this really needed?
|
||||
for(int x = 0; x < world.width(); x++){
|
||||
for(int y = 0; y < world.height(); y++){
|
||||
Tile tile = world.rawTile(x, y);
|
||||
if(tile.entity != null){
|
||||
tile.entity.add();
|
||||
}
|
||||
}
|
||||
}
|
||||
player.set(world.width() * tilesize/2f, world.height() * tilesize/2f);
|
||||
player.setDead(false);
|
||||
logic.play();
|
||||
});
|
||||
}
|
||||
|
||||
private void save(){
|
||||
String name = editor.getTags().get("name", "").trim();
|
||||
editor.getTags().put("rules", JsonIO.write(state.rules));
|
||||
|
||||
if(name.isEmpty()){
|
||||
infoDialog.show();
|
||||
@@ -216,7 +265,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
if(map != null && !map.custom){
|
||||
ui.showError("$editor.save.overwrite");
|
||||
}else{
|
||||
world.maps.saveMap(editor.getTags(), editor.tiles());
|
||||
world.maps.saveMap(editor.getTags());
|
||||
ui.showInfoFade("$editor.saved");
|
||||
}
|
||||
}
|
||||
@@ -281,9 +330,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
public void beginEditMap(FileHandle file){
|
||||
ui.loadAnd(() -> {
|
||||
try{
|
||||
Map map = MapIO.readMap(file, true);
|
||||
shownWithMap = true;
|
||||
editor.beginEdit(map);
|
||||
editor.beginEdit(MapIO.createMap(file, true));
|
||||
show();
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
|
||||
@@ -224,7 +224,7 @@ public class MapGenerateDialog extends FloatingDialog{
|
||||
Tile tile = editor.tile(x, y);
|
||||
input.begin(editor, x, y, tile.floor(), tile.block(), tile.overlay());
|
||||
filter.apply(input);
|
||||
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.getRotation());
|
||||
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.rotation());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ public class MapGenerateDialog extends FloatingDialog{
|
||||
Tile tile = editor.tile(x, y);
|
||||
DummyTile write = writeTiles[x][y];
|
||||
|
||||
tile.setRotation(write.rotation);
|
||||
tile.rotation(write.rotation);
|
||||
tile.setFloor((Floor)content.block(write.floor));
|
||||
tile.setBlock(content.block(write.block));
|
||||
tile.setTeam(Team.all[write.team]);
|
||||
@@ -322,7 +322,8 @@ public class MapGenerateDialog extends FloatingDialog{
|
||||
}
|
||||
|
||||
public static class DummyTile{
|
||||
public byte block, floor, ore, team, rotation;
|
||||
public byte team, rotation;
|
||||
public short block, floor, ore;
|
||||
|
||||
void set(Block floor, Block wall, Block ore, Team team, int rotation){
|
||||
this.floor = floor.id;
|
||||
@@ -341,7 +342,7 @@ public class MapGenerateDialog extends FloatingDialog{
|
||||
}
|
||||
|
||||
void set(Tile other){
|
||||
set(other.floor(), other.block(), other.overlay(), other.getTeam(), other.getRotation());
|
||||
set(other.floor(), other.block(), other.overlay(), other.getTeam(), other.rotation());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@ import io.anuke.arc.Core;
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.scene.ui.TextArea;
|
||||
import io.anuke.arc.scene.ui.TextField;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.core.Platform;
|
||||
import io.anuke.mindustry.game.Rules;
|
||||
import io.anuke.mindustry.ui.dialogs.CustomRulesDialog;
|
||||
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||
|
||||
public class MapInfoDialog extends FloatingDialog{
|
||||
private final MapEditor editor;
|
||||
private final WaveInfoDialog waveinfo;
|
||||
private final CustomRulesDialog ruleinfo = new CustomRulesDialog();
|
||||
|
||||
public MapInfoDialog(MapEditor editor){
|
||||
super("$editor.mapinfo");
|
||||
@@ -36,7 +40,6 @@ public class MapInfoDialog extends FloatingDialog{
|
||||
name.setMessageText("$unknown");
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.add("$editor.description").padRight(8).left();
|
||||
|
||||
TextArea description = cont.addArea(tags.get("description", ""), "textarea", text -> {
|
||||
@@ -44,7 +47,6 @@ public class MapInfoDialog extends FloatingDialog{
|
||||
}).size(400f, 140f).get();
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.add("$editor.author").padRight(8).left();
|
||||
|
||||
TextField author = cont.addField(tags.get("author", Core.settings.getString("mapAuthor", "")), text -> {
|
||||
@@ -54,14 +56,13 @@ public class MapInfoDialog extends FloatingDialog{
|
||||
}).size(400, 55f).get();
|
||||
author.setMessageText("$unknown");
|
||||
|
||||
cont.row();
|
||||
cont.add("$editor.rules").padRight(8).left();
|
||||
cont.addButton("$edit", () -> ruleinfo.show(Vars.state.rules, () -> Vars.state.rules = new Rules())).left().width(200f);;
|
||||
|
||||
cont.row();
|
||||
cont.add("$editor.waves").padRight(8).left();
|
||||
cont.table(t -> {
|
||||
t.add().growX();
|
||||
t.label(() -> tags.containsKey("waves") ? "" : Core.bundle.get("editor.default")).left();
|
||||
t.add().growX();
|
||||
t.addButton("$edit", waveinfo::show).growY().width(200f);
|
||||
}).size(400, 55f);
|
||||
cont.addButton("$edit", waveinfo::show).left().width(200f);
|
||||
|
||||
name.change();
|
||||
description.change();
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.graphics.IndexedRenderer;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.BlockPart;
|
||||
|
||||
import static io.anuke.mindustry.Vars.tilesize;
|
||||
|
||||
@@ -84,7 +85,6 @@ public class MapRenderer implements Disposable{
|
||||
}
|
||||
|
||||
public void updatePoint(int x, int y){
|
||||
//TODO spread out over multiple frames?
|
||||
updates.add(x + y * width);
|
||||
}
|
||||
|
||||
@@ -110,13 +110,13 @@ public class MapRenderer implements Disposable{
|
||||
int idxWall = (wx % chunksize) + (wy % chunksize) * chunksize;
|
||||
int idxDecal = (wx % chunksize) + (wy % chunksize) * chunksize + chunksize * chunksize;
|
||||
|
||||
if(wall != Blocks.air && (wall.synthetic() || wall == Blocks.part)){
|
||||
if(wall != Blocks.air && (wall.synthetic() || wall instanceof BlockPart)){
|
||||
region = !Core.atlas.isFound(wall.editorIcon()) ? Core.atlas.find("clear-editor") : wall.editorIcon();
|
||||
|
||||
if(wall.rotate){
|
||||
mesh.draw(idxWall, region,
|
||||
wx * tilesize + wall.offset(), wy * tilesize + wall.offset(),
|
||||
region.getWidth() * Draw.scl, region.getHeight() * Draw.scl, tile.getRotation() * 90 - 90);
|
||||
region.getWidth() * Draw.scl, region.getHeight() * Draw.scl, tile.rotation() * 90 - 90);
|
||||
}else{
|
||||
mesh.draw(idxWall, region,
|
||||
wx * tilesize + wall.offset() + (tilesize - region.getWidth() * Draw.scl) / 2f,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,10 +7,11 @@ import io.anuke.arc.function.Consumer;
|
||||
import io.anuke.arc.function.Predicate;
|
||||
import io.anuke.arc.graphics.Camera;
|
||||
import io.anuke.arc.math.geom.Rectangle;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.entities.traits.DrawTrait;
|
||||
import io.anuke.mindustry.entities.traits.Entity;
|
||||
|
||||
import static io.anuke.mindustry.Vars.collisions;
|
||||
|
||||
public class Entities{
|
||||
public static final int maxLeafObjects = 4;
|
||||
private static final Array<EntityGroup<?>> groupArray = new Array<>();
|
||||
@@ -48,7 +49,7 @@ public class Entities{
|
||||
group.updateEvents();
|
||||
|
||||
if(group.useTree()){
|
||||
Vars.collisions.updatePhysics(group);
|
||||
collisions.updatePhysics(group);
|
||||
}
|
||||
|
||||
for(Entity e : group.all()){
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -72,6 +72,11 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float lifetime(){
|
||||
return lifetime;
|
||||
@@ -98,7 +103,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{
|
||||
return;
|
||||
}
|
||||
|
||||
TileEntity entity = tile.target().entity;
|
||||
TileEntity entity = tile.link().entity;
|
||||
boolean damage = entity != null;
|
||||
|
||||
float flammability = baseFlammability + puddleFlammability;
|
||||
@@ -151,7 +156,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
public void readSave(DataInput stream, byte version) throws IOException{
|
||||
this.loadedPosition = stream.readInt();
|
||||
this.lifetime = stream.readFloat();
|
||||
this.time = stream.readFloat();
|
||||
|
||||
@@ -143,6 +143,11 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
|
||||
return liquid.flammability * amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitbox(Rectangle rectangle){
|
||||
rectangle.setCenter(x, y).setSize(tilesize);
|
||||
@@ -201,7 +206,7 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
|
||||
}
|
||||
});
|
||||
|
||||
if(liquid.temperature > 0.7f && (tile.target().entity != null) && Mathf.chance(0.3 * Time.delta())){
|
||||
if(liquid.temperature > 0.7f && (tile.link().entity != null) && Mathf.chance(0.3 * Time.delta())){
|
||||
Fire.create(tile);
|
||||
}
|
||||
|
||||
@@ -245,7 +250,7 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
public void readSave(DataInput stream, byte version) throws IOException{
|
||||
this.loadedPosition = stream.readInt();
|
||||
this.x = stream.readFloat();
|
||||
this.y = stream.readFloat();
|
||||
|
||||
@@ -40,10 +40,98 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
||||
float placeDistance = 220f;
|
||||
float mineDistance = 70f;
|
||||
|
||||
//due to iOS wierdness
|
||||
class BuildDataStatic{
|
||||
static Array<BuildRequest> removal = new Array<>();
|
||||
static Vector2[] tmptr = new Vector2[]{new Vector2(), new Vector2(), new Vector2(), new Vector2()};
|
||||
/**
|
||||
* Update building mechanism for this unit.
|
||||
* This includes mining.
|
||||
*/
|
||||
default void updateBuilding(){
|
||||
float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : placeDistance;
|
||||
Unit unit = (Unit)this;
|
||||
//remove already completed build requests
|
||||
removal.clear();
|
||||
for(BuildRequest req : getPlaceQueue()){
|
||||
removal.add(req);
|
||||
}
|
||||
|
||||
getPlaceQueue().clear();
|
||||
|
||||
for(BuildRequest request : removal){
|
||||
if(!((request.breaking && world.tile(request.x, request.y).block() == Blocks.air) ||
|
||||
(!request.breaking && (world.tile(request.x, request.y).rotation() == request.rotation || !request.block.rotate)
|
||||
&& world.tile(request.x, request.y).block() == request.block))){
|
||||
getPlaceQueue().addLast(request);
|
||||
}
|
||||
}
|
||||
|
||||
BuildRequest current = getCurrentRequest();
|
||||
|
||||
//update mining here
|
||||
if(current == null){
|
||||
if(getMineTile() != null){
|
||||
updateMining();
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
setMineTile(null);
|
||||
}
|
||||
|
||||
Tile tile = world.tile(current.x, current.y);
|
||||
|
||||
if(dst(tile) > finalPlaceDst){
|
||||
if(getPlaceQueue().size > 1){
|
||||
getPlaceQueue().removeFirst();
|
||||
getPlaceQueue().addLast(current);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!(tile.block() instanceof BuildBlock)){
|
||||
if(canCreateBlocks() && !current.breaking && Build.validPlace(getTeam(), current.x, current.y, current.block, current.rotation)){
|
||||
Call.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation);
|
||||
}else if(canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){
|
||||
Call.beginBreak(getTeam(), current.x, current.y);
|
||||
}else{
|
||||
getPlaceQueue().removeFirst();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TileEntity core = unit.getClosestCore();
|
||||
|
||||
//if there is no core to build with or no build entity, stop building!
|
||||
if((core == null && !state.rules.infiniteResources) || !(tile.entity instanceof BuildEntity)){
|
||||
return;
|
||||
}
|
||||
|
||||
//otherwise, update it.
|
||||
BuildEntity entity = tile.entity();
|
||||
|
||||
if(entity == null){
|
||||
return;
|
||||
}
|
||||
|
||||
if(unit.dst(tile) <= finalPlaceDst){
|
||||
unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f);
|
||||
}
|
||||
|
||||
//progress is synced, thus not updated clientside
|
||||
if(!Net.client()){
|
||||
//deconstructing is 2x as fast
|
||||
if(current.breaking){
|
||||
entity.deconstruct(unit, core, 2f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
|
||||
}else{
|
||||
entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
|
||||
}
|
||||
|
||||
current.progress = entity.progress();
|
||||
}else{
|
||||
entity.progress = current.progress;
|
||||
}
|
||||
|
||||
if(!current.initialized){
|
||||
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, unit.getTeam(), this, current.breaking)));
|
||||
current.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the queue for storing build requests. */
|
||||
@@ -148,97 +236,10 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
||||
return getPlaceQueue().size == 0 ? null : getPlaceQueue().first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update building mechanism for this unit.
|
||||
* This includes mining.
|
||||
*/
|
||||
default void updateBuilding(){
|
||||
Unit unit = (Unit)this;
|
||||
//remove already completed build requests
|
||||
removal.clear();
|
||||
for(BuildRequest req : getPlaceQueue()){
|
||||
removal.add(req);
|
||||
}
|
||||
|
||||
getPlaceQueue().clear();
|
||||
|
||||
for(BuildRequest request : removal){
|
||||
if(!((request.breaking && world.tile(request.x, request.y).block() == Blocks.air) ||
|
||||
(!request.breaking && (world.tile(request.x, request.y).getRotation() == request.rotation || !request.block.rotate)
|
||||
&& world.tile(request.x, request.y).block() == request.block))){
|
||||
getPlaceQueue().addLast(request);
|
||||
}
|
||||
}
|
||||
|
||||
BuildRequest current = getCurrentRequest();
|
||||
|
||||
//update mining here
|
||||
if(current == null){
|
||||
if(getMineTile() != null){
|
||||
updateMining();
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
setMineTile(null);
|
||||
}
|
||||
|
||||
Tile tile = world.tile(current.x, current.y);
|
||||
|
||||
if(dst(tile) > placeDistance){
|
||||
if(getPlaceQueue().size > 1){
|
||||
getPlaceQueue().removeFirst();
|
||||
getPlaceQueue().addLast(current);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!(tile.block() instanceof BuildBlock)){
|
||||
if(canCreateBlocks() && !current.breaking && Build.validPlace(getTeam(), current.x, current.y, current.block, current.rotation)){
|
||||
Call.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation);
|
||||
}else if(canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){
|
||||
Call.beginBreak(getTeam(), current.x, current.y);
|
||||
}else{
|
||||
getPlaceQueue().removeFirst();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TileEntity core = unit.getClosestCore();
|
||||
|
||||
//if there is no core to build with or no build entity, stop building!
|
||||
if(core == null || !(tile.entity instanceof BuildEntity)){
|
||||
return;
|
||||
}
|
||||
|
||||
//otherwise, update it.
|
||||
BuildEntity entity = tile.entity();
|
||||
|
||||
if(entity == null){
|
||||
return;
|
||||
}
|
||||
|
||||
if(unit.dst(tile) <= placeDistance){
|
||||
unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f);
|
||||
}
|
||||
|
||||
//progress is synced, thus not updated clientside
|
||||
if(!Net.client()){
|
||||
//deconstructing is 2x as fast
|
||||
if(current.breaking){
|
||||
entity.deconstruct(unit, core, 2f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
|
||||
}else{
|
||||
entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
|
||||
}
|
||||
|
||||
current.progress = entity.progress();
|
||||
}else{
|
||||
entity.progress = current.progress;
|
||||
}
|
||||
|
||||
if(!current.initialized){
|
||||
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, unit.getTeam(), this, current.breaking)));
|
||||
current.initialized = true;
|
||||
}
|
||||
//due to iOS wierdness, this is apparently required
|
||||
class BuildDataStatic{
|
||||
static Array<BuildRequest> removal = new Array<>();
|
||||
static Vector2[] tmptr = new Vector2[]{new Vector2(), new Vector2(), new Vector2(), new Vector2()};
|
||||
}
|
||||
|
||||
/** Do not call directly. */
|
||||
@@ -291,7 +292,7 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
||||
|
||||
Tile tile = world.tile(request.x, request.y);
|
||||
|
||||
if(dst(tile) > placeDistance){
|
||||
if(dst(tile) > placeDistance && !state.isEditor()){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@ package io.anuke.mindustry.entities.traits;
|
||||
* Marks an entity as serializable.
|
||||
*/
|
||||
public interface SaveTrait extends Entity, TypeTrait, Saveable{
|
||||
byte version();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -23,9 +23,7 @@ public interface TypeTrait{
|
||||
lastRegisteredID[0]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a syncable type by ID.
|
||||
*/
|
||||
/**Gets a syncable type by ID.*/
|
||||
static Supplier<? extends TypeTrait> 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()?");
|
||||
|
||||
@@ -306,11 +306,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
||||
return type.hitsize * 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float clipSize(){
|
||||
return isBoss() ? 10000000000f : super.clipSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(){
|
||||
Call.onUnitDeath(this);
|
||||
@@ -338,6 +333,11 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
||||
return unitGroups[team.ordinal()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSave(DataOutput stream) throws IOException{
|
||||
super.writeSave(stream);
|
||||
@@ -346,8 +346,8 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
super.readSave(stream);
|
||||
public void readSave(DataInput stream, byte version) throws IOException{
|
||||
super.readSave(stream, version);
|
||||
byte type = stream.readByte();
|
||||
this.spawner = stream.readInt();
|
||||
|
||||
@@ -365,7 +365,9 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
||||
@Override
|
||||
public void read(DataInput data) throws IOException{
|
||||
float lastx = x, lasty = y, lastrot = rotation;
|
||||
super.readSave(data);
|
||||
|
||||
super.readSave(data, version());
|
||||
|
||||
this.type = content.getByID(ContentType.unit, data.readByte());
|
||||
this.spawner = data.readInt();
|
||||
|
||||
|
||||
@@ -432,7 +432,7 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
if(getCurrentRequest() == request && request.progress > 0.001f) continue;
|
||||
|
||||
if(request.breaking){
|
||||
Block block = world.tile(request.x, request.y).target().block();
|
||||
Block block = world.ltile(request.x, request.y).block();
|
||||
|
||||
//draw removal request
|
||||
Lines.stroke(2f, Pal.removeBack);
|
||||
@@ -636,13 +636,17 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
}
|
||||
|
||||
protected void updateShooting(){
|
||||
if(isShooting() && mech.canShoot(this)){
|
||||
if(!state.isEditor() && isShooting() && mech.canShoot(this)){
|
||||
mech.weapon.update(this, pointerX, pointerY);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateFlying(){
|
||||
if(Units.invalidateTarget(target, this) && !(target instanceof TileEntity && ((TileEntity)target).damaged() && target.isValid() && ((TileEntity)target).isAdded() && target.getTeam() == team && mech.canHeal && dst(target) < getWeapon().bullet.range())){
|
||||
if(Units.invalidateTarget(target, this) && !(target instanceof TileEntity && ((TileEntity)target).damaged() && target.isValid() && target.getTeam() == team && mech.canHeal && dst(target) < getWeapon().bullet.range())){
|
||||
target = null;
|
||||
}
|
||||
|
||||
if(state.isEditor()){
|
||||
target = null;
|
||||
}
|
||||
|
||||
@@ -790,7 +794,11 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
|
||||
public void updateRespawning(){
|
||||
|
||||
if(spawner != null && spawner.isValid()){
|
||||
if(state.isEditor()){
|
||||
//instant respawn at center of map.
|
||||
set(world.width() * tilesize/2f, world.height() * tilesize/2f);
|
||||
setDead(false);
|
||||
}else if(spawner != null && spawner.isValid()){
|
||||
spawner.updateSpawning(this);
|
||||
}else if(!netServer.isWaitingForPlayers()){
|
||||
if(!Net.client()){
|
||||
@@ -817,8 +825,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 +841,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 +852,14 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
lastSpawner = (SpawnerTrait)stile.entity;
|
||||
}
|
||||
Player player = headless ? this : Vars.player;
|
||||
player.readSaveSuper(stream);
|
||||
player.readSaveSuper(stream, version);
|
||||
player.mech = content.getByID(ContentType.mech, mechid);
|
||||
player.dead = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void readSaveSuper(DataInput stream) throws IOException{
|
||||
super.readSave(stream);
|
||||
private void readSaveSuper(DataInput stream, byte version) throws IOException{
|
||||
super.readSave(stream, version);
|
||||
|
||||
add();
|
||||
}
|
||||
@@ -859,7 +867,7 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
@Override
|
||||
public void write(DataOutput buffer) throws IOException{
|
||||
super.writeSave(buffer, !isLocal);
|
||||
TypeIO.writeStringData(buffer, name); //TODO writing strings is very inefficient
|
||||
TypeIO.writeStringData(buffer, name);
|
||||
buffer.writeByte(Pack.byteValue(isAdmin) | (Pack.byteValue(dead) << 1) | (Pack.byteValue(isBoosting) << 2) | (Pack.byteValue(isTyping) << 3));
|
||||
buffer.writeInt(Color.rgba8888(color));
|
||||
buffer.writeByte(mech.id);
|
||||
@@ -873,7 +881,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;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package io.anuke.mindustry.entities.type;
|
||||
|
||||
import io.anuke.annotations.Annotations.Loc;
|
||||
import io.anuke.annotations.Annotations.Remote;
|
||||
import io.anuke.annotations.Annotations.*;
|
||||
import io.anuke.arc.Events;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.ObjectSet;
|
||||
import io.anuke.arc.math.geom.Point2;
|
||||
import io.anuke.arc.math.geom.Vector2;
|
||||
import io.anuke.arc.util.Interval;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.mindustry.entities.EntityGroup;
|
||||
import io.anuke.mindustry.entities.bullet.Bullet;
|
||||
import io.anuke.mindustry.entities.impl.BaseEntity;
|
||||
@@ -115,16 +113,35 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
|
||||
return dead || tile.entity != this;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void write(DataOutput stream) throws IOException{
|
||||
stream.writeShort((short)health);
|
||||
stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.rotation())); //team + rotation
|
||||
if(items != null) items.write(stream);
|
||||
if(power != null) power.write(stream);
|
||||
if(liquids != null) liquids.write(stream);
|
||||
if(cons != null) cons.write(stream);
|
||||
}
|
||||
|
||||
public void writeConfig(DataOutput stream) throws IOException{
|
||||
@CallSuper
|
||||
public void read(DataInput stream, byte revision) throws IOException{
|
||||
health = stream.readUnsignedShort();
|
||||
byte tr = stream.readByte();
|
||||
byte team = Pack.leftByte(tr);
|
||||
byte rotation = Pack.rightByte(tr);
|
||||
|
||||
tile.setTeam(Team.all[team]);
|
||||
tile.rotation(rotation);
|
||||
|
||||
if(items != null) items.read(stream);
|
||||
if(power != null) power.read(stream);
|
||||
if(liquids != null) liquids.read(stream);
|
||||
if(cons != null) cons.read(stream);
|
||||
}
|
||||
|
||||
public void read(DataInput stream) throws IOException{
|
||||
}
|
||||
|
||||
public void readConfig(DataInput stream) throws IOException{
|
||||
/** Returns the version of this TileEntity IO code.*/
|
||||
public byte version(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean collide(Bullet other){
|
||||
@@ -168,14 +185,14 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
|
||||
|
||||
Point2[] nearby = Edges.getEdges(block.size);
|
||||
for(Point2 point : nearby){
|
||||
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
|
||||
Tile other = world.ltile(tile.x + point.x, tile.y + point.y);
|
||||
//remove this tile from all nearby tile's proximities
|
||||
if(other != null){
|
||||
other = other.target();
|
||||
other.block().onProximityUpdate(other);
|
||||
}
|
||||
if(other != null && other.entity != null){
|
||||
other.entity.proximity.removeValue(tile, true);
|
||||
|
||||
if(other.entity != null){
|
||||
other.entity.proximity.removeValue(tile, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,10 +203,9 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
|
||||
|
||||
Point2[] nearby = Edges.getEdges(block.size);
|
||||
for(Point2 point : nearby){
|
||||
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
|
||||
Tile other = world.ltile(tile.x + point.x, tile.y + point.y);
|
||||
|
||||
if(other == null) continue;
|
||||
other = other.target();
|
||||
if(other.entity == null || !(other.interactable(tile.getTeam()))) continue;
|
||||
|
||||
other.block().onProximityUpdate(other);
|
||||
@@ -280,6 +296,11 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(){
|
||||
return !isDead() && tile.entity == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityGroup targetGroup(){
|
||||
return tileGroup;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.anuke.mindustry.entities.type;
|
||||
|
||||
import io.anuke.annotations.Annotations.Nullable;
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.Events;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
@@ -8,7 +9,8 @@ import io.anuke.arc.graphics.g2d.TextureRegion;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.math.geom.Geometry;
|
||||
import io.anuke.arc.math.geom.Vector2;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.util.Tmp;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.content.Fx;
|
||||
import io.anuke.mindustry.entities.*;
|
||||
@@ -138,7 +140,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
public void readSave(DataInput stream, byte version) throws IOException{
|
||||
byte team = stream.readByte();
|
||||
boolean dead = stream.readBoolean();
|
||||
float x = stream.readFloat();
|
||||
@@ -150,7 +152,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
|
||||
byte itemID = stream.readByte();
|
||||
short itemAmount = stream.readShort();
|
||||
|
||||
this.status.readSave(stream);
|
||||
this.status.readSave(stream, version);
|
||||
this.item.amount = itemAmount;
|
||||
this.item.item = content.item(itemID);
|
||||
this.dead = dead;
|
||||
@@ -221,7 +223,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
|
||||
velocity.add(moveVector.x / mass() * Time.delta(), moveVector.y / mass() * Time.delta());
|
||||
}
|
||||
|
||||
public TileEntity getClosestCore(){
|
||||
public @Nullable TileEntity getClosestCore(){
|
||||
TeamData data = state.teams.get(team);
|
||||
|
||||
Tile tile = Geometry.findClosest(x, y, data.cores);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +1,64 @@
|
||||
package io.anuke.mindustry.game;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.function.Supplier;
|
||||
import io.anuke.arc.function.Consumer;
|
||||
|
||||
/** Defines preset rule sets.. */
|
||||
public enum Gamemode{
|
||||
survival(() -> new Rules(){{
|
||||
waveTimer = true;
|
||||
waves = true;
|
||||
unitDrops = true;
|
||||
spawns = DefaultWaves.get();
|
||||
}}),
|
||||
sandbox(() -> new Rules(){{
|
||||
infiniteResources = true;
|
||||
waves = true;
|
||||
waveTimer = false;
|
||||
respawnTime = 0f;
|
||||
}}),
|
||||
attack(() -> new Rules(){{
|
||||
enemyCheat = true;
|
||||
unitDrops = true;
|
||||
waves = false;
|
||||
attackMode = true;
|
||||
}}),
|
||||
pvp(() -> new Rules(){{
|
||||
pvp = true;
|
||||
enemyCoreBuildRadius = 600f;
|
||||
respawnTime = 60 * 10;
|
||||
buildCostMultiplier = 0.5f;
|
||||
buildSpeedMultiplier = 2f;
|
||||
playerDamageMultiplier = 0.45f;
|
||||
playerHealthMultiplier = 0.8f;
|
||||
unitBuildSpeedMultiplier = 3f;
|
||||
unitHealthMultiplier = 2f;
|
||||
attackMode = true;
|
||||
}});
|
||||
survival(rules -> {
|
||||
rules.waveTimer = true;
|
||||
rules.waves = true;
|
||||
rules.unitDrops = true;
|
||||
}),
|
||||
sandbox(rules -> {
|
||||
rules.infiniteResources = true;
|
||||
rules.waves = true;
|
||||
rules.waveTimer = false;
|
||||
rules.respawnTime = 0f;
|
||||
}),
|
||||
attack(rules -> {
|
||||
rules.enemyCheat = true;
|
||||
rules.unitDrops = true;
|
||||
rules.waves = false;
|
||||
rules.attackMode = true;
|
||||
}),
|
||||
pvp(rules -> {
|
||||
rules.pvp = true;
|
||||
rules.enemyCoreBuildRadius = 600f;
|
||||
rules.respawnTime = 60 * 10;
|
||||
rules.buildCostMultiplier = 0.5f;
|
||||
rules.buildSpeedMultiplier = 2f;
|
||||
rules.playerDamageMultiplier = 0.45f;
|
||||
rules.playerHealthMultiplier = 0.8f;
|
||||
rules.unitBuildSpeedMultiplier = 3f;
|
||||
rules.unitHealthMultiplier = 2f;
|
||||
rules.attackMode = true;
|
||||
}),
|
||||
editor(true, rules -> {
|
||||
rules.infiniteResources = true;
|
||||
rules.editor = true;
|
||||
rules.waves = false;
|
||||
rules.enemyCoreBuildRadius = 0f;
|
||||
rules.waveTimer = false;
|
||||
rules.respawnTime = 0f;
|
||||
});
|
||||
|
||||
private final Supplier<Rules> rules;
|
||||
private final Consumer<Rules> rules;
|
||||
public final boolean hidden;
|
||||
|
||||
Gamemode(Supplier<Rules> rules){
|
||||
this.rules = rules;
|
||||
Gamemode(Consumer<Rules> rules){
|
||||
this(false, rules);
|
||||
}
|
||||
|
||||
public Rules get(){
|
||||
return rules.get();
|
||||
Gamemode(boolean hidden, Consumer<Rules> rules){
|
||||
this.rules = rules;
|
||||
this.hidden = hidden;
|
||||
}
|
||||
|
||||
/** Applies this preset to this ruleset. */
|
||||
public Rules apply(Rules in){
|
||||
rules.accept(in);
|
||||
return in;
|
||||
}
|
||||
|
||||
public String description(){
|
||||
|
||||
@@ -2,6 +2,8 @@ package io.anuke.mindustry.game;
|
||||
|
||||
import io.anuke.annotations.Annotations.Serialize;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.mindustry.io.JsonIO;
|
||||
import io.anuke.mindustry.type.Zone;
|
||||
|
||||
/**
|
||||
* Defines current rules on how the game should function.
|
||||
@@ -47,10 +49,10 @@ public class Rules{
|
||||
public float bossWaveMultiplier = 3f;
|
||||
/** How many times longer a launch wave takes. */
|
||||
public float launchWaveMultiplier = 2f;
|
||||
/** Zone ID, -1 for invalid zone. */
|
||||
public byte zone = -1;
|
||||
/** Zone for saves that have them.*/
|
||||
public Zone zone;
|
||||
/** Spawn layout. Should be assigned on save load based on map or zone. */
|
||||
public transient Array<SpawnGroup> spawns = DefaultWaves.get();
|
||||
public Array<SpawnGroup> spawns = DefaultWaves.get();
|
||||
/** Determines if there should be limited respawns. */
|
||||
public boolean limitedRespawns = false;
|
||||
/** How many times player can respawn during one wave. */
|
||||
@@ -59,4 +61,11 @@ public class Rules{
|
||||
public boolean waitForWaveToEnd = false;
|
||||
/** Determinates if gamemode is attack mode */
|
||||
public boolean attackMode = false;
|
||||
/** Whether this is the editor gamemode. */
|
||||
public boolean editor = false;
|
||||
|
||||
/** Copies this ruleset exactly. Not very efficient at all, do not use often. */
|
||||
public Rules copy(){
|
||||
return JsonIO.read(Rules.class, JsonIO.write(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,21 @@ import io.anuke.arc.Core;
|
||||
import io.anuke.arc.Events;
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.util.Strings;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.game.EventType.StateChangeEvent;
|
||||
import io.anuke.mindustry.io.SaveIO;
|
||||
import io.anuke.mindustry.io.SaveIO.SaveException;
|
||||
import io.anuke.mindustry.io.SaveMeta;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.type.Zone;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
import static io.anuke.mindustry.Vars.saveExtension;
|
||||
import static io.anuke.mindustry.Vars.state;
|
||||
|
||||
public class Saves{
|
||||
private int nextSlot;
|
||||
@@ -52,7 +51,7 @@ public class Saves{
|
||||
SaveSlot slot = new SaveSlot(index);
|
||||
saves.add(slot);
|
||||
saveMap.put(slot.index, slot);
|
||||
slot.meta = SaveIO.getData(index);
|
||||
slot.meta = SaveIO.getMeta(index);
|
||||
nextSlot = Math.max(index + 1, nextSlot);
|
||||
}
|
||||
}
|
||||
@@ -134,7 +133,7 @@ public class Saves{
|
||||
slot.setName(file.nameWithoutExtension());
|
||||
saves.add(slot);
|
||||
saveMap.put(slot.index, slot);
|
||||
slot.meta = SaveIO.getData(slot.index);
|
||||
slot.meta = SaveIO.getMeta(slot.index);
|
||||
current = slot;
|
||||
saveSlots();
|
||||
return slot;
|
||||
@@ -172,7 +171,7 @@ public class Saves{
|
||||
public void load() throws SaveException{
|
||||
try{
|
||||
SaveIO.loadFromSlot(index);
|
||||
meta = SaveIO.getData(index);
|
||||
meta = SaveIO.getMeta(index);
|
||||
current = this;
|
||||
totalPlaytime = meta.timePlayed;
|
||||
}catch(Exception e){
|
||||
@@ -186,7 +185,7 @@ public class Saves{
|
||||
totalPlaytime = time;
|
||||
|
||||
SaveIO.saveToSlot(index);
|
||||
meta = SaveIO.getData(index);
|
||||
meta = SaveIO.getMeta(index);
|
||||
if(!state.is(State.menu)){
|
||||
current = this;
|
||||
}
|
||||
@@ -224,7 +223,7 @@ public class Saves{
|
||||
}
|
||||
|
||||
public Zone getZone(){
|
||||
return meta == null || meta.rules == null ? null : content.getByID(ContentType.zone, meta.rules.zone);
|
||||
return meta == null || meta.rules == null ? null : meta.rules.zone;
|
||||
}
|
||||
|
||||
public int getBuild(){
|
||||
|
||||
@@ -9,7 +9,7 @@ import io.anuke.mindustry.type.*;
|
||||
@Serialize
|
||||
public class Stats{
|
||||
/** Items delivered to global resoure counter. Zones only. */
|
||||
public ObjectIntMap<Item> itemsDelivered = new ObjectIntMap<>();
|
||||
public transient ObjectIntMap<Item> itemsDelivered = new ObjectIntMap<>();
|
||||
/** Enemy (red team) units destroyed. */
|
||||
public int enemyUnitsDestroyed;
|
||||
/** Total waves lasted. */
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -62,7 +62,7 @@ public class FloorRenderer implements Disposable{
|
||||
|
||||
//loop through all layers, and add layer index if it exists
|
||||
for(int i = 0; i < layers; i++){
|
||||
if(chunk.caches[i] != -1){
|
||||
if(chunk.caches[i] != -1 && i != CacheLayer.walls.ordinal()){
|
||||
drawnLayerSet.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -5,7 +5,6 @@ import io.anuke.arc.graphics.g2d.*;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.Tmp;
|
||||
|
||||
//TODO remove
|
||||
public class Shapes{
|
||||
|
||||
public static void laser(String line, String edge, float x, float y, float x2, float y2, float scale){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -416,9 +415,8 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
|
||||
for(int x = dresult.x; x <= dresult.x2; x++){
|
||||
for(int y = dresult.y; y <= dresult.y2; y++){
|
||||
Tile other = world.tile(x, y);
|
||||
Tile other = world.ltile(x, y);
|
||||
if(other == null || !validBreak(other.x, other.y)) continue;
|
||||
other = other.target();
|
||||
|
||||
Draw.color(Pal.removeBack);
|
||||
Lines.square(other.drawx(), other.drawy() - 1, other.block().size * tilesize / 2f - 1);
|
||||
@@ -514,12 +512,10 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
int wx = lineStartX + x * Mathf.sign(tileX - lineStartX);
|
||||
int wy = lineStartY + y * Mathf.sign(tileY - lineStartY);
|
||||
|
||||
Tile tar = world.tile(wx, wy);
|
||||
Tile tar = world.ltile(wx, wy);
|
||||
|
||||
if(tar == null) continue;
|
||||
|
||||
tar = tar.target();
|
||||
|
||||
if(!hasRequest(world.tile(tar.x, tar.y)) && validBreak(tar.x, tar.y)){
|
||||
PlaceRequest request = new PlaceRequest(tar.x, tar.y);
|
||||
request.scale = 1f;
|
||||
@@ -535,7 +531,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
|
||||
if(tile == null) return false;
|
||||
|
||||
tryDropItems(tile.target(), Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y);
|
||||
tryDropItems(tile.link(), Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -585,11 +581,11 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
}else if(mode == placing && isPlacing() && validPlace(cursor.x, cursor.y, block, rotation) && !checkOverlapPlacement(cursor.x, cursor.y, block)){
|
||||
//add to selection queue if it's a valid place position
|
||||
selection.add(lastPlaced = new PlaceRequest(cursor.x, cursor.y, block, rotation));
|
||||
}else if(mode == breaking && validBreak(cursor.target().x, cursor.target().y) && !hasRequest(cursor.target())){
|
||||
}else if(mode == breaking && validBreak(cursor.link().x, cursor.link().y) && !hasRequest(cursor.link())){
|
||||
//add to selection queue if it's a valid BREAK position
|
||||
cursor = cursor.target();
|
||||
cursor = cursor.link();
|
||||
selection.add(new PlaceRequest(cursor.x, cursor.y));
|
||||
}else if(!canTapPlayer(worldx, worldy) && !tileTapped(cursor.target())){
|
||||
}else if(!canTapPlayer(worldx, worldy) && !tileTapped(cursor.link())){
|
||||
tryBeginMine(cursor);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.util.serialization.Json;
|
||||
import io.anuke.arc.util.serialization.JsonValue;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.game.Rules;
|
||||
import io.anuke.mindustry.game.SpawnGroup;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.type.Zone;
|
||||
|
||||
public class JsonIO{
|
||||
private static Json json = new Json(){{
|
||||
setIgnoreUnknownFields(true);
|
||||
setElementType(Rules.class, "spawns", SpawnGroup.class);
|
||||
|
||||
setSerializer(Zone.class, new Serializer<Zone>(){
|
||||
@Override
|
||||
public void write(Json json, Zone object, Class knownType){
|
||||
json.writeValue(object.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Zone read(Json json, JsonValue jsonData, Class type){
|
||||
return Vars.content.getByName(ContentType.zone, jsonData.asString());
|
||||
}
|
||||
});
|
||||
}};
|
||||
|
||||
public static String write(Object object){
|
||||
return json.toJson(object);
|
||||
}
|
||||
|
||||
public static <T> T read(Class<T> type, String string){
|
||||
return json.fromJson(type, string);
|
||||
}
|
||||
|
||||
public static String print(String in){
|
||||
return json.prettyPrint(in);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
import io.anuke.arc.graphics.Pixmap;
|
||||
import io.anuke.arc.util.Pack;
|
||||
import io.anuke.arc.util.Structs;
|
||||
import io.anuke.arc.util.serialization.Json;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.game.SpawnGroup;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.io.MapIO.TileProvider;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.world.*;
|
||||
import io.anuke.mindustry.world.LegacyColorMapper.LegacyBlock;
|
||||
import io.anuke.mindustry.world.blocks.BlockPart;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
/** Map IO for the "old" .mmap format.
|
||||
* Differentiate between legacy maps and new maps by checking the extension (or the header).*/
|
||||
public class LegacyMapIO{
|
||||
private static final ObjectMap<String, String> 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<SpawnGroup> groups = new Array<>(json.fromJson(SpawnGroup[].class, waves));
|
||||
|
||||
Tile[][] tiles = world.createTiles(map.width, map.height);
|
||||
for(int x = 0; x < map.width; x++){
|
||||
for(int y = 0; y < map.height; y++){
|
||||
tiles[x][y] = new CachedTile();
|
||||
}
|
||||
}
|
||||
state.rules.spawns = groups;
|
||||
readTiles(map, tiles);
|
||||
MapIO.writeMap(out, map);
|
||||
}
|
||||
|
||||
public static Map readMap(FileHandle file, boolean custom) throws IOException{
|
||||
try(DataInputStream stream = new DataInputStream(file.read(1024))){
|
||||
StringMap tags = new StringMap();
|
||||
|
||||
//meta is uncompressed
|
||||
int version = stream.readInt();
|
||||
int build = stream.readInt();
|
||||
short width = stream.readShort(), height = stream.readShort();
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
String name = stream.readUTF();
|
||||
String value = stream.readUTF();
|
||||
tags.put(name, value);
|
||||
}
|
||||
|
||||
return new Map(file, width, height, tags, custom, version, build);
|
||||
}
|
||||
}
|
||||
|
||||
public static void readTiles(Map map, Tile[][] tiles) throws IOException{
|
||||
readTiles(map, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
public static void readTiles(Map map, TileProvider tiles) throws IOException{
|
||||
readTiles(map.file, map.width, map.height, tiles);
|
||||
}
|
||||
|
||||
private static void readTiles(FileHandle file, int width, int height, Tile[][] tiles) throws IOException{
|
||||
readTiles(file, width, height, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
private static void readTiles(FileHandle file, int width, int height, TileProvider tiles) throws IOException{
|
||||
try(BufferedInputStream input = file.read(bufferSize)){
|
||||
|
||||
//read map
|
||||
{
|
||||
DataInputStream stream = new DataInputStream(input);
|
||||
|
||||
stream.readInt(); //version
|
||||
stream.readInt(); //build
|
||||
stream.readInt(); //width + height
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
stream.readUTF(); //key
|
||||
stream.readUTF(); //val
|
||||
}
|
||||
}
|
||||
|
||||
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){
|
||||
|
||||
try{
|
||||
byte mapped = stream.readByte();
|
||||
IntMap<Block> idmap = new IntMap<>();
|
||||
IntMap<String> namemap = new IntMap<>();
|
||||
|
||||
for(int i = 0; i < mapped; i++){
|
||||
byte type = stream.readByte();
|
||||
short total = stream.readShort();
|
||||
|
||||
for(int j = 0; j < total; j++){
|
||||
String name = stream.readUTF();
|
||||
if(type == 1){
|
||||
Block res = content.getByName(ContentType.block, fallback.get(name, name));
|
||||
idmap.put(j, res == null ? Blocks.air : res);
|
||||
namemap.put(j, fallback.get(name, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//read floor and create tiles first
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
int floorid = stream.readUnsignedByte();
|
||||
int oreid = stream.readUnsignedByte();
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
Tile tile = tiles.get(x, y);
|
||||
tile.setFloor((Floor)idmap.get(floorid));
|
||||
tile.setOverlay(idmap.get(oreid));
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
Tile newTile = tiles.get(newx, newy);
|
||||
newTile.setFloor((Floor)idmap.get(floorid));
|
||||
newTile.setOverlay(idmap.get(oreid));
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//read blocks
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
int id = stream.readUnsignedByte();
|
||||
Block block = idmap.get(id);
|
||||
|
||||
Tile tile = tiles.get(x, y);
|
||||
//the spawn block is saved in the block tile layer in older maps, shift it to the overlay
|
||||
if(block != Blocks.spawn){
|
||||
tile.setBlock(block);
|
||||
}else{
|
||||
tile.setOverlay(block);
|
||||
}
|
||||
|
||||
if(namemap.get(id).equals("part")){
|
||||
stream.readByte(); //link
|
||||
}else if(tile.entity != null){
|
||||
byte tr = stream.readByte();
|
||||
stream.readShort(); //read health (which is actually irrelevant)
|
||||
|
||||
byte team = Pack.leftByte(tr);
|
||||
byte rotation = Pack.rightByte(tr);
|
||||
|
||||
tile.setTeam(Team.all[team]);
|
||||
tile.entity.health = tile.block().health;
|
||||
tile.rotation(rotation);
|
||||
|
||||
if(tile.block() == Blocks.liquidSource || tile.block() == Blocks.unloader || tile.block() == Blocks.sorter){
|
||||
stream.readByte(); //these blocks have an extra config byte, read it
|
||||
}
|
||||
}else{ //no entity/part, read consecutives
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
tiles.get(newx, newy).setBlock(block);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads a pixmap in the 3.5 pixmap format. */
|
||||
public static void readPixmap(Pixmap pixmap, Tile[][] tiles){
|
||||
for(int x = 0; x < pixmap.getWidth(); x++){
|
||||
for(int y = 0; y < pixmap.getHeight(); y++){
|
||||
int color = pixmap.getPixel(x, pixmap.getHeight() - 1 - y);
|
||||
LegacyBlock block = LegacyColorMapper.get(color);
|
||||
Tile tile = tiles[x][y];
|
||||
|
||||
tile.setFloor(block.floor);
|
||||
tile.setBlock(block.wall);
|
||||
if(block.ore != null) tile.setOverlay(block.ore);
|
||||
|
||||
//place core
|
||||
if(color == Color.rgba8888(Color.GREEN)){
|
||||
for(int dx = 0; dx < 3; dx++){
|
||||
for(int dy = 0; dy < 3; dy++){
|
||||
int worldx = dx - 1 + x;
|
||||
int worldy = dy - 1 + y;
|
||||
|
||||
//multiblock parts
|
||||
if(Structs.inBounds(worldx, worldy, pixmap.getWidth(), pixmap.getHeight())){
|
||||
Tile write = tiles[worldx][worldy];
|
||||
write.setBlock(BlockPart.get(dx - 1, dy - 1));
|
||||
write.setTeam(Team.blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//actual core parts
|
||||
tile.setBlock(Blocks.coreShard);
|
||||
tile.setTeam(Team.blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,27 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.IntIntMap;
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.ObjectMap.Entry;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
import io.anuke.arc.graphics.Pixmap;
|
||||
import io.anuke.arc.graphics.Pixmap.Format;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.arc.util.io.CounterInputStream;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.game.Version;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.world.*;
|
||||
import io.anuke.mindustry.world.LegacyColorMapper.LegacyBlock;
|
||||
import io.anuke.mindustry.world.blocks.BlockPart;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import static io.anuke.mindustry.Vars.bufferSize;
|
||||
import static io.anuke.mindustry.Vars.content;
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
/** Reads and writes map files. */
|
||||
//TODO does this class even need to exist??? move to Maps?
|
||||
public class MapIO{
|
||||
public static final int version = 1;
|
||||
|
||||
private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
|
||||
private static ObjectMap<String, Block> missingBlocks;
|
||||
|
||||
private static void initBlocks(){
|
||||
if(missingBlocks != null) return;
|
||||
|
||||
//only for legacy maps
|
||||
missingBlocks = ObjectMap.of(
|
||||
"stained-stone", Blocks.shale,
|
||||
"stained-stone-red", Blocks.shale,
|
||||
"stained-stone-yellow", Blocks.shale,
|
||||
"stained-rocks", Blocks.shaleRocks,
|
||||
"stained-boulder", Blocks.shaleBoulder,
|
||||
"stained-rocks-red", Blocks.shaleRocks,
|
||||
"stained-rocks-yellow", Blocks.shaleRocks
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean isImage(FileHandle file){
|
||||
try(InputStream stream = file.read(32)){
|
||||
@@ -62,42 +36,107 @@ public class MapIO{
|
||||
}
|
||||
}
|
||||
|
||||
public static Map createMap(FileHandle file, boolean custom) throws IOException{
|
||||
try(InputStream is = new InflaterInputStream(file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
|
||||
SaveIO.readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveVersion ver = SaveIO.getSaveWriter(version);
|
||||
StringMap tags = new StringMap();
|
||||
ver.region("meta", stream, counter, in -> tags.putAll(ver.readStringMap(in)));
|
||||
return new Map(file, tags.getInt("width"), tags.getInt("height"), tags, custom, version, Version.build);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeMap(FileHandle file, Map map) throws IOException{
|
||||
try{
|
||||
SaveIO.write(file, map.tags);
|
||||
}catch(Exception e){
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadMap(Map map){
|
||||
SaveIO.load(map.file);
|
||||
}
|
||||
|
||||
public static void loadMap(Map map, WorldContext cons){
|
||||
SaveIO.load(map.file, cons);
|
||||
}
|
||||
|
||||
public static Pixmap generatePreview(Map map) throws IOException{
|
||||
Time.mark();
|
||||
Pixmap floors = new Pixmap(map.width, map.height, Format.RGBA8888);
|
||||
Pixmap walls = new Pixmap(map.width, map.height, Format.RGBA8888);
|
||||
int black = Color.rgba8888(Color.BLACK);
|
||||
int shade = Color.rgba8888(0f, 0f, 0f, 0.5f);
|
||||
CachedTile tile = new CachedTile(){
|
||||
@Override
|
||||
public void setFloor(Floor type){
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(type, Blocks.air, Blocks.air, getTeam()));
|
||||
}
|
||||
//by default, it does not have an enemy core or any other cores
|
||||
map.tags.put("enemycore", "false");
|
||||
map.tags.put("othercore", "false");
|
||||
|
||||
@Override
|
||||
public void setOverlayID(byte b){
|
||||
if(b != 0)
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(floor(), Blocks.air, content.block(b), getTeam()));
|
||||
}
|
||||
try(InputStream is = new InflaterInputStream(map.file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
|
||||
SaveIO.readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveVersion ver = SaveIO.getSaveWriter(version);
|
||||
ver.region("meta", stream, counter, ver::readStringMap);
|
||||
|
||||
@Override
|
||||
protected void changed(){
|
||||
super.changed();
|
||||
int c = colorFor(Blocks.air, block(), Blocks.air, getTeam());
|
||||
if(c != black){
|
||||
walls.drawPixel(x, floors.getHeight() - 1 - y, c);
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y + 1, shade);
|
||||
Pixmap floors = new Pixmap(map.width, map.height, Format.RGBA8888);
|
||||
Pixmap walls = new Pixmap(map.width, map.height, Format.RGBA8888);
|
||||
int black = Color.rgba8888(Color.BLACK);
|
||||
int shade = Color.rgba8888(0f, 0f, 0f, 0.5f);
|
||||
CachedTile tile = new CachedTile(){
|
||||
@Override
|
||||
public void setBlock(Block type){
|
||||
super.setBlock(type);
|
||||
int c = colorFor(Blocks.air, block(), Blocks.air, getTeam());
|
||||
if(c != black){
|
||||
walls.drawPixel(x, floors.getHeight() - 1 - y, c);
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y + 1, shade);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
readTiles(map, (x, y) -> {
|
||||
tile.x = (short)x;
|
||||
tile.y = (short)y;
|
||||
return tile;
|
||||
});
|
||||
floors.drawPixmap(walls, 0, 0);
|
||||
walls.dispose();
|
||||
return floors;
|
||||
|
||||
@Override
|
||||
public void setTeam(Team team){
|
||||
super.setTeam(team);
|
||||
if(block instanceof CoreBlock){
|
||||
if(team != defaultTeam){
|
||||
//map must have other team's cores
|
||||
map.tags.put("othercore", "true");
|
||||
}
|
||||
|
||||
if(team == waveTeam){
|
||||
//map must have default enemy team's core
|
||||
map.tags.put("enemycore", "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ver.region("content", stream, counter, ver::readContentHeader);
|
||||
ver.region("preview_map", stream, counter, in -> ver.readMap(in, new WorldContext(){
|
||||
@Override public void resize(int width, int height){}
|
||||
@Override public boolean isGenerating(){return false;}
|
||||
@Override public void begin(){}
|
||||
@Override public void end(){}
|
||||
|
||||
@Override
|
||||
public Tile tile(int x, int y){
|
||||
tile.x = (short)x;
|
||||
tile.y = (short)y;
|
||||
return tile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
|
||||
if(overlayID != 0){
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(Blocks.air, Blocks.air, content.block(overlayID), Team.none));
|
||||
}else{
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(content.block(floorID), Blocks.air, Blocks.air, Team.none));
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
}));
|
||||
|
||||
floors.drawPixmap(walls, 0, 0);
|
||||
walls.dispose();
|
||||
return floors;
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static Pixmap generatePreview(Tile[][] tiles){
|
||||
@@ -118,360 +157,6 @@ public class MapIO{
|
||||
return Color.rgba8888(wall.solid ? wall.color : ore == Blocks.air ? floor.color : ore.color);
|
||||
}
|
||||
|
||||
public static void writeMap(FileHandle file, Map map, Tile[][] tiles) throws IOException{
|
||||
OutputStream output = file.write(false, bufferSize);
|
||||
|
||||
{
|
||||
DataOutputStream stream = new DataOutputStream(output);
|
||||
stream.writeInt(version);
|
||||
stream.writeInt(Version.build);
|
||||
stream.writeShort(tiles.length);
|
||||
stream.writeShort(tiles[0].length);
|
||||
stream.writeByte((byte)map.tags.size);
|
||||
|
||||
for(Entry<String, String> entry : map.tags.entries()){
|
||||
stream.writeUTF(entry.key);
|
||||
stream.writeUTF(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
try(DataOutputStream stream = new DataOutputStream(new DeflaterOutputStream(output))){
|
||||
int width = map.width, height = map.height;
|
||||
|
||||
SaveIO.getSaveWriter().writeContentHeader(stream);
|
||||
|
||||
//floor first
|
||||
for(int i = 0; i < tiles.length * tiles[0].length; i++){
|
||||
Tile tile = tiles[i % width][i / width];
|
||||
stream.writeByte(tile.getFloorID());
|
||||
stream.writeByte(tile.getOverlayID());
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < width * height && consecutives < 255; j++){
|
||||
Tile nextTile = tiles[j % width][j / width];
|
||||
|
||||
if(nextTile.getFloorID() != tile.getFloorID() || nextTile.block() != Blocks.air || nextTile.getOverlayID() != tile.getOverlayID()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//then blocks
|
||||
for(int i = 0; i < tiles.length * tiles[0].length; i++){
|
||||
Tile tile = tiles[i % width][i / width];
|
||||
stream.writeByte(tile.getBlockID());
|
||||
|
||||
if(tile.block() instanceof BlockPart){
|
||||
stream.writeByte(tile.getLinkByte());
|
||||
}else if(tile.entity != null){
|
||||
stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation
|
||||
stream.writeShort(/*(short)tile.entity.health*/tile.block().health); //health
|
||||
tile.entity.writeConfig(stream);
|
||||
}else{
|
||||
//write consecutive non-entity blocks
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < width * height && consecutives < 255; j++){
|
||||
Tile nextTile = tiles[j % width][j / width];
|
||||
|
||||
if(nextTile.block() != tile.block()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Map readMap(FileHandle file, boolean custom) throws IOException{
|
||||
try(DataInputStream stream = new DataInputStream(file.read(1024))){
|
||||
ObjectMap<String, String> tags = new ObjectMap<>();
|
||||
|
||||
//meta is uncompressed
|
||||
int version = stream.readInt();
|
||||
if(version == 0){
|
||||
return readLegacyMap(file, custom);
|
||||
}
|
||||
int build = stream.readInt();
|
||||
short width = stream.readShort(), height = stream.readShort();
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
String name = stream.readUTF();
|
||||
String value = stream.readUTF();
|
||||
tags.put(name, value);
|
||||
}
|
||||
|
||||
return new Map(file, width, height, tags, custom, version, build);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads tiles from a map, version-agnostic. */
|
||||
public static void readTiles(Map map, Tile[][] tiles) throws IOException{
|
||||
readTiles(map, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
/** Reads tiles from a map, version-agnostic. */
|
||||
public static void readTiles(Map map, TileProvider tiles) throws IOException{
|
||||
if(map.version == 0){
|
||||
readLegacyMmapTiles(map.file, tiles);
|
||||
}else if(map.version == version){
|
||||
readTiles(map.file, map.width, map.height, tiles);
|
||||
}else{
|
||||
throw new IOException("Unknown map version. What?");
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads tiles from a map in the new build-65 format. */
|
||||
private static void readTiles(FileHandle file, int width, int height, Tile[][] tiles) throws IOException{
|
||||
readTiles(file, width, height, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
/** Reads tiles from a map in the new build-65 format. */
|
||||
private static void readTiles(FileHandle file, int width, int height, TileProvider tiles) throws IOException{
|
||||
try(BufferedInputStream input = file.read(bufferSize)){
|
||||
|
||||
//read map
|
||||
{
|
||||
DataInputStream stream = new DataInputStream(input);
|
||||
|
||||
stream.readInt(); //version
|
||||
stream.readInt(); //build
|
||||
stream.readInt(); //width + height
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
stream.readUTF(); //key
|
||||
stream.readUTF(); //val
|
||||
}
|
||||
}
|
||||
|
||||
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){
|
||||
|
||||
MappableContent[][] c = SaveIO.getSaveWriter().readContentHeader(stream);
|
||||
|
||||
try{
|
||||
content.setTemporaryMapper(c);
|
||||
|
||||
//read floor and create tiles first
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
byte floorid = stream.readByte();
|
||||
byte oreid = stream.readByte();
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
Tile tile = tiles.get(x, y);
|
||||
tile.setFloor((Floor)content.block(floorid));
|
||||
tile.setOverlay(content.block(oreid));
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
Tile newTile = tiles.get(newx, newy);
|
||||
newTile.setFloor((Floor)content.block(floorid));
|
||||
newTile.setOverlay(content.block(oreid));
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//read blocks
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
Block block = content.block(stream.readByte());
|
||||
|
||||
Tile tile = tiles.get(x, y);
|
||||
tile.setBlock(block);
|
||||
|
||||
if(block == Blocks.part){
|
||||
tile.setLinkByte(stream.readByte());
|
||||
}else if(tile.entity != null){
|
||||
byte tr = stream.readByte();
|
||||
short health = stream.readShort();
|
||||
|
||||
byte team = Pack.leftByte(tr);
|
||||
byte rotation = Pack.rightByte(tr);
|
||||
|
||||
tile.setTeam(Team.all[team]);
|
||||
tile.entity.health = /*health*/tile.block().health;
|
||||
tile.setRotation(rotation);
|
||||
|
||||
tile.entity.readConfig(stream);
|
||||
}else{ //no entity/part, read consecutives
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
tiles.get(newx, newy).setBlock(block);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//region legacy IO
|
||||
|
||||
/** Reads a pixmap in the 3.5 pixmap format. */
|
||||
public static void readLegacyPixmap(Pixmap pixmap, Tile[][] tiles){
|
||||
for(int x = 0; x < pixmap.getWidth(); x++){
|
||||
for(int y = 0; y < pixmap.getHeight(); y++){
|
||||
int color = pixmap.getPixel(x, pixmap.getHeight() - 1 - y);
|
||||
LegacyBlock block = LegacyColorMapper.get(color);
|
||||
Tile tile = tiles[x][y];
|
||||
|
||||
tile.setFloor(block.floor);
|
||||
tile.setBlock(block.wall);
|
||||
if(block.ore != null) tile.setOverlay(block.ore);
|
||||
|
||||
//place core
|
||||
if(color == Color.rgba8888(Color.GREEN)){
|
||||
for(int dx = 0; dx < 3; dx++){
|
||||
for(int dy = 0; dy < 3; dy++){
|
||||
int worldx = dx - 1 + x;
|
||||
int worldy = dy - 1 + y;
|
||||
|
||||
//multiblock parts
|
||||
if(Structs.inBounds(worldx, worldy, pixmap.getWidth(), pixmap.getHeight())){
|
||||
Tile write = tiles[worldx][worldy];
|
||||
write.setBlock(Blocks.part);
|
||||
write.setTeam(Team.blue);
|
||||
write.setLinkByte(Pack.byteByte((byte)(dx - 1 + 8), (byte)(dy - 1 + 8)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//actual core parts
|
||||
tile.setBlock(Blocks.coreShard);
|
||||
tile.setTeam(Team.blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads a pixmap in the old 4.0 .mmap format. */
|
||||
private static void readLegacyMmapTiles(FileHandle file, Tile[][] tiles) throws IOException{
|
||||
readLegacyMmapTiles(file, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
/** Reads a mmap in the old 4.0 .mmap format. */
|
||||
private static void readLegacyMmapTiles(FileHandle file, TileProvider tiles) throws IOException{
|
||||
try(DataInputStream stream = new DataInputStream(file.read(bufferSize))){
|
||||
stream.readInt(); //version
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
stream.readUTF(); //key
|
||||
stream.readUTF(); //val
|
||||
}
|
||||
|
||||
initBlocks();
|
||||
|
||||
//block id -> real id map
|
||||
IntIntMap map = new IntIntMap();
|
||||
IntIntMap oreMap = new IntIntMap();
|
||||
|
||||
short blocks = stream.readShort();
|
||||
for(int i = 0; i < blocks; i++){
|
||||
short id = stream.readShort();
|
||||
String name = stream.readUTF();
|
||||
Block block = content.getByName(ContentType.block, name);
|
||||
if(block == null){
|
||||
//substitute for replacement in missingBlocks if possible
|
||||
if(missingBlocks.containsKey(name)){
|
||||
block = missingBlocks.get(name);
|
||||
}else if(name.startsWith("ore-")){ //an ore floor combination
|
||||
String[] split = name.split("-");
|
||||
String itemName = split[1], floorName = Strings.join("-", Arrays.copyOfRange(split, 2, split.length));
|
||||
Item item = content.getByName(ContentType.item, itemName);
|
||||
Block oreBlock = item == null ? null : content.getByName(ContentType.block, "ore-" + item.name);
|
||||
Block floor = missingBlocks.get(floorName, content.getByName(ContentType.block, floorName));
|
||||
if(oreBlock != null && floor != null){
|
||||
oreMap.put(id, oreBlock.id);
|
||||
block = floor;
|
||||
}else{
|
||||
block = Blocks.air;
|
||||
}
|
||||
}else{
|
||||
block = Blocks.air;
|
||||
}
|
||||
|
||||
}
|
||||
map.put(id, block.id);
|
||||
}
|
||||
short width = stream.readShort(), height = stream.readShort();
|
||||
|
||||
for(int y = 0; y < height; y++){
|
||||
for(int x = 0; x < width; x++){
|
||||
Tile tile = tiles.get(x, y);
|
||||
byte floorb = stream.readByte();
|
||||
byte blockb = stream.readByte();
|
||||
byte link = stream.readByte();
|
||||
byte rotTeamb = stream.readByte();
|
||||
stream.readByte();//unused stuff
|
||||
|
||||
tile.setFloor((Floor)content.block(map.get(floorb, 0)));
|
||||
tile.setBlock(content.block(map.get(blockb, 0)));
|
||||
tile.setRotation(Pack.leftByte(rotTeamb));
|
||||
if(tile.block().synthetic()){
|
||||
tile.setTeam(Team.all[Mathf.clamp(Pack.rightByte(rotTeamb), 0, Team.all.length)]);
|
||||
}
|
||||
|
||||
if(tile.block() == Blocks.part){
|
||||
tile.setLinkByte(link);
|
||||
}
|
||||
|
||||
if(oreMap.containsKey(floorb)){
|
||||
tile.setOverlay(content.block(oreMap.get(floorb, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map readLegacyMap(FileHandle file, boolean custom) throws IOException{
|
||||
try(DataInputStream stream = new DataInputStream(file.read(bufferSize))){
|
||||
ObjectMap<String, String> tags = new ObjectMap<>();
|
||||
|
||||
int version = stream.readInt();
|
||||
if(version != 0) throw new IOException("Attempted to read non-legacy map in legacy method!");
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
String name = stream.readUTF();
|
||||
String value = stream.readUTF();
|
||||
tags.put(name, value);
|
||||
}
|
||||
|
||||
short blocks = stream.readShort();
|
||||
for(int i = 0; i < blocks; i++){
|
||||
stream.readShort();
|
||||
stream.readUTF();
|
||||
}
|
||||
short width = stream.readShort(), height = stream.readShort();
|
||||
|
||||
//note that build 64 is the default build of all maps <65; while this can be inaccurate it's better than nothing
|
||||
return new Map(file, width, height, tags, custom, 0, 64);
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
interface TileProvider{
|
||||
Tile get(int x, int y);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.ObjectMap.Entry;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.util.io.CounterInputStream;
|
||||
import io.anuke.arc.util.io.ReusableByteOutStream;
|
||||
import io.anuke.mindustry.world.WorldContext;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public abstract class SaveFileReader{
|
||||
protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream();
|
||||
protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput);
|
||||
protected final ReusableByteOutStream byteOutputSmall = new ReusableByteOutStream();
|
||||
protected final DataOutputStream dataBytesSmall = new DataOutputStream(byteOutputSmall);
|
||||
protected final ObjectMap<String, String> fallback = ObjectMap.of();
|
||||
|
||||
protected void region(String name, DataInput stream, CounterInputStream counter, IORunner<DataInput> cons) throws IOException{
|
||||
counter.resetCount();
|
||||
int length;
|
||||
try{
|
||||
length = readChunk(stream, cons);
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Error reading region \"" + name + "\".", e);
|
||||
}
|
||||
|
||||
if(length != counter.count() - 4){
|
||||
throw new IOException("Error reading region \"" + name + "\": read length mismatch. Expected: " + length + "; Actual: " + (counter.count() - 4));
|
||||
}
|
||||
}
|
||||
|
||||
protected void region(String name, DataOutput stream, IORunner<DataOutput> cons) throws IOException{
|
||||
try{
|
||||
writeChunk(stream, cons);
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Error writing region \"" + name + "\".", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeChunk(DataOutput output, IORunner<DataOutput> runner) throws IOException{
|
||||
writeChunk(output, false, runner);
|
||||
}
|
||||
|
||||
/** Write a chunk of input to the stream. An integer of some length is written first, followed by the data. */
|
||||
public void writeChunk(DataOutput output, boolean isByte, IORunner<DataOutput> runner) throws IOException{
|
||||
ReusableByteOutStream dout = isByte ? byteOutputSmall : byteOutput;
|
||||
//reset output position
|
||||
dout.reset();
|
||||
//write the needed info
|
||||
runner.accept(isByte ? dataBytesSmall : dataBytes);
|
||||
int length = dout.size();
|
||||
//write length (either int or byte) followed by the output bytes
|
||||
if(!isByte){
|
||||
output.writeInt(length);
|
||||
}else{
|
||||
if(length > Short.MAX_VALUE){
|
||||
throw new IOException("Byte write length exceeded: " + length + " > " + Short.MAX_VALUE);
|
||||
}
|
||||
output.writeShort(length);
|
||||
}
|
||||
output.write(dout.getBytes(), 0, length);
|
||||
}
|
||||
|
||||
public int readChunk(DataInput input, IORunner<DataInput> 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<DataInput> runner) throws IOException{
|
||||
int length = isByte ? input.readUnsignedShort() : input.readInt();
|
||||
runner.accept(input);
|
||||
return length;
|
||||
}
|
||||
|
||||
public void skipRegion(DataInput input) throws IOException{
|
||||
skipRegion(input, false);
|
||||
}
|
||||
|
||||
/** Skip a region completely. */
|
||||
public void skipRegion(DataInput input, boolean isByte) throws IOException{
|
||||
int length = readChunk(input, isByte, t -> {});
|
||||
int skipped = input.skipBytes(length);
|
||||
if(length != skipped){
|
||||
throw new IOException("Could not skip bytes. Expected length: " + length + "; Actual length: " + skipped);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeStringMap(DataOutput stream, ObjectMap<String, String> map) throws IOException{
|
||||
stream.writeShort(map.size);
|
||||
for(Entry<String, String> entry : map.entries()){
|
||||
stream.writeUTF(entry.key);
|
||||
stream.writeUTF(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
public StringMap readStringMap(DataInput stream) throws IOException{
|
||||
StringMap map = new StringMap();
|
||||
short size = stream.readShort();
|
||||
for(int i = 0; i < size; i++){
|
||||
map.put(stream.readUTF(), stream.readUTF());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public abstract void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException;
|
||||
|
||||
public abstract void write(DataOutputStream stream) throws IOException;
|
||||
|
||||
protected interface IORunner<T>{
|
||||
void accept(T stream) throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.util.Pack;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.entities.Entities;
|
||||
import io.anuke.mindustry.entities.EntityGroup;
|
||||
import io.anuke.mindustry.entities.traits.*;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.gen.Serialization;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.content;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public abstract class SaveFileVersion{
|
||||
public final int version;
|
||||
private final ObjectMap<String, String> fallback = ObjectMap.of(
|
||||
"alpha-dart-mech-pad", "dart-mech-pad"
|
||||
);
|
||||
|
||||
public SaveFileVersion(int version){
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public SaveMeta getData(DataInputStream stream) throws IOException{
|
||||
long time = stream.readLong();
|
||||
long playtime = stream.readLong();
|
||||
int build = stream.readInt();
|
||||
|
||||
Rules rules = Serialization.readRulesStreamJson(stream);
|
||||
String map = stream.readUTF();
|
||||
int wave = stream.readInt();
|
||||
return new SaveMeta(version, time, playtime, build, map, wave, rules);
|
||||
}
|
||||
|
||||
public void writeMap(DataOutputStream stream) throws IOException{
|
||||
//write world size
|
||||
stream.writeShort(world.width());
|
||||
stream.writeShort(world.height());
|
||||
|
||||
//floor first
|
||||
for(int i = 0; i < world.width() * world.height(); i++){
|
||||
Tile tile = world.tile(i % world.width(), i / world.width());
|
||||
stream.writeByte(tile.getFloorID());
|
||||
stream.writeByte(tile.getOverlayID());
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
||||
Tile nextTile = world.tile(j % world.width(), j / world.width());
|
||||
|
||||
if(nextTile.getFloorID() != tile.getFloorID() || nextTile.getOverlayID() != tile.getOverlayID()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//blocks
|
||||
for(int i = 0; i < world.width() * world.height(); i++){
|
||||
Tile tile = world.tile(i % world.width(), i / world.width());
|
||||
stream.writeByte(tile.getBlockID());
|
||||
|
||||
if(tile.block() == Blocks.part){
|
||||
stream.writeByte(tile.getLinkByte());
|
||||
}else if(tile.entity != null){
|
||||
stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation
|
||||
stream.writeShort((short)tile.entity.health); //health
|
||||
|
||||
if(tile.entity.items != null) tile.entity.items.write(stream);
|
||||
if(tile.entity.power != null) tile.entity.power.write(stream);
|
||||
if(tile.entity.liquids != null) tile.entity.liquids.write(stream);
|
||||
if(tile.entity.cons != null) tile.entity.cons.write(stream);
|
||||
|
||||
tile.entity.writeConfig(stream);
|
||||
tile.entity.write(stream);
|
||||
}else{
|
||||
//write consecutive non-entity blocks
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
||||
Tile nextTile = world.tile(j % world.width(), j / world.width());
|
||||
|
||||
if(nextTile.block() != tile.block()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readMap(DataInputStream stream) throws IOException{
|
||||
short width = stream.readShort();
|
||||
short height = stream.readShort();
|
||||
|
||||
world.beginMapLoad();
|
||||
|
||||
Tile[][] tiles = world.createTiles(width, height);
|
||||
|
||||
//read floor and create tiles first
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
byte floorid = stream.readByte();
|
||||
byte oreid = stream.readByte();
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
Block ore = content.block(oreid);
|
||||
|
||||
tiles[x][y] = new Tile(x, y, floorid, (byte)0);
|
||||
tiles[x][y].setOverlay(ore);
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
Tile newTile = new Tile(newx, newy, floorid, (byte)0);
|
||||
newTile.setOverlay(ore);
|
||||
tiles[newx][newy] = newTile;
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//read blocks
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
Block block = content.block(stream.readByte());
|
||||
Tile tile = tiles[x][y];
|
||||
tile.setBlock(block);
|
||||
|
||||
if(block == Blocks.part){
|
||||
tile.setLinkByte(stream.readByte());
|
||||
}else if(tile.entity != null){
|
||||
byte tr = stream.readByte();
|
||||
short health = stream.readShort();
|
||||
|
||||
byte team = Pack.leftByte(tr);
|
||||
byte rotation = Pack.rightByte(tr);
|
||||
|
||||
tile.setTeam(Team.all[team]);
|
||||
tile.entity.health = health;
|
||||
tile.setRotation(rotation);
|
||||
|
||||
if(tile.entity.items != null) tile.entity.items.read(stream);
|
||||
if(tile.entity.power != null) tile.entity.power.read(stream);
|
||||
if(tile.entity.liquids != null) tile.entity.liquids.read(stream);
|
||||
if(tile.entity.cons != null) tile.entity.cons.read(stream);
|
||||
|
||||
tile.entity.readConfig(stream);
|
||||
tile.entity.read(stream);
|
||||
}else{
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
tiles[newx][newy].setBlock(block);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
|
||||
content.setTemporaryMapper(null);
|
||||
world.endMapLoad();
|
||||
}
|
||||
|
||||
public void writeEntities(DataOutputStream stream) throws IOException{
|
||||
int groups = 0;
|
||||
|
||||
for(EntityGroup<?> group : Entities.getAllGroups()){
|
||||
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
|
||||
groups++;
|
||||
}
|
||||
}
|
||||
|
||||
stream.writeByte(groups);
|
||||
|
||||
for(EntityGroup<?> group : Entities.getAllGroups()){
|
||||
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
|
||||
stream.writeInt(group.size());
|
||||
for(Entity entity : group.all()){
|
||||
stream.writeByte(((SaveTrait)entity).getTypeID());
|
||||
((SaveTrait)entity).writeSave(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readEntities(DataInputStream stream) throws IOException{
|
||||
byte groups = stream.readByte();
|
||||
|
||||
for(int i = 0; i < groups; i++){
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
byte typeid = stream.readByte();
|
||||
SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get();
|
||||
trait.readSave(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MappableContent[][] readContentHeader(DataInputStream stream) throws IOException{
|
||||
|
||||
byte mapped = stream.readByte();
|
||||
|
||||
MappableContent[][] map = new MappableContent[ContentType.values().length][0];
|
||||
|
||||
for(int i = 0; i < mapped; i++){
|
||||
ContentType type = ContentType.values()[stream.readByte()];
|
||||
short total = stream.readShort();
|
||||
map[type.ordinal()] = new MappableContent[total];
|
||||
|
||||
for(int j = 0; j < total; j++){
|
||||
String name = stream.readUTF();
|
||||
map[type.ordinal()][j] = content.getByName(type, fallback.get(name, name));
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public void writeContentHeader(DataOutputStream stream) throws IOException{
|
||||
Array<Content>[] map = content.getContentMap();
|
||||
|
||||
int mappable = 0;
|
||||
for(Array<Content> arr : map){
|
||||
if(arr.size > 0 && arr.first() instanceof MappableContent){
|
||||
mappable++;
|
||||
}
|
||||
}
|
||||
|
||||
stream.writeByte(mappable);
|
||||
for(Array<Content> arr : map){
|
||||
if(arr.size > 0 && arr.first() instanceof MappableContent){
|
||||
stream.writeByte(arr.first().getContentType().ordinal());
|
||||
stream.writeShort(arr.size);
|
||||
for(Content c : arr){
|
||||
stream.writeUTF(((MappableContent)c).name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void read(DataInputStream stream) throws IOException;
|
||||
|
||||
public abstract void write(DataOutputStream stream) throws IOException;
|
||||
}
|
||||
@@ -2,39 +2,46 @@ package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.util.io.CounterInputStream;
|
||||
import io.anuke.arc.util.io.FastDeflaterOutputStream;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.io.versions.Save1;
|
||||
import io.anuke.mindustry.world.WorldContext;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
//TODO load backup meta if possible
|
||||
public class SaveIO{
|
||||
public static final IntArray breakingVersions = IntArray.with(47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 63);
|
||||
public static final IntMap<SaveFileVersion> versions = new IntMap<>();
|
||||
public static final Array<SaveFileVersion> versionArray = Array.with(new Save1());
|
||||
/** Format header. This is the string 'MSAV' in ASCII. */
|
||||
public static final byte[] header = {77, 83, 65, 86};
|
||||
public static final IntMap<SaveVersion> versions = new IntMap<>();
|
||||
public static final Array<SaveVersion> versionArray = Array.with(new Save1());
|
||||
|
||||
static{
|
||||
for(SaveFileVersion version : versionArray){
|
||||
for(SaveVersion version : versionArray){
|
||||
versions.put(version.version, version);
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveFileVersion getSaveWriter(){
|
||||
public static SaveVersion getSaveWriter(){
|
||||
return versionArray.peek();
|
||||
}
|
||||
|
||||
public static SaveVersion getSaveWriter(int version){
|
||||
return versions.get(version);
|
||||
}
|
||||
|
||||
public static void saveToSlot(int slot){
|
||||
FileHandle file = fileFor(slot);
|
||||
boolean exists = file.exists();
|
||||
if(exists) file.moveTo(file.sibling(file.name() + "-backup." + file.extension()));
|
||||
if(exists) file.moveTo(backupFileFor(file));
|
||||
try{
|
||||
write(fileFor(slot));
|
||||
}catch(Exception e){
|
||||
if(exists) file.sibling(file.name() + "-backup." + file.extension()).moveTo(file);
|
||||
if(exists) backupFileFor(file).moveTo(file);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
@@ -47,22 +54,30 @@ public class SaveIO{
|
||||
return new DataInputStream(new InflaterInputStream(fileFor(slot).read(bufferSize)));
|
||||
}
|
||||
|
||||
public static DataInputStream getBackupSlotStream(int slot){
|
||||
return new DataInputStream(new InflaterInputStream(backupFileFor(fileFor(slot)).read(bufferSize)));
|
||||
}
|
||||
|
||||
public static boolean isSaveValid(int slot){
|
||||
try{
|
||||
return isSaveValid(getSlotStream(slot));
|
||||
getMeta(slot);
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSaveValid(FileHandle file){
|
||||
return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize))));
|
||||
try{
|
||||
return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize))));
|
||||
}catch(Exception e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSaveValid(DataInputStream stream){
|
||||
|
||||
try{
|
||||
getData(stream);
|
||||
getMeta(stream);
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
@@ -70,15 +85,20 @@ public class SaveIO{
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveMeta getData(int slot){
|
||||
return getData(getSlotStream(slot));
|
||||
public static SaveMeta getMeta(int slot){
|
||||
try{
|
||||
return getMeta(getSlotStream(slot));
|
||||
}catch(Exception e){
|
||||
return getMeta(getBackupSlotStream(slot));
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveMeta getData(DataInputStream stream){
|
||||
public static SaveMeta getMeta(DataInputStream stream){
|
||||
|
||||
try{
|
||||
readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveMeta meta = versions.get(version).getData(stream);
|
||||
SaveMeta meta = versions.get(version).getMeta(stream);
|
||||
stream.close();
|
||||
return meta;
|
||||
}catch(IOException e){
|
||||
@@ -90,61 +110,79 @@ public class SaveIO{
|
||||
return saveDirectory.child(slot + "." + Vars.saveExtension);
|
||||
}
|
||||
|
||||
public static void write(FileHandle file){
|
||||
write(new DeflaterOutputStream(file.write(false, bufferSize)){
|
||||
byte[] tmp = {0};
|
||||
|
||||
public void write(int var1) throws IOException{
|
||||
tmp[0] = (byte)(var1 & 255);
|
||||
this.write(tmp, 0, 1);
|
||||
}
|
||||
});
|
||||
public static FileHandle backupFileFor(FileHandle file){
|
||||
return file.sibling(file.name() + "-backup." + file.extension());
|
||||
}
|
||||
|
||||
public static void write(OutputStream os){
|
||||
DataOutputStream stream;
|
||||
public static void write(FileHandle file, StringMap tags){
|
||||
write(new FastDeflaterOutputStream(file.write(false, bufferSize)), tags);
|
||||
}
|
||||
|
||||
try{
|
||||
stream = new DataOutputStream(os);
|
||||
getVersion().write(stream);
|
||||
stream.close();
|
||||
public static void write(FileHandle file){
|
||||
write(file, null);
|
||||
}
|
||||
|
||||
public static void write(OutputStream os, StringMap tags){
|
||||
try(DataOutputStream stream = new DataOutputStream(os)){
|
||||
stream.write(header);
|
||||
stream.writeInt(getVersion().version);
|
||||
if(tags == null){
|
||||
getVersion().write(stream);
|
||||
}else{
|
||||
getVersion().write(stream, tags);
|
||||
}
|
||||
}catch(Exception e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void load(FileHandle file) throws SaveException{
|
||||
load(file, world.context);
|
||||
}
|
||||
|
||||
public static void load(FileHandle file, WorldContext context) throws SaveException{
|
||||
try{
|
||||
//try and load; if any exception at all occurs
|
||||
load(new InflaterInputStream(file.read(bufferSize)));
|
||||
load(new InflaterInputStream(file.read(bufferSize)), context);
|
||||
}catch(SaveException e){
|
||||
e.printStackTrace();
|
||||
FileHandle backup = file.sibling(file.name() + "-backup." + file.extension());
|
||||
if(backup.exists()){
|
||||
load(new InflaterInputStream(backup.read(bufferSize)));
|
||||
load(new InflaterInputStream(backup.read(bufferSize)), context);
|
||||
}else{
|
||||
throw new SaveException(e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void load(InputStream is) throws SaveException{
|
||||
try(DataInputStream stream = new DataInputStream(is)){
|
||||
/** Loads from a deflated (!) input stream.*/
|
||||
public static void load(InputStream is, WorldContext context) throws SaveException{
|
||||
try(CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
|
||||
logic.reset();
|
||||
readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveFileVersion ver = versions.get(version);
|
||||
SaveVersion ver = versions.get(version);
|
||||
|
||||
ver.read(stream);
|
||||
ver.read(stream, counter, context);
|
||||
}catch(Exception e){
|
||||
content.setTemporaryMapper(null);
|
||||
throw new SaveException(e);
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveFileVersion getVersion(){
|
||||
public static SaveVersion getVersion(){
|
||||
return versionArray.peek();
|
||||
}
|
||||
|
||||
public static void readHeader(DataInput input) throws IOException{
|
||||
byte[] bytes = new byte[header.length];
|
||||
input.readFully(bytes);
|
||||
if(!Arrays.equals(bytes, header)){
|
||||
throw new IOException("Incorrect header! Expecting: " + Arrays.toString(header) + "; Actual: " + Arrays.toString(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveException extends RuntimeException{
|
||||
public SaveException(Throwable throwable){
|
||||
super(throwable);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.util.io.CounterInputStream;
|
||||
import io.anuke.mindustry.entities.Entities;
|
||||
import io.anuke.mindustry.entities.EntityGroup;
|
||||
import io.anuke.mindustry.entities.traits.*;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.world.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public abstract class SaveVersion extends SaveFileReader{
|
||||
public final int version;
|
||||
|
||||
public SaveVersion(int version){
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public SaveMeta getMeta(DataInput stream) throws IOException{
|
||||
stream.readInt(); //length of data, doesn't matter here
|
||||
StringMap map = readStringMap(stream);
|
||||
return new SaveMeta(map.getInt("version"), map.getLong("saved"), map.getLong("playtime"), map.getInt("build"), map.get("mapname"), map.getInt("wave"), JsonIO.read(Rules.class, map.get("rules", "{}")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(DataOutputStream stream) throws IOException{
|
||||
write(stream, new StringMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
|
||||
region("meta", stream, counter, this::readMeta);
|
||||
region("content", stream, counter, this::readContentHeader);
|
||||
region("map", stream, counter, in -> readMap(in, context));
|
||||
region("entities", stream, counter, this::readEntities);
|
||||
}
|
||||
|
||||
public final void write(DataOutputStream stream, StringMap extraTags) throws IOException{
|
||||
region("meta", stream, out -> writeMeta(out, extraTags));
|
||||
region("content", stream, this::writeContentHeader);
|
||||
region("map", stream, this::writeMap);
|
||||
region("entities", stream, this::writeEntities);
|
||||
}
|
||||
|
||||
public void writeMeta(DataOutput stream, StringMap tags) throws IOException{
|
||||
writeStringMap(stream, StringMap.of(
|
||||
"saved", Time.millis(),
|
||||
"playtime", headless ? 0 : control.saves.getTotalPlaytime(),
|
||||
"build", Version.build,
|
||||
"mapname", world.getMap() == null ? "unknown" : world.getMap().name(),
|
||||
"wave", state.wave,
|
||||
"wavetime", state.wavetime,
|
||||
"stats", JsonIO.write(state.stats),
|
||||
"rules", JsonIO.write(state.rules),
|
||||
"width", world.width(),
|
||||
"height", world.height()
|
||||
).merge(tags));
|
||||
}
|
||||
|
||||
public void readMeta(DataInput stream) throws IOException{
|
||||
StringMap map = readStringMap(stream);
|
||||
|
||||
state.wave = map.getInt("wave");
|
||||
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
|
||||
state.stats = JsonIO.read(Stats.class, map.get("stats", "{}"));
|
||||
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
|
||||
Map worldmap = world.maps.byName(map.get("mapname", "\\\\\\"));
|
||||
world.setMap(worldmap == null ? new Map(StringMap.of(
|
||||
"name", map.get("mapname", "Unknown"),
|
||||
"width", 1,
|
||||
"height", 1
|
||||
)) : worldmap);
|
||||
}
|
||||
|
||||
public void writeMap(DataOutput stream) throws IOException{
|
||||
//write world size
|
||||
stream.writeShort(world.width());
|
||||
stream.writeShort(world.height());
|
||||
|
||||
//floor + overlay
|
||||
for(int i = 0; i < world.width() * world.height(); i++){
|
||||
Tile tile = world.tile(i % world.width(), i / world.width());
|
||||
stream.writeShort(tile.floorID());
|
||||
stream.writeShort(tile.overlayID());
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
||||
Tile nextTile = world.tile(j % world.width(), j / world.width());
|
||||
|
||||
if(nextTile.floorID() != tile.floorID() || nextTile.overlayID() != tile.overlayID()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//blocks
|
||||
for(int i = 0; i < world.width() * world.height(); i++){
|
||||
Tile tile = world.tile(i % world.width(), i / world.width());
|
||||
stream.writeShort(tile.blockID());
|
||||
|
||||
if(tile.entity != null){
|
||||
writeChunk(stream, true, out -> {
|
||||
out.writeByte(tile.entity.version());
|
||||
tile.entity.write(out);
|
||||
});
|
||||
}else{
|
||||
//write consecutive non-entity blocks
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
||||
Tile nextTile = world.tile(j % world.width(), j / world.width());
|
||||
|
||||
if(nextTile.blockID() != tile.blockID()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readMap(DataInput stream, WorldContext context) throws IOException{
|
||||
int width = stream.readUnsignedShort();
|
||||
int height = stream.readUnsignedShort();
|
||||
|
||||
boolean generating = context.isGenerating();
|
||||
|
||||
if(!generating) context.begin();
|
||||
|
||||
context.resize(width, height);
|
||||
|
||||
//read floor and create tiles first
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
short floorid = stream.readShort();
|
||||
short oreid = stream.readShort();
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
context.create(x, y, floorid, oreid, (short)0);
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
context.create(newx, newy, floorid, oreid, (short)0);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//read blocks
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
Block block = content.block(stream.readShort());
|
||||
Tile tile = context.tile(x, y);
|
||||
tile.setBlock(block);
|
||||
|
||||
if(tile.entity != null){
|
||||
readChunk(stream, true, in -> {
|
||||
byte version = in.readByte();
|
||||
tile.entity.read(in, version);
|
||||
});
|
||||
}else{
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
context.tile(newx, newy).setBlock(block);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
|
||||
content.setTemporaryMapper(null);
|
||||
if(!generating) context.end();
|
||||
}
|
||||
|
||||
public void writeEntities(DataOutput stream) throws IOException{
|
||||
//write entity chunk
|
||||
int groups = 0;
|
||||
|
||||
for(EntityGroup<?> group : Entities.getAllGroups()){
|
||||
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
|
||||
groups++;
|
||||
}
|
||||
}
|
||||
|
||||
stream.writeByte(groups);
|
||||
|
||||
for(EntityGroup<?> group : Entities.getAllGroups()){
|
||||
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
|
||||
stream.writeInt(group.size());
|
||||
for(Entity entity : group.all()){
|
||||
SaveTrait save = (SaveTrait)entity;
|
||||
//each entity is a separate chunk.
|
||||
writeChunk(stream, true, out -> {
|
||||
out.writeByte(save.getTypeID());
|
||||
out.writeByte(save.version());
|
||||
save.writeSave(out);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readEntities(DataInput stream) throws IOException{
|
||||
byte groups = stream.readByte();
|
||||
|
||||
for(int i = 0; i < groups; i++){
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
//TODO throw exception on read fail
|
||||
readChunk(stream, true, in -> {
|
||||
byte typeid = in.readByte();
|
||||
byte version = in.readByte();
|
||||
SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get();
|
||||
trait.readSave(in, version);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readContentHeader(DataInput stream) throws IOException{
|
||||
|
||||
byte mapped = stream.readByte();
|
||||
|
||||
MappableContent[][] map = new MappableContent[ContentType.values().length][0];
|
||||
|
||||
for(int i = 0; i < mapped; i++){
|
||||
ContentType type = ContentType.values()[stream.readByte()];
|
||||
short total = stream.readShort();
|
||||
map[type.ordinal()] = new MappableContent[total];
|
||||
|
||||
for(int j = 0; j < total; j++){
|
||||
String name = stream.readUTF();
|
||||
map[type.ordinal()][j] = content.getByName(type, fallback.get(name, name));
|
||||
}
|
||||
}
|
||||
|
||||
content.setTemporaryMapper(map);
|
||||
}
|
||||
|
||||
public void writeContentHeader(DataOutput stream) throws IOException{
|
||||
Array<Content>[] map = content.getContentMap();
|
||||
|
||||
int mappable = 0;
|
||||
for(Array<Content> arr : map){
|
||||
if(arr.size > 0 && arr.first() instanceof MappableContent){
|
||||
mappable++;
|
||||
}
|
||||
}
|
||||
|
||||
stream.writeByte(mappable);
|
||||
for(Array<Content> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,12 +109,12 @@ public class TypeIO{
|
||||
|
||||
@WriteClass(Block.class)
|
||||
public static void writeBlock(ByteBuffer buffer, Block block){
|
||||
buffer.put(block.id);
|
||||
buffer.putShort(block.id);
|
||||
}
|
||||
|
||||
@ReadClass(Block.class)
|
||||
public static Block readBlock(ByteBuffer buffer){
|
||||
return content.block(buffer.get());
|
||||
return content.block(buffer.getShort());
|
||||
}
|
||||
|
||||
@WriteClass(BuildRequest[].class)
|
||||
@@ -124,7 +124,7 @@ public class TypeIO{
|
||||
buffer.put(request.breaking ? (byte)1 : 0);
|
||||
buffer.putInt(Pos.get(request.x, request.y));
|
||||
if(!request.breaking){
|
||||
buffer.put(request.block.id);
|
||||
buffer.putShort(request.block.id);
|
||||
buffer.put((byte)request.rotation);
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ public class TypeIO{
|
||||
if(type == 1){ //remove
|
||||
currentRequest = new BuildRequest(Pos.x(position), Pos.y(position));
|
||||
}else{ //place
|
||||
byte block = buffer.get();
|
||||
short block = buffer.getShort();
|
||||
byte rotation = buffer.get();
|
||||
currentRequest = new BuildRequest(Pos.x(position), Pos.y(position), rotation, content.block(block));
|
||||
}
|
||||
@@ -205,7 +205,7 @@ public class TypeIO{
|
||||
|
||||
@WriteClass(Mech.class)
|
||||
public static void writeMech(ByteBuffer buffer, Mech mech){
|
||||
buffer.put(mech.id);
|
||||
buffer.put((byte)mech.id);
|
||||
}
|
||||
|
||||
@ReadClass(Mech.class)
|
||||
@@ -215,33 +215,33 @@ public class TypeIO{
|
||||
|
||||
@WriteClass(Liquid.class)
|
||||
public static void writeLiquid(ByteBuffer buffer, Liquid liquid){
|
||||
buffer.put(liquid == null ? -1 : liquid.id);
|
||||
buffer.putShort(liquid == null ? -1 : liquid.id);
|
||||
}
|
||||
|
||||
@ReadClass(Liquid.class)
|
||||
public static Liquid readLiquid(ByteBuffer buffer){
|
||||
byte id = buffer.get();
|
||||
return id == -1 ? null : content.liquid(buffer.get());
|
||||
short id = buffer.getShort();
|
||||
return id == -1 ? null : content.liquid(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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,82 +1,10 @@
|
||||
package io.anuke.mindustry.io.versions;
|
||||
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.gen.Serialization;
|
||||
import io.anuke.mindustry.io.SaveFileVersion;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.type.Zone;
|
||||
import io.anuke.mindustry.io.SaveVersion;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class Save1 extends SaveFileVersion{
|
||||
public class Save1 extends SaveVersion{
|
||||
|
||||
public Save1(){
|
||||
super(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(DataInputStream stream) throws IOException{
|
||||
stream.readLong(); //time
|
||||
stream.readLong(); //total playtime
|
||||
stream.readInt(); //build
|
||||
|
||||
//general state
|
||||
state.rules = Serialization.readRulesStreamJson(stream);
|
||||
String mapname = stream.readUTF();
|
||||
Map map = world.maps.all().find(m -> m.name().equals(mapname));
|
||||
if(map == null) map = new Map(customMapDirectory.child(mapname), 1, 1, new ObjectMap<>(), true);
|
||||
world.setMap(map);
|
||||
state.rules.spawns = map.getWaves();
|
||||
if(content.getByID(ContentType.zone, state.rules.zone) != null){
|
||||
Rules rules = content.<Zone>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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
package io.anuke.mindustry.maps;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.graphics.Texture;
|
||||
import io.anuke.arc.util.Log;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.game.DefaultWaves;
|
||||
import io.anuke.mindustry.game.SpawnGroup;
|
||||
import io.anuke.mindustry.io.MapIO;
|
||||
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
import io.anuke.mindustry.game.Rules;
|
||||
import io.anuke.mindustry.io.JsonIO;
|
||||
|
||||
public class Map implements Comparable<Map>{
|
||||
/** Whether this is a custom map. */
|
||||
public final boolean custom;
|
||||
/** Metadata. Author description, display name, etc. */
|
||||
public final ObjectMap<String, String> tags;
|
||||
public final StringMap tags;
|
||||
/** Base file of this map. File can be named anything at all. */
|
||||
public final FileHandle file;
|
||||
/** Format version. */
|
||||
@@ -29,7 +24,7 @@ public class Map implements Comparable<Map>{
|
||||
/** Build that this map was created in. -1 = unknown or custom build. */
|
||||
public int build;
|
||||
|
||||
public Map(FileHandle file, int width, int height, ObjectMap<String, String> tags, boolean custom, int version, int build){
|
||||
public Map(FileHandle file, int width, int height, StringMap tags, boolean custom, int version, int build){
|
||||
this.custom = custom;
|
||||
this.tags = tags;
|
||||
this.file = file;
|
||||
@@ -39,26 +34,16 @@ public class Map implements Comparable<Map>{
|
||||
this.build = build;
|
||||
}
|
||||
|
||||
public Map(FileHandle file, int width, int height, ObjectMap<String, String> 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<String, String> tags, boolean custom){
|
||||
this(file, width, height, tags, custom, MapIO.version);
|
||||
public Map(FileHandle file, int width, int height, StringMap tags, boolean custom){
|
||||
this(file, width, height, tags, custom, -1);
|
||||
}
|
||||
|
||||
public Array<SpawnGroup> getWaves(){
|
||||
if(tags.containsKey("waves")){
|
||||
try{
|
||||
return world.maps.readWaves(tags.get("waves"));
|
||||
}catch(Exception e){
|
||||
Log.err("Malformed waves: {0}", tags.get("waves"));
|
||||
e.printStackTrace();
|
||||
return DefaultWaves.get();
|
||||
}
|
||||
}else{
|
||||
return DefaultWaves.get();
|
||||
}
|
||||
public Map(StringMap tags){
|
||||
this(Vars.customMapDirectory.child(tags.get("name", "unknown")), 0, 0, tags, true);
|
||||
}
|
||||
|
||||
public int getHightScore(){
|
||||
@@ -70,6 +55,23 @@ public class Map implements Comparable<Map>{
|
||||
Vars.data.modified();
|
||||
}
|
||||
|
||||
/** This creates a new instance.*/
|
||||
public Rules rules(){
|
||||
return JsonIO.read(Rules.class, tags.get("rules", "{}"));
|
||||
}
|
||||
|
||||
/** Whether this map has a core of the enemy 'wave' team. Default: true.
|
||||
* Used for checking Attack mode validity.*/
|
||||
public boolean hasEnemyCore(){
|
||||
return tags.get("enemycore", "true").equals("true");
|
||||
}
|
||||
|
||||
/** Whether this map has a core of any team except the default player team. Default: true.
|
||||
* Used for checking PvP mode validity.*/
|
||||
public boolean hasOtherCores(){
|
||||
return tags.get("othercore", "true").equals("true");
|
||||
}
|
||||
|
||||
public String author(){
|
||||
return tag("author");
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package io.anuke.mindustry.maps;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.graphics.Texture;
|
||||
import io.anuke.arc.util.Disposable;
|
||||
import io.anuke.arc.util.Log;
|
||||
import io.anuke.arc.util.serialization.Json;
|
||||
import io.anuke.mindustry.game.SpawnGroup;
|
||||
import io.anuke.mindustry.io.MapIO;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.io.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
@@ -52,7 +50,7 @@ public class Maps implements Disposable{
|
||||
FileHandle file = Core.files.internal("maps/" + name + "." + mapExtension);
|
||||
|
||||
try{
|
||||
return MapIO.readMap(file, false);
|
||||
return MapIO.createMap(file, false);
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -81,13 +79,12 @@ public class Maps implements Disposable{
|
||||
* Save a custom map to the directory. This updates all values and stored data necessary.
|
||||
* The tags are copied to prevent mutation later.
|
||||
*/
|
||||
public void saveMap(ObjectMap<String, String> baseTags, Tile[][] data){
|
||||
public void saveMap(ObjectMap<String, String> baseTags){
|
||||
|
||||
try{
|
||||
ObjectMap<String, String> tags = new ObjectMap<>(baseTags);
|
||||
StringMap tags = new StringMap(baseTags);
|
||||
String name = tags.get("name");
|
||||
if(name == null) throw new IllegalArgumentException("Can't save a map with no name. How did this happen?");
|
||||
//FileHandle file = customMapDirectory.child(name + "." + mapExtension);
|
||||
FileHandle file;
|
||||
|
||||
//find map with the same exact display name
|
||||
@@ -106,11 +103,11 @@ public class Maps implements Disposable{
|
||||
}
|
||||
|
||||
//create map, write it, etc etc etc
|
||||
Map map = new Map(file, data.length, data[0].length, tags, true);
|
||||
MapIO.writeMap(file, map, data);
|
||||
Map map = new Map(file, world.width(), world.height(), tags, true);
|
||||
MapIO.writeMap(file, map);
|
||||
|
||||
if(!headless){
|
||||
map.texture = new Texture(MapIO.generatePreview(data));
|
||||
map.texture = new Texture(MapIO.generatePreview(world.getTiles()));
|
||||
}
|
||||
maps.add(map);
|
||||
maps.sort();
|
||||
@@ -160,6 +157,34 @@ public class Maps implements Disposable{
|
||||
return str == null ? null : str.equals("[]") ? new Array<>() : Array.with(json.fromJson(SpawnGroup[].class, str));
|
||||
}
|
||||
|
||||
public void loadLegacyMaps(){
|
||||
boolean convertedAny = false;
|
||||
for(FileHandle file : customMapDirectory.list()){
|
||||
if(file.extension().equalsIgnoreCase(oldMapExtension)){
|
||||
try{
|
||||
LegacyMapIO.convertMap(file, file.sibling(file.nameWithoutExtension() + "." + mapExtension));
|
||||
//delete old, converted file; it is no longer useful
|
||||
file.delete();
|
||||
convertedAny = true;
|
||||
Log.info("Converted file {0}", file);
|
||||
}catch(IOException e){
|
||||
//rename the file to a 'mmap_conversion_failed' extension to keep it there just in case
|
||||
//but don't delete it
|
||||
file.copyTo(file.sibling(file.name() + "_conversion_failed"));
|
||||
file.delete();
|
||||
Log.err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//free up any potential memory that was used up during conversion
|
||||
if(convertedAny){
|
||||
world.createTiles(1, 1);
|
||||
//reload maps to load the converted ones
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
/** Find a new filename to put a map to. */
|
||||
private FileHandle findFile(){
|
||||
//find a map name that isn't used.
|
||||
@@ -171,7 +196,7 @@ public class Maps implements Disposable{
|
||||
}
|
||||
|
||||
private void loadMap(FileHandle file, boolean custom) throws IOException{
|
||||
Map map = MapIO.readMap(file, custom);
|
||||
Map map = MapIO.createMap(file, custom);
|
||||
|
||||
if(map.name() == null){
|
||||
throw new IOException("Map name cannot be empty! File: " + file);
|
||||
|
||||
@@ -156,8 +156,7 @@ public abstract class BasicGenerator extends RandomGenerator{
|
||||
block = tiles[x][y].block();
|
||||
ore = tiles[x][y].overlay();
|
||||
r.accept(x, y);
|
||||
tiles[x][y] = new Tile(x, y, floor.id, block.id);
|
||||
tiles[x][y].setOverlay(ore);
|
||||
tiles[x][y] = new Tile(x, y, floor.id, ore.id, block.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +199,7 @@ public abstract class BasicGenerator extends RandomGenerator{
|
||||
Tile child = tiles[newx][newy];
|
||||
if(!closed.get(child.x, child.y)){
|
||||
closed.set(child.x, child.y);
|
||||
child.setRotation(child.relativeTo(next.x, next.y));
|
||||
child.rotation(child.relativeTo(next.x, next.y));
|
||||
costs.put(child.pos(), th.cost(child) + baseCost);
|
||||
queue.add(child);
|
||||
}
|
||||
@@ -215,7 +214,7 @@ public abstract class BasicGenerator extends RandomGenerator{
|
||||
Tile current = end;
|
||||
while(current != start){
|
||||
out.add(current);
|
||||
Point2 p = Geometry.d4(current.getRotation());
|
||||
Point2 p = Geometry.d4(current.rotation());
|
||||
current = tiles[current.x + p.x][current.y + p.y];
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,10 @@ import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.type.Loadout;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
import io.anuke.mindustry.world.blocks.StaticWall;
|
||||
import io.anuke.mindustry.world.blocks.*;
|
||||
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
|
||||
import io.anuke.mindustry.world.blocks.storage.StorageBlock;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public class MapGenerator extends Generator{
|
||||
@@ -70,115 +67,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);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO this will probably not get the desired effect
|
||||
MapIO.loadMap(map);
|
||||
Array<Point2> players = new Array<>();
|
||||
Array<Point2> enemies = new Array<>();
|
||||
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
if(tiles[x][y].block() instanceof CoreBlock){
|
||||
players.add(new Point2(x, y));
|
||||
tiles[x][y].setBlock(Blocks.air);
|
||||
}
|
||||
|
||||
if(tiles[x][y].overlay() == Blocks.spawn && enemySpawns != -1){
|
||||
enemies.add(new Point2(x, y));
|
||||
tiles[x][y].setOverlay(Blocks.air);
|
||||
}
|
||||
|
||||
if(tiles[x][y].block() instanceof BlockPart){
|
||||
tiles[x][y].setBlock(Blocks.air);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MapIO.readTiles(map, tiles);
|
||||
Array<Point2> players = new Array<>();
|
||||
Array<Point2> enemies = new Array<>();
|
||||
Simplex simplex = new Simplex(Mathf.random(99999));
|
||||
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
if(tiles[x][y].block() instanceof CoreBlock){
|
||||
players.add(new Point2(x, y));
|
||||
tiles[x][y].setBlock(Blocks.air);
|
||||
}
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
final double scl = 10;
|
||||
Tile tile = tiles[x][y];
|
||||
int newX = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x, y) * distortion + x), 0, width - 1);
|
||||
int newY = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x + 9999, y + 9999) * distortion + y), 0, height - 1);
|
||||
|
||||
if(tiles[x][y].block() == Blocks.spawn && enemySpawns != -1){
|
||||
enemies.add(new Point2(x, y));
|
||||
tiles[x][y].setBlock(Blocks.air);
|
||||
}
|
||||
|
||||
if(tiles[x][y].block() == Blocks.part){
|
||||
tiles[x][y].setBlock(Blocks.air);
|
||||
}
|
||||
if(((tile.block() instanceof StaticWall
|
||||
&& tiles[newX][newY].block() instanceof StaticWall)
|
||||
|| (tile.block() == Blocks.air && !tiles[newX][newY].block().synthetic())
|
||||
|| (tiles[newX][newY].block() == Blocks.air && tile.block() instanceof StaticWall))){
|
||||
tile.setBlock(tiles[newX][newY].block());
|
||||
}
|
||||
}
|
||||
|
||||
Simplex simplex = new Simplex(Mathf.random(99999));
|
||||
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
final double scl = 10;
|
||||
Tile tile = tiles[x][y];
|
||||
int newX = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x, y) * distortion + x), 0, width - 1);
|
||||
int newY = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x + 9999, y + 9999) * distortion + y), 0, height - 1);
|
||||
|
||||
if(((tile.block() instanceof StaticWall
|
||||
&& tiles[newX][newY].block() instanceof StaticWall)
|
||||
|| (tile.block() == Blocks.air && !tiles[newX][newY].block().synthetic())
|
||||
|| (tiles[newX][newY].block() == Blocks.air && tile.block() instanceof StaticWall)) && tiles[newX][newY].block() != Blocks.spawn && tile.block() != Blocks.spawn){
|
||||
tile.setBlock(tiles[newX][newY].block());
|
||||
}
|
||||
|
||||
if(distortFloor){
|
||||
tile.setFloor(tiles[newX][newY].floor());
|
||||
if(distortFloor){
|
||||
tile.setFloor(tiles[newX][newY].floor());
|
||||
if(tiles[newX][newY].overlay() != Blocks.spawn && tile.overlay() != Blocks.spawn){
|
||||
tile.setOverlay(tiles[newX][newY].overlay());
|
||||
}
|
||||
}
|
||||
|
||||
for(Decoration decor : decorations){
|
||||
if(x > 0 && y > 0 && (tiles[x - 1][y].block() == decor.wall || tiles[x][y - 1].block() == decor.wall)){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(tile.block() == Blocks.air && !(decor.wall instanceof Floor) && tile.floor() == decor.floor && Mathf.chance(decor.chance)){
|
||||
tile.setBlock(decor.wall);
|
||||
}else if(tile.floor() == decor.floor && decor.wall instanceof Floor && Mathf.chance(decor.chance)){
|
||||
tile.setFloor((Floor)decor.wall);
|
||||
}
|
||||
for(Decoration decor : decorations){
|
||||
if(x > 0 && y > 0 && (tiles[x - 1][y].block() == decor.wall || tiles[x][y - 1].block() == decor.wall)){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(tile.block() instanceof StorageBlock && !(tile.block() instanceof CoreBlock) && world.getZone() != null){
|
||||
for(Item item : world.getZone().resources){
|
||||
if(Mathf.chance(0.3)){
|
||||
tile.entity.items.add(item, Math.min(Mathf.random(500), tile.block().itemCapacity));
|
||||
}
|
||||
if(tile.block() == Blocks.air && !(decor.wall instanceof Floor) && tile.floor() == decor.floor && Mathf.chance(decor.chance)){
|
||||
tile.setBlock(decor.wall);
|
||||
}else if(tile.floor() == decor.floor && decor.wall instanceof Floor && Mathf.chance(decor.chance)){
|
||||
tile.setFloor((Floor)decor.wall);
|
||||
}
|
||||
}
|
||||
|
||||
if(tile.block() instanceof StorageBlock && !(tile.block() instanceof CoreBlock) && world.getZone() != null){
|
||||
for(Item item : world.getZone().resources){
|
||||
if(Mathf.chance(0.3)){
|
||||
tile.entity.items.add(item, Math.min(Mathf.random(500), tile.block().itemCapacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(enemySpawns != -1){
|
||||
if(enemySpawns > enemies.size){
|
||||
throw new IllegalArgumentException("Enemy spawn pool greater than map spawn number for map: " + mapName);
|
||||
}
|
||||
if(enemySpawns != -1){
|
||||
if(enemySpawns > enemies.size){
|
||||
throw new IllegalArgumentException("Enemy spawn pool greater than map spawn number for map: " + mapName);
|
||||
}
|
||||
|
||||
enemies.shuffle();
|
||||
for(int i = 0; i < enemySpawns; i++){
|
||||
Point2 point = enemies.get(i);
|
||||
tiles[point.x][point.y].setBlock(Blocks.spawn);
|
||||
enemies.shuffle();
|
||||
for(int i = 0; i < enemySpawns; i++){
|
||||
Point2 point = enemies.get(i);
|
||||
tiles[point.x][point.y].setOverlay(Blocks.spawn);
|
||||
|
||||
int rad = 10, frad = 12;
|
||||
int rad = 10, frad = 12;
|
||||
|
||||
for(int x = -rad; x <= rad; x++){
|
||||
for(int y = -rad; y <= rad; y++){
|
||||
int wx = x + point.x, wy = y + point.y;
|
||||
double dst = Mathf.dst(x, y);
|
||||
if(dst < frad && Structs.inBounds(wx, wy, tiles) && (dst <= rad || Mathf.chance(0.5))){
|
||||
Tile tile = tiles[wx][wy];
|
||||
for(int x = -rad; x <= rad; x++){
|
||||
for(int y = -rad; y <= rad; y++){
|
||||
int wx = x + point.x, wy = y + point.y;
|
||||
double dst = Mathf.dst(x, y);
|
||||
if(dst < frad && Structs.inBounds(wx, wy, tiles) && (dst <= rad || Mathf.chance(0.5))){
|
||||
Tile tile = tiles[wx][wy];
|
||||
if(tile.overlay() != Blocks.spawn){
|
||||
tile.clearOverlay();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Point2 core = players.random();
|
||||
if(core == null){
|
||||
throw new IllegalArgumentException("All zone maps must have a core.");
|
||||
}
|
||||
|
||||
loadout.setup(core.x, core.y);
|
||||
|
||||
world.prepareTiles(tiles);
|
||||
world.setMap(map);
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Point2 core = players.random();
|
||||
if(core == null){
|
||||
throw new IllegalArgumentException("All zone maps must have a core.");
|
||||
}
|
||||
|
||||
loadout.setup(core.x, core.y);
|
||||
|
||||
world.prepareTiles(tiles);
|
||||
world.setMap(map);
|
||||
}
|
||||
|
||||
public static class Decoration{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user