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/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/44/2a/8535b446284235cb503947ece074",
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/f5/d3/8e941851c4310465646c4167afc1",
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/e2/2c/e609bb55002f672528428db9306c",
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/da/06/8e06488b46a0a213abde3cfeb364",
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/b8/ed/e18bec56ff1d094aae86517a7854",
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/49/5f/b29bb65369040892fe6601801637",
"assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/0c/cd/798753aa6c55f3a4cdccda0b23ab",
@ -442,7 +442,7 @@
"assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/0a/84/bbb6ed2abf66509406f534cbbb52",
"assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/c2/fc/dd1c15cf9ecb411d9defbd000c06",
"assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/db/eb/324f86a4b714240ae50ffeeed2f8",
"assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/6c/35/cc4d440d0c7a613860c3898e81d7",
"assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/a9/bc/ea61ebd23066c685fb779e23d10f",
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/f6/d0/335b952306d211d56172b5c72d8c",
"assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/78/5e/d24967ddaa15e0a574bd274544db",
"assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/e7/d8/ace32888249fc8b8cca0e2edb48b",
@ -4132,16 +4132,16 @@
"assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c",
"assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb",
"assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe",
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5a/42/5238d5de1cf94a07a513ea2b3e1b",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/39/48/2f0e4350a080373301de625e2cc4",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c0/1d/dbc0a5e2ca05b626cbeffd6def6c",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ec/a8/5c58d6b5e1d844640334c35ad3af",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3d/0a/76b615f9bcb9bf4f0ca745060d40",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/01/6b/0f623ac481e3553e352c1cd3cb7e",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c4/a5/3cad1ced77551369dc56c6bca5fb",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3b/3f/55094817619cae66f941a9f6db8c",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/ec/6b/7aa29831a746672f9984cbb1dbf1",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/07/4c/00a6136d0bd5573946d10e294720",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/8d/8d/af068e67244cbb28444b27ae66d7",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f4/62/eb7cb26c952df481b757dcf53f9c"
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e4/f0/c3261a8a7391a2b654da82c35c21",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/33/e2/85a8cbf23404612e6761cf02348b",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/5b/0d/d906be5fc2a75b5214a94cafe6ca",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/6b/53/72d665caf5dfed84312ae6862642",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d9/68/2d98f2d1ee67b6d54adbdf76c863",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e5/3a/33f8f311260542126acea44d05e2",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/18/eb/dd39bcfa33983864089d3fb7f666",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f8/1d/3440010fcbcf18c5a6b2966f7fca",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/9b/fa/b4ad4b8b82fefe1faad610065b9f",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7f/b9/03af92db6140135ddfa467a1545d",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/69/b9/687fc5fa38c3faf7a47eb4241a3c",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/cf/2b/589db924b489972981e10850a4e3"
}

View File

@ -416,6 +416,7 @@
<w>deathmatch</w>
<w>deek</w>
<w>defs</w>
<w>defsline</w>
<w>deivit</w>
<w>depcls</w>
<w>depdata</w>
@ -749,6 +750,7 @@
<w>getclass</w>
<w>getcollide</w>
<w>getcollidemodel</w>
<w>getcollision</w>
<w>getconf</w>
<w>getconfig</w>
<w>getcurrency</w>
@ -1285,6 +1287,7 @@
<w>outname</w>
<w>outpath</w>
<w>ouya</w>
<w>overloadsigs</w>
<w>packagedir</w>
<w>packagedirs</w>
<w>packagename</w>
@ -1888,6 +1891,7 @@
<w>toplevel</w>
<w>totaldudes</w>
<w>totalpts</w>
<w>totalwaves</w>
<w>totype</w>
<w>touchpad</w>
<w>tournamententry</w>
@ -2022,6 +2026,7 @@
<w>waaah</w>
<w>wanttype</w>
<w>wasdead</w>
<w>wavenum</w>
<w>weakref</w>
<w>weakrefs</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__/_benchmark.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_campaign.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_collision.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_coopgame.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_coopsession.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_dependency.cpython-37.opt-1.pyc",
@ -67,6 +68,7 @@
"ba_data/python/ba/_assetmanager.py",
"ba_data/python/ba/_benchmark.py",
"ba_data/python/ba/_campaign.py",
"ba_data/python/ba/_collision.py",
"ba_data/python/ba/_coopgame.py",
"ba_data/python/ba/_coopsession.py",
"ba_data/python/ba/_dependency.py",

View File

