Initial commit
This commit is contained in:
@@ -0,0 +1,581 @@
|
||||
package electroblob.wizardry;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import electroblob.wizardry.constants.Element;
|
||||
import electroblob.wizardry.enchantment.Imbuement;
|
||||
import electroblob.wizardry.entity.EntityShield;
|
||||
import electroblob.wizardry.entity.living.ISummonedCreature;
|
||||
import electroblob.wizardry.packet.PacketCastContinuousSpell;
|
||||
import electroblob.wizardry.packet.PacketPlayerSync;
|
||||
import electroblob.wizardry.packet.PacketTransportation;
|
||||
import electroblob.wizardry.packet.WizardryPacketHandler;
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryAchievements;
|
||||
import electroblob.wizardry.spell.None;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import electroblob.wizardry.util.MagicDamage;
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import electroblob.wizardry.util.SpellModifiers;
|
||||
import electroblob.wizardry.util.WizardryUtilities;
|
||||
import net.minecraft.enchantment.Enchantment;
|
||||
import net.minecraft.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.init.MobEffects;
|
||||
import net.minecraft.init.SoundEvents;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagInt;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.potion.PotionEffect;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.CapabilityInject;
|
||||
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
import net.minecraftforge.common.util.INBTSerializable;
|
||||
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
|
||||
|
||||
/** Capability-based replacement for the old ExtendedPlayer class from 1.7.10. This has been reworked to leave
|
||||
* minimum external changes (for my own sanity, mainly!). Turns out the only major difference between an internal
|
||||
* capability and an IEEP is a couple of redundant classes and a different way of registering it.
|
||||
* <p>
|
||||
* Forge seems to have separate classes to hold the Capability<...> instance ('key') and
|
||||
* methods for getting the capability, but in my opinion there are already too many classes to deal with, so I'm not
|
||||
* adding any more than are necessary, meaning those constants and values are kept here instead.
|
||||
* @since Wizardry 1.2
|
||||
* @author Electroblob */
|
||||
// On the plus side, having to rethink this class allowed me to clean it up a lot.
|
||||
public class WizardData implements INBTSerializable<NBTTagCompound> {
|
||||
|
||||
/** Static instance of what I like to refer to as the capability key. Private because, well, it's internal! */
|
||||
// This annotation does some crazy Forge magic behind the scenes and assigns this field a value.
|
||||
@CapabilityInject(WizardData.class)
|
||||
private static final Capability<WizardData> WIZARD_DATA_CAPABILITY = null;
|
||||
|
||||
private final EntityPlayer player;
|
||||
|
||||
// This one is still necessary, because I can't override the equip animation for items that aren't from Wizardry.
|
||||
private Map<Imbuement, Integer> imbuementDurations;
|
||||
|
||||
public boolean hasSpiritWolf;
|
||||
public boolean hasSpiritHorse;
|
||||
|
||||
/** Whether this player is currently casting a continuous spell via commands. Not saved over world reload
|
||||
* and reset on player death. */
|
||||
private Spell currentlyCasting;
|
||||
/** The time for which this player has been casting a continuous spell via commands. Increments by 1 each tick.
|
||||
* Not saved over world reload and reset on player death. */
|
||||
private int castingTick;
|
||||
/** SpellModifiers object for the current continuous spell cast via commands. Not saved over world reload and
|
||||
* reset on player death. */
|
||||
private SpellModifiers spellModifiers;
|
||||
/** Coordinates for the saved transportation stone circle location. Will be null if no location is saved. */
|
||||
private BlockPos stoneCircleLocation;
|
||||
/** Dimension id which the saved stone circle is in. */
|
||||
private int stoneCircleDimension;
|
||||
/** Time left until the player teleports under the effect of transportation */
|
||||
private int tpCountdown;
|
||||
|
||||
/** Coordinates for the saved clairvoyance location. Will be null if no location is saved. */
|
||||
private BlockPos clairvoyanceLocation;
|
||||
/** Dimension id which the saved clairvoyance point is in. */
|
||||
private int clairvoyanceDimension;
|
||||
|
||||
public EntityShield shield;
|
||||
|
||||
public WeakReference<ISummonedCreature> selectedMinion;
|
||||
|
||||
/** Set of this player's discovered spells. <b>Do not write to this list directly</b>, use
|
||||
* {@link WizardData#discoverSpell(Spell)} instead. */
|
||||
public Set<Spell> spellsDiscovered;
|
||||
|
||||
private Set<UUID> allies;
|
||||
/** List of usernames of this player's allies. May not be accurate 100% of the time. This is here so that a player
|
||||
* can view the usernames of their allies even when those allies are not online.
|
||||
* <b> Do not use this for any other purpose than displaying the names! */
|
||||
public Set<String> allyNames;
|
||||
|
||||
private Set<UUID> soulboundCreatures;
|
||||
|
||||
public WizardData(EntityPlayer player){
|
||||
this.player = player;
|
||||
this.imbuementDurations = new HashMap<Imbuement, Integer>();
|
||||
this.spellsDiscovered = new HashSet<Spell>();
|
||||
// All players can recognise magic missile. This is not done using discoverSpell because that seems to cause
|
||||
// a crash on load occasionally (probably something to do with achievements being initalised)
|
||||
this.spellsDiscovered.add(Spells.magic_missile);
|
||||
this.hasSpiritWolf = false;
|
||||
this.hasSpiritHorse = false;
|
||||
this.currentlyCasting = Spells.none;
|
||||
this.spellModifiers = new SpellModifiers();
|
||||
this.castingTick = 0;
|
||||
this.stoneCircleDimension = 0;
|
||||
this.clairvoyanceDimension = 0;
|
||||
this.setTpCountdown(0);
|
||||
this.allies = new HashSet<UUID>();
|
||||
this.allyNames = new HashSet<String>();
|
||||
this.soulboundCreatures = new HashSet<UUID>();
|
||||
}
|
||||
|
||||
public boolean hasSpellBeenDiscovered(Spell spell){
|
||||
return spellsDiscovered.contains(spell) || spell instanceof None;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given spell to the list of discovered spells for this player. Automatically takes into account
|
||||
* whether the spell has been discovered. Use this method rather than adding directly to the list because it
|
||||
* handles achievements.
|
||||
* @param spell The spell to be discovered
|
||||
* @return True if the spell had not already been discovered; false otherwise.
|
||||
*/
|
||||
public boolean discoverSpell(Spell spell){
|
||||
|
||||
if(spellsDiscovered == null){
|
||||
spellsDiscovered = new HashSet<Spell>();
|
||||
}
|
||||
// The 'none' spell cannot be discovered
|
||||
if(spell instanceof None) return false;
|
||||
// Tries to add the spell to the list of discovered spells, and returns false if it was already present
|
||||
if(!spellsDiscovered.add(spell)) return false;
|
||||
// If the spell had not already been discovered, achievements can be triggered and the method returns true
|
||||
if(spellsDiscovered.containsAll(Spell.getSpells(Spell::isEnabled))){
|
||||
this.player.addStat(WizardryAchievements.all_spells);
|
||||
}
|
||||
|
||||
for(Element element : Element.values()){
|
||||
if(element != Element.MAGIC && spellsDiscovered.containsAll(Spell.getSpells(new Spell.TierElementFilter(null, element)))){
|
||||
this.player.addStat(WizardryAchievements.element_master);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Sets the player's saved transportation stone location and dimension. */
|
||||
public void setStoneCircleLocation(BlockPos pos, int dimensionID){
|
||||
this.stoneCircleLocation = pos;
|
||||
this.stoneCircleDimension = dimensionID;
|
||||
}
|
||||
|
||||
/** Returns the coordinates of the associated player's saved transportation stone circle. */
|
||||
public BlockPos getStoneCircleLocation(){ return stoneCircleLocation; }
|
||||
/** Returns the dimension ID of the associated player's saved transportation stone circle. */
|
||||
public int getStoneCircleDimension(){ return stoneCircleDimension; }
|
||||
|
||||
public int getTpCountdown() { return tpCountdown; }
|
||||
|
||||
public void setTpCountdown(int tpCountdown) { this.tpCountdown = tpCountdown; }
|
||||
|
||||
/** Sets the player's saved clairvoyance location. */
|
||||
public void setClairvoyancePoint(BlockPos pos, int dimensionID){
|
||||
this.clairvoyanceLocation = pos;
|
||||
this.clairvoyanceDimension = dimensionID;
|
||||
}
|
||||
|
||||
/** Returns the coordinates for the saved clairvoyance location. Will be null if no location is saved. */
|
||||
public BlockPos getClairvoyanceLocation(){ return clairvoyanceLocation; }
|
||||
|
||||
/** Returns the dimension ID for the saved clairvoyance location. Will be null if no location is saved. */
|
||||
public int getClairvoyanceDimension(){ return clairvoyanceDimension; }
|
||||
|
||||
/** Overwrites the imbuement duration associated with the given imubement for this player, or creates it if there
|
||||
* was none previously.
|
||||
* @throws IllegalArgumentException if the given {@link Enchantment} is not an {@link Imbuement}. */
|
||||
public void setImbuementDuration(Enchantment enchantment, int duration){
|
||||
// It is best to throw an exception here, because otherwise the error would either go unnoticed (if non-imbuements
|
||||
// were ignored) or cause a ClassCastException later (if non-imbuements were allowed to be added).
|
||||
if(enchantment instanceof Imbuement){
|
||||
this.imbuementDurations.put((Imbuement) enchantment, duration);
|
||||
}else{
|
||||
throw new IllegalArgumentException("Attempted to set an imbuement duration for something that isn't an Imbuement! (This exception has been thrown now to prevent a ClassCastException from occurring later.)");
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the imbuement duration associated with the given imbuement for this player, or 0 if it does not exist. */
|
||||
public int getImbuementDuration(Enchantment enchantment){
|
||||
// Need to check that i is not null, otherwise it throws an NPE when Java auto-unboxes it.
|
||||
// What's nice here is that the map simply accepts objects as keys, so there's no need to cast or throw exceptions.
|
||||
Integer i = this.imbuementDurations.get(enchantment);
|
||||
// If i is null, returns 0; otherwise returns i, auto-unboxed to an int.
|
||||
return i == null ? 0 : i;
|
||||
}
|
||||
|
||||
/** Decrements the duration for each conjured item by 1, and removes from the map any that are 0 or less or that
|
||||
* the player no longer has. Also deletes the item from the player's inventory if it runs out of time. */
|
||||
private void updateImbuedItems(){
|
||||
|
||||
Set<Imbuement> activeImbuements = new HashSet<Imbuement>();
|
||||
|
||||
// For each item in the player's inventory
|
||||
for(ItemStack stack : player.inventory.mainInventory){
|
||||
if(stack != null && stack.isItemEnchanted()){
|
||||
|
||||
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(stack);
|
||||
|
||||
Iterator<Entry<Enchantment, Integer>> iterator = enchantments.entrySet().iterator();
|
||||
// For each of the item's enchantments
|
||||
while(iterator.hasNext()){
|
||||
Enchantment enchantment = iterator.next().getKey();
|
||||
// Ignores the enchantment unless it is an imbuement
|
||||
if(enchantment instanceof Imbuement){
|
||||
int duration = this.getImbuementDuration(enchantment);
|
||||
// If the imbuement is still active:
|
||||
if(duration > 0){
|
||||
// Decrements the timer
|
||||
this.imbuementDurations.put((Imbuement)enchantment, duration-1);
|
||||
// Adds this imbuement to the set of imbuements that need to be kept
|
||||
activeImbuements.add((Imbuement)enchantment);
|
||||
// Otherwise:
|
||||
}else{
|
||||
// Removes the enchantment from the enchantment map
|
||||
iterator.remove();
|
||||
// Applies the new enchantment map to the item
|
||||
EnchantmentHelper.setEnchantments(enchantments, stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Removes all imbuements from the map that are no longer active
|
||||
this.imbuementDurations.keySet().retainAll(activeImbuements);
|
||||
}
|
||||
|
||||
/** Adds the given player to the list of allies belonging to the associated player, or removes the player if
|
||||
* they are already in the list of allies. Returns true if the player was added, false if they were removed. */
|
||||
public boolean toggleAlly(EntityPlayer player){
|
||||
if(this.isPlayerAlly(player)){
|
||||
this.allies.remove(player.getUniqueID());
|
||||
// The remove method uses .equals() rather than == so this will work fine.
|
||||
this.allyNames.remove(player.getName());
|
||||
return false;
|
||||
}else{
|
||||
this.allies.add(player.getUniqueID());
|
||||
this.allyNames.add(player.getName());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns whether the given player is in this player's list of allies, or is on the same team as this player. */
|
||||
public boolean isPlayerAlly(EntityPlayer player){
|
||||
return this.allies.contains(player.getUniqueID()) || this.player.isOnSameTeam(player);
|
||||
}
|
||||
|
||||
/** Adds the given entity to this player's list of soulbound creatures, and returns whether it succeeded. */
|
||||
public boolean soulbind(EntityLivingBase target){
|
||||
return this.soulboundCreatures.add(target.getUniqueID());
|
||||
}
|
||||
|
||||
/** Returns whether the given entity has been soulbound to this player. */
|
||||
public boolean isCreatureSoulbound(EntityPlayer target){
|
||||
return this.soulboundCreatures.contains(target.getUniqueID());
|
||||
}
|
||||
|
||||
/** Damages all creatures soulbound to this player by the given amount, and removes from the list any that no
|
||||
* longer exist. */
|
||||
public void damageAllSoulboundCreatures(float damage){
|
||||
|
||||
for(Iterator<UUID> iterator = this.soulboundCreatures.iterator(); iterator.hasNext();){
|
||||
|
||||
Entity entity = WizardryUtilities.getEntityByUUID(this.player.worldObj, iterator.next());
|
||||
|
||||
if(entity == null) iterator.remove();
|
||||
|
||||
if(entity instanceof EntityLivingBase){
|
||||
// Retaliatory effect
|
||||
if(entity.attackEntityFrom(MagicDamage.causeDirectMagicDamage(this.player, DamageType.MAGIC, true), damage)){
|
||||
// Sound only plays if the damage succeeds
|
||||
player.playSound(SoundEvents.ENTITY_WITHER_HURT, 1.0F, player.worldObj.rand.nextFloat() * 0.2F + 1.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts casting the given spell with the given modifiers. */
|
||||
public void startCastingContinuousSpell(Spell spell, SpellModifiers modifiers){
|
||||
|
||||
this.currentlyCasting = spell;
|
||||
this.spellModifiers = modifiers;
|
||||
|
||||
if(!this.player.worldObj.isRemote){
|
||||
PacketCastContinuousSpell.Message message = new PacketCastContinuousSpell.Message(this.player.getEntityId(),
|
||||
spell.id(), this.spellModifiers);
|
||||
WizardryPacketHandler.net.sendToDimension(message, this.player.worldObj.provider.getDimension());
|
||||
}
|
||||
}
|
||||
|
||||
/** Stops casting the current spell. */
|
||||
public void stopCastingContinuousSpell(){
|
||||
|
||||
this.currentlyCasting = Spells.none;
|
||||
this.castingTick = 0;
|
||||
this.spellModifiers.reset();
|
||||
|
||||
if(!this.player.worldObj.isRemote){
|
||||
PacketCastContinuousSpell.Message message = new PacketCastContinuousSpell.Message(this.player.getEntityId(),
|
||||
Spells.none.id(), this.spellModifiers);
|
||||
WizardryPacketHandler.net.sendToDimension(message, this.player.worldObj.provider.getDimension());
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns whether this player is currently casting a continuous spell via commands. */
|
||||
public boolean isCasting(){
|
||||
return this.currentlyCasting != null && this.currentlyCasting != Spells.none;
|
||||
}
|
||||
|
||||
/** Returns the continuous spell this player is currently casting via commands, or the 'none' spell if they aren't
|
||||
* casting anything. */
|
||||
public Spell currentlyCasting(){
|
||||
return currentlyCasting;
|
||||
}
|
||||
|
||||
/** Called from the event handler each time the associated player is updated. */
|
||||
public void update(){
|
||||
|
||||
if(this.selectedMinion != null && this.selectedMinion.get() == null) this.selectedMinion = null;
|
||||
|
||||
// This new system removes a lot of repetitive event handler code and inflexible variables which had duplicate
|
||||
// functions, just for different enchantments.
|
||||
updateImbuedItems();
|
||||
|
||||
if(!player.worldObj.isRemote){
|
||||
if(getTpCountdown() == 1){
|
||||
player.setPositionAndUpdate(this.stoneCircleLocation.getX() + 0.5, this.stoneCircleLocation.getY(),
|
||||
this.stoneCircleLocation.getZ() + 0.5);
|
||||
player.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 50, 0));
|
||||
IMessage msg = new PacketTransportation.Message(player.getEntityId());
|
||||
WizardryPacketHandler.net.sendToDimension(msg, player.worldObj.provider.getDimension());
|
||||
}
|
||||
|
||||
if(getTpCountdown() > 0){
|
||||
setTpCountdown(getTpCountdown() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.currentlyCasting != null && this.currentlyCasting.isContinuous){
|
||||
this.currentlyCasting.cast(player.worldObj, player, EnumHand.MAIN_HAND, castingTick++, this.spellModifiers);
|
||||
}else{
|
||||
this.castingTick = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WizardData instance for the specified player.
|
||||
*/
|
||||
public static final WizardData get(EntityPlayer player){
|
||||
return player.getCapability(WIZARD_DATA_CAPABILITY, null);
|
||||
}
|
||||
|
||||
/** Called from the event handler each time the associated player entity is cloned, i.e. on respawn or when
|
||||
* travelling to a different dimension. Used to copy over any variables that should persist over player death.
|
||||
* This is the inverse of the old onPlayerDeath method, which reset the variables that shouldn't persist.
|
||||
* @param data The old WizardData whose variables are to be copied over.
|
||||
* @param respawn True if the player died and is respawning, false if they are just travelling between dimensions. */
|
||||
public void copyFrom(WizardData data, boolean respawn){
|
||||
// TODO: What happens with spirit wolf and spirit horse?
|
||||
this.hasSpiritHorse = data.hasSpiritHorse;
|
||||
this.hasSpiritWolf = data.hasSpiritWolf;
|
||||
this.allies = data.allies;
|
||||
this.allyNames = data.allyNames;
|
||||
this.clairvoyanceDimension = data.clairvoyanceDimension;
|
||||
this.clairvoyanceLocation = data.clairvoyanceLocation;
|
||||
this.selectedMinion = data.selectedMinion;
|
||||
// Curse of soulbinding is lifted when the caster dies, but not when they switch dimensions.
|
||||
if(!respawn) this.soulboundCreatures = data.soulboundCreatures;
|
||||
this.spellsDiscovered = data.spellsDiscovered;
|
||||
this.stoneCircleDimension = data.stoneCircleDimension;
|
||||
this.stoneCircleLocation = data.stoneCircleLocation;
|
||||
|
||||
// Imbuements are lost on death so their durations do not persist.
|
||||
// Command spell casting is reset on death so the associated variables do not persist.
|
||||
// tpCountdown is reset both when the player dies and when they switch dimensions.
|
||||
|
||||
}
|
||||
|
||||
/** Sends a packet to this player's client to synchronise necessary information. Only called server side. */
|
||||
public void sync(){
|
||||
if(this.player instanceof EntityPlayerMP){
|
||||
int id = -1;
|
||||
if(this.selectedMinion != null && this.selectedMinion.get() instanceof Entity) id = ((Entity)this.selectedMinion.get()).getEntityId();
|
||||
IMessage msg = new PacketPlayerSync.Message(this.spellsDiscovered, id);
|
||||
WizardryPacketHandler.net.sendTo(msg, (EntityPlayerMP)this.player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTagCompound serializeNBT() {
|
||||
|
||||
NBTTagCompound properties = new NBTTagCompound();
|
||||
|
||||
// ...so Java 8 allows you to do stuff like this:
|
||||
properties.setTag("imbuements", WizardryUtilities.mapToNBT(this.imbuementDurations,
|
||||
imbuement -> new NBTTagInt(Enchantment.getEnchantmentID((Enchantment)imbuement)), NBTTagInt::new));
|
||||
|
||||
properties.setBoolean("hasSpiritWolf", this.hasSpiritWolf);
|
||||
properties.setBoolean("hasSpiritHorse", this.hasSpiritHorse);
|
||||
|
||||
if(this.stoneCircleLocation != null) properties.setLong("stoneCircleLocation", this.stoneCircleLocation.toLong());
|
||||
properties.setInteger("stoneCircleDimension", this.stoneCircleDimension);
|
||||
properties.setInteger("tpCountdown", this.tpCountdown);
|
||||
|
||||
if(this.clairvoyanceLocation != null) properties.setLong("clairvoyanceLocation", this.clairvoyanceLocation.toLong());
|
||||
properties.setInteger("clairvoyanceDimension", this.getClairvoyanceDimension());
|
||||
|
||||
// THIS is why I wrote the list/map <-> NBT methods. Look how neat this is!
|
||||
properties.setTag("allies", WizardryUtilities.listToNBT(this.allies, WizardryUtilities::UUIDtoTagCompound));
|
||||
properties.setTag("allyNames", WizardryUtilities.listToNBT(this.allyNames, NBTTagString::new));
|
||||
properties.setTag("soulboundCreatures", WizardryUtilities.listToNBT(this.soulboundCreatures, WizardryUtilities::UUIDtoTagCompound));
|
||||
|
||||
// Might be worth converting this over to WizardryUtilities.listToNBT.
|
||||
int[] spells = new int[this.spellsDiscovered.size()];
|
||||
int i=0;
|
||||
for(Spell spell : this.spellsDiscovered){
|
||||
spells[i] = spell.id();
|
||||
i++;
|
||||
}
|
||||
properties.setIntArray("discoveredSpells", spells);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(NBTTagCompound nbt) {
|
||||
|
||||
if(nbt != null){
|
||||
|
||||
this.imbuementDurations = WizardryUtilities.NBTToMap(nbt.getTagList("imbuements", NBT.TAG_COMPOUND),
|
||||
(NBTTagInt tag) -> (Imbuement)Enchantment.getEnchantmentByID(tag.getInt()), NBTTagInt::getInt);
|
||||
|
||||
this.hasSpiritWolf = nbt.getBoolean("hasSpiritWolf");
|
||||
this.hasSpiritHorse = nbt.getBoolean("hasSpiritHorse");
|
||||
|
||||
this.stoneCircleLocation = BlockPos.fromLong(nbt.getLong("stoneCircleLocation"));
|
||||
this.stoneCircleDimension = nbt.getInteger("stoneCircleDimension");
|
||||
this.tpCountdown = nbt.getInteger("tpCountdown");
|
||||
|
||||
this.clairvoyanceLocation = BlockPos.fromLong(nbt.getLong("clairvoyanceLocation"));
|
||||
this.clairvoyanceDimension = nbt.getInteger("clairvoyanceDimension");
|
||||
|
||||
this.allies = new HashSet<UUID>(WizardryUtilities.NBTToList(nbt.getTagList("allies", NBT.TAG_COMPOUND),
|
||||
WizardryUtilities::tagCompoundToUUID));
|
||||
|
||||
this.allyNames = new HashSet<String>(WizardryUtilities.NBTToList(nbt.getTagList("allyNames", NBT.TAG_STRING),
|
||||
NBTTagString::getString));
|
||||
|
||||
this.soulboundCreatures = new HashSet<UUID>(WizardryUtilities.NBTToList(nbt.getTagList("soulboundCreatures", NBT.TAG_COMPOUND),
|
||||
WizardryUtilities::tagCompoundToUUID));
|
||||
|
||||
this.spellsDiscovered = new HashSet<Spell>();
|
||||
for(int id : nbt.getIntArray("discoveredSpells")){
|
||||
spellsDiscovered.add(Spell.get(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** This is a nested class for a few reasons: firstly, it makes sense because instances of this and WizardData go
|
||||
* hand-in-hand; secondly, it's too short to be worth a separate file; and thirdly (and most importantly) it allows
|
||||
* me to access WIZARD_DATA_CAPABILITY while keeping it private. */
|
||||
public static class Provider implements ICapabilitySerializable<NBTTagCompound> {
|
||||
|
||||
private final WizardData data;
|
||||
|
||||
public Provider(EntityPlayer player){
|
||||
data = new WizardData(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCapability(Capability<?> capability, EnumFacing facing) {
|
||||
return capability == WIZARD_DATA_CAPABILITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getCapability(Capability<T> capability, EnumFacing facing) {
|
||||
|
||||
if(capability == WIZARD_DATA_CAPABILITY){
|
||||
return WIZARD_DATA_CAPABILITY.cast(data);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTagCompound serializeNBT() {
|
||||
return data.serializeNBT();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(NBTTagCompound nbt) {
|
||||
data.deserializeNBT(nbt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Ended up deleting IWizardData because it was unnecessary. This is the comment that was at the start of it:
|
||||
|
||||
/* I'm not going to lie, I will never find the capabilities system even remotely intuitive so this is a bare-minimum
|
||||
* approach just to get things working (four classes where one would have done?!) At one point I considered simply
|
||||
* wrapping my old IEEP inside a single-field capability, but I eventually decided I would at least *try* to do it
|
||||
* properly.
|
||||
*
|
||||
* "...without having to directly implement many interfaces." - Forge Docs. I still can't see what's wrong with
|
||||
* implementing many interfaces; surely that's what Java interfaces are designed for?
|
||||
*
|
||||
* Other things I find annoying:
|
||||
* - IStorage. It's completely redundant in the majority of cases, and I don't understand why we need yet another
|
||||
* separate class.
|
||||
* - Making an interface, only to implement it once and once only. This completely defeats the point of interfaces.
|
||||
* - The EnumFacing parameter, which is again redundant for everything that isn't a tile entity. So much for a clean,
|
||||
* neat system.
|
||||
*
|
||||
* What Forge has effectively done is conflated two different functions: attaching data to stuff and cross-mod
|
||||
* integration/soft dependencies. I think this is bad design; it would have been better to keep the two features separate.
|
||||
*
|
||||
* Here's my current understanding of how the capability system works:
|
||||
* - You make an interface which defines the things your capability can do (this class). I will call this the TEMPLATE.
|
||||
* - You implement that interface with your default implementation (WizardData). This is the closest analog to your old
|
||||
* IEEP implementation class. THIS CLASS STORES ALL THE VARIABLES, and hence has one instance for each instance of whatever
|
||||
* it is attached to. I will call this the DATA.
|
||||
* - The DATA class implements INBTSerializable (assuming you want it to be saved, which is nearly always the case)
|
||||
* - Despite its name, Capability<T> does NOT represent a capability itself. Instead, it acts as a sort of identifier/key,
|
||||
* the idea being that you can access a particular instance of your DATA given the key (which tells forge that you want a
|
||||
* capability of type TEMPLATE) and the object you want the DATA for. This is what Entity.getCapability(...) does.
|
||||
*
|
||||
* To really understand what's going on though, you need to sift through Forge's verbose data structures and find where
|
||||
* capabilities are actually hooked into vanilla:
|
||||
* - Anything that implements ICapabilityProvider will have a private CapabilityDispatcher field. This holds other
|
||||
* ICapabilityProviders. (I know. This inheritance pattern DOES NOT MAKE SENSE, because these could, in theory, be OTHER
|
||||
* ENTITIES!)
|
||||
* - This field is assigned a value through Forge's event factory, which, as we are all familiar with, calls all the
|
||||
* methods marked with @SubscribeEvent. These methods add individual ICapabilityProviders to a Map stored in the event,
|
||||
* which the event factory then wraps in a CapabilityDispatcher (which is itself an ICapabilityProvider) for the object
|
||||
* that called it.
|
||||
* - In your event handler, you return a custom ICapabilityProvider which is effectively bolted on to the player, and
|
||||
* duplicates the ICapabilityProvider methods so you can hook into them and return an instance of your DATA class.
|
||||
* - Where before there was a simple collection of IEEPs stored in the player, there is now a tree of ICapabilityProviders:
|
||||
*
|
||||
* - Entity/TileEntity/ItemStack
|
||||
* - Vanilla ICapabilityProviders, mostly IItemHandlers, stored as fields.
|
||||
* - CapabilityDispatcher, stored as a field.
|
||||
* - Custom ICapabilityProviders
|
||||
* - Custom CapabilityDispatchers
|
||||
* - ...
|
||||
*
|
||||
* Most importantly, EACH PLAYER HOLDS THEIR OWN INSTANCE OF THIS TREE.
|
||||
*
|
||||
* When a capability is retrieved, the following process happens:
|
||||
* 1. For the Entity/TileEntity/ItemStack instance, ICapabilityProvider.getCapability(...) is called.
|
||||
* 2. The request propogates through the tree and finds the requested capability. */
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user