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
23 changes: 22 additions & 1 deletion src/main/java/net/tfminecraft/magic/gear/ArchetypeDef.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public final class ArchetypeDef {
private final boolean melee;
private final List<String> required;
private final Map<String, String> slots;
private final String itemName;
private int revision = 1;

public ArchetypeDef(
Expand All @@ -28,8 +29,23 @@ public ArchetypeDef(
boolean melee,
List<String> required,
Map<String, String> slots) {
this(type, name, template, icon, melee, required, slots, "");
}

public ArchetypeDef(
GearType type,
String name,
String template,
String icon,
boolean melee,
List<String> required,
Map<String, String> slots,
String itemName) {
this.type = type;
this.name = name == null || name.isBlank() ? type.getDisplayName() : name;
this.itemName = itemName == null || itemName.isBlank()
? GearName.MATERIAL + " " + this.name
: itemName;
this.template = template == null ? "" : template.trim();
this.icon = icon == null ? "" : icon.trim();
this.melee = melee;
Expand Down Expand Up @@ -66,6 +82,11 @@ public boolean isMelee() {
return melee;
}

/** Crafted weapon name, with {@link GearName#MATERIAL} standing for the core's material. */
public String getItemName() {
return itemName;
}

public List<String> getRequired() {
return required;
}
Expand Down Expand Up @@ -93,7 +114,7 @@ public void setRevision(int revision) {
this.revision = revision;
}

/** Canonical string of the fields that change gameplay. Name is left out. */
/** Canonical string of the fields that change gameplay. Name and item name are left out. */
public String buildRevisionContent() {
StringBuilder out = new StringBuilder();
out.append("template=").append(template).append(';');
Expand Down
24 changes: 20 additions & 4 deletions src/main/java/net/tfminecraft/magic/gear/GearItemBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,14 @@ public static ItemStack rewriteSockets(ItemStack stack, int band) {
}
List<PartDef> parts = GearProvenance.resolveParts(stack);
List<String> colours = SocketLayout.colours(archetype, parts, band);
ItemStack rewritten = applyMmoData(stack, colours, parts);
String name = GearName.expected(type, parts);
ItemStack rewritten = applyMmoData(stack, colours, parts, name);
boolean named = rewritten != stack;
rewritten = GearSkin.keep(stack, GearModelResolver.apply(rewritten, type, parts));
copyGearPdc(stack, rewritten);
if (named) {
GearName.stamp(rewritten, name);
}
GearProvenance.lockSockets(rewritten);
WeaponRequirement.fromItem(stack).persist(rewritten);
WeaponRift.copy(stack, rewritten);
Expand Down Expand Up @@ -75,9 +80,14 @@ private static ItemStack build(GearType type, Collection<PartDef> parts, boolean
return decoratePreview(base, type, parts);
}
List<String> colours = SocketLayout.colours(archetype, parts, band);
ItemStack withMmo = applyMmoData(base, colours, parts);
String name = GearName.expected(type, parts);
ItemStack withMmo = applyMmoData(base, colours, parts, name);
boolean named = withMmo != base;
withMmo = GearModelResolver.apply(withMmo, type, parts);
GearProvenance.stamp(withMmo, type, parts);
if (named) {
GearName.stamp(withMmo, name);
}
return WeaponLore.updateItem(withMmo);
}

Expand Down Expand Up @@ -111,7 +121,9 @@ private static boolean requiredPresent(ArchetypeDef archetype, Collection<PartDe
return true;
}

private static ItemStack applyMmoData(ItemStack stack, List<String> colours, Collection<PartDef> parts) {
/** The rebuilt item, or the same stack when MMOItems is missing or the rebuild failed. */
private static ItemStack applyMmoData(
ItemStack stack, List<String> colours, Collection<PartDef> parts, String name) {
if (stack == null || !mmoItemsPresent()) {
return stack;
}
Expand All @@ -121,6 +133,7 @@ private static ItemStack applyMmoData(ItemStack stack, List<String> colours, Col
mmo.setData(ItemStats.GEM_SOCKETS, new GemSocketsData(new ArrayList<>(colours)));
}
GearStatApplicator.apply(mmo, parts);
GearName.write(mmo, name);
ItemStack built = mmo.newBuilder().build();
return built == null || built.getType().isAir() ? stack : built;
} catch (Exception ex) {
Expand Down Expand Up @@ -194,7 +207,10 @@ private static ItemStack decoratePreview(
lore.add("§cMMOItems is not loaded");
}
meta.setLore(lore);
if (meta.getDisplayName().isBlank()) {
String name = GearName.expected(type, parts);
if (name != null) {
meta.setDisplayName(name);
} else if (meta.getDisplayName().isBlank()) {
meta.setDisplayName("§6" + type.getDisplayName());
}
stack.setItemMeta(meta);
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/net/tfminecraft/magic/gear/GearKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public static NamespacedKey archetypeRevision() {
return new NamespacedKey(Magic.plugin, "gear_archetype_revision");
}

/** Name Magic last wrote on the weapon, so a refresh can tell when it is stale. */
public static NamespacedKey gearName() {
return new NamespacedKey(Magic.plugin, "gear_name");
}

public static NamespacedKey majorityTier() {
return new NamespacedKey(Magic.plugin, "majority_tier");
}
Expand Down
107 changes: 107 additions & 0 deletions src/main/java/net/tfminecraft/magic/gear/GearName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package net.tfminecraft.magic.gear;

import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;

import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.stat.type.NameData;
import net.Indyuce.mmoitems.stat.type.StatHistory;

/**
* Names a mage weapon after its core, the way AdvancedCrafting names equipment after its
* majority material: the archetype's item name with {@link #MATERIAL} filled in, all of it
* in the material's colour.
*/
public final class GearName {

public static final String MATERIAL = "%material%";

private static final Pattern LEADING_CODES = Pattern.compile("^(?:§[0-9a-fk-orx])+", Pattern.CASE_INSENSITIVE);
private static final Pattern CORE_SUFFIX = Pattern.compile("\\s+(?:magical\\s+)?core$", Pattern.CASE_INSENSITIVE);

private GearName() {}

/** The name a weapon built from these parts carries, or null when its core gives no material. */
public static String expected(GearType type, Collection<PartDef> parts) {
ArchetypeDef archetype = ArchetypeRegistry.get(type);
if (archetype == null) {
return null;
}
String material = material(core(parts));
Matcher codes = LEADING_CODES.matcher(material);
String colour = codes.find() ? codes.group() : "";
String plain = material.substring(colour.length()).trim();
if (plain.isEmpty()) {
return null;
}
return colour + archetype.getItemName().replace(MATERIAL, plain);
}

/** True when the weapon's name no longer matches its core and archetype. */
public static boolean isOutdated(ItemStack stack) {
String expected = expected(GearProvenance.archetypeOf(stack), GearProvenance.resolveParts(stack));
return expected != null && !expected.equals(stored(stack));
}

/** The core's material, or its name without "Magical Core" when it sets none. */
static String material(PartDef core) {
if (core == null) {
return "";
}
if (!core.getMaterial().isEmpty()) {
return core.getMaterial();
}
Matcher suffix = CORE_SUFFIX.matcher(core.getName());
return suffix.find() ? core.getName().substring(0, suffix.start()) : "";
}

static PartDef core(Collection<PartDef> parts) {
if (parts == null) {
return null;
}
for (PartDef part : parts) {
if (part != null && PartSlots.CORE.equalsIgnoreCase(part.getPartType())) {
return part;
}
}
return null;
}

/** Writes the name to the MMOItems data and its original, so MMOItems updates keep it. */
public static void write(MMOItem mmo, String name) {
if (mmo == null || name == null) {
return;
}
NameData data = mmo.getData(ItemStats.NAME) instanceof NameData current ? current : new NameData(name);
data.setString(name);
mmo.replaceData(ItemStats.NAME, data);
StatHistory history = mmo.computeStatHistory(ItemStats.NAME);
if (history != null && history.getOriginalData() instanceof NameData original) {
original.setString(name);
mmo.setStatHistory(ItemStats.NAME, history);
}
}

/** Records the name written on the weapon. */
public static void stamp(ItemStack stack, String name) {
if (stack == null || name == null || !stack.hasItemMeta()) {
return;
}
ItemMeta meta = stack.getItemMeta();
meta.getPersistentDataContainer().set(GearKeys.gearName(), PersistentDataType.STRING, name);
stack.setItemMeta(meta);
}

static String stored(ItemStack stack) {
if (stack == null || !stack.hasItemMeta()) {
return null;
}
return stack.getItemMeta().getPersistentDataContainer().get(GearKeys.gearName(), PersistentDataType.STRING);
}
}
16 changes: 10 additions & 6 deletions src/main/java/net/tfminecraft/magic/gear/GearRefresher.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
/**
* Brings a crafted weapon back in line with the live gear config.
*
* Socket colours and part stats are rewritten from the stamped parts. Runes already in
* the weapon are merged into the new layout; any that no longer fit are held by
* Socket colours, part stats and the name are rewritten from the stamped parts. Runes already
* in the weapon are merged into the new layout; any that no longer fit are held by
* {@link GearBrokenMarker} instead of being dropped.
*/
public final class GearRefresher {
Expand Down Expand Up @@ -55,7 +55,8 @@ public static ItemStack refresh(ItemStack stack, Player holder, boolean force) {
return broken;
}

if (!force && !GearProvenance.isOutdated(stack) && !RuneRefresher.isOutdated(stack)) {
if (!force && !GearProvenance.isOutdated(stack) && !RuneRefresher.isOutdated(stack)
&& !GearName.isOutdated(stack)) {
int live = MajorityTierResolver.resolve(GearProvenance.resolveParts(stack));
if (live > 0 && GearProvenance.majorityOf(stack) != live) {
ItemStack clone = stack.clone();
Expand All @@ -72,15 +73,17 @@ public static ItemStack refresh(ItemStack stack, Player holder, boolean force) {
}
int band = WeaponRequirement.fromItem(stack).highestBand();
List<String> colours = SocketLayout.colours(archetype, GearProvenance.resolveParts(stack), band);
String name = GearName.expected(type, GearProvenance.resolveParts(stack));

List<GemstoneData> orphaned = new ArrayList<>();
Map<UUID, Integer> runeRevisions = new HashMap<>();
ItemStack rebuilt = rewrite(stack, colours, orphaned, runeRevisions);
ItemStack rebuilt = rewrite(stack, colours, name, orphaned, runeRevisions);
if (rebuilt == null) {
return null;
}

copyGearPdc(stack, rebuilt);
GearName.stamp(rebuilt, name);
RuneRefresher.stamp(rebuilt, runeRevisions);
WeaponRequirement.fromItem(stack).persist(rebuilt);
WeaponRift.copy(stack, rebuilt);
Expand All @@ -107,8 +110,8 @@ public static ItemStack refresh(ItemStack stack, Player holder, boolean force) {
* this puts every gem it can back into a matching empty socket first, then brings
* socketed runes up to their current template.
*/
private static ItemStack rewrite(ItemStack stack, List<String> colours, List<GemstoneData> orphaned,
Map<UUID, Integer> runeRevisions) {
private static ItemStack rewrite(ItemStack stack, List<String> colours, String name,
List<GemstoneData> orphaned, Map<UUID, Integer> runeRevisions) {
try {
LiveMMOItem mmo = new LiveMMOItem(NBTItem.get(stack));
List<GemstoneData> existing = new ArrayList<>();
Expand All @@ -128,6 +131,7 @@ private static ItemStack rewrite(ItemStack stack, List<String> colours, List<Gem
}
mmo.setData(ItemStats.GEM_SOCKETS, next);
GearStatApplicator.apply(mmo, GearProvenance.resolveParts(stack));
GearName.write(mmo, name);
runeRevisions.putAll(RuneRefresher.refresh(mmo, stack));
ItemStack built = mmo.newBuilder().build();
if (built == null || built.getType().isAir()) {
Expand Down
31 changes: 29 additions & 2 deletions src/main/java/net/tfminecraft/magic/gear/PartDef.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public final class PartDef {
private final String schemeId;
private final int schemeWeight;
private final boolean disabled;
private final String material;
private int revision = 1;

public PartDef(
Expand All @@ -45,8 +46,29 @@ public PartDef(
String schemeId,
int schemeWeight,
boolean disabled) {
this(id, name, partType, tier, types, itemPath, cost, partLimit, stats, sockets, lore,
schemeId, schemeWeight, disabled, "");
}

public PartDef(
String id,
String name,
String partType,
int tier,
Set<GearType> types,
String itemPath,
Map<String, Integer> cost,
List<String> partLimit,
Map<String, Double> stats,
Map<String, Integer> sockets,
List<String> lore,
String schemeId,
int schemeWeight,
boolean disabled,
String material) {
this.id = id == null ? "" : id.trim().toLowerCase(Locale.ROOT);
this.name = name == null || name.isBlank() ? this.id : name;
this.material = material == null ? "" : material.trim();
this.partType = partType == null ? "" : partType.trim().toLowerCase(Locale.ROOT);
this.tier = tier < 1 ? 0 : tier;
this.types = types == null || types.isEmpty()
Expand Down Expand Up @@ -185,6 +207,11 @@ public boolean isDisabled() {
return disabled;
}

/** Coloured material a core puts in the weapon's name, such as "Iron". Empty when unset. */
public String getMaterial() {
return material;
}

public int getRevision() {
return revision;
}
Expand All @@ -194,8 +221,8 @@ public void setRevision(int revision) {
}

/**
* Canonical string of the fields that change gameplay. Name and lore are left out
* so cosmetic edits do not bump the revision and refresh every crafted weapon.
* Canonical string of the fields that change gameplay. Name, lore and material are left
* out so cosmetic edits do not bump the revision and refresh every crafted weapon.
*/
public String buildRevisionContent() {
StringBuilder out = new StringBuilder();
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/net/tfminecraft/magic/loader/GearLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ private static boolean loadArchetypes(File file) {
section.getString("icon", ""),
section.getBoolean("melee", type == GearType.SWORD),
section.getStringList("required"),
slots);
slots,
MagicText.format(section.getString("item-name", "")));
def.setRevision(Magic.getRevisionTracker().resolveArchetype(
type.name(), RevisionTracker.sha256(def.buildRevisionContent())));
ArchetypeRegistry.register(def);
Expand Down Expand Up @@ -388,7 +389,8 @@ private static boolean loadParts(File file) {
lore,
schemeId,
schemeWeight,
section.getBoolean("disabled", false));
section.getBoolean("disabled", false),
MagicText.format(section.getString("material", "")));
if (def.totalSocketCount() > 4) {
Magic.plugin.getLogger().warning("[Magic] part '" + id
+ "' declares more than 4 sockets; extras are dropped at craft");
Expand Down
Loading
Loading