Add Dynamic ToolStatType to datapack#5573
Add Dynamic ToolStatType to datapack#5573Qikahome wants to merge 13 commits intoSlimeKnights:1.20.1from
Conversation
|
I know nothing about the test, so it cant build now, but it can run client |
KnightMiner
left a comment
There was a problem hiding this comment.
This has the start of something that could work, but it does seem you made some design decisions that will make this very hard to maintain. I left some comments proposing a cleaner design. Feel free to ping me on discord and we can discuss it more in depth.
| /** | ||
| * Clears all dynamic stat types.. | ||
| */ | ||
| void clearDynamicStatTypes(); |
There was a problem hiding this comment.
This should not be publicly exposed. We only need to clear then before stats load.
| * Fallback is not supported for dynamic stat types. | ||
| * @param type Stat type | ||
| */ | ||
| void registerDynamicStatType(FlexMaterialStatType type); |
There was a problem hiding this comment.
This does not seem necessary. Its loaded in JSON, why must it be registered?
If it has to be registered, a JSON only addon can't use it.
| materialStatTypesLoader = new MaterialStatTypesLoader(() -> { | ||
| statsLoaded = true; | ||
| checkAllLoaded(); | ||
| }); |
There was a problem hiding this comment.
Why do we need this? Just put the loading at the start of the material stats loader. You may need to override a method for that. Also ensures stat types are guaranteed to load before we need them instead of being non-deterministic.
Also, setting the same boolean as another loader is just wrong. But since this is the wrong approach don't worry about that part of the comment.
| @@ -0,0 +1,5 @@ | |||
| package slimeknights.tconstruct.library.materials.stats; | |||
|
|
|||
| public record IMaterialRegister() { | |||
There was a problem hiding this comment.
Why is there an empty record named like an interface here? This seems like an accidental commit.
New classes and public methods should have javadocs explaining how they are used.
| * Another registry, cleared on reload. | ||
| */ | ||
| @Getter | ||
| private IdAwareComponentRegistry<FlexMaterialStatType> dynamicStatTypes = null; |
There was a problem hiding this comment.
This probably does not need to be publicly exposed as the other is only exposed so you can register to it.
Part of me thinks it would be better to make this a Map instead of the registry as I am not sure its saving much using that class. Feel free to justify though where its making the code easier.
| public class FlexMaterialStatType extends MaterialStatType<FlexMaterialStat> { | ||
|
|
||
| public FlexMaterialStatType(MaterialStatsId id, boolean canRepair, | ||
| LinkedHashMap<String, Supplier<Stat<?, ?>>> stats) { |
There was a problem hiding this comment.
This should take a List<DynamicStatField> in the constructor.
Also, canRepair should be computed from that list, not passed in separately.
|
|
||
| public FlexMaterialStatType(MaterialStatsId id, boolean canRepair, | ||
| LinkedHashMap<String, Supplier<Stat<?, ?>>> stats) { | ||
| super(id, (FlexMaterialStat) null, null); |
There was a problem hiding this comment.
I really don't like passing in null to super here, we should be able to create the default from the List<DynamicStatField>, by giving each field a default value.
| @Override | ||
| public void registerDynamicStatType(FlexMaterialStatType type) { | ||
| throw new UnsupportedOperationException("No registration possible in test mock"); | ||
| } |
There was a problem hiding this comment.
These need not even exist in our tests. Entirely internal to the stats manager
|
|
||
| import slimeknights.tconstruct.library.tools.stat.ToolStatId; | ||
|
|
||
| class ToolStatTypeNotMatchException extends RuntimeException { |
There was a problem hiding this comment.
Again, why the custom exception? Looks to me like another json parse exception.
| ); | ||
|
|
||
| UpdateMaterialStatsPacket packetToDecode = sendAndReceivePacket(materialToStats); | ||
| Collection<FlexMaterialStatType> dynamic = new HashSet<>();// I dont know how to test so just an empty set |
There was a problem hiding this comment.
The way you test is you write JSON for custom stat types, and then use them to load stats with those types.
From the packet, make sure:
- The stat type you send in comes back out the same
- stats using the stat type you send in come back out the same
But it also needs tests for JSON parsing:
- make sure you can parse a stat type as expected.
- make sure stats using the custom stat type parse as expected.
|
I wrote the code for the test part, but it seems that ToolStats wasn't registered in the test, which caused my code to throw a JsonParseException. |
|
Tests are completed |
| public static final List<DynamicStatField<?>> statFields = List.of( | ||
| new FloatDynamicStatField("test1",ToolStats.DURABILITY,1f,FloatDynamicStatField.Operation.PERCENT, "desc1", "info1"), | ||
| new FloatDynamicStatField("test2",ToolStats.MINING_SPEED,2f,FloatDynamicStatField.Operation.UPDATE, "desc2", "info2"), | ||
| new TierDynamicStatField("test3",ToolStats.HARVEST_TIER,Tiers.STONE,"desc3") |
There was a problem hiding this comment.
Worth noting, we do use these stats in other tests. You might just need to ensure static init is run before the deserialize method runs.
But yeah, fake stats are fine for the tests. I sometimes use "test" as a namespace for them to distinguish them.
src/test/java/slimeknights/tconstruct/library/materials/stats/MaterialStatsManagerTest.java
Outdated
Show resolved
Hide resolved
|
May I ask how this PR is going? |
|
This is not the only thing I have to work on nor the only PR I have to review right now. You don't have to keep commenting on it requesting an update. |
KnightMiner
left a comment
There was a problem hiding this comment.
My main comment on this is you seem to be reinventing the wheel in how DynamicStatField works. You can simplify that a lot if you take advantage of the Loadable API we already use elsewhere.
Feel free to talk to me on #coding on our discord if you have questions about how to do that.
| */ | ||
| @Nullable | ||
| public DynamicStat getStat(String name) | ||
| { |
There was a problem hiding this comment.
Minor, but be consistent on formatting. In this case, brackets go on same line, not newline.
| * @return Stat, or null if not found | ||
| */ | ||
| @Nullable | ||
| public DynamicStat getStat(String name) |
There was a problem hiding this comment.
I understand what this method does from the javadoc, but not why it is needed. Does any other class use it or is it just used internally?
There was a problem hiding this comment.
In network transmission or serialization, we need specific property values, but it's not feasible to create a getter for every DynamicStat.
So I get the value from the name.
|
|
||
| @Override | ||
| public List<Component> getLocalizedInfo() { | ||
| return stats.values().stream().map(stat->stat.getLocalizedInfo()).collect(Collectors.toList()); |
There was a problem hiding this comment.
Probably worth caching these two lists as part of the constructor since they are not changing.
Also, you can use DynamicStat::getLocalizedInfo for the map call, IDEA has a warning in place if you don't do that so I tend to do it.
|
|
||
| @Override | ||
| public void apply(@Nonnull ModifierStatsBuilder builder, float scale) { | ||
| stats.values().forEach(stat -> { |
There was a problem hiding this comment.
Similarly to above, IDEA is configured to warn of single line lambda statements; can make this compact as stat -> stat.apply(builder, scale)
| * A record class that represents a dynamic material stat. | ||
| */ | ||
| public record DynamicMaterialStatRecord(MaterialStatType<?> type, List<DynamicStatField<?>> statFields) | ||
| implements RecordLoadable<DynamicMaterialStat> { |
There was a problem hiding this comment.
There is not a line length limit for this repository; keep implements on the same like as the class declaration unless its seriously long (in which case I tend to prefer splitting the record constructor instead).
| private boolean canRepair = false; | ||
| @Getter | ||
| @Setter | ||
| @Accessors(fluent = true) |
There was a problem hiding this comment.
Builders should chain, use chain=true.
| * @return The built material stat type. | ||
| */ | ||
| public DynamicMaterialStatType build() { | ||
| return new DynamicMaterialStatType(new MaterialStatsId(registryName), this.canRepair, fields); |
There was a problem hiding this comment.
Might as well make the field be MaterialStatsId instead of waiting to do so. Add name overload for ResourceLocation if you wish.
| * stat types is "data/foobar/materials/stat_types". | ||
| */ | ||
| @Log4j2 | ||
| public class MaterialStatTypesLoader extends MergingJsonDataLoader<MaterialStatTypeBuilder> { |
There was a problem hiding this comment.
Loaders should not be merging, we do not need multiple sources of what a stat type looks like.
Take a look at MaterialManager for reference.
| onLoaded.run(); | ||
| } | ||
|
|
||
| public void setDynamicStatTypes(Map<MaterialStatsId, DynamicMaterialStatType> dynamicStatTypes) { |
There was a problem hiding this comment.
Needs a javadoc. I assume this is set from the packet? If so, an @Internal is a good idea too.
| /** Map of material stat types, keyed by material ID */ | ||
| @Getter | ||
| @Setter | ||
| private Map<MaterialStatsId, DynamicMaterialStatType> statTypes; |
There was a problem hiding this comment.
This being nullable is just leading to a bunch of redundant checks. Just make it default to Map.of() (empty map).
It seems work well;
TEST THINGPACK:
testpack.zip