more game modernizing and bug fixes

This commit is contained in:
Eric Froemling 2020-05-21 01:14:58 -07:00
parent 06600d9eeb
commit 9d7085cb31
19 changed files with 205 additions and 220 deletions

View File

@ -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/66/68/37e1f6d2afd5d6a4cbbcebba2f3e",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/dc/d4/f954892306c82ca4d9c74d335c15",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/4d/b8/4cfc2035ec4cdeba78be2aee8aff",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3f/98/9edf61a1b38432213e93b9342a4e",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/86/43/f76e498f45bb42f2383986d3c15b",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a1/d6/a9dd60f83d58eb09b1b4c0771588",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d8/4f/9ded4658cf6e8d8d7fdf9477ae86",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a1/6b/56d9fa2709eb43be73c00aacb1b5",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/a9/41/2e78f2f5dfa4273ce70fc5a59e0e",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/9e/3d/12c0ba5235b6750ec0f37726de6e",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/8f/9c/edea76ee92634ef9565988c9ef6e",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/35/86/2aeb9cfac9f7639676e149e1323b"
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f7/15/2016db42f7d9d0cd341b29d4cf85",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/74/7a/5361dbc0c2dae7ab52c5501fc21d",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/04/9f/20f97048d27b7eff224cc280a5c3",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d2/d1/c023095a1fc0f2683e6214142ea2",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4e/50/1b6f2eb8ec843e1cf3a96ab0e458",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d6/0a/b979206cca7a41baa8c81d8f6225",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/37/23/4dcfca7889611fd21ece1c39bec5",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7e/6a/093312688ce930585cf2651860b3",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/81/73/63cb8f8ce715a5156bbd3127cee1",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/2c/de/9d59be806cb4a45fa50414276eb1",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f6/af/3eb4c3046770371ea72717e3442e",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/5f/3b/645e268f5f3a74acd23edc46ba2f"
}

View File

@ -52,7 +52,7 @@
<inspection_tool class="PyTypeCheckerInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyTypeHintsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnreachableCodeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="false">
<scope name="PyIgnoreUnresolved" level="WARNING" enabled="false">
<option name="ignoredIdentifiers">
<list>

View File

@ -698,7 +698,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
'ba.Activity.on_begin() never got called for ' + str(self) +
'; did you forget to call it in your on_begin override?')
# noinspection PyUnresolvedReferences
def _setup_player_and_team_types(self) -> None:
"""Pull player and team types from our typing.Generic params."""

View File

