Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ public void createConfigs() {

public void reload() {
loadConfigs();
AlloyManager.relinkAlloys();
revisionTracker.flush();
}
// Keep the existing legacy text representation, formatting, and exact-string comparisons.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ public static void removeAlloy(String id) {
public static void addAlloy(Alloy a) {
alloys.put(a.getId(), a);
}
/** After a config reload, so alloys use the reloaded types, hits and schemes. */
public static void relinkAlloys() {
for (Alloy a : alloys.values()) {
a.getData().relink();
}
}
public boolean hasStation(Location loc) {
return stations.containsKey(loc);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ public String getName() {
public HitType getType() {
return type;
}



// By id: a reload builds new objects, while alloys and open stations still hold the old ones.
@Override
public boolean equals(Object obj) {
return obj instanceof CraftingHit other && id.equals(other.id);
}

@Override
public int hashCode() {
return id.hashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,15 @@ public String getId() {
public String getName() {
return name;
}

// By id: a reload builds new objects, while open stations still hold the old ones.
@Override
public boolean equals(Object obj) {
return obj instanceof HitType other && id.equals(other.id);
}

@Override
public int hashCode() {
return id.hashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

import net.tfminecraft.advancedcrafting.loaders.HitLoader;
import net.tfminecraft.advancedcrafting.loaders.SchemeLoader;
import net.tfminecraft.advancedcrafting.loaders.TypeLoader;
import net.tfminecraft.advancedcrafting.objects.crafting.hits.CraftingHit;
import net.tfminecraft.advancedcrafting.objects.ingredients.Ingredient;
import net.tfminecraft.advancedcrafting.objects.ingredients.IngredientType;
Expand Down Expand Up @@ -121,6 +125,26 @@ public int getTier() {
return tier;
}

/** After a config reload: swaps in the reloaded type, hits and schemes, keeping any that are gone. */
public void relink() {
type = relinked(type, IngredientType::getId, TypeLoader::getIngredientTypeByString);
colourScheme = relinked(colourScheme, ColourScheme::getId, SchemeLoader::getColourSchemeByString);
modelScheme = relinked(modelScheme, ModelScheme::getId, SchemeLoader::getModelSchemeByString);
HashMap<CraftingHit, Integer> current = new HashMap<>();
hits.forEach((hit, amount) -> current.put(relinked(hit, CraftingHit::getId, HitLoader::getByString), amount));
// Cleared first: put() would keep the old key, which equals the new one.
hits.clear();
hits.putAll(current);
}

private static <T> T relinked(T old, Function<T, String> id, Function<String, T> lookup) {
if (old == null) {
return null;
}
T fresh = lookup.apply(id.apply(old));
return fresh != null ? fresh : old;
}

public String buildRevisionContent() {
StringBuilder sb = new StringBuilder();
sb.append("xp=").append(xp != null ? xp : "").append(';');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,15 @@ public String getId() {
public String getName() {
return name;
}

// By id: a reload builds new objects, while alloys and open stations still hold the old ones.
@Override
public boolean equals(Object obj) {
return obj instanceof IngredientType other && id.equals(other.id);
}

@Override
public int hashCode() {
return id.hashCode();
}
}
140 changes: 140 additions & 0 deletions src/test/java/net/tfminecraft/advancedcrafting/AlloyReloadTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package net.tfminecraft.advancedcrafting;

import static org.junit.jupiter.api.Assertions.*;

import java.util.*;
import net.tfminecraft.advancedcrafting.enums.StationFeedback;
import net.tfminecraft.advancedcrafting.loaders.*;
import net.tfminecraft.advancedcrafting.managers.AlloyManager;
import net.tfminecraft.advancedcrafting.objects.alloys.Alloy;
import net.tfminecraft.advancedcrafting.objects.crafting.CraftingStation;
import net.tfminecraft.advancedcrafting.objects.crafting.hits.*;
import net.tfminecraft.advancedcrafting.objects.data.*;
import net.tfminecraft.advancedcrafting.objects.ingredients.IngredientType;
import net.tfminecraft.advancedcrafting.objects.schemes.*;
import net.tfminecraft.advancedcrafting.utils.PDCKeys;
import org.bukkit.*;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.junit.jupiter.api.Test;

/** {@code ac reload} builds new types, hits and schemes; alloys loaded before it must keep working. */
class AlloyReloadTest extends CoverageSupport {
ItemStack alloyItem(String id) {
var i = new ItemStack(Material.IRON_INGOT);
var m = i.getItemMeta();
m.getPersistentDataContainer().set(PDCKeys.alloyId(), PersistentDataType.STRING, id);
i.setItemMeta(m);
return i;
}

Alloy alloy(String id, IngredientType type, HashMap<CraftingHit, Integer> hits) {
var data =
new AlloyData(
SchemeLoader.colours.get("default"),
2,
type,
SchemeLoader.models.get("default"),
new StatData(),
hits,
null,
null,
1,
null);
var alloy = new Alloy(id, id, data);
AlloyManager.addAlloy(alloy);
return alloy;
}

void reloadTypesAndHits() throws Exception {
TypeLoader.map.put("metal", new IngredientType("metal", yaml("name: Metal")));
TypeLoader.hMap.put("hammer", new HitType("hammer", yaml("name: Hammer")));
HitLoader.map.put(
"strike", new CraftingHit("strike", yaml("name: Strike\ntype: hammer\ntool: v.iron_axe")));
}

@Test
void alloyLoadedBeforeReloadStillFillsAStation() throws Exception {
alloy(
"darksteel",
TypeLoader.map.get("metal"),
new HashMap<>(Map.of(HitLoader.map.get("strike"), 2)));
reloadTypesAndHits();
var p = server.addPlayer();
p.setOp(true);
var station = new CraftingStation(new Location(server.addSimpleWorld("world"), 1, 2, 3));
station.setRecipe(recipe("recipe: ['metal.1']"));
var item = alloyItem("darksteel");
p.getInventory().setItemInMainHand(item);
assertEquals(StationFeedback.SUCCESS, station.addMaterial(p, item));
assertTrue(station.hasAllMaterials(p));
assertEquals(2, station.getHits().get(HitLoader.map.get("strike")).getNeeded());
// `ac give equipment ... alloy.<id>` fills a station this way.
var given =
new CraftingStation(
station.getLoc(),
recipe("recipe: ['metal.4']"),
new HashMap<>(Map.of("alloy.darksteel", 4)),
new HashMap<>());
assertEquals(4, given.getTypes().get(TypeLoader.map.get("metal")).getCurrent());
}

@Test
void relinkSwapsInReloadedObjectsAndKeepsMissingOnes() throws Exception {
var oldStrike = HitLoader.map.get("strike");
var gone = new CraftingHit("gone", yaml("name: Gone\ntype: hammer\ntool: v.stick"));
var hits = new HashMap<CraftingHit, Integer>();
hits.put(oldStrike, 2);
hits.put(gone, 1);
hits.put(null, 4);
var darksteel = alloy("darksteel", TypeLoader.map.get("metal"), hits).getData();
var typeless = alloy("typeless", null, new HashMap<>()).getData();
TypeLoader.map.put("wood", new IngredientType("wood", yaml("name: Wood")));
var wooden = alloy("wooden", TypeLoader.map.get("wood"), new HashMap<>()).getData();
var oldWood = TypeLoader.map.remove("wood");
reloadTypesAndHits();
SchemeLoader.colours.put(
"default", new ColourScheme("default", yaml("models: [3]\ncolours: ['#000000']")));
SchemeLoader.models.put("default", new ModelScheme("default", yaml("models: ['sword(8)']")));

AlloyManager.relinkAlloys();

assertSame(TypeLoader.map.get("metal"), darksteel.getType());
assertSame(SchemeLoader.colours.get("default"), darksteel.getColourScheme());
assertSame(SchemeLoader.models.get("default"), darksteel.getModelScheme());
assertSame(hits, darksteel.getHits());
assertEquals(3, hits.size());
for (var hit : hits.keySet()) {
if (hit != null && hit.getId().equals("strike")) assertSame(HitLoader.map.get("strike"), hit);
}
assertNotSame(oldStrike, HitLoader.map.get("strike"));
assertEquals(2, hits.get(HitLoader.map.get("strike")));
assertEquals(1, hits.get(gone));
assertEquals(4, hits.get(null));
assertNull(typeless.getType());
assertSame(oldWood, wooden.getType());
}

@Test
void typesAndHitsCompareById() throws Exception {
var metal = new IngredientType("metal", yaml("name: Metal"));
var renamed = new IngredientType("metal", yaml("name: Iron"));
assertEquals(metal, renamed);
assertEquals(metal.hashCode(), renamed.hashCode());
assertNotEquals(metal, new IngredientType("wood", yaml("name: Metal")));
assertNotEquals(metal, "metal");

var hammer = new HitType("hammer", yaml("name: Hammer"));
assertEquals(hammer, new HitType("hammer", yaml("name: Mallet")));
assertEquals(hammer.hashCode(), new HitType("hammer", yaml("name: Mallet")).hashCode());
assertNotEquals(hammer, new HitType("chisel", yaml("name: Hammer")));
assertNotEquals(hammer, "hammer");

var strike = new CraftingHit("strike", yaml("name: Strike\ntype: hammer\ntool: v.iron_axe"));
var moved = new CraftingHit("strike", yaml("name: Blow\ntype: hammer\ntool: v.stick"));
assertEquals(strike, moved);
assertEquals(strike.hashCode(), moved.hashCode());
assertNotEquals(strike, new CraftingHit("tap", yaml("name: Strike\ntype: hammer")));
assertNotEquals(strike, "strike");
}
}
Loading