rewrite: Shop and economy system.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from . import base
|
||||
from . import colours
|
||||
|
||||
@@ -1,81 +1,225 @@
|
||||
from typing import Type, TYPE_CHECKING
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
import discord
|
||||
from discord.ui.button import Button
|
||||
|
||||
from meta import LionBot
|
||||
|
||||
from meta import LionBot, LionCog
|
||||
from utils import ui
|
||||
from babel.translator import LazyStr
|
||||
|
||||
from ..data import ShopData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.lion import Lion
|
||||
|
||||
class MemberInventory:
|
||||
|
||||
class ShopCog(LionCog):
|
||||
"""
|
||||
Minimal base class for a ShopCog.
|
||||
"""
|
||||
_shop_cls_: Type['Shop']
|
||||
|
||||
active: list[Type['ShopCog']] = []
|
||||
|
||||
def __init__(self, bot: LionBot, data: ShopData):
|
||||
self.bot = bot
|
||||
self.data = data
|
||||
|
||||
async def load_into(self, cog: LionCog):
|
||||
"""
|
||||
Load this ShopCog into the parent Shopping Cog.
|
||||
|
||||
Usually just attaches the editshop placeholder group, if applicable.
|
||||
May also load the cog itself into the client,
|
||||
if the ShopCog needs to provide global features
|
||||
or commands.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def make_shop_for(self, customer: 'Customer'):
|
||||
"""
|
||||
Make a Shop instance for the provided Customer.
|
||||
"""
|
||||
shop = self._shop_cls_(self.bot, self.data, customer)
|
||||
await shop.refresh()
|
||||
return shop
|
||||
|
||||
@classmethod
|
||||
def register(self, shop):
|
||||
"""
|
||||
Helper decorator to register a given ShopCog as active.
|
||||
"""
|
||||
self.active.append(shop)
|
||||
return shop
|
||||
|
||||
|
||||
class Customer:
|
||||
"""
|
||||
An interface to the member's inventory.
|
||||
"""
|
||||
def __init__(self, bot, shop_data, lion, inventory):
|
||||
self.bot = bot
|
||||
self.lion = lion
|
||||
self.guildid = lion.guildid
|
||||
self.userid = lion.userid
|
||||
|
||||
# A list of InventoryItems held by this user
|
||||
_cache_ = WeakValueDictionary()
|
||||
|
||||
def __init__(self, bot: LionBot, shop_data: ShopData, lion, inventory: ShopData.MemberInventory):
|
||||
self.bot = bot
|
||||
self.data = shop_data
|
||||
|
||||
self.lion: 'Lion' = lion
|
||||
|
||||
# A list of InventoryItems held by this customer
|
||||
self.inventory = inventory
|
||||
|
||||
@property
|
||||
def guildid(self):
|
||||
return self.lion.guildid
|
||||
|
||||
@property
|
||||
def userid(self):
|
||||
return self.lion.userid
|
||||
|
||||
@property
|
||||
def balance(self):
|
||||
return self.lion.data['coins']
|
||||
|
||||
@classmethod
|
||||
async def fetch(cls, bot: LionBot, shop_data: ShopData, guildid: int, userid: int):
|
||||
lion = await bot.core.lions.fetch(guildid, userid)
|
||||
inventory = await shop_data.fetch_where(guildid=guildid, userid=userid)
|
||||
inventory = await shop_data.MemberInventoryInfo.fetch_inventory_info(guildid, userid)
|
||||
return cls(bot, shop_data, lion, inventory)
|
||||
|
||||
async def refresh(self):
|
||||
"""
|
||||
Refresh the data for this member.
|
||||
"""
|
||||
self.lion = self.bot.core.lions.fetch(self.guild.id, self.user.id)
|
||||
|
||||
data = self.bot.get_cog('Shopping').data
|
||||
self.inventory_items = await data.InventoryItem.fetch_where(userid=self.userid, guildid=self.guildid)
|
||||
self.lion = await self.bot.core.lions.fetch(self.guildid, self.userid)
|
||||
await self.lion.data.refresh()
|
||||
self.inventory = await self.data.MemberInventoryInfo.fetch_inventory_info(self.guildid, self.userid)
|
||||
return self
|
||||
|
||||
|
||||
class ShopItem:
|
||||
"""
|
||||
ABC representing a purchasable guild shop item.
|
||||
"""
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
Base class representing a purchasable guild shop item.
|
||||
|
||||
async def purchase(self, userid):
|
||||
"""
|
||||
Called when a member purchases this item.
|
||||
"""
|
||||
...
|
||||
In its most basic form, this is just a direct interface to the data,
|
||||
with some formatting methods.
|
||||
"""
|
||||
def __init__(self, bot: LionBot, data: ShopData.ShopItemInfo):
|
||||
self.bot = bot
|
||||
self.data = data
|
||||
|
||||
|
||||
class Shop:
|
||||
"""
|
||||
Base class representing a Shop for a particular member.
|
||||
Base class representing a Shop for a particular Customer.
|
||||
"""
|
||||
def __init__(self, bot: LionBot, shop_data: ShopData, member: discord.Member):
|
||||
# The name of this shop class, as a lazystring
|
||||
_name_: LazyStr
|
||||
|
||||
# Store class describing the shop UI.
|
||||
_store_cls_: Type['Store']
|
||||
|
||||
def __init__(self, bot: LionBot, shop_data: ShopData, customer: Customer):
|
||||
self.bot = bot
|
||||
self.data = shop_data
|
||||
self.member = member
|
||||
self.guild = member.guild
|
||||
self.customer = customer
|
||||
|
||||
# A list of ShopItems that are currently visible to the member
|
||||
self.items = []
|
||||
# A map itemid: ShopItem of items viewable by the customer
|
||||
self.items = {}
|
||||
|
||||
# Current inventory for the member
|
||||
self.inventory = None
|
||||
def purchasable(self):
|
||||
"""
|
||||
Retrieve a list of items purchasable by the customer.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def refresh(self):
|
||||
...
|
||||
"""
|
||||
Refresh the shop and customer data.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
The localised name of this shop.
|
||||
|
||||
Usually just a context-aware translated version of cls._name_
|
||||
"""
|
||||
t = self.bot.translator.t
|
||||
return t(self._name_)
|
||||
|
||||
async def purchase(self, itemid):
|
||||
"""
|
||||
Have the shop customer purchase the given (global) itemid.
|
||||
Checks that the item is actually purchasable by the customer.
|
||||
This method must be overridden in base classes.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def make_store(self):
|
||||
"""
|
||||
Initialise and return a new Store UI for this shop.
|
||||
"""
|
||||
return self._store_cls_(self)
|
||||
|
||||
|
||||
class Store(ui.LeoUI):
|
||||
"""
|
||||
Base UI for the different shops.
|
||||
ABC for the UI used to interact with a given shop.
|
||||
|
||||
This must always be an ephemeral UI,
|
||||
so extra permission checks are not required.
|
||||
(Note that each Shop instance is specific to a single customer.)
|
||||
"""
|
||||
def __init__(self, bot: LionBot, data, shops):
|
||||
self.bot = bot
|
||||
self.data = data
|
||||
self.shops = shops
|
||||
def __init__(self, shop: Shop, interaction: discord.Interaction, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# The shop this Store is an interface for
|
||||
# Client, shop, and customer data is taken from here
|
||||
# The Shop also manages most Customer object interaction, including purchases.
|
||||
self.shop = shop
|
||||
|
||||
# The row of Buttons used to access different shops, if any
|
||||
# Transient, will be deprecated by direct access to UILayout.
|
||||
self.store_row = ()
|
||||
|
||||
# Current embed page
|
||||
self.embed: Optional[discord.Embed] = None
|
||||
|
||||
# Current interaction to use
|
||||
self.interaction: discord.Interaction = interaction
|
||||
|
||||
def set_store_row(self, row):
|
||||
self.store_row = row
|
||||
for item in row:
|
||||
self.add_item(item)
|
||||
|
||||
async def refresh(self):
|
||||
"""
|
||||
Refresh all UI elements.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def redraw(self):
|
||||
"""
|
||||
Redraw the store UI.
|
||||
"""
|
||||
if self.interaction.is_expired():
|
||||
# This is actually possible,
|
||||
# If the user keeps using the UI,
|
||||
# but never closes it until the origin interaction expires
|
||||
raise ValueError("This interaction has expired!")
|
||||
return
|
||||
|
||||
if self.embed is None:
|
||||
await self.refresh()
|
||||
|
||||
await self.interaction.edit_original_response(embed=self.embed, view=self)
|
||||
|
||||
async def make_embed(self):
|
||||
"""
|
||||
Embed page for this shop.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user