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 @@ -19,6 +19,7 @@
import net.tfminecraft.advancedcrafting.objects.CraftStack;
import net.tfminecraft.advancedcrafting.objects.data.StatData;
import net.tfminecraft.advancedcrafting.objects.ingredients.Ingredient;
import net.tfminecraft.advancedcrafting.utils.TierBonusCalculator.TierSource;

public class IngredientManager implements Listener{
private HashMap<String, Ingredient> ingredients = new HashMap<>();
Expand Down Expand Up @@ -48,7 +49,7 @@ public void convertItem(PlayerInteractEvent e) {
if(i.getType().isAir()) return;

CraftStack cs = new CraftStack(i);
StatData source = resolveStatData(cs, i);
PreviewSource source = resolveSource(cs, i);
if(source == null) return;

e.setCancelled(true);
Expand All @@ -59,25 +60,32 @@ public void convertItem(PlayerInteractEvent e) {
}

InventoryManager inv = new InventoryManager();
inv.templatePreviewView(p, source);
inv.templatePreviewView(p, source.stats(), source.tier());
}

private StatData resolveStatData(CraftStack cs, ItemStack item) {
/** The hovered material's stats plus its type and tier (for templates with a tier-bonus). */
private record PreviewSource(StatData stats, TierSource tier) {
static PreviewSource of(Ingredient ing) {
return new PreviewSource(ing.getIngredientData().getStatData(), TierSource.of(ing.getIngredientData()));
}
}

private PreviewSource resolveSource(CraftStack cs, ItemStack item) {
if(cs.isIngredient()) {
Ingredient ing = cs.getIngredient();
if(ing != null) {
return ing.getIngredientData().getStatData();
return PreviewSource.of(ing);
}
}
if(cs.isAlloy()) {
Alloy alloy = cs.getAlloy();
if(alloy != null) {
return alloy.getData().getStatData();
return new PreviewSource(alloy.getData().getStatData(), TierSource.of(alloy.getData()));
}
}
Ingredient ing = getFromItem(item);
if(ing != null) {
return ing.getIngredientData().getStatData();
return PreviewSource.of(ing);
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import net.tfminecraft.advancedcrafting.objects.ingredients.IngredientType;
import net.tfminecraft.advancedcrafting.objects.stats.StatTemplate;
import net.tfminecraft.advancedcrafting.utils.StatTemplateMath;
import net.tfminecraft.advancedcrafting.utils.TierBonusCalculator;
import net.tfminecraft.advancedcrafting.utils.TierBonusCalculator.TierSource;


public class InventoryManager {
Expand Down Expand Up @@ -76,12 +78,17 @@ public void recipeView(Player p, RecipeCategory c) {
p.openInventory(i);
}

public void templatePreviewView(Player p, StatData source) {
templatePreviewView(p, source, null);
}

/** {@code tier}: the hovered material's type and tier, so templates with a tier-bonus show it. */
// Keep the existing legacy text representation, formatting, and exact-string comparisons.
@SuppressWarnings("deprecation")
public void templatePreviewView(Player p, StatData source) {
public void templatePreviewView(Player p, StatData source, TierSource tier) {
int count = 0;
for (StatTemplate template : StatTemplateLoader.getAll()) {
if (StatTemplateMath.hasOverlap(source, template)) {
if (StatTemplateMath.hasOverlap(TierBonusCalculator.withBonus(source, template, tier), template)) {
count++;
}
}
Expand All @@ -93,13 +100,14 @@ public void templatePreviewView(Player p, StatData source) {
Inventory i = AdvancedCrafting.plugin.getServer().createInventory(null, size, STAT_PREVIEW_TITLE);
int slot = 0;
for (StatTemplate template : StatTemplateLoader.getAll()) {
if (!StatTemplateMath.hasOverlap(source, template)) {
StatData shown = TierBonusCalculator.withBonus(source, template, tier);
if (!StatTemplateMath.hasOverlap(shown, template)) {
continue;
}
if (slot >= i.getSize()) {
break;
}
i.setItem(slot, getTemplatePreviewItem(source, template));
i.setItem(slot, getTemplatePreviewItem(shown, template));
slot++;
}
int slotn = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public class StatTemplate {
private Map<String, Double> factors = new HashMap<>();
private List<StatModifier> baseStats = new ArrayList<>();
private Map<String, Double> baseStatAmounts = new HashMap<>();
/** Ingredient type id -> stat added per tier of that type's average material tier. */
private Map<String, StatModifier> tierBonuses = new TreeMap<>();
private double tierBonusNeutral;
private int revision;

public StatTemplate(String key, ConfigurationSection config) {
Expand All @@ -52,6 +55,34 @@ public StatTemplate(String key, ConfigurationSection config) {
baseStatAmounts.put(base.getType().toLowerCase(), base.getAmount());
}
}
loadTierBonuses(key, config);
}

/**
* {@code tier-bonus: {leather: attack_damage(0.125)}} adds 0.125 x (average leather tier -
* {@code tier-bonus-neutral}) to the crafted item. The stat must also be in {@code stats}.
*/
private void loadTierBonuses(String key, ConfigurationSection config) {
tierBonusNeutral = config.getDouble("tier-bonus-neutral", 0);
ConfigurationSection section = config.getConfigurationSection("tier-bonus");
if (section == null) {
return;
}
for (String typeId : section.getKeys(false)) {
StatModifier bonus;
try {
bonus = new StatModifier(section.getString(typeId));
} catch (RuntimeException e) {
Bukkit.getLogger().warning("AC: Stat template " + key + " has an invalid tier-bonus for "
+ typeId + ": " + section.get(typeId));
continue;
}
if (!allowsIngredientStat(bonus.getType())) {
Bukkit.getLogger().warning("AC: Stat template " + key + " tier-bonus stat " + bonus.getType()
+ " is not in its stats list, so it has no effect");
}
tierBonuses.put(typeId.toLowerCase(), new StatModifier(bonus.getType().toLowerCase(), bonus.getAmount()));
}
}

public String getId() {
Expand Down Expand Up @@ -92,6 +123,30 @@ public double getBaseAmount(String statId) {
return amount != null ? amount : 0;
}

public boolean hasTierBonuses() {
return !tierBonuses.isEmpty();
}

public boolean hasTierBonus(String typeId) {
return typeId != null && tierBonuses.containsKey(typeId.toLowerCase());
}

/**
* The stat this template adds for materials of {@code typeId} at {@code averageTier}, or null when
* the type has no bonus or the bonus is zero (average tier equal to the neutral tier).
*/
public StatModifier getTierBonus(String typeId, double averageTier) {
if (!hasTierBonus(typeId)) {
return null;
}
StatModifier perTier = tierBonuses.get(typeId.toLowerCase());
double amount = perTier.getAmount() * (averageTier - tierBonusNeutral);
if (amount == 0) {
return null;
}
return new StatModifier(perTier.getType(), amount);
}

public int getRevision() {
return revision;
}
Expand All @@ -116,6 +171,14 @@ public String buildRevisionContent() {
.map(e -> e.getKey().toLowerCase() + "(" + e.getValue() + ")")
.collect(Collectors.toList());
sb.append("factors=").append(String.join(",", factorParts));
// Only templates that use tier bonuses add this part, so every other template keeps its revision.
if (hasTierBonuses()) {
List<String> bonusParts = tierBonuses.entrySet().stream()
.map(e -> e.getKey() + ":" + e.getValue().getType() + "(" + e.getValue().getAmount() + ")")
.collect(Collectors.toList());
sb.append(";tierBonus=").append(String.join(",", bonusParts))
.append(";tierBonusNeutral=").append(tierBonusNeutral);
}
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,15 @@ public static StatData compute(Map<String, Integer> materials) {
}

public static StatData computeFromInputs(List<CraftInput> inputs) {
if (inputs == null || inputs.isEmpty()) {
return new StatData();
}
return compute(toMaterials(inputs));
}

/** Craft record inputs as a {@code kind.id -> amount} material map (empty for no inputs). */
public static Map<String, Integer> toMaterials(List<CraftInput> inputs) {
Map<String, Integer> materials = new HashMap<>();
if (inputs == null) {
return materials;
}
for (CraftInput input : inputs) {
if (input == null || input.getAmount() <= 0) {
continue;
Expand All @@ -88,7 +93,7 @@ public static StatData computeFromInputs(List<CraftInput> inputs) {
String key = kind.toLowerCase() + "." + input.getId().toLowerCase();
materials.merge(key, input.getAmount(), Integer::sum);
}
return compute(materials);
return materials;
}

private static String resolveBucket(String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ public static StatData compute(CraftingRecipe recipe, List<CraftInput> inputs) {
if (recipe == null) {
return new StatData();
}
return applyPipeline(BucketStatAverager.computeFromInputs(inputs), recipe);
return compute(recipe, BucketStatAverager.toMaterials(inputs));
}

public static StatData compute(CraftingRecipe recipe, Map<String, Integer> materials) {
if (recipe == null) {
return new StatData();
}
return applyPipeline(BucketStatAverager.compute(materials), recipe);
StatData stats = BucketStatAverager.compute(materials);
TierBonusCalculator.apply(stats, recipe.getStatTemplate(), materials);
return applyTemplate(stats, recipe);
}

public static Set<String> collectManagedStatIds(CraftingRecipe recipe) {
Expand All @@ -48,10 +50,6 @@ public static Set<String> collectManagedStatIds(CraftingRecipe recipe) {
return ids;
}

private static StatData applyPipeline(StatData stats, CraftingRecipe recipe) {
return applyTemplate(stats, recipe);
}

private static StatData applyTemplate(StatData stats, CraftingRecipe recipe) {
StatTemplate template = recipe.getStatTemplate();
if (template == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package net.tfminecraft.advancedcrafting.utils;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import net.tfminecraft.advancedcrafting.loaders.IngredientLoader;
import net.tfminecraft.advancedcrafting.managers.AlloyManager;
import net.tfminecraft.advancedcrafting.objects.alloys.Alloy;
import net.tfminecraft.advancedcrafting.objects.data.AlloyData;
import net.tfminecraft.advancedcrafting.objects.data.IngredientData;
import net.tfminecraft.advancedcrafting.objects.data.StatData;
import net.tfminecraft.advancedcrafting.objects.ingredients.Ingredient;
import net.tfminecraft.advancedcrafting.objects.ingredients.IngredientType;
import net.tfminecraft.advancedcrafting.objects.stats.StatModifier;
import net.tfminecraft.advancedcrafting.objects.stats.StatTemplate;

/**
* Applies a stat template's {@code tier-bonus}: for each ingredient type the template names, the
* average tier of the materials of that type (weighted by amount) sets the bonus.
*/
public final class TierBonusCalculator {
private TierBonusCalculator() {
}

/** Type id and tier of one material; null-safe factory methods return null when either is missing. */
public record TierSource(String typeId, int tier) {
public static TierSource of(IngredientType type, int tier) {
if (type == null || tier <= 0) {
return null;
}
return new TierSource(type.getId().toLowerCase(), tier);
}

public static TierSource of(IngredientData data) {
return data == null ? null : of(data.getType(), data.getTier());
}

public static TierSource of(AlloyData data) {
return data == null ? null : of(data.getType(), data.getTier());
}
}

/** Adds the template's tier bonuses for these materials ({@code kind.id -> amount}) to {@code stats}. */
public static void apply(StatData stats, StatTemplate template, Map<String, Integer> materials) {
if (template == null || !template.hasTierBonuses() || materials == null) {
return;
}
Map<String, int[]> tierSums = new HashMap<>(); // type id -> {sum of tier x amount, amount}
for (Map.Entry<String, Integer> entry : materials.entrySet()) {
int amount = entry.getValue();
if (amount <= 0) {
continue;
}
TierSource source = resolve(entry.getKey());
if (source == null || !template.hasTierBonus(source.typeId())) {
continue;
}
int[] sum = tierSums.computeIfAbsent(source.typeId(), t -> new int[2]);
sum[0] += source.tier() * amount;
sum[1] += amount;
}
// Sum per stat first: StatData rounds to 2 decimals on every merge, so adding -0.125 twice
// would give 3.26 instead of 3.25.
Map<String, Double> totals = new TreeMap<>();
for (Map.Entry<String, int[]> entry : tierSums.entrySet()) {
double averageTier = (double) entry.getValue()[0] / entry.getValue()[1];
StatModifier bonus = template.getTierBonus(entry.getKey(), averageTier);
if (bonus != null) {
totals.merge(bonus.getType(), bonus.getAmount(), Double::sum);
}
}
for (Map.Entry<String, Double> total : totals.entrySet()) {
stats.addModifier(new StatModifier(total.getKey(), total.getValue()));
}
}

/** {@code source} plus the template's bonus for one material of that type and tier (preview). */
public static StatData withBonus(StatData source, StatTemplate template, TierSource tier) {
if (tier == null) {
return source;
}
StatModifier bonus = template.getTierBonus(tier.typeId(), tier.tier());
if (bonus == null) {
return source;
}
StatData result = StatData.copyOf(source);
result.addModifier(bonus);
return result;
}

static TierSource resolve(String materialKey) {
String[] split = materialKey.split("\\.", 2);
if (split.length < 2) {
return null;
}
String kind = split[0].toLowerCase();
String id = split[1].toLowerCase();
if (kind.equals("ingredient")) {
Ingredient ingredient = IngredientLoader.getByString(id);
return ingredient == null ? null : TierSource.of(ingredient.getIngredientData());
}
if (kind.equals("alloy")) {
Alloy alloy = AlloyManager.getAlloyById(id);
return alloy == null ? null : TierSource.of(alloy.getData());
}
return null;
}
}
1 change: 1 addition & 0 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ global-stat-offsets:

stat-aliases:
- max_item_damage->Durability
- entity_interaction_range->Reach
- max_health->Max Health
- health_regeneration->Health Regeneration
- armor->Armor
Expand Down
Loading
Loading