diff --git a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Category.kt b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Category.kt index 1506e2b3f9..7ba468cce0 100644 --- a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Category.kt +++ b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Category.kt @@ -78,4 +78,44 @@ object Category { else -> error("Category $id not found") } + fun id(name: String) = when (name) { + "throwable" -> THROWABLE + "arrow" -> ARROW + "bolt" -> BOLT + "construction" -> CONSTRUCTION + "furniture" -> FURNITURE + "uncooked_food" -> UNCOOKED_FOOD + "construction_storable_clothes" -> CONSTRUCTION_STORABLE_CLOTHES + "crafting" -> CRAFTING + "summoning_pouches" -> SUMMONING_POUCHES + "construction_plant" -> CONSTRUCTION_PLANT + "fletching" -> FLETCHING + "edible" -> EDIBLE + "herblore" -> HERBLORE + "hunter_required_item" -> HUNTER_REQUIRED_ITEM + "hunter_reward" -> HUNTER_REWARD + "jewellery" -> JEWELLERY + "magic_armour" -> MAGIC_ARMOUR + "magic_weapon" -> MAGIC_WEAPON + "melee_armour_low" -> MELEE_ARMOUR_LOW + "melee_armour_mid" -> MELEE_ARMOUR_MID + "melee_armour_high" -> MELEE_ARMOUR_HIGH + "melee_weapon_low" -> MELEE_WEAPON_LOW + "melee_weapon_mid" -> MELEE_WEAPON_MID + "melee_weapon_high" -> MELEE_WEAPON_HIGH + "mining_smelting" -> MINING_SMELTING + "potion" -> POTION + "prayer_armour" -> PRAYER_ARMOUR + "prayer_consumable" -> PRAYER_CONSUMABLE + "range_armour" -> RANGE_ARMOUR + "range_weapon" -> RANGE_WEAPON + "runecrafting" -> RUNECRAFTING + "teleport" -> TELEPORT + "seed" -> SEED + "summoning_scroll" -> SUMMONING_SCROLL + "item_on_item" -> ITEM_ON_ITEM + "log" -> LOG + else -> error("Category $name not found") + } + } \ No newline at end of file diff --git a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Parameterized.kt b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Parameterized.kt index c1002ee14a..5fd1bf009b 100644 --- a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Parameterized.kt +++ b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Parameterized.kt @@ -9,6 +9,7 @@ import world.gregs.voidps.cache.definition.Params.CATEGORY import world.gregs.voidps.cache.definition.Params.MAGIC_STRENGTH import world.gregs.voidps.cache.definition.Params.RANGED_STRENGTH import world.gregs.voidps.cache.definition.Params.STRENGTH +import kotlin.collections.set @Suppress("UNCHECKED_CAST") interface Parameterized { @@ -67,11 +68,12 @@ interface Parameterized { writer.writeByte(params.size) params.forEach { (id, value) -> writer.writeByte(value is String) - writer.writeMedium(id) - if (value is String) { - writer.writeString(value) - } else if (value is Int) { - writer.writeInt(value) + writer.writeMedium(if (value is Set<*>) CATEGORY else id) + when (value) { + is String -> writer.writeString(value) + is Int -> writer.writeInt(value) + is Double -> writer.writeInt((value * 10.0).toInt()) + is Set<*> -> writer.writeInt(Category.id((value as Set).first())) } } } diff --git a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Params.kt b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Params.kt index d1ea3f8d7e..fcc786ae12 100644 --- a/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Params.kt +++ b/cache/src/main/kotlin/world/gregs/voidps/cache/definition/Params.kt @@ -262,8 +262,10 @@ object Params { const val VARIABLES = 5263 const val SUMMONING_BEAST_OF_BURDEN_ESSENCE = 5264 const val SUMMONING_SPECIAL_COST = 5265 + const val DISENGAGE = 5266 private fun custom(name: String) = when (name) { + "disengage" -> DISENGAGE "summoning_beast_of_burden_essence" -> SUMMONING_BEAST_OF_BURDEN_ESSENCE "summoning_special_cost" -> SUMMONING_SPECIAL_COST "variables" -> VARIABLES diff --git a/data/activity/event/random/freaky_forester/freaky_forester.objs.toml b/data/activity/event/random/freaky_forester/freaky_forester.objs.toml index ca30e8a7c6..04694cf1ad 100644 --- a/data/activity/event/random/freaky_forester/freaky_forester.objs.toml +++ b/data/activity/event/random/freaky_forester/freaky_forester.objs.toml @@ -1,3 +1,6 @@ [bank_deposit_box_freaky_forester] id = 32931 examine = "Lets you put items into your bank." + +[exit_portal_freaky_forester] +id = 15645 \ No newline at end of file diff --git a/data/area/asgarnia/black_knights_fortress/black_knight.drops.toml b/data/area/asgarnia/black_knights_fortress/black_knight.drops.toml new file mode 100644 index 0000000000..fbe3d84850 --- /dev/null +++ b/data/area/asgarnia/black_knights_fortress/black_knight.drops.toml @@ -0,0 +1,6 @@ +[jailer_black_knights_base_drop_table] +type = "all" +drops = [ + { id = "bones" }, + { id = "jail_key" }, +] \ No newline at end of file diff --git a/data/area/asgarnia/black_knights_fortress/black_knight.npcs.toml b/data/area/asgarnia/black_knights_fortress/black_knight.npcs.toml index 2035626b1c..91cd7b1800 100644 --- a/data/area/asgarnia/black_knights_fortress/black_knight.npcs.toml +++ b/data/area/asgarnia/black_knights_fortress/black_knight.npcs.toml @@ -6,12 +6,17 @@ examine = "One of the Black Knights." id = 200 examine = "Wears a stylish suit of armour." +[velrak_the_explorer] +id = 798 +examine = "He looks cold and hungry." + [jailer_black_knights_base] id = 201 hitpoints = 470 att = 40 str = 40 def = 40 +drop_table = "jailer_black_knights_base" combat_def = "man" max_hit_crush = 50 respawn_delay = 50 diff --git a/data/area/asgarnia/taverley/dungeon/black_knight_base.npc-spawns.toml b/data/area/asgarnia/taverley/dungeon/black_knight_base.npc-spawns.toml index b86a8a64b3..30ffa6b7ab 100644 --- a/data/area/asgarnia/taverley/dungeon/black_knight_base.npc-spawns.toml +++ b/data/area/asgarnia/taverley/dungeon/black_knight_base.npc-spawns.toml @@ -46,5 +46,6 @@ spawns = [ { id = "jailer_black_knights_base", x = 2933, y = 9693 }, { id = "captain_gilroy", x = 2915, y = 9674, members = true }, { id = "lord_daquarius", x = 2892, y = 9680, members = true }, + { id = "velrak_the_explorer", x = 2931, y = 9686, members = true }, { id = "black_knight_void_stares_back", x = 2913, y = 9674, members = true }, ] \ No newline at end of file diff --git a/data/area/asgarnia/taverley/taverley.drops.toml b/data/area/asgarnia/taverley/taverley.drops.toml index d41b32a686..9c421d05ef 100644 --- a/data/area/asgarnia/taverley/taverley.drops.toml +++ b/data/area/asgarnia/taverley/taverley.drops.toml @@ -28,3 +28,83 @@ drops = [ { id = "limpwurt_root", chance = 3 }, { id = "antipoison_3" }, ] + +[crystal_chest_drop_table] +type = "all" +drops = [ + { id = "uncut_dragonstone" }, + { table = "crystal_chest_secondary" }, +] + +[crystal_chest_secondary] +roll = 128 +drops = [ + { table = "crystal_chest_1", chance = 34 }, + { table = "crystal_chest_2", chance = 12 }, + { table = "crystal_chest_3", chance = 12 }, + { id = "rune_bar", amount = 3, chance = 12 }, + { table = "crystal_chest_5", chance = 10 }, + { id = "iron_ore_noted", amount = 150, chance = 10 }, + { id = "coal_noted", amount = 100, chance = 10 }, + { table = "crystal_chest_8", chance = 8 }, + { id = "adamant_sq_shield", chance = 2 }, + { table = "crystal_chest_10", chance = 1 }, +] + +[crystal_chest_1] +type = "all" +drops = [ + { id = "spinach_roll" }, + { id = "coins", amount = 2000 }, +] + +[crystal_chest_2] +type = "all" +drops = [ + { id = "air_rune", amount = 50 }, + { id = "water_rune", amount = 50 }, + { id = "earth_rune", amount = 50 }, + { id = "fire_rune", amount = 50 }, + { id = "body_rune", amount = 50 }, + { id = "mind_rune", amount = 50 }, + { id = "chaos_rune", amount = 10 }, + { id = "death_rune", amount = 10 }, + { id = "cosmic_rune", amount = 10 }, + { id = "nature_rune", amount = 10 }, + { id = "law_rune", amount = 10 }, +] + +[crystal_chest_3] +type = "all" +drops = [ + { id = "ruby", amount = 2 }, + { id = "diamond", amount = 2 }, +] + +[crystal_chest_5] +type = "all" +drops = [ + { id = "coins", amount = 750 }, + { table = "crystal_key" }, +] + +[crystal_key] +roll = 10 +drops = [ + { id = "loop_half_of_a_key", chance = 5 }, + { id = "tooth_half_of_a_key", chance = 5 }, +] + +[crystal_chest_8] +type = "all" +drops = [ + { id = "raw_swordfish", amount = 5 }, + { id = "coins", amount = 1000 }, +] + +[crystal_chest_10] +roll = 2 +drops = [ + { id = "rune_platelegs" }, + { id = "rune_plateskirt" }, +] \ No newline at end of file diff --git a/data/area/asgarnia/taverley/taverley.objs.toml b/data/area/asgarnia/taverley/taverley.objs.toml new file mode 100644 index 0000000000..266ca31e7c --- /dev/null +++ b/data/area/asgarnia/taverley/taverley.objs.toml @@ -0,0 +1,7 @@ +[crystal_chest] +id = 172 +examine = "I wonder what's inside." + +[crystal_chest_open] +id = 173 +examine = "Perhaps I should search it." diff --git a/data/area/asgarnia/taverley/taverley.recipes.toml b/data/area/asgarnia/taverley/taverley.recipes.toml new file mode 100644 index 0000000000..92da27cf3f --- /dev/null +++ b/data/area/asgarnia/taverley/taverley.recipes.toml @@ -0,0 +1,4 @@ +[crystal_key] +remove = ["tooth_half_of_a_key", "loop_half_of_a_key"] +add = ["crystal_key"] +message = "You join the two halves of the key together." diff --git a/data/area/kandarin/yanille/yanille.npcs.toml b/data/area/kandarin/yanille/yanille.npcs.toml index 70cd486a80..8c284ec92e 100644 --- a/data/area/kandarin/yanille/yanille.npcs.toml +++ b/data/area/kandarin/yanille/yanille.npcs.toml @@ -56,6 +56,7 @@ examine = "A Supplier of Magical items." [aleck] id = 5110 +shop = "alecks_hunter_emporium" examine = "He acts as if he owns the place." [leon] @@ -76,6 +77,7 @@ examine = "A wizard." [frenita] id = 593 +shop = "frenitas_cookery_shop" examine = "A recipe for success- in Cooking." [pet_shop_owner_yanille] diff --git a/data/area/kharidian_desert/nardah/nardah.npc-spawns.toml b/data/area/kharidian_desert/nardah/nardah.npc-spawns.toml index 3ec15e0103..4b3f880ab5 100644 --- a/data/area/kharidian_desert/nardah/nardah.npc-spawns.toml +++ b/data/area/kharidian_desert/nardah/nardah.npc-spawns.toml @@ -10,7 +10,7 @@ spawns = [ { id = "meskhenet", x = 3427, y = 2900 }, { id = "zahra", x = 3448, y = 2898 }, { id = "zahur", x = 3442, y = 2886 }, - { id = "seddu", x = 3407, y = 2926 }, + { id = "seddu", x = 3406, y = 2922 }, { id = "kazemde", x = 3414, y = 2906 }, { id = "awusah_the_mayor", x = 3442, y = 2912, members = true }, { id = "tarik_nardah", x = 3438, y = 2917 }, diff --git a/data/area/kharidian_desert/nardah/nardah.npcs.toml b/data/area/kharidian_desert/nardah/nardah.npcs.toml index cf4c3b5518..67fcd2b1d7 100644 --- a/data/area/kharidian_desert/nardah/nardah.npcs.toml +++ b/data/area/kharidian_desert/nardah/nardah.npcs.toml @@ -1,5 +1,6 @@ [rokuh] id = 13181 +shop = "roks_chocs_box" [ghaslor_the_elder] id = 3029 @@ -35,10 +36,12 @@ examine = "A herbalist... sells herbs." [seddu] id = 3038 +shop = "seddus_adventurers_store" examine = "An armour merchant." [kazemde] id = 3039 +shop = "nardah_general_store" examine = "A seller of many things." [awusah_the_mayor] @@ -106,6 +109,7 @@ examine = "They'll eat anything!" [artimeus] id = 5109 +shop = "nardah_hunter_shop" examine = "He looks like he knows what he's doing." [ali_the_wise] diff --git a/data/area/kharidian_desert/nardah/nardah.vars.toml b/data/area/kharidian_desert/nardah/nardah.vars.toml new file mode 100644 index 0000000000..e90d0948be --- /dev/null +++ b/data/area/kharidian_desert/nardah/nardah.vars.toml @@ -0,0 +1,3 @@ +[spoke_nardah_herblist] +format = "boolean" +persist = true \ No newline at end of file diff --git a/data/area/misthalin/lumbridge/bobs_item_repair.tables.toml b/data/area/misthalin/lumbridge/bobs_item_repair.tables.toml new file mode 100644 index 0000000000..0437abe481 --- /dev/null +++ b/data/area/misthalin/lumbridge/bobs_item_repair.tables.toml @@ -0,0 +1,392 @@ +[repairable_items] +repaired = "item" + +[.ahrims_hood_100] +repaired = "ahrims_hood" + +[.ahrims_hood_75] +repaired = "ahrims_hood" + +[.ahrims_hood_50] +repaired = "ahrims_hood" + +[.ahrims_hood_25] +repaired = "ahrims_hood" + +[.ahrims_hood_broken] +repaired = "ahrims_hood" + +[.ahrims_staff_100] +repaired = "ahrims_staff" + +[.ahrims_staff_75] +repaired = "ahrims_staff" + +[.ahrims_staff_50] +repaired = "ahrims_staff" + +[.ahrims_staff_25] +repaired = "ahrims_staff" + +[.ahrims_staff_broken] +repaired = "ahrims_staff" + +[.ahrims_robe_top_100] +repaired = "ahrims_robe_top" + +[.ahrims_robe_top_75] +repaired = "ahrims_robe_top" + +[.ahrims_robe_top_50] +repaired = "ahrims_robe_top" + +[.ahrims_robe_top_25] +repaired = "ahrims_robe_top" + +[.ahrims_robe_top_broken] +repaired = "ahrims_robe_top" + +[.ahrims_robe_skirt_100] +repaired = "ahrims_robe_skirt" + +[.ahrims_robe_skirt_75] +repaired = "ahrims_robe_skirt" + +[.ahrims_robe_skirt_50] +repaired = "ahrims_robe_skirt" + +[.ahrims_robe_skirt_25] +repaired = "ahrims_robe_skirt" + +[.ahrims_robe_skirt_broken] +repaired = "ahrims_robe_skirt" + +[.dharoks_helm_100] +repaired = "dharoks_helm" + +[.dharoks_helm_75] +repaired = "dharoks_helm" + +[.dharoks_helm_50] +repaired = "dharoks_helm" + +[.dharoks_helm_25] +repaired = "dharoks_helm" + +[.dharoks_helm_broken] +repaired = "dharoks_helm" + +[.dharoks_greataxe_100] +repaired = "dharoks_greataxe" + +[.dharoks_greataxe_75] +repaired = "dharoks_greataxe" + +[.dharoks_greataxe_50] +repaired = "dharoks_greataxe" + +[.dharoks_greataxe_25] +repaired = "dharoks_greataxe" + +[.dharoks_greataxe_broken] +repaired = "dharoks_greataxe" + +[.dharoks_platebody_100] +repaired = "dharoks_platebody" + +[.dharoks_platebody_75] +repaired = "dharoks_platebody" + +[.dharoks_platebody_50] +repaired = "dharoks_platebody" + +[.dharoks_platebody_25] +repaired = "dharoks_platebody" + +[.dharoks_platebody_broken] +repaired = "dharoks_platebody" + +[.dharoks_platelegs_100] +repaired = "dharoks_platelegs" + +[.dharoks_platelegs_75] +repaired = "dharoks_platelegs" + +[.dharoks_platelegs_50] +repaired = "dharoks_platelegs" + +[.dharoks_platelegs_25] +repaired = "dharoks_platelegs" + +[.dharoks_platelegs_broken] +repaired = "dharoks_platelegs" + +[.guthans_helm_100] +repaired = "guthans_helm" + +[.guthans_helm_75] +repaired = "guthans_helm" + +[.guthans_helm_50] +repaired = "guthans_helm" + +[.guthans_helm_25] +repaired = "guthans_helm" + +[.guthans_helm_broken] +repaired = "guthans_helm" + +[.guthans_warspear_100] +repaired = "guthans_warspear" + +[.guthans_warspear_75] +repaired = "guthans_warspear" + +[.guthans_warspear_50] +repaired = "guthans_warspear" + +[.guthans_warspear_25] +repaired = "guthans_warspear" + +[.guthans_warspear_broken] +repaired = "guthans_warspear" + +[.guthans_platebody_100] +repaired = "guthans_platebody" + +[.guthans_platebody_75] +repaired = "guthans_platebody" + +[.guthans_platebody_50] +repaired = "guthans_platebody" + +[.guthans_platebody_25] +repaired = "guthans_platebody" + +[.guthans_platebody_broken] +repaired = "guthans_platebody" + +[.guthans_chainskirt_100] +repaired = "guthans_chainskirt" + +[.guthans_chainskirt_75] +repaired = "guthans_chainskirt" + +[.guthans_chainskirt_50] +repaired = "guthans_chainskirt" + +[.guthans_chainskirt_25] +repaired = "guthans_chainskirt" + +[.guthans_chainskirt_broken] +repaired = "guthans_chainskirt" + +[.karils_coif_100] +repaired = "karils_coif" + +[.karils_coif_75] +repaired = "karils_coif" + +[.karils_coif_50] +repaired = "karils_coif" + +[.karils_coif_25] +repaired = "karils_coif" + +[.karils_coif_broken] +repaired = "karils_coif" + +[.karils_crossbow_100] +repaired = "karils_crossbow" + +[.karils_crossbow_75] +repaired = "karils_crossbow" + +[.karils_crossbow_50] +repaired = "karils_crossbow" + +[.karils_crossbow_25] +repaired = "karils_crossbow" + +[.karils_crossbow_broken] +repaired = "karils_crossbow" + +[.karils_top_100] +repaired = "karils_top" + +[.karils_top_75] +repaired = "karils_top" + +[.karils_top_50] +repaired = "karils_top" + +[.karils_top_25] +repaired = "karils_top" + +[.karils_top_broken] +repaired = "karils_top" + +[.karils_skirt_100] +repaired = "karils_skirt" + +[.karils_skirt_75] +repaired = "karils_skirt" + +[.karils_skirt_50] +repaired = "karils_skirt" + +[.karils_skirt_25] +repaired = "karils_skirt" + +[.karils_skirt_broken] +repaired = "karils_skirt" + +[.torags_helm_100] +repaired = "torags_helm" + +[.torags_helm_75] +repaired = "torags_helm" + +[.torags_helm_50] +repaired = "torags_helm" + +[.torags_helm_25] +repaired = "torags_helm" + +[.torags_helm_broken] +repaired = "torags_helm" + +[.torags_hammers_100] +repaired = "torags_hammers" + +[.torags_hammers_75] +repaired = "torags_hammers" + +[.torags_hammers_50] +repaired = "torags_hammers" + +[.torags_hammers_25] +repaired = "torags_hammers" + +[.torags_hammers_broken] +repaired = "torags_hammers" + +[.torags_platebody_100] +repaired = "torags_platebody" + +[.torags_platebody_75] +repaired = "torags_platebody" + +[.torags_platebody_50] +repaired = "torags_platebody" + +[.torags_platebody_25] +repaired = "torags_platebody" + +[.torags_platebody_broken] +repaired = "torags_platebody" + +[.torags_platelegs_100] +repaired = "torags_platelegs" + +[.torags_platelegs_75] +repaired = "torags_platelegs" + +[.torags_platelegs_50] +repaired = "torags_platelegs" + +[.torags_platelegs_25] +repaired = "torags_platelegs" + +[.torags_platelegs_broken] +repaired = "torags_platelegs" + +[.veracs_helm_100] +repaired = "veracs_helm" + +[.veracs_helm_75] +repaired = "veracs_helm" + +[.veracs_helm_50] +repaired = "veracs_helm" + +[.veracs_helm_25] +repaired = "veracs_helm" + +[.veracs_helm_broken] +repaired = "veracs_helm" + +[.veracs_flail_100] +repaired = "veracs_flail" + +[.veracs_flail_75] +repaired = "veracs_flail" + +[.veracs_flail_50] +repaired = "veracs_flail" + +[.veracs_flail_25] +repaired = "veracs_flail" + +[.veracs_flail_broken] +repaired = "veracs_flail" + +[.veracs_brassard_100] +repaired = "veracs_brassard" + +[.veracs_brassard_75] +repaired = "veracs_brassard" + +[.veracs_brassard_50] +repaired = "veracs_brassard" + +[.veracs_brassard_25] +repaired = "veracs_brassard" + +[.veracs_brassard_broken] +repaired = "veracs_brassard" + +[.veracs_plateskirt_100] +repaired = "veracs_plateskirt" + +[.veracs_plateskirt_75] +repaired = "veracs_plateskirt" + +[.veracs_plateskirt_50] +repaired = "veracs_plateskirt" + +[.veracs_plateskirt_25] +repaired = "veracs_plateskirt" + +[.veracs_plateskirt_broken] +repaired = "veracs_plateskirt" + +[.torva_full_helm_broken] +repaired = "torva_full_helm" + +[.torva_platebody_broken] +repaired = "torva_platebody" + +[.torva_platelegs_broken] +repaired = "torva_platelegs" + +[.pernix_cowl_broken] +repaired = "pernix_cowl" + +[.pernix_body_broken] +repaired = "pernix_body" + +[.pernix_chaps_broken] +repaired = "pernix_chaps" + +[.virtus_mask_broken] +repaired = "virtus_mask" + +[.virtus_robe_top_broken] +repaired = "virtus_robe_top" + +[.virtus_robe_legs_broken] +repaired = "virtus_robe_legs" + +[.zaryte_bow_broken] +repaired = "zaryte_bow" diff --git a/data/area/morytania/slayer_tower/nechryael/nechryael.npcs.toml b/data/area/morytania/slayer_tower/nechryael/nechryael.npcs.toml index dbb554775b..b75e82cf40 100644 --- a/data/area/morytania/slayer_tower/nechryael/nechryael.npcs.toml +++ b/data/area/morytania/slayer_tower/nechryael/nechryael.npcs.toml @@ -6,13 +6,13 @@ str = 97 def = 105 attack_speed = 4 combat_def = "nechryael" -hunt_mode = "cowardly" respawn_delay = 30 wander_range = 4 slayer_xp = 105.0 slayer_level = 80 drop_table = "nechryael" categories = ["nechryael"] +disengage = false examine = "An evil death demon." [death_spawn] @@ -26,4 +26,5 @@ hunt_mode = "cowardly" style = "slash" slayer_level = 80 drop_table = "death_spawn" +disengage = false examine = "An evil death spawn." diff --git a/data/entity/npc/animal/wolf/wolf.npcs.toml b/data/entity/npc/animal/wolf/wolf.npcs.toml index 3ac39ae509..bbf48dc24c 100644 --- a/data/entity/npc/animal/wolf/wolf.npcs.toml +++ b/data/entity/npc/animal/wolf/wolf.npcs.toml @@ -26,6 +26,7 @@ slayer_xp = 34.0 hunt_mode = "aggressive" respawn_delay = 90 categories = ["wolves"] +drop_table = "wolf_bones" examine = "A vicious mountain wolf." [white_wolf_level_38] @@ -41,6 +42,7 @@ slayer_xp = 44.0 hunt_mode = "aggressive" respawn_delay = 90 categories = ["wolves"] +drop_table = "wolf_bones" examine = "A vicious mountain wolf." [big_wolf] diff --git a/data/entity/player/inventory/item_pack.tables.toml b/data/entity/player/inventory/item_pack.tables.toml new file mode 100644 index 0000000000..5e1b148f05 --- /dev/null +++ b/data/entity/player/inventory/item_pack.tables.toml @@ -0,0 +1,35 @@ +[packs] +unpacked = "item" +amount = "int" + +[.spirit_shard_pack] +unpacked = "spirit_shards" +amount = 5000 + +[.juju_vial_pack] +unpacked = "juju_vial_of_water_noted" +amount = 50 + +[.vial_pack] +unpacked = "vial_noted" +amount = 50 + +[.vial_of_water_pack] +unpacked = "vial_of_water_noted" +amount = 50 + +[.eye_of_newt_pack] +unpacked = "eye_of_newt_noted" +amount = 50 + +[.raw_bird_meat_pack] +unpacked = "raw_bird_meat_noted" +amount = 50 + +[.basket_pack] +unpacked = "basket_noted" +amount = 50 + +[.empty_sack_pack] +unpacked = "empty_sack_noted" +amount = 50 diff --git a/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.anims.toml b/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.anims.toml index 310ce63418..24f8d4cd51 100644 --- a/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.anims.toml +++ b/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.anims.toml @@ -40,6 +40,3 @@ id = 4433 [human_castcurse_walkmerge] id = 11428 - -[human_openchest] -id = 536 \ No newline at end of file diff --git a/data/skill/cooking/cooking.recipes.toml b/data/skill/cooking/cooking.recipes.toml index 0f54baf733..0a0f5e49ed 100644 --- a/data/skill/cooking/cooking.recipes.toml +++ b/data/skill/cooking/cooking.recipes.toml @@ -254,7 +254,7 @@ message = "You fill the pie with apple." skill = "cooking" level = 20 remove = ["redberries", "pie_shell"] -add = ["uncooked_meat_pie"] +add = ["uncooked_berry_pie"] ticks = 2 message = "You fill the pie with meat." diff --git a/data/skill/herblore/potion.recipes.toml b/data/skill/herblore/potion.recipes.toml index f5aa6feaa6..5f63f88210 100644 --- a/data/skill/herblore/potion.recipes.toml +++ b/data/skill/herblore/potion.recipes.toml @@ -1,7 +1,7 @@ [attack_potion_finished] skill = "herblore" level = 3 -xp = 1.0 +xp = 25.0 ticks = 2 remove = ["guam_potion_unf", "eye_of_newt"] add = ["attack_potion_3"] diff --git a/data/skill/skill.vars.toml b/data/skill/skill.vars.toml new file mode 100644 index 0000000000..ece85785e0 --- /dev/null +++ b/data/skill/skill.vars.toml @@ -0,0 +1,3 @@ +[skip_level_up_dialogues] +format = "boolean" +persist = true \ No newline at end of file diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ExecuteCommandHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ExecuteCommandHandler.kt index c5bed6d08f..1382974b49 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ExecuteCommandHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ExecuteCommandHandler.kt @@ -5,6 +5,8 @@ import world.gregs.voidps.engine.client.instruction.InstructionHandler import world.gregs.voidps.engine.client.command.Commands import world.gregs.voidps.engine.entity.character.move.tele import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.PlayerRights +import world.gregs.voidps.engine.entity.character.player.hasRights import world.gregs.voidps.engine.event.AuditLog import world.gregs.voidps.network.client.instruction.ExecuteCommand @@ -18,7 +20,7 @@ class ExecuteCommandHandler : InstructionHandler() { val parts = instruction.command.split(" ") val prefix = parts[0] val content = instruction.command.removePrefix(prefix).trim() - if (instruction.automatic) { + if (instruction.automatic && player.hasRights(PlayerRights.Admin)) { val params = content.split(",") val level = params[0].toInt() val x = params[1].toInt() shl 6 or params[3].toInt() diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/update/NPCTask.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/update/NPCTask.kt index 658a8e0b2e..cf302b2b5a 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/update/NPCTask.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/update/NPCTask.kt @@ -29,7 +29,7 @@ class NPCTask( // owner rather than wandering or standing still after a fight ends. val ownerIndex = character["owner_index", -1] val owner = if (ownerIndex != -1) Players.indexed(ownerIndex) else null - if (owner != null && owner.get("follower_index", -1) == character.index) { + if (owner != null && owner["follower_index", -1] == character.index) { character.mode = Follow(character, owner) } else if (wanders(character)) { character.mode = Wander(character) diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/combat/CombatMovement.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/combat/CombatMovement.kt index e6a6ab043a..bc1ce912b6 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/combat/CombatMovement.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/combat/CombatMovement.kt @@ -1,5 +1,6 @@ package world.gregs.voidps.engine.entity.character.mode.combat +import world.gregs.voidps.cache.definition.Params import world.gregs.voidps.engine.client.ui.dialogue import world.gregs.voidps.engine.client.variable.hasClock import world.gregs.voidps.engine.data.config.CombatDefinition @@ -93,7 +94,7 @@ class CombatMovement( } // Disengage from targets that are already in combat val attacker = target.get("attacker") - if (character is NPC && attacker != null && attacker != character && !target.contains("in_multi_combat")) { + if (character is NPC && character.def[Params.DISENGAGE, true] && attacker != null && attacker != character && !target.contains("in_multi_combat")) { character.mode = EmptyMode return } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/item/ItemKept.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/item/ItemKept.kt index 5cda6ada15..3155707cf5 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/item/ItemKept.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/item/ItemKept.kt @@ -27,9 +27,7 @@ enum class ItemKept { /** * Disappears regardless of if kept on death */ - Vanish, - - ; + Vanish; companion object { private val map = mapOf( @@ -40,6 +38,6 @@ enum class ItemKept { "Vanish" to Vanish, ) - fun by(name: String): ItemKept = map[name] ?: Never + fun by(name: String): ItemKept = map[name] ?: Wilderness } } diff --git a/game/src/main/kotlin/content/activity/event/random/MysteriousOldMan.kt b/game/src/main/kotlin/content/activity/event/random/MysteriousOldMan.kt new file mode 100644 index 0000000000..4b287abe00 --- /dev/null +++ b/game/src/main/kotlin/content/activity/event/random/MysteriousOldMan.kt @@ -0,0 +1,15 @@ +package content.activity.event.random + +import content.activity.event.random.pinball.Pinball +import world.gregs.voidps.engine.Script + +class MysteriousOldMan : Script { + init { + npcOperate("Talk-to", "mysterious_old_man") { (target) -> + when (get("random_event", "")) { + "pinball" -> Pinball.explainPinball(this, target) + else -> return@npcOperate + } + } + } +} diff --git a/game/src/main/kotlin/content/activity/event/random/RandomEventKidnap.kt b/game/src/main/kotlin/content/activity/event/random/RandomEventKidnap.kt index 06a187866a..11dd1b40b6 100644 --- a/game/src/main/kotlin/content/activity/event/random/RandomEventKidnap.kt +++ b/game/src/main/kotlin/content/activity/event/random/RandomEventKidnap.kt @@ -114,7 +114,7 @@ suspend fun Player.returnHome(vararg rewards: String) { finishReturn(*rewards) } -private suspend fun Player.finishReturn(vararg rewards: String) { +private fun Player.finishReturn(vararg rewards: String) { walkTrigger = null RandomEvents.complete(this, *rewards) anim("teleport_land_modern") diff --git a/game/src/main/kotlin/content/activity/event/random/RandomEventTrigger.kt b/game/src/main/kotlin/content/activity/event/random/RandomEventTrigger.kt index 3c9c3bd213..1855019656 100644 --- a/game/src/main/kotlin/content/activity/event/random/RandomEventTrigger.kt +++ b/game/src/main/kotlin/content/activity/event/random/RandomEventTrigger.kt @@ -47,7 +47,7 @@ class RandomEventTrigger : Script { } } - adminCommand("random_event", stringArg("event", optional = true), desc = "Start a random event") { args -> + adminCommand("random_event", stringArg("event", optional = true, autofill = RandomEvents.keys), desc = "Start a random event") { args -> val event = args.getOrNull(0) ?: RandomEvents.pick() if (event == null || !RandomEvents.start(this, event)) { message("No random event found${if (args.isEmpty()) "" else " for '${args[0]}'"}.") diff --git a/game/src/main/kotlin/content/activity/event/random/RandomEvents.kt b/game/src/main/kotlin/content/activity/event/random/RandomEvents.kt index e0aaaf2b0f..4731ec6951 100644 --- a/game/src/main/kotlin/content/activity/event/random/RandomEvents.kt +++ b/game/src/main/kotlin/content/activity/event/random/RandomEvents.kt @@ -40,6 +40,9 @@ object RandomEvents : AutoCloseable { private val events = mutableMapOf Unit>() + val keys: Set + get() = events.keys + fun register(id: String, launcher: suspend Player.() -> Unit) { Script.checkLoading() events[id] = launcher diff --git a/game/src/main/kotlin/content/activity/event/random/freaky_forester/FreakyForester.kt b/game/src/main/kotlin/content/activity/event/random/freaky_forester/FreakyForester.kt index e801f7f7ee..c02db6b15c 100644 --- a/game/src/main/kotlin/content/activity/event/random/freaky_forester/FreakyForester.kt +++ b/game/src/main/kotlin/content/activity/event/random/freaky_forester/FreakyForester.kt @@ -8,6 +8,7 @@ import content.activity.event.random.rewardCostumePoint import content.entity.combat.killer import content.entity.player.dialogue.Happy 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 import world.gregs.voidps.engine.client.message @@ -18,6 +19,7 @@ import world.gregs.voidps.engine.entity.character.player.name import world.gregs.voidps.engine.entity.item.floor.FloorItems import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove +import world.gregs.voidps.engine.inv.removeToLimit import world.gregs.voidps.type.Tile import world.gregs.voidps.type.random @@ -57,10 +59,21 @@ class FreakyForester : Script { } death.dropItems = false // The pheasant drops the raw bird for the killer to pick up and hand to the forester. - val correct = tailCount(id) == killer.get("freaky_forester_task", 0) + val correct = tailCount(id) == killer["freaky_forester_task", 0] val bird = if (correct) "raw_pheasant" else "raw_pheasant_incorrect" FloorItems.add(tile, bird, revealTicks = FloorItems.NEVER, disappearTicks = 300, owner = killer) } + + objectOperate("Enter", "exit_portal_freaky_forester") { + choice("Are you sure you wish to leave?") { + // TODO proper message + option("Yes I'm sure.") { + clearState() + returnHome() + } + option("No, I'll stick around.") + } + } } private suspend fun Player.startEvent() { @@ -101,11 +114,16 @@ class FreakyForester : Script { private suspend fun Player.leaveForest() { rewardCostumePoint("lederhosen") - inventory.remove("raw_pheasant") - clear("freaky_forester_task") + clearState() returnHome("random_event_gift") } + private fun Player.clearState() { + inventory.removeToLimit("raw_pheasant", 28) + inventory.removeToLimit("raw_pheasant_incorrect", 28) + clear("freaky_forester_task") + } + private fun Player.carriesRawPheasant() = inventory.contains("raw_pheasant") || inventory.contains("raw_pheasant_incorrect") private fun tailCount(pheasantId: String) = pheasantId.removePrefix("pheasant_").substringBefore("_").toIntOrNull() ?: 0 diff --git a/game/src/main/kotlin/content/activity/event/random/mime_theatre/Mime.kt b/game/src/main/kotlin/content/activity/event/random/mime_theatre/Mime.kt index 26d1705d5e..372147d292 100644 --- a/game/src/main/kotlin/content/activity/event/random/mime_theatre/Mime.kt +++ b/game/src/main/kotlin/content/activity/event/random/mime_theatre/Mime.kt @@ -16,6 +16,7 @@ import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.npc.NPCs import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.obj.GameObjects +import world.gregs.voidps.engine.queue.strongQueue import world.gregs.voidps.engine.suspend.Suspension import world.gregs.voidps.engine.suspend.pauseString import world.gregs.voidps.type.Tile @@ -49,11 +50,19 @@ class Mime : Script { set("mime_correct", 0) } mysteriousOldMan() + walkTrigger { + strongQueue("start") { + start() + } + } kidnap(SPAWN) // The old man stays behind at the kidnap origin, so he narrates by id rather than talkWith. npc("mysterious_old_man", "Here's a little challenge for you:
Copy the Mime's performance, then you'll be released.") - walkOverDelay(WATCH) + start() + } + private suspend fun Player.start() { + walkOverDelay(WATCH) val mime = NPCs.firstOrNull(MIME_TILE) { it.id == "mime" } ?: NPCs.add("mime", MIME_TILE, ticks = -1, owner = this) runMime(mime) } diff --git a/game/src/main/kotlin/content/activity/event/random/pinball/Pinball.kt b/game/src/main/kotlin/content/activity/event/random/pinball/Pinball.kt index f462262310..6a281c21b8 100644 --- a/game/src/main/kotlin/content/activity/event/random/pinball/Pinball.kt +++ b/game/src/main/kotlin/content/activity/event/random/pinball/Pinball.kt @@ -23,6 +23,7 @@ import world.gregs.voidps.engine.client.minimap import world.gregs.voidps.engine.client.ui.close import world.gregs.voidps.engine.client.ui.dialogue.talkWith import world.gregs.voidps.engine.client.ui.open +import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.npc.NPCs import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.obj.GameObject @@ -118,15 +119,7 @@ class Pinball : Script { oldMan.face(this) face(oldMan) talkWith(oldMan) - npc("The rules of the game are quite simple. You have to score 10 points by tagging the flashing pillars.") - npc("Don't tag the ones that do not have rings around the base, as that will reset your points, and don't try and get past those trolls until you are done!") - npc("See you later!") - oldMan.anim("emote_wave") - delay(4) - oldMan.gfx("imp_puff") - delay(1) - NPCs.remove(oldMan) - statement("Tag the post with the $FLASHING_RINGS.", clickToContinue = false) + explainPinball(this, oldMan) } private suspend fun Player.tagPost(post: GameObject) { @@ -214,6 +207,18 @@ class Pinball : Script { private data class Post(val id: String, val tile: Tile) companion object { + suspend fun explainPinball(player: Player, oldMan: NPC) { + player.npc("The rules of the game are quite simple. You have to score 10 points by tagging the flashing pillars.") + player.npc("Don't tag the ones that do not have rings around the base, as that will reset your points, and don't try and get past those trolls until you are done!") + player.npc("See you later!") + oldMan.anim("emote_wave") + player.delay(4) + oldMan.gfx("imp_puff") + player.delay(1) + NPCs.remove(oldMan) + player.statement("Tag the post with the $FLASHING_RINGS.", clickToContinue = false) + } + private const val REQUIRED = 10 private const val FLASHING_RINGS = "flashing rings" private val REGION = Region(30, 78) diff --git a/game/src/main/kotlin/content/area/asgarnia/taverley/CrystalChest.kt b/game/src/main/kotlin/content/area/asgarnia/taverley/CrystalChest.kt new file mode 100644 index 0000000000..30f197c424 --- /dev/null +++ b/game/src/main/kotlin/content/area/asgarnia/taverley/CrystalChest.kt @@ -0,0 +1,37 @@ +package content.area.asgarnia.taverley + +import content.entity.player.inv.item.addOrDrop +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.entity.character.player.chat.ChatType +import world.gregs.voidps.engine.entity.character.sound +import world.gregs.voidps.engine.entity.item.drop.DropTables +import world.gregs.voidps.engine.entity.obj.replace +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.remove + +class CrystalChest(val dropTables: DropTables) : Script { + init { + objectOperate("Open", "crystal_chest") { (target) -> + val table = dropTables.get("crystal_chest_drop_table") ?: return@objectOperate + if (!inventory.contains("crystal_key")) { + message("This chest is securely locked shut.") + sound("locked") + return@objectOperate + } + anim("open_chest") + sound("locked") + message("You unlock the chest with your key.", ChatType.Filter) + target.replace("crystal_chest_open", ticks = 2) + delay(1) + if (inventory.remove("crystal_key")) { + val drops = table.roll() + for (drop in drops) { + val item = drop.toItem() + addOrDrop(item.id, item.amount) + } + message("You find some treasure in the chest!") + } + } + } +} diff --git a/game/src/main/kotlin/content/area/asgarnia/taverley/DustyKey.kt b/game/src/main/kotlin/content/area/asgarnia/taverley/DustyKey.kt index 4a7a30ead0..68b2386f83 100644 --- a/game/src/main/kotlin/content/area/asgarnia/taverley/DustyKey.kt +++ b/game/src/main/kotlin/content/area/asgarnia/taverley/DustyKey.kt @@ -18,5 +18,15 @@ class DustyKey : Script { message("The gate is locked.") } } + + objectOperate("Open", "door_547_closed") { (target) -> + if (inventory.contains("jail_key")) { + sound("unlock") + enterDoor(target) + } else { + sound("locked") + message("This door is locked.") + } + } } } diff --git a/game/src/main/kotlin/content/area/asgarnia/taverley/VelrakTheExplorer.kt b/game/src/main/kotlin/content/area/asgarnia/taverley/VelrakTheExplorer.kt new file mode 100644 index 0000000000..b3e278fd76 --- /dev/null +++ b/game/src/main/kotlin/content/area/asgarnia/taverley/VelrakTheExplorer.kt @@ -0,0 +1,46 @@ +package content.area.asgarnia.taverley + +import content.entity.player.bank.ownsItem +import content.entity.player.dialogue.Confused +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.Sad +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.item +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import content.entity.player.inv.item.addOrDrop +import world.gregs.voidps.engine.Script + +class VelrakTheExplorer : Script { + init { + npcOperate("Talk-to", "velrak_the_explorer") { + if (ownsItem("dusty_key")) { + player("Are you still here?") + npc("Yes... I'm still plucking up the courage to run out past those Black Knights..") + return@npcOperate + } + npc("Thank you for rescuing me! It isn't very comfy in this cell!") + choice { + option("So... do you know anywhere good to explore?") { + npc("Well, this dungeon was quite good to explore ...until I got captured, anyway. I was given a key to an inner part of this dungeon by a mysterious cloaked stranger!") + npc("It's rather tough for me to get that far into the dungeon however... I just keep getting captured! Would you like to give it a go?") + choice { + option("Yes please!") { + addOrDrop("dusty_key") + item("dusty_key", "Velrak reaches somewhere mysterious and passes you a key.") + } + option("No, it's too dangerous for me too.") { + npc("I don't blame you!") + } + } + } + option("Do I get a reward?") { + player("Do I get a reward? For freeing you and all...") + npc("Well... not really... The Black Knights took all of my stuff before throwing me in here to rot!") + } + } + } + } +} diff --git a/game/src/main/kotlin/content/area/kandarin/yanille/Aleck.kt b/game/src/main/kotlin/content/area/kandarin/yanille/Aleck.kt new file mode 100644 index 0000000000..584ebbd616 --- /dev/null +++ b/game/src/main/kotlin/content/area/kandarin/yanille/Aleck.kt @@ -0,0 +1,33 @@ +package content.area.kandarin.yanille + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Confused +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import world.gregs.voidps.engine.Script + +class Aleck : Script { + init { + npcOperate("Talk-to", "aleck") { (target) -> + player("Hello.") + npc("Hello, hello, and a most warm welcome to my Hunter Emporium. We have everything the discerning Hunter could need.") + npc("Would you like me to show you our range of equipment? Or was there something specific you were after?") + choice { + option("Ok, let's see what you've got!") { + openShop(target.def["shop"]) + } + option("I'm not interested, thanks.") { + npc("Well, if you do ever find yourself in need of the finest Hunter equipment available, then you know where to come.") + } + option("Who's that guy over there?") { + npc("Him? I think he might be crazy. Either that or he's seeking attention.") + npc("He keeps trying to sell me these barmy looking weapons he's invented. I can't see them working, personally.") + } + } + } + } +} diff --git a/game/src/main/kotlin/content/area/kandarin/yanille/Frenita.kt b/game/src/main/kotlin/content/area/kandarin/yanille/Frenita.kt new file mode 100644 index 0000000000..3a29447995 --- /dev/null +++ b/game/src/main/kotlin/content/area/kandarin/yanille/Frenita.kt @@ -0,0 +1,22 @@ +package content.area.kandarin.yanille + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Happy +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 Frenita : Script { + init { + npcOperate("Talk-to", "frenita") { (target) -> + npc("Would you like to buy some cooking equipment?") + choice { + option("Yes please.") { + openShop(target.def["shop"]) + } + option("No thank you.") + } + } + } +} diff --git a/game/src/main/kotlin/content/area/kandarin/yanille/Leon.kt b/game/src/main/kotlin/content/area/kandarin/yanille/Leon.kt new file mode 100644 index 0000000000..313bc83bd6 --- /dev/null +++ b/game/src/main/kotlin/content/area/kandarin/yanille/Leon.kt @@ -0,0 +1,173 @@ +package content.area.kandarin.yanille + +import com.github.michaelbull.logging.InlineLogger +import content.entity.player.dialogue.* +import content.entity.player.dialogue.type.* +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.ui.chat.plural +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.transact.TransactionError +import world.gregs.voidps.engine.inv.transact.operation.AddItem.add +import world.gregs.voidps.engine.inv.transact.operation.RemoveItem.remove + +class Leon : Script { + + val logger = InlineLogger() + + init { + npcOperate("Talk-to", "leon") { + item("hunters_crossbow", "Leon is gazing intently at the crossbow in his hands.") + choice { + place() + infoCrossbow() + infoAmmo() + bye() + } + } + + npcOperate("Trade", "leon") { + choice { + option("Could I have a go with your crossbow?") { + buy() + } + makeAmmo() + bye() + } + } + } + + private fun ChoiceOption.place() = option("What is this place?") { + npc("This is Aleck's Hunter Emporium. Basically, it's just a shop with a fancy name; you can buy various weapons and traps here.") + choice { + shop() + infoCrossbow() + infoAmmo() + bye() + } + } + + private fun ChoiceOption.shop() = option("Can I buy some equipment from the shop then?") { + npc("Oh, this isn't my shop; the owner is Aleck over there behind the counter.") + npc("I experiment with weapon designs. I'm here because I've been trying to convince people to back my research and maybe sell some of my products - like this one I'm holding - in their shops.") + npc("Aleck doesn't seem to be interested, though.") + choice { + infoCrossbow() + infoAmmo() + bye() + } + } + + private fun ChoiceOption.infoCrossbow(): Unit = option("Can you tell me about your crossbow?") { + npc("It's good, isn't it? I designed it to incorporate the bones of various animals in its construction.") + npc("It's a fair bit faster than an ordinary crossbow too; it'll take you far less time to reload between shots.") + choice { + crazy() + trade() + infoAmmo() + bye() + } + } + + private fun ChoiceOption.crazy(): Unit = option("That's crazy, it'll never work!") { + npc("That's what they said about my wind-powered mouse traps, too.") + player("And did they work?") + npc("Well, they only ran into problems because people kept insisting on trying to use them indoors.") + npc("Anyway, I think my crossbow invention is showing a lot more promise.") + choice { + trade() + infoAmmo() + bye() + } + } + + private fun ChoiceOption.trade() { + option("That sounds good. Let's trade.") { + buy() + } + } + + private suspend fun Player.buy() { + npc("I'm afraid with it being a prototype, I've only got a few for my own testing purposes.") + npc("Of course, for the right price, it might be worth my while to make another prototype.") + player("How much?") + npc("To you, 1300 gold pieces.") + choice { + option("Ok, here you go.") { + inventory.transaction { + remove("coins", 1300) + add("hunters_crossbow") + } + when (inventory.transaction.error) { + is TransactionError.Deficient -> player("Oh, actually I don't have enough money with me.") + is TransactionError.Full -> npc("Well, you look a little overburdened there at the moment. Maybe you should free up some space to carry it in first.") + TransactionError.None -> { + item("hunters_crossbow", "You hand over the money and the weapon designer presents you with a prototype crossbow.") + npc("Here, you might as well take this one. Aleck over there doesn't seem interested and it'll save you a wait.") + } + else -> logger.warn { "Error buying hunters crossbow ${inventory.transaction.error}" } + } + } + option("I'll give it a miss, thanks.") { + npc("Your loss; they'll probably become highly valuable and sought after once I convince someone to market the things.") + } + } + } + + private fun ChoiceOption.infoAmmo() = option("Tell me about the ammo for your crossbow.") { + npc("Ah, I admit as a result of its... er... unique construction, it won't take just any old bolts.") + npc("If you can supply the materials and a token fee, I'd be happy to make some for you.") + npc("I need kebbit spikes, lots of 'em. Not all kebbits have spikes, mind you. You'll be wanting prickly kebbits or, even better, razor-backed kebbits to get material hard enough.") + choice { + option("Can't I just make my own?") { + npc("Yes, I suppose you could, although you'll need a steady hand with a knife and a chisel.") + npc("The bolts have an unusual diameter, but basically you'll just need to be able to carve kebbit spikes into straight shafts.") + npc("Meanwhile, since you're here, I can make some for you if you have the materials.") + choice { + makeAmmo() + infoCrossbow() + bye() + } + } + makeAmmo() + infoCrossbow() + bye() + } + } + + private fun ChoiceOption.bye() = option("I'll be off now, excuse me.") { + npc("Well, if you ever find yourself in need of that innovative edge, you can always find me here.") + player("... thanks.") + } + + private fun ChoiceOption.makeAmmo() = option("Okay, can you make ammo for me?") { + val (item, amount) = makeAmount(text = "How many batches would you like?", items = listOf("kebbit_bolts", "long_kebbit_bolts"), type = "Make", maximum = 27) + when (item) { + "kebbit_bolts" -> { + inventory.transaction { + remove("kebbit_spike", amount) + remove("coins", 20 * amount) + add("kebbit_bolts", 12 * amount) + } + when (inventory.transaction.error) { + is TransactionError.Deficient -> items("kebbit_spike", "coins_4", "You need $amount kebbit ${"spike".plural(amount)} and ${amount * 20} coins to make ${12 * amount} kebbit bolts.") + TransactionError.None -> item("kebbit_bolts", "You hand the weapon designer $amount ${"spike".plural(amount)} and ${amount * 20} coins. In return he presents you with ${12 * amount} bolts.") + else -> logger.warn { "Error buying kebbit bolts ${inventory.transaction.error}." } + } + } + "long_kebbit_bolts" -> { + inventory.transaction { + remove("long_kebbit_spike", amount) + remove("coins", 40 * amount) + add("long_kebbit_bolts", 12 * amount) + } + when (inventory.transaction.error) { + is TransactionError.Deficient -> items("long_kebbit_spike", "coins_4", "You need $amount long kebbit ${"spike".plural(amount)} and ${amount * 40} coins to make ${12 * amount} long kebbit bolts.") + TransactionError.None -> item("long_kebbit_bolts", "You hand the weapon designer $amount long ${"spike".plural(amount)} and ${amount * 40} coins. In return he presents you with ${12 * amount} long bolts.") + else -> logger.warn { "Error buying long kebbit bolts ${inventory.transaction.error}." } + } + } + else -> logger.warn { "Error making kebbit bolts." } + } + } +} diff --git a/game/src/main/kotlin/content/area/kharidian_desert/DesertHeat.kt b/game/src/main/kotlin/content/area/kharidian_desert/DesertHeat.kt index 6bd5f0730d..6bf87b399e 100644 --- a/game/src/main/kotlin/content/area/kharidian_desert/DesertHeat.kt +++ b/game/src/main/kotlin/content/area/kharidian_desert/DesertHeat.kt @@ -72,7 +72,7 @@ class DesertHeat : Script { message("You fail to cut the cactus correctly and it gives no water this time.") return@objectOperate } - if (!inventory.replace("waterskin_0", "waterkin_1") && !inventory.replace("waterskin_1", "waterkin_2") && !inventory.replace("waterskin_2", "waterkin_3") && !inventory.replace("waterskin_3", "waterkin_4")) { + if (!inventory.replace("waterskin_0", "waterskin_1") && !inventory.replace("waterskin_1", "waterskin_2") && !inventory.replace("waterskin_2", "waterskin_3") && !inventory.replace("waterskin_3", "waterskin_4")) { message("You fail to cut the cactus correctly and it gives no water this time.") return@objectOperate } diff --git a/game/src/main/kotlin/content/area/kharidian_desert/nardah/Artimeus.kt b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Artimeus.kt new file mode 100644 index 0000000000..4f34eedf10 --- /dev/null +++ b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Artimeus.kt @@ -0,0 +1,28 @@ +package content.area.kharidian_desert.nardah + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import world.gregs.voidps.engine.Script + +class Artimeus : Script { + init { + npcOperate("Talk-to", "artimeus") { (target) -> + npc("Greetings, friend; my business here deals with Hunter related items. Is there anything in which I can interest you?") + choice { + option("What kinds of items do you stock?") { + npc("Take a look for yourself.") + openShop(target.def["shop"]) + } + option("I'm not in the market for Hunter equipment right now, thanks.") { + player("I'm not in the market for Hunter equipment right now, thanks.") + npc("Maybe another time, then.") + } + } + } + } +} diff --git a/game/src/main/kotlin/content/area/kharidian_desert/nardah/Kazemde.kt b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Kazemde.kt new file mode 100644 index 0000000000..e4ba0fba4a --- /dev/null +++ b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Kazemde.kt @@ -0,0 +1,22 @@ +package content.area.kharidian_desert.nardah + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Happy +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 Kazemde : Script { + init { + npcOperate("Talk-to", "kazemde") { (target) -> + npc("Can I help you at all?") + choice { + option("Yes please. What are you selling?") { + openShop(target.def["shop"]) + } + option("No thank you.") + } + } + } +} diff --git a/game/src/main/kotlin/content/area/kharidian_desert/nardah/Nkuku.kt b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Nkuku.kt new file mode 100644 index 0000000000..566ac636f3 --- /dev/null +++ b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Nkuku.kt @@ -0,0 +1,21 @@ +package content.area.kharidian_desert.nardah + +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import content.quest.questCompleted +import world.gregs.voidps.engine.Script + +class Nkuku : Script { + init { + npcOperate("Talk-to", "nkuku") { (target) -> + if (questCompleted("spirits_of_the_elid")) { + npc("It still shocks me that the spirits of the river would put such a nasty curse on us for something so small.") + player("Well strange are the ways of ancient beings such as them.") + } else { + player("Good day to you.") + npc("May Saradomin be with you.") + } + } + } +} diff --git a/game/src/main/kotlin/content/area/kharidian_desert/nardah/Rokuh.kt b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Rokuh.kt new file mode 100644 index 0000000000..02fc6813c6 --- /dev/null +++ b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Rokuh.kt @@ -0,0 +1,32 @@ +package content.area.kharidian_desert.nardah + +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import content.quest.questCompleted +import world.gregs.voidps.engine.Script + +class Rokuh : Script { + init { + npcOperate("Talk-to", "rokuh*") { (target) -> + npc("Come one, come all, buy my amazing choc ice invention here! Chocolate on the outside. Iced cream on the inside. Oh I also have some chocolate left over from making choc ices which I'm selling too.") + choice { + option("Cool I'd like to buy some.") + option("No thanks, I'm not interested.") + option("How do you stop your icy snacks melting?") { + player("How do you stop your icy snacks melting? The middle of the desert is the last place I'd expect to see ice.") + npc("It's quite a surprise, isn't it, my friend. I actually have this special magic box of ice, which I bought for a princely sum, from a strange man while adventuring in lands far away. He said it is imbued with some sort of") + npc("powerful ice magic which keeps it cold all the time. I had never seen any ice magic before, it's clearly very rare and powerful, so I felt I had to buy it.") + } + if (questCompleted("spirits_of_the_elid")) { + option("How's business, now that the curse has gone?") { + npc("Ohh as good as ever. The people of Nardah seem to have taken well to the taste of chocolate and iced cream combined and, with the curse gone, all my friends are much happier too, so it's all good.") + } + } + } + } + } +} diff --git a/game/src/main/kotlin/content/area/kharidian_desert/nardah/Seddu.kt b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Seddu.kt new file mode 100644 index 0000000000..a795056243 --- /dev/null +++ b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Seddu.kt @@ -0,0 +1,22 @@ +package content.area.kharidian_desert.nardah + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Happy +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 Seddu : Script { + init { + npcOperate("Talk-to", "seddu") { (target) -> + npc("I buy and sell adventurer's equipment, do you want to trade?") + choice { + option("Yes please.") { + openShop(target.def["shop"]) + } + option("No thank you.") + } + } + } +} diff --git a/game/src/main/kotlin/content/area/kharidian_desert/nardah/Zahur.kt b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Zahur.kt new file mode 100644 index 0000000000..33b2b9b282 --- /dev/null +++ b/game/src/main/kotlin/content/area/kharidian_desert/nardah/Zahur.kt @@ -0,0 +1,60 @@ +package content.area.kharidian_desert.nardah + +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.Sad +import content.entity.player.dialogue.Shifty +import content.entity.player.dialogue.type.ChoiceOption +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message + +class Zahur : Script { + init { + npcOperate("Talk-to", "zahur") { (target) -> + if (get("spoke_nardah_herblist", false)) { + npc("Hello again. Do you need anything?") + } else { + npc("Hi there. Do you need anything? I can clean herbs for a fee of 200 coins per herb. I can clean all of your herbs at once, or I can do individual herbs if you show them to me.") + set("spoke_nardah_herblist", true) + npc("I can also decant your potion vials to into different doses. That service is free so long as you have the empty vials needed. If not, it's just a few coins.") + } + choice { + cleanHerbs() + decantPotions() + whyPay() + anotherTime() + } + } + } + + private fun ChoiceOption.whyPay() { + option("Why would I pay for you to clean herbs?") { + npc("Many of my customers wish to sell the herbs which they can't clean to other adventurers. They might hope for a better price by doing this.") + choice { + cleanHerbs() + decantPotions() + anotherTime() + } + } + } + + private fun ChoiceOption.anotherTime() { + option("Maybe another time.") + } + + private fun ChoiceOption.decantPotions() { + option("Please decant my potions.") { + npc("I didn't find anything that I could decant.") + message("Not yet implemented.") // TODO + } + } + + private fun ChoiceOption.cleanHerbs() { + option("Please clean all my herbs.") { + npc("I didn't find anything that I could clean.") + message("Not yet implemented.") // TODO + } + } +} diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/Bob.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/Bob.kt index 636e052f4a..1d909d39f4 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/Bob.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/Bob.kt @@ -7,14 +7,17 @@ import content.entity.player.dialogue.Quiz import content.entity.player.dialogue.Sad import content.entity.player.dialogue.type.choice import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.data.definition.Tables import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.entity.character.player.skill.level.Interpolation.interpolate import world.gregs.voidps.engine.entity.item.Item import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.transact.operation.AddItem.add +import world.gregs.voidps.engine.inv.transact.operation.ClearItem.clear import world.gregs.voidps.engine.inv.transact.operation.RemoveItem.remove -import world.gregs.voidps.engine.inv.transact.operation.ReplaceItem.replace class Bob : Script { @@ -34,8 +37,9 @@ class Bob : Script { } } - itemOnNPCOperate("*", "bob") { (_, item) -> - if (!repairable(item.id)) { + itemOnNPCOperate("*", "bob") { (_, item, slot) -> + val repaired = Tables.itemOrNull("repairable_items.${item.id}.repaired") + if (repaired == null) { npc("Sorry friend, but I can't do anything with that.") return@itemOnNPCOperate } @@ -45,10 +49,13 @@ class Bob : Script { option("Yes I'm sure!") { val repaired = inventory.transaction { remove("coins", cost) - replace(item.id, repaired(item.id)) + clear(slot) + add(repaired) } if (repaired) { npc("There you go. It's a pleasure doing business with you!") + } else { + player("Oh dear, I don't seem to have enough money.") } } option("On second thoughts, no thanks.") @@ -56,15 +63,6 @@ class Bob : Script { } } - fun repairable(item: String) = item.endsWith("_100") || item.endsWith("_75") || item.endsWith("_50") || item.endsWith("_25") || item.endsWith("_broken") - - fun repaired(item: String) = item - .replace("_100", "_new") - .replace("_75", "_new") - .replace("_50", "_new") - .replace("_25", "_new") - .replace("_broken", "_new") - fun repairCost(player: Player, item: Item): Int { val cost = item.def.cost val max = (cost * 0.6).toInt() diff --git a/game/src/main/kotlin/content/area/morytania/slayer_tower/Nechryael.kt b/game/src/main/kotlin/content/area/morytania/slayer_tower/Nechryael.kt index 8435032cc9..8617b26591 100644 --- a/game/src/main/kotlin/content/area/morytania/slayer_tower/Nechryael.kt +++ b/game/src/main/kotlin/content/area/morytania/slayer_tower/Nechryael.kt @@ -1,5 +1,6 @@ package content.area.morytania.slayer_tower +import content.entity.combat.killer import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.instruction.handle.interactPlayer import world.gregs.voidps.engine.entity.character.npc.NPCs @@ -39,5 +40,10 @@ class Nechryael : Script { target.inc("death_spawns") } } + + npcDeath("death_spawn") { + val killer = killer as? Player ?: return@npcDeath + killer.dec("death_spawns") + } } } diff --git a/game/src/main/kotlin/content/entity/combat/Combat.kt b/game/src/main/kotlin/content/entity/combat/Combat.kt index e9619c4828..c7098b97c4 100644 --- a/game/src/main/kotlin/content/entity/combat/Combat.kt +++ b/game/src/main/kotlin/content/entity/combat/Combat.kt @@ -218,7 +218,9 @@ class Combat(val combatDefinitions: CombatDefinitions) : CombatApi.start(character, target) } } - target.start("under_attack", 8) + if (target !is Player || !Target.isDeathSpawn(character)) { + target.start("under_attack", 8) + } target.start("hunted", 8) // FIXME Temp #1119 if (character is NPC) { CombatApi.swing(character, target, character.fightStyle) diff --git a/game/src/main/kotlin/content/entity/combat/Target.kt b/game/src/main/kotlin/content/entity/combat/Target.kt index 7d5c6a9ec0..ef18dabf43 100644 --- a/game/src/main/kotlin/content/entity/combat/Target.kt +++ b/game/src/main/kotlin/content/entity/combat/Target.kt @@ -98,7 +98,7 @@ object Target { } } } - if (source is NPC && source.id == "death_spawn") { + if ((isNechryael(target.attacker) && target.attacker == this) || isDeathSpawn(source) || isDeathSpawn(target.attacker) || isDeathSpawn(source.attacker)) { return true } // If the target I'm trying to attack is already in combat and I am not the attacker @@ -121,6 +121,10 @@ object Target { return true } + fun isDeathSpawn(target: Character?) = target is NPC && target.id == "death_spawn" + + fun isNechryael(target: Character?) = target is NPC && target.id == "nechryael" + fun isDemon(target: Character) = target is NPC && target.categories.contains("demons") fun isVampyre(target: Character) = target is NPC && target.categories.contains("vampyres") diff --git a/game/src/main/kotlin/content/entity/combat/hit/Hit.kt b/game/src/main/kotlin/content/entity/combat/hit/Hit.kt index b58b23c1bc..a592a220d5 100644 --- a/game/src/main/kotlin/content/entity/combat/hit/Hit.kt +++ b/game/src/main/kotlin/content/entity/combat/hit/Hit.kt @@ -11,6 +11,7 @@ import content.skill.melee.weapon.weapon import content.skill.prayer.Prayer import content.skill.summoning.familiarDefenceMultiplier import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.client.ui.closeInterfaces import world.gregs.voidps.engine.entity.character.Character import world.gregs.voidps.engine.entity.character.mode.combat.CombatApi import world.gregs.voidps.engine.entity.character.mode.combat.CombatAttack @@ -164,6 +165,9 @@ fun Character.hit( ): Int { val actualDamage = Damage.modify(this, target, offensiveType, damage, weapon, spell, special) .coerceAtMost(target.levels.get(Skill.Constitution)) + if (target is Player) { + target.closeInterfaces() + } if (this is Player) { CombatApi.attack(this, CombatAttack(target, actualDamage, offensiveType, weapon, spell, special, delay)) } else if (this is NPC) { @@ -194,6 +198,7 @@ fun Character.directHit(source: Character, damage: Int, type: String = "damage", return } if (this is Player) { + closeInterfaces() CombatApi.damage(this, CombatDamage(source, type, damage, weapon, spell, special)) } else if (this is NPC) { CombatApi.damage(this, CombatDamage(source, type, damage, weapon, spell, special)) diff --git a/game/src/main/kotlin/content/entity/player/combat/CombatLevel.kt b/game/src/main/kotlin/content/entity/player/combat/CombatLevel.kt index 13a31e9991..bb984e5b50 100644 --- a/game/src/main/kotlin/content/entity/player/combat/CombatLevel.kt +++ b/game/src/main/kotlin/content/entity/player/combat/CombatLevel.kt @@ -1,6 +1,7 @@ package content.entity.player.combat import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.entity.World import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.combatLevel @@ -13,8 +14,8 @@ class CombatLevel : Script { init { playerSpawn { - combatLevel = calculateCombatLevel(levels) - summoningCombatLevel = calculateCombatLevel(levels, true) + combatLevel = calculateCombatLevel(levels, summoning = World.members) + summoningCombatLevel = calculateCombatLevel(levels, summoning = true) } for (skill in Skill.entries.filter { it.ordinal <= 6 || it.ordinal == 23 }.toTypedArray()) { @@ -23,8 +24,40 @@ class CombatLevel : Script { } fun recalculate(player: Player, skill: Skill, from: Int, to: Int) { - player.combatLevel = calculateCombatLevel(player.levels) - player.summoningCombatLevel = calculateCombatLevel(player.levels, true) + val previous = player.summoningCombatLevel + val level = calculateCombatLevel(player.levels, summoning = World.members) + player.combatLevel = level + player.summoningCombatLevel = calculateCombatLevel(player.levels, summoning = true) + if (player["skip_level_up", false]) { + return + } + if (level > previous) { + when (level) { + 90, 100, 110, 120, 130 -> { + // 90 - https://youtu.be/Jjn4-f9eSMM?t=47 + // 100 - https://youtu.be/sk72rWo2VPI?t=219 + // 130 - https://youtu.be/bHnKZatcA2s?t=10 + player.message("Well done! You've reached the Combat level $level milestone!") + } + 126 -> { + // f2p - https://youtu.be/31_wDe_HJsU?t=64 + // mem - https://youtu.be/uKJpOSzeTpg?t=53 + player.message("Congratulations! Your Combat level is now 126 - the highest possible Combat level on free worlds!") + } + 138 -> { + // https://youtu.be/o6Ga2FGG1Dc?t=329 + player.message("Congratulations! Your Combat level is now 138! You've achieved the highest Combat level possible!") + } + else -> { + // 30 - https://youtu.be/b4vTaGjIk8Q?t=11 + // 80 - https://youtu.be/GKKIH2hca0E?t=68 + // 112 - https://youtu.be/3GG4KkmPUrU?t=70 + // 125 - https://youtu.be/lFY_jQn3pfE?t=54 + // 128 - https://youtu.be/tBB914-pLEw?t=28 + player.message("Congratulations! You've just reached Combat level $level!") + } + } + } } fun calculateCombatLevel(levels: Levels, summoning: Boolean = false): Int { diff --git a/game/src/main/kotlin/content/entity/player/command/SkillCommands.kt b/game/src/main/kotlin/content/entity/player/command/SkillCommands.kt index 721eedbb90..6350532dc2 100644 --- a/game/src/main/kotlin/content/entity/player/command/SkillCommands.kt +++ b/game/src/main/kotlin/content/entity/player/command/SkillCommands.kt @@ -45,6 +45,10 @@ class SkillCommands( adminCommands("level", self, other) commandSuggestion("level", "set_level") adminCommand("reset", stringArg("player-name", "target player (default self)", optional = true, autofill = accounts.displayNames.keys), desc = "Set all skills to level 1", handler = ::reset) + + playerCommand("level_dialogues", desc = "Toggle level up pop-up dialogues.") { + message("Level up pop-ups are now ${if (toggle("skip_level_up_dialogues")) "enabled" else "disabled"}.") + } } fun set(player: Player, args: List) { @@ -55,21 +59,25 @@ class SkillCommands( } val skill = Skill.valueOf(string) val level = args[1].toInt() + player["skip_level_up"] = true target.experience.set(skill, Level.experience(skill, level)) target.levels.set(skill, level) target.queue("flash_reset", 1) { target.removeVarbit("skill_stat_flash", skill.name.lowercase()) + player["skip_level_up"] = false } } fun master(player: Player, args: List) { val target = Players.find(player, args.getOrNull(0)) ?: return + player["skip_level_up"] = true for (skill in Skill.all) { target.experience.set(skill, 14000000.0) target.levels.restore(skill, 1000) } target.queue("clear_flash", 1) { player.clear("skill_stat_flash") + player["skip_level_up"] = false } } diff --git a/game/src/main/kotlin/content/entity/player/command/TeleportCommands.kt b/game/src/main/kotlin/content/entity/player/command/TeleportCommands.kt index 743c3a5563..36b4a008b7 100644 --- a/game/src/main/kotlin/content/entity/player/command/TeleportCommands.kt +++ b/game/src/main/kotlin/content/entity/player/command/TeleportCommands.kt @@ -17,7 +17,6 @@ import world.gregs.voidps.engine.entity.character.move.tele 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.chat.ChatType -import world.gregs.voidps.engine.entity.character.player.name import world.gregs.voidps.type.Region import world.gregs.voidps.type.Tile @@ -29,7 +28,7 @@ class TeleportCommands( private val places = mapOf( "draynor" to Tile(3086, 3248), "varrock" to Tile(3212, 3429), - "varrock" to Tile(3212, 3429), + "yanille" to Tile(2605, 3097), "lumbridge" to Tile(3222, 3219), "burthorpe" to Tile(2899, 3546), "falador" to Tile(2966, 3379), @@ -66,6 +65,7 @@ class TeleportCommands( "uzer" to Tile(3484, 3092), "pollnivneach" to Tile(3357, 2968), "daemonheim" to Tile(3450, 3710), + "naradh" to Tile(3432, 2916), ) init { diff --git a/game/src/main/kotlin/content/entity/player/dialogue/DialogueInput.kt b/game/src/main/kotlin/content/entity/player/dialogue/DialogueInput.kt index 6d722e3631..619464d762 100644 --- a/game/src/main/kotlin/content/entity/player/dialogue/DialogueInput.kt +++ b/game/src/main/kotlin/content/entity/player/dialogue/DialogueInput.kt @@ -63,7 +63,6 @@ class DialogueInput : Script { continueDialogue("dialogue_skill_creation:choice*") { val choice = it.substringAfter(":choice").toIntOrNull() ?: 0 (suspension as? Suspension.IntEntry)?.resume(choice - 1) - closeDialogue() } } } diff --git a/game/src/main/kotlin/content/entity/player/dialogue/type/LevelUp.kt b/game/src/main/kotlin/content/entity/player/dialogue/type/LevelUp.kt index 443655d118..6ae873a941 100644 --- a/game/src/main/kotlin/content/entity/player/dialogue/type/LevelUp.kt +++ b/game/src/main/kotlin/content/entity/player/dialogue/type/LevelUp.kt @@ -1,6 +1,7 @@ package content.entity.player.dialogue.type import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.client.ui.chat.an import world.gregs.voidps.engine.client.ui.close import world.gregs.voidps.engine.client.ui.open @@ -10,6 +11,7 @@ import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.entity.character.player.skill.Skill.* import world.gregs.voidps.engine.entity.character.player.skill.Skills import world.gregs.voidps.engine.entity.character.player.skill.exp.Experience +import world.gregs.voidps.engine.entity.character.player.skill.level.Level import world.gregs.voidps.engine.event.AuditLog import world.gregs.voidps.engine.queue.engineQueue import world.gregs.voidps.engine.suspend.pauseButton @@ -66,6 +68,11 @@ class LevelUp : Script { jingle("level_up_${skill.name.lowercase()}${if (unlock) "_unlock" else ""}", 0.5) addVarbit("skill_stat_flash", skill.name.lowercase()) val level = if (skill == Constitution) to / 10 else to + // https://youtu.be/o6Ga2FGG1Dc?t=326 + message("You've just advanced${skill.name.an()} ${skill.name} level! You have reached level $level.") + if (get("skip_level_up_dialogues", false)) { + return@maxLevelChanged + } engineQueue("level_up") { levelUp( this, @@ -75,6 +82,10 @@ class LevelUp : Script { You have now reached level $level! """, ) + if (skill == Dungeoneering && level == 120 || level == Level.MAX_LEVEL) { + // https://youtu.be/o6Ga2FGG1Dc?t=64 + message("Well done! You've achieved the highest possible level in this skill!") + } } } } diff --git a/game/src/main/kotlin/content/entity/player/inv/ItemPacks.kt b/game/src/main/kotlin/content/entity/player/inv/ItemPacks.kt new file mode 100644 index 0000000000..9b7533f08e --- /dev/null +++ b/game/src/main/kotlin/content/entity/player/inv/ItemPacks.kt @@ -0,0 +1,33 @@ +package content.entity.player.inv + +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.data.definition.Rows +import world.gregs.voidps.engine.entity.character.player.chat.inventoryFull +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.transact.TransactionError +import world.gregs.voidps.engine.inv.transact.operation.AddItem.add +import world.gregs.voidps.engine.inv.transact.operation.RemoveItem.remove +import world.gregs.voidps.engine.inv.transact.operation.RemoveItemLimit.removeToLimit + +class ItemPacks : Script { + init { + itemOption("Open", "*_pack") { (item) -> + val row = Rows.getOrNull("packs.${item.id}") ?: return@itemOption + inventory.transaction { + remove(item.id) + add(row.item("unpacked"), row.int("amount")) + } + } + + itemOption("Open-All", "*_pack") { (item) -> + val row = Rows.getOrNull("packs.${item.id}") ?: return@itemOption + inventory.transaction { + val removed = removeToLimit(item.id, item.amount) + add(row.item("unpacked"), removed * row.int("amount")) + } + if (inventory.transaction.error is TransactionError.Full) { + inventoryFull() + } + } + } +} diff --git a/game/src/main/kotlin/content/entity/player/kept/ItemsKeptOnDeath.kt b/game/src/main/kotlin/content/entity/player/kept/ItemsKeptOnDeath.kt index fd1b186540..c7da11717c 100644 --- a/game/src/main/kotlin/content/entity/player/kept/ItemsKeptOnDeath.kt +++ b/game/src/main/kotlin/content/entity/player/kept/ItemsKeptOnDeath.kt @@ -39,7 +39,7 @@ object ItemsKeptOnDeath { queue.pop() continue } - when (val type = item.def["kept", ItemKept.Never]) { + when (val type = item.def["kept", ItemKept.Wilderness]) { ItemKept.Never, ItemKept.Vanish -> { queue.pop() continue diff --git a/game/src/main/kotlin/content/skill/firemaking/Light.kt b/game/src/main/kotlin/content/skill/firemaking/Light.kt index cfe8219bf5..a01b6aab9f 100644 --- a/game/src/main/kotlin/content/skill/firemaking/Light.kt +++ b/game/src/main/kotlin/content/skill/firemaking/Light.kt @@ -1,28 +1,38 @@ package content.skill.firemaking +import world.gregs.voidps.engine.data.definition.Rows import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.equip.equipped import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.transact.operation.ReplaceItem.replace import world.gregs.voidps.network.login.protocol.visual.update.player.EquipSlot +import kotlin.collections.any object Light { fun hasLightSource(player: Player): Boolean { - if (player.inventory.items.any { it.id.endsWith("lantern_lit") || it.id.endsWith("candle_lit") || it.id == "firemaking_cape_t" || it.id == "firemaking_cape" }) { + if (player.inventory.items.any { it.isNotEmpty() && (isFiremakingCape(it.id) || Rows.getOrNull("extinguish.${it.id}") != null) }) { return true } - - val cape = player.equipped(EquipSlot.Cape).id - return cape == "firemaking_cape" || cape == "firemaking_cape_t" + if (player.equipped(EquipSlot.Shield).id == "lit_bug_lantern") { + return true + } + return isFiremakingCape(player.equipped(EquipSlot.Cape).id) } + private fun isFiremakingCape(item: String) = item == "firemaking_cape" || item == "firemaking_cape_t" + fun extinguish(player: Player) { player.inventory.transaction { for (index in inventory.indices) { val item = inventory[index] - if (item.id.endsWith("candle_lit")) { - replace(index, item.id, item.id.removeSuffix("_lit")) + val row = Rows.getOrNull("extinguish.${item.id}") ?: continue + val type = row.string("type") + if (type != "candle" && type != "torch") { + // Don't extinguish lanterns + continue } + val unlit = row.item(".unlit") + replace(index, item.id, unlit) } } } diff --git a/game/src/main/kotlin/content/skill/mining/Mining.kt b/game/src/main/kotlin/content/skill/mining/Mining.kt index 331b4c2580..a208b86b67 100644 --- a/game/src/main/kotlin/content/skill/mining/Mining.kt +++ b/game/src/main/kotlin/content/skill/mining/Mining.kt @@ -120,7 +120,7 @@ class Mining : Script { val xp = ore.int("xp") / 10.0 ShootingStarHandler.extraOreHandler(this, item, xp) val added = addOre(this, item, target) - if (added > 0) { + if (added > 0 || item == "stardust") { exp(Skill.Mining, xp * added) } if (added < 1 || deplete(target, ore)) { diff --git a/game/src/main/kotlin/content/skill/summoning/ShardSwapping.kt b/game/src/main/kotlin/content/skill/summoning/ShardSwapping.kt index 7fe9c3816c..79aabf7b8f 100644 --- a/game/src/main/kotlin/content/skill/summoning/ShardSwapping.kt +++ b/game/src/main/kotlin/content/skill/summoning/ShardSwapping.kt @@ -10,13 +10,10 @@ import world.gregs.voidps.engine.client.ui.open import world.gregs.voidps.engine.data.definition.EnumDefinitions import world.gregs.voidps.engine.data.definition.ItemDefinitions import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.entity.character.player.chat.inventoryFull import world.gregs.voidps.engine.entity.item.Item import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.engine.inv.transact.TransactionError import world.gregs.voidps.engine.inv.transact.operation.AddItem.add import world.gregs.voidps.engine.inv.transact.operation.RemoveItem.remove -import world.gregs.voidps.engine.inv.transact.operation.RemoveItemLimit.removeToLimit import kotlin.math.min class ShardSwapping : Script { @@ -70,23 +67,6 @@ class ShardSwapping : Script { "Trade-All" -> swapForShards(this, actualItem, Int.MAX_VALUE) } } - - itemOption("Open", "spirit_shard_pack") { - inventory.transaction { - remove(it.item.id) - add("spirit_shards", 5000) - } - } - - itemOption("Open-All", "spirit_shard_pack") { - inventory.transaction { - val removed = removeToLimit(it.item.id, it.item.amount) - add("spirit_shards", removed * 5000) - } - if (inventory.transaction.error is TransactionError.Full) { - inventoryFull() - } - } } /** diff --git a/game/src/main/kotlin/content/skill/thieving/TrapChests.kt b/game/src/main/kotlin/content/skill/thieving/TrapChests.kt index 7c4cb10247..2b2b51e075 100644 --- a/game/src/main/kotlin/content/skill/thieving/TrapChests.kt +++ b/game/src/main/kotlin/content/skill/thieving/TrapChests.kt @@ -19,7 +19,7 @@ import world.gregs.voidps.engine.inv.transact.operation.AddItem.add class TrapChests(val tables: DropTables) : Script { init { - objectOperate("Open", "*_chest") { (target) -> + objectOperate("Open", "dorgesh_kaan_chest,nature_chest,barrows_chest,culinaromancers_chest,blood_rune_chest,dorgesh_kaan_gem_chest,shantay_chest,capn_arnav_chest,coin_chest,ardougne_castle_chest,steel_arrows_chest,maze_chest,lumbridge_closed_chest,big_coin_chest,edgeville_chest") { (target) -> when (target.id) { "coin_chest" -> damage(20 + levels.get(Skill.Constitution) / 10) "big_coin_chest" -> damage(40 + (levels.get(Skill.Constitution) * 0.15).toInt()) diff --git a/game/src/test/kotlin/content/area/kharidian_desert/DesertHeatTest.kt b/game/src/test/kotlin/content/area/kharidian_desert/DesertHeatTest.kt new file mode 100644 index 0000000000..d9b9a4b9fd --- /dev/null +++ b/game/src/test/kotlin/content/area/kharidian_desert/DesertHeatTest.kt @@ -0,0 +1,61 @@ +package content.area.kharidian_desert + +import FakeRandom +import WorldTest +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import world.gregs.voidps.engine.client.instruction.handle.interactObject +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.obj.GameObjects +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.type.Tile +import world.gregs.voidps.type.setRandom + +class DesertHeatTest : WorldTest() { + + @Test + fun `Desert heat kills over time`() { + val player = createPlayer(Tile(3383, 2872)) + tick(1) + + assertTrue(player.timers.contains("desert_heat")) + tick(150) + + assertEquals(90, player.levels.get(Skill.Constitution)) + } + + @Test + fun `Desert heat usees waterskins`() { + val player = createPlayer(Tile(3383, 2872)) + player.inventory.add("waterskin_2") + tick(1) + + assertTrue(player.timers.contains("desert_heat")) + tick(150) + + assertTrue(player.inventory.contains("waterskin_1")) + assertFalse(player.inventory.contains("waterskin_2")) + assertEquals(100, player.levels.get(Skill.Constitution)) + } + + @Test + fun `Refill waterskin with cactus juice`() { + setRandom(object : FakeRandom() { + override fun nextInt(until: Int) = until - 1 + }) + // It'll quench ya! + val player = createPlayer(Tile(3383, 2872)) + player.inventory.add("waterskin_0") + player.inventory.add("knife") + tick(1) + + val cactus = GameObjects.find(Tile(3382, 2872), "desert_cactus_full") + + player.interactObject(cactus, "Cut") + tick(2) + + assertTrue(player.inventory.contains("waterskin_1")) + assertFalse(player.inventory.contains("waterskin_0")) + } +} diff --git a/game/src/test/kotlin/content/area/morytania/slayer_tower/NechryaelTest.kt b/game/src/test/kotlin/content/area/morytania/slayer_tower/NechryaelTest.kt index a1c0058167..d368c1c929 100644 --- a/game/src/test/kotlin/content/area/morytania/slayer_tower/NechryaelTest.kt +++ b/game/src/test/kotlin/content/area/morytania/slayer_tower/NechryaelTest.kt @@ -4,7 +4,10 @@ import FakeRandom import WorldTest import npcOption import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test +import world.gregs.voidps.engine.client.instruction.handle.interactPlayer +import world.gregs.voidps.engine.entity.character.mode.combat.CombatMovement import world.gregs.voidps.engine.entity.character.npc.NPCs import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.type.setRandom @@ -27,7 +30,7 @@ class NechryaelTest : WorldTest() { player.npcOption(nechryael, "Attack") - tick(2) + tick(3) assertEquals(1, player["death_spawns", 0]) } @@ -45,11 +48,13 @@ class NechryaelTest : WorldTest() { val nechryael = NPCs.add("nechryael", emptyTile.addY(1)) tick() - player.npcOption(nechryael, "Attack") + val spawn = NPCs.add("death_spawn", emptyTile.addY(1)) + spawn.interactPlayer(player, "Attack") + tick(2) - assertEquals(1, player["death_spawns", 0]) + assertTrue(spawn.mode is CombatMovement) } } diff --git a/game/src/test/kotlin/content/area/wilderness/abyss/AbyssalDemonTest.kt b/game/src/test/kotlin/content/area/wilderness/abyss/AbyssalDemonTest.kt index 2c519b6d50..0995f937aa 100644 --- a/game/src/test/kotlin/content/area/wilderness/abyss/AbyssalDemonTest.kt +++ b/game/src/test/kotlin/content/area/wilderness/abyss/AbyssalDemonTest.kt @@ -3,11 +3,11 @@ package content.area.wilderness.abyss import FakeRandom import WorldTest import npcOption +import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Test import world.gregs.voidps.engine.entity.character.npc.NPCs import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.type.setRandom -import kotlin.test.assertNotEquals class AbyssalDemonTest : WorldTest() { @@ -34,6 +34,7 @@ class AbyssalDemonTest : WorldTest() { fun `Abyssal demon teleports around in combat`() { setRandom(object : FakeRandom() { override fun nextInt(until: Int) = if (until == 6) 6 else 0 + override fun nextInt(from: Int, until: Int) = 0 }) val player = createPlayer(emptyTile) player.levels.set(Skill.Constitution, 50)