@ -197,7 +197,6 @@ def animate(node: ba.Node,
if _ba.app.test_build and not suppress_format_warning:
for item in items:
# (PyCharm seems to think item is a float, not a tuple)
# noinspection PyUnresolvedReferences
_ba.time_format_check(timeformat, item[0])
curve = _ba.newnode('animcurve',
@ -223,7 +222,6 @@ def animate(node: ba.Node,
# get disconnected.
if not loop:
# (PyCharm seems to think item is a float, not a tuple)
# noinspection PyUnresolvedReferences
_ba.timer(int(mult * items[-1][0]) + 1000,
curve.delete,
timeformat=TimeFormat.MILLISECONDS)
@ -264,7 +262,6 @@ def animate_array(node: ba.Node,
if _ba.app.test_build and not suppress_format_warning:
for item in items:
# (PyCharm seems to think item is a float, not a tuple)
# noinspection PyUnresolvedReferences
_ba.time_format_check(timeformat, item[0])
if timeformat is TimeFormat.SECONDS:
@ -291,7 +288,6 @@ def animate_array(node: ba.Node,
# curve after its done its job.
if not loop:
# (PyCharm seems to think item is a float, not a tuple)
# noinspection PyUnresolvedReferences
_ba.timer(int(mult * items[-1][0]) + 1000,
curve.delete,
timeformat=TimeFormat.MILLISECONDS)
@ -303,7 +299,6 @@ def animate_array(node: ba.Node,
# once we get disconnected.
if not loop:
# (PyCharm seems to think item is a float, not a tuple)
# noinspection PyUnresolvedReferences
_ba.timer(int(mult * items[-1][0]) + 1000,
combine.delete,
timeformat=TimeFormat.MILLISECONDS)

View File

@ -114,7 +114,6 @@ class ServerCallThread(threading.Thread):
_ba.set_thread_name('BA_ServerCallThread')
# Seems pycharm doesn't know about urllib.parse.
# noinspection PyUnresolvedReferences
parse = urllib.parse
if self._request_type == 'get':
response = urllib.request.urlopen(

View File

@ -31,7 +31,6 @@ if TYPE_CHECKING:
@dataclass
class PowerupMessage:
# noinspection PyUnresolvedReferences
"""A message telling an object to accept a powerup.
Category: Message Classes

View File

@ -115,8 +115,6 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
def _get_player_score_set_entry(
player: ba.SessionPlayer) -> Optional[ba.PlayerRecord]:
for p_rec in valid_players:
# PyCharm incorrectly thinks valid_players is a List[str]
# noinspection PyUnresolvedReferences
if p_rec[1].player is player:
return p_rec[1]
return None

View File

@ -53,7 +53,7 @@ class _Entry:
self._flash_timer: Optional[ba.Timer] = None
self._flash_counter: Optional[int] = None
self._flash_colors: Optional[bool] = None
self._score: Optional[int] = None
self._score: Optional[float] = None
safe_team_color = ba.safecolor(team.color, target_intensity=1.0)
@ -258,8 +258,8 @@ class _Entry:
self._set_flash_colors(not self._flash_colors)
def set_value(self,
score: int,
max_score: int = None,
score: float,
max_score: float = None,
countdown: bool = False,
flash: bool = True,
show_value: bool = True) -> None:
@ -360,8 +360,8 @@ class Scoreboard:
def set_team_value(self,
team: ba.Team,
score: int,
max_score: int = None,
score: float,
max_score: float = None,
countdown: bool = False,
flash: bool = True,
show_value: bool = True) -> None:
@ -373,7 +373,8 @@ class Scoreboard:
if '_scoreboard_entry' in team.gamedata:
raise Exception('existing _EntryProxy found')
team.gamedata['_scoreboard_entry'] = _EntryProxy(self, team)
# now set the entry..
# Now set the entry.
self._entries[team.id].set_value(score=score,
max_score=max_score,
countdown=countdown,

View File

@ -31,6 +31,7 @@ from typing import TYPE_CHECKING
import ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.flag import Flag
from bastd.actor.scoreboard import Scoreboard
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Sequence, Union
@ -85,7 +86,6 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]):
return ba.getmaps('team_flag')
def __init__(self, settings: Dict[str, Any]):
from bastd.actor.scoreboard import Scoreboard
super().__init__(settings)
self._scoreboard = Scoreboard()
self._last_score_time = 0.0

View File

@ -62,8 +62,7 @@ class CTFFlag(stdflag.Flag):
def reset_return_times(self) -> None:
"""Clear flag related times in the activity."""
self.time_out_respawn_time = int(
self.activity.settings_raw['Flag Idle Return Time'])
self.time_out_respawn_time = int(self.activity.flag_idle_return_time)
self.touch_return_time = float(self.activity.flag_touch_return_time)
@property
@ -167,21 +166,17 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
self._all_bases_material = ba.Material()
self._last_home_flag_notice_print_time = 0.0
self._score_to_win = int(settings['Score to Win'])
self._flag_touch_return_time = float(
settings['Flag Touch Return Time'])
self._epic_mode = bool(settings['Epic Mode'])
self._time_limit = float(settings['Time Limit'])
self.flag_touch_return_time = float(settings['Flag Touch Return Time'])
self.flag_idle_return_time = float(settings['Flag Idle return Time'])
# Base class overrides
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.FLAG_CATCHER)
@property
def flag_touch_return_time(self) -> float:
"""How long a flag must be touched for to return it to base."""
return self._flag_touch_return_time
def get_instance_description(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'Steal the enemy flag.'
@ -440,7 +435,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
def _award_players_touching_own_flag(self, team: Team) -> None:
for player in team.players:
if player.touching_own_flag > 0:
return_score = 10 + 5 * int(self._flag_touch_return_time)
return_score = 10 + 5 * int(self.flag_touch_return_time)
self.stats.player_scored(player,
return_score,
screenmessage=False)
@ -468,7 +463,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
# If return-time is zero, just kill it immediately.. otherwise keep
# track of touches and count down.
if float(self._flag_touch_return_time) <= 0.0:
if float(self.flag_touch_return_time) <= 0.0:
assert team.flag is not None
if not team.home_flag_at_base and team.flag.held_count == 0:

View File

@ -33,6 +33,7 @@ from bastd.actor.flag import Flag
if TYPE_CHECKING:
from typing import Any, Optional, Type, List, Dict, Sequence, Union
from bastd.actor.respawnicon import RespawnIcon
class ConquestFlag(Flag):
@ -57,10 +58,31 @@ class ConquestFlag(Flag):
class Player(ba.Player['Team']):
"""Our player type for this game."""
@property
def respawn_timer(self) -> Optional[ba.Timer]:
"""Type safe access to standard respawn timer."""
return self.gamedata.get('respawn_timer', None)
@respawn_timer.setter
def respawn_timer(self, value: Optional[ba.Timer]) -> None:
self.gamedata['respawn_timer'] = value
@property
def respawn_icon(self) -> Optional[RespawnIcon]:
"""Type safe access to standard respawn icon."""
return self.gamedata.get('respawn_icon', None)
@respawn_icon.setter
def respawn_icon(self, value: Optional[RespawnIcon]) -> None:
self.gamedata['respawn_icon'] = value
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.flags_held = 0
# ba_meta export game
class ConquestGame(ba.TeamGameActivity[Player, Team]):
@ -96,13 +118,18 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
def __init__(self, settings: Dict[str, Any]):
from bastd.actor.scoreboard import Scoreboard
super().__init__(settings)
if self.settings_raw['Epic Mode']:
self.slow_motion = True
self._scoreboard = Scoreboard()
self._score_sound = ba.getsound('score')
self._swipsound = ba.getsound('swip')
self._extraflagmat = ba.Material()
self._flags: List[ConquestFlag] = []
self._epic_mode = bool(settings['Epic Mode'])
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.GRAND_ROMP)
# We want flags to tell us they've been hit but not react physically.
self._extraflagmat.add_actions(
@ -116,27 +143,20 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'secure all ${ARG1} flags', len(self.map.flag_points)
def on_transition_in(self) -> None:
self.default_music = (ba.MusicType.EPIC
if self.settings_raw['Epic Mode'] else
ba.MusicType.GRAND_ROMP)
super().on_transition_in()
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scores()
team.gamedata['flags_held'] = 0
def on_player_join(self, player: Player) -> None:
player.gamedata['respawn_timer'] = None
player.respawn_timer = None
# Only spawn if this player's team has a flag currently.
if player.team.gamedata['flags_held'] > 0:
if player.team.flags_held > 0:
self.spawn_player(player)
def on_begin(self) -> None:
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()
# Set up flags with marker lights.
@ -177,27 +197,27 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
def _update_scores(self) -> None:
for team in self.teams:
team.gamedata['flags_held'] = 0
team.flags_held = 0
for flag in self._flags:
if flag.team is not None:
flag.team.gamedata['flags_held'] += 1
flag.team.flags_held += 1
for team in self.teams:
# If a team finds themselves with no flags, cancel all
# outstanding spawn-timers.
if team.gamedata['flags_held'] == 0:
if team.flags_held == 0:
for player in team.players:
player.gamedata['respawn_timer'] = None
player.gamedata['respawn_icon'] = None
if team.gamedata['flags_held'] == len(self._flags):
player.respawn_timer = None
player.respawn_icon = None
if team.flags_held == len(self._flags):
self.end_game()
self._scoreboard.set_team_value(team, team.gamedata['flags_held'],
self._scoreboard.set_team_value(team, team.flags_held,
len(self._flags))
def end_game(self) -> None:
results = ba.TeamGameResults()
for team in self.teams:
results.set_team_score(team, team.gamedata['flags_held'])
results.set_team_score(team, team.flags_held)
self.end(results=results)
def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None:
@ -239,7 +259,7 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
if (otherplayer.team is flag.team
and otherplayer.actor is not None
and not otherplayer.is_alive()
and otherplayer.gamedata['respawn_timer'] is None):
and otherplayer.respawn_timer is None):
self.spawn_player(otherplayer)
def handlemessage(self, msg: Any) -> Any:
@ -249,10 +269,10 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
# Respawn only if this team has a flag.
player = msg.getplayer(Player)
if player.team.gamedata['flags_held'] > 0:
if player.team.flags_held > 0:
self.respawn_player(player)
else:
player.gamedata['respawn_timer'] = None
player.respawn_timer = None
else:
super().handlemessage(msg)

View File

@ -34,6 +34,7 @@ from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.spazbot import BotSet, BouncyBot, SpazBotDeathMessage
from bastd.actor.onscreencountdown import OnScreenCountdown
from bastd.actor.scoreboard import Scoreboard
from bastd.actor.respawnicon import RespawnIcon
if TYPE_CHECKING:
from typing import Any, Type, Dict, List, Tuple, Optional
@ -42,10 +43,17 @@ if TYPE_CHECKING:
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.respawn_timer: Optional[ba.Timer] = None
self.respawn_icon: Optional[RespawnIcon] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game
class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
@ -88,15 +96,10 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
self._countdown: Optional[OnScreenCountdown] = None
self._bots: Optional[BotSet] = None
# Called when our game is transitioning in but not ready to start.
# ..we can go ahead and set our music and whatnot.
def on_transition_in(self) -> None:
# Base class overrides
self.default_music = ba.MusicType.FORWARD_MARCH
super().on_transition_in()
def on_team_join(self, team: Team) -> None:
team.gamedata['score'] = 0
if self.has_begun():
self._update_scoreboard()
@ -142,7 +145,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
player = (spaz.getplayer()
if hasattr(spaz, 'getplayer') else None)
if player and egg:
player.team.gamedata['score'] += 1
player.team.score += 1
# Displays a +1 (and adds to individual player score in
# teams mode).
@ -201,7 +204,6 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
# Respawn dead players.
if isinstance(msg, ba.PlayerDiedMessage):
from bastd.actor import respawnicon
# Augment standard behavior.
super().handlemessage(msg)
@ -213,10 +215,9 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
# Respawn them shortly.
assert self.initial_player_info is not None
respawn_time = 2.0 + len(self.initial_player_info) * 1.0
player.gamedata['respawn_timer'] = ba.Timer(
player.respawn_timer = ba.Timer(
respawn_time, ba.Call(self.spawn_player_if_exists, player))
player.gamedata['respawn_icon'] = respawnicon.RespawnIcon(
player, respawn_time)
player.respawn_icon = RespawnIcon(player, respawn_time)
# Whenever our evil bunny dies, respawn him and spew some eggs.
elif isinstance(msg, SpazBotDeathMessage):
@ -235,12 +236,12 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(team, team.gamedata['score'])
self._scoreboard.set_team_value(team, team.score)
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)

View File

@ -161,6 +161,12 @@ class Icon(ba.Actor):
if lives == 0:
ba.timer(0.6, self.update_for_lives)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
self.node.delete()
return None
return super().handlemessage(msg)
class Player(ba.Player['Team']):
"""Our player type for this game."""

View File

@ -69,10 +69,17 @@ class FootballFlag(stdflag.Flag):
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.respawn_timer: Optional[ba.Timer] = None
self.respawn_icon: Optional[RespawnIcon] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game
class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
@ -130,11 +137,13 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
self._flag: Optional[FootballFlag] = None
self._flag_respawn_timer: Optional[ba.Timer] = None
self._flag_respawn_light: Optional[ba.NodeActor] = None
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
def get_instance_description(self) -> Union[str, Sequence]:
touchdowns = self.settings_raw['Score to Win'] / 7
touchdowns = self._score_to_win / 7
# NOTE: if use just touchdowns = self.settings_raw['Score to Win'] // 7
# NOTE: if use just touchdowns = self._score_to_win // 7
# and we will need to score, for example, 27 points,
# we will be required to score 3 (not 4) goals ..
touchdowns = math.ceil(touchdowns)
@ -143,7 +152,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
return 'Score a touchdown.'
def get_instance_description_short(self) -> Union[str, Sequence]:
touchdowns = self.settings_raw['Score to Win'] / 7
touchdowns = self._score_to_win / 7
touchdowns = math.ceil(touchdowns)
if touchdowns > 1:
return 'score ${ARG1} touchdowns', touchdowns
@ -155,7 +164,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
def on_begin(self) -> None:
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()
self._flag_spawn_pos = (self.map.get_flag_position(None))
self._spawn_flag()
@ -182,7 +191,6 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
ba.playsound(self._chant_sound)
def on_team_join(self, team: Team) -> None:
team.gamedata['score'] = 0
self._update_scoreboard()
def _kill_flag(self) -> None:
@ -203,7 +211,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
break
for team in self.teams:
if team.id == i:
team.gamedata['score'] += 7
team.score += 7
# Tell all players to celebrate.
for player in team.players:
@ -218,8 +226,8 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
self.stats.player_scored(self._flag.last_holding_player,
50,
big_message=True)
# end game if we won
if team.gamedata['score'] >= self.settings_raw['Score to Win']:
# End the game if we won.
if team.score >= self._score_to_win:
self.end_game()
ba.playsound(self._score_sound)
ba.playsound(self._cheer_sound)
@ -242,15 +250,14 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
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, announce_delay=0.8)
def _update_scoreboard(self) -> None:
win_score = self.settings_raw['Score to Win']
assert self._scoreboard is not None
for team in self.teams:
self._scoreboard.set_team_value(team, team.gamedata['score'],
win_score)
self._scoreboard.set_team_value(team, team.score,
self._score_to_win)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, stdflag.FlagPickedUpMessage):
@ -348,7 +355,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
def __init__(self, settings: Dict[str, Any]):
settings['map'] = 'Football Stadium'
super().__init__(settings)
self._preset = self.settings_raw.get('preset', 'rookie')
self._preset = settings.get('preset', 'rookie')
# Load some media we need.
self._cheer_sound = ba.getsound('cheer')
@ -506,7 +513,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
color=(0.5, 0.4, 0.4))
for team in [self.teams[0], self._bot_team]:
team.gamedata['score'] = 0
team.score = 0
self.update_scores()
@ -664,7 +671,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
for team in [self.teams[0], self._bot_team]:
assert team is not None
if team.id == i:
team.gamedata['score'] += 7
team.score += 7
# Tell all players (or bots) to celebrate.
if i == 0:
@ -677,11 +684,11 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
# If the good guys scored, add more enemies.
if i == 0:
if self.teams[0].gamedata['score'] == 7:
if self.teams[0].score == 7:
assert self._bot_types_7 is not None
for bottype in self._bot_types_7:
self._spawn_bot(bottype)
elif self.teams[0].gamedata['score'] == 14:
elif self.teams[0].score == 14:
assert self._bot_types_14 is not None
for bottype in self._bot_types_14:
self._spawn_bot(bottype)
@ -717,7 +724,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
def on_continue(self) -> None:
# Subtract one touchdown from the bots and get them moving again.
assert self._bot_team is not None
self._bot_team.gamedata['score'] -= 7
self._bot_team.score -= 7
self._bots.start_moving()
self.update_scores()
@ -730,9 +737,8 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
for team in [self.teams[0], self._bot_team]:
assert team is not None
assert self._scoreboard is not None
self._scoreboard.set_team_value(team, team.gamedata['score'],
win_score)
if team.gamedata['score'] >= win_score:
self._scoreboard.set_team_value(team, team.score, win_score)
if team.score >= win_score:
if not have_scoring_team:
self.scoring_team = team
if team is self._bot_team:
@ -745,19 +751,19 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
if self._preset in ['rookie', 'rookie_easy']:
self._award_achievement('Rookie Football Victory',
sound=False)
if self._bot_team.gamedata['score'] == 0:
if self._bot_team.score == 0:
self._award_achievement(
'Rookie Football Shutout', sound=False)
elif self._preset in ['pro', 'pro_easy']:
self._award_achievement('Pro Football Victory',
sound=False)
if self._bot_team.gamedata['score'] == 0:
if self._bot_team.score == 0:
self._award_achievement('Pro Football Shutout',
sound=False)
elif self._preset in ['uber', 'uber_easy']:
self._award_achievement('Uber Football Victory',
sound=False)
if self._bot_team.gamedata['score'] == 0:
if self._bot_team.score == 0:
self._award_achievement(
'Uber Football Shutout', sound=False)
if (not self._player_has_dropped_bomb
@ -808,9 +814,9 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
respawn_time = 2.0 + len(self.initial_player_info) * 1.0
# Respawn them shortly.
player.gamedata['respawn_timer'] = ba.Timer(
player.respawn_timer = ba.Timer(
respawn_time, ba.Call(self.spawn_player_if_exists, player))
player.gamedata['respawn_icon'] = RespawnIcon(player, respawn_time)
player.respawn_icon = RespawnIcon(player, respawn_time)
# Augment standard behavior.
super().handlemessage(msg)

View File

@ -71,11 +71,21 @@ class Player(ba.Player['Team']):
def __init__(self) -> None:
self.distance_txt: Optional[ba.Node] = None
self.last_region = 0
self.lap = 0
self.distance = 0.0
self.finished = False
self.rank: Optional[int] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.time: Optional[float] = None
self.lap = 0
self.finished = False
# ba_meta export game
class RaceGame(ba.TeamGameActivity[Player, Team]):
@ -137,8 +147,6 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
self._race_started = False
super().__init__(settings)
self._scoreboard = Scoreboard()
if self.settings_raw['Epic Mode']:
self.slow_motion = True
self._score_sound = ba.getsound('score')
self._swipsound = ba.getsound('swip')
self._last_team_time: Optional[float] = None
@ -157,6 +165,18 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
self._player_order_update_timer: Optional[ba.Timer] = None
self._start_lights: Optional[List[ba.Node]] = None
self._bomb_spawn_timer: Optional[ba.Timer] = None
self._laps = int(settings['Laps'])
self._entire_team_must_finish = bool(
settings.get('Entire Team Must Finish', False))
self._time_limit = float(settings['Time Limit'])
self._mine_spawning = int(settings['Mine Spawning'])
self._bomb_spawning = int(settings['Bomb Spawning'])
self._epic_mode = bool(settings['Epic Mode'])
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC_RACE
if self._epic_mode else ba.MusicType.RACE)
def get_instance_description(self) -> Union[str, Sequence]:
if (isinstance(self.session, ba.DualTeamSession)
@ -175,11 +195,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
return 'run 1 lap'
def on_transition_in(self) -> None:
self.default_music = (ba.MusicType.EPIC_RACE
if self.settings_raw['Epic Mode'] else
ba.MusicType.RACE)
super().on_transition_in()
pts = self.map.get_def_points('race_point')
mat = self.race_region_material = ba.Material()
mat.add_actions(conditions=('they_have_material',
@ -222,7 +238,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
assert isinstance(player, Player)
assert isinstance(region, RaceRegion)
last_region = player.gamedata['last_region']
last_region = player.last_region
this_region = region.index
if last_region != this_region:
@ -242,29 +258,25 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
else:
# If this player is in first, note that this is the
# front-most race-point.
if player.gamedata['rank'] == 0:
if player.rank == 0:
self._front_race_region = this_region
player.gamedata['last_region'] = this_region
player.last_region = this_region
if last_region >= len(self._regions) - 2 and this_region == 0:
team = player.team
player.gamedata['lap'] = min(self.settings_raw['Laps'],
player.gamedata['lap'] + 1)
player.lap = min(self._laps, player.lap + 1)
# In teams mode with all-must-finish on, the team lap
# value is the min of all team players.
# Otherwise its the max.
if isinstance(
self.session, ba.DualTeamSession
) and self.settings_raw.get('Entire Team Must Finish'):
team.gamedata['lap'] = min(
[p.gamedata['lap'] for p in team.players])
if isinstance(self.session, ba.DualTeamSession
) and self._entire_team_must_finish:
team.lap = min([p.lap for p in team.players])
else:
team.gamedata['lap'] = max(
[p.gamedata['lap'] for p in team.players])
team.lap = max([p.lap for p in team.players])
# A player is finishing.
if player.gamedata['lap'] == self.settings_raw['Laps']:
if player.lap == self._laps:
# In teams mode, hand out points based on the order
# players come in.
@ -278,27 +290,22 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# Flash where the player is.
self._flash_player(player, 1.0)
player.gamedata['finished'] = True
player.finished = True
assert player.actor
player.actor.handlemessage(
ba.DieMessage(immediate=True))
# Makes sure noone behind them passes them in rank
# while finishing.
player.gamedata['distance'] = 9999.0
player.distance = 9999.0
# If the whole team has finished the race.
if team.gamedata['lap'] == self.settings_raw['Laps']:
if team.lap == self._laps:
ba.playsound(self._score_sound)
player.team.gamedata['finished'] = True
player.team.finished = True
assert self._timer is not None
cur_time = ba.time(
timeformat=ba.TimeFormat.MILLISECONDS)
start_time = self._timer.getstarttime(
timeformat=ba.TimeFormat.MILLISECONDS)
self._last_team_time = (
player.team.gamedata['time']) = (cur_time -
start_time)
elapsed = ba.time() - self._timer.getstarttime()
self._last_team_time = player.team.time = elapsed
self._check_end_game()
# Team has yet to finish.
@ -321,12 +328,11 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
})
player.actor.node.connectattr(
'torso_position', mathnode, 'input2')
tstr = ba.Lstr(
resource='lapNumberText',
subs=[('${CURRENT}',
str(player.gamedata['lap'] + 1)),
('${TOTAL}',
str(self.settings_raw['Laps']))])
tstr = ba.Lstr(resource='lapNumberText',
subs=[('${CURRENT}',
str(player.lap + 1)),
('${TOTAL}', str(self._laps))
])
txtnode = ba.newnode('text',
owner=mathnode,
attrs={
@ -348,19 +354,8 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
print('Exception printing lap:', exc)
def on_team_join(self, team: Team) -> None:
team.gamedata['time'] = None
team.gamedata['lap'] = 0
team.gamedata['finished'] = False
self._update_scoreboard()
def on_player_join(self, player: Player) -> None:
player.gamedata['last_region'] = 0
player.gamedata['lap'] = 0
player.gamedata['distance'] = 0.0
player.gamedata['finished'] = False
player.gamedata['rank'] = None
super().on_player_join(player)
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
@ -368,20 +363,20 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# is on (otherwise in teams mode everyone could just leave except the
# leading player to win).
if (isinstance(self.session, ba.DualTeamSession)
and self.settings_raw.get('Entire Team Must Finish')):
and self._entire_team_must_finish):
ba.screenmessage(ba.Lstr(
translate=('statements',
'${TEAM} is disqualified because ${PLAYER} left'),
subs=[('${TEAM}', player.team.name),
('${PLAYER}', player.get_name(full=True))]),
color=(1, 1, 0))
player.team.gamedata['finished'] = True
player.team.gamedata['time'] = None
player.team.gamedata['lap'] = 0
player.team.finished = True
player.team.time = None
player.team.lap = 0
ba.playsound(ba.getsound('boo'))
for otherplayer in player.team.players:
otherplayer.gamedata['lap'] = 0
otherplayer.gamedata['finished'] = True
otherplayer.lap = 0
otherplayer.finished = True
try:
if otherplayer.actor is not None:
otherplayer.actor.handlemessage(ba.DieMessage())
@ -393,28 +388,26 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
def _update_scoreboard(self) -> None:
for team in self.teams:
distances = [
player.gamedata['distance'] for player in team.players
]
distances = [player.distance for player in team.players]
if not distances:
teams_dist = 0
teams_dist = 0.0
else:
if (isinstance(self.session, ba.DualTeamSession)
and self.settings_raw.get('Entire Team Must Finish')):
and self._entire_team_must_finish):
teams_dist = min(distances)
else:
teams_dist = max(distances)
self._scoreboard.set_team_value(
team,
teams_dist,
self.settings_raw['Laps'],
flash=(teams_dist >= float(self.settings_raw['Laps'])),
self._laps,
flash=(teams_dist >= float(self._laps)),
show_value=False)
def on_begin(self) -> None:
from bastd.actor.onscreentimer import OnScreenTimer
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()
self._team_finish_pts = 100
@ -434,16 +427,15 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
}))
self._timer = OnScreenTimer()
if self.settings_raw['Mine Spawning'] != 0:
if self._mine_spawning != 0:
self._race_mines = [
RaceMine(point=p, mine=None)
for p in self.map.get_def_points('race_mine')
]
if self._race_mines:
self._race_mine_timer = ba.Timer(
0.001 * self.settings_raw['Mine Spawning'],
self._update_race_mine,
repeat=True)
self._race_mine_timer = ba.Timer(0.001 * self._mine_spawning,
self._update_race_mine,
repeat=True)
self._scoreboard_timer = ba.Timer(0.25,
self._update_scoreboard,
@ -516,16 +508,15 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
try:
assert isinstance(player.actor, PlayerSpaz)
player.actor.connect_controls_to_player()
except Exception as exc:
print('Exception in race player connects:', exc)
except Exception:
ba.print_exception('Error in race player connects')
assert self._timer is not None
self._timer.start()
if self.settings_raw['Bomb Spawning'] != 0:
self._bomb_spawn_timer = ba.Timer(
0.001 * self.settings_raw['Bomb Spawning'],
self._spawn_bomb,
repeat=True)
if self._bomb_spawning != 0:
self._bomb_spawn_timer = ba.Timer(0.001 * self._bomb_spawning,
self._spawn_bomb,
repeat=True)
self._race_started = True
@ -542,7 +533,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
except Exception:
pos = None
if pos is not None:
r_index = player.gamedata['last_region']
r_index = player.last_region
rg1 = self._regions[r_index]
r1pt = ba.Vec3(rg1.pos[:3])
rg2 = self._regions[0] if r_index == len(
@ -550,20 +541,17 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
r2pt = ba.Vec3(rg2.pos[:3])
r2dist = (pos - r2pt).length()
amt = 1.0 - (r2dist / (r2pt - r1pt).length())
amt = player.gamedata['lap'] + (r_index + amt) * (
1.0 / len(self._regions))
player.gamedata['distance'] = amt
amt = player.lap + (r_index + amt) * (1.0 / len(self._regions))
player.distance = amt
# Sort players by distance and update their ranks.
p_list = [(player.gamedata['distance'], player)
for player in self.players]
p_list = [(player.distance, player) for player in self.players]
p_list.sort(reverse=True, key=lambda x: x[0])
for i, plr in enumerate(p_list):
try:
plr[1].gamedata['rank'] = i
plr[1].rank = i
if plr[1].actor:
# noinspection PyUnresolvedReferences
node = plr[1].distance_txt
if node:
node.text = str(i + 1) if plr[1].is_alive() else ''
@ -626,13 +614,13 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
ba.timer(0.95, ba.Call(self._make_mine, m_index))
def spawn_player(self, player: Player) -> ba.Actor:
if player.team.gamedata['finished']:
if player.team.finished:
# FIXME: This is not type-safe!
# This call is expected to always return an Actor!
# Perhaps we need something like can_spawn_player()...
# noinspection PyTypeChecker
return None # type: ignore
pos = self._regions[player.gamedata['last_region']].pos
pos = self._regions[player.last_region].pos
# Don't use the full region so we're less likely to spawn off a cliff.
region_scale = 0.8
@ -674,17 +662,14 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
def _check_end_game(self) -> None:
# If there's no teams left racing, finish.
teams_still_in = len(
[t for t in self.teams if not t.gamedata['finished']])
teams_still_in = len([t for t in self.teams if not t.finished])
if teams_still_in == 0:
self.end_game()
return
# Count the number of teams that have completed the race.
teams_completed = len([
t for t in self.teams
if t.gamedata['finished'] and t.gamedata['time'] is not None
])
teams_completed = len(
[t for t in self.teams if t.finished and t.time is not None])
if teams_completed > 0:
session = self.session
@ -709,22 +694,21 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# Stop updating our time text, and set it to show the exact last
# finish time if we have one. (so users don't get upset if their
# final time differs from what they see onscreen by a tiny bit)
# final time differs from what they see onscreen by a tiny amount)
assert self._timer is not None
if self._timer.has_started():
cur_time = self._timer.getstarttime(
timeformat=ba.TimeFormat.MILLISECONDS)
self._timer.stop(
endtime=None if self._last_team_time is None else (
cur_time + self._last_team_time))
self._timer.getstarttime() + self._last_team_time))
results = ba.TeamGameResults()
for team in self.teams:
if team.gamedata['time'] is not None:
results.set_team_score(team, team.gamedata['time'])
# If game have ended before we
# get any result, use 'fail' screen
if team.time is not None:
# We store time in seconds, but pass a score in milliseconds.
results.set_team_score(team, int(team.time * 1000.0))
else:
results.set_team_score(team, None)
# We don't announce a winner in ffa mode since its probably been a
# while since the first place guy crossed the finish line so it seems
@ -738,10 +722,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# Augment default behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
if not player:
ba.print_error('got no player in PlayerDiedMessage')
return
if not player.gamedata['finished']:
if not player.finished:
self.respawn_player(player, respawn_time=1)
else:
super().handlemessage(msg)

View File

@ -35,7 +35,6 @@ if TYPE_CHECKING:
class HockeyStadium(ba.Map):
"""Stadium map used for ice hockey games."""
# noinspection PyUnresolvedReferences
from bastd.mapdata import hockey_stadium as defs
name = 'Hockey Stadium'
@ -183,7 +182,6 @@ class FootballStadium(ba.Map):
class Bridgit(ba.Map):
"""Map with a narrow bridge in the middle."""
# noinspection PyUnresolvedReferences
from bastd.mapdata import bridgit as defs
name = 'Bridgit'
@ -278,7 +276,6 @@ class Bridgit(ba.Map):
class BigG(ba.Map):
"""Large G shaped map for racing"""
# noinspection PyUnresolvedReferences
from bastd.mapdata import big_g as defs
name = 'Big G'
@ -375,7 +372,6 @@ class BigG(ba.Map):
class Roundabout(ba.Map):
"""CTF map featuring two platforms and a long way around between them"""
# noinspection PyUnresolvedReferences
from bastd.mapdata import roundabout as defs
name = 'Roundabout'
@ -469,7 +465,6 @@ class Roundabout(ba.Map):
class MonkeyFace(ba.Map):
"""Map sorta shaped like a monkey face; teehee!"""
# noinspection PyUnresolvedReferences
from bastd.mapdata import monkey_face as defs
name = 'Monkey Face'
@ -563,7 +558,6 @@ class MonkeyFace(ba.Map):
class ZigZag(ba.Map):
"""A very long zig-zaggy map"""
# noinspection PyUnresolvedReferences
from bastd.mapdata import zig_zag as defs
name = 'Zigzag'
@ -657,7 +651,6 @@ class ZigZag(ba.Map):
class ThePad(ba.Map):
"""A simple square shaped map with a raised edge."""
# noinspection PyUnresolvedReferences
from bastd.mapdata import the_pad as defs
name = 'The Pad'
@ -738,7 +731,6 @@ class ThePad(ba.Map):
class DoomShroom(ba.Map):
"""A giant mushroom. Of doom."""
# noinspection PyUnresolvedReferences
from bastd.mapdata import doom_shroom as defs
name = 'Doom Shroom'
@ -831,7 +823,6 @@ class DoomShroom(ba.Map):
class LakeFrigid(ba.Map):
"""An icy lake fit for racing."""
# noinspection PyUnresolvedReferences
from bastd.mapdata import lake_frigid as defs
name = 'Lake Frigid'
@ -912,7 +903,6 @@ class LakeFrigid(ba.Map):
class TipTop(ba.Map):
"""A pointy map good for king-of-the-hill-ish games."""
# noinspection PyUnresolvedReferences
from bastd.mapdata import tip_top as defs
name = 'Tip Top'
@ -984,7 +974,6 @@ class TipTop(ba.Map):
class CragCastle(ba.Map):
"""A lovely castle map."""
# noinspection PyUnresolvedReferences
from bastd.mapdata import crag_castle as defs
name = 'Crag Castle'
@ -1185,7 +1174,6 @@ class TowerD(ba.Map):
class HappyThoughts(ba.Map):
"""Flying map."""
# noinspection PyUnresolvedReferences
from bastd.mapdata import happy_thoughts as defs
name = 'Happy Thoughts'
@ -1291,7 +1279,6 @@ class HappyThoughts(ba.Map):
class StepRightUp(ba.Map):
"""Wide stepped map good for CTF or Assault."""
# noinspection PyUnresolvedReferences
from bastd.mapdata import step_right_up as defs
name = 'Step Right Up'

View File

@ -268,7 +268,6 @@ class ColorPickerExact(popup.PopupWindow):
# color to the delegate, so start doing that...
self._update_for_color()
# noinspection PyUnresolvedReferences
def _update_for_color(self) -> None:
if not self.root_widget:
return

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-05-20 for Ballistica version 1.5.0 build 20025</em></h4>
<h4><em>last updated on 2020-05-21 for Ballistica version 1.5.0 build 20025</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>

View File

@ -37,7 +37,6 @@ from typing import TYPE_CHECKING
# Pull in some standard snippets we want to expose.
# pylint: disable=unused-import
# noinspection PyUnresolvedReferences
from efrotools.snippets import (
PROJROOT, snippets_main, formatcode, formatscripts, formatmakefile,
cpplint, pylint, runpylint, mypy, runmypy, dmypy, tool_config_install,