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/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/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c4/9f/2df9fcc014404595eaa88a50d62b",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/72/c0/fd83de83a099f829444778a65fac",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d6/33/b77a77b8d82c616e2a66146cb6b3",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/cc/a6/f247e2bb7307cf759a791b39d876",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/c0/82/dc8e77df214bccf2dead8c25685b",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/1c/9b/45c1dafbe3e2556b4824ee5010ad"
"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/99/74/1709486aaf04f5fdd3f90ca0aab9",
"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/e9/ef/82e9a50695f333d68847585aa9a6",
"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/18/2e/cfa42c79a5024bb2111dfe347b51"
}

View File

@ -1,8 +1,10 @@
name: CI
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:
pull_request:
schedule:
# Note: '*' is a special character in YAML so we have to quote the str.
- cron: '0 12 * * *'

View File

@ -48,6 +48,7 @@
<scope name="UncheckedPython" level="WEAK WARNING" enabled="false" />
</inspection_tool>
<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">
<scope name="PyIgnoreUnresolved" level="WARNING" enabled="false">
<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)
# SOURCES_HASH=92329394206923431348342003830010439152
# SOURCES_HASH=262346642235815908245289158414705543661
# I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression
@ -3607,20 +3607,20 @@ def submit_analytics_counts() -> None:
def submit_score(game: str,
config: str,
name: Any,
score: int,
score: Optional[int],
callback: Callable,
friend_callback: Optional[Callable],
order: str = 'increasing',
tournament_id: Optional[str] = None,
score_type: str = 'points',
campaign: Optional[ba.Campaign] = None,
level: Optional[ba.Level] = None) -> None:
"""submit_score(game: str, config: str, name: Any, score: int,
campaign: Optional[str] = None,
level: Optional[str] = None) -> None:
"""submit_score(game: str, config: str, name: Any, score: Optional[int],
callback: Callable, friend_callback: Optional[Callable],
order: str = 'increasing', tournament_id: Optional[str] = None,
score_type: str = 'points',
campaign: Optional[ba.Campaign] = None,
level: Optional[ba.Level] = None) -> None
campaign: Optional[str] = None,
level: Optional[str] = None) -> None
(internal)

View File

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

View File

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

View File

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

View File

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

View File

@ -24,6 +24,7 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
from enum import Enum
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
class DieMessage:
"""A message telling an object to die.
@ -57,12 +70,11 @@ class DieMessage:
its time with lingering corpses, sound effects, etc.
how
The particular reason for death; 'fall', 'impact', 'leftGame', etc.
This can be examined for scoring or other purposes.
The particular reason for death.
"""
immediate: bool = False
how: str = 'generic'
how: DeathType = DeathType.GENERIC
@dataclass

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -350,7 +350,7 @@ class Flag(ba.Actor):
elif isinstance(msg, ba.OutOfBoundsMessage):
# We just kill ourselves when out-of-bounds.. would we ever not
# want this?..
self.handlemessage(ba.DieMessage(how='fall'))
self.handlemessage(ba.DieMessage(how=ba.DeathType.FALL))
elif isinstance(msg, ba.PickedUpMessage):
self._held_count += 1
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-locals
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]
if isinstance(texture, dict):
tint_color = texture['tint_color']
@ -156,13 +157,13 @@ class Image(ba.Actor):
cmb.input1 = position[1]
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:
ba.timer(transition_delay + transition_out_delay + 1.0,
ba.WeakCall(self.handlemessage, ba.DieMessage()))
def handlemessage(self, msg: Any) -> Any:
if __debug__ is True:
if __debug__:
self._handlemessage_sanity_check()
if isinstance(msg, ba.DieMessage):
if self.node:

View File

@ -53,7 +53,7 @@ class PlayerSpazDeathMessage:
"""
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."""
self.spaz = spaz
self.killed = was_killed
@ -252,7 +252,8 @@ class PlayerSpaz(basespaz.Spaz):
if not self._dead:
# 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()

View File

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

View File

