# Released under the MIT License. See LICENSE for details. # """ClientEffect related functionality. .. warning:: This is an internal api and subject to change at any time. Do not use it in mod code. """ import datetime from enum import Enum from dataclasses import dataclass, field from typing import Annotated, override, assert_never from efro.dataclassio import ioprepped, IOAttrs, IOMultiType class EffectTypeID(Enum): """Type ID for each of our subclasses.""" UNKNOWN = 'u' LEGACY_SCREEN_MESSAGE = 'm' SCREEN_MESSAGE = 'sm' SOUND = 's' DELAY = 'd' CHEST_WAIT_TIME_ANIMATION = 't' TICKETS_ANIMATION = 'ta' TOKENS_ANIMATION = 'toa' class Effect(IOMultiType[EffectTypeID]): """Something that can happen on the client. This can include screen messages, sounds, visual effects, etc. """ @override @classmethod def get_type_id(cls) -> EffectTypeID: # Require child classes to supply this themselves. If we did a # full type registry/lookup here it would require us to import # everything and would prevent lazy loading. raise NotImplementedError() @override @classmethod def get_type_id_storage_name(cls) -> str: # Pin to the original default for back-compat with stored data. return '_dciotype' @override @classmethod def get_type(cls, type_id: EffectTypeID) -> type[Effect]: """Return the subclass for each of our type-ids.""" # pylint: disable=cyclic-import # pylint: disable=too-many-return-statements t = EffectTypeID if type_id is t.UNKNOWN: return Unknown if type_id is t.LEGACY_SCREEN_MESSAGE: return LegacyScreenMessage if type_id is t.SCREEN_MESSAGE: return ScreenMessage if type_id is t.SOUND: return PlaySound if type_id is t.DELAY: return Delay if type_id is t.CHEST_WAIT_TIME_ANIMATION: return ChestWaitTimeAnimation if type_id is t.TICKETS_ANIMATION: return TicketsAnimation if type_id is t.TOKENS_ANIMATION: return TokensAnimation # Important to make sure we provide all types. assert_never(type_id) @override @classmethod def get_unknown_type_fallback(cls) -> Effect: # If we encounter some future message type we don't know # anything about, drop in a placeholder. return Unknown() @ioprepped @dataclass class Unknown(Effect): """Fallback substitute for types we don't recognize.""" @override @classmethod def get_type_id(cls) -> EffectTypeID: return EffectTypeID.UNKNOWN @ioprepped @dataclass class LegacyScreenMessage(Effect): """Display a screen-message (Legacy version). This will be processed as an Lstr with translation category 'serverResponses'. When possible, migrate to using :class:`ScreenMessage`. """ message: Annotated[str, IOAttrs('m')] subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field( default_factory=list ) color: Annotated[ tuple[float, float, float], IOAttrs('c', store_default=False) ] = (1.0, 1.0, 1.0) @override @classmethod def get_type_id(cls) -> EffectTypeID: return EffectTypeID.LEGACY_SCREEN_MESSAGE @ioprepped @dataclass class ScreenMessage(Effect): """Display a screen-message. Supported on engine build 22606 or newer. This version does no translation by default (expecting translation to happen server-side). Pass a Lstr json string and set is_lstr=True for client-side translation. """ message: Annotated[str, IOAttrs('m')] color: Annotated[ tuple[float, float, float], IOAttrs('c', store_default=False) ] = (1.0, 1.0, 1.0) is_lstr: Annotated[bool, IOAttrs('l', store_default=False)] = False @override @classmethod def get_type_id(cls) -> EffectTypeID: return EffectTypeID.SCREEN_MESSAGE class Sound(Enum): """Sounds that can be played.""" UNKNOWN = 'u' CASH_REGISTER = 'c' ERROR = 'e' POWER_DOWN = 'p' GUN_COCKING = 'g' @ioprepped @dataclass class PlaySound(Effect): """Play a sound.""" sound: Annotated[Sound, IOAttrs('s', enum_fallback=Sound.UNKNOWN)] volume: Annotated[float, IOAttrs('v', store_default=False)] = 1.0 @override @classmethod def get_type_id(cls) -> EffectTypeID: return EffectTypeID.SOUND @ioprepped @dataclass class ChestWaitTimeAnimation(Effect): """Animate chest wait time changing.""" chestid: Annotated[str, IOAttrs('c')] duration: Annotated[float, IOAttrs('u')] startvalue: Annotated[datetime.datetime, IOAttrs('o')] endvalue: Annotated[datetime.datetime, IOAttrs('n')] @override @classmethod def get_type_id(cls) -> EffectTypeID: return EffectTypeID.CHEST_WAIT_TIME_ANIMATION @ioprepped @dataclass class TicketsAnimation(Effect): """Animate tickets count.""" duration: Annotated[float, IOAttrs('u')] startvalue: Annotated[int, IOAttrs('s')] endvalue: Annotated[int, IOAttrs('e')] @override @classmethod def get_type_id(cls) -> EffectTypeID: return EffectTypeID.TICKETS_ANIMATION @ioprepped @dataclass class TokensAnimation(Effect): """Animate tokens count.""" duration: Annotated[float, IOAttrs('u')] startvalue: Annotated[int, IOAttrs('s')] endvalue: Annotated[int, IOAttrs('e')] @override @classmethod def get_type_id(cls) -> EffectTypeID: return EffectTypeID.TOKENS_ANIMATION @ioprepped @dataclass class Delay(Effect): """Delay effect processing.""" seconds: Annotated[float, IOAttrs('s')] @override @classmethod def get_type_id(cls) -> EffectTypeID: return EffectTypeID.DELAY