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:
+ *
+ * - Slot 0: Tool
+ * - Slot [1, dynamicComponentCapacity + 1): hot-swappable components.
+ * - Slot [dynamicComponentCapacity + 1, componentCapacity + 1): hard-wired components.
+ * - Slot [componentCapacity + 1, inventorySize): actual inventory.
+ *
+ * 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 000000000..b605cb100
Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/items/upgrade_experience.png differ
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 000000000..8b44cf7cb
Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/items/upgrade_inventory.png differ
diff --git a/src/main/scala/li/cil/oc/Items.scala b/src/main/scala/li/cil/oc/Items.scala
index 94d4d0416..8e345b733 100644
--- a/src/main/scala/li/cil/oc/Items.scala
+++ b/src/main/scala/li/cil/oc/Items.scala
@@ -172,5 +172,6 @@ object Items extends ItemAPI {
// v1.3.0
Recipes.addItemDelegate(new item.LinkedCard(multi), "linkedCard", "oc:linkedCard")
+ Recipes.addItemDelegate(new item.UpgradeExperience(multi), "experienceUpgrade", "oc:experienceUpgrade")
}
}
\ No newline at end of file
diff --git a/src/main/scala/li/cil/oc/client/PacketHandler.scala b/src/main/scala/li/cil/oc/client/PacketHandler.scala
index 917c0bb30..d9bfca2f5 100644
--- a/src/main/scala/li/cil/oc/client/PacketHandler.scala
+++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala
@@ -42,7 +42,6 @@ class PacketHandler extends CommonPacketHandler {
case PacketType.RobotEquippedUpgradeChange => 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
+}