This commit is contained in:
indev 2020-04-07 09:26:17 +03:00
commit c07aa0e357
30 changed files with 341 additions and 315 deletions

View File

@ -4114,10 +4114,10 @@
"assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c", "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/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", "assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c4/9f/2df9fcc014404595eaa88a50d62b", "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/01/9f/6ce9331a09f15f2da1087d4ffba9",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/72/c0/fd83de83a099f829444778a65fac", "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/99/74/1709486aaf04f5fdd3f90ca0aab9",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d6/33/b77a77b8d82c616e2a66146cb6b3", "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/27/a6/5749795ec376cb5cdaf652dacdaa",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/cc/a6/f247e2bb7307cf759a791b39d876", "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e9/ef/82e9a50695f333d68847585aa9a6",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/c0/82/dc8e77df214bccf2dead8c25685b", "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/c8/24/ac5bb7409ae0c5274e26a355a526",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/1c/9b/45c1dafbe3e2556b4824ee5010ad" "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/18/2e/cfa42c79a5024bb2111dfe347b51"
} }

View File

@ -1,8 +1,10 @@
name: CI name: CI
on: on:
# Run on pushes and also once per day (in case deps change under us) # Run on pushes, pull-requests, and also once per day
# (in case deps change under us, etc.)
push: push:
pull_request:
schedule: schedule:
# Note: '*' is a special character in YAML so we have to quote the str. # Note: '*' is a special character in YAML so we have to quote the str.
- cron: '0 12 * * *' - cron: '0 12 * * *'

View File

@ -48,6 +48,7 @@
<scope name="UncheckedPython" level="WEAK WARNING" enabled="false" /> <scope name="UncheckedPython" level="WEAK WARNING" enabled="false" />
</inspection_tool> </inspection_tool>
<inspection_tool class="PyTypeCheckerInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="PyTypeCheckerInspection" 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="true">
<scope name="PyIgnoreUnresolved" level="WARNING" enabled="false"> <scope name="PyIgnoreUnresolved" level="WARNING" enabled="false">
<option name="ignoredIdentifiers"> <option name="ignoredIdentifiers">

View File

@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand.
""" """
# (hash we can use to see if this file is out of date) # (hash we can use to see if this file is out of date)
# SOURCES_HASH=92329394206923431348342003830010439152 # SOURCES_HASH=262346642235815908245289158414705543661
# I'm sorry Pylint. I know this file saddens you. Be strong. # I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression # pylint: disable=useless-suppression
@ -3607,20 +3607,20 @@ def submit_analytics_counts() -> None:
def submit_score(game: str, def submit_score(game: str,
config: str, config: str,
name: Any, name: Any,
score: int, score: Optional[int],
callback: Callable, callback: Callable,
friend_callback: Optional[Callable], friend_callback: Optional[Callable],
order: str = 'increasing', order: str = 'increasing',
tournament_id: Optional[str] = None, tournament_id: Optional[str] = None,
score_type: str = 'points', score_type: str = 'points',
campaign: Optional[ba.Campaign] = None, campaign: Optional[str] = None,
level: Optional[ba.Level] = None) -> None: level: Optional[str] = None) -> None:
"""submit_score(game: str, config: str, name: Any, score: int, """submit_score(game: str, config: str, name: Any, score: Optional[int],
callback: Callable, friend_callback: Optional[Callable], callback: Callable, friend_callback: Optional[Callable],
order: str = 'increasing', tournament_id: Optional[str] = None, order: str = 'increasing', tournament_id: Optional[str] = None,
score_type: str = 'points', score_type: str = 'points',
campaign: Optional[ba.Campaign] = None, campaign: Optional[str] = None,
level: Optional[ba.Level] = None) -> None level: Optional[str] = None) -> None
(internal) (internal)

View File

@ -74,11 +74,11 @@ from ba._general import WeakCall, Call
from ba._level import Level from ba._level import Level
from ba._lobby import Lobby, Chooser from ba._lobby import Lobby, Chooser
from ba._math import normalized_color, is_point_in_box, vec3validate from ba._math import normalized_color, is_point_in_box, vec3validate
from ba._messages import (OutOfBoundsMessage, DieMessage, StandMessage, from ba._messages import (OutOfBoundsMessage, DeathType, DieMessage,
PickUpMessage, DropMessage, PickedUpMessage, StandMessage, PickUpMessage, DropMessage,
DroppedMessage, ShouldShatterMessage, PickedUpMessage, DroppedMessage,
ImpactDamageMessage, FreezeMessage, ThawMessage, ShouldShatterMessage, ImpactDamageMessage,
HitMessage) FreezeMessage, ThawMessage, HitMessage)
from ba._music import setmusic, MusicPlayer, MusicType, MusicPlayMode from ba._music import setmusic, MusicPlayer, MusicType, MusicPlayMode
from ba._powerup import PowerupMessage, PowerupAcceptMessage from ba._powerup import PowerupMessage, PowerupAcceptMessage
from ba._teambasesession import TeamBaseSession from ba._teambasesession import TeamBaseSession

View File

@ -172,16 +172,16 @@ class CoopGameActivity(GameActivity):
shadow=1.0 if vrmode else 0.5, shadow=1.0 if vrmode else 0.5,
transition_delay=0.0, transition_delay=0.0,
transition_out_delay=1.3 transition_out_delay=1.3
if self.slow_motion else 4000).autoretain() if self.slow_motion else 4.0).autoretain()
hval = 70 hval = 70
vval = -50 vval = -50
tdelay = 0 tdelay = 0.0
for ach in achievements: for ach in achievements:
tdelay += 50 tdelay += 0.05
ach.create_display(hval + 40, ach.create_display(hval + 40,
vval + v_offs, vval + v_offs,
0 + tdelay, 0 + tdelay,
outdelay=1300 if self.slow_motion else 4000, outdelay=1.3 if self.slow_motion else 4.0,
style='in_game') style='in_game')
vval -= 55 vval -= 55

View File

