diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index d5bd8e3c0c..d936e4b346 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -8,6 +8,7 @@
p.load(file('../core/assets/version.properties').newReader()); return p }
diff --git a/core/assets/bundles/global.properties b/core/assets/bundles/global.properties
index 6cee674379..fec3bb47db 100644
--- a/core/assets/bundles/global.properties
+++ b/core/assets/bundles/global.properties
@@ -37,4 +37,5 @@ sector.siege.credit = hhh i 17
sector.ravine.credit = Bepis
#megabase center
-sector.sector-serpulo-27.name = ᒆᐴᑫᑈᐴᐾᐶᒅᑜᒆ
\ No newline at end of file
+sector.sector-serpulo-27.name = ᒆᐴᑫᑈᐴᐾᐶᒅᑜᒆ
+threat.unreasonable = ᐰᐬᑀᐲᑚᑓᐭ
\ No newline at end of file
diff --git a/core/assets/maps/serpulo/hidden/111.msav b/core/assets/maps/serpulo/hidden/111.msav
index a1128616b9..1c06b2ba0c 100644
Binary files a/core/assets/maps/serpulo/hidden/111.msav and b/core/assets/maps/serpulo/hidden/111.msav differ
diff --git a/core/assets/maps/serpulo/hidden/13.msav b/core/assets/maps/serpulo/hidden/13.msav
index 6e97fea22d..0170bf609d 100644
Binary files a/core/assets/maps/serpulo/hidden/13.msav and b/core/assets/maps/serpulo/hidden/13.msav differ
diff --git a/core/assets/maps/serpulo/hidden/6.msav b/core/assets/maps/serpulo/hidden/6.msav
index 973faadbcb..804c456e3d 100644
Binary files a/core/assets/maps/serpulo/hidden/6.msav and b/core/assets/maps/serpulo/hidden/6.msav differ
diff --git a/core/assets/maps/serpulo/littoralShipyard.msav b/core/assets/maps/serpulo/littoralShipyard.msav
index bb83ca2d6b..3e72623fed 100644
Binary files a/core/assets/maps/serpulo/littoralShipyard.msav and b/core/assets/maps/serpulo/littoralShipyard.msav differ
diff --git a/core/assets/maps/serpulo/overgrowth.msav b/core/assets/maps/serpulo/overgrowth.msav
index 8b4983bf26..d4b154c65e 100644
Binary files a/core/assets/maps/serpulo/overgrowth.msav and b/core/assets/maps/serpulo/overgrowth.msav differ
diff --git a/core/assets/maps/serpulo/weatheredChannels.msav b/core/assets/maps/serpulo/weatheredChannels.msav
index aa02ffabf7..22721327b6 100644
Binary files a/core/assets/maps/serpulo/weatheredChannels.msav and b/core/assets/maps/serpulo/weatheredChannels.msav differ
diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java
index 49eabe420e..ae16aff021 100644
--- a/core/src/mindustry/content/Blocks.java
+++ b/core/src/mindustry/content/Blocks.java
@@ -2680,6 +2680,7 @@ public class Blocks{
laserColor2 = Color.valueOf("ffd9c2");
laserScale = 0.8f;
scaledHealth = 130;
+ useLod = false;
}};
turbineCondenser = new ThermalGenerator("turbine-condenser"){{
diff --git a/core/src/mindustry/core/Control.java b/core/src/mindustry/core/Control.java
index 896afe4388..557351b094 100644
--- a/core/src/mindustry/core/Control.java
+++ b/core/src/mindustry/core/Control.java
@@ -401,6 +401,8 @@ public class Control implements ApplicationListener, Loadable{
ui.loadAnd(() -> {
logic.reset();
world.loadMap(map, rules);
+ var oldRules = state.rules;
+ rules.retainContentFields(oldRules);
state.rules = rules;
if(playtest) state.playtestingMap = map;
state.rules.sector = null;
diff --git a/core/src/mindustry/ctype/Content.java b/core/src/mindustry/ctype/Content.java
index 7965d8f754..312152e17c 100644
--- a/core/src/mindustry/ctype/Content.java
+++ b/core/src/mindustry/ctype/Content.java
@@ -65,6 +65,11 @@ public abstract class Content implements Comparable{
return !isVanilla();
}
+ /** @return whether this content is from a map data patch. */
+ public boolean isPatchContent(){
+ return minfo.mod == DataPatcher.dpMod;
+ }
+
@Override
public int compareTo(Content c){
return Integer.compare(id, c.id);
diff --git a/core/src/mindustry/entities/Damage.java b/core/src/mindustry/entities/Damage.java
index f82eae6291..61bc01c95a 100644
--- a/core/src/mindustry/entities/Damage.java
+++ b/core/src/mindustry/entities/Damage.java
@@ -107,9 +107,9 @@ public class Damage{
float damagePerWave = explosiveness / 2f;
for(int i = 0; i < waves; i++){
- var shields = ignoreTeam == null ? null : indexer.getEnemy(ignoreTeam, BlockFlag.shield);
int f = i;
Time.run(i * 2f, () -> {
+ var shields = ignoreTeam == null ? null : indexer.getEnemy(ignoreTeam, BlockFlag.shield);
if(shields == null || shields.isEmpty() || !shields.contains(b -> b instanceof ExplosionShield s && s.absorbExplosion(x, y, damagePerWave))){
damage(ignoreTeam, x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), damagePerWave, false);
}
diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java
index 1f78d6f3ee..c8778bd566 100644
--- a/core/src/mindustry/entities/comp/UnitComp.java
+++ b/core/src/mindustry/entities/comp/UnitComp.java
@@ -269,6 +269,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
case health -> health;
case shield -> shield;
case maxHealth -> maxHealth;
+ case flying -> isFlying() ? 1f : 0f;
case x -> World.conv(x);
case y -> World.conv(y);
case velocityX -> vel.x * 60f / tilesize;
diff --git a/core/src/mindustry/game/Rules.java b/core/src/mindustry/game/Rules.java
index 5216a90b36..7af81b00ca 100644
--- a/core/src/mindustry/game/Rules.java
+++ b/core/src/mindustry/game/Rules.java
@@ -245,6 +245,23 @@ public class Rules{
return JsonIO.copy(this);
}
+ /**
+ * When a map is played, it uses rules from the rules dialog, which cannot contain patched content, since it doesn't exist at that point in time.
+ * This means that any existing rules containing patched content will contain garbage or empty data.
+ * This function copies original map rule data from {@param source} (obtained after map load) that may contain new content into this ruleset.
+ * */
+ public void retainContentFields(Rules source){
+ //these fields can't be modified in the custom rules anyway, so force-overwriting them is fine
+ spawns = source.spawns;
+ objectives = source.objectives;
+ weather = source.weather;
+
+ //TODO: this overwrites banned blocks/units and loadouts if someone set it in custom rules when playing; there isn't a good way to avoid this
+ if(Seq.with(source.bannedBlocks).contains(Content::isPatchContent)) bannedBlocks = source.bannedBlocks;
+ if(Seq.with(source.bannedUnits).contains(Content::isPatchContent)) bannedUnits = source.bannedUnits;
+ if(source.loadout.contains(i -> i.item.isPatchContent())) loadout = source.loadout;
+ }
+
/** Returns the gamemode that best fits these rules. */
public Gamemode mode(){
if(pvp){
diff --git a/core/src/mindustry/graphics/Drawf.java b/core/src/mindustry/graphics/Drawf.java
index 56e1333980..377b15c9e2 100644
--- a/core/src/mindustry/graphics/Drawf.java
+++ b/core/src/mindustry/graphics/Drawf.java
@@ -515,9 +515,13 @@ public class Drawf{
}
public static void laser(TextureRegion line, TextureRegion start, TextureRegion end, float x, float y, float x2, float y2, float scale, boolean light){
+ laser(line, start, end, x, y, x2, y2, scale, light, true);
+ }
+
+ public static void laser(TextureRegion line, TextureRegion start, TextureRegion end, float x, float y, float x2, float y2, float scale, boolean light, boolean useLod){
float scl = 8f * scale * Draw.scl, rot = Mathf.angle(x2 - x, y2 - y);
float vx = Mathf.cosDeg(rot) * scl, vy = Mathf.sinDeg(rot) * scl;
- float lod = start.width * scale * start.scl() < 10f ? Lod.alpha1 : Lod.alpha2;
+ float lod = useLod ? (start.width * scale * start.scl() < 10f ? Lod.alpha1 : Lod.alpha2) : 1f;
float a = Draw.getColorAlpha();
if(a >= 1f/255f){
diff --git a/core/src/mindustry/graphics/MenuRenderer.java b/core/src/mindustry/graphics/MenuRenderer.java
index 742216ba65..47ccd0ed5d 100644
--- a/core/src/mindustry/graphics/MenuRenderer.java
+++ b/core/src/mindustry/graphics/MenuRenderer.java
@@ -242,6 +242,7 @@ public class MenuRenderer implements Disposable{
flyerType.sample.elevation = 1f;
flyerType.sample.team = Team.sharded;
flyerType.sample.rotation = flyerRot;
+ flyerType.sample.setType(flyerType);
flyerType.sample.heal();
flyers((x, y) -> {
diff --git a/core/src/mindustry/io/JsonIO.java b/core/src/mindustry/io/JsonIO.java
index c612eccd13..56cc9bf4a9 100644
--- a/core/src/mindustry/io/JsonIO.java
+++ b/core/src/mindustry/io/JsonIO.java
@@ -20,11 +20,7 @@ import java.io.*;
@SuppressWarnings("unchecked")
public class JsonIO{
- private static final CustomJson jsonBase = new CustomJson();
-
public static final Json json = new Json(){
- { apply(this); }
-
@Override
public void writeValue(Object value, Class knownType, Class elementType){
if(value instanceof MappableContent c){
@@ -80,8 +76,9 @@ public class JsonIO{
return json.fromJson(type, string.replace("io.anuke.", ""));
}
- public static T read(Class type, T base, String string){
- return jsonBase.fromBaseJson(type, base, string.replace("io.anuke.", ""));
+ public static T read(T base, String string){
+ json.readFields(base, new JsonReader().parse(string.replace("io.anuke.", "")));
+ return base;
}
public static String print(String in){
@@ -90,10 +87,9 @@ public class JsonIO{
public static void classTag(String tag, Class> type){
json.addClassTag(tag, type);
- jsonBase.addClassTag(tag, type);
}
- static void apply(Json json){
+ static{
json.setElementType(Rules.class, "spawns", SpawnGroup.class);
json.setElementType(Rules.class, "loadout", ItemStack.class);
@@ -356,28 +352,4 @@ public class JsonIO{
json.addClassTag(Strings.camelize(i.getClass().getSimpleName().replace("Filter", "")), i.getClass());
}
}
-
- static class CustomJson extends Json{
- private Object baseObject;
-
- { apply(this); }
-
- @Override
- public T fromJson(Class type, String json){
- return fromBaseJson(type, null, json);
- }
-
- public T fromBaseJson(Class type, T base, String json){
- this.baseObject = base;
- return readValue(type, null, new JsonReader().parse(json));
- }
-
- @Override
- protected Object newInstance(Class type){
- if(baseObject == null || baseObject.getClass() != type){
- return super.newInstance(type);
- }
- return baseObject;
- }
- }
}
\ No newline at end of file
diff --git a/core/src/mindustry/io/SaveFileReader.java b/core/src/mindustry/io/SaveFileReader.java
index ff1523aef4..52084a9cbc 100644
--- a/core/src/mindustry/io/SaveFileReader.java
+++ b/core/src/mindustry/io/SaveFileReader.java
@@ -3,7 +3,6 @@ package mindustry.io;
import arc.struct.*;
import arc.struct.ObjectMap.*;
import arc.util.io.*;
-import mindustry.world.*;
import java.io.*;
@@ -190,7 +189,7 @@ public abstract class SaveFileReader{
return map;
}
- public abstract void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException;
+ public abstract void read(DataInputStream stream, CounterInputStream counter, SaveReadState state) throws IOException;
public abstract void write(DataOutputStream stream, SaveOptions options) throws IOException;
diff --git a/core/src/mindustry/io/SaveIO.java b/core/src/mindustry/io/SaveIO.java
index d697b73c7c..8aec7acaef 100644
--- a/core/src/mindustry/io/SaveIO.java
+++ b/core/src/mindustry/io/SaveIO.java
@@ -169,7 +169,7 @@ public class SaveIO{
if(ver == null) throw new IOException("Unknown save version: " + version + ". Are you trying to load a save from a newer version?");
- ver.read(stream, counter, context);
+ ver.read(stream, counter, new SaveReadState(context));
Events.fire(new SaveLoadEvent(context.isMap()));
}catch(Throwable e){
throw new SaveException(e);
diff --git a/core/src/mindustry/io/SaveReadState.java b/core/src/mindustry/io/SaveReadState.java
new file mode 100644
index 0000000000..81e1f95f6a
--- /dev/null
+++ b/core/src/mindustry/io/SaveReadState.java
@@ -0,0 +1,13 @@
+package mindustry.io;
+
+import arc.util.*;
+import mindustry.world.*;
+
+public class SaveReadState{
+ public final WorldContext context;
+ public @Nullable String ruleString;
+
+ public SaveReadState(WorldContext context){
+ this.context = context;
+ }
+}
diff --git a/core/src/mindustry/io/SaveVersion.java b/core/src/mindustry/io/SaveVersion.java
index dc7dd9162e..777afe9080 100644
--- a/core/src/mindustry/io/SaveVersion.java
+++ b/core/src/mindustry/io/SaveVersion.java
@@ -60,13 +60,13 @@ public abstract class SaveVersion extends SaveFileReader{
}
@Override
- public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
- readRegion("meta", stream, counter, in -> readMeta(in, context));
- if(version >= 12) readRegion("patches", stream, counter, this::readDataPatches);
+ public void read(DataInputStream stream, CounterInputStream counter, SaveReadState saveState) throws IOException{
+ readRegion("meta", stream, counter, in -> readMeta(in, saveState));
+ if(version >= 12) readRegion("patches", stream, counter, in -> readDataPatches(in, saveState));
try{
readRegion("content", stream, counter, this::readContentHeader);
- readRegion("map", stream, counter, in -> readMap(in, context));
+ readRegion("map", stream, counter, in -> readMap(in, saveState.context));
readRegion("entities", stream, counter, this::readEntities);
if(version >= 8) readRegion("markers", stream, counter, this::readMarkers);
readRegion("custom", stream, counter, this::readCustomChunks);
@@ -75,6 +75,7 @@ public abstract class SaveVersion extends SaveFileReader{
}
}
+ @Override
public void write(DataOutputStream stream, SaveOptions options) throws IOException{
writeRegion("meta", stream, out -> writeMeta(out, options.extraTags));
writeRegion("patches", stream, out -> writeDataPatches(out, options.embedAssets));
@@ -148,27 +149,20 @@ public abstract class SaveVersion extends SaveFileReader{
)));
}
- public void readMeta(DataInput stream, WorldContext context) throws IOException{
+ public void readMeta(DataInput stream, SaveReadState saveState) throws IOException{
StringMap map = readStringMap(stream);
state.wave = map.getInt("wave");
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
state.tick = map.getFloat("tick");
state.stats = JsonIO.read(GameStats.class, map.get("stats", "{}"));
- state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
state.mapLocales = JsonIO.read(MapLocales.class, map.get("locales", "{}"));
- if(state.rules.spawns.isEmpty()) state.rules.spawns = waves.get();
- if(context.getSector() != null){
- state.rules.sector = context.getSector();
- if(state.rules.sector != null){
- state.rules.sector.planet.applyRules(state.rules);
- }
- }
+ saveState.ruleString = map.get("rules", "{}");
- //replace the default serpulo env with erekir
- if(state.rules.planet == Planets.serpulo && state.rules.hasEnv(Env.scorching)){
- state.rules.planet = Planets.erekir;
+ //for versions >= 13, rules are parsed after data patches are loaded
+ if(version < 13){
+ readRules(saveState);
}
if(!headless){
@@ -196,6 +190,25 @@ public abstract class SaveVersion extends SaveFileReader{
)) : worldmap;
}
+ public void readRules(SaveReadState saveState){
+ if(saveState.ruleString == null) return; //in NetworkIO, rules are null, not read here
+ state.rules = JsonIO.read(Rules.class, saveState.ruleString);
+
+ if(state.rules.spawns.isEmpty()) state.rules.spawns = waves.get();
+
+ if(saveState.context.getSector() != null){
+ state.rules.sector = saveState.context.getSector();
+ if(state.rules.sector != null){
+ state.rules.sector.planet.applyRules(state.rules);
+ }
+ }
+
+ //replace the default serpulo env with erekir
+ if(state.rules.planet == Planets.serpulo && state.rules.hasEnv(Env.scorching)){
+ state.rules.planet = Planets.erekir;
+ }
+ }
+
public void writeMap(DataOutput stream) throws IOException{
//write world size
stream.writeShort(world.width());
@@ -527,7 +540,7 @@ public abstract class SaveVersion extends SaveFileReader{
readWorldEntities(stream, mapping);
}
- public void readDataPatches(DataInput stream) throws IOException{
+ public void readDataPatches(DataInput stream, SaveReadState saveState) throws IOException{
stream.readInt(); //version - ignored for now
int total = stream.readInt();
@@ -563,6 +576,9 @@ public abstract class SaveVersion extends SaveFileReader{
Events.fire(new DataPatchLoadEvent(assets));
state.data.load(assets);
+
+ //now that patches are loaded, the rules can actually be read
+ readRules(saveState);
}
public void writeDataPatches(DataOutput stream, boolean forceEmbed) throws IOException{
diff --git a/core/src/mindustry/io/versions/LegacyRegionSaveVersion.java b/core/src/mindustry/io/versions/LegacyRegionSaveVersion.java
index 4623352df2..f34a519ab4 100644
--- a/core/src/mindustry/io/versions/LegacyRegionSaveVersion.java
+++ b/core/src/mindustry/io/versions/LegacyRegionSaveVersion.java
@@ -1,7 +1,7 @@
package mindustry.io.versions;
import arc.util.io.*;
-import mindustry.world.*;
+import mindustry.io.*;
import java.io.*;
@@ -15,12 +15,12 @@ public class LegacyRegionSaveVersion extends ShortChunkSaveVersion{
}
@Override
- public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
- readRegion("meta", stream, counter, in -> readMeta(in, context));
+ public void read(DataInputStream stream, CounterInputStream counter, SaveReadState saveState) throws IOException{
+ readRegion("meta", stream, counter, in -> readMeta(in, saveState));
readRegion("content", stream, counter, this::readContentHeader);
try{
- readRegion("map", stream, counter, in -> readMap(in, context));
+ readRegion("map", stream, counter, in -> readMap(in, saveState.context));
readRegion("entities", stream, counter, this::readEntities);
}finally{
content.setTemporaryMapper(null);
diff --git a/core/src/mindustry/io/versions/Save11.java b/core/src/mindustry/io/versions/Save11.java
index ddf77c6b61..898d5dc405 100644
--- a/core/src/mindustry/io/versions/Save11.java
+++ b/core/src/mindustry/io/versions/Save11.java
@@ -7,7 +7,6 @@ import arc.util.io.*;
import mindustry.game.EventType.*;
import mindustry.io.*;
import mindustry.mod.data.*;
-import mindustry.world.*;
import java.io.*;
@@ -23,13 +22,13 @@ public class Save11 extends SaveVersion{
}
@Override
- public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
- readRegion("meta", stream, counter, in -> readMeta(in, context));
+ public void read(DataInputStream stream, CounterInputStream counter, SaveReadState saveState) throws IOException{
+ readRegion("meta", stream, counter, in -> readMeta(in, saveState));
readRegion("content", stream, counter, this::readContentHeader);
try{
- readRegion("patches", stream, counter, this::readDataPatches);
- readRegion("map", stream, counter, in -> readMap(in, context));
+ readRegion("patches", stream, counter, in -> readDataPatches(in, saveState));
+ readRegion("map", stream, counter, in -> readMap(in, saveState.context));
readRegion("entities", stream, counter, this::readEntities);
readRegion("markers", stream, counter, this::readMarkers);
readRegion("custom", stream, counter, this::readCustomChunks);
@@ -40,7 +39,7 @@ public class Save11 extends SaveVersion{
//old, simplified string-only data patches
@Override
- public void readDataPatches(DataInput stream) throws IOException{
+ public void readDataPatches(DataInput stream, SaveReadState saveState) throws IOException{
Seq assets = new Seq<>();
int amount = stream.readUnsignedByte();
diff --git a/core/src/mindustry/io/versions/Save12.java b/core/src/mindustry/io/versions/Save12.java
index b3f117820f..b0e41f429e 100644
--- a/core/src/mindustry/io/versions/Save12.java
+++ b/core/src/mindustry/io/versions/Save12.java
@@ -19,7 +19,7 @@ public class Save12 extends SaveVersion{
}
@Override
- public void readDataPatches(DataInput stream) throws IOException{
+ public void readDataPatches(DataInput stream, SaveReadState saveState) throws IOException{
stream.readInt(); //version - ignored for now
Seq assets = new Seq<>();
diff --git a/core/src/mindustry/logic/LAccess.java b/core/src/mindustry/logic/LAccess.java
index be03d5cbfa..4173784ef2 100644
--- a/core/src/mindustry/logic/LAccess.java
+++ b/core/src/mindustry/logic/LAccess.java
@@ -62,6 +62,7 @@ public enum LAccess{
team,
type,
flag,
+ flying,
controlled,
controller,
name,
diff --git a/core/src/mindustry/maps/Map.java b/core/src/mindustry/maps/Map.java
index 758b31aa04..fd607af2ff 100644
--- a/core/src/mindustry/maps/Map.java
+++ b/core/src/mindustry/maps/Map.java
@@ -100,9 +100,8 @@ public class Map implements Comparable