More modernizing and cleanup

This commit is contained in:
Eric Froemling 2020-05-25 01:55:30 -07:00
parent 72be5c0b8d
commit 808ea7dcdd
36 changed files with 820 additions and 717 deletions

View File

@ -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/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/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/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/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/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", "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/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/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/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/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/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", "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/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/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", "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/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/39/48/2f0e4350a080373301de625e2cc4", "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/c0/1d/dbc0a5e2ca05b626cbeffd6def6c", "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/ec/a8/5c58d6b5e1d844640334c35ad3af", "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/3d/0a/76b615f9bcb9bf4f0ca745060d40", "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/01/6b/0f623ac481e3553e352c1cd3cb7e", "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/c4/a5/3cad1ced77551369dc56c6bca5fb", "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/3b/3f/55094817619cae66f941a9f6db8c", "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/ec/6b/7aa29831a746672f9984cbb1dbf1", "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/07/4c/00a6136d0bd5573946d10e294720", "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/8d/8d/af068e67244cbb28444b27ae66d7", "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/f4/62/eb7cb26c952df481b757dcf53f9c" "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/cf/2b/589db924b489972981e10850a4e3"
} }

View File

@ -416,6 +416,7 @@
<w>deathmatch</w> <w>deathmatch</w>
<w>deek</w> <w>deek</w>
<w>defs</w> <w>defs</w>
<w>defsline</w>
<w>deivit</w> <w>deivit</w>
<w>depcls</w> <w>depcls</w>
<w>depdata</w> <w>depdata</w>
@ -749,6 +750,7 @@
<w>getclass</w> <w>getclass</w>
<w>getcollide</w> <w>getcollide</w>
<w>getcollidemodel</w> <w>getcollidemodel</w>
<w>getcollision</w>
<w>getconf</w> <w>getconf</w>
<w>getconfig</w> <w>getconfig</w>
<w>getcurrency</w> <w>getcurrency</w>
@ -1285,6 +1287,7 @@
<w>outname</w> <w>outname</w>
<w>outpath</w> <w>outpath</w>
<w>ouya</w> <w>ouya</w>
<w>overloadsigs</w>
<w>packagedir</w> <w>packagedir</w>
<w>packagedirs</w> <w>packagedirs</w>
<w>packagename</w> <w>packagename</w>
@ -1888,6 +1891,7 @@
<w>toplevel</w> <w>toplevel</w>
<w>totaldudes</w> <w>totaldudes</w>
<w>totalpts</w> <w>totalpts</w>
<w>totalwaves</w>
<w>totype</w> <w>totype</w>
<w>touchpad</w> <w>touchpad</w>
<w>tournamententry</w> <w>tournamententry</w>
@ -2022,6 +2026,7 @@
<w>waaah</w> <w>waaah</w>
<w>wanttype</w> <w>wanttype</w>
<w>wasdead</w> <w>wasdead</w>
<w>wavenum</w>
<w>weakref</w> <w>weakref</w>
<w>weakrefs</w> <w>weakrefs</w>
<w>weakrefset</w> <w>weakrefset</w>

View File

@ -13,6 +13,7 @@
"ba_data/python/ba/__pycache__/_assetmanager.cpython-37.opt-1.pyc", "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__/_benchmark.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_campaign.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__/_coopgame.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_coopsession.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", "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/_assetmanager.py",
"ba_data/python/ba/_benchmark.py", "ba_data/python/ba/_benchmark.py",
"ba_data/python/ba/_campaign.py", "ba_data/python/ba/_campaign.py",
"ba_data/python/ba/_collision.py",
"ba_data/python/ba/_coopgame.py", "ba_data/python/ba/_coopgame.py",
"ba_data/python/ba/_coopsession.py", "ba_data/python/ba/_coopsession.py",
"ba_data/python/ba/_dependency.py", "ba_data/python/ba/_dependency.py",

View File

@ -153,6 +153,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
build/ba_data/python/ba/_coopgame.py \ build/ba_data/python/ba/_coopgame.py \
build/ba_data/python/ba/_meta.py \ build/ba_data/python/ba/_meta.py \
build/ba_data/python/ba/_math.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/_servermode.py \
build/ba_data/python/ba/_appconfig.py \ build/ba_data/python/ba/_appconfig.py \
build/ba_data/python/ba/_gameresults.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__/_coopgame.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_meta.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__/_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__/_servermode.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_appconfig.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 \ 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: $^ @echo Compiling script: $^
@rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@ @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/__pycache__/_servermode.cpython-37.opt-1.pyc: \
build/ba_data/python/ba/_servermode.py build/ba_data/python/ba/_servermode.py
@echo Compiling script: $^ @echo Compiling script: $^

View File