@ -72,7 +72,7 @@ class CoopSession(Session):
except Exception: except Exception:
max_players = 4 max_players = 4
print('FIXME: COOP SESSION WOULD CALC DEPS.') # print('FIXME: COOP SESSION WOULD CALC DEPS.')
depsets: Sequence[ba.DependencySet] = [] depsets: Sequence[ba.DependencySet] = []
super().__init__(depsets, super().__init__(depsets,

View File

@ -658,7 +658,7 @@ class GameActivity(Activity):
def on_player_leave(self, player: ba.Player) -> None: def on_player_leave(self, player: ba.Player) -> None:
from ba._general import Call from ba._general import Call
from ba._messages import DieMessage from ba._messages import DieMessage, DeathType
super().on_player_leave(player) super().on_player_leave(player)
@ -668,7 +668,8 @@ class GameActivity(Activity):
# will incorrectly try to respawn them, etc. # will incorrectly try to respawn them, etc.
actor = player.actor actor = player.actor
if actor is not None: if actor is not None:
_ba.pushcall(Call(actor.handlemessage, DieMessage(how='leftGame'))) _ba.pushcall(
Call(actor.handlemessage, DieMessage(how=DeathType.LEFT_GAME)))
player.set_actor(None) player.set_actor(None)
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
@ -679,9 +680,9 @@ class GameActivity(Activity):
killer = msg.killerplayer killer = msg.killerplayer
# Inform our score-set of the demise. # Inform our score-set of the demise.
self.stats.player_lost_spaz(player, self.stats.player_was_killed(player,
killed=msg.killed, killed=msg.killed,
killer=killer) killer=killer)
# Award the killer points if he's on a different team. # Award the killer points if he's on a different team.
if killer and killer.team is not player.team: if killer and killer.team is not player.team:
@ -1070,7 +1071,6 @@ class GameActivity(Activity):
spaz.node.name = name spaz.node.name = name
spaz.node.name_color = display_color spaz.node.name_color = display_color
spaz.connect_controls_to_player() spaz.connect_controls_to_player()
self.stats.player_got_new_spaz(player, spaz)
# Move to the stand position and add a flash of light. # Move to the stand position and add a flash of light.
spaz.handlemessage( spaz.handlemessage(

View File

@ -24,6 +24,7 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from enum import Enum
import _ba import _ba
@ -40,6 +41,18 @@ class OutOfBoundsMessage:
""" """
class DeathType(Enum):
"""A reason for a death.
Category: Enums
"""
GENERIC = 'generic'
IMPACT = 'impact'
FALL = 'fall'
REACHED_GOAL = 'reached_goal'
LEFT_GAME = 'left_game'
@dataclass @dataclass
class DieMessage: class DieMessage:
"""A message telling an object to die. """A message telling an object to die.
@ -57,12 +70,11 @@ class DieMessage:
its time with lingering corpses, sound effects, etc. its time with lingering corpses, sound effects, etc.
how how
The particular reason for death; 'fall', 'impact', 'leftGame', etc. The particular reason for death.
This can be examined for scoring or other purposes.
""" """
immediate: bool = False immediate: bool = False
how: str = 'generic' how: DeathType = DeathType.GENERIC
@dataclass @dataclass

View File

@ -72,12 +72,11 @@ class PlayerRecord:
self.killed_count = 0 self.killed_count = 0
self.accum_killed_count = 0 self.accum_killed_count = 0
self._multi_kill_timer: Optional[ba.Timer] = None self._multi_kill_timer: Optional[ba.Timer] = None
self._multikillcount = 0 self._multi_kill_count = 0
self._stats = weakref.ref(stats) self._stats = weakref.ref(stats)
self._last_player: Optional[ba.Player] = None self._last_player: Optional[ba.Player] = None
self._player: Optional[ba.Player] = None self._player: Optional[ba.Player] = None
self.associate_with_player(player) self.associate_with_player(player)
self._spaz: Optional[ReferenceType[ba.Actor]] = None
self._team: Optional[ReferenceType[ba.Team]] = None self._team: Optional[ReferenceType[ba.Team]] = None
self.streak = 0 self.streak = 0
@ -115,16 +114,6 @@ class PlayerRecord:
assert player is not None assert player is not None
return player.get_icon() return player.get_icon()
def get_spaz(self) -> Optional[ba.Actor]:
"""Return the player entry's spaz."""
if self._spaz is None:
return None
return self._spaz()
def set_spaz(self, spaz: Optional[ba.Actor]) -> None:
"""(internal)"""
self._spaz = weakref.ref(spaz) if spaz is not None else None
def cancel_multi_kill_timer(self) -> None: def cancel_multi_kill_timer(self) -> None:
"""Cancel any multi-kill timer for this player entry.""" """Cancel any multi-kill timer for this player entry."""
self._multi_kill_timer = None self._multi_kill_timer = None
@ -144,12 +133,11 @@ class PlayerRecord:
self.character = player.character self.character = player.character
self._last_player = player self._last_player = player
self._player = player self._player = player
self._spaz = None
self.streak = 0 self.streak = 0
def _end_multi_kill(self) -> None: def _end_multi_kill(self) -> None:
self._multi_kill_timer = None self._multi_kill_timer = None
self._multikillcount = 0 self._multi_kill_count = 0
def get_last_player(self) -> ba.Player: def get_last_player(self) -> ba.Player:
"""Return the last ba.Player we were associated with.""" """Return the last ba.Player we were associated with."""
@ -162,52 +150,51 @@ class PlayerRecord:
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
from ba._lang import Lstr from ba._lang import Lstr
from ba._general import Call from ba._general import Call
from ba._enums import TimeFormat self._multi_kill_count += 1
self._multikillcount += 1
stats = self._stats() stats = self._stats()
assert stats assert stats
if self._multikillcount == 1: if self._multi_kill_count == 1:
score = 0 score = 0
name = None name = None
delay = 0 delay = 0.0
color = (0.0, 0.0, 0.0, 1.0) color = (0.0, 0.0, 0.0, 1.0)
scale = 1.0 scale = 1.0
sound = None sound = None
elif self._multikillcount == 2: elif self._multi_kill_count == 2:
score = 20 score = 20
name = Lstr(resource='twoKillText') name = Lstr(resource='twoKillText')
color = (0.1, 1.0, 0.0, 1) color = (0.1, 1.0, 0.0, 1)
scale = 1.0 scale = 1.0
delay = 0 delay = 0.0
sound = stats.orchestrahitsound1 sound = stats.orchestrahitsound1
elif self._multikillcount == 3: elif self._multi_kill_count == 3:
score = 40 score = 40
name = Lstr(resource='threeKillText') name = Lstr(resource='threeKillText')
color = (1.0, 0.7, 0.0, 1) color = (1.0, 0.7, 0.0, 1)
scale = 1.1 scale = 1.1
delay = 300 delay = 0.3
sound = stats.orchestrahitsound2 sound = stats.orchestrahitsound2
elif self._multikillcount == 4: elif self._multi_kill_count == 4:
score = 60 score = 60
name = Lstr(resource='fourKillText') name = Lstr(resource='fourKillText')
color = (1.0, 1.0, 0.0, 1) color = (1.0, 1.0, 0.0, 1)
scale = 1.2 scale = 1.2
delay = 600 delay = 0.6
sound = stats.orchestrahitsound3 sound = stats.orchestrahitsound3
elif self._multikillcount == 5: elif self._multi_kill_count == 5:
score = 80 score = 80
name = Lstr(resource='fiveKillText') name = Lstr(resource='fiveKillText')
color = (1.0, 0.5, 0.0, 1) color = (1.0, 0.5, 0.0, 1)
scale = 1.3 scale = 1.3
delay = 900 delay = 0.9
sound = stats.orchestrahitsound4 sound = stats.orchestrahitsound4
else: else:
score = 100 score = 100
name = Lstr(resource='multiKillText', name = Lstr(resource='multiKillText',
subs=[('${COUNT}', str(self._multikillcount))]) subs=[('${COUNT}', str(self._multi_kill_count))])
color = (1.0, 0.5, 0.0, 1) color = (1.0, 0.5, 0.0, 1)
scale = 1.3 scale = 1.3
delay = 1000 delay = 1.0
sound = stats.orchestrahitsound4 sound = stats.orchestrahitsound4
def _apply(name2: Lstr, score2: int, showpoints2: bool, def _apply(name2: Lstr, score2: int, showpoints2: bool,
@ -217,12 +204,9 @@ class PlayerRecord:
# Only award this if they're still alive and we can get # Only award this if they're still alive and we can get
# their pos. # their pos.
try: if self._player is not None and self._player.node:
actor = self.get_spaz() our_pos = self._player.node.position
assert actor is not None else:
assert actor.node
our_pos = actor.node.position
except Exception:
return return
# Jitter position a bit since these often come in clusters. # Jitter position a bit since these often come in clusters.
@ -249,10 +233,9 @@ class PlayerRecord:
activity.handlemessage(PlayerScoredMessage(score=score2)) activity.handlemessage(PlayerScoredMessage(score=score2))
if name is not None: if name is not None:
_ba.timer(300 + delay, _ba.timer(
Call(_apply, name, score, showpoints, color, scale, 0.3 + delay,
sound), Call(_apply, name, score, showpoints, color, scale, sound))
timeformat=TimeFormat.MILLISECONDS)
# Keep the tally rollin'... # Keep the tally rollin'...
# set a timer for a bit in the future. # set a timer for a bit in the future.
@ -304,6 +287,7 @@ class Stats:
def reset(self) -> None: def reset(self) -> None:
"""Reset the stats instance completely.""" """Reset the stats instance completely."""
# Just to be safe, lets make sure no multi-kill timers are gonna go off # Just to be safe, lets make sure no multi-kill timers are gonna go off
# for no-longer-on-the-list players. # for no-longer-on-the-list players.
for p_entry in list(self._player_records.values()): for p_entry in list(self._player_records.values()):
@ -345,17 +329,6 @@ class Stats:
records[record_id] = record records[record_id] = record
return records return records
def _get_spaz(self, player: ba.Player) -> Optional[ba.Actor]:
return self._player_records[player.get_name()].get_spaz()
def player_got_new_spaz(self, player: ba.Player, spaz: ba.Actor) -> None:
"""Call this when a player gets a new Spaz."""
record = self._player_records[player.get_name()]
if record.get_spaz() is not None:
raise Exception("got 2 player_got_new_spaz() messages in a row"
" without a lost-spaz message")
record.set_spaz(spaz)
def player_got_hit(self, player: ba.Player) -> None: def player_got_hit(self, player: ba.Player) -> None:
"""Call this when a player got hit.""" """Call this when a player got hit."""
s_player = self._player_records[player.get_name()] s_player = self._player_records[player.get_name()]
@ -388,7 +361,7 @@ class Stats:
from ba import _math from ba import _math
from ba._gameactivity import GameActivity from ba._gameactivity import GameActivity
from ba._lang import Lstr from ba._lang import Lstr
del victim_player # currently unused del victim_player # Currently unused.
name = player.get_name() name = player.get_name()
s_player = self._player_records[name] s_player = self._player_records[name]
@ -418,16 +391,9 @@ class Stats:
from ba import _error from ba import _error
_error.print_exception('error showing big_message') _error.print_exception('error showing big_message')
# If we currently have a spaz, pop up a score over it. # If we currently have a actor, pop up a score over it.
if display and showpoints: if display and showpoints:
our_pos: Optional[Sequence[float]] our_pos = player.node.position if player.node else None
try:
spaz = s_player.get_spaz()
assert spaz is not None
assert spaz.node
our_pos = spaz.node.position
except Exception:
our_pos = None
if our_pos is not None: if our_pos is not None:
if target is None: if target is None:
target = our_pos target = our_pos
@ -478,15 +444,14 @@ class Stats:
return points return points
def player_lost_spaz(self, def player_was_killed(self,
player: ba.Player, player: ba.Player,
killed: bool = False, killed: bool = False,
killer: ba.Player = None) -> None: killer: ba.Player = None) -> None:
"""Should be called when a player loses a spaz.""" """Should be called when a player is killed."""
from ba._lang import Lstr from ba._lang import Lstr
name = player.get_name() name = player.get_name()
prec = self._player_records[name] prec = self._player_records[name]
prec.set_spaz(None)
prec.streak = 0 prec.streak = 0
if killed: if killed:
prec.accum_killed_count += 1 prec.accum_killed_count += 1

View File

@ -66,9 +66,11 @@ class CoopScoreScreen(ba.Activity):
self._menu_icon_texture = ba.gettexture('menuIcon') self._menu_icon_texture = ba.gettexture('menuIcon')
self._next_level_icon_texture = ba.gettexture('nextLevelIcon') self._next_level_icon_texture = ba.gettexture('nextLevelIcon')
self._campaign: ba.Campaign = settings['campaign']
self._have_achievements = bool( self._have_achievements = bool(
get_achievements_for_coop_level(settings['campaign'].get_name() + get_achievements_for_coop_level(self._campaign.name + ":" +
":" + settings['level'])) settings['level']))
self._account_type = (_ba.get_account_type() if self._account_type = (_ba.get_account_type() if
_ba.get_account_state() == 'signed_in' else None) _ba.get_account_state() == 'signed_in' else None)
@ -122,12 +124,12 @@ class CoopScoreScreen(ba.Activity):
self._next_level_error: Optional[ba.Actor] = None self._next_level_error: Optional[ba.Actor] = None
# Score/gameplay bits. # Score/gameplay bits.
self._was_complete = None self._was_complete: Optional[bool] = None
self._is_complete = None self._is_complete: Optional[bool] = None
self._newly_complete = None self._newly_complete: Optional[bool] = None
self._is_more_levels = None self._is_more_levels: Optional[bool] = None
self._next_level_name = None self._next_level_name: Optional[str] = None
self._show_friend_scores = None self._show_friend_scores: Optional[bool] = None
self._show_info: Optional[Dict[str, Any]] = None self._show_info: Optional[Dict[str, Any]] = None
self._name_str: Optional[str] = None self._name_str: Optional[str] = None
self._friends_loading_status: Optional[ba.Actor] = None self._friends_loading_status: Optional[ba.Actor] = None
@ -135,9 +137,15 @@ class CoopScoreScreen(ba.Activity):
self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None
self._player_info = settings['player_info'] self._player_info = settings['player_info']
self._score = settings['score'] self._score: Optional[int] = settings['score']
self._fail_message = settings['fail_message'] assert isinstance(self._score, (int, type(None)))
self._fail_message: Optional[str] = settings['fail_message']
assert isinstance(self._fail_message, (str, type(None)))
self._begin_time = ba.time() self._begin_time = ba.time()
self._score_order: str
if 'score_order' in settings: if 'score_order' in settings:
if not settings['score_order'] in ['increasing', 'decreasing']: if not settings['score_order'] in ['increasing', 'decreasing']:
raise Exception("Invalid score order: " + raise Exception("Invalid score order: " +
@ -145,7 +153,9 @@ class CoopScoreScreen(ba.Activity):
self._score_order = settings['score_order'] self._score_order = settings['score_order']
else: else:
self._score_order = 'increasing' self._score_order = 'increasing'
assert isinstance(self._score_order, str)
self._score_type: str
if 'score_type' in settings: if 'score_type' in settings:
if not settings['score_type'] in ['points', 'time']: if not settings['score_type'] in ['points', 'time']:
raise Exception("Invalid score type: " + raise Exception("Invalid score type: " +
@ -153,12 +163,12 @@ class CoopScoreScreen(ba.Activity):
self._score_type = settings['score_type'] self._score_type = settings['score_type']
else: else:
self._score_type = 'points' self._score_type = 'points'
assert isinstance(self._score_type, str)
self._campaign = settings['campaign'] self._level_name: str = settings['level']
self._level_name = settings['level'] assert isinstance(self._level_name, str)
self._game_name_str = self._campaign.get_name( self._game_name_str = self._campaign.name + ":" + self._level_name
) + ":" + self._level_name
self._game_config_str = str(len( self._game_config_str = str(len(
self._player_info)) + "p" + self._campaign.get_level( self._player_info)) + "p" + self._campaign.get_level(
self._level_name).get_score_version_string().replace(' ', '_') self._level_name).get_score_version_string().replace(' ', '_')
@ -170,11 +180,11 @@ class CoopScoreScreen(ba.Activity):
try: try:
self._old_best_rank = self._campaign.get_level( self._old_best_rank = self._campaign.get_level(
self._level_name).get_rating() self._level_name).rating
except Exception: except Exception:
self._old_best_rank = 0.0 self._old_best_rank = 0.0
self._victory = (settings['outcome'] == 'victory') self._victory: bool = settings['outcome'] == 'victory'
def __del__(self) -> None: def __del__(self) -> None:
super().__del__() super().__del__()
@ -255,6 +265,7 @@ class CoopScoreScreen(ba.Activity):
# next one, set it as current (this won't happen otherwise). # next one, set it as current (this won't happen otherwise).
if (self._is_complete and self._is_more_levels if (self._is_complete and self._is_more_levels
and not self._newly_complete): and not self._newly_complete):
assert self._next_level_name is not None
self._campaign.set_selected_level(self._next_level_name) self._campaign.set_selected_level(self._next_level_name)
ba.containerwidget(edit=self._root_ui, transition='out_left') ba.containerwidget(edit=self._root_ui, transition='out_left')
with ba.Context(self): with ba.Context(self):
@ -294,7 +305,7 @@ class CoopScoreScreen(ba.Activity):
ba.DieMessage())) ba.DieMessage()))
def _should_show_worlds_best_button(self) -> bool: def _should_show_worlds_best_button(self) -> bool:
# link is too complicated to display with no browser # Link is too complicated to display with no browser.
return ba.is_browser_likely_available() return ba.is_browser_likely_available()
def request_ui(self) -> None: def request_ui(self) -> None:
@ -511,26 +522,26 @@ class CoopScoreScreen(ba.Activity):
# Calc whether the level is complete and other stuff. # Calc whether the level is complete and other stuff.
levels = self._campaign.get_levels() levels = self._campaign.get_levels()
level = self._campaign.get_level(self._level_name) level = self._campaign.get_level(self._level_name)
self._was_complete = level.get_complete() self._was_complete = level.complete
self._is_complete = (self._was_complete or self._victory) self._is_complete = (self._was_complete or self._victory)
self._newly_complete = (self._is_complete and not self._was_complete) self._newly_complete = (self._is_complete and not self._was_complete)
self._is_more_levels = ((level.get_index() < len(levels) - 1) self._is_more_levels = ((level.index < len(levels) - 1)
and self._campaign.is_sequential()) and self._campaign.sequential)
# Any time we complete a level, set the next one as unlocked. # Any time we complete a level, set the next one as unlocked.
if self._is_complete and self._is_more_levels: if self._is_complete and self._is_more_levels:
_ba.add_transaction({ _ba.add_transaction({
'type': 'COMPLETE_LEVEL', 'type': 'COMPLETE_LEVEL',
'campaign': self._campaign.get_name(), 'campaign': self._campaign.name,
'level': self._level_name 'level': self._level_name
}) })
self._next_level_name = levels[level.get_index() + 1].get_name() self._next_level_name = levels[level.index + 1].name
# If this is the first time we completed it, set the next one # If this is the first time we completed it, set the next one
# as current. # as current.
if self._newly_complete: if self._newly_complete:
cfg = ba.app.config cfg = ba.app.config
cfg['Selected Coop Game'] = (self._campaign.get_name() + ":" + cfg['Selected Coop Game'] = (self._campaign.name + ":" +
self._next_level_name) self._next_level_name)
cfg.commit() cfg.commit()
self._campaign.set_selected_level(self._next_level_name) self._campaign.set_selected_level(self._next_level_name)
@ -577,8 +588,7 @@ class CoopScoreScreen(ba.Activity):
pstr = ba.Lstr(value='- ${A} -', pstr = ba.Lstr(value='- ${A} -',
subs=[('${A}', subs=[('${A}',
ba.Lstr(resource='singlePlayerCountText'))]) ba.Lstr(resource='singlePlayerCountText'))])
ZoomText(self._campaign.get_level( ZoomText(self._campaign.get_level(self._level_name).displayname,
self._level_name).get_display_string(),
maxwidth=800, maxwidth=800,
flash=False, flash=False,
trail=False, trail=False,
@ -614,7 +624,7 @@ class CoopScoreScreen(ba.Activity):
ba.timer(0.35, ba.timer(0.35,
ba.Call(ba.playsound, self._score_display_sound_small)) ba.Call(ba.playsound, self._score_display_sound_small))
# Vestigial remain.. this stuff should just be instance vars. # Vestigial remain; this stuff should just be instance vars.
self._show_info = {} self._show_info = {}
if self._score is not None: if self._score is not None:
@ -678,13 +688,13 @@ class CoopScoreScreen(ba.Activity):
self._level_name).get_score_version_string()) self._level_name).get_score_version_string())
_ba.add_transaction({ _ba.add_transaction({
'type': 'SET_LEVEL_LOCAL_HIGH_SCORES', 'type': 'SET_LEVEL_LOCAL_HIGH_SCORES',
'campaign': self._campaign.get_name(), 'campaign': self._campaign.name,
'level': self._level_name, 'level': self._level_name,
'scoreVersion': sver, 'scoreVersion': sver,
'scores': our_high_scores_all 'scores': our_high_scores_all
}) })
if _ba.get_account_state() != 'signed_in': if _ba.get_account_state() != 'signed_in':
# we expect this only in kiosk mode; complain otherwise.. # We expect this only in kiosk mode; complain otherwise.
if not ba.app.kiosk_mode: if not ba.app.kiosk_mode:
print('got not-signed-in at score-submit; unexpected') print('got not-signed-in at score-submit; unexpected')
if self._show_friend_scores: if self._show_friend_scores:
@ -703,7 +713,7 @@ class CoopScoreScreen(ba.Activity):
order=self._score_order, order=self._score_order,
tournament_id=self.session.tournament_id, tournament_id=self.session.tournament_id,
score_type=self._score_type, score_type=self._score_type,
campaign=self._campaign.get_name() campaign=self._campaign.name
if self._campaign is not None else None, if self._campaign is not None else None,
level=self._level_name) level=self._level_name)
@ -1373,8 +1383,7 @@ class CoopScoreScreen(ba.Activity):
dostar(2, 10 - 30, -112, '7.5') dostar(2, 10 - 30, -112, '7.5')
dostar(3, 77 - 30, -112, '9.5') dostar(3, 77 - 30, -112, '9.5')
try: try:
best_rank = self._campaign.get_level( best_rank = self._campaign.get_level(self._level_name).rating
self._level_name).get_rating()
except Exception: except Exception:
best_rank = 0.0 best_rank = 0.0
@ -1449,6 +1458,8 @@ class CoopScoreScreen(ba.Activity):
def _show_score_val(self, offs_x: float) -> None: def _show_score_val(self, offs_x: float) -> None:
from bastd.actor.text import Text from bastd.actor.text import Text
from bastd.actor.zoomtext import ZoomText from bastd.actor.zoomtext import ZoomText
assert self._score_type is not None
assert self._score is not None
ZoomText((str(self._score) if self._score_type == 'points' else ZoomText((str(self._score) if self._score_type == 'points' else
ba.timestring(self._score * 10, ba.timestring(self._score * 10,
timeformat=ba.TimeFormat.MILLISECONDS)), timeformat=ba.TimeFormat.MILLISECONDS)),

View File

@ -150,7 +150,7 @@ class Background(ba.Actor):
ba.timer(self.fade_time + 0.1, self.node.delete) ba.timer(self.fade_time + 0.1, self.node.delete)
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if __debug__ is True: if __debug__:
self._handlemessage_sanity_check() self._handlemessage_sanity_check()
if isinstance(msg, ba.DieMessage): if isinstance(msg, ba.DieMessage):
self._die(msg.immediate) self._die(msg.immediate)

View File

@ -776,8 +776,8 @@ class Bomb(ba.Actor):
self.arm_timer = ba.Timer( self.arm_timer = ba.Timer(
0.2, ba.WeakCall(self.handlemessage, ArmMessage())) 0.2, ba.WeakCall(self.handlemessage, ArmMessage()))
self.warn_timer = ba.Timer( self.warn_timer = ba.Timer(
0.001 * (fuse_time - 1.7), fuse_time - 1.7, ba.WeakCall(self.handlemessage,
ba.WeakCall(self.handlemessage, WarnMessage())) WarnMessage()))
else: else:
fuse_time = 3.0 fuse_time = 3.0

View File

@ -469,7 +469,7 @@ class ControlsGuide(ba.Actor):
return not self._dead return not self._dead
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if __debug__ is True: if __debug__:
self._handlemessage_sanity_check() self._handlemessage_sanity_check()
if isinstance(msg, ba.DieMessage): if isinstance(msg, ba.DieMessage):
if msg.immediate: if msg.immediate:

View File

@ -350,7 +350,7 @@ class Flag(ba.Actor):
elif isinstance(msg, ba.OutOfBoundsMessage): elif isinstance(msg, ba.OutOfBoundsMessage):
# We just kill ourselves when out-of-bounds.. would we ever not # We just kill ourselves when out-of-bounds.. would we ever not
# want this?.. # want this?..
self.handlemessage(ba.DieMessage(how='fall')) self.handlemessage(ba.DieMessage(how=ba.DeathType.FALL))
elif isinstance(msg, ba.PickedUpMessage): elif isinstance(msg, ba.PickedUpMessage):
self._held_count += 1 self._held_count += 1
if self._held_count == 1 and self._counter is not None: if self._held_count == 1 and self._counter is not None:

View File

@ -51,8 +51,9 @@ class Image(ba.Actor):
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
super().__init__() super().__init__()
# if they provided a dict as texture, assume its an icon..
# otherwise its just a texture value itself # If they provided a dict as texture, assume its an icon.
# otherwise its just a texture value itself.
mask_texture: Optional[ba.Texture] mask_texture: Optional[ba.Texture]
if isinstance(texture, dict): if isinstance(texture, dict):
tint_color = texture['tint_color'] tint_color = texture['tint_color']
@ -156,13 +157,13 @@ class Image(ba.Actor):
cmb.input1 = position[1] cmb.input1 = position[1]
cmb.connectattr('output', self.node, 'position') cmb.connectattr('output', self.node, 'position')
# if we're transitioning out, die at the end of it # If we're transitioning out, die at the end of it.
if transition_out_delay is not None: if transition_out_delay is not None:
ba.timer(transition_delay + transition_out_delay + 1.0, ba.timer(transition_delay + transition_out_delay + 1.0,
ba.WeakCall(self.handlemessage, ba.DieMessage())) ba.WeakCall(self.handlemessage, ba.DieMessage()))
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if __debug__ is True: if __debug__:
self._handlemessage_sanity_check() self._handlemessage_sanity_check()
if isinstance(msg, ba.DieMessage): if isinstance(msg, ba.DieMessage):
if self.node: if self.node:

View File

@ -53,7 +53,7 @@ class PlayerSpazDeathMessage:
""" """
def __init__(self, spaz: PlayerSpaz, was_killed: bool, def __init__(self, spaz: PlayerSpaz, was_killed: bool,
killerplayer: Optional[ba.Player], how: str): killerplayer: Optional[ba.Player], how: ba.DeathType):
"""Instantiate a message with the given values.""" """Instantiate a message with the given values."""
self.spaz = spaz self.spaz = spaz
self.killed = was_killed self.killed = was_killed
@ -252,7 +252,8 @@ class PlayerSpaz(basespaz.Spaz):
if not self._dead: if not self._dead:
# Immediate-mode or left-game deaths don't count as 'kills'. # Immediate-mode or left-game deaths don't count as 'kills'.
killed = (not msg.immediate and msg.how != 'leftGame') killed = (not msg.immediate
and msg.how is not ba.DeathType.LEFT_GAME)
activity = self._activity() activity = self._activity()

View File

@ -122,7 +122,7 @@ class PopupText(ba.Actor):
lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage())) lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage()))
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if __debug__ is True: if __debug__:
self._handlemessage_sanity_check() self._handlemessage_sanity_check()
if isinstance(msg, ba.DieMessage): if isinstance(msg, ba.DieMessage):
if self.node: if self.node:

