Implemented Arhein daily pineapple purchase

Implemented Arhein daily seaweed purchase
Implemented NPCItemMemory data store for periodically resetting maps of players to npc:item pairs
This commit is contained in:
Lila Hioh
2022-07-21 12:51:21 +00:00
committed by Ryan
parent 466d6207f7
commit ac92932d09
5 changed files with 288 additions and 187 deletions
@@ -1,185 +0,0 @@
package core.game.content.quest.members.merlinscrystal;
import core.game.content.dialogue.DialoguePlugin;
import core.game.content.dialogue.FacialExpression;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.quest.Quest;
/**
* Represents the dialogue plugin used for the arhein npc.
* @author 'Vexia
* @version 1.0
*/
public final class ArheinShopDialogue extends DialoguePlugin {
/**
* Constructs a new {@code ArheinShopDialogue} {@code Object}.
*/
public ArheinShopDialogue() {
/**
* empty.
*/
}
/**
* Constructs a new {@code ArheinShopDialogue} {@code Object}.
* @param player the player.
*/
public ArheinShopDialogue(Player player) {
super(player);
}
@Override
public DialoguePlugin newInstance(Player player) {
return new ArheinShopDialogue(player);
}
@Override
public boolean open(Object... args) {
npc = (NPC) args[0];
if (args.length > 1) {
interpreter.sendDialogues(npc, null, "Hey buddy! Get away from my ship alright?");
stage = 2000;
return true;
}
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Hello! Would you like to trade? I've a variety of wares", "for sale!");
stage = 0;
return true;
}
@Override
public boolean handle(int interfaceId, int buttonId) {
Quest quest = player.getQuestRepository().getQuest("Merlin's Crystal");
switch (stage) {
case 0:
interpreter.sendOptions("Select an Option", "Let's trade.", "No thank you.", "Is that your ship?");
stage = 1;
break;
case 1:
switch (buttonId) {
case 1:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Let's trade.");
stage = 100;
break;
case 2:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "No thanks.");
stage = 200;
break;
case 3:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Is that your ship?");
stage = 300;
break;
}
break;
case 100:
end();
npc.openShop(player);
break;
case 300:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Yes, I use it to make deliveries to my customers up and", "down the coast. These crates here are all ready for my", "next trip.");
stage = 301;
break;
case 301:
interpreter.sendOptions("Select an Option", "Where do you deliver to?", "Are you rich then?", "Do you deliver to the fort just down the coast?");
stage = 303;
break;
case 303:
switch (buttonId) {
case 1:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Where do you deliver to?");
stage = 1000;
break;
case 2:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Are you rich then?");
stage = 2500;
break;
case 3:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Do you deliver to the fort just down the coast?");
stage = 3000;
break;
}
break;
case 1000:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Various places up and down the coast. Mostly Karamja", "and Port Sarim.");
stage = 1001;
break;
case 1001:
interpreter.sendOptions("Select an Option", "I don't suppose I could get a lift anywhere?", "Well, good luck with your business.");
stage = 1002;
break;
case 1002:
switch (buttonId) {
case 1:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I don't suppose I could get a lift anywhere?");
stage = 1003;
break;
case 2:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Well, good luck with your business.");
stage = 2700;
break;
}
break;
case 1003:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Sorry pal, but I'm afraid I'm not quite ready to sail yet.");
stage = 1004;
break;
case 1004:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "I'm waiting on a big delivery of candles which I need to", "deliver further along the coast.");
stage = 1005;
break;
case 1005:
end();
break;
case 2500:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Business is going reasonably well... I wouldn't say I was the", "richest of merchants every, but I'm doing fairly well all", "things considered.");
stage = 2501;
break;
case 2501:
end();
break;
case 2700:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Thanks buddy!");
stage = 2701;
break;
case 2701:
end();
break;
case 200:
end();
break;
case 2000:
interpreter.sendDialogues(player, null, "Yeah... uh... sorry...");
stage = 2001;
break;
case 2001:
end();
break;
case 3000:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Yes, I do have orders to deliver there from time to", "time. I think I may have some bits and pieces for them", "when I leave here next actually.");
if (quest.getStage(player) == 30 || quest.getStage(player) == 40) {
stage = 3001;
} else {
stage = 2701;
}
break;
case 3001:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Can you drop me off on the way down please?");
stage = 3002;
break;
case 3002:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "I don't think Sir Mordred would like that. He wants as", "few outsiders visiting as possible. I wouldn't want to lose", "his business.");
quest.setStage(player, 40);
stage = 2701;
break;
}
return true;
}
@Override
public int[] getIds() {
return new int[] { 563 };
}
}
@@ -79,7 +79,6 @@ public final class MerlinCrystal extends Quest {
public Quest newInstance(Object object) {
ClassScanner.definePlugins(new CrateCutscenePlugin(),
new MerlinCrystalPlugin(),
new ArheinShopDialogue(),
new BeggarDialogue(),
new CandleMakerDialogue(),
new KingArthurDialogue(),
@@ -3,6 +3,9 @@ package rs09
import api.PersistWorld
import api.ShutdownListener
import api.StartupListener
import api.getItemName
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import org.json.simple.JSONArray
import org.json.simple.JSONObject
import org.json.simple.parser.JSONParser
@@ -131,5 +134,71 @@ class ServerStore : PersistWorld {
}
return jArray
}
/** NPCItemMemory
* These next methods handle the NPCItemMemory database. these are server-stored JSON objects,
* which are based on an npc and an item.
* each NPC can have a JSON object for itself for any item.
* the NPC Item object can store integer values for individual players, using their names as keys.
* the first methods handle the serverstore filename and accessing the JSON object.
* these methods are wrapped by more convenient ones that allow access for a particular player, see below.
*/
fun NPCItemFilename(npc: Int, item: Int, period: String = "daily"): String {
val itemName = getItemName(item).lowercase().replace(" ","-")
val npcName = NPC(npc).name.lowercase()
return "$period-$npcName-$itemName"
}
fun NPCItemMemory(npc: Int, item: Int, period: String = "daily"): JSONObject {
return getArchive(NPCItemFilename(npc,item,period))
}
/** NPCItemMemory Player Access
* These next functions handle individual player access to the NPC Item Memory database.
* each of the functions has at least 3 mandatory arguments: npc, item, player.
* These functions can be used to allow players to access a daily- or weekly-reset stock of a particular item overseen by a particular NPC.
*
* note that none of these functions perform tangible actions on the player.
* whoever calls these functions is responsible for handling tangible actions like putting items in an inventory.
* these functions simply return and update the NPCItemMemory database numbers in useful ways.
*/
/** getNPCItemStock:
* gets the available stock of a particular item at a particular NPC for a particular player.
*/
fun getNPCItemStock(npc: Int, item: Int, limit: Int, player: Player, period: String = "daily"): Int {
val itemMemory = NPCItemMemory(npc,item)
val key = player.name
var stock = limit-itemMemory.getInt(key)
stock = maxOf(stock,0)
return stock
}
/** getNPCItemAmount:
* gets the usable amount of a particular item from a particular npc for a particular player.
* this is essentially just a requested amount cut to conform to stock and nonnegative requirements.
* what this function does is it takes a requested amount, cuts it down to be equal to available stock if
* available stock is less than the requested amount, and then additionally sets a minimum of 0.
*/
fun getNPCItemAmount(npc: Int, item: Int, limit: Int, player: Player, amount: Int, period: String = "daily"): Int {
val stock = getNPCItemStock(npc,item,limit,player,period)
var realamount = minOf(amount,stock)
realamount = maxOf(realamount,0)
return realamount
}
/** addNPCItemAmount:
* this function updates the NPC Item Memory database entry for a particular player.
* it is intended to be called after all the actions that use the number have been successfully completed.
* this function will *add* the amount argument to the current npc item memory entry for that player. this means that
* the amount argument is the amount to add, not the absolute final amount.
* this function will handle correctly setting the limit and any other required semantics.
*/
fun addNPCItemAmount(npc: Int, item: Int, limit: Int, player: Player, amount: Int, period: String = "daily") {
val itemMemory = NPCItemMemory(npc,item,period)
val key = player.name
var realamount = itemMemory.getInt(key) + amount
realamount = minOf(realamount,limit)
realamount = maxOf(realamount,0)
itemMemory[key] = realamount
}
}
}
@@ -0,0 +1,189 @@
package rs09.game.content.dialogue.region.catherby
import api.*
import core.game.content.dialogue.DialoguePlugin
import core.game.content.dialogue.FacialExpression
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.plugin.Initializable
import core.tools.StringUtils
import org.rs09.consts.NPCs
import rs09.ServerStore
import rs09.game.content.dialogue.Topic
import rs09.tools.END_DIALOGUE
import org.rs09.consts.Items
import rs09.game.content.quest.members.merlinsquest.ArheinMCDialogue
/**
* @author lila
*/
/**
* Arhein dialogue
* This class handles all dialogue from the Catherby dock NPC Arhein.
*
* Arhein is relevant to:
* Merlin's Crystal quest
* crafting/glassmaking - via selling seaweed
* farming/supercompost - via selling pineapples
*/
@Initializable
class ArheinDialogue(player: Player? = null) : DialoguePlugin(player) {
val limits = mapOf(
Items.PINEAPPLE_2114 to 40,
Items.SEAWEED_401 to 80
)
val period = "daily"
// this function handles Arhein's specialty stock (pineapple and seaweed) transactions.
fun getGoods(requestedItem: Int, requestedAmount: Int): Int {
val price = 2
val afford = player.getInventory().getAmount(Items.COINS_995) / price
var realamount = minOf(requestedAmount, afford)
val exactafford = (realamount == afford) && (afford == freeSlots(player) + 1)
realamount = minOf(realamount,if(exactafford) realamount else freeSlots(player))
realamount = ServerStore.getNPCItemAmount(NPCs.ARHEIN_563, requestedItem, limits.getOrDefault(requestedItem,0), player, realamount, period)
if (removeItem(player, Item(Items.COINS_995, realamount * price), Container.INVENTORY)) {
if (addItem(player, requestedItem, realamount, Container.INVENTORY)) {
ServerStore.addNPCItemAmount(NPCs.ARHEIN_563, requestedItem, limits.getOrDefault(requestedItem,0), player, realamount, period)
return realamount
}
}
return 0
}
// this function handles selecting which of the specialty stock items (pineapple or seaweed) to use.
fun selectGoods(requestedItem: Int) {
this.goods = requestedItem
this.stock = ServerStore.getNPCItemStock(NPCs.ARHEIN_563,this.goods,limits.getOrDefault(this.goods,0),player,period)
}
// some class variables that handle the special stock (pineapples and seaweed)
private var goods = Items.PINEAPPLE_2114
private var goodsMessage = ""
private var goodsName = ""
private var stock = 0
/** Arhein Dialogue Opener
* If the opener is called with nontrivial arguments, then this indicates that the player tried to board the ship without permission.
* In that case, Arhein tells the player to bugger off.
* Otherwise, start the normal dialogue.
* Before all this, we check and initialize some important information for Arhein's specialty stock.
*/
override fun open(vararg args: Any?): Boolean {
npc = args[0] as NPC
if (args.size > 1) {
npcl(FacialExpression.ANGRY, "Hey buddy! Get away from my ship alright?")
stage = 701
} else {
npcl(FacialExpression.HAPPY, "Hello! Would you like to trade? I've a variety of wares for sale!")
stage = 1
}
return true
}
/** Arhein Dialogue Handler
* Handler for the dialogue. the handler determines dialogue content and flow,
* as well as some specialty operations to handle special things like quest progress and specialty stock.
*/
override fun handle(interfaceId: Int, buttonId: Int): Boolean {
when(stage) {
1 -> showTopics(
Topic("Let's Trade.", 7),
Topic("I hear that you sell pineapples.",800),
Topic("I hear that you sell seaweed.",900),
Topic("No thank you.", END_DIALOGUE),
Topic("Is that your ship?", 100)
)
7 -> npc.openShop(player).also { stage = END_DIALOGUE }
100 -> npcl(FacialExpression.NEUTRAL, "Yes, I use it to make deliveries to my customers up and down the coast. These crates here are all ready for my next trip.").also { stage++ }
101 -> showTopics(
Topic("Where do you deliver to?",120),
Topic("Are you rich then?", 110),
Topic("Do you deliver to the fort just down the coast?",500)
)
110 -> npcl(FacialExpression.NEUTRAL, "Business is going reasonably well... I wouldn't say I was the richest of merchants ever, but I'm doing fairly well all things considered.").also { stage = END_DIALOGUE }
120 -> npcl(FacialExpression.NEUTRAL, "Various places up and down the coast. Mostly Karamja and Port Sarim.").also { stage++ }
121 -> showTopics(
Topic("I don't suppose I could get a lift anywhere?",140),
Topic("Well, good luck with your business.",130)
)
130 -> npcl(FacialExpression.HAPPY,"Thanks buddy!").also { stage = END_DIALOGUE }
140 -> npcl(FacialExpression.GUILTY,"Sorry pal, but I'm afraid I'm not quite ready to sail yet.").also { stage++ }
141 -> npcl(FacialExpression.NEUTRAL,"I'm waiting on a big delivery of candles which I need to deliver further along the coast.").also { stage = END_DIALOGUE }
500 -> npcl(FacialExpression.HALF_THINKING, "Yes, I do have orders to deliver there from time to time. I think I may have some bits and pieces for them when I leave here next actually.").also {
val queststage = player.questRepository.getStage("Merlin's Crystal")
if(queststage == 30 || queststage == 40) {
loadFile(ArheinMCDialogue(queststage))
} else {
stage = END_DIALOGUE
}
}
701 -> playerl(FacialExpression.GUILTY,"Yeah... uh... sorry...").also{ stage = END_DIALOGUE }
800 -> {
// Pineapples
selectGoods(Items.PINEAPPLE_2114)
// and now the conditional block to handle cases
if(stock==0) {
npcl(FacialExpression.SAD,"Actually, I've run out. Come back tomorrow and I should have some more.")
stage = END_DIALOGUE
} else {
// approximately authentic dialogue prompt for buying pineapples from arhein. (closest known dialogues do not make grammatical sense when spliced.)
this.goodsMessage = "I certainly do! I've got ${stock} in stock, going for 2 coins each. How many would you like?"
npcl(FacialExpression.HAPPY,this.goodsMessage)
this.goodsName = if(stock==1) getItemName(this.goods) else StringUtils.plusS(getItemName(this.goods))
stage = 1200
}
}
900 -> {
// Seaweed
selectGoods(Items.SEAWEED_401)
// and now the conditional block to handle cases
if(stock==0) {
npcl(FacialExpression.SAD,"Actually, I've run out. Come back tomorrow and I should have some more.")
stage = END_DIALOGUE
} else {
// approximately authentic dialogue prompt for buying seaweed from arhein. (closest known dialogues do not make grammatical sense when spliced.)
this.goodsMessage = "I certainly do! I've ${stock} at the moment and they cost 2 coins each. How many would you like?"
npcl(FacialExpression.HAPPY,this.goodsMessage)
this.goodsName = getItemName(this.goods)
stage = 1200
}
}
1200 -> {
// arhein specialty items sales
val goodsPrompt = "Arhein has ${this.stock} ${this.goodsName}. How many would you like to buy?"
sendInputDialogue(player,InputType.AMOUNT, goodsPrompt) { value ->
val amountReceived = getGoods(this.goods,Integer.parseInt(value.toString()))
var exitMsg = ""
if(amountReceived==this.stock) {
exitMsg = "Here you go! I've run out for now. Come again tomorrow and I should have more."
} else if (amountReceived>0) {
// inauthentic exit dialogue for arhein's pineapple/seaweed sales. documented dialogue that i found indicates that there is no exit message.
// i kept the spirit of arhein's personality, using "buddy!" that is known from another line.
exitMsg = "Here you go, buddy!"
} else {
// same deal here, inauthentic exit dialogue based on arhein's authentic personality.
exitMsg = "Take care, buddy!"
}
npcl(FacialExpression.HAPPY,exitMsg)
}
stage = END_DIALOGUE
}
}
return true
}
// finale funz - newInstance, getIds
override fun newInstance(player: Player?): DialoguePlugin {
return ArheinDialogue(player)
}
override fun getIds(): IntArray {
return intArrayOf(NPCs.ARHEIN_563)
}
}
@@ -0,0 +1,29 @@
package rs09.game.content.quest.members.merlinsquest
import core.game.content.dialogue.FacialExpression
import rs09.game.content.dialogue.DialogueFile
import rs09.tools.END_DIALOGUE
import rs09.tools.START_DIALOGUE
/**
* @author lila
*/
/**
* This class handles dialogue for Arhein for the Merlin's Crystal quest
*/
class ArheinMCDialogue (val questStage: Int) : DialogueFile() {
override fun handle(componentID: Int, buttonID: Int) {
when (stage) {
START_DIALOGUE -> playerl(FacialExpression.NEUTRAL, "Can you drop me off on the way down please?").also { stage++ }
1 -> {
npcl(FacialExpression.ANNOYED,"I don't think Sir Mordred would like that. He wants as few outsiders visiting as possible. I wouldn't want to lose his business.")
val quest = player!!.questRepository.getQuest("Merlin's Crystal")
player!!.questRepository.setStage(quest, 40)
stage = END_DIALOGUE
}
}
}
}