From a313c227d5047d6350c4963d4a84fb2a0d437489 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Fri, 2 Jun 2023 02:15:13 +0000 Subject: [PATCH] Implemented Falador cannonball bot that contributes stock to the GE --- .../content/global/bots/CannonballSmelter.kt | 278 ++++++++++++++++++ Server/src/main/core/game/bots/Script.java | 13 +- Server/src/main/core/game/bots/ScriptAPI.kt | 65 +++- .../core/game/bots/SkillingBotAssembler.kt | 7 +- Server/src/main/core/game/ge/GrandExchange.kt | 16 +- .../src/main/core/game/world/ImmerseWorld.kt | 5 + 6 files changed, 367 insertions(+), 17 deletions(-) create mode 100644 Server/src/main/content/global/bots/CannonballSmelter.kt diff --git a/Server/src/main/content/global/bots/CannonballSmelter.kt b/Server/src/main/content/global/bots/CannonballSmelter.kt new file mode 100644 index 000000000..fd611a26f --- /dev/null +++ b/Server/src/main/content/global/bots/CannonballSmelter.kt @@ -0,0 +1,278 @@ +package content.global.bots + +import content.global.skill.gather.mining.MiningNode +import content.global.skill.smithing.smelting.Bar +import content.global.skill.smithing.smelting.SmeltingPulse +import core.api.* +import core.game.bots.* +import core.game.ge.GrandExchange +import core.game.interaction.DestinationFlag +import core.game.interaction.IntType +import core.game.interaction.InteractionListeners +import core.game.interaction.MovementPulse +import core.game.node.Node +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.game.world.map.Location +import core.game.world.map.zone.ZoneBorders +import org.rs09.consts.Items + +@PlayerCompatible +@ScriptName("Falador Cannonball Smelter") +@ScriptDescription("Start in Falador East Bank with a pick equipped","or in your inventory.") +@ScriptIdentifier("fally_cballs") +class CannonballSmelter : Script() { + var state = State.INIT + val bottomLadder = ZoneBorders(3016,9736,3024,9742) + val topLadder = ZoneBorders(3016,3336,3022,3342) + val coalMine = ZoneBorders(3027,9733,3054,9743) + //val coalMine = ZoneBorders(3056, 9729, 3018, 9758) + val ironMine = ZoneBorders(3052,9768,3035,9777) + val northMineEntrance = ZoneBorders(3062, 3375, 3058, 3381) + val bank = ZoneBorders(3009,3355,3018,3358) + var overlay: ScriptAPI.BottingOverlay? = null + var coalAmount = 0 + + override fun tick() { + when(state){ + + State.INIT -> { + overlay = scriptAPI.getOverlay() + overlay!!.init() + overlay!!.setTitle("Mining") + overlay!!.setTaskLabel("Coal Mined:") + overlay!!.setAmount(0) + + if (coalMine.insideBorder(bot)){ + state = State.MINING + } else { + state = State.TO_BANK + } + } + + State.MINING -> { + bot.interfaceManager.close() + if(bot.inventory.freeSlots() == 0){ + state = State.TO_BANK + } else if(amountInInventory(bot, Items.COAL_453) >= 18) { + state = State.TO_IRONMINE + } else if(!coalMine.insideBorder(bot)){ + scriptAPI.walkTo(coalMine.randomLoc) + } else { + val rock = scriptAPI.getNearestObjectByPredicate({node -> node?.name?.equals("rocks", true)!! && MiningNode.forId(node?.id!!).reward == Items.COAL_453 }) + if(rock != null) { + scriptAPI.interact(bot, rock, "mine") + } else { + scriptAPI.walkTo(coalMine.randomLoc) + } + } + overlay!!.setAmount(amountInInventory(bot, Items.COAL_453)) + } + State.MINING_IRON -> { + bot.interfaceManager.close() + if(bot.inventory.freeSlots() == 0 || amountInInventory(bot, Items.IRON_ORE_440) >= 9) { + state = State.TO_BANK + } else if(!ironMine.insideBorder(bot)){ + var loc = ironMine.randomLoc + scriptAPI.walkTo(loc) + } else { + val rock = scriptAPI.getNearestObjectByPredicate({node -> node?.name?.equals("rocks", true)!! && MiningNode.forId(node?.id!!).reward == Items.IRON_ORE_440 }) + //rock?.let { InteractionListeners.run(rock.id, IntType.SCENERY,"mine",bot,rock) } + if(rock != null) { + scriptAPI.interact(bot, rock, "mine") + } else { + scriptAPI.walkTo(ironMine.randomLoc) + } + } + overlay!!.setAmount(amountInInventory(bot, Items.IRON_ORE_440)) + } + + State.TO_BANK -> { + if(bank.insideBorder(bot)){ + val bank = scriptAPI.getNearestNode("bank booth",true) + if(bank != null) { + state = State.BANKING + bot.pulseManager.run(object : BankingPulse(this, bank){ + override fun pulse(): Boolean { + return super.pulse() + } + }) + } + } else { + if(bot.location.y > 3400) { + if(bot.location.y < 9757) { + val ladder = scriptAPI.getNearestNode(30941, true) + //ladder ?: scriptAPI.walkTo(bottomLadder.randomLoc).also { return } + //ladder?.interaction?.handle(bot, ladder.interaction[0]).also { ladderSwitch = true } + scriptAPI.interact(bot, ladder, "climb-up") + } else { + val stairs = scriptAPI.getNearestNode(30943, true) + scriptAPI.interact(bot, stairs, "climb-up") + + } + } else { + if(northMineEntrance.insideBorder(bot)) { + val door = scriptAPI.getNearestNode(11714, true) + if(door != null) { + scriptAPI.interact(bot, door, "open") + } else { + scriptAPI.walkTo(bank.randomLoc) + } + } else { + scriptAPI.walkTo(bank.randomLoc) + } + } + } + } + + State.BANKING -> { + scriptAPI.bankAll({ + if(amountInBank(bot, Items.CANNONBALL_2) >= 500) { + val total = GrandExchange.getBotstockForId(Items.CANNONBALL_2) + bot.interfaceManager.close() + if(total < 5000) { + state = State.TO_GE + } + } + if(state != State.TO_GE) { + if(amountInBank(bot, Items.IRON_ORE_440) >= 9 && amountInBank(bot, Items.COAL_453) >= 18) { + scriptAPI.withdraw(Items.IRON_ORE_440, 9) + scriptAPI.withdraw(Items.COAL_453, 18) + scriptAPI.withdraw(Items.AMMO_MOULD_4, 1) + state = State.TO_FURNACE + bot.interfaceManager.close() + } else { + state = State.TO_MINE + } + } + }) + } + + State.TO_FURNACE -> { + if(bot.location.x > 2978) { + scriptAPI.walkTo(Location.create(2974, 3369, 0)) + } else if(amountInInventory(bot, Items.STEEL_BAR_2353) < 9) { + val furnace = scriptAPI.getNearestNode(11666, true) + scriptAPI.interact(bot, furnace, "smelt") + // TODO: should bots use real interfaces? + bot.pulseManager.run(SmeltingPulse(bot, null, Bar.STEEL, 9)); + } else { + state = State.SMELTING_CBALLS + } + } + + State.SMELTING_CBALLS -> { + if(amountInInventory(bot, Items.STEEL_BAR_2353) > 0) { + val furnace = scriptAPI.getNearestNode(11666, true) + scriptAPI.useWith(bot, Items.STEEL_BAR_2353, furnace) + if(bot.dialogueInterpreter.dialogue != null) { + bot.dialogueInterpreter.handle(309, 32) + } + } else { + state = State.TO_BANK + } + } + + State.TO_MINE -> { + if(bot.location.y < 3400) { + bot.interfaceManager.close() + if(!topLadder.insideBorder(bot.location)){ + scriptAPI.walkTo(topLadder.randomLoc) + } else { + val ladder = scriptAPI.getNearestNode("Ladder",true) + if(ladder != null){ + ladder.interaction.handle(bot,ladder.interaction[0]) + } else { + scriptAPI.walkTo(topLadder.randomLoc) + } + } + } else { + if(!coalMine.insideBorder(bot)){ + scriptAPI.walkTo(coalMine.randomLoc) + } else { + state = State.MINING + } + } + } + + State.TO_IRONMINE -> { + if(ironMine.insideBorder(bot.location)) { + state = State.MINING_IRON + } else if(bot.location.y < 9757) { + if (bot.location.regionId == ((47 shl 8) or 152)) { + val door = scriptAPI.getNearestNode(2112,true) + if(door != null) { + scriptAPI.interact(bot, door, "open") + } else { + scriptAPI.walkTo(Location.create(3046, 9756, 0)) + } + } else { + scriptAPI.walkTo(Location.create(3046, 9756, 0)) + } + } else { + scriptAPI.walkTo(ironMine.randomLoc) + } + } + + State.TO_GE -> { + scriptAPI.teleportToGE() + state = State.SELLING + } + + State.SELLING -> { + scriptAPI.sellOnGE(Items.CANNONBALL_2) + state = State.GO_BACK + } + + State.GO_BACK -> { + scriptAPI.teleport(bank.randomLoc) + state = State.TO_MINE + } + } + } + + open class BankingPulse(val script: Script, val bank: Node) : MovementPulse(script.bot,bank, DestinationFlag.OBJECT){ + override fun pulse(): Boolean { + script.bot.faceLocation(bank.location) + return true + } + } + + override fun newInstance(): Script { + val script = CannonballSmelter() + script.bot = SkillingBotAssembler().produce(SkillingBotAssembler.Wealth.POOR,bot.startLocation) + return script + } + + enum class State { + MINING, + TO_MINE, + TO_BANK, + TO_FURNACE, + SMELTING_CBALLS, + BANKING, + TO_GE, + SELLING, + GO_BACK, + TO_IRONMINE, + MINING_IRON, + INIT + } + + init { + equipment.add(Item(Items.RUNE_PICKAXE_1275)) + inventory.add(Item(Items.AMMO_MOULD_4)) + if(false) { + // spawn initial iron/coal to debug smelting + inventory.add(Item(Items.COAL_453, 18)) + inventory.add(Item(Items.IRON_ORE_440, 9)) + } + skills.put(Skills.ATTACK,40) + skills.put(Skills.STRENGTH,60) + skills.put(Skills.MINING,75) + skills.put(Skills.HITPOINTS,99) + skills.put(Skills.DEFENCE,99) + skills.put(Skills.SMITHING,35) + quests.add("Dwarf Cannon"); + } +} diff --git a/Server/src/main/core/game/bots/Script.java b/Server/src/main/core/game/bots/Script.java index 0c622998f..c4a580dfa 100644 --- a/Server/src/main/core/game/bots/Script.java +++ b/Server/src/main/core/game/bots/Script.java @@ -13,6 +13,7 @@ public abstract class Script { public ArrayList inventory = new ArrayList<>(20); public ArrayList equipment = new ArrayList<>(20); public Map skills = new HashMap<>(); + public ArrayList quests = new ArrayList<>(20); public Player bot; @@ -25,6 +26,13 @@ public abstract class Script { scriptAPI = new ScriptAPI(bot); if(!isPlayer) { + // Skills and quests need to be set before equipment in case equipment has level or quest requirements + for (Map.Entry skill : skills.entrySet()) { + setLevel(skill.getKey(), skill.getValue()); + } + for (String quest : quests) { + bot.getQuestRepository().setStage(bot.getQuestRepository().getQuest(quest), 100); + } for (Item i : equipment) { bot.getEquipment().add(i, true, false); } @@ -32,9 +40,6 @@ public abstract class Script { for (Item i : inventory) { bot.getInventory().add(i); } - for (Map.Entry skill : skills.entrySet()) { - setLevel(skill.getKey(), skill.getValue()); - } } } @@ -54,4 +59,4 @@ public abstract class Script { // This does not get called and all implementations should be removed public abstract Script newInstance(); -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/bots/ScriptAPI.kt b/Server/src/main/core/game/bots/ScriptAPI.kt index 11bde4dd7..2a66caf46 100644 --- a/Server/src/main/core/game/bots/ScriptAPI.kt +++ b/Server/src/main/core/game/bots/ScriptAPI.kt @@ -1,6 +1,9 @@ package core.game.bots import core.api.* +import core.game.interaction.NodeUsageEvent +import core.game.interaction.PluginInteractionManager +import core.game.interaction.UseWithHandler import core.cache.def.impl.ItemDefinition import core.game.component.Component import core.game.consumable.Consumable @@ -88,6 +91,36 @@ class ScriptAPI(private val bot: Player) { if(!InteractionListeners.run(node.id, type, option, bot, node)) node.interaction.handle(bot, opt) } + fun useWith(bot: Player, itemId: Int, node: Node?) { + if(node == null) return + + val type = when(node){ + is Scenery -> IntType.SCENERY + is NPC -> IntType.NPC + is Item -> IntType.ITEM + else -> null + } ?: return + + val item = bot.inventory.getItem(Item(itemId)) + + val childNode = node.asScenery()?.getChild(bot) + + if (InteractionListeners.run(item, node, type, bot)) + return + if (childNode != null && childNode.id != node.id) { + if (InteractionListeners.run(item, childNode, type, bot)) + return + } + val flipped = type == IntType.ITEM && item.id < node.id + val event = if (flipped) + NodeUsageEvent(bot, 0, node, item) + else + NodeUsageEvent(bot, 0, item, childNode ?: node) + if (PluginInteractionManager.handle(bot, event)) + return + UseWithHandler.run(event) + } + fun sendChat(message: String) { bot.sendChat(message) bot.updateMasks.register(EntityFlag.Chat, ChatMessage(bot, message, 0, 0)) @@ -144,7 +177,11 @@ class ScriptAPI(private val bot: Player) { return processEvaluationList(RegionManager.forId(bot.location.regionId).planes[bot.location.z].entities, acceptedName = listOf(name)) } - fun evaluateViability (e: Node?, minDistance: Double, maxDistance: Double, acceptedNames: List? = null, acceptedId: Int = -1): Boolean { + fun getNearestObjectByPredicate(predicate: (Node?) -> Boolean): Node? { + return processEvaluationList(RegionManager.forId(bot.location.regionId).planes[bot.location.z].objectList, acceptedPredicate = predicate) + } + + fun evaluateViability (e: Node?, minDistance: Double, maxDistance: Double, acceptedNames: List? = null, acceptedId: Int = -1, acceptedPredicate: ((Node?) -> Boolean)? = null): Boolean { if (e == null || !e.isActive) return false if (acceptedId != -1 && e.id != acceptedId) @@ -154,16 +191,20 @@ class ScriptAPI(private val bot: Player) { if (dist > maxDistance || dist > minDistance) return false - val name = e?.name - return (acceptedNames?.contains(name) ?: true && !Pathfinder.find(bot, e).isMoveNear) + if (acceptedPredicate != null) { + return acceptedPredicate(e) && !Pathfinder.find(bot, e).isMoveNear; + } else { + val name = e?.name + return (acceptedNames?.stream()?.anyMatch({ s -> s.equals(name, true) }) ?: true && !Pathfinder.find(bot, e).isMoveNear) + } } - fun processEvaluationList (list: List, acceptedName: List? = null, acceptedId: Int = -1): Node? { + fun processEvaluationList (list: List, acceptedName: List? = null, acceptedId: Int = -1, acceptedPredicate: ((Node?) -> Boolean)? = null): Node? { var entity: Node? = null var minDistance = Double.MAX_VALUE val maxDistance = ServerConstants.MAX_PATHFIND_DISTANCE.toDouble() for (e in list) { - if (evaluateViability(e, minDistance, maxDistance, acceptedName, acceptedId)) { + if (evaluateViability(e, minDistance, maxDistance, acceptedName, acceptedId, acceptedPredicate)) { entity = e minDistance = distance(bot, e) } @@ -583,15 +624,21 @@ class ScriptAPI(private val bot: Player) { * @param none * @author cfunnyman joe */ - fun bankAll(){ + fun bankAll(onComplete: (() -> Unit)? = null){ class BankingPulse() : Pulse(20){ override fun pulse(): Boolean { for(item in bot.inventory.toArray()){ - var itemAmount = bot.inventory.getAmount(item) + if(item != null) { + var itemAmount = bot.inventory.getAmount(item) - bot.inventory.remove(item) - bot.bank.add(item) + if(bot.inventory.remove(item)) { + bot.bank.add(item) + } + } } + if(onComplete != null) { + onComplete?.invoke() + } return true } } diff --git a/Server/src/main/core/game/bots/SkillingBotAssembler.kt b/Server/src/main/core/game/bots/SkillingBotAssembler.kt index 2586504d3..4e47773fa 100644 --- a/Server/src/main/core/game/bots/SkillingBotAssembler.kt +++ b/Server/src/main/core/game/bots/SkillingBotAssembler.kt @@ -21,8 +21,9 @@ class SkillingBotAssembler { val item = Item(i) val configs = item.definition.handlers val slot = configs["equipment_slot"] ?: continue - bot.equipment.add(item, slot as Int, - false,false) + if(bot.inventory.get(slot as Int) == null) { + bot.equipment.add(item, slot as Int,false,false) + } val reqs = configs["requirements"] if(reqs != null) for(req in configs["requirements"] as HashMap) @@ -41,4 +42,4 @@ class SkillingBotAssembler { val POORSETS = arrayOf(listOf(542,544), listOf(581), listOf(6654,6655,6656), listOf(6654,6656), listOf(636,646), listOf(638,648), listOf(), listOf(), listOf()) val AVGSETS = arrayOf(listOf(2649,342,344), listOf(2651,542,544), listOf(6654,6655,6656), listOf(6139,6141), listOf(9923,9924,9925), listOf(10400,10402,2649), listOf(10404,10406), listOf(12971,12978)) val RICHSETS = arrayOf(listOf(10330,10332,2649), listOf(12873,12880,1046), listOf(13858,13861,13864), listOf(13887,13893), listOf(3481,3483), listOf(2653,2655), listOf(2661,2663), listOf(2591,2593), listOf(14490,14492)) -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/ge/GrandExchange.kt b/Server/src/main/core/game/ge/GrandExchange.kt index fc7c5b12f..1998822ee 100644 --- a/Server/src/main/core/game/ge/GrandExchange.kt +++ b/Server/src/main/core/game/ge/GrandExchange.kt @@ -359,10 +359,24 @@ class GrandExchange : StartupListener, Commands { return offers } + fun getBotstockForId(itemId: Int): Int { + var total = 0 + GEDB.run { conn -> + val stmt = conn.prepareStatement("SELECT sum(amount) FROM bot_offers WHERE amount > 0 AND item_id = ?") + stmt.setInt(1, itemId) + + val results = stmt.executeQuery() + while (results.next()) { + total += results.getInt(1) + } + } + return total + } + } override fun startup(){ GEDB.init() boot() } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/world/ImmerseWorld.kt b/Server/src/main/core/game/world/ImmerseWorld.kt index 287f657ca..2efed45a8 100644 --- a/Server/src/main/core/game/world/ImmerseWorld.kt +++ b/Server/src/main/core/game/world/ImmerseWorld.kt @@ -36,6 +36,7 @@ class ImmerseWorld : StartupListener { immerseWilderness() immerseFishingGuild() immerseAdventurer() + immerseFalador() // immerseSlayer() immerseGE() } @@ -211,6 +212,10 @@ class ImmerseWorld : StartupListener { CoalMiner(), skillingBotAssembler.produce(SkillingBotAssembler.Wealth.POOR, Location.create(3037, 9737, 0)) ) + GeneralBotCreator( + CannonballSmelter(), + skillingBotAssembler.produce(SkillingBotAssembler.Wealth.AVERAGE, Location.create(3013, 3356, 0)) + ) } fun immerseSlayer() {