Bombsquad-Ballistica-Modded.../dist/ba_data/python/bacommon/clienteffect.py
2026-06-28 18:53:43 +05:30

228 lines
5.7 KiB
Python

# 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