Skip to content

Commit 70f0ce4

Browse files
XxFran10xXclaude
andauthored
fix: keep alloys working after ac reload (#40)
`ac reload` rebuilt ingredient types, hits and schemes as new objects, but loaded alloys and open stations kept the old ones, and stations looked them up by object. Every alloy then stopped counting as its type until a restart: `ac give equipment ... alloy.<id>` said "0 out of 4 Metal items" and adding an alloy to a station threw. Ingredient types, hit types and hits now compare by id, and a reload points the loaded alloys at the reloaded objects. Co-authored-by: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f512a24 commit 70f0ce4

7 files changed

Lines changed: 204 additions & 2 deletions

File tree

‎src/main/java/net/tfminecraft/advancedcrafting/AdvancedCrafting.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ public void createConfigs() {
186186

187187
public void reload() {
188188
loadConfigs();
189+
AlloyManager.relinkAlloys();
189190
revisionTracker.flush();
190191
}
191192
// Keep the existing legacy text representation, formatting, and exact-string comparisons.

‎src/main/java/net/tfminecraft/advancedcrafting/managers/AlloyManager.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ public static void removeAlloy(String id) {
5858
public static void addAlloy(Alloy a) {
5959
alloys.put(a.getId(), a);
6060
}
61+
/** After a config reload, so alloys use the reloaded types, hits and schemes. */
62+
public static void relinkAlloys() {
63+
for (Alloy a : alloys.values()) {
64+
a.getData().relink();
65+
}
66+
}
6167
public boolean hasStation(Location loc) {
6268
return stations.containsKey(loc);
6369
}

‎src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/hits/CraftingHit.java‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ public String getName() {
3232
public HitType getType() {
3333
return type;
3434
}
35-
36-
35+
36+
// By id: a reload builds new objects, while alloys and open stations still hold the old ones.
37+
@Override
38+
public boolean equals(Object obj) {
39+
return obj instanceof CraftingHit other && id.equals(other.id);
40+
}
41+
42+
@Override
43+
public int hashCode() {
44+
return id.hashCode();
45+
}
3746
}

‎src/main/java/net/tfminecraft/advancedcrafting/objects/crafting/hits/HitType.java‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,15 @@ public String getId() {
1818
public String getName() {
1919
return name;
2020
}
21+
22+
// By id: a reload builds new objects, while open stations still hold the old ones.
23+
@Override
24+
public boolean equals(Object obj) {
25+
return obj instanceof HitType other && id.equals(other.id);
26+
}
27+
28+
@Override
29+
public int hashCode() {
30+
return id.hashCode();
31+
}
2132
}

‎src/main/java/net/tfminecraft/advancedcrafting/objects/data/AlloyData.java‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import java.util.Collections;
55
import java.util.HashMap;
66
import java.util.List;
7+
import java.util.function.Function;
78
import java.util.stream.Collectors;
89

10+
import net.tfminecraft.advancedcrafting.loaders.HitLoader;
11+
import net.tfminecraft.advancedcrafting.loaders.SchemeLoader;
12+
import net.tfminecraft.advancedcrafting.loaders.TypeLoader;
913
import net.tfminecraft.advancedcrafting.objects.crafting.hits.CraftingHit;
1014
import net.tfminecraft.advancedcrafting.objects.ingredients.Ingredient;
1115
import net.tfminecraft.advancedcrafting.objects.ingredients.IngredientType;
@@ -121,6 +125,26 @@ public int getTier() {
121125
return tier;
122126
}
123127

128+
/** After a config reload: swaps in the reloaded type, hits and schemes, keeping any that are gone. */
129+
public void relink() {
130+
type = relinked(type, IngredientType::getId, TypeLoader::getIngredientTypeByString);
131+
colourScheme = relinked(colourScheme, ColourScheme::getId, SchemeLoader::getColourSchemeByString);
132+
modelScheme = relinked(modelScheme, ModelScheme::getId, SchemeLoader::getModelSchemeByString);
133+
HashMap<CraftingHit, Integer> current = new HashMap<>();
134+
hits.forEach((hit, amount) -> current.put(relinked(hit, CraftingHit::getId, HitLoader::getByString), amount));
135+
// Cleared first: put() would keep the old key, which equals the new one.
136+
hits.clear();
137+
hits.putAll(current);
138+
}
139+
140+
private static <T> T relinked(T old, Function<T, String> id, Function<String, T> lookup) {
141+
if (old == null) {
142+
return null;
143+
}
144+
T fresh = lookup.apply(id.apply(old));
145+
return fresh != null ? fresh : old;
146+
}
147+
124148
public String buildRevisionContent() {
125149
StringBuilder sb = new StringBuilder();
126150
sb.append("xp=").append(xp != null ? xp : "").append(';');

‎src/main/java/net/tfminecraft/advancedcrafting/objects/ingredients/IngredientType.java‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,15 @@ public String getId() {
2020
public String getName() {
2121
return name;
2222
}
23+
24+
// By id: a reload builds new objects, while alloys and open stations still hold the old ones.
25+
@Override
26+
public boolean equals(Object obj) {
27+
return obj instanceof IngredientType other && id.equals(other.id);
28+
}
29+
30+
@Override
31+
public int hashCode() {
32+
return id.hashCode();
33+
}
2334
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package net.tfminecraft.advancedcrafting;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import java.util.*;
6+
import net.tfminecraft.advancedcrafting.enums.StationFeedback;
7+
import net.tfminecraft.advancedcrafting.loaders.*;
8+
import net.tfminecraft.advancedcrafting.managers.AlloyManager;
9+
import net.tfminecraft.advancedcrafting.objects.alloys.Alloy;
10+
import net.tfminecraft.advancedcrafting.objects.crafting.CraftingStation;
11+
import net.tfminecraft.advancedcrafting.objects.crafting.hits.*;
12+
import net.tfminecraft.advancedcrafting.objects.data.*;
13+
import net.tfminecraft.advancedcrafting.objects.ingredients.IngredientType;
14+
import net.tfminecraft.advancedcrafting.objects.schemes.*;
15+
import net.tfminecraft.advancedcrafting.utils.PDCKeys;
16+
import org.bukkit.*;
17+
import org.bukkit.inventory.ItemStack;
18+
import org.bukkit.persistence.PersistentDataType;
19+
import org.junit.jupiter.api.Test;
20+
21+
/** {@code ac reload} builds new types, hits and schemes; alloys loaded before it must keep working. */
22+
class AlloyReloadTest extends CoverageSupport {
23+
ItemStack alloyItem(String id) {
24+
var i = new ItemStack(Material.IRON_INGOT);
25+
var m = i.getItemMeta();
26+
m.getPersistentDataContainer().set(PDCKeys.alloyId(), PersistentDataType.STRING, id);
27+
i.setItemMeta(m);
28+
return i;
29+
}
30+
31+
Alloy alloy(String id, IngredientType type, HashMap<CraftingHit, Integer> hits) {
32+
var data =
33+
new AlloyData(
34+
SchemeLoader.colours.get("default"),
35+
2,
36+
type,
37+
SchemeLoader.models.get("default"),
38+
new StatData(),
39+
hits,
40+
null,
41+
null,
42+
1,
43+
null);
44+
var alloy = new Alloy(id, id, data);
45+
AlloyManager.addAlloy(alloy);
46+
return alloy;
47+
}
48+
49+
void reloadTypesAndHits() throws Exception {
50+
TypeLoader.map.put("metal", new IngredientType("metal", yaml("name: Metal")));
51+
TypeLoader.hMap.put("hammer", new HitType("hammer", yaml("name: Hammer")));
52+
HitLoader.map.put(
53+
"strike", new CraftingHit("strike", yaml("name: Strike\ntype: hammer\ntool: v.iron_axe")));
54+
}
55+
56+
@Test
57+
void alloyLoadedBeforeReloadStillFillsAStation() throws Exception {
58+
alloy(
59+
"darksteel",
60+
TypeLoader.map.get("metal"),
61+
new HashMap<>(Map.of(HitLoader.map.get("strike"), 2)));
62+
reloadTypesAndHits();
63+
var p = server.addPlayer();
64+
p.setOp(true);
65+
var station = new CraftingStation(new Location(server.addSimpleWorld("world"), 1, 2, 3));
66+
station.setRecipe(recipe("recipe: ['metal.1']"));
67+
var item = alloyItem("darksteel");
68+
p.getInventory().setItemInMainHand(item);
69+
assertEquals(StationFeedback.SUCCESS, station.addMaterial(p, item));
70+
assertTrue(station.hasAllMaterials(p));
71+
assertEquals(2, station.getHits().get(HitLoader.map.get("strike")).getNeeded());
72+
// `ac give equipment ... alloy.<id>` fills a station this way.
73+
var given =
74+
new CraftingStation(
75+
station.getLoc(),
76+
recipe("recipe: ['metal.4']"),
77+
new HashMap<>(Map.of("alloy.darksteel", 4)),
78+
new HashMap<>());
79+
assertEquals(4, given.getTypes().get(TypeLoader.map.get("metal")).getCurrent());
80+
}
81+
82+
@Test
83+
void relinkSwapsInReloadedObjectsAndKeepsMissingOnes() throws Exception {
84+
var oldStrike = HitLoader.map.get("strike");
85+
var gone = new CraftingHit("gone", yaml("name: Gone\ntype: hammer\ntool: v.stick"));
86+
var hits = new HashMap<CraftingHit, Integer>();
87+
hits.put(oldStrike, 2);
88+
hits.put(gone, 1);
89+
hits.put(null, 4);
90+
var darksteel = alloy("darksteel", TypeLoader.map.get("metal"), hits).getData();
91+
var typeless = alloy("typeless", null, new HashMap<>()).getData();
92+
TypeLoader.map.put("wood", new IngredientType("wood", yaml("name: Wood")));
93+
var wooden = alloy("wooden", TypeLoader.map.get("wood"), new HashMap<>()).getData();
94+
var oldWood = TypeLoader.map.remove("wood");
95+
reloadTypesAndHits();
96+
SchemeLoader.colours.put(
97+
"default", new ColourScheme("default", yaml("models: [3]\ncolours: ['#000000']")));
98+
SchemeLoader.models.put("default", new ModelScheme("default", yaml("models: ['sword(8)']")));
99+
100+
AlloyManager.relinkAlloys();
101+
102+
assertSame(TypeLoader.map.get("metal"), darksteel.getType());
103+
assertSame(SchemeLoader.colours.get("default"), darksteel.getColourScheme());
104+
assertSame(SchemeLoader.models.get("default"), darksteel.getModelScheme());
105+
assertSame(hits, darksteel.getHits());
106+
assertEquals(3, hits.size());
107+
for (var hit : hits.keySet()) {
108+
if (hit != null && hit.getId().equals("strike")) assertSame(HitLoader.map.get("strike"), hit);
109+
}
110+
assertNotSame(oldStrike, HitLoader.map.get("strike"));
111+
assertEquals(2, hits.get(HitLoader.map.get("strike")));
112+
assertEquals(1, hits.get(gone));
113+
assertEquals(4, hits.get(null));
114+
assertNull(typeless.getType());
115+
assertSame(oldWood, wooden.getType());
116+
}
117+
118+
@Test
119+
void typesAndHitsCompareById() throws Exception {
120+
var metal = new IngredientType("metal", yaml("name: Metal"));
121+
var renamed = new IngredientType("metal", yaml("name: Iron"));
122+
assertEquals(metal, renamed);
123+
assertEquals(metal.hashCode(), renamed.hashCode());
124+
assertNotEquals(metal, new IngredientType("wood", yaml("name: Metal")));
125+
assertNotEquals(metal, "metal");
126+
127+
var hammer = new HitType("hammer", yaml("name: Hammer"));
128+
assertEquals(hammer, new HitType("hammer", yaml("name: Mallet")));
129+
assertEquals(hammer.hashCode(), new HitType("hammer", yaml("name: Mallet")).hashCode());
130+
assertNotEquals(hammer, new HitType("chisel", yaml("name: Hammer")));
131+
assertNotEquals(hammer, "hammer");
132+
133+
var strike = new CraftingHit("strike", yaml("name: Strike\ntype: hammer\ntool: v.iron_axe"));
134+
var moved = new CraftingHit("strike", yaml("name: Blow\ntype: hammer\ntool: v.stick"));
135+
assertEquals(strike, moved);
136+
assertEquals(strike.hashCode(), moved.hashCode());
137+
assertNotEquals(strike, new CraftingHit("tap", yaml("name: Strike\ntype: hammer")));
138+
assertNotEquals(strike, "strike");
139+
}
140+
}

0 commit comments

Comments
 (0)