176 lines
5.8 KiB
Python
176 lines
5.8 KiB
Python
from collections import defaultdict
|
|
from typing import Optional
|
|
|
|
from frozendict import frozendict
|
|
import discord
|
|
from discord.components import SelectOption
|
|
from discord.app_commands import Choice
|
|
|
|
from gui.base import AppSkin
|
|
from meta import LionBot
|
|
from meta.logger import log_wrap
|
|
|
|
from .data import CustomSkinData
|
|
|
|
|
|
def appskin_as_option(skin: AppSkin) -> SelectOption:
|
|
"""
|
|
Create a SelectOption from the given localised AppSkin
|
|
"""
|
|
return SelectOption(
|
|
label=skin.display_name,
|
|
description=skin.description,
|
|
value=skin.skin_id,
|
|
)
|
|
|
|
|
|
def appskin_as_choice(skin: AppSkin) -> Choice[str]:
|
|
"""
|
|
Create an appcmds.Choice from the given localised AppSkin
|
|
"""
|
|
return Choice(
|
|
name=skin.display_name,
|
|
value=skin.skin_id,
|
|
)
|
|
|
|
|
|
class FrozenCustomSkin:
|
|
__slots__ = ('base_skin_name', 'properties')
|
|
|
|
def __init__(self, base_skin_name: Optional[str], properties: dict[str, dict[str, str]]):
|
|
self.base_skin_name = base_skin_name
|
|
self.properties = frozendict((card, frozendict(props)) for card, props in properties.items())
|
|
|
|
def args_for(self, card_id: str):
|
|
args = {}
|
|
if self.base_skin_name is not None:
|
|
args["base_skin_id"] = self.base_skin_name
|
|
if card_id in self.properties:
|
|
args.update(self.properties[card_id])
|
|
return args
|
|
|
|
|
|
class CustomSkin:
|
|
def __init__(self,
|
|
bot: LionBot,
|
|
base_skin_name: Optional[str]=None,
|
|
properties: dict[str, dict[str, str]] = {},
|
|
data: Optional[CustomSkinData.CustomisedSkin]=None,
|
|
):
|
|
self.bot = bot
|
|
self.data = data
|
|
|
|
self.base_skin_name = base_skin_name
|
|
self.properties = properties
|
|
|
|
@property
|
|
def cog(self):
|
|
return self.bot.get_cog('CustomSkinCog')
|
|
|
|
@property
|
|
def skinid(self) -> Optional[int]:
|
|
return self.data.custom_skin_id if self.data else None
|
|
|
|
@property
|
|
def base_skin_id(self) -> Optional[int]:
|
|
if self.base_skin_name is not None:
|
|
return self.cog.appskin_names.inverse[self.base_skin_name]
|
|
|
|
@classmethod
|
|
async def fetch(cls, bot: LionBot, skinid: int) -> Optional['CustomSkin']:
|
|
"""
|
|
Fetch the specified skin from data.
|
|
"""
|
|
cog = bot.get_cog('CustomSkinCog')
|
|
row = await cog.data.CustomisedSkin.fetch(skinid)
|
|
if row is not None:
|
|
records = await cog.data.custom_skin_info.select_where(
|
|
custom_skin_id=skinid
|
|
)
|
|
properties = defaultdict(dict)
|
|
for record in records:
|
|
card_id = record['card_id']
|
|
prop_name = record['property_name']
|
|
prop_value = record['value']
|
|
properties[card_id][prop_name] = prop_value
|
|
if row.base_skin_id is not None:
|
|
base_skin_name = cog.appskin_names[row.base_skin_id]
|
|
else:
|
|
base_skin_name = None
|
|
self = cls(bot, base_skin_name, properties, data=row)
|
|
return self
|
|
|
|
@log_wrap(action='Save Skin')
|
|
async def save(self):
|
|
if self.data is None:
|
|
raise ValueError("Cannot save a dataless CustomSkin")
|
|
|
|
async with self.bot.db.connection() as conn:
|
|
self.bot.db.conn = conn
|
|
async with conn.transaction():
|
|
skinid = self.skinid
|
|
await self.data.update(base_skin_id=self.base_skin_id)
|
|
await self.cog.data.skin_properties.delete_where(skinid=skinid)
|
|
|
|
props = {
|
|
(card, name): value
|
|
for card, card_props in self.properties.items()
|
|
for name, value in card_props.items()
|
|
if value is not None
|
|
}
|
|
# Ensure the properties exist in cache
|
|
await self.cog.fetch_property_ids(*props.keys())
|
|
|
|
# Now bulk insert
|
|
await self.cog.data.skin_properties.insert_many(
|
|
('custom_skin_id', 'property_id', 'value'),
|
|
*(
|
|
(skinid, self.cog.skin_properties[propkey], value)
|
|
for propkey, value in props.items()
|
|
)
|
|
)
|
|
|
|
def resolve_propid(self, propid: int) -> tuple[str, str]:
|
|
return self.cog.skin_properties[propid]
|
|
|
|
def __getitem__(self, propid: int) -> Optional[str]:
|
|
card, name = self.resolve_propid(propid)
|
|
return self.properties.get(card, {}).get(name, None)
|
|
|
|
def __setitem__(self, propid: int, value: Optional[str]):
|
|
card, name = self.resolve_propid(propid)
|
|
cardprops = self.properties.get(card, None)
|
|
if value is None:
|
|
if cardprops is not None:
|
|
cardprops.pop(name, None)
|
|
else:
|
|
if cardprops is None:
|
|
cardprops = self.properties[card] = {}
|
|
cardprops[name] = value
|
|
|
|
def __delitem__(self, propid: int):
|
|
card, name = self.resolve_propid(propid)
|
|
self.properties.get(card, {}).pop(name, None)
|
|
|
|
def freeze(self) -> FrozenCustomSkin:
|
|
"""
|
|
Freeze the custom skin data into a memory efficient FrozenCustomSkin.
|
|
"""
|
|
return FrozenCustomSkin(self.base_skin_name, self.properties)
|
|
|
|
def load_frozen(self, frozen: FrozenCustomSkin):
|
|
"""
|
|
Update state from the given frozen state.
|
|
"""
|
|
self.base_skin_name = frozen.base_skin_name
|
|
self.properties = dict((card, dict(props)) for card, props in frozen.properties)
|
|
return self
|
|
|
|
def args_for(self, card_id: str):
|
|
args = {}
|
|
if self.base_skin_name is not None:
|
|
args["base_skin_id"] = self.base_skin_name
|
|
if card_id in self.properties:
|
|
args.update(self.properties[card_id])
|
|
return args
|