Files
Wizardry/src/main/java/electroblob/wizardry/data/DispenserCastingData.java
T
2019-12-17 17:12:31 +00:00

223 lines
7.8 KiB
Java

package electroblob.wizardry.data;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.item.ItemScroll;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.block.BlockDispenser;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.Capability.IStorage;
import net.minecraftforge.common.capabilities.CapabilityInject;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import java.util.ArrayList;
import java.util.List;
/**
* Internal capability for attaching data to dispensers. The sole purpose of this class is to keep track of continuous
* spell casting for dispensers.
* <p></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 4.2
* @author Electroblob
*/
@Mod.EventBusSubscriber
public class DispenserCastingData extends BlockCastingData<TileEntityDispenser> {
/** 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(DispenserCastingData.class)
private static final Capability<DispenserCastingData> DISPENSER_CASTING_CAPABILITY = null;
/** The time for which this dispenser will continue casting a continuous spell. When castingTick exceeds this value,
* the dispenser will either stop casting or, if it contains more of the same type of scroll, continue casting and
* increase this value by the duration that the spell should be cast for. */
private int duration;
public DispenserCastingData(){
this(null); // Nullary constructor for the registration method factory parameter
}
public DispenserCastingData(TileEntityDispenser dispenser){
super(dispenser);
}
/** Starts casting the given continuous spell from this dispenser. */
public void startCasting(Spell spell, double x, double y, double z, int duration, SpellModifiers modifiers){
startCasting(spell, x, y, z, modifiers);
this.castingTick = 1; // 1 because we already cast it once in BehaviourSpellDispense
this.duration = duration;
}
@Override
public void stopCasting(){
super.stopCasting();
}
@Override
protected Source getSource(){
return Source.DISPENSER;
}
@Override
protected EnumFacing getDirection(){
return tileEntity.getWorld().getBlockState(tileEntity.getPos()).getValue(BlockDispenser.FACING);
}
@Override
protected boolean shouldContinueCasting(){
return tileEntity.getWorld().isBlockPowered(tileEntity.getPos());
}
@Override
public void update(){
super.update();
// Check whether enough scrolls are left
if(this.isCasting() && this.spell.isContinuous){
if(castingTick > duration && !tileEntity.getWorld().isRemote){
if(findNewScroll()){
duration += ItemScroll.CASTING_TIME; // Best way to do it for now.
}else{
this.stopCastingAndNotify();
}
}
}
}
/** Searches through the dispenser's inventory for a new stack of scrolls of the same spell that is currently being
* cast and returns true if at least one such stack is found. Also consumes one scroll if a stack is found; if more
* than one applicable stack is found then one will be chosen at random. */
private boolean findNewScroll(){
if(spell == Spells.none) return false;
List<Integer> slots = new ArrayList<Integer>();
for(int i = 0; i < tileEntity.getSizeInventory(); i++){
ItemStack stack = tileEntity.getStackInSlot(i);
if(stack.getItem() instanceof ItemScroll && stack.getMetadata() == spell.metadata()) slots.add(i);
}
if(slots.isEmpty()) return false; // If no stack was found that matched the current spell
tileEntity.decrStackSize(slots.get(tileEntity.getWorld().rand.nextInt(slots.size())), 1); // Consumes 1 scroll
return true;
}
/** Returns the DispenserCastingData instance for the specified dispenser. */
public static DispenserCastingData get(TileEntityDispenser dispenser){
return dispenser.getCapability(DISPENSER_CASTING_CAPABILITY, null);
}
/** Called from preInit in the main mod class to register the DispenserCastingData capability. */
public static void register(){
CapabilityManager.INSTANCE.register(DispenserCastingData.class, new IStorage<DispenserCastingData>(){
@Override
public NBTBase writeNBT(Capability<DispenserCastingData> capability, DispenserCastingData instance, EnumFacing side){
return null;
}
@Override
public void readNBT(Capability<DispenserCastingData> capability, DispenserCastingData instance, EnumFacing side, NBTBase nbt){}
}, DispenserCastingData::new);
}
// Event handlers
@SubscribeEvent
// The type parameter here has to be SoundLoopSpellDispenser, not TileEntityDispenser, or the event won't get fired.
public static void onCapabilityLoad(AttachCapabilitiesEvent<TileEntity> event){
if(event.getObject() instanceof TileEntityDispenser)
event.addCapability(new ResourceLocation(Wizardry.MODID, "casting_data"),
new DispenserCastingData.Provider((TileEntityDispenser)event.getObject()));
}
// Only fired server-side
@SubscribeEvent
public static void onWorldTickEvent(TickEvent.WorldTickEvent event){
if(event.phase == TickEvent.Phase.END){
// This will fire once for each dimension, but since we want dispenser-casting to work in all dimensions,
// this is correct (the loaded tile entity list will of course be different in each case.
// Somehow this was throwing a CME, I have no idea why so I'm just going to cheat and copy the list
List<TileEntity> tileEntities = new ArrayList<>(event.world.loadedTileEntityList);
for(TileEntity tileentity : tileEntities){
if(tileentity instanceof TileEntityDispenser){
if(DispenserCastingData.get((TileEntityDispenser)tileentity) != null){
DispenserCastingData.get((TileEntityDispenser)tileentity).update();
}
}
}
}
}
/**
* This is a nested class for a few reasons: firstly, it makes sense because instances of this and
* DispenserCastingData 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 DISPENSER_CASTING_CAPABILITY while keeping it private.
*/
public static class Provider implements ICapabilitySerializable<NBTTagCompound> {
private final DispenserCastingData data;
public Provider(TileEntityDispenser dispenser){
data = new DispenserCastingData(dispenser);
}
@Override
public boolean hasCapability(Capability<?> capability, EnumFacing facing){
return capability == DISPENSER_CASTING_CAPABILITY;
}
@Override
public <T> T getCapability(Capability<T> capability, EnumFacing facing){
if(capability == DISPENSER_CASTING_CAPABILITY){
return DISPENSER_CASTING_CAPABILITY.cast(data);
}
return null;
}
@Override
public NBTTagCompound serializeNBT(){
return data.serializeNBT();
}
@Override
public void deserializeNBT(NBTTagCompound nbt){
data.deserializeNBT(nbt);
}
}
}