diff --git a/assets/src/ba_data/python/ba/_gameactivity.py b/assets/src/ba_data/python/ba/_gameactivity.py index 2bd99ab6..e59ff9fb 100644 --- a/assets/src/ba_data/python/ba/_gameactivity.py +++ b/assets/src/ba_data/python/ba/_gameactivity.py @@ -635,7 +635,7 @@ class GameActivity(Activity[PlayerType, TeamType]): from bastd.actor.playerspaz import PlayerSpazDeathMessage if isinstance(msg, PlayerSpazDeathMessage): - player = msg.getspaz(self).player + player = msg.playerspaz(self).player killer = msg.killerplayer # Inform our score-set of the demise. @@ -645,7 +645,8 @@ class GameActivity(Activity[PlayerType, TeamType]): # Award the killer points if he's on a different team. if killer and killer.team is not player.team: - pts, importance = msg.getspaz(self).get_death_points(msg.how) + pts, importance = msg.playerspaz(self).get_death_points( + msg.how) if not self.has_ended(): self.stats.player_scored(killer, pts, diff --git a/assets/src/ba_data/python/bastd/actor/playerspaz.py b/assets/src/ba_data/python/bastd/actor/playerspaz.py index f8ca79f1..5c66414c 100644 --- a/assets/src/ba_data/python/bastd/actor/playerspaz.py +++ b/assets/src/ba_data/python/bastd/actor/playerspaz.py @@ -60,7 +60,7 @@ class PlayerSpazDeathMessage: self.killerplayer = killerplayer self.how = how - def getspaz( + def playerspaz( self, activity: ba.Activity[PlayerType, TeamType]) -> PlayerSpaz[PlayerType]: """Return the spaz that died. @@ -149,7 +149,7 @@ class PlayerSpaz(Spaz, Generic[PlayerType]): Note that this may return None if the player has left. """ - # Convert invalid references to None. + # Return None in the case of a no-longer-valid reference. return self._player if self._player else None def connect_controls_to_player(self, @@ -295,9 +295,9 @@ class PlayerSpaz(Spaz, Generic[PlayerType]): else: killerplayer = None - # Convert dead-refs to None. - if not killerplayer: - killerplayer = None + # We should never wind up with a dead-reference here; + # we want to use None in that case. + assert killerplayer is None or killerplayer # Only report if both the player and the activity still exist. if killed and activity is not None and self.getplayer(): diff --git a/assets/src/ba_data/python/bastd/game/assault.py b/assets/src/ba_data/python/bastd/game/assault.py index 092c9495..efc46df3 100644 --- a/assets/src/ba_data/python/bastd/game/assault.py +++ b/assets/src/ba_data/python/bastd/game/assault.py @@ -26,17 +26,32 @@ from __future__ import annotations import random +from dataclasses import dataclass from typing import TYPE_CHECKING import ba from bastd.actor.playerspaz import PlayerSpaz, PlayerSpazDeathMessage +from bastd.actor.flag import Flag if TYPE_CHECKING: from typing import Any, Type, List, Dict, Tuple, Sequence, Union +@dataclass +class Player(ba.Player['Team']): + """Our player type for this game.""" + + +@dataclass +class Team(ba.Team[Player]): + """Our team type for this game.""" + base_pos: Sequence[float] + flag: Flag + score: int = 0 + + # ba_meta export game -class AssaultGame(ba.TeamGameActivity[ba.Player, ba.Team]): +class AssaultGame(ba.TeamGameActivity[Player, Team]): """Game where you score by touching the other team's flag.""" @classmethod @@ -59,106 +74,114 @@ class AssaultGame(ba.TeamGameActivity[ba.Player, ba.Team]): def get_settings( cls, sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]: - return [('Score to Win', {'min_value': 1, 'default': 3}), - ('Time Limit', { - 'choices': [('None', 0), ('1 Minute', 60), - ('2 Minutes', 120), ('5 Minutes', 300), - ('10 Minutes', 600), ('20 Minutes', 1200)], - 'default': 0}), - ('Respawn Times', { - 'choices': [('Shorter', 0.25), ('Short', 0.5), - ('Normal', 1.0), ('Long', 2.0), - ('Longer', 4.0)], - 'default': 1.0}), - ('Epic Mode', {'default': False})] # yapf: disable + return [ + ('Score to Win', { + 'min_value': 1, + 'default': 3 + }), + ('Time Limit', { + 'choices': [('None', 0), ('1 Minute', 60), ('2 Minutes', 120), + ('5 Minutes', 300), ('10 Minutes', 600), + ('20 Minutes', 1200)], + 'default': 0 + }), + ('Respawn Times', { + 'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0), + ('Long', 2.0), ('Longer', 4.0)], + 'default': 1.0 + }), + ('Epic Mode', { + 'default': False + }), + ] def __init__(self, settings: Dict[str, Any]): from bastd.actor.scoreboard import Scoreboard super().__init__(settings) self._scoreboard = Scoreboard() - if self.settings_raw['Epic Mode']: - self.slow_motion = True self._last_score_time = 0.0 self._score_sound = ba.getsound('score') self._base_region_materials: Dict[int, ba.Material] = {} + self._epic_mode = bool(settings['Epic Mode']) + self._score_to_win = int(settings['Score to Win']) + self._time_limit = float(settings['Time Limit']) + + # Base class overrides + self.slow_motion = self._epic_mode + self.default_music = (ba.MusicType.EPIC if self._epic_mode else + ba.MusicType.FORWARD_MARCH) def get_instance_description(self) -> Union[str, Sequence]: - if self.settings_raw['Score to Win'] == 1: + if self._score_to_win == 1: return 'Touch the enemy flag.' - return ('Touch the enemy flag ${ARG1} times.', - self.settings_raw['Score to Win']) + return 'Touch the enemy flag ${ARG1} times.', self._score_to_win def get_instance_scoreboard_description(self) -> Union[str, Sequence]: - if self.settings_raw['Score to Win'] == 1: + if self._score_to_win == 1: return 'touch 1 flag' - return 'touch ${ARG1} flags', self.settings_raw['Score to Win'] + return 'touch ${ARG1} flags', self._score_to_win - def on_transition_in(self) -> None: - self.default_music = (ba.MusicType.EPIC - if self.settings_raw['Epic Mode'] else - ba.MusicType.FORWARD_MARCH) - super().on_transition_in() + def create_team(self, sessionteam: ba.SessionTeam) -> Team: + base_pos = self.map.get_flag_position(sessionteam.id) + ba.newnode('light', + attrs={ + 'position': base_pos, + 'intensity': 0.6, + 'height_attenuated': False, + 'volume_intensity_scale': 0.1, + 'radius': 0.1, + 'color': sessionteam.color + }) + self.project_flag_stand(base_pos) + flag = Flag(touchable=False, + position=base_pos, + color=sessionteam.color) + team = Team(base_pos=base_pos, flag=flag) - def on_team_join(self, team: ba.Team) -> None: - team.gamedata['score'] = 0 + mat = self._base_region_materials[sessionteam.id] = ba.Material() + mat.add_actions( + conditions=('they_have_material', ba.sharedobj('player_material')), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', False), + ('call', 'at_connect', ba.Call(self._handle_base_collide, + team)), + ), + ) + + ba.newnode( + 'region', + owner=flag.node, + attrs={ + 'position': (base_pos[0], base_pos[1] + 0.75, base_pos[2]), + 'scale': (0.5, 0.5, 0.5), + 'type': 'sphere', + 'materials': [self._base_region_materials[sessionteam.id]] + }) + + return team + + def on_team_join(self, team: Team) -> None: + # Can't do this in create_team because the team's color/etc. have + # not been wired up yet at that point. self._update_scoreboard() def on_begin(self) -> None: - from bastd.actor.flag import Flag super().on_begin() - self.setup_standard_time_limit(self.settings_raw['Time Limit']) + self.setup_standard_time_limit(self._time_limit) self.setup_standard_powerup_drops() - for team in self.teams: - mat = self._base_region_materials[team.id] = ba.Material() - mat.add_actions(conditions=('they_have_material', - ba.sharedobj('player_material')), - actions=(('modify_part_collision', 'collide', - True), ('modify_part_collision', - 'physical', False), - ('call', 'at_connect', - ba.Call(self._handle_base_collide, - team)))) - - # Create a score region and flag for each team. - for team in self.teams: - team.gamedata['base_pos'] = self.map.get_flag_position(team.id) - - ba.newnode('light', - attrs={ - 'position': team.gamedata['base_pos'], - 'intensity': 0.6, - 'height_attenuated': False, - 'volume_intensity_scale': 0.1, - 'radius': 0.1, - 'color': team.color - }) - - self.project_flag_stand(team.gamedata['base_pos']) - team.gamedata['flag'] = Flag(touchable=False, - position=team.gamedata['base_pos'], - color=team.color) - basepos = team.gamedata['base_pos'] - ba.newnode('region', - owner=team.gamedata['flag'].node, - attrs={ - 'position': - (basepos[0], basepos[1] + 0.75, basepos[2]), - 'scale': (0.5, 0.5, 0.5), - 'type': 'sphere', - 'materials': [self._base_region_materials[team.id]] - }) def handlemessage(self, msg: Any) -> Any: if isinstance(msg, PlayerSpazDeathMessage): super().handlemessage(msg) # Augment standard. - self.respawn_player(msg.getspaz(self).player) + self.respawn_player(msg.playerspaz(self).player) else: super().handlemessage(msg) - def _flash_base(self, team: ba.Team, length: float = 2.0) -> None: + def _flash_base(self, team: Team, length: float = 2.0) -> None: light = ba.newnode('light', attrs={ - 'position': team.gamedata['base_pos'], + 'position': team.base_pos, 'height_attenuated': False, 'radius': 0.3, 'color': team.color @@ -166,7 +189,7 @@ class AssaultGame(ba.TeamGameActivity[ba.Player, ba.Team]): ba.animate(light, 'intensity', {0: 0, 0.25: 2.0, 0.5: 0}, loop=True) ba.timer(length, light.delete) - def _handle_base_collide(self, team: ba.Team) -> None: + 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') @@ -179,8 +202,10 @@ class AssaultGame(ba.TeamGameActivity[ba.Player, ba.Team]): if not player or not player.actor: return + assert isinstance(player, Player) + # If its another team's player, they scored. - player_team: ba.Team[ba.Player] = player.team + player_team = player.team if player_team is not team: # Prevent multiple simultaneous scores. @@ -233,19 +258,18 @@ class AssaultGame(ba.TeamGameActivity[ba.Player, ba.Team]): if player.actor: player.actor.handlemessage(ba.CelebrateMessage(2.0)) - player_team.gamedata['score'] += 1 + player_team.score += 1 self._update_scoreboard() - if (player_team.gamedata['score'] >= - self.settings_raw['Score to Win']): + if player_team.score >= self._score_to_win: self.end_game() def end_game(self) -> None: results = ba.TeamGameResults() for team in self.teams: - results.set_team_score(team, team.gamedata['score']) + results.set_team_score(team, team.score) self.end(results=results) def _update_scoreboard(self) -> None: for team in self.teams: - self._scoreboard.set_team_value(team, team.gamedata['score'], - self.settings_raw['Score to Win']) + self._scoreboard.set_team_value(team, team.score, + self._score_to_win) diff --git a/assets/src/ba_data/python/bastd/game/capturetheflag.py b/assets/src/ba_data/python/bastd/game/capturetheflag.py index 8934eede..50c1e09a 100644 --- a/assets/src/ba_data/python/bastd/game/capturetheflag.py +++ b/assets/src/ba_data/python/bastd/game/capturetheflag.py @@ -268,6 +268,8 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): return team def on_team_join(self, team: Team) -> None: + # Can't do this in create_team because the team's color/etc. have + # not been wired up yet at that point. self._spawn_flag_for_team(team) self._update_scoreboard() @@ -554,7 +556,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): if isinstance(msg, PlayerSpazDeathMessage): # Augment standard behavior. super().handlemessage(msg) - self.respawn_player(msg.getspaz(self).player) + self.respawn_player(msg.playerspaz(self).player) elif isinstance(msg, stdflag.FlagDeathMessage): assert isinstance(msg.flag, CTFFlag) ba.timer(0.1, ba.Call(self._spawn_flag_for_team, msg.flag.team)) diff --git a/assets/src/ba_data/python/bastd/game/chosenone.py b/assets/src/ba_data/python/bastd/game/chosenone.py index 65b41a4c..ce87a50d 100644 --- a/assets/src/ba_data/python/bastd/game/chosenone.py +++ b/assets/src/ba_data/python/bastd/game/chosenone.py @@ -327,7 +327,7 @@ class ChosenOneGame(ba.TeamGameActivity[ba.Player, ba.Team]): if isinstance(msg, playerspaz.PlayerSpazDeathMessage): # Augment standard behavior. super().handlemessage(msg) - player = msg.getspaz(self).player + player = msg.playerspaz(self).player if player is self._get_chosen_one_player(): killerplayer = msg.killerplayer self._set_chosen_one_player(None if ( diff --git a/assets/src/ba_data/python/bastd/game/conquest.py b/assets/src/ba_data/python/bastd/game/conquest.py index 2ffac1a0..361039bb 100644 --- a/assets/src/ba_data/python/bastd/game/conquest.py +++ b/assets/src/ba_data/python/bastd/game/conquest.py @@ -254,7 +254,7 @@ class ConquestGame(ba.TeamGameActivity[ba.Player, ba.Team]): super().handlemessage(msg) # Respawn only if this team has a flag. - player = msg.getspaz(self).player + player = msg.playerspaz(self).player if player.team.gamedata['flags_held'] > 0: self.respawn_player(player) else: diff --git a/assets/src/ba_data/python/bastd/game/deathmatch.py b/assets/src/ba_data/python/bastd/game/deathmatch.py index 3b5237c2..e6d64d2e 100644 --- a/assets/src/ba_data/python/bastd/game/deathmatch.py +++ b/assets/src/ba_data/python/bastd/game/deathmatch.py @@ -145,7 +145,7 @@ class DeathMatchGame(ba.TeamGameActivity[ba.Player, ba.Team]): # Augment standard behavior. super().handlemessage(msg) - player = msg.getspaz(self).player + player = msg.playerspaz(self).player self.respawn_player(player) killer = msg.killerplayer diff --git a/assets/src/ba_data/python/bastd/game/easteregghunt.py b/assets/src/ba_data/python/bastd/game/easteregghunt.py index eebdef24..41f61ece 100644 --- a/assets/src/ba_data/python/bastd/game/easteregghunt.py +++ b/assets/src/ba_data/python/bastd/game/easteregghunt.py @@ -212,7 +212,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[ba.Player, ba.Team]): # Augment standard behavior. super().handlemessage(msg) - player = msg.getspaz(self).getplayer() + player = msg.playerspaz(self).getplayer() if not player: return self.stats.player_was_killed(player) diff --git a/assets/src/ba_data/python/bastd/game/elimination.py b/assets/src/ba_data/python/bastd/game/elimination.py index 047db791..96e9bb91 100644 --- a/assets/src/ba_data/python/bastd/game/elimination.py +++ b/assets/src/ba_data/python/bastd/game/elimination.py @@ -491,7 +491,7 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]): # Augment standard behavior. super().handlemessage(msg) - player = msg.getspaz(self).player + player = msg.playerspaz(self).player player.gamedata['lives'] -= 1 if player.gamedata['lives'] < 0: diff --git a/assets/src/ba_data/python/bastd/game/football.py b/assets/src/ba_data/python/bastd/game/football.py index f5e630ca..1b98def2 100644 --- a/assets/src/ba_data/python/bastd/game/football.py +++ b/assets/src/ba_data/python/bastd/game/football.py @@ -274,7 +274,7 @@ class FootballTeamGame(ba.TeamGameActivity[ba.Player, ba.Team]): elif isinstance(msg, playerspaz.PlayerSpazDeathMessage): # Augment standard behavior. super().handlemessage(msg) - self.respawn_player(msg.getspaz(self).player) + self.respawn_player(msg.playerspaz(self).player) # Respawn dead flags. elif isinstance(msg, stdflag.FlagDeathMessage): @@ -812,7 +812,7 @@ class FootballCoopGame(ba.CoopGameActivity[ba.Player, ba.Team]): from bastd.actor import respawnicon # Respawn dead players. - player = msg.getspaz(self).player + player = msg.playerspaz(self).player self.stats.player_was_killed(player) assert self.initial_player_info is not None respawn_time = 2.0 + len(self.initial_player_info) * 1.0 diff --git a/assets/src/ba_data/python/bastd/game/hockey.py b/assets/src/ba_data/python/bastd/game/hockey.py index daa2b354..fa611c6b 100644 --- a/assets/src/ba_data/python/bastd/game/hockey.py +++ b/assets/src/ba_data/python/bastd/game/hockey.py @@ -341,7 +341,7 @@ class HockeyGame(ba.TeamGameActivity[ba.Player, ba.Team]): if isinstance(msg, playerspaz.PlayerSpazDeathMessage): # Augment standard behavior... super().handlemessage(msg) - self.respawn_player(msg.getspaz(self).player) + self.respawn_player(msg.playerspaz(self).player) # Respawn dead pucks. elif isinstance(msg, PuckDeathMessage): diff --git a/assets/src/ba_data/python/bastd/game/keepaway.py b/assets/src/ba_data/python/bastd/game/keepaway.py index 2a8839e9..41ea50a5 100644 --- a/assets/src/ba_data/python/bastd/game/keepaway.py +++ b/assets/src/ba_data/python/bastd/game/keepaway.py @@ -271,7 +271,7 @@ class KeepAwayGame(ba.TeamGameActivity[ba.Player, ba.Team]): if isinstance(msg, playerspaz.PlayerSpazDeathMessage): # Augment standard behavior. super().handlemessage(msg) - self.respawn_player(msg.getspaz(self).player) + self.respawn_player(msg.playerspaz(self).player) elif isinstance(msg, stdflag.FlagDeathMessage): self._spawn_flag() elif isinstance( diff --git a/assets/src/ba_data/python/bastd/game/kingofthehill.py b/assets/src/ba_data/python/bastd/game/kingofthehill.py index 3d0f5d8a..c0fc84bf 100644 --- a/assets/src/ba_data/python/bastd/game/kingofthehill.py +++ b/assets/src/ba_data/python/bastd/game/kingofthehill.py @@ -281,7 +281,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[ba.Player, ba.Team]): super().handlemessage(msg) # Augment default. # No longer can count as at_flag once dead. - player = msg.getspaz(self).player + player = msg.playerspaz(self).player player.gamedata['at_flag'] = 0 self._update_flag_state() self.respawn_player(player) diff --git a/assets/src/ba_data/python/bastd/game/meteorshower.py b/assets/src/ba_data/python/bastd/game/meteorshower.py index e87e6249..462ea14a 100644 --- a/assets/src/ba_data/python/bastd/game/meteorshower.py +++ b/assets/src/ba_data/python/bastd/game/meteorshower.py @@ -173,7 +173,7 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): # Record the player's moment of death. # assert isinstance(msg.spaz.player - msg.getspaz(self).player.death_time = curtime + msg.playerspaz(self).player.death_time = curtime # In co-op mode, end the game the instant everyone dies # (more accurate looking). diff --git a/assets/src/ba_data/python/bastd/game/ninjafight.py b/assets/src/ba_data/python/bastd/game/ninjafight.py index 24a262b9..04c44ef2 100644 --- a/assets/src/ba_data/python/bastd/game/ninjafight.py +++ b/assets/src/ba_data/python/bastd/game/ninjafight.py @@ -148,7 +148,8 @@ class NinjaFightGame(ba.TeamGameActivity[ba.Player, ba.Team]): # A player has died. if isinstance(msg, playerspaz.PlayerSpazDeathMessage): super().handlemessage(msg) # do standard stuff - self.respawn_player(msg.getspaz(self).player) # kick off a respawn + self.respawn_player( + msg.playerspaz(self).player) # kick off a respawn # A spaz-bot has died. elif isinstance(msg, spazbot.SpazBotDeathMessage): diff --git a/assets/src/ba_data/python/bastd/game/onslaught.py b/assets/src/ba_data/python/bastd/game/onslaught.py index 88d62efa..192da89f 100644 --- a/assets/src/ba_data/python/bastd/game/onslaught.py +++ b/assets/src/ba_data/python/bastd/game/onslaught.py @@ -1163,7 +1163,7 @@ class OnslaughtGame(ba.CoopGameActivity[ba.Player, ba.Team]): elif isinstance(msg, playerspaz.PlayerSpazDeathMessage): super().handlemessage(msg) # Augment standard behavior. - player = msg.getspaz(self).getplayer() + player = msg.playerspaz(self).getplayer() assert player is not None self._a_player_has_been_hurt = True diff --git a/assets/src/ba_data/python/bastd/game/race.py b/assets/src/ba_data/python/bastd/game/race.py index 89eb04aa..0260de26 100644 --- a/assets/src/ba_data/python/bastd/game/race.py +++ b/assets/src/ba_data/python/bastd/game/race.py @@ -733,7 +733,7 @@ class RaceGame(ba.TeamGameActivity[ba.Player, ba.Team]): if isinstance(msg, PlayerSpazDeathMessage): # Augment default behavior. super().handlemessage(msg) - player = msg.getspaz(self).getplayer() + player = msg.playerspaz(self).getplayer() if not player: ba.print_error('got no player in PlayerSpazDeathMessage') return diff --git a/assets/src/ba_data/python/bastd/game/runaround.py b/assets/src/ba_data/python/bastd/game/runaround.py index 5c6e2f06..302494a2 100644 --- a/assets/src/ba_data/python/bastd/game/runaround.py +++ b/assets/src/ba_data/python/bastd/game/runaround.py @@ -1127,7 +1127,7 @@ class RunaroundGame(ba.CoopGameActivity[ba.Player, ba.Team]): elif isinstance(msg, playerspaz.PlayerSpazDeathMessage): from bastd.actor import respawnicon self._a_player_has_been_killed = True - player = msg.getspaz(self).getplayer() + player = msg.playerspaz(self).getplayer() if player is None: ba.print_error('FIXME: getplayer() should no' ' longer ever be returning None') diff --git a/assets/src/ba_data/python/bastd/game/targetpractice.py b/assets/src/ba_data/python/bastd/game/targetpractice.py index ad3d1fce..505dec52 100644 --- a/assets/src/ba_data/python/bastd/game/targetpractice.py +++ b/assets/src/ba_data/python/bastd/game/targetpractice.py @@ -187,7 +187,7 @@ class TargetPracticeGame(ba.TeamGameActivity[ba.Player, ba.Team]): # When players die, respawn them. if isinstance(msg, playerspaz.PlayerSpazDeathMessage): super().handlemessage(msg) # Do standard stuff. - player = msg.getspaz(self).getplayer() + player = msg.playerspaz(self).getplayer() assert player is not None self.respawn_player(player) # Kick off a respawn. elif isinstance(msg, Target.TargetHitMessage): diff --git a/assets/src/ba_data/python/bastd/game/thelaststand.py b/assets/src/ba_data/python/bastd/game/thelaststand.py index a59fbbec..2cc04437 100644 --- a/assets/src/ba_data/python/bastd/game/thelaststand.py +++ b/assets/src/ba_data/python/bastd/game/thelaststand.py @@ -257,7 +257,7 @@ class TheLastStandGame(ba.CoopGameActivity[ba.Player, ba.Team]): def handlemessage(self, msg: Any) -> Any: if isinstance(msg, playerspaz.PlayerSpazDeathMessage): - player = msg.getspaz(self).getplayer() + player = msg.playerspaz(self).getplayer() if player is None: ba.print_error('FIXME: getplayer() should no longer ' 'ever be returning None.')