Implement vinesweeper

This commit is contained in:
Avi Weinstock 2021-10-07 00:25:15 +00:00 committed by Ceikry
parent c3086f9b27
commit 28f198cb72
8 changed files with 888 additions and 3 deletions

View file

@ -2290,5 +2290,11 @@
"name": "The Sound of Guthix",
"indexId": "626",
"borders": "{2500,5766,2620,5810}"
},
{
"id": "487",
"name": "Charmin' Farmin'",
"indexId": "516",
"borders": "{1600,4672,1663,4735}"
}
]

View file

@ -10790,5 +10790,29 @@
{
"npc_id": "1052",
"loc_data": "{3420,3442,0,1,0}-{3429,3436,0,1,0}-{3439,3437,0,1,0}-{3448,3444,0,1,0}-{3458,3433,0,1,0}-{3459,3419,0,1,0}-{3449,3420,0,1,0}-{3441,3421,0,1,0}-{3427,3421,0,1,0}-{3414,3422,0,1,0}-{3411,3423,0,1,0}-{3415,3414,0,1,0}-{3410,3409,0,1,0}-{3422,3405,0,1,0}-{3431,3402,0,1,0}-{3444,3405,0,1,0}-{3453,3397,0,1,0}-{3467,3402,0,1,0}-{3473,3395,0,1,0}-{3470,3385,0,1,0}-{3463,3379,0,1,0}-{3451,3380,0,1,0}-{3445,3374,0,1,0}-{3438,3365,0,1,0}-{3429,3367,0,1,0}-{3417,3367,0,1,0}-{3413,3362,0,1,0}-{3417,3358,0,1,0}-{3426,3352,0,1,0}-{3435,3349,0,1,0}-{3442,3353,0,1,0}-{3448,3358,0,1,0}-{3457,3355,0,1,0}-{3461,3348,0,1,0}-{3457,3343,0,1,0}-{3471,3344,0,1,0}-{3426,3338,0,1,0}-{3423,3335,0,1,0}"
},
{
"npc_id": "7132",
"loc_data": "{1630,4705,0,0,0}"
},
{
"npc_id": "7131",
"loc_data": "{1634,4699,0,1,0}"
},
{
"npc_id": "7125",
"loc_data": "{1605,4702,0,1,0}-{1605,4720,0,1,0}-{1631,4730,0,1,0}-{1649,4730,0,1,0}-{1658,4705,0,1,0}-{1658,4687,0,1,0}-{1633,4678,0,1,0}-{1615,4678,0,1,0}"
},
{
"npc_id": "7128",
"loc_data": "{1625,4689,0,1,0}-{1614,4704,0,1,0}-{1631,4719,0,1,0}-{1651,4705,0,1,0}"
},
{
"npc_id": "7129",
"loc_data": "{1625,4689,0,1,0}-{1614,4704,0,1,0}-{1631,4719,0,1,0}-{1651,4705,0,1,0}"
},
{
"npc_id": "7130",
"loc_data": "{1625,4689,0,1,0}-{1614,4704,0,1,0}-{1631,4719,0,1,0}-{1651,4705,0,1,0}"
}
]

View file

@ -495,6 +495,11 @@ public class NPC extends Entity {
nextWalk = GameWorld.getTicks() + 5 + RandomFunction.randomize(10);
}
public void resetWalk() {
nextWalk = GameWorld.getTicks() - 1;
getWalkingQueue().reset();
}
/**
* Called when the region goes inactive.
*/

View file

