diff --git a/src/main/resources/assets/opencomputers/lang/de_DE.lang b/src/main/resources/assets/opencomputers/lang/de_DE.lang index f41d7120ca..a75875b861 100644 --- a/src/main/resources/assets/opencomputers/lang/de_DE.lang +++ b/src/main/resources/assets/opencomputers/lang/de_DE.lang @@ -155,6 +155,9 @@ item.oc.UpgradeTank.name=Tank-Upgrade item.oc.UpgradeTankController.name=Tankbedienungs-Upgrade item.oc.UpgradeTractorBeam.name=Traktorstrahl-Upgrade item.oc.UpgradeTrading.name=Handels-Upgrade +item.oc.UpgradeSkin0.name=Skin-Upgrade (Stufe 1) +item.oc.UpgradeSkin1.name=Skin-Upgrade (Stufe 2) +item.oc.UpgradeSkin2.name=Skin-Upgrade (Stufe 3) item.oc.WirelessNetworkCard0.name=Drahtlosnetzwerkkarte (Stufe 1) item.oc.WirelessNetworkCard1.name=Drahtlosnetzwerkkarte (Stufe 2) item.oc.WorldSensorCard.name=Weltsensorkarte @@ -384,10 +387,14 @@ oc:tooltip.UpgradeLeash=Erlaubt es manchen Geräten, wie etwa Drohnen, B- *getus oc:tooltip.UpgradeNavigation=Erlaubt es Robotern, ihre Position und Ausrichtung zu bestimmen. Die Position ist relativ zur Mitte der Karte, die in diesem Upgrade verbaut wurde. oc:tooltip.UpgradePiston=Dieses Upgrade ist sehr drückend. Es macht es möglich Blöcke zu verschieben, ähnlich dem Kolben. Es kann jedoch §lkeine§7 Entities bewegen. oc:tooltip.UpgradeSign=Erlaubt das Lesen und Schreiben von Text auf Schildern. +oc:tooltip.UpgradeSkin0=Hast du dir deinen Roboter schon einmal angesehen und gedacht: "Das sieht langweilig aus, ich habe einen 3D-Drucker, den ich benutzen möchte"? Mit diesem Upgrade kannst du genau das. Du kannst deinem Roboter einen 3D-gedruckten Skin verpassen und ihm sogar 3D-gedruckte Arme mit maximal einem Gelenk geben. Obwohl die Arme nicht mit der Welt interagieren können, sehen sie cool aus. +oc:tooltip.UpgradeSkin1=Hast du dir deinen Roboter schon einmal angesehen und gedacht: "Das sieht langweilig aus, ich habe einen 3D-Drucker, den ich benutzen möchte"? Mit diesem Upgrade kannst du genau das. Du kannst deinem Roboter einen 3D-gedruckten Skin verpassen und ihm sogar 3D-gedruckte Arme mit maximal zwei Gelenken geben. Obwohl die Arme nicht mit der Welt interagieren können, sehen sie cool aus. +oc:tooltip.UpgradeSkin2=Hast du dir deinen Roboter schon einmal angesehen und gedacht: "Das sieht langweilig aus, ich habe einen 3D-Drucker, den ich benutzen möchte"? Mit diesem Upgrade kannst du genau das. Du kannst deinem Roboter einen 3D-gedruckten Skin verpassen und ihm sogar 3D-gedruckte Arme mit maximal drei Gelenken geben. Obwohl die Arme nicht mit der Welt interagieren können, sehen sie cool aus. oc:tooltip.UpgradeSolarGenerator=Kann verwendet werden, um unterwegs Energie aus Sonnenlicht zu generieren. Benötigt eine ungehinderte Sicht zum Himmel über dem Roboter. Generiert Energie mit %s%% der Geschwindigkeit eines Stirlingmotors. oc:tooltip.UpgradeTank=Dieses Upgrade gibt Robotern einen internen Tank. Ohne ein solches Upgrade können Roboter keine Flüssigkeiten verwahren. oc:tooltip.UpgradeTankController=Dieses Upgrade erlaubt es dem Roboter, präziser mit externen Tanks zu interagieren, und erlaubt es ihm, Flüssigkeiten in und aus sich im Inventar befindlichen Tank-Gegenständen zu pumpen. oc:tooltip.UpgradeTractorBeam=Stattet den Roboter mit unglaublich fortschrittlicher Technologie - Kosename: "Gegenstandsmagnet" - aus. Erlaubt es dem Roboter, Gegenstände, innerhalb von 3 Blöcken um sich herum, einzusammeln. +oc:tooltip.UpgradeTrading=Erlaubt Robotern und Dronen mit Dorfbewohnern zu handeln. oc:tooltip.Waypoint=Ein Orientierungpunkt für Geräte mit einem Navigations-Upgrade. oc:tooltip.WirelessNetworkCard=Erlaubt das drahtlose Senden von Netzwerknachrichten, zusätzlich zu normalen. Drahtlose Nachrichten werden nur gesendet, wenn eine §fSignalstärke§7 festgelegt wurde! oc:tooltip.WorldSensorCard=Erlaubt es, Informationen über die Welt auszulesen, wie etwa Gravitation und ob die Atmosphäre atembar ist. Verwendung von Messergebnissen auf eigene Gefahr. Der Hersteller übernimmt keinerlei Garantie. diff --git a/src/main/resources/assets/opencomputers/lang/en_US.lang b/src/main/resources/assets/opencomputers/lang/en_US.lang index 30da048b94..8a2910e1f6 100644 --- a/src/main/resources/assets/opencomputers/lang/en_US.lang +++ b/src/main/resources/assets/opencomputers/lang/en_US.lang @@ -155,6 +155,9 @@ item.oc.UpgradeTank.name=Tank Upgrade item.oc.UpgradeTankController.name=Tank Controller Upgrade item.oc.UpgradeTractorBeam.name=Tractor Beam Upgrade item.oc.UpgradeTrading.name=Trading Upgrade +item.oc.UpgradeSkin0.name=Skin Upgrade (Tier 1) +item.oc.UpgradeSkin1.name=Skin Upgrade (Tier 2) +item.oc.UpgradeSkin2.name=Skin Upgrade (Tier 3) item.oc.WirelessNetworkCard0.name=Wireless Network Card (Tier 1) item.oc.WirelessNetworkCard1.name=Wireless Network Card (Tier 2) item.oc.WorldSensorCard.name=World Sensor Card @@ -385,6 +388,9 @@ oc:tooltip.UpgradeLeash=Allows some devices, such as drones, to bind Isaa- excus oc:tooltip.UpgradeNavigation=Can be used to determine the position and orientation of a device. The position is relative to the center of the map that was used to craft this upgrade. oc:tooltip.UpgradePiston=This upgrade is very pushy. It allows moving blocks, similar to when using a piston. It does §lnot§7 move entities, however. oc:tooltip.UpgradeSign=Allows reading text on and writing text to signs. +oc:tooltip.UpgradeSkin0=Have you ever looked at your robot and thought 'This looks boring, I have a 3d printer that I wanna use'? With this upgrade, you can. You can apply a 3d-printed skin to your robot and even give it 3d-printed arms with a maximum of one joint. Although the arms can't interact with the world, they can look cool. +oc:tooltip.UpgradeSkin1=Have you ever looked at your robot and thought 'This looks boring, I have a 3d printer that I wanna use'? With this upgrade, you can. You can apply a 3d-printed skin to your robot and even give it 3d-printed arms with a maximum of two joints. Although the arms can't interact with the world, they can look cool. +oc:tooltip.UpgradeSkin2=Have you ever looked at your robot and thought 'This looks boring, I have a 3d printer that I wanna use'? With this upgrade, you can. You can apply a 3d-printed skin to your robot and even give it 3d-printed arms with a maximum of three joints. Although the arms can't interact with the world, they can look cool. oc:tooltip.UpgradeSolarGenerator=Can be used to generate energy from sunlight on the go. Requires a clear line of sight to the sky above the device. Generates energy at %s%% of the speed of a Stirling Engine. oc:tooltip.UpgradeTank=This upgrade provides a tank for fluid storage for robots and drones. Without one of these, they will not be able to store fluids internally. oc:tooltip.UpgradeTankController=This upgrade allows robots and drones more control in how they interacts with external tanks, and allows them to transfer fluids into and out of fluid tank items in their inventory. diff --git a/src/main/resources/assets/opencomputers/recipes/default.recipes b/src/main/resources/assets/opencomputers/recipes/default.recipes index c4eb8359d9..55df6114ca 100644 --- a/src/main/resources/assets/opencomputers/recipes/default.recipes +++ b/src/main/resources/assets/opencomputers/recipes/default.recipes @@ -324,6 +324,21 @@ signUpgrade { ["oc:circuitChip1", stickWood, "oc:circuitChip1"] [ingotIron, pistonStickyBase, ingotIron]] } +skinUpgrade1 { + input: [["oc:circuitChip1", leather, "oc:circuitChip1"] + [leather, chest, leather] + ["oc:circuitChip1", leather, "oc:circuitChip1"]] +} +skinUpgrade2 { + input: [["oc:circuitChip2", leather, "oc:circuitChip2"] + [leather, chest, leather] + ["oc:circuitChip2", leather, "oc:circuitChip2"]] +} +skinUpgrade3 { + input: [["oc:circuitChip3", leather, "oc:circuitChip3"] + [leather, chest, leather] + ["oc:circuitChip3", leather, "oc:circuitChip3"]] +} solarGeneratorUpgrade { input: [[blockGlass, blockGlass, blockGlass] ["oc:circuitChip3", blockLapis, "oc:circuitChip3"] diff --git a/src/main/resources/assets/opencomputers/textures/items/UpgradeSkin0.png b/src/main/resources/assets/opencomputers/textures/items/UpgradeSkin0.png new file mode 100644 index 0000000000..6733dbb8ab Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/items/UpgradeSkin0.png differ diff --git a/src/main/resources/assets/opencomputers/textures/items/UpgradeSkin1.png b/src/main/resources/assets/opencomputers/textures/items/UpgradeSkin1.png new file mode 100644 index 0000000000..140c694bbc Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/items/UpgradeSkin1.png differ diff --git a/src/main/resources/assets/opencomputers/textures/items/UpgradeSkin2.png b/src/main/resources/assets/opencomputers/textures/items/UpgradeSkin2.png new file mode 100644 index 0000000000..8364874a46 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/items/UpgradeSkin2.png differ diff --git a/src/main/scala/li/cil/oc/Constants.scala b/src/main/scala/li/cil/oc/Constants.scala index 061bec65ef..2f5dd9d257 100644 --- a/src/main/scala/li/cil/oc/Constants.scala +++ b/src/main/scala/li/cil/oc/Constants.scala @@ -147,6 +147,9 @@ object Constants { final val ServerTier2 = "server2" final val ServerTier3 = "server3" final val SignUpgrade = "signUpgrade" + final val SkinUpgradeTier1 = "skinUpgrade1" + final val SkinUpgradeTier2 = "skinUpgrade2" + final val SkinUpgradeTier3 = "skinUpgrade3" final val SolarGeneratorUpgrade = "solarGeneratorUpgrade" final val Tablet = "tablet" final val TabletCaseCreative = "tabletCaseCreative" diff --git a/src/main/scala/li/cil/oc/client/PacketHandler.scala b/src/main/scala/li/cil/oc/client/PacketHandler.scala index ccf46a956c..e81f88991d 100644 --- a/src/main/scala/li/cil/oc/client/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala @@ -519,6 +519,7 @@ object PacketHandler extends CommonPacketHandler { val stack = p.readItemStack() if (slot >= robot.getSizeInventory - robot.componentCount) { robot.info.components(slot - (robot.getSizeInventory - robot.componentCount)) = stack + robot.cacheSkinComponentsForRendering(stack) } else t.robot.setInventorySlotContents(slot, stack) case _ => // Invalid packet. 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 44a9aed712..51f18ae64b 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 @@ -1,23 +1,25 @@ package li.cil.oc.client.renderer.tileentity import com.google.common.base.Strings -import li.cil.oc.OpenComputers -import li.cil.oc.Settings +import li.cil.oc.{Constants, OpenComputers, Settings, api} 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.client.renderer.block.BlockRenderer.patchedRenderer +import li.cil.oc.client.renderer.block.{BlockRenderer, Print} import li.cil.oc.common.EventHandler +import li.cil.oc.common.component.UpgradeSkinComponent +import li.cil.oc.common.item.UpgradeSkin import li.cil.oc.common.tileentity +import li.cil.oc.common.tileentity.Robot import li.cil.oc.util.RenderState import net.minecraft.block.Block import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.GLAllocation -import net.minecraft.client.renderer.RenderBlocks -import net.minecraft.client.renderer.Tessellator -import net.minecraft.client.renderer.entity.RenderManager -import net.minecraft.client.renderer.entity.RendererLivingEntity +import net.minecraft.client.renderer.{GLAllocation, OpenGlHelper, RenderBlocks, Tessellator} +import net.minecraft.client.renderer.entity.{RenderItem, RenderManager, RendererLivingEntity} import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer +import net.minecraft.entity.item.EntityItem import net.minecraft.init.Items import net.minecraft.item.ItemBlock import net.minecraft.item.ItemStack @@ -283,8 +285,8 @@ object RobotRenderer extends TileEntitySpecialRenderer { val robot = proxy.robot val worldTime = entity.getWorldObj.getTotalWorldTime + f - GL11.glPushMatrix() GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) + GL11.glPushMatrix() GL11.glTranslated(x + 0.5, y + 0.5, z + 0.5) // If the move started while we were rendering and we have a reference to @@ -330,7 +332,13 @@ object RobotRenderer extends TileEntitySpecialRenderer { GL11.glTranslatef(-0.5f, -0.5f, -0.5f) val offset = timeJitter + worldTime / 20.0 - renderChassis(robot, offset) + + val pass = MinecraftForgeClient.getRenderPass + val renderedBodySkin = renderSkins(robot, f) + + if((!renderedBodySkin || !robot.hideBody) && pass == 0) { + renderChassis(robot, offset) + } if (!robot.renderingErrored && x * x + y * y + z * z < 24 * 24) { Option(robot.getStackInSlot(0)) match { @@ -434,7 +442,7 @@ object RobotRenderer extends TileEntitySpecialRenderer { case _ => } - if (MinecraftForgeClient.getRenderPass == 0) { + if (MinecraftForgeClient.getRenderPass == 0 && !robot.hideUpgrades) { 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)) @@ -475,7 +483,7 @@ object RobotRenderer extends TileEntitySpecialRenderer { GL11.glPopMatrix() val name = robot.name - if (Settings.get.robotLabels && MinecraftForgeClient.getRenderPass == 1 && !Strings.isNullOrEmpty(name) && x * x + y * y + z * z < RendererLivingEntity.NAME_TAG_RANGE) { + if (!robot.hideNameTag && Settings.get.robotLabels && MinecraftForgeClient.getRenderPass == 1 && !Strings.isNullOrEmpty(name) && x * x + y * y + z * z < RendererLivingEntity.NAME_TAG_RANGE) { GL11.glPushMatrix() // This is pretty much copy-pasta from the entity's label renderer. @@ -521,4 +529,109 @@ object RobotRenderer extends TileEntitySpecialRenderer { RenderState.checkError(getClass.getName + ".renderTileEntityAt: leaving") } + + def renderSkins(robot: Robot, f: Float): Boolean = { + val pass = MinecraftForgeClient.getRenderPass + + if(robot.skins(0).skin.isDefined || robot.skins(1).skin.isDefined || robot.skins(4).skin.isDefined) { // if(skinUpgrade.isDefined) { + val bodySkin = robot.skins(0) + val leftJ1Skin = robot.skins(1) + val leftJ2Skin = robot.skins(2) + val leftJ3Skin = robot.skins(3) + val rightJ1Skin = robot.skins(4) + val rightJ2Skin = robot.skins(5) + val rightJ3Skin = robot.skins(6) + GL11.glPushMatrix() + GL11.glTranslatef(0.5f, 0.53f, 0.5f) + GL11.glScalef(0.8f, 0.8f, 0.8f) + + if(bodySkin.skin.isDefined) { + renderSkin(bodySkin, robot, f, pass) + } + + if(leftJ1Skin.skin.isDefined) { + GL11.glPushMatrix() + GL11.glTranslatef(0.5f, 0f, 0f) + renderSkin(leftJ1Skin, robot, f, pass, 0.5f, 0, 0, false) + if(leftJ2Skin.skin.isDefined) { + GL11.glTranslatef(0.5f, 0f, 0f) + renderSkin(leftJ2Skin, robot, f, pass, 0.5f, 0, 0, false) + if(leftJ3Skin.skin.isDefined) { + GL11.glTranslatef(0.5f, 0f, 0f) + renderSkin(leftJ3Skin, robot, f, pass, 0.5f, 0, 0, false) + } + } + GL11.glPopMatrix() + } + + if(rightJ1Skin.skin.isDefined) { + GL11.glPushMatrix() + GL11.glTranslatef(-0.5f, 0f, 0f) + renderSkin(rightJ1Skin, robot, f, pass, -0.5f, 0, 0, true) + if(rightJ2Skin.skin.isDefined) { + GL11.glTranslatef(-0.5f, 0f, 0f) + renderSkin(rightJ2Skin, robot, f, pass, -0.5f, 0, 0, true) + if(rightJ3Skin.skin.isDefined) { + GL11.glTranslatef(-0.5f, 0f, 0f) + renderSkin(rightJ3Skin, robot, f, pass, -0.5f, 0, 0, true) + } + } + GL11.glPopMatrix() + } + + GL11.glPopMatrix() + } + robot.skins(0).skin.isDefined + } + + def renderSkin(skinComponent: UpgradeSkinComponent, robot: Robot, f: Float, pass: Int): Unit = { + renderSkin(skinComponent, robot, f, pass, 0, 0, 0, false) + } + + def renderSkin(skinComponent: UpgradeSkinComponent, robot: Robot, f: Float, pass: Int, offsetX: Float, offsetY: Float, offsetZ: Float, invertRot: Boolean): Unit = { + // val rotDir = if (invertRot) -1 else 1 + val rotDir = 1 + if(skinComponent.animationTicksTotal > 0) { + val remaining = (skinComponent.animationTicksLeft - f) / skinComponent.animationTicksTotal.toFloat + GL11.glRotatef(skinComponent.rotX - (skinComponent.rotX - skinComponent.oldRotX) * remaining, rotDir, 0, 0) + GL11.glRotatef(skinComponent.rotY - (skinComponent.rotY - skinComponent.oldRotY) * remaining, 0, rotDir, 0) + GL11.glRotatef(skinComponent.rotZ - (skinComponent.rotZ - skinComponent.oldRotZ) * remaining, 0, 0, rotDir) + } else { + GL11.glRotatef(skinComponent.rotX, rotDir, 0, 0) + GL11.glRotatef(skinComponent.rotY, 0, rotDir, 0) + GL11.glRotatef(skinComponent.rotZ, 0, 0, rotDir) + } + GL11.glTranslatef(offsetX, offsetY, offsetZ) + + val brightness = robot.world.getLightBrightnessForSkyBlocks(robot.x, robot.y, robot.z, 0) + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, brightness % 65536, brightness / 65536) + + val stack = skinComponent.skin.get + val itemRenderer = RenderManager.instance.itemRenderer + def renderPass(): Unit = { + val tint = stack.getItem.getColorFromItemStack(stack, pass) + val r = ((tint >> 16) & 0xFF) / 255f + val g = ((tint >> 8) & 0xFF) / 255f + val b = ((tint >> 0) & 0xFF) / 255f + GL11.glColor4f(r, g, b, 1) + itemRenderer.renderItem(Minecraft.getMinecraft.thePlayer, stack, pass) + } + + if (stack.getItem.requiresMultipleRenderPasses()) { + val passes = stack.getItem.getRenderPasses(stack.getItemDamage) + if (pass < passes) { + renderPass() + } + // Tile entities only get two render passes, so if items need + // more, we have to fake them. + if (pass == 1 && passes > 2) { + for (fakePass <- 2 until passes) { + renderPass() + } + } + } + else if (pass == 0) { + renderPass() + } + } } diff --git a/src/main/scala/li/cil/oc/common/component/UpgradeSkinComponent.scala b/src/main/scala/li/cil/oc/common/component/UpgradeSkinComponent.scala new file mode 100644 index 0000000000..e5475b21ee --- /dev/null +++ b/src/main/scala/li/cil/oc/common/component/UpgradeSkinComponent.scala @@ -0,0 +1,104 @@ +package li.cil.oc.common.component + +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound + +class UpgradeSkinComponent(pName:String) { + val name = pName + var skin = None : Option[ItemStack] + var rotX = 0f + var rotY = 0f + var rotZ = 0f + var oldRotX = 0f + var oldRotY = 0f + var oldRotZ = 0f + var animationTicksLeft = 0 + var animationTicksTotal = 0 + + def prepareRotationAnimation(pRotX: Float, pRotY: Float, pRotZ: Float, duration: Int): Unit = { + /*oldRotX = setOldRot(pRotX, rotX) + oldRotY = setOldRot(pRotY, rotY) + oldRotZ = setOldRot(pRotZ, rotZ) + rotX = floorMod(pRotX, 360) + rotY = floorMod(pRotY, 360) + rotZ = floorMod(pRotZ, 360)*/ + oldRotX = rotX + oldRotY = rotY + oldRotZ = rotZ + rotX = pRotX + rotY = pRotY + rotZ = pRotZ + animationTicksTotal = duration + animationTicksLeft = duration + } + + def setOldRot(newRot: Float, oldRot: Float): Float = { + if(newRot > 360) { + oldRot - 360 + } else if(newRot < 0) { + oldRot + 360 + } else { + oldRot + } + } + + def floorMod(a: Float, b: Int): Float = { + var am = a % b + if (am < 0) { + am += b + } + am + } + + def updateClient(): Unit = { + if(animationTicksLeft > 0) { + animationTicksLeft -= 1 + if(animationTicksLeft == 0) { + animationTicksTotal = 0 + } + } + } + + def save(nbt: NBTTagCompound): Unit = { + val tag = new NBTTagCompound() + + if(skin.isDefined) { + val skinNbt = new NBTTagCompound() + skin.get.writeToNBT(skinNbt) + tag.setTag("skin", skinNbt) + } + tag.setFloat("rotX", rotX) + tag.setFloat("rotY", rotY) + tag.setFloat("rotZ", rotZ) + tag.setFloat("oldRotX", oldRotX) + tag.setFloat("oldRotY", oldRotY) + tag.setFloat("oldRotZ", oldRotZ) + tag.setInteger("animationTicksLeft", animationTicksLeft) + tag.setInteger("animationTicksTotal", animationTicksTotal) + + nbt.setTag("skin" + name, tag) + } + + def load(nbt: NBTTagCompound): Unit = { + if(nbt.hasKey("skin" + name)) { + val tag = nbt.getCompoundTag("skin" + name) + + if(tag.hasKey("skin")) { + skin = Some(ItemStack.loadItemStackFromNBT(tag.getCompoundTag("skin"))) + } else { + skin = None + } + rotX = tag.getFloat("rotX") + rotY = tag.getFloat("rotY") + rotZ = tag.getFloat("rotZ") + + if(tag.hasKey("animationTicksLeft")) { + oldRotX = tag.getFloat("oldRotX") + oldRotY = tag.getFloat("oldRotY") + oldRotZ = tag.getFloat("oldRotZ") + animationTicksLeft = tag.getInteger("animationTicksLeft") + animationTicksTotal = tag.getInteger("animationTicksTotal") + } + } + } +} diff --git a/src/main/scala/li/cil/oc/common/init/Items.scala b/src/main/scala/li/cil/oc/common/init/Items.scala index 2acdc2e1b5..4a16d3b0ee 100644 --- a/src/main/scala/li/cil/oc/common/init/Items.scala +++ b/src/main/scala/li/cil/oc/common/init/Items.scala @@ -1,7 +1,6 @@ package li.cil.oc.common.init import java.util.concurrent.Callable - import cpw.mods.fml.common.registry.GameRegistry import li.cil.oc.Constants import li.cil.oc.OpenComputers @@ -14,8 +13,7 @@ import li.cil.oc.common.Loot import li.cil.oc.common.Tier import li.cil.oc.common.block.SimpleBlock import li.cil.oc.common.item -import li.cil.oc.common.item.Delegator -import li.cil.oc.common.item.UpgradeLeash +import li.cil.oc.common.item.{Delegator, UpgradeLeash, UpgradeSkin} import li.cil.oc.common.item.data.DroneData import li.cil.oc.common.item.data.HoverBootsData import li.cil.oc.common.item.data.MicrocontrollerData @@ -551,6 +549,11 @@ object Items extends ItemAPI { Recipes.addSubItem(new item.WirelessNetworkCard(multi, Tier.One), Constants.ItemName.WirelessNetworkCardTier1, "oc:wlanCard1") registerItem(new item.ComponentBus(multi, Tier.Four), Constants.ItemName.ComponentBusCreative) + // 1.8.4 + Recipes.addSubItem(new UpgradeSkin(multi, Tier.One), Constants.ItemName.SkinUpgradeTier1, "oc:skinUpgrade1") + Recipes.addSubItem(new item.UpgradeSkin(multi, Tier.Two), Constants.ItemName.SkinUpgradeTier2, "oc:skinUpgrade2") + Recipes.addSubItem(new item.UpgradeSkin(multi, Tier.Three), Constants.ItemName.SkinUpgradeTier3, "oc:skinUpgrade3") + // Register aliases. for ((k, v) <- aliases) { descriptors.getOrElseUpdate(k, descriptors(v)) diff --git a/src/main/scala/li/cil/oc/common/item/UpgradeSkin.scala b/src/main/scala/li/cil/oc/common/item/UpgradeSkin.scala new file mode 100644 index 0000000000..786258f633 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/item/UpgradeSkin.scala @@ -0,0 +1,9 @@ +package li.cil.oc.common.item + +class UpgradeSkin(val parent: Delegator, val tier: Int) extends traits.Delegate with traits.ItemTier { + override val unlocalizedName = super.unlocalizedName + tier + + override protected def tooltipName = Option(unlocalizedName) + + override protected def tooltipData = Seq(tier + 1) +} 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 14803b39d4..114143f805 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala @@ -1,7 +1,6 @@ package li.cil.oc.common.tileentity import java.util.UUID - import cpw.mods.fml.relauncher.Side import cpw.mods.fml.relauncher.SideOnly import li.cil.oc._ @@ -16,6 +15,7 @@ import li.cil.oc.client.gui import li.cil.oc.common.EventHandler import li.cil.oc.common.Slot import li.cil.oc.common.Tier +import li.cil.oc.common.component.UpgradeSkinComponent import li.cil.oc.common.inventory.InventoryProxy import li.cil.oc.common.inventory.InventorySelection import li.cil.oc.common.inventory.TankSelection @@ -88,6 +88,13 @@ class Robot extends traits.Computer with traits.PowerInformation with IFluidHand var selectedSlot = 0 + // Cache for rendering skin upgrade + var skins = Array(new UpgradeSkinComponent("body"), new UpgradeSkinComponent("leftJoint1"), new UpgradeSkinComponent("leftJoint2"), + new UpgradeSkinComponent("leftJoint3"), new UpgradeSkinComponent("rightJoint1"), new UpgradeSkinComponent("rightJoint2"), new UpgradeSkinComponent("rightJoint3")) + var hideBody = false + var hideNameTag = false + var hideUpgrades = false + override def setSelectedSlot(index: Int): Unit = { selectedSlot = index max 0 min mainInventory.getSizeInventory - 1 if (world != null) { @@ -309,7 +316,7 @@ class Robot extends traits.Computer with traits.PowerInformation with IFluidHand override def shouldRenderInPass(pass: Int) = true override def getRenderBoundingBox = - getBlockType.getCollisionBoundingBoxFromPool(world, x, y, z).expand(0.5, 0.5, 0.5) + getBlockType.getCollisionBoundingBoxFromPool(world, x, y, z).expand(2.5, 2.5, 2.5) // ----------------------------------------------------------------------- // @@ -324,6 +331,9 @@ class Robot extends traits.Computer with traits.PowerInformation with IFluidHand turnAxis = 0 } } + for(skin <- skins) { + skin.updateClient() + } super.updateEntity() if (isServer) { if (world.getTotalWorldTime % Settings.get.tickFrequency == 0) { @@ -463,6 +473,15 @@ class Robot extends traits.Computer with traits.PowerInformation with IFluidHand turnAxis = nbt.getByte("turnAxis") } connectComponents() + + var hasSkinComponent = false + (this.componentSlots ++ this.containerSlots).map(this.getStackInSlot).foreach(s => { + cacheSkinComponentsForRendering(s) + hasSkinComponent = true + }) + if(!hasSkinComponent) { + clearSkinComponentCache() + } } override def writeToNBTForClient(nbt: NBTTagCompound) = this.synchronized { @@ -690,6 +709,7 @@ class Robot extends traits.Computer with traits.PowerInformation with IFluidHand } override def setInventorySlotContents(slot: Int, stack: ItemStack) { + val wasSkinUpgrade = isComponentSlot(slot, stack) && isSkinUpgrade(getStackInSlot(slot)) if (slot < getSizeInventory - componentCount && (isItemValidForSlot(slot, stack) || stack == null)) { if (stack != null && stack.stackSize > 1 && isComponentSlot(slot, stack)) { super.setInventorySlotContents(slot, stack.splitStack(1)) @@ -698,9 +718,61 @@ class Robot extends traits.Computer with traits.PowerInformation with IFluidHand spawnStackInWorld(stack, Option(facing)) } } - else super.setInventorySlotContents(slot, stack) + else { + super.setInventorySlotContents(slot, stack) + if(isComponentSlot(slot, stack)) { + cacheSkinComponentsForRendering(stack) + } + } } else if (stack != null && stack.stackSize > 0 && !world.isRemote) spawnStackInWorld(stack, Option(ForgeDirection.UP)) + + if(!isSkinUpgrade(getStackInSlot(slot)) && wasSkinUpgrade) { + clearSkinComponentCache() + } + } + + def cacheSkinComponentsForRendering(stack: ItemStack): Unit = { + if(isSkinUpgrade(stack)) { + val compound = stack.getTagCompound + if(compound != null && compound.hasKey(Settings.namespace + "data")) { + val data = compound.getCompoundTag(Settings.namespace + "data") + skins(0).load(data) + skins(1).load(data) + skins(2).load(data) + skins(3).load(data) + skins(4).load(data) + skins(5).load(data) + skins(6).load(data) + if(data.hasKey("hideBody")) { + hideBody = data.getBoolean("hideBody") + } + if(data.hasKey("hideNameTag")) { + hideNameTag = data.getBoolean("hideNameTag") + } + if(data.hasKey("hideUpgrades")) { + hideUpgrades = data.getBoolean("hideUpgrades") + } + return + } + } + } + + def clearSkinComponentCache(): Unit = { + skins(0).skin = None + skins(1).skin = None + skins(2).skin = None + skins(3).skin = None + skins(4).skin = None + skins(5).skin = None + skins(6).skin = None + hideBody = true + hideNameTag = false + hideUpgrades = false + } + + private def isSkinUpgrade(stack: ItemStack): Boolean = { + stack != null && ((api.Items.get(stack) == api.Items.get(Constants.ItemName.SkinUpgradeTier1)) || (api.Items.get(stack) == api.Items.get(Constants.ItemName.SkinUpgradeTier2)) || (api.Items.get(stack) == api.Items.get(Constants.ItemName.SkinUpgradeTier3))) } override def isUseableByPlayer(player: EntityPlayer) = diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradeSkin.scala b/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradeSkin.scala new file mode 100644 index 0000000000..30bb594cd4 --- /dev/null +++ b/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradeSkin.scala @@ -0,0 +1,42 @@ +package li.cil.oc.integration.opencomputers + +import li.cil.oc.{Constants, api} +import li.cil.oc.api.driver.EnvironmentProvider +import li.cil.oc.api.driver.item.HostAware +import li.cil.oc.api.internal.Adapter +import li.cil.oc.api.network.EnvironmentHost +import li.cil.oc.common.{Slot, Tier, item} +import li.cil.oc.common.entity.Drone +import li.cil.oc.common.item.Delegator +import li.cil.oc.common.tileentity.Robot +import li.cil.oc.server.component +import net.minecraft.item.ItemStack + +object DriverUpgradeSkin extends Item with HostAware { + override def worksWith(stack: ItemStack) = isOneOf(stack, + api.Items.get(Constants.ItemName.SkinUpgradeTier1), + api.Items.get(Constants.ItemName.SkinUpgradeTier2), + api.Items.get(Constants.ItemName.SkinUpgradeTier3)) + + override def createEnvironment(stack: ItemStack, host: EnvironmentHost) = + if (host.world != null && host.world.isRemote) null + else host match { + case host: EnvironmentHost with Robot => new component.UpgradeSkin.Robot(host, tier(stack)) + case _ => null + } + + override def slot(stack: ItemStack) = Slot.Upgrade + + override def tier(stack: ItemStack) = Delegator.subItem(stack) match { + case Some(skin: item.UpgradeSkin) => skin.tier + case _ => Tier.One + } + + object Provider extends EnvironmentProvider { + override def getEnvironment(stack: ItemStack): Class[_] = + if (worksWith(stack)) + classOf[component.UpgradeSkin.Robot] + else null + } + +} diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala index c95d95be44..9a54f4fbd9 100644 --- a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala +++ b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala @@ -171,6 +171,7 @@ object ModOpenComputers extends ModProxy { api.Driver.add(DriverUpgradeTractorBeam) api.Driver.add(DriverUpgradeTrading) api.Driver.add(DriverUpgradeMF) + api.Driver.add(DriverUpgradeSkin) api.Driver.add(DriverAPU.Provider) api.Driver.add(DriverDataCard.Provider) @@ -229,6 +230,9 @@ object ModOpenComputers extends ModProxy { Constants.ItemName.NavigationUpgrade, Constants.ItemName.PistonUpgrade, Constants.ItemName.SolarGeneratorUpgrade, + Constants.ItemName.SkinUpgradeTier1, + Constants.ItemName.SkinUpgradeTier2, + Constants.ItemName.SkinUpgradeTier3, Constants.ItemName.TankUpgrade, Constants.ItemName.TractorBeamUpgrade, Constants.ItemName.LeashUpgrade, diff --git a/src/main/scala/li/cil/oc/server/component/UpgradeSkin.scala b/src/main/scala/li/cil/oc/server/component/UpgradeSkin.scala new file mode 100644 index 0000000000..d81f343c47 --- /dev/null +++ b/src/main/scala/li/cil/oc/server/component/UpgradeSkin.scala @@ -0,0 +1,334 @@ +package li.cil.oc.server.component + +import li.cil.oc.api.driver.DeviceInfo +import li.cil.oc.api.driver.DeviceInfo.{DeviceAttribute, DeviceClass} +import li.cil.oc.api.machine.{Arguments, Callback, Context} +import li.cil.oc.api.network._ +import li.cil.oc.api.{Network, internal, prefab} +import li.cil.oc.common.component.UpgradeSkinComponent +import li.cil.oc.common.tileentity +import li.cil.oc.server.component.traits.{InventoryAware, WorldAware} +import li.cil.oc.util.BlockPosition +import li.cil.oc.{Constants, Settings, api} +import net.minecraft.entity.item.EntityItem +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound + +import java.util +import scala.collection.convert.WrapAsJava._ + +object UpgradeSkin { + + trait Common extends DeviceInfo { + private final lazy val deviceInfo = Map( + DeviceAttribute.Class -> DeviceClass.Generic, + DeviceAttribute.Description -> "Skin Upgrade", + DeviceAttribute.Vendor -> Constants.DeviceInfo.DefaultVendor, + DeviceAttribute.Product -> "Skinner V1" + ) + + override def getDeviceInfo: util.Map[String, String] = deviceInfo + } + + // Drones are fancy enough on their own. At least for now. + /*class Drone(val host: EnvironmentHost with internal.Agent) extends prefab.ManagedEnvironment with Common { + override val node = Network.newNode(this, Visibility.Network). + withComponent("skin", Visibility.Neighbors). + create() + + // ----------------------------------------------------------------------- // + }*/ + + class Robot(val host: EnvironmentHost with tileentity.Robot, tier: Int) extends prefab.ManagedEnvironment with InventoryAware with WorldAware with Common { + override val node = Network.newNode(this, Visibility.Network). + withComponent("skin", Visibility.Neighbors). + withConnector(). + create() + + // ----------------------------------------------------------------------- // + + override def position = BlockPosition(host) + + override def inventory = host.mainInventory + + override def selectedSlot = host.selectedSlot + + override def selectedSlot_=(value: Int) = host.setSelectedSlot(value) + + private var skins = Array(new UpgradeSkinComponent("body"), new UpgradeSkinComponent("leftJoint1"), new UpgradeSkinComponent("leftJoint2"), + new UpgradeSkinComponent("leftJoint3"), new UpgradeSkinComponent("rightJoint1"), new UpgradeSkinComponent("rightJoint2"), new UpgradeSkinComponent("rightJoint3")) + private var hideBody = true + private var hideNameTag = false + private var hideUpgrades = false + + def swapSkin(skinComponent: UpgradeSkinComponent, onlyDeposit: Boolean): Boolean = { + var skin = skinComponent.skin + val stack = inventory.getStackInSlot(selectedSlot) + var extracted = None : Option[ItemStack] + var changed = false + var maxStackSize = inventory.getInventoryStackLimit + + if(stack != null) { + if(!onlyDeposit) { + extracted = Some(stack.splitStack(1)) + } + maxStackSize = math.min(maxStackSize, stack.getMaxStackSize) + } + + val shouldMerge = stack != null && stack.stackSize < maxStackSize && skin.isDefined && + stack.isItemEqual(skin.get) && ItemStack.areItemStackTagsEqual(stack, skin.get) + if(skin.isDefined && (stack == null || stack.stackSize == 0 || shouldMerge)) { + if(stack == null) { + inventory.setInventorySlotContents(selectedSlot, skin.get) + } else if(stack.stackSize == 0) { + inventory.setInventorySlotContents(selectedSlot, skin.get) + stack.stackSize += 1 + } else { + stack.stackSize += 1 + } + changed = true + skin = None + } + + if(!onlyDeposit) { + if (stack != null && skin.isEmpty && api.Items.get(extracted.get) == api.Items.get(Constants.BlockName.Print)) { + skin = extracted + changed = true + } else if (stack != null) { + stack.stackSize += 1 + } + + if (stack != null && stack.stackSize == 0) { + inventory.setInventorySlotContents(selectedSlot, null) + } + } + skinComponent.skin = skin + inventory.markDirty() + host.markDirty() + updateClient() + changed + } + + def setSkinRotation(skinComponent: UpgradeSkinComponent, args: Arguments): Array[AnyRef] = { + var rotX = 0f + var rotY = 0f + var rotZ = 0f + if(args.isInteger(1)) { + rotX = args.checkInteger(1) + } else if(args.isDouble(1)) { + rotX = args.checkDouble(1).toFloat + } else { + return result(Unit, "Number expected: rotX") + } + if(args.isInteger(2)) { + rotY = args.checkInteger(2) + } else if(args.isDouble(2)) { + rotY = args.checkDouble(2).toFloat + } else { + return result(Unit, "Number expected: rotY") + } + if(args.isInteger(3)) { + rotZ = args.checkInteger(3) + } else if(args.isDouble(3)) { + rotZ = args.checkDouble(3).toFloat + } else { + return result(Unit, "Number expected: rotZ") + } + + if(skinComponent.animationTicksLeft == 0) { + val maxRot = math.max(math.max(math.abs(rotX - skinComponent.oldRotX), math.abs(rotY - skinComponent.oldRotY)), math.abs(rotZ - skinComponent.oldRotZ)) + val moveTicks = math.max((Settings.get.turnDelay * 20).toInt, 1) // normalize the duration the rotation takes with the distance the rotation takes (90deg = turnDelay * 20) + skinComponent.prepareRotationAnimation(rotX, rotY, rotZ, moveTicks) + host.markDirty() + updateClient() + result(true) + } else { + result(Unit, "Skin is already rotating!") + } + } + + def hideSomething(apply: Boolean => Unit, args: Arguments): Array[AnyRef] = { + if(args.isBoolean(0)) { + apply(args.checkBoolean(0)) + } else { + apply(true) + } + host.markDirty() + updateClient() + result(true) + } + + private def updateClient(): Unit = host match { + case robot: internal.Robot => robot.synchronizeSlot(robot.componentSlot(node.address)) + case _ => + } + + def checkIfTierIsHighEnoughForPart(part: Int): Boolean = { + ((part == 2 || part == 5) && tier >= 1) || ((part == 3 || part == 6) && tier >= 2) || (part == 0 || part == 1 || part == 4) + } + + @Callback(doc = """function(part:number):boolean -- Swaps the skin of the specified part with the content of the currently selected inventory slot.""") + def swapSkin(context: Context, args: Arguments): Array[AnyRef] = { + val part = args.checkInteger(0) + if(checkIfTierIsHighEnoughForPart(part)) { + result(swapSkin(skins(part), false)) + } else { + result(Unit, part + " is not a valid part index") + } + } + + @Callback(doc = """function(part:number):string -- Returns the name of the skin of the specified part or nil of no skin is present for that part.""") + def getSkin(context: Context, args: Arguments): Array[AnyRef] = { + val part = args.checkInteger(0) + if(checkIfTierIsHighEnoughForPart(part)) { + result(if (skins(part).skin.isDefined) skins(part).skin.get.getDisplayName else null) + } else { + result(Unit, part + " is not a valid part index") + } + } + + @Callback(doc = """function(part:number):boolean -- Removes the skin from the specified part.""") + def removeSkin(context: Context, args: Arguments): Array[AnyRef] = { + val part = args.checkInteger(0) + if(checkIfTierIsHighEnoughForPart(part)) { + result(swapSkin(skins(part), true)) + } else { + result(Unit, part + " is not a valid part index") + } + } + + @Callback(doc = """function(part:number, rotX:number, rotY:number, rotZ:number[, blocking:boolean = true]):boolean -- Sets the rotation for the skin of the specified part.""") + def setSkinRotation(context: Context, args: Arguments): Array[AnyRef] = { + val part = args.checkInteger(0) + if(checkIfTierIsHighEnoughForPart(part)) { + if(skins(part).animationTicksLeft == 0) { + if (!node.tryChangeBuffer(-Settings.get.robotMoveCost)) { + result(Unit, "not enough energy") + } else { + val res = setSkinRotation(skins(part), args) + if (!args.isBoolean(4) || args.checkBoolean(4)) { + context.pause(Settings.get.moveDelay) + } + res + } + } else { + result(false) + } + } else { + result(Unit, part + " is not a valid part index") + } + } + + @Callback(doc = """function(part:number):number, number, number -- Returns the rotation of the skin of the specified part.""") + def getSkinRotation(context: Context, args: Arguments): Array[AnyRef] = { + val part = args.checkInteger(0) + if(checkIfTierIsHighEnoughForPart(part)) { + result(skins(part).rotX, skins(part).rotY, skins(part).rotZ) + } else { + result(Unit, part + " is not a valid part index") + } + } + + @Callback(doc = """function([hide:boolean=true]):boolean -- Sets if the body of the robot should be hidden when a body skin is applied.""") + def hideBody(context: Context, args: Arguments): Array[AnyRef] = { + hideSomething(b => hideBody = b, args) + } + + @Callback(doc = """function():boolean -- Returns if the body of the robot should be hidden when a body skin is applied.""") + def isBodyHidden(context: Context, args: Arguments): Array[AnyRef] = { + result(hideBody) + } + + @Callback(doc = """function([hide:boolean=true]):boolean -- Sets if the nametag of the robot should be hidden.""") + def hideNameTag(context: Context, args: Arguments): Array[AnyRef] = { + hideSomething(b => hideNameTag = b, args) + } + + @Callback(doc = """function():boolean -- Returns if the nametag of the robot should be hidden.""") + def isNameTagHidden(context: Context, args: Arguments): Array[AnyRef] = { + result(hideNameTag) + } + + @Callback(doc = """function([hide:boolean=true]):boolean -- Sets if the upgrades of the robot should be hidden.""") + def hideUpgrades(context: Context, args: Arguments): Array[AnyRef] = { + hideSomething(b => hideUpgrades = b, args) + } + + @Callback(doc = """function():boolean -- Returns if the upgrades of the robot should be hidden.""") + def isUpgradeHidden(context: Context, args: Arguments): Array[AnyRef] = { + result(hideUpgrades) + } + + override val canUpdate = true + + override def update(): Unit = { + super.update() + var shouldUpdateHost = false + for ( skin <- skins ) { + if(skin.animationTicksLeft > 0) { + skin.animationTicksLeft -= 1 + if(skin.animationTicksLeft == 0) { + skin.animationTicksTotal = 0 + shouldUpdateHost = true + } + } + } + + if(shouldUpdateHost) { + host.markDirty() + host.markChanged() + updateClient() + } + } + + override def save(nbt: NBTTagCompound) { + super.save(nbt) + for ( skin <- skins ) { + skin.save(nbt) + } + nbt.setBoolean("hideBody", hideBody) + nbt.setBoolean("hideNameTag", hideNameTag) + nbt.setBoolean("hideUpgrades", hideUpgrades) + } + + override def load(nbt: NBTTagCompound) { + super.load(nbt) + for ( skin <- skins ) { + skin.load(nbt) + } + if(nbt.hasKey("hideBody")) { + hideBody = nbt.getBoolean("hideBody") + } + if(nbt.hasKey("hideNameTag")) { + hideNameTag = nbt.getBoolean("hideNameTag") + } + if(nbt.hasKey("hideUpgrades")) { + hideUpgrades = nbt.getBoolean("hideUpgrades") + } + } + + override def onDisconnect(node: Node) { + super.onDisconnect(node) + if (node == this.node) { + def spawnStack(stack: ItemStack): Unit = { + val world = host.world + val entity = new EntityItem(world, host.xPosition, host.yPosition, host.zPosition, stack.copy()) + entity.motionY = 0.04 + entity.delayBeforeCanPickup = 5 + world.spawnEntityInWorld(entity) + } + for(skin <- skins) { + if(skin.skin.isDefined) { + spawnStack(skin.skin.get) + skin.skin = None + } + skin.rotX = 0 + skin.rotY = 0 + skin.rotZ = 0 + skin.animationTicksLeft = 0 + skin.animationTicksTotal = 0 + } + } + } + } +}