@ -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) # (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. # I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression # 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 __future__ import annotations
from typing import TYPE_CHECKING, overload, Sequence from typing import TYPE_CHECKING, overload, Sequence, TypeVar
from ba._enums import TimeFormat, TimeType from ba._enums import TimeFormat, TimeType
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import (Any, Dict, Callable, Tuple, List, Optional, Union, from typing import (Any, Dict, Callable, Tuple, List, Optional, Union,
List, Type) List, Type)
from typing_extensions import Literal
from ba._app import App from ba._app import App
import ba import ba
_T = TypeVar('_T')
app: App app: App
@ -713,13 +716,26 @@ class Node:
""" """
return str() return str()
def getdelegate(self) -> Any: @overload
"""getdelegate() -> Any def getdelegate(self,
type: Type[_T],
doraise: Literal[False] = False) -> Optional[_T]:
...
Returns the node's current delegate, which is the Python object @overload
designated to handle the Node's messages. 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) -> <varies>
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: def getnodetype(self) -> str:
"""getnodetype() -> str """getnodetype() -> str

View File

@ -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, from _ba import (CollideModel, Context, ContextCall, Data, InputDevice,
Material, Model, Node, SessionPlayer, Sound, Texture, Timer, Material, Model, Node, SessionPlayer, Sound, Texture, Timer,
Vec3, Widget, buttonwidget, camerashake, checkboxwidget, Vec3, Widget, buttonwidget, camerashake, checkboxwidget,
columnwidget, containerwidget, do_once, emitfx, columnwidget, containerwidget, do_once, emitfx, getactivity,
get_collision_info, getactivity, getcollidemodel, getmodel, getcollidemodel, getmodel, getnodes, getsession, getsound,
getnodes, getsession, getsound, gettexture, hscrollwidget, gettexture, hscrollwidget, imagewidget, log, new_activity,
imagewidget, log, new_activity, newnode, playsound, newnode, playsound, printnodes, printobjects, pushcall, quit,
printnodes, printobjects, pushcall, quit, rowwidget, rowwidget, safecolor, screenmessage, scrollwidget,
safecolor, screenmessage, scrollwidget, set_analytics_screen, set_analytics_screen, charstr, textwidget, time, timer,
charstr, textwidget, time, timer, open_url, widget) open_url, widget)
from ba._activity import Activity from ba._activity import Activity
from ba._actor import Actor from ba._actor import Actor
from ba._player import Player, playercast, playercast_o 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._powerup import PowerupMessage, PowerupAcceptMessage
from ba._multiteamsession import MultiTeamSession from ba._multiteamsession import MultiTeamSession
from ba.ui import Window, UIController, uicleanupcheck from ba.ui import Window, UIController, uicleanupcheck
from ba._collision import Collision, getcollision
app: App app: App

View File

@ -343,7 +343,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
raise TypeError('non-actor passed to retain_actor') raise TypeError('non-actor passed to retain_actor')
if (self.has_transitioned_in() if (self.has_transitioned_in()
and _ba.time() - self._last_prune_dead_actors_time > 10.0): 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;' ' being pruned in your activity;'
' did you call Activity.on_transition_in()' ' did you call Activity.on_transition_in()'
' from your subclass?; ' + str(self) + ' (loc. a)') ' from your subclass?; ' + str(self) + ' (loc. a)')
@ -775,12 +775,12 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
# Send expire notices to all remaining actors. # Send expire notices to all remaining actors.
for actor_ref in self._actor_weak_refs: for actor_ref in self._actor_weak_refs:
try: actor = actor_ref()
actor = actor_ref() if actor is not None:
if actor is not None: try:
actor.on_expire() actor.on_expire()
except Exception: except Exception:
print_exception(f'Error expiring Actor {actor_ref()}') print_exception(f'Error expiring Actor {actor_ref()}')
# Reset all Players. # Reset all Players.
# (releases any attached actors, clears game-data, etc) # (releases any attached actors, clears game-data, etc)
@ -804,7 +804,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
sessionteam.reset_gamedata() sessionteam.reset_gamedata()
except SessionTeamNotFoundError: except SessionTeamNotFoundError:
pass pass
# print_exception(f'Error resetting Team {team}')
except Exception: except Exception:
print_exception(f'Error resetting Team {team}') print_exception(f'Error resetting Team {team}')
@ -819,6 +818,12 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
'Error during ba.Activity._expire() destroying data:') 'Error during ba.Activity._expire() destroying data:')
def _prune_dead_actors(self) -> None: 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() 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
]

View File

@ -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

View File

@ -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. # Store this on the current activity so we only have one at a time.
# FIXME: Need a type safe way to do this. # FIXME: Need a type safe way to do this.
activity = _ba.getactivity() activity = _ba.getactivity()
# noinspection PyTypeHints
activity.camera_flash_data = [] # type: ignore activity.camera_flash_data = [] # type: ignore
for i in range(6): for i in range(6):
light = NodeActor( light = NodeActor(

View File

@ -226,6 +226,5 @@ def playercast_o(totype: Type[PlayerType],
Category: Gameplay Functions Category: Gameplay Functions
""" """
# noinspection PyTypeHints
assert isinstance(player, (totype, type(None))) assert isinstance(player, (totype, type(None)))
return player return player

View File

@ -19,6 +19,7 @@
# SOFTWARE. # SOFTWARE.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
"""Powerup related functionality.""" """Powerup related functionality."""
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING

View File

@ -265,7 +265,6 @@ class BombFactory:
actions=('message', 'our_node', 'at_connect', SplatMessage())) actions=('message', 'our_node', 'at_connect', SplatMessage()))
# noinspection PyTypeHints
def get_factory() -> BombFactory: def get_factory() -> BombFactory:
"""Get/create a shared bastd.actor.bomb.BombFactory object.""" """Get/create a shared bastd.actor.bomb.BombFactory object."""
activity = ba.getactivity() activity = ba.getactivity()
@ -602,34 +601,28 @@ class Blast(ba.Actor):
self.node.delete() self.node.delete()
elif isinstance(msg, ExplodeHitMessage): elif isinstance(msg, ExplodeHitMessage):
node = ba.get_collision_info('opposing_node') node = ba.getcollision().opposing_node
if node: assert self.node
assert self.node nodepos = self.node.position
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 node.handlemessage(
mag = 2000.0 ba.HitMessage(pos=nodepos,
if self.blast_type == 'ice': velocity=(0, 0, 0),
mag *= 0.5 magnitude=mag,
elif self.blast_type == 'land_mine': hit_type=self.hit_type,
mag *= 2.5 hit_subtype=self.hit_subtype,
elif self.blast_type == 'tnt': radius=self.radius,
mag *= 2.0 source_player=ba.existing(self.source_player)))
if self.blast_type == 'ice':
node.handlemessage( ba.playsound(get_factory().freeze_sound, 10, position=nodepos)
ba.HitMessage(pos=nodepos, node.handlemessage(ba.FreezeMessage())
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: else:
super().handlemessage(msg) super().handlemessage(msg)
@ -850,14 +843,13 @@ class Bomb(ba.Actor):
self.handlemessage(ba.DieMessage()) self.handlemessage(ba.DieMessage())
def _handle_impact(self) -> None: def _handle_impact(self) -> None:
node = ba.get_collision_info('opposing_node') node = ba.getcollision().opposing_node
# if we're an impact bomb and we came from this node, don't explode...
# 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 # alternately if we're hitting another impact-bomb from the same
# source, don't explode... # source, don't explode...
try: # try:
node_delegate = node.getdelegate() node_delegate = node.getdelegate(object)
except Exception:
node_delegate = None
if node: if node:
if (self.bomb_type == 'impact' and if (self.bomb_type == 'impact' and
(node is self.owner or (node is self.owner or
@ -883,7 +875,7 @@ class Bomb(ba.Actor):
lambda: _safesetattr(self.node, 'stick_to_owner', True)) lambda: _safesetattr(self.node, 'stick_to_owner', True))
def _handle_splat(self) -> None: def _handle_splat(self) -> None:
node = ba.get_collision_info('opposing_node') node = ba.getcollision().opposing_node
if (node is not self.owner if (node is not self.owner
and ba.time() - self._last_sticky_sound_time > 1.0): and ba.time() - self._last_sticky_sound_time > 1.0):
self._last_sticky_sound_time = ba.time() self._last_sticky_sound_time = ba.time()

View File

@ -105,19 +105,16 @@ class FlagFactory:
self.flag_texture = ba.gettexture('flagColor') self.flag_texture = ba.gettexture('flagColor')
@staticmethod
# noinspection PyTypeHints def get() -> FlagFactory:
def get_factory() -> FlagFactory: """Get/create a shared FlagFactory instance."""
"""Get/create a shared bastd.actor.flag.FlagFactory object.""" activity = ba.getactivity()
activity = ba.getactivity() factory = getattr(activity, 'shared_flag_factory', None)
factory: FlagFactory if factory is None:
try: factory = FlagFactory()
# FIXME: Find elegant way to handle shared data like this. setattr(activity, 'shared_flag_factory', factory)
factory = activity.shared_flag_factory # type: ignore assert isinstance(factory, FlagFactory)
except Exception: return factory
factory = activity.shared_flag_factory = FlagFactory() # type: ignore
assert isinstance(factory, FlagFactory)
return factory
@dataclass @dataclass
@ -201,7 +198,7 @@ class Flag(ba.Actor):
self._initial_position: Optional[Sequence[float]] = None self._initial_position: Optional[Sequence[float]] = None
self._has_moved = False self._has_moved = False
factory = get_factory() factory = FlagFactory.get()
if materials is None: if materials is None:
materials = [] materials = []

View File

@ -305,11 +305,9 @@ class PowerupBox(ba.Actor):
elif isinstance(msg, _TouchedMessage): elif isinstance(msg, _TouchedMessage):
if not self._powersgiven: if not self._powersgiven:
node = ba.get_collision_info('opposing_node') node = ba.getcollision().opposing_node
if node: node.handlemessage(
node.handlemessage( ba.PowerupMessage(self.poweruptype, source_node=self.node))
ba.PowerupMessage(self.poweruptype,
source_node=self.node))
elif isinstance(msg, ba.DieMessage): elif isinstance(msg, ba.DieMessage):
if self.node: if self.node:

View File

@ -62,7 +62,6 @@ def get_factory() -> SpazFactory:
activity = ba.getactivity() activity = ba.getactivity()
factory = getattr(activity, 'shared_spaz_factory', None) factory = getattr(activity, 'shared_spaz_factory', None)
if factory is None: if factory is None:
# noinspection PyTypeHints
factory = activity.shared_spaz_factory = SpazFactory() # type: ignore factory = activity.shared_spaz_factory = SpazFactory() # type: ignore
assert isinstance(factory, SpazFactory) assert isinstance(factory, SpazFactory)
return factory return factory
@ -1144,7 +1143,7 @@ class Spaz(ba.Actor):
elif isinstance(msg, PunchHitMessage): elif isinstance(msg, PunchHitMessage):
if not self.node: if not self.node:
return None return None
node = ba.get_collision_info('opposing_node') node = ba.getcollision().opposing_node
# Only allow one hit per node per punch. # Only allow one hit per node per punch.
if node and (node not in self._punched_nodes): if node and (node not in self._punched_nodes):
@ -1203,10 +1202,11 @@ class Spaz(ba.Actor):
if not self.node: if not self.node:
return None return None
opposing_node, opposing_body = ba.get_collision_info( try:
'opposing_node', 'opposing_body') collision = ba.getcollision()
opposing_node = collision.opposing_node
if opposing_node is None or not opposing_node: opposing_body = collision.opposing_body
except ba.NotFoundError:
return True return True
# Don't allow picking up of invincible dudes. # Don't allow picking up of invincible dudes.

View File

@ -62,7 +62,7 @@ class SpazBotPunchedMessage:
self.damage = damage self.damage = damage
class SpazBotDeathMessage: class SpazBotDiedMessage:
"""A message saying a ba.SpazBot has died. """A message saying a ba.SpazBot has died.
category: Message Classes category: Message Classes
@ -98,7 +98,7 @@ class SpazBot(Spaz):
navigate obstacles and so should only be used navigate obstacles and so should only be used
on wide-open maps. 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. to the current activity.
When a SpazBot is punched, it delivers a ba.SpazBotPunchedMessage When a SpazBot is punched, it delivers a ba.SpazBotPunchedMessage
@ -569,7 +569,7 @@ class SpazBot(Spaz):
killerplayer = None killerplayer = None
if activity is not None: if activity is not None:
activity.handlemessage( activity.handlemessage(
SpazBotDeathMessage(self, killerplayer, msg.how)) SpazBotDiedMessage(self, killerplayer, msg.how))
super().handlemessage(msg) # Augment standard behavior. super().handlemessage(msg) # Augment standard behavior.
# Keep track of the player who last hit us for point rewarding. # Keep track of the player who last hit us for point rewarding.
@ -894,7 +894,7 @@ class ExplodeyBotShielded(ExplodeyBot):
points_mult = 5 points_mult = 5
class BotSet: class SpazBotSet:
"""A container/controller for one or more ba.SpazBots. """A container/controller for one or more ba.SpazBots.
category: Bot Classes category: Bot Classes
@ -963,9 +963,7 @@ class BotSet:
def _update(self) -> None: def _update(self) -> None:
# Update one of our bot lists each time through. # Update one of our bot lists each time through.
# First off, remove dead bots from the list. Note that we check # First off, remove no-longer-existing bots from the list.
# exists() here via the bool operator instead of dead; we want to
# keep them around even if they're just a corpse.
try: try:
bot_list = self._bot_lists[self._bot_update_list] = ([ bot_list = self._bot_lists[self._bot_update_list] = ([
b for b in self._bot_lists[self._bot_update_list] if b 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: for player in ba.getactivity().players:
assert isinstance(player, ba.Player) assert isinstance(player, ba.Player)
try: 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(): if player.is_alive():
assert isinstance(player.actor, Spaz) assert isinstance(player.actor, Spaz)
assert player.actor.node assert player.actor.node

View File

@ -179,16 +179,9 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]):
ba.timer(length, light.delete) ba.timer(length, light.delete)
def _handle_base_collide(self, team: Team) -> None: def _handle_base_collide(self, team: Team) -> None:
player = ba.getcollision().opposing_node.getdelegate(
# Attempt to pull a living ba.Player from what we hit. PlayerSpaz, True).getplayer(Player)
cnode = ba.get_collision_info('opposing_node') if not player or not player.is_alive():
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:
return return
# If its another team's player, they scored. # If its another team's player, they scored.

View File

@ -28,15 +28,16 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import ba import ba
from bastd.actor import flag as stdflag
from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard from bastd.actor.scoreboard import Scoreboard
from bastd.actor.flag import (FlagFactory, Flag, FlagPickedUpMessage,
FlagDroppedMessage, FlagDiedMessage)
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Sequence, Union, Optional from typing import Any, Type, List, Dict, Sequence, Union, Optional
class CTFFlag(stdflag.Flag): class CTFFlag(Flag):
"""Special flag type for CTF games.""" """Special flag type for CTF games."""
activity: CaptureTheFlagGame activity: CaptureTheFlagGame
@ -73,10 +74,7 @@ class CTFFlag(stdflag.Flag):
@classmethod @classmethod
def from_node(cls, node: Optional[ba.Node]) -> Optional[CTFFlag]: def from_node(cls, node: Optional[ba.Node]) -> Optional[CTFFlag]:
"""Attempt to get a CTFFlag from a flag node.""" """Attempt to get a CTFFlag from a flag node."""
if not node: return node.getdelegate(CTFFlag) if node else None
return None
delegate = node.getdelegate()
return delegate if isinstance(delegate, CTFFlag) else None
class Player(ba.Player['Team']): 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. # We wanna know when *any* flag enters/leaves our base.
base_region_mat.add_actions( base_region_mat.add_actions(
conditions=('they_have_material', conditions=('they_have_material', FlagFactory.get().flagmaterial),
stdflag.get_factory().flagmaterial),
actions=(('modify_part_collision', 'collide', actions=(('modify_part_collision', 'collide',
True), ('modify_part_collision', 'physical', False), True), ('modify_part_collision', 'physical', False),
('call', 'at_connect', ('call', 'at_connect',
@ -277,9 +274,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
ba.playsound(self._swipsound, position=team.flag.node.position) ba.playsound(self._swipsound, position=team.flag.node.position)
def _handle_flag_entered_base(self, team: Team) -> None: def _handle_flag_entered_base(self, team: Team) -> None:
node = ba.get_collision_info('opposing_node') flag = CTFFlag.from_node(ba.getcollision().opposing_node)
assert isinstance(node, (ba.Node, type(None)))
flag = CTFFlag.from_node(node)
if not flag: if not flag:
print('Unable to get flag in _handle_flag_entered_base') print('Unable to get flag in _handle_flag_entered_base')
return return
@ -389,9 +384,12 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
def _handle_flag_left_base(self, team: Team) -> None: def _handle_flag_left_base(self, team: Team) -> None:
cur_time = ba.time() cur_time = ba.time()
op_node = ba.get_collision_info('opposing_node') try:
assert isinstance(op_node, (ba.Node, type(None))) flag = CTFFlag.from_node(ba.getcollision().opposing_node)
flag = CTFFlag.from_node(op_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: if not flag:
return return
if flag.team is team: 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.""" """Return a player if given a node that is part of one's actor."""
if not node: if not node:
return None return None
delegate = node.getdelegate() delegate = node.getdelegate(PlayerSpaz)
if not isinstance(delegate, PlayerSpaz): return None if delegate is None else delegate.getplayer(Player)
return None
return delegate.getplayer(Player)
def _handle_hit_own_flag(self, team: Team, val: int) -> None: def _handle_hit_own_flag(self, team: Team, val: int) -> None:
""" """
keep track of when each player is touching their keep track of when each player is touching their
own flag so we can award points when returned own flag so we can award points when returned
""" """
srcnode = ba.get_collision_info('source_node') player = self._player_from_node(ba.getcollision().source_node)
assert isinstance(srcnode, (ba.Node, type(None)))
player = self._player_from_node(srcnode)
if player: if player:
player.touching_own_flag += (1 if val else -1) 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 # Use a node message to kill the flag instead of just killing
# our team's. (avoids redundantly killing new flags if # our team's. (avoids redundantly killing new flags if
# multiple body parts generate callbacks in one step). # multiple body parts generate callbacks in one step).
node = ba.get_collision_info('opposing_node') node = ba.getcollision().opposing_node
if node: self._award_players_touching_own_flag(team)
self._award_players_touching_own_flag(team) node.handlemessage(ba.DieMessage())
node.handlemessage(ba.DieMessage())
# Takes a non-zero amount of time to return. # Takes a non-zero amount of time to return.
else: else:
@ -547,16 +540,17 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
# Augment standard behavior. # Augment standard behavior.
super().handlemessage(msg) super().handlemessage(msg)
self.respawn_player(msg.getplayer(Player)) self.respawn_player(msg.getplayer(Player))
elif isinstance(msg, stdflag.FlagDiedMessage): elif isinstance(msg, FlagDiedMessage):
assert isinstance(msg.flag, CTFFlag) assert isinstance(msg.flag, CTFFlag)
ba.timer(0.1, ba.Call(self._spawn_flag_for_team, msg.flag.team)) 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. # Store the last player to hold the flag for scoring purposes.
assert isinstance(msg.flag, CTFFlag) 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.held_count += 1
msg.flag.reset_return_times() 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. # Store the last player to hold the flag for scoring purposes.
assert isinstance(msg.flag, CTFFlag) assert isinstance(msg.flag, CTFFlag)
msg.flag.held_count -= 1 msg.flag.held_count -= 1

View File

@ -177,11 +177,10 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
# If we have a chosen one, ignore these. # If we have a chosen one, ignore these.
if self._get_chosen_one_player() is not None: if self._get_chosen_one_player() is not None:
return return
delegate = ba.get_collision_info('opposing_node').getdelegate() player = ba.getcollision().opposing_node.getdelegate(
if isinstance(delegate, PlayerSpaz): PlayerSpaz, True).getplayer(Player)
player = delegate.getplayer(Player) if player is not None and player.is_alive():
if player is not None and player.is_alive(): self._set_chosen_one_player(player)
self._set_chosen_one_player(player)
def _flash_flag_spawn(self) -> None: def _flash_flag_spawn(self) -> None:
light = ba.newnode('light', light = ba.newnode('light',

View File

@ -31,6 +31,7 @@ from typing import TYPE_CHECKING
import ba import ba
from bastd.actor.flag import Flag from bastd.actor.flag import Flag
from bastd.actor.scoreboard import Scoreboard from bastd.actor.scoreboard import Scoreboard
from bastd.actor.playerspaz import PlayerSpaz
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Optional, Type, List, Dict, Sequence, Union 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) ba.timer(length, light.delete)
def _handle_flag_player_collide(self) -> None: def _handle_flag_player_collide(self) -> None:
flagnode, playernode = ba.get_collision_info('source_node', collision = ba.getcollision()
'opposing_node') flag = collision.source_node.getdelegate(ConquestFlag)
try: player = collision.opposing_node.getdelegate(PlayerSpaz,
player = playernode.getdelegate().getplayer() True).getplayer(Player)
flag = flagnode.getdelegate() if not flag or not player:
except Exception: return
return # Player may have left and his body hit the flag.
assert isinstance(player, Player)
assert isinstance(flag, ConquestFlag)
assert flag.light assert flag.light
if flag.team is not player.team: if flag.team is not player.team:

View File

@ -31,7 +31,7 @@ from typing import TYPE_CHECKING
import ba import ba
from bastd.actor.bomb import Bomb from bastd.actor.bomb import Bomb
from bastd.actor.playerspaz import PlayerSpaz 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.onscreencountdown import OnScreenCountdown
from bastd.actor.scoreboard import Scoreboard from bastd.actor.scoreboard import Scoreboard
from bastd.actor.respawnicon import RespawnIcon from bastd.actor.respawnicon import RespawnIcon
@ -94,7 +94,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
self._eggs: List[Egg] = [] self._eggs: List[Egg] = []
self._update_timer: Optional[ba.Timer] = None self._update_timer: Optional[ba.Timer] = None
self._countdown: Optional[OnScreenCountdown] = None self._countdown: Optional[OnScreenCountdown] = None
self._bots: Optional[BotSet] = None self._bots: Optional[SpazBotSet] = None
# Base class overrides # Base class overrides
self.default_music = ba.MusicType.FORWARD_MARCH 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._update_timer = ba.Timer(0.25, self._update, repeat=True)
self._countdown = OnScreenCountdown(60, endcall=self.end_game) self._countdown = OnScreenCountdown(60, endcall=self.end_game)
ba.timer(4.0, self._countdown.start) ba.timer(4.0, self._countdown.start)
self._bots = BotSet() self._bots = SpazBotSet()
# Spawn evil bunny in co-op only. # Spawn evil bunny in co-op only.
if isinstance(self.session, ba.CoopSession) and self._pro_mode: 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) self._bots.spawn_bot(BouncyBot, pos=(6, 4, -7.8), spawn_time=10.0)
def _on_egg_player_collide(self) -> None: def _on_egg_player_collide(self) -> None:
if not self.has_ended(): if self.has_ended():
egg_node, playernode = ba.get_collision_info( return
'source_node', 'opposing_node') collision = ba.getcollision()
if egg_node is not None and playernode is not None: egg = collision.source_node.getdelegate(Egg)
egg = egg_node.getdelegate() player = collision.opposing_node.getdelegate(PlayerSpaz,
assert isinstance(egg, Egg) True).getplayer(Player)
spaz = playernode.getdelegate() if player and egg:
assert isinstance(spaz, PlayerSpaz) player.team.score += 1
player = spaz.getplayer(Player)
if player and egg:
player.team.score += 1
# Displays a +1 (and adds to individual player score in # Displays a +1 (and adds to individual player score in
# teams mode). # teams mode).
self.stats.player_scored(player, 1, screenmessage=False) self.stats.player_scored(player, 1, screenmessage=False)
if self._max_eggs < 5: if self._max_eggs < 5:
self._max_eggs += 1.0 self._max_eggs += 1.0
elif self._max_eggs < 10: elif self._max_eggs < 10:
self._max_eggs += 0.5 self._max_eggs += 0.5
elif self._max_eggs < 30: elif self._max_eggs < 30:
self._max_eggs += 0.3 self._max_eggs += 0.3
self._update_scoreboard() self._update_scoreboard()
ba.playsound(self._collect_sound, ba.playsound(self._collect_sound, 0.5, position=egg.node.position)
0.5,
position=egg.node.position)
# Create a flash. # Create a flash.
light = ba.newnode('light', light = ba.newnode('light',
attrs={ attrs={
'position': egg_node.position, 'position': egg.node.position,
'height_attenuated': False, 'height_attenuated': False,
'radius': 0.1, 'radius': 0.1,
'color': (1, 1, 0) 'color': (1, 1, 0)
}) })
ba.animate(light, ba.animate(light,
'intensity', { 'intensity', {
0: 0, 0: 0,
0.1: 1.0, 0.1: 1.0,
0.2: 0 0.2: 0
}, },
loop=False) loop=False)
ba.timer(0.200, light.delete) ba.timer(0.200, light.delete)
egg.handlemessage(ba.DieMessage()) egg.handlemessage(ba.DieMessage())
def _update(self) -> None: def _update(self) -> None:
# Misc. periodic updating. # Misc. periodic updating.
@ -219,7 +214,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
player.respawn_icon = RespawnIcon(player, respawn_time) player.respawn_icon = RespawnIcon(player, respawn_time)
# Whenever our evil bunny dies, respawn him and spew some eggs. # Whenever our evil bunny dies, respawn him and spew some eggs.
elif isinstance(msg, SpazBotDeathMessage): elif isinstance(msg, SpazBotDiedMessage):
self._spawn_evil_bunny() self._spawn_evil_bunny()
assert msg.badguy.node assert msg.badguy.node
pos = msg.badguy.node.position pos = msg.badguy.node.position

