Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -29,6 +29,10 @@ public class Cache {
public static HashMap<IngredientType, List<IngredientType>> combinations = new HashMap<>();

public static double maxFactor;
/** Base stats that alloys forged before {@link #alloyLegacyForgedBefore} were made with, per base ingredient. */
public static Map<String, net.tfminecraft.advancedcrafting.objects.data.StatData> alloyLegacyBaseStats = new HashMap<>();
/** Epoch millis; 0 turns the legacy base stats off. */
public static long alloyLegacyForgedBefore;

public static double hitOvershootWarnPercent = 30.0;
public static String hitOvershootWarnMessage = "§cYour over-reliance on %hit% ruins the result further";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package net.tfminecraft.advancedcrafting.database;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
Expand All @@ -19,38 +24,50 @@
import com.google.gson.GsonBuilder;

import net.tfminecraft.advancedcrafting.AdvancedCrafting;
import net.tfminecraft.advancedcrafting.cache.Cache;
import net.tfminecraft.advancedcrafting.loaders.IngredientLoader;
import net.tfminecraft.advancedcrafting.loaders.HitLoader;
import net.tfminecraft.advancedcrafting.loaders.SchemeLoader;
import net.tfminecraft.advancedcrafting.loaders.TypeLoader;
import net.tfminecraft.advancedcrafting.managers.AlloyManager;
import net.tfminecraft.advancedcrafting.objects.alloys.Alloy;
import net.tfminecraft.advancedcrafting.objects.alloys.AlloyStation;
import net.tfminecraft.advancedcrafting.objects.crafting.hits.CraftingHit;
import net.tfminecraft.advancedcrafting.objects.ingredients.Ingredient;
import net.tfminecraft.advancedcrafting.objects.data.AlloyData;
import net.tfminecraft.advancedcrafting.objects.data.AlloyRecipe;
import net.tfminecraft.advancedcrafting.objects.data.StatData;
import net.tfminecraft.advancedcrafting.objects.ingredients.IngredientType;
import net.tfminecraft.advancedcrafting.objects.schemes.ColourScheme;
import net.tfminecraft.advancedcrafting.objects.schemes.ModelScheme;
import net.tfminecraft.advancedcrafting.objects.stats.StatModifier;
import net.tfminecraft.advancedcrafting.utils.AlloyRebaser;
import net.tfminecraft.advancedcrafting.utils.IngredientLore;
import net.tfminecraft.advancedcrafting.utils.RevisionTracker;

public class AlloyDatabase {
private static final String BACKUP_STAMP = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
private JSONObject json;
private final JSONParser parser = new JSONParser();

private File alloysFolder() {
return new File(AdvancedCrafting.plugin.getDataFolder(), "data/alloys");
}

/** Closes the file again; an open reader keeps Windows from deleting or replacing it. */
private JSONObject read(File file) throws Exception {
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), "UTF-8")) {
return (JSONObject) parser.parse(reader);
}
}