View File

@ -128,8 +128,10 @@ class Spaz(ba.Actor):
else: else:
self._punch_power_scale = factory.punch_power_scale self._punch_power_scale = factory.punch_power_scale
self.fly = ba.sharedobj('globals').happy_thoughts_mode self.fly = ba.sharedobj('globals').happy_thoughts_mode
assert isinstance(activity, ba.GameActivity) if isinstance(activity, ba.GameActivity):
self._hockey = activity.map.is_hockey self._hockey = activity.map.is_hockey
else:
self._hockey = False
self._punched_nodes: Set[ba.Node] = set() self._punched_nodes: Set[ba.Node] = set()
self._cursed = False self._cursed = False
self._connected_to_player: Optional[ba.Player] = None self._connected_to_player: Optional[ba.Player] = None
@ -588,11 +590,12 @@ class Spaz(ba.Actor):
def on_punched(self, damage: int) -> None: def on_punched(self, damage: int) -> None:
"""Called when this spaz gets punched.""" """Called when this spaz gets punched."""
def get_death_points(self, how: str) -> Tuple[int, int]: def get_death_points(self, how: ba.DeathType) -> Tuple[int, int]:
"""Get the points awarded for killing this spaz.""" """Get the points awarded for killing this spaz."""
del how # unused arg del how # Unused.
num_hits = float(max(1, self._num_times_hit)) num_hits = float(max(1, self._num_times_hit))
# base points is simply 10 for 1-hit-kills and 5 otherwise
# Base points is simply 10 for 1-hit-kills and 5 otherwise.
importance = 2 if num_hits < 2 else 1 importance = 2 if num_hits < 2 else 1
return (10 if num_hits < 2 else 5) * self.points_mult, importance return (10 if num_hits < 2 else 5) * self.points_mult, importance
@ -604,7 +607,8 @@ class Spaz(ba.Actor):
if not self._cursed: if not self._cursed:
factory = get_factory() factory = get_factory()
self._cursed = True self._cursed = True
# add the curse material..
# Add the curse material.
for attr in ['materials', 'roller_materials']: for attr in ['materials', 'roller_materials']:
materials = getattr(self.node, attr) materials = getattr(self.node, attr)
if factory.curse_material not in materials: if factory.curse_material not in materials:
@ -616,7 +620,7 @@ class Spaz(ba.Actor):
if self.curse_time is None: if self.curse_time is None:
self.node.curse_death_time = -1 self.node.curse_death_time = -1
else: else:
# note: curse-death-time takes milliseconds # Note: curse-death-time takes milliseconds.
tval = ba.time() tval = ba.time()
assert isinstance(tval, int) assert isinstance(tval, int)
self.node.curse_death_time = int(1000.0 * self.node.curse_death_time = int(1000.0 *
@ -629,7 +633,7 @@ class Spaz(ba.Actor):
""" """
assert self.node assert self.node
self.node.boxing_gloves = True self.node.boxing_gloves = True
if self._demo_mode: # preserve old behavior if self._demo_mode: # Preserve old behavior.
self._punch_power_scale = 1.7 self._punch_power_scale = 1.7
self._punch_cooldown = 300 self._punch_cooldown = 300
else: else:
@ -664,7 +668,7 @@ class Spaz(ba.Actor):
self.shield_decay_timer = ba.Timer(0.5, self.shield_decay_timer = ba.Timer(0.5,
ba.WeakCall(self.shield_decay), ba.WeakCall(self.shield_decay),
repeat=True) repeat=True)
# so user can see the decay # So user can see the decay.
self.shield.always_show_health_bar = True self.shield.always_show_health_bar = True
def shield_decay(self) -> None: def shield_decay(self) -> None:
@ -691,24 +695,25 @@ class Spaz(ba.Actor):
# pylint: disable=too-many-return-statements # pylint: disable=too-many-return-statements
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
if __debug__ is True: if __debug__:
self._handlemessage_sanity_check() self._handlemessage_sanity_check()
if isinstance(msg, ba.PickedUpMessage): if isinstance(msg, ba.PickedUpMessage):
if self.node: if self.node:
self.node.handlemessage("hurt_sound") self.node.handlemessage("hurt_sound")
self.node.handlemessage("picked_up") self.node.handlemessage("picked_up")
# this counts as a hit
# This counts as a hit.
self._num_times_hit += 1 self._num_times_hit += 1
elif isinstance(msg, ba.ShouldShatterMessage): elif isinstance(msg, ba.ShouldShatterMessage):
# eww; seems we have to do this in a timer or it wont work right # Eww; seems we have to do this in a timer or it wont work right.
# (since we're getting called from within update() perhaps?..) # (since we're getting called from within update() perhaps?..)
# NOTE: should test to see if that's still the case # NOTE: should test to see if that's still the case.
ba.timer(0.001, ba.WeakCall(self.shatter)) ba.timer(0.001, ba.WeakCall(self.shatter))
elif isinstance(msg, ba.ImpactDamageMessage): elif isinstance(msg, ba.ImpactDamageMessage):
# eww; seems we have to do this in a timer or it wont work right # Eww; seems we have to do this in a timer or it wont work right.
# (since we're getting called from within update() perhaps?..) # (since we're getting called from within update() perhaps?..)
ba.timer(0.001, ba.WeakCall(self._hit_self, msg.intensity)) ba.timer(0.001, ba.WeakCall(self._hit_self, msg.intensity))
@ -799,7 +804,8 @@ class Spaz(ba.Actor):
timeformat=ba.TimeFormat.MILLISECONDS)) timeformat=ba.TimeFormat.MILLISECONDS))
elif msg.poweruptype == 'shield': elif msg.poweruptype == 'shield':
factory = get_factory() factory = get_factory()
# let's allow powerup-equipped shields to lose hp over time
# Let's allow powerup-equipped shields to lose hp over time.
self.equip_shields(decay=factory.shield_decay_rate > 0) self.equip_shields(decay=factory.shield_decay_rate > 0)
elif msg.poweruptype == 'curse': elif msg.poweruptype == 'curse':
self.curse() self.curse()
@ -825,7 +831,8 @@ class Spaz(ba.Actor):
elif msg.poweruptype == 'health': elif msg.poweruptype == 'health':
if self._cursed: if self._cursed:
self._cursed = False self._cursed = False
# remove cursed material
# Remove cursed material.
factory = get_factory() factory = get_factory()
for attr in ['materials', 'roller_materials']: for attr in ['materials', 'roller_materials']:
materials = getattr(self.node, attr) materials = getattr(self.node, attr)
@ -861,7 +868,7 @@ class Spaz(ba.Actor):
self.node.frozen = True self.node.frozen = True
ba.timer(5.0, ba.WeakCall(self.handlemessage, ba.timer(5.0, ba.WeakCall(self.handlemessage,
ba.ThawMessage())) ba.ThawMessage()))
# instantly shatter if we're already dead # Instantly shatter if we're already dead.
# (otherwise its hard to tell we're dead) # (otherwise its hard to tell we're dead)
if self.hitpoints <= 0: if self.hitpoints <= 0:
self.shatter() self.shatter()
@ -880,7 +887,7 @@ class Spaz(ba.Actor):
position=self.node.position) position=self.node.position)
return True return True
# if we were recently hit, don't count this as another # If we were recently hit, don't count this as another.
# (so punch flurries and bomb pileups essentially count as 1 hit) # (so punch flurries and bomb pileups essentially count as 1 hit)
local_time = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) local_time = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
assert isinstance(local_time, int) assert isinstance(local_time, int)
@ -893,13 +900,13 @@ class Spaz(ba.Actor):
velocity_mag = msg.velocity_magnitude * self.impact_scale velocity_mag = msg.velocity_magnitude * self.impact_scale
damage_scale = 0.22 damage_scale = 0.22
# if they've got a shield, deliver it to that instead.. # If they've got a shield, deliver it to that instead.
if self.shield: if self.shield:
if msg.flat_damage: if msg.flat_damage:
damage = msg.flat_damage * self.impact_scale damage = msg.flat_damage * self.impact_scale
else: else:
# hit our spaz with an impulse but tell it to only return # Hit our spaz with an impulse but tell it to only return
# theoretical damage; not apply the impulse.. # theoretical damage; not apply the impulse.
assert msg.force_direction is not None assert msg.force_direction is not None
self.node.handlemessage( self.node.handlemessage(
"impulse", msg.pos[0], msg.pos[1], msg.pos[2], "impulse", msg.pos[0], msg.pos[1], msg.pos[2],
@ -1086,7 +1093,8 @@ class Spaz(ba.Actor):
if self.frozen and (damage > 200 or self.hitpoints <= 0): if self.frozen and (damage > 200 or self.hitpoints <= 0):
self.shatter() self.shatter()
elif self.hitpoints <= 0: elif self.hitpoints <= 0:
self.node.handlemessage(ba.DieMessage(how='impact')) self.node.handlemessage(
ba.DieMessage(how=ba.DeathType.IMPACT))
# if we're dead, take a look at the smoothed damage val # if we're dead, take a look at the smoothed damage val
# (which gives us a smoothed average of recent damage) and shatter # (which gives us a smoothed average of recent damage) and shatter
@ -1114,8 +1122,9 @@ class Spaz(ba.Actor):
ba.timer(2.0, self.node.delete) ba.timer(2.0, self.node.delete)
elif isinstance(msg, ba.OutOfBoundsMessage): elif isinstance(msg, ba.OutOfBoundsMessage):
# by default we just die here # By default we just die here.
self.handlemessage(ba.DieMessage(how='fall')) self.handlemessage(ba.DieMessage(how=ba.DeathType.FALL))
elif isinstance(msg, ba.StandMessage): elif isinstance(msg, ba.StandMessage):
self._last_stand_pos = (msg.position[0], msg.position[1], self._last_stand_pos = (msg.position[0], msg.position[1],
msg.position[2]) msg.position[2])
@ -1123,8 +1132,10 @@ class Spaz(ba.Actor):
self.node.handlemessage("stand", msg.position[0], self.node.handlemessage("stand", msg.position[0],
msg.position[1], msg.position[2], msg.position[1], msg.position[2],
msg.angle) msg.angle)
elif isinstance(msg, CurseExplodeMessage): elif isinstance(msg, CurseExplodeMessage):
self.curse_explode() self.curse_explode()
elif isinstance(msg, PunchHitMessage): elif isinstance(msg, PunchHitMessage):
if not self.node: if not self.node:
return None return None

View File

@ -80,7 +80,7 @@ class SpazBotDeathMessage:
""" """
def __init__(self, badguy: SpazBot, killerplayer: Optional[ba.Player], def __init__(self, badguy: SpazBot, killerplayer: Optional[ba.Player],
how: str): how: ba.DeathType):
"""Instantiate with given values.""" """Instantiate with given values."""
self.badguy = badguy self.badguy = badguy
self.killerplayer = killerplayer self.killerplayer = killerplayer
@ -111,9 +111,9 @@ class SpazBot(basespaz.Spaz):
static = False static = False
bouncy = False bouncy = False
run = False run = False
charge_dist_min = 0.0 # when we can start a new charge charge_dist_min = 0.0 # When we can start a new charge.
charge_dist_max = 2.0 # when we can start a new charge charge_dist_max = 2.0 # When we can start a new charge.
run_dist_min = 0.0 # how close we can be to continue running run_dist_min = 0.0 # How close we can be to continue running.
charge_speed_min = 0.4 charge_speed_min = 0.4
charge_speed_max = 1.0 charge_speed_max = 1.0
throw_dist_min = 5.0 throw_dist_min = 5.0
@ -193,6 +193,7 @@ class SpazBot(basespaz.Spaz):
assert self._player_pts assert self._player_pts
for plpt, plvel in self._player_pts: for plpt, plvel in self._player_pts:
dist = (plpt - botpt).length() dist = (plpt - botpt).length()
# Ignore player-points that are significantly below the bot # Ignore player-points that are significantly below the bot
# (keeps bots from following players off cliffs). # (keeps bots from following players off cliffs).
if (closest_dist is None if (closest_dist is None
@ -218,9 +219,12 @@ class SpazBot(basespaz.Spaz):
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
if self.update_callback is not None: if self.update_callback is not None:
if self.update_callback(self): if self.update_callback(self):
return # true means bot has been handled # Bot has been handled.
return
if not self.node:
return
assert self.node
pos = self.node.position pos = self.node.position
our_pos = ba.Vec3(pos[0], 0, pos[2]) our_pos = ba.Vec3(pos[0], 0, pos[2])
can_attack = True can_attack = True
@ -239,11 +243,13 @@ class SpazBot(basespaz.Spaz):
holding_flag = False holding_flag = False
else: else:
holding_flag = False holding_flag = False
# If we're holding the flag, just walk left. # If we're holding the flag, just walk left.
if holding_flag: if holding_flag:
# just walk left # Just walk left.
self.node.move_left_right = -1.0 self.node.move_left_right = -1.0
self.node.move_up_down = 0.0 self.node.move_up_down = 0.0
# Otherwise try to go pick it up. # Otherwise try to go pick it up.
else: else:
assert self.target_flag.node assert self.target_flag.node
@ -273,6 +279,7 @@ class SpazBot(basespaz.Spaz):
self.node.pickup_pressed = True self.node.pickup_pressed = True
self.node.pickup_pressed = False self.node.pickup_pressed = False
return return
# Not a flag-bearer. If we're holding anything but a bomb, drop it. # Not a flag-bearer. If we're holding anything but a bomb, drop it.
if self.node.hold_node: if self.node.hold_node:
try: try:
@ -289,12 +296,13 @@ class SpazBot(basespaz.Spaz):
target_pt_raw, target_vel = self._get_target_player_pt() target_pt_raw, target_vel = self._get_target_player_pt()
if target_pt_raw is None: if target_pt_raw is None:
# use default target if we've got one # Use default target if we've got one.
if self.target_point_default is not None: if self.target_point_default is not None:
target_pt_raw = self.target_point_default target_pt_raw = self.target_point_default
target_vel = ba.Vec3(0, 0, 0) target_vel = ba.Vec3(0, 0, 0)
can_attack = False can_attack = False
# with no target, we stop moving and drop whatever we're holding
# With no target, we stop moving and drop whatever we're holding.
else: else:
self.node.move_left_right = 0 self.node.move_left_right = 0
self.node.move_up_down = 0 self.node.move_up_down = 0
@ -303,13 +311,14 @@ class SpazBot(basespaz.Spaz):
self.node.pickup_pressed = False self.node.pickup_pressed = False
return return
# we don't want height to come into play # We don't want height to come into play.
target_pt_raw[1] = 0.0 target_pt_raw[1] = 0.0
assert target_vel is not None assert target_vel is not None
target_vel[1] = 0.0 target_vel[1] = 0.0
dist_raw = (target_pt_raw - our_pos).length() dist_raw = (target_pt_raw - our_pos).length()
# use a point out in front of them as real target
# Use a point out in front of them as real target.
# (more out in front the farther from us they are) # (more out in front the farther from us they are)
target_pt = (target_pt_raw + target_pt = (target_pt_raw +
target_vel * dist_raw * 0.3 * self._lead_amount) target_vel * dist_raw * 0.3 * self._lead_amount)
@ -319,25 +328,26 @@ class SpazBot(basespaz.Spaz):
to_target = diff.normalized() to_target = diff.normalized()
if self._mode == 'throw': if self._mode == 'throw':
# we can only throw if alive and well.. # We can only throw if alive and well.
if not self._dead and not self.node.knockout: if not self._dead and not self.node.knockout:
assert self._throw_release_time is not None assert self._throw_release_time is not None
time_till_throw = self._throw_release_time - ba.time() time_till_throw = self._throw_release_time - ba.time()
if not self.node.hold_node: if not self.node.hold_node:
# if we haven't thrown yet, whip out the bomb # If we haven't thrown yet, whip out the bomb.
if not self._have_dropped_throw_bomb: if not self._have_dropped_throw_bomb:
self.drop_bomb() self.drop_bomb()
self._have_dropped_throw_bomb = True self._have_dropped_throw_bomb = True
# otherwise our lack of held node means we successfully
# released our bomb.. lets retreat now # Otherwise our lack of held node means we successfully
# released our bomb; lets retreat now.
else: else:
self._mode = 'flee' self._mode = 'flee'
# oh crap we're holding a bomb.. better throw it. # Oh crap, we're holding a bomb; better throw it.
elif time_till_throw <= 0.0: elif time_till_throw <= 0.0:
# jump and throw.. # Jump and throw.
def _safe_pickup(node: ba.Node) -> None: def _safe_pickup(node: ba.Node) -> None:
if node and self.node: if node and self.node:
self.node.pickup_pressed = True self.node.pickup_pressed = True
@ -346,25 +356,26 @@ class SpazBot(basespaz.Spaz):
if dist > 5.0: if dist > 5.0:
self.node.jump_pressed = True self.node.jump_pressed = True
self.node.jump_pressed = False self.node.jump_pressed = False
# throws:
# Throws:
ba.timer(0.1, ba.Call(_safe_pickup, self.node)) ba.timer(0.1, ba.Call(_safe_pickup, self.node))
else: else:
# throws: # Throws:
ba.timer(0.1, ba.Call(_safe_pickup, self.node)) ba.timer(0.1, ba.Call(_safe_pickup, self.node))
if self.static: if self.static:
if time_till_throw < 0.3: if time_till_throw < 0.3:
speed = 1.0 speed = 1.0
elif time_till_throw < 0.7 and dist > 3.0: elif time_till_throw < 0.7 and dist > 3.0:
speed = -1.0 # whiplash for long throws speed = -1.0 # Whiplash for long throws.
else: else:
speed = 0.02 speed = 0.02
else: else:
if time_till_throw < 0.7: if time_till_throw < 0.7:
# right before throw charge full speed towards target # Right before throw charge full speed towards target.
speed = 1.0 speed = 1.0
else: else:
# earlier we can hold or move backward for a whiplash # Earlier we can hold or move backward for a whiplash.
speed = 0.0125 speed = 0.0125
self.node.move_left_right = to_target.x * speed self.node.move_left_right = to_target.x * speed
self.node.move_up_down = to_target.z * -1.0 * speed self.node.move_up_down = to_target.z * -1.0 * speed
@ -373,8 +384,9 @@ class SpazBot(basespaz.Spaz):
if random.random() < 0.3: if random.random() < 0.3:
self._charge_speed = random.uniform(self.charge_speed_min, self._charge_speed = random.uniform(self.charge_speed_min,
self.charge_speed_max) self.charge_speed_max)
# if we're a runner we run during charges *except when near
# an edge (otherwise we tend to fly off easily) # If we're a runner we run during charges *except when near
# an edge (otherwise we tend to fly off easily).
if self.run and dist_raw > self.run_dist_min: if self.run and dist_raw > self.run_dist_min:
self._lead_amount = 0.3 self._lead_amount = 0.3
self._running = True self._running = True
@ -388,8 +400,8 @@ class SpazBot(basespaz.Spaz):
self.node.move_up_down = to_target.z * -1.0 * self._charge_speed self.node.move_up_down = to_target.z * -1.0 * self._charge_speed
elif self._mode == 'wait': elif self._mode == 'wait':
# every now and then, aim towards our target.. # Every now and then, aim towards our target.
# other than that, just stand there # Other than that, just stand there.
if ba.time(timeformat=ba.TimeFormat.MILLISECONDS) % 1234 < 100: if ba.time(timeformat=ba.TimeFormat.MILLISECONDS) % 1234 < 100:
self.node.move_left_right = to_target.x * (400.0 / 33000) self.node.move_left_right = to_target.x * (400.0 / 33000)
self.node.move_up_down = to_target.z * (-400.0 / 33000) self.node.move_up_down = to_target.z * (-400.0 / 33000)
@ -398,8 +410,8 @@ class SpazBot(basespaz.Spaz):
self.node.move_up_down = 0 self.node.move_up_down = 0
elif self._mode == 'flee': elif self._mode == 'flee':
# even if we're a runner, only run till we get away from our # Even if we're a runner, only run till we get away from our
# target (if we keep running we tend to run off edges) # target (if we keep running we tend to run off edges).
if self.run and dist < 3.0: if self.run and dist < 3.0:
self._running = True self._running = True
self.node.run = 1.0 self.node.run = 1.0
@ -409,20 +421,20 @@ class SpazBot(basespaz.Spaz):
self.node.move_left_right = to_target.x * -1.0 self.node.move_left_right = to_target.x * -1.0
self.node.move_up_down = to_target.z self.node.move_up_down = to_target.z
# we might wanna switch states unless we're doing a throw # We might wanna switch states unless we're doing a throw
# (in which case that's our sole concern) # (in which case that's our sole concern).
if self._mode != 'throw': if self._mode != 'throw':
# if we're currently charging, keep track of how far we are # If we're currently charging, keep track of how far we are
# from our target.. when this value increases it means our charge # from our target. When this value increases it means our charge
# is over (ran by them or something) # is over (ran by them or something).
if self._mode == 'charge': if self._mode == 'charge':
if (self._charge_closing_in if (self._charge_closing_in
and self._last_charge_dist < dist < 3.0): and self._last_charge_dist < dist < 3.0):
self._charge_closing_in = False self._charge_closing_in = False
self._last_charge_dist = dist self._last_charge_dist = dist
# if we have a clean shot, throw! # If we have a clean shot, throw!
if (self.throw_dist_min <= dist < self.throw_dist_max if (self.throw_dist_min <= dist < self.throw_dist_max
and random.random() < self.throwiness and can_attack): and random.random() < self.throwiness and can_attack):
self._mode = 'throw' self._mode = 'throw'
@ -434,15 +446,15 @@ class SpazBot(basespaz.Spaz):
(1.0 / self.throw_rate) * (1.0 / self.throw_rate) *
(0.8 + 1.3 * random.random())) (0.8 + 1.3 * random.random()))
# if we're static, always charge (which for us means barely move) # If we're static, always charge (which for us means barely move).
elif self.static: elif self.static:
self._mode = 'wait' self._mode = 'wait'
# if we're too close to charge (and aren't in the middle of an # If we're too close to charge (and aren't in the middle of an
# existing charge) run away # existing charge) run away.
elif dist < self.charge_dist_min and not self._charge_closing_in: elif dist < self.charge_dist_min and not self._charge_closing_in:
# ..unless we're near an edge, in which case we got no choice # ..unless we're near an edge, in which case we've got no
# but to charge.. # choice but to charge.
if self.map.is_point_near_edge(our_pos, self._running): if self.map.is_point_near_edge(our_pos, self._running):
if self._mode != 'charge': if self._mode != 'charge':
self._mode = 'charge' self._mode = 'charge'
@ -452,7 +464,7 @@ class SpazBot(basespaz.Spaz):
else: else:
self._mode = 'flee' self._mode = 'flee'
# we're within charging distance, backed against an edge, # We're within charging distance, backed against an edge,
# or farther than our max throw distance.. chaaarge! # or farther than our max throw distance.. chaaarge!
elif (dist < self.charge_dist_max or dist > self.throw_dist_max elif (dist < self.charge_dist_max or dist > self.throw_dist_max
or self.map.is_point_near_edge(our_pos, self._running)): or self.map.is_point_near_edge(our_pos, self._running)):
@ -462,11 +474,11 @@ class SpazBot(basespaz.Spaz):
self._charge_closing_in = True self._charge_closing_in = True
self._last_charge_dist = dist self._last_charge_dist = dist
# we're too close to throw but too far to charge - either run # We're too close to throw but too far to charge - either run
# away or just chill if we're near an edge # away or just chill if we're near an edge.
elif dist < self.throw_dist_min: elif dist < self.throw_dist_min:
# charge if either we're within charge range or # Charge if either we're within charge range or
# cant retreat to throw # cant retreat to throw.
self._mode = 'flee' self._mode = 'flee'
# Do some awesome jumps if we're running. # Do some awesome jumps if we're running.
@ -494,29 +506,30 @@ class SpazBot(basespaz.Spaz):
def on_expire(self) -> None: def on_expire(self) -> None:
basespaz.Spaz.on_expire(self) basespaz.Spaz.on_expire(self)
# we're being torn down; release
# our callback(s) so there's no chance of them # We're being torn down; release our callback(s) so there's
# keeping activities or other things alive.. # no chance of them keeping activities or other things alive.
self.update_callback = None self.update_callback = None
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
self._handlemessage_sanity_check() self._handlemessage_sanity_check()
# keep track of if we're being held and by who most recently # Keep track of if we're being held and by who most recently.
if isinstance(msg, ba.PickedUpMessage): if isinstance(msg, ba.PickedUpMessage):
super().handlemessage(msg) # augment standard behavior super().handlemessage(msg) # Augment standard behavior.
self.held_count += 1 self.held_count += 1
picked_up_by = msg.node.source_player picked_up_by = msg.node.source_player
if picked_up_by: if picked_up_by:
self.last_player_held_by = picked_up_by self.last_player_held_by = picked_up_by
elif isinstance(msg, ba.DroppedMessage): elif isinstance(msg, ba.DroppedMessage):
super().handlemessage(msg) # augment standard behavior super().handlemessage(msg) # Augment standard behavior.
self.held_count -= 1 self.held_count -= 1
if self.held_count < 0: if self.held_count < 0:
print("ERROR: spaz held_count < 0") print("ERROR: spaz held_count < 0")
# let's count someone dropping us as an attack..
# Let's count someone dropping us as an attack.
try: try:
if msg.node: if msg.node:
picked_up_by = msg.node.source_player picked_up_by = msg.node.source_player
@ -533,18 +546,19 @@ class SpazBot(basespaz.Spaz):
elif isinstance(msg, ba.DieMessage): elif isinstance(msg, ba.DieMessage):
# report normal deaths for scoring purposes # Report normal deaths for scoring purposes.
if not self._dead and not msg.immediate: if not self._dead and not msg.immediate:
killerplayer: Optional[ba.Player] killerplayer: Optional[ba.Player]
# if this guy was being held at the time of death, the
# holder is the killer # If this guy was being held at the time of death, the
# holder is the killer.
if self.held_count > 0 and self.last_player_held_by: if self.held_count > 0 and self.last_player_held_by:
killerplayer = self.last_player_held_by killerplayer = self.last_player_held_by
else: else:
# otherwise if they were attacked by someone in the # If they were attacked by someone in the last few
# last few seconds that person's the killer.. # seconds that person's the killer.
# otherwise it was a suicide # Otherwise it was a suicide.
if (self.last_player_attacked_by if (self.last_player_attacked_by
and ba.time() - self.last_attacked_time < 4.0): and ba.time() - self.last_attacked_time < 4.0):
killerplayer = self.last_player_attacked_by killerplayer = self.last_player_attacked_by
@ -552,15 +566,15 @@ class SpazBot(basespaz.Spaz):
killerplayer = None killerplayer = None
activity = self._activity() activity = self._activity()
# (convert dead refs to None) # (convert dead player refs to None)
if not killerplayer: if not killerplayer:
killerplayer = None killerplayer = None
if activity is not None: if activity is not None:
activity.handlemessage( activity.handlemessage(
SpazBotDeathMessage(self, killerplayer, msg.how)) SpazBotDeathMessage(self, killerplayer, msg.how))
super().handlemessage(msg) # augment standard behavior super().handlemessage(msg) # Augment standard behavior.
# keep track of the player who last hit us for point rewarding # Keep track of the player who last hit us for point rewarding.
elif isinstance(msg, ba.HitMessage): elif isinstance(msg, ba.HitMessage):
if msg.source_player: if msg.source_player:
self.last_player_attacked_by = msg.source_player self.last_player_attacked_by = msg.source_player
@ -889,8 +903,9 @@ class BotSet:
def __init__(self) -> None: def __init__(self) -> None:
"""Create a bot-set.""" """Create a bot-set."""
# we spread our bots out over a few lists so we can update
# them in a staggered fashion # We spread our bots out over a few lists so we can update
# them in a staggered fashion.
self._bot_list_count = 5 self._bot_list_count = 5
self._bot_add_list = 0 self._bot_add_list = 0
self._bot_update_list = 0 self._bot_update_list = 0
@ -963,7 +978,7 @@ class BotSet:
self._bot_update_list = (self._bot_update_list + self._bot_update_list = (self._bot_update_list +
1) % self._bot_list_count 1) % self._bot_list_count
# update our list of player points for the bots to use # Update our list of player points for the bots to use.
player_pts = [] player_pts = []
for player in ba.getactivity().players: for player in ba.getactivity().players:
try: try:
@ -980,7 +995,8 @@ class BotSet:
def clear(self) -> None: def clear(self) -> None:
"""Immediately clear out any bots in the set.""" """Immediately clear out any bots in the set."""
# don't do this if the activity is shutting down or dead
# Don't do this if the activity is shutting down or dead.
activity = ba.getactivity(doraise=False) activity = ba.getactivity(doraise=False)
if activity is None or activity.is_expired(): if activity is None or activity.is_expired():
return return
@ -1025,7 +1041,8 @@ class BotSet:
Use this when the bots have won a game. Use this when the bots have won a game.
""" """
self._bot_update_timer = None self._bot_update_timer = None
# at this point stop doing anything but jumping and celebrating
# At this point stop doing anything but jumping and celebrating.
for botlist in self._bot_lists: for botlist in self._bot_lists:
for bot in botlist: for bot in botlist:
if bot.node: if bot.node:

View File

@ -191,7 +191,7 @@ class Text(ba.Actor):
ba.WeakCall(self.handlemessage, ba.DieMessage())) ba.WeakCall(self.handlemessage, ba.DieMessage()))
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if __debug__ is True: if __debug__:
self._handlemessage_sanity_check() self._handlemessage_sanity_check()
if isinstance(msg, ba.DieMessage): if isinstance(msg, ba.DieMessage):
if self.node: if self.node:

