package electroblob.wizardry.util; import com.google.common.collect.Sets; import electroblob.wizardry.event.SpellCastEvent; import io.netty.buffer.ByteBuf; import net.minecraft.item.Item; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.fml.common.network.ByteBufUtils; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; /** * "{@code SpellModifiers} - modify all the things!" *

* Object that wraps any number of spell modifiers into one, allowing for expandability within the Spell#cast methods. * This class is essentially a glorified {@link Map} which can be written to and read from a {@link ByteBuf}. *

* Most external interaction with SpellModifiers objects will be in {@link SpellCastEvent.Pre}, where you can add * additional modifiers to them if desired for use with your own spells, or modify the existing ones. If you have added * a wand upgrade, this is not done automatically for you; you will have to do it yourself (for the simple reason * that not all wand upgrades affect spells). SpellModifiers objects are mutable, so you can simply change the * values they contain to modify the spell. *

* To use a SpellModifiers object within the Spell.cast methods, simply retrieve the desired modifier * using {@link SpellModifiers#get(Item)} for wand upgrades, or {@link SpellModifiers#get(String)} if the modifier is * not from a wand upgrade. * * @author Electroblob * @since Wizardry 1.2 * @see WandHelper */ // I have made the decision that the USERS of this class must decide whether the modifiers need syncing or not, on a // case-by-case basis. Why? Because assigning keys to either sync or not sync would be unnecessarily restrictive, and // would mean they have to be registered, and part of the point of SpellModifiers is that they can be added to on the // fly. public final class SpellModifiers { /** Constant string identifier for the potency modifier. */ public static final String POTENCY = "potency"; /** Constant string identifier for the mana cost modifier. */ public static final String COST = "cost"; /** Constant string identifier for the wand charge-up modifier. */ public static final String CHARGEUP = "chargeup"; /** Constant string identifier for the wand progression modifier. */ public static final String PROGRESSION = "progression"; private final Map multiplierMap; private final Map syncedMultiplierMap; /** * Creates an empty SpellModifiers object. All calls to get(...) on an empty SpellModifiers object will * return a value of 1. */ public SpellModifiers(){ multiplierMap = new HashMap<>(); syncedMultiplierMap = new HashMap<>(); } private SpellModifiers(Map multiplierMap, Map syncedMultiplierMap){ this.multiplierMap = multiplierMap; this.syncedMultiplierMap = syncedMultiplierMap; } /** Returns a deep copy (with copies of the underlying maps) of this {@code SpellModifiers} object. */ public SpellModifiers copy(){ return new SpellModifiers(new HashMap<>(this.multiplierMap), new HashMap<>(this.syncedMultiplierMap)); } /** * Combines the given SpellModifiers object with this one. More specifically: for each modifier, multiplies the * values from both objects together and sets it to sync if either object does so. This method changes the object * it is called on but not the one given as a parameter; apart from this it does not matter which way round the two * objects are. * @param modifiers The SpellModifiers object to combine with this one; will not be modified by this method. * @return The SpellModifiers object, allowing this method to be chained onto the constructor. */ public SpellModifiers combine(SpellModifiers modifiers){ for(String key : Sets.union(this.multiplierMap.keySet(), modifiers.multiplierMap.keySet())){ float newValue = this.get(key) * modifiers.get(key); // Also need to update the synced map if the modifier is synced in either object boolean sync = this.syncedMultiplierMap.containsKey(key) || modifiers.syncedMultiplierMap.containsKey(key); this.set(key, newValue, sync); } return this; } /** * Adds the given multiplier to this SpellModifiers object, using the string identifier that the given wand upgrade * item was registered with. * * @throws IllegalArgumentException if the given item is not a registered special wand upgrade. * @param upgrade The upgrade item the multiplier corresponds to. * @param multiplier The multiplier value, with 1 being default. Usage of modifiers is up to individual spells to * implement. * @param needsSyncing Whether this multiplier should be synchronised with the client via packets. Only set this * to true if particles will be spawned which need to know the value of the multiplier. * @return The SpellModifiers object, allowing this method to be chained onto the constructor. */ public SpellModifiers set(Item upgrade, float multiplier, boolean needsSyncing){ this.set(WandHelper.getIdentifier(upgrade), multiplier, needsSyncing); return this; } /** * Adds the given multiplier to this SpellModifiers object, using the given string key. In most cases, the * multiplier will correspond to a wand upgrade, in which case use {@link SpellModifiers#set(Item, float, boolean)} * instead. * * @param key The key used to identify the multiplier. * @param multiplier The multiplier value, with 1 being default. Usage of modifiers is up to individual spells to * implement. * @param needsSyncing Whether this multiplier should be synchronised with the client via packets. Only set this * to true if particles will be spawned which depend on the multiplier. * @return The SpellModifiers object, allowing this method to be chained onto the constructor. */ public SpellModifiers set(String key, float multiplier, boolean needsSyncing){ multiplierMap.put(key, multiplier); if(needsSyncing) syncedMultiplierMap.put(key, multiplier); return this; } /** * Returns the multiplier corresponding to the given wand upgrade item, or 1 if no multiplier was stored. * * @throws IllegalArgumentException if the given item is not a registered special wand upgrade. */ public float get(Item upgrade){ return get(WandHelper.getIdentifier(upgrade)); } /** * Returns the multiplier corresponding to the given string key, or 1 if no multiplier was stored. In most cases, * the multiplier will correspond to a wand upgrade, in which case use {@link SpellModifiers#get(Item)} instead. */ public float get(String key){ Float value = multiplierMap.get(key); // Must check for null before unboxing, and if it is null, return the default 1. return value == null ? 1 : value; } // Not sure this really makes sense with the current system, it may just be better to keep it how it is // /** // * Returns the level of upgrade (i.e. number of upgrades or wand tier) that would be required to // * generate a modifier with the given key. This does not necessarily mean that was how this modifier was // * applied; and the returned value may not be a whole number if commands were involved. // */ // public float level(String key){ // return 0; // } /** * Returns an amplified version of the multiplier corresponding to the given string key. An amplified * modifier is the original modifier scaled about 1 - for example, amplifying by 2 would produce the * following results:
* 1.3 -> 1.6
* 2 -> 3
* 0.7 -> 0.4
* 1 -> 1
* (In other words, the modifier is decreased by 1, multiplied by the scalar and then increased by 1 again.)
* N.B. This does not change the stored modifier. */ public float amplified(String key, float scalar){ return (get(key) - 1) * scalar + 1; } /** * Returns an unmodifiable map of the modifiers stored in this SpellModifiers object. Useful for iterating through * the modifiers. */ public Map getModifiers(){ return Collections.unmodifiableMap(this.multiplierMap); } /** Removes all modifiers from this SpellModifiers object, effectively resetting them all to 1. */ public void reset(){ this.multiplierMap.clear(); this.syncedMultiplierMap.clear(); } /** Reads this SpellModifiers object from the given ByteBuf. */ public void read(ByteBuf buf){ int entryCount = buf.readInt(); for(int i = 0; i < entryCount; i++){ this.set(ByteBufUtils.readUTF8String(buf), buf.readFloat(), false); } } /** Writes this SpellModifiers object to the given ByteBuf so it can be sent via packets. */ public void write(ByteBuf buf){ buf.writeInt(syncedMultiplierMap.size()); for(Entry entry : syncedMultiplierMap.entrySet()){ ByteBufUtils.writeUTF8String(buf, entry.getKey()); buf.writeFloat(entry.getValue()); } } // These two don't use the Map <-> NBT methods in WizardryUtilities because it's better to use the strings as keys // themselves rather than storing them separately. /** * Creates a new SpellModifiers object from the given NBTTagCompound. The NBTTagCompound should have 1 or more float * tags, which will be stored as modifiers under the same name as the tag. For example, the following NBT tag (in * command syntax) represents a SpellModifiers object with a damage modifier of 1.5 and a range modifier of 2: *

* {damage:1.5, range:2} *

* Note that needsSyncing is set to true for all returned modifiers. */ public static SpellModifiers fromNBT(NBTTagCompound nbt){ SpellModifiers modifiers = new SpellModifiers(); for(String key : nbt.getKeySet()){ modifiers.set(key, nbt.getFloat(key), true); } return modifiers; } /** * Creates a new NBTTagCompound for this SpellModifiers object. The NBTTagCompound will have a float tags for each * modifier, which will be stored using the modifier names as keys. For example, the following NBT tag (in * command syntax) represents a SpellModifiers object with a damage modifier of 1.5 and a range modifier of 2: *

* {damage:1.5, range:2} *

* Note that information about syncing of modifiers is discarded. */ public NBTTagCompound toNBT(){ NBTTagCompound nbt = new NBTTagCompound(); for(Entry entry : multiplierMap.entrySet()){ nbt.setFloat(entry.getKey(), entry.getValue()); } return nbt; } }