public Alloy loadAlloy(String result) {
File file = new File(alloysFolder(), result.toLowerCase() + ".json");
if (!file.exists()) {
return null;
}
try {
json = (JSONObject) parser.parse(new InputStreamReader(new FileInputStream(file), "UTF-8"));
json = read(file);
return finishAlloy(parseAlloyFromJson(json));
} catch (Exception ex) {
ex.printStackTrace();
Expand All @@ -67,15 +84,63 @@ public void loadAlloys() {
for (final File file : files) {
if (!file.isDirectory()) {
try {
json = (JSONObject) parser.parse(new InputStreamReader(new FileInputStream(file), "UTF-8"));
AlloyManager.addAlloy(finishAlloy(parseAlloyFromJson(json)));
json = read(file);
Alloy alloy = parseAlloyFromJson(json);
followBase(file, alloy);
AlloyManager.addAlloy(finishAlloy(alloy));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}

/**
* Startup only: moves the alloy's stats by how much its base ingredient changed since it was
* forged, then records the live base. The file is backed up before it is rewritten.
*/
void followBase(File file, Alloy alloy) {
AlloyData data = alloy.getData();
AlloyRecipe recipe = data.getRecipe();
if (recipe == null) {
return;
}
Ingredient base = IngredientLoader.getByString(recipe.getBaseId());
if (base == null) {
return;
}
StatData live = base.getIngredientData().getStatData();
StatData snapshot = AlloyRebaser.snapshotFor(data.getBaseStats(), recipe.getBaseId(), live, file.lastModified());
StatData moved = AlloyRebaser.rebase(data.getStatData(), snapshot, live, Cache.maxFactor);
if (moved == null && data.getBaseStats() != null) {
return;
}
if (!backup(file)) {
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (moved != null) {
AdvancedCrafting.plugin.getLogger().info("AC: alloy " + alloy.getId() + " follows " + recipe.getBaseId() + ": "
+ AlloyRebaser.describe(data.getStatData(), moved));
data.setStatData(moved);
}
data.setBaseStats(StatData.copyOf(live));
saveAlloy(alloy);
}

/** False when the copy failed; the alloy is then left exactly as it is. */
private boolean backup(File file) {
File folder = new File(AdvancedCrafting.plugin.getDataFolder(), "data/alloy-backups/" + BACKUP_STAMP);
folder.mkdirs();
try {
Files.copy(file.toPath(), new File(folder, file.getName()).toPath(), StandardCopyOption.REPLACE_EXISTING);
return true;
} catch (IOException ex) {
AdvancedCrafting.plugin.getLogger().warning("AC: could not back up " + file.getName()
+ ", leaving it unchanged: " + ex.getMessage());
return false;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

public String getResult(AlloyStation station) {
return AdvancedCrafting.getAlloyRecipeStore().getResult(station);
}
Expand Down Expand Up @@ -126,7 +191,7 @@ public void saveAlloy(Alloy a) {
pw.flush();
pw.close();
HashMap<String, Object> defaults = new HashMap<>();
json = (JSONObject) parser.parse(new InputStreamReader(new FileInputStream(file), "UTF-8"));
json = read(file);
defaults.put("id", a.getId().toLowerCase());
defaults.put("name", a.getName());
defaults.put("model", a.getData().getModel());
Expand All @@ -150,6 +215,9 @@ public void saveAlloy(Alloy a) {
i++;
}
defaults.put("stats", statArray);
if (a.getData().getBaseStats() != null) {
defaults.put("baseStats", toArray(a.getData().getBaseStats()));
}
JSONArray hitArray = new JSONArray();
for (CraftingHit h : a.getData().getHits().keySet()) {
String hit = h.getId() + "." + a.getData().getHits().get(h);
Expand All @@ -165,6 +233,31 @@ public void saveAlloy(Alloy a) {
}
}

@SuppressWarnings("unchecked")
private static JSONArray toArray(StatData stats) {
JSONArray array = new JSONArray();
for (StatModifier m : stats.getModifiers()) {
array.add(m.getType() + "(" + m.getAmount() + ")");
}
return array;
}

/** {@code merge} adds repeated stat types together (alloy stats); otherwise each entry stays separate. */
private static StatData parseStats(JSONArray array, boolean merge) {
StatData stats = new StatData();
for (Object entry : array) {
String s = entry.toString();
String st = s.split("\\(")[0];
double amount = Double.parseDouble(s.split("\\(")[1].replace(")", ""));
if (merge) {
stats.addModifier(new StatModifier(st, amount));
} else {
stats.getModifiers().add(new StatModifier(st, amount));
}
}
return stats;
}

@SuppressWarnings("unchecked")
private Alloy parseAlloyFromJson(JSONObject json) throws Exception {
String id = ((String) json.get("id")).toLowerCase();
Expand All @@ -174,18 +267,9 @@ private Alloy parseAlloyFromJson(JSONObject json) throws Exception {
ColourScheme colourScheme = SchemeLoader.getColourSchemeByString((String) json.get("colour scheme"));
IngredientType type = TypeLoader.getIngredientTypeByString((String) json.get("type"));
ModelScheme scheme = SchemeLoader.getModelSchemeByString((String) json.get("scheme"));
StatData stats = new StatData();
int i = 0;
JSONArray statArray = (JSONArray) json.get("stats");
while (i < statArray.size()) {
String s = statArray.get(i).toString();
String st = s.split("\\(")[0];
double amount = Double.parseDouble(s.split("\\(")[1].replace(")", ""));
stats.addModifier(new StatModifier(st, amount));
i++;
}
StatData stats = parseStats((JSONArray) json.get("stats"), true);
HashMap<CraftingHit, Integer> hits = new HashMap<>();
i = 0;
int i = 0;
JSONArray hitArray = (JSONArray) json.get("hits");
while (i < hitArray.size()) {
String s = hitArray.get(i).toString();
Expand All @@ -202,8 +286,11 @@ private Alloy parseAlloyFromJson(JSONObject json) throws Exception {
if (statMergeBucketId == null || statMergeBucketId.isBlank()) {
statMergeBucketId = type != null ? type.getId() : null;
}
return new Alloy(id, name,
new AlloyData(colourScheme, model, type, scheme, stats, hits, xp, recipe, tier, statMergeBucketId));
AlloyData data = new AlloyData(colourScheme, model, type, scheme, stats, hits, xp, recipe, tier, statMergeBucketId);
if (json.get("baseStats") instanceof JSONArray recorded) {
data.setBaseStats(parseStats(recorded, false));
}
return new Alloy(id, name, data);
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

import net.tfminecraft.tlibs.interfaces.LoaderInterface;
import net.tfminecraft.tlibs.objects.api.subapi.StringFormatter;
import net.tfminecraft.advancedcrafting.AdvancedCrafting;
import net.tfminecraft.advancedcrafting.cache.Cache;
import net.tfminecraft.advancedcrafting.objects.data.StatData;
import net.tfminecraft.advancedcrafting.objects.data.PermissionNamespace;
import net.tfminecraft.advancedcrafting.objects.ingredients.IngredientType;
import net.tfminecraft.advancedcrafting.utils.StatToString;
Expand Down Expand Up @@ -60,6 +62,15 @@ public void load(File configFile) {

Cache.maxFactor = config.getDouble("max-factor", 1.5);

Cache.alloyLegacyBaseStats.clear();
Cache.alloyLegacyForgedBefore = parseInstant(config.getString("alloy-legacy-base.forged-before", ""));
if (config.isConfigurationSection("alloy-legacy-base.stats")) {
for (String ingredientId : config.getConfigurationSection("alloy-legacy-base.stats").getKeys(false)) {
Cache.alloyLegacyBaseStats.put(ingredientId.toLowerCase(),
new StatData(config.getStringList("alloy-legacy-base.stats." + ingredientId)));
}
}

Cache.hitOvershootWarnPercent = config.getDouble("hit-overshoot-warn-percent", 30.0);
String overshootMessage = config.getString("hit-overshoot-warn-message");
if (overshootMessage == null || overshootMessage.isBlank()) {
Expand Down Expand Up @@ -140,6 +151,19 @@ private void parseGlobalStatOffset(String entry) {
}
}

/** ISO-8601 time to epoch millis; blank or invalid gives 0, which turns the setting off. */
public static long parseInstant(String text) {
if (text == null || text.isBlank()) {
return 0L;
}
try {
return java.time.Instant.parse(text.trim()).toEpochMilli();
} catch (java.time.format.DateTimeParseException ex) {
AdvancedCrafting.plugin.getLogger().warning("AC: alloy-legacy-base.forged-before is not an ISO-8601 time: " + text);
return 0L;
}
}

private static double clampPercent(double value) {
if (value < 0.0) {
return 0.0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package net.tfminecraft.advancedcrafting.managers;

import org.bukkit.Bukkit;
import org.bukkit.block.DoubleChest;
import org.bukkit.entity.Entity;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.BlockInventoryHolder;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;

Expand Down Expand Up @@ -78,6 +85,43 @@ public void onArmorEquip(ArmorEquipEvent event) {
});
}

/** After a restart every player rejoins, so this brings their carried items up to date without anyone acting. */
@EventHandler(priority = EventPriority.MONITOR)
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
Bukkit.getScheduler().runTask(AdvancedCrafting.plugin, () -> {
if (!player.isOnline()) {
return;
}
sweep(player.getInventory());
sweep(player.getEnderChest());
});
}

/** Chests, barrels and storage entities are checked when opened; plugin menus are left alone. */
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onInventoryOpen(InventoryOpenEvent event) {
Inventory inventory = event.getInventory();
if (!isWorldStorage(inventory.getHolder(false))) {
return;
}
Bukkit.getScheduler().runTask(AdvancedCrafting.plugin, () -> sweep(inventory));
}

/** Players are entities too, but a menu owned by a player is not storage. */
public static boolean isWorldStorage(InventoryHolder holder) {
return holder instanceof BlockInventoryHolder || holder instanceof DoubleChest
|| (holder instanceof Entity && !(holder instanceof HumanEntity));
}

private void sweep(Inventory inventory) {
ItemStack[] contents = inventory.getContents();
for (int slot = 0; slot < contents.length; slot++) {
int target = slot;
tryRefresh(contents[slot], item -> inventory.setItem(target, item));
}
}

private void tryRefresh(ItemStack item, ItemConsumer writer) {
if (item == null || item.getType().isAir() || !AcItemRefresher.isManaged(item)) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class AlloyData {
private AlloyRecipe recipe;
private int tier;
private String statMergeBucketId;
/** The base ingredient's stats when this alloy was forged; null for alloys forged before this was recorded. */
private StatData baseStats;

public AlloyData(Ingredient base, StatData stats, HashMap<CraftingHit, Integer> hits, String xp) {
colourScheme = base.getIngredientData().getScheme().getColourScheme();
Expand All @@ -37,6 +39,7 @@ public AlloyData(Ingredient base, StatData stats, HashMap<CraftingHit, Integer>
this.xp = xp;
this.tier = base.getIngredientData().hasTier() ? base.getIngredientData().getTier() : 1;
this.statMergeBucketId = base.getIngredientData().getStatMergeBucketId();
this.baseStats = StatData.copyOf(base.getIngredientData().getStatData());
}

public AlloyData(ColourScheme colourScheme, int model, IngredientType type, ModelScheme scheme,
Expand Down Expand Up @@ -78,6 +81,18 @@ public ModelScheme getModelScheme() {
return modelScheme;
}

public void setStatData(StatData stats) {
this.stats = stats;
}

public StatData getBaseStats() {
return baseStats;
}

public void setBaseStats(StatData baseStats) {
this.baseStats = baseStats;
}

public StatData getStatData() {
return stats;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ public StatData() {

}

/** Entry-for-entry copy; repeated stat types stay separate, as in the source. */
public static StatData copyOf(StatData source) {
StatData copy = new StatData();
for (StatModifier m : source.getModifiers()) {
copy.modifiers.add(m.copy());
}
return copy;
}

public boolean hasModifiers() {
if(modifiers.size() > 0) return true;
return false;
Expand Down
Loading
Loading