@ -153,6 +153,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
build/ba_data/python/ba/_coopgame.py \
build/ba_data/python/ba/_meta.py \
build/ba_data/python/ba/_math.py \
build/ba_data/python/ba/_collision.py \
build/ba_data/python/ba/_servermode.py \
build/ba_data/python/ba/_appconfig.py \
build/ba_data/python/ba/_gameresults.py \
@ -380,6 +381,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
build/ba_data/python/ba/__pycache__/_coopgame.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_meta.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_math.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_collision.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_servermode.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_appconfig.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_gameresults.cpython-37.opt-1.pyc \
@ -663,6 +665,11 @@ build/ba_data/python/ba/__pycache__/_math.cpython-37.opt-1.pyc: \
@echo Compiling script: $^
@rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@
build/ba_data/python/ba/__pycache__/_collision.cpython-37.opt-1.pyc: \
build/ba_data/python/ba/_collision.py
@echo Compiling script: $^
@rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@
build/ba_data/python/ba/__pycache__/_servermode.cpython-37.opt-1.pyc: \
build/ba_data/python/ba/_servermode.py
@echo Compiling script: $^

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)
# SOURCES_HASH=146544975806995156523304218095856323339
# SOURCES_HASH=164420280597992494471294420110866243586
# I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression
@ -51,16 +51,19 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand.
from __future__ import annotations
from typing import TYPE_CHECKING, overload, Sequence
from typing import TYPE_CHECKING, overload, Sequence, TypeVar
from ba._enums import TimeFormat, TimeType
if TYPE_CHECKING:
from typing import (Any, Dict, Callable, Tuple, List, Optional, Union,
List, Type)
from typing_extensions import Literal
from ba._app import App
import ba
_T = TypeVar('_T')
app: App
@ -713,13 +716,26 @@ class Node:
"""
return str()
def getdelegate(self) -> Any:
"""getdelegate() -> Any
@overload
def getdelegate(self,
type: Type[_T],
doraise: Literal[False] = False) -> Optional[_T]:
...
Returns the node's current delegate, which is the Python object
designated to handle the Node's messages.
@overload
def getdelegate(self, type: Type[_T], doraise: Literal[True]) -> _T:
...
def getdelegate(self, type: Any, doraise: bool = False) -> Any:
"""getdelegate(type: Type, doraise: bool = False) -> <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:
"""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,
Material, Model, Node, SessionPlayer, Sound, Texture, Timer,
Vec3, Widget, buttonwidget, camerashake, checkboxwidget,
columnwidget, containerwidget, do_once, emitfx,
get_collision_info, getactivity, getcollidemodel, getmodel,
getnodes, getsession, getsound, gettexture, hscrollwidget,
imagewidget, log, new_activity, newnode, playsound,
printnodes, printobjects, pushcall, quit, rowwidget,
safecolor, screenmessage, scrollwidget, set_analytics_screen,
charstr, textwidget, time, timer, open_url, widget)
columnwidget, containerwidget, do_once, emitfx, getactivity,
getcollidemodel, getmodel, getnodes, getsession, getsound,
gettexture, hscrollwidget, imagewidget, log, new_activity,
newnode, playsound, printnodes, printobjects, pushcall, quit,
rowwidget, safecolor, screenmessage, scrollwidget,
set_analytics_screen, charstr, textwidget, time, timer,
open_url, widget)
from ba._activity import Activity
from ba._actor import Actor
from ba._player import Player, playercast, playercast_o
@ -87,6 +87,7 @@ from ba._music import setmusic, MusicPlayer, MusicType, MusicPlayMode
from ba._powerup import PowerupMessage, PowerupAcceptMessage
from ba._multiteamsession import MultiTeamSession
from ba.ui import Window, UIController, uicleanupcheck
from ba._collision import Collision, getcollision
app: App

View File

@ -343,7 +343,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
raise TypeError('non-actor passed to retain_actor')
if (self.has_transitioned_in()
and _ba.time() - self._last_prune_dead_actors_time > 10.0):
print_error('it looks like nodes/actors are not'
print_error('It looks like nodes/actors are not'
' being pruned in your activity;'
' did you call Activity.on_transition_in()'
' from your subclass?; ' + str(self) + ' (loc. a)')
@ -775,12 +775,12 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
# Send expire notices to all remaining actors.
for actor_ref in self._actor_weak_refs:
try:
actor = actor_ref()
if actor is not None:
actor = actor_ref()
if actor is not None:
try:
actor.on_expire()
except Exception:
print_exception(f'Error expiring Actor {actor_ref()}')
except Exception:
print_exception(f'Error expiring Actor {actor_ref()}')
# Reset all Players.
# (releases any attached actors, clears game-data, etc)
@ -804,7 +804,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
sessionteam.reset_gamedata()
except SessionTeamNotFoundError:
pass
# print_exception(f'Error resetting Team {team}')
except Exception:
print_exception(f'Error resetting Team {team}')
@ -819,6 +818,12 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
'Error during ba.Activity._expire() destroying data:')
def _prune_dead_actors(self) -> None:
self._actor_refs = [a for a in self._actor_refs if a]
self._actor_weak_refs = [a for a in self._actor_weak_refs if a()]
self._last_prune_dead_actors_time = _ba.time()
# Prune our strong refs when the Actor's exists() call gives False
self._actor_refs = [a for a in self._actor_refs if a.exists()]
# Prune our weak refs once the Actor object has been freed.
self._actor_weak_refs = [
a for a in self._actor_weak_refs if a() is not None
]

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.
# FIXME: Need a type safe way to do this.
activity = _ba.getactivity()
# noinspection PyTypeHints
activity.camera_flash_data = [] # type: ignore
for i in range(6):
light = NodeActor(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<!-- 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,
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>
@ -51,7 +51,6 @@
<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_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_getnodes">ba.getnodes()</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>
</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>
<ul>
<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>
<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>
<h2><strong><a name="class_ba_Context">ba.Context</a></strong></h3>
<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>
<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
designated to handle the Node's messages.</p>
<p>Return the node's current delegate object if it matches a certain type.</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>
<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:
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>
<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>
@ -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
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>
<h2><strong><a name="function_ba_getmaps">ba.getmaps()</a></strong></h3>
<p><span>getmaps(playtype: str) -&gt; List[str]</span></p>

View File

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

View File

@ -80,7 +80,6 @@ class EntityTest(entity.Entity):
fval2 = entity.Field('f2', entity.Float3Value())
# noinspection PyTypeHints
def test_entity_values() -> None:
"""Test various entity assigns for value and type correctness."""
ent = EntityTest()
@ -134,7 +133,6 @@ def test_entity_values() -> None:
assert dict(ent.str_int_dict.items()) == {'foo': 123}
# noinspection PyTypeHints
def test_entity_values_2() -> None:
"""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)
# noinspection PyTypeHints
def test_field_copies() -> None:
"""Test copying various values between fields."""
ent1 = EntityTest()

View File

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

View File

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