More patcher tests + unit type reassign support

This commit is contained in:
Anuken
2025-10-25 16:39:07 -04:00
parent 4812f89161
commit 09783898aa
3 changed files with 68 additions and 29 deletions

View File

@@ -742,7 +742,7 @@ public class ContentParser{
} }
); );
private Prov<Unit> unitType(JsonValue value){ Prov<Unit> unitType(JsonValue value){
if(value == null) return UnitEntity::create; if(value == null) return UnitEntity::create;
return switch(value.asString()){ return switch(value.asString()){
case "flying" -> UnitEntity::create; case "flying" -> UnitEntity::create;

View File

@@ -81,6 +81,11 @@ public class ContentPatcher{
currentlyApplying = null; currentlyApplying = null;
}catch(Exception e){ }catch(Exception e){
PatchSet set = new PatchSet(patch, new JsonValue("error"));
set.error = true;
set.warnings.add(Strings.getSimpleMessage(e));
patches.add(set);
Log.err("Failed to apply patch: " + patch, e); Log.err("Failed to apply patch: " + patch, e);
} }
} }
@@ -283,10 +288,10 @@ public class ContentPatcher{
var fields = parser.getJson().getFields(actualType); var fields = parser.getJson().getFields(actualType);
var fdata = fields.get(field); var fdata = fields.get(field);
var fobj = object;
if(fdata != null){ if(fdata != null){
if(checkField(fdata.field)) return; if(checkField(fdata.field)) return;
var fobj = object;
assignValue(object, field, new FieldData(fdata), () -> Reflect.get(fobj, fdata.field), fv -> { assignValue(object, field, new FieldData(fdata), () -> Reflect.get(fobj, fdata.field), fv -> {
if(fv == null && !fdata.field.isAnnotationPresent(Nullable.class) && !(Vars.headless && ContentParser.implicitNullable.contains(fdata.field.getType()))){ if(fv == null && !fdata.field.isAnnotationPresent(Nullable.class) && !(Vars.headless && ContentParser.implicitNullable.contains(fdata.field.getType()))){
warn("Field '@' cannot be null.", fdata.field); warn("Field '@' cannot be null.", fdata.field);
@@ -311,6 +316,9 @@ public class ContentPatcher{
Log.err(e); Log.err(e);
warn("Failed to read consumers for '@': @", bl, Strings.getSimpleMessage(e)); warn("Failed to read consumers for '@': @", bl, Strings.getSimpleMessage(e));
} }
}else if(value instanceof JsonValue jsv && object instanceof UnitType && field.equals("type")){
var fmeta = fields.get("constructor");
assignValue(object, "constructor", new FieldData(fmeta), () -> Reflect.get(fobj, fmeta.field), val -> Reflect.set(fobj, fmeta.field, val), parser.unitType(jsv), true);
}else{ }else{
warn("Unknown field '@' for class '@'", field, actualType.getSimpleName()); warn("Unknown field '@' for class '@'", field, actualType.getSimpleName());
} }
@@ -320,37 +328,41 @@ public class ContentPatcher{
void assignValue(Object object, String field, FieldData metadata, Prov getter, Cons setter, Object value, boolean modify) throws Exception{ void assignValue(Object object, String field, FieldData metadata, Prov getter, Cons setter, Object value, boolean modify) throws Exception{
Object prevValue = getter.get(); Object prevValue = getter.get();
if(value instanceof JsonValue jsv){ //setting values from object try{
if(prevValue == null || !jsv.isObject() || jsv.has("type")){ if(value instanceof JsonValue jsv){ //setting values from object
if(UnlockableContent.class.isAssignableFrom(metadata.type) && jsv.isObject()){ if(prevValue == null || !jsv.isObject() || jsv.has("type")){
warn("New content must not be instantiated: @", jsv); if(UnlockableContent.class.isAssignableFrom(metadata.type) && jsv.isObject()){
return; warn("New content must not be instantiated: @", jsv);
} return;
}
if(modify) modifiedField(object, field, getter.get()); if(modify) modifiedField(object, field, getter.get());
//HACK: listen for creation of objects once //HACK: listen for creation of objects once
parser.listeners.add((type, jsonData, result) -> created(result, object)); parser.listeners.add((type, jsonData, result) -> created(result, object));
try{ try{
setter.get(parser.getJson().readValue(metadata.type, metadata.elementType, jsv)); setter.get(parser.getJson().readValue(metadata.type, metadata.elementType, jsv));
}catch(Throwable e){ }catch(Throwable e){
warn("Failed to read value @.@ = @: @ (type = @ elementType = @)\n@", object, field, value, e.getMessage(), metadata.type, metadata.elementType, Strings.getStackTrace(e)); warn("Failed to read value @.@ = @: @ (type = @ elementType = @)\n@", object, field, value, e.getMessage(), metadata.type, metadata.elementType, Strings.getStackTrace(e));
} }
parser.listeners.pop(); parser.listeners.pop();
}else{ }else{
//assign each field manually //assign each field manually
var childFields = parser.getJson().getFields(prevValue.getClass().isAnonymousClass() ? prevValue.getClass().getSuperclass() : prevValue.getClass()); var childFields = parser.getJson().getFields(prevValue.getClass().isAnonymousClass() ? prevValue.getClass().getSuperclass() : prevValue.getClass());
for(var child : jsv){ for(var child : jsv){
if(child.name != null){ if(child.name != null){
assign(prevValue, child.name, child, !childFields.containsKey(child.name) ? null : new FieldData(childFields.get(child.name)), object, field); assign(prevValue, child.name, child, !childFields.containsKey(child.name) ? null : new FieldData(childFields.get(child.name)), object, field);
}
} }
} }
} }else{
}else{ //direct value is set
//direct value is set if(modify) modifiedField(object, field, prevValue);
if(modify) modifiedField(object, field, prevValue);
setter.get(value); setter.get(value);
}
}catch(Throwable e){
warn("Failed to assign @.@ = @: @", object, field, value, Strings.getStackTrace(e));
} }
} }
@@ -476,6 +488,7 @@ public class ContentPatcher{
public String patch; public String patch;
public JsonValue json; public JsonValue json;
public String name; public String name;
public boolean error;
public Seq<String> warnings = new Seq<>(); public Seq<String> warnings = new Seq<>();
public PatchSet(String patch, JsonValue json){ public PatchSet(String patch, JsonValue json){

View File

@@ -3,6 +3,7 @@ import mindustry.*;
import mindustry.content.*; import mindustry.content.*;
import mindustry.entities.abilities.*; import mindustry.entities.abilities.*;
import mindustry.entities.bullet.*; import mindustry.entities.bullet.*;
import mindustry.gen.*;
import mindustry.type.*; import mindustry.type.*;
import mindustry.world.blocks.units.*; import mindustry.world.blocks.units.*;
import mindustry.world.meta.*; import mindustry.world.meta.*;
@@ -197,6 +198,20 @@ public class PatcherTests{
assertEquals(oldLength, UnitTypes.dagger.targetFlags.length); assertEquals(oldLength, UnitTypes.dagger.targetFlags.length);
} }
@Test
void testUnitType() throws Exception{
Vars.state.patcher.apply(Seq.with("""
unit.dagger.type: legs
"""));
assertEquals(0, Vars.state.patcher.patches.first().warnings.size);
assertEquals(LegsUnit.class, UnitTypes.dagger.constructor.get().getClass());
Vars.logic.reset();
assertEquals(MechUnit.class, UnitTypes.dagger.constructor.get().getClass());
}
@Test @Test
void testCannotPatch() throws Exception{ void testCannotPatch() throws Exception{
Vars.state.patcher.apply(Seq.with(""" Vars.state.patcher.apply(Seq.with("""
@@ -207,6 +222,17 @@ public class PatcherTests{
assertEquals(1, Blocks.conveyor.size); assertEquals(1, Blocks.conveyor.size);
} }
@Test
void testGibberish() throws Exception{
Vars.state.patcher.apply(Seq.with("""
}[35209509()jfkjhadsf,
,,,,,[]
]{
"""));
assertEquals(1, Vars.state.patcher.patches.first().warnings.size);
}
@Test @Test
void testUnknownFieldWarn() throws Exception{ void testUnknownFieldWarn() throws Exception{
Vars.state.patcher.apply(Seq.with(""" Vars.state.patcher.apply(Seq.with("""
@@ -218,7 +244,7 @@ public class PatcherTests{
unit.dagger.frogs: 10 unit.dagger.frogs: 10
""")); """));
assertEquals(2, Vars.state.patcher.patches.first().warnings.size); assertEquals(1, Vars.state.patcher.patches.first().warnings.size);
} }
@Test @Test