From ff043c0262491c3569d3753e6f824429e5266746 Mon Sep 17 00:00:00 2001
From: Eric Froemling
Date: Tue, 7 Apr 2020 17:16:25 -0700
Subject: [PATCH] Cleaned up Actor class to behave better in type-checking
world
---
assets/src/ba_data/python/ba/__init__.py | 3 +-
assets/src/ba_data/python/ba/_actor.py | 56 ++++++-----
assets/src/ba_data/python/ba/_apputils.py | 16 ++--
assets/src/ba_data/python/ba/_coopgame.py | 6 +-
assets/src/ba_data/python/ba/_dependency.py | 1 -
assets/src/ba_data/python/ba/_gameactivity.py | 21 +++--
assets/src/ba_data/python/ba/_map.py | 14 ++-
assets/src/ba_data/python/ba/_messages.py | 15 +++
assets/src/ba_data/python/ba/_nodeactor.py | 3 +
assets/src/ba_data/python/ba/_team.py | 15 ---
.../src/ba_data/python/ba/_teambasesession.py | 9 +-
.../ba_data/python/bastd/actor/playerspaz.py | 17 ++--
assets/src/ba_data/python/bastd/actor/spaz.py | 19 ++--
.../src/ba_data/python/bastd/actor/spazbot.py | 32 ++++---
.../src/ba_data/python/bastd/game/assault.py | 8 +-
.../python/bastd/game/capturetheflag.py | 6 +-
.../ba_data/python/bastd/game/chosenone.py | 4 +-
.../python/bastd/game/easteregghunt.py | 14 ++-
.../ba_data/python/bastd/game/elimination.py | 16 ++--
.../src/ba_data/python/bastd/game/football.py | 39 +++-----
.../src/ba_data/python/bastd/game/hockey.py | 8 +-
.../src/ba_data/python/bastd/game/keepaway.py | 2 +-
.../ba_data/python/bastd/game/ninjafight.py | 4 +-
.../ba_data/python/bastd/game/onslaught.py | 8 +-
assets/src/ba_data/python/bastd/game/race.py | 2 +
.../ba_data/python/bastd/game/runaround.py | 14 +--
.../ba_data/python/bastd/game/thelaststand.py | 1 +
assets/src/ba_data/python/bastd/mainmenu.py | 6 +-
assets/src/ba_data/python/bastd/maps.py | 4 +-
config/toolconfigsrc/projectile | 2 -
config/toolconfigsrc/pylintrc | 6 ++
docs/ba_module.md | 94 ++++++++++++++-----
32 files changed, 263 insertions(+), 202 deletions(-)
diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py
index 4478b4d2..1f5b4455 100644
--- a/assets/src/ba_data/python/ba/__init__.py
+++ b/assets/src/ba_data/python/ba/__init__.py
@@ -78,7 +78,8 @@ from ba._messages import (OutOfBoundsMessage, DeathType, DieMessage,
StandMessage, PickUpMessage, DropMessage,
PickedUpMessage, DroppedMessage,
ShouldShatterMessage, ImpactDamageMessage,
- FreezeMessage, ThawMessage, HitMessage)
+ FreezeMessage, ThawMessage, HitMessage,
+ CelebrateMessage)
from ba._music import setmusic, MusicPlayer, MusicType, MusicPlayMode
from ba._powerup import PowerupMessage, PowerupAcceptMessage
from ba._teambasesession import TeamBaseSession
diff --git a/assets/src/ba_data/python/ba/_actor.py b/assets/src/ba_data/python/ba/_actor.py
index b0efa0ce..42a9d611 100644
--- a/assets/src/ba_data/python/ba/_actor.py
+++ b/assets/src/ba_data/python/ba/_actor.py
@@ -25,6 +25,9 @@ from __future__ import annotations
import weakref
from typing import TYPE_CHECKING, TypeVar
+from ba._messages import DieMessage, DeathType, OutOfBoundsMessage
+from ba import _error
+
import _ba
if TYPE_CHECKING:
@@ -83,9 +86,11 @@ class Actor:
def __init__(self) -> None:
"""Instantiates an Actor in the current ba.Activity."""
- # FIXME: Actor should not be require to have a 'node' attr.
- self.node: Optional[ba.Node] = None
+ # FIXME: Actor should not be assumed to have a 'node' attr.
+ # self.node: Optional[ba.Node] = None
+ if __debug__:
+ self._root_actor_init_called = True
activity = _ba.getactivity()
self._activity = weakref.ref(activity)
activity.add_actor_weak_ref(self)
@@ -96,27 +101,20 @@ class Actor:
# That way we can treat DieMessage handling as the single
# point-of-action for death.
if not self.is_expired():
- from ba import _messages
- self.handlemessage(_messages.DieMessage())
+ self.handlemessage(DieMessage())
except Exception:
- from ba import _error
_error.print_exception('exception in ba.Actor.__del__() for', self)
def handlemessage(self, msg: Any) -> Any:
- """General message handling; can be passed any message object.
+ """General message handling; can be passed any message object."""
+ if __debug__:
+ self._handlemessage_sanity_check()
- The default implementation will handle ba.DieMessages by
- calling self.node.delete() if self contains a 'node' attribute.
- """
- from ba._error import UNHANDLED
- del msg # Unused.
- return UNHANDLED
+ # By default, actors going out-of-bounds simply kill themselves.
+ if isinstance(msg, OutOfBoundsMessage):
+ return self.handlemessage(DieMessage(how=DeathType.OUT_OF_BOUNDS))
- def _handlemessage_sanity_check(self) -> None:
- if self.is_expired():
- from ba import _error
- _error.print_error(
- f'handlemessage called on expired actor: {self}')
+ return _error.UNHANDLED
def autoretain(self: T) -> T:
"""Keep this Actor alive without needing to hold a reference to it.
@@ -131,8 +129,7 @@ class Actor:
"""
activity = self._activity()
if activity is None:
- from ba._error import ActivityNotFoundError
- raise ActivityNotFoundError()
+ raise _error.ActivityNotFoundError()
activity.retain_actor(self)
return self
@@ -192,6 +189,21 @@ class Actor:
"""
return True
+ def _handlemessage_sanity_check(self) -> None:
+ """Make sure things are kosher in handlemessage().
+
+ Place this in an 'if __debug__:' clause at the top of handlemessage()
+ overrides. This will will complain if anything is sending the Actor
+ messages after the activity has ended, which should be explicitly
+ avoided.
+ """
+ assert __debug__, "This should only be called in __debug__ mode."
+ if not getattr(self, '_root_actor_init_called', False):
+ _error.print_error('Root Actor __init__() not called.')
+ if self.is_expired():
+ _error.print_error(
+ f'handlemessage() called on expired actor: {self}')
+
@property
def activity(self) -> ba.Activity:
"""The Activity this Actor was created in.
@@ -200,8 +212,7 @@ class Actor:
"""
activity = self._activity()
if activity is None:
- from ba._error import ActivityNotFoundError
- raise ActivityNotFoundError()
+ raise _error.ActivityNotFoundError()
return activity
def getactivity(self, doraise: bool = True) -> Optional[ba.Activity]:
@@ -212,6 +223,5 @@ class Actor:
"""
activity = self._activity()
if activity is None and doraise:
- from ba._error import ActivityNotFoundError
- raise ActivityNotFoundError()
+ raise _error.ActivityNotFoundError()
return activity
diff --git a/assets/src/ba_data/python/ba/_apputils.py b/assets/src/ba_data/python/ba/_apputils.py
index 95a7fd3f..2b974b87 100644
--- a/assets/src/ba_data/python/ba/_apputils.py
+++ b/assets/src/ba_data/python/ba/_apputils.py
@@ -188,7 +188,6 @@ def print_live_object_warnings(when: Any,
ignore_session: ba.Session = None,
ignore_activity: ba.Activity = None) -> None:
"""Print warnings for remaining objects in the current context."""
- # pylint: disable=too-many-branches
# pylint: disable=cyclic-import
import gc
from ba import _session as bs_session
@@ -252,13 +251,14 @@ def print_live_object_warnings(when: Any,
for actor in actors:
_ba.app.printed_live_object_warning = True
print('ERROR: Actor found', when, ':', actor)
- if isinstance(actor, bs_actor.Actor):
- try:
- if actor.node:
- print(' - contains node:', actor.node.getnodetype(), ';',
- actor.node.get_name())
- except Exception as exc:
- print(' - exception checking actor node:', exc)
+ # if isinstance(actor, bs_actor.Actor):
+ # try:
+ # if actor.node:
+ # print(' - contains node:',
+ # actor.node.getnodetype(), ';',
+ # actor.node.get_name())
+ # except Exception as exc:
+ # print(' - exception checking actor node:', exc)
# refs = list(gc.get_referrers(actor))
# i = 1
# for ref in refs:
diff --git a/assets/src/ba_data/python/ba/_coopgame.py b/assets/src/ba_data/python/ba/_coopgame.py
index 4d43aace..1b183f4d 100644
--- a/assets/src/ba_data/python/ba/_coopgame.py
+++ b/assets/src/ba_data/python/ba/_coopgame.py
@@ -135,10 +135,10 @@ class CoopGameActivity(GameActivity):
a wave.
duration is given in seconds.
"""
+ from ba._messages import CelebrateMessage
for player in self.players:
- if player.actor is not None and player.actor.node:
- player.actor.node.handlemessage('celebrate',
- int(duration * 1000))
+ if player.actor:
+ player.actor.handlemessage(CelebrateMessage(duration))
def _preload_achievements(self) -> None:
from ba import _achievement
diff --git a/assets/src/ba_data/python/ba/_dependency.py b/assets/src/ba_data/python/ba/_dependency.py
index 8ac1c652..b7ba86cc 100644
--- a/assets/src/ba_data/python/ba/_dependency.py
+++ b/assets/src/ba_data/python/ba/_dependency.py
@@ -315,7 +315,6 @@ class AssetPackage(DependencyComponent):
def __init__(self) -> None:
super().__init__()
- # pylint: disable=no-member
# This is used internally by the get_package_xxx calls.
self.context = _ba.Context('current')
diff --git a/assets/src/ba_data/python/ba/_gameactivity.py b/assets/src/ba_data/python/ba/_gameactivity.py
index 67999626..de6ec230 100644
--- a/assets/src/ba_data/python/ba/_gameactivity.py
+++ b/assets/src/ba_data/python/ba/_gameactivity.py
@@ -337,13 +337,13 @@ class GameActivity(Activity):
self._game_scoreboard_description_text: Optional[ba.Actor] = None
self._standard_time_limit_time: Optional[int] = None
self._standard_time_limit_timer: Optional[ba.Timer] = None
- self._standard_time_limit_text: Optional[ba.Actor] = None
- self._standard_time_limit_text_input: Optional[ba.Actor] = None
+ self._standard_time_limit_text: Optional[ba.NodeActor] = None
+ self._standard_time_limit_text_input: Optional[ba.NodeActor] = None
self._tournament_time_limit: Optional[int] = None
self._tournament_time_limit_timer: Optional[ba.Timer] = None
- self._tournament_time_limit_title_text: Optional[ba.Actor] = None
- self._tournament_time_limit_text: Optional[ba.Actor] = None
- self._tournament_time_limit_text_input: Optional[ba.Actor] = None
+ self._tournament_time_limit_title_text: Optional[ba.NodeActor] = None
+ self._tournament_time_limit_text: Optional[ba.NodeActor] = None
+ self._tournament_time_limit_text_input: Optional[ba.NodeActor] = None
self._zoom_message_times: Dict[int, float] = {}
self._is_waiting_for_continue = False
@@ -1042,17 +1042,18 @@ class GameActivity(Activity):
from ba import _messages
from ba._gameutils import animate
from ba._coopsession import CoopSession
- from bastd.actor import playerspaz
+ from bastd.actor.playerspaz import PlayerSpaz
name = player.get_name()
color = player.color
highlight = player.highlight
light_color = _math.normalized_color(color)
display_color = _ba.safecolor(color, target_intensity=0.75)
- spaz = playerspaz.PlayerSpaz(color=color,
- highlight=highlight,
- character=player.character,
- player=player)
+ spaz = PlayerSpaz(color=color,
+ highlight=highlight,
+ character=player.character,
+ player=player)
+
player.set_actor(spaz)
assert spaz.node
diff --git a/assets/src/ba_data/python/ba/_map.py b/assets/src/ba_data/python/ba/_map.py
index 921c39b0..2bfdf8cb 100644
--- a/assets/src/ba_data/python/ba/_map.py
+++ b/assets/src/ba_data/python/ba/_map.py
@@ -211,7 +211,7 @@ class Map(Actor):
# This is expected to always be a ba.Node object (whether valid or not)
# should be set to something meaningful by child classes.
- self.node = None
+ self.node: Optional[_ba.Node] = None
# Make our class' preload-data available to us
# (and instruct the user if we weren't preloaded properly).
@@ -365,9 +365,8 @@ class Map(Actor):
player_pts = []
for player in players:
try:
- if player.actor is not None and player.actor.is_alive():
- assert player.actor.node
- pnt = _ba.Vec3(player.actor.node.position)
+ if player.node:
+ pnt = _ba.Vec3(player.node.position)
player_pts.append(pnt)
except Exception as exc:
print('EXC in get_ffa_start_position:', exc)
@@ -412,12 +411,17 @@ class Map(Actor):
return self.flag_points_default[:3]
return self.flag_points[team_index % len(self.flag_points)][:3]
+ def exists(self) -> bool:
+ return bool(self.node)
+
def handlemessage(self, msg: Any) -> Any:
from ba import _messages
if isinstance(msg, _messages.DieMessage):
if self.node:
self.node.delete()
- super().handlemessage(msg)
+ else:
+ return super().handlemessage(msg)
+ return None
def register_map(maptype: Type[Map]) -> None:
diff --git a/assets/src/ba_data/python/ba/_messages.py b/assets/src/ba_data/python/ba/_messages.py
index a82bd481..9ac55859 100644
--- a/assets/src/ba_data/python/ba/_messages.py
+++ b/assets/src/ba_data/python/ba/_messages.py
@@ -47,6 +47,7 @@ class DeathType(Enum):
Category: Enums
"""
GENERIC = 'generic'
+ OUT_OF_BOUNDS = 'out_of_bounds'
IMPACT = 'impact'
FALL = 'fall'
REACHED_GOAL = 'reached_goal'
@@ -187,6 +188,20 @@ class ThawMessage:
"""
+@dataclass
+class CelebrateMessage:
+ """Tells an object to celebrate.
+
+ Category: Message Classes
+
+ Attributes:
+
+ duration
+ Amount of time to celebrate in seconds.
+ """
+ duration: float = 10.0
+
+
@dataclass(init=False)
class HitMessage:
"""Tells an object it has been hit in some way.
diff --git a/assets/src/ba_data/python/ba/_nodeactor.py b/assets/src/ba_data/python/ba/_nodeactor.py
index 6a1305be..68f4dcf9 100644
--- a/assets/src/ba_data/python/ba/_nodeactor.py
+++ b/assets/src/ba_data/python/ba/_nodeactor.py
@@ -51,3 +51,6 @@ class NodeActor(Actor):
self.node.delete()
return None
return super().handlemessage(msg)
+
+ def exists(self) -> bool:
+ return bool(self.node)
diff --git a/assets/src/ba_data/python/ba/_team.py b/assets/src/ba_data/python/ba/_team.py
index c9735dff..23f53726 100644
--- a/assets/src/ba_data/python/ba/_team.py
+++ b/assets/src/ba_data/python/ba/_team.py
@@ -98,21 +98,6 @@ class Team:
"""Returns the numeric team ID."""
return self._team_id
- def celebrate(self, duration: float = 10.0) -> None:
- """Tells all players on the team to celebrate.
-
- duration is given in seconds.
- """
- for player in self.players:
- try:
- if player.actor is not None and player.actor.node:
- # Internal node-message is in milliseconds.
- player.actor.node.handlemessage('celebrate',
- int(duration * 1000))
- except Exception:
- from ba import _error
- _error.print_exception('Error on celebrate')
-
def reset(self) -> None:
"""(internal)"""
self.reset_gamedata()
diff --git a/assets/src/ba_data/python/ba/_teambasesession.py b/assets/src/ba_data/python/ba/_teambasesession.py
index 063c24ac..ffd93f36 100644
--- a/assets/src/ba_data/python/ba/_teambasesession.py
+++ b/assets/src/ba_data/python/ba/_teambasesession.py
@@ -254,22 +254,23 @@ class TeamBaseSession(Session):
announcement of the same.
"""
# pylint: disable=cyclic-import
+ # pylint: disable=too-many-locals
from ba import _math
from ba import _general
from ba._gameutils import cameraflash
from ba import _lang
from ba._freeforallsession import FreeForAllSession
+ from ba._messages import CelebrateMessage
_ba.timer(delay,
_general.Call(_ba.playsound, _ba.getsound("boxingBell")))
if announce_winning_team:
winning_team = results.get_winning_team()
if winning_team is not None:
# Have all players celebrate.
+ celebrate_msg = CelebrateMessage(duration=10.0)
for player in winning_team.players:
- if player.actor is not None and player.actor.node:
- # Note: celebrate message takes milliseconds
- # for historical reasons.
- player.actor.node.handlemessage('celebrate', 10000)
+ if player.actor:
+ player.actor.handlemessage(celebrate_msg)
cameraflash()
# Some languages say "FOO WINS" different for teams vs players.
diff --git a/assets/src/ba_data/python/bastd/actor/playerspaz.py b/assets/src/ba_data/python/bastd/actor/playerspaz.py
index a600f3a9..1034c17b 100644
--- a/assets/src/ba_data/python/bastd/actor/playerspaz.py
+++ b/assets/src/ba_data/python/bastd/actor/playerspaz.py
@@ -25,7 +25,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import ba
-from bastd.actor import spaz as basespaz
+from bastd.actor.spaz import Spaz
if TYPE_CHECKING:
from typing import Any, Optional, Sequence, Tuple
@@ -77,7 +77,7 @@ class PlayerSpazHurtMessage:
self.spaz = spaz
-class PlayerSpaz(basespaz.Spaz):
+class PlayerSpaz(Spaz):
"""A ba.Spaz subclass meant to be controlled by a ba.Player.
category: Gameplay Classes
@@ -102,13 +102,12 @@ class PlayerSpaz(basespaz.Spaz):
you must call connect_controls_to_player() to do so.
"""
- basespaz.Spaz.__init__(self,
- color=color,
- highlight=highlight,
- character=character,
- source_player=player,
- start_invincible=True,
- powerups_expire=powerups_expire)
+ super().__init__(color=color,
+ highlight=highlight,
+ character=character,
+ source_player=player,
+ start_invincible=True,
+ powerups_expire=powerups_expire)
self.last_player_attacked_by: Optional[ba.Player] = None
self.last_attacked_time = 0.0
self.last_attacked_type: Optional[Tuple[str, str]] = None
diff --git a/assets/src/ba_data/python/bastd/actor/spaz.py b/assets/src/ba_data/python/bastd/actor/spaz.py
index 1e38890b..d344ee36 100644
--- a/assets/src/ba_data/python/bastd/actor/spaz.py
+++ b/assets/src/ba_data/python/bastd/actor/spaz.py
@@ -155,7 +155,7 @@ class Spaz(ba.Actor):
media = factory.get_media(character)
punchmats = (factory.punch_material, ba.sharedobj('attack_material'))
pickupmats = (factory.pickup_material, ba.sharedobj('pickup_material'))
- self.node = ba.newnode(
+ self.node: ba.Node = ba.newnode(
type="spaz",
delegate=self,
attrs={
@@ -256,10 +256,13 @@ class Spaz(ba.Actor):
self.punch_callback: Optional[Callable[[Spaz], Any]] = None
self.pick_up_powerup_callback: Optional[Callable[[Spaz], Any]] = None
+ def exists(self) -> bool:
+ return bool(self.node)
+
def on_expire(self) -> None:
super().on_expire()
- # release callbacks/refs so we don't wind up with dependency loops..
+ # Release callbacks/refs so we don't wind up with dependency loops.
self._dropped_bomb_callbacks = []
self.punch_callback = None
self.pick_up_powerup_callback = None
@@ -296,8 +299,8 @@ class Spaz(ba.Actor):
assert isinstance(t_ms, int)
t_bucket = int(t_ms / 1000)
if t_bucket == self._turbo_filter_time_bucket:
- # add only once per timestep (filter out buttons triggering
- # multiple actions)
+ # Add only once per timestep (filter out buttons triggering
+ # multiple actions).
if t_ms != self._turbo_filter_times.get(source, 0):
self._turbo_filter_counts[source] = (
self._turbo_filter_counts.get(source, 0) + 1)
@@ -306,11 +309,11 @@ class Spaz(ba.Actor):
# ba.screenmessage( str(source) + " "
# + str(self._turbo_filter_counts[source]))
if self._turbo_filter_counts[source] == 15:
- # knock 'em out. That'll learn 'em.
+ # Knock 'em out. That'll learn 'em.
assert self.node
self.node.handlemessage("knockout", 500.0)
- # also issue periodic notices about who is turbo-ing
+ # Also issue periodic notices about who is turbo-ing.
now = ba.time(ba.TimeType.REAL)
if now > ba.app.last_spaz_turbo_warn_time + 30.0:
ba.app.last_spaz_turbo_warn_time = now
@@ -1226,6 +1229,10 @@ class Spaz(ba.Actor):
# hold_body needs to be set before hold_node.
self.node.hold_body = opposing_body
self.node.hold_node = opposing_node
+ elif isinstance(msg, ba.CelebrateMessage):
+ if self.node:
+ self.node.handlemessage('celebrate', int(msg.duration * 1000))
+
else:
return super().handlemessage(msg)
return None
diff --git a/assets/src/ba_data/python/bastd/actor/spazbot.py b/assets/src/ba_data/python/bastd/actor/spazbot.py
index a5eee8b9..c3e7e639 100644
--- a/assets/src/ba_data/python/bastd/actor/spazbot.py
+++ b/assets/src/ba_data/python/bastd/actor/spazbot.py
@@ -190,7 +190,7 @@ class SpazBot(basespaz.Spaz):
closest_dist = None
closest_vel = None
closest = None
- assert self._player_pts
+ assert self._player_pts is not None
for plpt, plvel in self._player_pts:
dist = (plpt - botpt).length()
@@ -983,6 +983,7 @@ class BotSet:
for player in ba.getactivity().players:
try:
if player.is_alive():
+ assert isinstance(player.actor, basespaz.Spaz)
assert player.actor is not None and player.actor.node
player_pts.append((ba.Vec3(player.actor.node.position),
ba.Vec3(player.actor.node.velocity)))
@@ -1006,16 +1007,6 @@ class BotSet:
bot.handlemessage(ba.DieMessage(immediate=True))
self._bot_lists[i] = []
- def celebrate(self, duration: float) -> None:
- """Tell all living bots in the set to celebrate momentarily.
-
- Duration is given in seconds.
- """
- for botlist in self._bot_lists:
- for bot in botlist:
- if bot.node:
- bot.node.handlemessage('celebrate', int(duration * 1000))
-
def start_moving(self) -> None:
"""Start processing bot AI updates so they start doing their thing."""
self._bot_update_timer = ba.Timer(0.05,
@@ -1035,6 +1026,17 @@ class BotSet:
bot.node.move_left_right = 0
bot.node.move_up_down = 0
+ def celebrate(self, duration: float) -> None:
+ """Tell all living bots in the set to celebrate momentarily.
+
+ Duration is given in seconds.
+ """
+ msg = ba.CelebrateMessage(duration=duration)
+ for botlist in self._bot_lists:
+ for bot in botlist:
+ if bot:
+ bot.handlemessage(msg)
+
def final_celebrate(self) -> None:
"""Tell all bots in the set to stop what they were doing and celebrate.
@@ -1045,12 +1047,12 @@ class BotSet:
# At this point stop doing anything but jumping and celebrating.
for botlist in self._bot_lists:
for bot in botlist:
- if bot.node:
+ if bot:
+ assert bot.node # (should exist if 'if bot' was True)
bot.node.move_left_right = 0
bot.node.move_up_down = 0
- ba.timer(
- 0.5 * random.random(),
- ba.Call(bot.node.handlemessage, 'celebrate', 10.0))
+ ba.timer(0.5 * random.random(),
+ ba.Call(bot.handlemessage, ba.CelebrateMessage()))
jump_duration = random.randrange(400, 500)
j = random.randrange(0, 200)
for _i in range(10):
diff --git a/assets/src/ba_data/python/bastd/game/assault.py b/assets/src/ba_data/python/bastd/game/assault.py
index 4a6be7c4..2a3d06f6 100644
--- a/assets/src/ba_data/python/bastd/game/assault.py
+++ b/assets/src/ba_data/python/bastd/game/assault.py
@@ -225,12 +225,8 @@ class AssaultGame(ba.TeamGameActivity):
# Have teammates celebrate.
for player in player_team.players:
- try:
- # Note: celebrate message is milliseconds
- # for historical reasons.
- player.actor.node.handlemessage('celebrate', 2000)
- except Exception:
- pass
+ if player.actor:
+ player.actor.handlemessage(ba.CelebrateMessage(2.0))
player_team.gamedata['score'] += 1
self._update_scoreboard()
diff --git a/assets/src/ba_data/python/bastd/game/capturetheflag.py b/assets/src/ba_data/python/bastd/game/capturetheflag.py
index 2d98ee98..8227854b 100644
--- a/assets/src/ba_data/python/bastd/game/capturetheflag.py
+++ b/assets/src/ba_data/python/bastd/game/capturetheflag.py
@@ -306,10 +306,8 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
# Have teammates celebrate
for player in team.players:
- if player.actor is not None and player.actor.node:
- # Note: celebrate message is milliseconds
- # for historical reasons.
- player.actor.node.handlemessage('celebrate', 2000)
+ if player.actor:
+ player.actor.handlemessage(ba.CelebrateMessage(2.0))
# Reset all flags/state.
for reset_team in self.teams:
diff --git a/assets/src/ba_data/python/bastd/game/chosenone.py b/assets/src/ba_data/python/bastd/game/chosenone.py
index 119ab31c..07e81941 100644
--- a/assets/src/ba_data/python/bastd/game/chosenone.py
+++ b/assets/src/ba_data/python/bastd/game/chosenone.py
@@ -279,7 +279,7 @@ class ChosenOneGame(ba.TeamGameActivity):
self._flag = None
self._chosen_one_player = player
- if player.actor.node:
+ if player.actor:
if self.settings['Chosen One Gets Shield']:
player.actor.handlemessage(
ba.PowerupMessage('shield'))
@@ -311,8 +311,10 @@ class ChosenOneGame(ba.TeamGameActivity):
0.4: 1.0
},
loop=True)
+ assert isinstance(player.actor, playerspaz.PlayerSpaz)
player.actor.node.connectattr('position', light.node,
'position')
+
except Exception:
ba.print_exception('EXC in _set_chosen_one_player')
diff --git a/assets/src/ba_data/python/bastd/game/easteregghunt.py b/assets/src/ba_data/python/bastd/game/easteregghunt.py
index 1bc75279..9f1ce390 100644
--- a/assets/src/ba_data/python/bastd/game/easteregghunt.py
+++ b/assets/src/ba_data/python/bastd/game/easteregghunt.py
@@ -143,7 +143,9 @@ class EasterEggHuntGame(ba.TeamGameActivity):
'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.PlayerSpaz)
player = (spaz.getplayer()
if hasattr(spaz, 'getplayer') else None)
if player and egg:
@@ -187,13 +189,8 @@ class EasterEggHuntGame(ba.TeamGameActivity):
ypos = random.uniform(3.5, 3.5)
zpos = random.uniform(-8.2, 3.7)
- def _is_exists(egg: Egg) -> bool:
- if egg.node is None:
- return False
- return egg.node.exists()
-
# Prune dead eggs from our list.
- self._eggs = [e for e in self._eggs if _is_exists(e)]
+ self._eggs = [e for e in self._eggs if e]
# Spawn more eggs if we've got space.
if len(self._eggs) < int(self._max_eggs):
@@ -283,12 +280,13 @@ class Egg(ba.Actor):
'materials': mats
})
+ def exists(self) -> bool:
+ return bool(self.node)
+
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
if self.node:
self.node.delete()
- elif isinstance(msg, ba.OutOfBoundsMessage):
- self.handlemessage(ba.DieMessage())
elif isinstance(msg, ba.HitMessage):
if self.node:
assert msg.force_direction is not None
diff --git a/assets/src/ba_data/python/bastd/game/elimination.py b/assets/src/ba_data/python/bastd/game/elimination.py
index 6e53f8f7..f17a116c 100644
--- a/assets/src/ba_data/python/bastd/game/elimination.py
+++ b/assets/src/ba_data/python/bastd/game/elimination.py
@@ -386,8 +386,8 @@ class EliminationGame(ba.TeamGameActivity):
for team in self.teams:
for tplayer in team.players:
if tplayer.is_alive():
- assert tplayer.actor is not None and tplayer.actor.node
- ppos = tplayer.actor.node.position
+ assert tplayer.node
+ ppos = tplayer.node.position
living_player = tplayer
living_player_pos = ppos
break
@@ -416,20 +416,16 @@ class EliminationGame(ba.TeamGameActivity):
def _print_lives(self, player: ba.Player) -> None:
from bastd.actor import popuptext
- if not player or not player.is_alive():
- return
- try:
- assert player.actor is not None and player.actor.node
- pos = player.actor.node.position
- except Exception as exc:
- print('EXC getting player pos in bs_elim', exc)
+ assert player # Shouldn't be passing invalid refs around.
+ if not player or not player.is_alive() or not player.node:
return
+
popuptext.PopupText('x' + str(player.gamedata['lives'] - 1),
color=(1, 1, 0, 1),
offset=(0, -0.8, 0),
random_offset=0.0,
scale=1.8,
- position=pos).autoretain()
+ position=player.node.position).autoretain()
def on_player_leave(self, player: ba.Player) -> None:
ba.TeamGameActivity.on_player_leave(self, player)
diff --git a/assets/src/ba_data/python/bastd/game/football.py b/assets/src/ba_data/python/bastd/game/football.py
index 3f814cb9..d74fd9e1 100644
--- a/assets/src/ba_data/python/bastd/game/football.py
+++ b/assets/src/ba_data/python/bastd/game/football.py
@@ -128,10 +128,10 @@ class FootballTeamGame(ba.TeamGameActivity):
True), ("modify_part_collision", "physical", False),
("call", "at_connect", self._handle_score)))
self._flag_spawn_pos: Optional[Sequence[float]] = None
- self._score_regions: List[ba.Actor] = []
+ self._score_regions: List[ba.NodeActor] = []
self._flag: Optional[FootballFlag] = None
self._flag_respawn_timer: Optional[ba.Timer] = None
- self._flag_respawn_light: Optional[ba.Actor] = None
+ self._flag_respawn_light: Optional[ba.NodeActor] = None
def get_instance_description(self) -> Union[str, Sequence]:
touchdowns = self.settings['Score to Win'] / 7
@@ -203,13 +203,8 @@ class FootballTeamGame(ba.TeamGameActivity):
# Tell all players to celebrate.
for player in team.players:
- if player.actor is not None and player.actor.node:
- try:
- # Note: celebrate message is milliseconds
- # (for historical reasons).
- player.actor.node.handlemessage('celebrate', 2000)
- except Exception:
- ba.print_exception('Error on celebrate')
+ if player.actor:
+ player.actor.handlemessage(ba.CelebrateMessage(2.0))
# If someone on this team was last to touch it,
# give them points.
@@ -376,7 +371,7 @@ class FootballCoopGame(ba.CoopGameActivity):
self._player_has_punched = False
self._scoreboard: Optional[Scoreboard] = None
self._flag_spawn_pos: Optional[Sequence[float]] = None
- self.score_regions: List[ba.Actor] = []
+ 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
@@ -384,8 +379,8 @@ class FootballCoopGame(ba.CoopGameActivity):
self._bot_types_14: Optional[List[Type[spazbot.SpazBot]]] = None
self._bot_team: Optional[ba.Team] = None
self._starttime_ms: Optional[int] = None
- self._time_text: Optional[ba.Actor] = None
- self._time_text_input: Optional[ba.Actor] = None
+ self._time_text: Optional[ba.NodeActor] = None
+ self._time_text_input: Optional[ba.NodeActor] = None
self._tntspawner: Optional[stdbomb.TNTSpawner] = None
self._bots = spazbot.BotSet()
self._bot_spawn_timer: Optional[ba.Timer] = None
@@ -580,13 +575,11 @@ class FootballCoopGame(ba.CoopGameActivity):
assert self._flag is not None
if self._flag.node:
for player in self.players:
- try:
- assert player.actor is not None and player.actor.node
+ if player.actor:
+ assert isinstance(player.actor, playerspaz.PlayerSpaz)
if (player.actor.is_alive() and
player.actor.node.hold_node == self._flag.node):
return
- except Exception:
- ba.print_exception("exception checking hold node")
flagpos = ba.Vec3(self._flag.node.position)
closest_bot = None
@@ -651,7 +644,6 @@ class FootballCoopGame(ba.CoopGameActivity):
""" a point has been scored """
# FIXME tidy this up
# pylint: disable=too-many-branches
- # pylint: disable=too-many-nested-blocks
# Our flag might stick around for a second or two;
# we don't want it to be able to score again.
@@ -674,16 +666,11 @@ class FootballCoopGame(ba.CoopGameActivity):
# Tell all players (or bots) to celebrate.
if i == 0:
for player in team.players:
- try:
- # Note: celebrate message is milliseconds
- # (for historical reasons).
- if player.actor is not None and player.actor.node:
- player.actor.node.handlemessage(
- 'celebrate', 2000)
- except Exception:
- ba.print_exception()
+ if player.actor:
+ player.actor.handlemessage(
+ ba.CelebrateMessage(2.0))
else:
- self._bots.celebrate(2000)
+ self._bots.celebrate(2.0)
# If the good guys scored, add more enemies.
if i == 0:
diff --git a/assets/src/ba_data/python/bastd/game/hockey.py b/assets/src/ba_data/python/bastd/game/hockey.py
index f8e394da..3a778a33 100644
--- a/assets/src/ba_data/python/bastd/game/hockey.py
+++ b/assets/src/ba_data/python/bastd/game/hockey.py
@@ -193,7 +193,7 @@ class HockeyGame(ba.TeamGameActivity):
True), ("modify_part_collision", "physical", False),
("call", "at_connect", self._handle_score)))
self._puck_spawn_pos: Optional[Sequence[float]] = None
- self._score_regions: Optional[List[ba.Actor]] = None
+ self._score_regions: Optional[List[ba.NodeActor]] = None
self._puck: Optional[Puck] = None
def get_instance_description(self) -> Union[str, Sequence]:
@@ -284,10 +284,8 @@ class HockeyGame(ba.TeamGameActivity):
# Tell all players to celebrate.
for player in team.players:
- if player.actor is not None and player.actor.node:
- # Note: celebrate message takes milliseconds
- # (for historical reasons).
- player.actor.node.handlemessage('celebrate', 2000)
+ if player.actor:
+ player.actor.handlemessage(ba.CelebrateMessage(2.0))
# If we've got the player from the scoring team that last
# touched us, give them points.
diff --git a/assets/src/ba_data/python/bastd/game/keepaway.py b/assets/src/ba_data/python/bastd/game/keepaway.py
index 882ba803..f39667eb 100644
--- a/assets/src/ba_data/python/bastd/game/keepaway.py
+++ b/assets/src/ba_data/python/bastd/game/keepaway.py
@@ -194,7 +194,7 @@ class KeepAwayGame(ba.TeamGameActivity):
for player in self.players:
holding_flag = False
try:
- assert player.actor is not None
+ assert isinstance(player.actor, playerspaz.PlayerSpaz)
if (player.actor.is_alive() and player.actor.node
and player.actor.node.hold_node):
holding_flag = (
diff --git a/assets/src/ba_data/python/bastd/game/ninjafight.py b/assets/src/ba_data/python/bastd/game/ninjafight.py
index d967cfa1..d80bf190 100644
--- a/assets/src/ba_data/python/bastd/game/ninjafight.py
+++ b/assets/src/ba_data/python/bastd/game/ninjafight.py
@@ -188,7 +188,9 @@ class NinjaFightGame(ba.TeamGameActivity):
ba.cameraflash()
ba.playsound(self._winsound)
for team in self.teams:
- team.celebrate() # Woooo! par-tay!
+ for player in team.players:
+ if player.actor:
+ player.actor.handlemessage(ba.CelebrateMessage())
results.set_team_score(team, elapsed_time_ms)
# Ends the activity.
diff --git a/assets/src/ba_data/python/bastd/game/onslaught.py b/assets/src/ba_data/python/bastd/game/onslaught.py
index 94c53c3e..e3eecfdf 100644
--- a/assets/src/ba_data/python/bastd/game/onslaught.py
+++ b/assets/src/ba_data/python/bastd/game/onslaught.py
@@ -105,7 +105,7 @@ class OnslaughtGame(ba.CoopGameActivity):
self._can_end_wave = True
self._score = 0
self._time_bonus = 0
- self._spawn_info_text: Optional[ba.Actor] = None
+ self._spawn_info_text: Optional[ba.NodeActor] = None
self._dingsound = ba.getsound('dingSmall')
self._dingsoundhigh = ba.getsound('dingSmallHigh')
self._have_tnt = False
@@ -115,9 +115,9 @@ class OnslaughtGame(ba.CoopGameActivity):
self._bots: Optional[spazbot.BotSet] = None
self._powerup_drop_timer: Optional[ba.Timer] = None
self._time_bonus_timer: Optional[ba.Timer] = None
- self._time_bonus_text: Optional[ba.Actor] = None
+ self._time_bonus_text: Optional[ba.NodeActor] = None
self._flawless_bonus: Optional[int] = None
- self._wave_text: Optional[ba.Actor] = None
+ self._wave_text: Optional[ba.NodeActor] = None
self._wave_update_timer: Optional[ba.Timer] = None
self._throw_off_kills = 0
self._land_mine_kills = 0
@@ -873,7 +873,7 @@ class OnslaughtGame(ba.CoopGameActivity):
if self._game_over:
return
- # respawn applicable players
+ # Respawn applicable players.
if self._wave > 1 and not self.is_waiting_for_continue():
for player in self.players:
if (not player.is_alive()
diff --git a/assets/src/ba_data/python/bastd/game/race.py b/assets/src/ba_data/python/bastd/game/race.py
index 3ea58f71..f4d1de6f 100644
--- a/assets/src/ba_data/python/bastd/game/race.py
+++ b/assets/src/ba_data/python/bastd/game/race.py
@@ -189,6 +189,7 @@ class RaceGame(ba.TeamGameActivity):
self._regions.append(RaceRegion(rpt, len(self._regions)))
def _flash_player(self, player: ba.Player, scale: float) -> None:
+ assert isinstance(player.actor, PlayerSpaz)
assert player.actor is not None and player.actor.node
pos = player.actor.node.position
light = ba.newnode('light',
@@ -523,6 +524,7 @@ class RaceGame(ba.TeamGameActivity):
for player in self.players:
pos: Optional[ba.Vec3]
try:
+ assert isinstance(player.actor, PlayerSpaz)
assert player.actor is not None and player.actor.node
pos = ba.Vec3(player.actor.node.position)
except Exception:
diff --git a/assets/src/ba_data/python/bastd/game/runaround.py b/assets/src/ba_data/python/bastd/game/runaround.py
index 88601355..32ba00e5 100644
--- a/assets/src/ba_data/python/bastd/game/runaround.py
+++ b/assets/src/ba_data/python/bastd/game/runaround.py
@@ -117,15 +117,15 @@ class RunaroundGame(ba.CoopGameActivity):
self._waves: Optional[List[Dict[str, Any]]] = None
self._bots = spazbot.BotSet()
self._tntspawner: Optional[TNTSpawner] = None
- self._lives_bg: Optional[ba.Actor] = None
+ self._lives_bg: Optional[ba.NodeActor] = None
self._start_lives = 10
self._lives = self._start_lives
- self._lives_text: Optional[ba.Actor] = None
+ self._lives_text: Optional[ba.NodeActor] = None
self._flawless = True
self._time_bonus_timer: Optional[ba.Timer] = None
- self._time_bonus_text: Optional[ba.Actor] = None
+ self._time_bonus_text: Optional[ba.NodeActor] = None
self._time_bonus_mult: Optional[float] = None
- self._wave_text: Optional[ba.Actor] = None
+ self._wave_text: Optional[ba.NodeActor] = None
self._flawless_bonus: Optional[int] = None
self._wave_update_timer: Optional[ba.Timer] = None
@@ -1050,10 +1050,12 @@ class RunaroundGame(ba.CoopGameActivity):
def _update_bot(self, bot: spazbot.SpazBot) -> bool:
# Yup; that's a lot of return statements right there.
# pylint: disable=too-many-return-statements
- assert bot.node
- if not bot.is_alive() or not bot.node.exists():
+
+ if not bool(bot):
return True
+ 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
diff --git a/assets/src/ba_data/python/bastd/game/thelaststand.py b/assets/src/ba_data/python/bastd/game/thelaststand.py
index 340d8c33..08f87f32 100644
--- a/assets/src/ba_data/python/bastd/game/thelaststand.py
+++ b/assets/src/ba_data/python/bastd/game/thelaststand.py
@@ -197,6 +197,7 @@ class TheLastStandGame(ba.CoopGameActivity):
for player in self.players:
try:
if player.is_alive():
+ assert isinstance(player.actor, playerspaz.PlayerSpaz)
assert player.actor is not None and player.actor.node
playerpts.append(player.actor.node.position)
except Exception as exc:
diff --git a/assets/src/ba_data/python/bastd/mainmenu.py b/assets/src/ba_data/python/bastd/mainmenu.py
index f6df76bb..ae5c4816 100644
--- a/assets/src/ba_data/python/bastd/mainmenu.py
+++ b/assets/src/ba_data/python/bastd/mainmenu.py
@@ -262,7 +262,7 @@ class MainMenuActivity(ba.Activity):
self._valid = True
self._message_duration = 10.0
self._message_spacing = 2.0
- self._text: Optional[ba.Actor] = None
+ self._text: Optional[ba.NodeActor] = None
self._activity = weakref.ref(activity)
# If we're signed in, fetch news immediately.
@@ -290,8 +290,8 @@ class MainMenuActivity(ba.Activity):
# If our news is way out of date, lets re-request it;
# otherwise, rotate our phrase.
- assert app.main_menu_last_news_fetch_time is not None
- if time.time() - app.main_menu_last_news_fetch_time > 600.0:
+ assert ba.app.main_menu_last_news_fetch_time is not None
+ if time.time() - ba.app.main_menu_last_news_fetch_time > 600.0:
self._fetch_news()
self._text = None
else:
diff --git a/assets/src/ba_data/python/bastd/maps.py b/assets/src/ba_data/python/bastd/maps.py
index 4f74678a..f55fbee2 100644
--- a/assets/src/ba_data/python/bastd/maps.py
+++ b/assets/src/ba_data/python/bastd/maps.py
@@ -1169,8 +1169,8 @@ class TowerD(ba.Map):
running: bool = False) -> bool:
# see if we're within edge_box
boxes = self.defs.boxes
- box_position = boxes['edge_box1'][0:3]
- box_scale = boxes['edge_box1'][6:9]
+ box_position = boxes['edge_box'][0:3]
+ box_scale = boxes['edge_box'][6:9]
box_position2 = boxes['edge_box2'][0:3]
box_scale2 = boxes['edge_box2'][6:9]
xpos = (point.x - box_position[0]) / box_scale[0]
diff --git a/config/toolconfigsrc/projectile b/config/toolconfigsrc/projectile
index 1f9c0e47..e7af40de 100644
--- a/config/toolconfigsrc/projectile
+++ b/config/toolconfigsrc/projectile
@@ -1,4 +1,2 @@
+/tools
-+/src/ballistica
-+/src/generated_src
+/assets/src/ba_data/python
diff --git a/config/toolconfigsrc/pylintrc b/config/toolconfigsrc/pylintrc
index 8662f2fa..3300f2f1 100644
--- a/config/toolconfigsrc/pylintrc
+++ b/config/toolconfigsrc/pylintrc
@@ -80,6 +80,12 @@ good-names=i,
U,
_
+[TYPECHECK]
+# Mypy does most all the type checking we need, and in some cases
+# Pylint has conflicting views about what types it thinks things are,
+# so let's just flip stuff off here.
+disable=no-member
+
[MISCELLANEOUS]
# We've got various TODO and FIXME notes in scripts, but don't want
# lint to trigger over that fact.
diff --git a/docs/ba_module.md b/docs/ba_module.md
index 8760c93a..18cb3bcb 100644
--- a/docs/ba_module.md
+++ b/docs/ba_module.md
@@ -1,5 +1,5 @@
-last updated on 2020-04-06 for Ballistica version 1.5.0 build 20001
+last updated on 2020-04-07 for Ballistica version 1.5.0 build 20001
This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please let me know. Happy modding!
@@ -112,6 +112,7 @@
General message handling; can be passed any message object.
-The default implementation will handle ba.DieMessages by
-calling self.node.delete() if self contains a 'node' attribute.
-
is_alive(self) -> bool
@@ -1183,6 +1181,29 @@ mycall()
Set the Level currently selected in the UI (by name).
+
+
+
+<top level class>
+
+Tells an object to celebrate.
+
+Category: Message Classes
+
+Attributes:
+
+-
+
float
+Amount of time to celebrate in seconds.
+
+
+
+Methods:
+
+-
+
ba.CelebrateMessage(duration: float = 10.0)
+
@@ -1586,6 +1607,7 @@ the data object is requested and when it's value is accessed.
Values:
- GENERIC
+- OUT_OF_BOUNDS
- IMPACT
- FALL
- REACHED_GOAL
@@ -2782,15 +2804,35 @@ etc.
Methods Inherited:
-
+
Methods Defined or Overridden:
-
+
-
ba.Map(vr_overlay_offset: Optional[Sequence[float]] = None)
Instantiate a map.
+
+-
+
exists(self) -> bool
+
+Returns whether the Actor is still present in a meaningful way.
+
+Note that a dying character should still return True here as long as
+their corpse is visible; this is about presence, not being 'alive'
+(see ba.Actor.is_alive() for that).
+
+If this returns False, it is assumed the Actor can be completely
+deleted without affecting the game; this call is often used
+when pruning lists of Actors, such as with ba.Actor.autoretain()
+
+The default implementation of this method always return True.
+
+Note that the boolean operator for the Actor class calls this method,
+so a simple "if myactor" test will conveniently do the right thing
+even if myactor is set to None.
+
-
get_def_bound_box(self, name: str) -> Optional[Tuple[float, float, float, float, float, float]]
@@ -2872,9 +2914,6 @@ as far from these players as possible.
General message handling; can be passed any message object.
-The default implementation will handle ba.DieMessages by
-calling self.node.delete() if self contains a 'node' attribute.
-
-
is_point_near_edge(self, point: ba.Vec3, running: bool = False) -> bool
@@ -3347,24 +3386,41 @@ acting as an alternative to setting node attributes.
Methods Inherited:
-
+
Methods Defined or Overridden:
-
+
-
ba.NodeActor(node: ba.Node)
Instantiates an Actor in the current ba.Activity.
+
+-
+
exists(self) -> bool
+
+Returns whether the Actor is still present in a meaningful way.
+
+Note that a dying character should still return True here as long as
+their corpse is visible; this is about presence, not being 'alive'
+(see ba.Actor.is_alive() for that).
+
+If this returns False, it is assumed the Actor can be completely
+deleted without affecting the game; this call is often used
+when pruning lists of Actors, such as with ba.Actor.autoretain()
+
+The default implementation of this method always return True.
+
+Note that the boolean operator for the Actor class calls this method,
+so a simple "if myactor" test will conveniently do the right thing
+even if myactor is set to None.
+
-
handlemessage(self, msg: Any) -> Any
General message handling; can be passed any message object.
-The default implementation will handle ba.DieMessages by
-calling self.node.delete() if self contains a 'node' attribute.
-
@@ -4267,7 +4323,7 @@ of the session.
Methods:
-
+
-
ba.Team(team_id: 'int' = 0, name: 'Union[ba.Lstr, str]' = '', color: 'Sequence[float]' = (1.0, 1.0, 1.0))
@@ -4277,14 +4333,6 @@ of the session.
In most cases, all teams are provided to you by the ba.Session,
ba.Session, so calling this shouldn't be necessary.
-
--
-
celebrate(self, duration: float = 10.0) -> None
-
-Tells all players on the team to celebrate.
-
-duration is given in seconds.
-
-
get_id(self) -> int