@ -128,8 +128,10 @@ class Spaz(ba.Actor):
else:
self._punch_power_scale = factory.punch_power_scale
self.fly = ba.sharedobj('globals').happy_thoughts_mode
assert isinstance(activity, ba.GameActivity)
self._hockey = activity.map.is_hockey
if isinstance(activity, ba.GameActivity):
self._hockey = activity.map.is_hockey
else:
self._hockey = False
self._punched_nodes: Set[ba.Node] = set()
self._cursed = False
self._connected_to_player: Optional[ba.Player] = None
@ -588,11 +590,12 @@ class Spaz(ba.Actor):
def on_punched(self, damage: int) -> None:
"""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."""
del how # unused arg
del how # Unused.
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
return (10 if num_hits < 2 else 5) * self.points_mult, importance
@ -604,7 +607,8 @@ class Spaz(ba.Actor):
if not self._cursed:
factory = get_factory()
self._cursed = True
# add the curse material..
# Add the curse material.
for attr in ['materials', 'roller_materials']:
materials = getattr(self.node, attr)
if factory.curse_material not in materials:
@ -616,7 +620,7 @@ class Spaz(ba.Actor):
if self.curse_time is None:
self.node.curse_death_time = -1
else:
# note: curse-death-time takes milliseconds
# Note: curse-death-time takes milliseconds.
tval = ba.time()
assert isinstance(tval, int)
self.node.curse_death_time = int(1000.0 *
@ -629,7 +633,7 @@ class Spaz(ba.Actor):
"""
assert self.node
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_cooldown = 300
else:
@ -664,7 +668,7 @@ class Spaz(ba.Actor):
self.shield_decay_timer = ba.Timer(0.5,
ba.WeakCall(self.shield_decay),
repeat=True)
# so user can see the decay
# So user can see the decay.
self.shield.always_show_health_bar = True
def shield_decay(self) -> None:
@ -691,24 +695,25 @@ class Spaz(ba.Actor):
# pylint: disable=too-many-return-statements
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
if __debug__ is True:
if __debug__:
self._handlemessage_sanity_check()
if isinstance(msg, ba.PickedUpMessage):
if self.node:
self.node.handlemessage("hurt_sound")
self.node.handlemessage("picked_up")
# this counts as a hit
# This counts as a hit.
self._num_times_hit += 1
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?..)
# 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))
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?..)
ba.timer(0.001, ba.WeakCall(self._hit_self, msg.intensity))
@ -799,7 +804,8 @@ class Spaz(ba.Actor):
timeformat=ba.TimeFormat.MILLISECONDS))
elif msg.poweruptype == 'shield':
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)
elif msg.poweruptype == 'curse':
self.curse()
@ -825,7 +831,8 @@ class Spaz(ba.Actor):
elif msg.poweruptype == 'health':
if self._cursed:
self._cursed = False
# remove cursed material
# Remove cursed material.
factory = get_factory()
for attr in ['materials', 'roller_materials']:
materials = getattr(self.node, attr)
@ -861,7 +868,7 @@ class Spaz(ba.Actor):
self.node.frozen = True
ba.timer(5.0, ba.WeakCall(self.handlemessage,
ba.ThawMessage()))
# instantly shatter if we're already dead
# Instantly shatter if we're already dead.
# (otherwise its hard to tell we're dead)
if self.hitpoints <= 0:
self.shatter()
@ -880,7 +887,7 @@ class Spaz(ba.Actor):
position=self.node.position)
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)
local_time = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
assert isinstance(local_time, int)
@ -893,13 +900,13 @@ class Spaz(ba.Actor):
velocity_mag = msg.velocity_magnitude * self.impact_scale
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 msg.flat_damage:
damage = msg.flat_damage * self.impact_scale
else:
# hit our spaz with an impulse but tell it to only return
# theoretical damage; not apply the impulse..
# Hit our spaz with an impulse but tell it to only return
# theoretical damage; not apply the impulse.
assert msg.force_direction is not None
self.node.handlemessage(
"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):
self.shatter()
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
# (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)
elif isinstance(msg, ba.OutOfBoundsMessage):
# by default we just die here
self.handlemessage(ba.DieMessage(how='fall'))
# By default we just die here.
self.handlemessage(ba.DieMessage(how=ba.DeathType.FALL))
elif isinstance(msg, ba.StandMessage):
self._last_stand_pos = (msg.position[0], msg.position[1],
msg.position[2])
@ -1123,8 +1132,10 @@ class Spaz(ba.Actor):
self.node.handlemessage("stand", msg.position[0],
msg.position[1], msg.position[2],
msg.angle)
elif isinstance(msg, CurseExplodeMessage):
self.curse_explode()
elif isinstance(msg, PunchHitMessage):
if not self.node:
return None

View File

@ -80,7 +80,7 @@ class SpazBotDeathMessage:
"""
def __init__(self, badguy: SpazBot, killerplayer: Optional[ba.Player],
how: str):
how: ba.DeathType):
"""Instantiate with given values."""
self.badguy = badguy
self.killerplayer = killerplayer
@ -111,9 +111,9 @@ class SpazBot(basespaz.Spaz):
static = False
bouncy = False
run = False
charge_dist_min = 0.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
charge_dist_min = 0.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.
charge_speed_min = 0.4
charge_speed_max = 1.0
throw_dist_min = 5.0
@ -193,6 +193,7 @@ class SpazBot(basespaz.Spaz):
assert self._player_pts
for plpt, plvel in self._player_pts:
dist = (plpt - botpt).length()
# Ignore player-points that are significantly below the bot
# (keeps bots from following players off cliffs).
if (closest_dist is None
@ -218,9 +219,12 @@ class SpazBot(basespaz.Spaz):
# pylint: disable=too-many-locals
if self.update_callback is not None:
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
our_pos = ba.Vec3(pos[0], 0, pos[2])
can_attack = True
@ -239,11 +243,13 @@ class SpazBot(basespaz.Spaz):
holding_flag = False
else:
holding_flag = False
# If we're holding the flag, just walk left.
if holding_flag:
# just walk left
# Just walk left.
self.node.move_left_right = -1.0
self.node.move_up_down = 0.0
# Otherwise try to go pick it up.
else:
assert self.target_flag.node
@ -273,6 +279,7 @@ class SpazBot(basespaz.Spaz):
self.node.pickup_pressed = True
self.node.pickup_pressed = False
return
# Not a flag-bearer. If we're holding anything but a bomb, drop it.
if self.node.hold_node:
try:
@ -289,12 +296,13 @@ class SpazBot(basespaz.Spaz):
target_pt_raw, target_vel = self._get_target_player_pt()
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:
target_pt_raw = self.target_point_default
target_vel = ba.Vec3(0, 0, 0)
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:
self.node.move_left_right = 0
self.node.move_up_down = 0
@ -303,13 +311,14 @@ class SpazBot(basespaz.Spaz):
self.node.pickup_pressed = False
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
assert target_vel is not None
target_vel[1] = 0.0
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)
target_pt = (target_pt_raw +
target_vel * dist_raw * 0.3 * self._lead_amount)
@ -319,25 +328,26 @@ class SpazBot(basespaz.Spaz):
to_target = diff.normalized()
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:
assert self._throw_release_time is not None
time_till_throw = self._throw_release_time - ba.time()
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:
self.drop_bomb()
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:
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:
# jump and throw..
# Jump and throw.
def _safe_pickup(node: ba.Node) -> None:
if node and self.node:
self.node.pickup_pressed = True
@ -346,25 +356,26 @@ class SpazBot(basespaz.Spaz):
if dist > 5.0:
self.node.jump_pressed = True
self.node.jump_pressed = False
# throws:
# Throws:
ba.timer(0.1, ba.Call(_safe_pickup, self.node))
else:
# throws:
# Throws:
ba.timer(0.1, ba.Call(_safe_pickup, self.node))
if self.static:
if time_till_throw < 0.3:
speed = 1.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:
speed = 0.02
else:
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
else:
# earlier we can hold or move backward for a whiplash
# Earlier we can hold or move backward for a whiplash.
speed = 0.0125
self.node.move_left_right = to_target.x * 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:
self._charge_speed = random.uniform(self.charge_speed_min,
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:
self._lead_amount = 0.3
self._running = True
@ -388,8 +400,8 @@ class SpazBot(basespaz.Spaz):
self.node.move_up_down = to_target.z * -1.0 * self._charge_speed
elif self._mode == 'wait':
# every now and then, aim towards our target..
# other than that, just stand there
# Every now and then, aim towards our target.
# Other than that, just stand there.
if ba.time(timeformat=ba.TimeFormat.MILLISECONDS) % 1234 < 100:
self.node.move_left_right = to_target.x * (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
elif self._mode == 'flee':
# 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)
# 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).
if self.run and dist < 3.0:
self._running = True
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_up_down = to_target.z
# we might wanna switch states unless we're doing a throw
# (in which case that's our sole concern)
# We might wanna switch states unless we're doing a throw
# (in which case that's our sole concern).
if self._mode != 'throw':
# if we're currently charging, keep track of how far we are
# from our target.. when this value increases it means our charge
# is over (ran by them or something)
# If we're currently charging, keep track of how far we are
# from our target. When this value increases it means our charge
# is over (ran by them or something).
if self._mode == 'charge':
if (self._charge_closing_in
and self._last_charge_dist < dist < 3.0):
self._charge_closing_in = False
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
and random.random() < self.throwiness and can_attack):
self._mode = 'throw'
@ -434,15 +446,15 @@ class SpazBot(basespaz.Spaz):
(1.0 / self.throw_rate) *
(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:
self._mode = 'wait'
# if we're too close to charge (and aren't in the middle of an
# existing charge) run away
# If we're too close to charge (and aren't in the middle of an
# existing charge) run away.
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
# but to charge..
# ..unless we're near an edge, in which case we've got no
# choice but to charge.
if self.map.is_point_near_edge(our_pos, self._running):
if self._mode != 'charge':
self._mode = 'charge'
@ -452,7 +464,7 @@ class SpazBot(basespaz.Spaz):
else:
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!
elif (dist < self.charge_dist_max or dist > self.throw_dist_max
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._last_charge_dist = dist
# we're too close to throw but too far to charge - either run
# away or just chill if we're near an edge
# We're too close to throw but too far to charge - either run
# away or just chill if we're near an edge.
elif dist < self.throw_dist_min:
# charge if either we're within charge range or
# cant retreat to throw
# Charge if either we're within charge range or
# cant retreat to throw.
self._mode = 'flee'
# Do some awesome jumps if we're running.
@ -494,29 +506,30 @@ class SpazBot(basespaz.Spaz):
def on_expire(self) -> None:
basespaz.Spaz.on_expire(self)
# we're being torn down; release
# our callback(s) so there's no chance of them
# keeping activities or other things alive..
# We're being torn down; release our callback(s) so there's
# no chance of them keeping activities or other things alive.
self.update_callback = None
def handlemessage(self, msg: Any) -> Any:
# pylint: disable=too-many-branches
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):
super().handlemessage(msg) # augment standard behavior
super().handlemessage(msg) # Augment standard behavior.
self.held_count += 1
picked_up_by = msg.node.source_player
if picked_up_by:
self.last_player_held_by = picked_up_by
elif isinstance(msg, ba.DroppedMessage):
super().handlemessage(msg) # augment standard behavior
super().handlemessage(msg) # Augment standard behavior.
self.held_count -= 1
if self.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:
if msg.node:
picked_up_by = msg.node.source_player
@ -533,18 +546,19 @@ class SpazBot(basespaz.Spaz):
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:
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:
killerplayer = self.last_player_held_by
else:
# otherwise if they were attacked by someone in the
# last few seconds that person's the killer..
# otherwise it was a suicide
# If they were attacked by someone in the last few
# seconds that person's the killer.
# Otherwise it was a suicide.
if (self.last_player_attacked_by
and ba.time() - self.last_attacked_time < 4.0):
killerplayer = self.last_player_attacked_by
@ -552,15 +566,15 @@ class SpazBot(basespaz.Spaz):
killerplayer = None
activity = self._activity()
# (convert dead refs to None)
# (convert dead player refs to None)
if not killerplayer:
killerplayer = None
if activity is not None:
activity.handlemessage(
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):
if msg.source_player:
self.last_player_attacked_by = msg.source_player
@ -889,8 +903,9 @@ class BotSet:
def __init__(self) -> None:
"""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_add_list = 0
self._bot_update_list = 0
@ -963,7 +978,7 @@ class BotSet:
self._bot_update_list = (self._bot_update_list +
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 = []
for player in ba.getactivity().players:
try:
@ -980,7 +995,8 @@ class BotSet:
def clear(self) -> None:
"""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)
if activity is None or activity.is_expired():
return
@ -1025,7 +1041,8 @@ class BotSet:
Use this when the bots have won a game.
"""
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 bot in botlist:
if bot.node:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -61,7 +61,7 @@ class CoopBrowserWindow(ba.Window):
app = ba.app
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]]
if origin_widget is not None:
self._transition_out = 'out_scale'
@ -71,8 +71,8 @@ class CoopBrowserWindow(ba.Window):
self._transition_out = 'out_right'
scale_origin = None
# try to recreate the same number of buttons we had last time so our
# re-selection code works
# Try to recreate the same number of buttons we had last time so our
# re-selection code works.
try:
self._tournament_button_count = app.config['Tournament Rows']
except Exception:
@ -159,7 +159,7 @@ class CoopBrowserWindow(ba.Window):
self._store_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 :-(
self._update_corner_button_positions()
self._update_corner_button_positions_timer = ba.Timer(
@ -179,7 +179,7 @@ class CoopBrowserWindow(ba.Window):
self._selected_challenge_level = (cfg.get(
'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
v = self._height - 95
txt = ba.textwidget(
@ -229,22 +229,23 @@ class CoopBrowserWindow(ba.Window):
simple_culling_v=10.0)
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()
# same for fg/bg state..
# Same for fg/bg state.
self._fg_state = app.fg_state
self._refresh()
self._restore_state()
# even though we might display cached tournament data immediately, we
# don't consider it valid until we've pinged
# Even though we might display cached tournament data immediately, we
# don't consider it valid until we've pinged.
# the server for an update
self._tourney_data_up_to_date = False
# if we've got a cached tournament list for our account and info for
# each one of those tournaments,
# go ahead and display it as a starting point...
# If we've got a cached tournament list for our account and info for
# each one of those tournaments, go ahead and display it as a
# starting point.
if (app.account_tournament_list is not None and
app.account_tournament_list[0] == _ba.get_account_state_num()
and all([
@ -257,7 +258,7 @@ class CoopBrowserWindow(ba.Window):
]
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,
ba.WeakCall(self._update),
timetype=ba.TimeType.REAL,
@ -267,31 +268,32 @@ class CoopBrowserWindow(ba.Window):
def _update(self) -> None:
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
# connection goes down for a while)
# connection goes down for a while).
if (self._last_tournament_query_response_time is None
or ba.time(ba.TimeType.REAL) -
self._last_tournament_query_response_time > 60.0 * 2):
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()
if account_state_num != self._account_state_num:
self._account_state_num = account_state_num
self._save_state()
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:
self._last_tournament_query_time = None
# if we've been backgrounded/foregrounded, invalidate our
# tournament entries (they will be refreshed below asap)
# If we've been backgrounded/foregrounded, invalidate our
# tournament entries (they will be refreshed below asap).
if self._fg_state != ba.app.fg_state:
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 (
self._last_tournament_query_time is None
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._last_tournament_query_time = cur_time
self._doing_tournament_query = True
_ba.tournament_query(args={
'source': 'coop window refresh',
'numScores': 1
},
callback=ba.WeakCall(
self._on_tournament_query_response))
_ba.tournament_query(
args={
'source': 'coop window refresh',
'numScores': 1
},
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()
for tbtn in self._tournament_buttons:
tbtn['time_remaining'] = max(0, tbtn['time_remaining'] - 1)
if tbtn['time_remaining_value_text'] is not None:
ba.textwidget(
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']
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():
ba.imagewidget(edit=tbtn['entry_fee_ad_image'],
opacity=1.0 if ads_enabled else 0.25)
@ -427,7 +433,8 @@ class CoopBrowserWindow(ba.Window):
leader_score = (
ba.timestring(score[0] * 10,
centi=True,
timeformat=ba.TimeFormat.MILLISECONDS)
timeformat=ba.TimeFormat.MILLISECONDS,
suppress_format_warning=True)
if entry['scoreType'] == 'time' else str(score[0]))
else:
tbtn['leader'] = None
@ -445,7 +452,9 @@ class CoopBrowserWindow(ba.Window):
'-' if entry is None or 'totalTime' not in entry else ba.Lstr(
resource=self._r + '.ofTotalTimeText',
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'],
text=out_of_time_text)
@ -516,18 +525,14 @@ class CoopBrowserWindow(ba.Window):
final_fee_str = (
ba.charstr(ba.SpecialChar.TICKET_BACKING) +
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[
tbtn['tournament_id']]['adTriesRemaining']
free_tries_remaining = ba.app.tournament_info[
tbtn['tournament_id']]['freeTriesRemaining']
# now, if this fee allows ads and we support video ads, show
# the 'or ad' version
# Now, if this fee allows ads and we support video ads, show
# the 'or ad' version.
if allow_ads and _ba.has_video_ads():
ads_enabled = _ba.have_incentivized_ad()
ba.imagewidget(edit=tbtn['entry_fee_ad_image'],
@ -542,7 +547,8 @@ class CoopBrowserWindow(ba.Window):
tbtn['button_y'] + tbtn['button_scale_y'] - 60),
scale=1.3,
text=final_fee_str)
# possibly show number of ad-plays remaining
# Possibly show number of ad-plays remaining.
ba.textwidget(
edit=tbtn['entry_fee_text_remaining'],
position=(tbtn['button_x'] + 360,
@ -559,7 +565,8 @@ class CoopBrowserWindow(ba.Window):
tbtn['button_y'] + tbtn['button_scale_y'] - 80),
scale=1.3,
text=final_fee_str)
# possibly show number of free-plays remaining
# Possibly show number of free-plays remaining.
ba.textwidget(
edit=tbtn['entry_fee_text_remaining'],
position=(tbtn['button_x'] + 360,
@ -927,15 +934,8 @@ class CoopBrowserWindow(ba.Window):
'Challenges:Meteor Shower',
'Challenges:Target Practice B',
'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.
if _ba.get_account_misc_read_val(
'easter', False) or _ba.get_purchased('games.easter_egg_hunt'):
@ -1456,7 +1456,7 @@ class CoopBrowserWindow(ba.Window):
ba.playsound(ba.getsound('error'))
return
# game is whatever the tournament tells us it is
# Game is whatever the tournament tells us it is.
game = ba.app.tournament_info[
tournament_button['tournament_id']]['game']
@ -1469,7 +1469,7 @@ class CoopBrowserWindow(ba.Window):
height=130)
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.
if tournament_button is None and game in (
'Challenges:Infinite Runaround',

View File

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

View File

@ -1,5 +1,5 @@
<!-- 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,
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>
@ -165,6 +165,7 @@
</ul>
<h4><a name="class_category_Enums">Enums</a></h4>
<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_MusicType">ba.MusicType</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>
</dl>
<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>
<p>inherits from: <a href="#class_typing_Generic">typing.Generic</a></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>
<dl>
<dt><h4><a name="attr_ba_DieMessage__how">how</a></h4></dt><dd>
<p><span>str</span></p>
<p>The particular reason for death; 'fall', 'impact', 'leftGame', etc.
This can be examined for scoring or other purposes.</p>
<p><span>DeathType</span></p>
<p>The particular reason for death.</p>
</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>
<dl>
<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>
</dl>
@ -3666,7 +3682,7 @@ other players.</p>
</dd>
</dl>
<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>
<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>
@ -3701,12 +3717,6 @@ other players.</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>
<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>
@ -4145,7 +4155,7 @@ session.set_activity(foo) and then <a href="#function_ba_newnode">ba.newnode</a>
</p>
<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>
<dt><h4><a name="method_ba_Stats____init__">&lt;constructor&gt;</a></dt></h4><dd>
<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>
</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>
<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>
@ -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>
</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>
<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>