Modernized assault code a bit

This commit is contained in:
Eric Froemling 2020-05-18 16:39:30 -07:00
parent 6d0b257934
commit 0742bce678
20 changed files with 132 additions and 104 deletions

View File

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

View File

@ -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():

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.')