diff --git a/data/minigame/castle_wars/castle_wars.items.toml b/data/minigame/castle_wars/castle_wars.items.toml index 2bb3a3a282..03cee77a4f 100644 --- a/data/minigame/castle_wars/castle_wars.items.toml +++ b/data/minigame/castle_wars/castle_wars.items.toml @@ -358,121 +358,145 @@ kept = "Reclaim" [cw_super_attack_potion_4] id = 18715 weight = 0.035 +excess = "cw_super_attack_potion_3" examine = "4 doses of super attack potion." [cw_super_attack_potion_3] id = 18716 weight = 0.026 +excess = "cw_super_attack_potion_2" examine = "3 doses of super attack potion." [cw_super_attack_potion_2] id = 18717 weight = 0.017 +excess = "cw_super_attack_potion_1" examine = "2 doses of super attack potion." [cw_super_attack_potion_1] id = 18718 weight = 0.008 +heals = 0 examine = "1 dose of super attack potion." [cw_super_strength_potion_4] id = 18719 weight = 0.035 +excess = "cw_super_strength_potion_3" examine = "4 doses of super strength potion." [cw_super_strength_potion_3] id = 18720 weight = 0.026 +excess = "cw_super_strength_potion_2" examine = "3 doses of super strength potion." [cw_super_strength_potion_2] id = 18721 weight = 0.017 +excess = "cw_super_strength_potion_1" examine = "2 doses of super strength potion." [cw_super_strength_potion_1] id = 18722 weight = 0.008 +heals = 0 examine = "1 dose of super strength potion." [cw_super_defence_potion_4] id = 18723 weight = 0.035 +excess = "cw_super_defence_potion_3" examine = "4 doses of super defence potion." [cw_super_defence_potion_3] id = 18724 weight = 0.026 +excess = "cw_super_defence_potion_2" examine = "3 doses of super defence potion." [cw_super_defence_potion_2] id = 18725 weight = 0.017 +excess = "cw_super_defence_potion_1" examine = "2 doses of super defence potion." [cw_super_defence_potion_1] id = 18726 weight = 0.008 +heals = 0 examine = "1 dose of super defence potion." [cw_super_energy_potion_4] id = 18727 weight = 0.035 +excess = "cw_super_energy_potion_3" examine = "4 doses of super energy potion." [cw_super_energy_potion_3] id = 18728 weight = 0.026 +excess = "cw_super_energy_potion_2" examine = "3 doses of super energy potion." [cw_super_energy_potion_2] id = 18729 weight = 0.017 +excess = "cw_super_energy_potion_1" examine = "2 doses of super energy potion." [cw_super_energy_potion_1] id = 18730 weight = 0.008 +heals = 0 examine = "1 dose of super energy potion." [cw_super_ranging_potion_4] id = 18731 weight = 0.035 +excess = "cw_super_ranging_potion_3" examine = "4 doses of super ranging potion." [cw_super_ranging_potion_3] id = 18732 weight = 0.026 +excess = "cw_super_ranging_potion_2" examine = "3 doses of super ranging potion." [cw_super_ranging_potion_2] id = 18733 weight = 0.017 +excess = "cw_super_ranging_potion_1" examine = "2 doses of super ranging potion." [cw_super_ranging_potion_1] id = 18734 weight = 0.008 +heals = 0 examine = "1 dose of super ranging potion." [cw_super_magic_potion_4] id = 18735 weight = 0.035 +excess = "cw_super_magic_potion_3" examine = "4 doses of super magic potion." [cw_super_magic_potion_3] id = 18736 weight = 0.026 +excess = "cw_super_magic_potion_2" examine = "3 doses of super magic potion." [cw_super_magic_potion_2] id = 18737 weight = 0.017 +excess = "cw_super_magic_potion_1" examine = "2 doses of super magic potion." [cw_super_magic_potion_1] id = 18738 weight = 0.008 +heals = 0 examine = "1 dose of super magic potion." [castle_wars_flag_cape] diff --git a/data/minigame/herblore_habitat/herblore_habitat.gfx.toml b/data/minigame/herblore_habitat/herblore_habitat.gfx.toml new file mode 100644 index 0000000000..0e957e7874 --- /dev/null +++ b/data/minigame/herblore_habitat/herblore_habitat.gfx.toml @@ -0,0 +1,5 @@ +[juju_mining_bank] +id = 2896 + +[juju_woodcutting_bank] +id = 2897 diff --git a/data/minigame/stealing_creation/stealing_creation.items.toml b/data/minigame/stealing_creation/stealing_creation.items.toml index 49881d4e73..53835887e3 100644 --- a/data/minigame/stealing_creation/stealing_creation.items.toml +++ b/data/minigame/stealing_creation/stealing_creation.items.toml @@ -641,6 +641,7 @@ examine = "The sacred clay has transformed into a wood-like substance." [prayer_potion_stealing_creation_5] id = 14207 weight = 0.5 +excess = "prayer_potion_stealing_creation_4" examine = "Five doses of Prayer restore potion." [prayer_potion_stealing_creation_5_noted] @@ -649,6 +650,7 @@ id = 14208 [prayer_potion_stealing_creation_4] id = 14209 weight = 0.4 +excess = "prayer_potion_stealing_creation_3" examine = "Four doses of Prayer restore potion." [prayer_potion_stealing_creation_4_noted] @@ -657,6 +659,7 @@ id = 14210 [prayer_potion_stealing_creation_3] id = 14211 weight = 0.3 +excess = "prayer_potion_stealing_creation_2" examine = "Three doses of Prayer restore potion." [prayer_potion_stealing_creation_3_noted] @@ -665,6 +668,7 @@ id = 14212 [prayer_potion_stealing_creation_2] id = 14213 weight = 0.2 +excess = "prayer_potion_stealing_creation_1" examine = "Two doses of Prayer restore potion." [prayer_potion_stealing_creation_2_noted] @@ -673,6 +677,7 @@ id = 14214 [prayer_potion_stealing_creation_1] id = 14215 weight = 0.1 +heals = 0 examine = "One dose of Prayer restore potion." [prayer_potion_stealing_creation_1_noted] @@ -681,6 +686,7 @@ id = 14216 [energy_potion_stealing_creation_5] id = 14217 weight = 0.5 +excess = "energy_potion_stealing_creation_4" examine = "Five doses of energy potion." [energy_potion_stealing_creation_5_noted] @@ -689,6 +695,7 @@ id = 14218 [energy_potion_stealing_creation_4] id = 14219 weight = 0.4 +excess = "energy_potion_stealing_creation_3" examine = "Four doses of energy potion." [energy_potion_stealing_creation_4_noted] @@ -697,6 +704,7 @@ id = 14220 [energy_potion_stealing_creation_3] id = 14221 weight = 0.3 +excess = "energy_potion_stealing_creation_2" examine = "Three doses of energy potion." [energy_potion_stealing_creation_3_noted] @@ -705,6 +713,7 @@ id = 14222 [energy_potion_stealing_creation_2] id = 14223 weight = 0.2 +excess = "energy_potion_stealing_creation_1" examine = "Two doses of energy potion." [energy_potion_stealing_creation_2_noted] @@ -713,6 +722,7 @@ id = 14224 [energy_potion_stealing_creation_1] id = 14225 weight = 0.1 +heals = 0 examine = "One dose of energy potion." [energy_potion_stealing_creation_1_noted] @@ -721,6 +731,7 @@ id = 14226 [super_attack_potion_stealing_creation_5] id = 14227 weight = 0.5 +excess = "super_attack_potion_stealing_creation_4" examine = "Five doses of super Attack potion." [super_attack_potion_stealing_creation_5_noted] @@ -729,6 +740,7 @@ id = 14228 [super_attack_potion_stealing_creation_4] id = 14229 weight = 0.4 +excess = "super_attack_potion_stealing_creation_3" examine = "Four doses of super Attack potion." [super_attack_potion_stealing_creation_4_noted] @@ -737,6 +749,7 @@ id = 14230 [super_attack_potion_stealing_creation_3] id = 14231 weight = 0.3 +excess = "super_attack_potion_stealing_creation_2" examine = "Three doses of super Attack potion." [super_attack_potion_stealing_creation_3_noted] @@ -745,6 +758,7 @@ id = 14232 [super_attack_potion_stealing_creation_2] id = 14233 weight = 0.2 +excess = "super_attack_potion_stealing_creation_1" examine = "Two doses of super Attack potion." [super_attack_potion_stealing_creation_2_noted] @@ -753,6 +767,7 @@ id = 14234 [super_attack_potion_stealing_creation_1] id = 14235 weight = 0.1 +heals = 0 examine = "One dose of super Attack potion." [super_attack_potion_stealing_creation_1_noted] @@ -761,6 +776,7 @@ id = 14236 [super_strength_potion_stealing_creation_5] id = 14237 weight = 0.5 +excess = "super_strength_potion_stealing_creation_4" examine = "Five doses of super Strength potion." [super_strength_potion_stealing_creation_5_noted] @@ -769,6 +785,7 @@ id = 14238 [super_strength_potion_stealing_creation_4] id = 14239 weight = 0.4 +excess = "super_strength_potion_stealing_creation_3" examine = "Four doses of super Strength potion." [super_strength_potion_stealing_creation_4_noted] @@ -777,6 +794,7 @@ id = 14240 [super_strength_potion_stealing_creation_3] id = 14241 weight = 0.3 +excess = "super_strength_potion_stealing_creation_2" examine = "Three doses of super Strength potion." [super_strength_potion_stealing_creation_3_noted] @@ -785,6 +803,7 @@ id = 14242 [super_strength_potion_stealing_creation_2] id = 14243 weight = 0.2 +excess = "super_strength_potion_stealing_creation_1" examine = "Two doses of super Strength potion." [super_strength_potion_stealing_creation_2_noted] @@ -793,6 +812,7 @@ id = 14244 [super_strength_potion_stealing_creation_1] id = 14245 weight = 0.1 +heals = 0 examine = "One dose of super Strength potion." [super_strength_potion_stealing_creation_1_noted] @@ -801,6 +821,7 @@ id = 14246 [ranging_potion_stealing_creation_5] id = 14247 weight = 0.5 +excess = "ranging_potion_stealing_creation_4" examine = "Five doses of ranging potion." [ranging_potion_stealing_creation_5_noted] @@ -809,6 +830,7 @@ id = 14248 [ranging_potion_stealing_creation_4] id = 14249 weight = 0.4 +excess = "ranging_potion_stealing_creation_3" examine = "Four doses of ranging potion." [ranging_potion_stealing_creation_4_noted] @@ -817,6 +839,7 @@ id = 14250 [ranging_potion_stealing_creation_3] id = 14251 weight = 0.3 +excess = "ranging_potion_stealing_creation_2" examine = "Three doses of ranging potion." [ranging_potion_stealing_creation_3_noted] @@ -825,6 +848,7 @@ id = 14252 [ranging_potion_stealing_creation_2] id = 14253 weight = 0.2 +excess = "ranging_potion_stealing_creation_1" examine = "Two doses of ranging potion." [ranging_potion_stealing_creation_2_noted] @@ -833,6 +857,7 @@ id = 14254 [ranging_potion_stealing_creation_1] id = 14255 weight = 0.1 +heals = 0 examine = "One dose of ranging potion." [ranging_potion_stealing_creation_1_noted] @@ -841,6 +866,7 @@ id = 14256 [defence_potion_stealing_creation_5] id = 14257 weight = 0.5 +excess = "defence_potion_stealing_creation_4" examine = "Five doses of Defence potion." [defence_potion_stealing_creation_5_noted] @@ -849,6 +875,7 @@ id = 14258 [defence_potion_stealing_creation_4] id = 14259 weight = 0.4 +excess = "defence_potion_stealing_creation_3" examine = "Four doses of Defence potion." [defence_potion_stealing_creation_4_noted] @@ -857,6 +884,7 @@ id = 14260 [defence_potion_stealing_creation_3] id = 14261 weight = 0.3 +excess = "defence_potion_stealing_creation_2" examine = "Three doses of Defence potion." [defence_potion_stealing_creation_3_noted] @@ -865,6 +893,7 @@ id = 14262 [defence_potion_stealing_creation_2] id = 14263 weight = 0.2 +excess = "defence_potion_stealing_creation_1" examine = "Two doses of Defence potion." [defence_potion_stealing_creation_2_noted] @@ -873,6 +902,7 @@ id = 14264 [defence_potion_stealing_creation_1] id = 14265 weight = 0.1 +heals = 0 examine = "One dose of Defence potion." [defence_potion_stealing_creation_1_noted] @@ -881,6 +911,7 @@ id = 14266 [magic_potion_stealing_creation_5] id = 14267 weight = 0.5 +excess = "magic_potion_stealing_creation_4" examine = "Five doses of Magic potion." [magic_potion_stealing_creation_5_noted] @@ -889,6 +920,7 @@ id = 14268 [magic_potion_stealing_creation_4] id = 14269 weight = 0.4 +excess = "magic_potion_stealing_creation_3" examine = "Four doses of Magic potion." [magic_potion_stealing_creation_4_noted] @@ -897,6 +929,7 @@ id = 14270 [magic_potion_stealing_creation_3] id = 14271 weight = 0.3 +excess = "magic_potion_stealing_creation_2" examine = "Three doses of Magic potion." [magic_potion_stealing_creation_3_noted] @@ -905,6 +938,7 @@ id = 14272 [magic_potion_stealing_creation_2] id = 14273 weight = 0.2 +excess = "magic_potion_stealing_creation_1" examine = "Two doses of Magic potion." [magic_potion_stealing_creation_2_noted] @@ -913,6 +947,7 @@ id = 14274 [magic_potion_stealing_creation_1] id = 14275 weight = 0.1 +heals = 0 examine = "One dose of Magic potion." [magic_potion_stealing_creation_1_noted] @@ -921,6 +956,7 @@ id = 14276 [summoning_potion_stealing_creation_5] id = 14277 weight = 0.5 +excess = "summoning_potion_stealing_creation_4" examine = "Five doses of Summoning potion." [summoning_potion_stealing_creation_5_noted] @@ -929,6 +965,7 @@ id = 14278 [summoning_potion_stealing_creation_4] id = 14279 weight = 0.4 +excess = "summoning_potion_stealing_creation_3" examine = "Four doses of Summoning potion." [summoning_potion_stealing_creation_4_noted] @@ -937,6 +974,7 @@ id = 14280 [summoning_potion_stealing_creation_3] id = 14281 weight = 0.3 +excess = "summoning_potion_stealing_creation_2" examine = "Three doses of Summoning potion." [summoning_potion_stealing_creation_3_noted] @@ -945,6 +983,7 @@ id = 14282 [summoning_potion_stealing_creation_2] id = 14283 weight = 0.2 +excess = "summoning_potion_stealing_creation_1" examine = "Two doses of Summoning potion." [summoning_potion_stealing_creation_2_noted] @@ -953,6 +992,7 @@ id = 14284 [summoning_potion_stealing_creation_1] id = 14285 weight = 0.1 +heals = 0 examine = "One dose of Summoning potion." [summoning_potion_stealing_creation_1_noted] diff --git a/data/quest/free/ernest_the_chicken/ernest_the_chicken.items.toml b/data/quest/free/ernest_the_chicken/ernest_the_chicken.items.toml index 114500c421..18f381b241 100644 --- a/data/quest/free/ernest_the_chicken/ernest_the_chicken.items.toml +++ b/data/quest/free/ernest_the_chicken/ernest_the_chicken.items.toml @@ -4,6 +4,9 @@ price = 139 limit = 100 heals = 0 weight = 0.056 +excess = "cocktail_glass" +empty = "cocktail_glass" +eat_message = "You drink the strange green liquid." examine = "Stankers gives out these strange cocktails for free." [poison_chalice_noted] diff --git a/data/quest/members/missing_my_mummy/missing_my_mummy.items.toml b/data/quest/members/missing_my_mummy/missing_my_mummy.items.toml index 0247a5655f..913987d9d5 100644 --- a/data/quest/members/missing_my_mummy/missing_my_mummy.items.toml +++ b/data/quest/members/missing_my_mummy/missing_my_mummy.items.toml @@ -146,7 +146,7 @@ kept = "Wilderness" id = 14815 tradeable = false weight = 0.453 -empty = "jub" +empty = "jug" destroy = "I can make a new jug of spiced wine using wine and spices." examine = "Spiced in a way unpalatable to modern tastes." kept = "Wilderness" diff --git a/data/skill/herblore/barbarian_mix.items.toml b/data/skill/herblore/barbarian_mix.items.toml index 21bf93fda9..a4232a4f4b 100644 --- a/data/skill/herblore/barbarian_mix.items.toml +++ b/data/skill/herblore/barbarian_mix.items.toml @@ -471,7 +471,7 @@ id = 11501 price = 2155 weight = 0.075 heals = 30 -excess = "super_antipoison_mix_1" +excess = "antidote+_mix_1" empty = "vial" eat_message = "You drink the lumpy potion." examine = "Two doses of fishy extra strength antidote potion." diff --git a/data/skill/herblore/herblore.anims.toml b/data/skill/herblore/herblore.anims.toml index 93cca66282..1953d54cab 100644 --- a/data/skill/herblore/herblore.anims.toml +++ b/data/skill/herblore/herblore.anims.toml @@ -1,6 +1,9 @@ [electric_shock] id = 3170 +[overload] +id = 3170 + [mixing_potion] id = 363 diff --git a/data/skill/herblore/herblore.vars.toml b/data/skill/herblore/herblore.vars.toml index 8895c8191b..798bf07503 100644 --- a/data/skill/herblore/herblore.vars.toml +++ b/data/skill/herblore/herblore.vars.toml @@ -2,6 +2,48 @@ format = "int" persist = true +[juju_mining] +format = "int" +persist = true + +[juju_woodcutting] +format = "int" +persist = true + +[juju_farming] +format = "int" +persist = true + +[juju_fishing] +format = "int" +persist = true + +[scentless] +format = "int" +persist = true + +[saradomins_blessing] +format = "int" +persist = true + +[guthixs_gift] +format = "int" +persist = true + +[zamoraks_favour] +format = "int" +persist = true + +# Transient banking window, deliberately not carried across a logout. +[juju_mining_bank] +format = "int" +persist = false + +# Transient banking window, deliberately not carried across a logout. +[juju_woodcutting_bank] +format = "int" +persist = false + [overload_refreshes_remaining] format = "int" persist = true diff --git a/data/skill/herblore/potion.items.toml b/data/skill/herblore/potion.items.toml index 4a8ee87132..2507b9b39a 100644 --- a/data/skill/herblore/potion.items.toml +++ b/data/skill/herblore/potion.items.toml @@ -24,6 +24,7 @@ id = 116 id = 117 price = 435 weight = 0.025 +excess = "strength_potion_1" empty = "vial" examine = "2 doses of strength potion." @@ -1561,31 +1562,31 @@ examine = "1 dose of extreme defence potion." [extreme_magic_4] id = 15320 -weight = 0.02 +weight = 0.035 excess = "extreme_magic_3" empty = "vial" -examine = "1 dose of extreme magic potion." +examine = "4 doses of extreme magic potion." [extreme_magic_3] id = 15321 -weight = 0.025 +weight = 0.03 excess = "extreme_magic_2" empty = "vial" -examine = "2 doses of extreme magic potion." +examine = "3 doses of extreme magic potion." [extreme_magic_2] id = 15322 -weight = 0.03 +weight = 0.025 excess = "extreme_magic_1" empty = "vial" -examine = "3 doses of extreme magic potion." +examine = "2 doses of extreme magic potion." [extreme_magic_1] id = 15323 -weight = 0.035 +weight = 0.02 excess = "vial" empty = "vial" -examine = "4 doses of extreme magic potion." +examine = "1 dose of extreme magic potion." [extreme_ranging_4] id = 15324 diff --git a/game/src/main/kotlin/content/skill/constitution/drink/Antifire.kt b/game/src/main/kotlin/content/skill/constitution/drink/Antifire.kt index 169f88bad4..7bd4ead34e 100644 --- a/game/src/main/kotlin/content/skill/constitution/drink/Antifire.kt +++ b/game/src/main/kotlin/content/skill/constitution/drink/Antifire.kt @@ -19,15 +19,21 @@ class Antifire : Script { } } + playerDeath { + clear("antifire") + clear("super_antifire") + } + timerStart("fire_resistance") { 30 } timerStart("fire_immunity") { 20 } timerTick("fire_resistance") { decrease(this, "antifire") } timerTick("fire_immunity") { decrease(this, "super_antifire") } - timerStop("fire_resistance,fire_immunity", ::clear) + timerStop("fire_resistance") { logout -> expire(this, "antifire", logout) } + timerStop("fire_immunity") { logout -> expire(this, "super_antifire", logout) } } fun decrease(player: Player, key: String): Int { - val remaining = player.dec(key, 0) + val remaining = player.dec(key) if (remaining <= 0) { return Timer.CANCEL } @@ -37,9 +43,11 @@ class Antifire : Script { return Timer.CONTINUE } - fun clear(player: Player, logout: Boolean) { + fun expire(player: Player, key: String, logout: Boolean) { + if (logout) { + return + } player.message("Your resistance to dragonfire has run out.") - player["antifire"] = 0 - player["super_antifire"] = 0 + player.clear(key) } } diff --git a/game/src/main/kotlin/content/skill/constitution/drink/CadavaPotion.kt b/game/src/main/kotlin/content/skill/constitution/drink/CadavaPotion.kt new file mode 100644 index 0000000000..deef218984 --- /dev/null +++ b/game/src/main/kotlin/content/skill/constitution/drink/CadavaPotion.kt @@ -0,0 +1,13 @@ +package content.skill.constitution.drink + +import content.entity.player.dialogue.type.item +import world.gregs.voidps.engine.Script + +class CadavaPotion : Script { + + init { + itemOption("Drink", "cadava_potion") { + item("cadava_potion", "You dare not drink.") + } + } +} diff --git a/game/src/main/kotlin/content/skill/constitution/drink/DungeoneeringPotions.kt b/game/src/main/kotlin/content/skill/constitution/drink/DungeoneeringPotions.kt index 9d36e9ac4a..cc66449574 100644 --- a/game/src/main/kotlin/content/skill/constitution/drink/DungeoneeringPotions.kt +++ b/game/src/main/kotlin/content/skill/constitution/drink/DungeoneeringPotions.kt @@ -18,7 +18,7 @@ class DungeoneeringPotions : Script { levels.boost(Skill.Magic, 2, 0.07) } - consumed("weak_range_potion") { _, _ -> + consumed("weak_ranged_potion") { _, _ -> levels.boost(Skill.Ranged, 2, 0.07) } diff --git a/game/src/main/kotlin/content/skill/constitution/drink/Juju.kt b/game/src/main/kotlin/content/skill/constitution/drink/Juju.kt new file mode 100644 index 0000000000..44243e338b --- /dev/null +++ b/game/src/main/kotlin/content/skill/constitution/drink/Juju.kt @@ -0,0 +1,100 @@ +package content.skill.constitution.drink + +import content.entity.player.bank.bank +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.timer.Timer +import world.gregs.voidps.type.random + +/** + * Herblore Habitat potions all last five minutes. The mining and woodcutting potions open a + * banking window rather than sending every resource to the bank, though the roll is repeated on + * each resource gathered, so in practice the window is usually refreshed before it closes. + */ +private const val INTERVAL = 25 +private const val DURATION = 500 / INTERVAL +private const val BANK_DURATION = 75 / INTERVAL +private const val BANK_PERCENT = 11 + +const val JUJU_HERB_ONE_IN = 3 +const val JUJU_SHARK_PERCENT = 30 + +private val EFFECTS = listOf( + "juju_mining", + "juju_woodcutting", + "juju_farming", + "juju_fishing", + "scentless", + "saradomins_blessing", + "guthixs_gift", + "zamoraks_favour", +) + +private val WINDOWS = listOf("juju_mining_bank", "juju_woodcutting_bank") + +fun Player.startJuju(effect: String) { + set(effect, DURATION) + timers.restart(effect) +} + +fun Player.jujuActive(effect: String): Boolean = timers.contains(effect) + +/** + * Rolls for the banking window on every resource gathered, then banks [amount] of [item] while + * that window is open. Returns false when the bank cannot take them, closing the window so the + * caller keeps its normal inventory handling rather than destroying the resource. + */ +fun Player.jujuBank(effect: String, item: String, amount: Int): Boolean { + if (jujuActive(effect) && random.nextInt(100) < BANK_PERCENT) { + set("${effect}_bank", BANK_DURATION) + timers.restart("${effect}_bank") + } + if (!jujuActive("${effect}_bank")) { + return false + } + if (!bank.add(item, amount)) { + timers.stop("${effect}_bank") + message("Your bank is too full to send anything else to it.") + return false + } + gfx("${effect}_bank") + return true +} + +class Juju : Script { + + init { + playerSpawn { + for (timer in EFFECTS) { + if (get(timer, 0) > 0) { + timers.restart(timer) + } + } + } + + playerDeath { + for (timer in EFFECTS + WINDOWS) { + clear(timer) + } + } + + for (timer in EFFECTS + WINDOWS) { + timerStart(timer) { INTERVAL } + timerTick(timer) { + if (dec(timer) <= 0) Timer.CANCEL else Timer.CONTINUE + } + timerStop(timer) { logout -> + if (!logout) { + clear(timer) + } + } + } + + consumable("juju_hunter_potion*") { + message("You decide that only plants are likely to enjoy the taste of this potion.") + false + } + } +} diff --git a/game/src/main/kotlin/content/skill/constitution/drink/Overload.kt b/game/src/main/kotlin/content/skill/constitution/drink/Overload.kt index 1e896b4585..216536cb0f 100644 --- a/game/src/main/kotlin/content/skill/constitution/drink/Overload.kt +++ b/game/src/main/kotlin/content/skill/constitution/drink/Overload.kt @@ -28,6 +28,10 @@ class Overload : Script { } } + playerDeath { + set("overload_refreshes_remaining", 0) + } + timerStart("overload", ::start) timerTick("overload", ::tick) timerStop("overload", ::stop) @@ -71,7 +75,7 @@ class Overload : Script { repeat(5) { player.directHit(100) player.anim("overload") - player.gfx("electric_shock") + player.gfx("overload") pause(2) } } @@ -99,11 +103,9 @@ class Overload : Script { } fun applyBoost(player: Player) { - player.levels.boost(Skill.Attack, 5, 0.22) - player.levels.boost(Skill.Strength, 5, 0.22) - player.levels.boost(Skill.Defence, 5, 0.22) - player.levels.boost(Skill.Magic, 7) - player.levels.boost(Skill.Ranged, 4, 0.1923) + for (skill in skills) { + player.extremeBoost(skill) + } } fun removeBoost(player: Player) { diff --git a/game/src/main/kotlin/content/skill/constitution/drink/PoisonChalice.kt b/game/src/main/kotlin/content/skill/constitution/drink/PoisonChalice.kt new file mode 100644 index 0000000000..a401308768 --- /dev/null +++ b/game/src/main/kotlin/content/skill/constitution/drink/PoisonChalice.kt @@ -0,0 +1,67 @@ +package content.skill.constitution.drink + +import content.entity.combat.hit.directHit +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.chat.ChatType +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.type.random + +class PoisonChalice : Script { + + init { + consumed("poison_chalice") { _, _ -> + when (random.nextInt(7)) { + 0 -> message("It has a slight taste of apricot.", ChatType.Filter) + 1 -> { + val amount = random.nextInt(1, 5) + levels.drain(Skill.Attack, amount) + levels.drain(Skill.Strength, amount) + levels.drain(Skill.Defence, amount) + levels.boost(Skill.Crafting, 1) + message("You feel a little better.", ChatType.Filter) + } + 2 -> { + levels.drain(Skill.Attack, 1) + levels.drain(Skill.Strength, 1) + levels.drain(Skill.Defence, 1) + levels.boost(Skill.Thieving, 1) + message("You feel a little strange.", ChatType.Filter) + } + 3 -> { + levels.restore(Skill.Constitution, heal(this, 10, 0.07)) + message("It heals some health.", ChatType.Filter) + } + 4 -> { + levels.restore(Skill.Constitution, heal(this, 20, 0.14)) + levels.boost(Skill.Thieving, 1) + message("You feel a lot better!", ChatType.Filter) + } + 5 -> { + levels.boost(Skill.Attack, 4) + levels.boost(Skill.Strength, 4) + levels.boost(Skill.Defence, 4) + levels.boost(Skill.Thieving, 1) + message("Wow! That was amazing! You feel really invigorated.", ChatType.Filter) + } + else -> { + val amount = random.nextInt(1, 3) + levels.boost(Skill.Attack, amount) + levels.boost(Skill.Strength, amount) + levels.boost(Skill.Defence, amount) + message("That tasted a bit dodgy. You feel a bit ill.", ChatType.Filter) + directHit(random.nextInt(1, 50)) + } + } + } + } + + /** + * Scales with the drinker's maximum life points, which are ten times their level. + */ + private fun heal(player: Player, base: Int, factor: Double): Int { + val maximum = player.levels.getMax(Skill.Constitution) + return base + ((maximum - 100) * factor).toInt() + } +} diff --git a/game/src/main/kotlin/content/skill/constitution/drink/PotionEffects.kt b/game/src/main/kotlin/content/skill/constitution/drink/PotionEffects.kt index 4b71a63eeb..50d2995560 100644 --- a/game/src/main/kotlin/content/skill/constitution/drink/PotionEffects.kt +++ b/game/src/main/kotlin/content/skill/constitution/drink/PotionEffects.kt @@ -4,9 +4,12 @@ import content.entity.combat.hit.directHit import content.entity.effect.toxin.antiDisease import content.entity.effect.toxin.antiPoison import content.entity.effect.toxin.cureDisease +import content.entity.effect.toxin.poisonDamage +import content.entity.effect.toxin.poisoned import content.entity.player.combat.special.MAX_SPECIAL_ATTACK import content.entity.player.combat.special.specialAttackEnergy import content.entity.player.effect.antifire +import content.entity.player.effect.energy.MAX_RUN_ENERGY import content.entity.player.effect.energy.runEnergy import content.entity.player.effect.superAntifire import world.gregs.voidps.engine.client.message @@ -20,45 +23,83 @@ import java.util.concurrent.TimeUnit private fun Player.hasHolyItem() = equipped(EquipSlot.Cape).id.startsWith("prayer_cape") || carriesItem("holy_wrench") -fun Player.potionEffects(potion: String) { - when { - potion.startsWith("antifire") -> antifire(6) - potion.startsWith("super_antifire") -> superAntifire(6) - potion.startsWith("overload") -> { +/** + * Overload applies the boost of all five extreme potions at once, so both share this definition. + */ +fun Player.extremeBoost(skill: Skill) { + when (skill) { + Skill.Magic -> levels.boost(Skill.Magic, 7) + Skill.Ranged -> levels.boost(Skill.Ranged, 4, 0.1923) + else -> levels.boost(skill, 5, 0.22) + } +} + +private fun Player.restoreAllSkills() { + for (skill in Skill.all) { + if (skill == Skill.Constitution) { + continue + } + levels.restore(skill, 8, if (skill == Skill.Prayer && hasHolyItem()) 0.27 else 0.25) + } +} + +private fun MutableMap Unit>.effect(vararg names: String, effect: Player.() -> Unit) { + for (name in names) { + require(put(name, effect) == null) { "Duplicate potion effect '$name'." } + } +} + +/** + * Every dosed potion, keyed by its item id with the dose suffix removed. Membership is what marks + * an item as a potion, so a potion with no effect yet is still registered with an empty one. + */ +object PotionEffects { + val effects: Map Unit> = buildMap { + effect("antifire", "antifire_mix") { antifire(6) } + effect("super_antifire") { superAntifire(6) } + effect("overload") { set("overload_refreshes_remaining", 20) timers.start("overload") } - potion.startsWith("attack_") -> levels.boost(Skill.Attack, 3, 0.1) - potion.startsWith("strength_") -> levels.boost(Skill.Strength, 3, 0.1) - potion.startsWith("defence_") -> levels.boost(Skill.Defence, 3, 0.1) - potion.startsWith("magic_essence") -> levels.boost(Skill.Magic, 3) - potion.startsWith("agility_") -> levels.boost(Skill.Agility, 3) - potion.startsWith("fishing_") -> levels.boost(Skill.Fishing, 3) - potion.startsWith("crafting_") -> levels.boost(Skill.Crafting, 3) - potion.startsWith("hunter_potion") || potion.startsWith("hunting_mix") -> levels.boost(Skill.Hunter, 3) - potion.startsWith("fletching_") -> levels.boost(Skill.Fletching, 3) - potion.startsWith("super_attack") -> levels.boost(Skill.Attack, 5, 0.15) - potion.startsWith("super_strength") -> levels.boost(Skill.Strength, 5, 0.15) - potion.startsWith("super_defence") -> levels.boost(Skill.Defence, 5, 0.15) - potion.startsWith("super_magic_") -> levels.boost(Skill.Magic, 5, 0.15) - potion.startsWith("super_ranging_") -> levels.boost(Skill.Ranged, 4, 0.10) - potion.startsWith("combat_") -> { + effect("extreme_attack") { extremeBoost(Skill.Attack) } + effect("extreme_strength") { extremeBoost(Skill.Strength) } + effect("extreme_defence") { extremeBoost(Skill.Defence) } + effect("extreme_magic") { extremeBoost(Skill.Magic) } + effect("extreme_ranging") { extremeBoost(Skill.Ranged) } + effect("attack_potion", "attack_mix") { levels.boost(Skill.Attack, 3, 0.1) } + effect("strength_potion", "strength_mix") { levels.boost(Skill.Strength, 3, 0.1) } + effect("defence_potion", "defence_mix", "defence_potion_stealing_creation") { levels.boost(Skill.Defence, 3, 0.1) } + effect("magic_essence", "magic_essence_mix") { levels.boost(Skill.Magic, 3) } + effect("agility_potion", "agility_mix") { levels.boost(Skill.Agility, 3) } + effect("fishing_potion", "fishing_mix") { levels.boost(Skill.Fishing, 3) } + effect("crafting_potion") { levels.boost(Skill.Crafting, 3) } + effect("hunter_potion", "hunting_mix") { levels.boost(Skill.Hunter, 3) } + effect("fletching_potion") { levels.boost(Skill.Fletching, 3) } + effect("super_attack", "super_attack_mix", "super_attack_potion_stealing_creation", "cw_super_attack_potion") { levels.boost(Skill.Attack, 5, 0.15) } + effect("super_strength", "super_strength_mix", "super_strength_potion_stealing_creation", "cw_super_strength_potion") { levels.boost(Skill.Strength, 5, 0.15) } + effect("super_defence", "super_defence_mix", "cw_super_defence_potion") { levels.boost(Skill.Defence, 5, 0.15) } + effect("super_magic_potion", "super_magic_mix", "magic_potion_stealing_creation", "cw_super_magic_potion") { levels.boost(Skill.Magic, 5, 0.15) } + effect("super_ranging_potion", "super_ranging_mix", "ranging_potion_stealing_creation", "cw_super_ranging_potion") { levels.boost(Skill.Ranged, 4, 0.10) } + effect("combat_potion", "combat_mix") { levels.boost(Skill.Attack, 3, 0.1) levels.boost(Skill.Strength, 3, 0.1) } - potion.startsWith("summoning_") -> { - levels.boost(Skill.Summoning, 7, 0.25) - // familiar.specialEnergy = (familiar.specialEnergy / 100) * 15 - } - potion.startsWith("relicyms_") -> cureDisease() - potion.startsWith("sanfew_serum") -> { + effect("summoning_potion", "summoning_potion_stealing_creation") { levels.boost(Skill.Summoning, 7, 0.25) } + effect("juju_mining_potion") { startJuju("juju_mining") } + effect("juju_woodcutting_potion") { startJuju("juju_woodcutting") } + effect("juju_farming_potion") { startJuju("juju_farming") } + effect("juju_fishing_potion") { startJuju("juju_fishing") } + effect("scentless_potion") { startJuju("scentless") } + effect("saradomins_blessing") { startJuju("saradomins_blessing") } + effect("guthixs_gift") { startJuju("guthixs_gift") } + effect("zamoraks_favour") { startJuju("zamoraks_favour") } + effect("relicyms_balm", "relicyms_mix") { cureDisease() } + effect("sanfew_serum") { antiPoison(6) antiDisease(15) - Skill.all.filterNot { it == Skill.Constitution }.forEach { skill -> - levels.restore(skill, 8, if (skill == Skill.Prayer && hasHolyItem()) 0.27 else 0.25) - } + restoreAllSkills() } - potion.startsWith("zamorak_") -> { + effect("zamorak_brew", "zamorak_mix") { levels.boost(Skill.Attack, 2, 0.2) levels.boost(Skill.Strength, 2, 0.12) levels.drain(Skill.Defence, 2, 0.1) @@ -66,7 +107,7 @@ fun Player.potionEffects(potion: String) { val damage = ((health / 100) * 10) + 20 directHit(damage) } - potion.startsWith("saradomin_brew") -> { + effect("saradomin_brew") { levels.boost(Skill.Constitution, 20, 0.15) levels.boost(Skill.Defence, 2, 0.2) levels.drain(Skill.Attack, 2, 0.1) @@ -74,31 +115,47 @@ fun Player.potionEffects(potion: String) { levels.drain(Skill.Magic, 2, 0.1) levels.drain(Skill.Ranged, 2, 0.1) } - potion.startsWith("prayer_") -> levels.restore(Skill.Prayer, 7, if (hasHolyItem()) 0.27 else 0.25) - potion.startsWith("antipoison") -> antiPoison(90, TimeUnit.SECONDS) - potion.startsWith("super_antipoison") -> antiPoison(6) - potion.startsWith("antipoison++") -> antiPoison(12) - potion.startsWith("antipoison+") -> antiPoison(9) - potion.startsWith("restore_") -> { + effect("prayer_potion", "prayer_mix", "prayer_potion_stealing_creation") { levels.restore(Skill.Prayer, 7, if (hasHolyItem()) 0.27 else 0.25) } + effect("super_prayer") { levels.restore(Skill.Prayer, 7, if (hasHolyItem()) 0.37 else 0.35) } + effect("antipoison", "antipoison_mix") { antiPoison(90, TimeUnit.SECONDS) } + effect("super_antipoison", "super_antipoison_mix") { antiPoison(6) } + effect("antipoison+", "antidote+_mix") { antiPoison(9) } + effect("antipoison++") { antiPoison(12) } + effect("restore_potion", "restore_mix") { levels.restore(Skill.Attack, 10, 0.3) levels.restore(Skill.Strength, 10, 0.3) levels.restore(Skill.Defence, 10, 0.3) levels.restore(Skill.Magic, 10, 0.3) levels.restore(Skill.Ranged, 10, 0.3) } - potion.startsWith("super_restore") -> { - Skill.all.filterNot { it == Skill.Constitution }.forEach { skill -> - levels.restore(skill, 8, if (skill == Skill.Prayer && hasHolyItem()) 0.27 else 0.25) - } - } - potion.startsWith("energy_") -> runEnergy = (runEnergy / 100) * 10 - potion.startsWith("super_energy_") -> runEnergy = (runEnergy / 100) * 20 - potion.startsWith("recover_special") -> { + effect("super_restore", "super_restore_mix") { restoreAllSkills() } + effect("energy_potion", "energy_mix", "energy_potion_stealing_creation") { runEnergy += (MAX_RUN_ENERGY / 100) * 10 } + effect("super_energy", "super_energy_mix", "cw_super_energy_potion") { runEnergy += (MAX_RUN_ENERGY / 100) * 20 } + effect("recover_special") { specialAttackEnergy = (specialAttackEnergy + (MAX_SPECIAL_ATTACK / 4)).coerceAtMost(MAX_SPECIAL_ATTACK) val percentage = ((specialAttackEnergy / MAX_SPECIAL_ATTACK.toDouble()) * 100).toInt() message("Your special attack energy is now $percentage%.") set("recover_special_delay", TimeUnit.SECONDS.toTicks(30) / 10) softTimers.start("recover_special") } + // Drinkable, but with no effect of their own. + effect("guthix_rest") { + if (poisoned) { + poisonDamage -= 10 + } + runEnergy += (MAX_RUN_ENERGY / 100) * 5 + levels.boost(Skill.Constitution, 50, maximum = 50) + } + // Registered so a dose still reports itself, but their effects are not implemented. + effect("juju_cooking_potion", "juju_hunter_potion") { } + // Used on something else rather than drunk, so drinking them does nothing. + effect("compost_potion", "guthix_balance", "serum_207", "serum_208", "olive_oil", "sacred_oil") { } } } + +/** + * Applies the effect of drinking one dose of [potion], which may be any dose of the item id. + */ +fun Player.potionEffects(potion: String) { + PotionEffects.effects[potion.substringBeforeLast('_')]?.invoke(this) +} diff --git a/game/src/main/kotlin/content/skill/constitution/drink/Potions.kt b/game/src/main/kotlin/content/skill/constitution/drink/Potions.kt index 28327517b7..10564926bc 100644 --- a/game/src/main/kotlin/content/skill/constitution/drink/Potions.kt +++ b/game/src/main/kotlin/content/skill/constitution/drink/Potions.kt @@ -3,26 +3,26 @@ package content.skill.constitution.drink import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.client.ui.chat.plural -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.engine.inv.remove class Potions : Script { init { - consumed("*") { item, slot -> - if (!item.id.endsWith("_1") && !item.id.endsWith("_2") && !item.id.endsWith("_3") && !item.id.endsWith("_4")) { + consumed("*") { item, _ -> + val separator = item.id.lastIndexOf('_') + if (separator == -1) { return@consumed } - val doses = item.id.last().digitToInt() - if (doses != 1) { - message("You have ${doses - 1} ${"dose".plural(doses - 1)} of the potion left.") - potionEffects(item.id) + val doses = item.id.substring(separator + 1).toIntOrNull() + if (doses == null || doses !in 1..5) { + return@consumed + } + if (!PotionEffects.effects.containsKey(item.id.substring(0, separator))) { return@consumed } - message("You have finished your potion.") - if (contains("smash_vials")) { - inventory.remove(slot, item.id) - message("You quickly smash the empty vial using the tick a Barbarian taught you.") + if (doses > 1) { + message("You have ${doses - 1} ${"dose".plural(doses - 1)} of the potion left.") + } else { + message("You have finished your potion.") } potionEffects(item.id) } diff --git a/game/src/main/kotlin/content/skill/constitution/drink/Tea.kt b/game/src/main/kotlin/content/skill/constitution/drink/Tea.kt index 916e678df9..cef2a86394 100644 --- a/game/src/main/kotlin/content/skill/constitution/drink/Tea.kt +++ b/game/src/main/kotlin/content/skill/constitution/drink/Tea.kt @@ -1,7 +1,5 @@ package content.skill.constitution.drink -import content.entity.effect.toxin.poisonDamage -import content.entity.effect.toxin.poisoned import content.entity.player.dialogue.type.item import content.entity.player.effect.energy.MAX_RUN_ENERGY import content.entity.player.effect.energy.runEnergy @@ -22,16 +20,8 @@ class Tea : Script { levels.boost(Skill.Attack, 3) } - consumed("guthix_rest_4,guthix_rest_3,guthix_rest_2,guthix_rest_1") { _, _ -> - if (poisoned) { - poisonDamage -= 10 - } - runEnergy += (MAX_RUN_ENERGY / 100) * 5 - levels.boost(Skill.Constitution, 50, maximum = 50) - } - consumed("nettle_tea") { _, _ -> - runEnergy = (MAX_RUN_ENERGY / 100) * 5 + runEnergy += (MAX_RUN_ENERGY / 100) * 5 } itemOption("Look-in", "tea_flask") { diff --git a/game/src/main/kotlin/content/skill/farming/FarmingPatchPick.kt b/game/src/main/kotlin/content/skill/farming/FarmingPatchPick.kt index 7f8624f0d1..23cc5bb2c2 100644 --- a/game/src/main/kotlin/content/skill/farming/FarmingPatchPick.kt +++ b/game/src/main/kotlin/content/skill/farming/FarmingPatchPick.kt @@ -1,5 +1,7 @@ package content.skill.farming +import content.skill.constitution.drink.JUJU_HERB_ONE_IN +import content.skill.constitution.drink.jujuActive import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.client.ui.chat.an @@ -20,6 +22,7 @@ import world.gregs.voidps.engine.inv.add import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.queue.weakQueue +import world.gregs.voidps.type.random class FarmingPatchPick : Script { @@ -92,6 +95,9 @@ class FarmingPatchPick : Script { message("You have run out of inventory space.", ChatType.Filter) return@weakQueue } + if (jujuActive("juju_farming") && obj.id.startsWith("farming_herb_patch") && random.nextInt(JUJU_HERB_ONE_IN) == 0) { + inventory.add(item.id) + } if (tree) { message("You pick ${item.id.an()} ${item.def.name.lowercase()}.", ChatType.Filter) } diff --git a/game/src/main/kotlin/content/skill/fishing/Fishing.kt b/game/src/main/kotlin/content/skill/fishing/Fishing.kt index 1618d423e3..247cbc8343 100644 --- a/game/src/main/kotlin/content/skill/fishing/Fishing.kt +++ b/game/src/main/kotlin/content/skill/fishing/Fishing.kt @@ -1,6 +1,8 @@ package content.skill.fishing import com.github.michaelbull.logging.InlineLogger +import content.skill.constitution.drink.JUJU_SHARK_PERCENT +import content.skill.constitution.drink.jujuActive import content.skill.summoning.familiarActsAsHarpoon import content.skill.summoning.familiarBoost import net.pearx.kasechange.toLowerSpaceCase @@ -147,6 +149,9 @@ class Fishing : Script { if (bigCatch(fish)) { fish = fish.replace("raw_", "big_") message = "You catch an enormous ${catch.toLowerSpaceCase()}!" + } else if (fish == "raw_shark" && player.jujuActive("juju_fishing") && random.nextInt(100) < JUJU_SHARK_PERCENT) { + fish = "raw_baron_shark" + message = "You catch a raw baron shark!" } player.inventory.add(fish) when (player.inventory.transaction.error) { diff --git a/game/src/main/kotlin/content/skill/mining/Mining.kt b/game/src/main/kotlin/content/skill/mining/Mining.kt index a208b86b67..59c35b9ff0 100644 --- a/game/src/main/kotlin/content/skill/mining/Mining.kt +++ b/game/src/main/kotlin/content/skill/mining/Mining.kt @@ -4,6 +4,7 @@ import content.activity.shooting_star.ShootingStarHandler import content.entity.player.bank.bank import content.entity.player.bank.ownsItem import content.quest.questCompleted +import content.skill.constitution.drink.jujuBank import content.skill.summoning.familiarBoost import net.pearx.kasechange.toLowerSpaceCase import world.gregs.voidps.engine.Script @@ -177,11 +178,13 @@ class Mining : Script { "mineral_deposit_coal" -> random.nextInt(1..2) else -> 1 } - val added = player.inventory.addToLimit(ore, amount) + val banked = player.jujuBank("juju_mining", ore, amount) + val added = if (banked) amount else player.inventory.addToLimit(ore, amount) when (added) { 1 -> player.message("You manage to mine some ${ore.toLowerSpaceCase()}.") 2 -> player.message("You manage to mine two ${ore.toLowerSpaceCase().plural(added)}!") 3 -> player.message("You manage to mine three ${ore.toLowerSpaceCase().plural(added)}!") + 4 -> player.message("You manage to mine four ${ore.toLowerSpaceCase().plural(added)}!") else -> player.inventoryFull() } if (diaryDoubleOre(player, ore)) { diff --git a/game/src/main/kotlin/content/skill/woodcutting/Woodcutting.kt b/game/src/main/kotlin/content/skill/woodcutting/Woodcutting.kt index aabaf76224..073ba126f9 100644 --- a/game/src/main/kotlin/content/skill/woodcutting/Woodcutting.kt +++ b/game/src/main/kotlin/content/skill/woodcutting/Woodcutting.kt @@ -1,5 +1,6 @@ package content.skill.woodcutting +import content.skill.constitution.drink.jujuBank import content.skill.summoning.familiarBoost import net.pearx.kasechange.toLowerSpaceCase import world.gregs.voidps.engine.Script @@ -144,6 +145,10 @@ class Woodcutting(val drops: DropTables) : Script { if (log == "poison_ivy_berries") { return true } + if (player.jujuBank("juju_woodcutting", log, 1)) { + player.message("You get some ${log.toLowerSpaceCase()}.") + return true + } val added = player.inventory.add(log) if (added) { player.message("You get some ${log.toLowerSpaceCase()}.") diff --git a/game/src/test/kotlin/content/skill/constitution/ConsumableItemsTest.kt b/game/src/test/kotlin/content/skill/constitution/ConsumableItemsTest.kt new file mode 100644 index 0000000000..46959ae4d9 --- /dev/null +++ b/game/src/test/kotlin/content/skill/constitution/ConsumableItemsTest.kt @@ -0,0 +1,75 @@ +package content.skill.constitution + +import WorldTest +import content.skill.constitution.drink.PotionEffects +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import world.gregs.voidps.engine.data.definition.ItemDefinitions + +internal class ConsumableItemsTest : WorldTest() { + + /** + * `excess` is the item left after a dose is drunk and `empty` is the container it came in, so + * both have to name a real item or the consumable silently breaks. + */ + @Test + fun `Every excess and empty item exists`() { + val missing = mutableListOf() + for (definition in ItemDefinitions.definitions) { + for (param in listOf("excess", "empty")) { + val target: String = definition.getOrNull(param) ?: continue + if (!ItemDefinitions.ids.containsKey(target)) { + missing.add("${definition.stringId} $param = \"$target\"") + } + } + } + assertEquals(emptyList(), missing) + } + + /** + * A dosed potion is recognised by its base name, so one missing from the registry is drunk + * silently and one in the registry that no item matches is a typo. + */ + @Test + fun `Every dosed potion is registered and every registered potion exists`() { + val bases = mutableSetOf() + for (definition in ItemDefinitions.definitions) { + val id = definition.stringId + if (id.endsWith("_noted") || id.length < 3 || id[id.length - 2] != '_') { + continue + } + if (id.last() !in '1'..'5') { + continue + } + if (!definition.contains("heals") && !definition.contains("excess")) { + continue + } + bases.add(id.substringBeforeLast('_')) + } + + assertEquals(emptySet(), bases - NOT_POTIONS - PotionEffects.effects.keys, "dosed potions with no effect entry") + val unmatched = PotionEffects.effects.keys.filter { base -> + (1..5).none { ItemDefinitions.ids.containsKey("${base}_$it") } + } + assertEquals(emptyList(), unmatched, "effect entries matching no item") + } + + private companion object { + /** + * Items shaped like a dosed potion without being one: food and cocktails that come in + * portions, and a second copy of an item whose name happens to end in a digit. + */ + private val NOT_POTIONS = setOf( + "blurberry_special", + "cooked_crab_meat", + "cooked_karambwan", + "drunk_dragon", + "easter_egg", + "food_class", + "fruit_blast", + "jug_of_bad_wine", + "pineapple_punch", + "short_green_guy", + ) + } +} diff --git a/game/src/test/kotlin/content/skill/constitution/drink/AntifireTest.kt b/game/src/test/kotlin/content/skill/constitution/drink/AntifireTest.kt new file mode 100644 index 0000000000..50a11ef461 --- /dev/null +++ b/game/src/test/kotlin/content/skill/constitution/drink/AntifireTest.kt @@ -0,0 +1,64 @@ +package content.skill.constitution.drink + +import WorldTest +import content.entity.player.effect.antifire +import content.entity.player.effect.superAntifire +import itemOption +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory + +internal class AntifireTest : WorldTest() { + + @Test + fun `Antifire potion wears off`() { + val player = createPlayer(emptyTile) + player.inventory.add("antifire_4") + + player.itemOption("Drink", "antifire_4") + assertTrue(player.antifire) + + tick(601) + + assertFalse(player.antifire) + } + + @Test + fun `Super antifire wearing off leaves antifire running`() { + val player = createPlayer(emptyTile) + player.antifire(6) + player.superAntifire(1) + + tick(150) + + assertFalse(player.superAntifire) + assertTrue(player.antifire) + } + + @Test + fun `Dying clears dragonfire resistance`() { + val player = createPlayer(emptyTile) + player.antifire(6) + player.superAntifire(6) + + player.levels.set(Skill.Constitution, 0) + tick(12) + + assertFalse(player.antifire) + assertFalse(player.superAntifire) + } + + @Test + fun `Dying clears an overload`() { + val player = createPlayer(emptyTile) + player["overload_refreshes_remaining"] = 20 + + player.levels.set(Skill.Constitution, 0) + tick(12) + + assertFalse(player.get("overload_refreshes_remaining", 0) > 0) + } +} diff --git a/game/src/test/kotlin/content/skill/constitution/drink/JujuTest.kt b/game/src/test/kotlin/content/skill/constitution/drink/JujuTest.kt new file mode 100644 index 0000000000..841b3e90ef --- /dev/null +++ b/game/src/test/kotlin/content/skill/constitution/drink/JujuTest.kt @@ -0,0 +1,193 @@ +package content.skill.constitution.drink + +import FakeRandom +import WorldTest +import content.entity.player.bank.bank +import itemOption +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import world.gregs.voidps.engine.data.config.VariableDefinition.Companion.persist +import world.gregs.voidps.engine.data.definition.InventoryDefinitions +import world.gregs.voidps.engine.data.definition.VariableDefinitions +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.Level +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.type.setRandom + +internal class JujuTest : WorldTest() { + + @Test + fun `Juju mining potion starts a five minute effect`() { + val player = createPlayer(emptyTile) + player.inventory.add("juju_mining_potion_4") + + player.itemOption("Drink", "juju_mining_potion_4") + + assertTrue(player.inventory.contains("juju_mining_potion_3")) + assertTrue(player.jujuActive("juju_mining")) + } + + @Test + fun `Last dose of a juju potion leaves a juju vial`() { + val player = createPlayer(emptyTile) + player.inventory.add("juju_woodcutting_potion_1") + + player.itemOption("Drink", "juju_woodcutting_potion_1") + + assertTrue(player.inventory.contains("juju_vial")) + assertTrue(player.jujuActive("juju_woodcutting")) + } + + @Test + fun `Juju hunter potion is not drinkable`() { + val player = createPlayer(emptyTile) + player.inventory.add("juju_hunter_potion_4") + + player.itemOption("Drink", "juju_hunter_potion_4") + + assertTrue(player.inventory.contains("juju_hunter_potion_4")) + assertFalse(player.jujuActive("juju_hunter")) + } + + @Test + fun `God blessings start their own effects`() { + for (blessing in listOf("saradomins_blessing", "guthixs_gift", "zamoraks_favour")) { + val player = createPlayer(emptyTile) + player.inventory.add("${blessing}_4") + + player.itemOption("Drink", "${blessing}_4") + + assertTrue(player.inventory.contains("${blessing}_3"), blessing) + assertTrue(player.jujuActive(blessing), blessing) + } + } + + @Test + fun `Juju mining potion sends ore to the bank`() { + setRandom( + object : FakeRandom() { + override fun nextInt(from: Int, until: Int): Int = if (until == 100) 0 else from + }, + ) + val player = createPlayer(emptyTile) + player.experience.set(Skill.Mining, Level.experience(99)) + player.levels.set(Skill.Mining, 99) + player.startJuju("juju_mining") + + val banked = player.jujuBank("juju_mining", "copper_ore", 1) + + assertTrue(banked) + assertEquals(1, player.bank.count("copper_ore")) + assertFalse(player.inventory.contains("copper_ore")) + } + + @Test + fun `Ore is not banked without the potion`() { + setRandom( + object : FakeRandom() { + override fun nextInt(from: Int, until: Int): Int = if (until == 100) 0 else from + }, + ) + val player = createPlayer(emptyTile) + + val banked = player.jujuBank("juju_mining", "copper_ore", 1) + + assertFalse(banked) + assertEquals(0, player.bank.count("copper_ore")) + } + + @Test + fun `Ore is kept when the bank is full`() { + setRandom( + object : FakeRandom() { + override fun nextInt(from: Int, until: Int): Int = if (until == 100) 0 else from + }, + ) + val player = createPlayer(emptyTile) + player.startJuju("juju_mining") + fillBank(player) + + val banked = player.jujuBank("juju_mining", "copper_ore", 1) + + assertFalse(banked) + assertFalse(player.bank.contains("copper_ore")) + } + + @Test + fun `Logs are kept when the bank is full`() { + setRandom( + object : FakeRandom() { + override fun nextInt(from: Int, until: Int): Int = if (until == 100) 0 else from + }, + ) + val player = createPlayer(emptyTile) + player.startJuju("juju_woodcutting") + fillBank(player) + + val banked = player.jujuBank("juju_woodcutting", "logs", 1) + + assertFalse(banked) + assertFalse(player.bank.contains("logs")) + } + + @Test + fun `Juju effects persist across logout`() { + for (key in listOf( + "juju_mining", + "juju_woodcutting", + "juju_farming", + "juju_fishing", + "scentless", + "saradomins_blessing", + "guthixs_gift", + "zamoraks_favour", + )) { + assertTrue(VariableDefinitions.get(key).persist, key) + } + } + + @Test + fun `Banking windows do not persist across logout`() { + for (key in listOf("juju_mining_bank", "juju_woodcutting_bank")) { + assertFalse(VariableDefinitions.get(key).persist, key) + } + } + + @Test + fun `A full bank closes the banking window`() { + setRandom( + object : FakeRandom() { + override fun nextInt(from: Int, until: Int): Int = if (until == 100) 0 else from + }, + ) + val player = createPlayer(emptyTile) + player.startJuju("juju_mining") + fillBank(player) + + player.jujuBank("juju_mining", "copper_ore", 1) + + assertFalse(player.jujuActive("juju_mining_bank")) + } + + @Test + fun `Dying clears juju effects`() { + val player = createPlayer(emptyTile) + player.startJuju("juju_mining") + assertTrue(player.jujuActive("juju_mining")) + + player.levels.set(Skill.Constitution, 0) + tick(12) + + assertEquals(0, player["juju_mining", 0]) + } + + private fun fillBank(player: Player) { + for (index in 0 until InventoryDefinitions.get("bank").length) { + player.bank.set(index, "coins", 1) + } + } +} diff --git a/game/src/test/kotlin/content/skill/constitution/drink/OverloadTest.kt b/game/src/test/kotlin/content/skill/constitution/drink/OverloadTest.kt new file mode 100644 index 0000000000..74c4e17196 --- /dev/null +++ b/game/src/test/kotlin/content/skill/constitution/drink/OverloadTest.kt @@ -0,0 +1,44 @@ +package content.skill.constitution.drink + +import WorldTest +import itemOption +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.data.definition.AnimationDefinitions +import world.gregs.voidps.engine.data.definition.GraphicDefinitions +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.level.Level +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory + +internal class OverloadTest : WorldTest() { + + @Test + fun `Overload plays the shock animation for every hit`() { + val player = createPlayer(emptyTile) + player.inventory.add("overload_4") + player.experience.set(Skill.Constitution, Level.experience(99)) + player.levels.set(Skill.Constitution, 990) + val animation = AnimationDefinitions.get("overload").id + val graphic = GraphicDefinitions.get("overload").id + + player.itemOption("Drink", "overload_4") + + var animations = 0 + var graphics = 0 + repeat(12) { + if (player.visuals.animation.stand == animation) { + animations++ + } + if (player.visuals.secondaryGraphic.id == graphic || player.visuals.primaryGraphic.id == graphic) { + graphics++ + } + tick() + } + + assertEquals(5, animations) + assertEquals(5, graphics) + assertTrue(player.levels.get(Skill.Constitution) in 490..495) + } +} diff --git a/game/src/test/kotlin/content/skill/constitution/drink/PoisonChaliceTest.kt b/game/src/test/kotlin/content/skill/constitution/drink/PoisonChaliceTest.kt new file mode 100644 index 0000000000..7b62183a86 --- /dev/null +++ b/game/src/test/kotlin/content/skill/constitution/drink/PoisonChaliceTest.kt @@ -0,0 +1,86 @@ +package content.skill.constitution.drink + +import FakeRandom +import WorldTest +import itemOption +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.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.level.Level +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.type.setRandom + +internal class PoisonChaliceTest : WorldTest() { + + @Test + fun `Poison chalice can invigorate the drinker`() { + setRandom( + object : FakeRandom() { + override fun nextInt(from: Int, until: Int): Int = if (until == 7) 5 else from + }, + ) + val player = createPlayer(emptyTile) + player.inventory.add("poison_chalice") + + player.itemOption("Drink", "poison_chalice") + + assertTrue(player.inventory.contains("cocktail_glass")) + assertEquals(5, player.levels.get(Skill.Attack)) + assertEquals(5, player.levels.get(Skill.Strength)) + assertEquals(5, player.levels.get(Skill.Defence)) + assertEquals(2, player.levels.get(Skill.Thieving)) + } + + @Test + fun `Poison chalice can make the drinker ill`() { + setRandom( + object : FakeRandom() { + override fun nextInt(from: Int, until: Int): Int = when (until) { + 7 -> 6 + 50 -> 30 + else -> from + } + }, + ) + val player = createPlayer(emptyTile) + player.inventory.add("poison_chalice") + player.experience.set(Skill.Constitution, Level.experience(10)) + + player.itemOption("Drink", "poison_chalice") + + assertTrue(player.inventory.contains("cocktail_glass")) + assertEquals(2, player.levels.get(Skill.Attack)) + assertEquals(70, player.levels.get(Skill.Constitution)) + } + + @Test + fun `Poison chalice can drain the drinker`() { + setRandom( + object : FakeRandom() { + override fun nextInt(from: Int, until: Int): Int = if (until == 7) 2 else from + }, + ) + val player = createPlayer(emptyTile) + player.inventory.add("poison_chalice") + player.experience.set(Skill.Attack, Level.experience(10)) + player.levels.set(Skill.Attack, 10) + + player.itemOption("Drink", "poison_chalice") + + assertEquals(9, player.levels.get(Skill.Attack)) + assertEquals(2, player.levels.get(Skill.Thieving)) + } + + @Test + fun `Cadava potion cannot be drunk`() { + val player = createPlayer(emptyTile) + player.inventory.add("cadava_potion") + + player.itemOption("Drink", "cadava_potion") + + assertTrue(player.interfaces.contains("dialogue_obj_box")) + assertTrue(player.inventory.contains("cadava_potion")) + } +} diff --git a/game/src/test/kotlin/content/skill/constitution/drink/PotionEffectsTest.kt b/game/src/test/kotlin/content/skill/constitution/drink/PotionEffectsTest.kt new file mode 100644 index 0000000000..ee8da32fb2 --- /dev/null +++ b/game/src/test/kotlin/content/skill/constitution/drink/PotionEffectsTest.kt @@ -0,0 +1,219 @@ +package content.skill.constitution.drink + +import WorldTest +import content.entity.player.effect.energy.runEnergy +import itemOption +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.level.Level +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.timer.toTicks +import java.util.concurrent.TimeUnit + +internal class PotionEffectsTest : WorldTest() { + + @Test + fun `Energy potion adds a tenth of maximum run energy`() { + val player = createPlayer(emptyTile) + player.inventory.add("energy_potion_4") + player.runEnergy = 5000 + + player.itemOption("Drink", "energy_potion_4") + + assertTrue(player.inventory.contains("energy_potion_3")) + assertEquals(6000, player.runEnergy) + } + + @Test + fun `Super energy potion adds a fifth of maximum run energy`() { + val player = createPlayer(emptyTile) + player.inventory.add("super_energy_4") + player.runEnergy = 5000 + + player.itemOption("Drink", "super_energy_4") + + assertTrue(player.inventory.contains("super_energy_3")) + assertEquals(7000, player.runEnergy) + } + + @Test + fun `Two dose strength potion can be drunk`() { + val player = createPlayer(emptyTile) + player.inventory.add("strength_potion_2") + + player.itemOption("Drink", "strength_potion_2") + + assertTrue(player.inventory.contains("strength_potion_1")) + assertEquals(4, player.levels.get(Skill.Strength)) + } + + @Test + fun `Weak ranged potion boosts ranged`() { + val player = createPlayer(emptyTile) + player.inventory.add("weak_ranged_potion") + + player.itemOption("Drink", "weak_ranged_potion") + + assertTrue(player.inventory.contains("vial_dungeoneering")) + assertEquals(3, player.levels.get(Skill.Ranged)) + } + + @Test + fun `Antidote plus mix keeps its own dose chain`() { + val player = createPlayer(emptyTile) + player.inventory.add("antidote+_mix_2") + player.experience.set(Skill.Constitution, Level.experience(15)) + player.levels.set(Skill.Constitution, 100) + + player.itemOption("Drink", "antidote+_mix_2") + + assertTrue(player.inventory.contains("antidote+_mix_1")) + } + + @Test + fun `Each antipoison tier gives its own immunity duration`() { + val tiers = mapOf( + "antipoison_4" to TimeUnit.SECONDS.toTicks(90), + "super_antipoison_4" to TimeUnit.MINUTES.toTicks(6), + "antipoison+_4" to TimeUnit.MINUTES.toTicks(9), + "antipoison++_4" to TimeUnit.MINUTES.toTicks(12), + ) + for ((potion, ticks) in tiers) { + val player = createPlayer(emptyTile) + player.inventory.add(potion) + + player.itemOption("Drink", potion) + + assertEquals(-(ticks * 2), player["poison", 0], potion) + } + } + + @Test + fun `Antidote plus mix gives nine minutes of immunity`() { + val player = createPlayer(emptyTile) + player.inventory.add("antidote+_mix_2") + player.experience.set(Skill.Constitution, Level.experience(15)) + player.levels.set(Skill.Constitution, 100) + + player.itemOption("Drink", "antidote+_mix_2") + + assertEquals(-(TimeUnit.MINUTES.toTicks(9) * 2), player["poison", 0]) + } + + @Test + fun `Food ending in a dose suffix is not treated as a potion`() { + val player = createPlayer(emptyTile) + player.inventory.add("cooked_crab_meat_4") + player.experience.set(Skill.Constitution, Level.experience(15)) + player.levels.set(Skill.Constitution, 100) + + player.itemOption("Eat", "cooked_crab_meat_4") + + assertTrue(player.inventory.contains("cooked_crab_meat_3")) + } + + @Test + fun `Extreme attack potion boosts attack past a super attack potion`() { + val player = createPlayer(emptyTile) + player.inventory.add("extreme_attack_4") + player.experience.set(Skill.Attack, Level.experience(99)) + player.levels.set(Skill.Attack, 99) + + player.itemOption("Drink", "extreme_attack_4") + + assertTrue(player.inventory.contains("extreme_attack_3")) + assertEquals(99 + 5 + 21, player.levels.get(Skill.Attack)) + } + + @Test + fun `Overload boosts ranged by the same amount as an extreme ranging potion`() { + val drinker = createPlayer(emptyTile) + drinker.inventory.add("extreme_ranging_4") + drinker.experience.set(Skill.Ranged, Level.experience(99)) + drinker.levels.set(Skill.Ranged, 99) + + drinker.itemOption("Drink", "extreme_ranging_4") + + val overloaded = createPlayer(emptyTile) + overloaded.inventory.add("overload_4") + overloaded.experience.set(Skill.Constitution, Level.experience(99)) + overloaded.levels.set(Skill.Constitution, 990) + overloaded.experience.set(Skill.Ranged, Level.experience(99)) + overloaded.levels.set(Skill.Ranged, 99) + + overloaded.itemOption("Drink", "overload_4") + + assertEquals(drinker.levels.get(Skill.Ranged), overloaded.levels.get(Skill.Ranged)) + } + + @Test + fun `Super prayer potion restores more than a prayer potion`() { + val player = createPlayer(emptyTile) + player.inventory.add("super_prayer_4") + player.experience.set(Skill.Prayer, Level.experience(99)) + player.levels.set(Skill.Prayer, 1) + + player.itemOption("Drink", "super_prayer_4") + + assertTrue(player.inventory.contains("super_prayer_3")) + assertEquals(1 + 7 + 34, player.levels.get(Skill.Prayer)) + } + + @Test + fun `Five dose stealing creation potion can be drunk`() { + val player = createPlayer(emptyTile) + player.inventory.add("super_attack_potion_stealing_creation_5") + player.experience.set(Skill.Attack, Level.experience(99)) + player.levels.set(Skill.Attack, 99) + + player.itemOption("Drink", "super_attack_potion_stealing_creation_5") + + assertTrue(player.inventory.contains("super_attack_potion_stealing_creation_4")) + assertEquals(99 + 5 + 14, player.levels.get(Skill.Attack)) + } + + @Test + fun `Last dose of a stealing creation potion breaks`() { + val player = createPlayer(emptyTile) + player.inventory.add("prayer_potion_stealing_creation_1") + player.experience.set(Skill.Prayer, Level.experience(99)) + player.levels.set(Skill.Prayer, 1) + + player.itemOption("Drink", "prayer_potion_stealing_creation_1") + + assertFalse(player.inventory.contains("prayer_potion_stealing_creation_1")) + assertFalse(player.inventory.contains("vial")) + assertEquals(1 + 7 + 24, player.levels.get(Skill.Prayer)) + } + + @Test + fun `Last dose of a castle wars potion breaks`() { + val player = createPlayer(emptyTile) + player.inventory.add("cw_super_strength_potion_1") + player.experience.set(Skill.Strength, Level.experience(99)) + player.levels.set(Skill.Strength, 99) + + player.itemOption("Drink", "cw_super_strength_potion_1") + + assertFalse(player.inventory.contains("cw_super_strength_potion_1")) + assertFalse(player.inventory.contains("vial")) + assertEquals(99 + 5 + 14, player.levels.get(Skill.Strength)) + } + + @Test + fun `Castle wars potion boosts the same as its super potion`() { + val player = createPlayer(emptyTile) + player.inventory.add("cw_super_strength_potion_4") + player.experience.set(Skill.Strength, Level.experience(99)) + player.levels.set(Skill.Strength, 99) + + player.itemOption("Drink", "cw_super_strength_potion_4") + + assertTrue(player.inventory.contains("cw_super_strength_potion_3")) + assertEquals(99 + 5 + 14, player.levels.get(Skill.Strength)) + } +} diff --git a/game/src/test/kotlin/content/skill/constitution/drink/TeaTest.kt b/game/src/test/kotlin/content/skill/constitution/drink/TeaTest.kt index 3861007ded..b986d34892 100644 --- a/game/src/test/kotlin/content/skill/constitution/drink/TeaTest.kt +++ b/game/src/test/kotlin/content/skill/constitution/drink/TeaTest.kt @@ -67,6 +67,19 @@ internal class TeaTest : WorldTest() { assertEquals(500, player.runEnergy) } + @Test + fun `Nettle tea adds to existing energy`() { + val player = createPlayer(emptyTile) + player.inventory.add("nettle_tea") + player.experience.set(Skill.Constitution, Level.experience(15)) + player.levels.set(Skill.Constitution, 100) + player.runEnergy = 5000 + + player.itemOption("Drink", "nettle_tea") + + assertEquals(5500, player.runEnergy) + } + @Test fun `Tea flask boosts attack`() { val player = createPlayer(emptyTile) diff --git a/game/src/test/kotlin/content/skill/mining/MiningTest.kt b/game/src/test/kotlin/content/skill/mining/MiningTest.kt index 40d48c89ac..d741296ea3 100644 --- a/game/src/test/kotlin/content/skill/mining/MiningTest.kt +++ b/game/src/test/kotlin/content/skill/mining/MiningTest.kt @@ -1,7 +1,10 @@ package content.skill.mining +import FakeRandom import WorldTest +import containsMessage import objectOption +import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -31,4 +34,26 @@ internal class MiningTest : WorldTest() { assertTrue(player.experience.get(Skill.Mining) > 0) assertNotEquals(rocks.id, GameObjects.getLayer(tile, ObjectLayer.GROUND)?.id) } + + @Test + fun `Mining a full yield does not claim the inventory is full`() { + // Force the yield roll to its maximum, but leave the success and gem rolls to chance. + val rolls = Random(42) + setRandom( + object : FakeRandom() { + override fun nextInt(from: Int, until: Int): Int = if (until == 5) 4 else rolls.nextInt(from, until) + override fun nextInt(until: Int): Int = rolls.nextInt(until) + }, + ) + val player = createPlayer(emptyTile) + player.levels.set(Skill.Mining, 100) + val deposit = createObject("mineral_deposit_gold", emptyTile.addY(1)) + player.inventory.add("bronze_pickaxe") + + player.objectOption(deposit, "Mine") + tickIf { !player.inventory.contains("gold_ore") } + + assertTrue(player.containsMessage("You manage to mine four gold ore")) + assertFalse(player.containsMessage("You don't have enough inventory space.")) + } }