diff --git a/assets/src/ba_data/python/bastd/actor/bomb.py b/assets/src/ba_data/python/bastd/actor/bomb.py index f5a4b6cd..f5b7743b 100644 --- a/assets/src/ba_data/python/bastd/actor/bomb.py +++ b/assets/src/ba_data/python/bastd/actor/bomb.py @@ -34,10 +34,6 @@ from bastd.gameutils import SharedObjects if TYPE_CHECKING: from typing import Any, Sequence, Optional, Callable, List, Tuple, Type -# Attr we store these objects as on the current activity. -# (based on our module so hopefully avoids conflicts) -STORAGE_ATTR_NAME = '_' + __name__.replace('.', '_') + '_bombfactory' - PlayerType = TypeVar('PlayerType', bound='ba.Player') @@ -150,14 +146,16 @@ class BombFactory: ba.Sound for a rolling bomb. """ - @staticmethod - def get() -> BombFactory: + _STORENAME = ba.storagename() + + @classmethod + def get(cls) -> BombFactory: """Get/create a shared bastd.actor.bomb.BombFactory object.""" activity = ba.getactivity() - factory = getattr(activity, STORAGE_ATTR_NAME, None) + factory = activity.customdata.get(cls._STORENAME) if factory is None: factory = BombFactory() - setattr(activity, STORAGE_ATTR_NAME, factory) + activity.customdata[cls._STORENAME] = factory assert isinstance(factory, BombFactory) return factory diff --git a/assets/src/ba_data/python/bastd/actor/flag.py b/assets/src/ba_data/python/bastd/actor/flag.py index b8cad76e..78cfb666 100644 --- a/assets/src/ba_data/python/bastd/actor/flag.py +++ b/assets/src/ba_data/python/bastd/actor/flag.py @@ -59,6 +59,8 @@ class FlagFactory: The ba.Texture for flags. """ + _STORENAME = ba.storagename() + def __init__(self) -> None: """Instantiate a FlagFactory. @@ -123,14 +125,14 @@ class FlagFactory: self.flag_texture = ba.gettexture('flagColor') - @staticmethod - def get() -> FlagFactory: + @classmethod + def get(cls) -> FlagFactory: """Get/create a shared FlagFactory instance.""" activity = ba.getactivity() - factory = getattr(activity, 'shared_flag_factory', None) + factory = activity.customdata.get(cls._STORENAME) if factory is None: factory = FlagFactory() - setattr(activity, 'shared_flag_factory', factory) + activity.customdata[cls._STORENAME] = factory assert isinstance(factory, FlagFactory) return factory diff --git a/assets/src/ba_data/python/bastd/actor/spaz.py b/assets/src/ba_data/python/bastd/actor/spaz.py index 8e13d6a4..2ae01b4b 100644 --- a/assets/src/ba_data/python/bastd/actor/spaz.py +++ b/assets/src/ba_data/python/bastd/actor/spaz.py @@ -29,6 +29,7 @@ from typing import TYPE_CHECKING import ba from bastd.actor import bomb as stdbomb from bastd.actor.powerupbox import PowerupBoxFactory +from bastd.actor.spazfactory import SpazFactory from bastd.gameutils import SharedObjects if TYPE_CHECKING: @@ -56,18 +57,6 @@ class BombDiedMessage: """A bomb has died and thus can be recycled.""" -def get_factory() -> SpazFactory: - """Return the shared ba.SpazFactory object, creating it if necessary.""" - # pylint: disable=cyclic-import - from bastd.actor.spazfactory import SpazFactory - activity = ba.getactivity() - factory = getattr(activity, 'shared_spaz_factory', None) - if factory is None: - factory = activity.shared_spaz_factory = SpazFactory() # type: ignore - assert isinstance(factory, SpazFactory) - return factory - - class Spaz(ba.Actor): """ Base class for various Spazzes. @@ -112,7 +101,7 @@ class Spaz(ba.Actor): shared = SharedObjects.get() activity = self.activity - factory = get_factory() + factory = SpazFactory.get() # we need to behave slightly different in the tutorial self._demo_mode = demo_mode @@ -466,7 +455,7 @@ class Spaz(ba.Actor): ba.timer( 0.1, ba.WeakCall(self._safe_play_sound, - get_factory().swish_sound, 0.8)) + SpazFactory.get().swish_sound, 0.8)) self._turbo_filter_add_press('punch') def _safe_play_sound(self, sound: ba.Sound, volume: float) -> None: @@ -605,7 +594,7 @@ class Spaz(ba.Actor): he will explode in 5 seconds. """ if not self._cursed: - factory = get_factory() + factory = SpazFactory.get() self._cursed = True # Add the curse material. @@ -637,7 +626,7 @@ class Spaz(ba.Actor): self._punch_power_scale = 1.7 self._punch_cooldown = 300 else: - factory = get_factory() + factory = SpazFactory.get() self._punch_power_scale = factory.punch_power_scale_gloves self._punch_cooldown = factory.punch_cooldown_gloves @@ -650,7 +639,7 @@ class Spaz(ba.Actor): ba.print_error('Can\'t equip shields; no node.') return - factory = get_factory() + factory = SpazFactory.get() if self.shield is None: self.shield = ba.newnode('shield', owner=self.node, @@ -685,7 +674,7 @@ class Spaz(ba.Actor): self.shield = None self.shield_decay_timer = None assert self.node - ba.playsound(get_factory().shield_down_sound, + ba.playsound(SpazFactory.get().shield_down_sound, 1.0, position=self.node.position) else: @@ -802,7 +791,7 @@ class Spaz(ba.Actor): ba.WeakCall(self._gloves_wear_off), timeformat=ba.TimeFormat.MILLISECONDS)) elif msg.poweruptype == 'shield': - factory = get_factory() + factory = SpazFactory.get() # Let's allow powerup-equipped shields to lose hp over time. self.equip_shields(decay=factory.shield_decay_rate > 0) @@ -832,7 +821,7 @@ class Spaz(ba.Actor): self._cursed = False # Remove cursed material. - factory = get_factory() + factory = SpazFactory.get() for attr in ['materials', 'roller_materials']: materials = getattr(self.node, attr) if factory.curse_material in materials: @@ -856,7 +845,7 @@ class Spaz(ba.Actor): if not self.node: return None if self.node.invincible: - ba.playsound(get_factory().block_sound, + ba.playsound(SpazFactory.get().block_sound, 1.0, position=self.node.position) return None @@ -881,7 +870,7 @@ class Spaz(ba.Actor): if not self.node: return None if self.node.invincible: - ba.playsound(get_factory().block_sound, + ba.playsound(SpazFactory.get().block_sound, 1.0, position=self.node.position) return True @@ -924,13 +913,13 @@ class Spaz(ba.Actor): # without damaging the player. # However, massive damage events should still be able to # damage the player. This hopefully gives us a happy medium. - max_spillover = get_factory().max_shield_spillover_damage + max_spillover = SpazFactory.get().max_shield_spillover_damage if self.shield_hitpoints <= 0: # FIXME: Transition out perhaps? self.shield.delete() self.shield = None - ba.playsound(get_factory().shield_down_sound, + ba.playsound(SpazFactory.get().shield_down_sound, 1.0, position=self.node.position) @@ -944,7 +933,7 @@ class Spaz(ba.Actor): chunk_type='spark') else: - ba.playsound(get_factory().shield_hit_sound, + ba.playsound(SpazFactory.get().shield_hit_sound, 0.5, position=self.node.position) @@ -1001,14 +990,14 @@ class Spaz(ba.Actor): # Let's always add in a super-punch sound with boxing # gloves just to differentiate them. if msg.hit_subtype == 'super_punch': - ba.playsound(get_factory().punch_sound_stronger, + ba.playsound(SpazFactory.get().punch_sound_stronger, 1.0, position=self.node.position) if damage > 500: - sounds = get_factory().punch_sound_strong + sounds = SpazFactory.get().punch_sound_strong sound = sounds[random.randrange(len(sounds))] else: - sound = get_factory().punch_sound + sound = SpazFactory.get().punch_sound ba.playsound(sound, 1.0, position=self.node.position) # Throw up some chunks. @@ -1118,7 +1107,7 @@ class Spaz(ba.Actor): elif self.node: self.node.hurt = 1.0 if self.play_big_death_sound and not wasdead: - ba.playsound(get_factory().single_player_death_sound) + ba.playsound(SpazFactory.get().single_player_death_sound) self.node.dead = True ba.timer(2.0, self.node.delete) @@ -1160,7 +1149,7 @@ class Spaz(ba.Actor): # If its something besides another spaz, just do a muffled # punch sound. if node.getnodetype() != 'spaz': - sounds = get_factory().impact_sounds_medium + sounds = SpazFactory.get().impact_sounds_medium sound = sounds[random.randrange(len(sounds))] ba.playsound(sound, 1.0, position=self.node.position) @@ -1347,11 +1336,11 @@ class Spaz(ba.Actor): scale=0.3, spread=0.2, chunk_type='ice') - ba.playsound(get_factory().shatter_sound, + ba.playsound(SpazFactory.get().shatter_sound, 1.0, position=self.node.position) else: - ba.playsound(get_factory().splatter_sound, + ba.playsound(SpazFactory.get().splatter_sound, 1.0, position=self.node.position) self.handlemessage(ba.DieMessage()) @@ -1369,11 +1358,11 @@ class Spaz(ba.Actor): self.node.handlemessage('knockout', max(0.0, 50.0 * intensity)) sounds: Sequence[ba.Sound] if intensity > 5.0: - sounds = get_factory().impact_sounds_harder + sounds = SpazFactory.get().impact_sounds_harder elif intensity > 3.0: - sounds = get_factory().impact_sounds_hard + sounds = SpazFactory.get().impact_sounds_hard else: - sounds = get_factory().impact_sounds_medium + sounds = SpazFactory.get().impact_sounds_medium sound = sounds[random.randrange(len(sounds))] ba.playsound(sound, position=pos, volume=5.0) @@ -1418,7 +1407,7 @@ class Spaz(ba.Actor): self._punch_power_scale = 1.2 self._punch_cooldown = BASE_PUNCH_COOLDOWN else: - factory = get_factory() + factory = SpazFactory.get() self._punch_power_scale = factory.punch_power_scale self._punch_cooldown = factory.punch_cooldown self._has_boxing_gloves = False diff --git a/assets/src/ba_data/python/bastd/actor/spazfactory.py b/assets/src/ba_data/python/bastd/actor/spazfactory.py index 6946088a..346723e2 100644 --- a/assets/src/ba_data/python/bastd/actor/spazfactory.py +++ b/assets/src/ba_data/python/bastd/actor/spazfactory.py @@ -26,8 +26,6 @@ from typing import TYPE_CHECKING import ba from bastd.gameutils import SharedObjects -from bastd.actor.spaz import (PickupMessage, PunchHitMessage, - CurseExplodeMessage) import _ba if TYPE_CHECKING: @@ -97,12 +95,20 @@ class SpazFactory: A ba.Material applied to a cursed ba.Spaz that triggers an explosion. """ + _STORENAME = ba.storagename() + def _preload(self, character: str) -> None: """Preload media needed for a given character.""" self.get_media(character) def __init__(self) -> None: """Instantiate a factory object.""" + # pylint: disable=cyclic-import + # FIXME: should probably put these somewhere common so we don't + # have to import them from a module that imports us. + from bastd.actor.spaz import (PickupMessage, PunchHitMessage, + CurseExplodeMessage) + shared = SharedObjects.get() self.impact_sounds_medium = (ba.getsound('impactMedium'), ba.getsound('impactMedium2')) @@ -265,3 +271,14 @@ class SpazFactory: else: media = self.spaz_media[character] return media + + @classmethod + def get(cls) -> SpazFactory: + """Return the shared ba.SpazFactory, creating it if necessary.""" + # pylint: disable=cyclic-import + activity = ba.getactivity() + factory = activity.customdata.get(cls._STORENAME) + if factory is None: + factory = activity.customdata[cls._STORENAME] = SpazFactory() + assert isinstance(factory, SpazFactory) + return factory diff --git a/assets/src/ba_data/python/bastd/game/elimination.py b/assets/src/ba_data/python/bastd/game/elimination.py index 14c428fc..63c579ed 100644 --- a/assets/src/ba_data/python/bastd/game/elimination.py +++ b/assets/src/ba_data/python/bastd/game/elimination.py @@ -28,7 +28,7 @@ from __future__ import annotations from typing import TYPE_CHECKING import ba -from bastd.actor.spaz import get_factory +from bastd.actor.spazfactory import SpazFactory from bastd.actor.scoreboard import Scoreboard if TYPE_CHECKING: @@ -525,7 +525,7 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): # Play big death sound on our last death # or for every one in solo mode. if self._solo_mode or player.lives == 0: - ba.playsound(get_factory().single_player_death_sound) + ba.playsound(SpazFactory.get().single_player_death_sound) # If we hit zero lives, we're dead (and our team might be too). if player.lives == 0: diff --git a/assets/src/ba_data/python/bastd/gameutils.py b/assets/src/ba_data/python/bastd/gameutils.py index 4519e988..de34b2bd 100644 --- a/assets/src/ba_data/python/bastd/gameutils.py +++ b/assets/src/ba_data/python/bastd/gameutils.py @@ -29,10 +29,6 @@ import ba if TYPE_CHECKING: from typing import Sequence, Optional -# Attr we store these objects as on the current activity. -# (based on our module so hopefully avoids conflicts) -STORAGE_ATTR_NAME = '_' + __name__.replace('.', '_') + '_sharedobjs' - class SharedObjects: """Various common components for use in games. @@ -44,9 +40,11 @@ class SharedObjects: standard materials. """ + _STORENAME = ba.storagename() + def __init__(self) -> None: activity = ba.getactivity() - if hasattr(activity, STORAGE_ATTR_NAME): + if hasattr(activity, self._STORENAME): raise RuntimeError('Use SharedObjects.get() to fetch the' ' shared instance for this activity.') self._object_material: Optional[ba.Material] = None @@ -58,14 +56,14 @@ class SharedObjects: self._region_material: Optional[ba.Material] = None self._railing_material: Optional[ba.Material] = None - @staticmethod - def get() -> SharedObjects: + @classmethod + def get(cls) -> SharedObjects: """Fetch/create the instance of this class for the current activity.""" activity = ba.getactivity() - shobs = getattr(activity, STORAGE_ATTR_NAME, None) + shobs = activity.customdata.get(cls._STORENAME) if shobs is None: shobs = SharedObjects() - setattr(activity, STORAGE_ATTR_NAME, shobs) + activity.customdata[cls._STORENAME] = shobs assert isinstance(shobs, SharedObjects) return shobs diff --git a/assets/src/ba_data/python/bastd/mainmenu.py b/assets/src/ba_data/python/bastd/mainmenu.py index ae792fbb..4be9c553 100644 --- a/assets/src/ba_data/python/bastd/mainmenu.py +++ b/assets/src/ba_data/python/bastd/mainmenu.py @@ -28,7 +28,6 @@ import weakref from typing import TYPE_CHECKING import ba -from bastd.actor import spaz import _ba if TYPE_CHECKING: @@ -886,6 +885,7 @@ def _preload2() -> None: def _preload3() -> None: + from bastd.actor.spazfactory import SpazFactory for mname in ['bomb', 'bombSticky', 'impactBomb']: ba.getmodel(mname) for tname in [ @@ -895,7 +895,7 @@ def _preload3() -> None: ba.gettexture(tname) for sname in ['freeze', 'fuse01', 'activateBeep', 'warnBeep']: ba.getsound(sname) - spaz.get_factory() + SpazFactory.get() ba.timer(0.2, _preload4) diff --git a/tools/efrotools/efrocache.py b/tools/efrotools/efrocache.py index 896d9ae4..d6724e0a 100644 --- a/tools/efrotools/efrocache.py +++ b/tools/efrotools/efrocache.py @@ -120,7 +120,7 @@ def get_target(path: str) -> None: # Just expand it and it get placed wherever it belongs. # Strangely, decompressing lots of these simultaneously leads to occasional - # "File does not exist" errors when running on Windows Subystem for Linux. + # "File does not exist" errors when running on Windows Subsystem for Linux. # There should be no overlap in files getting written, but perhaps # something about how tar rebuilds the directory structure causes clashes. # It seems that just explicitly creating necessary directories first