Files
croccybot/src/modules/skins/skinlib.py

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