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) { public Quest newInstance(Object object) {
ClassScanner.definePlugins(new CrateCutscenePlugin(), ClassScanner.definePlugins(new CrateCutscenePlugin(),
new MerlinCrystalPlugin(), new MerlinCrystalPlugin(),
new ArheinShopDialogue(),
new BeggarDialogue(), new BeggarDialogue(),
new CandleMakerDialogue(), new CandleMakerDialogue(),
new KingArthurDialogue(), new KingArthurDialogue(),
+70 -1
View File
@@ -3,6 +3,9 @@ package rs09
import api.PersistWorld import api.PersistWorld
import api.ShutdownListener import api.ShutdownListener
import api.StartupListener 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.JSONArray
import org.json.simple.JSONObject import org.json.simple.JSONObject
import org.json.simple.parser.JSONParser import org.json.simple.parser.JSONParser
@@ -131,5 +134,71 @@ class ServerStore : PersistWorld {
} }
return jArray 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
}
}
}
}