diff --git a/.efrocachemap b/.efrocachemap index e70c719a..46f06447 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4132,16 +4132,16 @@ "assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c", "assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb", "assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe", - "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d9/29/c569224bc159225daed5cabdd517", - "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b5/52/a015232b381b5a361e26cc4e33d6", - "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b5/31/c229f5293e5ec5b3b8feb9308216", - "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3e/8c/2b05b2168897862e0eefc0d5ddaa", - "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a6/e5/923be95c40b9e7432f941bb98f79", - "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f5/3e/f929b7330662fc64f91e9613d6b3", - "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e9/3a/25571131b13d74f19150e8fdf786", - "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/4f/b3/9627d8ee06297e66f7238fbc4838", - "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/fe/45/5646002baebd720592914c7f1c5b", - "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/f3/ee/10a2c2eaf9783c9abd81141ee5e8", - "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/8e/7b/e121ed5e35abf9cce71415fdecac", - "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/16/d7/b53476ad786d0b1636fcf1906578" + "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8d/c2/9fd3ab19a28b05160f818f787115", + "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/67/25/34c42457f20c9d87d538f4f69320", + "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d9/3d/39326060f1a1df1b18070d75ade8", + "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9f/b7/d63ab7e13f1b9d931b83ef652262", + "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/39/45/f4d0bbe6dfa0286b1faaad5d4c0e", + "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a8/2b/251dcadd37ae0d2c08a0f3bb683d", + "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3a/1a/b80f37d9802d40e625b2df3696b1", + "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/46/fd/6400f9eba88a419487f5d3732e54", + "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7d/6a/3dc3c77c340471c0a7b79bf077a4", + "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/97/5d/824c5c71f61d871c904c61fabc56", + "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/91/92/1f309a3edf4b4aa595ea83472e7a", + "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d5/3c/fefdeed4e8048332724167fe1442" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 4859d5d1..9ab6b481 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -186,6 +186,7 @@ bname bndl boffs + bombfactory bombsquad bombsquadcb bombsquadgame @@ -1637,6 +1638,7 @@ sessionclass sessiondata sessionglobals + sessionglobalsnode sessionname sessionplayer sessionteam @@ -1655,6 +1657,7 @@ sharedobjs shiftdelay shiftposition + shobs shortname shouldn showpoints diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py index 796e7e29..942dc9d2 100644 --- a/assets/src/ba_data/python/_ba.py +++ b/assets/src/ba_data/python/_ba.py @@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand. """ # (hash we can use to see if this file is out of date) -# SOURCES_HASH=237466057120267570582079835997969754357 +# SOURCES_HASH=214732868903831008614942145147141302125 # I'm sorry Pylint. I know this file saddens you. Be strong. # pylint: disable=useless-suppression @@ -527,7 +527,7 @@ class Material: # example 3: play some sounds when we're contacting the ground: m = ba.Material() m.add_actions(conditions=('they_have_material', - ba.sharedobj('footing_material')), + shared.footing_material), actions=(('impact_sound', ba.getsound('metalHit'), 2, 5), ('skid_sound', ba.getsound('metalSkid'), 2, 5))) @@ -635,6 +635,10 @@ class Node: move_left_right: float = 0.0 curse_death_time: int = 0 boxing_gloves: bool = False + use_fixed_vr_overlay: bool = False + allow_kick_idle_players: bool = False + music_continuous: bool = False + music_count: int = 0 hurt: float = 0.0 always_show_health_bar: bool = False mini_billboard_1_texture: Optional[ba.Texture] = None @@ -648,7 +652,20 @@ class Node: mini_billboard_3_end_time: int = 0 boxing_gloves_flashing: bool = False dead: bool = False + floor_reflection: bool = False + debris_friction: float = 0.0 + debris_kill_height: float = 0.0 + vr_near_clip: float = 0.0 + shadow_ortho: bool = False + happy_thoughts_mode: bool = False + shadow_offset: Sequence[float] = (0.0, 0.0) + paused: bool = False + time: int = 0 + ambient_color: Sequence[float] = (1.0, 1.0, 1.0) + camera_mode: str = 'rotate' frozen: bool = False + area_of_interest_bounds: Sequence[float] = (-1, -1, -1, 1, 1, 1) + shadow_range: Sequence[float] = (0, 0, 0, 0) counter_text: str = '' counter_texture: Optional[ba.Texture] = None shattered: int = 0 diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 6b79f6e0..3576a2cd 100644 --- a/assets/src/ba_data/python/ba/__init__.py +++ b/assets/src/ba_data/python/ba/__init__.py @@ -39,7 +39,8 @@ from _ba import (CollideModel, Context, ContextCall, Data, InputDevice, open_url, widget) from ba._activity import Activity from ba._actor import Actor -from ba._player import PlayerInfo, Player, playercast, playercast_o +from ba._player import (PlayerInfo, Player, playercast, playercast_o, + StandLocation) from ba._nodeactor import NodeActor from ba._app import App from ba._coopgame import CoopGameActivity @@ -71,7 +72,7 @@ from ba._appdelegate import AppDelegate from ba._apputils import is_browser_likely_available from ba._campaign import Campaign from ba._gameutils import (animate, animate_array, show_damage_count, - sharedobj, timestring, cameraflash) + timestring, cameraflash) from ba._general import (WeakCall, Call, existing, Existable, verify_object_death) from ba._level import Level diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py index 6088ed89..e46bb06d 100644 --- a/assets/src/ba_data/python/ba/_activity.py +++ b/assets/src/ba_data/python/ba/_activity.py @@ -26,7 +26,8 @@ from typing import TYPE_CHECKING, Generic, TypeVar from ba._team import Team from ba._player import Player -from ba._error import print_exception, print_error, SessionTeamNotFoundError +from ba._error import (print_exception, SessionTeamNotFoundError, + NodeNotFoundError) from ba._dependency import DependencyComponent from ba._general import Call, verify_object_death from ba._messages import UNHANDLED @@ -155,6 +156,11 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # Create our internal engine data. self._activity_data = _ba.register_activity(self) + assert isinstance(settings, dict) + assert _ba.getactivity() is self + + self._globalsnode: Optional[ba.Node] = None + # Player/Team types should have been specified as type args; # grab those. self._playertype: Type[PlayerType] @@ -174,11 +180,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # Preloaded data for actors, maps, etc; indexed by type. self.preloads: Dict[Type, Any] = {} - if not isinstance(settings, dict): - raise TypeError('expected dict for settings') - if _ba.getactivity(doraise=False) is not self: - raise Exception('invalid context state') - # Hopefully can eventually kill this; activities should # validate/store whatever settings they need at init time # (in a more type-safe way). @@ -191,9 +192,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._should_end_immediately_results: ( Optional[ba.TeamGameResults]) = None self._should_end_immediately_delay = 0.0 - self._called_activity_on_transition_in = False - self._called_activity_on_begin = False - self._activity_death_check_timer: Optional[ba.Timer] = None self._expired = False @@ -234,6 +232,16 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): Call(session.transitioning_out_activity_was_freed, self.can_show_ad_on_death)) + @property + def globalsnode(self) -> ba.Node: + """The 'globals' ba.Node for the activity. This contains various + global controls and values. + """ + node = self._globalsnode + if not node: + raise NodeNotFoundError() + return node + @property def stats(self) -> ba.Stats: """The stats instance accessible while the activity is running. @@ -334,12 +342,11 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): from ba import _actor as bsactor if not isinstance(actor, bsactor.Actor): raise TypeError('non-actor passed to retain_actor') - if (self.has_transitioned_in() - and _ba.time() - self._last_prune_dead_actors_time > 10.0): - print_error('It looks like nodes/actors are not' - ' being pruned in your activity;' - ' did you call Activity.on_transition_in()' - ' from your subclass?; ' + str(self) + ' (loc. a)') + + # Make sure our pruning is happening... + assert (not self.has_transitioned_in() + or _ba.time() - self._last_prune_dead_actors_time < 10.0) + self._actor_refs.append(actor) def add_actor_weak_ref(self, actor: ba.Actor) -> None: @@ -350,12 +357,11 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): from ba import _actor as bsactor if not isinstance(actor, bsactor.Actor): raise TypeError('non-actor passed to add_actor_weak_ref') - if (self.has_transitioned_in() - and _ba.time() - self._last_prune_dead_actors_time > 10.0): - print_error('It looks like nodes/actors are ' - 'not being pruned in your activity;' - ' did you call Activity.on_transition_in()' - ' from your subclass?; ' + str(self) + ' (loc. b)') + + # Make sure our pruning is happening... + assert (not self.has_transitioned_in() + or _ba.time() - self._last_prune_dead_actors_time < 10.0) + self._actor_weak_refs.append(weakref.ref(actor)) @property @@ -396,7 +402,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): or teams, however. They remain owned by the previous Activity up until ba.Activity.on_begin() is called. """ - self._called_activity_on_transition_in = True def on_transition_out(self) -> None: """Called when your activity begins transitioning out. @@ -411,28 +416,12 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): At this point the activity's initial players and teams are filled in and it should begin its actual game logic. """ - self._called_activity_on_begin = True def handlemessage(self, msg: Any) -> Any: """General message handling; can be passed any message object.""" del msg # Unused arg. return UNHANDLED - def end(self, - results: Any = None, - delay: float = 0.0, - force: bool = False) -> None: - """Commences Activity shutdown and delivers results to the ba.Session. - - 'delay' is the time delay before the Activity actually ends - (in seconds). Further calls to end() will be ignored up until - this time, unless 'force' is True, in which case the new results - will replace the old. - """ - - # Ask the session to end us. - self.session.end_activity(self, results, delay, force) - def has_transitioned_in(self) -> bool: """Return whether on_transition_in() has been called.""" return self._has_transitioned_in @@ -455,15 +444,15 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): (internal) """ from ba._general import WeakCall - from ba._gameutils import sharedobj assert not self._has_transitioned_in self._has_transitioned_in = True # Set up the globals node based on our settings. with _ba.Context(self): + glb = self._globalsnode = _ba.newnode('globals') + # Now that it's going to be front and center, # set some global values based on what the activity wants. - glb = sharedobj('globals') glb.use_fixed_vr_overlay = self.use_fixed_vr_overlay glb.allow_kick_idle_players = self.allow_kick_idle_players if self.inherits_slow_motion and prev_globals is not None: @@ -498,7 +487,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): try: self.on_transition_in() except Exception: - print_exception('Error in on_transition_in for', self) + print_exception(f'Error in on_transition_in for {self}') # Tell the C++ layer that this activity is the main one, so it uses # settings from our globals, directs various events to us, etc. @@ -538,8 +527,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._has_begun = True self.on_begin() - self._sanity_check_begin_call() - # If the whole session wants to die and was waiting on us, # can kick off that process now. if session.wants_to_end: @@ -550,6 +537,21 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self.end(self._should_end_immediately_results, self._should_end_immediately_delay) + def end(self, + results: Any = None, + delay: float = 0.0, + force: bool = False) -> None: + """Commences Activity shutdown and delivers results to the ba.Session. + + 'delay' is the time delay before the Activity actually ends + (in seconds). Further calls to end() will be ignored up until + this time, unless 'force' is True, in which case the new results + will replace the old. + """ + + # Ask the session to end us. + self.session.end_activity(self, results, delay, force) + def create_player(self, sessionplayer: ba.SessionPlayer) -> PlayerType: """Create the Player instance for this Activity. @@ -559,7 +561,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): point as it is not yet fully wired up; wait for on_player_join() for that. """ - del sessionplayer # Unused + del sessionplayer # Unused. player = self._playertype() return player @@ -686,19 +688,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): sessionteam.gameteam = None - def _sanity_check_begin_call(self) -> None: - # Make sure ba.Activity.on_transition_in() got called at some point. - if not self._called_activity_on_transition_in: - print_error( - 'ba.Activity.on_transition_in() never got called for ' + - str(self) + '; did you forget to call it' - ' in your on_transition_in override?') - # Make sure that ba.Activity.on_begin() got called at some point. - if not self._called_activity_on_begin: - print_error( - 'ba.Activity.on_begin() never got called for ' + str(self) + - '; did you forget to call it in your on_begin override?') - def _setup_player_and_team_types(self) -> None: """Pull player and team types from our typing.Generic params.""" @@ -819,7 +808,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # It is expected that Team objects may last longer than # the SessionTeam they came from (game objects may hold # team references past the point at which the underlying - # player/team leaves) + # player/team has left the game) pass except Exception: print_exception(f'Error resetting Team {team}') diff --git a/assets/src/ba_data/python/ba/_app.py b/assets/src/ba_data/python/ba/_app.py index 99129efc..8847c13c 100644 --- a/assets/src/ba_data/python/ba/_app.py +++ b/assets/src/ba_data/python/ba/_app.py @@ -623,7 +623,7 @@ class App: # FIXME: Shouldn't be touching scene stuff here; # should just pass the request on to the host-session. with _ba.Context(activity): - globs = _gameutils.sharedobj('globals') + globs = activity.globalsnode if not globs.paused: _ba.playsound(_ba.getsound('refWhistle')) globs.paused = True @@ -645,14 +645,13 @@ class App: If there's a foreground host-activity that's currently paused, tell it to resume. """ - from ba._gameutils import sharedobj # FIXME: Shouldn't be touching scene stuff here; # should just pass the request on to the host-session. activity = _ba.get_foreground_host_activity() if activity is not None: with _ba.Context(activity): - globs = sharedobj('globals') + globs = activity.globalsnode if globs.paused: _ba.playsound(_ba.getsound('refWhistle')) globs.paused = False diff --git a/assets/src/ba_data/python/ba/_coopgame.py b/assets/src/ba_data/python/ba/_coopgame.py index ee60ea96..08c72572 100644 --- a/assets/src/ba_data/python/ba/_coopgame.py +++ b/assets/src/ba_data/python/ba/_coopgame.py @@ -249,7 +249,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): def fade_to_red(self) -> None: """Fade the screen to red; (such as when the good guys have lost).""" from ba import _gameutils - c_existing = _gameutils.sharedobj('globals').tint + c_existing = self.globalsnode.tint cnode = _ba.newnode('combine', attrs={ 'input0': c_existing[0], @@ -259,7 +259,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): }) _gameutils.animate(cnode, 'input1', {0: c_existing[1], 2.0: 0}) _gameutils.animate(cnode, 'input2', {0: c_existing[2], 2.0: 0}) - cnode.connectattr('output', _gameutils.sharedobj('globals'), 'tint') + cnode.connectattr('output', self.globalsnode, 'tint') def setup_low_life_warning_sound(self) -> None: """Set up a beeping noise to play when any players are near death.""" diff --git a/assets/src/ba_data/python/ba/_gameactivity.py b/assets/src/ba_data/python/ba/_gameactivity.py index b2bc28fa..aa74790a 100644 --- a/assets/src/ba_data/python/ba/_gameactivity.py +++ b/assets/src/ba_data/python/ba/_gameactivity.py @@ -403,6 +403,7 @@ class GameActivity(Activity[PlayerType, TeamType]): return '' def on_transition_in(self) -> None: + super().on_transition_in() # Make our map. @@ -455,7 +456,6 @@ class GameActivity(Activity[PlayerType, TeamType]): # pylint: disable=too-many-nested-blocks # pylint: disable=cyclic-import from bastd.ui.continues import ContinuesWindow - from ba._gameutils import sharedobj from ba._coopsession import CoopSession from ba._enums import TimeType @@ -472,7 +472,7 @@ class GameActivity(Activity[PlayerType, TeamType]): if isinstance(session, CoopSession): assert session.campaign is not None if session.campaign.sequential: - gnode = sharedobj('globals') + gnode = self.globalsnode # Only attempt this if we're not currently paused # and there appears to be no UI. @@ -1024,7 +1024,6 @@ class GameActivity(Activity[PlayerType, TeamType]): This will be displayed at the top of the screen. If the time-limit expires, end_game() will be called. """ - from ba._gameutils import sharedobj from ba._nodeactor import NodeActor if duration <= 0.0: return @@ -1048,8 +1047,9 @@ class GameActivity(Activity[PlayerType, TeamType]): 'time2': duration * 1000, 'timemin': 0 })) - sharedobj('globals').connectattr( - 'time', self._standard_time_limit_text_input.node, 'time1') + self.globalsnode.connectattr('time', + self._standard_time_limit_text_input.node, + 'time1') assert self._standard_time_limit_text_input.node assert self._standard_time_limit_text.node self._standard_time_limit_text_input.node.connectattr( diff --git a/assets/src/ba_data/python/ba/_gameutils.py b/assets/src/ba_data/python/ba/_gameutils.py index 7391925d..8c48ce76 100644 --- a/assets/src/ba_data/python/ba/_gameutils.py +++ b/assets/src/ba_data/python/ba/_gameutils.py @@ -23,10 +23,10 @@ from __future__ import annotations from typing import TYPE_CHECKING -# from typing_extensions import Protocol import _ba from ba._enums import TimeType, TimeFormat, SpecialChar +from ba._error import ActivityNotFoundError if TYPE_CHECKING: from typing import Any, Dict, Sequence, Optional @@ -41,15 +41,6 @@ TROPHY_CHARS = { '4': SpecialChar.TROPHY4 } -# class Respawnable(Protocol): -# """A Protocol for objects able to be respawned. - -# Category: Protocols -# """ - -# respawn_timer: Optional[ba.Timer] -# respawn_icon: Optional[RespawnIcon] - def get_trophy_string(trophy_id: str) -> str: """Given a trophy id, returns a string to visualize it.""" @@ -58,125 +49,6 @@ def get_trophy_string(trophy_id: str) -> str: return '?' -def sharedobj(name: str) -> Any: - """Return a predefined object for the current Activity, creating if needed. - - Category: Gameplay Functions - - Available values for 'name': - - 'globals': returns the 'globals' ba.Node, containing various global - controls & values. - - 'object_material': a ba.Material that should be applied to any small, - normal, physical objects such as bombs, boxes, players, etc. Other - materials often check for the presence of this material as a - prerequisite for performing certain actions (such as disabling collisions - between initially-overlapping objects) - - 'player_material': a ba.Material to be applied to player parts. Generally, - materials related to the process of scoring when reaching a goal, etc - will look for the presence of this material on things that hit them. - - 'pickup_material': a ba.Material; collision shapes used for picking things - up will have this material applied. To prevent an object from being - picked up, you can add a material that disables collisions against things - containing this material. - - 'footing_material': anything that can be 'walked on' should have this - ba.Material applied; generally just terrain and whatnot. A character will - snap upright whenever touching something with this material so it should - not be applied to props, etc. - - 'attack_material': a ba.Material applied to explosion shapes, punch - shapes, etc. An object not wanting to receive impulse/etc messages can - disable collisions against this material. - - 'death_material': a ba.Material that sends a ba.DieMessage() to anything - that touches it; handy for terrain below a cliff, etc. - - 'region_material': a ba.Material used for non-physical collision shapes - (regions); collisions can generally be allowed with this material even - when initially overlapping since it is not physical. - - 'railing_material': a ba.Material with a very low friction/stiffness/etc - that can be applied to invisible 'railings' useful for gently keeping - characters from falling off of cliffs. - """ - # pylint: disable=too-many-branches - from ba._messages import DieMessage - - # We store these on the current context; whether its an activity or - # session. - activity = _ba.getactivity(doraise=False) - if activity is not None: - - # Grab shared-objs dict. - sharedobjs = getattr(activity, 'sharedobjs', None) - - # Grab item out of it. - try: - return sharedobjs[name] # (pylint bug?) pylint: disable=E1136 - except KeyError: - pass - - obj: Any - - # Hmm looks like it doesn't yet exist; create it if its a valid value. - if name == 'globals': - node_obj = _ba.newnode('globals') - obj = node_obj - elif name in [ - 'object_material', 'player_material', 'pickup_material', - 'footing_material', 'attack_material' - ]: - obj = _ba.Material() - elif name == 'death_material': - mat = obj = _ba.Material() - mat.add_actions( - ('message', 'their_node', 'at_connect', DieMessage())) - elif name == 'region_material': - obj = _ba.Material() - elif name == 'railing_material': - mat = obj = _ba.Material() - mat.add_actions(('modify_part_collision', 'collide', False)) - mat.add_actions(('modify_part_collision', 'stiffness', 0.003)) - mat.add_actions(('modify_part_collision', 'damping', 0.00001)) - mat.add_actions(conditions=('they_have_material', - sharedobj('player_material')), - actions=(('modify_part_collision', 'collide', - True), ('modify_part_collision', - 'friction', 0.0))) - else: - raise ValueError( - "unrecognized shared object (activity context): '" + name + - "'") - else: - session = _ba.getsession(doraise=False) - if session is not None: - - # Grab shared-objs dict (creating if necessary). - sharedobjs = session.sharedobjs - - # Grab item out of it. - obj = sharedobjs.get(name) - if obj is not None: - return obj - - # Hmm looks like it doesn't yet exist; create if its a valid value. - if name == 'globals': - obj = _ba.newnode('sessionglobals') - else: - raise ValueError('unrecognized shared object ' - "(session context): '" + name + "'") - else: - raise RuntimeError('no current activity or session context') - - # Ok, got a shiny new shared obj; store it for quick access next time. - sharedobjs[name] = obj - return obj - - def animate(node: ba.Node, attr: str, keys: Dict[float, float], @@ -238,7 +110,14 @@ def animate(node: ba.Node, # Do the connects last so all our attrs are in place when we push initial # values through. - sharedobj('globals').connectattr(driver, curve, 'in') + + # We operate in either activities or sessions.. + try: + globalsnode = _ba.getactivity().globalsnode + except ActivityNotFoundError: + globalsnode = _ba.getsession().sessionglobalsnode + + globalsnode.connectattr(driver, curve, 'in') curve.connectattr('out', node, attr) return curve @@ -282,12 +161,18 @@ def animate_array(node: ba.Node, else: raise ValueError('invalid timeformat value: "' + str(timeformat) + '"') + # We operate in either activities or sessions.. + try: + globalsnode = _ba.getactivity().globalsnode + except ActivityNotFoundError: + globalsnode = _ba.getsession().sessionglobalsnode + for i in range(size): curve = _ba.newnode('animcurve', owner=node, name=('Driving ' + str(node) + ' \'' + attr + '\' member ' + str(i))) - sharedobj('globals').connectattr(driver, curve, 'in') + globalsnode.connectattr(driver, curve, 'in') curve.times = [int(mult * time) for time, val in items] curve.values = [val[i] for time, val in items] curve.offset = _ba.time(timeformat=TimeFormat.MILLISECONDS) + int( diff --git a/assets/src/ba_data/python/ba/_map.py b/assets/src/ba_data/python/ba/_map.py index 9af476a8..ead68a99 100644 --- a/assets/src/ba_data/python/ba/_map.py +++ b/assets/src/ba_data/python/ba/_map.py @@ -226,7 +226,7 @@ class Map(Actor): ' staticmethod in the activity constructor') # Set various globals. - gnode = _gameutils.sharedobj('globals') + gnode = _ba.getactivity().globalsnode # Set area-of-interest bounds. aoi_bounds = self.get_def_bound_box('area_of_interest_bounds') diff --git a/assets/src/ba_data/python/ba/_music.py b/assets/src/ba_data/python/ba/_music.py index a1962adb..48bf5005 100644 --- a/assets/src/ba_data/python/ba/_music.py +++ b/assets/src/ba_data/python/ba/_music.py @@ -506,7 +506,7 @@ def setmusic(musictype: Optional[MusicType], continuous: bool = False) -> None: # the do_play_music call in our music controller. This way we can # seamlessly support custom soundtracks in replays/etc since we're being # driven purely by node data. - gnode = _gameutils.sharedobj('globals') + gnode = _ba.getactivity().globalsnode gnode.music_continuous = continuous gnode.music = '' if musictype is None else musictype.value gnode.music_count += 1 diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py index d4f6a7bf..9bea0bae 100644 --- a/assets/src/ba_data/python/ba/_player.py +++ b/assets/src/ba_data/python/ba/_player.py @@ -46,6 +46,16 @@ class PlayerInfo: character: str +@dataclass +class StandLocation: + """Describes a point in space and an angle to face. + + Category: Gameplay Classes + """ + position: _ba.Vec3 + angle: Optional[float] = None + + class Player(Generic[TeamType]): """A player in a specific ba.Activity. diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py index 30b4f8b7..913ee62e 100644 --- a/assets/src/ba_data/python/ba/_session.py +++ b/assets/src/ba_data/python/ba/_session.py @@ -25,7 +25,7 @@ import weakref from typing import TYPE_CHECKING import _ba -from ba._error import print_error, print_exception +from ba._error import print_error, print_exception, NodeNotFoundError from ba._lang import Lstr from ba._player import Player @@ -113,7 +113,6 @@ class Session: # pylint: disable=cyclic-import from ba._lobby import Lobby from ba._stats import Stats - from ba._gameutils import sharedobj from ba._gameactivity import GameActivity from ba._activity import Activity from ba._team import SessionTeam @@ -201,7 +200,15 @@ class Session: self.stats = Stats() # Instantiate our session globals node which will apply its settings. - sharedobj('globals') + self._sessionglobalsnode = _ba.newnode('sessionglobals') + + @property + def sessionglobalsnode(self) -> ba.Node: + """The sessionglobals ba.Node for the session.""" + node = self._sessionglobalsnode + if not node: + raise NodeNotFoundError() + return node def on_player_request(self, player: ba.SessionPlayer) -> bool: """Called when a new ba.Player wants to join the Session. @@ -347,16 +354,6 @@ class Session: def on_team_leave(self, team: ba.SessionTeam) -> None: """Called when a ba.Team is leaving the session.""" - def _complete_end_activity(self, activity: ba.Activity, - results: Any) -> None: - # Run the subclass callback in the session context. - try: - with _ba.Context(self): - self.on_activity_end(activity, results) - except Exception: - print_exception('exception in on_activity_end() for session', self, - 'activity', activity, 'with results', results) - def end_activity(self, activity: ba.Activity, results: Any, delay: float, force: bool) -> None: """Commence shutdown of a ba.Activity (if not already occurring). @@ -414,7 +411,6 @@ class Session: (on_transition_in, etc) to get it. (so you can't do session.set_activity(foo) and then ba.newnode() to add a node to foo) """ - from ba._gameutils import sharedobj from ba._enums import TimeType # Sanity test: make sure this doesn't get called recursively. @@ -439,11 +435,8 @@ class Session: str(self._next_activity) + ')') prev_activity = self._activity_retained - if prev_activity is not None: - with _ba.Context(prev_activity): - prev_globals = sharedobj('globals') - else: - prev_globals = None + prev_globals = (prev_activity.globalsnode + if prev_activity is not None else None) # Let the activity do its thing. activity.transition_in(prev_globals) @@ -491,6 +484,16 @@ class Session: """ return [] + def _complete_end_activity(self, activity: ba.Activity, + results: Any) -> None: + # Run the subclass callback in the session context. + try: + with _ba.Context(self): + self.on_activity_end(activity, results) + except Exception: + print_exception('exception in on_activity_end() for session', self, + 'activity', activity, 'with results', results) + def _request_player(self, sessionplayer: ba.SessionPlayer) -> bool: """Called by the C++ layer when players want to join.""" diff --git a/assets/src/ba_data/python/bastd/actor/bomb.py b/assets/src/ba_data/python/bastd/actor/bomb.py index ec6cf25e..7eb080b8 100644 --- a/assets/src/ba_data/python/bastd/actor/bomb.py +++ b/assets/src/ba_data/python/bastd/actor/bomb.py @@ -26,12 +26,19 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypeVar import ba +from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Sequence, Optional, Callable, List, Tuple + 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') class BombFactory: @@ -153,6 +160,7 @@ class BombFactory: You shouldn't need to do this; call bastd.actor.bomb.get_factory() to get a shared instance. """ + shared = SharedObjects.get() self.bomb_model = ba.getmodel('bomb') self.sticky_bomb_model = ba.getmodel('bombSticky') @@ -191,17 +199,24 @@ class BombFactory: self.sticky_material = ba.Material() self.bomb_material.add_actions( - conditions=((('we_are_younger_than', 100), 'or', - ('they_are_younger_than', 100)), - 'and', ('they_have_material', - ba.sharedobj('object_material'))), - actions=('modify_node_collision', 'collide', False)) + conditions=( + ( + ('we_are_younger_than', 100), + 'or', + ('they_are_younger_than', 100), + ), + 'and', + ('they_have_material', shared.object_material), + ), + actions=('modify_node_collision', 'collide', False), + ) # we want pickup materials to always hit us even if we're currently not # colliding with their node (generally due to the above rule) - self.bomb_material.add_actions( - conditions=('they_have_material', ba.sharedobj('pickup_material')), - actions=('modify_part_collision', 'use_node_collide', False)) + self.bomb_material.add_actions(conditions=('they_have_material', + shared.pickup_material), + actions=('modify_part_collision', + 'use_node_collide', False)) self.bomb_material.add_actions(actions=('modify_part_collision', 'friction', 0.3)) @@ -209,36 +224,54 @@ class BombFactory: self.land_mine_no_explode_material = ba.Material() self.land_mine_blast_material = ba.Material() self.land_mine_blast_material.add_actions( - conditions=(('we_are_older_than', - 200), 'and', ('they_are_older_than', - 200), 'and', ('eval_colliding', ), - 'and', (('they_dont_have_material', - self.land_mine_no_explode_material), 'and', - (('they_have_material', - ba.sharedobj('object_material')), 'or', - ('they_have_material', - ba.sharedobj('player_material'))))), - actions=('message', 'our_node', 'at_connect', ImpactMessage())) + conditions=( + ('we_are_older_than', 200), + 'and', + ('they_are_older_than', 200), + 'and', + ('eval_colliding', ), + 'and', + ( + ('they_dont_have_material', + self.land_mine_no_explode_material), + 'and', + ( + ('they_have_material', shared.object_material), + 'or', + ('they_have_material', shared.player_material), + ), + ), + ), + actions=('message', 'our_node', 'at_connect', ImpactMessage()), + ) self.impact_blast_material = ba.Material() self.impact_blast_material.add_actions( - conditions=(('we_are_older_than', - 200), 'and', ('they_are_older_than', - 200), 'and', ('eval_colliding', ), - 'and', (('they_have_material', - ba.sharedobj('footing_material')), 'or', - ('they_have_material', - ba.sharedobj('object_material')))), - actions=('message', 'our_node', 'at_connect', ImpactMessage())) + conditions=( + ('we_are_older_than', 200), + 'and', + ('they_are_older_than', 200), + 'and', + ('eval_colliding', ), + 'and', + ( + ('they_have_material', shared.footing_material), + 'or', + ('they_have_material', shared.object_material), + ), + ), + actions=('message', 'our_node', 'at_connect', ImpactMessage()), + ) self.blast_material = ba.Material() self.blast_material.add_actions( - conditions=(('they_have_material', - ba.sharedobj('object_material'))), - actions=(('modify_part_collision', 'collide', True), - ('modify_part_collision', 'physical', - False), ('message', 'our_node', 'at_connect', - ExplodeHitMessage()))) + conditions=(('they_have_material', shared.object_material), ), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', False), + ('message', 'our_node', 'at_connect', ExplodeHitMessage()), + ), + ) self.dink_sounds = (ba.getsound('bombDrop01'), ba.getsound('bombDrop02')) @@ -247,10 +280,11 @@ class BombFactory: # collision sounds self.normal_sound_material.add_actions( - conditions=('they_have_material', - ba.sharedobj('footing_material')), - actions=(('impact_sound', self.dink_sounds, 2, 0.8), - ('roll_sound', self.roll_sound, 3, 6))) + conditions=('they_have_material', shared.footing_material), + actions=( + ('impact_sound', self.dink_sounds, 2, 0.8), + ('roll_sound', self.roll_sound, 3, 6), + )) self.sticky_material.add_actions(actions=(('modify_part_collision', 'stiffness', 0.1), @@ -258,24 +292,22 @@ class BombFactory: 'damping', 1.0))) self.sticky_material.add_actions( - conditions=(('they_have_material', - ba.sharedobj('player_material')), - 'or', ('they_have_material', - ba.sharedobj('footing_material'))), - actions=('message', 'our_node', 'at_connect', SplatMessage())) + conditions=( + ('they_have_material', shared.player_material), + 'or', + ('they_have_material', shared.footing_material), + ), + actions=('message', 'our_node', 'at_connect', SplatMessage()), + ) def get_factory() -> BombFactory: """Get/create a shared bastd.actor.bomb.BombFactory object.""" activity = ba.getactivity() - - # FIXME: Need to figure out an elegant way to store - # shared actor data with an activity. - factory: BombFactory - try: - factory = activity.shared_bomb_factory # type: ignore - except Exception: - factory = activity.shared_bomb_factory = BombFactory() # type: ignore + factory = getattr(activity, STORAGE_ATTR_NAME, None) + if factory is None: + factory = BombFactory() + setattr(activity, STORAGE_ATTR_NAME, factory) assert isinstance(factory, BombFactory) return factory @@ -329,26 +361,27 @@ class Blast(ba.Actor): super().__init__() + shared = SharedObjects.get() factory = get_factory() self.blast_type = blast_type - self.source_player = source_player + self._source_player = source_player self.hit_type = hit_type self.hit_subtype = hit_subtype self.radius = blast_radius # set our position a bit lower so we throw more things upward - rmats = (factory.blast_material, ba.sharedobj('attack_material')) - self.node = ba.newnode('region', - delegate=self, - attrs={ - 'position': (position[0], position[1] - 0.1, - position[2]), - 'scale': - (self.radius, self.radius, self.radius), - 'type': 'sphere', - 'materials': rmats - }) + rmats = (factory.blast_material, shared.attack_material) + self.node = ba.newnode( + 'region', + delegate=self, + attrs={ + 'position': (position[0], position[1] - 0.1, position[2]), + 'scale': (self.radius, self.radius, self.radius), + 'type': 'sphere', + 'materials': rmats + }, + ) ba.timer(0.05, self.node.delete) @@ -619,7 +652,7 @@ class Blast(ba.Actor): hit_type=self.hit_type, hit_subtype=self.hit_subtype, radius=self.radius, - source_player=ba.existing(self.source_player))) + source_player=ba.existing(self._source_player))) if self.blast_type == 'ice': ba.playsound(get_factory().freeze_sound, 10, position=nodepos) node.handlemessage(ba.FreezeMessage()) @@ -654,6 +687,7 @@ class Bomb(ba.Actor): """ super().__init__() + shared = SharedObjects.get() factory = get_factory() if bomb_type not in ('ice', 'impact', 'land_mine', 'normal', 'sticky', @@ -681,7 +715,7 @@ class Bomb(ba.Actor): self._explode_callbacks: List[Callable[[Bomb, Blast], Any]] = [] # the player this came from - self.source_player = source_player + self._source_player = source_player # by default our hit type/subtype is our own, but we pick up types of # whoever sets us off so we know what caused a chain reaction @@ -702,12 +736,10 @@ class Bomb(ba.Actor): # ground.. perhaps we don't wanna add this even in the tnt case?.. materials: Tuple[ba.Material, ...] if self.bomb_type == 'tnt': - materials = (factory.bomb_material, - ba.sharedobj('footing_material'), - ba.sharedobj('object_material')) + materials = (factory.bomb_material, shared.footing_material, + shared.object_material) else: - materials = (factory.bomb_material, - ba.sharedobj('object_material')) + materials = (factory.bomb_material, shared.object_material) if self.bomb_type == 'impact': materials = materials + (factory.impact_blast_material, ) @@ -824,11 +856,20 @@ class Bomb(ba.Actor): ba.animate(self.node, 'model_scale', {0: 0, 0.2: 1.3, 0.26: 1}) - def get_source_player(self) -> Optional[ba.Player]: - """Returns a ba.Player representing the source of this bomb. + def get_source_player( + self, playertype: Type[PlayerType]) -> Optional[PlayerType]: + """Return the source-player if there is one and they still exist. - Be prepared for values of None or invalid Player refs.""" - return self.source_player + The type of player for the current activity should be passed so that + the type-checker properly identifies the returned value as one. + """ + player: Any = self._source_player + assert isinstance(player, (playertype, type(None))) + + # We should not be delivering invalid refs. + # (technically if someone holds on to this message this can happen) + assert player is None or player.exists() + return player def on_expire(self) -> None: super().on_expire() @@ -899,7 +940,7 @@ class Bomb(ba.Actor): velocity=self.node.velocity, blast_radius=self.blast_radius, blast_type=self.bomb_type, - source_player=self.source_player, + source_player=ba.existing(self._source_player), hit_type=self.hit_type, hit_subtype=self.hit_subtype).autoretain() for callback in self._explode_callbacks: @@ -979,7 +1020,7 @@ class Bomb(ba.Actor): # person causing them). source_player = msg.get_source_player(ba.Player) if source_player is not None: - self.source_player = source_player + self._source_player = source_player # Also inherit the hit type (if a landmine sets off by a bomb, # the credit should go to the mine) @@ -1007,12 +1048,14 @@ class Bomb(ba.Actor): self.explode() elif isinstance(msg, ImpactMessage): self._handle_impact() - elif isinstance(msg, ba.PickedUpMessage): - # change our source to whoever just picked us up *only* if its None - # this way we can get points for killing bots with their own bombs - # hmm would there be a downside to this?... - if self.source_player is not None: - self.source_player = msg.node.source_player + # Ok the logic below looks like it was backwards to me. Disabling + # until further notice. + # elif isinstance(msg, ba.PickedUpMessage): + # # Change our source to whoever just picked us up *only* if it + # # is None. This way we can get points for killing bots with their + # # own bombs. Hmm would there be a downside to this? + # if self._source_player is not None: + # self._source_player = msg.node.source_player elif isinstance(msg, SplatMessage): self._handle_splat() elif isinstance(msg, ba.DroppedMessage): diff --git a/assets/src/ba_data/python/bastd/actor/flag.py b/assets/src/ba_data/python/bastd/actor/flag.py index 59515af0..0e5b014f 100644 --- a/assets/src/ba_data/python/bastd/actor/flag.py +++ b/assets/src/ba_data/python/bastd/actor/flag.py @@ -26,6 +26,7 @@ from dataclasses import dataclass from typing import TYPE_CHECKING import ba +from bastd.gameutils import SharedObjects if TYPE_CHECKING: from typing import Any, Sequence, Optional @@ -64,13 +65,13 @@ class FlagFactory: You shouldn't need to do this; call bastd.actor.flag.get_factory() to get a shared instance. """ - + shared = SharedObjects.get() self.flagmaterial = ba.Material() self.flagmaterial.add_actions( conditions=( ('we_are_younger_than', 100), 'and', - ('they_have_material', ba.sharedobj('object_material')), + ('they_have_material', shared.object_material), ), actions=('modify_node_collision', 'collide', False), ) @@ -78,7 +79,7 @@ class FlagFactory: self.flagmaterial.add_actions( conditions=( 'they_have_material', - ba.sharedobj('footing_material'), + shared.footing_material, ), actions=( ('message', 'our_node', 'at_connect', 'footing', 1), @@ -91,7 +92,7 @@ class FlagFactory: self.flagmaterial.add_actions( conditions=( 'they_have_material', - ba.sharedobj('footing_material'), + shared.footing_material, ), actions=( ('impact_sound', self.impact_sound, 2, 5), @@ -102,9 +103,9 @@ class FlagFactory: self.no_hit_material = ba.Material() self.no_hit_material.add_actions( conditions=( - ('they_have_material', ba.sharedobj('pickup_material')), + ('they_have_material', shared.pickup_material), 'or', - ('they_have_material', ba.sharedobj('attack_material')), + ('they_have_material', shared.attack_material), ), actions=('modify_part_collision', 'collide', False), ) @@ -112,9 +113,9 @@ class FlagFactory: # We also don't want anything moving it. self.no_hit_material.add_actions( conditions=( - ('they_have_material', ba.sharedobj('object_material')), + ('they_have_material', shared.object_material), 'or', - ('they_dont_have_material', ba.sharedobj('footing_material')), + ('they_dont_have_material', shared.footing_material), ), actions=(('modify_part_collision', 'collide', False), ('modify_part_collision', 'physical', False)), @@ -215,6 +216,7 @@ class Flag(ba.Actor): self._initial_position: Optional[Sequence[float]] = None self._has_moved = False + shared = SharedObjects.get() factory = FlagFactory.get() if materials is None: @@ -225,9 +227,8 @@ class Flag(ba.Actor): if not touchable: materials = [factory.no_hit_material] + materials - finalmaterials = ( - [ba.sharedobj('object_material'), factory.flagmaterial] + - materials) + finalmaterials = ([shared.object_material, factory.flagmaterial] + + materials) self.node = ba.newnode('flag', attrs={ 'position': diff --git a/assets/src/ba_data/python/bastd/actor/onscreencountdown.py b/assets/src/ba_data/python/bastd/actor/onscreencountdown.py index 59b5070c..6e767e54 100644 --- a/assets/src/ba_data/python/bastd/actor/onscreencountdown.py +++ b/assets/src/ba_data/python/bastd/actor/onscreencountdown.py @@ -79,7 +79,7 @@ class OnScreenCountdown(ba.Actor): def start(self) -> None: """Start the timer.""" - globalsnode = ba.sharedobj('globals') + globalsnode = ba.getactivity().globalsnode globalsnode.connectattr('time', self.inputnode, 'time1') self.inputnode.time2 = (globalsnode.time + (self._timeremaining + 1) * 1000) @@ -87,7 +87,8 @@ class OnScreenCountdown(ba.Actor): def on_expire(self) -> None: super().on_expire() - # release callbacks/refs + + # Release callbacks/refs. self._endcall = None def _update(self, forcevalue: int = None) -> None: diff --git a/assets/src/ba_data/python/bastd/actor/onscreentimer.py b/assets/src/ba_data/python/bastd/actor/onscreentimer.py index 8b5699b6..3facd7e3 100644 --- a/assets/src/ba_data/python/bastd/actor/onscreentimer.py +++ b/assets/src/ba_data/python/bastd/actor/onscreentimer.py @@ -66,7 +66,8 @@ class OnScreenTimer(ba.Actor): assert isinstance(tval, int) self._starttime_ms = tval self.inputnode.time1 = self._starttime_ms - ba.sharedobj('globals').connectattr('time', self.inputnode, 'time2') + ba.getactivity().globalsnode.connectattr('time', self.inputnode, + 'time2') def has_started(self) -> bool: """Return whether this timer has started yet.""" diff --git a/assets/src/ba_data/python/bastd/actor/powerupbox.py b/assets/src/ba_data/python/bastd/actor/powerupbox.py index 77dc6ab9..2af9a7fd 100644 --- a/assets/src/ba_data/python/bastd/actor/powerupbox.py +++ b/assets/src/ba_data/python/bastd/actor/powerupbox.py @@ -26,6 +26,7 @@ import random from typing import TYPE_CHECKING import ba +from bastd.gameutils import SharedObjects if TYPE_CHECKING: from typing import List, Any, Optional, Sequence @@ -104,6 +105,7 @@ class PowerupBoxFactory: to get a shared instance. """ from ba.internal import get_default_powerup_distribution + shared = SharedObjects.get() self._lastpoweruptype: Optional[str] = None self.model = ba.getmodel('powerup') self.model_simple = ba.getmodel('powerupSimple') @@ -130,19 +132,22 @@ class PowerupBoxFactory: # Pass a powerup-touched message to applicable stuff. self.powerup_material.add_actions( conditions=('they_have_material', self.powerup_accept_material), - actions=(('modify_part_collision', 'collide', - True), ('modify_part_collision', 'physical', False), - ('message', 'our_node', 'at_connect', _TouchedMessage()))) + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', False), + ('message', 'our_node', 'at_connect', _TouchedMessage()), + )) # We don't wanna be picked up. self.powerup_material.add_actions( - conditions=('they_have_material', ba.sharedobj('pickup_material')), - actions=('modify_part_collision', 'collide', False)) + conditions=('they_have_material', shared.pickup_material), + actions=('modify_part_collision', 'collide', False), + ) self.powerup_material.add_actions( - conditions=('they_have_material', - ba.sharedobj('footing_material')), - actions=('impact_sound', self.drop_sound, 0.5, 0.1)) + conditions=('they_have_material', shared.footing_material), + actions=('impact_sound', self.drop_sound, 0.5, 0.1), + ) self._powerupdist: List[str] = [] for powerup, freq in get_default_powerup_distribution(): @@ -228,7 +233,7 @@ class PowerupBox(ba.Actor): """ super().__init__() - + shared = SharedObjects.get() factory = PowerupBoxFactory.get() self.poweruptype = poweruptype self._powersgiven = False @@ -270,7 +275,7 @@ class PowerupBox(ba.Actor): 'reflection': 'powerup', 'reflection_scale': [1.0], 'materials': (factory.powerup_material, - ba.sharedobj('object_material')) + shared.object_material) }) # yapf: disable # Animate in. diff --git a/assets/src/ba_data/python/bastd/actor/spaz.py b/assets/src/ba_data/python/bastd/actor/spaz.py index cf710b07..5d769078 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.gameutils import SharedObjects if TYPE_CHECKING: from typing import (Any, Sequence, Optional, Dict, List, Union, Callable, @@ -108,6 +109,7 @@ class Spaz(ba.Actor): # pylint: disable=too-many-statements super().__init__() + shared = SharedObjects.get() activity = self.activity factory = get_factory() @@ -126,7 +128,7 @@ class Spaz(ba.Actor): self._punch_power_scale = 1.2 else: self._punch_power_scale = factory.punch_power_scale - self.fly = ba.sharedobj('globals').happy_thoughts_mode + self.fly = ba.getactivity().globalsnode.happy_thoughts_mode if isinstance(activity, ba.GameActivity): self._hockey = activity.map.is_hockey else: @@ -135,14 +137,10 @@ class Spaz(ba.Actor): self._cursed = False self._connected_to_player: Optional[ba.Player] = None materials = [ - factory.spaz_material, - ba.sharedobj('object_material'), - ba.sharedobj('player_material') - ] - roller_materials = [ - factory.roller_material, - ba.sharedobj('player_material') + factory.spaz_material, shared.object_material, + shared.player_material ] + roller_materials = [factory.roller_material, shared.player_material] extras_material = [] if can_accept_powerups: @@ -152,8 +150,8 @@ class Spaz(ba.Actor): extras_material.append(pam) media = factory.get_media(character) - punchmats = (factory.punch_material, ba.sharedobj('attack_material')) - pickupmats = (factory.pickup_material, ba.sharedobj('pickup_material')) + punchmats = (factory.punch_material, shared.attack_material) + pickupmats = (factory.pickup_material, shared.pickup_material) self.node: ba.Node = ba.newnode( type='spaz', delegate=self, diff --git a/assets/src/ba_data/python/bastd/actor/spazfactory.py b/assets/src/ba_data/python/bastd/actor/spazfactory.py index ac7e8983..6946088a 100644 --- a/assets/src/ba_data/python/bastd/actor/spazfactory.py +++ b/assets/src/ba_data/python/bastd/actor/spazfactory.py @@ -24,9 +24,11 @@ from __future__ import annotations from typing import TYPE_CHECKING -import _ba import ba -from bastd.actor import spaz as basespaz +from bastd.gameutils import SharedObjects +from bastd.actor.spaz import (PickupMessage, PunchHitMessage, + CurseExplodeMessage) +import _ba if TYPE_CHECKING: from typing import Any, Dict @@ -101,6 +103,7 @@ class SpazFactory: def __init__(self) -> None: """Instantiate a factory object.""" + shared = SharedObjects.get() self.impact_sounds_medium = (ba.getsound('impactMedium'), ba.getsound('impactMedium2')) self.impact_sounds_hard = (ba.getsound('impactHard'), @@ -123,14 +126,14 @@ class SpazFactory: self.pickup_material = ba.Material() self.curse_material = ba.Material() - footing_material = ba.sharedobj('footing_material') - object_material = ba.sharedobj('object_material') - player_material = ba.sharedobj('player_material') - region_material = ba.sharedobj('region_material') + footing_material = shared.footing_material + object_material = shared.object_material + player_material = shared.player_material + region_material = shared.region_material - # send footing messages to spazzes so they know when they're on solid - # ground. - # eww this should really just be built into the spaz node + # Send footing messages to spazzes so they know when they're on + # solid ground. + # Eww; this probably should just be built into the spaz node. self.roller_material.add_actions( conditions=('they_have_material', footing_material), actions=(('message', 'our_node', 'at_connect', 'footing', 1), @@ -140,27 +143,36 @@ class SpazFactory: conditions=('they_have_material', footing_material), actions=(('message', 'our_node', 'at_connect', 'footing', 1), ('message', 'our_node', 'at_disconnect', 'footing', -1))) - # punches + + # Punches. self.punch_material.add_actions( conditions=('they_are_different_node_than_us', ), - actions=(('modify_part_collision', 'collide', - True), ('modify_part_collision', 'physical', False), - ('message', 'our_node', 'at_connect', - basespaz.PunchHitMessage()))) - # pickups + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', False), + ('message', 'our_node', 'at_connect', PunchHitMessage()), + )) + + # Pickups. self.pickup_material.add_actions( conditions=(('they_are_different_node_than_us', ), 'and', ('they_have_material', object_material)), - actions=(('modify_part_collision', 'collide', - True), ('modify_part_collision', 'physical', False), - ('message', 'our_node', 'at_connect', - basespaz.PickupMessage()))) - # curse + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', False), + ('message', 'our_node', 'at_connect', PickupMessage()), + )) + + # Curse. self.curse_material.add_actions( - conditions=(('they_are_different_node_than_us', ), 'and', - ('they_have_material', player_material)), + conditions=( + ('they_are_different_node_than_us', ), + 'and', + ('they_have_material', player_material), + ), actions=('message', 'our_node', 'at_connect', - basespaz.CurseExplodeMessage())) + CurseExplodeMessage()), + ) self.foot_impact_sounds = (ba.getsound('footImpact01'), ba.getsound('footImpact02'), @@ -171,34 +183,45 @@ class SpazFactory: self.roller_material.add_actions( conditions=('they_have_material', footing_material), - actions=(('impact_sound', self.foot_impact_sounds, 1, - 0.2), ('skid_sound', self.foot_skid_sound, 20, 0.3), - ('roll_sound', self.foot_roll_sound, 20, 3.0))) + actions=( + ('impact_sound', self.foot_impact_sounds, 1, 0.2), + ('skid_sound', self.foot_skid_sound, 20, 0.3), + ('roll_sound', self.foot_roll_sound, 20, 3.0), + )) self.skid_sound = ba.getsound('gravelSkid') self.spaz_material.add_actions( conditions=('they_have_material', footing_material), - actions=(('impact_sound', self.foot_impact_sounds, 20, - 6), ('skid_sound', self.skid_sound, 2.0, 1), - ('roll_sound', self.skid_sound, 2.0, 1))) + actions=( + ('impact_sound', self.foot_impact_sounds, 20, 6), + ('skid_sound', self.skid_sound, 2.0, 1), + ('roll_sound', self.skid_sound, 2.0, 1), + )) self.shield_up_sound = ba.getsound('shieldUp') self.shield_down_sound = ba.getsound('shieldDown') self.shield_hit_sound = ba.getsound('shieldHit') - # we don't want to collide with stuff we're initially overlapping - # (unless its marked with a special region material) + # We don't want to collide with stuff we're initially overlapping + # (unless its marked with a special region material). self.spaz_material.add_actions( - conditions=((('we_are_younger_than', 51), 'and', - ('they_are_different_node_than_us', )), 'and', - ('they_dont_have_material', region_material)), - actions=('modify_node_collision', 'collide', False)) + conditions=( + ( + ('we_are_younger_than', 51), + 'and', + ('they_are_different_node_than_us', ), + ), + 'and', + ('they_dont_have_material', region_material), + ), + actions=('modify_node_collision', 'collide', False), + ) self.spaz_media: Dict[str, Any] = {} - # lets load some basic rules (allows them to be tweaked from the - # master server) + # Lets load some basic rules. + # (allows them to be tweaked from the master server) self.shield_decay_rate = _ba.get_account_misc_read_val('rsdr', 10.0) self.punch_cooldown = _ba.get_account_misc_read_val('rpc', 400) self.punch_cooldown_gloves = (_ba.get_account_misc_read_val( diff --git a/assets/src/ba_data/python/bastd/game/assault.py b/assets/src/ba_data/python/bastd/game/assault.py index 062a030e..8e0bf524 100644 --- a/assets/src/ba_data/python/bastd/game/assault.py +++ b/assets/src/ba_data/python/bastd/game/assault.py @@ -32,6 +32,7 @@ import ba from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.flag import Flag from bastd.actor.scoreboard import Scoreboard +from bastd.gameutils import SharedObjects if TYPE_CHECKING: from typing import Any, Type, List, Dict, Sequence, Union @@ -111,6 +112,7 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): return 'touch ${ARG1} flags', self._score_to_win def create_team(self, sessionteam: ba.SessionTeam) -> Team: + shared = SharedObjects.get() base_pos = self.map.get_flag_position(sessionteam.id) ba.newnode('light', attrs={ @@ -129,7 +131,7 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): mat = self._base_region_materials[sessionteam.id] = ba.Material() mat.add_actions( - conditions=('they_have_material', ba.sharedobj('player_material')), + conditions=('they_have_material', shared.player_material), actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), diff --git a/assets/src/ba_data/python/bastd/game/chosenone.py b/assets/src/ba_data/python/bastd/game/chosenone.py index f65d12c8..740631bc 100644 --- a/assets/src/ba_data/python/bastd/game/chosenone.py +++ b/assets/src/ba_data/python/bastd/game/chosenone.py @@ -31,6 +31,7 @@ import ba from bastd.actor.flag import Flag from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard +from bastd.gameutils import SharedObjects if TYPE_CHECKING: from typing import Any, Type, List, Dict, Optional, Sequence, Union @@ -142,6 +143,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): def on_begin(self) -> None: super().on_begin() + shared = SharedObjects.get() self.setup_standard_time_limit(self._time_limit) self.setup_standard_powerup_drops() self._flag_spawn_pos = self.map.get_flag_position(None) @@ -155,7 +157,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): mat.add_actions( conditions=( 'they_have_material', - ba.sharedobj('player_material'), + shared.player_material, ), actions=( ('modify_part_collision', 'collide', True), @@ -329,8 +331,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): super().handlemessage(msg) player = msg.getplayer(Player) if player is self._get_chosen_one_player(): - killerplayer = ba.playercast_o(Player, - msg.getkillerplayer(Player)) + killerplayer = msg.getkillerplayer(Player) self._set_chosen_one_player(None if ( killerplayer is None or killerplayer is player or not killerplayer.is_alive()) else killerplayer) diff --git a/assets/src/ba_data/python/bastd/game/conquest.py b/assets/src/ba_data/python/bastd/game/conquest.py index c6ba9c4d..938b4c51 100644 --- a/assets/src/ba_data/python/bastd/game/conquest.py +++ b/assets/src/ba_data/python/bastd/game/conquest.py @@ -32,6 +32,7 @@ import ba from bastd.actor.flag import Flag from bastd.actor.scoreboard import Scoreboard from bastd.actor.playerspaz import PlayerSpaz +from bastd.gameutils import SharedObjects if TYPE_CHECKING: from typing import Any, Optional, Type, List, Dict, Sequence, Union @@ -119,6 +120,7 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): def __init__(self, settings: Dict[str, Any]): super().__init__(settings) + shared = SharedObjects.get() self._scoreboard = Scoreboard() self._score_sound = ba.getsound('score') self._swipsound = ba.getsound('swip') @@ -134,7 +136,7 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): # We want flags to tell us they've been hit but not react physically. self._extraflagmat.add_actions( - conditions=('they_have_material', ba.sharedobj('player_material')), + conditions=('they_have_material', shared.player_material), actions=(('modify_part_collision', 'collide', True), ('call', 'at_connect', self._handle_flag_player_collide))) diff --git a/assets/src/ba_data/python/bastd/game/easteregghunt.py b/assets/src/ba_data/python/bastd/game/easteregghunt.py index 244e79d4..31262e3a 100644 --- a/assets/src/ba_data/python/bastd/game/easteregghunt.py +++ b/assets/src/ba_data/python/bastd/game/easteregghunt.py @@ -35,6 +35,7 @@ from bastd.actor.spazbot import SpazBotSet, BouncyBot, SpazBotDiedMessage from bastd.actor.onscreencountdown import OnScreenCountdown from bastd.actor.scoreboard import Scoreboard from bastd.actor.respawnicon import RespawnIcon +from bastd.gameutils import SharedObjects if TYPE_CHECKING: from typing import Any, Type, Dict, List, Tuple, Optional @@ -78,6 +79,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): def __init__(self, settings: Dict[str, Any]): super().__init__(settings) + shared = SharedObjects.get() self._last_player_death_time = None self._scoreboard = Scoreboard() self.egg_model = ba.getmodel('egg') @@ -89,7 +91,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): self._max_eggs = 1.0 self.egg_material = ba.Material() self.egg_material.add_actions( - conditions=('they_have_material', ba.sharedobj('player_material')), + conditions=('they_have_material', shared.player_material), actions=(('call', 'at_connect', self._on_egg_player_collide), )) self._eggs: List[Egg] = [] self._update_timer: Optional[ba.Timer] = None @@ -243,12 +245,13 @@ class Egg(ba.Actor): super().__init__() activity = self.activity assert isinstance(activity, EasterEggHuntGame) + shared = SharedObjects.get() # Spawn just above the provided point. self._spawn_pos = (position[0], position[1] + 1.0, position[2]) ctex = (activity.egg_tex_1, activity.egg_tex_2, activity.egg_tex_3)[random.randrange(3)] - mats = [ba.sharedobj('object_material'), activity.egg_material] + mats = [shared.object_material, activity.egg_material] self.node = ba.newnode('prop', delegate=self, attrs={ diff --git a/assets/src/ba_data/python/bastd/game/football.py b/assets/src/ba_data/python/bastd/game/football.py index e0f5d7b0..d44573a7 100644 --- a/assets/src/ba_data/python/bastd/game/football.py +++ b/assets/src/ba_data/python/bastd/game/football.py @@ -530,8 +530,8 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): })) self._time_text_input = ba.NodeActor( ba.newnode('timedisplay', attrs={'showsubseconds': True})) - ba.sharedobj('globals').connectattr('time', self._time_text_input.node, - 'time2') + self.globalsnode.connectattr('time', self._time_text_input.node, + 'time2') assert self._time_text_input.node assert self._time_text.node self._time_text_input.node.connectattr('output', self._time_text.node, diff --git a/assets/src/ba_data/python/bastd/game/hockey.py b/assets/src/ba_data/python/bastd/game/hockey.py index bfb6e196..333a5d19 100644 --- a/assets/src/ba_data/python/bastd/game/hockey.py +++ b/assets/src/ba_data/python/bastd/game/hockey.py @@ -31,6 +31,7 @@ import ba from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard from bastd.actor.powerupbox import PowerupBoxFactory +from bastd.gameutils import SharedObjects if TYPE_CHECKING: from typing import Any, Sequence, Dict, Type, List, Optional, Union @@ -48,6 +49,7 @@ class Puck(ba.Actor): def __init__(self, position: Sequence[float] = (0.0, 1.0, 0.0)): super().__init__() + shared = SharedObjects.get() activity = self.getactivity() # Spawn just above the provided point. @@ -56,7 +58,7 @@ class Puck(ba.Actor): self.scored = False assert activity is not None assert isinstance(activity, HockeyGame) - pmats = [ba.sharedobj('object_material'), activity.puck_material] + pmats = [shared.object_material, activity.puck_material] self.node = ba.newnode('prop', delegate=self, attrs={ @@ -153,6 +155,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): def __init__(self, settings: Dict[str, Any]): super().__init__(settings) + shared = SharedObjects.get() self._scoreboard = Scoreboard() self._cheer_sound = ba.getsound('cheer') self._chant_sound = ba.getsound('crowdChant') @@ -165,22 +168,26 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): self.puck_material = ba.Material() self.puck_material.add_actions(actions=(('modify_part_collision', 'friction', 0.5))) + self.puck_material.add_actions(conditions=('they_have_material', + shared.pickup_material), + actions=('modify_part_collision', + 'collide', False)) self.puck_material.add_actions( - conditions=('they_have_material', ba.sharedobj('pickup_material')), - actions=('modify_part_collision', 'collide', False)) - self.puck_material.add_actions( - conditions=(('we_are_younger_than', 100), - 'and', ('they_have_material', - ba.sharedobj('object_material'))), - actions=('modify_node_collision', 'collide', False)) - self.puck_material.add_actions( - conditions=('they_have_material', - ba.sharedobj('footing_material')), - actions=('impact_sound', self._puck_sound, 0.2, 5)) + conditions=( + ('we_are_younger_than', 100), + 'and', + ('they_have_material', shared.object_material), + ), + actions=('modify_node_collision', 'collide', False), + ) + self.puck_material.add_actions(conditions=('they_have_material', + shared.footing_material), + actions=('impact_sound', + self._puck_sound, 0.2, 5)) # Keep track of which player last touched the puck self.puck_material.add_actions( - conditions=('they_have_material', ba.sharedobj('player_material')), + conditions=('they_have_material', shared.player_material), actions=(('call', 'at_connect', self._handle_puck_player_collide), )) diff --git a/assets/src/ba_data/python/bastd/game/kingofthehill.py b/assets/src/ba_data/python/bastd/game/kingofthehill.py index b0dfe47c..4b70add0 100644 --- a/assets/src/ba_data/python/bastd/game/kingofthehill.py +++ b/assets/src/ba_data/python/bastd/game/kingofthehill.py @@ -33,6 +33,7 @@ import ba from bastd.actor.flag import Flag from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard +from bastd.gameutils import SharedObjects if TYPE_CHECKING: from weakref import ReferenceType @@ -97,6 +98,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): def __init__(self, settings: Dict[str, Any]): super().__init__(settings) + shared = SharedObjects.get() self._scoreboard = Scoreboard() self._swipsound = ba.getsound('swip') self._tick_sound = ba.getsound('tick') @@ -121,7 +123,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): self._time_limit = float(settings['Time Limit']) self._flag_region_material = ba.Material() self._flag_region_material.add_actions( - conditions=('they_have_material', ba.sharedobj('player_material')), + conditions=('they_have_material', shared.player_material), actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), @@ -145,6 +147,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): def on_begin(self) -> None: super().on_begin() + shared = SharedObjects.get() self.setup_standard_time_limit(self._time_limit) self.setup_standard_powerup_drops() self._flag_pos = self.map.get_flag_position(None) @@ -164,10 +167,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): 'color': (0.2, 0.2, 0.2) }) # Flag region. - flagmats = [ - self._flag_region_material, - ba.sharedobj('region_material') - ] + flagmats = [self._flag_region_material, shared.region_material] ba.newnode('region', attrs={ 'position': self._flag_pos, diff --git a/assets/src/ba_data/python/bastd/game/race.py b/assets/src/ba_data/python/bastd/game/race.py index 41485b2e..f28d2736 100644 --- a/assets/src/ba_data/python/bastd/game/race.py +++ b/assets/src/ba_data/python/bastd/game/race.py @@ -33,6 +33,7 @@ import ba from bastd.actor.bomb import Bomb from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard +from bastd.gameutils import SharedObjects if TYPE_CHECKING: from typing import (Any, Type, Tuple, List, Sequence, Optional, Dict, @@ -196,14 +197,17 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): def on_transition_in(self) -> None: super().on_transition_in() + shared = SharedObjects.get() pts = self.map.get_def_points('race_point') mat = self.race_region_material = ba.Material() mat.add_actions(conditions=('they_have_material', - ba.sharedobj('player_material')), - actions=(('modify_part_collision', 'collide', True), - ('modify_part_collision', 'physical', - False), ('call', 'at_connect', - self._handle_race_point_collide))) + shared.player_material), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', False), + ('call', 'at_connect', + self._handle_race_point_collide), + )) for rpt in pts: self._regions.append(RaceRegion(rpt, len(self._regions))) diff --git a/assets/src/ba_data/python/bastd/game/runaround.py b/assets/src/ba_data/python/bastd/game/runaround.py index 7f6ca1d7..b268fe4f 100644 --- a/assets/src/ba_data/python/bastd/game/runaround.py +++ b/assets/src/ba_data/python/bastd/game/runaround.py @@ -34,6 +34,7 @@ from bastd.actor.bomb import TNTSpawner from bastd.actor.scoreboard import Scoreboard from bastd.actor.respawnicon import RespawnIcon from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory +from bastd.gameutils import SharedObjects from bastd.actor.spazbot import ( SpazBotSet, SpazBot, SpazBotDiedMessage, BomberBot, BrawlerBot, TriggerBot, TriggerBotPro, BomberBotProShielded, TriggerBotProShielded, ChargerBot, @@ -88,6 +89,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): def __init__(self, settings: Dict[str, Any]): settings['map'] = 'Tower D' super().__init__(settings) + shared = SharedObjects.get() self._preset = self.settings_raw.get('preset', 'pro') self._player_death_sound = ba.getsound('playerDeath') @@ -109,7 +111,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._score_region_material = ba.Material() self._score_region_material.add_actions( - conditions=('they_have_material', ba.sharedobj('player_material')), + conditions=('they_have_material', shared.player_material), actions=(('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('call', 'at_connect', self._handle_reached_end))) diff --git a/assets/src/ba_data/python/bastd/game/targetpractice.py b/assets/src/ba_data/python/bastd/game/targetpractice.py index ea34c785..0c4c0252 100644 --- a/assets/src/ba_data/python/bastd/game/targetpractice.py +++ b/assets/src/ba_data/python/bastd/game/targetpractice.py @@ -171,9 +171,9 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]): # Feed the explosion point to all our targets and get points in return. # Note: we operate on a copy of self._targets since the list may change # under us if we hit stuff (don't wanna get points for new targets). - player = ba.playercast_o(Player, bomb.get_source_player()) + player = bomb.get_source_player(Player) if not player: - return # could happen if they leave after throwing a bomb.. + return # Could happen if they leave after throwing a bomb. bullseye = any( target.do_hit_at_position(pos, player) diff --git a/assets/src/ba_data/python/bastd/gameutils.py b/assets/src/ba_data/python/bastd/gameutils.py index 17cf39a8..4519e988 100644 --- a/assets/src/ba_data/python/bastd/gameutils.py +++ b/assets/src/ba_data/python/bastd/gameutils.py @@ -24,5 +24,142 @@ from __future__ import annotations from typing import TYPE_CHECKING +import ba + if TYPE_CHECKING: - from typing import Sequence + 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. + + Category: Gameplay Classes + + Objects contained here are created on-demand as accessed and shared + by everything in the current activity. This includes things such as + standard materials. + """ + + def __init__(self) -> None: + activity = ba.getactivity() + if hasattr(activity, STORAGE_ATTR_NAME): + raise RuntimeError('Use SharedObjects.get() to fetch the' + ' shared instance for this activity.') + self._object_material: Optional[ba.Material] = None + self._player_material: Optional[ba.Material] = None + self._pickup_material: Optional[ba.Material] = None + self._footing_material: Optional[ba.Material] = None + self._attack_material: Optional[ba.Material] = None + self._death_material: Optional[ba.Material] = None + self._region_material: Optional[ba.Material] = None + self._railing_material: Optional[ba.Material] = None + + @staticmethod + def get() -> SharedObjects: + """Fetch/create the instance of this class for the current activity.""" + activity = ba.getactivity() + shobs = getattr(activity, STORAGE_ATTR_NAME, None) + if shobs is None: + shobs = SharedObjects() + setattr(activity, STORAGE_ATTR_NAME, shobs) + assert isinstance(shobs, SharedObjects) + return shobs + + @property + def player_material(self) -> ba.Material: + """a ba.Material to be applied to player parts. Generally, + materials related to the process of scoring when reaching a goal, etc + will look for the presence of this material on things that hit them. + """ + if self._player_material is None: + self._player_material = ba.Material() + return self._player_material + + @property + def object_material(self) -> ba.Material: + """A ba.Material that should be applied to any small, + normal, physical objects such as bombs, boxes, players, etc. Other + materials often check for the presence of this material as a + prerequisite for performing certain actions (such as disabling + collisions between initially-overlapping objects) + """ + if self._object_material is None: + self._object_material = ba.Material() + return self._object_material + + @property + def pickup_material(self) -> ba.Material: + """A ba.Material; collision shapes used for picking things + up will have this material applied. To prevent an object from being + picked up, you can add a material that disables collisions against + things containing this material. + """ + if self._pickup_material is None: + self._pickup_material = ba.Material() + return self._pickup_material + + @property + def footing_material(self) -> ba.Material: + """Anything that can be 'walked on' should have this + ba.Material applied; generally just terrain and whatnot. A character + will snap upright whenever touching something with this material so it + should not be applied to props, etc. + """ + if self._footing_material is None: + self._footing_material = ba.Material() + return self._footing_material + + @property + def attack_material(self) -> ba.Material: + """A ba.Material applied to explosion shapes, punch + shapes, etc. An object not wanting to receive impulse/etc messages can + disable collisions against this material. + """ + if self._attack_material is None: + self._attack_material = ba.Material() + return self._attack_material + + @property + def death_material(self) -> ba.Material: + """A ba.Material that sends a ba.DieMessage() to anything + that touches it; handy for terrain below a cliff, etc. + """ + if self._death_material is None: + mat = self._death_material = ba.Material() + mat.add_actions( + ('message', 'their_node', 'at_connect', ba.DieMessage())) + return self._death_material + + @property + def region_material(self) -> ba.Material: + """A ba.Material used for non-physical collision shapes + (regions); collisions can generally be allowed with this material even + when initially overlapping since it is not physical. + """ + if self._region_material is None: + self._region_material = ba.Material() + return self._region_material + + @property + def railing_material(self) -> ba.Material: + """A ba.Material with a very low friction/stiffness/etc + that can be applied to invisible 'railings' useful for gently keeping + characters from falling off of cliffs. + """ + if self._railing_material is None: + mat = self._railing_material = ba.Material() + mat.add_actions(('modify_part_collision', 'collide', False)) + mat.add_actions(('modify_part_collision', 'stiffness', 0.003)) + mat.add_actions(('modify_part_collision', 'damping', 0.00001)) + mat.add_actions( + conditions=('they_have_material', self.player_material), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'friction', 0.0), + ), + ) + return self._railing_material diff --git a/assets/src/ba_data/python/bastd/mainmenu.py b/assets/src/ba_data/python/bastd/mainmenu.py index 391c082e..61cfb0f2 100644 --- a/assets/src/ba_data/python/bastd/mainmenu.py +++ b/assets/src/ba_data/python/bastd/mainmenu.py @@ -182,7 +182,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): vr_bottom_fill_model = ba.getmodel('thePadVRFillBottom') vr_top_fill_model = ba.getmodel('thePadVRFillTop') - gnode = ba.sharedobj('globals') + gnode = self.globalsnode gnode.camera_mode = 'rotate' tint = (1.14, 1.1, 1.0) diff --git a/assets/src/ba_data/python/bastd/maps.py b/assets/src/ba_data/python/bastd/maps.py index 1e5158c6..29de16eb 100644 --- a/assets/src/ba_data/python/bastd/maps.py +++ b/assets/src/ba_data/python/bastd/maps.py @@ -26,7 +26,7 @@ from __future__ import annotations from typing import TYPE_CHECKING import ba -# from bastd import stdmap +from bastd.gameutils import SharedObjects if TYPE_CHECKING: from typing import Any, List, Dict @@ -65,6 +65,7 @@ class HockeyStadium(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode('terrain', delegate=self, attrs={ @@ -75,7 +76,7 @@ class HockeyStadium(ba.Map): 'color_texture': self.preloaddata['tex'], 'materials': [ - ba.sharedobj('footing_material'), + shared.footing_material, self.preloaddata['ice_material'] ] }) @@ -87,9 +88,7 @@ class HockeyStadium(ba.Map): 'background': True, 'color_texture': self.preloaddata['stands_tex'] }) - mats = [ - ba.sharedobj('footing_material'), self.preloaddata['ice_material'] - ] + mats = [shared.footing_material, self.preloaddata['ice_material']] self.floor = ba.newnode('terrain', attrs={ 'model': self.preloaddata['models'][1], @@ -105,7 +104,7 @@ class HockeyStadium(ba.Map): 'visible_in_reflections': False, 'color_texture': self.preloaddata['stands_tex'] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.floor_reflection = True gnode.debris_friction = 0.3 gnode.debris_kill_height = -0.3 @@ -145,6 +144,7 @@ class FootballStadium(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -152,7 +152,7 @@ class FootballStadium(ba.Map): 'model': self.preloaddata['model'], 'collide_model': self.preloaddata['collide_model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) ba.newnode('terrain', attrs={ @@ -162,7 +162,7 @@ class FootballStadium(ba.Map): 'background': True, 'color_texture': self.preloaddata['tex'] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.3, 1.2, 1.0) gnode.ambient_color = (1.3, 1.2, 1.0) gnode.vignette_outer = (0.57, 0.57, 0.57) @@ -218,6 +218,7 @@ class Bridgit(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -225,7 +226,7 @@ class Bridgit(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model_top'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.bottom = ba.newnode('terrain', attrs={ @@ -253,7 +254,7 @@ class Bridgit(ba.Map): 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], - 'materials': [ba.sharedobj('railing_material')], + 'materials': [shared.railing_material], 'bumper': True }) self.bg_collide = ba.newnode('terrain', @@ -261,12 +262,12 @@ class Bridgit(ba.Map): 'collide_model': self.preloaddata['collide_bg'], 'materials': [ - ba.sharedobj('footing_material'), + shared.footing_material, self.preloaddata['bg_material'], - ba.sharedobj('death_material') + shared.death_material ] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.1, 1.2, 1.3) gnode.ambient_color = (1.1, 1.2, 1.3) gnode.vignette_outer = (0.65, 0.6, 0.55) @@ -312,6 +313,7 @@ class BigG(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -320,7 +322,7 @@ class BigG(ba.Map): 'color': (0.7, 0.7, 0.7), 'model': self.preloaddata['model_top'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.bottom = ba.newnode('terrain', attrs={ @@ -349,7 +351,7 @@ class BigG(ba.Map): 'terrain', attrs={ 'collide_model': self.preloaddata['bumper_collide_model'], - 'materials': [ba.sharedobj('railing_material')], + 'materials': [shared.railing_material], 'bumper': True }) self.bg_collide = ba.newnode('terrain', @@ -357,12 +359,12 @@ class BigG(ba.Map): 'collide_model': self.preloaddata['collide_bg'], 'materials': [ - ba.sharedobj('footing_material'), + shared.footing_material, self.preloaddata['bg_material'], - ba.sharedobj('death_material') + shared.death_material ] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.1, 1.2, 1.3) gnode.ambient_color = (1.1, 1.2, 1.3) gnode.vignette_outer = (0.65, 0.6, 0.55) @@ -406,6 +408,7 @@ class Roundabout(ba.Map): def __init__(self) -> None: super().__init__(vr_overlay_offset=(0, -1, 1)) + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -413,7 +416,7 @@ class Roundabout(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.bottom = ba.newnode('terrain', attrs={ @@ -442,19 +445,19 @@ class Roundabout(ba.Map): 'collide_model': self.preloaddata['collide_bg'], 'materials': [ - ba.sharedobj('footing_material'), + shared.footing_material, self.preloaddata['bg_material'], - ba.sharedobj('death_material') + shared.death_material ] }) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], - 'materials': [ba.sharedobj('railing_material')], + 'materials': [shared.railing_material], 'bumper': True }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.0, 1.05, 1.1) gnode.ambient_color = (1.0, 1.05, 1.1) gnode.shadow_ortho = True @@ -499,6 +502,7 @@ class MonkeyFace(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -506,7 +510,7 @@ class MonkeyFace(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.bottom = ba.newnode('terrain', attrs={ @@ -535,19 +539,19 @@ class MonkeyFace(ba.Map): 'collide_model': self.preloaddata['collide_bg'], 'materials': [ - ba.sharedobj('footing_material'), + shared.footing_material, self.preloaddata['bg_material'], - ba.sharedobj('death_material') + shared.death_material ] }) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], - 'materials': [ba.sharedobj('railing_material')], + 'materials': [shared.railing_material], 'bumper': True }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.1, 1.2, 1.2) gnode.ambient_color = (1.2, 1.3, 1.3) gnode.vignette_outer = (0.60, 0.62, 0.66) @@ -593,6 +597,7 @@ class ZigZag(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -600,7 +605,7 @@ class ZigZag(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.background = ba.newnode( 'terrain', @@ -628,19 +633,19 @@ class ZigZag(ba.Map): 'collide_model': self.preloaddata['collide_bg'], 'materials': [ - ba.sharedobj('footing_material'), + shared.footing_material, self.preloaddata['bg_material'], - ba.sharedobj('death_material') + shared.death_material ] }) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], - 'materials': [ba.sharedobj('railing_material')], + 'materials': [shared.railing_material], 'bumper': True }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.0, 1.15, 1.15) gnode.ambient_color = (1.0, 1.15, 1.15) gnode.vignette_outer = (0.57, 0.59, 0.63) @@ -682,6 +687,7 @@ class ThePad(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -689,7 +695,7 @@ class ThePad(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.bottom = ba.newnode('terrain', attrs={ @@ -709,7 +715,7 @@ class ThePad(ba.Map): 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], - 'materials': [ba.sharedobj('railing_material')], + 'materials': [shared.railing_material], 'bumper': True }) ba.newnode('terrain', @@ -721,7 +727,7 @@ class ThePad(ba.Map): 'background': True, 'color_texture': self.preloaddata['vr_fill_mound_tex'] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.1, 1.1, 1.0) gnode.ambient_color = (1.1, 1.1, 1.0) gnode.vignette_outer = (0.7, 0.65, 0.75) @@ -760,6 +766,7 @@ class DoomShroom(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -767,7 +774,7 @@ class DoomShroom(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.background = ba.newnode( 'terrain', @@ -791,16 +798,13 @@ class DoomShroom(ba.Map): 'lighting': False, 'color_texture': self.preloaddata['tex'] }) - self.bg_collide = ba.newnode('terrain', - attrs={ - 'collide_model': - self.preloaddata['collide_bg'], - 'materials': [ - ba.sharedobj('footing_material'), - ba.sharedobj('death_material') - ] - }) - gnode = ba.sharedobj('globals') + self.bg_collide = ba.newnode( + 'terrain', + attrs={ + 'collide_model': self.preloaddata['collide_bg'], + 'materials': [shared.footing_material, shared.death_material] + }) + gnode = ba.getactivity().globalsnode gnode.tint = (0.82, 1.10, 1.15) gnode.ambient_color = (0.9, 1.3, 1.1) gnode.shadow_ortho = False @@ -854,6 +858,7 @@ class LakeFrigid(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode('terrain', delegate=self, attrs={ @@ -864,7 +869,7 @@ class LakeFrigid(ba.Map): 'color_texture': self.preloaddata['tex'], 'materials': [ - ba.sharedobj('footing_material'), + shared.footing_material, self.preloaddata['ice_material'] ] }) @@ -890,7 +895,7 @@ class LakeFrigid(ba.Map): 'background': True, 'color_texture': self.preloaddata['tex'] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1, 1, 1) gnode.ambient_color = (1, 1, 1) gnode.shadow_ortho = True @@ -931,6 +936,7 @@ class TipTop(ba.Map): def __init__(self) -> None: super().__init__(vr_overlay_offset=(0, -0.2, 2.5)) + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -939,7 +945,7 @@ class TipTop(ba.Map): 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], 'color': (0.7, 0.7, 0.7), - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.bottom = ba.newnode('terrain', attrs={ @@ -961,10 +967,10 @@ class TipTop(ba.Map): 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], - 'materials': [ba.sharedobj('railing_material')], + 'materials': [shared.railing_material], 'bumper': True }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (0.8, 0.9, 1.3) gnode.ambient_color = (0.8, 0.9, 1.3) gnode.vignette_outer = (0.79, 0.79, 0.69) @@ -1006,6 +1012,7 @@ class CragCastle(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -1013,7 +1020,7 @@ class CragCastle(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.bottom = ba.newnode('terrain', attrs={ @@ -1033,7 +1040,7 @@ class CragCastle(ba.Map): 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], - 'materials': [ba.sharedobj('railing_material')], + 'materials': [shared.railing_material], 'bumper': True }) ba.newnode('terrain', @@ -1045,7 +1052,7 @@ class CragCastle(ba.Map): 'background': True, 'color_texture': self.preloaddata['vr_fill_mound_tex'] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.shadow_ortho = True gnode.shadow_offset = (0, 0, -5.0) gnode.tint = (1.15, 1.05, 0.75) @@ -1106,6 +1113,7 @@ class TowerD(ba.Map): def __init__(self) -> None: super().__init__(vr_overlay_offset=(0, 1, 1)) + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -1113,7 +1121,7 @@ class TowerD(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.node_bottom = ba.newnode( 'terrain', @@ -1147,7 +1155,7 @@ class TowerD(ba.Map): 'affect_bg_dynamics': False, 'materials': [self.preloaddata['player_wall_material']] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.15, 1.11, 1.03) gnode.ambient_color = (1.2, 1.1, 1.0) gnode.vignette_outer = (0.7, 0.73, 0.7) @@ -1209,6 +1217,7 @@ class HappyThoughts(ba.Map): def __init__(self) -> None: super().__init__(vr_overlay_offset=(0, -3.7, 2.5)) + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -1216,7 +1225,7 @@ class HappyThoughts(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.bottom = ba.newnode('terrain', attrs={ @@ -1241,7 +1250,7 @@ class HappyThoughts(ba.Map): 'background': True, 'color_texture': self.preloaddata['vr_fill_mound_tex'] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.happy_thoughts_mode = True gnode.shadow_offset = (0.0, 8.0, 5.0) gnode.tint = (1.3, 1.23, 1.0) @@ -1309,6 +1318,7 @@ class StepRightUp(ba.Map): def __init__(self) -> None: super().__init__(vr_overlay_offset=(0, -1, 2)) + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -1316,7 +1326,7 @@ class StepRightUp(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.node_bottom = ba.newnode( 'terrain', @@ -1343,7 +1353,7 @@ class StepRightUp(ba.Map): 'background': True, 'color_texture': self.preloaddata['bgtex'] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.2, 1.1, 1.0) gnode.ambient_color = (1.2, 1.1, 1.0) gnode.vignette_outer = (0.7, 0.65, 0.75) @@ -1394,6 +1404,7 @@ class Courtyard(ba.Map): def __init__(self) -> None: super().__init__() + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -1401,7 +1412,7 @@ class Courtyard(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.background = ba.newnode( 'terrain', @@ -1437,7 +1448,7 @@ class Courtyard(ba.Map): 'affect_bg_dynamics': False, 'materials': [self.preloaddata['player_wall_material']] }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.2, 1.17, 1.1) gnode.ambient_color = (1.2, 1.17, 1.1) gnode.vignette_outer = (0.6, 0.6, 0.64) @@ -1489,6 +1500,7 @@ class Rampage(ba.Map): def __init__(self) -> None: super().__init__(vr_overlay_offset=(0, 0, 2)) + shared = SharedObjects.get() self.node = ba.newnode( 'terrain', delegate=self, @@ -1496,7 +1508,7 @@ class Rampage(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [ba.sharedobj('footing_material')] + 'materials': [shared.footing_material] }) self.background = ba.newnode( 'terrain', @@ -1531,10 +1543,10 @@ class Rampage(ba.Map): 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], - 'materials': [ba.sharedobj('railing_material')], + 'materials': [shared.railing_material], 'bumper': True }) - gnode = ba.sharedobj('globals') + gnode = ba.getactivity().globalsnode gnode.tint = (1.2, 1.1, 0.97) gnode.ambient_color = (1.3, 1.2, 1.03) gnode.vignette_outer = (0.62, 0.64, 0.69) diff --git a/docs/ba_module.md b/docs/ba_module.md index dbdf5458..75bccd57 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2020-05-28 for Ballistica version 1.5.0 build 20033

