diff --git a/data/area/misthalin/draynor/draynor.npcs.toml b/data/area/misthalin/draynor/draynor.npcs.toml index 1a2ceb7174..ba11f0e1f3 100644 --- a/data/area/misthalin/draynor/draynor.npcs.toml +++ b/data/area/misthalin/draynor/draynor.npcs.toml @@ -165,6 +165,7 @@ examine = "A busy-body who loves a bit of gossip." [martin_the_master_gardener] id = 3299 +pickpocket = { level = 38, stun_ticks = 8, stun_hit = 30, xp = 43.0, chance_min = 90, chance_max = 240, table = "master_farmer" } examine = "A master at gardening." [wise_old_man_2] diff --git a/data/area/misthalin/lumbridge/freds_farm/freds_farm.npcs.toml b/data/area/misthalin/lumbridge/freds_farm/freds_farm.npcs.toml index e509dc9799..b7a9b869ba 100644 --- a/data/area/misthalin/lumbridge/freds_farm/freds_farm.npcs.toml +++ b/data/area/misthalin/lumbridge/freds_farm/freds_farm.npcs.toml @@ -35,6 +35,7 @@ id = 5423 [farmer] id = 7 +pickpocket = { level = 10, stun_hit = 10, stun_ticks = 8, xp = 14.5, chance_min = 150, chance_max = 240, table = "farmer" } examine = "He grows the crops in this area." [hay_bales_2] diff --git a/data/area/troll_country/god_wars_dungeon/god_wars_dungeon.items.toml b/data/area/troll_country/god_wars_dungeon/god_wars_dungeon.items.toml index bf63438385..e0b95fba84 100644 --- a/data/area/troll_country/god_wars_dungeon/god_wars_dungeon.items.toml +++ b/data/area/troll_country/god_wars_dungeon/god_wars_dungeon.items.toml @@ -202,6 +202,7 @@ special = "shove" god = "zamorak" slot = "Weapon" type = "TwoHanded" +weapon_style = 27 examine = "A versatile spear wielded by agents of chaos." [zamorakian_spear_noted] diff --git a/data/entity/player/combat/combat_styles/weapon_styles.toml b/data/entity/player/combat/combat_styles/weapon_styles.toml index 065e3e90d7..30a84ef12e 100644 --- a/data/entity/player/combat/combat_styles/weapon_styles.toml +++ b/data/entity/player/combat/combat_styles/weapon_styles.toml @@ -159,3 +159,10 @@ id = 26 attack_types = [ "jab", "swipe", "fend" ] attack_styles = [ "accurate", "aggressive", "defensive" ] combat_styles = [ "stab", "slash", "crush" ] + +# Custom +[zamorakian_spear] +id = 27 +attack_types = [ "lunge", "swipe", "pound", "block" ] +attack_styles = [ "controlled", "controlled", "controlled", "defensive" ] +combat_styles = [ "stab", "slash", "crush", "stab" ] diff --git a/data/quest/free/cooks_assistant/cooks_assistant.books.toml b/data/quest/free/cooks_assistant/cooks_assistant.books.toml new file mode 100644 index 0000000000..e859421f7d --- /dev/null +++ b/data/quest/free/cooks_assistant/cooks_assistant.books.toml @@ -0,0 +1,69 @@ +[cook_o_matic_manual] +title = "Cook-o-matic manual" +type = "long" +pages = [ + [ + "Thank you for purchasing the", + "Cook-o-Matic 25 cooking", + "appliance.This model uses", + "Burn-u-less (tm) technology, a", + "combination of state-of-the-art", + "temperature regulation and", + "magic that reduces the chance of", + "burning a selection of dishes", + "listed in the Cooking Table", + "Section.", + "", + "", + "", + "", + "", + "Contents", + "", + "User Instructions", + "Cooking Table", + ], + [ + "User Instructions", + "", + "This range is designed to cook a", + "wide variety of foodstuffs.", + "Just use the food on the", + "range to initiate cooking. The", + "rest of the cooking should", + "happen automatically.", + "", + "This range is not suitable for", + "spit-roasting.", + "", + "", + "", + "", + "Cooking Table", + "", + "Dishes benefiting from the", + "Burn-u-less (tm) technology:", + "", + "Bread", + "Redberry and meat pies ", + "Stew", + "Red meats", + "Chicken and turkey", + "Thin snail", + "Lean snail", + ], + [ + "Fat snail", + "Crayfish", + "Shrimps", + "Anchovies", + "Sardine", + "Herring", + "Mackerel", + "Trout", + "Cod", + "Pike", + "Salmon", + "Cake", + ] +] diff --git a/data/quest/free/cooks_assistant/cooks_assistant.items.toml b/data/quest/free/cooks_assistant/cooks_assistant.items.toml index 13aa12482c..0284d3cdf4 100644 --- a/data/quest/free/cooks_assistant/cooks_assistant.items.toml +++ b/data/quest/free/cooks_assistant/cooks_assistant.items.toml @@ -5,6 +5,7 @@ weight = 0.01 destroy = "You will need to speak to the cook in Lumbridge Castle to get another." examine = "Everything you've always wanted to know about the Cook-o-Matic 25." kept = "Vanish" +book = "cook_o_matic_manual" [super_large_egg] id = 15412 diff --git a/data/quest/quest.ifaces.toml b/data/quest/quest.ifaces.toml index 162e763b0f..ecf47a64b9 100644 --- a/data/quest/quest.ifaces.toml +++ b/data/quest/quest.ifaces.toml @@ -76,6 +76,16 @@ type = "quest_journals_tab" id = 18 options = { View = 0, "View journal" = 1, "Show on world map" = 2 } +[.filter] +id = 10 + +[.hide_done] +id = 12 + +[.order] +id = 15 +options = { "Select Free/Members" = 0, "Select Progress" = 1, "Select Difficulty" = 2 } + [quest_scroll] id = 275 type = "wide_screen" diff --git a/data/quest/quest.varbits.toml b/data/quest/quest.varbits.toml index 3f6694093a..ca8aca2118 100644 --- a/data/quest/quest.varbits.toml +++ b/data/quest/quest.varbits.toml @@ -307,3 +307,32 @@ values = { id = 6924 persist = true format = "boolean" + +[quest_journal_reversed] +id = 4538 +persist = true +format = "boolean" + +[quest_journal_show_all] +id = 4537 +persist = true +format = "boolean" + +[quest_journal_order] +id = 4536 +persist = true +format = "map" +default = "free_members" +values = { + free_members = 0, + progress = 1, + difficulty = 2, + demo_quests = 3 +} + +# This varbit doesn't correctly update +# on the client side for some reason. +#[quest_journal_hide_done] +#id = 7264 +#persist = true +#format = "boolean" diff --git a/data/skill/melee/melee.anims.toml b/data/skill/melee/melee.anims.toml index 16ad0014b8..53a7aa83df 100644 --- a/data/skill/melee/melee.anims.toml +++ b/data/skill/melee/melee.anims.toml @@ -76,6 +76,9 @@ id = 12006 [zamorakian_spear_lunge] id = 12007 +[zamorakian_spear_block] +id = 12005 + [zamorakian_spear_defend] id = 12008 diff --git a/data/skill/thieving/thieving.anims.toml b/data/skill/thieving/thieving.anims.toml index 25f4bc53ca..f0b8c4ea50 100644 --- a/data/skill/thieving/thieving.anims.toml +++ b/data/skill/thieving/thieving.anims.toml @@ -7,3 +7,5 @@ id = 536 [pick_pocket] id = 881 ticks = 4 +walk = false +run = false diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/Character.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/Character.kt index 378352ba62..4c2e9eb8a5 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/Character.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/Character.kt @@ -245,9 +245,9 @@ interface Character : } private fun nearestTile(entity: Entity): Tile = when (entity) { - is GameObject -> Distance.getNearest(entity.tile, entity.width, entity.height, this.tile) - is NPC -> Distance.getNearest(entity.tile, entity.def.size, entity.def.size, this.tile) - is Player -> Distance.getNearest(entity.tile, entity.appearance.size, entity.appearance.size, this.tile) + is GameObject -> Distance.nearest(entity.tile, entity.width, entity.height, this.tile) + is NPC -> Distance.nearest(entity.tile, entity.def.size, entity.def.size, this.tile) + is Player -> Distance.nearest(entity.tile, entity.appearance.size, entity.appearance.size, this.tile) else -> entity.tile } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt index 6f271ba7f2..ff8bf46cf5 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt @@ -191,7 +191,8 @@ open class Movement( if ((character !is NPC || !character.def["allowed_under", false]) && Overlap.isUnder(character.tile, character.size, character.size, strategy.tile, strategy.width, strategy.height)) { return false } - if (!character.tile.within(strategy.tile, distance)) { + val nearest = strategy.nearest(character) + if (!character.tile.within(nearest, distance)) { return false } if (!strategy.requiresLineOfSight()) { diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/target/TargetStrategy.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/target/TargetStrategy.kt index c8a60f8e66..bdcdf1eeaa 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/target/TargetStrategy.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/target/TargetStrategy.kt @@ -6,6 +6,7 @@ import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.item.floor.FloorItem import world.gregs.voidps.engine.entity.obj.GameObject import world.gregs.voidps.engine.map.collision.Collisions +import world.gregs.voidps.type.Distance import world.gregs.voidps.type.Tile interface TargetStrategy { @@ -30,20 +31,26 @@ interface TargetStrategy { fun requiresLineOfSight(): Boolean = true - fun reached(character: Character): Boolean = ReachStrategy.reached( - flags = Collisions.map, - srcX = character.tile.x, - srcZ = character.tile.y, - level = character.tile.level, - srcSize = character.size, - destX = tile.x, - destZ = tile.y, - destWidth = sizeX, - destHeight = sizeY, - objRot = rotation, - objShape = shape, - blockAccessFlags = bitMask, - ) + fun within(tile: Tile, range: Int): Boolean = tile.within(this.tile, range) + + fun nearest(source: Character): Tile = Distance.nearest(tile, width, height, source.tile) + + fun reached(character: Character): Boolean { + return ReachStrategy.reached( + flags = Collisions.map, + srcX = character.tile.x, + srcZ = character.tile.y, + level = character.tile.level, + srcSize = character.size, + destX = tile.x, + destZ = tile.y, + destWidth = sizeX, + destHeight = sizeY, + objRot = rotation, + objShape = shape, + blockAccessFlags = bitMask, + ) + } companion object { operator fun invoke(source: Character, entity: T): TargetStrategy = when (entity) { diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/obj/GameObject.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/obj/GameObject.kt index af337c0ce0..ea2e45451e 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/obj/GameObject.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/obj/GameObject.kt @@ -49,8 +49,8 @@ value class GameObject(internal val packed: Long) : Entity { .add(tile.zone, ObjectAnimation(tile.id, get().get(id).id, shape, rotation)) fun nearestTo(tile: Tile) = Tile( - x = Distance.getNearest(x, width, tile.x), - y = Distance.getNearest(y, height, tile.y), + x = Distance.nearest(x, width, tile.x), + y = Distance.nearest(y, height, tile.y), level = level, ) diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/Hank.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/Hank.kt new file mode 100644 index 0000000000..c4fa9950a2 --- /dev/null +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/Hank.kt @@ -0,0 +1,21 @@ +package content.area.misthalin.lumbridge + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import world.gregs.voidps.engine.Script + +class Hank : Script { + init { + npcOperate("Talk-to", "hank") { + npc("Good day to you! Welcome to my fishing shop. Would you like to buy some fishing equipment or sell some fish?") + choice { + option("Can I see your fishing supplies?") { + openShop(it.target.def["shop"]) + } + option("Farewell.") + } + } + } +} diff --git a/game/src/main/kotlin/content/entity/npc/combat/Attack.kt b/game/src/main/kotlin/content/entity/npc/combat/Attack.kt index 44b316c649..b8a98bec3f 100644 --- a/game/src/main/kotlin/content/entity/npc/combat/Attack.kt +++ b/game/src/main/kotlin/content/entity/npc/combat/Attack.kt @@ -9,6 +9,7 @@ import content.entity.effect.toxin.poison import content.entity.gfx.areaGfx import content.entity.proj.shoot import net.pearx.kasechange.toPascalCase +import org.rsmod.game.pathfinder.LineValidator import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.data.config.CombatDefinition @@ -18,19 +19,22 @@ import world.gregs.voidps.engine.data.definition.CombatDefinitions import world.gregs.voidps.engine.entity.character.Character import world.gregs.voidps.engine.entity.character.areaSound import world.gregs.voidps.engine.entity.character.mode.combat.CombatApi -import world.gregs.voidps.engine.entity.character.mode.move.target.NPCCharacterTargetStrategy +import world.gregs.voidps.engine.entity.character.mode.move.hasLineOfSight +import world.gregs.voidps.engine.entity.character.mode.move.target.TargetStrategy import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.Players import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.entity.character.sound -import world.gregs.voidps.engine.entity.distanceTo import world.gregs.voidps.engine.entity.item.Item +import world.gregs.voidps.engine.map.Overlap +import world.gregs.voidps.type.Distance import world.gregs.voidps.type.Tile import world.gregs.voidps.type.random class Attack( val definitions: CombatDefinitions, + val lineValidator: LineValidator, ) : Script { init { @@ -54,7 +58,8 @@ class Attack( say(attack.say) } if (attack.approach) { - if (tile.within(primaryTarget.tile, attack.range)) { + val nearest = Distance.nearest(primaryTarget.tile, primaryTarget.size, primaryTarget.size, tile) + if (tile.within(nearest, attack.range)) { clear("attack_range") } else { set("attack_range", attack.range) @@ -159,18 +164,17 @@ class Attack( } fun selectAttack(source: NPC, target: Character, definition: CombatDefinition): CombatDefinition.CombatAttack? { - val distance = source.tile.distanceTo(target) val next: String? = source["next_attack"] if (next != null) { val attack = definition.attacks[next] ?: return null - return if (withinRange(source, target, distance, attack)) attack else null + return if (withinRange(source, target, attack)) attack else null } val validAttacks = mutableListOf>() for (attack in definition.attacks.values) { if (!CombatApi.condition(source, target, attack.condition)) { continue } - if (!attack.approach && !withinRange(source, target, distance, attack)) { + if (!attack.approach && !withinRange(source, target, attack)) { continue } validAttacks.add(attack to attack.chance) @@ -181,11 +185,24 @@ class Attack( return weightedSample(validAttacks) } - fun withinRange(source: NPC, target: Character, distance: Int, attack: CombatDefinition.CombatAttack): Boolean { - if (attack.range == 1 && (attack.targetHits.any { Hit.meleeType(it.offense) } || source.size > 1)) { - return NPCCharacterTargetStrategy(source).reached(target) + fun withinRange(source: NPC, target: Character, attack: CombatDefinition.CombatAttack): Boolean { + // Duplicate of Movement.arrived + val attackRange = attack.range + val strategy = TargetStrategy(source, target) + if (attackRange == 1) { + return strategy.reached(source) } - return distance in 1..attack.range + if (!source.def["allowed_under", false] && Overlap.isUnder(source.tile, source.size, source.size, strategy.tile, strategy.width, strategy.height)) { + return false + } + val nearest = strategy.nearest(source) + if (!source.tile.within(nearest, attackRange)) { + return false + } + if (!strategy.requiresLineOfSight()) { + return true + } + return lineValidator.hasLineOfSight(source, strategy.tile, strategy.width, strategy.height) } @Suppress("UNCHECKED_CAST") diff --git a/game/src/main/kotlin/content/entity/obj/ObjectTeleports.kt b/game/src/main/kotlin/content/entity/obj/ObjectTeleports.kt index ef3adc1851..373acfc542 100644 --- a/game/src/main/kotlin/content/entity/obj/ObjectTeleports.kt +++ b/game/src/main/kotlin/content/entity/obj/ObjectTeleports.kt @@ -49,7 +49,7 @@ class ObjectTeleports { fun teleportTile(player: Player, definition: TeleportDefinition): Tile = when { definition.delta != Delta.EMPTY && definition.to != Tile.EMPTY -> - Distance.getNearest(definition.to, definition.delta.x, definition.delta.y, player.tile) + Distance.nearest(definition.to, definition.delta.x, definition.delta.y, player.tile) definition.delta != Delta.EMPTY -> player.tile.add(definition.delta) definition.to != Tile.EMPTY -> definition.to else -> player.tile diff --git a/game/src/main/kotlin/content/entity/player/command/ServerCommands.kt b/game/src/main/kotlin/content/entity/player/command/ServerCommands.kt index f1e50267a8..df6593d0ca 100644 --- a/game/src/main/kotlin/content/entity/player/command/ServerCommands.kt +++ b/game/src/main/kotlin/content/entity/player/command/ServerCommands.kt @@ -12,6 +12,8 @@ import world.gregs.voidps.engine.client.PlayerAccountLoader import world.gregs.voidps.engine.client.command.adminCommand import world.gregs.voidps.engine.client.command.stringArg import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.client.ui.menu +import world.gregs.voidps.engine.client.ui.open import world.gregs.voidps.engine.data.Settings import world.gregs.voidps.engine.data.SettingsReload import world.gregs.voidps.engine.data.configFiles @@ -79,7 +81,13 @@ class ServerCommands(val accountLoader: PlayerAccountLoader) : Script { val files = configFiles() when (args.joinToString("_")) { "combat" -> get().load(files.list(Settings["definitions.combatAttacks"])) - "book", "books" -> get().load(files.list(Settings["definitions.books"])) + "book", "books" -> { + get().load(files.list(Settings["definitions.books"])) + val menu = player.menu + if (menu != null && menu.startsWith("book_")) { + player.open(menu) + } + } "stairs", "tele", "teles", "teleports" -> get().load(files.list(Settings["map.teleports"])) "tracks", "songs", "music_tracks" -> get().load(files.find(Settings["map.music"])) "fairy_ring", "fairy_rings", "fairy_codes" -> get().load(files.find(Settings["definitions.fairyCodes"])) diff --git a/game/src/main/kotlin/content/entity/player/equip/EquipBonuses.kt b/game/src/main/kotlin/content/entity/player/equip/EquipBonuses.kt index 0b9c837ef4..f2227534b8 100644 --- a/game/src/main/kotlin/content/entity/player/equip/EquipBonuses.kt +++ b/game/src/main/kotlin/content/entity/player/equip/EquipBonuses.kt @@ -33,14 +33,12 @@ object EquipBonuses { } fun format(key: String, value: Int, bonuses: Boolean): String = when (key) { - "magic_damage", "absorb_melee", "absorb_magic", "absorb_range" -> "${if (value >= 0) "+" else "-"}$value%" + "magic_damage", "absorb_melee", "absorb_magic", "absorb_range" -> "${if (value >= 0) "+" else ""}$value%" "strength", "ranged_strength" -> "${if (value > 0) { "+" - } else if (value < 0) { - "-" } else { "" }}${value.toDouble()}" - else -> if (bonuses) "${if (value >= 0) "+" else "-"}$value" else value.toString() + else -> if (bonuses) "${if (value >= 0) "+" else ""}$value" else value.toString() } } diff --git a/game/src/main/kotlin/content/entity/player/equip/EquipmentBonuses.kt b/game/src/main/kotlin/content/entity/player/equip/EquipmentBonuses.kt index f4ee8bbfd1..da5678a349 100644 --- a/game/src/main/kotlin/content/entity/player/equip/EquipmentBonuses.kt +++ b/game/src/main/kotlin/content/entity/player/equip/EquipmentBonuses.kt @@ -103,22 +103,24 @@ class EquipmentBonuses : Script { fun updateStats(player: Player, item: Item, add: Boolean) { names.forEach { (name, key) -> val value = item.def[key, 0] - if (value > 0) { - val current = player[key, 0] - val modified = if (add) { - current + value - } else { - current - value - } - player[key] = modified - sendBonus(player, name, key, modified) + if (value == 0) { + return@forEach } + val current = player[key, 0] + val modified = if (add) { + current + value + } else { + current - value + } + player[key] = modified + sendBonus(player, name, key, modified) } } fun sendBonus(player: Player, name: String, key: String, value: Int) { if (player.menu == "equipment_bonuses") { - player.interfaces.sendText("equipment_bonuses", key, "$name: ${EquipBonuses.format(key, value, true)}") + val format = EquipBonuses.format(key, value, true) + player.interfaces.sendText("equipment_bonuses", key, "$name: $format") } } diff --git a/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt b/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt index 4a7cbe4f44..a26762d51f 100644 --- a/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt +++ b/game/src/main/kotlin/content/entity/player/modal/tab/QuestJournals.kt @@ -15,6 +15,8 @@ class QuestJournals(val questDefinitions: QuestDefinitions) : Script { init { playerSpawn { clearCamera() + sendVariable("quest_journal_show_all") + sendVariable("quest_journal_order") } timerStart("refresh_quest_journal") { 1 } @@ -29,6 +31,7 @@ class QuestJournals(val questDefinitions: QuestDefinitions) : Script { interfaceOpened("quest_journals") { id -> interfaceOptions.unlock(id, "journals", 0 until 201, "View") + interfaceOptions.unlockAll(id, "order", 0 until 3) sendVariable("quest_points") sendVariable("quest_points_total") // set total quest points available in variables-player.yml sendVariable("unstable_foundations") @@ -45,5 +48,24 @@ class QuestJournals(val questDefinitions: QuestDefinitions) : Script { } InterfaceApi.openQuestJournal(this, quest.stringId) } + + interfaceOption("Filter quests", "quest_journals:filter") { + toggle("quest_journal_show_all") + } + + interfaceOption(id = "quest_journals:order") { + val type = when (it.itemSlot) { + 0 -> "free_members" + 1 -> "progress" + 2 -> "difficulty" + else -> return@interfaceOption + } + if (get("quest_journal_order", "free_members") == type) { + toggle("quest_journal_reversed") + } else { + set("quest_journal_reversed", false) + set("quest_journal_order", type) + } + } } } diff --git a/game/src/main/kotlin/content/skill/farming/ToolLeprechaun.kt b/game/src/main/kotlin/content/skill/farming/ToolLeprechaun.kt index e8d99c1275..6f180f1092 100644 --- a/game/src/main/kotlin/content/skill/farming/ToolLeprechaun.kt +++ b/game/src/main/kotlin/content/skill/farming/ToolLeprechaun.kt @@ -6,6 +6,7 @@ import content.entity.player.dialogue.* import content.entity.player.dialogue.type.* import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.ui.open +import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.event.AuditLog import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.transact.TransactionError @@ -19,22 +20,7 @@ class ToolLeprechaun : Script { init { npcOperate("Talk-to", "tool_leprechaun*") { npc("Ah, 'tis a foine day to be sure! Were yez wantin' me to store yer tools, or maybe ye might be wantin' yer stuff back from me?") - choice("What would you like to say?") { - yesPlease() - option("What can you store?") { - npc("We'll hold onto yer rake, yer seed dibber, yer spade, yer secateurs, yer waterin' can and yer trowel - but mind it's not one of them fancy trowels only archaeologists use!") - npc("We'll take a few buckets off yer hands too, and even yer compost, supercompost an' ultracompost! Also plant cure vials.") - npc("Aside from that, if ye hands me yer farming produce, I can mebbe change it into banknotes for ye.") - npc("So... do ye want to be using the store?") - choice("What would you like to say?") { - yesPlease() - whatDoYouDo() - noThanks() - } - } - whatDoYouDo() - noThanks() - } + menu() } npcOperate("Exchange", "tool_leprechaun*") { @@ -67,6 +53,25 @@ class ToolLeprechaun : Script { } } + private suspend fun Player.menu() { + choice("What would you like to say?") { + yesPlease() + option("What can you store?") { + npc("We'll hold onto yer rake, yer seed dibber, yer spade, yer secateurs, yer waterin' can and yer trowel - but mind it's not one of them fancy trowels only archaeologists use!") + npc("We'll take a few buckets off yer hands too, and even yer compost, supercompost an' ultracompost! Also plant cure vials.") + npc("Aside from that, if ye hands me yer farming produce, I can mebbe change it into banknotes for ye.") + npc("So... do ye want to be using the store?") + choice("What would you like to say?") { + yesPlease() + whatDoYouDo() + noThanks() + } + } + whatDoYouDo() + noThanks() + } + } + private fun ChoiceOption.yesPlease() { option("Yes please.") { open("farming_equipment_store") @@ -82,6 +87,9 @@ class ToolLeprechaun : Script { private fun ChoiceOption.whatDoYouDo() { option("What do you do with the tools you're storing?") { player("What do you do with the tools you're storing? They can't possibly all fit in your pockets!") + npc("We leprechauns have a shed where we keep 'em. It's a magic shed, so ye can get yer items back from any of us leprechauns whenever ye want. Saves ye havin' to carry loads of stuff around the country!") + npc("So... do ye want to be using the store?") + menu() } } } diff --git a/game/src/main/kotlin/content/skill/melee/Weapon.kt b/game/src/main/kotlin/content/skill/melee/Weapon.kt index f5817f9e1a..872e0e08fc 100644 --- a/game/src/main/kotlin/content/skill/melee/Weapon.kt +++ b/game/src/main/kotlin/content/skill/melee/Weapon.kt @@ -30,7 +30,7 @@ class Weapon : Script { } fun updateWeapon(player: Player, weapon: Item, range: Int = 0) { - player.attackRange = if (player.contains("autocast")) 8 else (weapon.def["attack_range", 1] + range).coerceAtMost(10) + player.attackRange = if (player.contains("autocast")) 10 else (weapon.def["attack_range", 1] + range).coerceAtMost(10) player["attack_speed"] = weapon.def["attack_speed", 4] player.weapon = weapon } diff --git a/game/src/test/kotlin/content/skill/melee/armour/BerserkerNecklaceFormulaTest.kt b/game/src/test/kotlin/content/skill/melee/armour/BerserkerNecklaceFormulaTest.kt index 6f449af910..f363fe8aea 100644 --- a/game/src/test/kotlin/content/skill/melee/armour/BerserkerNecklaceFormulaTest.kt +++ b/game/src/test/kotlin/content/skill/melee/armour/BerserkerNecklaceFormulaTest.kt @@ -20,10 +20,10 @@ internal class BerserkerNecklaceFormulaTest : CombatFormulaTest() { val (offensiveRating, defensiveRating, maxHit, chance) = calculate(player, npc, "melee", weapon) - assertEquals(5848, offensiveRating) + assertEquals(4988, offensiveRating) assertEquals(704, defensiveRating) assertEquals(132, maxHit) - assertEquals(0.9396, chance, 0.0001) + assertEquals(0.9292, chance, 0.0001) } @Test @@ -36,10 +36,10 @@ internal class BerserkerNecklaceFormulaTest : CombatFormulaTest() { val (offensiveRating, defensiveRating, maxHit, chance) = calculate(player, npc, "melee", weapon) - assertEquals(9546, offensiveRating) + assertEquals(8686, offensiveRating) assertEquals(704, defensiveRating) assertEquals(246, maxHit) - assertEquals(0.963, chance, 0.0001) + assertEquals(0.9594, chance, 0.0001) } @Test @@ -52,10 +52,10 @@ internal class BerserkerNecklaceFormulaTest : CombatFormulaTest() { val (offensiveRating, defensiveRating, maxHit, chance) = calculate(player, npc, "melee", weapon) - assertEquals(12384, offensiveRating) + assertEquals(11524, offensiveRating) assertEquals(704, defensiveRating) assertEquals(318, maxHit) - assertEquals(0.9714, chance, 0.0001) + assertEquals(0.9694, chance, 0.0001) } @Test @@ -68,10 +68,10 @@ internal class BerserkerNecklaceFormulaTest : CombatFormulaTest() { val (offensiveRating, defensiveRating, maxHit, chance) = calculate(player, npc, "melee", weapon) - assertEquals(10836, offensiveRating) + assertEquals(9976, offensiveRating) assertEquals(704, defensiveRating) assertEquals(260, maxHit) - assertEquals(0.9674, chance, 0.0001) + assertEquals(0.9646, chance, 0.0001) } @Test @@ -84,9 +84,9 @@ internal class BerserkerNecklaceFormulaTest : CombatFormulaTest() { val (offensiveRating, defensiveRating, maxHit, chance) = calculate(player, npc, "melee", weapon) - assertEquals(9632, offensiveRating) + assertEquals(8772, offensiveRating) assertEquals(704, defensiveRating) assertEquals(225, maxHit) - assertEquals(0.9633, chance, 0.0001) + assertEquals(0.9598, chance, 0.0001) } } diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/types/Dungeon.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/types/Dungeon.kt index e334e54644..36c85e4e2b 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/types/Dungeon.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/types/Dungeon.kt @@ -2,11 +2,11 @@ package world.gregs.voidps.tools.map.obj.types import world.gregs.voidps.tools.map.obj.ObjectIdentificationContext import world.gregs.voidps.type.Distance.euclidean -import world.gregs.voidps.type.Distance.getNearest +import world.gregs.voidps.type.Distance.nearest import world.gregs.voidps.type.Tile val distanceToTile: ObjectIdentificationContext.(Pair) -> Double = { target -> - val nearest = getNearest(obj.tile, obj.width, obj.height, target.first) + val nearest = nearest(obj.tile, obj.width, obj.height, target.first) val distance = euclidean(nearest, target.first) if (distance >= 10.0) { 0.0 diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/types/Objects.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/types/Objects.kt index 2ad2d3d478..48139b5ab2 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/types/Objects.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/types/Objects.kt @@ -4,7 +4,7 @@ import world.gregs.voidps.engine.entity.obj.GameObject import world.gregs.voidps.tools.map.obj.GameObjectOption import world.gregs.voidps.tools.map.obj.ObjectIdentificationContext import world.gregs.voidps.type.Distance.euclidean -import world.gregs.voidps.type.Distance.getNearest +import world.gregs.voidps.type.Distance.nearest import world.gregs.voidps.type.Distance.levenshtein import world.gregs.voidps.type.Tile import kotlin.math.* @@ -72,7 +72,7 @@ private fun onSurface(tile: Tile) = tile.y < DUNGEON_DIFFERENCE private fun inDungeon(tile: Tile) = tile.y > DUNGEON_DIFFERENCE private fun getDistance(tile: Tile, width: Int, height: Int, target: GameObject): Double { - val nearest = getNearest(tile, width, height, target.tile) + val nearest = nearest(tile, width, height, target.tile) val nearestTarget = target.nearestTo(tile) return euclidean(nearest, nearestTarget, level = nearest.level == 3 || nearest.x != nearestTarget.x || nearest.y != nearestTarget.y) .scale(0.0, 5.0) diff --git a/types/src/main/kotlin/world/gregs/voidps/type/Distance.kt b/types/src/main/kotlin/world/gregs/voidps/type/Distance.kt index 604567827a..f51fa22305 100644 --- a/types/src/main/kotlin/world/gregs/voidps/type/Distance.kt +++ b/types/src/main/kotlin/world/gregs/voidps/type/Distance.kt @@ -10,20 +10,20 @@ object Distance { /** * Get the nearest point for [tile] with [width], [height] to [target] */ - fun getNearest(tile: Tile, width: Int, height: Int, target: Tile) = tile.copy( - x = getNearest(tile.x, width, target.x), - y = getNearest(tile.y, height, target.y), + fun nearest(tile: Tile, width: Int, height: Int, target: Tile) = tile.copy( + x = nearest(tile.x, width, target.x), + y = nearest(tile.y, height, target.y), ) fun Rectangle.nearestTo(tile: Tile) = Tile( - x = getNearest(minX, width, tile.x), - y = getNearest(minY, height, tile.y), + x = nearest(minX, width, tile.x), + y = nearest(minY, height, tile.y), ) /** * Get the nearest coordinate for axis [source] with [size] to [target] */ - fun getNearest(source: Int, size: Int, target: Int): Int { + fun nearest(source: Int, size: Int, target: Int): Int { val max = source + size - 1 return when { target > max -> max diff --git a/types/src/main/kotlin/world/gregs/voidps/type/Tile.kt b/types/src/main/kotlin/world/gregs/voidps/type/Tile.kt index 15c0257ac2..7f0302c6a7 100644 --- a/types/src/main/kotlin/world/gregs/voidps/type/Tile.kt +++ b/types/src/main/kotlin/world/gregs/voidps/type/Tile.kt @@ -23,7 +23,7 @@ value class Tile(val id: Int) { fun copy(x: Int = this.x, y: Int = this.y, level: Int = this.level) = Tile(x, y, level) - fun distanceTo(other: Tile, width: Int, height: Int) = distanceTo(Distance.getNearest(other, width, height, this)) + fun distanceTo(other: Tile, width: Int, height: Int) = distanceTo(Distance.nearest(other, width, height, this)) fun distanceTo(other: Tile): Int { if (level != other.level) {