From f2798786c02c64d678c8da91b81c5358065f1b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Tue, 6 May 2014 21:07:55 +0200 Subject: [PATCH] Started to modularize robots, making experience an upgrade and introducing a bunch of events. --- .../cil/oc/api/event/RobotAnalyzeEvent.java | 21 +++ .../oc/api/event/RobotAttackEntityEvent.java | 38 +++++ .../oc/api/event/RobotBreakBlockEvent.java | 79 +++++++++ .../java/li/cil/oc/api/event/RobotEvent.java | 18 +++ .../oc/api/event/RobotExhaustionEvent.java | 19 +++ .../li/cil/oc/api/event/RobotMoveEvent.java | 38 +++++ .../oc/api/event/RobotPlaceBlockEvent.java | 53 ++++++ .../li/cil/oc/api/event/RobotRenderEvent.java | 51 ++++++ .../li/cil/oc/api/event/RobotUsedTool.java | 70 ++++++++ .../java/li/cil/oc/api/machine/Robot.java | 69 +++++++- .../opencomputers/recipes/default.recipes | 5 + .../textures/items/upgrade_experience.png | Bin 0 -> 325 bytes .../textures/items/upgrade_inventory.png | Bin 0 -> 483 bytes src/main/scala/li/cil/oc/Items.scala | 1 + .../li/cil/oc/client/PacketHandler.scala | 9 -- .../renderer/tileentity/RobotRenderer.scala | 152 ++++++++++++------ .../scala/li/cil/oc/common/PacketType.scala | 1 - src/main/scala/li/cil/oc/common/Proxy.scala | 8 + .../li/cil/oc/common/block/RobotProxy.scala | 2 +- .../event/ExperienceUpgradeHandler.scala | 109 +++++++++++++ .../oc/common/event/RobotCommonHandler.scala | 18 +++ .../UniversalElectricityToolHandler.scala | 19 +++ .../oc/common/item/UpgradeExperience.scala | 25 +++ .../li/cil/oc/common/tileentity/Robot.scala | 73 +++------ .../cil/oc/common/tileentity/RobotProxy.scala | 15 +- .../scala/li/cil/oc/server/PacketSender.scala | 9 -- .../server/component/UpgradeExperience.scala | 58 +++++++ .../oc/server/component/robot/Player.scala | 66 ++++---- .../cil/oc/server/component/robot/Robot.scala | 12 +- .../driver/item/UpgradeExperience.scala | 20 +++ 30 files changed, 884 insertions(+), 174 deletions(-) create mode 100644 src/main/java/li/cil/oc/api/event/RobotAnalyzeEvent.java create mode 100644 src/main/java/li/cil/oc/api/event/RobotAttackEntityEvent.java create mode 100644 src/main/java/li/cil/oc/api/event/RobotBreakBlockEvent.java create mode 100644 src/main/java/li/cil/oc/api/event/RobotEvent.java create mode 100644 src/main/java/li/cil/oc/api/event/RobotExhaustionEvent.java create mode 100644 src/main/java/li/cil/oc/api/event/RobotMoveEvent.java create mode 100644 src/main/java/li/cil/oc/api/event/RobotPlaceBlockEvent.java create mode 100644 src/main/java/li/cil/oc/api/event/RobotRenderEvent.java create mode 100644 src/main/java/li/cil/oc/api/event/RobotUsedTool.java create mode 100644 src/main/resources/assets/opencomputers/textures/items/upgrade_experience.png create mode 100644 src/main/resources/assets/opencomputers/textures/items/upgrade_inventory.png create mode 100644 src/main/scala/li/cil/oc/common/event/ExperienceUpgradeHandler.scala create mode 100644 src/main/scala/li/cil/oc/common/event/RobotCommonHandler.scala create mode 100644 src/main/scala/li/cil/oc/common/event/UniversalElectricityToolHandler.scala create mode 100644 src/main/scala/li/cil/oc/common/item/UpgradeExperience.scala create mode 100644 src/main/scala/li/cil/oc/server/component/UpgradeExperience.scala create mode 100644 src/main/scala/li/cil/oc/server/driver/item/UpgradeExperience.scala diff --git a/src/main/java/li/cil/oc/api/event/RobotAnalyzeEvent.java b/src/main/java/li/cil/oc/api/event/RobotAnalyzeEvent.java new file mode 100644 index 000000000..61aade1ad --- /dev/null +++ b/src/main/java/li/cil/oc/api/event/RobotAnalyzeEvent.java @@ -0,0 +1,21 @@ +package li.cil.oc.api.event; + +import li.cil.oc.api.machine.Robot; +import net.minecraft.entity.player.EntityPlayer; + +/** + * Fired when an analyzer is used on a robot. + *

+ * Use this to echo additional information for custom components. + */ +public class RobotAnalyzeEvent extends RobotEvent { + /** + * The player that used the analyzer. + */ + public final EntityPlayer player; + + public RobotAnalyzeEvent(Robot robot, EntityPlayer player) { + super(robot); + this.player = player; + } +} diff --git a/src/main/java/li/cil/oc/api/event/RobotAttackEntityEvent.java b/src/main/java/li/cil/oc/api/event/RobotAttackEntityEvent.java new file mode 100644 index 000000000..6fbb89483 --- /dev/null +++ b/src/main/java/li/cil/oc/api/event/RobotAttackEntityEvent.java @@ -0,0 +1,38 @@ +package li.cil.oc.api.event; + +import li.cil.oc.api.machine.Robot; +import net.minecraft.entity.Entity; +import net.minecraftforge.event.Cancelable; + +public class RobotAttackEntityEvent extends RobotEvent { + /** + * The entity that the robot will attack. + */ + public final Entity target; + + protected RobotAttackEntityEvent(Robot robot, Entity target) { + super(robot); + this.target = target; + } + + /** + * Fired when a robot is about to attack an entity. + *

+ * Canceling this event will prevent the attack. + */ + @Cancelable + public static class Pre extends RobotAttackEntityEvent { + public Pre(Robot robot, Entity target) { + super(robot, target); + } + } + + /** + * Fired after a robot has attacked an entity. + */ + public static class Post extends RobotAttackEntityEvent { + public Post(Robot robot, Entity target) { + super(robot, target); + } + } +} diff --git a/src/main/java/li/cil/oc/api/event/RobotBreakBlockEvent.java b/src/main/java/li/cil/oc/api/event/RobotBreakBlockEvent.java new file mode 100644 index 000000000..c50c0c58c --- /dev/null +++ b/src/main/java/li/cil/oc/api/event/RobotBreakBlockEvent.java @@ -0,0 +1,79 @@ +package li.cil.oc.api.event; + +import li.cil.oc.api.machine.Robot; +import net.minecraft.world.World; +import net.minecraftforge.event.Cancelable; + +public abstract class RobotBreakBlockEvent extends RobotEvent { + protected RobotBreakBlockEvent(Robot robot) { + super(robot); + } + + /** + * Fired when a robot is about to break a block. + *

+ * Canceling this event will prevent the block from getting broken. + */ + @Cancelable + public static class Pre extends RobotBreakBlockEvent { + /** + * The world in which the block will be broken. + */ + public final World world; + + /** + * The coordinates at which the block will be broken. + */ + public final int x, y, z; + + /** + * The time it takes to break the block. + */ + private double breakTime; + + public Pre(Robot robot, World world, int x, int y, int z, double breakTime) { + super(robot); + this.world = world; + this.x = x; + this.y = y; + this.z = z; + this.breakTime = breakTime; + } + + /** + * Sets the time it should take the robot to break the block. + *

+ * Note that the robot will still break the block instantly, but the + * robot's execution is paused for the specified amount of time. + * + * @param breakTime the time in seconds the break operation takes. + */ + public void setBreakTime(double breakTime) { + this.breakTime = Math.max(0.05, breakTime); + } + + /** + * Gets the time that it will take to break the block. + * + * @see #setBreakTime(double) + */ + public double getBreakTime() { + return breakTime; + } + } + + /** + * Fired after a robot broke a block. + */ + public static class Post extends RobotBreakBlockEvent { + /** + * The amount of experience the block that was broken generated (e.g. certain ores). + */ + public final double experience; + + public Post(Robot robot, double experience) { + super(robot); + this.experience = experience; + } + } +} diff --git a/src/main/java/li/cil/oc/api/event/RobotEvent.java b/src/main/java/li/cil/oc/api/event/RobotEvent.java new file mode 100644 index 000000000..6e18f9af6 --- /dev/null +++ b/src/main/java/li/cil/oc/api/event/RobotEvent.java @@ -0,0 +1,18 @@ +package li.cil.oc.api.event; + +import li.cil.oc.api.machine.Robot; +import net.minecraftforge.event.Event; + +/** + * Base class for events generated by robots. + */ +public abstract class RobotEvent extends Event { + /** + * The robot for which this event was fired. + */ + public final Robot robot; + + protected RobotEvent(Robot robot) { + this.robot = robot; + } +} diff --git a/src/main/java/li/cil/oc/api/event/RobotExhaustionEvent.java b/src/main/java/li/cil/oc/api/event/RobotExhaustionEvent.java new file mode 100644 index 000000000..99a03b7ee --- /dev/null +++ b/src/main/java/li/cil/oc/api/event/RobotExhaustionEvent.java @@ -0,0 +1,19 @@ +package li.cil.oc.api.event; + +import li.cil.oc.api.machine.Robot; + +/** + * Fired when a robot performed an action that would cause exhaustion for a + * player. Used for the experience upgrade, for example. + */ +public class RobotExhaustionEvent extends RobotEvent { + /** + * The amount of exhaustion that was generated. + */ + public final double exhaustion; + + public RobotExhaustionEvent(Robot robot, double exhaustion) { + super(robot); + this.exhaustion = exhaustion; + } +} diff --git a/src/main/java/li/cil/oc/api/event/RobotMoveEvent.java b/src/main/java/li/cil/oc/api/event/RobotMoveEvent.java new file mode 100644 index 000000000..216f9d50a --- /dev/null +++ b/src/main/java/li/cil/oc/api/event/RobotMoveEvent.java @@ -0,0 +1,38 @@ +package li.cil.oc.api.event; + +import li.cil.oc.api.machine.Robot; +import net.minecraftforge.common.ForgeDirection; +import net.minecraftforge.event.Cancelable; + +public abstract class RobotMoveEvent extends RobotEvent { + /** + * The direction in which the robot will be moving. + */ + public final ForgeDirection direction; + + protected RobotMoveEvent(Robot robot, ForgeDirection direction) { + super(robot); + this.direction = direction; + } + + /** + * Fired when a robot is about to move. + *

+ * Canceling the event will prevent the robot from moving. + */ + @Cancelable + public static class Pre extends RobotMoveEvent { + public Pre(Robot robot, ForgeDirection direction) { + super(robot, direction); + } + } + + /** + * Fired after a robot moved. + */ + public static class Post extends RobotMoveEvent { + public Post(Robot robot, ForgeDirection direction) { + super(robot, direction); + } + } +} diff --git a/src/main/java/li/cil/oc/api/event/RobotPlaceBlockEvent.java b/src/main/java/li/cil/oc/api/event/RobotPlaceBlockEvent.java new file mode 100644 index 000000000..b8fbfe1e1 --- /dev/null +++ b/src/main/java/li/cil/oc/api/event/RobotPlaceBlockEvent.java @@ -0,0 +1,53 @@ +package li.cil.oc.api.event; + +import li.cil.oc.api.machine.Robot; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import net.minecraftforge.event.Cancelable; + +public abstract class RobotPlaceBlockEvent extends RobotEvent { + /** + * The item that is used to place the block. + */ + public final ItemStack stack; + + /** + * The world in which the block will be placed. + */ + public final World world; + + /** + * The coordinates at which the block will be placed. + */ + public final int x, y, z; + + protected RobotPlaceBlockEvent(Robot robot, ItemStack stack, World world, int x, int y, int z) { + super(robot); + this.stack = stack; + this.world = world; + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Fired when a robot is about to place a block. + *

+ * Canceling this event will prevent the block from being placed. + */ + @Cancelable + public static class Pre extends RobotPlaceBlockEvent { + public Pre(Robot robot, ItemStack stack, World world, int x, int y, int z) { + super(robot, stack, world, x, y, z); + } + } + + /** + * Fired after a robot placed a block. + */ + public static class Post extends RobotPlaceBlockEvent { + public Post(Robot robot, ItemStack stack, World world, int x, int y, int z) { + super(robot, stack, world, x, y, z); + } + } +} diff --git a/src/main/java/li/cil/oc/api/event/RobotRenderEvent.java b/src/main/java/li/cil/oc/api/event/RobotRenderEvent.java new file mode 100644 index 000000000..bc90b6ac3 --- /dev/null +++ b/src/main/java/li/cil/oc/api/event/RobotRenderEvent.java @@ -0,0 +1,51 @@ +package li.cil.oc.api.event; + +import li.cil.oc.api.machine.Robot; +import net.minecraft.util.Vec3; +import net.minecraftforge.event.Cancelable; + +/** + * Fired directly before the robot's chassis is rendered. + *

+ * If this event is canceled, the chassis will not be rendered. + * Component items' item renderes will still be invoked, at the possibly + * modified mount points. + *

+ * Important: the robot instance may be null in this event, in + * case the render pass is for rendering the robot in an inventory. + */ +@Cancelable +public class RobotRenderEvent extends RobotEvent { + /** + * Points on the robot at which component models may be rendered. + *

+ * By convention, components should be rendered in order of their slots, + * meaning that some components may not be rendered at all, if there are + * not enough mount points. + *

+ * The equipped tool is rendered at a fixed position, this list does not + * contain a mount point for it. + */ + public final MountPoint[] mountPoints; + + public RobotRenderEvent(Robot robot, MountPoint[] mountPoints) { + super(robot); + this.mountPoints = mountPoints; + } + + /** + * Describes points on the robot model at which components are "mounted", + * i.e. where component models may be rendered. + */ + public static class MountPoint { + /** + * The position of the mount point, relative to the robot's center. + */ + public final Vec3 offset = Vec3.createVectorHelper(0, 0, 0); + + /** + * The vector the mount point is facing. + */ + public final Vec3 normal = Vec3.createVectorHelper(0, 0, 0); + } +} diff --git a/src/main/java/li/cil/oc/api/event/RobotUsedTool.java b/src/main/java/li/cil/oc/api/event/RobotUsedTool.java new file mode 100644 index 000000000..a75ade6f2 --- /dev/null +++ b/src/main/java/li/cil/oc/api/event/RobotUsedTool.java @@ -0,0 +1,70 @@ +package li.cil.oc.api.event; + +import li.cil.oc.api.machine.Robot; +import net.minecraft.item.ItemStack; + +public class RobotUsedTool extends RobotEvent { + /** + * The tool that was used, before and after use. + */ + public final ItemStack toolBeforeUse, toolAfterUse; + + protected double damageRate; + + protected RobotUsedTool(Robot robot, ItemStack toolBeforeUse, ItemStack toolAfterUse, double damageRate) { + super(robot); + this.toolBeforeUse = toolBeforeUse; + this.toolAfterUse = toolAfterUse; + this.damageRate = damageRate; + } + + /** + * The rate at which the used tool should lose durability, where one means + * it loses durability at full speed, zero means it doesn't lose durability + * at all. + *

+ * This value is in an interval of [0, 1]. + */ + public double getDamageRate() { + return damageRate; + } + + /** + * Fired when a robot used a tool and is about to apply the damage rate to + * partially undo the durability loss. This step is used to compute the + * rate at which the tool should lose durability, which is used by the + * experience upgrade, for example. + */ + public static class ComputeDamageRate extends RobotUsedTool { + public ComputeDamageRate(Robot robot, ItemStack toolBeforeUse, ItemStack toolAfterUse, double damageRate) { + super(robot, toolBeforeUse, toolAfterUse, damageRate); + } + + /** + * Set the rate at which the tool actually gets damaged. + *

+ * This will be clamped to an iterval of [0, 1]. + * + * @param damageRate the new damage rate. + */ + public void setDamageRate(double damageRate) { + this.damageRate = Math.max(0, Math.min(1, damageRate)); + } + } + + /** + * Fired when a robot used a tool and the previously fired damage rate + * computation returned a value smaller than one. The callbacks of this + * method are responsible for applying the inverse damage the tool took. + * The toolAfterUse item stack represents the actual tool, any + * changes must be applied to that variable. The toolBeforeUse + * item stack is passed for reference, to compute the actual amount of + * durability that was lost. This may be required for tools where the + * durability is stored in the item's NBT tag. + */ + public static class ApplyDamageRate extends RobotUsedTool { + public ApplyDamageRate(Robot robot, ItemStack toolBeforeUse, ItemStack toolAfterUse, double damageRate) { + super(robot, toolBeforeUse, toolAfterUse, damageRate); + } + } +} diff --git a/src/main/java/li/cil/oc/api/machine/Robot.java b/src/main/java/li/cil/oc/api/machine/Robot.java index 956513357..613b340c7 100644 --- a/src/main/java/li/cil/oc/api/machine/Robot.java +++ b/src/main/java/li/cil/oc/api/machine/Robot.java @@ -1,7 +1,9 @@ package li.cil.oc.api.machine; import li.cil.oc.api.Rotatable; +import li.cil.oc.api.network.Environment; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; /** * This interface allows interaction with robots. @@ -9,6 +11,18 @@ import net.minecraft.entity.player.EntityPlayer; * It is intended to be used by components when installed in a robot. In that * case, the robot in question is the tile entity passed to item driver when * asked to create the component's environment. + *

+ * A robot's inventory contains component items and items in the actual + * inventory. The physical layout in the underlying 'real' inventory is as + * follows: + *

+ * Note that either of these intervals may be empty, depending on the parts + * the robot is built from. */ public interface Robot extends Rotatable { /** @@ -17,18 +31,59 @@ public interface Robot extends Rotatable { *

* This will automatically be positioned and rotated to represent the * robot's current position and rotation in the world. Use this to trigger - * events involving the robot that require a player entity, and for - * interacting with the robots' inventory. - *

- * Note that the inventory of each robot is structured such that the first - * four slots are the "equipment" slots, from left to right, i.e. slot one - * is the tool slot, slot two is the card slot, three the disk slot and - * slot four is for upgrades. The inventory proper starts after that. + * events involving the robot that require a player entity, and for more + * in-depth interaction with the robots' inventory. * * @return the fake player for the robot. */ EntityPlayer player(); + /** + * The number of hot-swappable component slots in this robot. + */ + int dynamicComponentCapacity(); + + /** + * The total number of component slots in this robot, including + * hot-swappable component slots. + */ + int componentCapacity(); + + /** + * The total inventory space in this robot, including tool and + * component slots. + */ + int inventorySize(); + + /** + * Get the item stack in the specified inventory slot. + *

+ * This operates on the underlying, real inventory, as described in the + * comment on top of this class. The starting index of the part of the + * inventory that is accessible to the robot for manipulation is at + * componentCapacity + 1. + *

+ * This will return null for empty slots. + * + * @param index the index of the slot from which to get the stack. + * @return the content of that slot, or null. + */ + ItemStack getStackInSlot(int index); + + /** + * Get the environment for the component in the specified slot. + *

+ * This operates on the underlying, real inventory, as described in the + * comment on top of this class. + *

+ * This will return null for slots that do not contain components, + * or components that do not have an environment (on the calling side). + * + * @param index the index of the slot from which to get the environment. + * @return the environment for that slot, or null. + */ + Environment getComponentInSlot(int index); + /** * Gets the index of the currently selected slot in the robot's inventory. * diff --git a/src/main/resources/assets/opencomputers/recipes/default.recipes b/src/main/resources/assets/opencomputers/recipes/default.recipes index adc119a86..e7944eb3b 100644 --- a/src/main/resources/assets/opencomputers/recipes/default.recipes +++ b/src/main/resources/assets/opencomputers/recipes/default.recipes @@ -123,6 +123,11 @@ craftingUpgrade { ["oc:circuitChip1", workbench, "oc:circuitChip1"] [ingotIron, "oc:materialCircuitBoardPrinted", ingotIron]] } +experienceUpgrade { + input: [[ingotIron, "", ingotIron] + ["oc:circuitChip1", expBottle, "oc:circuitChip1"] + [ingotIron, "oc:materialCircuitBoardPrinted", ingotIron]] +} generatorUpgrade { input: [[ingotIron, "", ingotIron] ["oc:circuitChip1", craftingPiston, "oc:circuitChip1"] diff --git a/src/main/resources/assets/opencomputers/textures/items/upgrade_experience.png b/src/main/resources/assets/opencomputers/textures/items/upgrade_experience.png new file mode 100644 index 0000000000000000000000000000000000000000..b605cb10029d3d31e1d53baecba89abef901d20d GIT binary patch literal 325 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!fon*g5>SI1@sHE)KnH4O3l8Pd+ADZ4Rv)kyl!V>t8wjCCPv z{XYixeg>mthM)zkdQl8B|IhTB%V3_zF!RjJ|NsAgm?BpWG)JT)$S)YkMF$N2{?7%0 z^7WoBjv*DdeEK|@4lD35^Ul!a&Rq2W|JF^xmtH(OD5cCGvYzAlW3{F8XEWN)n!M@y zECbG+tU7T13sYXwJ|-T9pMu<3t3Tzc1D(R)>FVdQ&MBb@ E0B?7Ld;kCd literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/opencomputers/textures/items/upgrade_inventory.png b/src/main/resources/assets/opencomputers/textures/items/upgrade_inventory.png new file mode 100644 index 0000000000000000000000000000000000000000..8b44cf7cb1b8e3d958613ab860e380e24e1019fc GIT binary patch literal 483 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8X#qYVu2v=neF?H@XVNMHC7o<6=H;rL`G3aS)p6$knW~C1 zQsN>7KH}XeS`~p38p?7yYKls7Qk{uvsV<_+tF5PI>&Ds(H#avdDA1}7mdx;y&GA!g zj+AbVlAfKZF!RjJWtHZOOAW&UeB2HA+M}gQLzHLa8#hNt_jGq;xQQ0}NE8(0E?h7# z%0{RyPSMXoBHUUiCOY!}|NkW?4gUdMXjBs97YyW*1{in)X1oXLUh3)M7*cU7_y?s!P`#C3*3(4_PH6w{pH9lj)M#h zhH6J*Ho6G928OD7ZZw|Iv$FiOv6rCQi<33-bJhthORZZiDbJ=~Z*TdWP0qLY%(n6+ z8}~DOSm*2_e)0Fg8OuC0Lr)ntc<0>R+juyp;P$4PR&ExPKHH_|N^2*^bJW}~oMSL0 e=v6-hGsFI$lKOv)d{lrAX7F_Nb6Mw<&;$UfJ onRobotEquippedUpgradeChange(p) case PacketType.RobotMove => onRobotMove(p) case PacketType.RobotSelectedSlotChange => onRobotSelectedSlotChange(p) - case PacketType.RobotXp => onRobotXp(p) case PacketType.RotatableState => onRotatableState(p) case PacketType.RouterActivity => onRouterActivity(p) case PacketType.TextBufferColorChange => onTextBufferColorChange(p) @@ -230,14 +229,6 @@ class PacketHandler extends CommonPacketHandler { case _ => // Invalid packet. } - def onRobotXp(p: PacketParser) = - p.readTileEntity[RobotProxy]() match { - case Some(t) => - t.robot.xp = p.readDouble() - t.robot.updateXpInfo() - case _ => // Invalid packet. - } - def onRotatableState(p: PacketParser) = p.readTileEntity[Rotatable]() match { case Some(t) => diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/RobotRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/RobotRenderer.scala index e68a19cc8..dfa45f78f 100644 --- a/src/main/scala/li/cil/oc/client/renderer/tileentity/RobotRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/RobotRenderer.scala @@ -2,6 +2,7 @@ package li.cil.oc.client.renderer.tileentity import com.google.common.base.Strings import java.util.logging.Level +import li.cil.oc.api.event.RobotRenderEvent import li.cil.oc.client.Textures import li.cil.oc.common.tileentity import li.cil.oc.util.RenderState @@ -17,12 +18,14 @@ import net.minecraftforge.client.IItemRenderer.ItemRendererHelper._ import net.minecraftforge.client.IItemRenderer.ItemRenderType import net.minecraftforge.client.IItemRenderer.ItemRenderType._ import net.minecraftforge.client.MinecraftForgeClient -import net.minecraftforge.common.ForgeDirection +import net.minecraftforge.common.{MinecraftForge, ForgeDirection} import org.lwjgl.opengl.{GL12, GL11} object RobotRenderer extends TileEntitySpecialRenderer { private val displayList = GLAllocation.generateDisplayLists(2) + private val mountPoints = Array.fill(7)(new RobotRenderEvent.MountPoint()) + private val gap = 1.0f / 28.0f private val gt = 0.5f + gap private val gb = 0.5f - gap @@ -104,7 +107,64 @@ object RobotRenderer extends TileEntitySpecialRenderer { compileList() - def renderChassis(isRunning: Boolean = false, level: Int = 0, offset: Double = 0) { + def resetMountPoints() { + // Back. + mountPoints(0).offset.xCoord = 0 + mountPoints(0).offset.yCoord = 0.33 + mountPoints(0).offset.zCoord = -0.33 + mountPoints(0).normal.xCoord = 0 + mountPoints(0).normal.yCoord = 0 + mountPoints(0).normal.zCoord = -1 + + mountPoints(0).offset.xCoord = 0 + mountPoints(0).offset.yCoord = -0.33 + mountPoints(0).offset.zCoord = -0.33 + mountPoints(0).normal.xCoord = 0 + mountPoints(0).normal.yCoord = 0 + mountPoints(0).normal.zCoord = -1 + + // Front. + mountPoints(0).offset.xCoord = 0 + mountPoints(0).offset.yCoord = -0.33 + mountPoints(0).offset.zCoord = 0.33 + mountPoints(0).normal.xCoord = 0 + mountPoints(0).normal.yCoord = 0 + mountPoints(0).normal.zCoord = 1 + + // Left. + mountPoints(0).offset.xCoord = -0.33 + mountPoints(0).offset.yCoord = 0.33 + mountPoints(0).offset.zCoord = 0 + mountPoints(0).normal.xCoord = -1 + mountPoints(0).normal.yCoord = 0 + mountPoints(0).normal.zCoord = 0 + + mountPoints(0).offset.xCoord = -0.33 + mountPoints(0).offset.yCoord = -0.33 + mountPoints(0).offset.zCoord = 0 + mountPoints(0).normal.xCoord = -1 + mountPoints(0).normal.yCoord = 0 + mountPoints(0).normal.zCoord = 0 + + // Right. + mountPoints(0).offset.xCoord = 0.33 + mountPoints(0).offset.yCoord = 0.33 + mountPoints(0).offset.zCoord = 0 + mountPoints(0).normal.xCoord = 1 + mountPoints(0).normal.yCoord = 0 + mountPoints(0).normal.zCoord = 0 + + mountPoints(0).offset.xCoord = 0.33 + mountPoints(0).offset.yCoord = -0.33 + mountPoints(0).offset.zCoord = 0 + mountPoints(0).normal.xCoord = 1 + mountPoints(0).normal.yCoord = 0 + mountPoints(0).normal.zCoord = 0 + } + + def renderChassis(robot: tileentity.Robot = null, offset: Double = 0) { + val isRunning = if (robot == null) false else robot.isRunning + val size = 0.3f val l = 0.5f - size val h = 0.5f + size @@ -118,57 +178,53 @@ object RobotRenderer extends TileEntitySpecialRenderer { (0.25f - vStep, 0.25f + vStep, 0.75f - vStep, 0.75f + vStep) } - bindTexture(Textures.blockRobot) - if (level > 19) { - GL11.glColor3f(0.4f, 1, 1) - } - else if (level > 9) { - GL11.glColor3f(1, 1, 0.4f) - } - else { - GL11.glColor3f(0.5f, 0.5f, 0.5f) - } - if (!isRunning) { - GL11.glTranslatef(0, -2 * gap, 0) - } - GL11.glCallList(displayList) - if (!isRunning) { - GL11.glTranslatef(0, 2 * gap, 0) - } - GL11.glCallList(displayList + 1) - GL11.glColor3f(1, 1, 1) + resetMountPoints() + val event = new RobotRenderEvent(robot, mountPoints) + MinecraftForge.EVENT_BUS.post(event) + if (!event.isCanceled) { + bindTexture(Textures.blockRobot) + if (!isRunning) { + GL11.glTranslatef(0, -2 * gap, 0) + } + GL11.glCallList(displayList) + if (!isRunning) { + GL11.glTranslatef(0, 2 * gap, 0) + } + GL11.glCallList(displayList + 1) + GL11.glColor3f(1, 1, 1) - if (MinecraftForgeClient.getRenderPass == 0) { - RenderState.disableLighting() - } + if (isRunning) { + if (MinecraftForgeClient.getRenderPass == 0) { + RenderState.disableLighting() + } - if (isRunning) { - val t = Tessellator.instance - t.startDrawingQuads() - t.addVertexWithUV(l, gt, l, u0, v0) - t.addVertexWithUV(l, gb, l, u0, v1) - t.addVertexWithUV(l, gb, h, u1, v1) - t.addVertexWithUV(l, gt, h, u1, v0) + val t = Tessellator.instance + t.startDrawingQuads() + t.addVertexWithUV(l, gt, l, u0, v0) + t.addVertexWithUV(l, gb, l, u0, v1) + t.addVertexWithUV(l, gb, h, u1, v1) + t.addVertexWithUV(l, gt, h, u1, v0) - t.addVertexWithUV(l, gt, h, u0, v0) - t.addVertexWithUV(l, gb, h, u0, v1) - t.addVertexWithUV(h, gb, h, u1, v1) - t.addVertexWithUV(h, gt, h, u1, v0) + t.addVertexWithUV(l, gt, h, u0, v0) + t.addVertexWithUV(l, gb, h, u0, v1) + t.addVertexWithUV(h, gb, h, u1, v1) + t.addVertexWithUV(h, gt, h, u1, v0) - t.addVertexWithUV(h, gt, h, u0, v0) - t.addVertexWithUV(h, gb, h, u0, v1) - t.addVertexWithUV(h, gb, l, u1, v1) - t.addVertexWithUV(h, gt, l, u1, v0) + t.addVertexWithUV(h, gt, h, u0, v0) + t.addVertexWithUV(h, gb, h, u0, v1) + t.addVertexWithUV(h, gb, l, u1, v1) + t.addVertexWithUV(h, gt, l, u1, v0) - t.addVertexWithUV(h, gt, l, u0, v0) - t.addVertexWithUV(h, gb, l, u0, v1) - t.addVertexWithUV(l, gb, l, u1, v1) - t.addVertexWithUV(l, gt, l, u1, v0) - t.draw() - } + t.addVertexWithUV(h, gt, l, u0, v0) + t.addVertexWithUV(h, gb, l, u0, v1) + t.addVertexWithUV(l, gb, l, u1, v1) + t.addVertexWithUV(l, gt, l, u1, v0) + t.draw() - if (MinecraftForgeClient.getRenderPass == 0) { - RenderState.enableLighting() + if (MinecraftForgeClient.getRenderPass == 0) { + RenderState.enableLighting() + } + } } } @@ -224,7 +280,7 @@ object RobotRenderer extends TileEntitySpecialRenderer { if (MinecraftForgeClient.getRenderPass == 0) { val offset = timeJitter + worldTime / 20.0 - renderChassis(robot.isRunning, robot.level, offset) + renderChassis(robot, offset) } robot.equippedItem match { diff --git a/src/main/scala/li/cil/oc/common/PacketType.scala b/src/main/scala/li/cil/oc/common/PacketType.scala index 05d4473cc..8ab519100 100644 --- a/src/main/scala/li/cil/oc/common/PacketType.scala +++ b/src/main/scala/li/cil/oc/common/PacketType.scala @@ -22,7 +22,6 @@ object PacketType extends Enumeration { RobotEquippedUpgradeChange, RobotMove, RobotSelectedSlotChange, - RobotXp, RotatableState, RouterActivity, TextBufferColorChange, diff --git a/src/main/scala/li/cil/oc/common/Proxy.scala b/src/main/scala/li/cil/oc/common/Proxy.scala index 3acd2df09..8160e9b47 100644 --- a/src/main/scala/li/cil/oc/common/Proxy.scala +++ b/src/main/scala/li/cil/oc/common/Proxy.scala @@ -21,6 +21,7 @@ import net.minecraft.item.{Item, ItemStack} import net.minecraft.block.Block import net.minecraftforge.oredict.OreDictionary import scala.collection.convert.WrapAsScala._ +import li.cil.oc.common.event.{ExperienceUpgradeHandler, UniversalElectricityToolHandler, RobotCommonHandler} class Proxy { def preInit(e: FMLPreInitializationEvent) { @@ -77,6 +78,7 @@ class Proxy { api.Driver.add(driver.item.RedstoneCard) api.Driver.add(driver.item.Screen) api.Driver.add(driver.item.UpgradeCrafting) + api.Driver.add(driver.item.UpgradeExperience) api.Driver.add(driver.item.UpgradeGenerator) api.Driver.add(driver.item.UpgradeNavigation) api.Driver.add(driver.item.UpgradeSign) @@ -94,6 +96,12 @@ class Proxy { api.Driver.add(driver.converter.FluidTankInfo) api.Driver.add(driver.converter.ItemStack) + MinecraftForge.EVENT_BUS.register(RobotCommonHandler) + MinecraftForge.EVENT_BUS.register(ExperienceUpgradeHandler) + if (Mods.UniversalElectricity.isAvailable) { + MinecraftForge.EVENT_BUS.register(UniversalElectricityToolHandler) + } + Recipes.init() GameRegistry.registerCraftingHandler(CraftingHandler) diff --git a/src/main/scala/li/cil/oc/common/block/RobotProxy.scala b/src/main/scala/li/cil/oc/common/block/RobotProxy.scala index 1cee19434..832be07c7 100644 --- a/src/main/scala/li/cil/oc/common/block/RobotProxy.scala +++ b/src/main/scala/li/cil/oc/common/block/RobotProxy.scala @@ -162,7 +162,7 @@ class RobotProxy(val parent: SpecialDelegator) extends RedstoneAware with Specia case proxy: tileentity.RobotProxy => val robot = proxy.robot if (robot.player == player) return false - if (!world.isRemote && (!player.capabilities.isCreativeMode || proxy.globalBuffer > 1 || proxy.robot.xp > 0)) { + if (!world.isRemote) { parent.dropBlockAsItem(world, x, y, z, robot.createItemStack()) } if (Blocks.blockSpecial.subBlock(world, robot.moveFromX, robot.moveFromY, robot.moveFromZ).exists(_ == Blocks.robotAfterimage)) { diff --git a/src/main/scala/li/cil/oc/common/event/ExperienceUpgradeHandler.scala b/src/main/scala/li/cil/oc/common/event/ExperienceUpgradeHandler.scala new file mode 100644 index 000000000..1b215c3cf --- /dev/null +++ b/src/main/scala/li/cil/oc/common/event/ExperienceUpgradeHandler.scala @@ -0,0 +1,109 @@ +package li.cil.oc.common.event + +import li.cil.oc.Settings +import li.cil.oc.api.event._ +import li.cil.oc.api.machine.Robot +import li.cil.oc.server.component +import net.minecraft.util.ChatMessageComponent +import net.minecraftforge.event.ForgeSubscribe +import org.lwjgl.opengl.GL11 + +object ExperienceUpgradeHandler { + @ForgeSubscribe + def onRobotAnalyze(e: RobotAnalyzeEvent) { + val (level, experience) = getLevelAndExperience(e.robot) + // This is basically a 'does it have an experience upgrade' check. + if (experience != 0.0) { + e.player.sendChatToPlayer(ChatMessageComponent.createFromTranslationWithSubstitutions( + Settings.namespace + "gui.Analyzer.RobotXp", "%.2f".format(experience), level: Integer)) + } + } + + @ForgeSubscribe + def onRobotComputeDamageRate(e: RobotUsedTool.ComputeDamageRate) { + e.setDamageRate(e.getDamageRate * math.max(0, 1 - getLevel(e.robot) * Settings.get.toolEfficiencyPerLevel)) + } + + @ForgeSubscribe + def onRobotBreakBlockPre(e: RobotBreakBlockEvent.Pre) { + val boost = math.max(0, 1 - getLevel(e.robot) * Settings.get.harvestSpeedBoostPerLevel) + e.setBreakTime(e.getBreakTime * boost) + } + + @ForgeSubscribe + def onRobotAttackEntityPost(e: RobotAttackEntityEvent.Post) { + if (e.robot.getComponentInSlot(e.robot.selectedSlot()) != null && e.target.isDead) { + addExperience(e.robot, Settings.get.robotActionXp) + } + } + + @ForgeSubscribe + def onRobotBreakBlockPost(e: RobotBreakBlockEvent.Post) { + addExperience(e.robot, e.experience * Settings.get.robotOreXpRate + Settings.get.robotActionXp) + } + + @ForgeSubscribe + def onRobotPlaceBlockPost(e: RobotPlaceBlockEvent.Post) { + addExperience(e.robot, Settings.get.robotActionXp) + } + + @ForgeSubscribe + def onRobotMovePost(e: RobotMoveEvent.Post) { + addExperience(e.robot, Settings.get.robotExhaustionXpRate * 0.01) + } + + @ForgeSubscribe + def onRobotExhaustion(e: RobotExhaustionEvent) { + addExperience(e.robot, Settings.get.robotExhaustionXpRate * e.exhaustion) + } + + @ForgeSubscribe + def onRobotRender(e: RobotRenderEvent) { + val level = if (e.robot != null) getLevel(e.robot) else 0 + if (level > 19) { + GL11.glColor3f(0.4f, 1, 1) + } + else if (level > 9) { + GL11.glColor3f(1, 1, 0.4f) + } + else { + GL11.glColor3f(0.5f, 0.5f, 0.5f) + } + } + + private def getLevel(robot: Robot) = { + var level = 0 + for (index <- 1 to robot.componentCapacity) { + robot.getComponentInSlot(index) match { + case upgrade: component.UpgradeExperience => + level += upgrade.level + case _ => + } + } + level + } + + private def getLevelAndExperience(robot: Robot) = { + var level = 0 + var experience = 0.0 + for (index <- 1 to robot.componentCapacity) { + robot.getComponentInSlot(index) match { + case upgrade: component.UpgradeExperience => + level += upgrade.level + experience += upgrade.experience + case _ => + } + } + (level, experience) + } + + private def addExperience(robot: Robot, amount: Double) { + for (index <- 1 to robot.componentCapacity) { + robot.getComponentInSlot(index) match { + case upgrade: component.UpgradeExperience => + upgrade.addExperience(amount) + case _ => + } + } + } +} diff --git a/src/main/scala/li/cil/oc/common/event/RobotCommonHandler.scala b/src/main/scala/li/cil/oc/common/event/RobotCommonHandler.scala new file mode 100644 index 000000000..5ec2b8a43 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/event/RobotCommonHandler.scala @@ -0,0 +1,18 @@ +package li.cil.oc.common.event + +import net.minecraftforge.event.ForgeSubscribe +import li.cil.oc.api.event.RobotUsedTool + +object RobotCommonHandler { + @ForgeSubscribe + def onRobotApplyDamageRate(e: RobotUsedTool.ApplyDamageRate) { + if (e.toolAfterUse.isItemStackDamageable) { + val damage = e.toolAfterUse.getItemDamage - e.toolBeforeUse.getItemDamage + if (damage > 0) { + val actualDamage = damage * e.getDamageRate + val repairedDamage = if (e.robot.player.getRNG.nextDouble() > 0.5) damage - math.floor(actualDamage).toInt else damage - math.ceil(actualDamage).toInt + e.toolAfterUse.setItemDamage(e.toolAfterUse.getItemDamage - repairedDamage) + } + } + } +} diff --git a/src/main/scala/li/cil/oc/common/event/UniversalElectricityToolHandler.scala b/src/main/scala/li/cil/oc/common/event/UniversalElectricityToolHandler.scala new file mode 100644 index 000000000..0dd510f2f --- /dev/null +++ b/src/main/scala/li/cil/oc/common/event/UniversalElectricityToolHandler.scala @@ -0,0 +1,19 @@ +package li.cil.oc.common.event + +import net.minecraftforge.event.ForgeSubscribe +import li.cil.oc.api.event.RobotUsedTool +import li.cil.oc.util.mods.UniversalElectricity + +object UniversalElectricityToolHandler { + @ForgeSubscribe + def onRobotApplyDamageRate(e: RobotUsedTool.ApplyDamageRate) { + if (UniversalElectricity.isEnergyItem(e.toolAfterUse)) { + val damage = UniversalElectricity.getEnergyInItem(e.toolBeforeUse) - UniversalElectricity.getEnergyInItem(e.toolAfterUse) + if (damage > 0) { + val actualDamage = damage * e.getDamageRate + val repairedDamage = if (e.robot.player.getRNG.nextDouble() > 0.5) damage - math.floor(actualDamage).toLong else damage - math.ceil(actualDamage).toLong + UniversalElectricity.chargeItem(e.toolAfterUse, repairedDamage) + } + } + } +} diff --git a/src/main/scala/li/cil/oc/common/item/UpgradeExperience.scala b/src/main/scala/li/cil/oc/common/item/UpgradeExperience.scala new file mode 100644 index 000000000..86ae06c08 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/item/UpgradeExperience.scala @@ -0,0 +1,25 @@ +package li.cil.oc.common.item + +import java.util +import li.cil.oc.Settings +import li.cil.oc.util.Tooltip +import net.minecraft.client.renderer.texture.IconRegister +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.item.{ItemStack, EnumRarity} + +class UpgradeExperience(val parent: Delegator) extends Delegate { + val unlocalizedName = "UpgradeExperience" + + override def rarity = EnumRarity.epic + + override def tooltipLines(stack: ItemStack, player: EntityPlayer, tooltip: util.List[String], advanced: Boolean) { + tooltip.addAll(Tooltip.get(unlocalizedName)) + super.tooltipLines(stack, player, tooltip, advanced) + } + + override def registerIcons(iconRegister: IconRegister) = { + super.registerIcons(iconRegister) + + icon = iconRegister.registerIcon(Settings.resourceDomain + ":upgrade_experience") + } +} diff --git a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala index e9a7bdc45..2bd2db7a5 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala @@ -5,6 +5,7 @@ import java.util.logging.Level import li.cil.oc._ import li.cil.oc.api.Driver import li.cil.oc.api.driver.Slot +import li.cil.oc.api.event.{RobotAnalyzeEvent, RobotMoveEvent} import li.cil.oc.api.network._ import li.cil.oc.client.gui import li.cil.oc.common.block.Delegator @@ -18,7 +19,7 @@ import net.minecraft.inventory.ISidedInventory import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound import net.minecraft.util.ChatMessageComponent -import net.minecraftforge.common.ForgeDirection +import net.minecraftforge.common.{MinecraftForge, ForgeDirection} import net.minecraftforge.fluids.{BlockFluidBase, FluidRegistry} import scala.io.Source @@ -39,8 +40,13 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe // ----------------------------------------------------------------------- // - // Note: we implement IRobotContext in the TE to allow external components - //to cast their owner to it (to allow interacting with their owning robot). + override def dynamicComponentCapacity = 3 + + override def componentCapacity = 3 + + override def inventorySize = getSizeInventory + + override def getComponentInSlot(index: Int) = components(index).orNull var selectedSlot = actualSlot(0) @@ -92,16 +98,6 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe var tag: NBTTagCompound = _ - var xp = 0.0 - - def xpForNextLevel = xpForLevel(level + 1) - - def xpForLevel(level: Int) = Settings.get.baseXpToLevel + Math.pow(level * Settings.get.constantXpGrowth, Settings.get.exponentialXpGrowth) - - var level = 0 - - var xpChanged = false - var globalBuffer, globalBufferSize = 0.0 var equippedItem: Option[ItemStack] = None @@ -120,25 +116,6 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe private lazy val player_ = new robot.Player(this) - def addXp(value: Double) { - if (level < 30 && isServer) { - xp = xp + value - xpChanged = true - if (xp >= xpForNextLevel) { - updateXpInfo() - } - } - } - - def updateXpInfo() { - // xp(level) = base + (level * const) ^ exp - // pow(xp(level) - base, 1/exp) / const = level - level = math.min((Math.pow(xp - Settings.get.baseXpToLevel, 1 / Settings.get.exponentialXpGrowth) / Settings.get.constantXpGrowth).toInt, 30) - if (isServer) { - bot.node.setLocalBufferSize(Settings.get.bufferRobot + Settings.get.bufferPerLevel * level) - } - } - override def maxComponents = 8 // ----------------------------------------------------------------------- // @@ -148,8 +125,7 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe Settings.namespace + "gui.Analyzer.RobotOwner", owner)) player.sendChatToPlayer(ChatMessageComponent.createFromTranslationWithSubstitutions( Settings.namespace + "gui.Analyzer.RobotName", player_.getCommandSenderName)) - player.sendChatToPlayer(ChatMessageComponent.createFromTranslationWithSubstitutions( - Settings.namespace + "gui.Analyzer.RobotXp", xp.formatted("%.2f"), level: Integer)) + MinecraftForge.EVENT_BUS.post(new RobotAnalyzeEvent(this, player)) super.onAnalyze(player, side, hitX, hitY, hitZ) } @@ -166,6 +142,13 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe if (!world.blockExists(nx, ny, nz)) { return false // Don't fall off the earth. } + + if (isServer) { + val event = new RobotMoveEvent.Pre(this, direction) + MinecraftForge.EVENT_BUS.post(event) + if (event.isCanceled) return false + } + val blockId = world.getBlockId(nx, ny, nz) val metadata = world.getBlockMetadata(nx, ny, nz) try { @@ -193,6 +176,7 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe if (isServer) { ServerPacketSender.sendRobotMove(this, ox, oy, oz, direction) checkRedstoneInputChanged() + MinecraftForge.EVENT_BUS.post(new RobotMoveEvent.Post(this, direction)) } else { // If we broke some replaceable block (like grass) play its break sound. @@ -226,9 +210,6 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe val stack = Blocks.robotProxy.createItemStack() val tag = if (this.tag != null) this.tag.copy.asInstanceOf[NBTTagCompound] else new NBTTagCompound("tag") stack.setTagCompound(tag) - if (xp > 0) { - tag.setDouble(Settings.namespace + "xp", xp) - } if (globalBuffer > 1) { tag.setInteger(Settings.namespace + "storedEnergy", globalBuffer.toInt) } @@ -238,9 +219,9 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe def parseItemStack(stack: ItemStack) { if (stack.hasTagCompound) { tag = stack.getTagCompound.copy.asInstanceOf[NBTTagCompound] - xp = tag.getDouble(Settings.namespace + "xp") - updateXpInfo() bot.node.changeBuffer(stack.getTagCompound.getInteger(Settings.namespace + "storedEnergy")) + // TODO migration: xp to xp upgrade + // xp = tag.getDouble(Settings.namespace + "xp") } else { tag = new NBTTagCompound("tag") @@ -323,10 +304,6 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe globalBuffer = bot.node.globalBuffer globalBufferSize = bot.node.globalBufferSize updatePowerInformation() - if (xpChanged && world.getWorldInfo.getWorldTotalTime % 200 == 0) { - xpChanged = false - ServerPacketSender.sendRobotXp(this) - } } else if (isRunning && isAnimatingMove) { client.Sound.updatePosition(this) @@ -339,7 +316,6 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe case Some(item) => player_.getAttributeMap.applyAttributeModifiers(item.getAttributeModifiers) case _ => } - updateXpInfo() // Ensure we have a node address, because the proxy needs this to initialize // its own node to the same address ours has. @@ -372,8 +348,6 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe if (nbt.hasKey(Settings.namespace + "tag")) { tag = nbt.getCompoundTag(Settings.namespace + "tag") } - xp = nbt.getDouble(Settings.namespace + "xp") max 0 - updateXpInfo() selectedSlot = nbt.getInteger(Settings.namespace + "selectedSlot") max actualSlot(0) min (getSizeInventory - 1) animationTicksTotal = nbt.getInteger(Settings.namespace + "animationTicksTotal") animationTicksLeft = nbt.getInteger(Settings.namespace + "animationTicksLeft") @@ -384,6 +358,9 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe swingingTool = nbt.getBoolean(Settings.namespace + "swingingTool") turnAxis = nbt.getByte(Settings.namespace + "turnAxis") } + + // TODO migration: xp to xp upgrade + // xp = nbt.getDouble(Settings.namespace + "xp") max 0 } // Side check for Waila (and other mods that may call this client side). @@ -398,7 +375,6 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe if (tag != null) { nbt.setCompoundTag(Settings.namespace + "tag", tag) } - nbt.setDouble(Settings.namespace + "xp", xp) nbt.setInteger(Settings.namespace + "selectedSlot", selectedSlot) if (isAnimatingMove || isAnimatingSwing || isAnimatingTurn) { nbt.setInteger(Settings.namespace + "animationTicksTotal", animationTicksTotal) @@ -424,8 +400,6 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe if (nbt.hasKey("upgrade")) { equippedUpgrade = Option(ItemStack.loadItemStackFromNBT(nbt.getCompoundTag("upgrade"))) } - xp = nbt.getDouble(Settings.namespace + "xp") - updateXpInfo() animationTicksTotal = nbt.getInteger("animationTicksTotal") animationTicksLeft = nbt.getInteger("animationTicksLeft") moveFromX = nbt.getInteger("moveFromX") @@ -459,7 +433,6 @@ class Robot(val isRemote: Boolean) extends traits.Computer with traits.TextBuffe } nbt.setNewCompoundTag("upgrade", getStackInSlot(3).writeToNBT) } - nbt.setDouble(Settings.namespace + "xp", xp) if (isAnimatingMove || isAnimatingSwing || isAnimatingTurn) { nbt.setInteger("animationTicksTotal", animationTicksTotal) nbt.setInteger("animationTicksLeft", animationTicksLeft) diff --git a/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala b/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala index c0bd84f34..dab7b5355 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala @@ -29,17 +29,22 @@ class RobotProxy(val robot: Robot) extends traits.Computer with traits.TextBuffe // ----------------------------------------------------------------------- // - // Note: we implement IRobotContext in the TE to allow external components - //to cast their owner to it (to allow interacting with their owning robot). - override def isRunning = robot.isRunning override def setRunning(value: Boolean) = robot.setRunning(value) - override def selectedSlot() = robot.selectedSlot - override def player() = robot.player() + override def dynamicComponentCapacity = robot.dynamicComponentCapacity + + override def componentCapacity = robot.componentCapacity + + override def inventorySize = robot.inventorySize + + override def getComponentInSlot(index: Int) = robot.getComponentInSlot(index) + + override def selectedSlot() = robot.selectedSlot + override def saveUpgrade() = robot.saveUpgrade() // ----------------------------------------------------------------------- // diff --git a/src/main/scala/li/cil/oc/server/PacketSender.scala b/src/main/scala/li/cil/oc/server/PacketSender.scala index 85a56c028..fbb81fa76 100644 --- a/src/main/scala/li/cil/oc/server/PacketSender.scala +++ b/src/main/scala/li/cil/oc/server/PacketSender.scala @@ -201,15 +201,6 @@ object PacketSender { pb.sendToNearbyPlayers(t, 16) } - def sendRobotXp(t: tileentity.Robot) { - val pb = new PacketBuilder(PacketType.RobotXp) - - pb.writeTileEntity(t) - pb.writeDouble(t.xp) - - pb.sendToNearbyPlayers(t) - } - def sendRotatableState(t: Rotatable) { val pb = new PacketBuilder(PacketType.RotatableState) diff --git a/src/main/scala/li/cil/oc/server/component/UpgradeExperience.scala b/src/main/scala/li/cil/oc/server/component/UpgradeExperience.scala new file mode 100644 index 000000000..f1f26e8c0 --- /dev/null +++ b/src/main/scala/li/cil/oc/server/component/UpgradeExperience.scala @@ -0,0 +1,58 @@ +package li.cil.oc.server.component + +import li.cil.oc.{Settings, api} +import li.cil.oc.api.network.{Arguments, Context, Callback, Visibility} +import li.cil.oc.common.component.ManagedComponent +import net.minecraft.nbt.NBTTagCompound + +class UpgradeExperience extends ManagedComponent { + def node = api.Network.newNode(this, Visibility.Network). + withComponent("experience"). + withConnector(30 * Settings.get.bufferPerLevel). + create() + + var experience = 0.0 + + var level = 0 + + def xpForLevel(level: Int) = Settings.get.baseXpToLevel + Math.pow(level * Settings.get.constantXpGrowth, Settings.get.exponentialXpGrowth) + + def xpForNextLevel = xpForLevel(level + 1) + + def addExperience(value: Double) { + if (level < 30) { + experience = experience + value + if (experience >= xpForNextLevel) { + updateXpInfo() + } + // ServerPacketSender.sendRobotXp(this) + } + } + + def updateXpInfo() { + // xp(level) = base + (level * const) ^ exp + // pow(xp(level) - base, 1/exp) / const = level + level = math.min((Math.pow(experience - Settings.get.baseXpToLevel, 1 / Settings.get.exponentialXpGrowth) / Settings.get.constantXpGrowth).toInt, 30) + if (node != null) { + node.setLocalBufferSize(Settings.get.bufferPerLevel * level) + } + } + + @Callback(direct = true) + def level(context: Context, args: Arguments): Array[AnyRef] = { + val xpNeeded = xpForNextLevel - xpForLevel(level) + val xpProgress = math.max(0, experience - xpForLevel(level)) + result(level + xpProgress / xpNeeded) + } + + override def save(nbt: NBTTagCompound) { + super.save(nbt) + nbt.setDouble(Settings.namespace + "xp", experience) + } + + override def load(nbt: NBTTagCompound) { + super.load(nbt) + experience = nbt.getDouble(Settings.namespace + "xp") max 0 + updateXpInfo() + } +} diff --git a/src/main/scala/li/cil/oc/server/component/robot/Player.scala b/src/main/scala/li/cil/oc/server/component/robot/Player.scala index 7a6cb7357..bcfc0a6f8 100644 --- a/src/main/scala/li/cil/oc/server/component/robot/Player.scala +++ b/src/main/scala/li/cil/oc/server/component/robot/Player.scala @@ -2,8 +2,9 @@ package li.cil.oc.server.component.robot import cpw.mods.fml.common.ObfuscationReflectionHelper import java.util.logging.Level +import li.cil.oc.api.event._ import li.cil.oc.common.tileentity -import li.cil.oc.util.mods.{Mods, UniversalElectricity, TinkersConstruct, PortalGun} +import li.cil.oc.util.mods.{Mods, TinkersConstruct, PortalGun} import li.cil.oc.{OpenComputers, Settings} import net.minecraft.block.{BlockPistonBase, BlockFluid, Block} import net.minecraft.entity.item.EntityItem @@ -99,9 +100,11 @@ class Player(val robot: tileentity.Robot) extends EntityPlayer(robot.world, Sett callUsingItemInSlot(0, stack => entity match { case player: EntityPlayer if !canAttackPlayer(player) => // Avoid player damage. case _ => - super.attackTargetEntityWithCurrentItem(entity) - if (stack != null && entity.isDead) { - robot.addXp(Settings.get.robotActionXp) + val event = new RobotAttackEntityEvent.Pre(robot, entity) + MinecraftForge.EVENT_BUS.post(event) + if (!event.isCanceled) { + super.attackTargetEntityWithCurrentItem(entity) + MinecraftForge.EVENT_BUS.post(new RobotAttackEntityEvent.Post(robot, entity)) } }) } @@ -262,7 +265,11 @@ class Player(val robot: tileentity.Robot) extends EntityPlayer(robot.world, Sett val breakTime = if (cobwebOverride) Settings.get.swingDelay else hardness * 1.5 / strength - val adjustedBreakTime = math.max(0.05, breakTime * Settings.get.harvestRatio * math.max(1 - robot.level * Settings.get.harvestSpeedBoostPerLevel, 0)) + + val preEvent = new RobotBreakBlockEvent.Pre(robot, world, x, y, z, breakTime * Settings.get.harvestRatio) + MinecraftForge.EVENT_BUS.post(preEvent) + if (preEvent.isCanceled) return 0 + val adjustedBreakTime = math.max(0.05, preEvent.getBreakTime) // Special handling for Tinkers Construct - tools like the hammers do // their break logic in onBlockStartBreak but return true to cancel @@ -295,10 +302,10 @@ class Player(val robot: tileentity.Robot) extends EntityPlayer(robot.world, Sett // check only serves to test whether the block can drop anything at all. if (block.canHarvestBlock(this, metadata)) { block.harvestBlock(world, this, x, y, z, metadata) - robot.addXp(breakEvent.getExpToDrop * Settings.get.robotOreXpRate) + MinecraftForge.EVENT_BUS.post(new RobotBreakBlockEvent.Post(robot, breakEvent.getExpToDrop)) } - if (stack != null) { - robot.addXp(Settings.get.robotActionXp) + else if (stack != null) { + MinecraftForge.EVENT_BUS.post(new RobotBreakBlockEvent.Post(robot, 0)) } return adjustedBreakTime } @@ -351,37 +358,28 @@ class Player(val robot: tileentity.Robot) extends EntityPlayer(robot.world, Sett } private def tryRepair(stack: ItemStack, oldStack: ItemStack) { - def repair(high: Long, low: Long) = { - val needsRepairing = low < high - val damageRate = Settings.get.itemDamageRate * math.max(1 - robot.level * Settings.get.toolEfficiencyPerLevel, 0) - val shouldRepair = needsRepairing && getRNG.nextDouble() >= damageRate - if (shouldRepair) { - // If an item takes a lot of damage at once we don't necessarily want to - // make *all* of that damage go away. Instead we scale it according to - // our damage probability. This makes sure we don't discard massive - // damage spikes (e.g. on axes when using the TreeCapitator mod or such). - math.ceil((high - low) * (1 - damageRate)).toInt - } - else 0 - } - if (Mods.UniversalElectricity.isAvailable && UniversalElectricity.isEnergyItem(stack)) { - UniversalElectricity.chargeItem(stack, repair(UniversalElectricity.getEnergyInItem(oldStack), UniversalElectricity.getEnergyInItem(stack))) - } - else if (stack.isItemStackDamageable) { - stack.setItemDamage(stack.getItemDamage - repair(stack.getItemDamage, oldStack.getItemDamage)) + val damageRate = new RobotUsedTool.ComputeDamageRate(robot, stack, oldStack, Settings.get.itemDamageRate) + MinecraftForge.EVENT_BUS.post(damageRate) + if (damageRate.getDamageRate < 1) { + MinecraftForge.EVENT_BUS.post(new RobotUsedTool.ApplyDamageRate(robot, stack, oldStack, damageRate.getDamageRate)) } } private def tryPlaceBlockWhileHandlingFunnySpecialCases(stack: ItemStack, x: Int, y: Int, z: Int, side: Int, hitX: Float, hitY: Float, hitZ: Float) = { stack != null && stack.stackSize > 0 && { - val fakeEyeHeight = if (rotationPitch < 0 && isSomeKindOfPiston(stack)) 1.82 else 0 - setPosition(posX, posY - fakeEyeHeight, posZ) - val didPlace = stack.tryPlaceItemIntoWorld(this, world, x, y, z, side, hitX, hitY, hitZ) - setPosition(posX, posY + fakeEyeHeight, posZ) - if (didPlace) { - robot.addXp(Settings.get.robotActionXp) + val event = new RobotPlaceBlockEvent.Pre(robot, stack, world, x, y, z) + MinecraftForge.EVENT_BUS.post(event) + if (event.isCanceled) false + else { + val fakeEyeHeight = if (rotationPitch < 0 && isSomeKindOfPiston(stack)) 1.82 else 0 + setPosition(posX, posY - fakeEyeHeight, posZ) + val didPlace = stack.tryPlaceItemIntoWorld(this, world, x, y, z, side, hitX, hitY, hitZ) + setPosition(posX, posY + fakeEyeHeight, posZ) + if (didPlace) { + MinecraftForge.EVENT_BUS.post(new RobotPlaceBlockEvent.Post(robot, stack, world, x, y, z)) + } + didPlace } - didPlace } } @@ -409,7 +407,7 @@ class Player(val robot: tileentity.Robot) extends EntityPlayer(robot.world, Sett if (Settings.get.robotExhaustionCost > 0) { robot.bot.node.changeBuffer(-Settings.get.robotExhaustionCost * amount) } - robot.addXp(Settings.get.robotExhaustionXpRate * amount) + MinecraftForge.EVENT_BUS.post(new RobotExhaustionEvent(robot, amount)) } override def openGui(mod: AnyRef, modGuiId: Int, world: World, x: Int, y: Int, z: Int) {} diff --git a/src/main/scala/li/cil/oc/server/component/robot/Robot.scala b/src/main/scala/li/cil/oc/server/component/robot/Robot.scala index 05d7d3f7a..1af4c77d4 100644 --- a/src/main/scala/li/cil/oc/server/component/robot/Robot.scala +++ b/src/main/scala/li/cil/oc/server/component/robot/Robot.scala @@ -1,6 +1,6 @@ package li.cil.oc.server.component.robot -import li.cil.oc.{Items, api, OpenComputers, Settings} +import li.cil.oc.{api, OpenComputers, Settings} import li.cil.oc.api.network._ import li.cil.oc.common.tileentity import li.cil.oc.server.{PacketSender => ServerPacketSender} @@ -22,7 +22,7 @@ import li.cil.oc.common.component.ManagedComponent class Robot(val robot: tileentity.Robot) extends ManagedComponent { val node = api.Network.newNode(this, Visibility.Neighbors). withComponent("robot"). - withConnector(Settings.get.bufferRobot + 30 * Settings.get.bufferPerLevel). + withConnector(Settings.get.bufferRobot). create() def actualSlot(n: Int) = robot.actualSlot(n) @@ -48,13 +48,6 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent { def hasAngelUpgrade = api.Items.get(robot.getStackInSlot(3)) == api.Items.get("angelUpgrade") - @Callback(direct = true) - def level(context: Context, args: Arguments): Array[AnyRef] = { - val xpNeeded = robot.xpForNextLevel - robot.xpForLevel(robot.level) - val xpProgress = math.max(0, robot.xp - robot.xpForLevel(robot.level)) - result(robot.level + xpProgress / xpNeeded) - } - @Callback def name(context: Context, args: Arguments): Array[AnyRef] = result(robot.name) @@ -521,7 +514,6 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent { } else if (robot.move(direction)) { context.pause(Settings.get.moveDelay) - robot.addXp(Settings.get.robotExhaustionXpRate * 0.01) result(true) } else { diff --git a/src/main/scala/li/cil/oc/server/driver/item/UpgradeExperience.scala b/src/main/scala/li/cil/oc/server/driver/item/UpgradeExperience.scala new file mode 100644 index 000000000..8292e7480 --- /dev/null +++ b/src/main/scala/li/cil/oc/server/driver/item/UpgradeExperience.scala @@ -0,0 +1,20 @@ +package li.cil.oc.server.driver.item + +import li.cil.oc.api +import li.cil.oc.api.driver.Slot +import li.cil.oc.api.machine.Robot +import li.cil.oc.server.component +import net.minecraft.item.ItemStack +import net.minecraftforge.common.MinecraftForge + +object UpgradeExperience extends Item { + override def worksWith(stack: ItemStack) = isOneOf(stack, api.Items.get("experienceUpgrade")) + + override def createEnvironment(stack: ItemStack, container: component.Container) = + container.tileEntity match { + case Some(robot: Robot) => new component.UpgradeExperience() + case _ => null + } + + override def slot(stack: ItemStack) = Slot.Upgrade +}