@ -651,6 +651,17 @@ object ContentAPI {
return RegionManager.getLocalNpcs(entity).filter { it.id in ids }.toList()
}
/**
* Gets a list of nearby NPCs that match the given IDs.
* @param entity the entity to check around
* @param ids the IDs of the NPCs to look for
* @param distance The maximum distance to the entity.
*/
@JvmStatic
fun findLocalNPCs(entity: Entity, ids: IntArray, distance: Int): List<NPC>{
return RegionManager.getLocalNpcs(entity, distance).filter { it.id in ids }.toList()
}
/**
* Gets the value of an attribute key from the Entity's attributes store
* @param entity the entity to get the attribute from
@ -1243,4 +1254,4 @@ object ContentAPI {
is Graphics -> Graphics.send(gfx, location)
}
}
}
}

View file

@ -0,0 +1,635 @@
import api.ContentAPI
import core.game.component.Component
import core.game.node.entity.Entity
import core.game.node.entity.impl.Projectile
import core.game.node.entity.npc.AbstractNPC
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills;
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import core.game.node.scenery.Scenery
import core.game.node.scenery.SceneryBuilder
import core.game.system.task.Pulse
import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.game.world.map.zone.MapZone
import core.game.world.map.zone.ZoneBorders
import core.game.world.map.zone.ZoneBuilder
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.plugin.Initializable
import core.tools.RandomFunction
import org.rs09.consts.Items
import rs09.game.content.dialogue.DialogueFile
import rs09.game.interaction.InteractionListener
import rs09.game.interaction.InterfaceListener
import rs09.game.world.GameWorld;
val AVACH_NIMPORTO_LOC = Location.create(1637, 4709)
val PORTAL = 29534
val SIGNS = intArrayOf(29461, 29462, 29463, 29464)
val HOLES = intArrayOf(29476, 29477, 29478)
val NUMBERS = intArrayOf(29447, 29448, 29449, 29450, 29451, 29452, 29453, 29454, 29455)
val DEAD_PLANT_OBJ = 29456
val FLAG_OBJ = 29457
val TUTORIAL = 685
val INSTRUCTION_SIGNS = hashMapOf(
29463 to 684,
29464 to 687,
29462 to 688,
29461 to 690
)
val RABBIT = 7125
val FARMERS = intArrayOf(7128, 7129, 7130)
val FARMER_BLINKIN = 7131
val MRS_WINKIN = 7132
val MAX_SEEDS = 300
val FARMER_CLEAR_RADIUS = 3
val VINESWEEPER_BORDERS = ZoneBorders(1600,4672,1663,4735)
fun sendUpdatedPoints(player: Player) {
val points = player.getAttribute("vinesweeper:points", 0);
player.varpManager.get(1195).setVarbit(6, points).send(player)
}
data class SeedDestination(val player: Player, val loc: Location, val alive: Boolean) {
override fun equals(other: Any?): Boolean {
if(other is SeedDestination) {
return loc.equals(other.loc)
} else {
return false
}
}
}
var SEED_LOCS: HashSet<Location> = HashSet()
fun isSeed(loc: Location): Boolean {
val scenery = ContentAPI.getScenery(loc)
return scenery != null && SEED_LOCS.contains(scenery.location)
}
fun populateSeeds() {
while(SEED_LOCS.size < MAX_SEEDS) {
val loc = VINESWEEPER_BORDERS.getRandomLoc()
val scenery = ContentAPI.getScenery(loc)
if(scenery != null && HOLES.contains(scenery.id)) {
SEED_LOCS.add(loc)
}
}
}
fun isHole(loc: Location): Boolean {
val scenery = ContentAPI.getScenery(loc)
return scenery != null && HOLES.contains(scenery.id)
}
fun scheduleNPCs(player: Player, loc: Location, alive: Boolean, rabbit: Boolean) {
val dest = SeedDestination(player, loc, alive)
val ids = if(rabbit) { intArrayOf(RABBIT, *FARMERS) } else { FARMERS }
for(npc in ContentAPI.findLocalNPCs(player, ids, 30)) {
if(npc is VinesweeperNPC) {
npc.seedDestinations.add(dest)
npc.resetWalk()
}
}
}
object VinesweeperTeleport {
@JvmStatic
fun teleport(npc: NPC, player: Player) {
npc.animate(Animation(437))
npc.faceTemporary(player, 1)
npc.graphics(Graphics(108))
player.lock()
player.audioManager.send(125)
Projectile.create(npc, player, 109).send()
npc.sendChat("Avach nimporto!")
GameWorld.Pulser.submit(object : Pulse(1) {
var counter = 0
override fun pulse(): Boolean {
when (counter++) {
2 -> {
player.savedData.globalData.essenceTeleporter = npc.id
player.setAttribute("/save:vinesweeper:return-tele:x", npc.location.x)
player.setAttribute("/save:vinesweeper:return-tele:y", npc.location.y)
player.properties.teleportLocation = AVACH_NIMPORTO_LOC
}
3 -> {
player.graphics(Graphics(110))
player.unlock()
return true
}
}
return false
}
})
}
}
class VinesweeperListener : InteractionListener() {
fun dig(player: Player, loc: Location) {
if(isSeed(loc)) {
val oldPoints = player.getAttribute("vinesweeper:points", 0)
player.setAttribute("/save:vinesweeper:points", Math.max(oldPoints-10, 0))
sendUpdatedPoints(player)
player.sendMessage("Oh dear! It looks like you dug up a potato seed by mistake.");
scheduleNPCs(player, loc, false, false)
val scenery = ContentAPI.getScenery(loc)
if(scenery != null) {
SceneryBuilder.replace(scenery, scenery.transform(DEAD_PLANT_OBJ))
}
} else {
player.incrementAttribute("/save:vinesweeper:points", 1)
sendUpdatedPoints(player)
var count = 0
for(dx in -1..1) {
for(dy in -1..1) {
if(isSeed(loc.transform(dx, dy, 0))) {
count += 1
}
}
}
val scenery = ContentAPI.getScenery(loc)
if(scenery != null) {
SceneryBuilder.replace(scenery, scenery.transform(NUMBERS[count]))
}
if(count == 0) {
for(dx in -1..1) {
for(dy in -1..1) {
val newLoc = loc.transform(dx, dy, 0)
if(isHole(newLoc))
dig(player, newLoc)
}
}
}
}
}
override fun defineListeners() {
ZoneBuilder.configure(VinesweeperZone())
populateSeeds()
on(PORTAL, SCENERY, "enter") { player, _ ->
val x = player.getAttribute("vinesweeper:return-tele:x", 3052)
val y = player.getAttribute("vinesweeper:return-tele:y", 3304)
ContentAPI.teleport(player, Location(x, y))
return@on true
}
on(SIGNS, SCENERY, "read") { player, node ->
player.interfaceManager.open(Component(INSTRUCTION_SIGNS[node.id]!!))
return@on true
}
on(HOLES, SCENERY, "dig") { player, node ->
if(!player.inventory.contains(Items.SPADE_952, 1)) {
// TODO (crash): authenticity
player.sendMessage("You need a spade to dig here.")
} else {
player.visualize(Animation(8709), Graphics(1543))
dig(player, node.location)
}
return@on true
}
on(HOLES, SCENERY, "flag") { player, node ->
if(player.inventory.remove(Item(Items.FLAG_12625, 1))) {
player.sendMessage("You add a flag to the patch.")
val scenery = node as Scenery
SceneryBuilder.replace(scenery, scenery.transform(FLAG_OBJ))
scheduleNPCs(player, scenery.location, true, true)
} else {
// TODO (crash): authenticity
player.sendMessage("You do not have a flag to place in the patch.")
}
return@on true
}
on(HOLES, SCENERY, "inspect") { player, node ->
player.animate(Animation(8710))
player.lock(5)
GameWorld.Pulser.submit(object : Pulse(5) {
override fun pulse(): Boolean {
val msg = when(RandomFunction.random(0, 7)) {
0 -> "You don't see anything interesting. You can't be sure if there's a seed there or not."
1 -> "You get some mud in your eye and it stings! You have no idea what is in the hole."
2 -> "The mud seems to be too thick to see what is there."
3 -> "A slimy worm wriggles out of the mud, making you jump and lose concentration. You're not sure if there is a seed here or not."
else -> if(isSeed(node.location)) {
"You notice a seed hidden in the dirt."
} else {
"You are certain there is no seed planted here."
}
}
ContentAPI.sendDialogue(player, msg)
return true
}
})
return@on true
}
on(MRS_WINKIN, NPC, "talk-to") { player, npc ->
ContentAPI.openDialogue(player, WinkinDialogue(), npc)
return@on true
}
on(MRS_WINKIN, NPC, "trade") { player, _ ->
player.interfaceManager.open(Component(686))
return@on true
}
on(MRS_WINKIN, NPC, "buy flags") { player, npc ->
val dialogue = WinkinDialogue()
dialogue.stage = 20
ContentAPI.openDialogue(player, dialogue, npc)
return@on true
}
on(RABBIT, NPC, "feed") { player, node ->
if(player.inventory.remove(Item(Items.OGLEROOT_12624, 1))) {
val npc = node as NPC
// TODO: find burrow animation id
//npc.animate(9603)
npc.sendChat("Squeak!")
npc.lock(3)
player.skills.addExperience(Skills.HUNTER, 30.0)
GameWorld.Pulser.submit(object : Pulse(3) {
override fun pulse(): Boolean {
//npc.setInvisible(true)
npc.respawnTick = GameWorld.ticks + 50
npc.location = npc.properties.spawnLocation
return true
}
})
} else {
// TODO (crash): authenticity
player.sendMessage("You don't have any ogleroots to feed the rabbit.")
}
return@on true
}
on(FARMER_BLINKIN, NPC, "talk-to") { player, npc ->
//player.interfaceManager.open(Component(TUTORIAL))
ContentAPI.openDialogue(player, BlinkinDialogue(), npc)
return@on true
}
on(FARMER_BLINKIN, NPC, "buy-flags") { player, npc ->
val dialogue = BlinkinDialogue()
dialogue.stage = 21
ContentAPI.openDialogue(player, dialogue, npc)
return@on true
}
on(FARMER_BLINKIN, NPC, "buy-roots") { player, npc ->
val dialogue = BlinkinDialogue()
dialogue.stage = 40
ContentAPI.openDialogue(player, dialogue, npc)
return@on true
}
}
}
@Initializable
class VinesweeperNPC : AbstractNPC {
fun compareDistance(a: SeedDestination, b: SeedDestination): Int {
val da = a.loc.getDistance(location).toInt()
val db = b.loc.getDistance(location).toInt()
return db - da
}
var seedDestinations: ArrayList<SeedDestination> = ArrayList();
constructor() : super(RABBIT, null, true) {}
private constructor(id: Int, location: Location) : super(id, location) {}
override fun construct(id: Int, location: Location, vararg objects: Any?): AbstractNPC {
return VinesweeperNPC(id, location)
}
init {
walkRadius = 22
}
override fun getIds(): IntArray {
return intArrayOf(RABBIT, *FARMERS)
}
override fun handleTickActions() {
val dest = seedDestinations.find({sd -> sd.loc == location})
if(dest != null) {
for(npc in ContentAPI.findLocalNPCs(this, intArrayOf(RABBIT, *FARMERS), 30)) {
if(npc is VinesweeperNPC) {
npc.seedDestinations.remove(dest)
npc.resetWalk()
}
}
val scenery = ContentAPI.getScenery(dest.loc)
if(scenery != null) {
if(id == RABBIT) {
val replacement = if(SEED_LOCS.contains(dest.loc)) { DEAD_PLANT_OBJ } else { HOLES[0] }
SceneryBuilder.replace(scenery, scenery.transform(replacement))
scheduleNPCs(dest.player, dest.loc, false, false)
} else {
if(dest.alive) {
handleFarmerFlag(scenery, dest)
} else {
sendChat("Hmm. Looks like there's a plant here.")
lock(3)
GameWorld.Pulser.submit(object : Pulse(3) {
override fun pulse(): Boolean {
sendChat("Gracious me! This one's dead")
SceneryBuilder.replace(scenery, scenery.transform(HOLES[0]))
farmerClear(dest)
return true
}
})
}
}
}
seedDestinations.remove(dest)
}
super.handleTickActions()
}
override fun getMovementDestination(): Location? {
if(seedDestinations.size > 0) {
seedDestinations.sortBy({a -> a.loc.getDistance(location).toInt()})
return seedDestinations.get(0).loc
} else {
return super.getMovementDestination()
}
}
fun handleFarmerFlag(scenery: Scenery, dest: SeedDestination) {
sendChat("Ah, another flag to clear. Let's see what's there.")
lock(3)
animate(Animation(451))
if(SEED_LOCS.contains(dest.loc)) {
val npc = this
GameWorld.Pulser.submit(object : Pulse(3) {
override fun pulse(): Boolean {
sendChat("Ah! A seed. Points for everyone near me!")
val level = dest.player.skills.getStaticLevel(Skills.FARMING)
val points = RandomFunction.random(level, 4 * level)
dest.player.incrementAttribute("/save:vinesweeper:points", points)
dest.player.inventory.add(Item(Items.FLAG_12625, 1))
sendUpdatedPoints(dest.player)
for(neighbor in RegionManager.getLocalPlayers(npc)) {
if(neighbor != dest.player) {
neighbor.incrementAttribute("/save:vinesweeper:points", points / 2)
sendUpdatedPoints(neighbor)
}
}
SceneryBuilder.replace(scenery, scenery.transform(HOLES[0]))
farmerClear(dest)
return true
}
})
} else {
SceneryBuilder.replace(scenery, scenery.transform(HOLES[0]))
var i = 0
val lines = arrayOf("Hmm, no seeds planted here, I'm afraid.", "I'll have to keep this 'ere flag. Sorry.")
GameWorld.Pulser.submit(object : Pulse(3) {
override fun pulse(): Boolean {
sendChat(lines[i++])
return i >= lines.size
}
})
}
}
fun farmerClear(dest: SeedDestination) {
for(dx in -FARMER_CLEAR_RADIUS..FARMER_CLEAR_RADIUS) {
for(dy in -FARMER_CLEAR_RADIUS..FARMER_CLEAR_RADIUS) {
val toClear = ContentAPI.getScenery(dest.loc.transform(dx, dy, 0))
if(toClear != null && intArrayOf(DEAD_PLANT_OBJ, *NUMBERS).contains(toClear.id)) {
SceneryBuilder.replace(toClear, toClear.transform(HOLES[0]))
}
}
}
SEED_LOCS.remove(dest.loc)
populateSeeds()
}
}
class VinesweeperZone : MapZone("Vinesweeper", true) {
override fun enter(e: Entity): Boolean {
if(e is Player) {
e.interfaceManager.openOverlay(Component(689))
sendUpdatedPoints(e)
}
return super.enter(e)
}
override fun leave(e: Entity, logout: Boolean): Boolean {
if(e is Player) {
e.interfaceManager.closeOverlay()
if(!logout) {
e.sendMessage("Winkin's Farm thanks you for your visit.")
e.sendMessage("Leftover ogleroots and flags have been returned to the establishment.")
e.sendMessage("You have been reimbursed at a rate of 10gp per ogleroot and the flags have been collected.")
val flags = e.inventory.getAmount(Item(Items.FLAG_12625))
if(flags > 0) {
e.setAttribute("/save:vinesweeper:stored-flags", flags)
e.inventory.remove(Item(Items.FLAG_12625, flags))
}
val roots = e.inventory.getAmount(Item(Items.OGLEROOT_12624))
if(roots > 0) {
e.inventory.remove(Item(Items.OGLEROOT_12624, roots))
e.inventory.add(Item(Items.COINS_995, roots * 10))
}
}
}
return super.leave(e, logout)
}
override fun configure() {
super.registerRegion(6473)
}
}
class VinesweeperRewards : InterfaceListener() {
val IFACE = 686
val TRADE_FOR_XP_BUTTON = 53
val XP_CONFIRM = 72
val XP_DENY = 73
enum class Opcode(val value: Int) {
VALUE(155),
BUY1(196),
BUY5(124),
BUY10(199),
BUYX(234),
}
data class Reward(val itemID: Int, val points: Int) {}
val REWARDS = hashMapOf(
18 to Reward(Items.TOMATO_SEED_5322, 10),
19 to Reward(Items.SWEETCORN_SEED_5320, 150),
20 to Reward(Items.STRAWBERRY_SEED_5323, 165),
21 to Reward(Items.WATERMELON_SEED_5321, 680),
22 to Reward(Items.GUAM_SEED_5291, 10),
23 to Reward(Items.MARRENTILL_SEED_5292, 10),
24 to Reward(Items.RANARR_SEED_5295, 4000),
25 to Reward(Items.KWUARM_SEED_5299, 1000),
26 to Reward(Items.TARROMIN_SEED_5293, 10),
27 to Reward(Items.NASTURTIUM_SEED_5098, 10),
28 to Reward(Items.WOAD_SEED_5099, 30),
29 to Reward(Items.LIMPWURT_SEED_5100, 70),
30 to Reward(Items.ASGARNIAN_SEED_5308, 5),
31 to Reward(Items.KRANDORIAN_SEED_5310, 20),
32 to Reward(Items.REDBERRY_SEED_5101, 5),
33 to Reward(Items.CADAVABERRY_SEED_5102, 5),
34 to Reward(Items.DWELLBERRY_SEED_5103, 5),
35 to Reward(Items.JANGERBERRY_SEED_5104, 10),
36 to Reward(Items.WHITEBERRY_SEED_5105, 25),
37 to Reward(Items.POISON_IVY_SEED_5106, 30),
38 to Reward(Items.ACORN_5312, 100),
39 to Reward(Items.WILLOW_SEED_5313, 1800),
40 to Reward(Items.MAPLE_SEED_5314, 12000),
41 to Reward(Items.PINEAPPLE_SEED_5287, 10000),
42 to Reward(Items.YEW_SEED_5315, 29000),
43 to Reward(Items.PALM_TREE_SEED_5289, 35000),
44 to Reward(Items.SPIRIT_SEED_5317, 55000),
45 to Reward(Items.COMPOST_POTION4_6470, 5000),
46 to Reward(Items.FLAG_12625, 50),
// Magic number from dumps/scripts/2003.cs2
TRADE_FOR_XP_BUTTON to Reward(11209, 0)
)
fun buy(player: Player, buttonID: Int, amount: Int) {
val reward = REWARDS[buttonID] ?: return
val cost = amount * reward.points
val points = player.getAttribute("vinesweeper:points", 0)
if(cost < points) {
val item = Item(reward.itemID, amount)
if(!player.inventory.add(item)) {
GroundItemManager.create(item, player)
}
player.incrementAttribute("/save:vinesweeper:points", -cost)
sendUpdatedPoints(player)
} else {
// TODO (crash): authenticity
player.sendMessage("You don't have enough points for that.")
}
}
override fun defineListeners() {
onOpen(IFACE) { _, _ ->
/*for((buttonID, reward) in REWARDS) {
ContentAPI.sendItemOnInterface(player, IFACE, buttonID, reward.itemID, 5)
}*/
//player.packetDispatch.sendRunScript(2003, "")
return@onOpen true
}
on(IFACE) { player, _, opcode, buttonID, _, _ ->
when(opcode) {
Opcode.VALUE.value -> {
when(buttonID) {
TRADE_FOR_XP_BUTTON -> {
player.packetDispatch.sendInterfaceConfig(686, 60, false)
}
XP_CONFIRM -> {
player.packetDispatch.sendInterfaceConfig(686, 60, true)
val level = player.skills.getStaticLevel(Skills.FARMING)
// TODO: more precise formula
val points_per_xp = if (level < 40) { 2.0*(40.0 - level.toDouble())/10.0 } else { 1.0 }
val points = player.getAttribute("vinesweeper:points", 0)
val xp = points / points_per_xp;
player.skills.addExperience(Skills.FARMING, xp)
player.setAttribute("/save:vinesweeper:points", 0)
sendUpdatedPoints(player)
}
XP_DENY -> {
player.packetDispatch.sendInterfaceConfig(686, 60, true)
}
else -> {
val reward = REWARDS[buttonID] ?: return@on true
player.sendMessage("${Item(reward.itemID).name}: ${reward.points} vinesweeper points")
}
}
}
Opcode.BUY1.value -> {
buy(player, buttonID, 1)
return@on true
}
Opcode.BUY5.value -> {
buy(player, buttonID, 5)
return@on true
}
Opcode.BUY10.value -> {
buy(player, buttonID, 10)
return@on true
}
Opcode.BUYX.value -> {
player.setAttribute("runscript") { amount: Int ->
buy(player, buttonID, amount)
}
player.dialogueInterpreter.sendInput(false, "Enter the amount:")
return@on true
}
else -> {}
}
return@on true
}
}
}
/*
https://www.youtube.com/watch?v=WkCVAOOR7Sw
buy-flags
Player: "Have you got any flags?"
NPC: "Let me check for you."
NPC: "Alright, you can have a total of 10 flags. To obtain a\nfull set of flags will cost you 5000 coins. Would you\nlike to buy these flags?"
"Yes, please." "No thanks."
NPC: "Here you are then, dear."
talk-to
NPC: "Oh hello there, dear. How can I help you?"
"Where are we?" "Have you got any flags?" "Do you have a spare spade?" "Do you have anything for trade?" "Nothing. I'm fine, thanks."
Player: "Do you have a spare spade?"
NPC: "Why, of course. I can sell you one for 5 gold pieces."
"Okay, thanks." "Actually, I've changed my mind."
NPC: "Here you are, then."
inspect:
chat log: "You examine the hole to see what might be in it."
chat dialog: "You notice a seed hidden in the dirt."
Farmer overhead:
"Ah, another flag to clear. Let's see what's there."
"Ah! A seed. Points for everyone near me!"
https://www.youtube.com/watch?v=UjxfJdHkJnM
inspect:
"Oh dear! It looks like you dug up a potato seed by mistake."
Farmer overhead:
"Hmm. Looks like there's a plant here."
"Gracious me! This one's dead"
flag:
"You add a flag to the patch."
digging ogleroot:
"You uncover a rather odd-looking root vegetable."
https://www.youtube.com/watch?v=fKyy0sgrBYM
Rabbit overhead:
"Squeak!"
https://www.youtube.com/watch?v=RnhNrwbUjjQ
Player: "Have you got any flags?"
NPC: "Let me check for you."
NPC: "Ah! First things first. One of the farmers dropped off\nsome flags for you. You can have them back. Here you\ngo."
NPC: "It looks like you have all the flags you need. You don't\nneed to buy any more."
inspect:
"You notice a seed hidden in the dirt."
"A slimy worm wriggles out of the mud, making you jump and lose\nconcentration. You're not sure if there is a seed here or not."
"You are certain there is no seed planted here."
farmer overhead:
"Hmm, no seeds planted here, I'm afraid."
"I'll have to keep this 'ere flag. Sorry."
*/

