Completely rewrote the shop system.

Shop restocking now works properly, with per-item-in-shop granularity as well. (Thanos tool update released that allows defining this)
Shops properly restrict ironmen from buying from player stock/overstocked items
Fully functioning individualized player stock support (need to set a server config variable to enable, disabled by default. Specifically, set personalized_shops = true in the world section of the config.)
The shop pricing formula now scales up/down with the authentic ratios based on stock quantity.
Buying or selling, for example, 1000 of an item, now does the proper calculation for each individual item in the purchase/sale rather than exchanging the full 1000 for the initial price at the time. (For example, in the old shops, feather price at full stock was, say, 6gp. You could buy 1000 feathers at once for 6000gp. Authentically, the pricing for each feather should scale as the stock goes down. Now 1000 feathers at once costs a bit over 7k gp, despite the initial price remaining 6gp.)
General store player stock is now shared between all general stores
This commit is contained in:
Ceikry
2022-04-12 08:46:21 +00:00
committed by Ryan
parent f9aea18497
commit e958b8d255
202 changed files with 1910 additions and 2385 deletions
+49
View File
@@ -0,0 +1,49 @@
package api
import core.game.node.entity.player.Player
import rs09.game.system.command.Command
import rs09.game.system.command.CommandMapping
import rs09.game.system.command.Privilege
import rs09.tools.stringtools.colorize
/**
* An interface for writing content that allows the class to define commands.
*
* Includes methods [reject] and [notify] for notifying the player of a command rejection or some output text respectively.
*
* Includes the method [define] to define individual commands
*
* Commands should ideally be defined in the required [defineCommands] method.
*/
interface Commands : ContentInterface {
/**
* Glorified player.sendMessage with red text coloring. Please use this
* rather than just player.sendMessage for anything that involves informing the player
* of proper usage or invalid syntax. Throws an IllegalStateException and ends command execution immediately.
*/
fun reject(player: Player, vararg message: String){
for(msg in message) {
player.sendMessage(colorize("-->%R$msg"))
}
throw IllegalStateException()
}
/**
* Glorified player.sendMessage with black text coloring and an arrow. Use this when you need to
* notify/inform a player of some information from within the command without ending execution.
*/
fun notify(player: Player, message: String){
player.sendMessage(colorize("-->$message"))
}
/**
* Used to define commands. define("name",(optional) privilege override){ handle }
* @param name the name of the command. Example: ::example would be just "example"
* @param privilege the rights level needed to execute the command. Options are [Privilege.STANDARD], [Privilege.MODERATOR], [Privilege.ADMIN]. Defaults to [Privilege.STANDARD]
*/
fun define(name: String, privilege: Privilege = Privilege.STANDARD, handle: (Player, Array<String>) -> Unit){
CommandMapping.register(Command(name, privilege, handle))
}
fun defineCommands()
}
+16
View File
@@ -1,6 +1,7 @@
package api
import Cutscene
import com.moandjiezana.toml.Toml
import core.cache.def.impl.ItemDefinition
import core.cache.def.impl.SceneryDefinition
import core.game.component.Component
@@ -45,6 +46,7 @@ import rs09.game.content.global.GlobalKillCounter
import rs09.game.interaction.InteractionListeners
import rs09.game.system.SystemLogger
import rs09.game.system.config.ItemConfigParser
import rs09.game.system.config.ServerConfigParser
import rs09.game.world.GameWorld
import rs09.game.world.GameWorld.Pulser
import rs09.game.world.repository.Repository
@@ -714,6 +716,15 @@ fun findLocalNPCs(entity: Entity, ids: IntArray, distance: Int): List<NPC>{
return RegionManager.getLocalNpcs(entity, distance).filter { it.id in ids }.toList()
}
/**
* @param regionId the ID of the region
* @return a [ZoneBorders] encapsulating the entire region indicated by the provided regionId
*/
fun getRegionBorders(regionId: Int) : ZoneBorders
{
return ZoneBorders.forRegion(regionId)
}
/**
* Gets the value of an attribute key from the Entity's attributes store
* @param entity the entity to get the attribute from
@@ -1447,4 +1458,9 @@ fun Player.getCutscene(): Cutscene? {
fun Player.getCutsceneStage(): Int {
return getAttribute(this, Cutscene.ATTRIBUTE_CUTSCENE_STAGE, 0)
}
fun getServerConfig() : Toml
{
return ServerConfigParser.tomlData ?: Toml()
}
@@ -0,0 +1,7 @@
package api
interface ContentInterface {
/**
* A dummy/shell interface that allows us to more easily load content via class scanning
*/
}
@@ -0,0 +1,151 @@
package api
/**
* Used to generate interface settings hashes.
* Deprecates [core.game.container.access.BitregisterAssembler]
* @author Ceikry
*/
class IfaceSettingsBuilder {
/**
* Contains the value which should be sent in access mask packet.
*/
private var value = 0
/**
* Sets right click option settings. If specified option is not allowed, it
* might not appear in the context menu, and if it does, it will throw an error when clicked.
* @param optionId The option index.
*/
fun enableOption(optionId: Int): IfaceSettingsBuilder {
require(!(optionId < 0 || optionId > 9)) { "Option index must be 0-9." }
value = value or (0x1 shl optionId + 1)
return this
}
fun enableOptions(vararg ids: Int): IfaceSettingsBuilder {
for (i in ids.indices) {
enableOption(ids[i])
}
return this
}
fun enableOptions(ids: IntRange): IfaceSettingsBuilder {
for (i in ids.start..ids.endInclusive) {
enableOption(i)
}
return this
}
fun enableAllOptions(): IfaceSettingsBuilder {
for (i in 0..9) {
enableOption(i)
}
return this
}
fun enableOptions(vararg options: String?): IfaceSettingsBuilder {
for (i in options.indices) {
enableOption(i)
}
return this
}
/**
* Sets use on option settings. If nothing is allowed then 'use' option will
* not appear in right click menu.
*/
fun setUseOnSettings(
groundItems: Boolean,
npcs: Boolean,
objects: Boolean,
otherPlayer: Boolean,
selfPlayer: Boolean,
component: Boolean
): IfaceSettingsBuilder {
var useFlag = 0
if (groundItems) {
useFlag = useFlag or 0x1
}
if (npcs) {
useFlag = useFlag or 0x2
}
if (objects) {
useFlag = useFlag or 0x4
}
if (otherPlayer) {
useFlag = useFlag or 0x8
}
if (selfPlayer) {
useFlag = useFlag or 0x10
}
if (component) {
useFlag = useFlag or 0x20
}
value = value or (useFlag shl 11)
return this
}
/**
* Sets interface events depth. For example, we have inventory interface
* which is opened on gameframe interface (548) If depth is 1, then the
* clicks in inventory will also invoke click event handler scripts on
* gameframe interface. Setting depth to 2 also allows dragged items to
* leave the bounds of their container, useful for things such as bank tabs.
* @param depth The depth value.
*/
fun setInterfaceEventsDepth(depth: Int): IfaceSettingsBuilder {
require(!(depth < 0 || depth > 7)) { "depth must be 0-7." }
value = value and (0x7 shl 18).inv()
value = value or (depth shl 18)
return this
}
/**
* Allows items in this interface container to be switched around
* @return this builder
*/
fun enableSlotSwitch(): IfaceSettingsBuilder {
value = value or (1 shl 21)
return this
}
/**
* Allows items in this interface container to have a "Use" option
* @return this builder
*/
fun enableUseOption(): IfaceSettingsBuilder {
value = value or (1 shl 17)
return this
}
fun enableExamine(): IfaceSettingsBuilder {
value = value or (1 shl 9)
return this
}
/**
* Allows this component to have items used on it
* @return this builder
*/
fun enableUseOnSelf(): IfaceSettingsBuilder {
value = value or (1 shl 22)
return this
}
/**
* Allows this component to switch item slots with a slot that contains a null (empty slot)
* @return this builder
*/
fun enableNullSlotSwitch(): IfaceSettingsBuilder {
value = value or (1 shl 23)
return this
}
/**
* Gets the current value.
* @return The value.
*/
fun build(): Int {
return value
}
}
+6 -1
View File
@@ -2,7 +2,12 @@ package api
import core.game.node.entity.player.Player
interface LoginListener {
/**
* An interface for writing content that allows the class to execute some code when a player logs in.
*
* Login listeners are called *before* [PersistPlayer] data is parsed.
*/
interface LoginListener : ContentInterface {
/**
* NOTE: This should NOT reference any non-static class-local variables.
* If you need to access a player's specific instance, use an attribute.
+6 -1
View File
@@ -2,7 +2,12 @@ package api
import core.game.node.entity.player.Player
interface LogoutListener {
/**
* An interface for writing content that allows code to be executed by the class when a player logs out.
*
* Logout listeners are called *before* [PersistPlayer] data is saved.
*/
interface LogoutListener : ContentInterface {
/**
* NOTE: This should NOT reference any non-static class-local variables.
* If you need to access a player's specific instance, use an attribute.
+18
View File
@@ -0,0 +1,18 @@
package api
import core.game.node.entity.Entity
import core.game.world.map.Location
import core.game.world.map.zone.ZoneBorders
import core.game.world.map.zone.ZoneRestriction
/**
* Interface that allows a class to define a map area.
* Optionally-overridable methods include [getRestrictions], [areaEnter], [areaLeave] and [entityStep]
*/
interface MapArea {
fun defineAreaBorders() : Array<ZoneBorders>
fun getRestrictions() : Array<ZoneRestriction> {return arrayOf()}
fun areaEnter(entity: Entity) {}
fun areaLeave(entity: Entity, logout: Boolean) {}
fun entityStep(entity: Entity, location: Location, lastLocation: Location) {}
}
+8 -1
View File
@@ -3,7 +3,14 @@ package api
import core.game.node.entity.player.Player
import org.json.simple.JSONObject
interface PersistPlayer {
/**
* An interface for writing content that allows data to be saved and loaded from player saves.
*
* Parsing is called *after* any [LoginListener] is executed.
*
* Saving is called *after* any [LogoutListener] is executed.
*/
interface PersistPlayer : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
* You need to fetch a player's specific instance of the data and save from that.
@@ -1,6 +0,0 @@
package api
interface PersistWorld {
fun saveWorld()
fun parseWorld()
}
@@ -2,7 +2,10 @@ package api
import rs09.game.system.SystemLogger
interface ShutdownListener {
/**
* An interface for writing content that allows the class to execute code as the server is shutting down
*/
interface ShutdownListener : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
*/
@@ -2,7 +2,10 @@ package api
import rs09.game.system.SystemLogger
interface StartupListener {
/**
* An interface for writing content that allows the class to execute code when the server is started.
*/
interface StartupListener : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
*/
+4 -1
View File
@@ -1,6 +1,9 @@
package api
interface TickListener {
/**
* An interface for writing content that allows the class to be updated each tick.
*/
interface TickListener : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
* TickListeners are generally for NON-player, WORLD tick events.