View File

@ -103,7 +103,7 @@ class TipsText(ba.Actor):
self.node.text = next_tip self.node.text = next_tip
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if __debug__ is True: if __debug__:
self._handlemessage_sanity_check() self._handlemessage_sanity_check()
if isinstance(msg, ba.DieMessage): if isinstance(msg, ba.DieMessage):
if self.node: if self.node:

View File

@ -171,7 +171,7 @@ class ZoomText(ba.Actor):
ba.DieMessage())) ba.DieMessage()))
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if __debug__ is True: if __debug__:
self._handlemessage_sanity_check() self._handlemessage_sanity_check()
if isinstance(msg, ba.DieMessage): if isinstance(msg, ba.DieMessage):
if not self._dying and self.node: if not self._dying and self.node:

View File

@ -213,7 +213,7 @@ class EasterEggHuntGame(ba.TeamGameActivity):
player = msg.spaz.getplayer() player = msg.spaz.getplayer()
if not player: if not player:
return return
self.stats.player_lost_spaz(player) self.stats.player_was_killed(player)
# Respawn them shortly. # Respawn them shortly.
assert self.initial_player_info is not None assert self.initial_player_info is not None

View File

@ -814,7 +814,7 @@ class FootballCoopGame(ba.CoopGameActivity):
# Respawn dead players. # Respawn dead players.
player = msg.spaz.player player = msg.spaz.player
self.stats.player_lost_spaz(player) self.stats.player_was_killed(player)
assert self.initial_player_info is not None assert self.initial_player_info is not None
respawn_time = 2.0 + len(self.initial_player_info) * 1.0 respawn_time = 2.0 + len(self.initial_player_info) * 1.0