View file

@ -0,0 +1,195 @@
import api.ContentAPI
import core.game.component.Component
import core.game.node.entity.player.Player
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import org.rs09.consts.Items
import rs09.game.content.dialogue.DialogueFile
import rs09.tools.END_DIALOGUE
val BLINKIN_FLAG_LINES = arrayOf(
"Let me check for ya.",
"Flags? It appears ya don't have enough room for 'em. Make some space and talk to me again.",
"Ah! First things first. One of the farm lads dropped off some flags for ya. Ya can have them back. Here ya go.",
"Righty-ho! Ya can have a total of 10 flags. To get yerself a full set of flags'll cost ya %d gold pieces. Would ya like to buy these flags?",
"Here ya go, then.",
"Flags? It appears ya don't have enough room for 'em. Make some space and talk to me again.",
"Ya don't have the coins fer these, I'm afraid! Come back when yer a little bit richer p'raps?",
"Right y'are then! See ya.",
"It looks like ya got all the flags ya need right now. Ya don't need to buy any more."
)
val WINKIN_FLAG_LINES = arrayOf(
"Let me check for you.",
"I'm sorry dear, you don't appear to have enough room. Make some space and talk to me again.",
"Ah! First things first. One of the farmers dropped off some flags for you. You can have them back. Here you go.",
"Alright. You can have a total of 10 flags. To obtain a full set of flags will cost you %d coins. Would you like to buy these flags?",
"Here you are then, dear.",
"I'm sorry dear, you don't appear to have enough room. Make some space and talk to me again.",
"I'm afraid it looks like you don't have enough money, dear. Come back and see me again when you have a bit more.",
"Ok, dear. Goodbye.",
"It looks like you have all the flags you need. You don't need to buy any more."
)
abstract class FarmerDialogue : DialogueFile() {
fun handleFlags(componentID: Int, buttonID: Int, lines: Array<String>) {
when(stage) {
20 -> npcl(lines[0]).also { stage++ }
21 -> {
val flags = player!!.getAttribute("vinesweeper:stored-flags", 10)
if(flags > 0) {
if(!player!!.inventory.add(Item(Items.FLAG_12625, flags))) {
npcl(lines[1])
stage = END_DIALOGUE
} else {
player!!.setAttribute("/save:vinesweeper:stored-flags", 0)
npcl(lines[2])
stage++
}
} else {
stage++
handle(componentID, buttonID)
}
}
22 -> {
val flags = player!!.inventory.getAmount(Items.FLAG_12625)
if(flags < 10) {
val price = 500 * (10 - flags)
npcl(String.format(lines[3], price))
stage = 220
} else {
stage = 23
handle(componentID, buttonID)
}
}
220 -> options("Yes, please.", "No, thanks").also { stage++ }
221 -> when(buttonID) {
1 -> playerl("Yes, please.").also { stage = 222 }
2 -> playerl("No, thanks.").also { stage = 223 }
}
222 -> {
val flags = player!!.inventory.getAmount(Items.FLAG_12625)
val price = Item(Items.COINS_995, 500 * (10 - flags))
if(player!!.inventory.containsItem(price) && player!!.inventory.remove(price)) {
if(player!!.inventory.add(Item(Items.FLAG_12625, 10 - flags))) {
npcl(lines[4])
stage = 23
} else {
npcl(lines[5])
// Refund the coins, can't fail because we just removed them
player!!.inventory.add(price)
stage = END_DIALOGUE
}
} else {
npcl(lines[6])
stage = END_DIALOGUE
}
}
223 -> npcl(lines[7]).also { stage = END_DIALOGUE }
23 -> npcl(lines[8]).also { stage = END_DIALOGUE }
}
}
}
class BlinkinDialogue : FarmerDialogue() {
override fun handle(componentID: Int, buttonID: Int) {
when(stage) {
0 -> npcl("'Ello there! Welcome to Winkin's Farm. What can I do for ya?").also { stage++ }
1 -> options("What is this place?", "Do you have any flags?", "Where is Mr. Winkin?").also { stage++ }
2 -> when(buttonID) {
1 -> playerl("What is this place?").also { stage = 10 }
2 -> playerl("Do you have any flags?").also { stage = 20 }
3 -> playerl("Where is Mr. Winkin?").also { stage = 30 }
}
10 -> npcl("Ha! I told ya, it's Winkin's Farm. This is where we grow the magical ogleroots for the rest of the world.").also { stage++ }
11 -> playerl("So, what can I do here?").also { stage++ }
12 -> npcl("Ya can improve yer Farming skill by getting some flags from me or Mrs. Winkin, inside. Then, head out to the fields and flag where ya think plants are, er, planted.").also { stage++ }
13 -> playerl("Is that it?").also { stage++ }
14 -> npcl("Not at all! There's more. When ya've placed yar flags, the farmers will collect them up and give out points. Ya can trade these points for seeds or experience at the shop inside.").also { stage++ }
15 -> playerl("Okay, that sounds great! I'll get planting, then.").also { stage++ }
16 -> npcl("Aye, but be careful where ya plant the flags. If there's no plant under yar flag, the farmer will keep it and they cost a pretty penny to buy more.").also { stage++ }
17 -> playerl("I see. Thanks for the help.").also { stage++ }
18 -> npcl("Bye, for now. If I have to say 'flag' one more time, I tell ya...").also { stage = END_DIALOGUE }
20, 21, 22, 220, 221, 222, 223, 23 -> handleFlags(componentID, buttonID, BLINKIN_FLAG_LINES)
30 -> npcl("Farmer Winkin? Well, last I 'eard he was heading into market with a fresh load of Ogleroots.").also { stage++ }
31 -> playerl("Ogleroots?").also { stage++ }
32 -> npcl("Aye! We get them growing a lot here in the farmyard. We dig 'em up and sell 'em to yar lot.").also { stage++ }
33 -> playerl("My lot?").also { stage++ }
34 -> npcl("Aye! Humans.").also { stage++ }
35 -> playerl("Oh, I see. Thanks!").also { stage++ }
36 -> npcl("Bye now!").also { stage = END_DIALOGUE }
// Not accessible in normal conversation, requires "buy-roots" right click
40 -> playerl("Do you have any Ogleroots to feed the rabbits?").also { stage++ }
41 -> npcl("I sure do. They'll cost ya 10 gold each. Any ya leave with will be returned to us, but ya'll get yer money back for 'em. How many do ya want?").also { stage++ }
42 -> {
player!!.setAttribute("runscript") { amount: Int ->
val price = Item(Items.COINS_995, 10 * amount)
if(player!!.inventory.containsItem(price) && player!!.inventory.remove(price)) {
if(player!!.inventory.add(Item(Items.OGLEROOT_12624, amount))) {
npcl("There ya go! Good luck!")
stage = END_DIALOGUE
} else {
npcl("TODO (crash): dialogue for no space for ogleroots")
// Refund the coins, can't fail because we just removed them
player!!.inventory.add(price)
stage = END_DIALOGUE
}
} else {
npcl("Sorry, ya can't afford that many. Come back when yer feeling a bit richer if ya like!")
stage = END_DIALOGUE
}
//ContentAPI.openDialogue(player!!, this, npc as Any)
}
player!!.dialogueInterpreter.sendInput(false, "Enter the amount:")
}
}
}
}
class WinkinDialogue : FarmerDialogue() {
override fun handle(componentID: Int, buttonID: Int) {
when(stage) {
0 -> npcl("Oh, hello there, dear. How can I help you?").also { stage++ }
1 -> options("Where are we?", "Have you got any flags?", "Do you have a spare spade?", "Do you have anything for trade?", "Nothing. I'm fine, thanks.").also { stage++ }
2 -> when(buttonID) {
1 -> playerl("Where are we?").also { stage = 10 }
2 -> playerl("Have you got any flags?").also { stage = 20 }
3 -> playerl("Do you have a spare spade?").also { stage = 30 }
4 -> playerl("Do you have anything for trade?").also { stage = 40 }
5 -> playerl("Nothing. I'm fine, thanks.").also { stage = 50 }
}
10 -> npcl("This is Winkin's Farm, dear.").also { stage++ }
11 -> playerl("Oh, I see. So where is Mr. Winkin?").also { stage++ }
12 -> npcl("Oh, he headed off to the market a while back. We look after the farm while he's gone.").also { stage++ }
13 -> npcl("Now, was there anything else you wanted?").also { stage = 1 }
20, 21, 22, 220, 221, 222, 223, 23 -> handleFlags(componentID, buttonID, WINKIN_FLAG_LINES)
30 -> npcl("Why, of course. I can sell you one for 5 gold pieces.").also { stage++ }
31 -> options("Okay, thanks.", "Actually, I've changed my mind.").also { stage++ }
32 -> when(buttonID) {
1 -> playerl("Okay, thanks.").also { stage = 320 }
2 -> playerl("Actually, I've changed my mind.").also { stage = 330 }
}
320 -> {
val price = Item(Items.COINS_995, 5)
if(player!!.inventory.containsItem(price) && player!!.inventory.remove(price)) {
npcl("Here you are, then.")
val spade = Item(Items.SPADE_952)
if(!player!!.inventory.add(spade)) {
GroundItemManager.create(spade, player)
}
stage = END_DIALOGUE
} else {
npcl("I'm afraid it looks like you don't have enough money, dear. Come back and see me again when you have a bit more.")
stage = END_DIALOGUE
}
}
330 -> npcl("Okay then.").also { stage = END_DIALOGUE }
40 -> npcl("Of course.").also { stage++ }
41 -> {
end()
player!!.interfaceManager.open(Component(686))
}
50 -> playerl("Nothing. I'm fine, thanks.").also { stage++ }
51 -> npcl("Okay, dear.").also { stage = END_DIALOGUE }
}
}
}

