Bombsquad-Ballistica-Modded.../dist/ba_data/python/bacommon/clienteffect.py

225 lines
5.6 KiB
Python
Raw Normal View History

2026-02-15 14:00:00 +05:30
# 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.
"""
from __future__ import annotations
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(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