From 50e2b828f7a1f32dd656b56f950abffa406d0b0b Mon Sep 17 00:00:00 2001 From: Anuken Date: Sat, 25 Oct 2025 22:24:32 -0400 Subject: [PATCH] More tests, fixed patcher error handler --- core/src/mindustry/mod/ContentPatcher.java | 43 ++++++++++++++++---- core/src/mindustry/world/meta/Attribute.java | 6 +++ tests/src/test/java/PatcherTests.java | 35 ++++++++++++++++ 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/core/src/mindustry/mod/ContentPatcher.java b/core/src/mindustry/mod/ContentPatcher.java index 43547894a1..ed03f6ca89 100644 --- a/core/src/mindustry/mod/ContentPatcher.java +++ b/core/src/mindustry/mod/ContentPatcher.java @@ -3,8 +3,8 @@ package mindustry.mod; import arc.func.*; import arc.struct.*; import arc.util.*; -import arc.util.serialization.*; import arc.util.serialization.Json.*; +import arc.util.serialization.*; import arc.util.serialization.Jval.*; import mindustry.*; import mindustry.core.*; @@ -12,8 +12,10 @@ import mindustry.ctype.*; import mindustry.entities.part.*; import mindustry.type.*; import mindustry.world.*; +import mindustry.world.blocks.*; import mindustry.world.consumers.*; import mindustry.world.draw.*; +import mindustry.world.meta.*; import java.lang.reflect.*; import java.util.*; @@ -81,10 +83,8 @@ public class ContentPatcher{ currentlyApplying = null; }catch(Exception e){ - PatchSet set = new PatchSet(patch, new JsonValue("error")); - set.error = true; - set.warnings.add(Strings.getSimpleMessage(e)); - patches.add(set); + patches.peek().error = true; + patches.peek().warnings.add(Strings.getSimpleMessage(e)); Log.err("Failed to apply patch: " + patch, e); } @@ -260,7 +260,23 @@ public class ContentPatcher{ }else if(object instanceof ObjectSet set && field.equals("+")){ modifiedField(parentObject, parentField, set.copy()); - assignValue(object, field, metadata, () -> null, val -> set.add(val), value, false); + var meta = new FieldData(metadata.elementType, null, null); + boolean multiAdd; + + if(value instanceof JsonValue jval && jval.isArray()){ + meta = metadata; + multiAdd = true; + }else{ + multiAdd = false; + } + + assignValue(object, field, multiAdd ? meta : metadata, () -> null, val -> { + if(multiAdd){ + set.addAll((ObjectSet)val); + }else{ + set.add(val); + } + }, value, false); }else if(object instanceof ObjectMap map){ if(metadata == null){ warn("ObjectMap cannot be parsed without metadata: @.@", parentObject, parentField); @@ -282,6 +298,19 @@ public class ContentPatcher{ }else{ assignValue(object, field, new FieldData(metadata.elementType, null, null), () -> map.get(key), val -> map.put(key, val), value, false); } + }else if(object instanceof Attributes map && value instanceof JsonValue jval){ + Attribute key = Attribute.getOrNull(field); + if(key == null){ + warn("Unknown attribute: '@'", field); + return; + } + if(!jval.isNumber()){ + warn("Attribute value must be a number: '@'", jval); + return; + } + float prev = map.get(key); + reset(() -> map.set(key, prev)); + map.set(key, jval.asFloat()); }else{ Class actualType = object.getClass(); if(actualType.isAnonymousClass()) actualType = actualType.getSuperclass(); @@ -330,7 +359,7 @@ public class ContentPatcher{ try{ if(value instanceof JsonValue jsv){ //setting values from object - if(prevValue == null || !jsv.isObject() || jsv.has("type")){ + if(prevValue == null || !jsv.isObject() || jsv.has("type") || (metadata != null && metadata.type == Attributes.class)){ if(UnlockableContent.class.isAssignableFrom(metadata.type) && jsv.isObject()){ warn("New content must not be instantiated: @", jsv); return; diff --git a/core/src/mindustry/world/meta/Attribute.java b/core/src/mindustry/world/meta/Attribute.java index 8b960f2b36..2f6a9ab040 100644 --- a/core/src/mindustry/world/meta/Attribute.java +++ b/core/src/mindustry/world/meta/Attribute.java @@ -1,6 +1,7 @@ package mindustry.world.meta; import arc.struct.*; +import arc.util.*; import mindustry.*; public class Attribute{ @@ -47,6 +48,11 @@ public class Attribute{ return map.getThrow(name, () -> new IllegalArgumentException("Unknown Attribute type: " + name)); } + /** May return null. */ + public static @Nullable Attribute getOrNull(String name){ + return map.get(name); + } + /** @return Whether an attribute exists. */ public static boolean exists(String name){ return map.containsKey(name); diff --git a/tests/src/test/java/PatcherTests.java b/tests/src/test/java/PatcherTests.java index d113b78882..d272510e7a 100644 --- a/tests/src/test/java/PatcherTests.java +++ b/tests/src/test/java/PatcherTests.java @@ -247,6 +247,41 @@ public class PatcherTests{ assertEquals(1, Vars.state.patcher.patches.first().warnings.size); } + @Test + void testAttributes() throws Exception{ + Vars.state.patcher.apply(Seq.with(""" + block.grass.attributes: { + oil: 99 + } + block.grass.attributes.heat: 77 + """)); + + assertEquals(new Seq<>(), Vars.state.patcher.patches.first().warnings); + assertEquals(99, Blocks.grass.attributes.get(Attribute.oil)); + assertEquals(77, Blocks.grass.attributes.get(Attribute.heat)); + + Vars.logic.reset(); + + assertEquals(0, Blocks.grass.attributes.get(Attribute.oil)); + assertEquals(0, Blocks.grass.attributes.get(Attribute.heat)); + } + + @Test + void testSetMultiAdd() throws Exception{ + Vars.state.patcher.apply(Seq.with(""" + unit.dagger.immunities.+: [slow, fast] + """)); + + assertEquals(new Seq<>(), Vars.state.patcher.patches.first().warnings); + assertTrue(UnitTypes.dagger.immunities.contains(StatusEffects.slow)); + assertTrue(UnitTypes.dagger.immunities.contains(StatusEffects.fast)); + + Vars.logic.reset(); + + assertFalse(UnitTypes.dagger.immunities.contains(StatusEffects.slow)); + assertFalse(UnitTypes.dagger.immunities.contains(StatusEffects.fast)); + } + @Test void testBigPatch() throws Exception{ Vars.state.patcher.apply(Seq.with("""