diff --git a/Server/src/main/content/minigame/blastfurnace/BFBeltOre.kt b/Server/src/main/content/minigame/blastfurnace/BFBeltOre.kt new file mode 100644 index 000000000..650696030 --- /dev/null +++ b/Server/src/main/content/minigame/blastfurnace/BFBeltOre.kt @@ -0,0 +1,61 @@ +package content.minigame.blastfurnace + +import core.api.animate +import core.api.queueScript +import core.api.teleport +import core.game.interaction.QueueStrength +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.world.map.Direction +import core.game.world.map.Location +import org.json.simple.JSONObject + +class BFBeltOre (val player: Player, val id: Int, val amount: Int, var location: Location, var npcInstance: NPC? = null) { + val state = BlastFurnace.getPlayerState(player) + + fun tick(): Boolean { + if (location == ORE_END_LOC && npcInstance != null) { + state.container.addOre(id, amount) + npcInstance?.clear() + npcInstance = null + return true + } + else if (npcInstance != null) { + location = location.transform(Direction.SOUTH, 1) + teleport(npcInstance!!, location) + + if (location == ORE_END_LOC) { + val npc = npcInstance!! + queueScript(npc, 1, QueueStrength.STRONG) {_ -> + animate(npc, ORE_DEPOSIT_ANIM) + return@queueScript true + } + } + } + return false + } + + fun createNpc() { + if (npcInstance != null) return + val npcId = BlastFurnace.getNpcForOre(id) + val npc = NPC.create(npcId, location) + npc.isWalks = false + npc.init() + + npcInstance = npc + } + + fun toJson(): JSONObject { + val root = JSONObject() + root["id"] = id.toString() + root["amount"] = amount.toString() + root["location"] = location.toString() + return root + } + + companion object { + val ORE_START_LOC = Location.create(1942, 4966, 0) + val ORE_END_LOC = Location.create(1942, 4963, 0) + val ORE_DEPOSIT_ANIM = 2434 + } +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/blastfurnace/BFOreContainer.kt b/Server/src/main/content/minigame/blastfurnace/BFOreContainer.kt new file mode 100644 index 000000000..5ed5c2cc6 --- /dev/null +++ b/Server/src/main/content/minigame/blastfurnace/BFOreContainer.kt @@ -0,0 +1,194 @@ +package content.minigame.blastfurnace + +import content.global.skill.smithing.smelting.Bar +import content.minigame.blastfurnace.BlastConsts.BAR_LIMIT +import content.minigame.blastfurnace.BlastFurnace.Companion.getBarForOreId +import content.minigame.blastfurnace.BlastFurnace.Companion.getNeededCoal +import core.game.node.item.Item +import org.json.simple.JSONArray +import org.json.simple.JSONObject +import org.rs09.consts.Items + +class BFOreContainer { + private var coalRemaining = 0 //this is the actual important value needed for the calculations and is unit tested + private var ores = Array(BlastConsts.ORE_LIMIT * 2) {-1} + private var barAmounts = Array(9) {0} + + fun addCoal(amount: Int) : Int { + val maxAdd = BlastConsts.COAL_LIMIT - coalRemaining + val toAdd = amount.coerceAtMost(maxAdd) + coalRemaining += toAdd + return amount - toAdd + } + + fun coalAmount() : Int { + return coalRemaining + } + + fun addOre (id: Int, amount: Int): Int { + if (id == Items.COAL_453) + return addCoal(amount) + + var limit = BlastConsts.ORE_LIMIT + if (getBarForOreId(id, -1, 99) == Bar.BRONZE) + limit *= 2 + + var amountLeft = amount + var maxAdd = getAvailableSpace(id, 99) + for (i in 0 until limit) { + if (ores[i] == -1) { + ores[i] = id + if (--amountLeft == 0 || --maxAdd == 0) break + } + } + return amountLeft + } + + fun getOreAmount (id: Int) : Int { + if (id == Items.COAL_453) + return coalAmount() + + var oreCount = 0 + for (i in 0 until BlastConsts.ORE_LIMIT) { + if (ores[i] == id) oreCount++ + } + return oreCount + } + + fun indexOfOre (id: Int) : Int { + for (i in ores.indices) + if (ores[i] == id) return i + return -1 + } + + fun getOreAmounts() : HashMap { + val map = HashMap() + for (ore in ores) { + if (ore == -1) break + map[ore] = (map[ore] ?: 0) + 1 + } + return map + } + + fun convertToBars(level: Int = 99) : Double { + val newOres = Array(BlastConsts.ORE_LIMIT * 2) {-1} + var oreIndex = 0 + var xpReward = 0.0 + for (i in 0 until BlastConsts.ORE_LIMIT) { + val bar = getBarForOreId(ores[i], coalRemaining, level) + + if (bar == null) { + ores[i] = -1 + continue + } + + if (barAmounts[bar.ordinal] >= BAR_LIMIT) { + newOres[oreIndex++] = ores[i] + continue + } + + val coalNeeded = getNeededCoal(bar) + + //special handling for bronze bar edge case, no other ore does this. + if (bar == Bar.BRONZE) { + val indexOfComplement = when (ores[i]) { + Items.COPPER_ORE_436 -> indexOfOre(Items.TIN_ORE_438) + Items.TIN_ORE_438 -> indexOfOre(Items.COPPER_ORE_436) + else -> -1 + } + if (indexOfComplement == -1) { + newOres[oreIndex++] = ores[i] + continue + } + ores[indexOfComplement] = -1 + } + + if (coalRemaining >= coalNeeded) { + coalRemaining -= coalNeeded + ores[i] = -1 + barAmounts[bar.ordinal]++ + xpReward += bar.experience + } else { + newOres[oreIndex++] = ores[i] + } + } + ores = newOres + return xpReward + } + + fun getBarAmount (bar: Bar) : Int { + return barAmounts[bar.ordinal] + } + + fun getTotalBarAmount() : Int { + var total = 0 + for (i in barAmounts) total += i + return total + } + + fun takeBars (bar: Bar, amount: Int) : Item? { + val amt = amount.coerceAtMost(barAmounts[bar.ordinal]) + if (amt == 0) return null + + barAmounts[bar.ordinal] -= amt + return Item(bar.product.id, amt) + } + + fun getAvailableSpace (ore: Int, level: Int = 99) : Int { + if (ore == Items.COAL_453) + return BlastConsts.COAL_LIMIT - coalRemaining + + var freeSlots = 0 + val bar = getBarForOreId(ore, coalRemaining, level)!! + val oreAmounts = HashMap() + for (i in 0 until BlastConsts.ORE_LIMIT) + if (ores[i] == -1) { + var oreLimit = BlastConsts.ORE_LIMIT + if (bar == Bar.BRONZE) oreLimit *= 2 + freeSlots = oreLimit - i + break + } + else oreAmounts[ores[i]] = (oreAmounts[ores[i]] ?: 0) + 1 + + val currentAmount = oreAmounts[ore] ?: 0 + freeSlots = (BlastConsts.ORE_LIMIT - currentAmount).coerceAtMost(freeSlots) + + return (freeSlots - getBarAmount(bar)).coerceAtLeast(0) + } + + fun hasAnyOre() : Boolean { + return ores[0] != -1 + } + + fun toJson() : JSONObject { + val root = JSONObject() + val ores = JSONArray() + val bars = JSONArray() + + for (ore in this.ores) + ores.add(ore.toString()) + for (amount in barAmounts) + bars.add(amount.toString()) + + root["ores"] = ores + root["bars"] = bars + root["coal"] = coalRemaining.toString() + return root + } + + companion object { + fun fromJson (root: JSONObject) : BFOreContainer { + val cont = BFOreContainer() + val jsonOres = root["ores"] as? JSONArray ?: return cont + val jsonBars = root["bars"] as? JSONArray ?: return cont + + + for (i in 0 until BlastConsts.ORE_LIMIT) + cont.ores[i] = jsonOres[i].toString().toIntOrNull() ?: -1 + for (i in 0 until 9) + cont.barAmounts[i] = jsonBars[i].toString().toIntOrNull() ?: 0 + cont.coalRemaining = root["coal"].toString().toIntOrNull() ?: 0 + return cont + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/blastfurnace/BFPlayerState.kt b/Server/src/main/content/minigame/blastfurnace/BFPlayerState.kt new file mode 100644 index 000000000..a7f44f336 --- /dev/null +++ b/Server/src/main/content/minigame/blastfurnace/BFPlayerState.kt @@ -0,0 +1,156 @@ +package content.minigame.blastfurnace + +import content.global.skill.smithing.smelting.Bar +import core.api.* +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.world.map.Location +import org.json.simple.JSONArray +import org.json.simple.JSONObject + +class BFPlayerState (val player: Player) { + var container = BFOreContainer() + val oresOnBelt = ArrayList() + var barsNeedCooled = false + private set + + fun processOresIntoBars() : Boolean { + if (barsNeedCooled && getVarbit(player, DISPENSER_STATE) == 1) { + setVarbit(player, DISPENSER_STATE, 2, true) + return false + } + if (getVarbit(player, DISPENSER_STATE) != 0 || !container.hasAnyOre()) + return false + + val xpReward = container.convertToBars(getStatLevel(player, Skills.SMITHING)) + if (xpReward > 0) { + rewardXP(player, Skills.SMITHING, xpReward) + setVarbit(player, DISPENSER_STATE, 1, true) + barsNeedCooled = true + return true + } + + return false + } + + fun updateOres() { + val toRemove = ArrayList() + for (ore in oresOnBelt) { + if (ore.tick()) + toRemove.add(ore) + } + oresOnBelt.removeAll(toRemove) + } + + fun coolBars() { + barsNeedCooled = false + setVarbit(player, DISPENSER_STATE, 3, true) + } + + fun checkBars() { + if (getVarbit(player, DISPENSER_STATE) == 3) + setVarbit(player, DISPENSER_STATE, 0, true) + } + + fun hasBarsClaimable(): Boolean { + return container.getTotalBarAmount() > 0 + } + + fun claimBars (bar: Bar, amount: Int) : Boolean { + if (barsNeedCooled) return false + + val maxAmt = amount.coerceAtMost(freeSlots(player)) + if (maxAmt == 0) return false + + val reward = container.takeBars(bar, maxAmt) ?: return false + addItem(player, reward.id, reward.amount) + setBarClaimVarbits() + return true + } + + fun setBarClaimVarbits() { + for (bar in Bar.values()) { + val amount = container.getBarAmount(bar) + val varbit = getVarbitForBar(bar) + if (varbit == 0) continue + + if (getVarbit(player, varbit) == amount) + continue + + setVarbit(player, varbit, amount, true) + } + + var totalCoalNeeded = 0 + val level = getStatLevel(player, Skills.SMITHING) + for ((id, amount) in container.getOreAmounts()) { + val barType = BlastFurnace.getBarForOreId(id, container.coalAmount(), level) + totalCoalNeeded += BlastFurnace.getNeededCoal(barType!!) * amount + } + + setVarbit(player, COAL_NEEDED, (totalCoalNeeded - container.coalAmount()).coerceAtLeast(0)) + } + + private fun getVarbitForBar (bar: Bar) : Int { + return when (bar) { + Bar.BRONZE -> BRONZE_COUNT + Bar.IRON -> IRON_COUNT + Bar.STEEL -> STEEL_COUNT + Bar.MITHRIL -> MITHRIL_COUNT + Bar.ADAMANT -> ADDY_COUNT + Bar.RUNITE -> RUNITE_COUNT + Bar.SILVER -> SILVER_COUNT + Bar.GOLD -> GOLD_COUNT + else -> 0 + } + } + + fun toJson() : JSONObject { + val save = JSONObject() + save["bf-ore-cont"] = container.toJson() + + val beltOres = JSONArray() + for (ore in oresOnBelt) { + beltOres.add(ore.toJson()) + } + + if (beltOres.isNotEmpty()) + save["bf-belt-ores"] = beltOres + save["barsHot"] = barsNeedCooled + + return save + } + + fun readJson (data: JSONObject) { + oresOnBelt.clear() + if (data.containsKey("bf-ore-cont")) { + val contJson = data["bf-ore-cont"] as JSONObject + container = BFOreContainer.fromJson(contJson) + } + if (data.containsKey("bf-belt-ores")) { + val beltArray = data["bf-belt-ores"] as JSONArray + for (oreObj in beltArray) { + val oreInfo = oreObj as JSONObject + val id = oreInfo["id"].toString().toInt() + val amount = oreInfo["amount"].toString().toInt() + val location = Location.fromString(oreInfo["location"].toString()) + val ore = BFBeltOre(player, id, amount, location) + oresOnBelt.add(ore) + } + } + if (data.containsKey("barsHot")) + barsNeedCooled = data["barsHot"] as Boolean + } + + companion object { + val DISPENSER_STATE = 936 + val COAL_NEEDED = 940 + val BRONZE_COUNT = 941 + val IRON_COUNT = 942 + val STEEL_COUNT = 943 + val MITHRIL_COUNT = 944 + val ADDY_COUNT = 945 + val RUNITE_COUNT = 946 + val GOLD_COUNT = 947 + val SILVER_COUNT = 948 + } +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/blastfurnace/BFSceneryController.kt b/Server/src/main/content/minigame/blastfurnace/BFSceneryController.kt new file mode 100644 index 000000000..d2d3e1606 --- /dev/null +++ b/Server/src/main/content/minigame/blastfurnace/BFSceneryController.kt @@ -0,0 +1,110 @@ +package content.minigame.blastfurnace + +import core.api.animateScenery +import core.api.getScenery +import core.api.replaceScenery +import core.game.world.map.Location + +class BFSceneryController { + fun updateBreakable (potPipeBroken: Boolean, pumpPipeBroken: Boolean, beltBroken: Boolean, cogBroken: Boolean) { + val beltObj = getScenery(beltGearRight)!! + val gearObj = getScenery(cogRightLoc)!! + val potPipe = getScenery(potPipeLoc)!! + val pumpPipe = getScenery(pumpPipeLoc)!! + + if (potPipeBroken && potPipe.id != BROKEN_POT_PIPE) + replaceScenery(potPipe, BROKEN_POT_PIPE, -1) + else if (!potPipeBroken && potPipe.id == BROKEN_POT_PIPE) + replaceScenery(potPipe, DEFAULT_POT_PIPE, -1) + + if (pumpPipeBroken && pumpPipe.id != BROKEN_PUMP_PIPE) + replaceScenery(pumpPipe, BROKEN_PUMP_PIPE, -1) + else if (!pumpPipeBroken && pumpPipe.id == BROKEN_PUMP_PIPE) + replaceScenery(pumpPipe, DEFAULT_PUMP_PIPE, -1) + + if (beltBroken && beltObj.id != BROKEN_BELT) + replaceScenery(beltObj, BROKEN_BELT, -1) + else if (!beltBroken && beltObj.id == BROKEN_BELT) + replaceScenery(beltObj, DEFAULT_BELT, -1) + + if (cogBroken && gearObj.id != BROKEN_COG) + replaceScenery(gearObj, BROKEN_COG, -1) + else if (!cogBroken && gearObj.id == BROKEN_COG) + replaceScenery(gearObj, DEFAULT_COG, -1) + } + + fun updateAnimations (pedaling: Boolean, beltBroken: Boolean, cogBroken: Boolean) { + val belt1 = getScenery(belt1Loc)!! + val belt2 = getScenery(belt2Loc)!! + val belt3 = getScenery(belt3Loc)!! + val beltGearLeft = getScenery(beltGearLeft)!! + val beltGearRight = getScenery(beltGearRight)!! + val cogLeft = getScenery(cogLeftLoc)!! + val cogRight = getScenery(cogRightLoc)!! + val cogCenter = getScenery(centralGearLoc)!! + + val beltAnim = if (pedaling && !beltBroken && !cogBroken) BELT_ANIM else -1 + val gearAnim = if (pedaling && !beltBroken && !cogBroken) GEAR_ANIM else -1 + + animateScenery(belt1, beltAnim) + animateScenery(belt2, beltAnim) + animateScenery(belt3, beltAnim) + animateScenery(beltGearLeft, gearAnim) + animateScenery(beltGearRight, gearAnim) + animateScenery(cogLeft, gearAnim) + animateScenery(cogRight, gearAnim) + animateScenery(cogCenter, gearAnim) + } + + fun updateStove (temp: Int) { + val stoveObj = getScenery(stoveLoc)!! + + if (temp >= 67 && stoveObj.id != STOVE_HOT) + replaceScenery(stoveObj, STOVE_HOT, -1) + else if (temp in 34..66 && stoveObj.id != STOVE_WARM) + replaceScenery(stoveObj, STOVE_WARM, -1) + else if (temp in 0..33 && stoveObj.id != STOVE_COLD) + replaceScenery(stoveObj, STOVE_COLD, -1) + } + + fun resetAllScenery() { + val beltObj = getScenery(beltGearRight)!! + val gearObj = getScenery(cogRightLoc)!! + val potPipe = getScenery(potPipeLoc)!! + val pumpPipe = getScenery(pumpPipeLoc)!! + val stoveObj = getScenery(stoveLoc)!! + replaceScenery(gearObj, DEFAULT_COG, -1) + replaceScenery(beltObj, DEFAULT_BELT, -1) + replaceScenery(pumpPipe, DEFAULT_PUMP_PIPE, -1) + replaceScenery(potPipe, DEFAULT_POT_PIPE, -1) + replaceScenery(stoveObj, STOVE_COLD, -1) + } + + companion object { + val belt1Loc = Location(1943, 4967, 0) + val belt2Loc = Location(1943, 4966, 0) + val belt3Loc = Location(1943, 4965, 0) + var potPipeLoc = Location(1943, 4961, 0) + var pumpPipeLoc = Location(1947, 4961, 0) + var cogLeftLoc = Location(1945, 4965, 0) + var cogRightLoc = Location(1945, 4967, 0) + var beltGearLeft = Location(1944, 4965, 0) + var beltGearRight = Location(1944, 4967, 0) + var centralGearLoc = Location(1945, 4966, 0) + var stoveLoc = Location(1948, 4963, 0) + + val DEFAULT_BELT = 9102 + val BROKEN_BELT = 9103 + val DEFAULT_COG = 9104 + val BROKEN_COG = 9105 + val DEFAULT_POT_PIPE = 9116 + val BROKEN_POT_PIPE = 9117 + val DEFAULT_PUMP_PIPE = 9120 + val BROKEN_PUMP_PIPE = 9121 + val STOVE_COLD = 9085 + val STOVE_WARM = 9086 + val STOVE_HOT = 9087 + val BELT_ANIM = 2435 + val GEAR_ANIM = 2436 + } +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/blastfurnace/BFTempEntranceTimer.kt b/Server/src/main/content/minigame/blastfurnace/BFTempEntranceTimer.kt new file mode 100644 index 000000000..733c4162a --- /dev/null +++ b/Server/src/main/content/minigame/blastfurnace/BFTempEntranceTimer.kt @@ -0,0 +1,19 @@ +package content.minigame.blastfurnace + +import core.api.resetAnimator +import core.api.teleport +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.system.timer.PersistTimer + +class BFTempEntranceTimer : PersistTimer(BlastConsts.FEE_ENTRANCE_DURATION, "bf-tempentrance") { + override fun run(entity: Entity): Boolean { + if (entity !is Player) return false + if (BlastFurnace.insideBorders(entity)) { + teleport(entity, BlastConsts.EXIT_LOC) + entity.pulseManager.clear() + resetAnimator(entity) + } + return false + } +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/blastfurnace/BlastConsts.kt b/Server/src/main/content/minigame/blastfurnace/BlastConsts.kt new file mode 100644 index 000000000..a2d098254 --- /dev/null +++ b/Server/src/main/content/minigame/blastfurnace/BlastConsts.kt @@ -0,0 +1,28 @@ +package content.minigame.blastfurnace + +import core.game.world.map.Location + +object BlastConsts { + val ENTRANCE_LOC = Location (1940, 4958, 0) + val EXIT_LOC = Location (2931, 10197, 0) + val STAIRLOC_ENTRANCE = Location (2930, 10196) + val STAIRLOC_EXIT = Location (1939, 4956) + val STAIR_ENTRANCE_ID = 9084 + val STAIR_EXIT_ID = 9138 + val BELT = 9100 + val PEDALS = 9097 + val STOVE = intArrayOf(BFSceneryController.STOVE_COLD, BFSceneryController.STOVE_WARM, BFSceneryController.STOVE_HOT) + val PUMP = 9090 + val COKE = 9088 + val TEMP_GAUGE = 9089 + val SINK = 9143 + val DISPENSER = intArrayOf(9093, 9094, 9095, 9096) + + val SMITH_REQ = 60 + val ENTRANCE_FEE = 2500 + val FEE_ENTRANCE_DURATION = 1000 + val COAL_LIMIT = 226 + val ORE_LIMIT = 28 + val BAR_LIMIT = 28 + val COKE_LIMIT = 15 +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/blastfurnace/BlastFurnace.kt b/Server/src/main/content/minigame/blastfurnace/BlastFurnace.kt index ec93900a4..5bbdedef1 100644 --- a/Server/src/main/content/minigame/blastfurnace/BlastFurnace.kt +++ b/Server/src/main/content/minigame/blastfurnace/BlastFurnace.kt @@ -2,308 +2,259 @@ package content.minigame.blastfurnace import content.global.skill.smithing.smelting.Bar import core.api.* -import core.game.container.impl.EquipmentContainer +import core.game.node.entity.Entity import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills import core.game.node.item.Item -import core.game.system.task.Pulse -import core.game.world.map.Location +import core.game.world.map.zone.ZoneBorders import core.tools.RandomFunction +import org.json.simple.JSONObject import org.rs09.consts.Items -import org.rs09.consts.Items.GOLDSMITH_GAUNTLETS_776 +import org.rs09.consts.NPCs +import kotlin.math.max -/**le Blast Furnace has arrived: - * Handles most of the Blast Furnace's operating logic, there is some crosstalk between - * this file and BlastFurnaceListeners.kt as well as the BlastFurnaceZone.kt. - * This also handles a lot of the varbit fuckery for the furnace. The other - * varbit fuckery is handled in BlastFurnaceListeners. - * Varbits are still slightly busted but not in a way that breaks the operation or usage of - * Blast Furnace, just visual and I've had enough of the funny binary numbers to say fuck it - * and will just come back to it at a later date. - * @author phil lips*/ +class BlastFurnace : MapArea, PersistPlayer, TickListener { + override fun defineAreaBorders(): Array { + return arrayOf(bfArea) + } -object BlastFurnace { + override fun savePlayer(player: Player, save: JSONObject) { + val state = playerStates[player.details.uid] + if (state != null) { + save["bf-state"] = state.toJson() + } + } - val belt1 = getScenery(1943, 4967, 0) - val belt2 = getScenery(1943, 4966, 0) - val belt3 = getScenery(1943, 4965, 0) - val disLoc = getScenery(1941, 4963, 0) - var pipe1 = getScenery(1943, 4961, 0) - var pipe2 = getScenery(1947, 4961, 0) - var cogs1 = getScenery(1945, 4965, 0) - var cogs2 = getScenery(1945, 4967, 0) - var beltLoc1 = getScenery(1944, 4965, 0) - var beltLoc2 = getScenery(1944, 4967, 0) - var gearsLoc = getScenery(1945, 4966, 0) - var stoveLoc = getScenery(1948, 4963, 0) + override fun parsePlayer(player: Player, data: JSONObject) { + playerStates.remove(player.details.uid) + if (data.containsKey("bf-state")) { + val stateObj = data["bf-state"] as JSONObject + getPlayerState(player).readJson(stateObj) + } + } - val mPot = 9098 + override fun areaEnter(entity: Entity) { + if (entity is Player) { + playersInArea.add(entity) + val state = getPlayerState(entity) + for (ore in state.oresOnBelt) + ore.createNpc() + } + } - var gaugeViewList = ArrayList() - var blastFurnacePlayerList = ArrayList() - var furnaceTemp = 0 - var pumpPipeBroken = false - var potPipeBroken = false - var stoveCoke = 0 - var stoveTemp = 0 - var beltRunning = false - var makeBars = false - var beltBroken = false - var cogBroken = false - var pumping = false - var pedaling = false - var barsHot = false - var giveSmithXp = 0 - - /**This pulse object handles checks between*/ - var blastPulse = object : Pulse() { - override fun pulse(): Boolean { - gaugeViewList.forEach { - var anim = furnaceTemp + 2452 - animateInterface(it, 30, 4, anim) + override fun areaLeave(entity: Entity, logout: Boolean) { + if (entity is Player) { + playersInArea.remove(entity) + val state = getPlayerState(entity) + for (ore in state.oresOnBelt) { + ore.npcInstance?.clear() + ore.npcInstance = null } - blastFurnacePlayerList.forEach { - if(it.getAttribute("BlastTimer",0) > 0 && it.getSkills().getLevel(Skills.SMITHING) < 60){ - it.incrementAttribute("BlastTimer",-1) - } else if(it.getAttribute("BlastTimer",0) <= 0 && it.getSkills().getLevel(Skills.SMITHING) < 60){ - it.removeAttribute("BlastTimer") - sendDialogue(it,"Your time in the Blast Furnace has run out!") - it.properties.teleportLocation = Location.create(2931, 10197, 0) - } else if(it.getAttribute("BlastTimer",false) && it.getSkills().getLevel(Skills.SMITHING) >= 60){ - it.removeAttribute("BlastTimer") - } - if(giveSmithXp > 0){ - rewardXP(it,Skills.SMITHING,1.0) - giveSmithXp-- + } + } + + override fun tick() { + if (state.beltBroken || state.cogBroken) pedaler = null + if (state.potPipeBroken || state.pumpPipeBroken) pumper = null + state.tick(pumper != null, pedaler != null) + + if (playersInArea.size > 0) { + updateVisuals() + if (getWorldTicks() % 2 == 0) { + updatePedaler() + updatePumper() + } + processBars() + } + + //Reset each tick + pumper = null + pedaler = null + } + + private fun updatePumper() { + if (pumper != null) { + if (state.stoveTemp == 0) return + if (state.furnaceTemp == 100 && RandomFunction.roll(5)) { + impact(pumper!!, (0.2 * pumper!!.skills.maximumLifepoints).toInt()) + sendMessage(pumper!!, "A blast of hot air cooks you a bit.") + pumper!!.pulseManager.clear() + return + } + rewardXP(pumper!!, Skills.STRENGTH, 4.0) + } + } + + private fun updatePedaler() { + if (pedaler == null) return + var oresPedaled = false + for (state in playerStates.values) { + if (state.oresOnBelt.isEmpty()) continue + state.updateOres() + oresPedaled = true + } + if (oresPedaled) { + rewardXP(pedaler!!, Skills.AGILITY, 2.0) + pedaler!!.settings.runEnergy -= 2 + } + } + + private fun updateVisuals() { + sceneryController.updateStove(state.stoveTemp) + sceneryController.updateBreakable( + state.potPipeBroken, + state.pumpPipeBroken, + state.beltBroken, + state.cogBroken + ) + sceneryController.updateAnimations(pedaler != null, state.beltBroken, state.cogBroken) + } + + private fun processBars() { + if (state.furnaceTemp !in 51..66) return + var totalProcessed = 0 + for (player in playersInArea) { + if (getPlayerState(player).processOresIntoBars()) + totalProcessed++ + } + if (totalProcessed == 0) return + for (player in playersInArea) + rewardXP(player, Skills.SMITHING, totalProcessed.toDouble()) + } + + companion object { + val bfArea = ZoneBorders (1934, 4955, 1958, 4975) + val playersInArea = ArrayList() + val playerStates = HashMap() + val state = BlastState() + val sceneryController = BFSceneryController() + var pedaler: Player? = null + var pumper: Player? = null + + + fun insideBorders (player: Player) : Boolean { + return bfArea.insideBorder(player.location) + } + + fun placeAllOre (p: Player, id: Int = -1, accountForSkill: Boolean = false) { + val oreCounts = HashMap() + val oreContainer = getOreContainer(p) + val level = if (accountForSkill) getStatLevel(p, Skills.SMITHING) else 99 + + for (item in p.inventory.toArray()) { + if (item == null) continue + if (getNpcForOre(item.id) == -1) continue + if (id != -1 && item.id != id) continue + + val bar = getBarForOreId(item.id, oreContainer.coalAmount(), level)!! + if (bar.level > level) continue + + oreCounts[item.id] = (oreCounts[item.id] ?: 0) + item.amount + } + + for ((oreId, amount) in oreCounts) { + var maxAmt = oreContainer.getAvailableSpace(oreId, level) + + if (oreId == Items.COPPER_ORE_436 || oreId == Items.TIN_ORE_438) + maxAmt += (BlastConsts.ORE_LIMIT - getAmountOnBelt(p, oreId)) + + if (oreId == Items.COAL_453) + maxAmt -= getAmountOnBelt(p, oreId) + else + maxAmt -= getTotalOreOnBelt(p) + + maxAmt = maxAmt.coerceAtMost(amount).coerceAtLeast(0) + if (maxAmt == 0) continue + + if (removeItem(p, Item(oreId, maxAmt))) + addOreToBelt(p, oreId, maxAmt) + } + } + + fun getPlayerState (p: Player) : BFPlayerState { + if (playerStates[p.details.uid] != null) + return playerStates[p.details.uid]!! + val state = BFPlayerState(p) + playerStates[p.details.uid] = state + return state + } + + fun getOreContainer (p: Player) : BFOreContainer { + return getPlayerState(p).container + } + + fun addOreToBelt (p: Player, id: Int, amount: Int) : BFBeltOre { + val beltOre = BFBeltOre(p, id, amount, BFBeltOre.ORE_START_LOC) + beltOre.createNpc() + getPlayerState(p).oresOnBelt.add(beltOre) + return beltOre + } + + fun getAmountOnBelt (p: Player, id: Int) : Int { + var total = 0 + for (ore in getPlayerState(p).oresOnBelt) { + if (ore.id == id) + total += ore.amount + } + return total + } + + fun getTotalOreOnBelt (p: Player) : Int { + var total = 0 + for (ore in getPlayerState(p).oresOnBelt) + if (ore.id != Items.COAL_453) total += ore.amount + return total + } + + fun getNeededCoal (bar: Bar) : Int { + var coalAmount = 0 + + if (bar.ores.size == 1) + return coalAmount + + for (ore in bar.ores) { + if (ore.id == Items.COAL_453) { + coalAmount = ore.amount + break } } - //interfaceManager() - runConveyor() - stoveCokeTemperature() - furnaceTemperature() - operateFurnace() - breakStuff() - fixStuff() - return false - } - } - - /**This handles the coke stove temperature, if there is coke in the - * coke oven the temperature will raise, otherwise the temperature - * will lower, 0-33 is cold, 34-66 is warm, 67-100 is hot*/ - fun stoveCokeTemperature() { - if (getWorldTicks() % 10 == 0 && stoveCoke > 0) { - stoveCoke-- - } - if (stoveCoke > 0) { - if (stoveTemp < 100) { - stoveTemp += 1 - } - } else if (stoveTemp > 0) { - stoveTemp-- - } - if (stoveTemp > 66 && stoveLoc!!.id != 9087) { - stoveLoc = getScenery(1948, 4963, 0) - replaceScenery(stoveLoc!!, 9087, -1) - } else if (stoveTemp in 34..66 && stoveLoc!!.id != 9086) { - stoveLoc = getScenery(1948, 4963, 0) - replaceScenery(stoveLoc!!, 9086, -1) - } else if (stoveTemp < 32 && stoveLoc!!.id != 9085) { - stoveLoc = getScenery(1948, 4963, 0) - replaceScenery(stoveLoc!!, 9085, -1) - } - } - - /**This handles the raising and lowering of the furnace temperature - * depending on whether or not the coke stove is hot and if the - * furnace pump is being operated. A hot coke stove will raise the - * temperature the fastest, a warm coke stove will raise it slowly, - * and a cold stove will raise it the slowest*/ - fun furnaceTemperature() { - if (stoveTemp >= 67 && pumping && furnaceTemp < 100 && (!pumpPipeBroken || !pumpPipeBroken)) { - furnaceTemp += 3 - } else if (stoveTemp >= 34 && pumping && furnaceTemp < 100 && (!pumpPipeBroken || !pumpPipeBroken)) { - furnaceTemp += 2 - } else if (stoveTemp > 0 && pumping && furnaceTemp < 100 && (!pumpPipeBroken || !pumpPipeBroken)) { - furnaceTemp += 1 - } else if (furnaceTemp > 0) { - furnaceTemp -= 1 - } - } - - /**Tells the furnace whether or not to smelt bars depending on if - * the furnace temperature is in the right range. Also contains the - * logic for smelting the players ore given the fact that they actually - * have ore in their ore container and/or have coal in their coal container*/ - fun operateFurnace() { - makeBars = furnaceTemp in 51..66 - blastFurnacePlayerList.forEach { player -> - var playerCoal = player.blastCoal.getAmount(Items.COAL_453) - var playerOre = player.blastOre.toArray().filterNotNull() - var barsAmountFree = player.blastBars.freeSlots() - var barsAmount = player.blastBars.itemCount() - var oreAmount = player.blastOre.itemCount() - var totalAmount = barsAmount + oreAmount - if (barsHot) { - setVarbit(player, 936, 2) - }else if (makeBars && playerOre.isNotEmpty() && barsAmountFree > 0 && totalAmount < 56 && player.getAttribute("OreInPot",false) == true) { - playerOre.forEach { oreID -> - playerCoal = player.blastCoal.getAmount(Items.COAL_453) - var bars = arrayOf(Bar.forOre(oreID.id)!!) - if(oreID.id == Items.IRON_ORE_440) { - bars = arrayOf(Bar.STEEL, Bar.IRON) - } - inner@ for(bar in bars) { - var hasRequirements = true - for(required in bar.ores) { - if(required.id == Items.COAL_453) { - if(!player.blastCoal.contains(Items.COAL_453, required.amount / 2)) { - hasRequirements = false - } - } else if(!player.blastOre.containsItem(required)) { - hasRequirements = false - } - } - if(hasRequirements) { - var removed = true - for(required in bar.ores) { - if(required.id == Items.COAL_453) { - if(!player.blastCoal.remove(Item(Items.COAL_453, required.amount / 2))) { - removed = false - } - } else { - if(!player.blastOre.remove(required)) { - removed = false - } - } - } - if(removed) { - setVarbit(player, 936, 1) - barsHot = true - player.blastBars.add(bar.product) - var experience = bar.experience - if(bar.product.id == Items.GOLD_BAR_2357 && - player.equipment[EquipmentContainer.SLOT_HANDS] != null && - player.equipment[EquipmentContainer.SLOT_HANDS].id == GOLDSMITH_GAUNTLETS_776) { - experience *= 2.5 - } - rewardXP(player, Skills.SMITHING, experience) - totalAmount = barsAmount + oreAmount - giveSmithXp++ - break@inner - } - } - } - } - } - if(!barsHot && playerOre.isNotEmpty()) { - var coalToAdd = 0 - playerOre.forEach { oreID -> - when(oreID.id){ - Items.RUNITE_ORE_451 -> coalToAdd = (player.blastOre.getAmount(451) * 4) - playerCoal - Items.ADAMANTITE_ORE_449 -> coalToAdd = (player.blastOre.getAmount(449) * 3) - playerCoal - Items.MITHRIL_ORE_447 -> coalToAdd = (player.blastOre.getAmount(447) * 2) - playerCoal - Items.IRON_ORE_440 -> coalToAdd = player.blastOre.getAmount(440) - playerCoal - } - } - if (coalToAdd < 0){ - setVarbit(player, 940, 0) - } else setVarbit(player, 940, coalToAdd) - } - else if(playerOre.isEmpty()) { - setVarbit(player, 940, 0) - } - if(playerOre.isEmpty() && player.getAttribute("OreInPot",false)){ - player.removeAttribute("OreInPot") - } - } - } - - /**This handles the conveyor belt, if the pedals are running and the belt - * isn't broken then the conveyor belt will run*/ - fun runConveyor() { - if (pedaling && (!beltBroken && !cogBroken)) { - animateScenery(belt1!!, 2435) - animateScenery(belt2!!, 2435) - animateScenery(belt3!!, 2435) - animateScenery(beltLoc1!!, 2436) - animateScenery(beltLoc2!!, 2436) - animateScenery(cogs1!!, 2436) - animateScenery(cogs2!!, 2436) - animateScenery(gearsLoc!!, 2436) - beltRunning = true - } else { - animateScenery(belt1!!, -1) - animateScenery(belt2!!, -1) - animateScenery(belt3!!, -1) - animateScenery(beltLoc1!!, -1) - animateScenery(beltLoc2!!, -1) - animateScenery(cogs1!!, -1) - animateScenery(cogs2!!, -1) - animateScenery(gearsLoc!!, -1) - beltRunning = false + if (coalAmount > 1) coalAmount /= 2 + return coalAmount } - } - /**Its just one of those days*/ - fun breakStuff() { - if (pumping && furnaceTemp > 76) { - if (RandomFunction.random(1, 100) > 20 && (!potPipeBroken || !pumpPipeBroken)) { - if (RandomFunction.random(1, 100) > 50 && !potPipeBroken) { - pipe1 = getScenery(1943, 4961, 0) - potPipeBroken = true - } - if (RandomFunction.random(1, 100) <= 50 && !pumpPipeBroken) { - pipe2 = getScenery(1947, 4961, 0) - pumpPipeBroken = true - } + fun getBarForOreId (id: Int, coalAmount: Int, level: Int) : Bar? { + return when (id) { + Items.COPPER_ORE_436, Items.TIN_ORE_438 -> Bar.BRONZE + Items.IRON_ORE_440 -> if (coalAmount >= 1 && level >= Bar.STEEL.level) Bar.STEEL else Bar.IRON + else -> Bar.forOre(id) } } - if (beltRunning && (!beltBroken || !cogBroken)) { - if (RandomFunction.random(1, 100) <= 2) { - beltLoc2 = getScenery(1944, 4967, 0) - beltBroken = true - } else if (RandomFunction.random(1, 100) <= 2) { - cogs2 = getScenery(1945, 4967, 0) - cogBroken = true + + fun getNpcForOre (id: Int) : Int { + return when (id) { + Items.IRON_ORE_440 -> NPCs.IRON_ORE_2556 + Items.COPPER_ORE_436 -> NPCs.COPPER_ORE_2555 + Items.TIN_ORE_438 -> NPCs.TIN_ORE_2554 + Items.COAL_453 -> NPCs.COAL_2562 + Items.MITHRIL_ORE_447 -> NPCs.MITHRIL_ORE_2557 + Items.ADAMANTITE_ORE_449 -> NPCs.ADAMANTITE_ORE_2558 + Items.SILVER_ORE_442 -> NPCs.SILVER_ORE_2560 + Items.GOLD_ORE_444 -> NPCs.GOLD_ORE_2561 + Items.RUNITE_ORE_451 -> NPCs.RUNITE_ORE_2559 + else -> -1 } } - if (beltBroken && beltLoc2!!.id != 9103) { - beltLoc2 = getScenery(1944, 4967, 0) - replaceScenery(beltLoc2!!, 9103, -1) - } - if (cogBroken && cogs2!!.id != 9105) { - cogs2 = getScenery(1945, 4967, 0) - replaceScenery(cogs2!!, 9105, -1) - } - if (potPipeBroken && pipe1!!.id != 9117) { - pipe1 = getScenery(1943, 4961, 0) - replaceScenery(pipe1!!, 9117, -1) - } - if (pumpPipeBroken && pipe2!!.id != 9121) { - pipe2 = getScenery(1947, 4961, 0) - replaceScenery(pipe2!!, 9121, -1) - } - } - /**This replaces the breakable objects IDs back to their unbroken state*/ - fun fixStuff() { - if (!potPipeBroken && pipe1!!.id != 9116) { - pipe1 = getScenery(1943, 4961, 0) - replaceScenery(pipe1!!, 9116, -1) + fun getEntranceFee (hasCharos: Boolean, smithLevel: Int) : Int { + if (smithLevel >= BlastConsts.SMITH_REQ) return 0 + return if (hasCharos) BlastConsts.ENTRANCE_FEE / 2 else BlastConsts.ENTRANCE_FEE } - if (!pumpPipeBroken && pipe2!!.id != 9120) { - pipe2 = getScenery(1947, 4961, 0) - replaceScenery(pipe2!!, 9120, -1) - } - if (!beltBroken && beltLoc2!!.id != 9102) { - beltLoc2 = getScenery(1944, 4967, 0) - replaceScenery(beltLoc2!!, 9102, -1) - } - if (!cogBroken && cogs2!!.id != 9104) { - cogs2 = getScenery(1945, 4967, 0) - replaceScenery(cogs2!!, 9104, -1) + + fun enter (player: Player, feePaid: Boolean) { + if (feePaid && !hasTimerActive(player)) + registerTimer(player, BFTempEntranceTimer()) + teleport(player, BlastConsts.ENTRANCE_LOC) } } -} +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/blastfurnace/BlastFurnaceInterfaceListener.kt b/Server/src/main/content/minigame/blastfurnace/BlastFurnaceInterfaceListener.kt index b0f39522e..61986f2e2 100644 --- a/Server/src/main/content/minigame/blastfurnace/BlastFurnaceInterfaceListener.kt +++ b/Server/src/main/content/minigame/blastfurnace/BlastFurnaceInterfaceListener.kt @@ -1,117 +1,51 @@ package content.minigame.blastfurnace +import content.global.skill.smithing.smelting.Bar import core.api.* import core.game.interaction.InterfaceListener -import core.game.node.item.Item +import core.game.system.task.Pulse import org.rs09.consts.Components -import org.rs09.consts.Items - -/** - * Handles the blast furnace bar stock interface. - * @author phil, bushtail - * @version 2.0 - */ class BlastFurnaceInterfaceListener : InterfaceListener { - override fun defineInterfaceListeners() { - - onOpen(28,) { player, component -> - var playerBars = player.blastBars.toArray().filterNotNull() - var bronzeBit = 1 - var ironBit = 1 - var steelBit = 1 - var mithrilBit = 1 - var adamantiteBit = 1 - var runiteBit = 1 - var silverBit = 1 - var goldBit = 1 - var barTotal = 0 - val barCounts = HashMap() - if (playerBars.isEmpty()) { - return@onOpen true - } else { - playerBars.forEach { barItem -> - when (barItem.id) { - Items.BRONZE_BAR_2349 -> barCounts[barItem.id] = bronzeBit++ - Items.IRON_BAR_2351 -> barCounts[barItem.id] = ironBit++ - Items.STEEL_BAR_2353 -> barCounts[barItem.id] = steelBit++ - Items.MITHRIL_BAR_2359 -> barCounts[barItem.id] = mithrilBit++ - Items.ADAMANTITE_BAR_2361 -> barCounts[barItem.id] = adamantiteBit++ - Items.RUNITE_BAR_2363 -> barCounts[barItem.id] = runiteBit++ - Items.SILVER_BAR_2355 -> barCounts[barItem.id] = silverBit++ - Items.GOLD_BAR_2357 -> barCounts[barItem.id] = goldBit++ - } - } - for ((id, amount) in barCounts) { - when (id) { - Items.BRONZE_BAR_2349 -> setVarbit(player, 941, amount) - Items.IRON_BAR_2351 -> setVarbit(player, 942, amount) - Items.STEEL_BAR_2353 -> setVarbit(player, 943, amount) - Items.MITHRIL_BAR_2359 -> setVarbit(player, 944, amount) - Items.ADAMANTITE_BAR_2361 -> setVarbit(player, 945, amount) - Items.RUNITE_BAR_2363 -> setVarbit(player, 946, amount) - Items.SILVER_BAR_2355 -> setVarbit(player, 948, amount) - Items.GOLD_BAR_2357 -> setVarbit(player, 947, amount) - } - } - return@onOpen true - } + onOpen(Components.BLAST_FURNACE_BAR_STOCK_28) { player, component -> + val state = BlastFurnace.getPlayerState(player) + state.setBarClaimVarbits() + state.checkBars() + return@onOpen true } + onOpen(Components.BLAST_FURNACE_TEMP_GAUGE_30) {player, _ -> + submitIndividualPulse(player, object : Pulse() { + override fun pulse(): Boolean { + val anim = BlastFurnace.state.furnaceTemp + 2452 + animateInterface(player, 30, 4, anim) + return false + } + }) + return@onOpen true + } + onClose(Components.BLAST_FURNACE_TEMP_GAUGE_30) {player, _ -> player.pulseManager.clear(); return@onClose true } + on(Components.BLAST_FURNACE_BAR_STOCK_28){ player, _, _, buttonID, _, _ -> - val bar = BFBars.forId(buttonID) ?: return@on false - val isAll = buttonID == bar.allButtonId - val playerAmt = getVarbit(player, bar.varbit) - if(playerAmt == 0) return@on true - - var amtToWithdraw = if(isAll) playerAmt else 1 - - if(freeSlots(player) < amtToWithdraw){ - amtToWithdraw = freeSlots(player) - } - - val barItemId = when(bar){ - BFBars.BRONZE -> Items.BRONZE_BAR_2349 - BFBars.IRON -> Items.IRON_BAR_2351 - BFBars.STEEL -> Items.STEEL_BAR_2353 - BFBars.MITHRIL -> Items.MITHRIL_BAR_2359 - BFBars.ADAMANT -> Items.ADAMANTITE_BAR_2361 - BFBars.RUNITE -> Items.RUNITE_BAR_2363 - BFBars.SILVER -> Items.SILVER_BAR_2355 - BFBars.GOLD -> Items.GOLD_BAR_2357 - } - - setVarbit(player, bar.varbit, playerAmt - amtToWithdraw) - - while(amtToWithdraw > 0){ - if(addItem(player, barItemId, 1) && player.blastBars.remove(Item(barItemId))) { - amtToWithdraw-- - } else break - } - if(player.blastBars.isEmpty) - setVarp(player, 543, 0) + val (isAll, bar) = getBarForButton(buttonID) + val state = BlastFurnace.getPlayerState(player) + state.claimBars(bar, if (isAll) state.container.getBarAmount(bar) else 1) return@on true } } - internal enum class BFBars(val buttonId: Int, val varbit: Int, val allButtonId: Int = buttonId + 2){ - BRONZE(43, 941, 44), - IRON(40, 942, 41), - STEEL(36, 943), - MITHRIL(33, 944), - ADAMANT(30, 945), - RUNITE(27, 946), - SILVER(24, 948), - GOLD(21, 947); - - companion object { - private val idMap = values().associateBy { it.buttonId } - - fun forId(buttonId: Int): BFBars? { - return idMap[buttonId] ?: idMap[buttonId - 2] ?: idMap[buttonId - 1] - } + private fun getBarForButton (id: Int) : Pair { + return when (id) { + 43,44 -> Pair(id == 44, Bar.BRONZE) + 40,41 -> Pair(id == 41, Bar.IRON) + 36,38 -> Pair(id == 38, Bar.STEEL) + 33,35 -> Pair(id == 35, Bar.MITHRIL) + 30,32 -> Pair(id == 32, Bar.ADAMANT) + 27,29 -> Pair(id == 29, Bar.RUNITE) + 24,26 -> Pair(id == 26, Bar.SILVER) + 21,23 -> Pair(id == 23, Bar.GOLD) + else -> Pair(false, Bar.BRONZE) } } - } diff --git a/Server/src/main/content/minigame/blastfurnace/BlastFurnaceListeners.kt b/Server/src/main/content/minigame/blastfurnace/BlastFurnaceListeners.kt index 820a79f8a..deaaae199 100644 --- a/Server/src/main/content/minigame/blastfurnace/BlastFurnaceListeners.kt +++ b/Server/src/main/content/minigame/blastfurnace/BlastFurnaceListeners.kt @@ -1,121 +1,128 @@ package content.minigame.blastfurnace -import content.global.skill.smithing.smelting.Bar +import content.region.misc.keldagrim.handlers.BlastFurnaceDoorDialogue import core.api.* +import core.game.dialogue.DialogueFile +import core.game.dialogue.Topic +import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.node.entity.skill.Skills -import core.game.node.item.Item import core.game.system.task.Pulse import core.game.world.map.Location +import core.tools.END_DIALOGUE import org.rs09.consts.Items -/**"Most" of the listeners for blast furnace live in this file, handles - * listeners for most things from interacting with the temp gauge to putting ore on the - * conveyor belt. The only thing that's not in here as far as listeners goes is Ordan's unnoting. - * That lives in OrdanDialogue.kt - * @author phil, bushtail*/ class BlastFurnaceListeners : InteractionListener { - - val disLoc = getScenery(1941, 4963, 0) - val brokenPotPipe = 9117 - val brokenPumpPipe = 9121 - val pump = 9090 - val stove = intArrayOf(9085, 9086, 9087) - val coke = 9088 - val pedals = 9097 - val conveyorLoad = 9100 - val dispenser = intArrayOf(9093, 9094, 9095, 9096) - val brokenBelt = 9103 - val brokenCog = 9105 - val tGauge = 9089 - val sink = 9143 - val playerOre = intArrayOf( - Items.IRON_ORE_440, - Items.COPPER_ORE_436, - Items.TIN_ORE_438, - Items.COAL_453, - Items.MITHRIL_ORE_447, - Items.ADAMANTITE_ORE_449, - Items.SILVER_ORE_442, - Items.GOLD_ORE_444, - Items.RUNITE_ORE_451 - ) - val notedOre = intArrayOf( - Items.IRON_ORE_441, - Items.COPPER_ORE_437, - Items.TIN_ORE_439, - Items.COAL_454, - Items.MITHRIL_ORE_448, - Items.ADAMANTITE_ORE_450, - Items.SILVER_ORE_443, - Items.GOLD_ORE_445, - Items.RUNITE_ORE_452 - ) + companion object { + var initialized = false + val PUMP_LOC = Location.create(1950, 4961, 0) + val PUMP_FACELOC = Location.create(1949, 4961, 0) + val PEDAL_LOC = Location.create(1947, 4966, 0) + val PEDAL_DISMOUNT = Location.create(1947, 4967, 0) + val PEDAL_FACELOC = Location.create(1946, 4966, 0) + val PUMP_ANIM = 2432 + val PEDAL_ANIM = 2433 + val PEDAL_SCENERY = getScenery(PEDAL_LOC)!! + val PUMP_SCENERY = getScenery(PUMP_LOC)!! + val validOreIds = intArrayOf( + Items.IRON_ORE_440, + Items.COPPER_ORE_436, + Items.TIN_ORE_438, + Items.COAL_453, + Items.MITHRIL_ORE_447, + Items.ADAMANTITE_ORE_449, + Items.SILVER_ORE_442, + Items.GOLD_ORE_444, + Items.RUNITE_ORE_451 + ) + } override fun defineListeners() { + if (initialized) return + initialized = true - /**Handles operating the pump*/ - - on(pump, SCENERY, "operate") { player, node -> - if(player.getSkills().getLevel(Skills.STRENGTH) >= 30) { - val pumpL = Location(1950, 4961, 0) - val pumpF = Location(1949, 4961, 0) - player.properties.teleportLocation = pumpL - submitIndividualPulse(player, object : Pulse() { - override fun pulse(): Boolean { - face(player, pumpF,) - animate(player, 2432) - BlastFurnace.pumping = true - if (!BlastFurnace.pumpPipeBroken || !BlastFurnace.potPipeBroken) rewardXP( - player, - Skills.STRENGTH, - 4.0 - ) - return false - } - - override fun stop() { - BlastFurnace.pumping = false - super.stop() - } - }) - }else{ - sendDialogue(player,"I need 30 Strength to do this!") + on (BlastConsts.STAIR_ENTRANCE_ID, IntType.SCENERY, "climb-down") { p, _ -> + val hasCharos = inEquipment(p, Items.RING_OF_CHAROS_4202) || inEquipment(p, Items.RING_OF_CHAROSA_6465) + val fee = BlastFurnace.getEntranceFee(hasCharos, getStatLevel(p, Skills.SMITHING)) + if (fee > 0 && !hasTimerActive(p)) { + openDialogue(p, BlastFurnaceDoorDialogue(fee)) + return@on true } + BlastFurnace.enter(p, false) return@on true } - /**Logic for the pedals that run the conveyor, rewards Agility XP every tick that they're being - * pedaled but will stop once the conveyor breaks.*/ - - on(pedals, SCENERY, "pedal") { player, node -> - if(player.getSkills().getLevel(Skills.AGILITY) >= 30) { - val pedalL = Location(1947, 4966, 0) - val pedalF = Location(1946, 4966, 0) - player.properties.teleportLocation = pedalL - submitIndividualPulse(player, object : Pulse() { - override fun pulse(): Boolean { - face(player, pedalF,) - animate(player, 2433) - BlastFurnace.pedaling = true - if (BlastFurnace.beltRunning) rewardXP(player, Skills.AGILITY, 2.0) - return false - } - override fun stop() { - BlastFurnace.pedaling = false - super.stop() - } - }) - }else{ - sendDialogue(player,"I need 30 Agility to do this!") - } + on (BlastConsts.STAIR_EXIT_ID, IntType.SCENERY, "climb-up") { p, _ -> + teleport(p, BlastConsts.EXIT_LOC) return@on true } - /**Lets players use a spade to take coke from the pile of coke*/ + on (BlastConsts.PUMP, IntType.SCENERY, "operate") { p, _ -> + if (getDynLevel(p, Skills.STRENGTH) >= 30 && BlastFurnace.pumper == null) { + forceMove(p, p.location, PUMP_LOC, 0, 30) { + animate(p, PUMP_ANIM) + removeScenery(PUMP_SCENERY) + submitIndividualPulse(p, object : Pulse() { + override fun pulse(): Boolean { + face(p, PUMP_FACELOC) + animate(p, PUMP_ANIM) + BlastFurnace.pumper = p + return false + } - on(coke, SCENERY, "collect") { player, _ -> + override fun stop() { + resetAnimator(p) + BlastFurnace.pumper = null + addScenery(PUMP_SCENERY) + super.stop() + } + }) + } + } + else if (BlastFurnace.pumper != null) + sendMessage(p, "Someone else is already doing that.") + else + sendMessage(p, "You need a Strength level of 30 to do that.") + return@on true + } + setDest(IntType.SCENERY, BlastConsts.PUMP) { _, _ -> Location.create(1951, 4961, 0) } + + on (BlastConsts.PEDALS, IntType.SCENERY, "pedal") { p, n -> + if (getDynLevel(p, Skills.AGILITY) > 30 && BlastFurnace.pedaler == null) { + forceMove(p, p.location, PEDAL_LOC, 0, 30) { + animate(p, PEDAL_ANIM) + removeScenery(PEDAL_SCENERY) + submitIndividualPulse(p, object : Pulse() { + override fun pulse(): Boolean { + if (p.settings.runEnergy < 1.0) { + teleport(p, PEDAL_DISMOUNT) + return true + } + face(p, PEDAL_FACELOC) + animate(p, PEDAL_ANIM) + BlastFurnace.pedaler = p + return false + } + + override fun stop() { + resetAnimator(p) + BlastFurnace.pedaler = null + addScenery(PEDAL_SCENERY) + super.stop() + } + }) + } + } + else if (BlastFurnace.pedaler != null) + sendMessage(p, "Someone else is already doing that.") + else + sendMessage(p, "You need an Agility level of 30 to do that.") + return@on true + } + setDest(IntType.SCENERY, BlastConsts.PEDALS) { _, _ -> Location.create(1948, 4966, 0) } + + on (BlastConsts.COKE, SCENERY, "collect") { player, _ -> if (inInventory(player, Items.SPADE_952, 1)) { if(removeItem(player, Items.SPADE_952, Container.INVENTORY) && addItem(player, Items.SPADEFUL_OF_COKE_6448, 1)) { lockInteractions(player,1) @@ -124,273 +131,173 @@ class BlastFurnaceListeners : InteractionListener { } else { sendMessage(player, "You need a spade to do this!") } - return@on true + return@on true } - /**This code is for handling the coke stove, lets players interact with it with the spadeful of coke - * and rewards firemaking XP when shoving coke in there, it will not allow players to shovel more - * coke in if it is already full.*/ + on (Items.SPADEFUL_OF_COKE_6448, ITEM, "empty") {p, n -> + if (removeItem(p, n)) addItem(p, Items.SPADE_952) + return@on true + } - on(stove, SCENERY,"refuel") { player, _ -> - if (inInventory(player, Items.SPADEFUL_OF_COKE_6448, 1) && BlastFurnace.stoveCoke < 30 && player.getSkills().getLevel(Skills.FIREMAKING) >= 30) { + on (BlastConsts.STOVE, SCENERY, "refuel") { player, _ -> + if (inInventory(player, Items.SPADEFUL_OF_COKE_6448, 1)) { + if (BlastFurnace.state.cokeInStove >= BlastConsts.COKE_LIMIT) { + sendMessage(player, "The stove is already full of coke!") + return@on true + } + if (getDynLevel(player, Skills.FIREMAKING) < 30) { + sendMessage(player, "You need a Firemaking level of 30 to do this.") + return@on true + } animate(player, 2442) - lockInteractions(player,2) + lock(player,2) submitIndividualPulse(player, object : Pulse() { override fun pulse(): Boolean { return if(removeItem(player, Items.SPADEFUL_OF_COKE_6448, Container.INVENTORY) && addItem(player, Items.SPADE_952, 1)) { animate(player, 2443) + rewardXP(player, Skills.FIREMAKING, 5.0) + BlastFurnace.state.addCoke(1) true } else { false } } }) - rewardXP(player, Skills.FIREMAKING, 5.0) - BlastFurnace.stoveCoke++ - }else if(inInventory(player, Items.SPADEFUL_OF_COKE_6448,1) && BlastFurnace.stoveCoke >= 30 && player.getSkills().getLevel(Skills.FIREMAKING) >= 30){ - sendDialogue(player,"The coke stove is already full!") - } else if(player.getSkills().getLevel(Skills.FIREMAKING) < 30){ - sendDialogue(player,"I need 30 Firemaking to do this") } else { sendMessage(player,"You need some coke to do that!") } - return@on true + return@on true } - /**This block of code handles the logic for putting coal and ores - * on the conveyor belt. It won't let you put anything on there if there's no room - * in your blast ore or blast coal player containers, the way it works is that it - * puts your coal and ore into the blast furnace containers however, the blast furnace will NOT - * smelt your ores because it's waiting for a player attribute to be set by the ore NPCs that this spawns*/ - - on(conveyorLoad, SCENERY, "put-ore-on") { player, node -> - val rocksInInventory = playerOre.filter {inInventory(player, it)} - var oreToActuallyAdd = 0 - var coalToActuallyAdd = 0 - if(player.blastCoal.freeSlots() > 0 || player.blastOre.freeSlots() > 0) { - player.dialogueInterpreter.sendOptions("Add all your ore to the furnace?", "Yes", "No") - player.dialogueInterpreter.addAction { player, button -> - if (button == 2 && rocksInInventory.isNotEmpty()) { - rocksInInventory.forEach { oreID -> - val oreAmount = amountInInventory(player, oreID) - val copperInPot = player.blastOre.getAmount(436) - val tinInPot = player.blastOre.getAmount(438) - if(oreID == Items.COAL_453 && oreAmount <= player.blastCoal.freeSlots()){ - coalToActuallyAdd = oreAmount - }else if(oreID == Items.COAL_453 && oreAmount > player.blastCoal.freeSlots() && player.blastCoal.freeSlots() > 0){ - coalToActuallyAdd = player.blastCoal.freeSlots() - } - if(oreID != Items.COAL_453 && oreAmount <= player.blastOre.freeSlots()){ - oreToActuallyAdd = oreAmount - }else if(oreID != Items.COAL_453 && oreAmount > player.blastOre.freeSlots() && player.blastOre.freeSlots() > 0){ - oreToActuallyAdd = player.blastOre.freeSlots() - } - if(oreID == Items.COPPER_ORE_436 && (oreToActuallyAdd + copperInPot) > 14){ - while(oreToActuallyAdd + copperInPot > 14){ - oreToActuallyAdd-- - } - } - if(oreID == Items.TIN_ORE_438 && (oreToActuallyAdd + tinInPot) > 14){ - while(oreToActuallyAdd + tinInPot > 14){ - oreToActuallyAdd-- - } - } - val bar = Bar.forOre(oreID) - if(oreID == Items.COAL_453) { - if (coalToActuallyAdd > 0 && getStatLevel(player, Skills.SMITHING) >= 30) { - if(removeItem(player, Item(oreID, coalToActuallyAdd), Container.INVENTORY) && player.blastCoal.add(Item(Items.COAL_453,coalToActuallyAdd))) { - BlastFurnaceOre(player, BFOreVariant.values()[playerOre.indexOf(oreID)],coalToActuallyAdd).init() - } - } - else if(getStatLevel(player, Skills.SMITHING) < 30){ - sendDialogue(player, "My Smithing level is not high enough to use Coal!") - } - else sendDialogue(player, "It looks like the melting pot is already full of coal!") - } else if (bar != null) { - if (oreToActuallyAdd > 0 && getStatLevel(player, Skills.SMITHING) >= bar.level) { - if(removeItem(player, Item(oreID, oreToActuallyAdd), Container.INVENTORY) && player.blastOre.add(Item(oreID,oreToActuallyAdd))) { - BlastFurnaceOre( - player, - BFOreVariant.values()[playerOre.indexOf(oreID)], - oreToActuallyAdd - ).init() - } - } - else if(getStatLevel(player, Skills.SMITHING) < bar.level){ - sendDialogue(player, "My Smithing level is not high enough to smelt ${bar.name.lowercase()}!") - } - else sendDialogue(player, "It looks like the melting pot is already full of ore!") - } + on (BlastConsts.BELT, IntType.SCENERY, "put-ore-on") { p, _ -> + openDialogue(p, object : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (stage) { + 0 -> sendDialogue(p, "Really place all your ore on the belt?").also { stage++ } + 1 -> showTopics( + Topic("Yes.", 100, true), + Topic("Nevermind.", END_DIALOGUE, true) + ) + 100 -> { + end() + BlastFurnace.placeAllOre(p, accountForSkill = true) } - } else if (button == 2 && rocksInInventory.isEmpty()){ - sendDialogue(player,"I should make sure that I have some ore before doing this.") } } - } - return@on true + }) + return@on true } - /**Added this because clicking on ore then the belt and getting "Nothing interesting happens" is annoying - * it's essentially the same thing as above except without the dialogue because if you're clicking on ore - * and trying to use it on the conveyor belt then you know what you're trying to do.*/ - onUseWith(SCENERY,playerOre,conveyorLoad){ player, oreType, _ -> - val amountInInventory = player.inventory.getAmount(oreType.id) - val spaceForCoal = player.blastCoal.freeSlots() - val spaceForOre = player.blastOre.freeSlots() - val copperInPot = player.blastOre.getAmount(436) - val tinInPot = player.blastOre.getAmount(438) - var amountToAdd = 0 - if(oreType.id == Items.COAL_453 && amountInInventory <= spaceForCoal){ - amountToAdd = amountInInventory - }else if(oreType.id == Items.COAL_453 && amountInInventory > spaceForCoal){ - amountToAdd = spaceForOre - } - if(oreType.id != Items.COAL_453 && amountInInventory <= spaceForOre){ - amountToAdd = amountInInventory - }else if(oreType.id != Items.COAL_453 && amountInInventory > spaceForOre){ - amountToAdd = spaceForOre - } - if(oreType.id == Items.COPPER_ORE_436 && (amountToAdd + copperInPot) > 14){ - while(amountToAdd + copperInPot > 14){ - amountToAdd-- - } - } - if(oreType.id == Items.TIN_ORE_438 && (amountToAdd + tinInPot) > 14){ - while(amountToAdd + tinInPot > 14){ - amountToAdd-- - } - } - val bar = Bar.forOre(oreType.id) - if(oreType.id == Items.COAL_453) { - if (amountToAdd > 0 && getStatLevel(player, Skills.SMITHING) >= 30) { - if(removeItem(player, Item(oreType.id, amountToAdd), Container.INVENTORY) && player.blastCoal.add(Item(oreType.id,amountToAdd))) { - BlastFurnaceOre( - player, - BFOreVariant.values()[playerOre.indexOf(oreType.id)], - amountToAdd - ).init() + onUseWith (IntType.SCENERY, validOreIds, BlastConsts.BELT) { p, used, _ -> + BlastFurnace.placeAllOre(p, used.id, accountForSkill = true) + return@onUseWith true + } + + on(BlastConsts.TEMP_GAUGE, IntType.SCENERY, "read") { p, _ -> + openInterface(p, 30) + return@on true + } + + on(BlastConsts.DISPENSER, IntType.SCENERY, "search", "take") { p, _ -> + openInterface(p, 28) + return@on true + } + + onUseWith(SCENERY, Items.BUCKET_OF_WATER_1929, *BlastConsts.DISPENSER){ p, _, _ -> + when (getVarbit(p, BFPlayerState.DISPENSER_STATE)) { + 1,2 -> { + var barsCooled = false + for (player in BlastFurnace.playersInArea) { + val state = BlastFurnace.getPlayerState(player) + if (state.barsNeedCooled) { + state.coolBars() + barsCooled = true + } + } + if (barsCooled) { + removeItem(p, Items.BUCKET_OF_WATER_1929, Container.INVENTORY) + addItem(p, Items.BUCKET_1925) } - }else if(getStatLevel(player,Skills.SMITHING) < 30){ - sendDialogue(player,"My Smithing level is not high enough to use Coal!") } - else sendDialogue(player, "It looks like the melting pot is already full of ore!") - } else if(bar != null) { - if (amountToAdd > 0 && getStatLevel(player, Skills.SMITHING) >= bar.level) { - player.blastOre.add(Item(oreType.id,amountToAdd)) - removeItem(player, Item(oreType.id, amountToAdd), Container.INVENTORY) - BlastFurnaceOre(player, BFOreVariant.values()[playerOre.indexOf(oreType.id)],amountToAdd).init() - }else if(getStatLevel(player,Skills.SMITHING) < bar.level){ - sendDialogue(player,"My Smithing level is not high enough to smelt Iron!") - } - else sendDialogue(player, "It looks like the melting pot is already full of ore!") + 0 -> sendDialogue(p,"There's nothing to cool off!") + 3 -> sendDialogue(p,"These bars have already cooled off!") } return@onUseWith true } - /**This handles interacting with the temperature gauge on the furnace*/ - - on(tGauge, SCENERY, "read") { player, _ -> - player.interfaceManager.openComponent(30) - return@on true - } - - /**This handles taking bars from the dispenser*/ - - on(dispenser,SCENERY,"search", "take"){ player, _ -> - if (getVarbit(player, 936) == 0 || getVarbit(player, 936) == 3) { - player.interfaceManager.openComponent(28) - } - return@on true - } - - /**Handles using a bucket of water on the bar dispenser*/ - - onUseWith(SCENERY,Items.BUCKET_OF_WATER_1929,*dispenser){ player, _, _ -> - when (getVarbit(player, 936)) { - 2 -> { - removeItem(player,Items.BUCKET_OF_WATER_1929,Container.INVENTORY) - addItem(player,Items.BUCKET_1925) - BlastFurnace.barsHot = false - setVarbit(player, 936, 3) - } - 0 -> { - sendDialogue(player,"There's nothing to cool off!") - } - 1 -> { - sendDialogue(player,"I should wait until the machine is finished") - } - 3 -> { - sendDialogue(player,"These bars have already cooled off!") - } - } - return@onUseWith true - } - - /**Handles pipe repairing*/ - - on(brokenPotPipe,SCENERY,"repair"){ player, _ -> + on(BFSceneryController.BROKEN_POT_PIPE, SCENERY, "repair"){ player, _ -> if(player.getSkills().getLevel(Skills.CRAFTING) >= 30){ + if (!BlastFurnace.state.potPipeBroken) { + sendMessage(player, "You can't fix something that isn't broken.") + return@on true + } if(inInventory(player,Items.HAMMER_2347,1)) { rewardXP(player, Skills.CRAFTING, 50.0) - BlastFurnace.potPipeBroken = false - } - else { + BlastFurnace.state.potPipeBroken = false + } else { sendMessage(player, "I need a hammer to do this!") } - }else{ + } else { sendDialogue(player,"I need 30 Crafting in order to do this") } return@on true } - on(brokenPumpPipe,SCENERY, "repair"){ player, _ -> + on(BFSceneryController.BROKEN_PUMP_PIPE, SCENERY, "repair"){ player, _ -> if(player.getSkills().getLevel(Skills.CRAFTING) >= 30){ + if (!BlastFurnace.state.pumpPipeBroken) { + sendMessage(player, "You can't fix something that isn't broken.") + return@on true + } if(inInventory(player,Items.HAMMER_2347,1)) { rewardXP(player, Skills.CRAFTING, 50.0) - BlastFurnace.pumpPipeBroken = false - } - else { + BlastFurnace.state.pumpPipeBroken = false + } else { sendMessage(player, "I need a hammer to do this!") } - }else{ + } else { sendDialogue(player,"I need 30 Crafting in order to do this") } return@on true } - on(brokenBelt,SCENERY,"repair"){ player, _ -> + on(BFSceneryController.BROKEN_BELT, SCENERY, "repair"){ player, _ -> if(player.getSkills().getLevel(Skills.CRAFTING) >= 30){ + if (!BlastFurnace.state.beltBroken) { + sendMessage(player, "You can't fix something that isn't broken.") + return@on true + } if(inInventory(player,Items.HAMMER_2347,1)) { rewardXP(player, Skills.CRAFTING, 50.0) - BlastFurnace.beltBroken = false - } - else { + BlastFurnace.state.beltBroken = false + } else { sendMessage(player, "I need a hammer to do this!") } - }else{ + } else { sendDialogue(player,"I need 30 Crafting in order to do this") } return@on true } - on(brokenCog,SCENERY,"repair"){ player, _ -> + on(BFSceneryController.BROKEN_COG, SCENERY, "repair"){ player, _ -> if(player.getSkills().getLevel(Skills.CRAFTING) >= 30){ + if (!BlastFurnace.state.cogBroken) { + sendMessage(player, "You can't fix something that isn't broken.") + return@on true + } if(inInventory(player,Items.HAMMER_2347,1)) { rewardXP(player, Skills.CRAFTING, 50.0) - BlastFurnace.cogBroken = false - } - else { + BlastFurnace.state.cogBroken = false + } else { sendMessage(player, "I need a hammer to do this!") } - }else{ + } else { sendDialogue(player,"I need 30 Craft in order to do this") } return@on true } - /**Handles filling buckets from the sink*/ - - on(sink, SCENERY,"fill-bucket"){ player, _ -> + on(BlastConsts.SINK, SCENERY,"fill-bucket"){ player, _ -> player.pulseManager.run(object : Pulse(1){ override fun pulse(): Boolean { if(removeItem(player, Items.BUCKET_1925)) diff --git a/Server/src/main/content/minigame/blastfurnace/BlastFurnaceOre.kt b/Server/src/main/content/minigame/blastfurnace/BlastFurnaceOre.kt deleted file mode 100644 index 314bc5159..000000000 --- a/Server/src/main/content/minigame/blastfurnace/BlastFurnaceOre.kt +++ /dev/null @@ -1,91 +0,0 @@ -package content.minigame.blastfurnace - -import core.api.* -import core.game.node.entity.npc.AbstractNPC -import core.game.node.entity.player.Player -import core.game.system.task.Pulse -import core.game.world.map.Location -import core.game.world.update.flag.context.Animation -import core.plugin.Initializable -import org.rs09.consts.NPCs - -/**On god this is like 95% ceikry and 5% me adding a setAttribute to the clear code - * @author funny alium man*/ - -@Initializable -class BlastFurnaceOre : AbstractNPC { - //Arios constructor spaghetti - constructor() : super(NPCs.COAL_2562, null, true) {} - private constructor(id: Int, location: Location) : super(id, location) {} - - constructor(owner: Player, variant: BFOreVariant, amount: Int) : super( - when(variant){ - BFOreVariant.IRON -> NPCs.IRON_ORE_2556 - BFOreVariant.COPPER -> NPCs.COPPER_ORE_2555 - BFOreVariant.TIN -> NPCs.TIN_ORE_2554 - BFOreVariant.COAL -> NPCs.COAL_2562 - BFOreVariant.MITHRIL -> NPCs.MITHRIL_ORE_2557 - BFOreVariant.ADAMANT -> NPCs.ADAMANTITE_ORE_2558 - BFOreVariant.SILVER -> NPCs.SILVER_ORE_2560 - BFOreVariant.GOLD -> NPCs.GOLD_ORE_2561 - BFOreVariant.RUNITE -> NPCs.RUNITE_ORE_2559 - }, Location.create(1942, 4966, 0)) {this.owner = owner; isRespawn = false; } - - var owner: Player? = null - var delay = 2 - var counter = 3 - - - override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { - return BlastFurnaceOre(id, location) - } - - override fun getIds(): IntArray { - return intArrayOf( - NPCs.IRON_ORE_2556, - NPCs.COPPER_ORE_2555, - NPCs.TIN_ORE_2554, - NPCs.COAL_2562, - NPCs.MITHRIL_ORE_2557, - NPCs.ADAMANTITE_ORE_2558, - NPCs.SILVER_ORE_2560, - NPCs.GOLD_ORE_2561, - NPCs.PERFECT_GOLD_ORE_2563, - NPCs.RUNITE_ORE_2559) - } - - override fun handleTickActions() { - if (BlastFurnace.beltRunning) { - delay-- - if (delay <= 0 && getWorldTicks() % 2 == 0) { //run every other tick - if (counter > 0) { - properties.teleportLocation = location.transform(0, -1, 0) - counter-- - if (counter == 0) { - val animation = Animation(2434) - animate(animation) - owner?.setAttribute("/save:OreInPot",true) - submitIndividualPulse(this, object : Pulse(animationDuration(animation)) { - override fun pulse(): Boolean { - clear() - return true - } - }) - } - } - } - } - } -} - -enum class BFOreVariant { - IRON, - COPPER, - TIN, - COAL, - MITHRIL, - ADAMANT, - SILVER, - GOLD, - RUNITE -} \ No newline at end of file diff --git a/Server/src/main/content/minigame/blastfurnace/BlastFurnaceZone.kt b/Server/src/main/content/minigame/blastfurnace/BlastFurnaceZone.kt deleted file mode 100644 index 2a2c75b0a..000000000 --- a/Server/src/main/content/minigame/blastfurnace/BlastFurnaceZone.kt +++ /dev/null @@ -1,58 +0,0 @@ -package content.minigame.blastfurnace - -import core.api.* -import core.game.node.entity.Entity -import core.game.node.item.Item -import core.game.shops.Shops -import core.game.world.map.zone.ZoneBorders -import core.tools.secondsToTicks -import org.rs09.consts.NPCs -import kotlin.math.min - -/**Code for defining the Blast Furnace zone, Blast Furnace will only - * operate and run its logic if there are actual players in this zone - * @author phil lips, ceikry*/ - -class BlastFurnaceZone : MapArea, TickListener { - var pulseStarted = false - var lastShopRestock = 0 - - override fun defineAreaBorders(): Array { - return arrayOf(ZoneBorders(1935,4956,1956,4974)) - } - - override fun areaEnter(entity: Entity) { - if (!pulseStarted){ - submitWorldPulse(BlastFurnace.blastPulse) - pulseStarted = true - } - if (entity.isPlayer) { - BlastFurnace.blastFurnacePlayerList.add(entity.asPlayer()) - } - } - - override fun areaLeave(entity: Entity, logout: Boolean) { - if (entity.isPlayer) { - BlastFurnace.blastFurnacePlayerList.remove(entity.asPlayer()) - } - } - - override fun tick() { - if (getWorldTicks() - lastShopRestock > secondsToTicks(600)) { - lastShopRestock = getWorldTicks() - restockRandomOre() - } - } - - private fun restockRandomOre() { - val shop = Shops.shopsByNpc[NPCs.ORDAN_2564] ?: return - val stockContainer = shop.stockInstances.values.firstOrNull() ?: return - val restockThisTick = shop.stock.random() - stockContainer.add( - Item( - restockThisTick.itemId, - min(30, 100 - stockContainer.getAmount(restockThisTick.itemId)) - ) - ) - } -} \ No newline at end of file diff --git a/Server/src/main/content/minigame/blastfurnace/BlastState.kt b/Server/src/main/content/minigame/blastfurnace/BlastState.kt new file mode 100644 index 000000000..07dafe3b5 --- /dev/null +++ b/Server/src/main/content/minigame/blastfurnace/BlastState.kt @@ -0,0 +1,76 @@ +package content.minigame.blastfurnace + +import core.tools.RandomFunction + +class BlastState { + var disableBreaking = false + var forceBreaking = false + var potPipeBroken = false + var pumpPipeBroken = false + var beltBroken = false + var cogBroken = false + var ticksElapsed = 0 + + var stoveTemp = 0 + private set + var furnaceTemp = 0 + private set + var cokeInStove = 0 + private set + + fun tick(pumping: Boolean, pedaling: Boolean) { + ticksElapsed++ + + adjustStoveTemperature() + adjustFurnaceTemperature(pumping) + + checkForCokeDecrement() + checkForBreakage(pedaling, pumping) + } + + private fun adjustStoveTemperature() { + if (cokeInStove > 0) + stoveTemp = (stoveTemp + 1).coerceAtMost(100) + else stoveTemp = (stoveTemp - 1).coerceAtLeast(0) + } + + private fun adjustFurnaceTemperature(pumping: Boolean) { + if (pumping && !pumpPipeBroken && !potPipeBroken && !beltBroken && !cogBroken) { + when (stoveTemp) { + in 1..32 -> furnaceTemp += 1 + in 32..66 -> furnaceTemp += 2 + in 67..100 -> furnaceTemp += 3 + } + } else furnaceTemp-- + + furnaceTemp = furnaceTemp + .coerceAtLeast(0) + .coerceAtMost(100) + } + + private fun checkForBreakage(pedaling: Boolean, pumping: Boolean) { + if (disableBreaking) return + if (pumping && (!potPipeBroken || !pumpPipeBroken)) { + if (RandomFunction.roll(50) || forceBreaking) { + if (RandomFunction.nextBool()) potPipeBroken = true + else pumpPipeBroken = true + } + } + + if (pedaling && (!beltBroken || !cogBroken)) { + if (RandomFunction.roll(50) || forceBreaking) + beltBroken = true + else if (RandomFunction.roll(50) || forceBreaking) + cogBroken = true + } + } + + private fun checkForCokeDecrement() { + if (ticksElapsed % 10 == 0) + cokeInStove = (cokeInStove - 1).coerceAtLeast(0) + } + + fun addCoke (amount: Int) { + cokeInStove += amount.coerceAtMost(BlastConsts.COKE_LIMIT - cokeInStove) + } +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/blastfurnace/PhunnyGaugeTempInterfaceListener.kt b/Server/src/main/content/minigame/blastfurnace/PhunnyGaugeTempInterfaceListener.kt deleted file mode 100644 index 7edbab79c..000000000 --- a/Server/src/main/content/minigame/blastfurnace/PhunnyGaugeTempInterfaceListener.kt +++ /dev/null @@ -1,24 +0,0 @@ -package content.minigame.blastfurnace - -import core.game.interaction.InterfaceListener - -/**Handles adding and removing players to the temp gauge viewing list. - * Only updates the gauge if people are actually looking at it - * @author phil lips*/ - -class PhunnyGaugeTempInterfaceListener : InterfaceListener { - - override fun defineInterfaceListeners() { - onOpen(30) {player, component -> - BlastFurnace.gaugeViewList.add(player) - return@onOpen true - } - - onClose(30) {player, component -> - BlastFurnace.gaugeViewList.remove(player) - return@onClose true - } - } - - -} \ No newline at end of file diff --git a/Server/src/main/content/region/misc/keldagrim/handlers/KeldagrimPlugin.kt b/Server/src/main/content/region/misc/keldagrim/handlers/KeldagrimPlugin.kt index 205fb29d6..24a7a333b 100644 --- a/Server/src/main/content/region/misc/keldagrim/handlers/KeldagrimPlugin.kt +++ b/Server/src/main/content/region/misc/keldagrim/handlers/KeldagrimPlugin.kt @@ -1,18 +1,19 @@ package content.region.misc.keldagrim.handlers +import content.minigame.blastfurnace.BlastFurnace import core.api.* import core.cache.def.impl.SceneryDefinition -import core.game.dialogue.DialoguePlugin import core.game.interaction.OptionHandler import core.game.node.Node import core.game.node.entity.player.Player import core.game.node.item.Item -import core.game.node.scenery.Scenery import core.game.world.map.Location import core.plugin.Initializable import core.plugin.Plugin import core.game.dialogue.DialogueFile -import core.game.node.entity.skill.Skills +import core.game.dialogue.DialoguePlugin +import core.tools.END_DIALOGUE +import org.rs09.consts.Items /** * File that contains several plugins relating to Keldagrim, @@ -40,16 +41,6 @@ class KeldagrimOptionHandlers : OptionHandler() { 5998 -> player.properties.teleportLocation = Location.create(2780, 10161) } } - "climb-up" -> { - when(node.id){ - 9138 -> player.properties.teleportLocation = Location.create(2930, 10197, 0) - } - } - "climb-down" -> { - when(node.id){ - 9084 -> player.dialogueInterpreter.open(BlastFurnaceDoorDialogue(),Scenery(9084, location(2929,10196,0))) - } - } "open" -> { when(node.id){ 28094 -> player.dialogueInterpreter.open(GETrapdoorDialogueID) @@ -113,38 +104,23 @@ class GETrapdoorDialogue(player: Player? = null) : DialoguePlugin(player){ } -class BlastFurnaceDoorDialogue : DialogueFile(){ +class BlastFurnaceDoorDialogue(val fee: Int) : DialogueFile(){ var init = true override fun handle(componentID: Int, buttonID: Int) { - if (init) { - stage = if (player!!.getSkills().getLevel(Skills.SMITHING) >= 60) { - 100 - } else { - 5 - } - init = false - } when(stage){ - 5 -> sendDialogue(player!!,"You must be Smithing Level 60 or higher in order to enter the Blast Furnace").also { stage = 10 } - 10 -> sendDialogue(player!!,"However, you may enter if you pay the entrance fee").also { stage = 20 } - 20 -> options("Yes","No").also { stage = 30 } - 30 -> when(buttonID){ + 0 -> sendDialogue(player!!,"You must be Smithing Level 60 or higher in order to enter the Blast Furnace").also { stage++ } + 1 -> sendDialogue(player!!,"However, you may enter for 10 minutes if you pay the entrance fee.
($fee gp)").also { stage++ } + 2 -> options("Yes","No").also { stage++ } + 3 -> when(buttonID){ 1 -> { - if(player!!.equipment.contains(6465,1) && player!!.inventory.contains(995, 1250)){ - removeItem(player!!, Item(995, 1250), Container.INVENTORY) - player!!.setAttribute("BlastTimer",1000) - player?.properties?.teleportLocation = Location.create(1940, 4958, 0).also { end() } - } - else if (player!!.inventory.contains(995, 2500)) { - removeItem(player!!, Item(995, 2500), Container.INVENTORY) - player!!.setAttribute("BlastTimer",1000) - player?.properties?.teleportLocation = Location.create(1940, 4958, 0).also { end() } - } else sendDialogue(player!!, "You don't have enough gold to pay the fee!").also { stage = 40 } + if (removeItem(player!!, Item(Items.COINS_995, fee))) + BlastFurnace.enter(player!!, true) + else + sendDialogue(player!!, "You don't have enough gold to cover the entrance fee!") + stage = END_DIALOGUE } - 20 -> sendDialogue(player!!,"Then get out of here!").also { stage = 40 } + 20 -> sendDialogue(player!!,"Then get out of here!").also { stage = END_DIALOGUE } } - 40 -> end() - 100 -> player?.properties?.teleportLocation = Location.create(1940, 4958, 0).also { stage = 40 } } } } diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index 3d912f475..a79d15c38 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -472,6 +472,10 @@ fun addScenery (sceneryId: Int, location: Location, rotation: Int = 0, type: Int return scenery } +fun addScenery (scenery: Scenery) { + SceneryBuilder.add(scenery) +} + /** * Remove a scenery from the world * @param scenery the Scenery object to remove. @@ -1271,7 +1275,7 @@ fun truncateLoc (mover: Entity, destination: Location) : Pair * @param callback (optional) a callback called when the forced movement completes * @see NOTE: There are 30 client cycles per second. */ -fun forceMove (player: Player, start: Location, dest: Location, startArrive: Int, destArrive: Int, dir: Direction? = null, anim: Int = -1, callback: (()->Unit)? = null) { +fun forceMove (player: Player, start: Location, dest: Location, startArrive: Int, destArrive: Int, dir: Direction? = null, anim: Int = 819, callback: (()->Unit)? = null) { var direction: Direction if (dir == null) { diff --git a/Server/src/test/kotlin/content/activity/blastfurnace/BFBeltOreTests.kt b/Server/src/test/kotlin/content/activity/blastfurnace/BFBeltOreTests.kt new file mode 100644 index 000000000..0d8f33627 --- /dev/null +++ b/Server/src/test/kotlin/content/activity/blastfurnace/BFBeltOreTests.kt @@ -0,0 +1,44 @@ +package content.activity.blastfurnace + +import TestUtils +import content.minigame.blastfurnace.BFBeltOre +import content.minigame.blastfurnace.BlastFurnace +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.rs09.consts.Items + +class BFBeltOreTests { + init {TestUtils.preTestSetup()} + + @Test fun oreShouldMoveCloserToPotWhenTicked() { + TestUtils.getMockPlayer("bf-oremove-test").use { p -> + p.location = BFBeltOre.ORE_START_LOC + val ore = BlastFurnace.addOreToBelt(p, Items.IRON_ORE_440, 5) + Assertions.assertEquals(BFBeltOre.ORE_START_LOC, ore.location) + Assertions.assertEquals(BFBeltOre.ORE_START_LOC, ore.npcInstance?.location) + + ore.tick() + val expectedLoc = BFBeltOre.ORE_START_LOC.transform(0, -1, 0) + Assertions.assertEquals(expectedLoc, ore.location) + Assertions.assertEquals(expectedLoc, ore.npcInstance?.properties?.teleportLocation) + + TestUtils.advanceTicks(1, false) + ore.tick() + Assertions.assertEquals(expectedLoc, ore.npcInstance?.location) + } + } + + @Test fun oreShouldBeAddedToOreContainerAfterReachingEnd() { + TestUtils.getMockPlayer("bf-oremoveadd-test").use { p -> + p.location = BFBeltOre.ORE_START_LOC + val ore = BlastFurnace.addOreToBelt(p, Items.IRON_ORE_440, 5) + + for (i in 0 until 4) + if (ore.tick()) BlastFurnace.getPlayerState(p).oresOnBelt.remove(ore) + + val container = BlastFurnace.getOreContainer(p) + Assertions.assertEquals(5, container.getOreAmount(Items.IRON_ORE_440)) + Assertions.assertEquals(0, BlastFurnace.getAmountOnBelt(p, Items.IRON_ORE_440)) + } + } +} \ No newline at end of file diff --git a/Server/src/test/kotlin/content/activity/blastfurnace/BFPlayerStateTests.kt b/Server/src/test/kotlin/content/activity/blastfurnace/BFPlayerStateTests.kt new file mode 100644 index 000000000..cafd2694d --- /dev/null +++ b/Server/src/test/kotlin/content/activity/blastfurnace/BFPlayerStateTests.kt @@ -0,0 +1,101 @@ +package content.activity.blastfurnace + +import TestUtils +import content.global.skill.smithing.smelting.Bar +import content.minigame.blastfurnace.BFPlayerState +import content.minigame.blastfurnace.BlastFurnace +import core.api.addItem +import core.api.amountInInventory +import core.api.setVarbit +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.rs09.consts.Items + +class BFPlayerStateTests { + init { TestUtils.preTestSetup() } + + @Test fun processOreIntoBarsShouldDoNothingIfBarsNotCooled() { + TestUtils.getMockPlayer("bf-barsnotcooled").use { p -> + val state = BlastFurnace.getPlayerState(p) + state.container.addOre(Items.IRON_ORE_440, 28) + Assertions.assertEquals(true, state.processOresIntoBars()) + Assertions.assertEquals(28, state.container.getBarAmount(Bar.IRON)) + + state.container.addCoal(40) + state.container.addOre(Items.RUNITE_ORE_451, 10) + Assertions.assertEquals(false, state.processOresIntoBars()) + + Assertions.assertEquals(0, state.container.getBarAmount(Bar.RUNITE)) + } + } + + @Test fun processOreIntoBarsShouldDoNothingIfBarsUnchecked() { + TestUtils.getMockPlayer("bf-barsnotchecked").use { p -> + val state = BlastFurnace.getPlayerState(p) + state.container.addOre(Items.IRON_ORE_440, 28) + Assertions.assertEquals(true, state.processOresIntoBars()) + state.coolBars() + + state.container.addCoal(40) + state.container.addOre(Items.RUNITE_ORE_451, 10) + Assertions.assertEquals(false, state.processOresIntoBars()) + Assertions.assertEquals(0, state.container.getBarAmount(Bar.RUNITE)) + + state.checkBars() + Assertions.assertEquals(true, state.processOresIntoBars()) + Assertions.assertEquals(10, state.container.getBarAmount(Bar.RUNITE)) + } + } + + @Test fun shouldBeAbleToClaimBars() { + TestUtils.getMockPlayer("bf-barclaiminig").use { p -> + val state = BlastFurnace.getPlayerState(p) + state.container.addOre(Items.IRON_ORE_440, 28) + Assertions.assertEquals(true, state.processOresIntoBars()) + state.coolBars() + + Assertions.assertEquals(true, state.claimBars(Bar.IRON, 5)) + Assertions.assertEquals(5, amountInInventory(p, Items.IRON_BAR_2351)) + Assertions.assertEquals(23, state.container.getBarAmount(Bar.IRON)) + } + } + + @Test fun shouldNotBeAbleToClaimMoreBarsThanFreeInventorySlots() { + TestUtils.getMockPlayer("bf-claimbarslessslots").use { p -> + val state = BlastFurnace.getPlayerState(p) + state.container.addOre(Items.IRON_ORE_440, 28) + Assertions.assertEquals(true, state.processOresIntoBars()) + state.coolBars() + + addItem(p, Items.ABYSSAL_WHIP_4151, 27) + + Assertions.assertEquals(true, state.claimBars(Bar.IRON, 5)) + Assertions.assertEquals(1, amountInInventory(p, Items.IRON_BAR_2351)) + Assertions.assertEquals(27, state.container.getBarAmount(Bar.IRON)) + } + } + + @Test fun claimBarsShouldDoNothingAndGrantNoItemIfInventoryFull() { + TestUtils.getMockPlayer("bf-claimbarsnoslots").use { p -> + val state = BlastFurnace.getPlayerState(p) + state.container.addOre(Items.IRON_ORE_440, 28) + Assertions.assertEquals(true, state.processOresIntoBars()) + state.coolBars() + + addItem(p, Items.ABYSSAL_WHIP_4151, 28) + + Assertions.assertEquals(false, state.claimBars(Bar.IRON, 5)) + Assertions.assertEquals(0, amountInInventory(p, Items.IRON_BAR_2351)) + Assertions.assertEquals(28, state.container.getBarAmount(Bar.IRON)) + } + } + + @Test fun claimBarsShouldDoNothingIfBarsNotCooled() { + TestUtils.getMockPlayer("bf-claimbarsnotcooled").use { p -> + val state = BlastFurnace.getPlayerState(p) + state.container.addOre(Items.IRON_ORE_440, 28) + Assertions.assertEquals(true, state.processOresIntoBars()) + Assertions.assertEquals(false, state.claimBars(Bar.IRON, 5)) + } + } +} \ No newline at end of file diff --git a/Server/src/test/kotlin/content/activity/blastfurnace/BFSceneryControllerTests.kt b/Server/src/test/kotlin/content/activity/blastfurnace/BFSceneryControllerTests.kt new file mode 100644 index 000000000..046281852 --- /dev/null +++ b/Server/src/test/kotlin/content/activity/blastfurnace/BFSceneryControllerTests.kt @@ -0,0 +1,42 @@ +package content.activity.blastfurnace + +import content.minigame.blastfurnace.BFSceneryController +import core.api.getScenery +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class BFSceneryControllerTests { + init { TestUtils.preTestSetup(); BFSceneryController().resetAllScenery() } + + @Test + fun updateBrokenShouldReplaceWithCorrectVariants() { + val scenCont = BFSceneryController() + + scenCont.updateBreakable(true, true, true, true) + Assertions.assertEquals(BFSceneryController.BROKEN_BELT, getScenery(BFSceneryController.beltGearRight)?.id) + Assertions.assertEquals(BFSceneryController.BROKEN_COG, getScenery(BFSceneryController.cogRightLoc)?.id) + Assertions.assertEquals(BFSceneryController.BROKEN_POT_PIPE, getScenery(BFSceneryController.potPipeLoc)?.id) + Assertions.assertEquals(BFSceneryController.BROKEN_PUMP_PIPE, getScenery(BFSceneryController.pumpPipeLoc)?.id) + + scenCont.updateBreakable(false, false, false, false) + Assertions.assertEquals(BFSceneryController.DEFAULT_BELT, getScenery(BFSceneryController.beltGearRight)?.id) + Assertions.assertEquals(BFSceneryController.DEFAULT_COG, getScenery(BFSceneryController.cogRightLoc)?.id) + Assertions.assertEquals(BFSceneryController.DEFAULT_POT_PIPE, getScenery(BFSceneryController.potPipeLoc)?.id) + Assertions.assertEquals(BFSceneryController.DEFAULT_PUMP_PIPE, getScenery(BFSceneryController.pumpPipeLoc)?.id) + } + + @Test + fun stoveIdShouldCorrespondToTemperature() { + val testData = arrayOf( + Pair(0, BFSceneryController.STOVE_COLD), + Pair(40, BFSceneryController.STOVE_WARM), + Pair(80, BFSceneryController.STOVE_HOT) + ) + for ((temp, expectedId) in testData) { + val cont = BFSceneryController() + cont.resetAllScenery() + cont.updateStove(temp) + Assertions.assertEquals(expectedId, getScenery(BFSceneryController.stoveLoc)!!.id) + } + } +} \ No newline at end of file diff --git a/Server/src/test/kotlin/content/activity/blastfurnace/BlastFurnaceAreaTests.kt b/Server/src/test/kotlin/content/activity/blastfurnace/BlastFurnaceAreaTests.kt new file mode 100644 index 000000000..8926443f1 --- /dev/null +++ b/Server/src/test/kotlin/content/activity/blastfurnace/BlastFurnaceAreaTests.kt @@ -0,0 +1,245 @@ +package content.activity.blastfurnace + +import TestUtils +import content.minigame.blastfurnace.BlastFurnace +import content.minigame.blastfurnace.BlastFurnaceListeners +import content.minigame.blastfurnace.BlastConsts +import core.ServerConstants +import core.api.addItem +import core.api.amountInInventory +import core.game.node.entity.skill.Skills +import core.game.world.map.Location +import core.game.world.map.RegionManager +import org.json.simple.JSONObject +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.rs09.consts.Items + +class BlastFurnaceAreaTests { + @Test fun shouldBeAbleToEnterBfArea() { + TestUtils.getMockPlayer("bf-enterable").use { p -> + p.skills.setStaticLevel(Skills.SMITHING, 60) + p.location = Location(2932, 10195, 0) + TestUtils.simulateInteraction(p, RegionManager.getObject(BlastConsts.STAIRLOC_ENTRANCE)!!, 0) + TestUtils.advanceTicks(5, false) + Assertions.assertEquals(BlastConsts.ENTRANCE_LOC, p.location) + } + } + + @Test fun shouldBeAbleToLeaveBfArea() { + TestUtils.getMockPlayer("bf-leavable").use { p -> + p.location = BlastConsts.ENTRANCE_LOC + TestUtils.simulateInteraction(p, RegionManager.getObject(BlastConsts.STAIRLOC_EXIT)!!, 0) + TestUtils.advanceTicks(5, false) + Assertions.assertEquals(BlastConsts.EXIT_LOC, p.location) + } + } + + @Test fun shouldNotBeAbleToEnterWithLessThan60Smithing() { + TestUtils.getMockPlayer("bf-60smith").use { p -> + p.location = Location(2932, 10195, 0) + TestUtils.simulateInteraction(p, RegionManager.getObject(BlastConsts.STAIRLOC_ENTRANCE)!!, 0) + TestUtils.advanceTicks(5, false) + Assertions.assertNotEquals(BlastConsts.ENTRANCE_LOC, p.location) + } + } + + @Test fun getFeePriceReturnsExpectedValues() { + val testData = arrayOf( + Triple(false, 10, 2500), + Triple(true, 10, 1250), + Triple(false, 60, 0), + Triple(true, 60, 0), + Triple(false, 59, 2500), + Triple(true, 59, 1250) + ) + for ((hasCharos, smithLevel, expected) in testData) + Assertions.assertEquals(expected, BlastFurnace.getEntranceFee(hasCharos, smithLevel)) + } + + @Test fun enterWithFeeShouldKickPlayerOutAfter10Minutes() { + TestUtils.getMockPlayer("bf-fee-kickout").use { p -> + BlastFurnace.enter(p, true) + TestUtils.advanceTicks(BlastConsts.FEE_ENTRANCE_DURATION + 2, false) + Assertions.assertNotEquals(BlastConsts.ENTRANCE_LOC, p.location) + } + } + + @Test fun shouldBeAbleToLeaveAndEnterFreelyWhileTimerActive() { + TestUtils.getMockPlayer("bf-fee-reentry").use { p -> + BlastFurnace.enter(p, true) + TestUtils.advanceTicks(2, false) + Assertions.assertEquals(BlastConsts.ENTRANCE_LOC, p.location) + TestUtils.simulateInteraction(p, RegionManager.getObject(BlastConsts.STAIRLOC_EXIT)!!, 0) + TestUtils.advanceTicks(2, false) + Assertions.assertEquals(BlastConsts.EXIT_LOC, p.location) + TestUtils.simulateInteraction(p, RegionManager.getObject(BlastConsts.STAIRLOC_ENTRANCE)!!, 0) + TestUtils.advanceTicks(2, false) + Assertions.assertEquals(BlastConsts.ENTRANCE_LOC, p.location) + + p.location = BlastConsts.EXIT_LOC + TestUtils.advanceTicks(BlastConsts.FEE_ENTRANCE_DURATION, false) + //should not allow free reentry if timer has run out + TestUtils.simulateInteraction(p, RegionManager.getObject(BlastConsts.STAIRLOC_ENTRANCE)!!, 0) + TestUtils.advanceTicks(2, false) + Assertions.assertEquals(BlastConsts.EXIT_LOC, p.location) + } + } + + @Test fun playerShouldOnlyBeTeleportedIfInsideBFArea() { + TestUtils.getMockPlayer("bf-fee-timeout-notele-outside-bf").use { p -> + BlastFurnace.enter(p, true) + TestUtils.advanceTicks(2, false) + Assertions.assertEquals(BlastConsts.ENTRANCE_LOC, p.location) + + p.location = ServerConstants.HOME_LOCATION + + TestUtils.advanceTicks(BlastConsts.FEE_ENTRANCE_DURATION, false) + Assertions.assertEquals(ServerConstants.HOME_LOCATION, p.location) + } + } + + @Test fun playerShouldBeAbleToPlaceOreOnBelt() { + TestUtils.getMockPlayer("bf-placeoreonbelt").use { p -> + addItem(p, Items.COAL_453, 2) + BlastFurnace.placeAllOre(p) + Assertions.assertEquals(2, BlastFurnace.getAmountOnBelt(p, Items.COAL_453)) + Assertions.assertEquals(0, amountInInventory(p, Items.COAL_453)) + } + } + + @Test fun playerShouldNotBeAbleToPlaceMoreOreThanCanFitOnBelt() { + TestUtils.getMockPlayer("bf-toomuchoreonbelt").use { p -> + val cont = BlastFurnace.getOreContainer(p) + cont.addCoal(BlastConsts.COAL_LIMIT - 15) + cont.addOre(Items.IRON_ORE_440, BlastConsts.ORE_LIMIT - 13) + + addItem(p, Items.COAL_453, 15) + addItem(p, Items.IRON_ORE_440, 13) + BlastFurnace.placeAllOre(p) + + addItem(p, Items.COAL_453, 15) + addItem(p, Items.IRON_ORE_440, 13) + BlastFurnace.placeAllOre(p) + + Assertions.assertEquals(15, amountInInventory(p, Items.COAL_453)) + Assertions.assertEquals(13, amountInInventory(p, Items.IRON_ORE_440)) + } + } + + @Test fun beltOreLimitsShouldAccountForCreatedBars() { + TestUtils.getMockPlayer("bf-baraccountedfor").use { p -> + val cont = BlastFurnace.getOreContainer(p) + cont.addOre(Items.IRON_ORE_440, BlastConsts.ORE_LIMIT - 15) + cont.convertToBars() + + addItem(p, Items.IRON_ORE_440, 20) + BlastFurnace.placeAllOre(p) + + Assertions.assertEquals(5, amountInInventory(p, Items.IRON_ORE_440)) + } + } + + @Test fun playerShouldBeAbleToOnlyPlaceSpecificOreOnBelt() { + TestUtils.getMockPlayer("bf-specific-oreplace").use { p -> + addItem(p, Items.IRON_ORE_440, 5) + addItem(p, Items.COAL_453, 3) + addItem(p, Items.RUNITE_ORE_451, 10) + BlastFurnace.placeAllOre(p, Items.RUNITE_ORE_451) + Assertions.assertEquals(0, amountInInventory(p, Items.RUNITE_ORE_451)) + Assertions.assertEquals(5, amountInInventory(p, Items.IRON_ORE_440)) + Assertions.assertEquals(3, amountInInventory(p, Items.COAL_453)) + } + } + + @Test fun playerShouldNotBeAbleToPlaceOresTheyLackTheLevelFor() { + TestUtils.getMockPlayer("bf-levelgate-oreplace").use { p -> + addItem(p, Items.RUNITE_ORE_451, 10) + BlastFurnace.placeAllOre(p, Items.RUNITE_ORE_451, accountForSkill = true) + Assertions.assertEquals(10, amountInInventory(p, Items.RUNITE_ORE_451)) + } + } + + @Test fun shouldNotBeAbleToOccupyExtraBronzeSlotsWithMoreThan28TinOrCopper() { + TestUtils.getMockPlayer("bf-bronze-orelimit").use { p -> + //Edge case - bronze bars have an edge case that allows 28 of both {copper, tin}, so this needs to make sure you can't, for example, add 56 copper. + addItem(p, Items.COPPER_ORE_436, 28) + BlastFurnace.placeAllOre(p) + addItem(p, Items.COPPER_ORE_436, 28) + BlastFurnace.placeAllOre(p) + + Assertions.assertEquals(28, BlastFurnace.getAmountOnBelt(p, Items.COPPER_ORE_436)) + Assertions.assertEquals(28, amountInInventory(p, Items.COPPER_ORE_436)) + + p.inventory.clear() + addItem(p, Items.TIN_ORE_438, 28) + BlastFurnace.placeAllOre(p) + addItem(p, Items.TIN_ORE_438, 28) + BlastFurnace.placeAllOre(p) + + Assertions.assertEquals(28, BlastFurnace.getAmountOnBelt(p, Items.TIN_ORE_438)) + Assertions.assertEquals(28, amountInInventory(p, Items.TIN_ORE_438)) + } + } + + @Test fun shouldNotBeAbleToPlaceMoreThan28TotalNonCoalOreOnTheBelt() { + TestUtils.getMockPlayer("bf-orelimit").use { p -> + addItem(p, Items.GOLD_ORE_444, 28) + BlastFurnace.placeAllOre(p) + addItem(p, Items.RUNITE_ORE_451, 28) + BlastFurnace.placeAllOre(p) + + Assertions.assertEquals(28, BlastFurnace.getAmountOnBelt(p, Items.GOLD_ORE_444)) + Assertions.assertEquals(0, amountInInventory(p, Items.GOLD_ORE_444)) + Assertions.assertEquals(0, BlastFurnace.getAmountOnBelt(p, Items.RUNITE_ORE_451)) + Assertions.assertEquals(28, amountInInventory(p, Items.RUNITE_ORE_451)) + } + } + + @Test fun shouldBeAbleToPlace28CopperAndTinOreOnTheBelt() { + TestUtils.getMockPlayer("bf-orelimit").use { p -> + addItem(p, Items.COPPER_ORE_436, 28) + BlastFurnace.placeAllOre(p) + addItem(p, Items.TIN_ORE_438, 28) + BlastFurnace.placeAllOre(p) + + Assertions.assertEquals(28, BlastFurnace.getAmountOnBelt(p, Items.COPPER_ORE_436)) + Assertions.assertEquals(0, amountInInventory(p, Items.COPPER_ORE_436)) + Assertions.assertEquals(28, BlastFurnace.getAmountOnBelt(p, Items.TIN_ORE_438)) + Assertions.assertEquals(0, amountInInventory(p, Items.TIN_ORE_438)) + } + } + + @Test fun BFAreaShouldPersistInfoAcrossPlayerRelogs() { + TestUtils.getMockPlayer("bf-persistence").use { p -> + var container = BlastFurnace.getOreContainer(p) + container.addOre(Items.IRON_ORE_440, 15) + container.addCoal(40) + + BlastFurnace.addOreToBelt(p, Items.IRON_ORE_440, 4) + BlastFurnace.addOreToBelt(p, Items.RUNITE_ORE_451, 10) + BlastFurnace.addOreToBelt(p, Items.COAL_453, 20) + + val area = BlastFurnace() + val saveObj = JSONObject() + area.savePlayer(p, saveObj) + + BlastFurnace.playerStates.clear() + + area.parsePlayer(p, saveObj) + container = BlastFurnace.getOreContainer(p) + Assertions.assertEquals(15, container.getOreAmount(Items.IRON_ORE_440)) + Assertions.assertEquals(40, container.coalAmount()) + Assertions.assertEquals(4, BlastFurnace.getAmountOnBelt(p, Items.IRON_ORE_440)) + Assertions.assertEquals(10, BlastFurnace.getAmountOnBelt(p, Items.RUNITE_ORE_451)) + Assertions.assertEquals(20, BlastFurnace.getAmountOnBelt(p, Items.COAL_453)) + } + } + + companion object { + init { + TestUtils.preTestSetup() + BlastFurnaceListeners().defineListeners() + } + } +} \ No newline at end of file diff --git a/Server/src/test/kotlin/content/activity/blastfurnace/BlastFurnaceOreContainerTests.kt b/Server/src/test/kotlin/content/activity/blastfurnace/BlastFurnaceOreContainerTests.kt new file mode 100644 index 000000000..70db557f4 --- /dev/null +++ b/Server/src/test/kotlin/content/activity/blastfurnace/BlastFurnaceOreContainerTests.kt @@ -0,0 +1,169 @@ +package content.activity.blastfurnace + +import content.global.skill.smithing.smelting.Bar +import content.minigame.blastfurnace.BFOreContainer +import content.minigame.blastfurnace.BlastConsts +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.rs09.consts.Items + +class BlastFurnaceOreContainerTests { + @Test fun shouldBeAbleToAddCoal() { + val cont = BFOreContainer() + cont.addCoal(15) + Assertions.assertEquals(15, cont.coalAmount()) + } + + @Test fun addCoalShouldReturnExtraAmountIfAddingMoreThanPossible() { + val cont = BFOreContainer() + cont.addCoal(BlastConsts.COAL_LIMIT - 26) + Assertions.assertEquals(2, cont.addCoal(28)) + } + + @Test fun shouldBeAbleToAddOres() { + val cont = BFOreContainer() + cont.addOre (Items.IRON_ORE_440, 20) + Assertions.assertEquals(20, cont.getOreAmount(Items.IRON_ORE_440)) + } + + @Test fun addOreShouldReturnExtraAmountIfAddingMoreThanPossible() { + val cont = BFOreContainer() + cont.addOre(Items.IRON_ORE_440, BlastConsts.ORE_LIMIT - 2) + Assertions.assertEquals(3, cont.addOre(Items.IRON_ORE_440, 5)) + } + + @Test fun addOreShouldReturnExtraAmountWhenAddingMoreCopperOrTinThanPossible() { + val contTin = BFOreContainer() + Assertions.assertEquals(28, contTin.addOre(Items.TIN_ORE_438, 56)) + + val contCopper = BFOreContainer() + Assertions.assertEquals(28, contCopper.addOre(Items.COPPER_ORE_436, 56)) + } + + @Test fun convertToBarsShouldYieldExpectedResults() { + data class Data (val coalAmount: Int, val oreAmount: Array>, val expectedOreAmounts: Array>, val expectedCoalAmount: Int, val expectedBarResult: Array>) + val testData = arrayOf( + Data( //standard case - 20 iron with no coal creates 20 iron bars + 0, arrayOf(Pair(Items.IRON_ORE_440, 20)), + arrayOf(Pair(Items.IRON_ORE_440, 0)), 0, arrayOf(Pair(Bar.IRON, 20)) + ), + Data ( //edge case - 20 iron with 10 coal produces 10 steel and 10 iron + 10, arrayOf(Pair(Items.IRON_ORE_440, 20)), + arrayOf(Pair(Items.IRON_ORE_440, 0)), 0, arrayOf(Pair(Bar.STEEL, 10), Pair(Bar.IRON, 10)) + ), + Data ( //standard case - 20 coal with 10 mithril produces 10 mithril bars + 20, arrayOf(Pair(Items.MITHRIL_ORE_447, 10)), + arrayOf(Pair(Items.MITHRIL_ORE_447, 0)), 0, arrayOf(Pair(Bar.MITHRIL, 10)) + ), + Data ( //standard case - 30 coal with 10 adamantite produces 10 addy bars + 30, arrayOf(Pair(Items.ADAMANTITE_ORE_449, 10)), + arrayOf(Pair(Items.ADAMANTITE_ORE_449, 0)), 0, arrayOf(Pair(Bar.ADAMANT, 10)) + ), + Data ( //standard case - 40 coal with 10 runite produces 10 runite bars + 40, arrayOf(Pair(Items.RUNITE_ORE_451, 10)), + arrayOf(Pair(Items.RUNITE_ORE_451, 0)), 0, arrayOf(Pair(Bar.RUNITE, 10)) + ), + Data ( //semi-edge case - 28 gold with 150 coal produces 28 gold bars with 150 coal remaining (doesn't use any coal when not needed) + 150, arrayOf(Pair(Items.GOLD_ORE_444, 28)), + arrayOf(Pair(Items.GOLD_ORE_444, 0)), 150, arrayOf(Pair(Bar.GOLD, 28)) + ), + Data ( //edge case - 18 silver and 10 runite with 58 coal produces 18 silver bars and 10 runite bars with 18 coal remaining + 58, arrayOf(Pair(Items.SILVER_ORE_442, 18), Pair(Items.RUNITE_ORE_451, 10)), + arrayOf(Pair(Items.SILVER_ORE_442, 0), Pair(Items.RUNITE_ORE_451, 0)), 18, arrayOf(Pair(Bar.SILVER, 18), Pair(Bar.RUNITE, 10)) + ), + Data ( //edge case - only 20 coal but 10 runite (half of what's needed) produces 5 runite bars, with 5 runite ore and 0 coal remaining. + 20, arrayOf(Pair(Items.RUNITE_ORE_451, 10)), + arrayOf(Pair(Items.RUNITE_ORE_451, 5)), 0, arrayOf(Pair(Bar.RUNITE, 5)) + ), + Data (//technically an edge case - 28 copper and 28 tin makes 28 bronze with nothing leftover. + 0, arrayOf(Pair(Items.COPPER_ORE_436, 28), Pair(Items.TIN_ORE_438, 28)), + arrayOf(Pair(Items.COPPER_ORE_436, 0), Pair(Items.TIN_ORE_438, 0)), 0, arrayOf(Pair(Bar.BRONZE, 28)) + ), + Data (//edge case - 10 copper and no tin makes nothing with 10 copper leftover + 0, arrayOf(Pair(Items.COPPER_ORE_436, 10)), + arrayOf(Pair(Items.COPPER_ORE_436, 10)), 0, arrayOf(Pair(Bar.BRONZE, 0)) + ), + Data (//edge case - 14 copper and 5 tin make 5 bronze bars with 9 copper leftover + 0, arrayOf(Pair(Items.COPPER_ORE_436, 14), Pair(Items.TIN_ORE_438, 5)), + arrayOf(Pair(Items.COPPER_ORE_436, 9), Pair(Items.TIN_ORE_438, 0)), 0, arrayOf(Pair(Bar.BRONZE, 5)) + ) + ) + + var index = 0 + for ((initialCoal, initialOres, expectedOres, expectedCoal, expectedBars) in testData) { + val cont = BFOreContainer() + cont.addCoal(initialCoal) + for ((ore, amount) in initialOres) cont.addOre(ore, amount) + cont.convertToBars() + + for ((ore, amount) in expectedOres) + Assertions.assertEquals(amount, cont.getOreAmount(ore), "Problem testcase was $index - Missing $ore") + for ((bar, amount) in expectedBars) + Assertions.assertEquals(amount, cont.getBarAmount(bar), "Problem testcase was $index - Missing ${bar.name}") + Assertions.assertEquals(expectedCoal, cont.coalAmount(), "Problem testcase was $index") + index++ + } + } + + @Test fun convertToBarsShouldNotConsumeMaterialsForAlreadyFilledBarType() { + val cont = BFOreContainer() + cont.addOre(Items.IRON_ORE_440, 28) + cont.convertToBars() + Assertions.assertEquals(28, cont.getBarAmount(Bar.IRON)) + + cont.addOre(Items.IRON_ORE_440, 28) + cont.convertToBars() + Assertions.assertEquals(28, cont.getBarAmount(Bar.IRON)) + Assertions.assertEquals(28, cont.getOreAmount(Items.IRON_ORE_440)) + } + + @Test fun oreContainerShouldCleanlySerializeAndDeserializeFromJson() { + val cont = BFOreContainer() + cont.addOre(Items.IRON_ORE_440, 28) + cont.convertToBars() + + cont.addOre(Items.RUNITE_ORE_451, 15) + cont.addOre(Items.MITHRIL_ORE_447, 13) + cont.addCoal(150) + + val json = cont.toJson() + val deserialized = BFOreContainer.fromJson(json) + + Assertions.assertEquals(28, deserialized.getBarAmount(Bar.IRON)) + Assertions.assertEquals(15, cont.getOreAmount(Items.RUNITE_ORE_451)) + Assertions.assertEquals(13, cont.getOreAmount(Items.MITHRIL_ORE_447)) + Assertions.assertEquals(150, cont.coalAmount()) + } + + @Test fun shouldBeAbleToRemoveBars() { + val cont = BFOreContainer() + cont.addOre(Items.IRON_ORE_440, 28) + cont.convertToBars() + + val bars = cont.takeBars(Bar.IRON, 15) + Assertions.assertEquals(15, bars?.amount) + Assertions.assertEquals(13, cont.getBarAmount(Bar.IRON)) + } + + @Test fun shouldNotBeAbleToRemoveMoreBarsThanPossible() { + val cont = BFOreContainer() + cont.addOre(Items.IRON_ORE_440, 28) + cont.convertToBars() + + val bars = cont.takeBars(Bar.IRON, 50) + Assertions.assertEquals(28, bars?.amount) + Assertions.assertEquals(0, cont.getBarAmount(Bar.IRON)) + } + + @Test fun convertToBarsShouldReturnXPReward() { + val cont = BFOreContainer() + cont.addOre(Items.IRON_ORE_440, 28) + + Assertions.assertEquals(350.0, cont.convertToBars()) + } + + @Test fun removingBarsWithNoStockReturnsNull() { + val cont = BFOreContainer() + Assertions.assertEquals(null, cont.takeBars(Bar.RUNITE, 1)) + } +} \ No newline at end of file diff --git a/Server/src/test/kotlin/content/activity/blastfurnace/BlastStateTests.kt b/Server/src/test/kotlin/content/activity/blastfurnace/BlastStateTests.kt new file mode 100644 index 000000000..02b959450 --- /dev/null +++ b/Server/src/test/kotlin/content/activity/blastfurnace/BlastStateTests.kt @@ -0,0 +1,95 @@ +package content.activity.blastfurnace + +import content.minigame.blastfurnace.BlastState +import content.minigame.blastfurnace.BlastConsts +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class BlastStateTests { + @Test + fun tickPassedWithCokeInStoveShouldIncreaseStoveTemp() { + val state = BlastState(); state.disableBreaking = true + state.addCoke(1) + Assertions.assertEquals(0, state.stoveTemp) + + state.tick(true, false) + Assertions.assertEquals(1, state.stoveTemp) + } + + @Test + fun stoveTempShouldNeverExceed100() { + val state = BlastState(); state.disableBreaking = true + state.addCoke(BlastConsts.COKE_LIMIT) + + for (i in 0 until 150) + state.tick(true, false) + Assertions.assertEquals(100, state.stoveTemp) + } + + @Test fun cokeShouldDisappearFromStove() { + val state = BlastState(); state.disableBreaking = true + state.addCoke(1) + for (i in 0 until 10) + state.tick(true, false) + Assertions.assertEquals(0, state.cokeInStove) + } + + @Test fun stoveTempShouldLowerWithoutCoke() { + val state = BlastState(); state.disableBreaking = true + state.addCoke(1) + for (i in 0 until 10) + state.tick(true, false) + Assertions.assertEquals(10, state.stoveTemp) + for (i in 0 until 10) + state.tick(true, false) + Assertions.assertEquals(0, state.stoveTemp) + } + + @Test + fun stoveTempShouldNeverGoBelow0() { + val state = BlastState(); state.disableBreaking = true + for (i in 0 until 100) + state.tick(true, false) + Assertions.assertEquals(0, state.stoveTemp) + } + + @Test fun furnaceTempShouldIncreaseProportionallyToStoveTempWhilePumping() { + val testData = arrayOf( + Pair(0, 0), + Pair(1, 1), + Pair(4, 2), + Pair(7, 3) + ) + for ((cokeToAdd, expectedTempRise) in testData) { + val state = BlastState(); state.disableBreaking = true + state.addCoke(cokeToAdd) + for (i in 0 until state.cokeInStove * 10) + state.tick(false, false) + + state.tick(true, false) + Assertions.assertEquals(expectedTempRise, state.furnaceTemp) + } + } + + @Test fun pumpingShouldNotTransferHeatIfPipesBroken() { + val state1 = BlastState(); state1.disableBreaking = true + state1.addCoke(BlastConsts.COKE_LIMIT) + state1.pumpPipeBroken = true + for (i in 0 until 20) state1.tick(true, false) + Assertions.assertEquals(0, state1.furnaceTemp) + + val state2 = BlastState(); state2.disableBreaking = true + state2.addCoke(BlastConsts.COKE_LIMIT) + state2.potPipeBroken = true + for (i in 0 until 20) state2.tick(true, false) + Assertions.assertEquals(0, state2.furnaceTemp) + } + + @Test fun pumpingShouldNotTransferHeatIfBeltBroken() { + val state = BlastState(); state.disableBreaking = true + state.addCoke(BlastConsts.COKE_LIMIT) + state.beltBroken = true + for (i in 0 until 20) state.tick(true, false) + Assertions.assertEquals(0, state.furnaceTemp) + } +} \ No newline at end of file