From 301d589eefbb287e0128f0b69c30953a67c61ced Mon Sep 17 00:00:00 2001 From: Bishop Date: Thu, 23 Jul 2026 19:11:53 -0500 Subject: [PATCH] Ready for primetime --- .../skill/construction/BuildOptionPlugin.java | 5 +- .../skill/construction/BuildRoomDialogue.java | 2 +- .../skill/construction/BuildingUtils.java | 20 ++- .../construction/ConstructionInterface.kt | 61 ++++++--- .../skill/construction/FlatpackListener.kt | 18 +-- .../global/skill/construction/HouseZone.java | 21 +++ .../construction/decoration/StaySeated.kt | 51 +++++++ .../decoration/workshop/WorkbenchListeners.kt | 127 +++++++++++------- 8 files changed, 222 insertions(+), 83 deletions(-) create mode 100644 Server/src/main/content/global/skill/construction/decoration/StaySeated.kt diff --git a/Server/src/main/content/global/skill/construction/BuildOptionPlugin.java b/Server/src/main/content/global/skill/construction/BuildOptionPlugin.java index fc75c068a..ee74e1f0d 100644 --- a/Server/src/main/content/global/skill/construction/BuildOptionPlugin.java +++ b/Server/src/main/content/global/skill/construction/BuildOptionPlugin.java @@ -13,7 +13,6 @@ import core.game.world.map.RegionManager; import core.plugin.Initializable; import core.plugin.Plugin; import core.tools.Log; -import core.tools.SystemLogger; import core.plugin.ClassScanner; import static core.api.ContentAPIKt.log; @@ -50,7 +49,7 @@ public final class BuildOptionPlugin extends OptionHandler { player.getDialogueInterpreter().open("con:removedec", object); return true; } - setAttribute(player, ConstructionInterface.attributeHsObject, node); + setAttribute(player, ConstructionInterface.attributeHotspotObj, node); if (BuildingUtils.isDoorHotspot(object)) { int[] pos = BuildingUtils.roomExists(player, object); if (pos != null) { @@ -83,7 +82,7 @@ public final class BuildOptionPlugin extends OptionHandler { int dy = h.getCurrentY() - object.getLocation().getChunkOffsetY(); Scenery primaryObj = RegionManager.getObject(object.getLocation().transform(dx, dy, 0)); if (primaryObj != null) { - setAttribute(player, ConstructionInterface.attributeHsObject, primaryObj); + setAttribute(player, ConstructionInterface.attributeHotspotObj, primaryObj); setAttribute(player, ConstructionInterface.attributeHotspot, h); BuildingUtils.openBuildInterface(player, linked[0]); } diff --git a/Server/src/main/content/global/skill/construction/BuildRoomDialogue.java b/Server/src/main/content/global/skill/construction/BuildRoomDialogue.java index 08d75d933..397342e25 100644 --- a/Server/src/main/content/global/skill/construction/BuildRoomDialogue.java +++ b/Server/src/main/content/global/skill/construction/BuildRoomDialogue.java @@ -107,7 +107,7 @@ public final class BuildRoomDialogue extends DialoguePlugin { stage = 2; return true; } - this.door = (Scenery) getAttribute(player, ConstructionInterface.attributeHsObject, null); + this.door = (Scenery) getAttribute(player, ConstructionInterface.attributeHotspotObj, null); int[] pos = BuildingUtils.getRoomPosition(player, door); roomX = pos[0]; roomY = pos[1]; diff --git a/Server/src/main/content/global/skill/construction/BuildingUtils.java b/Server/src/main/content/global/skill/construction/BuildingUtils.java index c98f18d64..e85eec085 100644 --- a/Server/src/main/content/global/skill/construction/BuildingUtils.java +++ b/Server/src/main/content/global/skill/construction/BuildingUtils.java @@ -63,7 +63,7 @@ public final class BuildingUtils { /** * The removing a decoration animation. */ - public static final Animation REMOVE_ANIMATION = Animation.create(3685); + private static final Animation REMOVE_ANIMATION = Animation.create(3685); /** * The plank item. @@ -139,9 +139,13 @@ public final class BuildingUtils { * @param object The object. */ public static void buildDecoration(final Player player, final Hotspot hotspot, final Decoration deco, final Scenery object) { + buildDecoration(player, hotspot, deco, object, false, false); + } + + public static void buildDecoration(final Player player, final Hotspot hotspot, final Decoration deco, final Scenery object, final boolean bypassCost, final boolean bypassXp) { final int nailAmount = deco.getNailAmount(); final NailType type = nailAmount > 0 ? NailType.get(player, nailAmount) : null; - if (nailAmount > 0 && type == null) { + if (nailAmount > 0 && type == null && !bypassCost) { player.getPacketDispatch().sendMessage("You don't have the right materials."); return; } @@ -165,7 +169,7 @@ public final class BuildingUtils { NailType nail = type; @Override public boolean pulse() { - if (nails > 0) { + if (nails > 0 && !bypassCost) { if (!type.isBend()) { player.getPacketDispatch().sendMessage("You use a nail."); nails--; @@ -189,12 +193,14 @@ public final class BuildingUtils { } } } - if (player.getInventory().remove(deco.getItems()) || player.isAdmin()) { + if (player.getInventory().remove(deco.getItems()) || bypassCost || player.isAdmin()) { setDecoration(player, r, room, hotspot, object, deco); - player.getSkills().addExperience(Skills.CONSTRUCTION, deco.getExperience(), true); + if (!bypassXp) { + player.getSkills().addExperience(Skills.CONSTRUCTION, deco.getExperience(), true); - if (getObjectIdsThatGiveFarmingExperience().contains(deco.getObjectId())) { - player.getSkills().addExperience(Skills.FARMING, deco.getExperience(), true); + if (getObjectIdsThatGiveFarmingExperience().contains(deco.getObjectId())) { + player.getSkills().addExperience(Skills.FARMING, deco.getExperience(), true); + } } player.unlock(); } diff --git a/Server/src/main/content/global/skill/construction/ConstructionInterface.kt b/Server/src/main/content/global/skill/construction/ConstructionInterface.kt index 110a09a4f..23a26e878 100644 --- a/Server/src/main/content/global/skill/construction/ConstructionInterface.kt +++ b/Server/src/main/content/global/skill/construction/ConstructionInterface.kt @@ -9,8 +9,8 @@ import core.game.node.scenery.Scenery import core.tools.Log /** - * Handles the creation of a decoration object. - * @author Emperor + * Handles three interfaces related to the Construction skill, namely those which allow a player to build decorations, + * toggle building mode/expel guests, and select a room to add to the POH. * @author Bishop */ @@ -21,41 +21,57 @@ class ConstructionInterface : InterfaceListener { const val pohMenuInterface = 398 const val roomInterface = 402 - const val attributeHotspot = "con:hotspot" - const val attributeHsObject = "con:hsobject" + const val attributeHotspot = "con:hotspot" + const val attributeHotspotObj = "con:hsobject" + + // Register furniture here when it is only obtainable by upgrading, to send the correct rejection message. + val upgradeOnly = arrayOf( + Decoration.WORKBENCH_WITH_VICE, + Decoration.WORKBENCH_WITH_LATHE, + ) } override fun defineInterfaceListeners() { + /** + * Handles the interface for selecting a decoration to build. + */ on(decorationInterface) { player, _, _, buttonID, slot, _ -> when (buttonID) { 132 -> { val hotspot = getAttribute(player, attributeHotspot, null) - val `object` = getAttribute(player, attributeHsObject, null) + val hotspotObj = getAttribute(player, attributeHotspotObj, null) val flatpackMode = getAttribute(player, WorkbenchListeners.attributeFlatpackMode, false) if (!flatpackMode) { closeInterface(player) } - if ((hotspot == null || `object` == null) && !flatpackMode) { - log(this.javaClass, Log.ERR, "Failed building decoration $hotspot : $`object`") + if ((hotspot == null || hotspotObj == null) && !flatpackMode) { + log(this.javaClass, Log.ERR, "Failed building decoration $hotspot : $hotspotObj") return@on false } - val slot = (if (slot % 2 != 0) 4 else 0) + (slot shr 1) + val decoIndex = (if (slot % 2 != 0) 4 else 0) + (slot shr 1) val buildHotspot = if (flatpackMode) { getAttribute(player, WorkbenchListeners.attributeSelection, null)?: return@on false } else { hotspot!!.hotspot } - if (slot >= buildHotspot.decorations.size) { - log(this.javaClass, Log.ERR, "Failed building decoration " + slot + "/" + buildHotspot.decorations.size) + if (decoIndex >= buildHotspot.decorations.size) { + log(this.javaClass, Log.ERR, "Failed building decoration $decoIndex/${buildHotspot.decorations.size}") return@on false } - val deco = buildHotspot.decorations[slot] + val deco = buildHotspot.decorations[decoIndex] if (!player.isAdmin) { if (getDynLevel(player, Skills.CONSTRUCTION) < deco.level) { - sendMessage(player, "You need to have a Construction level of " + deco.level + " to build that.") + closeInterface(player) + sendMessage(player, "You need to have a Construction level of ${deco.level} to build that.") return@on true } - if (!player.inventory.containsItems(*deco.items)) { // search for contentAPI way to do this + if (deco in upgradeOnly) { + closeInterface(player) + sendMessage(player, "That can only be built by upgrading the previous piece of furniture.") + return@on true + } + if (!player.inventory.containsItems(*deco.items)) { // No clean ContentAPI substitute for this + closeInterface(player) sendMessage(player, "You don't have the right materials.") return@on true } @@ -76,20 +92,22 @@ class ConstructionInterface : InterfaceListener { } continue } - if (!player.inventory.contains(tool, 1)) { + if (!inInventory(player, tool)) { + closeInterface(player) sendMessage(player, "You need a ${ItemDefinition.forId(tool).name} to build this.") return@on true } } - if (flatpackMode && WorkbenchListeners.getBenchLevel(getAttribute(player, WorkbenchListeners.attributeWorkbenchId, 0)) < deco.level) { - sendMessage(player, "You need a better workbench to make this flatpack.") // TODO: find authentic dialogue + if (flatpackMode && WorkbenchListeners.getBenchLevel(getAttribute(player, WorkbenchListeners.attributeWorkbenchId, 20)) < deco.level) { + closeInterface(player) + sendMessage(player, "You cannot make this with this workbench.") // Placeholder return@on true } } if (flatpackMode) { WorkbenchListeners.produceFlatpack(player, deco) } else { - BuildingUtils.buildDecoration(player, hotspot, deco, `object`) + BuildingUtils.buildDecoration(player, hotspot, deco, hotspotObj) } return@on true } @@ -97,6 +115,9 @@ class ConstructionInterface : InterfaceListener { } } + /** + * Handles the interface for managing building mode and POH guests. + */ on(pohMenuInterface) { player, _, _, buttonID, _, _ -> when (buttonID) { 14 -> { @@ -123,12 +144,14 @@ class ConstructionInterface : InterfaceListener { } } + /** + * Handles the interface for selecting a new room to add to a POH. + */ on(roomInterface) { player, _, _, buttonID, _, _ -> val index = buttonID - 160 log(this.javaClass, Log.FINE, "BuildRoom Interface Index: $index") if (index > -1 && index < RoomProperties.values().size) { - // ContentAPI impl won't work for this - player.dialogueInterpreter.open("con:room", RoomProperties.values()[index]) + player.dialogueInterpreter.open("con:room", RoomProperties.values()[index]) // ContentAPI impl won't work for this } return@on true } diff --git a/Server/src/main/content/global/skill/construction/FlatpackListener.kt b/Server/src/main/content/global/skill/construction/FlatpackListener.kt index 7e71991ac..d502415d7 100644 --- a/Server/src/main/content/global/skill/construction/FlatpackListener.kt +++ b/Server/src/main/content/global/skill/construction/FlatpackListener.kt @@ -1,6 +1,5 @@ package content.global.skill.construction -import content.global.skill.construction.BuildingUtils.buildDecoration import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener @@ -16,7 +15,7 @@ import org.rs09.consts.Items */ class FlatpackListener : InteractionListener { - val flatpacks = Decoration.values().map { it.flatpackItemID }.toIntArray() + val flatpacks = Decoration.values().map { it.flatpackItemID }.filter { it != -1 }.toIntArray() val hotspots = BuildHotspot.values().map { it.objectId }.toIntArray() override fun defineListeners() { @@ -27,20 +26,23 @@ class FlatpackListener : InteractionListener { } } - private fun buildFlatpackOnHotspot(player: Player, used: Item, with: Scenery):Boolean { + private fun buildFlatpackOnHotspot(player: Player, used: Item, with: Scenery): Boolean { val hotspotUsed = player.houseManager.getHotspot(with) val decorationUsed = Decoration.forFlatpackItemId(used.id) - if(!hotspotUsed.hotspot.decorations.contains(decorationUsed)) { + if (!hotspotUsed.hotspot.decorations.contains(decorationUsed)) { sendMessage(player, "You can't build that here.") return false } - if (!player.inventory.containsItems(Item(Items.HAMMER_2347),Item(Items.SAW_8794))) { + if (!inInventory(player, Items.HAMMER_2347) || !inInventory(player, Items.SAW_8794)) { sendMessage(player, "You need a hammer and a saw to build this.") return false } - buildDecoration(player, hotspotUsed, decorationUsed, with.asScenery()) - return true - + if (removeItem(player, used)) { + BuildingUtils.buildDecoration(player, hotspotUsed, decorationUsed, with.asScenery(), true, true) + return true + } else { + return false + } } } \ No newline at end of file diff --git a/Server/src/main/content/global/skill/construction/HouseZone.java b/Server/src/main/content/global/skill/construction/HouseZone.java index 31a57f6f8..1923e4f66 100644 --- a/Server/src/main/content/global/skill/construction/HouseZone.java +++ b/Server/src/main/content/global/skill/construction/HouseZone.java @@ -3,6 +3,9 @@ package content.global.skill.construction; import core.api.Container; import core.game.world.map.Location; +import core.net.packet.PacketRepository; +import core.net.packet.context.PlayerContext; +import core.net.packet.out.ClearMinimapFlag; import org.rs09.consts.Items; import core.game.node.entity.Entity; import core.game.node.entity.player.Player; @@ -12,6 +15,7 @@ import core.game.world.map.Region; import core.game.system.task.Pulse; import core.game.world.map.zone.ZoneType; +import content.global.skill.construction.decoration.workshop.WorkbenchListeners; import static core.api.ContentAPIKt.*; /** @@ -80,6 +84,23 @@ public final class HouseZone extends MapZone { return super.enter(e); } + @Override + public boolean move(Entity e, Location from, Location to) { + if (e instanceof Player) { + Player p = (Player) e; + if (getAttribute(p, WorkbenchListeners.attributeSitting, false)) { + p.getPulseManager().clear(); + p.getWalkingQueue().reset(); + PacketRepository.send(ClearMinimapFlag.class, new PlayerContext(p)); + if (!getAttribute(p, WorkbenchListeners.attributeStandingUp, false)) { + WorkbenchListeners.standUpFromWorkbench(p); + } + return false; + } + } + return super.move(e, from, to); + } + @Override public boolean death(Entity e, Entity killer) { if (e instanceof Player) { diff --git a/Server/src/main/content/global/skill/construction/decoration/StaySeated.kt b/Server/src/main/content/global/skill/construction/decoration/StaySeated.kt new file mode 100644 index 000000000..65f8b7000 --- /dev/null +++ b/Server/src/main/content/global/skill/construction/decoration/StaySeated.kt @@ -0,0 +1,51 @@ +package content.global.skill.construction.decoration + +import content.global.skill.construction.decoration.workshop.WorkbenchListeners +import core.api.animate +import core.api.getScenery +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.system.timer.PersistTimer +import core.game.system.timer.RSTimer +import org.rs09.consts.Scenery +import kotlin.collections.get + +/** + * Makes a player stay seated by reapplying the current animation occasionally. + * An alternative to some features of ChairBenchPlugin. + * Written with workbench stools in mind. + * @author Bishop + */ + +class StaySeated : PersistTimer(10 /*ticks*/, "con:sitting", isSoft = true) { + lateinit var player: Player + + private val standAnim = 808 // Same as AppearanceCache.STAND_ANIM + + // Register new chairs and animations here + private val sittables = mapOf( + Scenery.STOOL_13719 to WorkbenchListeners.animationSit, // wooden workbench stool + Scenery.STOOL_13720 to WorkbenchListeners.animationSitOak, // oak workbench stool + ) + + override fun run(entity: Entity): Boolean { + player = entity as Player + val localObj = getScenery(player.location)?.id + // Don't interrupt a player doing a different animation unless it's the idle animation + val currentAnimId = player.animator.animation?.id ?: -1 + if (localObj in sittables) { + if (currentAnimId != sittables[localObj] && + currentAnimId != standAnim && + currentAnimId != -1) { + return true + } + animate(player, sittables[localObj]) + return true + } + return false + } + + override fun getTimer(vararg args: Any): RSTimer { + return StaySeated() + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/construction/decoration/workshop/WorkbenchListeners.kt b/Server/src/main/content/global/skill/construction/decoration/workshop/WorkbenchListeners.kt index ddb42b10f..478152370 100644 --- a/Server/src/main/content/global/skill/construction/decoration/workshop/WorkbenchListeners.kt +++ b/Server/src/main/content/global/skill/construction/decoration/workshop/WorkbenchListeners.kt @@ -1,6 +1,7 @@ package content.global.skill.construction.decoration.workshop import content.global.skill.construction.* +import content.global.skill.construction.decoration.StaySeated import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener @@ -10,7 +11,6 @@ import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills import core.game.node.item.Item import core.game.world.map.Direction -import core.game.world.update.flag.context.Animation import core.tools.ticksToCycles import org.rs09.consts.Items import org.rs09.consts.Scenery @@ -27,9 +27,10 @@ class WorkbenchListeners : InteractionListener, InterfaceListener { const val attributeWorkbenchId = "con:workbench:id" const val attributeFlatpackMode = "con:workbench:active" - const val attributeDirections = "con:workbench:directions" + const val attributeDirections = "con:workbench:directions" const val attributeSelection = "con:workbench:hotspot" const val attributeSitting = "con:workbench:sitting" + const val attributeStandingUp = "con:workbench:standing" const val animationSitDown = 4103 const val animationStandUp = 4105 @@ -47,70 +48,89 @@ class WorkbenchListeners : InteractionListener, InterfaceListener { ) private val stools = intArrayOf( - Scenery.STOOL_13719, // wooden - Scenery.STOOL_13720, // oak + Scenery.STOOL_13719, // wooden stool, bottom 2 tiers + Scenery.STOOL_13720, // oak stool, top 3 tiers ) private val decoCategories = arrayOf( - BuildHotspot.CHAIRS_1, // 111 - BuildHotspot.BOOKCASE, // 112 - BuildHotspot.BARRELS, // 113 - BuildHotspot.KITCHEN_TABLE, // 114 - BuildHotspot.DINING_TABLE, // 115 - BuildHotspot.DINING_BENCH_1, // 116 - BuildHotspot.BED, // 117 - BuildHotspot.DRESSER, // 118 - BuildHotspot.DRAWERS, // 119 - BuildHotspot.CLOCK, // 120 - BuildHotspot.CAPE_RACK, // 121 - BuildHotspot.MAGIC_WARDROBE, // 122 - BuildHotspot.ARMOUR_CASE, // 123 - BuildHotspot.TREASURE_CHEST, // 124 - BuildHotspot.COSTUME_BOX, // 125 - BuildHotspot.TOY_BOX, // 126 + BuildHotspot.CHAIRS_1, // ButtonID 111 + BuildHotspot.BOOKCASE, // ButtonID 112 + BuildHotspot.BARRELS, // ButtonID 113 + BuildHotspot.KITCHEN_TABLE, // ButtonID 114 + BuildHotspot.DINING_TABLE, // ButtonID 115 + BuildHotspot.DINING_BENCH_1, // ButtonID 116 + BuildHotspot.BED, // ButtonID 117 + BuildHotspot.DRESSER, // ButtonID 118 + BuildHotspot.DRAWERS, // ButtonID 119 + BuildHotspot.CLOCK, // ButtonID 120 + BuildHotspot.CAPE_RACK, // ButtonID 121 + BuildHotspot.MAGIC_WARDROBE, // ButtonID 122 + BuildHotspot.ARMOUR_CASE, // ButtonID 123 + BuildHotspot.TREASURE_CHEST, // ButtonID 124 + BuildHotspot.COSTUME_BOX, // ButtonID 125 + BuildHotspot.TOY_BOX, // ButtonID 126 ) /** - * Returns the maximum construction level of decoration that can be built at a given workbench by id + * Returns the maximum construction level of decoration that can be built at a given workbench by id. */ fun getBenchLevel(workbenchId: Int): Int { - return (workbenches.indexOf(workbenchId) + 1) * 20 + return ((workbenches.indexOf(workbenchId) + 1) * 20) } + /** + * Animates the player crafting a flatpack, and exchanges its materials for the flatpack item. + * Flatpacks are free to produce for players with admin rights. + */ fun produceFlatpack(player: Player, deco: Decoration) { val reward = deco.flatpackItemID val animation = if (getScenery(player.location)?.id == Scenery.STOOL_13720) { animationCraftOak } else { animationCraft } if (reward == -1) return if (removeItemsIfPlayerHasEnough(player, *deco.items) || player.isAdmin) { closeInterface(player) - lock(player, Animation(animation).duration) + lock(player, getAnimation(animation).duration) animate(player, animation) rewardXP(player, Skills.CONSTRUCTION, deco.experience.toDouble()) addItemOrDrop(player, reward) } } - private fun standUp(player: Player) { + /** + * Animates the player getting up from the workbench's stool. + * This occurs on the player's first attempt at movement off of the workbench. + */ + @JvmStatic + fun standUpFromWorkbench(player: Player) { val directions = getAttribute(player, attributeDirections, arrayOf(Direction.WEST, Direction.NORTH, Direction.EAST)) - lock(player, animationStandUp) + val animation = if (getScenery(player.location)?.id == Scenery.STOOL_13720) { animationSitOak } else { animationSit } + setAttribute(player, attributeStandingUp, true) + lock(player, 1) + animate(player, animation) queueScript(player, 0, QueueStrength.SOFT) { forceMove(player, player.location, player.location.transform(directions[2], 1), - 0, ticksToCycles(1), directions[2], animationStandUp) - removeAttributes(player, attributeSitting, attributeDirections) + 0, ticksToCycles(1), directions[2], animationStandUp) { + removeAttributes(player, attributeDirections, attributeSitting, attributeStandingUp) + removeTimer(player) + } return@queueScript stopExecuting(player) } } } override fun defineListeners() { + /** + * Animates the player sitting down at the workbench, and opens the workbench interface. + */ on(workbenches, IntType.SCENERY, "work-at") { player, node -> setAttribute(player, attributeWorkbenchId, node.id) + // Skips the animation if the player is already sitting down. if (getAttribute(player, attributeSitting, false)) { val animation = if (getScenery(player.location)?.id == Scenery.STOOL_13720) { animationSitOak } else { animationSit } animate(player, animation) openInterface(player, workbenchInterface) return@on true } + // Ascertains the room rotation from where the stool is, to coordinate animations val baseX = node.location.x - node.location.chunkOffsetX val baseY = node.location.y - node.location.chunkOffsetY for (x in 3..4) { @@ -126,8 +146,10 @@ class WorkbenchListeners : InteractionListener, InterfaceListener { setAttribute(player, attributeDirections, directions) forceMove(player, player.location, player.location.transform(directions[0], 1), 0, ticksToCycles(1), directions[1], animationSitDown) { - animate(player, if (obj.id == Scenery.STOOL_13720) animationSitOak else animationSit) + val animation = if (obj.id == Scenery.STOOL_13720) { animationSitOak } else { animationSit } + animate(player, animation) setAttribute(player, attributeSitting, true) + registerTimer(player, StaySeated()) openInterface(player, workbenchInterface) } return@on true @@ -137,31 +159,43 @@ class WorkbenchListeners : InteractionListener, InterfaceListener { return@on true } + /** + * Handles upgrading a workbench with the minimenu option. + */ on(workbenches, IntType.SCENERY, "upgrade") { player, node -> - if (!inInventory(player, Items.OAK_PLANK_8778, 2) || !inInventory(player, Items.STEEL_BAR_2353)) { - sendDialogue(player, "You need two oak planks and a steel bar to upgrade this workbench.") // TODO: find authentic dialogue + // I could not find any video of how this looks or what the messages are, so all messages in this listener are placeholders. + val hotspot = Hotspot(BuildHotspot.WORKBENCH, 3, 4, 4, 4) + val nodeObj = node as core.game.node.scenery.Scenery + val resultDeco = Decoration.forObjectId(node.id + 1) + if (!player.houseManager.isBuildingMode) { + sendMessage(player, "You have to be in building mode to do this.") // I don't actually know if this is true but better safe than sorry. return@on true } - if (removeItem(player, Item(Items.OAK_PLANK_8778, 2)) && removeItem(player, Items.STEEL_BAR_2353)) { - val hotspotSearchTile = node.location - // only the 2nd and 3rd best workbenches have upgrade options - val resultDeco = if (node.id == Scenery.WORKBENCH_13706) { // steel framed - Decoration.forObjectId(Scenery.WORKBENCH_13707) // with vice - } else { - Decoration.forObjectId(Scenery.WORKBENCH_13708) // with lathe - } + if (getDynLevel(player, Skills.CONSTRUCTION) < resultDeco.level) { + sendMessage(player, "You need a Construction level of ${resultDeco.level} to upgrade this.") + return@on true + } + if (!inInventory(player, Items.OAK_PLANK_8778, 2) || !inInventory(player, Items.STEEL_BAR_2353)) { + sendMessage(player, "You need two oak planks and a steel bar to upgrade this.") + return@on true + } + if (!inInventory(player, Items.HAMMER_2347) || !inInventory(player, Items.SAW_8794)) { + sendMessage(player, "You need a hammer and a saw to upgrade this.") + return@on true + } + if (removeItemsIfPlayerHasEnough(player, Item(Items.OAK_PLANK_8778, 2), Item(Items.STEEL_BAR_2353))) { queueScript(player, 0, QueueStrength.SOFT) { stage -> when (stage) { 0 -> { - lock(player, BuildingUtils.REMOVE_ANIMATION.duration + BuildingUtils.BUILD_MID_ANIM.duration) - BuildingUtils.removeDecoration(player, node as core.game.node.scenery.Scenery) - return@queueScript delayScript(player, BuildingUtils.REMOVE_ANIMATION.duration) + lock(player, BuildingUtils.BUILD_MID_ANIM.duration) + // Replaces the workbench with the upgraded form in HouseManager but not immediately in the instance + BuildingUtils.buildDecoration(player, hotspot, resultDeco, nodeObj, true, false) + return@queueScript delayScript(player, BuildingUtils.BUILD_MID_ANIM.duration) } 1 -> { - val hotspot = Hotspot(BuildHotspot.WORKBENCH, 3, 4, 4, 4) - val hotspotObj = getScenery(hotspotSearchTile) - BuildingUtils.buildDecoration(player, hotspot, resultDeco, hotspotObj) - return@queueScript delayScript(player, BuildingUtils.BUILD_MID_ANIM.duration) + // Temporary replacement until POH can rebuild on next entrance with the correct workbench + replaceScenery(nodeObj, resultDeco.objectId, -1) + return@queueScript stopExecuting(player) } else -> return@queueScript stopExecuting(player) } @@ -172,6 +206,9 @@ class WorkbenchListeners : InteractionListener, InterfaceListener { } override fun defineInterfaceListeners() { + /** + * Directs the player to the appropriate decoration building interface. + */ on(workbenchInterface) { player, _, _, buttonID, _, _ -> val index = buttonID - 111 if (index < 0 || index >= decoCategories.size) return@on false