diff --git a/src/main/java/li/cil/oc/api/driver/item/UpgradeRenderer.java b/src/main/java/li/cil/oc/api/driver/item/UpgradeRenderer.java index ca7bae41f..763549c52 100644 --- a/src/main/java/li/cil/oc/api/driver/item/UpgradeRenderer.java +++ b/src/main/java/li/cil/oc/api/driver/item/UpgradeRenderer.java @@ -6,6 +6,8 @@ import li.cil.oc.api.event.RobotRenderEvent; import li.cil.oc.api.internal.Robot; import net.minecraft.item.ItemStack; +import java.util.Set; + /** * This interface can be implemented by items to allow custom rendering of * upgrades installed in robots. @@ -19,38 +21,32 @@ import net.minecraft.item.ItemStack; @SideOnly(Side.CLIENT) public interface UpgradeRenderer { /** - * The priority with which to render this upgrade. + * Returns which mount point this renderer wants to render the specified + * upgrade in. *

- * Upgrades with a higher priority are preferred for rendering over other - * upgrades. Upgrades with higher priorities will be rendered in the top - * slots, i.e. the assigned slot order is top then bottom. + * This method is used to determine which upgrade is rendered where, and is + * called for every installed, renderable upgrade. The available mount + * point names are defined in {@link MountPointName}, with the two special + * values None and Any. *

- * You usually won't need the robot parameter, but in case you do - * need some contextual information, this should provide you with anything - * you could need. + * None means that the upgrade should not be rendered at all. This + * can be the case when there is no slot remaining that the upgrade may be + * rendered in. Returning null is equivalent to returning None. + *

+ * Any means that the upgrade doesn't really care where it's being + * rendered. Mount points not assigned by another upgrade preferring to be + * rendered in it will be assigned to such upgrades in the order they are + * installed in the robot. + *

+ * Returning a mount point not in the list of available mount points will + * be equivalent to returning None. * - * @param stack the item stack of the upgrade to render. - * @param robot the robot the upgrade is rendered on. - * @return the priority with which to render the upgrade. + * @param stack the item stack of the upgrade to render. + * @param robot the robot the upgrade is rendered on. + * @param availableMountPoints the mount points available for rendering in. + * @return the mount point to reserve for the upgrade. */ - int priority(ItemStack stack, Robot robot); - - /** - * Whether the upgrade can be rendered in the specified mount point. - *

- * This is used to determine whether an upgrade can be rendered in a - * specific mount point, or not. Note that if the upgrade refuses to - * be rendered in the offered mount point, it will not be rendered at all, - * i.e. it will not be offered another mount point. To give the upgrade - * a better chance to get a usable mount point, specify an appropriate - * priority via {@link #priority}. - * - * @param stack the item stack of the upgrade to render. - * @param mountPoint the mount-point to render the upgrade at. - * @param robot the robot the upgrade is rendered on. - * @return whether the upgrade can be rendered in the specified mount point. - */ - boolean canRender(ItemStack stack, RobotRenderEvent.MountPoint mountPoint, Robot robot); + String computePreferredMountPoint(ItemStack stack, Robot robot, Set availableMountPoints); /** * Render the specified upgrade on a robot. @@ -63,7 +59,8 @@ public interface UpgradeRenderer { * relative to. *

* If the stack cannot be rendered, the renderer should indicate so in - * {@link #canRender}, otherwise it will still consume a mount point. + * {@link #computePreferredMountPoint}, otherwise it will still consume a mount + * point. *

* You usually won't need the robot parameter, but in case you do * need some contextual information, this should provide you with anything @@ -75,4 +72,23 @@ public interface UpgradeRenderer { * @param pt partial tick time, e.g. for animations. */ void render(ItemStack stack, RobotRenderEvent.MountPoint mountPoint, Robot robot, float pt); + + /** + * Mount point names for {@link #computePreferredMountPoint}. + */ + final class MountPointName { + public static final String None = "none"; + public static final String Any = "any"; + + public static final String TopLeft = "top_left"; + public static final String TopRight = "top_right"; + public static final String TopBack = "top_back"; + public static final String BottomLeft = "bottom_left"; + public static final String BottomRight = "bottom_right"; + public static final String BottomBack = "bottom_back"; + public static final String BottomFront = "bottom_front"; + + private MountPointName() { + } + } } diff --git a/src/main/scala/li/cil/oc/client/renderer/item/UpgradeRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/item/UpgradeRenderer.scala index 9dbc174c9..fa3f64206 100644 --- a/src/main/scala/li/cil/oc/client/renderer/item/UpgradeRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/item/UpgradeRenderer.scala @@ -2,6 +2,7 @@ package li.cil.oc.client.renderer.item import li.cil.oc.Constants import li.cil.oc.api +import li.cil.oc.api.driver.item.UpgradeRenderer.MountPointName import li.cil.oc.api.event.RobotRenderEvent.MountPoint import li.cil.oc.client.Textures import li.cil.oc.integration.opencomputers.Item @@ -16,12 +17,15 @@ object UpgradeRenderer { lazy val generatorUpgrade = api.Items.get(Constants.ItemName.GeneratorUpgrade) lazy val inventoryUpgrade = api.Items.get(Constants.ItemName.InventoryUpgrade) - def priority(stack: ItemStack): Int = { + def preferredMountPoint(stack: ItemStack, availableMountPoints: java.util.Set[String]): String = { val descriptor = api.Items.get(stack) - if (descriptor == craftingUpgrade) 5 - else if (descriptor == generatorUpgrade) 0 - else 10 + if (descriptor == craftingUpgrade || descriptor == generatorUpgrade || descriptor == inventoryUpgrade) { + if (descriptor == generatorUpgrade && availableMountPoints.contains(MountPointName.BottomBack)) MountPointName.BottomBack + else if (descriptor == inventoryUpgrade && availableMountPoints.contains(MountPointName.TopBack)) MountPointName.TopBack + else MountPointName.Any + } + else MountPointName.None } def canRender(stack: ItemStack): Boolean = { 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 a986a1057..12431e3c0 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 @@ -4,6 +4,7 @@ import com.google.common.base.Strings import li.cil.oc.OpenComputers import li.cil.oc.Settings import li.cil.oc.api.driver.item.UpgradeRenderer +import li.cil.oc.api.driver.item.UpgradeRenderer.MountPointName import li.cil.oc.api.event.RobotRenderEvent import li.cil.oc.client.Textures import li.cil.oc.common.EventHandler @@ -19,6 +20,7 @@ import net.minecraft.client.renderer.entity.RendererLivingEntity import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer import net.minecraft.init.Items import net.minecraft.item.ItemBlock +import net.minecraft.item.ItemStack import net.minecraft.tileentity.TileEntity import net.minecraft.util.EnumChatFormatting import net.minecraft.util.Vec3 @@ -30,11 +32,24 @@ import net.minecraftforge.common.util.ForgeDirection import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL12 +import scala.collection.convert.WrapAsJava._ +import scala.collection.mutable + object RobotRenderer extends TileEntitySpecialRenderer { private val displayList = GLAllocation.generateDisplayLists(2) private val mountPoints = Array.fill(7)(new RobotRenderEvent.MountPoint()) + private val slotNameMapping = Map( + UpgradeRenderer.MountPointName.TopLeft -> 0, + UpgradeRenderer.MountPointName.TopRight -> 1, + UpgradeRenderer.MountPointName.TopBack -> 2, + UpgradeRenderer.MountPointName.BottomLeft -> 3, + UpgradeRenderer.MountPointName.BottomRight -> 4, + UpgradeRenderer.MountPointName.BottomBack -> 5, + UpgradeRenderer.MountPointName.BottomFront -> 6 + ) + private val gap = 1.0f / 28.0f private val gt = 0.5f + gap private val gb = 0.5f - gap @@ -405,20 +420,31 @@ object RobotRenderer extends TileEntitySpecialRenderer { } if (MinecraftForgeClient.getRenderPass == 0) { - var filterMount = 0 - //noinspection SortFilter We need to sort before we filter, because the filter is index sensitive. - val stacks = (robot.componentSlots ++ robot.containerSlots).map(robot.getStackInSlot). - collect { case stack if stack != null && stack.getItem.isInstanceOf[UpgradeRenderer] => (stack, stack.getItem.asInstanceOf[UpgradeRenderer]) }. - sortBy { case (stack, renderer) => -renderer.priority(stack, robot) }. - filter { - case (stack, renderer) if filterMount < mountPoints.length && renderer.canRender(stack, mountPoints(filterMount), robot) => - filterMount += 1 - true - case _ => false + lazy val availableSlots = slotNameMapping.keys.to[mutable.Set] + lazy val wildcardRenderers = mutable.Buffer.empty[(ItemStack, UpgradeRenderer)] + lazy val slotMapping = Array.fill(mountPoints.length)(null: (ItemStack, UpgradeRenderer)) + + val renderers = (robot.componentSlots ++ robot.containerSlots).map(robot.getStackInSlot). + collect { case stack if stack != null && stack.getItem.isInstanceOf[UpgradeRenderer] => (stack, stack.getItem.asInstanceOf[UpgradeRenderer]) } + + for ((stack, renderer) <- renderers) { + val preferredSlot = renderer.computePreferredMountPoint(stack, robot, availableSlots) + if (availableSlots.remove(preferredSlot)) { + slotMapping(slotNameMapping(preferredSlot)) = (stack, renderer) + } + else if (preferredSlot == MountPointName.Any) { + wildcardRenderers += ((stack, renderer)) + } } - val minLength = math.min(mountPoints.length, stacks.length) - for (((stack, renderer), mountPoint) <- (stacks.take(minLength), mountPoints.take(minLength)).zipped) try { + var firstEmpty = slotMapping.indexOf(null) + for (entry <- wildcardRenderers if firstEmpty >= 0) { + slotMapping(firstEmpty) = entry + firstEmpty = slotMapping.indexOf(null) + } + + for ((info, mountPoint) <- (slotMapping, mountPoints).zipped if info != null) try { + val (stack, renderer) = info GL11.glPushMatrix() GL11.glTranslatef(0.5f, 0.5f, 0.5f) renderer.render(stack, mountPoint, robot, f) diff --git a/src/main/scala/li/cil/oc/common/item/Delegator.scala b/src/main/scala/li/cil/oc/common/item/Delegator.scala index 5f3e7ce23..87e9a371e 100644 --- a/src/main/scala/li/cil/oc/common/item/Delegator.scala +++ b/src/main/scala/li/cil/oc/common/item/Delegator.scala @@ -254,9 +254,7 @@ class Delegator extends Item with driver.item.UpgradeRenderer { // ----------------------------------------------------------------------- // - def priority(stack: ItemStack, robot: Robot): Int = UpgradeRenderer.priority(stack) + override def computePreferredMountPoint(stack: ItemStack, robot: Robot, availableMountPoints: util.Set[String]): String = UpgradeRenderer.preferredMountPoint(stack, availableMountPoints) - def canRender(stack: ItemStack, mountPoint: MountPoint, robot: Robot): Boolean = UpgradeRenderer.canRender(stack) - - def render(stack: ItemStack, mountPoint: MountPoint, robot: Robot, pt: Float): Unit = UpgradeRenderer.render(stack, mountPoint) + override def render(stack: ItemStack, mountPoint: MountPoint, robot: Robot, pt: Float): Unit = UpgradeRenderer.render(stack, mountPoint) }