Skip to content
1 change: 1 addition & 0 deletions data/area/misthalin/draynor/draynor.npcs.toml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ examine = "A busy-body who loves a bit of gossip."

[martin_the_master_gardener]
id = 3299
pickpocket = { level = 38, stun_ticks = 8, stun_hit = 30, xp = 43.0, chance_min = 90, chance_max = 240, table = "master_farmer" }
examine = "A master at gardening."

[wise_old_man_2]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ id = 5423

[farmer]
id = 7
pickpocket = { level = 10, stun_hit = 10, stun_ticks = 8, xp = 14.5, chance_min = 150, chance_max = 240, table = "farmer" }
examine = "He grows the crops in this area."

[hay_bales_2]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ special = "shove"
god = "zamorak"
slot = "Weapon"
type = "TwoHanded"
weapon_style = 27
examine = "A versatile spear wielded by agents of chaos."

[zamorakian_spear_noted]
Expand Down
7 changes: 7 additions & 0 deletions data/entity/player/combat/combat_styles/weapon_styles.toml
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,10 @@ id = 26
attack_types = [ "jab", "swipe", "fend" ]
attack_styles = [ "accurate", "aggressive", "defensive" ]
combat_styles = [ "stab", "slash", "crush" ]

# Custom
[zamorakian_spear]
id = 27
attack_types = [ "lunge", "swipe", "pound", "block" ]
attack_styles = [ "controlled", "controlled", "controlled", "defensive" ]
combat_styles = [ "stab", "slash", "crush", "stab" ]
69 changes: 69 additions & 0 deletions data/quest/free/cooks_assistant/cooks_assistant.books.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[cook_o_matic_manual]
title = "Cook-o-matic manual"
type = "long"
pages = [
[
"Thank you for purchasing the",
"Cook-o-Matic 25 cooking",
"appliance.This model uses",
"Burn-u-less (tm) technology, a",
"combination of state-of-the-art",
"temperature regulation and",
"magic that reduces the chance of",
"burning a selection of dishes",
"listed in the Cooking Table",
"Section.",
"",
"",
"",
"",
"",
"Contents",
"",
"User Instructions",
"Cooking Table",
],
[
"User Instructions",
"",
"This range is designed to cook a",
"wide variety of foodstuffs.",
"Just use the food on the",
"range to initiate cooking. The",
"rest of the cooking should",
"happen automatically.",
"",
"This range is not suitable for",
"spit-roasting.",
"",
"",
"",
"",
"Cooking Table",
"",
"Dishes benefiting from the",
"Burn-u-less (tm) technology:",
"",
"Bread",
"Redberry and meat pies ",
"Stew",
"Red meats",
"Chicken and turkey",
"Thin snail",
"Lean snail",
],
[
"Fat snail",
"Crayfish",
"Shrimps",
"Anchovies",
"Sardine",
"Herring",
"Mackerel",
"Trout",
"Cod",
"Pike",
"Salmon",
"Cake",
]
]
1 change: 1 addition & 0 deletions data/quest/free/cooks_assistant/cooks_assistant.items.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ weight = 0.01
destroy = "You will need to speak to the cook in Lumbridge Castle to get another."
examine = "Everything you've always wanted to know about the Cook-o-Matic 25."
kept = "Vanish"
book = "cook_o_matic_manual"

[super_large_egg]
id = 15412
Expand Down
10 changes: 10 additions & 0 deletions data/quest/quest.ifaces.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ type = "quest_journals_tab"
id = 18
options = { View = 0, "View journal" = 1, "Show on world map" = 2 }

[.filter]
id = 10

[.hide_done]
id = 12

[.order]
id = 15
options = { "Select Free/Members" = 0, "Select Progress" = 1, "Select Difficulty" = 2 }

[quest_scroll]
id = 275
type = "wide_screen"
Expand Down
29 changes: 29 additions & 0 deletions data/quest/quest.varbits.toml
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,32 @@ values = {
id = 6924
persist = true
format = "boolean"

[quest_journal_reversed]
id = 4538
persist = true
format = "boolean"

[quest_journal_show_all]
id = 4537
persist = true
format = "boolean"

[quest_journal_order]
id = 4536
persist = true
format = "map"
default = "free_members"
values = {
free_members = 0,
progress = 1,
difficulty = 2,
demo_quests = 3
}

# This varbit doesn't correctly update
# on the client side for some reason.
#[quest_journal_hide_done]
#id = 7264
#persist = true
#format = "boolean"
3 changes: 3 additions & 0 deletions data/skill/melee/melee.anims.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ id = 12006
[zamorakian_spear_lunge]
id = 12007

[zamorakian_spear_block]
id = 12005

[zamorakian_spear_defend]
id = 12008

Expand Down
2 changes: 2 additions & 0 deletions data/skill/thieving/thieving.anims.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ id = 536
[pick_pocket]
id = 881
ticks = 4
walk = false
run = false
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,9 @@ interface Character :
}

