diff --git a/.efrocachemap b/.efrocachemap index 8c2365bf..8b7ec793 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -420,7 +420,7 @@ "assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/75/1d/868bb04cf691736035c917d02762", "assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/44/2a/8535b446284235cb503947ece074", "assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/f5/d3/8e941851c4310465646c4167afc1", - "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/e2/2c/e609bb55002f672528428db9306c", + "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/da/06/8e06488b46a0a213abde3cfeb364", "assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/b8/ed/e18bec56ff1d094aae86517a7854", "assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/49/5f/b29bb65369040892fe6601801637", "assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/0c/cd/798753aa6c55f3a4cdccda0b23ab", @@ -442,7 +442,7 @@ "assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/0a/84/bbb6ed2abf66509406f534cbbb52", "assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/c2/fc/dd1c15cf9ecb411d9defbd000c06", "assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/db/eb/324f86a4b714240ae50ffeeed2f8", - "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/6c/35/cc4d440d0c7a613860c3898e81d7", + "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/a9/bc/ea61ebd23066c685fb779e23d10f", "assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/f6/d0/335b952306d211d56172b5c72d8c", "assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/78/5e/d24967ddaa15e0a574bd274544db", "assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/e7/d8/ace32888249fc8b8cca0e2edb48b", @@ -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/5a/42/5238d5de1cf94a07a513ea2b3e1b", - "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/39/48/2f0e4350a080373301de625e2cc4", - "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c0/1d/dbc0a5e2ca05b626cbeffd6def6c", - "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ec/a8/5c58d6b5e1d844640334c35ad3af", - "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3d/0a/76b615f9bcb9bf4f0ca745060d40", - "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/01/6b/0f623ac481e3553e352c1cd3cb7e", - "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c4/a5/3cad1ced77551369dc56c6bca5fb", - "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3b/3f/55094817619cae66f941a9f6db8c", - "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/ec/6b/7aa29831a746672f9984cbb1dbf1", - "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/07/4c/00a6136d0bd5573946d10e294720", - "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/8d/8d/af068e67244cbb28444b27ae66d7", - "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f4/62/eb7cb26c952df481b757dcf53f9c" + "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e4/f0/c3261a8a7391a2b654da82c35c21", + "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/33/e2/85a8cbf23404612e6761cf02348b", + "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/5b/0d/d906be5fc2a75b5214a94cafe6ca", + "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/6b/53/72d665caf5dfed84312ae6862642", + "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d9/68/2d98f2d1ee67b6d54adbdf76c863", + "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e5/3a/33f8f311260542126acea44d05e2", + "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/18/eb/dd39bcfa33983864089d3fb7f666", + "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f8/1d/3440010fcbcf18c5a6b2966f7fca", + "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/9b/fa/b4ad4b8b82fefe1faad610065b9f", + "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7f/b9/03af92db6140135ddfa467a1545d", + "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/69/b9/687fc5fa38c3faf7a47eb4241a3c", + "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/cf/2b/589db924b489972981e10850a4e3" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index fe3fd86d..898fe13e 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -416,6 +416,7 @@ deathmatch deek defs + defsline deivit depcls depdata @@ -749,6 +750,7 @@ getclass getcollide getcollidemodel + getcollision getconf getconfig getcurrency @@ -1285,6 +1287,7 @@ outname outpath ouya + overloadsigs packagedir packagedirs packagename @@ -1888,6 +1891,7 @@ toplevel totaldudes totalpts + totalwaves totype touchpad tournamententry @@ -2022,6 +2026,7 @@ waaah wanttype wasdead + wavenum weakref weakrefs weakrefset diff --git a/assets/.asset_manifest_public.json b/assets/.asset_manifest_public.json index 92c51171..fea31d89 100644 --- a/assets/.asset_manifest_public.json +++ b/assets/.asset_manifest_public.json @@ -13,6 +13,7 @@ "ba_data/python/ba/__pycache__/_assetmanager.cpython-37.opt-1.pyc", "ba_data/python/ba/__pycache__/_benchmark.cpython-37.opt-1.pyc", "ba_data/python/ba/__pycache__/_campaign.cpython-37.opt-1.pyc", + "ba_data/python/ba/__pycache__/_collision.cpython-37.opt-1.pyc", "ba_data/python/ba/__pycache__/_coopgame.cpython-37.opt-1.pyc", "ba_data/python/ba/__pycache__/_coopsession.cpython-37.opt-1.pyc", "ba_data/python/ba/__pycache__/_dependency.cpython-37.opt-1.pyc", @@ -67,6 +68,7 @@ "ba_data/python/ba/_assetmanager.py", "ba_data/python/ba/_benchmark.py", "ba_data/python/ba/_campaign.py", + "ba_data/python/ba/_collision.py", "ba_data/python/ba/_coopgame.py", "ba_data/python/ba/_coopsession.py", "ba_data/python/ba/_dependency.py", diff --git a/assets/Makefile b/assets/Makefile index 82113061..c77bdbb8 100644 --- a/assets/Makefile +++ b/assets/Makefile @@ -153,6 +153,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ build/ba_data/python/ba/_coopgame.py \ build/ba_data/python/ba/_meta.py \ build/ba_data/python/ba/_math.py \ + build/ba_data/python/ba/_collision.py \ build/ba_data/python/ba/_servermode.py \ build/ba_data/python/ba/_appconfig.py \ build/ba_data/python/ba/_gameresults.py \ @@ -380,6 +381,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ build/ba_data/python/ba/__pycache__/_coopgame.cpython-37.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_meta.cpython-37.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_math.cpython-37.opt-1.pyc \ + build/ba_data/python/ba/__pycache__/_collision.cpython-37.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_servermode.cpython-37.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_appconfig.cpython-37.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_gameresults.cpython-37.opt-1.pyc \ @@ -663,6 +665,11 @@ build/ba_data/python/ba/__pycache__/_math.cpython-37.opt-1.pyc: \ @echo Compiling script: $^ @rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@ +build/ba_data/python/ba/__pycache__/_collision.cpython-37.opt-1.pyc: \ + build/ba_data/python/ba/_collision.py + @echo Compiling script: $^ + @rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@ + build/ba_data/python/ba/__pycache__/_servermode.cpython-37.opt-1.pyc: \ build/ba_data/python/ba/_servermode.py @echo Compiling script: $^ diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py index 30763c4a..6b593af3 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=146544975806995156523304218095856323339 +# SOURCES_HASH=164420280597992494471294420110866243586 # I'm sorry Pylint. I know this file saddens you. Be strong. # pylint: disable=useless-suppression @@ -51,16 +51,19 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand. from __future__ import annotations -from typing import TYPE_CHECKING, overload, Sequence +from typing import TYPE_CHECKING, overload, Sequence, TypeVar from ba._enums import TimeFormat, TimeType if TYPE_CHECKING: from typing import (Any, Dict, Callable, Tuple, List, Optional, Union, List, Type) + from typing_extensions import Literal from ba._app import App import ba +_T = TypeVar('_T') + app: App @@ -713,13 +716,26 @@ class Node: """ return str() - def getdelegate(self) -> Any: - """getdelegate() -> Any + @overload + def getdelegate(self, + type: Type[_T], + doraise: Literal[False] = False) -> Optional[_T]: + ... - Returns the node's current delegate, which is the Python object - designated to handle the Node's messages. + @overload + def getdelegate(self, type: Type[_T], doraise: Literal[True]) -> _T: + ... + + def getdelegate(self, type: Any, doraise: bool = False) -> Any: + """getdelegate(type: Type, doraise: bool = False) -> + + Return the node's current delegate object if it matches a certain type. + + If the node has no delegate or it is not an instance of the passed + type, then None will be returned. If 'doraise' is True, then an + Exception will be raised instead in such cases. """ - return _uninferrable() + return None def getnodetype(self) -> str: """getnodetype() -> str diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 3435ad09..25869a72 100644 --- a/assets/src/ba_data/python/ba/__init__.py +++ b/assets/src/ba_data/python/ba/__init__.py @@ -30,13 +30,13 @@ In some specific cases you may need to pull in individual submodules instead. from _ba import (CollideModel, Context, ContextCall, Data, InputDevice, Material, Model, Node, SessionPlayer, Sound, Texture, Timer, Vec3, Widget, buttonwidget, camerashake, checkboxwidget, - columnwidget, containerwidget, do_once, emitfx, - get_collision_info, getactivity, getcollidemodel, getmodel, - getnodes, getsession, getsound, gettexture, hscrollwidget, - imagewidget, log, new_activity, newnode, playsound, - printnodes, printobjects, pushcall, quit, rowwidget, - safecolor, screenmessage, scrollwidget, set_analytics_screen, - charstr, textwidget, time, timer, open_url, widget) + columnwidget, containerwidget, do_once, emitfx, getactivity, + getcollidemodel, getmodel, getnodes, getsession, getsound, + gettexture, hscrollwidget, imagewidget, log, new_activity, + newnode, playsound, printnodes, printobjects, pushcall, quit, + rowwidget, safecolor, screenmessage, scrollwidget, + set_analytics_screen, charstr, textwidget, time, timer, + open_url, widget) from ba._activity import Activity from ba._actor import Actor from ba._player import Player, playercast, playercast_o @@ -87,6 +87,7 @@ from ba._music import setmusic, MusicPlayer, MusicType, MusicPlayMode from ba._powerup import PowerupMessage, PowerupAcceptMessage from ba._multiteamsession import MultiTeamSession from ba.ui import Window, UIController, uicleanupcheck +from ba._collision import Collision, getcollision app: App diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py index 05f3508d..c372069d 100644 --- a/assets/src/ba_data/python/ba/_activity.py +++ b/assets/src/ba_data/python/ba/_activity.py @@ -343,7 +343,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): 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' + 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)') @@ -775,12 +775,12 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # Send expire notices to all remaining actors. for actor_ref in self._actor_weak_refs: - try: - actor = actor_ref() - if actor is not None: + actor = actor_ref() + if actor is not None: + try: actor.on_expire() - except Exception: - print_exception(f'Error expiring Actor {actor_ref()}') + except Exception: + print_exception(f'Error expiring Actor {actor_ref()}') # Reset all Players. # (releases any attached actors, clears game-data, etc) @@ -804,7 +804,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): sessionteam.reset_gamedata() except SessionTeamNotFoundError: pass - # print_exception(f'Error resetting Team {team}') except Exception: print_exception(f'Error resetting Team {team}') @@ -819,6 +818,12 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): 'Error during ba.Activity._expire() destroying data:') def _prune_dead_actors(self) -> None: - self._actor_refs = [a for a in self._actor_refs if a] - self._actor_weak_refs = [a for a in self._actor_weak_refs if a()] self._last_prune_dead_actors_time = _ba.time() + + # Prune our strong refs when the Actor's exists() call gives False + self._actor_refs = [a for a in self._actor_refs if a.exists()] + + # Prune our weak refs once the Actor object has been freed. + self._actor_weak_refs = [ + a for a in self._actor_weak_refs if a() is not None + ] diff --git a/assets/src/ba_data/python/ba/_collision.py b/assets/src/ba_data/python/ba/_collision.py new file mode 100644 index 00000000..b41aa104 --- /dev/null +++ b/assets/src/ba_data/python/ba/_collision.py @@ -0,0 +1,84 @@ +# Copyright (c) 2011-2020 Eric Froemling +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ----------------------------------------------------------------------------- +"""Collision related functionality.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import _ba +from ba._error import NodeNotFoundError + +if TYPE_CHECKING: + import ba + + +class Collision: + """A class providing info about occurring collisions.""" + + @property + def position(self) -> ba.Vec3: + """The position of the current collision.""" + return _ba.Vec3(_ba.get_collision_info('position')) + + @property + def source_node(self) -> ba.Node: + """The node containing the material triggering the current callback. + + Throws a ba.NodeNotFoundError if the node does not exist, though + the node should always exist (at least at the start of the collision + callback). + """ + node = _ba.get_collision_info('source_node') + assert isinstance(node, (_ba.Node, type(None))) + if not node: + raise NodeNotFoundError() + return node + + @property + def opposing_node(self) -> ba.Node: + """The node the current callback material node is hitting. + + Throws a ba.NodeNotFoundError if the node does not exist. + This can be expected in some cases such as in 'disconnect' + callbacks triggered by deleting a currently-colliding node. + """ + node = _ba.get_collision_info('opposing_node') + assert isinstance(node, (_ba.Node, type(None))) + if not node: + raise NodeNotFoundError() + return node + + @property + def opposing_body(self) -> int: + """The body index on the opposing node in the current collision.""" + body = _ba.get_collision_info('opposing_body') + assert isinstance(body, int) + return body + + +# Simply recycle one instance... +_collision = Collision() + + +def getcollision() -> Collision: + """Return the in-progress collision.""" + return _collision diff --git a/assets/src/ba_data/python/ba/_gameutils.py b/assets/src/ba_data/python/ba/_gameutils.py index 61b62458..fcaf3f4b 100644 --- a/assets/src/ba_data/python/ba/_gameutils.py +++ b/assets/src/ba_data/python/ba/_gameutils.py @@ -447,7 +447,6 @@ def cameraflash(duration: float = 999.0) -> None: # Store this on the current activity so we only have one at a time. # FIXME: Need a type safe way to do this. activity = _ba.getactivity() - # noinspection PyTypeHints activity.camera_flash_data = [] # type: ignore for i in range(6): light = NodeActor( diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py index 9df3e897..bc5c44bb 100644 --- a/assets/src/ba_data/python/ba/_player.py +++ b/assets/src/ba_data/python/ba/_player.py @@ -226,6 +226,5 @@ def playercast_o(totype: Type[PlayerType], Category: Gameplay Functions """ - # noinspection PyTypeHints assert isinstance(player, (totype, type(None))) return player diff --git a/assets/src/ba_data/python/ba/_powerup.py b/assets/src/ba_data/python/ba/_powerup.py index dbef03fb..0e3d2d83 100644 --- a/assets/src/ba_data/python/ba/_powerup.py +++ b/assets/src/ba_data/python/ba/_powerup.py @@ -19,6 +19,7 @@ # SOFTWARE. # ----------------------------------------------------------------------------- """Powerup related functionality.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/assets/src/ba_data/python/bastd/actor/bomb.py b/assets/src/ba_data/python/bastd/actor/bomb.py index 7f362867..068dd7c9 100644 --- a/assets/src/ba_data/python/bastd/actor/bomb.py +++ b/assets/src/ba_data/python/bastd/actor/bomb.py @@ -265,7 +265,6 @@ class BombFactory: actions=('message', 'our_node', 'at_connect', SplatMessage())) -# noinspection PyTypeHints def get_factory() -> BombFactory: """Get/create a shared bastd.actor.bomb.BombFactory object.""" activity = ba.getactivity() @@ -602,34 +601,28 @@ class Blast(ba.Actor): self.node.delete() elif isinstance(msg, ExplodeHitMessage): - node = ba.get_collision_info('opposing_node') - if node: - assert self.node - nodepos = self.node.position + node = ba.getcollision().opposing_node + assert self.node + nodepos = self.node.position + mag = 2000.0 + if self.blast_type == 'ice': + mag *= 0.5 + elif self.blast_type == 'land_mine': + mag *= 2.5 + elif self.blast_type == 'tnt': + mag *= 2.0 - # new - mag = 2000.0 - if self.blast_type == 'ice': - mag *= 0.5 - elif self.blast_type == 'land_mine': - mag *= 2.5 - elif self.blast_type == 'tnt': - mag *= 2.0 - - node.handlemessage( - ba.HitMessage(pos=nodepos, - velocity=(0, 0, 0), - magnitude=mag, - hit_type=self.hit_type, - hit_subtype=self.hit_subtype, - radius=self.radius, - 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()) + node.handlemessage( + ba.HitMessage(pos=nodepos, + velocity=(0, 0, 0), + magnitude=mag, + hit_type=self.hit_type, + hit_subtype=self.hit_subtype, + radius=self.radius, + 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()) else: super().handlemessage(msg) @@ -850,14 +843,13 @@ class Bomb(ba.Actor): self.handlemessage(ba.DieMessage()) def _handle_impact(self) -> None: - node = ba.get_collision_info('opposing_node') - # if we're an impact bomb and we came from this node, don't explode... + node = ba.getcollision().opposing_node + + # If we're an impact bomb and we came from this node, don't explode... # alternately if we're hitting another impact-bomb from the same # source, don't explode... - try: - node_delegate = node.getdelegate() - except Exception: - node_delegate = None + # try: + node_delegate = node.getdelegate(object) if node: if (self.bomb_type == 'impact' and (node is self.owner or @@ -883,7 +875,7 @@ class Bomb(ba.Actor): lambda: _safesetattr(self.node, 'stick_to_owner', True)) def _handle_splat(self) -> None: - node = ba.get_collision_info('opposing_node') + node = ba.getcollision().opposing_node if (node is not self.owner and ba.time() - self._last_sticky_sound_time > 1.0): self._last_sticky_sound_time = ba.time() diff --git a/assets/src/ba_data/python/bastd/actor/flag.py b/assets/src/ba_data/python/bastd/actor/flag.py index 31de6e7e..e06ea38a 100644 --- a/assets/src/ba_data/python/bastd/actor/flag.py +++ b/assets/src/ba_data/python/bastd/actor/flag.py @@ -105,19 +105,16 @@ class FlagFactory: self.flag_texture = ba.gettexture('flagColor') - -# noinspection PyTypeHints -def get_factory() -> FlagFactory: - """Get/create a shared bastd.actor.flag.FlagFactory object.""" - activity = ba.getactivity() - factory: FlagFactory - try: - # FIXME: Find elegant way to handle shared data like this. - factory = activity.shared_flag_factory # type: ignore - except Exception: - factory = activity.shared_flag_factory = FlagFactory() # type: ignore - assert isinstance(factory, FlagFactory) - return factory + @staticmethod + def get() -> FlagFactory: + """Get/create a shared FlagFactory instance.""" + activity = ba.getactivity() + factory = getattr(activity, 'shared_flag_factory', None) + if factory is None: + factory = FlagFactory() + setattr(activity, 'shared_flag_factory', factory) + assert isinstance(factory, FlagFactory) + return factory @dataclass @@ -201,7 +198,7 @@ class Flag(ba.Actor): self._initial_position: Optional[Sequence[float]] = None self._has_moved = False - factory = get_factory() + factory = FlagFactory.get() if materials is None: materials = [] diff --git a/assets/src/ba_data/python/bastd/actor/powerupbox.py b/assets/src/ba_data/python/bastd/actor/powerupbox.py index f04fc25a..2bbe156b 100644 --- a/assets/src/ba_data/python/bastd/actor/powerupbox.py +++ b/assets/src/ba_data/python/bastd/actor/powerupbox.py @@ -305,11 +305,9 @@ class PowerupBox(ba.Actor): elif isinstance(msg, _TouchedMessage): if not self._powersgiven: - node = ba.get_collision_info('opposing_node') - if node: - node.handlemessage( - ba.PowerupMessage(self.poweruptype, - source_node=self.node)) + node = ba.getcollision().opposing_node + node.handlemessage( + ba.PowerupMessage(self.poweruptype, source_node=self.node)) elif isinstance(msg, ba.DieMessage): if self.node: diff --git a/assets/src/ba_data/python/bastd/actor/spaz.py b/assets/src/ba_data/python/bastd/actor/spaz.py index 5c599ec2..ba314e21 100644 --- a/assets/src/ba_data/python/bastd/actor/spaz.py +++ b/assets/src/ba_data/python/bastd/actor/spaz.py @@ -62,7 +62,6 @@ def get_factory() -> SpazFactory: activity = ba.getactivity() factory = getattr(activity, 'shared_spaz_factory', None) if factory is None: - # noinspection PyTypeHints factory = activity.shared_spaz_factory = SpazFactory() # type: ignore assert isinstance(factory, SpazFactory) return factory @@ -1144,7 +1143,7 @@ class Spaz(ba.Actor): elif isinstance(msg, PunchHitMessage): if not self.node: return None - node = ba.get_collision_info('opposing_node') + node = ba.getcollision().opposing_node # Only allow one hit per node per punch. if node and (node not in self._punched_nodes): @@ -1203,10 +1202,11 @@ class Spaz(ba.Actor): if not self.node: return None - opposing_node, opposing_body = ba.get_collision_info( - 'opposing_node', 'opposing_body') - - if opposing_node is None or not opposing_node: + try: + collision = ba.getcollision() + opposing_node = collision.opposing_node + opposing_body = collision.opposing_body + except ba.NotFoundError: return True # Don't allow picking up of invincible dudes. diff --git a/assets/src/ba_data/python/bastd/actor/spazbot.py b/assets/src/ba_data/python/bastd/actor/spazbot.py index 5457cb20..d86cc1b2 100644 --- a/assets/src/ba_data/python/bastd/actor/spazbot.py +++ b/assets/src/ba_data/python/bastd/actor/spazbot.py @@ -62,7 +62,7 @@ class SpazBotPunchedMessage: self.damage = damage -class SpazBotDeathMessage: +class SpazBotDiedMessage: """A message saying a ba.SpazBot has died. category: Message Classes @@ -98,7 +98,7 @@ class SpazBot(Spaz): navigate obstacles and so should only be used on wide-open maps. - When a SpazBot is killed, it delivers a ba.SpazBotDeathMessage + When a SpazBot is killed, it delivers a ba.SpazBotDiedMessage to the current activity. When a SpazBot is punched, it delivers a ba.SpazBotPunchedMessage @@ -569,7 +569,7 @@ class SpazBot(Spaz): killerplayer = None if activity is not None: activity.handlemessage( - SpazBotDeathMessage(self, killerplayer, msg.how)) + SpazBotDiedMessage(self, killerplayer, msg.how)) super().handlemessage(msg) # Augment standard behavior. # Keep track of the player who last hit us for point rewarding. @@ -894,7 +894,7 @@ class ExplodeyBotShielded(ExplodeyBot): points_mult = 5 -class BotSet: +class SpazBotSet: """A container/controller for one or more ba.SpazBots. category: Bot Classes @@ -963,9 +963,7 @@ class BotSet: def _update(self) -> None: # Update one of our bot lists each time through. - # First off, remove dead bots from the list. Note that we check - # exists() here via the bool operator instead of dead; we want to - # keep them around even if they're just a corpse. + # First off, remove no-longer-existing bots from the list. try: bot_list = self._bot_lists[self._bot_update_list] = ([ b for b in self._bot_lists[self._bot_update_list] if b @@ -982,6 +980,9 @@ class BotSet: for player in ba.getactivity().players: assert isinstance(player, ba.Player) try: + # TODO: could use abstracted player.position here so we + # don't have to assume their actor type, but we have no + # abstracted velocity as of yet. if player.is_alive(): assert isinstance(player.actor, Spaz) assert player.actor.node diff --git a/assets/src/ba_data/python/bastd/game/assault.py b/assets/src/ba_data/python/bastd/game/assault.py index 6b402b88..7f2d814d 100644 --- a/assets/src/ba_data/python/bastd/game/assault.py +++ b/assets/src/ba_data/python/bastd/game/assault.py @@ -179,16 +179,9 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): ba.timer(length, light.delete) def _handle_base_collide(self, team: Team) -> None: - - # Attempt to pull a living ba.Player from what we hit. - cnode = ba.get_collision_info('opposing_node') - assert isinstance(cnode, ba.Node) - actor = cnode.getdelegate() - if not isinstance(actor, PlayerSpaz): - return - - player = actor.getplayer(Player) - if not player or not player.actor: + player = ba.getcollision().opposing_node.getdelegate( + PlayerSpaz, True).getplayer(Player) + if not player or not player.is_alive(): return # If its another team's player, they scored. diff --git a/assets/src/ba_data/python/bastd/game/capturetheflag.py b/assets/src/ba_data/python/bastd/game/capturetheflag.py index e99cb251..55d59a8e 100644 --- a/assets/src/ba_data/python/bastd/game/capturetheflag.py +++ b/assets/src/ba_data/python/bastd/game/capturetheflag.py @@ -28,15 +28,16 @@ from __future__ import annotations from typing import TYPE_CHECKING import ba -from bastd.actor import flag as stdflag from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard +from bastd.actor.flag import (FlagFactory, Flag, FlagPickedUpMessage, + FlagDroppedMessage, FlagDiedMessage) if TYPE_CHECKING: from typing import Any, Type, List, Dict, Sequence, Union, Optional -class CTFFlag(stdflag.Flag): +class CTFFlag(Flag): """Special flag type for CTF games.""" activity: CaptureTheFlagGame @@ -73,10 +74,7 @@ class CTFFlag(stdflag.Flag): @classmethod def from_node(cls, node: Optional[ba.Node]) -> Optional[CTFFlag]: """Attempt to get a CTFFlag from a flag node.""" - if not node: - return None - delegate = node.getdelegate() - return delegate if isinstance(delegate, CTFFlag) else None + return node.getdelegate(CTFFlag) if node else None class Player(ba.Player['Team']): @@ -246,8 +244,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): # We wanna know when *any* flag enters/leaves our base. base_region_mat.add_actions( - conditions=('they_have_material', - stdflag.get_factory().flagmaterial), + conditions=('they_have_material', FlagFactory.get().flagmaterial), actions=(('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('call', 'at_connect', @@ -277,9 +274,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): ba.playsound(self._swipsound, position=team.flag.node.position) def _handle_flag_entered_base(self, team: Team) -> None: - node = ba.get_collision_info('opposing_node') - assert isinstance(node, (ba.Node, type(None))) - flag = CTFFlag.from_node(node) + flag = CTFFlag.from_node(ba.getcollision().opposing_node) if not flag: print('Unable to get flag in _handle_flag_entered_base') return @@ -389,9 +384,12 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): def _handle_flag_left_base(self, team: Team) -> None: cur_time = ba.time() - op_node = ba.get_collision_info('opposing_node') - assert isinstance(op_node, (ba.Node, type(None))) - flag = CTFFlag.from_node(op_node) + try: + flag = CTFFlag.from_node(ba.getcollision().opposing_node) + except ba.NodeNotFoundError: + # We still get this call even if the flag stopped touching us + # because it was deleted; that's ok. + flag = None if not flag: return if flag.team is team: @@ -445,19 +443,15 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): """Return a player if given a node that is part of one's actor.""" if not node: return None - delegate = node.getdelegate() - if not isinstance(delegate, PlayerSpaz): - return None - return delegate.getplayer(Player) + delegate = node.getdelegate(PlayerSpaz) + return None if delegate is None else delegate.getplayer(Player) def _handle_hit_own_flag(self, team: Team, val: int) -> None: """ keep track of when each player is touching their own flag so we can award points when returned """ - srcnode = ba.get_collision_info('source_node') - assert isinstance(srcnode, (ba.Node, type(None))) - player = self._player_from_node(srcnode) + player = self._player_from_node(ba.getcollision().source_node) if player: player.touching_own_flag += (1 if val else -1) @@ -470,10 +464,9 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): # Use a node message to kill the flag instead of just killing # our team's. (avoids redundantly killing new flags if # multiple body parts generate callbacks in one step). - node = ba.get_collision_info('opposing_node') - if node: - self._award_players_touching_own_flag(team) - node.handlemessage(ba.DieMessage()) + node = ba.getcollision().opposing_node + self._award_players_touching_own_flag(team) + node.handlemessage(ba.DieMessage()) # Takes a non-zero amount of time to return. else: @@ -547,16 +540,17 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): # Augment standard behavior. super().handlemessage(msg) self.respawn_player(msg.getplayer(Player)) - elif isinstance(msg, stdflag.FlagDiedMessage): + elif isinstance(msg, FlagDiedMessage): assert isinstance(msg.flag, CTFFlag) ba.timer(0.1, ba.Call(self._spawn_flag_for_team, msg.flag.team)) - elif isinstance(msg, stdflag.FlagPickedUpMessage): + elif isinstance(msg, FlagPickedUpMessage): # Store the last player to hold the flag for scoring purposes. assert isinstance(msg.flag, CTFFlag) - msg.flag.last_player_to_hold = msg.node.getdelegate().getplayer() + msg.flag.last_player_to_hold = msg.node.getdelegate( + PlayerSpaz, True).getplayer(Player) msg.flag.held_count += 1 msg.flag.reset_return_times() - elif isinstance(msg, stdflag.FlagDroppedMessage): + elif isinstance(msg, FlagDroppedMessage): # Store the last player to hold the flag for scoring purposes. assert isinstance(msg.flag, CTFFlag) msg.flag.held_count -= 1 diff --git a/assets/src/ba_data/python/bastd/game/chosenone.py b/assets/src/ba_data/python/bastd/game/chosenone.py index bc1e2394..63cb7401 100644 --- a/assets/src/ba_data/python/bastd/game/chosenone.py +++ b/assets/src/ba_data/python/bastd/game/chosenone.py @@ -177,11 +177,10 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): # If we have a chosen one, ignore these. if self._get_chosen_one_player() is not None: return - delegate = ba.get_collision_info('opposing_node').getdelegate() - if isinstance(delegate, PlayerSpaz): - player = delegate.getplayer(Player) - if player is not None and player.is_alive(): - self._set_chosen_one_player(player) + player = ba.getcollision().opposing_node.getdelegate( + PlayerSpaz, True).getplayer(Player) + if player is not None and player.is_alive(): + self._set_chosen_one_player(player) def _flash_flag_spawn(self) -> None: light = ba.newnode('light', diff --git a/assets/src/ba_data/python/bastd/game/conquest.py b/assets/src/ba_data/python/bastd/game/conquest.py index 0f301525..a9511482 100644 --- a/assets/src/ba_data/python/bastd/game/conquest.py +++ b/assets/src/ba_data/python/bastd/game/conquest.py @@ -31,6 +31,7 @@ from typing import TYPE_CHECKING import ba from bastd.actor.flag import Flag from bastd.actor.scoreboard import Scoreboard +from bastd.actor.playerspaz import PlayerSpaz if TYPE_CHECKING: from typing import Any, Optional, Type, List, Dict, Sequence, Union @@ -233,15 +234,12 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): ba.timer(length, light.delete) def _handle_flag_player_collide(self) -> None: - flagnode, playernode = ba.get_collision_info('source_node', - 'opposing_node') - try: - player = playernode.getdelegate().getplayer() - flag = flagnode.getdelegate() - except Exception: - return # Player may have left and his body hit the flag. - assert isinstance(player, Player) - assert isinstance(flag, ConquestFlag) + collision = ba.getcollision() + flag = collision.source_node.getdelegate(ConquestFlag) + player = collision.opposing_node.getdelegate(PlayerSpaz, + True).getplayer(Player) + if not flag or not player: + return assert flag.light if flag.team is not player.team: diff --git a/assets/src/ba_data/python/bastd/game/easteregghunt.py b/assets/src/ba_data/python/bastd/game/easteregghunt.py index e8b21055..a03b40ce 100644 --- a/assets/src/ba_data/python/bastd/game/easteregghunt.py +++ b/assets/src/ba_data/python/bastd/game/easteregghunt.py @@ -31,7 +31,7 @@ from typing import TYPE_CHECKING import ba from bastd.actor.bomb import Bomb from bastd.actor.playerspaz import PlayerSpaz -from bastd.actor.spazbot import BotSet, BouncyBot, SpazBotDeathMessage +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 @@ -94,7 +94,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): self._eggs: List[Egg] = [] self._update_timer: Optional[ba.Timer] = None self._countdown: Optional[OnScreenCountdown] = None - self._bots: Optional[BotSet] = None + self._bots: Optional[SpazBotSet] = None # Base class overrides self.default_music = ba.MusicType.FORWARD_MARCH @@ -117,7 +117,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): self._update_timer = ba.Timer(0.25, self._update, repeat=True) self._countdown = OnScreenCountdown(60, endcall=self.end_game) ba.timer(4.0, self._countdown.start) - self._bots = BotSet() + self._bots = SpazBotSet() # Spawn evil bunny in co-op only. if isinstance(self.session, ba.CoopSession) and self._pro_mode: @@ -134,49 +134,44 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): self._bots.spawn_bot(BouncyBot, pos=(6, 4, -7.8), spawn_time=10.0) def _on_egg_player_collide(self) -> None: - if not self.has_ended(): - egg_node, playernode = ba.get_collision_info( - 'source_node', 'opposing_node') - if egg_node is not None and playernode is not None: - egg = egg_node.getdelegate() - assert isinstance(egg, Egg) - spaz = playernode.getdelegate() - assert isinstance(spaz, PlayerSpaz) - player = spaz.getplayer(Player) - if player and egg: - player.team.score += 1 + if self.has_ended(): + return + collision = ba.getcollision() + egg = collision.source_node.getdelegate(Egg) + player = collision.opposing_node.getdelegate(PlayerSpaz, + True).getplayer(Player) + if player and egg: + player.team.score += 1 - # Displays a +1 (and adds to individual player score in - # teams mode). - self.stats.player_scored(player, 1, screenmessage=False) - if self._max_eggs < 5: - self._max_eggs += 1.0 - elif self._max_eggs < 10: - self._max_eggs += 0.5 - elif self._max_eggs < 30: - self._max_eggs += 0.3 - self._update_scoreboard() - ba.playsound(self._collect_sound, - 0.5, - position=egg.node.position) + # Displays a +1 (and adds to individual player score in + # teams mode). + self.stats.player_scored(player, 1, screenmessage=False) + if self._max_eggs < 5: + self._max_eggs += 1.0 + elif self._max_eggs < 10: + self._max_eggs += 0.5 + elif self._max_eggs < 30: + self._max_eggs += 0.3 + self._update_scoreboard() + ba.playsound(self._collect_sound, 0.5, position=egg.node.position) - # Create a flash. - light = ba.newnode('light', - attrs={ - 'position': egg_node.position, - 'height_attenuated': False, - 'radius': 0.1, - 'color': (1, 1, 0) - }) - ba.animate(light, - 'intensity', { - 0: 0, - 0.1: 1.0, - 0.2: 0 - }, - loop=False) - ba.timer(0.200, light.delete) - egg.handlemessage(ba.DieMessage()) + # Create a flash. + light = ba.newnode('light', + attrs={ + 'position': egg.node.position, + 'height_attenuated': False, + 'radius': 0.1, + 'color': (1, 1, 0) + }) + ba.animate(light, + 'intensity', { + 0: 0, + 0.1: 1.0, + 0.2: 0 + }, + loop=False) + ba.timer(0.200, light.delete) + egg.handlemessage(ba.DieMessage()) def _update(self) -> None: # Misc. periodic updating. @@ -219,7 +214,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): player.respawn_icon = RespawnIcon(player, respawn_time) # Whenever our evil bunny dies, respawn him and spew some eggs. - elif isinstance(msg, SpazBotDeathMessage): + elif isinstance(msg, SpazBotDiedMessage): self._spawn_evil_bunny() assert msg.badguy.node pos = msg.badguy.node.position diff --git a/assets/src/ba_data/python/bastd/game/football.py b/assets/src/ba_data/python/bastd/game/football.py index 4c4f5c72..a9b472e0 100644 --- a/assets/src/ba_data/python/bastd/game/football.py +++ b/assets/src/ba_data/python/bastd/game/football.py @@ -30,20 +30,26 @@ from typing import TYPE_CHECKING import math import ba -from bastd.actor import spazbot -from bastd.actor import flag as stdflag from bastd.actor.bomb import TNTSpawner from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard from bastd.actor.respawnicon import RespawnIcon from bastd.actor.powerupbox import PowerupBoxFactory, PowerupBox +from bastd.actor.flag import (FlagFactory, Flag, FlagPickedUpMessage, + FlagDroppedMessage, FlagDiedMessage) +from bastd.actor.spazbot import (SpazBotDiedMessage, SpazBotPunchedMessage, + SpazBotSet, BrawlerBotLite, BrawlerBot, + BomberBotLite, BomberBot, TriggerBot, + ChargerBot, TriggerBotPro, BrawlerBotPro, + StickyBot, ExplodeyBot) if TYPE_CHECKING: from typing import Any, List, Type, Dict, Sequence, Optional, Union from bastd.actor.spaz import Spaz + from bastd.actor.spazbot import SpazBot -class FootballFlag(stdflag.Flag): +class FootballFlag(Flag): """Custom flag class for football games.""" def __init__(self, position: Sequence[float]): @@ -129,8 +135,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): self._whistle_sound = ba.getsound('refWhistle') self._score_region_material = ba.Material() self._score_region_material.add_actions( - conditions=('they_have_material', - stdflag.get_factory().flagmaterial), + conditions=('they_have_material', FlagFactory.get().flagmaterial), actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), @@ -204,7 +209,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): assert self._flag is not None if self._flag.scored: return - region = ba.get_collision_info('source_node') + region = ba.getcollision().source_node i = None for i in range(len(self._score_regions)): if region == self._score_regions[i].node: @@ -238,7 +243,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): ba.timer(1.0, self._kill_flag) light = ba.newnode('light', attrs={ - 'position': ba.get_collision_info('position'), + 'position': ba.getcollision().position, 'height_attenuated': False, 'color': (1, 0, 0) }) @@ -260,18 +265,14 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): self._score_to_win) def handlemessage(self, msg: Any) -> Any: - if isinstance(msg, stdflag.FlagPickedUpMessage): + if isinstance(msg, FlagPickedUpMessage): assert isinstance(msg.flag, FootballFlag) - try: - player = msg.node.getdelegate().getplayer() - if player: - msg.flag.last_holding_player = player - msg.flag.held_count += 1 - except Exception: - ba.print_exception('exception in Football FlagPickedUpMessage;' - " this shouldn't happen") + player = msg.node.getdelegate(PlayerSpaz, True).getplayer(Player) + if player: + msg.flag.last_holding_player = player + msg.flag.held_count += 1 - elif isinstance(msg, stdflag.FlagDroppedMessage): + elif isinstance(msg, FlagDroppedMessage): assert isinstance(msg.flag, FootballFlag) msg.flag.held_count -= 1 @@ -282,7 +283,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): self.respawn_player(msg.getplayer(Player)) # Respawn dead flags. - elif isinstance(msg, stdflag.FlagDiedMessage): + elif isinstance(msg, FlagDiedMessage): if not self.has_ended(): self._flag_respawn_timer = ba.Timer(3.0, self._spawn_flag) self._flag_respawn_light = ba.NodeActor( @@ -368,8 +369,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self._score_to_win = 21 self._score_region_material = ba.Material() self._score_region_material.add_actions( - conditions=('they_have_material', - stdflag.get_factory().flagmaterial), + conditions=('they_have_material', FlagFactory.get().flagmaterial), actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), @@ -384,15 +384,15 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self._score_regions: List[ba.NodeActor] = [] self._exclude_powerups: List[str] = [] self._have_tnt = False - self._bot_types_initial: Optional[List[Type[spazbot.SpazBot]]] = None - self._bot_types_7: Optional[List[Type[spazbot.SpazBot]]] = None - self._bot_types_14: Optional[List[Type[spazbot.SpazBot]]] = None + self._bot_types_initial: Optional[List[Type[SpazBot]]] = None + self._bot_types_7: Optional[List[Type[SpazBot]]] = None + self._bot_types_14: Optional[List[Type[SpazBot]]] = None self._bot_team: Optional[Team] = None self._starttime_ms: Optional[int] = None self._time_text: Optional[ba.NodeActor] = None self._time_text_input: Optional[ba.NodeActor] = None self._tntspawner: Optional[TNTSpawner] = None - self._bots = spazbot.BotSet() + self._bots = SpazBotSet() self._bot_spawn_timer: Optional[ba.Timer] = None self._powerup_drop_timer: Optional[ba.Timer] = None self._scoring_team: Optional[Team] = None @@ -440,64 +440,56 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): controlsguide.ControlsGuide(delay=3.0, lifespan=10.0, bright=True).autoretain() assert self.initial_player_info is not None - abot: Type[spazbot.SpazBot] - bbot: Type[spazbot.SpazBot] - cbot: Type[spazbot.SpazBot] + abot: Type[SpazBot] + bbot: Type[SpazBot] + cbot: Type[SpazBot] if self._preset in ['rookie', 'rookie_easy']: self._exclude_powerups = ['curse'] self._have_tnt = False - abot = (spazbot.BrawlerBotLite - if self._preset == 'rookie_easy' else spazbot.BrawlerBot) + abot = (BrawlerBotLite + if self._preset == 'rookie_easy' else BrawlerBot) self._bot_types_initial = [abot] * len(self.initial_player_info) - bbot = (spazbot.BomberBotLite - if self._preset == 'rookie_easy' else spazbot.BomberBot) + bbot = (BomberBotLite + if self._preset == 'rookie_easy' else BomberBot) self._bot_types_7 = ( [bbot] * (1 if len(self.initial_player_info) < 3 else 2)) - cbot = (spazbot.BomberBot - if self._preset == 'rookie_easy' else spazbot.TriggerBot) + cbot = (BomberBot if self._preset == 'rookie_easy' else TriggerBot) self._bot_types_14 = ( [cbot] * (1 if len(self.initial_player_info) < 3 else 2)) elif self._preset == 'tournament': self._exclude_powerups = [] self._have_tnt = True self._bot_types_initial = ( - [spazbot.BrawlerBot] * - (1 if len(self.initial_player_info) < 2 else 2)) + [BrawlerBot] * (1 if len(self.initial_player_info) < 2 else 2)) self._bot_types_7 = ( - [spazbot.TriggerBot] * - (1 if len(self.initial_player_info) < 3 else 2)) + [TriggerBot] * (1 if len(self.initial_player_info) < 3 else 2)) self._bot_types_14 = ( - [spazbot.ChargerBot] * - (1 if len(self.initial_player_info) < 4 else 2)) + [ChargerBot] * (1 if len(self.initial_player_info) < 4 else 2)) elif self._preset in ['pro', 'pro_easy', 'tournament_pro']: self._exclude_powerups = ['curse'] self._have_tnt = True - self._bot_types_initial = [spazbot.ChargerBot] * len( + self._bot_types_initial = [ChargerBot] * len( self.initial_player_info) - abot = (spazbot.BrawlerBot - if self._preset == 'pro' else spazbot.BrawlerBotLite) - typed_bot_list: List[Type[spazbot.SpazBot]] = [] + abot = (BrawlerBot if self._preset == 'pro' else BrawlerBotLite) + typed_bot_list: List[Type[SpazBot]] = [] self._bot_types_7 = ( - typed_bot_list + [abot] + [spazbot.BomberBot] * + typed_bot_list + [abot] + [BomberBot] * (1 if len(self.initial_player_info) < 3 else 2)) - bbot = (spazbot.TriggerBotPro - if self._preset == 'pro' else spazbot.TriggerBot) + bbot = (TriggerBotPro if self._preset == 'pro' else TriggerBot) self._bot_types_14 = ( [bbot] * (1 if len(self.initial_player_info) < 3 else 2)) elif self._preset in ['uber', 'uber_easy']: self._exclude_powerups = [] self._have_tnt = True - abot = (spazbot.BrawlerBotPro - if self._preset == 'uber' else spazbot.BrawlerBot) - bbot = (spazbot.TriggerBotPro - if self._preset == 'uber' else spazbot.TriggerBot) - typed_bot_list_2: List[Type[spazbot.SpazBot]] = [] - self._bot_types_initial = (typed_bot_list_2 + [spazbot.StickyBot] + + abot = (BrawlerBotPro if self._preset == 'uber' else BrawlerBot) + bbot = (TriggerBotPro if self._preset == 'uber' else TriggerBot) + typed_bot_list_2: List[Type[SpazBot]] = [] + self._bot_types_initial = (typed_bot_list_2 + [StickyBot] + [abot] * len(self.initial_player_info)) self._bot_types_7 = ( [bbot] * (1 if len(self.initial_player_info) < 3 else 2)) self._bot_types_14 = ( - [spazbot.ExplodeyBot] * + [ExplodeyBot] * (1 if len(self.initial_player_info) < 3 else 2)) else: raise Exception() @@ -549,7 +541,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): if self._have_tnt: self._tntspawner = TNTSpawner(position=(0, 1, -1)) - self._bots = spazbot.BotSet() + self._bots = SpazBotSet() self._bot_spawn_timer = ba.Timer(1.0, self._update_bots, repeat=True) for bottype in self._bot_types_initial: @@ -558,12 +550,12 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: self._show_standard_scores_to_beat_ui(scores) - def _on_bot_spawn(self, spaz: spazbot.SpazBot) -> None: + def _on_bot_spawn(self, spaz: SpazBot) -> None: # We want to move to the left by default. spaz.target_point_default = ba.Vec3(0, 0, 0) def _spawn_bot(self, - spaz_type: Type[spazbot.SpazBot], + spaz_type: Type[SpazBot], immediate: bool = False) -> None: assert self._bot_team is not None pos = self.map.get_start_position(self._bot_team.id) @@ -594,7 +586,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): return flagpos = ba.Vec3(self._flag.node.position) - closest_bot: Optional[spazbot.SpazBot] = None + closest_bot: Optional[SpazBot] = None closest_dist = 0.0 # Always gets assigned first time through. for bot in bots: # If a bot is picked up, he should forget about the flag. @@ -662,7 +654,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): return # See which score region it was. - region = ba.get_collision_info('source_node') + region = ba.getcollision().source_node i = None for i in range(len(self._score_regions)): if region == self._score_regions[i].node: @@ -707,7 +699,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self.update_scores() light = ba.newnode('light', attrs={ - 'position': ba.get_collision_info('position'), + 'position': ba.getcollision().position, 'height_attenuated': False, 'color': (1, 0, 0) }) @@ -821,12 +813,12 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): # Augment standard behavior. super().handlemessage(msg) - elif isinstance(msg, spazbot.SpazBotDeathMessage): + elif isinstance(msg, SpazBotDiedMessage): # Every time a bad guy dies, spawn a new one. ba.timer(3.0, ba.Call(self._spawn_bot, (type(msg.badguy)))) - elif isinstance(msg, spazbot.SpazBotPunchedMessage): + elif isinstance(msg, SpazBotPunchedMessage): if self._preset in ['rookie', 'rookie_easy']: if msg.damage >= 500: self._award_achievement('Super Punch') @@ -835,7 +827,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self._award_achievement('Super Mega Punch') # Respawn dead flags. - elif isinstance(msg, stdflag.FlagDiedMessage): + elif isinstance(msg, FlagDiedMessage): assert isinstance(msg.flag, FootballFlag) msg.flag.respawn_timer = ba.Timer(3.0, self._spawn_flag) self._flag_respawn_light = ba.NodeActor( diff --git a/assets/src/ba_data/python/bastd/game/hockey.py b/assets/src/ba_data/python/bastd/game/hockey.py index b027c3eb..805480ef 100644 --- a/assets/src/ba_data/python/bastd/game/hockey.py +++ b/assets/src/ba_data/python/bastd/game/hockey.py @@ -28,6 +28,7 @@ from __future__ import annotations from typing import TYPE_CHECKING import ba +from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard from bastd.actor.powerupbox import PowerupBoxFactory @@ -243,15 +244,10 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): self._update_scoreboard() def _handle_puck_player_collide(self) -> None: - try: - pucknode, playernode = ba.get_collision_info( - 'source_node', 'opposing_node') - puck = pucknode.getdelegate() - player = playernode.getdelegate().getplayer() - except Exception: - player = puck = None - assert isinstance(player, Player) - assert isinstance(puck, Puck) + collision = ba.getcollision() + puck = collision.source_node.getdelegate(Puck) + player = collision.opposing_node.getdelegate(PlayerSpaz, + True).getplayer(Player) if player and puck: puck.last_players_to_touch[player.team.id] = player @@ -269,7 +265,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): if self._puck.scored: return - region = ba.get_collision_info('source_node') + region = ba.getcollision().source_node index = 0 for index in range(len(self._score_regions)): if region == self._score_regions[index].node: @@ -308,7 +304,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): light = ba.newnode('light', attrs={ - 'position': ba.get_collision_info('position'), + 'position': ba.getcollision().position, 'height_attenuated': False, 'color': (1, 0, 0) }) diff --git a/assets/src/ba_data/python/bastd/game/kingofthehill.py b/assets/src/ba_data/python/bastd/game/kingofthehill.py index 4aea7d06..e1a4cef5 100644 --- a/assets/src/ba_data/python/bastd/game/kingofthehill.py +++ b/assets/src/ba_data/python/bastd/game/kingofthehill.py @@ -247,10 +247,8 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): ba.playsound(self._swipsound) def _handle_player_flag_region_collide(self, colliding: bool) -> None: - delegate = ba.get_collision_info('opposing_node').getdelegate() - if not isinstance(delegate, PlayerSpaz): - return - player = delegate.getplayer(Player) + player = ba.getcollision().opposing_node.getdelegate( + PlayerSpaz, True).getplayer(Player) if not player: return diff --git a/assets/src/ba_data/python/bastd/game/ninjafight.py b/assets/src/ba_data/python/bastd/game/ninjafight.py index 8176178f..439aab2a 100644 --- a/assets/src/ba_data/python/bastd/game/ninjafight.py +++ b/assets/src/ba_data/python/bastd/game/ninjafight.py @@ -29,7 +29,7 @@ import random from typing import TYPE_CHECKING import ba -from bastd.actor.spazbot import BotSet, ChargerBot, SpazBotDeathMessage +from bastd.actor.spazbot import SpazBotSet, ChargerBot, SpazBotDiedMessage from bastd.actor.onscreentimer import OnScreenTimer if TYPE_CHECKING: @@ -76,7 +76,7 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): self._winsound = ba.getsound('score') self._won = False self._timer: Optional[OnScreenTimer] = None - self._bots = BotSet() + self._bots = SpazBotSet() # Called when our game is transitioning in but not ready to begin; # we can go ahead and start creating stuff, playing music, etc. @@ -150,7 +150,7 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): self.respawn_player(msg.getplayer(Player)) # A spaz-bot has died. - elif isinstance(msg, SpazBotDeathMessage): + elif isinstance(msg, SpazBotDiedMessage): # Unfortunately the bot-set will always tell us there are living # bots if we ask here (the currently-dying bot isn't officially # marked dead yet) ..so lets push a call into the event loop to diff --git a/assets/src/ba_data/python/bastd/game/onslaught.py b/assets/src/ba_data/python/bastd/game/onslaught.py index 05f45aa5..dd7bf670 100644 --- a/assets/src/ba_data/python/bastd/game/onslaught.py +++ b/assets/src/ba_data/python/bastd/game/onslaught.py @@ -39,7 +39,7 @@ from bastd.actor.scoreboard import Scoreboard from bastd.actor.controlsguide import ControlsGuide from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory from bastd.actor.spazbot import ( - SpazBotDeathMessage, BotSet, ChargerBot, StickyBot, BomberBot, + SpazBotDiedMessage, SpazBotSet, ChargerBot, StickyBot, BomberBot, BomberBotLite, BrawlerBot, BrawlerBotLite, TriggerBot, BomberBotStaticLite, TriggerBotStatic, BomberBotProStatic, TriggerBotPro, ExplodeyBot, BrawlerBotProShielded, ChargerBotProShielded, BomberBotPro, @@ -189,7 +189,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): raise Exception('Unsupported map: ' + str(settings['map'])) self._scoreboard: Optional[Scoreboard] = None self._game_over = False - self._wave = 0 + self._wavenum = 0 self._can_end_wave = True self._score = 0 self._time_bonus = 0 @@ -200,7 +200,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): self._excluded_powerups: Optional[List[str]] = None self._waves: List[Wave] = [] self._tntspawner: Optional[TNTSpawner] = None - self._bots: Optional[BotSet] = None + self._bots: Optional[SpazBotSet] = None self._powerup_drop_timer: Optional[ba.Timer] = None self._time_bonus_timer: Optional[ba.Timer] = None self._time_bonus_text: Optional[ba.NodeActor] = None @@ -214,6 +214,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): def on_transition_in(self) -> None: super().on_transition_in() session = ba.getsession() + # Show special landmine tip on rookie preset. if self._preset in {Preset.ROOKIE, Preset.ROOKIE_EASY}: # Show once per session only (then we revert to regular tips). @@ -550,33 +551,28 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): self.setup_low_life_warning_sound() self._update_scores() - self._bots = BotSet() + self._bots = SpazBotSet() ba.timer(4.0, self._start_updating_waves) def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: self._show_standard_scores_to_beat_ui(scores) + def _get_dist_grp_totals(self, grps: List[Any]) -> Tuple[int, int]: + totalpts = 0 + totaldudes = 0 + for grp in grps: + for grpentry in grp: + dudes = grpentry[1] + totalpts += grpentry[0] * dudes + totaldudes += dudes + return totalpts, totaldudes + def _get_distribution(self, target_points: int, min_dudes: int, max_dudes: int, group_count: int, max_level: int) -> List[List[Tuple[int, int]]]: - """ calculate a distribution of bad guys given some params """ - # FIXME; This method wears the cone of shame - # pylint: disable=too-many-branches - # pylint: disable=too-many-statements - # pylint: disable=too-many-locals - # pylint: disable=too-many-nested-blocks + """Calculate a distribution of bad guys given some params.""" max_iterations = 10 + max_dudes * 2 - def _get_totals(grps: List[Any]) -> Tuple[int, int]: - totalpts = 0 - totaldudes = 0 - for grp in grps: - for grpentry in grp: - dudes = grpentry[1] - totalpts += grpentry[0] * dudes - totaldudes += dudes - return totalpts, totaldudes - groups: List[List[Tuple[int, int]]] = [] for _g in range(group_count): groups.append([]) @@ -588,83 +584,29 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): if max_level > 3: types.append(4) for iteration in range(max_iterations): + diff = self._add_dist_entry_if_possible(groups, max_dudes, + target_points, types) - # See how much we're off our target by. - total_points, total_dudes = _get_totals(groups) - diff = target_points - total_points - dudes_diff = max_dudes - total_dudes - - # Add an entry if one will fit. - value = types[random.randrange(len(types))] - group = groups[random.randrange(len(groups))] - if not group: - max_count = random.randint(1, 6) - else: - max_count = 2 * random.randint(1, 3) - max_count = min(max_count, dudes_diff) - count = min(max_count, diff // value) - if count > 0: - group.append((value, count)) - total_points += value * count - total_dudes += count - diff = target_points - total_points - - total_points, total_dudes = _get_totals(groups) + total_points, total_dudes = self._get_dist_grp_totals(groups) full = (total_points >= target_points) if full: # Every so often, delete a random entry just to # shake up our distribution. if random.random() < 0.2 and iteration != max_iterations - 1: - entry_count = 0 - for group in groups: - for _ in group: - entry_count += 1 - if entry_count > 1: - del_entry = random.randrange(entry_count) - entry_count = 0 - for group in groups: - for entry in group: - if entry_count == del_entry: - group.remove(entry) - break - entry_count += 1 + self._delete_random_dist_entry(groups) # If we don't have enough dudes, kill the group with # the biggest point value. elif (total_dudes < min_dudes and iteration != max_iterations - 1): - biggest_value = 9999 - biggest_entry = None - biggest_entry_group = None - for group in groups: - for entry in group: - if (entry[0] > biggest_value - or biggest_entry is None): - biggest_value = entry[0] - biggest_entry = entry - biggest_entry_group = group - if biggest_entry is not None: - assert biggest_entry_group is not None - biggest_entry_group.remove(biggest_entry) + self._delete_biggest_dist_entry(groups) # If we've got too many dudes, kill the group with the # smallest point value. elif (total_dudes > max_dudes and iteration != max_iterations - 1): - smallest_value = 9999 - smallest_entry = None - smallest_entry_group = None - for group in groups: - for entry in group: - if (entry[0] < smallest_value - or smallest_entry is None): - smallest_value = entry[0] - smallest_entry = entry - smallest_entry_group = group - assert smallest_entry is not None - assert smallest_entry_group is not None - smallest_entry_group.remove(smallest_entry) + self._delete_smallest_dist_entry(groups) # Close enough.. we're done. else: @@ -673,6 +615,76 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): return groups + def _add_dist_entry_if_possible(self, groups: List[List[Tuple[int, int]]], + max_dudes: int, target_points: int, + types: List[int]) -> int: + # See how much we're off our target by. + total_points, total_dudes = self._get_dist_grp_totals(groups) + diff = target_points - total_points + dudes_diff = max_dudes - total_dudes + + # Add an entry if one will fit. + value = types[random.randrange(len(types))] + group = groups[random.randrange(len(groups))] + if not group: + max_count = random.randint(1, 6) + else: + max_count = 2 * random.randint(1, 3) + max_count = min(max_count, dudes_diff) + count = min(max_count, diff // value) + if count > 0: + group.append((value, count)) + total_points += value * count + total_dudes += count + diff = target_points - total_points + return diff + + def _delete_smallest_dist_entry( + self, groups: List[List[Tuple[int, int]]]) -> None: + smallest_value = 9999 + smallest_entry = None + smallest_entry_group = None + for group in groups: + for entry in group: + if entry[0] < smallest_value or smallest_entry is None: + smallest_value = entry[0] + smallest_entry = entry + smallest_entry_group = group + assert smallest_entry is not None + assert smallest_entry_group is not None + smallest_entry_group.remove(smallest_entry) + + def _delete_biggest_dist_entry( + self, groups: List[List[Tuple[int, int]]]) -> None: + biggest_value = 9999 + biggest_entry = None + biggest_entry_group = None + for group in groups: + for entry in group: + if entry[0] > biggest_value or biggest_entry is None: + biggest_value = entry[0] + biggest_entry = entry + biggest_entry_group = group + if biggest_entry is not None: + assert biggest_entry_group is not None + biggest_entry_group.remove(biggest_entry) + + def _delete_random_dist_entry(self, + groups: List[List[Tuple[int, int]]]) -> None: + entry_count = 0 + for group in groups: + for _ in group: + entry_count += 1 + if entry_count > 1: + del_entry = random.randrange(entry_count) + entry_count = 0 + for group in groups: + for entry in group: + if entry_count == del_entry: + group.remove(entry) + break + entry_count += 1 + def spawn_player(self, player: Player) -> ba.Actor: # We keep track of who got hurt each wave for score purposes. @@ -734,7 +746,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): if outcome == 'defeat': self.fade_to_red() score: Optional[int] - if self._wave >= 2: + if self._wavenum >= 2: score = self._score fail_message = None else: @@ -777,7 +789,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}: won = False else: - won = (self._wave == len(self._waves)) + won = (self._wavenum == len(self._waves)) base_delay = 4.0 if won else 0.0 @@ -789,7 +801,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): base_delay += 1.0 # Reward flawless bonus. - if self._wave > 0: + if self._wavenum > 0: have_flawless = False for player in self.players: if player.is_alive() and not player.has_been_hurt: @@ -806,9 +818,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): scale=1.0, duration=4.0) self.celebrate(20.0) - self._award_completion_achievements() - ba.timer(base_delay, ba.WeakCall(self._award_completion_bonus)) base_delay += 0.85 ba.playsound(self._winsound) @@ -822,10 +832,10 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): ba.timer(base_delay, ba.WeakCall(self.do_end, 'victory')) return - self._wave += 1 + self._wavenum += 1 # Short celebration after waves. - if self._wave > 1: + if self._wavenum > 1: self.celebrate(0.5) ba.timer(base_delay, ba.WeakCall(self._start_next_wave)) @@ -903,17 +913,17 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): def _respawn_players_for_wave(self) -> None: # Respawn applicable players. - if self._wave > 1 and not self.is_waiting_for_continue(): + if self._wavenum > 1 and not self.is_waiting_for_continue(): for player in self.players: if (not player.is_alive() - and player.respawn_wave == self._wave): + and player.respawn_wave == self._wavenum): self.spawn_player(player) self._update_player_spawn_info() def _setup_wave_spawns(self, wave: Wave) -> None: tval = 0.0 dtime = 0.2 - if self._wave == 1: + if self._wavenum == 1: spawn_time = 3.973 tval += 0.5 else: @@ -970,7 +980,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}: wave = self._generate_random_wave() else: - wave = self._waves[self._wave - 1] + wave = self._waves[self._wavenum - 1] self._setup_wave_spawns(wave) self._update_wave_ui_and_bonuses() ba.timer(0.4, ba.Call(ba.playsound, self._new_wave_sound)) @@ -980,7 +990,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): self.show_zoom_message(ba.Lstr(value='${A} ${B}', subs=[('${A}', ba.Lstr(resource='waveText')), - ('${B}', str(self._wave))]), + ('${B}', str(self._wavenum))]), scale=1.0, duration=1.0, trail=True) @@ -1012,7 +1022,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): wttxt = ba.Lstr( value='${A} ${B}', subs=[('${A}', ba.Lstr(resource='waveText')), - ('${B}', str(self._wave) + + ('${B}', str(self._wavenum) + ('' if self._preset in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT] else ('/' + str(len(self._waves)))))]) @@ -1032,7 +1042,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): })) def _bot_levels_for_wave(self) -> List[List[Type[SpazBot]]]: - level = self._wave + level = self._wavenum bot_types = [ BomberBot, BrawlerBot, TriggerBot, ChargerBot, BomberBotPro, BrawlerBotPro, TriggerBotPro, BomberBotProShielded, ExplodeyBot, @@ -1067,6 +1077,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): [b for b in bot_types if b.points_mult == 2], [b for b in bot_types if b.points_mult == 3], [b for b in bot_types if b.points_mult == 4]] + # Make sure all lists have something in them if not all(bot_levels): raise RuntimeError('Got empty bot level') @@ -1099,7 +1110,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): Spacing(40.0 if random.random() < 0.5 else 80.0)) def _generate_random_wave(self) -> Wave: - level = self._wave + level = self._wavenum bot_levels = self._bot_levels_for_wave() target_points = level * 3 - 2 @@ -1199,20 +1210,19 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): self._a_player_has_been_hurt = True # Make note with the player when they can respawn: - if self._wave < 10: - player.respawn_wave = max(2, self._wave + 1) - elif self._wave < 15: - player.respawn_wave = max(2, self._wave + 2) + if self._wavenum < 10: + player.respawn_wave = max(2, self._wavenum + 1) + elif self._wavenum < 15: + player.respawn_wave = max(2, self._wavenum + 2) else: - player.respawn_wave = max(2, self._wave + 3) + player.respawn_wave = max(2, self._wavenum + 3) ba.timer(0.1, self._update_player_spawn_info) ba.timer(0.1, self._checkroundover) - elif isinstance(msg, SpazBotDeathMessage): + elif isinstance(msg, SpazBotDiedMessage): pts, importance = msg.badguy.get_death_points(msg.how) if msg.killerplayer is not None: self._handle_kill_achievements(msg) - target: Optional[Sequence[float]] try: assert msg.badguy.node @@ -1242,47 +1252,57 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): else: super().handlemessage(msg) - def _handle_kill_achievements(self, msg: SpazBotDeathMessage) -> None: - # pylint: disable=too-many-branches - # Toss-off-map achievement: + def _handle_kill_achievements(self, msg: SpazBotDiedMessage) -> None: if self._preset in {Preset.TRAINING, Preset.TRAINING_EASY}: - if msg.badguy.last_attacked_type == ('picked_up', 'default'): - self._throw_off_kills += 1 - if self._throw_off_kills >= 3: - self._award_achievement('Off You Go Then') - - # Land-mine achievement: + self._handle_training_kill_achievements(msg) elif self._preset in {Preset.ROOKIE, Preset.ROOKIE_EASY}: - if msg.badguy.last_attacked_type == ('explosion', 'land_mine'): - self._land_mine_kills += 1 - if self._land_mine_kills >= 3: - self._award_achievement('Mine Games') + self._handle_rookie_kill_achievements(msg) + elif self._preset in {Preset.PRO, Preset.PRO_EASY}: + self._handle_pro_kill_achievements(msg) + elif self._preset in {Preset.UBER, Preset.UBER_EASY}: + self._handle_uber_kill_achievements(msg) + + def _handle_uber_kill_achievements(self, msg: SpazBotDiedMessage) -> None: + + # Uber mine achievement: + if msg.badguy.last_attacked_type == ('explosion', 'land_mine'): + self._land_mine_kills += 1 + if self._land_mine_kills >= 6: + self._award_achievement('Gold Miner') + + # Uber tnt achievement: + if msg.badguy.last_attacked_type == ('explosion', 'tnt'): + self._tnt_kills += 1 + if self._tnt_kills >= 6: + ba.timer(0.5, ba.WeakCall(self._award_achievement, + 'TNT Terror')) + + def _handle_pro_kill_achievements(self, msg: SpazBotDiedMessage) -> None: # TNT achievement: - elif self._preset in {Preset.PRO, Preset.PRO_EASY}: - if msg.badguy.last_attacked_type == ('explosion', 'tnt'): - self._tnt_kills += 1 - if self._tnt_kills >= 3: - ba.timer( - 0.5, - ba.WeakCall(self._award_achievement, - 'Boom Goes the Dynamite')) + if msg.badguy.last_attacked_type == ('explosion', 'tnt'): + self._tnt_kills += 1 + if self._tnt_kills >= 3: + ba.timer( + 0.5, + ba.WeakCall(self._award_achievement, + 'Boom Goes the Dynamite')) - elif self._preset in {Preset.UBER, Preset.UBER_EASY}: + def _handle_rookie_kill_achievements(self, + msg: SpazBotDiedMessage) -> None: + # Land-mine achievement: + if msg.badguy.last_attacked_type == ('explosion', 'land_mine'): + self._land_mine_kills += 1 + if self._land_mine_kills >= 3: + self._award_achievement('Mine Games') - # Uber mine achievement: - if msg.badguy.last_attacked_type == ('explosion', 'land_mine'): - self._land_mine_kills += 1 - if self._land_mine_kills >= 6: - self._award_achievement('Gold Miner') - - # Uber tnt achievement: - if msg.badguy.last_attacked_type == ('explosion', 'tnt'): - self._tnt_kills += 1 - if self._tnt_kills >= 6: - ba.timer( - 0.5, ba.WeakCall(self._award_achievement, - 'TNT Terror')) + def _handle_training_kill_achievements(self, + msg: SpazBotDiedMessage) -> None: + # Toss-off-map achievement: + if msg.badguy.last_attacked_type == ('picked_up', 'default'): + self._throw_off_kills += 1 + if self._throw_off_kills >= 3: + self._award_achievement('Off You Go Then') def _set_can_end_wave(self) -> None: self._can_end_wave = True @@ -1308,7 +1328,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): return if not any(player.is_alive() for player in self.teams[0].players): # Allow continuing after wave 1. - if self._wave > 1: + if self._wavenum > 1: self.continue_or_end_game() else: self.end_game() diff --git a/assets/src/ba_data/python/bastd/game/race.py b/assets/src/ba_data/python/bastd/game/race.py index 05bc93f0..ecb6e38f 100644 --- a/assets/src/ba_data/python/bastd/game/race.py +++ b/assets/src/ba_data/python/bastd/game/race.py @@ -226,17 +226,13 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-nested-blocks - region_node, playernode = ba.get_collision_info( - 'source_node', 'opposing_node') - try: - player = playernode.getdelegate().getplayer() - except Exception: - player = None - region = region_node.getdelegate() + collision = ba.getcollision() + region = collision.source_node.getdelegate(RaceRegion) + playerspaz = collision.opposing_node.getdelegate(PlayerSpaz, + doraise=False) + player = playerspaz.getplayer(Player) if playerspaz else None if not player or not region: return - assert isinstance(player, Player) - assert isinstance(region, RaceRegion) last_region = player.last_region this_region = region.index diff --git a/assets/src/ba_data/python/bastd/game/runaround.py b/assets/src/ba_data/python/bastd/game/runaround.py index 46dc7f02..fbcce6e7 100644 --- a/assets/src/ba_data/python/bastd/game/runaround.py +++ b/assets/src/ba_data/python/bastd/game/runaround.py @@ -29,11 +29,16 @@ import random from typing import TYPE_CHECKING import ba -from bastd.actor import spazbot +from bastd.actor.popuptext import PopupText 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.actor.spazbot import ( + SpazBotSet, SpazBot, SpazBotDiedMessage, BomberBot, BrawlerBot, TriggerBot, + TriggerBotPro, BomberBotProShielded, TriggerBotProShielded, ChargerBot, + ChargerBotProShielded, StickyBot, ExplodeyBot, BrawlerBotProShielded, + BomberBotPro, BrawlerBotPro) if TYPE_CHECKING: from typing import Type, Any, List, Dict, Tuple, Sequence, Optional @@ -57,22 +62,23 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): 'No, you can\'t get up on the ledge. You have to throw bombs.', 'Whip back and forth to get more distance on your throws..' ] + default_music = ba.MusicType.MARCHING # How fast our various bot types walk. - _bot_speed_map: Dict[Type[spazbot.SpazBot], float] = { - spazbot.BomberBot: 0.48, - spazbot.BomberBotPro: 0.48, - spazbot.BomberBotProShielded: 0.48, - spazbot.BrawlerBot: 0.57, - spazbot.BrawlerBotPro: 0.57, - spazbot.BrawlerBotProShielded: 0.57, - spazbot.TriggerBot: 0.73, - spazbot.TriggerBotPro: 0.78, - spazbot.TriggerBotProShielded: 0.78, - spazbot.ChargerBot: 1.0, - spazbot.ChargerBotProShielded: 1.0, - spazbot.ExplodeyBot: 1.0, - spazbot.StickyBot: 0.5 + _bot_speed_map: Dict[Type[SpazBot], float] = { + BomberBot: 0.48, + BomberBotPro: 0.48, + BomberBotProShielded: 0.48, + BrawlerBot: 0.57, + BrawlerBotPro: 0.57, + BrawlerBotProShielded: 0.57, + TriggerBot: 0.73, + TriggerBotPro: 0.78, + TriggerBotProShielded: 0.78, + ChargerBot: 1.0, + ChargerBotProShielded: 1.0, + ExplodeyBot: 1.0, + StickyBot: 0.5 } def __init__(self, settings: Dict[str, Any]): @@ -108,7 +114,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._player_has_picked_up_powerup = False self._scoreboard: Optional[Scoreboard] = None self._game_over = False - self._wave = 0 + self._wavenum = 0 self._can_end_wave = True self._score = 0 self._time_bonus = 0 @@ -118,7 +124,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._exclude_powerups: Optional[List[str]] = None self._have_tnt: Optional[bool] = None self._waves: Optional[List[Dict[str, Any]]] = None - self._bots = spazbot.BotSet() + self._bots = SpazBotSet() self._tntspawner: Optional[TNTSpawner] = None self._lives_bg: Optional[ba.NodeActor] = None self._start_lives = 10 @@ -133,7 +139,6 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._wave_update_timer: Optional[ba.Timer] = None def on_transition_in(self) -> None: - self.default_music = ba.MusicType.MARCHING super().on_transition_in() self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'), score_split=0.5) @@ -157,110 +162,110 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._have_tnt = True self._waves = [ {'entries': [ - {'type': spazbot.BomberBot, 'path': 3 if hard else 2}, - {'type': spazbot.BomberBot, 'path': 2}, - {'type': spazbot.BomberBot, 'path': 2} if hard else None, - {'type': spazbot.BomberBot, 'path': 2} if player_count > 1 + {'type': BomberBot, 'path': 3 if hard else 2}, + {'type': BomberBot, 'path': 2}, + {'type': BomberBot, 'path': 2} if hard else None, + {'type': BomberBot, 'path': 2} if player_count > 1 else None, - {'type': spazbot.BomberBot, 'path': 1} if hard else None, - {'type': spazbot.BomberBot, 'path': 1} if player_count > 2 + {'type': BomberBot, 'path': 1} if hard else None, + {'type': BomberBot, 'path': 1} if player_count > 2 else None, - {'type': spazbot.BomberBot, 'path': 1} if player_count > 3 + {'type': BomberBot, 'path': 1} if player_count > 3 else None, ]}, {'entries': [ - {'type': spazbot.BomberBot, 'path': 1} if hard else None, - {'type': spazbot.BomberBot, 'path': 2} if hard else None, - {'type': spazbot.BomberBot, 'path': 2}, - {'type': spazbot.BomberBot, 'path': 2}, - {'type': spazbot.BomberBot, 'path': 2} if player_count > 3 + {'type': BomberBot, 'path': 1} if hard else None, + {'type': BomberBot, 'path': 2} if hard else None, + {'type': BomberBot, 'path': 2}, + {'type': BomberBot, 'path': 2}, + {'type': BomberBot, 'path': 2} if player_count > 3 else None, - {'type': spazbot.BrawlerBot, 'path': 3}, - {'type': spazbot.BrawlerBot, 'path': 3}, - {'type': spazbot.BrawlerBot, 'path': 3} if hard else None, - {'type': spazbot.BrawlerBot, 'path': 3} if player_count > 1 + {'type': BrawlerBot, 'path': 3}, + {'type': BrawlerBot, 'path': 3}, + {'type': BrawlerBot, 'path': 3} if hard else None, + {'type': BrawlerBot, 'path': 3} if player_count > 1 else None, - {'type': spazbot.BrawlerBot, 'path': 3} if player_count > 2 + {'type': BrawlerBot, 'path': 3} if player_count > 2 else None, ]}, {'entries': [ - {'type': spazbot.ChargerBot, 'path': 2} if hard else None, - {'type': spazbot.ChargerBot, 'path': 2} if player_count > 2 + {'type': ChargerBot, 'path': 2} if hard else None, + {'type': ChargerBot, 'path': 2} if player_count > 2 else None, - {'type': spazbot.TriggerBot, 'path': 2}, - {'type': spazbot.TriggerBot, 'path': 2} if player_count > 1 + {'type': TriggerBot, 'path': 2}, + {'type': TriggerBot, 'path': 2} if player_count > 1 else None, {'type': 'spacing', 'duration': 3.0}, - {'type': spazbot.BomberBot, 'path': 2} if hard else None, - {'type': spazbot.BomberBot, 'path': 2} if hard else None, - {'type': spazbot.BomberBot, 'path': 2}, - {'type': spazbot.BomberBot, 'path': 3} if hard else None, - {'type': spazbot.BomberBot, 'path': 3}, - {'type': spazbot.BomberBot, 'path': 3}, - {'type': spazbot.BomberBot, 'path': 3} if player_count > 3 + {'type': BomberBot, 'path': 2} if hard else None, + {'type': BomberBot, 'path': 2} if hard else None, + {'type': BomberBot, 'path': 2}, + {'type': BomberBot, 'path': 3} if hard else None, + {'type': BomberBot, 'path': 3}, + {'type': BomberBot, 'path': 3}, + {'type': BomberBot, 'path': 3} if player_count > 3 else None, ]}, {'entries': [ - {'type': spazbot.TriggerBot, 'path': 1} if hard else None, + {'type': TriggerBot, 'path': 1} if hard else None, {'type': 'spacing', 'duration': 1.0} if hard else None, - {'type': spazbot.TriggerBot, 'path': 2}, + {'type': TriggerBot, 'path': 2}, {'type': 'spacing', 'duration': 1.0}, - {'type': spazbot.TriggerBot, 'path': 3}, + {'type': TriggerBot, 'path': 3}, {'type': 'spacing', 'duration': 1.0}, - {'type': spazbot.TriggerBot, 'path': 1} if hard else None, + {'type': TriggerBot, 'path': 1} if hard else None, {'type': 'spacing', 'duration': 1.0} if hard else None, - {'type': spazbot.TriggerBot, 'path': 2}, + {'type': TriggerBot, 'path': 2}, {'type': 'spacing', 'duration': 1.0}, - {'type': spazbot.TriggerBot, 'path': 3}, + {'type': TriggerBot, 'path': 3}, {'type': 'spacing', 'duration': 1.0}, - {'type': spazbot.TriggerBot, 'path': 1} + {'type': TriggerBot, 'path': 1} if (player_count > 1 and hard) else None, {'type': 'spacing', 'duration': 1.0}, - {'type': spazbot.TriggerBot, 'path': 2} if player_count > 2 + {'type': TriggerBot, 'path': 2} if player_count > 2 else None, {'type': 'spacing', 'duration': 1.0}, - {'type': spazbot.TriggerBot, 'path': 3} if player_count > 3 + {'type': TriggerBot, 'path': 3} if player_count > 3 else None, {'type': 'spacing', 'duration': 1.0}, ]}, {'entries': [ - {'type': spazbot.ChargerBotProShielded if hard - else spazbot.ChargerBot, 'path': 1}, - {'type': spazbot.BrawlerBot, 'path': 2} if hard else None, - {'type': spazbot.BrawlerBot, 'path': 2}, - {'type': spazbot.BrawlerBot, 'path': 2}, - {'type': spazbot.BrawlerBot, 'path': 3} if hard else None, - {'type': spazbot.BrawlerBot, 'path': 3}, - {'type': spazbot.BrawlerBot, 'path': 3}, - {'type': spazbot.BrawlerBot, 'path': 3} if player_count > 1 + {'type': ChargerBotProShielded if hard + else ChargerBot, 'path': 1}, + {'type': BrawlerBot, 'path': 2} if hard else None, + {'type': BrawlerBot, 'path': 2}, + {'type': BrawlerBot, 'path': 2}, + {'type': BrawlerBot, 'path': 3} if hard else None, + {'type': BrawlerBot, 'path': 3}, + {'type': BrawlerBot, 'path': 3}, + {'type': BrawlerBot, 'path': 3} if player_count > 1 else None, - {'type': spazbot.BrawlerBot, 'path': 3} if player_count > 2 + {'type': BrawlerBot, 'path': 3} if player_count > 2 else None, - {'type': spazbot.BrawlerBot, 'path': 3} if player_count > 3 + {'type': BrawlerBot, 'path': 3} if player_count > 3 else None, ]}, {'entries': [ - {'type': spazbot.BomberBotProShielded, 'path': 3}, + {'type': BomberBotProShielded, 'path': 3}, {'type': 'spacing', 'duration': 1.5}, - {'type': spazbot.BomberBotProShielded, 'path': 2}, + {'type': BomberBotProShielded, 'path': 2}, {'type': 'spacing', 'duration': 1.5}, - {'type': spazbot.BomberBotProShielded, 'path': 1} if hard + {'type': BomberBotProShielded, 'path': 1} if hard else None, {'type': 'spacing', 'duration': 1.0} if hard else None, - {'type': spazbot.BomberBotProShielded, 'path': 3}, + {'type': BomberBotProShielded, 'path': 3}, {'type': 'spacing', 'duration': 1.5}, - {'type': spazbot.BomberBotProShielded, 'path': 2}, + {'type': BomberBotProShielded, 'path': 2}, {'type': 'spacing', 'duration': 1.5}, - {'type': spazbot.BomberBotProShielded, 'path': 1} if hard + {'type': BomberBotProShielded, 'path': 1} if hard else None, {'type': 'spacing', 'duration': 1.5} if hard else None, - {'type': spazbot.BomberBotProShielded, 'path': 3} + {'type': BomberBotProShielded, 'path': 3} if player_count > 1 else None, {'type': 'spacing', 'duration': 1.5}, - {'type': spazbot.BomberBotProShielded, 'path': 2} + {'type': BomberBotProShielded, 'path': 2} if player_count > 2 else None, {'type': 'spacing', 'duration': 1.5}, - {'type': spazbot.BomberBotProShielded, 'path': 1} + {'type': BomberBotProShielded, 'path': 1} if player_count > 3 else None, ]}, ] # yapf: disable @@ -269,84 +274,84 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._have_tnt = True self._waves = [ {'entries': [ - {'type': spazbot.TriggerBot, 'path': 1} if hard else None, - {'type': spazbot.TriggerBot, 'path': 2}, - {'type': spazbot.TriggerBot, 'path': 2}, - {'type': spazbot.TriggerBot, 'path': 3}, - {'type': spazbot.BrawlerBotPro if hard - else spazbot.BrawlerBot, 'point': 'bottom_left'}, - {'type': spazbot.BrawlerBotPro, 'point': 'bottom_right'} + {'type': TriggerBot, 'path': 1} if hard else None, + {'type': TriggerBot, 'path': 2}, + {'type': TriggerBot, 'path': 2}, + {'type': TriggerBot, 'path': 3}, + {'type': BrawlerBotPro if hard + else BrawlerBot, 'point': 'bottom_left'}, + {'type': BrawlerBotPro, 'point': 'bottom_right'} if player_count > 2 else None, ]}, {'entries': [ - {'type': spazbot.ChargerBot, 'path': 2}, - {'type': spazbot.ChargerBot, 'path': 3}, - {'type': spazbot.ChargerBot, 'path': 1} if hard else None, - {'type': spazbot.ChargerBot, 'path': 2}, - {'type': spazbot.ChargerBot, 'path': 3}, - {'type': spazbot.ChargerBot, 'path': 1} if player_count > 2 + {'type': ChargerBot, 'path': 2}, + {'type': ChargerBot, 'path': 3}, + {'type': ChargerBot, 'path': 1} if hard else None, + {'type': ChargerBot, 'path': 2}, + {'type': ChargerBot, 'path': 3}, + {'type': ChargerBot, 'path': 1} if player_count > 2 else None, ]}, {'entries': [ - {'type': spazbot.BomberBotProShielded, 'path': 1} if hard + {'type': BomberBotProShielded, 'path': 1} if hard else None, - {'type': spazbot.BomberBotProShielded, 'path': 2}, - {'type': spazbot.BomberBotProShielded, 'path': 2}, - {'type': spazbot.BomberBotProShielded, 'path': 3}, - {'type': spazbot.BomberBotProShielded, 'path': 3}, - {'type': spazbot.ChargerBot, 'point': 'bottom_right'}, - {'type': spazbot.ChargerBot, 'point': 'bottom_left'} + {'type': BomberBotProShielded, 'path': 2}, + {'type': BomberBotProShielded, 'path': 2}, + {'type': BomberBotProShielded, 'path': 3}, + {'type': BomberBotProShielded, 'path': 3}, + {'type': ChargerBot, 'point': 'bottom_right'}, + {'type': ChargerBot, 'point': 'bottom_left'} if player_count > 2 else None, ]}, {'entries': [ - {'type': spazbot.TriggerBotPro, 'path': 1} + {'type': TriggerBotPro, 'path': 1} if hard else None, - {'type': spazbot.TriggerBotPro, 'path': 1 if hard else 2}, - {'type': spazbot.TriggerBotPro, 'path': 1 if hard else 2}, - {'type': spazbot.TriggerBotPro, 'path': 1 if hard else 2}, - {'type': spazbot.TriggerBotPro, 'path': 1 if hard else 2}, - {'type': spazbot.TriggerBotPro, 'path': 1 if hard else 2}, - {'type': spazbot.TriggerBotPro, 'path': 1 if hard else 2} + {'type': TriggerBotPro, 'path': 1 if hard else 2}, + {'type': TriggerBotPro, 'path': 1 if hard else 2}, + {'type': TriggerBotPro, 'path': 1 if hard else 2}, + {'type': TriggerBotPro, 'path': 1 if hard else 2}, + {'type': TriggerBotPro, 'path': 1 if hard else 2}, + {'type': TriggerBotPro, 'path': 1 if hard else 2} if player_count > 1 else None, - {'type': spazbot.TriggerBotPro, 'path': 1 if hard else 2} + {'type': TriggerBotPro, 'path': 1 if hard else 2} if player_count > 3 else None, ]}, {'entries': [ - {'type': spazbot.TriggerBotProShielded if hard - else spazbot.TriggerBotPro, 'point': 'bottom_left'}, - {'type': spazbot.TriggerBotProShielded, + {'type': TriggerBotProShielded if hard + else TriggerBotPro, 'point': 'bottom_left'}, + {'type': TriggerBotProShielded, 'point': 'bottom_right'} if hard else None, - {'type': spazbot.TriggerBotProShielded, + {'type': TriggerBotProShielded, 'point': 'bottom_right'} if player_count > 2 else None, - {'type': spazbot.BomberBot, 'path': 3}, - {'type': spazbot.BomberBot, 'path': 3}, + {'type': BomberBot, 'path': 3}, + {'type': BomberBot, 'path': 3}, {'type': 'spacing', 'duration': 5.0}, - {'type': spazbot.BrawlerBot, 'path': 2}, - {'type': spazbot.BrawlerBot, 'path': 2}, + {'type': BrawlerBot, 'path': 2}, + {'type': BrawlerBot, 'path': 2}, {'type': 'spacing', 'duration': 5.0}, - {'type': spazbot.TriggerBot, 'path': 1} if hard else None, - {'type': spazbot.TriggerBot, 'path': 1} if hard else None, + {'type': TriggerBot, 'path': 1} if hard else None, + {'type': TriggerBot, 'path': 1} if hard else None, ]}, {'entries': [ - {'type': spazbot.BomberBotProShielded, 'path': 2}, - {'type': spazbot.BomberBotProShielded, 'path': 2} if hard + {'type': BomberBotProShielded, 'path': 2}, + {'type': BomberBotProShielded, 'path': 2} if hard else None, - {'type': spazbot.StickyBot, 'point': 'bottom_right'}, - {'type': spazbot.BomberBotProShielded, 'path': 2}, - {'type': spazbot.BomberBotProShielded, 'path': 2}, - {'type': spazbot.StickyBot, 'point': 'bottom_right'} + {'type': StickyBot, 'point': 'bottom_right'}, + {'type': BomberBotProShielded, 'path': 2}, + {'type': BomberBotProShielded, 'path': 2}, + {'type': StickyBot, 'point': 'bottom_right'} if player_count > 2 else None, - {'type': spazbot.BomberBotProShielded, 'path': 2}, - {'type': spazbot.ExplodeyBot, 'point': 'bottom_left'}, - {'type': spazbot.BomberBotProShielded, 'path': 2}, - {'type': spazbot.BomberBotProShielded, 'path': 2} + {'type': BomberBotProShielded, 'path': 2}, + {'type': ExplodeyBot, 'point': 'bottom_left'}, + {'type': BomberBotProShielded, 'path': 2}, + {'type': BomberBotProShielded, 'path': 2} if player_count > 1 else None, {'type': 'spacing', 'duration': 5.0}, - {'type': spazbot.StickyBot, 'point': 'bottom_left'}, + {'type': StickyBot, 'point': 'bottom_left'}, {'type': 'spacing', 'duration': 2.0}, - {'type': spazbot.ExplodeyBot, 'point': 'bottom_right'}, + {'type': ExplodeyBot, 'point': 'bottom_right'}, ]}, ] # yapf: disable elif self._preset in ['endless', 'endless_tournament']: @@ -401,9 +406,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): ba.timer(2.0, self._start_updating_waves) def _handle_reached_end(self) -> None: - oppnode = ba.get_collision_info('opposing_node') - spaz = oppnode.getdelegate() - + spaz = ba.getcollision().opposing_node.getdelegate(SpazBot, True) if not spaz.is_alive(): return # Ignore bodies flying in. @@ -495,7 +498,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): def _drop_powerups(self, standard_points: bool = False, force_first: str = None) -> None: - """ Generic powerup drop """ + """Generic powerup drop.""" # If its been a minute since our last wave finished emerging, stop # giving out land-mine powerups. (prevents players from waiting @@ -528,14 +531,6 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): extra_excludes)).autoretain() def end_game(self) -> None: - - # FIXME: If we don't start our bots moving again we get stuck. This - # is because the bot-set never prunes itself while movement is off - # and on_expire() never gets called for some bots because - # _prune_dead_objects() saw them as dead and pulled them off the - # weak-ref lists. this is an architectural issue; can hopefully fix - # this by having _actor_weak_refs not look at exists(). - self._bots.start_moving() ba.pushcall(ba.Call(self.do_end, 'defeat')) ba.setmusic(None) ba.playsound(self._player_death_sound) @@ -550,7 +545,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): delay = 0 score: Optional[int] - if self._wave >= 2: + if self._wavenum >= 2: score = self._score fail_message = None else: @@ -583,7 +578,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): won = False else: assert self._waves is not None - won = (self._wave == len(self._waves)) + won = (self._wavenum == len(self._waves)) # Reward time bonus. base_delay = 4.0 if won else 0 @@ -594,7 +589,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): base_delay += 1.0 # Reward flawless bonus. - if self._wave > 0 and self._flawless: + if self._wavenum > 0 and self._flawless: ba.timer(base_delay, self._award_flawless_bonus) base_delay += 1.0 @@ -636,69 +631,60 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): ba.timer(base_delay, ba.Call(self.do_end, 'victory')) return - self._wave += 1 + self._wavenum += 1 # Short celebration after waves. - if self._wave > 1: + if self._wavenum > 1: self.celebrate(0.5) ba.timer(base_delay, self._start_next_wave) def _award_completion_bonus(self) -> None: - from bastd.actor import popuptext bonus = 200 ba.playsound(self._cashregistersound) - popuptext.PopupText(ba.Lstr( - value='+${A} ${B}', - subs=[('${A}', str(bonus)), - ('${B}', ba.Lstr(resource='completionBonusText'))]), - color=(0.7, 0.7, 1.0, 1), - scale=1.6, - position=(0, 1.5, -1)).autoretain() + PopupText(ba.Lstr(value='+${A} ${B}', + subs=[('${A}', str(bonus)), + ('${B}', + ba.Lstr(resource='completionBonusText'))]), + color=(0.7, 0.7, 1.0, 1), + scale=1.6, + position=(0, 1.5, -1)).autoretain() self._score += bonus self._update_scores() def _award_lives_bonus(self) -> None: - from bastd.actor import popuptext bonus = self._lives * 30 ba.playsound(self._cashregistersound) - popuptext.PopupText(ba.Lstr(value='+${A} ${B}', - subs=[('${A}', str(bonus)), - ('${B}', - ba.Lstr(resource='livesBonusText')) - ]), - color=(0.7, 1.0, 0.3, 1), - scale=1.3, - position=(0, 1, -1)).autoretain() + PopupText(ba.Lstr(value='+${A} ${B}', + subs=[('${A}', str(bonus)), + ('${B}', ba.Lstr(resource='livesBonusText'))]), + color=(0.7, 1.0, 0.3, 1), + scale=1.3, + position=(0, 1, -1)).autoretain() self._score += bonus self._update_scores() def _award_time_bonus(self, bonus: int) -> None: - from bastd.actor import popuptext ba.playsound(self._cashregistersound) - popuptext.PopupText(ba.Lstr(value='+${A} ${B}', - subs=[('${A}', str(bonus)), - ('${B}', - ba.Lstr(resource='timeBonusText')) - ]), - color=(1, 1, 0.5, 1), - scale=1.0, - position=(0, 3, -1)).autoretain() + PopupText(ba.Lstr(value='+${A} ${B}', + subs=[('${A}', str(bonus)), + ('${B}', ba.Lstr(resource='timeBonusText'))]), + color=(1, 1, 0.5, 1), + scale=1.0, + position=(0, 3, -1)).autoretain() self._score += self._time_bonus self._update_scores() def _award_flawless_bonus(self) -> None: - from bastd.actor import popuptext ba.playsound(self._cashregistersound) - popuptext.PopupText(ba.Lstr(value='+${A} ${B}', - subs=[('${A}', str(self._flawless_bonus)), - ('${B}', - ba.Lstr(resource='perfectWaveText')) - ]), - color=(1, 1, 0.2, 1), - scale=1.2, - position=(0, 2, -1)).autoretain() + PopupText(ba.Lstr(value='+${A} ${B}', + subs=[('${A}', str(self._flawless_bonus)), + ('${B}', ba.Lstr(resource='perfectWaveText')) + ]), + color=(1, 1, 0.2, 1), + scale=1.2, + position=(0, 2, -1)).autoretain() assert self._flawless_bonus is not None self._score += self._flawless_bonus @@ -717,7 +703,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self.show_zoom_message(ba.Lstr(value='${A} ${B}', subs=[('${A}', ba.Lstr(resource='waveText')), - ('${B}', str(self._wave))]), + ('${B}', str(self._wavenum))]), scale=1.0, duration=1.0, trail=True) @@ -728,52 +714,49 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): bot_types: List[Optional[Dict[str, Any]]] = [] if self._preset in ['endless', 'endless_tournament']: - level = self._wave + level = self._wavenum target_points = (level + 1) * 8.0 group_count = random.randint(1, 3) entries = [] - spaz_types: List[Tuple[Type[spazbot.SpazBot], float]] = [] + spaz_types: List[Tuple[Type[SpazBot], float]] = [] if level < 6: - spaz_types += [(spazbot.BomberBot, 5.0)] + spaz_types += [(BomberBot, 5.0)] if level < 10: - spaz_types += [(spazbot.BrawlerBot, 5.0)] + spaz_types += [(BrawlerBot, 5.0)] if level < 15: - spaz_types += [(spazbot.TriggerBot, 6.0)] + spaz_types += [(TriggerBot, 6.0)] if level > 5: - spaz_types += [(spazbot.TriggerBotPro, 7.5) - ] * (1 + (level - 5) // 7) + spaz_types += [(TriggerBotPro, 7.5)] * (1 + (level - 5) // 7) if level > 2: - spaz_types += [(spazbot.BomberBotProShielded, 8.0) + spaz_types += [(BomberBotProShielded, 8.0) ] * (1 + (level - 2) // 6) if level > 6: - spaz_types += [(spazbot.TriggerBotProShielded, 12.0) + spaz_types += [(TriggerBotProShielded, 12.0) ] * (1 + (level - 6) // 5) if level > 1: - spaz_types += ([(spazbot.ChargerBot, 10.0)] * - (1 + (level - 1) // 4)) + spaz_types += ([(ChargerBot, 10.0)] * (1 + (level - 1) // 4)) if level > 7: - spaz_types += [(spazbot.ChargerBotProShielded, 15.0) + spaz_types += [(ChargerBotProShielded, 15.0) ] * (1 + (level - 7) // 3) # Bot type, their effect on target points. - defender_types: List[Tuple[Type[spazbot.SpazBot], float]] = [ - (spazbot.BomberBot, 0.9), - (spazbot.BrawlerBot, 0.9), - (spazbot.TriggerBot, 0.85), + defender_types: List[Tuple[Type[SpazBot], float]] = [ + (BomberBot, 0.9), + (BrawlerBot, 0.9), + (TriggerBot, 0.85), ] if level > 2: - defender_types += [(spazbot.ChargerBot, 0.75)] + defender_types += [(ChargerBot, 0.75)] if level > 4: - defender_types += ([(spazbot.StickyBot, 0.7)] * - (1 + (level - 5) // 6)) + defender_types += ([(StickyBot, 0.7)] * (1 + (level - 5) // 6)) if level > 6: - defender_types += ([(spazbot.ExplodeyBot, 0.7)] * - (1 + (level - 5) // 5)) + defender_types += ([(ExplodeyBot, 0.7)] * (1 + + (level - 5) // 5)) if level > 8: - defender_types += ([(spazbot.BrawlerBotProShielded, 0.65)] * + defender_types += ([(BrawlerBotProShielded, 0.65)] * (1 + (level - 5) // 4)) if level > 10: - defender_types += ([(spazbot.TriggerBotProShielded, 0.6)] * + defender_types += ([(TriggerBotProShielded, 0.6)] * (1 + (level - 6) // 3)) for group in range(group_count): @@ -821,8 +804,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): elif path == 6: this_target_point_s *= 0.7 - def _add_defender(defender_type: Tuple[Type[spazbot.SpazBot], - float], + def _add_defender(defender_type: Tuple[Type[SpazBot], float], pnt: str) -> Tuple[float, Dict[str, Any]]: # FIXME: should look into this warning # pylint: disable=cell-var-from-loop @@ -884,7 +866,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): else: assert self._waves is not None - wave = self._waves[self._wave - 1] + wave = self._waves[self._wavenum - 1] bot_types += wave['entries'] self._time_bonus_mult = 1.0 @@ -965,15 +947,13 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): # dropping land-mines powerups at some point (otherwise a crafty # player could fill the whole map with them) self._last_wave_end_time = ba.time() + t_sec - assert self._waves is not None + totalwaves = str(len(self._waves)) if self._waves is not None else '??' txtval = ba.Lstr( value='${A} ${B}', - subs=[ - ('${A}', ba.Lstr(resource='waveText')), - ('${B}', str(self._wave) + - ('' if self._preset in ['endless', 'endless_tournament'] else - ('/' + str(len(self._waves))))) - ]) + subs=[('${A}', ba.Lstr(resource='waveText')), + ('${B}', str(self._wavenum) + + ('' if self._preset in ['endless', 'endless_tournament'] + else f'/{totalwaves}'))]) self._wave_text = ba.NodeActor( ba.newnode('text', attrs={ @@ -989,16 +969,16 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): 'text': txtval })) - # noinspection PyTypeHints - def _on_bot_spawn(self, path: int, spaz: spazbot.SpazBot) -> None: + def _on_bot_spawn(self, path: int, spaz: SpazBot) -> None: + # Add our custom update callback and set some info for this bot. spaz_type = type(spaz) assert spaz is not None spaz.update_callback = self._update_bot - # FIXME: Do this in a type-safe way. - spaz.r_walk_row = path # type: ignore - spaz.r_walk_speed = self._get_bot_speed(spaz_type) # type: ignore + # Tack some custom attrs onto the spaz. + setattr(spaz, 'r_walk_row', path) + setattr(spaz, 'r_walk_speed', self._get_bot_speed(spaz_type)) def add_bot_at_point(self, point: str, @@ -1047,7 +1027,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): assert self._scoreboard is not None self._scoreboard.set_team_value(self.teams[0], score, max_score=None) - def _update_bot(self, bot: spazbot.SpazBot) -> bool: + def _update_bot(self, bot: SpazBot) -> bool: # Yup; that's a lot of return statements right there. # pylint: disable=too-many-return-statements @@ -1057,8 +1037,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): assert bot.node # FIXME: Do this in a type safe way. - r_walk_speed: float = bot.r_walk_speed # type: ignore - r_walk_row: int = bot.r_walk_row # type: ignore + r_walk_speed: float = getattr(bot, 'r_walk_speed') + r_walk_row: int = getattr(bot, 'r_walk_row') speed = r_walk_speed pos = bot.node.position @@ -1109,6 +1089,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): if ((ba.is_point_in_box(pos, boxes['b8']) and not ba.is_point_in_box(pos, boxes['b9'])) or pos == (0.0, 0.0, 0.0)): + # Default to walking right if we're still in the walking area. bot.node.move_left_right = speed bot.node.move_up_down = 0 @@ -1136,7 +1117,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): respawn_time, ba.Call(self.spawn_player_if_exists, player)) player.gamedata['respawn_icon'] = RespawnIcon(player, respawn_time) - elif isinstance(msg, spazbot.SpazBotDeathMessage): + elif isinstance(msg, SpazBotDiedMessage): if msg.how is ba.DeathType.REACHED_GOAL: return pts, importance = msg.badguy.get_death_points(msg.how) @@ -1161,7 +1142,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._dingsoundhigh, volume=0.6) except Exception as exc: - print('EXC in Runaround on SpazBotDeathMessage:', exc) + print('EXC in Runaround on SpazBotDiedMessage:', exc) # Normally we pull scores from the score-set, but if there's no # player lets be explicit. @@ -1172,7 +1153,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): else: super().handlemessage(msg) - def _get_bot_speed(self, bot_type: Type[spazbot.SpazBot]) -> float: + def _get_bot_speed(self, bot_type: Type[SpazBot]) -> float: speed = self._bot_speed_map.get(bot_type) if speed is None: raise TypeError('Invalid bot type to _get_bot_speed(): ' + diff --git a/assets/src/ba_data/python/bastd/game/thelaststand.py b/assets/src/ba_data/python/bastd/game/thelaststand.py index 14c3f54e..6840f909 100644 --- a/assets/src/ba_data/python/bastd/game/thelaststand.py +++ b/assets/src/ba_data/python/bastd/game/thelaststand.py @@ -26,14 +26,20 @@ import random from typing import TYPE_CHECKING import ba -from bastd.actor import spazbot from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.bomb import TNTSpawner from bastd.actor.scoreboard import Scoreboard from bastd.actor.powerupbox import PowerupBoxFactory, PowerupBox +from bastd.actor.spazbot import (SpazBotSet, SpazBotDiedMessage, BomberBot, + BomberBotPro, BomberBotProShielded, + BrawlerBot, BrawlerBotPro, + BrawlerBotProShielded, TriggerBot, + TriggerBotPro, TriggerBotProShielded, + ChargerBot, StickyBot, ExplodeyBot) if TYPE_CHECKING: from typing import Any, Dict, Type, List, Optional, Sequence + from bastd.actor.spazbot import SpazBot class Player(ba.Player['Team']): @@ -76,7 +82,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): self._excludepowerups: List[str] = [] self._scoreboard: Optional[Scoreboard] = None self._score = 0 - self._bots = spazbot.BotSet() + self._bots = SpazBotSet() self._dingsound = ba.getsound('dingSmall') self._dingsoundhigh = ba.getsound('dingSmallHigh') self._tntspawner: Optional[TNTSpawner] = None @@ -86,18 +92,18 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): # For each bot type: [spawn-rate, increase, d_increase] self._bot_spawn_types = { - spazbot.BomberBot: [1.00, 0.00, 0.000], - spazbot.BomberBotPro: [0.00, 0.05, 0.001], - spazbot.BomberBotProShielded: [0.00, 0.02, 0.002], - spazbot.BrawlerBot: [1.00, 0.00, 0.000], - spazbot.BrawlerBotPro: [0.00, 0.05, 0.001], - spazbot.BrawlerBotProShielded: [0.00, 0.02, 0.002], - spazbot.TriggerBot: [0.30, 0.00, 0.000], - spazbot.TriggerBotPro: [0.00, 0.05, 0.001], - spazbot.TriggerBotProShielded: [0.00, 0.02, 0.002], - spazbot.ChargerBot: [0.30, 0.05, 0.000], - spazbot.StickyBot: [0.10, 0.03, 0.001], - spazbot.ExplodeyBot: [0.05, 0.02, 0.002] + BomberBot: [1.00, 0.00, 0.000], + BomberBotPro: [0.00, 0.05, 0.001], + BomberBotProShielded: [0.00, 0.02, 0.002], + BrawlerBot: [1.00, 0.00, 0.000], + BrawlerBotPro: [0.00, 0.05, 0.001], + BrawlerBotProShielded: [0.00, 0.02, 0.002], + TriggerBot: [0.30, 0.00, 0.000], + TriggerBotPro: [0.00, 0.05, 0.001], + TriggerBotProShielded: [0.00, 0.02, 0.002], + ChargerBot: [0.30, 0.05, 0.000], + StickyBot: [0.10, 0.03, 0.001], + ExplodeyBot: [0.05, 0.02, 0.002] } # yapf: disable def on_transition_in(self) -> None: @@ -206,9 +212,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): for i in range(3): for playerpt in playerpts: dists[i] += abs(playerpt[0] - botspawnpts[i][0]) - - # Little random variation. - dists[i] += random.random() * 5.0 + dists[i] += random.random() * 5.0 # Minor random variation. if dists[0] > dists[1] and dists[0] > dists[2]: spawnpt = botspawnpts[0] elif dists[1] > dists[2]: @@ -227,7 +231,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): # Now go back through and see where this value falls. total = 0 - bottype: Optional[Type[spazbot.SpazBot]] = None + bottype: Optional[Type[SpazBot]] = None for spawntype in self._bot_spawn_types.items(): total += spawntype[1][0] if randval <= total: @@ -244,8 +248,9 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): spawntype[1][1] += spawntype[1][2] # incr spawn rate incr rate def _update_scores(self) -> None: - # Do achievements in default preset only. score = self._score + + # Achievements apply to the default preset only. if self._preset == 'default': if score >= 250: self._award_achievement('Last Stand Master') @@ -266,28 +271,21 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): self._score += msg.score self._update_scores() - elif isinstance(msg, spazbot.SpazBotDeathMessage): + elif isinstance(msg, SpazBotDiedMessage): pts, importance = msg.badguy.get_death_points(msg.how) target: Optional[Sequence[float]] if msg.killerplayer: - try: - assert msg.badguy.node - target = msg.badguy.node.position - except Exception: - ba.print_exception() - target = None - try: - self.stats.player_scored(msg.killerplayer, - pts, - target=target, - kill=True, - screenmessage=False, - importance=importance) - ba.playsound(self._dingsound - if importance == 1 else self._dingsoundhigh, - volume=0.6) - except Exception as exc: - print('EXC on last-stand SpazBotDeathMessage', exc) + assert msg.badguy.node + target = msg.badguy.node.position + self.stats.player_scored(msg.killerplayer, + pts, + target=target, + kill=True, + screenmessage=False, + importance=importance) + ba.playsound(self._dingsound + if importance == 1 else self._dingsoundhigh, + volume=0.6) # Normally we pull scores from the score-set, but if there's no # player lets be explicit. diff --git a/assets/src/ba_data/python/bastd/mainmenu.py b/assets/src/ba_data/python/bastd/mainmenu.py index 1856296c..391c082e 100644 --- a/assets/src/ba_data/python/bastd/mainmenu.py +++ b/assets/src/ba_data/python/bastd/mainmenu.py @@ -906,8 +906,8 @@ def _preload4() -> None: ba.getmodel(mname) for sname in ['metalHit', 'metalSkid', 'refWhistle', 'achievement']: ba.getsound(sname) - from bastd.actor.flag import get_factory - get_factory() + from bastd.actor.flag import FlagFactory + FlagFactory.get() class MainMenuSession(ba.Session): diff --git a/assets/src/ba_data/python/bastd/ui/party.py b/assets/src/ba_data/python/bastd/ui/party.py index a60787ab..9782d494 100644 --- a/assets/src/ba_data/python/bastd/ui/party.py +++ b/assets/src/ba_data/python/bastd/ui/party.py @@ -464,7 +464,6 @@ def handle_party_invite(name: str, invite_id: str) -> None: # FIXME: Ugly. # Let's store the invite-id away on the confirm window so we know if # we need to kill it later. - # noinspection PyTypeHints conf.party_invite_id = invite_id # type: ignore # store a weak-ref so we can get at this later diff --git a/docs/ba_module.md b/docs/ba_module.md index 943b679a..281527df 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2020-05-24 for Ballistica version 1.5.0 build 20026

+

last updated on 2020-05-25 for Ballistica version 1.5.0 build 20027

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!


@@ -51,7 +51,6 @@
  • ba.camerashake()
  • ba.emitfx()
  • ba.existing()
  • -
  • ba.get_collision_info()
  • ba.getactivity()
  • ba.getnodes()
  • ba.getsession()
  • @@ -200,6 +199,14 @@
  • ba.WidgetNotFoundError
  • +

    Misc Classes

    + +

    Misc Functions

    +

    Protocols