diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 60ad3a248..1e1d8d8e3 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -1264,12 +1264,12 @@ }, { "examine": "Like a mini man!", - "melee_animation": "422", - "range_animation": "422", + "melee_animation": "190", + "range_animation": "190", "magic_level": "1", - "defence_animation": "404", + "defence_animation": "193", "magic_animation": "422", - "death_animation": "9055", + "death_animation": "196", "name": "Gnome", "defence_level": "1", "safespot": null, @@ -1281,12 +1281,12 @@ }, { "examine": "Like a mini man!", - "melee_animation": "422", - "range_animation": "422", + "melee_animation": "190", + "range_animation": "190", "magic_level": "1", - "defence_animation": "404", + "defence_animation": "193", "magic_animation": "422", - "death_animation": "9055", + "death_animation": "196", "name": "Gnome", "defence_level": "1", "safespot": null, @@ -1298,12 +1298,12 @@ }, { "examine": "Like a mini man!", - "melee_animation": "422", - "range_animation": "422", + "melee_animation": "190", + "range_animation": "190", "magic_level": "1", - "defence_animation": "404", + "defence_animation": "193", "magic_animation": "422", - "death_animation": "9055", + "death_animation": "196", "name": "Gnome", "defence_level": "1", "safespot": null, @@ -1907,17 +1907,17 @@ }, { "examine": "Eeek! A ghost!", - "melee_animation": "5540", + "melee_animation": "5532", "range_animation": "0", "combat_audio": "436,439,438", "attack_speed": "4", "magic_level": "1", "respawn_delay": "40", - "defence_animation": "5541", + "defence_animation": "5533", "weakness": "5", "slayer_exp": "25", "magic_animation": "0", - "death_animation": "5542", + "death_animation": "5534", "name": "Ghost", "defence_level": "18", "safespot": null, @@ -2182,11 +2182,11 @@ "attack_speed": "4", "magic_level": "1", "respawn_delay": "25", - "defence_animation": "360", + "defence_animation": "4651", "weakness": "8", "slayer_exp": "75", "magic_animation": "359", - "death_animation": "361", + "death_animation": "4653", "name": "Cyclops", "defence_level": "35", "safespot": null, @@ -47350,12 +47350,15 @@ "attack_level": "1" }, { + "combat_audio": "931,923,922", + "melee_animation": "5578", + "defence_animation": "5567", "slayer_exp": "30", + "death_animation": "5569", "name": "Zombie", "defence_level": "1", "safespot": null, "lifepoints": "40", - "combat_audio": "931,923,922", "strength_level": "1", "id": "5394", "range_level": "1", @@ -76059,8 +76062,21 @@ "id": "2239" }, { + "examine": "Like a mini man!", + "combat_style": "1", + "force_talk": "", + "melee_animation": "190", + "attack_speed": "4", + "respawn_delay": "60", + "defence_animation": "193", + "death_animation": "196", "name": "Gnome", - "id": "2251" + "defence_level": "1", + "lifepoints": "3", + "strength_level": "1", + "id": "2251", + "range_level": "1", + "attack_level": "1" }, { "name": "Crow", diff --git a/Server/data/configs/ranged_weapon_configs.json b/Server/data/configs/ranged_weapon_configs.json index 335c9f031..dc6fbe429 100644 --- a/Server/data/configs/ranged_weapon_configs.json +++ b/Server/data/configs/ranged_weapon_configs.json @@ -1546,5 +1546,41 @@ "animation": "426", "drop_ammo": "true", "ammunition": "14202,14203,14204,14205,14206" + }, + { + "itemId": "10149", + "name": "Swamp lizard", + "ammo_slot": "13", + "weapon_type": "0", + "animation": "5247", + "drop_ammo": "false", + "ammunition": "10142" + }, + { + "itemId": "10146", + "name": "Orange salamander", + "ammo_slot": "13", + "weapon_type": "0", + "animation": "5247", + "drop_ammo": "false", + "ammunition": "10143" + }, + { + "itemId": "10147", + "name": "Red salamander", + "ammo_slot": "13", + "weapon_type": "0", + "animation": "5247", + "drop_ammo": "false", + "ammunition": "10144" + }, + { + "itemId": "10148", + "name": "Black salamander", + "ammo_slot": "13", + "weapon_type": "0", + "animation": "5247", + "drop_ammo": "false", + "ammunition": "10145" } ] diff --git a/Server/src/main/content/data/EnchantedJewellery.kt b/Server/src/main/content/data/EnchantedJewellery.kt index a0c53ad00..61373e8f3 100644 --- a/Server/src/main/content/data/EnchantedJewellery.kt +++ b/Server/src/main/content/data/EnchantedJewellery.kt @@ -9,15 +9,14 @@ import core.game.event.TeleportEvent import core.game.interaction.QueueStrength import core.game.node.entity.player.Player import core.game.node.entity.player.link.TeleportManager +import core.game.node.entity.player.link.diary.DiaryType import core.game.node.item.Item -import core.game.system.task.Pulse import core.game.world.GameWorld import core.game.world.map.Location import core.game.world.update.flag.context.Animation import core.game.world.update.flag.context.Graphics -import org.rs09.consts.Items -import core.game.world.GameWorld.Pulser import core.tools.Log +import org.rs09.consts.Items import org.rs09.consts.Sounds import java.util.* @@ -258,6 +257,11 @@ enum class EnchantedJewellery( resetAnimator(player) unlock(player) player.dispatch(TeleportEvent(TeleportManager.TeleportType.NORMAL, TeleportMethod.JEWELRY, item, location)) + + if (DIGSITE_PENDANT.ids.contains(item.id)) { + player.achievementDiaryManager.finishTask(player, DiaryType.VARROCK, 1, 10) + } + if (!replace) { return@queueScript stopExecuting(player) } diff --git a/Server/src/main/content/data/consumables/Consumables.java b/Server/src/main/content/data/consumables/Consumables.java index 2d3744679..3730caf32 100644 --- a/Server/src/main/content/data/consumables/Consumables.java +++ b/Server/src/main/content/data/consumables/Consumables.java @@ -317,6 +317,7 @@ public enum Consumables { TCHIKI_MONKEY_PASTE(new Food(new int[] {7575}, new HealingEffect(5), "You eat the Tchiki monkey nut paste. It sticks to the roof of your mouth.")), OOMLIE_WRAP(new Food(new int[] {Items.COOKED_OOMLIE_WRAP_2343}, new MultiEffect(new HealingEffect(14), new AchievementEffect(DiaryType.KARAMJA, 2, 2)))), ROE(new Food(new int[]{11324}, new HealingEffect(3))), + CAVIAR(new Food(new int[]{11326}, new HealingEffect(5))), EQUA_LEAVES(new Food(new int[]{2128}, new HealingEffect(1))), CHOC_ICE(new Food(new int[]{6794}, new HealingEffect(7))), EDIBLE_SEAWEED(new Food(new int[] {403}, new HealingEffect(4))), diff --git a/Server/src/main/content/global/activity/cchallange/npc/LesserDemonChampionNPC.kt b/Server/src/main/content/global/activity/cchallange/npc/LesserDemonChampionNPC.kt index e62f73bf2..e41fd5d72 100644 --- a/Server/src/main/content/global/activity/cchallange/npc/LesserDemonChampionNPC.kt +++ b/Server/src/main/content/global/activity/cchallange/npc/LesserDemonChampionNPC.kt @@ -1,8 +1,6 @@ package content.global.activity.cchallange.npc import core.api.* -import core.game.container.impl.EquipmentContainer -import core.game.global.action.EquipHandler import core.game.node.entity.Entity import core.game.node.entity.combat.BattleState import core.game.node.entity.npc.AbstractNPC @@ -62,12 +60,11 @@ class LesserDemonChampionNPC(id: Int = 0, location: Location? = null) : Abstract super.checkImpact(state) val player = state.attacker if (player is Player) { - if (!player.equipment[3].hasItemPlugin()) { + if (player.equipment.isEmpty) { state.neutralizeHits() state.estimatedHit = state.maximumHit } else { - EquipHandler.unequip(player, EquipmentContainer.SLOT_WEAPON, id) - sendMessage(player, "You cannot use weapons in this challenge.") + sendMessage(player, "You cannot wear any equipment in this challenge.") if (state.estimatedHit > -1) { state.estimatedHit = 0 return diff --git a/Server/src/main/content/global/activity/cchallange/npc/ZombieChampionNPC.kt b/Server/src/main/content/global/activity/cchallange/npc/ZombieChampionNPC.kt index 0be9eeb3d..bab84b5b5 100644 --- a/Server/src/main/content/global/activity/cchallange/npc/ZombieChampionNPC.kt +++ b/Server/src/main/content/global/activity/cchallange/npc/ZombieChampionNPC.kt @@ -63,8 +63,8 @@ class ZombieChampionNPC(id: Int = 0, location: Location? = null) : AbstractNPC(i val player = state.attacker if (player is Player) { if (state.style == CombatStyle.MELEE || state.style == CombatStyle.RANGE) { - state.estimatedHit = state.maximumHit state.neutralizeHits() + state.estimatedHit = state.maximumHit } if (state.style == CombatStyle.MAGIC) { sendMessage(player, "You cannot use spells in this challenge.") diff --git a/Server/src/main/content/global/activity/penguinhns/PenguinManager.kt b/Server/src/main/content/global/activity/penguinhns/PenguinManager.kt index c284214a7..48cdc7dec 100644 --- a/Server/src/main/content/global/activity/penguinhns/PenguinManager.kt +++ b/Server/src/main/content/global/activity/penguinhns/PenguinManager.kt @@ -30,7 +30,7 @@ class PenguinManager{ return tagMapping[Penguin.forLocation(location)?.ordinal]?.contains(player.username.toLowerCase()) ?: false } - private fun updateStoreFile(){ + fun updateStoreFile(){ val jsonTags = JSONArray() tagMapping.filter { it.value.isNotEmpty() }.forEach { (ordinal,taggers) -> log(this::class.java, Log.FINE, "$ordinal - ${taggers.first()}") diff --git a/Server/src/main/content/global/activity/penguinhns/PenguinSpawner.kt b/Server/src/main/content/global/activity/penguinhns/PenguinSpawner.kt index d19b9a82a..2d6abe7bb 100644 --- a/Server/src/main/content/global/activity/penguinhns/PenguinSpawner.kt +++ b/Server/src/main/content/global/activity/penguinhns/PenguinSpawner.kt @@ -44,18 +44,65 @@ enum class Penguin(val id: Int, val hint: String, val location: Location){ BUSH_8(NPCs.BUSH_8105,"located in the kingdom of Asgarnia.",Location.create(2951, 3511, 0)), BUSH_9(NPCs.BUSH_8105,"located in the northern desert.",Location.create(3350, 3311, 0)), BUSH_10(NPCs.BUSH_8105,"located somewhere in the kingdom of Kandarin.",Location.create(2633, 3501, 0)), - BUSH_11(NPCs.BUSH_8105,"located south of Ardougne.",Location.create(2440, 3206, 0)), - BUSH_12(NPCs.BUSH_8105,"located where wizards study.",Location.create(3112, 3149, 0)), + BUSH_11(NPCs.BUSH_8105,"located where wizards study.",Location.create(3112, 3149, 0)), ROCK_1(NPCs.ROCK_8109,"located where the Imperial Guard train.",Location.create(2852, 3578, 0)), ROCK_2(NPCs.ROCK_8109,"located in the kingdom of Misthalin.",Location.create(3356, 3416, 0)), - ROCK_3(NPCs.ROCK_8109,"located near some ogres.",Location.create(2631, 2980, 0)), + ROCK_3(NPCs.ROCK_8109,"located near some ogres.",Location.create(2631, 2980, 0)), //potentially incorrect location ROCK_4(NPCs.ROCK_8109,"located in the Kingdom of Asgarnia.",Location.create(3013, 3501, 0)), ROCK_5(NPCs.ROCK_8109,"located between Fremennik and barbarians.",Location.create(2532, 3630, 0)), CRATE_1(NPCs.CRATE_8108,"located in the kingdom of Misthalin.",Location.create(3112, 3332, 0)), CRATE_2(NPCs.CRATE_8108,"located in the Kingdom of Misthalin.",Location.create(3305, 3508, 0)), - BARREL_1(NPCs.CRATE_8108,"located where no weapons may go.",Location.create(2806, 3383, 0)), + CRATE_3(NPCs.CRATE_8108,"located south of Ardougne.",Location.create(2440, 3206, 0)), + BARREL_1(NPCs.BARREL_8104,"located where no weapons may go.",Location.create(2806, 3383, 0)), TOADSTOOL_1(NPCs.TOADSTOOL_8110,"located in the kingdom of Misthalin.",Location.create(3156, 3178, 0)), - TOADSTOOL_2(NPCs.TOADSTOOL_8110,"located in the fairy realm.",Location.create(2409, 4462, 0)); + TOADSTOOL_2(NPCs.TOADSTOOL_8110,"located in the fairy realm.",Location.create(2409, 4462, 0)), //potentially a spawn from 2010 + + BUSH_12(NPCs.BUSH_8105,"located on an island.",Location.create(2534, 3871, 0)), + BARREL_2(NPCs.BARREL_8104,"located near the ghost town.",Location.create(3654, 3491, 0)), + BUSH_13(NPCs.BUSH_8105,"located where bloodsuckers rule.",Location.create(3600, 3487, 0)), + BUSH_14(NPCs.BUSH_8105,"located on islands where brothers quarrel.",Location.create(2355, 3848, 0)), + ROCK_6(NPCs.ROCK_8109,"located on a large crescent island.",Location.create(2118, 3943, 0)), + ROCK_7(NPCs.ROCK_8109,"located on islands where brothers quarrel.",Location.create(2357, 3797, 0)), + ROCK_8(NPCs.ROCK_8109,"located in the Wilderness.",Location.create(3169, 3650, 0)), + BARREL_3(NPCs.BARREL_8104,"located where pirates feel mostly harmless.",Location.create(3738, 3001, 0)), + CACTUS_3(NPCs.CACTUS_8107,"located in the southern desert.",Location.create(3276, 2797, 0)), + TOADSTOOL_3(NPCs.TOADSTOOL_8110,"located near the pointy-eared ones.",Location.create(2314, 3174, 0)), + BUSH_15(NPCs.BUSH_8105,"located deep in the jungle.",Location.create(2938, 2978, 0)), + TOADSTOOL_4(NPCs.TOADSTOOL_8110,"located near the pointy-eared ones.",Location.create(2219, 3227, 0)), + BARREL_4(NPCs.BARREL_8104,"located south of Ardougne.",Location.create(2662, 3152, 0)), + //BARREL_5(NPCs.BARREL_8104,"located where monkeys rule.",Location.create(2751, 2700, 0)), + //currently not well accessible, will need to add when Ape Atoll is sorted. + //BUSH_16(NPCs.BUSH_8105,"located on islands where brothers quarrel.",Location.create(2353, 3834, 0)), + //currently not well accessible, will need to add when Neitiznot bridges are fixed. + ROCK_9(NPCs.ROCK_8109,"located near a mountain of wolves.",Location.create(2852, 3504, 0)), + ROCK_10(NPCs.ROCK_8109,"located on islands where brothers quarrel.",Location.create(2413, 3846, 0)), + + BUSH_17(NPCs.BUSH_8105,"located near some ogres.",Location.create(2578, 2909, 0)), + CRATE_4(NPCs.CRATE_8108,"located where banana smugglers dwell.",Location.create(2869, 3157, 0)), + //CRATE_5(NPCs.CRATE_8108,"located near the island of Dragontooth.",Location.create(3824, 3562, 0)), + //current not accessible, will need to add when Dragontooth island is sorted. + ROCK_11(NPCs.ROCK_8109,"located where bloodsuckers rule.",Location.create(3550, 3439, 0)), + TOADSTOOL_5(NPCs.TOADSTOOL_8110,"located near the pointy-eared ones.",Location.create(2181, 3172, 0)), + BUSH_18(NPCs.BUSH_8105,"located where bloodsuckers rule.",Location.create(3472, 3392, 0)), + BUSH_19(NPCs.BUSH_8105,"located near Port Sarim.",Location.create(2989, 3121, 0)), + CRATE_6(NPCs.CRATE_8108,"located where fishers colonise.",Location.create(2322, 3658, 0)), + ROCK_12(NPCs.ROCK_8109,"located between Fremennik and barbarians.",Location.create(2675, 3717, 0)), + ROCK_13(NPCs.ROCK_8109,"located near the pointy-eared ones.",Location.create(2296, 3270, 0)), + CRATE_7(NPCs.CRATE_8108,"located where bloodsuckers rule.",Location.create(3637, 3486, 0)), + TOADSTOOL_6(NPCs.TOADSTOOL_8110,"located where bloodsuckers rule.",Location.create(3416, 3437, 0)), + BUSH_20(NPCs.BUSH_8105,"located where monkeys rule.",Location.create(2802, 2806, 0)), + + ROCK_14(NPCs.ROCK_8109,"located near some ogres.",Location.create(2438, 3050, 0)), + CACTUS_4(NPCs.CACTUS_8107,"located in the southern desert.",Location.create(3252, 2963, 0)), + ROCK_15(NPCs.ROCK_8109,"located near some ogres.",Location.create(2340, 3064, 0)), + ROCK_16(NPCs.ROCK_8109,"located in the Wilderness.",Location.create(3108, 3837, 0)), + CACTUS_5(NPCs.CACTUS_8107,"located in the southern desert.",Location.create(3433, 3000, 0)), + CACTUS_6(NPCs.CACTUS_8107,"located in the southern desert.",Location.create(3274, 2813, 0)), + ROCK_17(NPCs.ROCK_8109,"located in the Wilderness.",Location.create(3019, 3866, 0)), + ROCK_18(NPCs.ROCK_8109,"located in the Wilderness.",Location.create(2991, 3824, 0)), + BUSH_21(NPCs.BUSH_8105,"located north of Ardougne.",Location.create(2398, 3361, 0)), + ROCK_19(NPCs.ROCK_8109,"located near the coast.",Location.create(2733, 3283, 0)), + ROCK_20(NPCs.ROCK_8109,"located in the Wilderness.",Location.create(3236, 3927, 0)); companion object { private val locationMap = values().map { it.location.toString() to it }.toMap() diff --git a/Server/src/main/content/global/handlers/iface/FurClothingInterface.kt b/Server/src/main/content/global/handlers/iface/FurClothingInterface.kt index e9025bad9..a335a5fbb 100644 --- a/Server/src/main/content/global/handlers/iface/FurClothingInterface.kt +++ b/Server/src/main/content/global/handlers/iface/FurClothingInterface.kt @@ -6,6 +6,7 @@ import core.game.component.ComponentDefinition import core.game.component.ComponentPlugin import core.game.container.access.InterfaceContainer import core.game.node.entity.player.Player +import core.game.node.entity.player.link.diary.DiaryType import core.game.node.item.Item import core.plugin.Initializable import core.plugin.Plugin @@ -165,6 +166,10 @@ class FurClothingInterface : ComponentPlugin(){ if (removeItem(player, requiredFur, Container.INVENTORY) && removeItem(player, coins, Container.INVENTORY)) { addItem(player, clothing.product.id, amount) + + if (clothing == FUR_CLOTHING.DASH_CAPE) { + player.achievementDiaryManager.finishTask(player, DiaryType.VARROCK, 2, 2) + } } } diff --git a/Server/src/main/content/global/skill/agility/WildernessCourse.kt b/Server/src/main/content/global/skill/agility/WildernessCourse.kt index 00f08cf31..d48d00a1a 100644 --- a/Server/src/main/content/global/skill/agility/WildernessCourse.kt +++ b/Server/src/main/content/global/skill/agility/WildernessCourse.kt @@ -2,6 +2,7 @@ package content.global.skill.agility import core.api.* import core.cache.def.impl.SceneryDefinition +import core.game.interaction.QueueStrength import core.game.node.Node import core.game.node.scenery.Scenery import core.game.node.entity.player.Player @@ -168,25 +169,27 @@ class WildernessCourse private fun handleSteppingStones(player: Player, `object`: Scenery) { lock(player, 50) val fail = AgilityHandler.hasFailed(player, 1, 0.3) - val origLoc = player.location - registerLogoutListener(player, "steppingstone"){p -> - player.location = origLoc - } - submitWorldPulse(object : Pulse(2, player){ - var counter = 0 - override fun pulse(): Boolean { - if (counter == 3 && fail) { - AgilityHandler.fail(player, -1, Location.create(3001, 3963, 0), Animation.create(771), (player.skills.lifepoints * 0.26).toInt(), "...You lose your footing and fall into the lava.") - return true - } - AgilityHandler.forceWalk(player, if (counter == 5) 2 else -1, player.location, player.location.transform(-1, 0, 0), Animation.create(741), 10, if (counter == 5) 20.0 else 0.0, if (counter != 0) null else "You carefully start crossing the stepping stones...") - if(++counter == 6){ - unlock(player) - clearLogoutListener(player, "steppingstone") - } - return counter == 6 + queueScript(player, 0, QueueStrength.SOFT) { stage -> + val courseIndex = if (stage == 5) 2 else -1 + val start = player.location + val end = player.location.transform(-1, 0, 0) + val anim = Animation(741) + val xp = if (stage == 5) 20.0 else 0.0 + val message = if (stage == 0) "You carefully start crossing the stepping stones..." else null + AgilityHandler.forceWalk(player, courseIndex, start, end, anim, 10, xp, message) + if (stage == 2 && fail) { + val dest = Location(3001, 3963, 0) + val failanim = Animation(771) + val hit = (player.skills.lifepoints * 0.26).toInt() + AgilityHandler.fail(player, -1, dest, failanim, hit, "... You lose your footing and fall into the lava.") + return@queueScript stopExecuting(player) } - }) + if (stage == 5) { + unlock(player) + return@queueScript stopExecuting(player) + } + return@queueScript delayScript(player, 2) + } } /** diff --git a/Server/src/main/content/global/skill/construction/HouseZone.java b/Server/src/main/content/global/skill/construction/HouseZone.java index 8e5a04e85..31a57f6f8 100644 --- a/Server/src/main/content/global/skill/construction/HouseZone.java +++ b/Server/src/main/content/global/skill/construction/HouseZone.java @@ -122,7 +122,7 @@ public final class HouseZone extends MapZone { house.expelGuests(p); int toRemove = previousRegion; int dungRemove = previousDungeon; - submitWorldPulse(new Pulse(2) { + submitWorldPulse(new Pulse(1) { public boolean pulse() { Region r = RegionManager.forId(toRemove); Region dr = dungRemove != -1 ? RegionManager.forId(dungRemove) : null; diff --git a/Server/src/main/content/global/skill/magic/SpellListener.kt b/Server/src/main/content/global/skill/magic/SpellListener.kt index 64383b0a7..e938f3ef0 100644 --- a/Server/src/main/content/global/skill/magic/SpellListener.kt +++ b/Server/src/main/content/global/skill/magic/SpellListener.kt @@ -47,11 +47,10 @@ abstract class SpellListener(val bookName: String) : Listener { player.sendMessage("You need a magic level of $magicLevel to cast this spell.") throw IllegalStateException() } - for(rune in runes){ - if(!SpellUtils.hasRune(player,rune)){ - player.sendMessage("You don't have enough ${rune.definition.name.lowercase()}s to cast this spell.") - throw IllegalStateException() - } + val missing = SpellUtils.hasRunes(player, runes) + if (missing != null) { + player.sendMessage("You don't have enough ${missing.definition.name.lowercase()}s to cast this spell.") + throw IllegalStateException() } for(item in specialEquipment){ if(!player.equipment.contains(item,1)){ diff --git a/Server/src/main/content/global/skill/magic/SpellUtils.kt b/Server/src/main/content/global/skill/magic/SpellUtils.kt index beafbf64d..57e8b4c6d 100644 --- a/Server/src/main/content/global/skill/magic/SpellUtils.kt +++ b/Server/src/main/content/global/skill/magic/SpellUtils.kt @@ -3,9 +3,9 @@ package content.global.skill.magic import core.game.node.entity.combat.spell.CombinationRune import core.game.node.entity.combat.spell.MagicStaff import core.game.node.entity.combat.spell.Runes -import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.item.Item +import kotlin.math.min object SpellUtils { /** @@ -32,70 +32,57 @@ object SpellUtils { return false } - fun hasRune(p:Player,rune:Item):Boolean{ - val removeItems = p.getAttribute("spell:runes",ArrayList()) - if(usingStaff(p,rune.id)) return true - if(p.inventory.containsItem(rune)){ - removeItems.add(rune) - p.setAttribute("spell:runes",removeItems) + /** + * Validates if the player has the necessary runes to cast a spell. + * + * If the player is able to cast the spell, the "spell:runes" attribute will be set to the list of items that should + * be removed from the player's inventory after successfully casting the spell. This accounts for staves and + * combination runes. + * + * @param p The player casting the spell + * @param runes The runes and other items required to cast the spell + * @return null if the player can cast the spell or an Item representing at least one of the runes the player is missing + */ + @JvmStatic + fun hasRunes(p: Player, runes: Array): Item? { + val cost = HashMap() + // `runes` are mostly actual runes but occasionally other items like staves or unpowered orbs + for (rune in runes) { + if (usingStaff(p, rune.id)) continue + cost[rune.id] = cost.getOrDefault(rune.id, 0) + rune.amount } - val baseAmt = p.inventory.getAmount(rune.id) - var amtRemaining = rune.amount - baseAmt - val possibleComboRunes = CombinationRune.eligibleFor(Runes.forId(rune.id)) - for (r in possibleComboRunes) { - if (p.inventory.containsItem(Item(r.id)) && amtRemaining > 0) { - val amt = p.inventory.getAmount(r.id) - if (amtRemaining <= amt) { - removeItems.add(Item(r.id,amtRemaining)) - amtRemaining = 0 - break - } - removeItems.add(Item(r.id,p.inventory.getAmount(r.id))) - amtRemaining -= p.inventory.getAmount(r.id) + val toRemove = ArrayList() + + // Combination runes are used before elemental runes. + // https://runescape.wiki/w/Runecrafting?oldid=2618332#Function_and_Usage_of_Combination_Runes: + for (combo in CombinationRune.values()) { + val available = p.inventory.getAmount(combo.id) + val maxUsage = combo.types.mapNotNull { cost[it.id] }.maxOrNull() ?: 0 + if (maxUsage > 0 && available >= 0) { + val usage = min(maxUsage, available) + + toRemove.add(Item(combo.id, usage)) + // Even if a spell uses both parts of a combo rune, it should only consume a single rune. For example, + // a spell that requires an air rune and an earth rune should only consume a single dust rune. + // https://youtu.be/9gAiqEmF-Hc?t=67 + combo.types.forEach { cost[it.id] = cost.getOrDefault(it.id, 0) - usage } } } - p.setAttribute("spell:runes",removeItems) - return amtRemaining <= 0 - } - fun hasRune(p: Player, item: Item, toRemove: MutableList, message: Boolean): Boolean { - if (!usingStaff(p, item.id)) { - val hasBaseRune = p.inventory.contains(item.id, item.amount) - if (!hasBaseRune) { - val baseAmt = p.inventory.getAmount(item.id) - if (baseAmt > 0) { - toRemove.add(Item(item.id, p.inventory.getAmount(item.id))) - } - var amtRemaining = item.amount - baseAmt - val possibleComboRunes = CombinationRune.eligibleFor(Runes.forId(item.id)) - for (r in possibleComboRunes) { - if (p.inventory.containsItem(Item(r.id)) && amtRemaining > 0) { - val amt = p.inventory.getAmount(r.id) - if (amtRemaining < amt) { - toRemove.add(Item(r.id, amtRemaining)) - amtRemaining = 0 - continue - } - amtRemaining -= p.inventory.getAmount(r.id) - toRemove.add(Item(r.id, p.inventory.getAmount(r.id))) - } - } - return if (amtRemaining <= 0) { - true - } else { - p.packetDispatch.sendMessage("You don't have enough " + item.name + "s to cast this spell.") - false - } + for ((runeId, amount) in cost) { + if (amount <= 0) continue + + val available = p.inventory.getAmount(runeId) + if (available < amount) { + return Item(runeId, amount) } - toRemove.add(item) - return true - } - return true - } - fun attackableNPC(npc: NPC): Boolean{ - return npc.definition.hasAction("attack") + toRemove.add(Item(runeId, amount)) + } + + p.setAttribute("spell:runes", toRemove) + return null } @JvmStatic diff --git a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt index 914b06646..777beee18 100644 --- a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt +++ b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt @@ -2,7 +2,7 @@ package content.global.skill.magic.modern import content.data.Quests import content.global.skill.magic.SpellListener -import content.global.skill.magic.SpellUtils.hasRune +import content.global.skill.magic.SpellUtils.hasRunes import content.global.skill.magic.TeleportMethod import content.global.skill.magic.homeTeleport import content.global.skill.magic.spellconsts.Modern @@ -323,11 +323,10 @@ class ModernListeners : SpellListener("modern"){ sendMessage(player, "You need a magic level of ${spell.level} to cast this spell.") return@queueScript stopExecuting(player) } - for (rune in spell.requiredRunes) { - if(!hasRune(player,rune)){ - sendMessage(player, "You don't have enough ${rune.name.lowercase()}s to cast this spell.") - return@queueScript stopExecuting(player) - } + val missing = hasRunes(player, spell.requiredRunes) + if (missing != null) { + sendMessage(player, "You don't have enough ${missing.name.lowercase()}s to cast this spell.") + return@queueScript stopExecuting(player) } visualizeSpell(player, CHARGE_ORB_ANIM, spell.graphics, spell.sound) removeRunes(player) diff --git a/Server/src/main/content/minigame/vinesweeper/Vinesweeper.kt b/Server/src/main/content/minigame/vinesweeper/Vinesweeper.kt index 9fe119605..04fe7686a 100644 --- a/Server/src/main/content/minigame/vinesweeper/Vinesweeper.kt +++ b/Server/src/main/content/minigame/vinesweeper/Vinesweeper.kt @@ -523,8 +523,7 @@ class Vinesweeper : InteractionListener, InterfaceListener, MapArea { sendNPCDialogue(player, npc.id, "I can't do that, you're teleblocked!", core.game.dialogue.FacialExpression.OLD_ANGRY1) return } - npc.animate(Animation(437)) - npc.faceTemporary(player, 1) + // https://youtu.be/61jVjmXf8tU?t=45 npc.graphics(Graphics(108)) player.lock() playAudio(player, Sounds.CURSE_ALL_125, 0, 1) diff --git a/Server/src/main/content/region/asgarnia/falador/diary/FaladorAchievementDiary.kt b/Server/src/main/content/region/asgarnia/falador/diary/FaladorAchievementDiary.kt index 4831afd9c..aac922c9a 100644 --- a/Server/src/main/content/region/asgarnia/falador/diary/FaladorAchievementDiary.kt +++ b/Server/src/main/content/region/asgarnia/falador/diary/FaladorAchievementDiary.kt @@ -33,7 +33,7 @@ class FaladorAchievementDiary : DiaryEventHookBase(DiaryType.FALADOR) { private val WAYNES_CHAINS_AREA = ZoneBorders(2969, 3310, 2975, 3314) private val SARAHS_FARMING_SHOP_AREA = ZoneBorders(3021, 3285, 3040, 3296) private val FALADOR_GENERAL_AREA = ZoneBorders(2934, 3399, 3399, 3307) - private val CHEMIST_AREA = ZoneBorders(2929, 3213, 2936, 3207) + private val CHEMIST_AREA = ZoneBorders(2925, 3213, 2939, 3207) private val PORT_SARIM_FLOWER_PATCH = ZoneBorders(3053, 3306, 3056, 3309) diff --git a/Server/src/main/content/region/desert/alkharid/quest/princealirescue/PrinceAliRescue.java b/Server/src/main/content/region/desert/alkharid/quest/princealirescue/PrinceAliRescue.java index 049e805f6..1c37aebab 100644 --- a/Server/src/main/content/region/desert/alkharid/quest/princealirescue/PrinceAliRescue.java +++ b/Server/src/main/content/region/desert/alkharid/quest/princealirescue/PrinceAliRescue.java @@ -64,16 +64,16 @@ public class PrinceAliRescue extends Quest { break; case 10: line(player, "I started this quest by speaking to Hassan in Al-Kharid", 4+ 7); - line(player, "Palace. he told me I should speak to Osman the spymaster.", 5+ 7); + line(player, "Palace. He told me I should speak to Osman the spymaster.", 5+ 7); line(player, BLUE + "I should go and speak to " + RED + "Osman " + BLUE + "for details on the quest.", 6+ 7); break; case 20: line(player, "I started this quest by speaking to Hassan in Al-Kharid", 4+ 7); - line(player, "Palace. he told me I should speak to Osman the spymaster.", 5+ 7); + line(player, "Palace. He told me I should speak to Osman the spymaster.", 5+ 7); line(player, "I should go and speak to Osman for details on the quest.", 6+ 7); line(player, RED + "Prince Ali " + BLUE + "has been " + RED + "kidnapped " + BLUE + "but luckily the spy " + RED + "Leela " + BLUE + "has", 7+ 7); - line(player, BLUE + "found he is being held near " + RED + "Draynor village. " + BLUE + "I will need to", 8+ 7); - line(player, RED + "disguise " + BLUE + "the " + RED + "Price " + BLUE + "and " + RED + "tie " + BLUE + "up his " + RED + "captor " + BLUE + "to " + RED + "free " + BLUE + "him from", 9+ 7); + line(player, BLUE + "found he is being held near " + RED + "draynor village. " + BLUE + "I will need to", 8+ 7); //authentic typo on the 'draynor village' capitialization + line(player, RED + "disguise " + BLUE + "the " + RED + "Prince " + BLUE + "and " + RED + "tie " + BLUE + "up his " + RED + "captor " + BLUE + "to " + RED + "free " + BLUE + "him from", 9+ 7); line(player, BLUE + "their " + RED + "clutches.", 10+ 7); line(player, BLUE + "To do this I should:-", 11+ 7); line(player, BLUE + "Talk to " + RED + "Leela " + BLUE + "near " + RED + "Draynor Village " + BLUE + "for advice.", 12+ 7); @@ -85,10 +85,10 @@ public class PrinceAliRescue extends Quest { break; case 30: line(player, "I started this quest by speaking to Hassan in Al-Kharid", 4+ 7); - line(player, "Palace. he told me I should speak to Osman the spymaster.", 5+ 7); + line(player, "Palace. He told me I should speak to Osman the spymaster.", 5+ 7); line(player, "I should go and speak to Osman for details on the quest.", 6+ 7); line(player, RED + "Prince Ali " + BLUE + "has been " + RED + "kidnapped " + BLUE + "but luckily the spy " + RED + "Leela " + BLUE + "has", 7+ 7); - line(player, BLUE + "found he is being held near " + RED + "Draynor village. " + BLUE + "I will need to", 8+ 7); + line(player, BLUE + "found he is being held near " + RED + "Draynor Village. " + BLUE + "I will need to", 8+ 7); line(player, RED + "disguise " + BLUE + "the " + RED + "Prince " + BLUE + "and " + RED + "tie " + BLUE + "up his " + RED + "captor " + BLUE + "to " + RED + "free " + BLUE + "him from", 9+ 7); line(player, BLUE + "their " + RED + "clutches.", 10+ 7); line(player, BLUE + "To do this I should:-", 11+ 7); @@ -97,15 +97,15 @@ public class PrinceAliRescue extends Quest { line(player, hasItem(player, ROPE) ? "I have some rope with me." : BLUE + "Get some " + RED + "rope " + BLUE + "to tie up the Princes' " + RED + "kidnapper.", 14+ 7); line(player, hasItem(player, PASTE) ? "I have some skin paste suitable for disguise with me." : BLUE + "Get something to " + RED + "colour " + BLUE + "the " + RED + "Princes' skin " + BLUE + "as a " + RED + "disguise.", 15+ 7); line(player, hasItem(player, SKIRT) ? "I have a skirt suitable for a disguise with me." : BLUE + "Get a " + RED + "skirt " + BLUE + "similar to his " + RED + "kidnapper " + BLUE + "as " + RED + "disguise.", 16+ 7); - line(player, hasItem(player, YELLOW_WIG) ? "I have a wig suitable for disguise with me." : BLUE + "Get a " + RED + "Wig " + BLUE + "to " + RED + "help disguise" + BLUE + "the " + RED + "prince.", 17+ 7); + line(player, hasItem(player, YELLOW_WIG) ? "I have a wig suitable for disguise with me." : BLUE + "Get a " + RED + "Wig " + BLUE + "to " + RED + "help disguise " + BLUE + "the " + RED + "prince.", 17+ 7); break; case 40: line(player, "I started this quest by speaking to Hassan in Al-Kharid", 4+ 7); - line(player, "Palace. he told me I should speak to Osman the spymaster.", 5+ 7); + line(player, "Palace. He told me I should speak to Osman the spymaster.", 5+ 7); line(player, "I should go and speak to Osman for details on the quest.", 6+ 7); if (player.getAttribute("guard-drunk", false)) { line(player, "Prince Ali has been kidnapped but luckily the spy Leela has", 7+ 7); - line(player, "found he is being held near Draynor village. I will need to", 8+ 7); + line(player, "found he is being held near Draynor Village. I will need to", 8+ 7); line(player, "disguise the Prince and tie up his captor to free him from", 9+ 7); line(player, "their clutches.", 10+ 7); line(player, "I also had to prevent the Guard from seeing that I was up", 10+ 7); @@ -116,32 +116,32 @@ public class PrinceAliRescue extends Quest { } else { line(player, BLUE + "Do something to prevent " + RED + "Joe the Guard " + BLUE + "seeing the", 7+ 7); line(player, BLUE + "escape.", 8+ 7); - line(player, BLUE + "Use the " + RED + "Skin potion" + BLUE + ", " + RED + "Pink Skirt" + BLUE + "," + RED + "Rope" + BLUE + "," + RED + "Blonde Wig " + BLUE + "and " + RED + "Cell", 9+ 7); + line(player, BLUE + "Use the " + RED + "Skin Potion" + BLUE + ", " + RED + "Pink Skirt" + BLUE + ", " + RED + "Rope" + BLUE + ", " + RED + "Blonde Wig " + BLUE + "and " + RED + "Cell", 9+ 7); line(player, RED + "Key" + BLUE + " to free " + RED + "Prince Ali " + BLUE + "from his cell somehow.", 10+ 7); } break; case 50: line(player, "I started this quest by speaking to Hassan in Al-Kharid", 4+ 7); - line(player, "Palace. he told me I should speak to Osman the spymaster.", 5+ 7); + line(player, "Palace. He told me I should speak to Osman the spymaster.", 5+ 7); line(player, "I should go and speak to Osman for details on the quest.", 6+ 7); line(player, "Prince Ali has been kidnapped but luckily the spy Leela has", 7+ 7); - line(player, "found he is being held near Draynor village. I will need to", 8+ 7); + line(player, "found he is being held near Draynor Village. I will need to", 8+ 7); line(player, "disguise the Prince and tie up his captor to free him from", 9+ 7); line(player, "their clutches.", 10+ 7); line(player, "I also had to prevent the Guard from seeing that I was up", 10+ 7); line(player, "to, by getting him drunk.", 11+ 7); line(player, "With the guard disposed of, I used my rope to tie up Lady", 11+ 7); line(player, "Keli in a cupboard, so I could disguise the Prince.", 12+ 7); - line(player, BLUE + "I need to " + RED + "Unlock the cell door " + BLUE + "and then give the Prince the", 13+ 7); - line(player, RED + "Pink Skirt" + BLUE + ", the " + RED + "Skin paste " + BLUE + "and the " + RED + "Blonde Swig " + BLUE + "so that the", 14+ 7); + line(player, BLUE + "I need to " + RED + "unlock the cell door " + BLUE + "and then give the Prince the", 13+ 7); + line(player, RED + "Pink Skirt" + BLUE + ", the " + RED + "Skin Paste " + BLUE + "and the " + RED + "Blonde Wig " + BLUE + "so that they", 14+ 7); line(player, BLUE + "can safely " + RED + "escape " + BLUE + "disguised as " + RED + "Lady Keli.", 15+ 7); break; case 60: line(player, "I started this quest by speaking to Hassan in Al-Kharid", 4+ 7); - line(player, "Palace. he told me I should speak to Osman the spymaster.", 5+ 7); + line(player, "Palace. He told me I should speak to Osman the spymaster.", 5+ 7); line(player, "I should go and speak to Osman for details on the quest.", 6+ 7); line(player, "Prince Ali has been kidnapped but luckily the spy Leela has", 7+ 7); - line(player, "found he is being held near Draynor village. I will need to", 8+ 7); + line(player, "found he is being held near Draynor Village. I will need to", 8+ 7); line(player, "disguise the Prince and tie up his captor to free him from", 9+ 7); line(player, "their clutches.", 10+ 7); line(player, "I also had to prevent the Guard from seeing that I was up", 10+ 7); @@ -155,10 +155,10 @@ public class PrinceAliRescue extends Quest { break; case 100: line(player, "I started this quest by speaking to Hassan in Al-Kharid", 4+ 7); - line(player, "Palace. he told me I should speak to Osman the spymaster.", 5+ 7); + line(player, "Palace. He told me I should speak to Osman the spymaster.", 5+ 7); line(player, "I should go and speak to Osman for details on the quest.", 6+ 7); line(player, "Prince Ali has been kidnapped but luckily the spy Leela has", 7+ 7); - line(player, "found he is being held near Draynor village. I will need to", 8+ 7); + line(player, "found he is being held near Draynor Village. I will need to", 8+ 7); line(player, "disguise the Prince and tie up his captor to free him from", 9+ 7); line(player, "their clutches.", 10+ 7); line(player, "I also had to prevent the Guard from seeing that I was up", 10+ 7); @@ -170,7 +170,7 @@ public class PrinceAliRescue extends Quest { line(player, "freedom with Leela after unlocking his cell door.", 15+ 7); line(player, "Hassan the chancellor rewarded me for all of my help.", 16+ 7); line(player, "I am now a friend of Al-Kharid and may pass through the", 17+ 7); - line(player, "gate leading between Lumbridge and Al-Kharid for free", 18+ 7); + line(player, "gate leading between Lumbridge and Al-Kharid for free.", 18+ 7); line(player, "QUEST COMPLETE!", 19+ 7); break; } diff --git a/Server/src/main/content/region/kandarin/ardougne/quest/hazeelcult/ClivetNPC.kt b/Server/src/main/content/region/kandarin/ardougne/quest/hazeelcult/ClivetNPC.kt index 84474795a..c33424281 100644 --- a/Server/src/main/content/region/kandarin/ardougne/quest/hazeelcult/ClivetNPC.kt +++ b/Server/src/main/content/region/kandarin/ardougne/quest/hazeelcult/ClivetNPC.kt @@ -1,6 +1,5 @@ package content.region.kandarin.ardougne.quest.hazeelcult -import core.api.* import core.game.node.entity.npc.AbstractNPC import core.game.world.map.Location import core.plugin.Initializable @@ -16,16 +15,18 @@ class ClivetNPC(id: Int = 0, location: Location? = null, ) : AbstractNPC(id, loc override fun getIds(): IntArray = intArrayOf(NPCs.CLIVET_893) - private var invisibilityTimerRunning = false + private var invisTicks = 0 override fun tick() { + if (isInvisible && invisTicks <= 0) { + invisTicks = 20 + } - if (isInvisible && !invisibilityTimerRunning) { - invisibilityTimerRunning = true + if (invisTicks > 0) { + invisTicks-- - runTask(this, 20) { + if (invisTicks == 0) { isInvisible = false - invisibilityTimerRunning = false } } diff --git a/Server/src/main/content/region/kandarin/pisc/dialogue/MathiasFlaconryDialogue.java b/Server/src/main/content/region/kandarin/pisc/dialogue/MathiasFlaconryDialogue.java index dd3a27bd2..b926d7fde 100644 --- a/Server/src/main/content/region/kandarin/pisc/dialogue/MathiasFlaconryDialogue.java +++ b/Server/src/main/content/region/kandarin/pisc/dialogue/MathiasFlaconryDialogue.java @@ -8,6 +8,7 @@ import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; import core.plugin.Initializable; import core.game.node.item.Item; +import org.rs09.consts.Items; /** * Handles the MathiasFlaconryDialogue dialogue. @@ -49,7 +50,8 @@ public class MathiasFlaconryDialogue extends DialoguePlugin { } break; case 323: - end(); + interpreter.sendDialogues(5093, FacialExpression.NEUTRAL, "Well, you're welcome to come back if you change", "your mind."); + stage = 967; break; case 95: if (player.getBank().containsItem(FALCON) || player.getEquipment().containsItem(FALCON) || player.getInventory().containsItem(FALCON)) { @@ -58,7 +60,7 @@ public class MathiasFlaconryDialogue extends DialoguePlugin { return true; } if (player.getEquipment().get(EquipmentContainer.SLOT_HANDS) != null || player.getEquipment().get(EquipmentContainer.SLOT_SHIELD) != null || player.getEquipment().get(EquipmentContainer.SLOT_WEAPON) != null) { - interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "Sorry, free your hands, weapon, and shield slot first."); + interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "Sorry, you really need both hands free for falconry. I'd", "suggest that you put away your weapons and gloves before", "we start."); stage = 99; break; } @@ -69,11 +71,11 @@ public class MathiasFlaconryDialogue extends DialoguePlugin { stage = 97; } else { end(); - player.getPacketDispatch().sendMessage("You need 500 gold goins."); + player.getPacketDispatch().sendMessage("You need 500 gold coins."); } break; case 97: - interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "Don't worry: I'll keep and eye on you to make sure", "you don't upset it roo much."); + interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "Don't worry; I'll keep an eye on you to make sure", "you don't upset it too much."); stage = 99; break; case 99: @@ -84,22 +86,109 @@ public class MathiasFlaconryDialogue extends DialoguePlugin { stage = 501; break; case 501: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Could I have a go with your bird?"); + interpreter.sendOptions("Select an Option", "Do you have any quests I could do?", "What is this place?", "Could I have a go with your bird?"); stage = 502; break; case 502: + switch (buttonId) { + case 1: + interpreter.sendDialogues(player, FacialExpression.ASKING, "Do you have any quests I could do?"); + stage = 600; + break; + case 2: + interpreter.sendDialogues(player, FacialExpression.ASKING, "What is this place?"); + stage = 700; + break; + case 3: + interpreter.sendDialogues(player, FacialExpression.ASKING, "Could I have a go with your bird?"); + stage = 800; + break; + } + break; + case 600: + interpreter.sendDialogues(5093, FacialExpression.ASKING, "A quest? What a strange notion. Do you normally go", "around asking complete strangers for quests?"); + stage = 601; + break; + case 601: + interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Er, yes, now you come to mention it."); + stage = 602; + break; + case 602: + interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "Oh, ok then. Well, no, I don't; sorry."); + stage = 967; + break; + + case 700: + interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "A good question; straight and to the point. My name is ", "Matthias, I am a falconer, and this is where I train", "my birds."); + stage = 701; + break; + case 701: + interpreter.sendOptions("Select an Option", "Do you have any quests I could do?", "That sounds like fun; could I have a go?", "That doesn't sound like my sort of thing.", "What's this falconry thing all about then?"); + stage = 702; + break; + case 702: + switch (buttonId) { + case 1: + interpreter.sendDialogues(player, FacialExpression.ASKING, "Do you have any quests I could do?"); + stage = 600; + break; + case 2: + interpreter.sendDialogues(player, FacialExpression.ASKING, "That sounds like fun; could I have a go?"); + stage = 800; + break; + case 3: + interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "That doesn't sound like my sort of thing."); + stage = 720; + break; + case 4: + interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "What's this falconry thing all about then?"); + stage = 750; + break; + } + break; + case 720: + interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "Fair enough; it does require a great deal of patience and", "skill, so I can understand if you might feel intimidated."); + stage = 967; + break; + case 750: + interpreter.sendDialogues(5093, FacialExpression.NEUTRAL, "Well, some people see it as a sport, although such a term", "does not really convey the amount of patience and", "dedication required to be proficient at the task."); + stage = 751; + break; + case 751: + interpreter.sendDialogues(5093, FacialExpression.NEUTRAL, "Putting it simply, it is the training and use of birds of", "prey in hunting quarry."); + stage = 752; + break; + case 752: + interpreter.sendDialogues(player, FacialExpression.ASKING, "So it's like keeping a pet then?"); + stage = 753; + break; + case 753: + interpreter.sendDialogues(5093, FacialExpression.NEUTRAL, "Not exactly, no. Such a bird can never really be", "considered tame in the same way that a dog can."); + stage = 754; + break; + case 754: + interpreter.sendDialogues(5093, FacialExpression.NEUTRAL, "They can be trained to associate people or places with", "food though, and, as such, a good falconer can get a", "trained bird to do as he wishes."); + stage = 701; + break; + + case 800: if (player.getSkills().getLevel(Skills.HUNTER) < 43) { npc("Try coming back when you're more experienced", "I wouldn't want my birds being injured."); stage = 967; return true; } - interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "Training falcons is a lot of work and I", "doubt you're up to the task. However, I suppose", "I could let you try hunting with one."); - stage = 503; + interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "Training falcons is a lot of work and I doubt you're up", "to the task. However, I suppose I could let you try", "hunting with one."); + stage = 801; break; - case 503: - interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "I have some tamer birds that I occasionally lend to rich", "noblemen who consider it a sufficiently refined sport for", "their tastes. and you look like the kind who might", "appreciate a good hunt."); + case 801: + interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "I have some tamer birds that I occasionally lend to rich", "noblemen who consider it a sufficiently refined sport for", "their tastes, and you look like the kind who might", "appreciate a good hunt."); + stage = 802; + break; + case 802: + interpreter.sendDialogues(5093, FacialExpression.NEUTRAL, "I'd have to request a small fee, mind you; how does", "500 gold pieces sound?"); stage = 90; break; + case 900: interpreter.sendOptions("Select an Option", "Yes, please.", "No thank you."); stage = 901; @@ -125,7 +214,48 @@ public class MathiasFlaconryDialogue extends DialoguePlugin { case 967: end(); break; + + case 1000: + interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "Ah, you're back. How are you getting along with her then?"); + stage = 1001; + break; + case 1001: + interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "It's certainly harder than it looks."); + stage = 1002; + break; + case 1002: + interpreter.sendDialogues(5093, FacialExpression.HALF_GUILTY, "Sorry, but I was talking to the falcon, not you. But yes it", "is. Have you had enough yet?"); + stage = 1003; + break; + case 1003: + interpreter.sendOptions("Select an Option", "Actually, I'd like to keep trying a little longer.", "I think I'll leave it for now."); + stage = 1004; + break; + case 1004: + switch (buttonId) { + case 1: + interpreter.sendDialogues(player, FacialExpression.ASKING, "Actually, I'd like to keep trying a little longer."); + stage = 1010; + break; + case 2: + interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I think I'll leave it for now."); + stage = 1020; + break; + } + break; + case 1010: + interpreter.sendDialogues(5093, FacialExpression.NEUTRAL, "Ok then, just come talk to me when you're done."); + stage = 967; + break; + case 1020: + player.getInventory().remove(FALCON); + player.getEquipment().remove(FALCON, true); + + interpreter.sendDialogue("You give the falcon and glove back to Matthias."); + stage = 967; + break; } + return true; } @@ -145,6 +275,11 @@ public class MathiasFlaconryDialogue extends DialoguePlugin { stage = 900; return true; } + if (player.getEquipment().contains(10024, 1) || player.getInventory().contains(10024, 1)) { + interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Hello again."); + stage = 1000; + return true; + } if (args.length == 2) quick = true; if (quick) { diff --git a/Server/src/main/content/region/misthalin/lumbridge/diary/LumbridgeAchivementDiary.kt b/Server/src/main/content/region/misthalin/lumbridge/diary/LumbridgeAchivementDiary.kt index 5a8ca76db..965dcb157 100644 --- a/Server/src/main/content/region/misthalin/lumbridge/diary/LumbridgeAchivementDiary.kt +++ b/Server/src/main/content/region/misthalin/lumbridge/diary/LumbridgeAchivementDiary.kt @@ -20,6 +20,7 @@ import core.game.diary.DiaryEventHookBase import core.game.diary.DiaryLevel import core.game.event.* import content.data.Quests +import core.game.node.entity.player.link.SpellBookManager class LumbridgeAchivementDiary : DiaryEventHookBase(DiaryType.LUMBRIDGE) { @@ -401,14 +402,12 @@ class LumbridgeAchivementDiary : DiaryEventHookBase(DiaryType.LUMBRIDGE) { } override fun onSpellCast(player: Player, event: SpellCastEvent) { - when (event.spellId) { - Modern.LUMBRIDGE_TELEPORT -> { - finishTask( - player, - DiaryLevel.MEDIUM, - MediumTasks.CAST_LUMBRIDGE_TELEPORT - ) - } + if (event.spellBook == SpellBookManager.SpellBook.MODERN && event.spellId == Modern.LUMBRIDGE_TELEPORT) { + finishTask( + player, + DiaryLevel.MEDIUM, + MediumTasks.CAST_LUMBRIDGE_TELEPORT + ) } } diff --git a/Server/src/main/content/region/misthalin/lumbridge/quest/therestlessghost/FatherUhrneyDialogue.java b/Server/src/main/content/region/misthalin/lumbridge/quest/therestlessghost/FatherUhrneyDialogue.java index 1097d1d5e..219c0c562 100644 --- a/Server/src/main/content/region/misthalin/lumbridge/quest/therestlessghost/FatherUhrneyDialogue.java +++ b/Server/src/main/content/region/misthalin/lumbridge/quest/therestlessghost/FatherUhrneyDialogue.java @@ -1,6 +1,7 @@ package content.region.misthalin.lumbridge.quest.therestlessghost; import content.data.Quests; +import core.game.dialogue.FacialExpression; import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.diary.DiaryType; @@ -41,7 +42,7 @@ public final class FatherUhrneyDialogue extends DialoguePlugin { @Override public boolean open(Object... args) { npc = (NPC) args[0]; - npc("Go away! I'm meditating!"); + npc(FacialExpression.ANGRY, "Go away! I'm meditating!"); stage = 0; return true; } @@ -51,13 +52,13 @@ public final class FatherUhrneyDialogue extends DialoguePlugin { switch (stage) { case 0: if (player.getQuestRepository().getQuest(Quests.THE_RESTLESS_GHOST).getStage(player) == 0) { - options("Well, that's friendly.", "I've come to respossess your house."); + options("Well, that's friendly.", "I've come to repossess your house."); stage = 1; } else if (player.getQuestRepository().getQuest(Quests.THE_RESTLESS_GHOST).getStage(player) == 10) { - options("Well, that's friendly.", "I've come to respossess your house.", "Father Aereck sent me to talk to you."); + options("Well, that's friendly.", "Father Aereck sent me to talk to you.", "I've come to repossess your house."); stage = 500; } else if (player.getGameAttributes().getAttributes().containsKey("restless-ghost:urhney") || player.getQuestRepository().isComplete(Quests.THE_RESTLESS_GHOST)) { - options("Well, that's friendly.", "I've come to respossess your house.", "I've lost the Amulet of Ghostspeak."); + options("Well, that's friendly.", "I've lost the Amulet of Ghostspeak.", "I've come to repossess your house."); stage = 514; } break; @@ -68,17 +69,17 @@ public final class FatherUhrneyDialogue extends DialoguePlugin { stage = 10; break; case 2: - player("I've come to repossess your house."); - stage = 20; - break; - case 3: player("Father Aereck sent me to talk to you."); stage = 501; break; + case 3: + player("I've come to repossess your house."); + stage = 20; + break; } break; case 501: - npc("I suppose I'd better talk to you then. What problems", "has he got himself into this time?"); + npc(FacialExpression.ANGRY, "I suppose I'd better talk to you then. What problems", "has he got himself into this time?"); stage = 502; break; case 502: @@ -86,23 +87,23 @@ public final class FatherUhrneyDialogue extends DialoguePlugin { stage = 503; break; case 503: - npc("Oh, the silly fool."); + npc(FacialExpression.ANGRY, "Oh, the silly fool."); stage = 504; break; case 504: - npc("I leave town for just five months, and ALREADY he", "can't manage."); + npc(FacialExpression.ANGRY, "I leave town for just five months, and ALREADY he", "can't manage."); stage = 505; break; case 505: - npc("(sigh)"); + npc(FacialExpression.SAD, "(sigh)"); stage = 506; break; case 506: - npc("Well, I can't go back and exorcise it. I vowed not to", "leave this place. Until I had done a full two years of", "prayer and meditation."); + npc(FacialExpression.ANGRY, "Well, I can't go back and exorcise it. I vowed not to", "leave this place. Until I had done a full two years of", "prayer and meditation."); stage = 507; break; case 507: - npc("Tell you what I can do though; take this amulet."); + npc(FacialExpression.NEUTRAL, "Tell you what I can do though; take this amulet."); stage = 508; break; case 508: @@ -118,15 +119,15 @@ public final class FatherUhrneyDialogue extends DialoguePlugin { stage = 509; break; case 509: - npc("It is an Amulet of Ghostspeak."); + npc(FacialExpression.NEUTRAL, "It is an Amulet of Ghostspeak."); stage = 510; break; case 510: - npc("So called, because when you wear it you can speak to", "ghosts. A lot of ghosts are doomed to be ghosts because", "they have left some important task uncompleted."); + npc(FacialExpression.NEUTRAL, "So called, because when you wear it you can speak to", "ghosts. A lot of ghosts are doomed to be ghosts because", "they have left some important task uncompleted."); stage = 511; break; case 511: - npc("Maybe if you know what this task is, you can get rid of", "the ghost. I'm not making any gurantees mind you,", "but it is the best I can do right now."); + npc(FacialExpression.NEUTRAL, "Maybe if you know what this task is, you can get rid of", "the ghost. I'm not making any guarantees mind you,", "but it is the best I can do right now."); stage = 512; break; case 512: @@ -143,12 +144,12 @@ public final class FatherUhrneyDialogue extends DialoguePlugin { stage = 10; break; case 2: - player("I've come to repossess your house."); - stage = 20; + player(FacialExpression.NEUTRAL, "I've lost the Amulet of Ghostspeak."); + stage = 515; break; case 3: - player("I've lost the Amulet of Ghostpeak."); - stage = 515; + player("I've come to repossess your house."); + stage = 20; break; } break; @@ -171,14 +172,14 @@ public final class FatherUhrneyDialogue extends DialoguePlugin { stage = 518; break; case 517: - npc("What are you talking about? I can see you've got it", "in your bank!"); + npc(FacialExpression.ANGRY, "You come here wasting my time... Has it even", "occurred to you to look in your bank? Now GO", "AWAY!"); stage = 518; break; case 518: end(); break; case 519: - npc("How careless can you get? Those things aren't easy to", "come by you know! It's a good job I've got a spare."); + npc(FacialExpression.ANGRY, "How careless can you get? Those things aren't easy to", "come by you know! It's a good job I've got a spare."); stage = 520; break; case 520: @@ -188,7 +189,7 @@ public final class FatherUhrneyDialogue extends DialoguePlugin { stage = 521; break; case 521: - npc("Be more careful this time."); + npc(FacialExpression.ANGRY, "Be more careful this time."); stage = 522; break; case 522: @@ -252,7 +253,7 @@ public final class FatherUhrneyDialogue extends DialoguePlugin { stage = 102; break; case 102: - player("Sorry. I mus thave got the wrong address. All the", "houses look the same around here."); + player("Sorry. I must have got the wrong address. All the", "houses look the same around here."); stage = 103; break; case 103: diff --git a/Server/src/main/content/region/misthalin/quest/asoulsbane/ASoulsBaneListeners.kt b/Server/src/main/content/region/misthalin/quest/asoulsbane/ASoulsBaneListeners.kt index 256c7e17d..b19d00484 100644 --- a/Server/src/main/content/region/misthalin/quest/asoulsbane/ASoulsBaneListeners.kt +++ b/Server/src/main/content/region/misthalin/quest/asoulsbane/ASoulsBaneListeners.kt @@ -5,6 +5,7 @@ import core.game.interaction.InteractionListener import core.game.world.map.Location import org.rs09.consts.Scenery import content.data.Quests +import core.game.node.entity.player.link.diary.DiaryType // Temporary access since the monsters in there drop nothing. class ASoulsBaneListener : InteractionListener { @@ -16,6 +17,7 @@ class ASoulsBaneListener : InteractionListener { on(RIFT_IDS, SCENERY, "enter") { player, _ -> if (hasRequirement(player, Quests.A_SOULS_BANE)) { teleport(player, Location(3297, 9824, 0)) + player.achievementDiaryManager.finishTask(player, DiaryType.VARROCK, 1, 9) } return@on true } diff --git a/Server/src/main/content/region/misthalin/varrock/dialogue/RomilyWeaklaxDialogue.java b/Server/src/main/content/region/misthalin/varrock/dialogue/RomilyWeaklaxDialogue.java index ed93e45e1..26eac9af2 100644 --- a/Server/src/main/content/region/misthalin/varrock/dialogue/RomilyWeaklaxDialogue.java +++ b/Server/src/main/content/region/misthalin/varrock/dialogue/RomilyWeaklaxDialogue.java @@ -247,7 +247,7 @@ public class RomilyWeaklaxDialogue extends DialoguePlugin { @Override public boolean handle(NodeUsageEvent event) { if (!event.getPlayer().getAchievementDiaryManager().getDiary(DiaryType.VARROCK).isComplete(2,5)) { - event.getPlayer().getDialogueInterpreter().open(3205, event.getUsedItem()); + event.getPlayer().getDialogueInterpreter().open(3205, event.getUsedWith().asNpc(), event.getUsedItem()); } return true; } diff --git a/Server/src/main/content/region/misthalin/varrock/quest/familycrest/DimintheisDialogue.kt b/Server/src/main/content/region/misthalin/varrock/quest/familycrest/DimintheisDialogue.kt index e5e2f9ba0..fc857c7e6 100644 --- a/Server/src/main/content/region/misthalin/varrock/quest/familycrest/DimintheisDialogue.kt +++ b/Server/src/main/content/region/misthalin/varrock/quest/familycrest/DimintheisDialogue.kt @@ -7,6 +7,7 @@ import core.game.node.entity.player.Player import core.plugin.Initializable import org.rs09.consts.Items import content.data.Quests +import core.game.node.entity.player.link.diary.DiaryType @Initializable @@ -173,6 +174,7 @@ class DimintheisDialogue(player: Player? = null): core.game.dialogue.DialoguePlu 6000 -> npc("Not to worry, here they are").also { stage = 1000 addItem(player, getAttribute(player, "family-crest:gauntlets", Items.FAMILY_GAUNTLETS_778)) + player.achievementDiaryManager.finishTask(player, DiaryType.VARROCK, 2, 9) } 1000 -> end() diff --git a/Server/src/main/core/game/bots/ScriptAPI.kt b/Server/src/main/core/game/bots/ScriptAPI.kt index 5a3306e4c..c2ac69649 100644 --- a/Server/src/main/core/game/bots/ScriptAPI.kt +++ b/Server/src/main/core/game/bots/ScriptAPI.kt @@ -502,7 +502,10 @@ class ScriptAPI(private val bot: Player) { } val canSell = GrandExchange.addBotOffer(actualId, itemAmt) if (canSell && saleIsBigNews(actualId, itemAmt)) { - Repository.sendNews(SERVER_GE_NAME + " just offered " + itemAmt + " " + ItemDefinition.forId(actualId).name.toLowerCase() + " on the GE.") + Repository.sendGrandExchangeNews( + SERVER_GE_NAME + " just offered " + itemAmt + " " + + ItemDefinition.forId(actualId).name.toLowerCase() + " on the GE." + ) } bot.bank.remove(Item(id, itemAmt)) bot.bank.refresh() @@ -532,7 +535,10 @@ class ScriptAPI(private val bot: Player) { } val canSell = GrandExchange.addBotOffer(actualId, itemAmt) if (canSell && saleIsBigNews(actualId, itemAmt)) { - Repository.sendNews(SERVER_GE_NAME + " just offered " + itemAmt + " " + ItemDefinition.forId(actualId).name.toLowerCase() + " on the GE.") + Repository.sendGrandExchangeNews( + SERVER_GE_NAME + " just offered " + itemAmt + " " + + ItemDefinition.forId(actualId).name.toLowerCase() + " on the GE." + ) } bot.bank.remove(item) bot.bank.refresh() @@ -568,7 +574,10 @@ class ScriptAPI(private val bot: Player) { 1517 -> continue 1519 -> continue 1521 -> continue - else -> sendNews(SERVER_GE_NAME + " just offered " + itemAmt + " " + ItemDefinition.forId(actualId).name.lowercase() + " on the GE.") + else -> Repository.sendGrandExchangeNews( + SERVER_GE_NAME + " just offered " + itemAmt + " " + + ItemDefinition.forId(actualId).name.lowercase() + " on the GE." + ) } } bot.bank.remove(item) diff --git a/Server/src/main/core/game/consumable/Potion.java b/Server/src/main/core/game/consumable/Potion.java index 60afb00c3..89912d9f1 100644 --- a/Server/src/main/core/game/consumable/Potion.java +++ b/Server/src/main/core/game/consumable/Potion.java @@ -1,12 +1,16 @@ package core.game.consumable; import content.data.consumables.Consumables; +import core.game.interaction.QueueStrength; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.audio.Audio; import core.game.node.item.Item; +import core.game.system.task.Pulse; +import core.game.world.GameWorld; +import core.game.world.update.flag.context.Animation; import org.rs09.consts.Sounds; -import static core.api.ContentAPIKt.playAudio; +import static core.api.ContentAPIKt.*; public class Potion extends Drink { @@ -15,7 +19,7 @@ public class Potion extends Drink { private static final Audio SOUND = new Audio(2401, 1, 1); public Potion(final int[] ids, final ConsumableEffect effect, final String... messages) { - super(ids, effect, messages); + super(ids, effect, new Animation(829), messages); } @Override @@ -55,13 +59,17 @@ public class Potion extends Drink { } final int dosesLeft = ids.length - consumedDoses; player.getPacketDispatch().sendMessage("You drink some of your " + getFormattedName(item) + "."); - if (dosesLeft > 1) { - player.getPacketDispatch().sendMessage("You have " + dosesLeft + " doses of potion left."); - } else if (dosesLeft == 1) { - player.getPacketDispatch().sendMessage("You have 1 dose of potion left."); - } else { - player.getPacketDispatch().sendMessage("You have finished your potion."); - } + // Remaining dosages message should be delayed - https://youtu.be/n6CCf4Rj8Lg?t=79 + queueScript(player, 2, QueueStrength.SOFT, false, (Integer stage) -> { + if (dosesLeft > 1) { + player.getPacketDispatch().sendMessage("You have " + dosesLeft + " doses of potion left."); + } else if (dosesLeft == 1) { + player.getPacketDispatch().sendMessage("You have 1 dose of potion left."); + } else { + player.getPacketDispatch().sendMessage("You have finished your potion."); + } + return stopExecuting(player); + }); } public int getDose(Item potion){ diff --git a/Server/src/main/core/game/container/impl/BankContainer.java b/Server/src/main/core/game/container/impl/BankContainer.java index 5f8a51215..b27ecf700 100644 --- a/Server/src/main/core/game/container/impl/BankContainer.java +++ b/Server/src/main/core/game/container/impl/BankContainer.java @@ -1,13 +1,10 @@ package core.game.container.impl; -import core.api.IfaceSettingsBuilder; -import core.game.container.access.InterfaceContainer; -import kotlin.Unit; -import kotlin.ranges.IntRange; -import org.rs09.consts.Vars; import core.ServerConstants; +import core.api.IfaceSettingsBuilder; import core.game.component.Component; import core.game.container.*; +import core.game.container.access.InterfaceContainer; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.IronmanMode; import core.game.node.item.Item; @@ -16,8 +13,8 @@ import core.game.world.GameWorld; import core.net.packet.PacketRepository; import core.net.packet.context.ContainerContext; import core.net.packet.out.ContainerPacket; - -import java.nio.ByteBuffer; +import kotlin.ranges.IntRange; +import org.rs09.consts.Vars; import static core.api.ContentAPIKt.*; @@ -201,14 +198,9 @@ public final class BankContainer extends Container { } item = new Item(item.getId(), amount, item.getCharge()); - boolean unnote = !item.getDefinition().isUnnoted(); + Item add = item.toUnnotedItem(); - Item add = unnote ? new Item(item.getDefinition().getNoteId(), amount, item.getCharge()) : item; - if (unnote && !add.getDefinition().isUnnoted()) { - add = item; - } - - int maxCount = super.getMaximumAdd(add); + int maxCount = getMaximumAdd(add); if (amount > maxCount) { add.setAmount(maxCount); item.setAmount(maxCount); @@ -474,6 +466,11 @@ public final class BankContainer extends Container { return open; } + @Override + public int getMaximumAdd(Item item) { + return super.getMaximumAdd(item.toUnnotedItem()); + } + /** * Listens to the bank container. * @author Emperor diff --git a/Server/src/main/core/game/ge/GrandExchange.kt b/Server/src/main/core/game/ge/GrandExchange.kt index 03c01380a..4472ed2f1 100644 --- a/Server/src/main/core/game/ge/GrandExchange.kt +++ b/Server/src/main/core/game/ge/GrandExchange.kt @@ -221,7 +221,9 @@ class GrandExchange : StartupListener, Commands { //GrandExchangeRecords.getInstance(player).update(offer) if (offer.sell && !player.isArtificial) { - sendNews(player.username + " just offered " + offer.amount + " " + getItemName(offer.itemID) + " on the GE.") + Repository.sendGrandExchangeNews( + player.username + " just offered " + offer.amount + " " + getItemName(offer.itemID) + " on the GE." + ) } if (ServerConstants.I_AM_A_CHEATER) { diff --git a/Server/src/main/core/game/node/entity/combat/equipment/BoltEffect.java b/Server/src/main/core/game/node/entity/combat/equipment/BoltEffect.java index 06ab0edf2..71d26d8b6 100644 --- a/Server/src/main/core/game/node/entity/combat/equipment/BoltEffect.java +++ b/Server/src/main/core/game/node/entity/combat/equipment/BoltEffect.java @@ -239,7 +239,7 @@ public enum BoltEffect { // and should be considered a TODO: temporary abuse prevention measure (bolt effect on slayer monsters) boolean rollSuccess = RandomFunction.random(13) == 5; if (!(state.getVictim() instanceof NPC)) return rollSuccess; - if (!(state.getAttacker() instanceof Player) && ((NPC) state.getVictim()).getTask() == null) return rollSuccess; + if (!(state.getAttacker() instanceof Player) || ((NPC) state.getVictim()).getTask() == null) return rollSuccess; if (state.getVictim().asNpc().getTask().levelReq > state.getAttacker().asPlayer().getSkills().getLevel(Skills.SLAYER)) return false; return rollSuccess; } diff --git a/Server/src/main/core/game/node/entity/combat/spell/MagicSpell.java b/Server/src/main/core/game/node/entity/combat/spell/MagicSpell.java index 896a3ec62..544372bba 100644 --- a/Server/src/main/core/game/node/entity/combat/spell/MagicSpell.java +++ b/Server/src/main/core/game/node/entity/combat/spell/MagicSpell.java @@ -1,5 +1,6 @@ package core.game.node.entity.combat.spell; +import content.global.skill.magic.SpellUtils; import core.game.event.SpellCastEvent; import core.game.node.Node; import core.game.node.entity.Entity; @@ -17,7 +18,6 @@ import core.plugin.Plugin; import core.tools.RandomFunction; import java.util.ArrayList; -import java.util.List; import static core.api.ContentAPIKt.playGlobalAudio; @@ -223,16 +223,17 @@ public abstract class MagicSpell implements Plugin { if (runes == null) { return true; } - List toRemove = new ArrayList<>(20); - for (Item item : runes) { - if (!hasRune(p, item, toRemove, message)) { - return false; + Item missing = SpellUtils.hasRunes(p, runes); + if (missing != null) { + if (message) { + p.getPacketDispatch().sendMessage("You don't have enough " + missing.getName() + "s to cast this spell."); } + return false; } if (remove) { - toRemove.forEach(i -> { - p.getInventory().remove(i); - }); + ArrayList toRemove = p.getAttribute("spell:runes", new ArrayList<>()); + toRemove.forEach(i -> p.getInventory().remove(i)); + p.removeAttribute("spell:runes"); } return true; } @@ -255,49 +256,6 @@ public abstract class MagicSpell implements Plugin { return true; } - /** - * Checks if the player has a rune to remove. - * @param p the player. - * @param item the item. - * @param toRemove the list of items to remove. - * @param message the message. - * @return {@code True} if so. - */ - public boolean hasRune(Player p, Item item, List toRemove, boolean message) { - if (!usingStaff(p, item.getId())) { - boolean hasBaseRune = p.getInventory().contains(item.getId(),item.getAmount()); - if(!hasBaseRune){ - int baseAmt = p.getInventory().getAmount(item.getId()); - if(baseAmt > 0){ - toRemove.add(new Item(item.getId(),p.getInventory().getAmount(item.getId()))); - } - int amtRemaining = item.getAmount() - baseAmt; - List possibleComboRunes = CombinationRune.eligibleFor(Runes.forId(item.getId())); - for(CombinationRune r : possibleComboRunes){ - if(p.getInventory().containsItem(new Item(r.id)) && amtRemaining > 0){ - int amt = p.getInventory().getAmount(r.id); - if(amtRemaining < amt){ - toRemove.add(new Item(r.id,amtRemaining)); - amtRemaining = 0; - continue; - } - amtRemaining -= p.getInventory().getAmount(r.id); - toRemove.add(new Item(r.id,p.getInventory().getAmount(r.id))); - } - } - if(amtRemaining <= 0){ - return true; - } else { - p.getPacketDispatch().sendMessage("You don't have enough " + item.getName() + "s to cast this spell."); - return false; - } - } - toRemove.add(item); - return true; - } - return true; - } - /** * Adds the experience for casting this spell. * @param entity The entity to reward with experience. diff --git a/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java b/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java index 8a8e51827..dbbb06e81 100644 --- a/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java +++ b/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java @@ -1,8 +1,8 @@ package core.game.node.entity.player.link.diary; import core.game.node.entity.player.Player; -import org.rs09.consts.Items; import core.game.node.item.Item; +import org.rs09.consts.Items; /** * An achievement diary type. @@ -118,7 +118,7 @@ public enum DiaryType { "Select a colour for a new kitten", // TODO need ring of charos(a) and garden of tranquility to start "Use the shortcut under the wall, north-west of the Grand

Exchange", "Enter the A Soul's Bane rift", - "Teleport to the Digsite using a Digsite pendant", // TODO need Digsite and museum + "Teleport to the Digsite using a Digsite pendant", "Craft an earth tiara on the Earth Altar", "Pickpocket a guard in the Varrock Palace courtyard", "Use the teleport to Varrock spell", @@ -140,7 +140,7 @@ public enum DiaryType { "Craft an air battlestaff", "Give your player-owned house a tropical wood or fancy stone

finish at the Varrock estate agent's", "Make a Varrock teleport tablet on a mahogany lectern", - "Obtain a new set of Family Crest gauntlets from Dimintheis", // TODO need family crest + "Obtain a new set of Family Crest gauntlets from Dimintheis", "Make a Waka Canoe near Edgeville", "Use the Home Teleport spell in the Ancient Magicks spellbook

to teleport to Edgeville", "Use the skull sceptre to teleport to Barbarian Village" diff --git a/Server/src/main/core/game/node/item/Item.java b/Server/src/main/core/game/node/item/Item.java index 086784cf6..c95077106 100644 --- a/Server/src/main/core/game/node/item/Item.java +++ b/Server/src/main/core/game/node/item/Item.java @@ -135,6 +135,19 @@ public class Item extends Node{ return getId(); } + /** + * Converts noted items into unnoted items. + * + * @return The unnoted version of the item. Returns the original Item if already unnoted. + */ + public Item toUnnotedItem() { + if (definition.isUnnoted()) { + return this; + } else { + return new Item(definition.getNoteId(), getAmount(), getCharge()); + } + } + /** * @return the id */ diff --git a/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt b/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt index fb8a8d50e..53004f2b9 100644 --- a/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt @@ -10,6 +10,7 @@ import core.cache.def.impl.NPCDefinition import core.cache.def.impl.VarbitDefinition import core.cache.def.impl.Struct import core.game.node.entity.combat.ImpactHandler.HitsplatType +import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.player.link.SpellBookManager import core.game.node.entity.player.link.diary.DiaryType @@ -30,7 +31,19 @@ import core.tools.Log import core.game.node.entity.player.info.Rights import core.game.node.entity.skill.Skills import core.game.world.map.Location +import core.game.world.map.RegionManager.getLocalEntitys import core.game.world.repository.Repository +import org.json.simple.JSONArray +import kotlin.collections.set +import content.global.activity.penguinhns.Penguin +import content.global.activity.penguinhns.PenguinHNSEvent +import content.global.activity.penguinhns.PenguinManager +import content.global.activity.penguinhns.PenguinManager.Companion.penguins +import content.global.activity.penguinhns.PenguinManager.Companion.spawner +import content.global.activity.penguinhns.PenguinManager.Companion.tagMapping +import content.global.activity.penguinhns.PenguinManager.Companion.updateStoreFile +import core.ServerStore.Companion.toJSONArray +import org.rs09.consts.NPCs @Initializable class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) { @@ -304,7 +317,7 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) { setAttribute (player, "routedraw", !getAttribute(player, "routedraw", false)) } - define ("fmstart", Privilege.ADMIN, description = "Marks your current tile as the force-movement start point.") {player, _ -> + define ("fmstart", Privilege.ADMIN, description = "Marks your current tile as the force-movement start point.") {player, _ -> setAttribute(player, "fmstart", Location.create(player.location)) } @@ -320,7 +333,7 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) { setAttribute(player, "fmspeedend", args[1].toIntOrNull() ?: 10) } - define("testfm", Privilege.ADMIN, description = "Runs the configured force-movement from the saved start to end point.") { player, _ -> + define("testfm", Privilege.ADMIN, description = "Runs the configured force-movement from the saved start to end point.") { player, _ -> val start = getAttribute(player, "fmstart", Location.create(player.location)) val end = getAttribute(player, "fmend", Location.create(player.location)) val speed = getAttribute(player, "fmspeed", 10) @@ -427,5 +440,54 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) { target.skills.addExperience(skill, xp!!) } - } + + define("renewpenguins", Privilege.ADMIN, "", "Generates a fresh set of weekly penguins") { player, _ -> + val spawnedOrdinals = (PenguinHNSEvent.getStoreFile()["spawned-penguins"] as JSONArray).map { it.toString().toInt() } + val penguinNPCs = arrayListOf(NPCs.BARREL_8104, NPCs.BUSH_8105,NPCs.CACTUS_8107,NPCs.CRATE_8108,NPCs.ROCK_8109,NPCs.TOADSTOOL_8110) + + spawnedOrdinals.forEach { + val peng = Penguin.values()[it] + val nearNPCs = getLocalEntitys(peng.location,1) + nearNPCs.forEach { npc -> + if (npc.id in penguinNPCs) { + poofClear(npc as NPC) + } + } + } + penguins = spawner.spawnPenguins(10) + PenguinHNSEvent.getStoreFile()["spawned-penguins"] = penguins.toJSONArray() + tagMapping.clear() + for (p in penguins) { + tagMapping.put(p, JSONArray()) + val pengCoord = Penguin.values()[p].location + player.debug("Penguin spawned at:$pengCoord") + } + updateStoreFile() + player.debug("Penguin positions have been renewed") + } + + define("spawnpenguin",Privilege.ADMIN,"::spawnPenguin Ordinal","Adds a new Penguin spawn to this weeks list based on the ordinal provided 0-64"){player,args-> + if (args.size!=2) reject (player,"Usage: ::spawnpenguin Ordinal") + val ordinal = args[1].toIntOrNull() + if (ordinal == null) reject(player,"Ordinal must be an integer.") + if (ordinal!! > 64 || ordinal < 0) reject(player,"Ordinal must be in the range 0-64 inclusive.") + + val store = PenguinHNSEvent.getStoreFile() + val ordinals = (store["spawned-penguins"] as? JSONArray) + ?.map { it.toString().toInt() } + ?.toMutableList() + ?: mutableListOf() + val peng = Penguin.values()[ordinal] + NPC(peng.id,peng.location) + .also { PenguinManager.npcs.add(it); it.isNeverWalks = true; it.isWalks = false }.init() + tagMapping.clear() + for (p in ordinals) { + tagMapping.put(p, JSONArray()) + } + updateStoreFile() + val pengCoords = peng.location + player.debug("Penguin spawned at:$pengCoords") + } + + } } diff --git a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt index 51023b308..281491ae2 100644 --- a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt @@ -25,6 +25,7 @@ import core.game.system.command.CommandMapping import core.game.system.command.Privilege import core.game.system.communication.CommunicationInfo import core.game.world.map.RegionManager +import core.game.world.repository.GE_NEWS_MUTE_ATTRIBUTE import core.game.world.map.build.DynamicRegion import core.game.world.repository.Repository import core.game.node.entity.combat.equipment.WeaponInterface @@ -252,21 +253,38 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ } /** - * Shows the player a list of currently active GE sell offers + * Shows the player a list of currently active GE sell offers or toggles muting sell offer News announcements. */ - define("ge", Privilege.STANDARD, "::ge MODE (Modes: buying, selling, search, bots, botsearch)", "Various commands for viewing GE offers.") { player, args -> - if(args.size < 2){ - reject(player, "Usage: ::ge mode", "Available modes: buying, selling, search, bots, botsearch") + define( + "ge", + Privilege.STANDARD, + "::ge MODE (Modes: buying, selling, search, bots, botsearch, mute)", + "Various commands for viewing GE offers." + ) { player, args -> + if (args.size < 2) { + reject(player, "Usage: ::ge mode", "Available modes: buying, selling, search, bots, botsearch, mute") } - val mode = args[1] - when(mode){ + when (mode) { "buying" -> showGeBuy(player) "selling" -> showGeSell(player) "search" -> showGeInputDialogue(player, args, ::showOffers) "bots" -> showGeBots(player) "botsearch" -> showGeInputDialogue(player, args, ::showGeBotsearch) - else -> reject(player, "Invalid mode used. Available modes are: buying, selling, search") + "mute" -> { + val currentlyMuted = getAttribute(player, GE_NEWS_MUTE_ATTRIBUTE, false) + if (currentlyMuted) { + removeAttribute(player, GE_NEWS_MUTE_ATTRIBUTE) + } else { + setAttribute(player, GE_NEWS_MUTE_ATTRIBUTE, true) + } + sendMessage(player, "GE sell offer news is now ${if (currentlyMuted) "visible" else "hidden"}.") + } + + else -> reject( + player, + "Invalid mode used. Available modes are: buying, selling, search, bots, botsearch, mute" + ) } } /** diff --git a/Server/src/main/core/game/world/repository/Repository.kt b/Server/src/main/core/game/world/repository/Repository.kt index 81d44d5f4..0b97f197c 100644 --- a/Server/src/main/core/game/world/repository/Repository.kt +++ b/Server/src/main/core/game/world/repository/Repository.kt @@ -1,16 +1,17 @@ package core.game.world.repository -import core.game.node.entity.npc.NPC import content.region.wilderness.handlers.revenants.RevenantNPC +import core.ServerConstants +import core.api.sendMessage +import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.world.map.Location import core.game.world.map.RegionManager -import core.ServerConstants -import core.api.sendMessage import core.game.world.update.UpdateSequence -import java.util.* import java.util.concurrent.CopyOnWriteArrayList +const val GE_NEWS_MUTE_ATTRIBUTE = "/save:ge:news-mute" + /** * The repository holding all node lists, etc in the game world. * @author Emperor @@ -64,11 +65,22 @@ object Repository { */ @JvmStatic fun sendNews(string: String, icon: Int = 12, color: String = "CC6600") { + sendNews(string, icon, color, false) + } + + @JvmStatic + fun sendGrandExchangeNews(string: String, icon: Int = 12, color: String = "CC6600") { + sendNews(string, icon, color, true) + } + + @JvmStatic + fun sendNews(string: String, icon: Int, color: String, isGeNews: Boolean) { if (!ServerConstants.ENABLE_GLOBAL_CHAT) return val players: Array = playerNames.values.toTypedArray() val size = players.size for (i in 0 until size) { - val player = players[i] as Player ?: continue + val player = players[i] as? Player ?: continue + if (isGeNews && player.getAttribute(GE_NEWS_MUTE_ATTRIBUTE, false)) continue sendMessage(player, "News: $string") } } diff --git a/Server/src/main/core/game/world/update/flag/PlayerFlags530.kt b/Server/src/main/core/game/world/update/flag/PlayerFlags530.kt index bcc1e3356..a83f0235c 100644 --- a/Server/src/main/core/game/world/update/flag/PlayerFlags530.kt +++ b/Server/src/main/core/game/world/update/flag/PlayerFlags530.kt @@ -11,7 +11,6 @@ import core.tools.* import core.api.* import core.game.world.GameWorld -import java.nio.charset.StandardCharsets import kotlin.reflect.* import kotlin.math.max @@ -28,12 +27,13 @@ sealed class PlayerFlags530 (p: Int, o: Int, f: EntityFlag) : EFlagProvider (530 else buffer.p1 (context.chatIcon) val chatBuf = ByteArray(256) - chatBuf[0] = context.text.length.toByte() + val encodedText = CP1252.toBytes(context.text) + chatBuf[0] = encodedText.size.toByte() val offset = 1 + StringUtils.encryptPlayerChat ( chatBuf, 0, 1, - context.text.length, - context.text.toByteArray(StandardCharsets.UTF_8) + encodedText.size, + encodedText ) buffer.p1 (offset + 1) buffer.putReverse (chatBuf, 0, offset) diff --git a/Server/src/main/core/net/packet/out/CommunicationMessage.java b/Server/src/main/core/net/packet/out/CommunicationMessage.java index 5cd5e592d..77e86af95 100644 --- a/Server/src/main/core/net/packet/out/CommunicationMessage.java +++ b/Server/src/main/core/net/packet/out/CommunicationMessage.java @@ -6,6 +6,7 @@ import core.net.packet.IoBuffer; import core.net.packet.OutgoingPacket; import core.net.packet.PacketHeader; import core.net.packet.context.MessageContext; +import core.tools.CP1252; import core.tools.StringUtils; import core.game.bots.AIPlayer; @@ -21,20 +22,21 @@ public final class CommunicationMessage implements OutgoingPacket= 128 && out < 160) { - int cp1252 = charMap[out - 128]; + int cp1252 = CHAR_MAP[out - 128]; if (cp1252 == 0) { cp1252 = 63; } @@ -20,4 +24,26 @@ public class CP1252 { } return (char) out; } + + public static byte getByte(char value) { + if ((value > 0 && value < 128) || (value >= 160 && value <= 255)) { + return (byte) value; + } + + for (int i = 0; i < CHAR_MAP.length; i++) { + if (CHAR_MAP[i] != 0 && CHAR_MAP[i] == value) { + return (byte) (i + 128); + } + } + + return 63; + } + + public static byte[] toBytes(CharSequence value) { + byte[] out = new byte[value.length()]; + for (int i = 0; i < value.length(); i++) { + out[i] = getByte(value.charAt(i)); + } + return out; + } } diff --git a/Server/src/main/core/tools/StringUtils.java b/Server/src/main/core/tools/StringUtils.java index 84ac0e3e0..bc55800b6 100644 --- a/Server/src/main/core/tools/StringUtils.java +++ b/Server/src/main/core/tools/StringUtils.java @@ -502,7 +502,7 @@ public final class StringUtils { return ""; int charsDecoded = 0; int i_4_ = 0; - String s = ""; + StringBuilder s = new StringBuilder(totalChars); for (;;) { byte i_7_ = (byte) buffer.get(); if (i_7_ >= 0) @@ -511,7 +511,7 @@ public final class StringUtils { i_4_ = anIntArray241[i_4_]; int i_8_; if ((i_8_ = anIntArray241[i_4_]) < 0) { - s += (char) (byte) (i_8_ ^ 0xffffffff); + s.append(CP1252.getFromByte((byte) (i_8_ ^ 0xffffffff))); if (totalChars <= ++charsDecoded) break; i_4_ = 0; @@ -521,7 +521,7 @@ public final class StringUtils { else i_4_++; if ((i_8_ = anIntArray241[i_4_]) < 0) { - s += (char) (byte) (i_8_ ^ 0xffffffff); + s.append(CP1252.getFromByte((byte) (i_8_ ^ 0xffffffff))); if (++charsDecoded >= totalChars) break; i_4_ = 0; @@ -531,7 +531,7 @@ public final class StringUtils { else i_4_ = anIntArray241[i_4_]; if ((i_8_ = anIntArray241[i_4_]) < 0) { - s += (char) (byte) (i_8_ ^ 0xffffffff); + s.append(CP1252.getFromByte((byte) (i_8_ ^ 0xffffffff))); if (totalChars <= ++charsDecoded) break; i_4_ = 0; @@ -541,7 +541,7 @@ public final class StringUtils { else i_4_ = anIntArray241[i_4_]; if ((i_8_ = anIntArray241[i_4_]) < 0) { - s += (char) (byte) (i_8_ ^ 0xffffffff); + s.append(CP1252.getFromByte((byte) (i_8_ ^ 0xffffffff))); if (totalChars <= ++charsDecoded) break; @@ -552,7 +552,7 @@ public final class StringUtils { else i_4_++; if ((i_8_ = anIntArray241[i_4_]) < 0) { - s += (char) (byte) (i_8_ ^ 0xffffffff); + s.append(CP1252.getFromByte((byte) (i_8_ ^ 0xffffffff))); if (++charsDecoded >= totalChars) break; i_4_ = 0; @@ -562,7 +562,7 @@ public final class StringUtils { else i_4_ = anIntArray241[i_4_]; if ((i_8_ = anIntArray241[i_4_]) < 0) { - s += (char) (byte) (i_8_ ^ 0xffffffff); + s.append(CP1252.getFromByte((byte) (i_8_ ^ 0xffffffff))); if (totalChars <= ++charsDecoded) break; i_4_ = 0; @@ -572,7 +572,7 @@ public final class StringUtils { else i_4_++; if ((i_8_ = anIntArray241[i_4_]) < 0) { - s += (char) (byte) (i_8_ ^ 0xffffffff); + s.append(CP1252.getFromByte((byte) (i_8_ ^ 0xffffffff))); if (totalChars <= ++charsDecoded) break; i_4_ = 0; @@ -582,13 +582,13 @@ public final class StringUtils { else i_4_++; if ((i_8_ = anIntArray241[i_4_]) < 0) { - s += (char) (byte) (i_8_ ^ 0xffffffff); + s.append(CP1252.getFromByte((byte) (i_8_ ^ 0xffffffff))); if (++charsDecoded >= totalChars) break; i_4_ = 0; } } - return s; + return s.toString(); } catch (RuntimeException runtimeexception) { runtimeexception.printStackTrace(); }