View File

@ -415,7 +415,8 @@ class RunaroundGame(ba.CoopGameActivity):
}) })
ba.animate(light, 'intensity', {0.0: 0, 0.1: 1, 0.5: 0}, loop=False) ba.animate(light, 'intensity', {0.0: 0, 0.1: 1, 0.5: 0}, loop=False)
ba.timer(1.0, light.delete) ba.timer(1.0, light.delete)
spaz.handlemessage(ba.DieMessage(immediate=True, how='goal')) spaz.handlemessage(
ba.DieMessage(immediate=True, how=ba.DeathType.REACHED_GOAL))
if self._lives > 0: if self._lives > 0:
self._lives -= 1 self._lives -= 1
@ -1130,7 +1131,7 @@ class RunaroundGame(ba.CoopGameActivity):
return return
if not player: if not player:
return return
self.stats.player_lost_spaz(player) self.stats.player_was_killed(player)
# Respawn them shortly. # Respawn them shortly.
assert self.initial_player_info is not None assert self.initial_player_info is not None
@ -1141,7 +1142,7 @@ class RunaroundGame(ba.CoopGameActivity):
player, respawn_time) player, respawn_time)
elif isinstance(msg, spazbot.SpazBotDeathMessage): elif isinstance(msg, spazbot.SpazBotDeathMessage):
if msg.how == 'goal': if msg.how is ba.DeathType.REACHED_GOAL:
return return
pts, importance = msg.badguy.get_death_points(msg.how) pts, importance = msg.badguy.get_death_points(msg.how)
if msg.killerplayer is not None: if msg.killerplayer is not None:

View File

@ -263,7 +263,7 @@ class TheLastStandGame(ba.CoopGameActivity):
return return
if not player: if not player:
return return
self.stats.player_lost_spaz(player) self.stats.player_was_killed(player)
ba.timer(0.1, self._checkroundover) ba.timer(0.1, self._checkroundover)
elif isinstance(msg, ba.PlayerScoredMessage): elif isinstance(msg, ba.PlayerScoredMessage):

View File

@ -61,7 +61,7 @@ class CoopBrowserWindow(ba.Window):
app = ba.app app = ba.app
cfg = app.config cfg = app.config
# if they provided an origin-widget, scale up from that # If they provided an origin-widget, scale up from that.
scale_origin: Optional[Tuple[float, float]] scale_origin: Optional[Tuple[float, float]]
if origin_widget is not None: if origin_widget is not None:
self._transition_out = 'out_scale' self._transition_out = 'out_scale'
@ -71,8 +71,8 @@ class CoopBrowserWindow(ba.Window):
self._transition_out = 'out_right' self._transition_out = 'out_right'
scale_origin = None scale_origin = None
# try to recreate the same number of buttons we had last time so our # Try to recreate the same number of buttons we had last time so our
# re-selection code works # re-selection code works.
try: try:
self._tournament_button_count = app.config['Tournament Rows'] self._tournament_button_count = app.config['Tournament Rows']
except Exception: except Exception:
@ -159,7 +159,7 @@ class CoopBrowserWindow(ba.Window):
self._store_button_widget = None self._store_button_widget = None
self._league_rank_button_widget = None self._league_rank_button_widget = None
# move our corner buttons dynamically to keep them out of the way of # Move our corner buttons dynamically to keep them out of the way of
# the party icon :-( # the party icon :-(
self._update_corner_button_positions() self._update_corner_button_positions()
self._update_corner_button_positions_timer = ba.Timer( self._update_corner_button_positions_timer = ba.Timer(
@ -179,7 +179,7 @@ class CoopBrowserWindow(ba.Window):
self._selected_challenge_level = (cfg.get( self._selected_challenge_level = (cfg.get(
'Selected Coop Challenge Level', None)) 'Selected Coop Challenge Level', None))
# Don't want initial construction affecting our last-selected # Don't want initial construction affecting our last-selected.
self._do_selection_callbacks = False self._do_selection_callbacks = False
v = self._height - 95 v = self._height - 95
txt = ba.textwidget( txt = ba.textwidget(
@ -229,22 +229,23 @@ class CoopBrowserWindow(ba.Window):
simple_culling_v=10.0) simple_culling_v=10.0)
self._subcontainer: Optional[ba.Widget] = None self._subcontainer: Optional[ba.Widget] = None
# take note of our account state; we'll refresh later if this changes # Take note of our account state; we'll refresh later if this changes.
self._account_state_num = _ba.get_account_state_num() self._account_state_num = _ba.get_account_state_num()
# same for fg/bg state..
# Same for fg/bg state.
self._fg_state = app.fg_state self._fg_state = app.fg_state
self._refresh() self._refresh()
self._restore_state() self._restore_state()
# even though we might display cached tournament data immediately, we # Even though we might display cached tournament data immediately, we
# don't consider it valid until we've pinged # don't consider it valid until we've pinged.
# the server for an update # the server for an update
self._tourney_data_up_to_date = False self._tourney_data_up_to_date = False
# if we've got a cached tournament list for our account and info for # If we've got a cached tournament list for our account and info for
# each one of those tournaments, # each one of those tournaments, go ahead and display it as a
# go ahead and display it as a starting point... # starting point.
if (app.account_tournament_list is not None and if (app.account_tournament_list is not None and
app.account_tournament_list[0] == _ba.get_account_state_num() app.account_tournament_list[0] == _ba.get_account_state_num()
and all([ and all([
@ -257,7 +258,7 @@ class CoopBrowserWindow(ba.Window):
] ]
self._update_for_data(tourney_data) self._update_for_data(tourney_data)
# this will pull new data periodically, update timers, etc.. # This will pull new data periodically, update timers, etc.
self._update_timer = ba.Timer(1.0, self._update_timer = ba.Timer(1.0,
ba.WeakCall(self._update), ba.WeakCall(self._update),
timetype=ba.TimeType.REAL, timetype=ba.TimeType.REAL,
@ -267,31 +268,32 @@ class CoopBrowserWindow(ba.Window):
def _update(self) -> None: def _update(self) -> None:
cur_time = ba.time(ba.TimeType.REAL) cur_time = ba.time(ba.TimeType.REAL)
# if its been a while since we got a tournament update, consider the # If its been a while since we got a tournament update, consider the
# data invalid (prevents us from joining tournaments if our internet # data invalid (prevents us from joining tournaments if our internet
# connection goes down for a while) # connection goes down for a while).
if (self._last_tournament_query_response_time is None if (self._last_tournament_query_response_time is None
or ba.time(ba.TimeType.REAL) - or ba.time(ba.TimeType.REAL) -
self._last_tournament_query_response_time > 60.0 * 2): self._last_tournament_query_response_time > 60.0 * 2):
self._tourney_data_up_to_date = False self._tourney_data_up_to_date = False
# if our account state has changed, do a full request # If our account state has changed, do a full request.
account_state_num = _ba.get_account_state_num() account_state_num = _ba.get_account_state_num()
if account_state_num != self._account_state_num: if account_state_num != self._account_state_num:
self._account_state_num = account_state_num self._account_state_num = account_state_num
self._save_state() self._save_state()
self._refresh() self._refresh()
# also encourage a new tournament query since this will clear out
# our current results.. # Also encourage a new tournament query since this will clear out
# our current results.
if not self._doing_tournament_query: if not self._doing_tournament_query:
self._last_tournament_query_time = None self._last_tournament_query_time = None
# if we've been backgrounded/foregrounded, invalidate our # If we've been backgrounded/foregrounded, invalidate our
# tournament entries (they will be refreshed below asap) # tournament entries (they will be refreshed below asap).
if self._fg_state != ba.app.fg_state: if self._fg_state != ba.app.fg_state:
self._tourney_data_up_to_date = False self._tourney_data_up_to_date = False
# send off a new tournament query if its been long enough or whatnot.. # Send off a new tournament query if its been long enough or whatnot.
if not self._doing_tournament_query and ( if not self._doing_tournament_query and (
self._last_tournament_query_time is None self._last_tournament_query_time is None
or cur_time - self._last_tournament_query_time > 30.0 or cur_time - self._last_tournament_query_time > 30.0
@ -299,24 +301,28 @@ class CoopBrowserWindow(ba.Window):
self._fg_state = ba.app.fg_state self._fg_state = ba.app.fg_state
self._last_tournament_query_time = cur_time self._last_tournament_query_time = cur_time
self._doing_tournament_query = True self._doing_tournament_query = True
_ba.tournament_query(args={ _ba.tournament_query(
'source': 'coop window refresh', args={
'numScores': 1 'source': 'coop window refresh',
}, 'numScores': 1
callback=ba.WeakCall( },
self._on_tournament_query_response)) callback=ba.WeakCall(self._on_tournament_query_response),
)
# decrement time on our tournament buttons.. # Decrement time on our tournament buttons.
ads_enabled = _ba.have_incentivized_ad() ads_enabled = _ba.have_incentivized_ad()
for tbtn in self._tournament_buttons: for tbtn in self._tournament_buttons:
tbtn['time_remaining'] = max(0, tbtn['time_remaining'] - 1) tbtn['time_remaining'] = max(0, tbtn['time_remaining'] - 1)
if tbtn['time_remaining_value_text'] is not None: if tbtn['time_remaining_value_text'] is not None:
ba.textwidget( ba.textwidget(
edit=tbtn['time_remaining_value_text'], edit=tbtn['time_remaining_value_text'],
text=ba.timestring(tbtn['time_remaining'], centi=False) if text=ba.timestring(tbtn['time_remaining'],
centi=False,
suppress_format_warning=True) if
(tbtn['has_time_remaining'] (tbtn['has_time_remaining']
and self._tourney_data_up_to_date) else '-') and self._tourney_data_up_to_date) else '-')
# also adjust the ad icon visibility
# Also adjust the ad icon visibility.
if tbtn.get('allow_ads', False) and _ba.has_video_ads(): if tbtn.get('allow_ads', False) and _ba.has_video_ads():
ba.imagewidget(edit=tbtn['entry_fee_ad_image'], ba.imagewidget(edit=tbtn['entry_fee_ad_image'],
opacity=1.0 if ads_enabled else 0.25) opacity=1.0 if ads_enabled else 0.25)
@ -427,7 +433,8 @@ class CoopBrowserWindow(ba.Window):
leader_score = ( leader_score = (
ba.timestring(score[0] * 10, ba.timestring(score[0] * 10,
centi=True, centi=True,
timeformat=ba.TimeFormat.MILLISECONDS) timeformat=ba.TimeFormat.MILLISECONDS,
suppress_format_warning=True)
if entry['scoreType'] == 'time' else str(score[0])) if entry['scoreType'] == 'time' else str(score[0]))
else: else:
tbtn['leader'] = None tbtn['leader'] = None
@ -445,7 +452,9 @@ class CoopBrowserWindow(ba.Window):
'-' if entry is None or 'totalTime' not in entry else ba.Lstr( '-' if entry is None or 'totalTime' not in entry else ba.Lstr(
resource=self._r + '.ofTotalTimeText', resource=self._r + '.ofTotalTimeText',
subs=[('${TOTAL}', subs=[('${TOTAL}',
ba.timestring(entry['totalTime'], centi=False))])) ba.timestring(entry['totalTime'],
centi=False,
suppress_format_warning=True))]))
ba.textwidget(edit=tbtn['time_remaining_out_of_text'], ba.textwidget(edit=tbtn['time_remaining_out_of_text'],
text=out_of_time_text) text=out_of_time_text)
@ -516,18 +525,14 @@ class CoopBrowserWindow(ba.Window):
final_fee_str = ( final_fee_str = (
ba.charstr(ba.SpecialChar.TICKET_BACKING) + ba.charstr(ba.SpecialChar.TICKET_BACKING) +
str(final_fee)) str(final_fee))
# final_fee_str: Union[str, ba.Lstr] = (
# '' if fee_var is None else ba.Lstr(
# resource='getTicketsWindow.freeText') if final_fee == 0
# else (ba.specialchar('ticket_backing') + str(final_fee)))
ad_tries_remaining = ba.app.tournament_info[ ad_tries_remaining = ba.app.tournament_info[
tbtn['tournament_id']]['adTriesRemaining'] tbtn['tournament_id']]['adTriesRemaining']
free_tries_remaining = ba.app.tournament_info[ free_tries_remaining = ba.app.tournament_info[
tbtn['tournament_id']]['freeTriesRemaining'] tbtn['tournament_id']]['freeTriesRemaining']
# now, if this fee allows ads and we support video ads, show # Now, if this fee allows ads and we support video ads, show
# the 'or ad' version # the 'or ad' version.
if allow_ads and _ba.has_video_ads(): if allow_ads and _ba.has_video_ads():
ads_enabled = _ba.have_incentivized_ad() ads_enabled = _ba.have_incentivized_ad()
ba.imagewidget(edit=tbtn['entry_fee_ad_image'], ba.imagewidget(edit=tbtn['entry_fee_ad_image'],
@ -542,7 +547,8 @@ class CoopBrowserWindow(ba.Window):
tbtn['button_y'] + tbtn['button_scale_y'] - 60), tbtn['button_y'] + tbtn['button_scale_y'] - 60),
scale=1.3, scale=1.3,
text=final_fee_str) text=final_fee_str)
# possibly show number of ad-plays remaining
# Possibly show number of ad-plays remaining.
ba.textwidget( ba.textwidget(
edit=tbtn['entry_fee_text_remaining'], edit=tbtn['entry_fee_text_remaining'],
position=(tbtn['button_x'] + 360, position=(tbtn['button_x'] + 360,
@ -559,7 +565,8 @@ class CoopBrowserWindow(ba.Window):
tbtn['button_y'] + tbtn['button_scale_y'] - 80), tbtn['button_y'] + tbtn['button_scale_y'] - 80),
scale=1.3, scale=1.3,
text=final_fee_str) text=final_fee_str)
# possibly show number of free-plays remaining
# Possibly show number of free-plays remaining.
ba.textwidget( ba.textwidget(
edit=tbtn['entry_fee_text_remaining'], edit=tbtn['entry_fee_text_remaining'],
position=(tbtn['button_x'] + 360, position=(tbtn['button_x'] + 360,
@ -927,15 +934,8 @@ class CoopBrowserWindow(ba.Window):
'Challenges:Meteor Shower', 'Challenges:Meteor Shower',
'Challenges:Target Practice B', 'Challenges:Target Practice B',
'Challenges:Target Practice', 'Challenges:Target Practice',
# 'Challenges:Lake Frigid Race',
# 'Challenges:Uber Runaround',
# 'Challenges:Runaround',
# 'Challenges:Pro Race',
# 'Challenges:Pro Football',
# 'Challenges:Epic Meteor Shower',
# 'Challenges:Testing',
# 'User:Ninja Fight',
] ]
# Show easter-egg-hunt either if its easter or we own it. # Show easter-egg-hunt either if its easter or we own it.
if _ba.get_account_misc_read_val( if _ba.get_account_misc_read_val(
'easter', False) or _ba.get_purchased('games.easter_egg_hunt'): 'easter', False) or _ba.get_purchased('games.easter_egg_hunt'):
@ -1456,7 +1456,7 @@ class CoopBrowserWindow(ba.Window):
ba.playsound(ba.getsound('error')) ba.playsound(ba.getsound('error'))
return return
# game is whatever the tournament tells us it is # Game is whatever the tournament tells us it is.
game = ba.app.tournament_info[ game = ba.app.tournament_info[
tournament_button['tournament_id']]['game'] tournament_button['tournament_id']]['game']
@ -1469,7 +1469,7 @@ class CoopBrowserWindow(ba.Window):
height=130) height=130)
return return
# infinite onslaught/runaround require pro; bring up a store link if # Infinite onslaught/runaround require pro; bring up a store link if
# need be. # need be.
if tournament_button is None and game in ( if tournament_button is None and game in (
'Challenges:Infinite Runaround', 'Challenges:Infinite Runaround',

View File

@ -96,7 +96,7 @@ class CompoundField(BaseField, Generic[TC]):
value: TC, value: TC,
store_default: bool = True) -> None: store_default: bool = True) -> None:
super().__init__(d_key) super().__init__(d_key)
if __debug__ is True: if __debug__:
from efro.entity._value import CompoundValue from efro.entity._value import CompoundValue
assert isinstance(value, CompoundValue) assert isinstance(value, CompoundValue)
assert not hasattr(value, 'd_data') assert not hasattr(value, 'd_data')

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND --> <!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-04-05 for Ballistica version 1.5.0 build 20001</em></h4> <h4><em>last updated on 2020-04-06 for Ballistica version 1.5.0 build 20001</em></h4>
<p>This page documents the Python classes and functions in the 'ba' module, <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> 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> <hr>
@ -165,6 +165,7 @@
</ul> </ul>
<h4><a name="class_category_Enums">Enums</a></h4> <h4><a name="class_category_Enums">Enums</a></h4>
<ul> <ul>
<li><a href="#class_ba_DeathType">ba.DeathType</a></li>
<li><a href="#class_ba_MusicPlayMode">ba.MusicPlayMode</a></li> <li><a href="#class_ba_MusicPlayMode">ba.MusicPlayMode</a></li>
<li><a href="#class_ba_MusicType">ba.MusicType</a></li> <li><a href="#class_ba_MusicType">ba.MusicType</a></li>
<li><a href="#class_ba_Permission">ba.Permission</a></li> <li><a href="#class_ba_Permission">ba.Permission</a></li>
@ -1575,6 +1576,22 @@ the data object is requested and when it's value is accessed.</p>
</dd> </dd>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_DeathType">ba.DeathType</a></strong></h3>
<p>inherits from: enum.Enum</p>
<p>A reason for a death.</p>
<p>Category: <a href="#class_category_Enums">Enums</a>
</p>
<h3>Values:</h3>
<ul>
<li>GENERIC</li>
<li>IMPACT</li>
<li>FALL</li>
<li>REACHED_GOAL</li>
<li>LEFT_GAME</li>
</ul>
<hr>
<h2><strong><a name="class_ba_Dependency">ba.Dependency</a></strong></h3> <h2><strong><a name="class_ba_Dependency">ba.Dependency</a></strong></h3>
<p>inherits from: <a href="#class_typing_Generic">typing.Generic</a></p> <p>inherits from: <a href="#class_typing_Generic">typing.Generic</a></p>
<p>A dependency on a DependencyComponent (with an optional config).</p> <p>A dependency on a DependencyComponent (with an optional config).</p>
@ -1747,9 +1764,8 @@ Exception types on other errors).</p>
<h5><a href="#attr_ba_DieMessage__how">how</a>, <a href="#attr_ba_DieMessage__immediate">immediate</a></h5> <h5><a href="#attr_ba_DieMessage__how">how</a>, <a href="#attr_ba_DieMessage__immediate">immediate</a></h5>
<dl> <dl>
<dt><h4><a name="attr_ba_DieMessage__how">how</a></h4></dt><dd> <dt><h4><a name="attr_ba_DieMessage__how">how</a></h4></dt><dd>
<p><span>str</span></p> <p><span>DeathType</span></p>
<p>The particular reason for death; 'fall', 'impact', 'leftGame', etc. <p>The particular reason for death.</p>
This can be examined for scoring or other purposes.</p>
</dd> </dd>
<dt><h4><a name="attr_ba_DieMessage__immediate">immediate</a></h4></dt><dd> <dt><h4><a name="attr_ba_DieMessage__immediate">immediate</a></h4></dt><dd>
@ -1764,7 +1780,7 @@ its time with lingering corpses, sound effects, etc.</p>
<h3>Methods:</h3> <h3>Methods:</h3>
<dl> <dl>
<dt><h4><a name="method_ba_DieMessage____init__">&lt;constructor&gt;</a></dt></h4><dd> <dt><h4><a name="method_ba_DieMessage____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.DieMessage(immediate: 'bool' = False, how: 'str' = 'generic')</span></p> <p><span>ba.DieMessage(immediate: bool = False, how: DeathType = &lt;DeathType.GENERIC: generic&gt;)</span></p>
</dd> </dd>
</dl> </dl>
@ -3666,7 +3682,7 @@ other players.</p>
</dd> </dd>
</dl> </dl>
<h3>Methods:</h3> <h3>Methods:</h3>
<h5><a href="#method_ba_PlayerRecord____init__">&lt;constructor&gt;</a>, <a href="#method_ba_PlayerRecord__associate_with_player">associate_with_player()</a>, <a href="#method_ba_PlayerRecord__cancel_multi_kill_timer">cancel_multi_kill_timer()</a>, <a href="#method_ba_PlayerRecord__get_icon">get_icon()</a>, <a href="#method_ba_PlayerRecord__get_last_player">get_last_player()</a>, <a href="#method_ba_PlayerRecord__get_name">get_name()</a>, <a href="#method_ba_PlayerRecord__get_spaz">get_spaz()</a>, <a href="#method_ba_PlayerRecord__getactivity">getactivity()</a>, <a href="#method_ba_PlayerRecord__submit_kill">submit_kill()</a></h5> <h5><a href="#method_ba_PlayerRecord____init__">&lt;constructor&gt;</a>, <a href="#method_ba_PlayerRecord__associate_with_player">associate_with_player()</a>, <a href="#method_ba_PlayerRecord__cancel_multi_kill_timer">cancel_multi_kill_timer()</a>, <a href="#method_ba_PlayerRecord__get_icon">get_icon()</a>, <a href="#method_ba_PlayerRecord__get_last_player">get_last_player()</a>, <a href="#method_ba_PlayerRecord__get_name">get_name()</a>, <a href="#method_ba_PlayerRecord__getactivity">getactivity()</a>, <a href="#method_ba_PlayerRecord__submit_kill">submit_kill()</a></h5>
<dl> <dl>
<dt><h4><a name="method_ba_PlayerRecord____init__">&lt;constructor&gt;</a></dt></h4><dd> <dt><h4><a name="method_ba_PlayerRecord____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.PlayerRecord(name: str, name_full: str, player: <a href="#class_ba_Player">ba.Player</a>, stats: <a href="#class_ba_Stats">ba.Stats</a>)</span></p> <p><span>ba.PlayerRecord(name: str, name_full: str, player: <a href="#class_ba_Player">ba.Player</a>, stats: <a href="#class_ba_Stats">ba.Stats</a>)</span></p>
@ -3701,12 +3717,6 @@ other players.</p>
<p>Return the player entry's name.</p> <p>Return the player entry's name.</p>
</dd>
<dt><h4><a name="method_ba_PlayerRecord__get_spaz">get_spaz()</a></dt></h4><dd>
<p><span>get_spaz(self) -&gt; Optional[<a href="#class_ba_Actor">ba.Actor</a>]</span></p>
<p>Return the player entry's spaz.</p>
</dd> </dd>
<dt><h4><a name="method_ba_PlayerRecord__getactivity">getactivity()</a></dt></h4><dd> <dt><h4><a name="method_ba_PlayerRecord__getactivity">getactivity()</a></dt></h4><dd>
<p><span>getactivity(self) -&gt; Optional[<a href="#class_ba_Activity">ba.Activity</a>]</span></p> <p><span>getactivity(self) -&gt; Optional[<a href="#class_ba_Activity">ba.Activity</a>]</span></p>
@ -4145,7 +4155,7 @@ session.set_activity(foo) and then <a href="#function_ba_newnode">ba.newnode</a>
</p> </p>
<h3>Methods:</h3> <h3>Methods:</h3>
<h5><a href="#method_ba_Stats____init__">&lt;constructor&gt;</a>, <a href="#method_ba_Stats__get_records">get_records()</a>, <a href="#method_ba_Stats__getactivity">getactivity()</a>, <a href="#method_ba_Stats__player_got_hit">player_got_hit()</a>, <a href="#method_ba_Stats__player_got_new_spaz">player_got_new_spaz()</a>, <a href="#method_ba_Stats__player_lost_spaz">player_lost_spaz()</a>, <a href="#method_ba_Stats__player_scored">player_scored()</a>, <a href="#method_ba_Stats__register_player">register_player()</a>, <a href="#method_ba_Stats__reset">reset()</a>, <a href="#method_ba_Stats__reset_accum">reset_accum()</a>, <a href="#method_ba_Stats__set_activity">set_activity()</a></h5> <h5><a href="#method_ba_Stats____init__">&lt;constructor&gt;</a>, <a href="#method_ba_Stats__get_records">get_records()</a>, <a href="#method_ba_Stats__getactivity">getactivity()</a>, <a href="#method_ba_Stats__player_got_hit">player_got_hit()</a>, <a href="#method_ba_Stats__player_scored">player_scored()</a>, <a href="#method_ba_Stats__player_was_killed">player_was_killed()</a>, <a href="#method_ba_Stats__register_player">register_player()</a>, <a href="#method_ba_Stats__reset">reset()</a>, <a href="#method_ba_Stats__reset_accum">reset_accum()</a>, <a href="#method_ba_Stats__set_activity">set_activity()</a></h5>
<dl> <dl>
<dt><h4><a name="method_ba_Stats____init__">&lt;constructor&gt;</a></dt></h4><dd> <dt><h4><a name="method_ba_Stats____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.Stats()</span></p> <p><span>ba.Stats()</span></p>
@ -4170,18 +4180,6 @@ session.set_activity(foo) and then <a href="#function_ba_newnode">ba.newnode</a>
<p>Call this when a player got hit.</p> <p>Call this when a player got hit.</p>
</dd>
<dt><h4><a name="method_ba_Stats__player_got_new_spaz">player_got_new_spaz()</a></dt></h4><dd>
<p><span>player_got_new_spaz(self, player: <a href="#class_ba_Player">ba.Player</a>, spaz: <a href="#class_ba_Actor">ba.Actor</a>) -&gt; None</span></p>
<p>Call this when a player gets a new Spaz.</p>
</dd>
<dt><h4><a name="method_ba_Stats__player_lost_spaz">player_lost_spaz()</a></dt></h4><dd>
<p><span>player_lost_spaz(self, player: <a href="#class_ba_Player">ba.Player</a>, killed: bool = False, killer: <a href="#class_ba_Player">ba.Player</a> = None) -&gt; None</span></p>
<p>Should be called when a player loses a spaz.</p>
</dd> </dd>
<dt><h4><a name="method_ba_Stats__player_scored">player_scored()</a></dt></h4><dd> <dt><h4><a name="method_ba_Stats__player_scored">player_scored()</a></dt></h4><dd>
<p><span>player_scored(self, player: <a href="#class_ba_Player">ba.Player</a>, base_points: int = 1, target: Sequence[float] = None, kill: bool = False, victim_player: <a href="#class_ba_Player">ba.Player</a> = None, scale: float = 1.0, color: Sequence[float] = None, title: Union[str, <a href="#class_ba_Lstr">ba.Lstr</a>] = None, screenmessage: bool = True, display: bool = True, importance: int = 1, showpoints: bool = True, big_message: bool = False) -&gt; int</span></p> <p><span>player_scored(self, player: <a href="#class_ba_Player">ba.Player</a>, base_points: int = 1, target: Sequence[float] = None, kill: bool = False, victim_player: <a href="#class_ba_Player">ba.Player</a> = None, scale: float = 1.0, color: Sequence[float] = None, title: Union[str, <a href="#class_ba_Lstr">ba.Lstr</a>] = None, screenmessage: bool = True, display: bool = True, importance: int = 1, showpoints: bool = True, big_message: bool = False) -&gt; int</span></p>
@ -4190,6 +4188,12 @@ session.set_activity(foo) and then <a href="#function_ba_newnode">ba.newnode</a>
<p>Return value is actual score with multipliers and such factored in.</p> <p>Return value is actual score with multipliers and such factored in.</p>
</dd>
<dt><h4><a name="method_ba_Stats__player_was_killed">player_was_killed()</a></dt></h4><dd>
<p><span>player_was_killed(self, player: <a href="#class_ba_Player">ba.Player</a>, killed: bool = False, killer: <a href="#class_ba_Player">ba.Player</a> = None) -&gt; None</span></p>
<p>Should be called when a player is killed.</p>
</dd> </dd>
<dt><h4><a name="method_ba_Stats__register_player">register_player()</a></dt></h4><dd> <dt><h4><a name="method_ba_Stats__register_player">register_player()</a></dt></h4><dd>
<p><span>register_player(self, player: <a href="#class_ba_Player">ba.Player</a>) -&gt; None</span></p> <p><span>register_player(self, player: <a href="#class_ba_Player">ba.Player</a>) -&gt; None</span></p>