View File

@ -30,20 +30,26 @@ from typing import TYPE_CHECKING
import math import math
import ba import ba
from bastd.actor import spazbot
from bastd.actor import flag as stdflag
from bastd.actor.bomb import TNTSpawner from bastd.actor.bomb import TNTSpawner
from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard from bastd.actor.scoreboard import Scoreboard
from bastd.actor.respawnicon import RespawnIcon from bastd.actor.respawnicon import RespawnIcon
from bastd.actor.powerupbox import PowerupBoxFactory, PowerupBox 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: if TYPE_CHECKING:
from typing import Any, List, Type, Dict, Sequence, Optional, Union from typing import Any, List, Type, Dict, Sequence, Optional, Union
from bastd.actor.spaz import Spaz 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.""" """Custom flag class for football games."""
def __init__(self, position: Sequence[float]): def __init__(self, position: Sequence[float]):
@ -129,8 +135,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
self._whistle_sound = ba.getsound('refWhistle') self._whistle_sound = ba.getsound('refWhistle')
self._score_region_material = ba.Material() self._score_region_material = ba.Material()
self._score_region_material.add_actions( self._score_region_material.add_actions(
conditions=('they_have_material', conditions=('they_have_material', FlagFactory.get().flagmaterial),
stdflag.get_factory().flagmaterial),
actions=( actions=(
('modify_part_collision', 'collide', True), ('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False), ('modify_part_collision', 'physical', False),
@ -204,7 +209,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
assert self._flag is not None assert self._flag is not None
if self._flag.scored: if self._flag.scored:
return return
region = ba.get_collision_info('source_node') region = ba.getcollision().source_node
i = None i = None
for i in range(len(self._score_regions)): for i in range(len(self._score_regions)):
if region == self._score_regions[i].node: if region == self._score_regions[i].node:
@ -238,7 +243,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
ba.timer(1.0, self._kill_flag) ba.timer(1.0, self._kill_flag)
light = ba.newnode('light', light = ba.newnode('light',
attrs={ attrs={
'position': ba.get_collision_info('position'), 'position': ba.getcollision().position,
'height_attenuated': False, 'height_attenuated': False,
'color': (1, 0, 0) 'color': (1, 0, 0)
}) })
@ -260,18 +265,14 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
self._score_to_win) self._score_to_win)
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, stdflag.FlagPickedUpMessage): if isinstance(msg, FlagPickedUpMessage):
assert isinstance(msg.flag, FootballFlag) assert isinstance(msg.flag, FootballFlag)
try: player = msg.node.getdelegate(PlayerSpaz, True).getplayer(Player)
player = msg.node.getdelegate().getplayer() if player:
if player: msg.flag.last_holding_player = player
msg.flag.last_holding_player = player msg.flag.held_count += 1
msg.flag.held_count += 1
except Exception:
ba.print_exception('exception in Football FlagPickedUpMessage;'
" this shouldn't happen")
elif isinstance(msg, stdflag.FlagDroppedMessage): elif isinstance(msg, FlagDroppedMessage):
assert isinstance(msg.flag, FootballFlag) assert isinstance(msg.flag, FootballFlag)
msg.flag.held_count -= 1 msg.flag.held_count -= 1
@ -282,7 +283,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
self.respawn_player(msg.getplayer(Player)) self.respawn_player(msg.getplayer(Player))
# Respawn dead flags. # Respawn dead flags.
elif isinstance(msg, stdflag.FlagDiedMessage): elif isinstance(msg, FlagDiedMessage):
if not self.has_ended(): if not self.has_ended():
self._flag_respawn_timer = ba.Timer(3.0, self._spawn_flag) self._flag_respawn_timer = ba.Timer(3.0, self._spawn_flag)
self._flag_respawn_light = ba.NodeActor( self._flag_respawn_light = ba.NodeActor(
@ -368,8 +369,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
self._score_to_win = 21 self._score_to_win = 21
self._score_region_material = ba.Material() self._score_region_material = ba.Material()
self._score_region_material.add_actions( self._score_region_material.add_actions(
conditions=('they_have_material', conditions=('they_have_material', FlagFactory.get().flagmaterial),
stdflag.get_factory().flagmaterial),
actions=( actions=(
('modify_part_collision', 'collide', True), ('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False), ('modify_part_collision', 'physical', False),
@ -384,15 +384,15 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
self._score_regions: List[ba.NodeActor] = [] self._score_regions: List[ba.NodeActor] = []
self._exclude_powerups: List[str] = [] self._exclude_powerups: List[str] = []
self._have_tnt = False self._have_tnt = False
self._bot_types_initial: Optional[List[Type[spazbot.SpazBot]]] = None self._bot_types_initial: Optional[List[Type[SpazBot]]] = None
self._bot_types_7: Optional[List[Type[spazbot.SpazBot]]] = None self._bot_types_7: Optional[List[Type[SpazBot]]] = None
self._bot_types_14: Optional[List[Type[spazbot.SpazBot]]] = None self._bot_types_14: Optional[List[Type[SpazBot]]] = None
self._bot_team: Optional[Team] = None self._bot_team: Optional[Team] = None
self._starttime_ms: Optional[int] = None self._starttime_ms: Optional[int] = None
self._time_text: Optional[ba.NodeActor] = None self._time_text: Optional[ba.NodeActor] = None
self._time_text_input: Optional[ba.NodeActor] = None self._time_text_input: Optional[ba.NodeActor] = None
self._tntspawner: Optional[TNTSpawner] = None self._tntspawner: Optional[TNTSpawner] = None
self._bots = spazbot.BotSet() self._bots = SpazBotSet()
self._bot_spawn_timer: Optional[ba.Timer] = None self._bot_spawn_timer: Optional[ba.Timer] = None
self._powerup_drop_timer: Optional[ba.Timer] = None self._powerup_drop_timer: Optional[ba.Timer] = None
self._scoring_team: Optional[Team] = 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, controlsguide.ControlsGuide(delay=3.0, lifespan=10.0,
bright=True).autoretain() bright=True).autoretain()
assert self.initial_player_info is not None assert self.initial_player_info is not None
abot: Type[spazbot.SpazBot] abot: Type[SpazBot]
bbot: Type[spazbot.SpazBot] bbot: Type[SpazBot]
cbot: Type[spazbot.SpazBot] cbot: Type[SpazBot]
if self._preset in ['rookie', 'rookie_easy']: if self._preset in ['rookie', 'rookie_easy']:
self._exclude_powerups = ['curse'] self._exclude_powerups = ['curse']
self._have_tnt = False self._have_tnt = False
abot = (spazbot.BrawlerBotLite abot = (BrawlerBotLite
if self._preset == 'rookie_easy' else spazbot.BrawlerBot) if self._preset == 'rookie_easy' else BrawlerBot)
self._bot_types_initial = [abot] * len(self.initial_player_info) self._bot_types_initial = [abot] * len(self.initial_player_info)
bbot = (spazbot.BomberBotLite bbot = (BomberBotLite
if self._preset == 'rookie_easy' else spazbot.BomberBot) if self._preset == 'rookie_easy' else BomberBot)
self._bot_types_7 = ( self._bot_types_7 = (
[bbot] * (1 if len(self.initial_player_info) < 3 else 2)) [bbot] * (1 if len(self.initial_player_info) < 3 else 2))
cbot = (spazbot.BomberBot cbot = (BomberBot if self._preset == 'rookie_easy' else TriggerBot)
if self._preset == 'rookie_easy' else spazbot.TriggerBot)
self._bot_types_14 = ( self._bot_types_14 = (
[cbot] * (1 if len(self.initial_player_info) < 3 else 2)) [cbot] * (1 if len(self.initial_player_info) < 3 else 2))
elif self._preset == 'tournament': elif self._preset == 'tournament':
self._exclude_powerups = [] self._exclude_powerups = []
self._have_tnt = True self._have_tnt = True
self._bot_types_initial = ( self._bot_types_initial = (
[spazbot.BrawlerBot] * [BrawlerBot] * (1 if len(self.initial_player_info) < 2 else 2))
(1 if len(self.initial_player_info) < 2 else 2))
self._bot_types_7 = ( self._bot_types_7 = (
[spazbot.TriggerBot] * [TriggerBot] * (1 if len(self.initial_player_info) < 3 else 2))
(1 if len(self.initial_player_info) < 3 else 2))
self._bot_types_14 = ( self._bot_types_14 = (
[spazbot.ChargerBot] * [ChargerBot] * (1 if len(self.initial_player_info) < 4 else 2))
(1 if len(self.initial_player_info) < 4 else 2))
elif self._preset in ['pro', 'pro_easy', 'tournament_pro']: elif self._preset in ['pro', 'pro_easy', 'tournament_pro']:
self._exclude_powerups = ['curse'] self._exclude_powerups = ['curse']
self._have_tnt = True self._have_tnt = True
self._bot_types_initial = [spazbot.ChargerBot] * len( self._bot_types_initial = [ChargerBot] * len(
self.initial_player_info) self.initial_player_info)
abot = (spazbot.BrawlerBot abot = (BrawlerBot if self._preset == 'pro' else BrawlerBotLite)
if self._preset == 'pro' else spazbot.BrawlerBotLite) typed_bot_list: List[Type[SpazBot]] = []
typed_bot_list: List[Type[spazbot.SpazBot]] = []
self._bot_types_7 = ( 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)) (1 if len(self.initial_player_info) < 3 else 2))
bbot = (spazbot.TriggerBotPro bbot = (TriggerBotPro if self._preset == 'pro' else TriggerBot)
if self._preset == 'pro' else spazbot.TriggerBot)
self._bot_types_14 = ( self._bot_types_14 = (
[bbot] * (1 if len(self.initial_player_info) < 3 else 2)) [bbot] * (1 if len(self.initial_player_info) < 3 else 2))
elif self._preset in ['uber', 'uber_easy']: elif self._preset in ['uber', 'uber_easy']:
self._exclude_powerups = [] self._exclude_powerups = []
self._have_tnt = True self._have_tnt = True
abot = (spazbot.BrawlerBotPro abot = (BrawlerBotPro if self._preset == 'uber' else BrawlerBot)
if self._preset == 'uber' else spazbot.BrawlerBot) bbot = (TriggerBotPro if self._preset == 'uber' else TriggerBot)
bbot = (spazbot.TriggerBotPro typed_bot_list_2: List[Type[SpazBot]] = []
if self._preset == 'uber' else spazbot.TriggerBot) self._bot_types_initial = (typed_bot_list_2 + [StickyBot] +
typed_bot_list_2: List[Type[spazbot.SpazBot]] = []
self._bot_types_initial = (typed_bot_list_2 + [spazbot.StickyBot] +
[abot] * len(self.initial_player_info)) [abot] * len(self.initial_player_info))
self._bot_types_7 = ( self._bot_types_7 = (
[bbot] * (1 if len(self.initial_player_info) < 3 else 2)) [bbot] * (1 if len(self.initial_player_info) < 3 else 2))
self._bot_types_14 = ( self._bot_types_14 = (
[spazbot.ExplodeyBot] * [ExplodeyBot] *
(1 if len(self.initial_player_info) < 3 else 2)) (1 if len(self.initial_player_info) < 3 else 2))
else: else:
raise Exception() raise Exception()
@ -549,7 +541,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
if self._have_tnt: if self._have_tnt:
self._tntspawner = TNTSpawner(position=(0, 1, -1)) 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) self._bot_spawn_timer = ba.Timer(1.0, self._update_bots, repeat=True)
for bottype in self._bot_types_initial: 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: def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None:
self._show_standard_scores_to_beat_ui(scores) 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. # We want to move to the left by default.
spaz.target_point_default = ba.Vec3(0, 0, 0) spaz.target_point_default = ba.Vec3(0, 0, 0)
def _spawn_bot(self, def _spawn_bot(self,
spaz_type: Type[spazbot.SpazBot], spaz_type: Type[SpazBot],
immediate: bool = False) -> None: immediate: bool = False) -> None:
assert self._bot_team is not None assert self._bot_team is not None
pos = self.map.get_start_position(self._bot_team.id) pos = self.map.get_start_position(self._bot_team.id)
@ -594,7 +586,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
return return
flagpos = ba.Vec3(self._flag.node.position) 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. closest_dist = 0.0 # Always gets assigned first time through.
for bot in bots: for bot in bots:
# If a bot is picked up, he should forget about the flag. # If a bot is picked up, he should forget about the flag.
@ -662,7 +654,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
return return
# See which score region it was. # See which score region it was.
region = ba.get_collision_info('source_node') region = ba.getcollision().source_node
i = None i = None
for i in range(len(self._score_regions)): for i in range(len(self._score_regions)):
if region == self._score_regions[i].node: if region == self._score_regions[i].node:
@ -707,7 +699,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
self.update_scores() self.update_scores()
light = ba.newnode('light', light = ba.newnode('light',
attrs={ attrs={
'position': ba.get_collision_info('position'), 'position': ba.getcollision().position,
'height_attenuated': False, 'height_attenuated': False,
'color': (1, 0, 0) 'color': (1, 0, 0)
}) })
@ -821,12 +813,12 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
# Augment standard behavior. # Augment standard behavior.
super().handlemessage(msg) super().handlemessage(msg)
elif isinstance(msg, spazbot.SpazBotDeathMessage): elif isinstance(msg, SpazBotDiedMessage):
# Every time a bad guy dies, spawn a new one. # Every time a bad guy dies, spawn a new one.
ba.timer(3.0, ba.Call(self._spawn_bot, (type(msg.badguy)))) 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 self._preset in ['rookie', 'rookie_easy']:
if msg.damage >= 500: if msg.damage >= 500:
self._award_achievement('Super Punch') self._award_achievement('Super Punch')
@ -835,7 +827,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
self._award_achievement('Super Mega Punch') self._award_achievement('Super Mega Punch')
# Respawn dead flags. # Respawn dead flags.
elif isinstance(msg, stdflag.FlagDiedMessage): elif isinstance(msg, FlagDiedMessage):
assert isinstance(msg.flag, FootballFlag) assert isinstance(msg.flag, FootballFlag)
msg.flag.respawn_timer = ba.Timer(3.0, self._spawn_flag) msg.flag.respawn_timer = ba.Timer(3.0, self._spawn_flag)
self._flag_respawn_light = ba.NodeActor( self._flag_respawn_light = ba.NodeActor(

View File

@ -28,6 +28,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import ba import ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard from bastd.actor.scoreboard import Scoreboard
from bastd.actor.powerupbox import PowerupBoxFactory from bastd.actor.powerupbox import PowerupBoxFactory
@ -243,15 +244,10 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
self._update_scoreboard() self._update_scoreboard()
def _handle_puck_player_collide(self) -> None: def _handle_puck_player_collide(self) -> None:
try: collision = ba.getcollision()
pucknode, playernode = ba.get_collision_info( puck = collision.source_node.getdelegate(Puck)
'source_node', 'opposing_node') player = collision.opposing_node.getdelegate(PlayerSpaz,
puck = pucknode.getdelegate() True).getplayer(Player)
player = playernode.getdelegate().getplayer()
except Exception:
player = puck = None
assert isinstance(player, Player)
assert isinstance(puck, Puck)
if player and puck: if player and puck:
puck.last_players_to_touch[player.team.id] = player puck.last_players_to_touch[player.team.id] = player
@ -269,7 +265,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
if self._puck.scored: if self._puck.scored:
return return
region = ba.get_collision_info('source_node') region = ba.getcollision().source_node
index = 0 index = 0
for index in range(len(self._score_regions)): for index in range(len(self._score_regions)):
if region == self._score_regions[index].node: if region == self._score_regions[index].node:
@ -308,7 +304,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
light = ba.newnode('light', light = ba.newnode('light',
attrs={ attrs={
'position': ba.get_collision_info('position'), 'position': ba.getcollision().position,
'height_attenuated': False, 'height_attenuated': False,
'color': (1, 0, 0) 'color': (1, 0, 0)
}) })

View File

@ -247,10 +247,8 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
ba.playsound(self._swipsound) ba.playsound(self._swipsound)
def _handle_player_flag_region_collide(self, colliding: bool) -> None: def _handle_player_flag_region_collide(self, colliding: bool) -> None:
delegate = ba.get_collision_info('opposing_node').getdelegate() player = ba.getcollision().opposing_node.getdelegate(
if not isinstance(delegate, PlayerSpaz): PlayerSpaz, True).getplayer(Player)
return
player = delegate.getplayer(Player)
if not player: if not player:
return return

View File

@ -29,7 +29,7 @@ import random
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import ba import ba
from bastd.actor.spazbot import BotSet, ChargerBot, SpazBotDeathMessage from bastd.actor.spazbot import SpazBotSet, ChargerBot, SpazBotDiedMessage
from bastd.actor.onscreentimer import OnScreenTimer from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING: if TYPE_CHECKING:
@ -76,7 +76,7 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]):
self._winsound = ba.getsound('score') self._winsound = ba.getsound('score')
self._won = False self._won = False
self._timer: Optional[OnScreenTimer] = None self._timer: Optional[OnScreenTimer] = None
self._bots = BotSet() self._bots = SpazBotSet()
# Called when our game is transitioning in but not ready to begin; # Called when our game is transitioning in but not ready to begin;
# we can go ahead and start creating stuff, playing music, etc. # 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)) self.respawn_player(msg.getplayer(Player))
# A spaz-bot has died. # A spaz-bot has died.
elif isinstance(msg, SpazBotDeathMessage): elif isinstance(msg, SpazBotDiedMessage):
# Unfortunately the bot-set will always tell us there are living # Unfortunately the bot-set will always tell us there are living
# bots if we ask here (the currently-dying bot isn't officially # 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 # marked dead yet) ..so lets push a call into the event loop to

View File

@ -39,7 +39,7 @@ from bastd.actor.scoreboard import Scoreboard
from bastd.actor.controlsguide import ControlsGuide from bastd.actor.controlsguide import ControlsGuide
from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory
from bastd.actor.spazbot import ( from bastd.actor.spazbot import (
SpazBotDeathMessage, BotSet, ChargerBot, StickyBot, BomberBot, SpazBotDiedMessage, SpazBotSet, ChargerBot, StickyBot, BomberBot,
BomberBotLite, BrawlerBot, BrawlerBotLite, TriggerBot, BomberBotStaticLite, BomberBotLite, BrawlerBot, BrawlerBotLite, TriggerBot, BomberBotStaticLite,
TriggerBotStatic, BomberBotProStatic, TriggerBotPro, ExplodeyBot, TriggerBotStatic, BomberBotProStatic, TriggerBotPro, ExplodeyBot,
BrawlerBotProShielded, ChargerBotProShielded, BomberBotPro, BrawlerBotProShielded, ChargerBotProShielded, BomberBotPro,
@ -189,7 +189,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
raise Exception('Unsupported map: ' + str(settings['map'])) raise Exception('Unsupported map: ' + str(settings['map']))
self._scoreboard: Optional[Scoreboard] = None self._scoreboard: Optional[Scoreboard] = None
self._game_over = False self._game_over = False
self._wave = 0 self._wavenum = 0
self._can_end_wave = True self._can_end_wave = True
self._score = 0 self._score = 0
self._time_bonus = 0 self._time_bonus = 0
@ -200,7 +200,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
self._excluded_powerups: Optional[List[str]] = None self._excluded_powerups: Optional[List[str]] = None
self._waves: List[Wave] = [] self._waves: List[Wave] = []
self._tntspawner: Optional[TNTSpawner] = None self._tntspawner: Optional[TNTSpawner] = None
self._bots: Optional[BotSet] = None self._bots: Optional[SpazBotSet] = None
self._powerup_drop_timer: Optional[ba.Timer] = None self._powerup_drop_timer: Optional[ba.Timer] = None
self._time_bonus_timer: Optional[ba.Timer] = None self._time_bonus_timer: Optional[ba.Timer] = None
self._time_bonus_text: Optional[ba.NodeActor] = 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: def on_transition_in(self) -> None:
super().on_transition_in() super().on_transition_in()
session = ba.getsession() session = ba.getsession()
# Show special landmine tip on rookie preset. # Show special landmine tip on rookie preset.
if self._preset in {Preset.ROOKIE, Preset.ROOKIE_EASY}: if self._preset in {Preset.ROOKIE, Preset.ROOKIE_EASY}:
# Show once per session only (then we revert to regular tips). # 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.setup_low_life_warning_sound()
self._update_scores() self._update_scores()
self._bots = BotSet() self._bots = SpazBotSet()
ba.timer(4.0, self._start_updating_waves) ba.timer(4.0, self._start_updating_waves)
def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None:
self._show_standard_scores_to_beat_ui(scores) 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, def _get_distribution(self, target_points: int, min_dudes: int,
max_dudes: int, group_count: int, max_dudes: int, group_count: int,
max_level: int) -> List[List[Tuple[int, int]]]: max_level: int) -> List[List[Tuple[int, int]]]:
""" calculate a distribution of bad guys given some params """ """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
max_iterations = 10 + max_dudes * 2 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]]] = [] groups: List[List[Tuple[int, int]]] = []
for _g in range(group_count): for _g in range(group_count):
groups.append([]) groups.append([])
@ -588,83 +584,29 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
if max_level > 3: if max_level > 3:
types.append(4) types.append(4)
for iteration in range(max_iterations): 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 = self._get_dist_grp_totals(groups)
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)
full = (total_points >= target_points) full = (total_points >= target_points)
if full: if full:
# Every so often, delete a random entry just to # Every so often, delete a random entry just to
# shake up our distribution. # shake up our distribution.
if random.random() < 0.2 and iteration != max_iterations - 1: if random.random() < 0.2 and iteration != max_iterations - 1:
entry_count = 0 self._delete_random_dist_entry(groups)
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
# If we don't have enough dudes, kill the group with # If we don't have enough dudes, kill the group with
# the biggest point value. # the biggest point value.
elif (total_dudes < min_dudes elif (total_dudes < min_dudes
and iteration != max_iterations - 1): and iteration != max_iterations - 1):
biggest_value = 9999 self._delete_biggest_dist_entry(groups)
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)
# If we've got too many dudes, kill the group with the # If we've got too many dudes, kill the group with the
# smallest point value. # smallest point value.
elif (total_dudes > max_dudes elif (total_dudes > max_dudes
and iteration != max_iterations - 1): and iteration != max_iterations - 1):
smallest_value = 9999 self._delete_smallest_dist_entry(groups)
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)
# Close enough.. we're done. # Close enough.. we're done.
else: else:
@ -673,6 +615,76 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
return groups 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: def spawn_player(self, player: Player) -> ba.Actor:
# We keep track of who got hurt each wave for score purposes. # 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': if outcome == 'defeat':
self.fade_to_red() self.fade_to_red()
score: Optional[int] score: Optional[int]
if self._wave >= 2: if self._wavenum >= 2:
score = self._score score = self._score
fail_message = None fail_message = None
else: else:
@ -777,7 +789,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}: if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}:
won = False won = False
else: else:
won = (self._wave == len(self._waves)) won = (self._wavenum == len(self._waves))
base_delay = 4.0 if won else 0.0 base_delay = 4.0 if won else 0.0
@ -789,7 +801,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
base_delay += 1.0 base_delay += 1.0
# Reward flawless bonus. # Reward flawless bonus.
if self._wave > 0: if self._wavenum > 0:
have_flawless = False have_flawless = False
for player in self.players: for player in self.players:
if player.is_alive() and not player.has_been_hurt: if player.is_alive() and not player.has_been_hurt:
@ -806,9 +818,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
scale=1.0, scale=1.0,
duration=4.0) duration=4.0)
self.celebrate(20.0) self.celebrate(20.0)
self._award_completion_achievements() self._award_completion_achievements()
ba.timer(base_delay, ba.WeakCall(self._award_completion_bonus)) ba.timer(base_delay, ba.WeakCall(self._award_completion_bonus))
base_delay += 0.85 base_delay += 0.85
ba.playsound(self._winsound) ba.playsound(self._winsound)
@ -822,10 +832,10 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
ba.timer(base_delay, ba.WeakCall(self.do_end, 'victory')) ba.timer(base_delay, ba.WeakCall(self.do_end, 'victory'))
return return
self._wave += 1 self._wavenum += 1
# Short celebration after waves. # Short celebration after waves.
if self._wave > 1: if self._wavenum > 1:
self.celebrate(0.5) self.celebrate(0.5)
ba.timer(base_delay, ba.WeakCall(self._start_next_wave)) 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: def _respawn_players_for_wave(self) -> None:
# Respawn applicable players. # 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: for player in self.players:
if (not player.is_alive() if (not player.is_alive()
and player.respawn_wave == self._wave): and player.respawn_wave == self._wavenum):
self.spawn_player(player) self.spawn_player(player)
self._update_player_spawn_info() self._update_player_spawn_info()
def _setup_wave_spawns(self, wave: Wave) -> None: def _setup_wave_spawns(self, wave: Wave) -> None:
tval = 0.0 tval = 0.0
dtime = 0.2 dtime = 0.2
if self._wave == 1: if self._wavenum == 1:
spawn_time = 3.973 spawn_time = 3.973
tval += 0.5 tval += 0.5
else: else:
@ -970,7 +980,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}: if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}:
wave = self._generate_random_wave() wave = self._generate_random_wave()
else: else:
wave = self._waves[self._wave - 1] wave = self._waves[self._wavenum - 1]
self._setup_wave_spawns(wave) self._setup_wave_spawns(wave)
self._update_wave_ui_and_bonuses() self._update_wave_ui_and_bonuses()
ba.timer(0.4, ba.Call(ba.playsound, self._new_wave_sound)) 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}', self.show_zoom_message(ba.Lstr(value='${A} ${B}',
subs=[('${A}', subs=[('${A}',
ba.Lstr(resource='waveText')), ba.Lstr(resource='waveText')),
('${B}', str(self._wave))]), ('${B}', str(self._wavenum))]),
scale=1.0, scale=1.0,
duration=1.0, duration=1.0,
trail=True) trail=True)
@ -1012,7 +1022,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
wttxt = ba.Lstr( wttxt = ba.Lstr(
value='${A} ${B}', value='${A} ${B}',
subs=[('${A}', ba.Lstr(resource='waveText')), subs=[('${A}', ba.Lstr(resource='waveText')),
('${B}', str(self._wave) + ('${B}', str(self._wavenum) +
('' if self._preset ('' if self._preset
in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT] else in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT] else
('/' + str(len(self._waves)))))]) ('/' + str(len(self._waves)))))])
@ -1032,7 +1042,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
})) }))
def _bot_levels_for_wave(self) -> List[List[Type[SpazBot]]]: def _bot_levels_for_wave(self) -> List[List[Type[SpazBot]]]:
level = self._wave level = self._wavenum
bot_types = [ bot_types = [
BomberBot, BrawlerBot, TriggerBot, ChargerBot, BomberBotPro, BomberBot, BrawlerBot, TriggerBot, ChargerBot, BomberBotPro,
BrawlerBotPro, TriggerBotPro, BomberBotProShielded, ExplodeyBot, 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 == 2],
[b for b in bot_types if b.points_mult == 3], [b for b in bot_types if b.points_mult == 3],
[b for b in bot_types if b.points_mult == 4]] [b for b in bot_types if b.points_mult == 4]]
# Make sure all lists have something in them # Make sure all lists have something in them
if not all(bot_levels): if not all(bot_levels):
raise RuntimeError('Got empty bot level') 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)) Spacing(40.0 if random.random() < 0.5 else 80.0))
def _generate_random_wave(self) -> Wave: def _generate_random_wave(self) -> Wave:
level = self._wave level = self._wavenum
bot_levels = self._bot_levels_for_wave() bot_levels = self._bot_levels_for_wave()
target_points = level * 3 - 2 target_points = level * 3 - 2
@ -1199,20 +1210,19 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
self._a_player_has_been_hurt = True self._a_player_has_been_hurt = True
# Make note with the player when they can respawn: # Make note with the player when they can respawn:
if self._wave < 10: if self._wavenum < 10:
player.respawn_wave = max(2, self._wave + 1) player.respawn_wave = max(2, self._wavenum + 1)
elif self._wave < 15: elif self._wavenum < 15:
player.respawn_wave = max(2, self._wave + 2) player.respawn_wave = max(2, self._wavenum + 2)
else: 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._update_player_spawn_info)
ba.timer(0.1, self._checkroundover) ba.timer(0.1, self._checkroundover)
elif isinstance(msg, SpazBotDeathMessage): elif isinstance(msg, SpazBotDiedMessage):
pts, importance = msg.badguy.get_death_points(msg.how) pts, importance = msg.badguy.get_death_points(msg.how)
if msg.killerplayer is not None: if msg.killerplayer is not None:
self._handle_kill_achievements(msg) self._handle_kill_achievements(msg)
target: Optional[Sequence[float]] target: Optional[Sequence[float]]
try: try:
assert msg.badguy.node assert msg.badguy.node
@ -1242,47 +1252,57 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
else: else:
super().handlemessage(msg) super().handlemessage(msg)
def _handle_kill_achievements(self, msg: SpazBotDeathMessage) -> None: def _handle_kill_achievements(self, msg: SpazBotDiedMessage) -> None:
# pylint: disable=too-many-branches
# Toss-off-map achievement:
if self._preset in {Preset.TRAINING, Preset.TRAINING_EASY}: if self._preset in {Preset.TRAINING, Preset.TRAINING_EASY}:
if msg.badguy.last_attacked_type == ('picked_up', 'default'): self._handle_training_kill_achievements(msg)
self._throw_off_kills += 1
if self._throw_off_kills >= 3:
self._award_achievement('Off You Go Then')
# Land-mine achievement:
elif self._preset in {Preset.ROOKIE, Preset.ROOKIE_EASY}: elif self._preset in {Preset.ROOKIE, Preset.ROOKIE_EASY}:
if msg.badguy.last_attacked_type == ('explosion', 'land_mine'): self._handle_rookie_kill_achievements(msg)
self._land_mine_kills += 1 elif self._preset in {Preset.PRO, Preset.PRO_EASY}:
if self._land_mine_kills >= 3: self._handle_pro_kill_achievements(msg)
self._award_achievement('Mine Games') 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: # TNT achievement:
elif self._preset in {Preset.PRO, Preset.PRO_EASY}: if msg.badguy.last_attacked_type == ('explosion', 'tnt'):
if msg.badguy.last_attacked_type == ('explosion', 'tnt'): self._tnt_kills += 1
self._tnt_kills += 1 if self._tnt_kills >= 3:
if self._tnt_kills >= 3: ba.timer(
ba.timer( 0.5,
0.5, ba.WeakCall(self._award_achievement,
ba.WeakCall(self._award_achievement, 'Boom Goes the Dynamite'))
'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: def _handle_training_kill_achievements(self,
if msg.badguy.last_attacked_type == ('explosion', 'land_mine'): msg: SpazBotDiedMessage) -> None:
self._land_mine_kills += 1 # Toss-off-map achievement:
if self._land_mine_kills >= 6: if msg.badguy.last_attacked_type == ('picked_up', 'default'):
self._award_achievement('Gold Miner') self._throw_off_kills += 1
if self._throw_off_kills >= 3:
# Uber tnt achievement: self._award_achievement('Off You Go Then')
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 _set_can_end_wave(self) -> None: def _set_can_end_wave(self) -> None:
self._can_end_wave = True self._can_end_wave = True
@ -1308,7 +1328,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
return return
if not any(player.is_alive() for player in self.teams[0].players): if not any(player.is_alive() for player in self.teams[0].players):
# Allow continuing after wave 1. # Allow continuing after wave 1.
if self._wave > 1: if self._wavenum > 1:
self.continue_or_end_game() self.continue_or_end_game()
else: else:
self.end_game() self.end_game()

View File

@ -226,17 +226,13 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
# pylint: disable=too-many-nested-blocks # pylint: disable=too-many-nested-blocks
region_node, playernode = ba.get_collision_info( collision = ba.getcollision()
'source_node', 'opposing_node') region = collision.source_node.getdelegate(RaceRegion)
try: playerspaz = collision.opposing_node.getdelegate(PlayerSpaz,
player = playernode.getdelegate().getplayer() doraise=False)
except Exception: player = playerspaz.getplayer(Player) if playerspaz else None
player = None
region = region_node.getdelegate()
if not player or not region: if not player or not region:
return return
assert isinstance(player, Player)
assert isinstance(region, RaceRegion)
last_region = player.last_region last_region = player.last_region
this_region = region.index this_region = region.index

View File

@ -29,11 +29,16 @@ import random
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import ba import ba
from bastd.actor import spazbot from bastd.actor.popuptext import PopupText
from bastd.actor.bomb import TNTSpawner from bastd.actor.bomb import TNTSpawner
from bastd.actor.scoreboard import Scoreboard from bastd.actor.scoreboard import Scoreboard
from bastd.actor.respawnicon import RespawnIcon from bastd.actor.respawnicon import RespawnIcon
from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory 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: if TYPE_CHECKING:
from typing import Type, Any, List, Dict, Tuple, Sequence, Optional 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.', '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..' 'Whip back and forth to get more distance on your throws..'
] ]
default_music = ba.MusicType.MARCHING
# How fast our various bot types walk. # How fast our various bot types walk.
_bot_speed_map: Dict[Type[spazbot.SpazBot], float] = { _bot_speed_map: Dict[Type[SpazBot], float] = {
spazbot.BomberBot: 0.48, BomberBot: 0.48,
spazbot.BomberBotPro: 0.48, BomberBotPro: 0.48,
spazbot.BomberBotProShielded: 0.48, BomberBotProShielded: 0.48,
spazbot.BrawlerBot: 0.57, BrawlerBot: 0.57,
spazbot.BrawlerBotPro: 0.57, BrawlerBotPro: 0.57,
spazbot.BrawlerBotProShielded: 0.57, BrawlerBotProShielded: 0.57,
spazbot.TriggerBot: 0.73, TriggerBot: 0.73,
spazbot.TriggerBotPro: 0.78, TriggerBotPro: 0.78,
spazbot.TriggerBotProShielded: 0.78, TriggerBotProShielded: 0.78,
spazbot.ChargerBot: 1.0, ChargerBot: 1.0,
spazbot.ChargerBotProShielded: 1.0, ChargerBotProShielded: 1.0,
spazbot.ExplodeyBot: 1.0, ExplodeyBot: 1.0,
spazbot.StickyBot: 0.5 StickyBot: 0.5
} }
def __init__(self, settings: Dict[str, Any]): 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._player_has_picked_up_powerup = False
self._scoreboard: Optional[Scoreboard] = None self._scoreboard: Optional[Scoreboard] = None
self._game_over = False self._game_over = False
self._wave = 0 self._wavenum = 0
self._can_end_wave = True self._can_end_wave = True
self._score = 0 self._score = 0
self._time_bonus = 0 self._time_bonus = 0
@ -118,7 +124,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._exclude_powerups: Optional[List[str]] = None self._exclude_powerups: Optional[List[str]] = None
self._have_tnt: Optional[bool] = None self._have_tnt: Optional[bool] = None
self._waves: Optional[List[Dict[str, Any]]] = None self._waves: Optional[List[Dict[str, Any]]] = None
self._bots = spazbot.BotSet() self._bots = SpazBotSet()
self._tntspawner: Optional[TNTSpawner] = None self._tntspawner: Optional[TNTSpawner] = None
self._lives_bg: Optional[ba.NodeActor] = None self._lives_bg: Optional[ba.NodeActor] = None
self._start_lives = 10 self._start_lives = 10
@ -133,7 +139,6 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._wave_update_timer: Optional[ba.Timer] = None self._wave_update_timer: Optional[ba.Timer] = None
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self.default_music = ba.MusicType.MARCHING
super().on_transition_in() super().on_transition_in()
self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'), self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'),
score_split=0.5) score_split=0.5)
@ -157,110 +162,110 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._have_tnt = True self._have_tnt = True
self._waves = [ self._waves = [
{'entries': [ {'entries': [
{'type': spazbot.BomberBot, 'path': 3 if hard else 2}, {'type': BomberBot, 'path': 3 if hard else 2},
{'type': spazbot.BomberBot, 'path': 2}, {'type': BomberBot, 'path': 2},
{'type': spazbot.BomberBot, 'path': 2} if hard else None, {'type': BomberBot, 'path': 2} if hard else None,
{'type': spazbot.BomberBot, 'path': 2} if player_count > 1 {'type': BomberBot, 'path': 2} if player_count > 1
else None, else None,
{'type': spazbot.BomberBot, 'path': 1} if hard else None, {'type': BomberBot, 'path': 1} if hard else None,
{'type': spazbot.BomberBot, 'path': 1} if player_count > 2 {'type': BomberBot, 'path': 1} if player_count > 2
else None, else None,
{'type': spazbot.BomberBot, 'path': 1} if player_count > 3 {'type': BomberBot, 'path': 1} if player_count > 3
else None, else None,
]}, ]},
{'entries': [ {'entries': [
{'type': spazbot.BomberBot, 'path': 1} if hard else None, {'type': BomberBot, 'path': 1} if hard else None,
{'type': spazbot.BomberBot, 'path': 2} if hard else None, {'type': BomberBot, 'path': 2} if hard else None,
{'type': spazbot.BomberBot, 'path': 2}, {'type': BomberBot, 'path': 2},
{'type': spazbot.BomberBot, 'path': 2}, {'type': BomberBot, 'path': 2},
{'type': spazbot.BomberBot, 'path': 2} if player_count > 3 {'type': BomberBot, 'path': 2} if player_count > 3
else None, else None,
{'type': spazbot.BrawlerBot, 'path': 3}, {'type': BrawlerBot, 'path': 3},
{'type': spazbot.BrawlerBot, 'path': 3}, {'type': BrawlerBot, 'path': 3},
{'type': spazbot.BrawlerBot, 'path': 3} if hard else None, {'type': BrawlerBot, 'path': 3} if hard else None,
{'type': spazbot.BrawlerBot, 'path': 3} if player_count > 1 {'type': BrawlerBot, 'path': 3} if player_count > 1
else None, else None,
{'type': spazbot.BrawlerBot, 'path': 3} if player_count > 2 {'type': BrawlerBot, 'path': 3} if player_count > 2
else None, else None,
]}, ]},
{'entries': [ {'entries': [
{'type': spazbot.ChargerBot, 'path': 2} if hard else None, {'type': ChargerBot, 'path': 2} if hard else None,
{'type': spazbot.ChargerBot, 'path': 2} if player_count > 2 {'type': ChargerBot, 'path': 2} if player_count > 2
else None, else None,
{'type': spazbot.TriggerBot, 'path': 2}, {'type': TriggerBot, 'path': 2},
{'type': spazbot.TriggerBot, 'path': 2} if player_count > 1 {'type': TriggerBot, 'path': 2} if player_count > 1
else None, else None,
{'type': 'spacing', 'duration': 3.0}, {'type': 'spacing', 'duration': 3.0},
{'type': spazbot.BomberBot, 'path': 2} if hard else None, {'type': BomberBot, 'path': 2} if hard else None,
{'type': spazbot.BomberBot, 'path': 2} if hard else None, {'type': BomberBot, 'path': 2} if hard else None,
{'type': spazbot.BomberBot, 'path': 2}, {'type': BomberBot, 'path': 2},
{'type': spazbot.BomberBot, 'path': 3} if hard else None, {'type': BomberBot, 'path': 3} if hard else None,
{'type': spazbot.BomberBot, 'path': 3}, {'type': BomberBot, 'path': 3},
{'type': spazbot.BomberBot, 'path': 3}, {'type': BomberBot, 'path': 3},
{'type': spazbot.BomberBot, 'path': 3} if player_count > 3 {'type': BomberBot, 'path': 3} if player_count > 3
else None, else None,
]}, ]},
{'entries': [ {'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': 'spacing', 'duration': 1.0} if hard else None,
{'type': spazbot.TriggerBot, 'path': 2}, {'type': TriggerBot, 'path': 2},
{'type': 'spacing', 'duration': 1.0}, {'type': 'spacing', 'duration': 1.0},
{'type': spazbot.TriggerBot, 'path': 3}, {'type': TriggerBot, 'path': 3},
{'type': 'spacing', 'duration': 1.0}, {'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': 'spacing', 'duration': 1.0} if hard else None,
{'type': spazbot.TriggerBot, 'path': 2}, {'type': TriggerBot, 'path': 2},
{'type': 'spacing', 'duration': 1.0}, {'type': 'spacing', 'duration': 1.0},
{'type': spazbot.TriggerBot, 'path': 3}, {'type': TriggerBot, 'path': 3},
{'type': 'spacing', 'duration': 1.0}, {'type': 'spacing', 'duration': 1.0},
{'type': spazbot.TriggerBot, 'path': 1} {'type': TriggerBot, 'path': 1}
if (player_count > 1 and hard) else None, if (player_count > 1 and hard) else None,
{'type': 'spacing', 'duration': 1.0}, {'type': 'spacing', 'duration': 1.0},
{'type': spazbot.TriggerBot, 'path': 2} if player_count > 2 {'type': TriggerBot, 'path': 2} if player_count > 2
else None, else None,
{'type': 'spacing', 'duration': 1.0}, {'type': 'spacing', 'duration': 1.0},
{'type': spazbot.TriggerBot, 'path': 3} if player_count > 3 {'type': TriggerBot, 'path': 3} if player_count > 3
else None, else None,
{'type': 'spacing', 'duration': 1.0}, {'type': 'spacing', 'duration': 1.0},
]}, ]},
{'entries': [ {'entries': [
{'type': spazbot.ChargerBotProShielded if hard {'type': ChargerBotProShielded if hard
else spazbot.ChargerBot, 'path': 1}, else ChargerBot, 'path': 1},
{'type': spazbot.BrawlerBot, 'path': 2} if hard else None, {'type': BrawlerBot, 'path': 2} if hard else None,
{'type': spazbot.BrawlerBot, 'path': 2}, {'type': BrawlerBot, 'path': 2},
{'type': spazbot.BrawlerBot, 'path': 2}, {'type': BrawlerBot, 'path': 2},
{'type': spazbot.BrawlerBot, 'path': 3} if hard else None, {'type': BrawlerBot, 'path': 3} if hard else None,
{'type': spazbot.BrawlerBot, 'path': 3}, {'type': BrawlerBot, 'path': 3},
{'type': spazbot.BrawlerBot, 'path': 3}, {'type': BrawlerBot, 'path': 3},
{'type': spazbot.BrawlerBot, 'path': 3} if player_count > 1 {'type': BrawlerBot, 'path': 3} if player_count > 1
else None, else None,
{'type': spazbot.BrawlerBot, 'path': 3} if player_count > 2 {'type': BrawlerBot, 'path': 3} if player_count > 2
else None, else None,
{'type': spazbot.BrawlerBot, 'path': 3} if player_count > 3 {'type': BrawlerBot, 'path': 3} if player_count > 3
else None, else None,
]}, ]},
{'entries': [ {'entries': [
{'type': spazbot.BomberBotProShielded, 'path': 3}, {'type': BomberBotProShielded, 'path': 3},
{'type': 'spacing', 'duration': 1.5}, {'type': 'spacing', 'duration': 1.5},
{'type': spazbot.BomberBotProShielded, 'path': 2}, {'type': BomberBotProShielded, 'path': 2},
{'type': 'spacing', 'duration': 1.5}, {'type': 'spacing', 'duration': 1.5},
{'type': spazbot.BomberBotProShielded, 'path': 1} if hard {'type': BomberBotProShielded, 'path': 1} if hard
else None, else None,
{'type': 'spacing', 'duration': 1.0} 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': 'spacing', 'duration': 1.5},
{'type': spazbot.BomberBotProShielded, 'path': 2}, {'type': BomberBotProShielded, 'path': 2},
{'type': 'spacing', 'duration': 1.5}, {'type': 'spacing', 'duration': 1.5},
{'type': spazbot.BomberBotProShielded, 'path': 1} if hard {'type': BomberBotProShielded, 'path': 1} if hard
else None, else None,
{'type': 'spacing', 'duration': 1.5} 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, if player_count > 1 else None,
{'type': 'spacing', 'duration': 1.5}, {'type': 'spacing', 'duration': 1.5},
{'type': spazbot.BomberBotProShielded, 'path': 2} {'type': BomberBotProShielded, 'path': 2}
if player_count > 2 else None, if player_count > 2 else None,
{'type': 'spacing', 'duration': 1.5}, {'type': 'spacing', 'duration': 1.5},
{'type': spazbot.BomberBotProShielded, 'path': 1} {'type': BomberBotProShielded, 'path': 1}
if player_count > 3 else None, if player_count > 3 else None,
]}, ]},
] # yapf: disable ] # yapf: disable
@ -269,84 +274,84 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._have_tnt = True self._have_tnt = True
self._waves = [ self._waves = [
{'entries': [ {'entries': [
{'type': spazbot.TriggerBot, 'path': 1} if hard else None, {'type': TriggerBot, 'path': 1} if hard else None,
{'type': spazbot.TriggerBot, 'path': 2}, {'type': TriggerBot, 'path': 2},
{'type': spazbot.TriggerBot, 'path': 2}, {'type': TriggerBot, 'path': 2},
{'type': spazbot.TriggerBot, 'path': 3}, {'type': TriggerBot, 'path': 3},
{'type': spazbot.BrawlerBotPro if hard {'type': BrawlerBotPro if hard
else spazbot.BrawlerBot, 'point': 'bottom_left'}, else BrawlerBot, 'point': 'bottom_left'},
{'type': spazbot.BrawlerBotPro, 'point': 'bottom_right'} {'type': BrawlerBotPro, 'point': 'bottom_right'}
if player_count > 2 else None, if player_count > 2 else None,
]}, ]},
{'entries': [ {'entries': [
{'type': spazbot.ChargerBot, 'path': 2}, {'type': ChargerBot, 'path': 2},
{'type': spazbot.ChargerBot, 'path': 3}, {'type': ChargerBot, 'path': 3},
{'type': spazbot.ChargerBot, 'path': 1} if hard else None, {'type': ChargerBot, 'path': 1} if hard else None,
{'type': spazbot.ChargerBot, 'path': 2}, {'type': ChargerBot, 'path': 2},
{'type': spazbot.ChargerBot, 'path': 3}, {'type': ChargerBot, 'path': 3},
{'type': spazbot.ChargerBot, 'path': 1} if player_count > 2 {'type': ChargerBot, 'path': 1} if player_count > 2
else None, else None,
]}, ]},
{'entries': [ {'entries': [
{'type': spazbot.BomberBotProShielded, 'path': 1} if hard {'type': BomberBotProShielded, 'path': 1} if hard
else None, else None,
{'type': spazbot.BomberBotProShielded, 'path': 2}, {'type': BomberBotProShielded, 'path': 2},
{'type': spazbot.BomberBotProShielded, 'path': 2}, {'type': BomberBotProShielded, 'path': 2},
{'type': spazbot.BomberBotProShielded, 'path': 3}, {'type': BomberBotProShielded, 'path': 3},
{'type': spazbot.BomberBotProShielded, 'path': 3}, {'type': BomberBotProShielded, 'path': 3},
{'type': spazbot.ChargerBot, 'point': 'bottom_right'}, {'type': ChargerBot, 'point': 'bottom_right'},
{'type': spazbot.ChargerBot, 'point': 'bottom_left'} {'type': ChargerBot, 'point': 'bottom_left'}
if player_count > 2 else None, if player_count > 2 else None,
]}, ]},
{'entries': [ {'entries': [
{'type': spazbot.TriggerBotPro, 'path': 1} {'type': TriggerBotPro, 'path': 1}
if hard else None, if hard else None,
{'type': spazbot.TriggerBotPro, 'path': 1 if hard else 2}, {'type': 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': spazbot.TriggerBotPro, 'path': 1 if hard else 2}, {'type': 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': spazbot.TriggerBotPro, 'path': 1 if hard else 2}, {'type': TriggerBotPro, 'path': 1 if hard else 2},
{'type': spazbot.TriggerBotPro, 'path': 1 if hard else 2} {'type': TriggerBotPro, 'path': 1 if hard else 2}
if player_count > 1 else None, 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, if player_count > 3 else None,
]}, ]},
{'entries': [ {'entries': [
{'type': spazbot.TriggerBotProShielded if hard {'type': TriggerBotProShielded if hard
else spazbot.TriggerBotPro, 'point': 'bottom_left'}, else TriggerBotPro, 'point': 'bottom_left'},
{'type': spazbot.TriggerBotProShielded, {'type': TriggerBotProShielded,
'point': 'bottom_right'} 'point': 'bottom_right'}
if hard else None, if hard else None,
{'type': spazbot.TriggerBotProShielded, {'type': TriggerBotProShielded,
'point': 'bottom_right'} 'point': 'bottom_right'}
if player_count > 2 else None, if player_count > 2 else None,
{'type': spazbot.BomberBot, 'path': 3}, {'type': BomberBot, 'path': 3},
{'type': spazbot.BomberBot, 'path': 3}, {'type': BomberBot, 'path': 3},
{'type': 'spacing', 'duration': 5.0}, {'type': 'spacing', 'duration': 5.0},
{'type': spazbot.BrawlerBot, 'path': 2}, {'type': BrawlerBot, 'path': 2},
{'type': spazbot.BrawlerBot, 'path': 2}, {'type': BrawlerBot, 'path': 2},
{'type': 'spacing', 'duration': 5.0}, {'type': 'spacing', 'duration': 5.0},
{'type': spazbot.TriggerBot, 'path': 1} if hard else None, {'type': TriggerBot, 'path': 1} if hard else None,
{'type': spazbot.TriggerBot, 'path': 1} if hard else None, {'type': TriggerBot, 'path': 1} if hard else None,
]}, ]},
{'entries': [ {'entries': [
{'type': spazbot.BomberBotProShielded, 'path': 2}, {'type': BomberBotProShielded, 'path': 2},
{'type': spazbot.BomberBotProShielded, 'path': 2} if hard {'type': BomberBotProShielded, 'path': 2} if hard
else None, else None,
{'type': spazbot.StickyBot, 'point': 'bottom_right'}, {'type': StickyBot, 'point': 'bottom_right'},
{'type': spazbot.BomberBotProShielded, 'path': 2}, {'type': BomberBotProShielded, 'path': 2},
{'type': spazbot.BomberBotProShielded, 'path': 2}, {'type': BomberBotProShielded, 'path': 2},
{'type': spazbot.StickyBot, 'point': 'bottom_right'} {'type': StickyBot, 'point': 'bottom_right'}
if player_count > 2 else None, if player_count > 2 else None,
{'type': spazbot.BomberBotProShielded, 'path': 2}, {'type': BomberBotProShielded, 'path': 2},
{'type': spazbot.ExplodeyBot, 'point': 'bottom_left'}, {'type': ExplodeyBot, 'point': 'bottom_left'},
{'type': spazbot.BomberBotProShielded, 'path': 2}, {'type': BomberBotProShielded, 'path': 2},
{'type': spazbot.BomberBotProShielded, 'path': 2} {'type': BomberBotProShielded, 'path': 2}
if player_count > 1 else None, if player_count > 1 else None,
{'type': 'spacing', 'duration': 5.0}, {'type': 'spacing', 'duration': 5.0},
{'type': spazbot.StickyBot, 'point': 'bottom_left'}, {'type': StickyBot, 'point': 'bottom_left'},
{'type': 'spacing', 'duration': 2.0}, {'type': 'spacing', 'duration': 2.0},
{'type': spazbot.ExplodeyBot, 'point': 'bottom_right'}, {'type': ExplodeyBot, 'point': 'bottom_right'},
]}, ]},
] # yapf: disable ] # yapf: disable
elif self._preset in ['endless', 'endless_tournament']: 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) ba.timer(2.0, self._start_updating_waves)
def _handle_reached_end(self) -> None: def _handle_reached_end(self) -> None:
oppnode = ba.get_collision_info('opposing_node') spaz = ba.getcollision().opposing_node.getdelegate(SpazBot, True)
spaz = oppnode.getdelegate()
if not spaz.is_alive(): if not spaz.is_alive():
return # Ignore bodies flying in. return # Ignore bodies flying in.
@ -495,7 +498,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
def _drop_powerups(self, def _drop_powerups(self,
standard_points: bool = False, standard_points: bool = False,
force_first: str = None) -> None: force_first: str = None) -> None:
""" Generic powerup drop """ """Generic powerup drop."""
# If its been a minute since our last wave finished emerging, stop # If its been a minute since our last wave finished emerging, stop
# giving out land-mine powerups. (prevents players from waiting # giving out land-mine powerups. (prevents players from waiting
@ -528,14 +531,6 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
extra_excludes)).autoretain() extra_excludes)).autoretain()
def end_game(self) -> None: 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.pushcall(ba.Call(self.do_end, 'defeat'))
ba.setmusic(None) ba.setmusic(None)
ba.playsound(self._player_death_sound) ba.playsound(self._player_death_sound)
@ -550,7 +545,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
delay = 0 delay = 0
score: Optional[int] score: Optional[int]
if self._wave >= 2: if self._wavenum >= 2:
score = self._score score = self._score
fail_message = None fail_message = None
else: else:
@ -583,7 +578,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
won = False won = False
else: else:
assert self._waves is not None assert self._waves is not None
won = (self._wave == len(self._waves)) won = (self._wavenum == len(self._waves))
# Reward time bonus. # Reward time bonus.
base_delay = 4.0 if won else 0 base_delay = 4.0 if won else 0
@ -594,7 +589,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
base_delay += 1.0 base_delay += 1.0
# Reward flawless bonus. # 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) ba.timer(base_delay, self._award_flawless_bonus)
base_delay += 1.0 base_delay += 1.0
@ -636,69 +631,60 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
ba.timer(base_delay, ba.Call(self.do_end, 'victory')) ba.timer(base_delay, ba.Call(self.do_end, 'victory'))
return return
self._wave += 1 self._wavenum += 1
# Short celebration after waves. # Short celebration after waves.
if self._wave > 1: if self._wavenum > 1:
self.celebrate(0.5) self.celebrate(0.5)
ba.timer(base_delay, self._start_next_wave) ba.timer(base_delay, self._start_next_wave)
def _award_completion_bonus(self) -> None: def _award_completion_bonus(self) -> None:
from bastd.actor import popuptext
bonus = 200 bonus = 200
ba.playsound(self._cashregistersound) ba.playsound(self._cashregistersound)
popuptext.PopupText(ba.Lstr( PopupText(ba.Lstr(value='+${A} ${B}',
value='+${A} ${B}', subs=[('${A}', str(bonus)),
subs=[('${A}', str(bonus)), ('${B}',
('${B}', ba.Lstr(resource='completionBonusText'))]), ba.Lstr(resource='completionBonusText'))]),
color=(0.7, 0.7, 1.0, 1), color=(0.7, 0.7, 1.0, 1),
scale=1.6, scale=1.6,
position=(0, 1.5, -1)).autoretain() position=(0, 1.5, -1)).autoretain()
self._score += bonus self._score += bonus
self._update_scores() self._update_scores()
def _award_lives_bonus(self) -> None: def _award_lives_bonus(self) -> None:
from bastd.actor import popuptext
bonus = self._lives * 30 bonus = self._lives * 30
ba.playsound(self._cashregistersound) ba.playsound(self._cashregistersound)
popuptext.PopupText(ba.Lstr(value='+${A} ${B}', PopupText(ba.Lstr(value='+${A} ${B}',
subs=[('${A}', str(bonus)), subs=[('${A}', str(bonus)),
('${B}', ('${B}', ba.Lstr(resource='livesBonusText'))]),
ba.Lstr(resource='livesBonusText')) color=(0.7, 1.0, 0.3, 1),
]), scale=1.3,
color=(0.7, 1.0, 0.3, 1), position=(0, 1, -1)).autoretain()
scale=1.3,
position=(0, 1, -1)).autoretain()
self._score += bonus self._score += bonus
self._update_scores() self._update_scores()
def _award_time_bonus(self, bonus: int) -> None: def _award_time_bonus(self, bonus: int) -> None:
from bastd.actor import popuptext
ba.playsound(self._cashregistersound) ba.playsound(self._cashregistersound)
popuptext.PopupText(ba.Lstr(value='+${A} ${B}', PopupText(ba.Lstr(value='+${A} ${B}',
subs=[('${A}', str(bonus)), subs=[('${A}', str(bonus)),
('${B}', ('${B}', ba.Lstr(resource='timeBonusText'))]),
ba.Lstr(resource='timeBonusText')) color=(1, 1, 0.5, 1),
]), scale=1.0,
color=(1, 1, 0.5, 1), position=(0, 3, -1)).autoretain()
scale=1.0,
position=(0, 3, -1)).autoretain()
self._score += self._time_bonus self._score += self._time_bonus
self._update_scores() self._update_scores()
def _award_flawless_bonus(self) -> None: def _award_flawless_bonus(self) -> None:
from bastd.actor import popuptext
ba.playsound(self._cashregistersound) ba.playsound(self._cashregistersound)
popuptext.PopupText(ba.Lstr(value='+${A} ${B}', PopupText(ba.Lstr(value='+${A} ${B}',
subs=[('${A}', str(self._flawless_bonus)), subs=[('${A}', str(self._flawless_bonus)),
('${B}', ('${B}', ba.Lstr(resource='perfectWaveText'))
ba.Lstr(resource='perfectWaveText')) ]),
]), color=(1, 1, 0.2, 1),
color=(1, 1, 0.2, 1), scale=1.2,
scale=1.2, position=(0, 2, -1)).autoretain()
position=(0, 2, -1)).autoretain()
assert self._flawless_bonus is not None assert self._flawless_bonus is not None
self._score += self._flawless_bonus self._score += self._flawless_bonus
@ -717,7 +703,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self.show_zoom_message(ba.Lstr(value='${A} ${B}', self.show_zoom_message(ba.Lstr(value='${A} ${B}',
subs=[('${A}', subs=[('${A}',
ba.Lstr(resource='waveText')), ba.Lstr(resource='waveText')),
('${B}', str(self._wave))]), ('${B}', str(self._wavenum))]),
scale=1.0, scale=1.0,
duration=1.0, duration=1.0,
trail=True) trail=True)
@ -728,52 +714,49 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
bot_types: List[Optional[Dict[str, Any]]] = [] bot_types: List[Optional[Dict[str, Any]]] = []
if self._preset in ['endless', 'endless_tournament']: if self._preset in ['endless', 'endless_tournament']:
level = self._wave level = self._wavenum
target_points = (level + 1) * 8.0 target_points = (level + 1) * 8.0
group_count = random.randint(1, 3) group_count = random.randint(1, 3)
entries = [] entries = []
spaz_types: List[Tuple[Type[spazbot.SpazBot], float]] = [] spaz_types: List[Tuple[Type[SpazBot], float]] = []
if level < 6: if level < 6:
spaz_types += [(spazbot.BomberBot, 5.0)] spaz_types += [(BomberBot, 5.0)]
if level < 10: if level < 10:
spaz_types += [(spazbot.BrawlerBot, 5.0)] spaz_types += [(BrawlerBot, 5.0)]
if level < 15: if level < 15:
spaz_types += [(spazbot.TriggerBot, 6.0)] spaz_types += [(TriggerBot, 6.0)]
if level > 5: if level > 5:
spaz_types += [(spazbot.TriggerBotPro, 7.5) spaz_types += [(TriggerBotPro, 7.5)] * (1 + (level - 5) // 7)
] * (1 + (level - 5) // 7)
if level > 2: if level > 2:
spaz_types += [(spazbot.BomberBotProShielded, 8.0) spaz_types += [(BomberBotProShielded, 8.0)
] * (1 + (level - 2) // 6) ] * (1 + (level - 2) // 6)
if level > 6: if level > 6:
spaz_types += [(spazbot.TriggerBotProShielded, 12.0) spaz_types += [(TriggerBotProShielded, 12.0)
] * (1 + (level - 6) // 5) ] * (1 + (level - 6) // 5)
if level > 1: if level > 1:
spaz_types += ([(spazbot.ChargerBot, 10.0)] * spaz_types += ([(ChargerBot, 10.0)] * (1 + (level - 1) // 4))
(1 + (level - 1) // 4))
if level > 7: if level > 7:
spaz_types += [(spazbot.ChargerBotProShielded, 15.0) spaz_types += [(ChargerBotProShielded, 15.0)
] * (1 + (level - 7) // 3) ] * (1 + (level - 7) // 3)
# Bot type, their effect on target points. # Bot type, their effect on target points.
defender_types: List[Tuple[Type[spazbot.SpazBot], float]] = [ defender_types: List[Tuple[Type[SpazBot], float]] = [
(spazbot.BomberBot, 0.9), (BomberBot, 0.9),
(spazbot.BrawlerBot, 0.9), (BrawlerBot, 0.9),
(spazbot.TriggerBot, 0.85), (TriggerBot, 0.85),
] ]
if level > 2: if level > 2:
defender_types += [(spazbot.ChargerBot, 0.75)] defender_types += [(ChargerBot, 0.75)]
if level > 4: if level > 4:
defender_types += ([(spazbot.StickyBot, 0.7)] * defender_types += ([(StickyBot, 0.7)] * (1 + (level - 5) // 6))
(1 + (level - 5) // 6))
if level > 6: if level > 6:
defender_types += ([(spazbot.ExplodeyBot, 0.7)] * defender_types += ([(ExplodeyBot, 0.7)] * (1 +
(1 + (level - 5) // 5)) (level - 5) // 5))
if level > 8: if level > 8:
defender_types += ([(spazbot.BrawlerBotProShielded, 0.65)] * defender_types += ([(BrawlerBotProShielded, 0.65)] *
(1 + (level - 5) // 4)) (1 + (level - 5) // 4))
if level > 10: if level > 10:
defender_types += ([(spazbot.TriggerBotProShielded, 0.6)] * defender_types += ([(TriggerBotProShielded, 0.6)] *
(1 + (level - 6) // 3)) (1 + (level - 6) // 3))
for group in range(group_count): for group in range(group_count):
@ -821,8 +804,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
elif path == 6: elif path == 6:
this_target_point_s *= 0.7 this_target_point_s *= 0.7
def _add_defender(defender_type: Tuple[Type[spazbot.SpazBot], def _add_defender(defender_type: Tuple[Type[SpazBot], float],
float],
pnt: str) -> Tuple[float, Dict[str, Any]]: pnt: str) -> Tuple[float, Dict[str, Any]]:
# FIXME: should look into this warning # FIXME: should look into this warning
# pylint: disable=cell-var-from-loop # pylint: disable=cell-var-from-loop
@ -884,7 +866,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
else: else:
assert self._waves is not None assert self._waves is not None
wave = self._waves[self._wave - 1] wave = self._waves[self._wavenum - 1]
bot_types += wave['entries'] bot_types += wave['entries']
self._time_bonus_mult = 1.0 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 # dropping land-mines powerups at some point (otherwise a crafty
# player could fill the whole map with them) # player could fill the whole map with them)
self._last_wave_end_time = ba.time() + t_sec 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( txtval = ba.Lstr(
value='${A} ${B}', value='${A} ${B}',
subs=[ subs=[('${A}', ba.Lstr(resource='waveText')),
('${A}', ba.Lstr(resource='waveText')), ('${B}', str(self._wavenum) +
('${B}', str(self._wave) + ('' if self._preset in ['endless', 'endless_tournament']
('' if self._preset in ['endless', 'endless_tournament'] else else f'/{totalwaves}'))])
('/' + str(len(self._waves)))))
])
self._wave_text = ba.NodeActor( self._wave_text = ba.NodeActor(
ba.newnode('text', ba.newnode('text',
attrs={ attrs={
@ -989,16 +969,16 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
'text': txtval 'text': txtval
})) }))
# noinspection PyTypeHints def _on_bot_spawn(self, path: int, spaz: SpazBot) -> None:
def _on_bot_spawn(self, path: int, spaz: spazbot.SpazBot) -> None:
# Add our custom update callback and set some info for this bot. # Add our custom update callback and set some info for this bot.
spaz_type = type(spaz) spaz_type = type(spaz)
assert spaz is not None assert spaz is not None
spaz.update_callback = self._update_bot spaz.update_callback = self._update_bot
# FIXME: Do this in a type-safe way. # Tack some custom attrs onto the spaz.
spaz.r_walk_row = path # type: ignore setattr(spaz, 'r_walk_row', path)
spaz.r_walk_speed = self._get_bot_speed(spaz_type) # type: ignore setattr(spaz, 'r_walk_speed', self._get_bot_speed(spaz_type))
def add_bot_at_point(self, def add_bot_at_point(self,
point: str, point: str,
@ -1047,7 +1027,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
assert self._scoreboard is not None assert self._scoreboard is not None
self._scoreboard.set_team_value(self.teams[0], score, max_score=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. # Yup; that's a lot of return statements right there.
# pylint: disable=too-many-return-statements # pylint: disable=too-many-return-statements
@ -1057,8 +1037,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
assert bot.node assert bot.node
# FIXME: Do this in a type safe way. # FIXME: Do this in a type safe way.
r_walk_speed: float = bot.r_walk_speed # type: ignore r_walk_speed: float = getattr(bot, 'r_walk_speed')
r_walk_row: int = bot.r_walk_row # type: ignore r_walk_row: int = getattr(bot, 'r_walk_row')
speed = r_walk_speed speed = r_walk_speed
pos = bot.node.position pos = bot.node.position
@ -1109,6 +1089,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
if ((ba.is_point_in_box(pos, boxes['b8']) if ((ba.is_point_in_box(pos, boxes['b8'])
and not ba.is_point_in_box(pos, boxes['b9'])) and not ba.is_point_in_box(pos, boxes['b9']))
or pos == (0.0, 0.0, 0.0)): or pos == (0.0, 0.0, 0.0)):
# Default to walking right if we're still in the walking area. # Default to walking right if we're still in the walking area.
bot.node.move_left_right = speed bot.node.move_left_right = speed
bot.node.move_up_down = 0 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)) respawn_time, ba.Call(self.spawn_player_if_exists, player))
player.gamedata['respawn_icon'] = RespawnIcon(player, respawn_time) 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: if msg.how is ba.DeathType.REACHED_GOAL:
return return
pts, importance = msg.badguy.get_death_points(msg.how) pts, importance = msg.badguy.get_death_points(msg.how)
@ -1161,7 +1142,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._dingsoundhigh, self._dingsoundhigh,
volume=0.6) volume=0.6)
except Exception as exc: 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 # Normally we pull scores from the score-set, but if there's no
# player lets be explicit. # player lets be explicit.
@ -1172,7 +1153,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
else: else:
super().handlemessage(msg) 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) speed = self._bot_speed_map.get(bot_type)
if speed is None: if speed is None:
raise TypeError('Invalid bot type to _get_bot_speed(): ' + raise TypeError('Invalid bot type to _get_bot_speed(): ' +

View File

@ -26,14 +26,20 @@ import random
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import ba import ba
from bastd.actor import spazbot
from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.bomb import TNTSpawner from bastd.actor.bomb import TNTSpawner
from bastd.actor.scoreboard import Scoreboard from bastd.actor.scoreboard import Scoreboard
from bastd.actor.powerupbox import PowerupBoxFactory, PowerupBox 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: if TYPE_CHECKING:
from typing import Any, Dict, Type, List, Optional, Sequence from typing import Any, Dict, Type, List, Optional, Sequence
from bastd.actor.spazbot import SpazBot
class Player(ba.Player['Team']): class Player(ba.Player['Team']):
@ -76,7 +82,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
self._excludepowerups: List[str] = [] self._excludepowerups: List[str] = []
self._scoreboard: Optional[Scoreboard] = None self._scoreboard: Optional[Scoreboard] = None
self._score = 0 self._score = 0
self._bots = spazbot.BotSet() self._bots = SpazBotSet()
self._dingsound = ba.getsound('dingSmall') self._dingsound = ba.getsound('dingSmall')
self._dingsoundhigh = ba.getsound('dingSmallHigh') self._dingsoundhigh = ba.getsound('dingSmallHigh')
self._tntspawner: Optional[TNTSpawner] = None self._tntspawner: Optional[TNTSpawner] = None
@ -86,18 +92,18 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
# For each bot type: [spawn-rate, increase, d_increase] # For each bot type: [spawn-rate, increase, d_increase]
self._bot_spawn_types = { self._bot_spawn_types = {
spazbot.BomberBot: [1.00, 0.00, 0.000], BomberBot: [1.00, 0.00, 0.000],
spazbot.BomberBotPro: [0.00, 0.05, 0.001], BomberBotPro: [0.00, 0.05, 0.001],
spazbot.BomberBotProShielded: [0.00, 0.02, 0.002], BomberBotProShielded: [0.00, 0.02, 0.002],
spazbot.BrawlerBot: [1.00, 0.00, 0.000], BrawlerBot: [1.00, 0.00, 0.000],
spazbot.BrawlerBotPro: [0.00, 0.05, 0.001], BrawlerBotPro: [0.00, 0.05, 0.001],
spazbot.BrawlerBotProShielded: [0.00, 0.02, 0.002], BrawlerBotProShielded: [0.00, 0.02, 0.002],
spazbot.TriggerBot: [0.30, 0.00, 0.000], TriggerBot: [0.30, 0.00, 0.000],
spazbot.TriggerBotPro: [0.00, 0.05, 0.001], TriggerBotPro: [0.00, 0.05, 0.001],
spazbot.TriggerBotProShielded: [0.00, 0.02, 0.002], TriggerBotProShielded: [0.00, 0.02, 0.002],
spazbot.ChargerBot: [0.30, 0.05, 0.000], ChargerBot: [0.30, 0.05, 0.000],
spazbot.StickyBot: [0.10, 0.03, 0.001], StickyBot: [0.10, 0.03, 0.001],
spazbot.ExplodeyBot: [0.05, 0.02, 0.002] ExplodeyBot: [0.05, 0.02, 0.002]
} # yapf: disable } # yapf: disable
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
@ -206,9 +212,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
for i in range(3): for i in range(3):
for playerpt in playerpts: for playerpt in playerpts:
dists[i] += abs(playerpt[0] - botspawnpts[i][0]) dists[i] += abs(playerpt[0] - botspawnpts[i][0])
dists[i] += random.random() * 5.0 # Minor random variation.
# Little random variation.
dists[i] += random.random() * 5.0
if dists[0] > dists[1] and dists[0] > dists[2]: if dists[0] > dists[1] and dists[0] > dists[2]:
spawnpt = botspawnpts[0] spawnpt = botspawnpts[0]
elif dists[1] > dists[2]: 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. # Now go back through and see where this value falls.
total = 0 total = 0
bottype: Optional[Type[spazbot.SpazBot]] = None bottype: Optional[Type[SpazBot]] = None
for spawntype in self._bot_spawn_types.items(): for spawntype in self._bot_spawn_types.items():
total += spawntype[1][0] total += spawntype[1][0]
if randval <= total: if randval <= total:
@ -244,8 +248,9 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
spawntype[1][1] += spawntype[1][2] # incr spawn rate incr rate spawntype[1][1] += spawntype[1][2] # incr spawn rate incr rate
def _update_scores(self) -> None: def _update_scores(self) -> None:
# Do achievements in default preset only.
score = self._score score = self._score
# Achievements apply to the default preset only.
if self._preset == 'default': if self._preset == 'default':
if score >= 250: if score >= 250:
self._award_achievement('Last Stand Master') self._award_achievement('Last Stand Master')
@ -266,28 +271,21 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
self._score += msg.score self._score += msg.score
self._update_scores() self._update_scores()
elif isinstance(msg, spazbot.SpazBotDeathMessage): elif isinstance(msg, SpazBotDiedMessage):
pts, importance = msg.badguy.get_death_points(msg.how) pts, importance = msg.badguy.get_death_points(msg.how)
target: Optional[Sequence[float]] target: Optional[Sequence[float]]
if msg.killerplayer: if msg.killerplayer:
try: assert msg.badguy.node
assert msg.badguy.node target = msg.badguy.node.position
target = msg.badguy.node.position self.stats.player_scored(msg.killerplayer,
except Exception: pts,
ba.print_exception() target=target,
target = None kill=True,
try: screenmessage=False,
self.stats.player_scored(msg.killerplayer, importance=importance)
pts, ba.playsound(self._dingsound
target=target, if importance == 1 else self._dingsoundhigh,
kill=True, volume=0.6)
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)
# Normally we pull scores from the score-set, but if there's no # Normally we pull scores from the score-set, but if there's no
# player lets be explicit. # player lets be explicit.

View File

@ -906,8 +906,8 @@ def _preload4() -> None:
ba.getmodel(mname) ba.getmodel(mname)
for sname in ['metalHit', 'metalSkid', 'refWhistle', 'achievement']: for sname in ['metalHit', 'metalSkid', 'refWhistle', 'achievement']:
ba.getsound(sname) ba.getsound(sname)
from bastd.actor.flag import get_factory from bastd.actor.flag import FlagFactory
get_factory() FlagFactory.get()
class MainMenuSession(ba.Session): class MainMenuSession(ba.Session):

View File

@ -464,7 +464,6 @@ def handle_party_invite(name: str, invite_id: str) -> None:
# FIXME: Ugly. # FIXME: Ugly.
# Let's store the invite-id away on the confirm window so we know if # Let's store the invite-id away on the confirm window so we know if
# we need to kill it later. # we need to kill it later.
# noinspection PyTypeHints
conf.party_invite_id = invite_id # type: ignore conf.party_invite_id = invite_id # type: ignore
# store a weak-ref so we can get at this later # store a weak-ref so we can get at this later

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND --> <!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-05-24 for Ballistica version 1.5.0 build 20026</em></h4> <h4><em>last updated on 2020-05-25 for Ballistica version 1.5.0 build 20027</em></h4>
<p>This page documents the Python classes and functions in the 'ba' module, <p>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 <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p> 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 <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
<hr> <hr>
@ -51,7 +51,6 @@
<li><a href="#function_ba_camerashake">ba.camerashake()</a></li> <li><a href="#function_ba_camerashake">ba.camerashake()</a></li>
<li><a href="#function_ba_emitfx">ba.emitfx()</a></li> <li><a href="#function_ba_emitfx">ba.emitfx()</a></li>
<li><a href="#function_ba_existing">ba.existing()</a></li> <li><a href="#function_ba_existing">ba.existing()</a></li>
<li><a href="#function_ba_get_collision_info">ba.get_collision_info()</a></li>
<li><a href="#function_ba_getactivity">ba.getactivity()</a></li> <li><a href="#function_ba_getactivity">ba.getactivity()</a></li>
<li><a href="#function_ba_getnodes">ba.getnodes()</a></li> <li><a href="#function_ba_getnodes">ba.getnodes()</a></li>
<li><a href="#function_ba_getsession">ba.getsession()</a></li> <li><a href="#function_ba_getsession">ba.getsession()</a></li>
@ -200,6 +199,14 @@
<li><a href="#class_ba_WidgetNotFoundError">ba.WidgetNotFoundError</a></li> <li><a href="#class_ba_WidgetNotFoundError">ba.WidgetNotFoundError</a></li>
</ul> </ul>
</ul> </ul>
<h4><a name="class_category_Misc_Classes">Misc Classes</a></h4>
<ul>
<li><a href="#class_ba_Collision">ba.Collision</a></li>
</ul>
<h4><a name="function_category_Misc_Functions">Misc Functions</a></h4>
<ul>
<li><a href="#function_ba_getcollision">ba.getcollision()</a></li>
</ul>
<h4><a name="class_category_Protocols">Protocols</a></h4> <h4><a name="class_category_Protocols">Protocols</a></h4>
<ul> <ul>
<li><a href="#class_ba_Existable">ba.Existable</a></li> <li><a href="#class_ba_Existable">ba.Existable</a></li>
@ -1368,6 +1375,44 @@ mycall()</pre>
<p>Use <a href="#function_ba_getcollidemodel">ba.getcollidemodel</a>() to instantiate one.</p> <p>Use <a href="#function_ba_getcollidemodel">ba.getcollidemodel</a>() to instantiate one.</p>
<hr>
<h2><strong><a name="class_ba_Collision">ba.Collision</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
<p>A class providing info about occurring collisions.</p>
<h3>Attributes:</h3>
<h5><a href="#attr_ba_Collision__opposing_body">opposing_body</a>, <a href="#attr_ba_Collision__opposing_node">opposing_node</a>, <a href="#attr_ba_Collision__position">position</a>, <a href="#attr_ba_Collision__source_node">source_node</a></h5>
<dl>
<dt><h4><a name="attr_ba_Collision__opposing_body">opposing_body</a></h4></dt><dd>
<p><span>int</span></p>
<p>The body index on the opposing node in the current collision.</p>
</dd>
<dt><h4><a name="attr_ba_Collision__opposing_node">opposing_node</a></h4></dt><dd>
<p><span><a href="#class_ba_Node">ba.Node</a></span></p>
<p>The node the current callback material node is hitting.</p>
<p> Throws a <a href="#class_ba_NodeNotFoundError">ba.NodeNotFoundError</a> 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.</p>
</dd>
<dt><h4><a name="attr_ba_Collision__position">position</a></h4></dt><dd>
<p><span><a href="#class_ba_Vec3">ba.Vec3</a></span></p>
<p>The position of the current collision.</p>
</dd>
<dt><h4><a name="attr_ba_Collision__source_node">source_node</a></h4></dt><dd>
<p><span><a href="#class_ba_Node">ba.Node</a></span></p>
<p>The node containing the material triggering the current callback.</p>
<p> Throws a <a href="#class_ba_NodeNotFoundError">ba.NodeNotFoundError</a> if the node does not exist, though
the node should always exist (at least at the start of the collision
callback).</p>
</dd>
</dl>
<hr> <hr>
<h2><strong><a name="class_ba_Context">ba.Context</a></strong></h3> <h2><strong><a name="class_ba_Context">ba.Context</a></strong></h3>
<p><em>&lt;top level class&gt;</em> <p><em>&lt;top level class&gt;</em>
@ -3547,10 +3592,13 @@ the right thing both for Node objects and values of None.</p>
</dd> </dd>
<dt><h4><a name="method_ba_Node__getdelegate">getdelegate()</a></dt></h4><dd> <dt><h4><a name="method_ba_Node__getdelegate">getdelegate()</a></dt></h4><dd>
<p><span>getdelegate() -&gt; Any</span></p> <p><span>getdelegate(type: Type, doraise: bool = False) -&gt; &lt;varies&gt;</span></p>
<p>Returns the node's current delegate, which is the Python object <p>Return the node's current delegate object if it matches a certain type.</p>
designated to handle the Node's messages.</p>
<p>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.</p>
</dd> </dd>
<dt><h4><a name="method_ba_Node__getnodetype">getnodetype()</a></dt></h4><dd> <dt><h4><a name="method_ba_Node__getnodetype">getnodetype()</a></dt></h4><dd>
@ -5732,18 +5780,6 @@ method) and will convert it to a None value if it does not exist.
For more info, see notes on 'existables' here: For more info, see notes on 'existables' here:
https://ballistica.net/wiki/Coding-Style-Guide</p> https://ballistica.net/wiki/Coding-Style-Guide</p>
<hr>
<h2><strong><a name="function_ba_get_collision_info">ba.get_collision_info()</a></strong></h3>
<p><span>get_collision_info(*args: Any) -&gt; Any</span></p>
<p>Return collision related values</p>
<p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p>
<p>Returns a single collision value or tuple of values such as location,
depth, nodes involved, etc. Only call this in the handler of a
collision-triggered callback or message</p>
<hr> <hr>
<h2><strong><a name="function_ba_get_valid_languages">ba.get_valid_languages()</a></strong></h3> <h2><strong><a name="function_ba_get_valid_languages">ba.get_valid_languages()</a></strong></h3>
<p><span>get_valid_languages() -&gt; List[str]</span></p> <p><span>get_valid_languages() -&gt; List[str]</span></p>
@ -5785,6 +5821,12 @@ to be loaded. To avoid hitches, instantiate your media objects in
advance of when you will be using them, allowing time for them to load advance of when you will be using them, allowing time for them to load
in the background if necessary.</p> in the background if necessary.</p>
<hr>
<h2><strong><a name="function_ba_getcollision">ba.getcollision()</a></strong></h3>
<p><span>getcollision() -&gt; Collision</span></p>
<p>Return the in-progress collision.</p>
<hr> <hr>
<h2><strong><a name="function_ba_getmaps">ba.getmaps()</a></strong></h3> <h2><strong><a name="function_ba_getmaps">ba.getmaps()</a></strong></h3>
<p><span>getmaps(playtype: str) -&gt; List[str]</span></p> <p><span>getmaps(playtype: str) -&gt; List[str]</span></p>

View File

@ -149,7 +149,6 @@ def test_validate() -> None:
dataclass_validate(tclass) dataclass_validate(tclass)
# No longer valid. # No longer valid.
# noinspection PyTypeHints
tclass.ival = None # type: ignore tclass.ival = None # type: ignore
with pytest.raises(TypeError): with pytest.raises(TypeError):
dataclass_validate(tclass) dataclass_validate(tclass)

View File

@ -80,7 +80,6 @@ class EntityTest(entity.Entity):
fval2 = entity.Field('f2', entity.Float3Value()) fval2 = entity.Field('f2', entity.Float3Value())
# noinspection PyTypeHints
def test_entity_values() -> None: def test_entity_values() -> None:
"""Test various entity assigns for value and type correctness.""" """Test various entity assigns for value and type correctness."""
ent = EntityTest() ent = EntityTest()
@ -134,7 +133,6 @@ def test_entity_values() -> None:
assert dict(ent.str_int_dict.items()) == {'foo': 123} assert dict(ent.str_int_dict.items()) == {'foo': 123}
# noinspection PyTypeHints
def test_entity_values_2() -> None: def test_entity_values_2() -> None:
"""Test various entity assigns for value and type correctness.""" """Test various entity assigns for value and type correctness."""
@ -191,7 +189,6 @@ def test_entity_values_2() -> None:
assert static_type_equals(ent.grp.compoundlist[0].subval, bool) assert static_type_equals(ent.grp.compoundlist[0].subval, bool)
# noinspection PyTypeHints
def test_field_copies() -> None: def test_field_copies() -> None:
"""Test copying various values between fields.""" """Test copying various values between fields."""
ent1 = EntityTest() ent1 = EntityTest()

View File

@ -121,7 +121,6 @@ class EntityMixin:
self.d_data = target.d_data self.d_data = target.d_data
# Make sure target blows up if someone tries to use it. # Make sure target blows up if someone tries to use it.
# noinspection PyTypeHints
target.d_data = None # type: ignore target.d_data = None # type: ignore
def pruned_data(self) -> Dict[str, Any]: def pruned_data(self) -> Dict[str, Any]:

View File

@ -99,7 +99,7 @@ class DispatchMethodWrapper(Generic[TARG, TRET]):
registry: Dict[Any, Callable] registry: Dict[Any, Callable]
# noinspection PyTypeHints, PyProtectedMember # noinspection PyProtectedMember
def dispatchmethod( def dispatchmethod(
func: Callable[[Any, TARG], func: Callable[[Any, TARG],
TRET]) -> DispatchMethodWrapper[TARG, TRET]: TRET]) -> DispatchMethodWrapper[TARG, TRET]: