Merge branch 2009scape:master into master

This commit is contained in:
Tooze 2026-05-30 16:24:37 +00:00
commit 1821f3879e
39 changed files with 654 additions and 314 deletions

View file

@ -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",

View file

@ -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"
}
]

View file

@ -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)
}

View file

@ -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))),

View file

@ -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

View file

@ -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.")

View file

@ -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()}")

View file

@ -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()

View file

@ -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)
}
}
}

View file

@ -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)
}
}
/**

View file

@ -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;

View file

@ -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)){

View file

@ -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<Item>())
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>): Item? {
val cost = HashMap<Int, Int>()
// `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<Item>()
// 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<Item?>, 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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -64,16 +64,16 @@ public class PrinceAliRescue extends Quest {
break;
case 10:
line(player, "<str>I started this quest by speaking to Hassan in Al-Kharid", 4+ 7);
line(player, "<str>Palace. he told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>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, "<str>I started this quest by speaking to Hassan in Al-Kharid", 4+ 7);
line(player, "<str>Palace. he told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>Palace. He told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>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, "<str>I started this quest by speaking to Hassan in Al-Kharid", 4+ 7);
line(player, "<str>Palace. he told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>Palace. He told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<Str>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) ? "<str>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) ? "<str>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) ? "<str>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) ? "<str>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) ? "<str>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, "<str>I started this quest by speaking to Hassan in Al-Kharid", 4+ 7);
line(player, "<str>Palace. he told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>Palace. He told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>I should go and speak to Osman for details on the quest.", 6+ 7);
if (player.getAttribute("guard-drunk", false)) {
line(player, "<str>Prince Ali has been kidnapped but luckily the spy Leela has", 7+ 7);
line(player, "<str>found he is being held near Draynor village. I will need to", 8+ 7);
line(player, "<str>found he is being held near Draynor Village. I will need to", 8+ 7);
line(player, "<str>disguise the Prince and tie up his captor to free him from", 9+ 7);
line(player, "<str>their clutches.", 10+ 7);
line(player, "<str>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, "<str>I started this quest by speaking to Hassan in Al-Kharid", 4+ 7);
line(player, "<str>Palace. he told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>Palace. He told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>I should go and speak to Osman for details on the quest.", 6+ 7);
line(player, "<str>Prince Ali has been kidnapped but luckily the spy Leela has", 7+ 7);
line(player, "<str>found he is being held near Draynor village. I will need to", 8+ 7);
line(player, "<str>found he is being held near Draynor Village. I will need to", 8+ 7);
line(player, "<str>disguise the Prince and tie up his captor to free him from", 9+ 7);
line(player, "<str>their clutches.", 10+ 7);
line(player, "<str>I also had to prevent the Guard from seeing that I was up", 10+ 7);
line(player, "<str>to, by getting him drunk.", 11+ 7);
line(player, "<str>With the guard disposed of, I used my rope to tie up Lady", 11+ 7);
line(player, "<str>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, "<str>I started this quest by speaking to Hassan in Al-Kharid", 4+ 7);
line(player, "<str>Palace. he told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>Palace. He told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>I should go and speak to Osman for details on the quest.", 6+ 7);
line(player, "<str>Prince Ali has been kidnapped but luckily the spy Leela has", 7+ 7);
line(player, "<str>found he is being held near Draynor village. I will need to", 8+ 7);
line(player, "<str>found he is being held near Draynor Village. I will need to", 8+ 7);
line(player, "<str>disguise the Prince and tie up his captor to free him from", 9+ 7);
line(player, "<str>their clutches.", 10+ 7);
line(player, "<str>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, "<str>I started this quest by speaking to Hassan in Al-Kharid", 4+ 7);
line(player, "<str>Palace. he told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>Palace. He told me I should speak to Osman the spymaster.", 5+ 7);
line(player, "<str>I should go and speak to Osman for details on the quest.", 6+ 7);
line(player, "<str>Prince Ali has been kidnapped but luckily the spy Leela has", 7+ 7);
line(player, "<str>found he is being held near Draynor village. I will need to", 8+ 7);
line(player, "<str>found he is being held near Draynor Village. I will need to", 8+ 7);
line(player, "<str>disguise the Prince and tie up his captor to free him from", 9+ 7);
line(player, "<str>their clutches.", 10+ 7);
line(player, "<str>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, "<str>freedom with Leela after unlocking his cell door.", 15+ 7);
line(player, "<str>Hassan the chancellor rewarded me for all of my help.", 16+ 7);
line(player, "<str>I am now a friend of Al-Kharid and may pass through the", 17+ 7);
line(player, "<str>gate leading between Lumbridge and Al-Kharid for free", 18+ 7);
line(player, "<str>gate leading between Lumbridge and Al-Kharid for free.", 18+ 7);
line(player, "<col=FF0000>QUEST COMPLETE!", 19+ 7);
break;
}

View file

@ -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
}
}

View file

@ -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) {

View file

@ -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
)
}
}

View file

@ -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:

View file

@ -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
}

View file

@ -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;
}

View file

@ -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()

View file

@ -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)

View file

@ -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){

View file

@ -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

View file

@ -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) {

View file

@ -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;
}

View file

@ -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<SpellType> {
if (runes == null) {
return true;
}
List<Item> 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<Item> 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<SpellType> {
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<Item> 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<CombinationRune> 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.

View file

@ -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<br><br>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<br><br>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<br><br>to teleport to Edgeville",
"Use the skull sceptre to teleport to Barbarian Village"

View file

@ -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
*/

View file

@ -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 <lt>Ordinal<gt>","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")
}
}
}

View file

@ -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 <lt>MODE<gt> (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 <lt>MODE<gt> (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"
)
}
}
/**

View file

@ -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<Any> = 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, "<img=$icon><col=$color>News: $string")
}
}

View file

@ -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)

View file

@ -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<MessageContext
public void send(MessageContext context) {
IoBuffer buffer = new IoBuffer(context.getOpcode(), PacketHeader.BYTE);
String message = context.getMessage();
byte[] encodedMessage = CP1252.toBytes(message);
Player player = context.getPlayer();
String other = context.getOther();
switch (context.getOpcode()) {
case MessageContext.SEND_MESSAGE:
byte[] bytes = new byte[256];
int length = StringUtils.encryptPlayerChat(bytes, 0, 0, message.length(), message.getBytes());
int length = StringUtils.encryptPlayerChat(bytes, 0, 0, encodedMessage.length, encodedMessage);
buffer.putLong(StringUtils.stringToLong(other));
buffer.put((byte) message.length());
buffer.put((byte) encodedMessage.length);
buffer.putBytes(bytes, 0, length);
break;
case MessageContext.RECIEVE_MESSAGE:
bytes = new byte[256];
bytes[0] = (byte) message.length();
length = 1 + StringUtils.encryptPlayerChat(bytes, 0, 1, message.length(), message.getBytes());
bytes[0] = (byte) encodedMessage.length;
length = 1 + StringUtils.encryptPlayerChat(bytes, 0, 1, encodedMessage.length, encodedMessage);
player.setAttribute("replyTo", other);
buffer.putLong(StringUtils.stringToLong(other)).putShort(new Random().nextInt(0xFFFF)).putTri(getMessageIndex(player)).put((byte) context.getChatIcon()).putBytes(bytes, 0, length);
break;
@ -44,8 +46,8 @@ public final class CommunicationMessage implements OutgoingPacket<MessageContext
return;
}
bytes = new byte[256];
bytes[0] = (byte) context.getMessage().length();
length = 1 + StringUtils.encryptPlayerChat(bytes, 0, 1, message.length(), message.getBytes());
bytes[0] = (byte) encodedMessage.length;
length = 1 + StringUtils.encryptPlayerChat(bytes, 0, 1, encodedMessage.length, encodedMessage);
buffer.putLong(StringUtils.stringToLong(other));
buffer.put((byte) 0);// it's just read does nothing
buffer.putLong(StringUtils.stringToLong(clan.getName()));
@ -72,4 +74,4 @@ public final class CommunicationMessage implements OutgoingPacket<MessageContext
return count;
}
}
}

View file

@ -1,18 +1,22 @@
package core.tools;
public class CP1252 {
private static char[] charMap = {'\u20ac', '\0', '\u201a', '\u0192', '\u201e', '\u2026', '\u2020', '\u2021',
public final class CP1252 {
private static final char[] CHAR_MAP = {'\u20ac', '\0', '\u201a', '\u0192', '\u201e', '\u2026', '\u2020', '\u2021',
'\u02c6', '\u2030', '\u0160', '\u2039', '\u0152', '\0', '\u017d', '\0', '\0', '\u2018', '\u2019',
'\u201c', '\u201d', '\u2022', '\u2013', '\u2014', '\u02dc', '\u2122', '\u0161', '\u203a', '\u0153',
'\0', '\u017e', '\u0178'};
private CP1252() {
/* utility class */
}
public static char getFromByte(byte value) {
int out = value & 0xff;
if (out == 0) {
throw new IllegalArgumentException("Non cp1252 character 0x" + Integer.toString(out, 16) + " provided");
}
if (out >= 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;
}
}

View file

@ -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();
}