private fun nearestTile(entity: Entity): Tile = when (entity) {
is GameObject -> Distance.getNearest(entity.tile, entity.width, entity.height, this.tile)
is NPC -> Distance.getNearest(entity.tile, entity.def.size, entity.def.size, this.tile)
is Player -> Distance.getNearest(entity.tile, entity.appearance.size, entity.appearance.size, this.tile)
is GameObject -> Distance.nearest(entity.tile, entity.width, entity.height, this.tile)
is NPC -> Distance.nearest(entity.tile, entity.def.size, entity.def.size, this.tile)
is Player -> Distance.nearest(entity.tile, entity.appearance.size, entity.appearance.size, this.tile)
else -> entity.tile
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ open class Movement(
if ((character !is NPC || !character.def["allowed_under", false]) && Overlap.isUnder(character.tile, character.size, character.size, strategy.tile, strategy.width, strategy.height)) {
return false
}
if (!character.tile.within(strategy.tile, distance)) {
val nearest = strategy.nearest(character)
if (!character.tile.within(nearest, distance)) {
return false
}
if (!strategy.requiresLineOfSight()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import world.gregs.voidps.engine.entity.character.npc.NPC
import world.gregs.voidps.engine.entity.item.floor.FloorItem
import world.gregs.voidps.engine.entity.obj.GameObject
import world.gregs.voidps.engine.map.collision.Collisions
import world.gregs.voidps.type.Distance
import world.gregs.voidps.type.Tile

interface TargetStrategy {
Expand All @@ -30,20 +31,26 @@ interface TargetStrategy {

fun requiresLineOfSight(): Boolean = true

fun reached(character: Character): Boolean = ReachStrategy.reached(
flags = Collisions.map,
srcX = character.tile.x,
srcZ = character.tile.y,
level = character.tile.level,
srcSize = character.size,
destX = tile.x,
destZ = tile.y,
destWidth = sizeX,
destHeight = sizeY,
objRot = rotation,
objShape = shape,
blockAccessFlags = bitMask,
)
fun within(tile: Tile, range: Int): Boolean = tile.within(this.tile, range)

fun nearest(source: Character): Tile = Distance.nearest(tile, width, height, source.tile)

fun reached(character: Character): Boolean {
return ReachStrategy.reached(
flags = Collisions.map,
srcX = character.tile.x,
srcZ = character.tile.y,
level = character.tile.level,
srcSize = character.size,
destX = tile.x,
destZ = tile.y,
destWidth = sizeX,
destHeight = sizeY,
objRot = rotation,
objShape = shape,
blockAccessFlags = bitMask,
)
}

companion object {
operator fun <T : Any> invoke(source: Character, entity: T): TargetStrategy = when (entity) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ value class GameObject(internal val packed: Long) : Entity {
.add(tile.zone, ObjectAnimation(tile.id, get<AnimationDefinitions>().get(id).id, shape, rotation))

fun nearestTo(tile: Tile) = Tile(
x = Distance.getNearest(x, width, tile.x),
y = Distance.getNearest(y, height, tile.y),
x = Distance.nearest(x, width, tile.x),
y = Distance.nearest(y, height, tile.y),
level = level,
)

Expand Down
21 changes: 21 additions & 0 deletions game/src/main/kotlin/content/area/misthalin/lumbridge/Hank.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package content.area.misthalin.lumbridge

import content.entity.npc.shop.openShop
import content.entity.player.dialogue.Neutral
import content.entity.player.dialogue.type.choice
import content.entity.player.dialogue.type.npc
import world.gregs.voidps.engine.Script

class Hank : Script {

Check warning on line 9 in game/src/main/kotlin/content/area/misthalin/lumbridge/Hank.kt

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Unused symbol

Class "Hank" is never used
init {
npcOperate("Talk-to", "hank") {
npc<Neutral>("Good day to you! Welcome to my fishing shop. Would you like to buy some fishing equipment or sell some fish?")
choice {
option("Can I see your fishing supplies?") {
openShop(it.target.def["shop"])
}
option("Farewell.")
}
}
}
}
37 changes: 27 additions & 10 deletions game/src/main/kotlin/content/entity/npc/combat/Attack.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import content.entity.effect.toxin.poison
import content.entity.gfx.areaGfx
import content.entity.proj.shoot
import net.pearx.kasechange.toPascalCase
import org.rsmod.game.pathfinder.LineValidator
import world.gregs.voidps.engine.Script
import world.gregs.voidps.engine.client.message
import world.gregs.voidps.engine.data.config.CombatDefinition
Expand All @@ -18,19 +19,22 @@ import world.gregs.voidps.engine.data.definition.CombatDefinitions
import world.gregs.voidps.engine.entity.character.Character
import world.gregs.voidps.engine.entity.character.areaSound
import world.gregs.voidps.engine.entity.character.mode.combat.CombatApi
import world.gregs.voidps.engine.entity.character.mode.move.target.NPCCharacterTargetStrategy
import world.gregs.voidps.engine.entity.character.mode.move.hasLineOfSight
import world.gregs.voidps.engine.entity.character.mode.move.target.TargetStrategy
import world.gregs.voidps.engine.entity.character.npc.NPC
import world.gregs.voidps.engine.entity.character.player.Player
import world.gregs.voidps.engine.entity.character.player.Players
import world.gregs.voidps.engine.entity.character.player.skill.Skill
import world.gregs.voidps.engine.entity.character.sound
import world.gregs.voidps.engine.entity.distanceTo
import world.gregs.voidps.engine.entity.item.Item
import world.gregs.voidps.engine.map.Overlap
import world.gregs.voidps.type.Distance
import world.gregs.voidps.type.Tile
import world.gregs.voidps.type.random

class Attack(
val definitions: CombatDefinitions,
val lineValidator: LineValidator,
) : Script {

init {
Expand All @@ -54,7 +58,8 @@ class Attack(
say(attack.say)
}
if (attack.approach) {
if (tile.within(primaryTarget.tile, attack.range)) {
val nearest = Distance.nearest(primaryTarget.tile, primaryTarget.size, primaryTarget.size, tile)
if (tile.within(nearest, attack.range)) {
clear("attack_range")
} else {
set("attack_range", attack.range)
Expand Down Expand Up @@ -159,18 +164,17 @@ class Attack(
}

fun selectAttack(source: NPC, target: Character, definition: CombatDefinition): CombatDefinition.CombatAttack? {
val distance = source.tile.distanceTo(target)
val next: String? = source["next_attack"]
if (next != null) {
val attack = definition.attacks[next] ?: return null
return if (withinRange(source, target, distance, attack)) attack else null
return if (withinRange(source, target, attack)) attack else null
}
val validAttacks = mutableListOf<Pair<CombatDefinition.CombatAttack, Int>>()
for (attack in definition.attacks.values) {
if (!CombatApi.condition(source, target, attack.condition)) {
continue
}
if (!attack.approach && !withinRange(source, target, distance, attack)) {
if (!attack.approach && !withinRange(source, target, attack)) {
continue
}
validAttacks.add(attack to attack.chance)
Expand All @@ -181,11 +185,24 @@ class Attack(
return weightedSample(validAttacks)
}

fun withinRange(source: NPC, target: Character, distance: Int, attack: CombatDefinition.CombatAttack): Boolean {
if (attack.range == 1 && (attack.targetHits.any { Hit.meleeType(it.offense) } || source.size > 1)) {
return NPCCharacterTargetStrategy(source).reached(target)
fun withinRange(source: NPC, target: Character, attack: CombatDefinition.CombatAttack): Boolean {
// Duplicate of Movement.arrived
val attackRange = attack.range
val strategy = TargetStrategy(source, target)
if (attackRange == 1) {
return strategy.reached(source)
}
return distance in 1..attack.range
if (!source.def["allowed_under", false] && Overlap.isUnder(source.tile, source.size, source.size, strategy.tile, strategy.width, strategy.height)) {
return false
}
val nearest = strategy.nearest(source)
if (!source.tile.within(nearest, attackRange)) {
return false
}
if (!strategy.requiresLineOfSight()) {
return true
}
return lineValidator.hasLineOfSight(source, strategy.tile, strategy.width, strategy.height)
}

@Suppress("UNCHECKED_CAST")
Expand Down
2 changes: 1 addition & 1 deletion game/src/main/kotlin/content/entity/obj/ObjectTeleports.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class ObjectTeleports {

fun teleportTile(player: Player, definition: TeleportDefinition): Tile = when {
definition.delta != Delta.EMPTY && definition.to != Tile.EMPTY ->
Distance.getNearest(definition.to, definition.delta.x, definition.delta.y, player.tile)
Distance.nearest(definition.to, definition.delta.x, definition.delta.y, player.tile)
definition.delta != Delta.EMPTY -> player.tile.add(definition.delta)
definition.to != Tile.EMPTY -> definition.to
else -> player.tile
Expand Down
Loading