mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-05 08:40:08 +00:00
Add shops support.
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
require 'java'
|
||||||
|
|
||||||
|
java_import 'org.apollo.cache.def.ItemDefinition'
|
||||||
|
|
||||||
|
# A currency that can be used to purchase items in a Shop.
|
||||||
|
class Currency
|
||||||
|
attr_reader :name
|
||||||
|
|
||||||
|
# Creates the Currency.
|
||||||
|
def initialize(id, name = ItemDefinition.lookup(id).name)
|
||||||
|
fail 'Currency must have a name.' if name.nil?
|
||||||
|
@id = id
|
||||||
|
@name = name.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
# Adds the specified amount of this `Currency` to the specified `Player`'s inventory.
|
||||||
|
def add(player, amount)
|
||||||
|
player.inventory.add(@id, amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Removes the specified amount of this `Currency` from the specified `Player`'s inventory.
|
||||||
|
def remove(player, amount)
|
||||||
|
player.inventory.remove(@id, amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Gets the amount of this Currency in the specified player's inventory.
|
||||||
|
def total(player)
|
||||||
|
player.inventory.get_amount(@id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sell_value(id)
|
||||||
|
(ItemDefinition.lookup(id).value * 0.60).floor
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<plugin>
|
||||||
|
<id>shops</id>
|
||||||
|
<version>0.1</version>
|
||||||
|
<name>Shops</name>
|
||||||
|
<description>Adds shop support.</description>
|
||||||
|
<authors>
|
||||||
|
<author>Stuart</author>
|
||||||
|
<author>Major</author>
|
||||||
|
</authors>
|
||||||
|
<scripts>
|
||||||
|
<script>currency.rb</script>
|
||||||
|
<script>shop.rb</script>
|
||||||
|
<script>shop_item.rb</script>
|
||||||
|
<script>shops.rb</script>
|
||||||
|
</scripts>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>util</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</plugin>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
require 'java'
|
||||||
|
|
||||||
|
java_import 'org.apollo.game.model.inv.Inventory'
|
||||||
|
|
||||||
|
# A shop containing items that can be sold.
|
||||||
|
class Shop
|
||||||
|
attr_reader :buys, :currency, :items, :inventory, :name, :npc_options
|
||||||
|
|
||||||
|
def initialize(name, items, currency, options, buys)
|
||||||
|
@name = name
|
||||||
|
@items = items
|
||||||
|
@currency = currency
|
||||||
|
@buys = buys
|
||||||
|
@npc_options = options
|
||||||
|
@inventory = Inventory.new(DEFAULT_CAPACITY, Inventory::StackMode::STACK_ALWAYS)
|
||||||
|
|
||||||
|
items.each { |item| @inventory.add(item.id, item.amount) }
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# The `Currency` used by default.
|
||||||
|
DEFAULT_CURRENCY = Currency.new(995, 'money')
|
||||||
|
|
||||||
|
# The default capacity of a shop.
|
||||||
|
DEFAULT_CAPACITY = 30
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
require 'java'
|
||||||
|
|
||||||
|
java_import 'org.apollo.cache.def.ItemDefinition'
|
||||||
|
|
||||||
|
java_import 'org.apollo.game.model.Item'
|
||||||
|
|
||||||
|
# An Item in a Shop.
|
||||||
|
class ShopItem
|
||||||
|
attr_reader :amount, :cost, :id, :name
|
||||||
|
|
||||||
|
# Creates the ShopItem.
|
||||||
|
def initialize(id, amount, cost = nil)
|
||||||
|
definition = ItemDefinition.lookup(id)
|
||||||
|
@id = id
|
||||||
|
@amount = amount
|
||||||
|
@cost = cost.nil? ? definition.value : cost
|
||||||
|
@name = definition.name
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
require 'java'
|
||||||
|
|
||||||
|
java_import 'org.apollo.cache.def.ItemDefinition'
|
||||||
|
|
||||||
|
java_import 'org.apollo.game.action.DistancedAction'
|
||||||
|
java_import 'org.apollo.game.message.impl.SetWidgetTextMessage'
|
||||||
|
java_import 'org.apollo.game.message.handler.ItemVerificationHandler'
|
||||||
|
java_import 'org.apollo.game.model.inv.SynchronizationInventoryListener'
|
||||||
|
java_import 'org.apollo.game.model.inter.InterfaceListener'
|
||||||
|
|
||||||
|
# The hash of npc ids to Shops.
|
||||||
|
SHOPS = {}
|
||||||
|
|
||||||
|
# Creates the Shop from the specified Hash.
|
||||||
|
def create_shop(hash)
|
||||||
|
unless hash.has_keys?(:items, :name, :npc_id)
|
||||||
|
fail 'Shop name, npc, and items must be specified to create a shop.'
|
||||||
|
end
|
||||||
|
|
||||||
|
npc_id, name = hash[:npc_id], hash[:name]
|
||||||
|
currency = hash[:currency] || DEFAULT_CURRENCY
|
||||||
|
|
||||||
|
options = hash[:npc_options] || [1]
|
||||||
|
buys = hash[:buys] || :own
|
||||||
|
|
||||||
|
items = hash.delete(:items).collect { |data| ShopItem.new(*data) }
|
||||||
|
shop = Shop.new(name, items, currency, options, buys)
|
||||||
|
|
||||||
|
SHOPS[npc_id] = shop
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# The sidebar id for the inventory, when a Shop window is open.
|
||||||
|
INVENTORY_SIDEBAR = 3822
|
||||||
|
|
||||||
|
# The container id for the above inventory, when a Shop window is open.
|
||||||
|
INVENTORY_CONTAINER = 3823
|
||||||
|
|
||||||
|
# The Shop interface id.
|
||||||
|
SHOP_INTERFACE = 3824
|
||||||
|
|
||||||
|
# The container id for the Shop interface.
|
||||||
|
SHOP_CONTAINER = 3900
|
||||||
|
|
||||||
|
# The widget that displays the shop name.
|
||||||
|
SHOP_NAME_WIDGET = 3901
|
||||||
|
|
||||||
|
# The delay before a Shop is opened when the Player is in range of the Npc, in ticks.
|
||||||
|
SHOP_OPEN_DELAY = 0
|
||||||
|
|
||||||
|
# The distance, in tiles, the Player must reach before a Shop can be opened.
|
||||||
|
SHOP_DISTANCE = 1
|
||||||
|
|
||||||
|
# An `InventorySupplier` for a `Shop`.
|
||||||
|
class ShopInventorySupplier
|
||||||
|
java_implements ItemVerificationHandler::InventorySupplier
|
||||||
|
|
||||||
|
def getInventory(player)
|
||||||
|
shop = player.open_shop
|
||||||
|
shop == -1 ? nil : SHOPS[shop].inventory
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# An `InventorySupplier` for a `Player` with the shop window open.
|
||||||
|
class PlayerInventorySupplier
|
||||||
|
java_implements ItemVerificationHandler::InventorySupplier
|
||||||
|
|
||||||
|
def getInventory(player)
|
||||||
|
player.open_shop == -1 ? nil : player.inventory
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
ItemVerificationHandler.add_inventory(SHOP_CONTAINER, ShopInventorySupplier.new)
|
||||||
|
ItemVerificationHandler.add_inventory(INVENTORY_CONTAINER, PlayerInventorySupplier.new)
|
||||||
|
|
||||||
|
# A DistancedAction causing a Player to open a shop.
|
||||||
|
class OpenShopAction < DistancedAction
|
||||||
|
attr_reader :player, :npc, :shop
|
||||||
|
|
||||||
|
# Creates the OpenShopAction.
|
||||||
|
def initialize(player, npc, shop)
|
||||||
|
super(SHOP_OPEN_DELAY, true, player, npc.position, 1)
|
||||||
|
@npc = npc
|
||||||
|
@shop = shop
|
||||||
|
end
|
||||||
|
|
||||||
|
# Executes this DistancedAction, opening the shop.
|
||||||
|
def executeAction
|
||||||
|
mob.interacting_mob = @npc
|
||||||
|
open_shop(mob, @npc.id)
|
||||||
|
stop
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns whether or not this DistancedAction is equal to the specified Object.
|
||||||
|
def equals(other)
|
||||||
|
get_class == other.get_class && @npc == other.npc && @shop == other.shop
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# An InterfaceListener for when a Shop is closed.
|
||||||
|
class ShopCloseInterfaceListener
|
||||||
|
java_implements InterfaceListener
|
||||||
|
|
||||||
|
# Creates the ShopCloseInterfaceListener.
|
||||||
|
def initialize(player, inventory_listener, shop_listener)
|
||||||
|
@player = player
|
||||||
|
@inventory_listener = inventory_listener
|
||||||
|
@shop_listener = shop_listener
|
||||||
|
end
|
||||||
|
|
||||||
|
# Executed when the Shop interface is closed.
|
||||||
|
def interface_closed
|
||||||
|
@player.inventory.remove_listener(@inventory_listener)
|
||||||
|
SHOPS[@player.open_shop].inventory.remove_listener(@shop_listener)
|
||||||
|
|
||||||
|
@player.open_shop = -1
|
||||||
|
@player.reset_interacting_mob
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Intercept the npc action message.
|
||||||
|
on :message, :first_npc_action do |player, message|
|
||||||
|
npc = $world.npc_repository.get(message.index)
|
||||||
|
|
||||||
|
if SHOPS.key?(npc.id)
|
||||||
|
shop = SHOPS[npc.id]
|
||||||
|
|
||||||
|
valid = shop.npc_options.empty? || shop.npc_options.include?(message.option)
|
||||||
|
player.start_action(OpenShopAction.new(player, npc, SHOPS[npc.id])) if valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Opens the Shop registered to the specified npc.
|
||||||
|
def open_shop(player, npc)
|
||||||
|
shop = SHOPS[npc]
|
||||||
|
fail "No shop registered to npc #{npc} exists." if shop.nil?
|
||||||
|
|
||||||
|
player.open_shop = npc
|
||||||
|
|
||||||
|
inventory_listener = SynchronizationInventoryListener.new(player, INVENTORY_CONTAINER)
|
||||||
|
shop_listener = SynchronizationInventoryListener.new(player, SHOP_CONTAINER)
|
||||||
|
|
||||||
|
player_inventory, shop_inventory = player.inventory, shop.inventory
|
||||||
|
|
||||||
|
player_inventory.add_listener(inventory_listener)
|
||||||
|
player_inventory.force_refresh
|
||||||
|
|
||||||
|
shop_inventory.add_listener(shop_listener)
|
||||||
|
shop_inventory.force_refresh
|
||||||
|
|
||||||
|
player.send(SetWidgetTextMessage.new(SHOP_NAME_WIDGET, shop.name))
|
||||||
|
|
||||||
|
listener = ShopCloseInterfaceListener.new(player, inventory_listener, shop_listener)
|
||||||
|
player.interface_set.open_window_with_sidebar(listener, SHOP_INTERFACE, INVENTORY_SIDEBAR)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Intercept the Item action.
|
||||||
|
on :message, :item_action do |player, message|
|
||||||
|
interface = message.interface_id
|
||||||
|
|
||||||
|
if interface == INVENTORY_CONTAINER || interface == SHOP_CONTAINER
|
||||||
|
if player.open_shop == -1 || !SHOPS.key?(player.open_shop)
|
||||||
|
message.terminate
|
||||||
|
next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shop = SHOPS[player.open_shop]
|
||||||
|
inventory = shop.inventory
|
||||||
|
currency = shop.currency
|
||||||
|
slot = message.slot
|
||||||
|
|
||||||
|
player_inventory = player.inventory
|
||||||
|
|
||||||
|
if interface == INVENTORY_CONTAINER
|
||||||
|
id = message.id
|
||||||
|
contains = inventory.contains(id)
|
||||||
|
|
||||||
|
if !shop.buys == :none || shop.buys == :own && !contains
|
||||||
|
player.send_message('You can\'t sell this item to this shop.')
|
||||||
|
message.terminate
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
if !contains && inventory.free_space == 0
|
||||||
|
player.send_message('The shop is currently full at the moment.')
|
||||||
|
message.terminate
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
item = player_inventory.get(slot)
|
||||||
|
value = currency.sell_value(id)
|
||||||
|
|
||||||
|
option = message.option
|
||||||
|
if option == 1
|
||||||
|
player.send_message("#{item.definition.name}: shop will buy for #{value} #{currency.name}.")
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
sell_amount = case option
|
||||||
|
when 2 then 1
|
||||||
|
when 3 then 5
|
||||||
|
when 4 then 10
|
||||||
|
else next
|
||||||
|
end
|
||||||
|
|
||||||
|
available = player_inventory.get_amount(id)
|
||||||
|
sell_amount = available if sell_amount > available
|
||||||
|
|
||||||
|
total_value = (value * sell_amount).floor
|
||||||
|
|
||||||
|
player_inventory.remove(id, sell_amount)
|
||||||
|
inventory.add(id, sell_amount)
|
||||||
|
currency.add(player, total_value) if total_value > 0
|
||||||
|
|
||||||
|
message.terminate
|
||||||
|
elsif interface == SHOP_CONTAINER
|
||||||
|
buy(shop, player, message, currency)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Buys the item from the `Shop`.
|
||||||
|
def buy(shop, player, message, currency)
|
||||||
|
inventory, slot = shop.inventory, message.slot
|
||||||
|
shop_item, invent_item = shop.items[slot], inventory.get(slot)
|
||||||
|
|
||||||
|
id = shop_item.id
|
||||||
|
|
||||||
|
option = message.option
|
||||||
|
if option == 1
|
||||||
|
player.send_message("#{shop_item.name}: currently costs #{shop_item.cost} #{currency.name}.")
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
buy_amount = case option
|
||||||
|
when 2 then 1
|
||||||
|
when 3 then 5
|
||||||
|
when 4 then 10
|
||||||
|
else next
|
||||||
|
end
|
||||||
|
|
||||||
|
no_stock = false
|
||||||
|
if buy_amount > invent_item.amount
|
||||||
|
buy_amount = invent_item.amount
|
||||||
|
no_stock = true
|
||||||
|
end
|
||||||
|
|
||||||
|
player_inventory = player.inventory
|
||||||
|
has_item = player_inventory.get_amount(id) == 0
|
||||||
|
|
||||||
|
definition = invent_item.definition
|
||||||
|
space_required = if definition.stackable && has_item then 0
|
||||||
|
elsif !definition.stackable then buy_amount
|
||||||
|
else 1
|
||||||
|
end
|
||||||
|
|
||||||
|
free_slots = player_inventory.free_slots
|
||||||
|
not_enough_space = false
|
||||||
|
|
||||||
|
if space_required > free_slots
|
||||||
|
not_enough_space = true
|
||||||
|
buy_amount = free_slots
|
||||||
|
end
|
||||||
|
|
||||||
|
total_currency = shop.currency.total(player)
|
||||||
|
too_poor = false
|
||||||
|
total_cost = buy_amount * shop_item.cost
|
||||||
|
|
||||||
|
if total_cost > total_currency
|
||||||
|
buy_amount = (total_currency / shop_item.cost).floor
|
||||||
|
too_poor = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if buy_amount > 0
|
||||||
|
currency.remove(player, buy_amount * shop_item.cost)
|
||||||
|
player_inventory.add(id, buy_amount)
|
||||||
|
|
||||||
|
keep = invent_item.amount == buy_amount && shop.buys == :own
|
||||||
|
keep ? inventory.set(slot, Item.new(id, 0)) : inventory.remove(id, buy_amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
warning = if too_poor then "You don't have enough #{currency.name}."
|
||||||
|
elsif no_stock then 'The shop has run out of stock.'
|
||||||
|
elsif not_enough_space then 'You don\'t have enough inventory space.'
|
||||||
|
end
|
||||||
|
|
||||||
|
player.send_message(warning) unless warning.nil?
|
||||||
|
message.terminate
|
||||||
|
end
|
||||||
|
|
||||||
|
# Declares the open_shop attribute, which contains the id of the currently open shop.
|
||||||
|
declare_attribute(:open_shop, -1)
|
||||||
@@ -25,7 +25,7 @@ public final class ItemVerificationHandler extends MessageHandler<InventoryItemM
|
|||||||
* @author Major
|
* @author Major
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public static interface InventorySupplier {
|
public interface InventorySupplier {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the appropriate {@link Inventory}.
|
* Gets the appropriate {@link Inventory}.
|
||||||
@@ -33,7 +33,7 @@ public final class ItemVerificationHandler extends MessageHandler<InventoryItemM
|
|||||||
* @param player The {@link Player} who prompted the verification call.
|
* @param player The {@link Player} who prompted the verification call.
|
||||||
* @return The inventory. Must not be {@code null}.
|
* @return The inventory. Must not be {@code null}.
|
||||||
*/
|
*/
|
||||||
public Inventory getInventory(Player player);
|
Inventory getInventory(Player player);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ public final class ItemVerificationHandler extends MessageHandler<InventoryItemM
|
|||||||
Inventory inventory = supplier.getInventory(player);
|
Inventory inventory = supplier.getInventory(player);
|
||||||
|
|
||||||
int slot = message.getSlot();
|
int slot = message.getSlot();
|
||||||
if (slot < 0 || slot >= inventory.capacity()) {
|
if (inventory == null || slot < 0 || slot >= inventory.capacity()) {
|
||||||
message.terminate();
|
message.terminate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user