package com.minemaarten.signals.capabilities; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import net.minecraft.block.BlockHopper; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.item.EntityMinecart; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.InventoryBasic; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityFurnace; import net.minecraft.tileentity.TileEntityHopper; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.CapabilityInject; import net.minecraftforge.common.capabilities.CapabilityManager; import net.minecraftforge.common.capabilities.ICapabilitySerializable; import net.minecraftforge.event.entity.minecart.MinecartUpdateEvent; import net.minecraftforge.fml.common.network.NetworkRegistry; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.ItemHandlerHelper; import net.minecraftforge.items.wrapper.InvWrapper; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import com.minemaarten.signals.api.IRail; import com.minemaarten.signals.api.access.IDestinationAccessor; import com.minemaarten.signals.api.access.ISignal.EnumLampStatus; import com.minemaarten.signals.chunkloading.ChunkLoadManager; import com.minemaarten.signals.config.SignalsConfig; import com.minemaarten.signals.init.ModItems; import com.minemaarten.signals.inventory.EngineItemHandler; import com.minemaarten.signals.lib.Log; import com.minemaarten.signals.lib.SignalsUtils; import com.minemaarten.signals.network.GuiSynced; import com.minemaarten.signals.network.NetworkHandler; import com.minemaarten.signals.network.PacketSpawnParticle; import com.minemaarten.signals.network.PacketUpdateMinecartEngineState; import com.minemaarten.signals.rail.RailManager; import com.minemaarten.signals.rail.network.NetworkRail; import com.minemaarten.signals.rail.network.NetworkSignal; import com.minemaarten.signals.rail.network.NetworkState; import com.minemaarten.signals.rail.network.RailNetwork; import com.minemaarten.signals.rail.network.mc.MCPos; import com.minemaarten.signals.rail.network.mc.RailNetworkManager; import com.minemaarten.signals.tileentity.IGUITextFieldSensitive; public class CapabilityMinecartDestination implements IGUITextFieldSensitive, IDestinationAccessor{ @CapabilityInject(CapabilityMinecartDestination.class) public static Capability<CapabilityMinecartDestination> INSTANCE; private static final Pattern EMPTY_PATTERN = Pattern.compile(""); @GuiSynced private String destinationStations = ""; //'\n' separated list of destinations private Pattern[] destinationRegexes = new Pattern[0]; //Cache of the regexes of the destinations @GuiSynced private int curDestinationIndex; @GuiSynced private String invalidDestinations = ""; //Destinations become invalid when the regex is invalid. private boolean chunkloading; //True when a ChunkLoader upgrade has been applied. private String chunkloadingPlayer = ""; //The player that keeps this cart chunkloaded. private boolean motorized; //True when an engine upgrade has been applied. @GuiSynced private int fuelLeft; @GuiSynced private int totalBurnTime; private int hopperTimer; private final InventoryBasic fuelInv = new InventoryBasic("cartEngineInv", true, 5){ @Override public boolean isItemValidForSlot(int index, ItemStack stack){ return stack == null || TileEntityFurnace.isItemFuel(stack); } }; private final IItemHandler fuelItemHandler = new InvWrapper(fuelInv); private final IItemHandler engineItemHandler = new EngineItemHandler(this, fuelItemHandler); private boolean motorActive; public boolean travelingBetweenDimensions; public static void register(){ CapabilityManager.INSTANCE.register(CapabilityMinecartDestination.class, new Capability.IStorage<CapabilityMinecartDestination>(){ @Override public NBTBase writeNBT(Capability<CapabilityMinecartDestination> capability, CapabilityMinecartDestination instance, EnumFacing side){ NBTTagCompound tag = new NBTTagCompound(); tag.setString("destinations", instance.destinationStations); tag.setInteger("destIndex", instance.curDestinationIndex); tag.setBoolean("chunkloading", instance.chunkloading); tag.setString("chunkloadingPlayer", instance.chunkloadingPlayer); tag.setBoolean("motorized", instance.motorized); if(instance.motorized) { tag.setInteger("fuelLeft", instance.fuelLeft); tag.setInteger("totalBurnTime", instance.totalBurnTime); SignalsUtils.writeInventoryToNBT(tag, instance.fuelInv); } return tag; } @Override public void readNBT(Capability<CapabilityMinecartDestination> capability, CapabilityMinecartDestination instance, EnumFacing side, NBTBase base){ NBTTagCompound tag = (NBTTagCompound)base; instance.destinationStations = tag.getString("destinations"); instance.recompileRegexes(); instance.curDestinationIndex = tag.getInteger("destIndex"); instance.chunkloading = tag.getBoolean("chunkloading"); instance.chunkloadingPlayer = tag.getString("chunkloadingPlayer"); instance.motorized = tag.getBoolean("motorized"); if(instance.motorized) { instance.fuelLeft = tag.getInteger("fuelLeft"); instance.totalBurnTime = tag.getInteger("totalBurnTime"); SignalsUtils.readInventoryFromNBT(tag, instance.fuelInv); } } }, CapabilityMinecartDestination::new); } @Override public void setText(int textFieldID, String text){ destinationStations = text; recompileRegexes(); } @Override public void setDestinations(String... destinations){ Validate.noNullElements(destinations, "The destinations array contains null at position %d"); destinationStations = StringUtils.joinWith("\n", (Object[])destinations); recompileRegexes(); } private void recompileRegexes(){ String[] destinations = getDestinations(); destinationRegexes = new Pattern[destinations.length]; invalidDestinations = ""; for(int i = 0; i < destinations.length; i++) { try { destinationRegexes[i] = Pattern.compile(destinations[i]); } catch(PatternSyntaxException e) { if(!invalidDestinations.equals("")) { invalidDestinations += ","; } invalidDestinations += "" + i; destinationRegexes[i] = EMPTY_PATTERN; } } getCurrentDestination(); //Update to a valid destination index. } @Override public int[] getInvalidDestinationIndeces(){ if(invalidDestinations.equals("")) return new int[0]; String[] strings = invalidDestinations.split(","); int[] ints = new int[strings.length]; for(int i = 0; i < strings.length; i++) { ints[i] = Integer.parseInt(strings[i]); } return ints; } @Override public String getText(int textFieldID){ return destinationStations; } public String getDestination(int index){ return getDestinations()[index]; } @Override public String[] getDestinations(){ return destinationStations.equals("") ? new String[0] : destinationStations.split("\n"); } @Override public int getTotalDestinations(){ return getDestinations().length; } @Override public String getCurrentDestination(){ String[] destinations = getDestinations(); if(curDestinationIndex >= destinations.length || curDestinationIndex == -1) nextDestination(); return curDestinationIndex >= 0 ? destinations[curDestinationIndex] : ""; } @Override public int getDestinationIndex(){ getCurrentDestination(); //Trigger updating destination index. return curDestinationIndex; } public void nextDestination(){ setCurrentDestinationIndex(curDestinationIndex + 1); } @Override public void setCurrentDestinationIndex(int index){ String[] destinations = getDestinations(); if(index >= destinations.length || index < 0) { curDestinationIndex = destinations.length > 0 ? 0 : -1; } else { curDestinationIndex = index; } } public Pattern getCurrentDestinationRegex(){ getCurrentDestination(); return curDestinationIndex >= 0 ? destinationRegexes[curDestinationIndex] : EMPTY_PATTERN; } public boolean setChunkloading(EntityPlayer associatedPlayer, EntityMinecart cart){ chunkloadingPlayer = associatedPlayer.getGameProfile().getId().toString(); if(ChunkLoadManager.INSTANCE.markAsChunkLoader(associatedPlayer, cart)) { chunkloading = true; return true; } else { return false; } } public boolean isChunkLoading(){ return chunkloading; } public void setMotorized(){ motorized = true; } public boolean isMotorized(){ return motorized; } /** * Tries to use fuel and returns true if succeeded. * @return */ public boolean useFuel(EntityMinecart cart){ if(motorized) { if(fuelLeft == 0) { for(int i = 0; i < fuelInv.getSizeInventory(); i++) { ItemStack fuel = fuelInv.getStackInSlot(i); if(!fuel.isEmpty()) { int fuelValue = TileEntityFurnace.getItemBurnTime(fuel); if(fuelValue > 0) { fuel.shrink(1); if(fuel.isEmpty()) { fuelInv.setInventorySlotContents(i, fuel.getItem().getContainerItem(fuel)); } fuelLeft += fuelValue; totalBurnTime = fuelValue; break; } } } } if(fuelLeft > 0) { fuelLeft--; double randX = cart.getPositionVector().x + (cart.world.rand.nextDouble() - 0.5) * 0.5; double randY = cart.getPositionVector().y + (cart.world.rand.nextDouble() - 0.5) * 0.5; double randZ = cart.getPositionVector().z + (cart.world.rand.nextDouble() - 0.5) * 0.5; NetworkHandler.sendToAllAround(new PacketSpawnParticle(EnumParticleTypes.SMOKE_LARGE, randX, randY, randZ, 0, 0, 0), cart.world); return true; } } return false; } public InventoryBasic getFuelInv(){ return fuelInv; } public IItemHandler getEngineItemHandler(){ return motorized ? fuelItemHandler : engineItemHandler; } public int getScaledFuel(int barLength){ return totalBurnTime == 0 ? 0 : barLength * fuelLeft / totalBurnTime; } public void onCartBroken(EntityMinecart cart){ if(!travelingBetweenDimensions) { if(motorized) { motorized = false; cart.dropItem(ModItems.CART_ENGINE, 1); for(int i = 0; i < fuelInv.getSizeInventory(); i++) { ItemStack fuel = fuelInv.getStackInSlot(i); if(fuel != null) cart.entityDropItem(fuel, 0); } } } if(chunkloading) { ChunkLoadManager.INSTANCE.unmarkAsChunkLoader(cart); if(!travelingBetweenDimensions) { chunkloading = false; if(!SignalsConfig.disableChunkLoaderUpgrades) cart.dropItem(ModItems.CHUNKLOADER_UPGRADE, 1); } } travelingBetweenDimensions = false; } public void onCartJoinWorld(EntityMinecart cart){ if(chunkloading) { if(!ChunkLoadManager.INSTANCE.markAsChunkLoader(chunkloadingPlayer, cart)) { Log.warning("Could not chunkload cart for player '" + chunkloadingPlayer + "'!"); } } } public void setEngineActive(boolean active){ motorActive = active; } public void onCartUpdate(MinecartUpdateEvent event){ EntityMinecart cart = event.getMinecart(); if(!cart.world.isRemote) { if(isMotorized()) { boolean shouldRun = true; EnumFacing cartDir = cart.getAdjustedHorizontalFacing(); if(new Vec3d(cart.motionX, cart.motionY, cart.motionZ).lengthVector() < 0.05) { shouldRun = false; if(hopperTimer > 0) { hopperTimer--; } if(hopperTimer == 0) { hopperTimer = extractFuelFromHopper(cart, event.getPos()) ? 8 : 40; } } else { hopperTimer = 0; NetworkRail<MCPos> rail = RailNetworkManager.getInstance(cart.world.isRemote).getRail(cart.world, event.getPos()); if(rail == null) { //When not traveling over a Signals managed rail network //Try to look up a rail using block states. IBlockState state = cart.world.getBlockState(event.getPos()); IRail r = RailManager.getInstance().getRail(cart.world, event.getPos(), state); shouldRun = r != null; //Power the engine when a rail is found } else { RailNetwork<MCPos> network = RailNetworkManager.getInstance(cart.world.isRemote).getNetwork(); NetworkSignal<MCPos> signal = network.railObjects.getNeighborSignals(rail.getPotentialNeighborObjectLocations()).filter(s -> s.getRailPos().equals(rail.getPos())).findFirst().orElse(null); NetworkState<MCPos> state = RailNetworkManager.getInstance(cart.world.isRemote).getState(); shouldRun = signal == null || state.getLampStatus(signal.getPos()) == EnumLampStatus.GREEN; if(!shouldRun) { cart.motionX = 0; cart.motionZ = 0; } } } if(shouldRun && useFuel(cart)) { if(!motorActive) NetworkHandler.sendToAllAround(new PacketUpdateMinecartEngineState(cart, true), new NetworkRegistry.TargetPoint(cart.world.provider.getDimension(), cart.getPositionVector().x, cart.getPositionVector().y, cart.getPositionVector().z, 64)); motorActive = true; double acceleration = 0.03D; cart.motionX += cartDir.getFrontOffsetX() * acceleration; cart.motionZ += cartDir.getFrontOffsetZ() * acceleration; cart.motionX = MathHelper.clamp(cart.motionX, -cart.getMaxCartSpeedOnRail(), cart.getMaxCartSpeedOnRail()); cart.motionZ = MathHelper.clamp(cart.motionZ, -cart.getMaxCartSpeedOnRail(), cart.getMaxCartSpeedOnRail()); } else { if(motorActive) NetworkHandler.sendToAllAround(new PacketUpdateMinecartEngineState(cart, true), new NetworkRegistry.TargetPoint(cart.world.provider.getDimension(), cart.getPositionVector().x, cart.getPositionVector().y, cart.getPositionVector().z, 64)); motorActive = false; } } if(chunkloading) { double x = cart.posX + cart.world.rand.nextDouble() - 0.5; double y = cart.posY + cart.world.rand.nextDouble() - 0.5; double z = cart.posZ + cart.world.rand.nextDouble() - 0.5; NetworkHandler.sendToAllAround(new PacketSpawnParticle(EnumParticleTypes.PORTAL, x, y, z, 0, 0, 0), cart.world); if(SignalsConfig.disableChunkLoaderUpgrades) { ChunkLoadManager.INSTANCE.unmarkAsChunkLoader(cart); chunkloading = false; } } } else { if(motorActive) { cart.world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, cart.getPositionVector().x, cart.getPositionVector().y, cart.getPositionVector().z, 0, 0, 0); } } } /** * * @param cart * @return true if there was a valid hopper (not necessarily if extracted an item) */ private boolean extractFuelFromHopper(EntityMinecart cart, BlockPos pos){ boolean foundHopper = false; for(EnumFacing dir : EnumFacing.VALUES) { BlockPos neighbor = pos; for(int offsetTimes = 0; offsetTimes < (dir == EnumFacing.UP ? 2 : 1); offsetTimes++) { neighbor = neighbor.offset(dir); TileEntity te = cart.world.getTileEntity(neighbor); if(te instanceof TileEntityHopper) { EnumFacing hopperDir = cart.world.getBlockState(neighbor).getValue(BlockHopper.FACING); if(hopperDir.getOpposite() == dir) { TileEntityHopper hopper = (TileEntityHopper)te; for(int i = 0; i < hopper.getSizeInventory(); i++) { ItemStack stack = hopper.getStackInSlot(i); if(!stack.isEmpty() && getFuelInv().isItemValidForSlot(0, stack)) { ItemStack inserted = stack.copy(); inserted.setCount(1); ItemStack left = ItemHandlerHelper.insertItemStacked(getEngineItemHandler(), inserted, false); if(left.isEmpty()) { stack.shrink(1); hopper.markDirty(); return true; } } } foundHopper = true; } } } } return foundHopper; } public static class Provider implements ICapabilitySerializable<NBTBase>{ private final CapabilityMinecartDestination cap = new CapabilityMinecartDestination(); @Override public boolean hasCapability(Capability<?> capability, EnumFacing facing){ return capability == INSTANCE; } @Override public <T> T getCapability(Capability<T> capability, EnumFacing facing){ if(hasCapability(capability, facing)) { return (T)cap; } else { return null; } } @Override public NBTBase serializeNBT(){ return INSTANCE.getStorage().writeNBT(INSTANCE, cap, null); } @Override public void deserializeNBT(NBTBase nbt){ INSTANCE.getStorage().readNBT(INSTANCE, cap, null, nbt); } } }