From ecff23d027f9263bfb07bdb5212d90adf7e265cf Mon Sep 17 00:00:00 2001 From: SquidDev Date: Fri, 16 Feb 2018 10:33:32 +0000 Subject: [PATCH] Add turtle events The main aim of this is to allow for greater extensibility for other mods. For instance, you can now prevent turtles placing dirt blocks, or turning when on gravel. --- .../api/turtle/ITurtleUpgrade.java | 7 + .../api/turtle/event/TurtleAction.java | 84 ++++++ .../api/turtle/event/TurtleActionEvent.java | 76 ++++++ .../api/turtle/event/TurtleAttackEvent.java | 80 ++++++ .../api/turtle/event/TurtleBlockEvent.java | 241 ++++++++++++++++++ .../api/turtle/event/TurtleEvent.java | 43 ++++ .../turtle/event/TurtleInventoryEvent.java | 84 ++++++ .../api/turtle/event/TurtlePlayerEvent.java | 44 ++++ .../api/turtle/event/package-info.java | 10 + .../shared/turtle/apis/TurtleAPI.java | 10 + .../shared/turtle/core/TurtleDropCommand.java | 12 + .../turtle/core/TurtleEquipCommand.java | 9 + .../turtle/core/TurtleInspectCommand.java | 22 +- .../shared/turtle/core/TurtleMoveCommand.java | 10 +- .../turtle/core/TurtlePlaceCommand.java | 21 +- .../turtle/core/TurtleRefuelCommand.java | 9 + .../shared/turtle/core/TurtleSuckCommand.java | 11 + .../shared/turtle/core/TurtleTurnCommand.java | 9 + .../shared/turtle/upgrades/TurtleTool.java | 35 ++- 19 files changed, 801 insertions(+), 16 deletions(-) create mode 100644 src/main/java/dan200/computercraft/api/turtle/event/TurtleAction.java create mode 100644 src/main/java/dan200/computercraft/api/turtle/event/TurtleActionEvent.java create mode 100644 src/main/java/dan200/computercraft/api/turtle/event/TurtleAttackEvent.java create mode 100644 src/main/java/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java create mode 100644 src/main/java/dan200/computercraft/api/turtle/event/TurtleEvent.java create mode 100644 src/main/java/dan200/computercraft/api/turtle/event/TurtleInventoryEvent.java create mode 100644 src/main/java/dan200/computercraft/api/turtle/event/TurtlePlayerEvent.java create mode 100644 src/main/java/dan200/computercraft/api/turtle/event/package-info.java diff --git a/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java b/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java index 83bb94581..d9741c6b7 100644 --- a/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java +++ b/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java @@ -8,11 +8,15 @@ import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.turtle.event.TurtleAttackEvent; +import dan200.computercraft.api.turtle.event.TurtleBlockEvent; import net.minecraft.client.renderer.block.model.IBakedModel; import net.minecraft.client.renderer.block.model.ModelResourceLocation; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; +import net.minecraftforge.event.entity.player.AttackEntityEvent; +import net.minecraftforge.event.world.BlockEvent; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import org.apache.commons.lang3.tuple.Pair; @@ -100,6 +104,9 @@ public interface ITurtleUpgrade * Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called * by the turtle, and the tool is required to do some work. * + * Conforming implementations should fire {@link BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig}for digging, + * {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking. + * * @param turtle Access to the turtle that the tool resides on. * @param side Which side of the turtle (left or right) the tool resides on. * @param verb Which action (dig or attack) the turtle is being called on to perform. diff --git a/src/main/java/dan200/computercraft/api/turtle/event/TurtleAction.java b/src/main/java/dan200/computercraft/api/turtle/event/TurtleAction.java new file mode 100644 index 000000000..d7fd7fd4e --- /dev/null +++ b/src/main/java/dan200/computercraft/api/turtle/event/TurtleAction.java @@ -0,0 +1,84 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only. + * For help using the API, and posting your mods, visit the forums at computercraft.info. + */ + +package dan200.computercraft.api.turtle.event; + +/** + * A basic action that a turtle may perform, as accessed by the {@code turtle} API. + * + * @see TurtleActionEvent + */ +public enum TurtleAction +{ + /** + * A turtle moves to a new position. + * + * @see TurtleBlockEvent.Move + */ + MOVE, + + /** + * A turtle turns in a specific direction. + */ + TURN, + + /** + * A turtle attempts to dig a block. + * + * @see TurtleBlockEvent.Dig + */ + DIG, + + /** + * A turtle attempts to place a block or item in the world. + * + * @see TurtleBlockEvent.Place + */ + PLACE, + + /** + * A turtle attempts to attack an entity. + * + * @see TurtleActionEvent + */ + ATTACK, + + /** + * Drop an item into an inventory/the world. + * + * @see TurtleInventoryEvent.Drop + */ + DROP, + + /** + * Suck an item from an inventory or the world. + * + * @see TurtleInventoryEvent.Suck + */ + SUCK, + + /** + * Refuel the turtle's fuel levels. + */ + REFUEL, + + /** + * Equip or unequip an item. + */ + EQUIP, + + /** + * Inspect a block in world + * + * @see TurtleBlockEvent.Inspect + */ + INSPECT, + + /** + * Gather metdata about an item in the turtle's inventory. + */ + INSPECT_ITEM, +} diff --git a/src/main/java/dan200/computercraft/api/turtle/event/TurtleActionEvent.java b/src/main/java/dan200/computercraft/api/turtle/event/TurtleActionEvent.java new file mode 100644 index 000000000..cfd0f0fe1 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/turtle/event/TurtleActionEvent.java @@ -0,0 +1,76 @@ +package dan200.computercraft.api.turtle.event; + +import com.google.common.base.Preconditions; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import net.minecraftforge.fml.common.eventhandler.Cancelable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * An event fired when a turtle is performing a known action. + */ +@Cancelable +public class TurtleActionEvent extends TurtleEvent +{ + private final TurtleAction action; + private String failureMessage; + + public TurtleActionEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action ) + { + super( turtle ); + + Preconditions.checkNotNull( action, "action cannot be null" ); + this.action = action; + } + + public TurtleAction getAction() + { + return action; + } + + /** + * Sets the cancellation state of this action. + * + * If {@code cancel} is {@code true}, this action will not be carried out. + * + * @param cancel The new canceled value. + * @see TurtleCommandResult#failure() + * @deprecated Use {@link #setCanceled(boolean, String)} instead. + */ + @Override + @Deprecated + public void setCanceled( boolean cancel ) + { + setCanceled( cancel, null ); + } + + /** + * Set the cancellation state of this action, setting a failure message if required. + * + * If {@code cancel} is {@code true}, this action will not be carried out. + * + * @param cancel The new canceled value. + * @param failureMessage The message to return to the user explaining the failure. + * @see TurtleCommandResult#failure(String) + */ + public void setCanceled( boolean cancel, @Nullable String failureMessage ) + { + super.setCanceled( cancel ); + this.failureMessage = cancel ? failureMessage : null; + } + + /** + * Get the message with which this will fail. + * + * @return The failure message. + * @see TurtleCommandResult#failure() + * @see #setCanceled(boolean, String) + */ + @Nullable + public String getFailureMessage() + { + return failureMessage; + } +} diff --git a/src/main/java/dan200/computercraft/api/turtle/event/TurtleAttackEvent.java b/src/main/java/dan200/computercraft/api/turtle/event/TurtleAttackEvent.java new file mode 100644 index 000000000..7b8347392 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/turtle/event/TurtleAttackEvent.java @@ -0,0 +1,80 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only. + * For help using the API, and posting your mods, visit the forums at computercraft.info. + */ + +package dan200.computercraft.api.turtle.event; + +import com.google.common.base.Preconditions; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.api.turtle.TurtleVerb; +import net.minecraft.entity.Entity; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.event.entity.player.AttackEntityEvent; + +import javax.annotation.Nonnull; + +/** + * Fired when a turtle attempts to attack an entity. + * + * This must be fired by {@link ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, EnumFacing)}, + * as the base {@code turtle.attack()} command does not fire it. + * + * Note that such commands should also fire {@link AttackEntityEvent}, so you do not need to listen to both. + * + * @see TurtleAction#ATTACK + */ +public class TurtleAttackEvent extends TurtlePlayerEvent +{ + private final Entity target; + private final ITurtleUpgrade upgrade; + private final TurtleSide side; + + public TurtleAttackEvent( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull Entity target, @Nonnull ITurtleUpgrade upgrade, @Nonnull TurtleSide side ) + { + super( turtle, TurtleAction.ATTACK, player ); + Preconditions.checkNotNull( target, "target cannot be null" ); + Preconditions.checkNotNull( upgrade, "upgrade cannot be null" ); + Preconditions.checkNotNull( side, "side cannot be null" ); + this.target = target; + this.upgrade = upgrade; + this.side = side; + } + + /** + * Get the entity being attacked by this turtle. + * + * @return The entity being attacked. + */ + @Nonnull + public Entity getTarget() + { + return target; + } + + /** + * Get the upgrade responsible for attacking. + * + * @return The upgrade responsible for attacking. + */ + @Nonnull + public ITurtleUpgrade getUpgrade() + { + return upgrade; + } + + /** + * Get the side the attacking upgrade is on. + * + * @return The upgrade's side. + */ + @Nonnull + public TurtleSide getSide() + { + return side; + } +} diff --git a/src/main/java/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java b/src/main/java/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java new file mode 100644 index 000000000..cc037287d --- /dev/null +++ b/src/main/java/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java @@ -0,0 +1,241 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only. + * For help using the API, and posting your mods, visit the forums at computercraft.info. + */ + +package dan200.computercraft.api.turtle.event; + +import com.google.common.base.Preconditions; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.api.turtle.TurtleVerb; +import net.minecraft.block.state.IBlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.event.world.BlockEvent; +import net.minecraftforge.fml.common.eventhandler.Cancelable; + +import javax.annotation.Nonnull; +import java.util.Map; + +/** + * A general event for when a turtle interacts with a block or region. + * + * You should generally listen to one of the sub-events instead, cancelling them where + * appropriate. + * + * Note that you are not guaranteed to receive this event, if it has been cancelled by other + * mechanisms, such as block protection systems. + * + * Be aware that some events (such as {@link TurtleInventoryEvent}) do not necessarily interact + * with a block, simply objects within that block space. + */ +@Cancelable +public abstract class TurtleBlockEvent extends TurtlePlayerEvent +{ + private final World world; + private final BlockPos pos; + + protected TurtleBlockEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos ) + { + super( turtle, action, player ); + + Preconditions.checkNotNull( world, "world cannot be null" ); + Preconditions.checkNotNull( pos, "pos cannot be null" ); + this.world = world; + this.pos = pos; + } + + /** + * Get the world the turtle is interacting in. + * + * @return The world the turtle is interacting in. + */ + public World getWorld() + { + return world; + } + + /** + * Get the position the turtle is interacting with. Note that this is different + * to {@link ITurtleAccess#getPosition()}. + * + * @return The position the turtle is interacting with. + */ + public BlockPos getPos() + { + return pos; + } + + /** + * Fired when a turtle attempts to dig a block. + * + * This must be fired by {@link ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, EnumFacing)}, + * as the base {@code turtle.dig()} command does not fire it. + * + * Note that such commands should also fire {@link BlockEvent.BreakEvent}, so you do not need to listen to both. + * + * @see TurtleAction#DIG + */ + @Cancelable + public static class Dig extends TurtleBlockEvent + { + private final IBlockState block; + private final ITurtleUpgrade upgrade; + private final TurtleSide side; + + public Dig( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState block, @Nonnull ITurtleUpgrade upgrade, @Nonnull TurtleSide side ) + { + super( turtle, TurtleAction.DIG, player, world, pos ); + + Preconditions.checkNotNull( block, "block cannot be null" ); + Preconditions.checkNotNull( upgrade, "upgrade cannot be null" ); + Preconditions.checkNotNull( side, "side cannot be null" ); + this.block = block; + this.upgrade = upgrade; + this.side = side; + } + + /** + * Get the block which is about to be broken. + * + * @return The block which is going to be broken. + */ + @Nonnull + public IBlockState getBlock() + { + return block; + } + + /** + * Get the upgrade doing the digging + * + * @return The upgrade doing the digging. + */ + @Nonnull + public ITurtleUpgrade getUpgrade() + { + return upgrade; + } + + /** + * Get the side the upgrade doing the digging is on. + * + * @return The upgrade's side. + */ + @Nonnull + public TurtleSide getSide() + { + return side; + } + } + + /** + * Fired when a turtle attempts to move into a block. + * + * @see TurtleAction#MOVE + */ + @Cancelable + public static class Move extends TurtleBlockEvent + { + public Move( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos ) + { + super( turtle, TurtleAction.MOVE, player, world, pos ); + } + } + + /** + * Fired when a turtle attempts to place a block in the world. + * + * @see TurtleAction#PLACE + */ + @Cancelable + public static class Place extends TurtleBlockEvent + { + private final ItemStack stack; + + public Place( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull ItemStack stack ) + { + super( turtle, TurtleAction.PLACE, player, world, pos ); + + Preconditions.checkNotNull( stack, "stack cannot be null" ); + this.stack = stack; + } + + /** + * Get the item stack that will be placed. This should not be modified. + * + * @return The item stack to be placed. + */ + @Nonnull + public ItemStack getStack() + { + return stack; + } + } + + /** + * Fired when a turtle gathers data on a block in world. + * + * You may prevent blocks being inspected, or add additional information to the result. + * + * @see TurtleAction#INSPECT + */ + @Cancelable + public static class Inspect extends TurtleBlockEvent + { + private final IBlockState state; + private final Map data; + + public Inspect( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState state, @Nonnull Map data ) + { + super( turtle, TurtleAction.INSPECT, player, world, pos ); + + Preconditions.checkNotNull( state, "state cannot be null" ); + Preconditions.checkNotNull( data, "data cannot be null" ); + this.data = data; + this.state = state; + } + + /** + * Get the block state which is being inspected. + * + * @return The inspected block state. + */ + @Nonnull + public IBlockState getState() + { + return state; + } + + /** + * Get the "inspection data" from this block, which will be returned to the user. + * + * @return This block's inspection data. + */ + @Nonnull + public Map getData() + { + return data; + } + + /** + * Add new information to the inspection result. Note this will override fields with the same name. + * + * @param newData The data to add. Note all values should be convertable to Lua (see + * {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}). + */ + public void addData( @Nonnull Map newData ) + { + Preconditions.checkNotNull( newData, "newData cannot be null" ); + data.putAll( newData ); + } + } +} diff --git a/src/main/java/dan200/computercraft/api/turtle/event/TurtleEvent.java b/src/main/java/dan200/computercraft/api/turtle/event/TurtleEvent.java new file mode 100644 index 000000000..b6d27d683 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/turtle/event/TurtleEvent.java @@ -0,0 +1,43 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only. + * For help using the API, and posting your mods, visit the forums at computercraft.info. + */ + +package dan200.computercraft.api.turtle.event; + +import com.google.common.base.Preconditions; +import dan200.computercraft.api.turtle.ITurtleAccess; +import net.minecraftforge.fml.common.eventhandler.Event; + +import javax.annotation.Nonnull; + +/** + * A base class for all events concerning a turtle. This will only ever constructed and fired on the server side, + * so sever specific methods on {@link ITurtleAccess} are safe to use. + * + * You should generally not need to subscribe to this event, preferring one of the more specific classes. + * + * @see TurtleActionEvent + */ +public abstract class TurtleEvent extends Event +{ + private final ITurtleAccess turtle; + + protected TurtleEvent( @Nonnull ITurtleAccess turtle ) + { + Preconditions.checkNotNull( turtle, "turtle cannot be null" ); + this.turtle = turtle; + } + + /** + * Get the turtle which is performing this action. + * + * @return The access for this turtle. + */ + @Nonnull + public ITurtleAccess getTurtle() + { + return turtle; + } +} diff --git a/src/main/java/dan200/computercraft/api/turtle/event/TurtleInventoryEvent.java b/src/main/java/dan200/computercraft/api/turtle/event/TurtleInventoryEvent.java new file mode 100644 index 000000000..e52faf8e9 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/turtle/event/TurtleInventoryEvent.java @@ -0,0 +1,84 @@ +package dan200.computercraft.api.turtle.event; + +import com.google.common.base.Preconditions; +import dan200.computercraft.api.turtle.ITurtleAccess; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.fml.common.eventhandler.Cancelable; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Fired when a turtle attempts to interact with an inventory. + */ +@Cancelable +public abstract class TurtleInventoryEvent extends TurtleBlockEvent +{ + private final IItemHandler handler; + + protected TurtleInventoryEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler ) + { + super( turtle, action, player, world, pos ); + this.handler = handler; + } + + /** + * Get the inventory being interacted with + * + * @return The inventory being interacted with, {@code null} if the item will be dropped to/sucked from the world. + */ + @Nullable + public IItemHandler getItemHandler() + { + return handler; + } + + /** + * Fired when a turtle attempts to suck from an inventory. + * + * @see TurtleAction#SUCK + */ + @Cancelable + public static class Suck extends TurtleInventoryEvent + { + public Suck( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler ) + { + super( turtle, TurtleAction.SUCK, player, world, pos, handler ); + } + } + + /** + * Fired when a turtle attempts to drop an item into an inventory. + * + * @see TurtleAction#DROP + */ + @Cancelable + public static class Drop extends TurtleInventoryEvent + { + private final ItemStack stack; + + public Drop( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler, @Nonnull ItemStack stack ) + { + super( turtle, TurtleAction.DROP, player, world, pos, handler ); + + Preconditions.checkNotNull( stack, "stack cannot be null" ); + this.stack = stack; + } + + /** + * The item which will be inserted into the inventory/dropped on the ground. + * + * Note that this is a copy of the original stack, and so should not be modified, as that will have no effect. + * + * @return The item stack which will be dropped. + */ + public ItemStack getStack() + { + return stack.copy(); + } + } +} diff --git a/src/main/java/dan200/computercraft/api/turtle/event/TurtlePlayerEvent.java b/src/main/java/dan200/computercraft/api/turtle/event/TurtlePlayerEvent.java new file mode 100644 index 000000000..46179f3de --- /dev/null +++ b/src/main/java/dan200/computercraft/api/turtle/event/TurtlePlayerEvent.java @@ -0,0 +1,44 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only. + * For help using the API, and posting your mods, visit the forums at computercraft.info. + */ + +package dan200.computercraft.api.turtle.event; + +import com.google.common.base.Preconditions; +import dan200.computercraft.api.turtle.ITurtleAccess; +import net.minecraftforge.common.util.FakePlayer; + +import javax.annotation.Nonnull; + +/** + * An action done by a turtle which is normally done by a player. + * + * {@link #getPlayer()} may be used to modify the player's attributes or perform permission checks. + */ +public abstract class TurtlePlayerEvent extends TurtleActionEvent +{ + private final FakePlayer player; + + protected TurtlePlayerEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player ) + { + super( turtle, action ); + + Preconditions.checkNotNull( player, "player cannot be null" ); + this.player = player; + } + + /** + * A fake player, representing this turtle. + * + * This may be used for triggering permission checks. + * + * @return A {@link FakePlayer} representing this turtle. + */ + @Nonnull + public FakePlayer getPlayer() + { + return player; + } +} diff --git a/src/main/java/dan200/computercraft/api/turtle/event/package-info.java b/src/main/java/dan200/computercraft/api/turtle/event/package-info.java new file mode 100644 index 000000000..a1459ab31 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/turtle/event/package-info.java @@ -0,0 +1,10 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only. + * For help using the API, and posting your mods, visit the forums at computercraft.info. + */ + +@API(owner = "ComputerCraft", provides = "ComputerCraft|API|Turtle|Event", apiVersion = "${version}") +package dan200.computercraft.api.turtle.event; + +import net.minecraftforge.fml.common.API; diff --git a/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java b/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java index 1d5b6083f..dcee1f488 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java +++ b/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java @@ -11,11 +11,14 @@ import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleCommand; import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.api.turtle.event.TurtleAction; +import dan200.computercraft.api.turtle.event.TurtleActionEvent; import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.ILuaAPI; import dan200.computercraft.shared.turtle.core.*; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; import javax.annotation.Nonnull; import java.util.HashMap; @@ -439,6 +442,13 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O table.put( "name", name ); table.put( "damage", damage ); table.put( "count", count ); + + TurtleActionEvent event = new TurtleActionEvent( m_turtle, TurtleAction.INSPECT_ITEM ); + if( MinecraftForge.EVENT_BUS.post( event ) ) + { + return new Object[] { false, event.getFailureMessage() }; + } + return new Object[] { table }; } else diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java index e2f259e31..d89d2d05c 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java @@ -10,12 +10,14 @@ import dan200.computercraft.api.turtle.ITurtleCommand; import dan200.computercraft.api.turtle.TurtleAnimation; import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleInventoryEvent; import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.WorldUtil; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.items.IItemHandler; import javax.annotation.Nonnull; @@ -59,6 +61,16 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) EnumFacing side = direction.getOpposite(); IItemHandler inventory = InventoryUtil.getInventory( world, newPosition, side ); + + // Fire the event, restoring the inventory and exiting if it is cancelled. + TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); + TurtleInventoryEvent.Drop event = new TurtleInventoryEvent.Drop( turtle, player, world, newPosition, inventory, stack ); + if( MinecraftForge.EVENT_BUS.post( event ) ) + { + InventoryUtil.storeItems( stack, turtle.getItemHandler(), turtle.getSelectedSlot() ); + return TurtleCommandResult.failure( event.getFailureMessage() ); + } + if( inventory != null ) { // Drop the item into the inventory diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java index ed4ac9f76..c257eb83b 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java @@ -8,11 +8,14 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.turtle.*; +import dan200.computercraft.api.turtle.event.TurtleAction; +import dan200.computercraft.api.turtle.event.TurtleActionEvent; import dan200.computercraft.shared.proxy.CCTurtleProxyCommon; import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.WorldUtil; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.items.IItemHandler; import javax.annotation.Nonnull; @@ -63,6 +66,12 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) oldUpgradeStack = null; } + TurtleActionEvent event = new TurtleActionEvent( turtle, TurtleAction.EQUIP ); + if( MinecraftForge.EVENT_BUS.post( event ) ) + { + return TurtleCommandResult.failure( event.getFailureMessage() ); + } + // Do the swapping: if( newUpgradeStack != null ) { diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java index b947da7c2..2c62abca7 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java @@ -10,13 +10,15 @@ import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleCommand; import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleBlockEvent; import dan200.computercraft.shared.util.WorldUtil; import net.minecraft.block.Block; import net.minecraft.block.properties.IProperty; import net.minecraft.block.state.IBlockState; -import net.minecraft.util.math.BlockPos; import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; import javax.annotation.Nonnull; import java.util.HashMap; @@ -46,14 +48,14 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) if( WorldUtil.isBlockInWorld( world, newPosition ) ) { - if( !FAIL_ON_AIR || !world.isAirBlock( newPosition ) ) + IBlockState state = world.getBlockState( newPosition ); + if( !FAIL_ON_AIR || !state.getBlock().isAir( state, world, newPosition ) ) { - IBlockState state = world.getBlockState( newPosition ); Block block = state.getBlock(); String name = Block.REGISTRY.getNameForObject( block ).toString(); int metadata = block.getMetaFromState( state ); - Map table = new HashMap<>(); + Map table = new HashMap<>(); table.put( "name", name ); table.put( "metadata", metadata ); @@ -73,7 +75,15 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) } table.put( "state", stateTable ); - return TurtleCommandResult.success( new Object[]{ table } ); + // Fire the event, exiting if it is cancelled + TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); + TurtleBlockEvent.Inspect event = new TurtleBlockEvent.Inspect( turtle, turtlePlayer, world, newPosition, state, table ); + if( MinecraftForge.EVENT_BUS.post( event ) ) + { + return TurtleCommandResult.failure( event.getFailureMessage() ); + } + + return TurtleCommandResult.success( new Object[] { table } ); } } @@ -83,7 +93,7 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) table.put( "name", "minecraft:air" ); table.put( "metadata", 0 ); table.put( "state", new HashMap<>() ); - return TurtleCommandResult.success( new Object[]{ table } ); + return TurtleCommandResult.success( new Object[] { table } ); } return TurtleCommandResult.failure( "No block to inspect" ); } diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java index cadc8b73e..99072163a 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java @@ -11,13 +11,15 @@ import dan200.computercraft.api.turtle.ITurtleCommand; import dan200.computercraft.api.turtle.TurtleAnimation; import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleBlockEvent; import dan200.computercraft.shared.util.WorldUtil; import net.minecraft.block.Block; import net.minecraft.entity.Entity; +import net.minecraft.util.EnumFacing; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.EnumFacing; import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; import javax.annotation.Nonnull; import java.util.List; @@ -103,6 +105,12 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) } } + TurtleBlockEvent.Move moveEvent = new TurtleBlockEvent.Move( turtle, turtlePlayer, oldWorld, newPosition ); + if( MinecraftForge.EVENT_BUS.post( moveEvent ) ) + { + return TurtleCommandResult.failure( moveEvent.getFailureMessage() ); + } + // Check fuel level if( turtle.isFuelNeeded() && turtle.getFuelLevel() < 1 ) { diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java index 26b1a4780..7ceb9b5b8 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java @@ -11,6 +11,7 @@ import dan200.computercraft.api.turtle.ITurtleCommand; import dan200.computercraft.api.turtle.TurtleAnimation; import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleBlockEvent; import dan200.computercraft.shared.util.DirectionUtil; import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.WorldUtil; @@ -31,6 +32,7 @@ import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.fml.common.eventhandler.Event; import org.apache.commons.lang3.tuple.Pair; @@ -64,6 +66,16 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) World world = turtle.getWorld(); BlockPos coordinates = WorldUtil.moveCoords( turtle.getPosition(), direction ); + // Create a fake player, and orient it appropriately + BlockPos playerPosition = WorldUtil.moveCoords( turtle.getPosition(), direction ); + TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction ); + + TurtleBlockEvent.Place place = new TurtleBlockEvent.Place( turtle, turtlePlayer, turtle.getWorld(), coordinates, stack ); + if( MinecraftForge.EVENT_BUS.post( place ) ) + { + return TurtleCommandResult.failure( place.getFailureMessage() ); + } + IBlockState previousState; if( WorldUtil.isBlockInWorld( world, coordinates ) ) { @@ -75,8 +87,8 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) } // Do the deploying - String[] errorMessage = new String[1]; - ItemStack remainder = deploy( stack, turtle, direction, m_extraArguments, errorMessage ); + String[] errorMessage = new String[ 1 ]; + ItemStack remainder = deploy( stack, turtle, turtlePlayer, direction, m_extraArguments, errorMessage ); if( remainder != stack ) { // Put the remaining items back @@ -117,6 +129,11 @@ public static ItemStack deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, BlockPos playerPosition = WorldUtil.moveCoords( turtle.getPosition(), direction ); TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction ); + return deploy( stack, turtle, turtlePlayer, direction, extraArguments, o_errorMessage ); + } + + public static ItemStack deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, EnumFacing direction, Object[] extraArguments, String[] o_errorMessage ) + { // Deploy on an entity ItemStack remainder = deployOnEntity( stack, turtle, turtlePlayer, direction, extraArguments, o_errorMessage ); if( remainder != stack ) diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java index 0e4fd84eb..d69144a03 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java @@ -10,9 +10,12 @@ import dan200.computercraft.api.turtle.ITurtleCommand; import dan200.computercraft.api.turtle.TurtleAnimation; import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleAction; +import dan200.computercraft.api.turtle.event.TurtleActionEvent; import dan200.computercraft.shared.util.InventoryUtil; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntityFurnace; +import net.minecraftforge.common.MinecraftForge; import javax.annotation.Nonnull; @@ -71,6 +74,12 @@ private TurtleCommandResult refuel( ITurtleAccess turtle, @Nonnull ItemStack sta return TurtleCommandResult.failure( "Items not combustible" ); } + TurtleActionEvent event = new TurtleActionEvent( turtle, TurtleAction.REFUEL ); + if( MinecraftForge.EVENT_BUS.post( event ) ) + { + return TurtleCommandResult.failure( event.getFailureMessage() ); + } + if( !testOnly ) { // Determine fuel to give and replacement item to leave behind diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java index a9db1f9b0..a7ed73df3 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java @@ -10,6 +10,7 @@ import dan200.computercraft.api.turtle.ITurtleCommand; import dan200.computercraft.api.turtle.TurtleAnimation; import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleInventoryEvent; import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.WorldUtil; import net.minecraft.entity.Entity; @@ -19,6 +20,7 @@ import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.items.IItemHandler; import javax.annotation.Nonnull; @@ -56,6 +58,15 @@ public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) EnumFacing side = direction.getOpposite(); IItemHandler inventory = InventoryUtil.getInventory( world, newPosition, side ); + + // Fire the event, exiting if it is cancelled. + TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); + TurtleInventoryEvent.Suck event = new TurtleInventoryEvent.Suck( turtle, player, world, newPosition, inventory ); + if( MinecraftForge.EVENT_BUS.post( event ) ) + { + return TurtleCommandResult.failure( event.getFailureMessage() ); + } + if( inventory != null ) { // Take from inventory of thing in front diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleTurnCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleTurnCommand.java index 5b253d7d0..3a50d4b0f 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleTurnCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleTurnCommand.java @@ -10,7 +10,10 @@ import dan200.computercraft.api.turtle.ITurtleCommand; import dan200.computercraft.api.turtle.TurtleAnimation; import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleAction; +import dan200.computercraft.api.turtle.event.TurtleActionEvent; import dan200.computercraft.shared.util.DirectionUtil; +import net.minecraftforge.common.MinecraftForge; import javax.annotation.Nonnull; @@ -27,6 +30,12 @@ public TurtleTurnCommand( TurnDirection direction ) @Override public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) { + TurtleActionEvent event = new TurtleActionEvent( turtle, TurtleAction.TURN ); + if( MinecraftForge.EVENT_BUS.post( event ) ) + { + return TurtleCommandResult.failure( event.getFailureMessage() ); + } + switch( m_direction ) { case Left: diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java index 4f8738f54..cb7fdba4c 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java @@ -9,6 +9,8 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.turtle.*; +import dan200.computercraft.api.turtle.event.TurtleAttackEvent; +import dan200.computercraft.api.turtle.event.TurtleBlockEvent; import dan200.computercraft.shared.turtle.core.TurtleBrain; import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand; import dan200.computercraft.shared.turtle.core.TurtlePlayer; @@ -128,11 +130,11 @@ public TurtleCommandResult useTool( @Nonnull ITurtleAccess turtle, @Nonnull Turt { case Attack: { - return attack( turtle, direction ); + return attack( turtle, direction, side ); } case Dig: { - return dig( turtle, direction ); + return dig( turtle, direction, side ); } default: { @@ -161,7 +163,7 @@ protected float getDamageMultiplier() return 3.0f; } - private TurtleCommandResult attack( final ITurtleAccess turtle, EnumFacing direction ) + private TurtleCommandResult attack( final ITurtleAccess turtle, EnumFacing direction, TurtleSide side ) { // Create a fake player, and orient it appropriately final World world = turtle.getWorld(); @@ -178,8 +180,21 @@ private TurtleCommandResult attack( final ITurtleAccess turtle, EnumFacing direc ItemStack stackCopy = m_item.copy(); turtlePlayer.loadInventory( stackCopy ); - // Start claiming entity drops Entity hitEntity = hit.getKey(); + + // Fire several events to ensure we have permissions. + if( MinecraftForge.EVENT_BUS.post( new AttackEntityEvent( turtlePlayer, hitEntity ) ) || !hitEntity.canBeAttackedWithItem() ) + { + return TurtleCommandResult.failure( "Nothing to attack here" ); + } + + TurtleAttackEvent attackEvent = new TurtleAttackEvent( turtle, turtlePlayer, hitEntity, this, side ); + if( MinecraftForge.EVENT_BUS.post( attackEvent ) ) + { + return TurtleCommandResult.failure( attackEvent.getFailureMessage() ); + } + + // Start claiming entity drops ComputerCraft.setEntityDropConsumer( hitEntity, ( entity, drop ) -> { ItemStack remainder = InventoryUtil.storeItems( drop, turtle.getItemHandler(), turtle.getSelectedSlot() ); @@ -191,8 +206,7 @@ private TurtleCommandResult attack( final ITurtleAccess turtle, EnumFacing direc // Attack the entity boolean attacked = false; - if( hitEntity.canBeAttackedWithItem() && !hitEntity.hitByEntity( turtlePlayer ) - && !MinecraftForge.EVENT_BUS.post( new AttackEntityEvent( turtlePlayer, hitEntity ) ) ) + if( !hitEntity.hitByEntity( turtlePlayer ) ) { float damage = (float)turtlePlayer.getEntityAttribute( SharedMonsterAttributes.ATTACK_DAMAGE ).getAttributeValue(); damage *= getDamageMultiplier(); @@ -233,7 +247,7 @@ private TurtleCommandResult attack( final ITurtleAccess turtle, EnumFacing direc return TurtleCommandResult.failure( "Nothing to attack here" ); } - private TurtleCommandResult dig( ITurtleAccess turtle, EnumFacing direction ) + private TurtleCommandResult dig( ITurtleAccess turtle, EnumFacing direction, TurtleSide side ) { // Get ready to dig World world = turtle.getWorld(); @@ -266,6 +280,13 @@ private TurtleCommandResult dig( ITurtleAccess turtle, EnumFacing direction ) return TurtleCommandResult.failure( "Unbreakable block detected" ); } + // Fire the dig event, checking whether it was cancelled. + TurtleBlockEvent.Dig digEvent = new TurtleBlockEvent.Dig( turtle, turtlePlayer, world, newPosition, world.getBlockState( newPosition ), this, side ); + if( MinecraftForge.EVENT_BUS.post( digEvent ) ) + { + return TurtleCommandResult.failure( digEvent.getFailureMessage() ); + } + // Consume the items the block drops if( canHarvestBlock( world, newPosition ) ) {