View file

@ -66,6 +66,10 @@ abstract class DialogueFile {
return npc(expr, *splitLines(msg!!))
}
open fun npcl(msg: String?): Component? {
return npc(*splitLines(msg!!))
}
/**
* Use the automatic linesplitting feature in DialUtils to produce player dialogues
* @param expr the FacialExpression to use, located in the FacialExpression enum.
@ -75,6 +79,10 @@ abstract class DialogueFile {
return player(expr, *splitLines(msg!!))
}
open fun playerl(msg: String?): Component? {
return player(*splitLines(msg!!))
}
open fun end(){
if(interpreter != null) interpreter!!.close()
}
@ -126,4 +134,4 @@ abstract class DialogueFile {
player?.dialogueInterpreter?.sendDialogue(*messages)
}
}
}

View file

@ -5,6 +5,7 @@ import core.game.component.Component
import core.game.content.dialogue.FacialExpression
import core.game.interaction.OptionHandler
import core.game.node.Node
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.plugin.Initializable
import core.plugin.Plugin
@ -27,7 +28,7 @@ class ToolLeprechaunHandler : OptionHandler() {
node ?: return false
when(option){
"exchange" -> player?.interfaceManager?.open(Component(Components.FARMING_TOOLS_125))
"teleport" -> player?.dialogueInterpreter?.sendDialogues(node.id,if(node.id == 4965) FacialExpression.FRIENDLY else FacialExpression.OLD_NORMAL, "I forgot how to do that it seems.")
"teleport" -> VinesweeperTeleport.teleport(node!! as NPC, player!!)
}
return true
}