diff --git a/src/main/java/electroblob/wizardry/Settings.java b/src/main/java/electroblob/wizardry/Settings.java index e6eb637c..0b715922 100644 --- a/src/main/java/electroblob/wizardry/Settings.java +++ b/src/main/java/electroblob/wizardry/Settings.java @@ -215,6 +215,8 @@ public final class Settings { public boolean wandsMustBeHeldToDecrementCooldown = false; /** [Server-only] Whether to enable Wizardry mob loot injection. Allows an easier switch instead of blacklisting all entities. */ public boolean injectMobDrops = true; + /** [Server-only] The time in ticks after which recent spell casts expire and no longer count toward progression penalties. */ + public int recentSpellExpiryTime = 1200; /** * [Server-only] List of registry names of entities which summoned creatures are allowed to attack, in addition * to the defaults. @@ -866,6 +868,14 @@ public final class Settings { injectMobDrops = property.getBoolean(); propOrder.add(property.getName()); + property = config.get(TWEAKS_CATEGORY, "recentSpellExpiryTime", 1200, + "The time in ticks after which recent spell casts expire and no longer count toward progression penalties. Default is 1200 ticks (1 minute). Lower values make progression penalties shorter-lived, higher values make them last longer.", + 60, 72000); // Between 3 seconds and 1 hour + property.setLanguageKey("config." + Wizardry.MODID + ".recent_spell_expiry_time"); + Wizardry.proxy.setToNumberSliderEntry(property); + recentSpellExpiryTime = property.getInt(); + propOrder.add(property.getName()); + property = config.get(TWEAKS_CATEGORY, "mobLootTableWhitelist", new String[0], "Whitelist for loot tables to inject additional mob drops (as specified in loot_tables/entities/mob_additions.json) into. Wizardry makes a best guess as to which loot tables belong to hostile mobs, but this may not always be correct or appropriate; add loot table locations (not entity IDs) to this list to manually include them."); property.setLanguageKey("config." + Wizardry.MODID + ".mob_loot_table_whitelist"); property.setRequiresMcRestart(true); diff --git a/src/main/java/electroblob/wizardry/data/WizardData.java b/src/main/java/electroblob/wizardry/data/WizardData.java index 6cd36067..40bb0a94 100644 --- a/src/main/java/electroblob/wizardry/data/WizardData.java +++ b/src/main/java/electroblob/wizardry/data/WizardData.java @@ -47,6 +47,7 @@ import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.util.*; import java.util.stream.Collectors; +import java.util.AbstractMap.SimpleEntry; /** * Capability-based replacement for the old ExtendedPlayer class from 1.7.10. This has been reworked to leave minimum @@ -126,7 +127,7 @@ public class WizardData implements INBTSerializable { * be modified via {@link WizardData#setVariable(IVariable, Object)}, which (as a method) is able to enforce it. */ private final Map spellData; - private Queue recentSpells; + private Queue> recentSpells; // This one is still necessary, because I can't override the equip animation for items that aren't from Wizardry. // Leaving this for now because merging it into the spell data system will be more tricky @@ -294,15 +295,19 @@ public class WizardData implements INBTSerializable { * @param spell The spell to be tracked. */ public void trackRecentSpell(Spell spell){ - this.recentSpells.add(spell); + this.recentSpells.add(new SimpleEntry<>(spell, player.world.getTotalWorldTime())); } /** * Returns the number of times the given spell is tracked in this player's recently-cast spells. + * Only counts spells cast within the configured expiry time. * @param spell The spell to count casts for. */ public int countRecentCasts(Spell spell){ - return (int)this.recentSpells.stream().filter(s -> s == spell).count(); // We know this can't be more than 5 + long currentTime = player.world.getTotalWorldTime(); + return (int)this.recentSpells.stream() + .filter(entry -> entry.getKey() == spell && (currentTime - entry.getValue()) < Wizardry.settings.recentSpellExpiryTime) + .count(); // We know this can't be more than 5 } // Imbuements @@ -513,6 +518,12 @@ public class WizardData implements INBTSerializable { if(player.ticksExisted % IMBUEMENT_UPDATE_INTERVAL == 0) updateImbuedItems(); updateContinuousSpellCasting(); + // Clean up expired recent spells every 60 ticks (1 second) + if(player.ticksExisted % 60 == 0) { + long currentTime = player.world.getTotalWorldTime(); + this.recentSpells.removeIf(entry -> (currentTime - entry.getValue()) >= Wizardry.settings.recentSpellExpiryTime); + } + this.spellData.forEach((k, v) -> this.spellData.put(k, k.update(player, v))); this.spellData.keySet().removeIf(k -> k.canPurge(player, this.spellData.get(k))); } @@ -579,7 +590,15 @@ public class WizardData implements INBTSerializable { properties.setInteger("maxTierReached", maxTierReached.ordinal()); - NBTExtras.storeTagSafely(properties, "recentSpells", NBTExtras.listToNBT(recentSpells, s -> new NBTTagInt(s.metadata()))); + // Serialize recent spells with timestamps as compound tags + NBTTagList recentSpellsList = new NBTTagList(); + for(SimpleEntry entry : recentSpells) { + NBTTagCompound spellTag = new NBTTagCompound(); + spellTag.setInteger("spellId", entry.getKey().metadata()); + spellTag.setLong("timestamp", entry.getValue()); + recentSpellsList.appendTag(spellTag); + } + NBTExtras.storeTagSafely(properties, "recentSpells", recentSpellsList); storedVariables.forEach(k -> k.write(properties, this.spellData.get(k))); @@ -606,8 +625,14 @@ public class WizardData implements INBTSerializable { // Probably won't be null but we may as well just reinitialise it instead of clearing it this.recentSpells = EvictingQueue.create(MAX_RECENT_SPELLS); - this.recentSpells.addAll(NBTExtras.NBTToList(nbt.getTagList("recentSpells", NBT.TAG_INT), - (NBTTagInt tag) -> Spell.byMetadata(tag.getInt()))); + // Deserialize recent spells with timestamps + NBTTagList recentSpellsList = nbt.getTagList("recentSpells", NBT.TAG_COMPOUND); + for(int i = 0; i < recentSpellsList.tagCount(); i++) { + NBTTagCompound spellTag = recentSpellsList.getCompoundTagAt(i); + Spell spell = Spell.byMetadata(spellTag.getInteger("spellId")); + long timestamp = spellTag.getLong("timestamp"); + this.recentSpells.add(new SimpleEntry<>(spell, timestamp)); + } try{ storedVariables.forEach(k -> this.spellData.put(k, k.read(nbt)));