+

last updated on 2020-05-29 for Ballistica version 1.5.0 build 20033

This page documents the Python classes and functions in the 'ba' module, which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please let me know. Happy modding!


@@ -40,6 +40,7 @@
  • ba.SessionPlayer
  • ba.SessionTeam
  • +
  • ba.StandLocation
  • ba.Stats
  • ba.Team
  • ba.TeamGameResults
  • @@ -61,7 +62,6 @@
  • ba.playsound()
  • ba.printnodes()
  • ba.setmusic()
  • -
  • ba.sharedobj()
  • ba.show_damage_count()
  • General Utility Classes

    @@ -345,7 +345,7 @@ actually award achievements.

    can overlap during transitions.

    Attributes:

    -
    expired, players, playertype, session, settings_raw, stats, teams, teamtype
    +
    expired, globalsnode, players, playertype, session, settings_raw, stats, teams, teamtype

    expired

    bool

    @@ -355,6 +355,12 @@ actually award achievements.

    At this point no new nodes, timers, etc should be made, run, etc, and the activity should be considered a 'zombie'.

    +
    +

    globalsnode

    +

    ba.Node

    +

    The 'globals' ba.Node for the activity. This contains various + global controls and values.

    +

    players

    List[PlayerType]

    @@ -1541,7 +1547,7 @@ start_long_action(callback_when_done=ba.ContextC

    Attributes Inherited:

    players, settings_raw, teams

    Attributes Defined Here:

    -
    expired, map, playertype, session, stats, teamtype
    +
    expired, globalsnode, map, playertype, session, stats, teamtype

    expired

    bool

    @@ -1551,6 +1557,12 @@ start_long_action(callback_when_done=ba.ContextC At this point no new nodes, timers, etc should be made, run, etc, and the activity should be considered a 'zombie'.

    +
    +

    globalsnode

    +

    ba.Node

    +

    The 'globals' ba.Node for the activity. This contains various + global controls and values.

    +

    map

    ba.Map

    @@ -1660,12 +1672,18 @@ and it should begin its actual game logic.

    Attributes Inherited:

    allow_mid_activity_joins, lobby, max_players, min_players, players, teams, use_team_colors, use_teams

    Attributes Defined Here:

    +
    campaign, sessionglobalsnode

    campaign

    Optional[ba.Campaign]

    The ba.Campaign instance this Session represents, or None if there is no associated Campaign.

    +
    +

    sessionglobalsnode

    +

    ba.Node

    +

    The sessionglobals ba.Node for the session.

    +

    Methods Inherited:

    @@ -2011,6 +2029,14 @@ its time with lingering corpses, sound effects, etc.

    Attributes Inherited:

    allow_mid_activity_joins, lobby, max_players, min_players, players, teams, use_team_colors, use_teams
    +

    Attributes Defined Here:

    +
    +

    sessionglobalsnode

    +

    ba.Node

    +

    The sessionglobals ba.Node for the session.

    + +
    +

    Methods Inherited:

    announce_game_results(), begin_next_activity(), end(), end_activity(), get_custom_menu_entries(), get_ffa_series_length(), get_game_number(), get_max_players(), get_next_game_description(), get_series_length(), getactivity(), handlemessage(), launch_end_session_activity(), on_activity_end(), on_player_leave(), on_player_request(), on_team_join(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()

    Methods Defined or Overridden:

    @@ -2049,6 +2075,14 @@ its time with lingering corpses, sound effects, etc.

    Attributes Inherited:

    allow_mid_activity_joins, lobby, max_players, min_players, players, teams, use_team_colors, use_teams
    +

    Attributes Defined Here:

    +
    +

    sessionglobalsnode

    +

    ba.Node

    +

    The sessionglobals ba.Node for the session.

    + +
    +

    Methods Inherited:

    announce_game_results(), begin_next_activity(), end(), end_activity(), get_custom_menu_entries(), get_ffa_series_length(), get_game_number(), get_max_players(), get_next_game_description(), get_series_length(), getactivity(), handlemessage(), launch_end_session_activity(), on_activity_end(), on_player_leave(), on_player_request(), on_team_join(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()

    Methods Defined or Overridden:

    @@ -2098,7 +2132,7 @@ its time with lingering corpses, sound effects, etc.

    Attributes Inherited:

    players, settings_raw, teams

    Attributes Defined Here:

    -
    expired, map, playertype, session, stats, teamtype
    +
    expired, globalsnode, map, playertype, session, stats, teamtype

    expired

    bool

    @@ -2108,6 +2142,12 @@ its time with lingering corpses, sound effects, etc.

    At this point no new nodes, timers, etc should be made, run, etc, and the activity should be considered a 'zombie'.

    +
    +

    globalsnode

    +

    ba.Node

    +

    The 'globals' ba.Node for the activity. This contains various + global controls and values.

    +

    map

    ba.Map

    @@ -3299,7 +3339,7 @@ m.add_actions(actions=(('modify_part_collision', 'physical', False),
    # example 3: play some sounds when we're contacting the ground:
     m = ba.Material()
     m.add_actions(conditions=('they_have_material',
    -                          ba.sharedobj('footing_material')),
    +                          shared.footing_material),
                   actions=(('impact_sound', ba.getsound('metalHit'), 2, 5),
                            ('skid_sound', ba.getsound('metalSkid'), 2, 5)))
    @@ -3329,6 +3369,14 @@ Use ba.getmodel() to instantiate one.

    Attributes Inherited:

    allow_mid_activity_joins, lobby, max_players, min_players, players, teams, use_team_colors, use_teams
    +

    Attributes Defined Here:

    +
    +

    sessionglobalsnode

    +

    ba.Node

    +

    The sessionglobals ba.Node for the session.

    + +
    +

    Methods Inherited:

    begin_next_activity(), end(), end_activity(), get_custom_menu_entries(), getactivity(), handlemessage(), launch_end_session_activity(), on_player_leave(), on_player_request(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()

    Methods Defined or Overridden:

    @@ -4272,7 +4320,7 @@ Pass 0 or a negative number for no ban time.

    maintaining state between them (players, teams, score tallies, etc).

    Attributes:

    -
    allow_mid_activity_joins, lobby, max_players, min_players, players, teams, use_team_colors, use_teams
    +
    allow_mid_activity_joins, lobby, max_players, min_players, players, sessionglobalsnode, teams, use_team_colors, use_teams

    allow_mid_activity_joins

    bool

    @@ -4305,6 +4353,11 @@ to proceed past the initial joining screen.

    list in ba.Activity; not this. Some players, such as those who have not yet selected a character, will only appear on this list.

    +
    +

    sessionglobalsnode

    +

    ba.Node

    +

    The sessionglobals ba.Node for the session.

    +

    teams

    List[ba.SessionTeam]

    @@ -4812,6 +4865,22 @@ of the session.

  • MIKIROG

  • +

    ba.StandLocation

    +

    <top level class> +

    +

    Describes a point in space and an angle to face.

    + +

    Category: Gameplay Classes +

    + +

    Methods:

    +
    +

    <constructor>

    +

    ba.StandLocation(position: _ba.Vec3, angle: Optional[float] = None)

    + +
    +
    +

    ba.StandMessage

    <top level class>

    @@ -4961,7 +5030,7 @@ of the session.

    Attributes Inherited:

    players, settings_raw, teams

    Attributes Defined Here:

    -
    expired, map, playertype, session, stats, teamtype
    +
    expired, globalsnode, map, playertype, session, stats, teamtype

    expired

    bool

    @@ -4971,6 +5040,12 @@ of the session.

    At this point no new nodes, timers, etc should be made, run, etc, and the activity should be considered a 'zombie'.

    +
    +

    globalsnode

    +

    ba.Node

    +

    The 'globals' ba.Node for the activity. This contains various + global controls and values.

    +

    map

    ba.Map

    @@ -6303,54 +6378,6 @@ user can override particular game music with their own.

    if 'continuous' is True and musictype is the same as what is already playing, the playing track will not be restarted.

    -
    -

    ba.sharedobj()

    -

    sharedobj(name: str) -> Any

    - -

    Return a predefined object for the current Activity, creating if needed.

    - -

    Category: Gameplay Functions

    - -

    Available values for 'name':

    - -

    'globals': returns the 'globals' ba.Node, containing various global - controls & values.

    - -

    'object_material': a ba.Material that should be applied to any small, - normal, physical objects such as bombs, boxes, players, etc. Other - materials often check for the presence of this material as a - prerequisite for performing certain actions (such as disabling collisions - between initially-overlapping objects)

    - -

    'player_material': a ba.Material to be applied to player parts. Generally, - materials related to the process of scoring when reaching a goal, etc - will look for the presence of this material on things that hit them.

    - -

    'pickup_material': a ba.Material; collision shapes used for picking things - up will have this material applied. To prevent an object from being - picked up, you can add a material that disables collisions against things - containing this material.

    - -

    'footing_material': anything that can be 'walked on' should have this - ba.Material applied; generally just terrain and whatnot. A character will - snap upright whenever touching something with this material so it should - not be applied to props, etc.

    - -

    'attack_material': a ba.Material applied to explosion shapes, punch - shapes, etc. An object not wanting to receive impulse/etc messages can - disable collisions against this material.

    - -

    'death_material': a ba.Material that sends a ba.DieMessage() to anything - that touches it; handy for terrain below a cliff, etc.

    - -

    'region_material': a ba.Material used for non-physical collision shapes - (regions); collisions can generally be allowed with this material even - when initially overlapping since it is not physical.

    - -

    'railing_material': a ba.Material with a very low friction/stiffness/etc - that can be applied to invisible 'railings' useful for gently keeping - characters from falling off of cliffs.

    -

    ba.show_damage_count()

    show_damage_count(damage: str, position: Sequence[float], direction: Sequence[float]) -> None