mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-04 22:43:17 +08:00
More minigame modernizing
This commit is contained in:
parent
0742bce678
commit
d29cb35ff1
@ -4132,16 +4132,16 @@
|
|||||||
"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-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/33/81/1bf0d898c26582776d0c2ef76b68",
|
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/75/82/99d3b14b1c7ccab2de8c64bf9dea",
|
||||||
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/7b/b6/9cf8cb137735545a5d9c5d2abc14",
|
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fe/78/0e5383f059887070323d08f6fa9a",
|
||||||
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/4c/a0/b18786c5c4a3b8c8ec9417167412",
|
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/0d/23/a18e8d3f8a70f9865938d95d7184",
|
||||||
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fe/41/1bf6ad4d57a589fb26cece393563",
|
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/27/57/8f95c8da763731b971488a18d9b4",
|
||||||
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/42/6d/39d0af901ac06b9ad655a9837a6d",
|
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e9/c6/cd1d7d7568edf9f5e3d313e75eca",
|
||||||
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/98/0d/0d4594d20813a5b3dacb3aa2022f",
|
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/7e/92/c4407e3e9017523745e381f681d8",
|
||||||
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/8e/73/1e215e66ce6fd155b4af840465f2",
|
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c9/57/5273080f7d383a3de8e3a519f96b",
|
||||||
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a2/21/aad47597886fe15f228386fdf0a5",
|
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7d/d3/01ce8f52b62fc308a358d9a5470e",
|
||||||
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/3c/e5/bdd60cba90f6955ba7a4a932bd45",
|
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/c6/44/bd76f8dc1be8b2b862876d112420",
|
||||||
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/4f/b7/86dec4a8ab32edaf850d3c0fe2c2",
|
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/68/86/6e1418947d35a647d563aff2aebe",
|
||||||
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/09/ab/48ca17f389375fd4db02295bab73",
|
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/9f/3d/f091bf3ed6b286a29f660a1e14cc",
|
||||||
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f4/12/6fb56bf6484b5b93fbfd3206bbdf"
|
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a0/b3/ad0fa29417f197bed6f3fb184a3f"
|
||||||
}
|
}
|
||||||
@ -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=266649817838802754126771358652920545389
|
# SOURCES_HASH=122350585846084418668853979161934598264
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
@ -331,106 +331,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
|||||||
raise RuntimeError(f'destroy() called when'
|
raise RuntimeError(f'destroy() called when'
|
||||||
f' already expired for {self}')
|
f' already expired for {self}')
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _check_activity_death(cls, activity_ref: ReferenceType[Activity],
|
|
||||||
counter: List[int]) -> None:
|
|
||||||
"""Sanity check to make sure an Activity was destroyed properly.
|
|
||||||
|
|
||||||
Receives a weakref to a ba.Activity which should have torn itself
|
|
||||||
down due to no longer being referenced anywhere. Will complain
|
|
||||||
and/or print debugging info if the Activity still exists.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
import gc
|
|
||||||
import types
|
|
||||||
activity = activity_ref()
|
|
||||||
print('ERROR: Activity is not dying when expected:', activity,
|
|
||||||
'(warning ' + str(counter[0] + 1) + ')')
|
|
||||||
print('This means something is still strong-referencing it.')
|
|
||||||
counter[0] += 1
|
|
||||||
|
|
||||||
# FIXME: Running the code below shows us references but winds up
|
|
||||||
# keeping the object alive; need to figure out why.
|
|
||||||
# For now we just print refs if the count gets to 3, and then we
|
|
||||||
# kill the app at 4 so it doesn't matter anyway.
|
|
||||||
if counter[0] == 3:
|
|
||||||
print('Activity references for', activity, ':')
|
|
||||||
refs = list(gc.get_referrers(activity))
|
|
||||||
i = 1
|
|
||||||
for ref in refs:
|
|
||||||
if isinstance(ref, types.FrameType):
|
|
||||||
continue
|
|
||||||
print(' reference', i, ':', ref)
|
|
||||||
i += 1
|
|
||||||
if counter[0] == 4:
|
|
||||||
print('Killing app due to stuck activity... :-(')
|
|
||||||
_ba.quit()
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
print_exception('exception on _check_activity_death:')
|
|
||||||
|
|
||||||
def _expire(self) -> None:
|
|
||||||
"""Put the activity in a state where it can be garbage-collected.
|
|
||||||
|
|
||||||
This involves clearing anything that might be holding a reference
|
|
||||||
to it, etc.
|
|
||||||
"""
|
|
||||||
assert not self._expired
|
|
||||||
self._expired = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.on_expire()
|
|
||||||
except Exception:
|
|
||||||
print_exception(f'Error in Activity on_expire() for {self}')
|
|
||||||
|
|
||||||
# Send expire notices to all remaining actors.
|
|
||||||
for actor_ref in self._actor_weak_refs:
|
|
||||||
try:
|
|
||||||
actor = actor_ref()
|
|
||||||
if actor is not None:
|
|
||||||
actor.on_expire()
|
|
||||||
except Exception:
|
|
||||||
print_exception(f'Error expiring Actor {actor_ref()}')
|
|
||||||
|
|
||||||
# Reset all Players.
|
|
||||||
# (releases any attached actors, clears game-data, etc)
|
|
||||||
for player in self.players:
|
|
||||||
if player:
|
|
||||||
try:
|
|
||||||
sessionplayer = player.sessionplayer
|
|
||||||
sessionplayer.set_node(None)
|
|
||||||
sessionplayer.set_activity(None)
|
|
||||||
sessionplayer.gameplayer = None
|
|
||||||
sessionplayer.reset()
|
|
||||||
except Exception:
|
|
||||||
print_exception(f'Error resetting Player {player}')
|
|
||||||
|
|
||||||
# Ditto with Teams.
|
|
||||||
for team in self.teams:
|
|
||||||
try:
|
|
||||||
sessionteam = team.sessionteam
|
|
||||||
sessionteam.gameteam = None
|
|
||||||
sessionteam.reset_gamedata()
|
|
||||||
except SessionTeamNotFoundError:
|
|
||||||
pass
|
|
||||||
except Exception:
|
|
||||||
print_exception(f'Error resetting Team {team}')
|
|
||||||
|
|
||||||
# Regardless of what happened here, we want to destroy our data, as
|
|
||||||
# our activity might not go down if we don't. This will kill all
|
|
||||||
# Timers, Nodes, etc, which should clear up any remaining refs to our
|
|
||||||
# Actors and Activity and allow us to die peacefully.
|
|
||||||
try:
|
|
||||||
self._activity_data.destroy()
|
|
||||||
except Exception:
|
|
||||||
print_exception(
|
|
||||||
'Exception during ba.Activity._expire() destroying data:')
|
|
||||||
|
|
||||||
def _prune_dead_actors(self) -> None:
|
|
||||||
self._actor_refs = [a for a in self._actor_refs if a]
|
|
||||||
self._actor_weak_refs = [a for a in self._actor_weak_refs if a()]
|
|
||||||
self._last_prune_dead_actors_time = _ba.time()
|
|
||||||
|
|
||||||
def retain_actor(self, actor: ba.Actor) -> None:
|
def retain_actor(self, actor: ba.Actor) -> None:
|
||||||
"""Add a strong-reference to a ba.Actor to this Activity.
|
"""Add a strong-reference to a ba.Actor to this Activity.
|
||||||
|
|
||||||
@ -619,127 +519,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
|||||||
except Exception:
|
except Exception:
|
||||||
print_exception('Error in on_transition_out for', self)
|
print_exception('Error in on_transition_out for', self)
|
||||||
|
|
||||||
def create_player(self, sessionplayer: ba.SessionPlayer) -> PlayerType:
|
|
||||||
"""Create the Player instance for this Activity.
|
|
||||||
|
|
||||||
Subclasses can override this if the activity's player class
|
|
||||||
requires a custom constructor; otherwise it will be called with
|
|
||||||
no args. Note that the player object should not be used at this
|
|
||||||
point as it is not yet fully wired up; wait for on_player_join()
|
|
||||||
for that.
|
|
||||||
"""
|
|
||||||
del sessionplayer # Unused
|
|
||||||
player = self._playertype()
|
|
||||||
return player
|
|
||||||
|
|
||||||
def create_team(self, sessionteam: ba.SessionTeam) -> TeamType:
|
|
||||||
"""Create the Team instance for this Activity.
|
|
||||||
|
|
||||||
Subclasses can override this if the activity's team class
|
|
||||||
requires a custom constructor; otherwise it will be called with
|
|
||||||
no args. Note that the team object should not be used at this
|
|
||||||
point as it is not yet fully wired up; wait for on_team_join()
|
|
||||||
for that.
|
|
||||||
"""
|
|
||||||
del sessionteam # Unused.
|
|
||||||
team = self._teamtype()
|
|
||||||
return team
|
|
||||||
|
|
||||||
def add_player(self, sessionplayer: ba.SessionPlayer) -> None:
|
|
||||||
"""(internal)"""
|
|
||||||
assert sessionplayer.team is not None
|
|
||||||
|
|
||||||
sessionplayer.reset_input()
|
|
||||||
sessionteam = sessionplayer.team
|
|
||||||
assert sessionplayer in sessionteam.players
|
|
||||||
team = sessionteam.gameteam
|
|
||||||
assert team is not None
|
|
||||||
sessionplayer.set_activity(self)
|
|
||||||
with _ba.Context(self):
|
|
||||||
sessionplayer.gameplayer = player = self.create_player(
|
|
||||||
sessionplayer)
|
|
||||||
player.postinit(sessionplayer)
|
|
||||||
team.players.append(player)
|
|
||||||
self.players.append(player)
|
|
||||||
try:
|
|
||||||
self.on_player_join(player)
|
|
||||||
except Exception:
|
|
||||||
print_exception('Error in on_player_join for', self)
|
|
||||||
|
|
||||||
def remove_player(self, sessionplayer: ba.SessionPlayer) -> None:
|
|
||||||
"""(internal)"""
|
|
||||||
|
|
||||||
# This should only be called on unexpired activities
|
|
||||||
# the player has been added to.
|
|
||||||
assert not self.expired
|
|
||||||
player = sessionplayer.gameplayer
|
|
||||||
assert isinstance(player, self._playertype)
|
|
||||||
assert player in self.players
|
|
||||||
|
|
||||||
self.players.remove(player)
|
|
||||||
with _ba.Context(self):
|
|
||||||
# Make a decent attempt to persevere if user code breaks.
|
|
||||||
try:
|
|
||||||
self.on_player_leave(player)
|
|
||||||
except Exception:
|
|
||||||
print_exception(f'Error in on_player_leave for {self}')
|
|
||||||
try:
|
|
||||||
sessionplayer.reset()
|
|
||||||
sessionplayer.set_node(None)
|
|
||||||
sessionplayer.set_activity(None)
|
|
||||||
except Exception:
|
|
||||||
print_exception(f'Error resetting player for {self}')
|
|
||||||
|
|
||||||
def add_team(self, sessionteam: ba.SessionTeam) -> None:
|
|
||||||
"""(internal)"""
|
|
||||||
assert not self.expired
|
|
||||||
|
|
||||||
with _ba.Context(self):
|
|
||||||
sessionteam.gameteam = team = self.create_team(sessionteam)
|
|
||||||
team.postinit(sessionteam)
|
|
||||||
self.teams.append(team)
|
|
||||||
try:
|
|
||||||
self.on_team_join(team)
|
|
||||||
except Exception:
|
|
||||||
print_exception(f'Error in on_team_join for {self}')
|
|
||||||
|
|
||||||
def remove_team(self, sessionteam: ba.SessionTeam) -> None:
|
|
||||||
"""(internal)"""
|
|
||||||
|
|
||||||
# This should only be called on unexpired activities the team has
|
|
||||||
# been added to.
|
|
||||||
assert not self.expired
|
|
||||||
assert sessionteam.gameteam is not None
|
|
||||||
assert sessionteam.gameteam in self.teams
|
|
||||||
|
|
||||||
team = sessionteam.gameteam
|
|
||||||
assert isinstance(team, self._teamtype)
|
|
||||||
self.teams.remove(team)
|
|
||||||
with _ba.Context(self):
|
|
||||||
# Make a decent attempt to persevere if user code breaks.
|
|
||||||
try:
|
|
||||||
self.on_team_leave(team)
|
|
||||||
except Exception:
|
|
||||||
print_exception(f'Error in on_team_leave for {self}')
|
|
||||||
try:
|
|
||||||
sessionteam.reset_gamedata()
|
|
||||||
except Exception:
|
|
||||||
print_exception(f'Error in reset_gamedata for {self}')
|
|
||||||
sessionteam.gameteam = None
|
|
||||||
|
|
||||||
def _sanity_check_begin_call(self) -> None:
|
|
||||||
# Make sure ba.Activity.on_transition_in() got called at some point.
|
|
||||||
if not self._called_activity_on_transition_in:
|
|
||||||
print_error(
|
|
||||||
'ba.Activity.on_transition_in() never got called for ' +
|
|
||||||
str(self) + '; did you forget to call it'
|
|
||||||
' in your on_transition_in override?')
|
|
||||||
# Make sure that ba.Activity.on_begin() got called at some point.
|
|
||||||
if not self._called_activity_on_begin:
|
|
||||||
print_error(
|
|
||||||
'ba.Activity.on_begin() never got called for ' + str(self) +
|
|
||||||
'; did you forget to call it in your on_begin override?')
|
|
||||||
|
|
||||||
def begin(self, session: ba.Session) -> None:
|
def begin(self, session: ba.Session) -> None:
|
||||||
"""Begin the activity.
|
"""Begin the activity.
|
||||||
|
|
||||||
@ -779,6 +558,146 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
|||||||
self.end(self._should_end_immediately_results,
|
self.end(self._should_end_immediately_results,
|
||||||
self._should_end_immediately_delay)
|
self._should_end_immediately_delay)
|
||||||
|
|
||||||
|
def create_player(self, sessionplayer: ba.SessionPlayer) -> PlayerType:
|
||||||
|
"""Create the Player instance for this Activity.
|
||||||
|
|
||||||
|
Subclasses can override this if the activity's player class
|
||||||
|
requires a custom constructor; otherwise it will be called with
|
||||||
|
no args. Note that the player object should not be used at this
|
||||||
|
point as it is not yet fully wired up; wait for on_player_join()
|
||||||
|
for that.
|
||||||
|
"""
|
||||||
|
del sessionplayer # Unused
|
||||||
|
player = self._playertype()
|
||||||
|
return player
|
||||||
|
|
||||||
|
def create_team(self, sessionteam: ba.SessionTeam) -> TeamType:
|
||||||
|
"""Create the Team instance for this Activity.
|
||||||
|
|
||||||
|
Subclasses can override this if the activity's team class
|
||||||
|
requires a custom constructor; otherwise it will be called with
|
||||||
|
no args. Note that the team object should not be used at this
|
||||||
|
point as it is not yet fully wired up; wait for on_team_join()
|
||||||
|
for that.
|
||||||
|
"""
|
||||||
|
del sessionteam # Unused.
|
||||||
|
team = self._teamtype()
|
||||||
|
return team
|
||||||
|
|
||||||
|
def add_player(self, sessionplayer: ba.SessionPlayer) -> None:
|
||||||
|
"""(internal)"""
|
||||||
|
assert sessionplayer.team is not None
|
||||||
|
sessionplayer.reset_input()
|
||||||
|
sessionteam = sessionplayer.team
|
||||||
|
assert sessionplayer in sessionteam.players
|
||||||
|
team = sessionteam.gameteam
|
||||||
|
assert team is not None
|
||||||
|
sessionplayer.set_activity(self)
|
||||||
|
with _ba.Context(self):
|
||||||
|
sessionplayer.gameplayer = player = self.create_player(
|
||||||
|
sessionplayer)
|
||||||
|
player.postinit(sessionplayer)
|
||||||
|
|
||||||
|
assert player not in team.players
|
||||||
|
team.players.append(player)
|
||||||
|
assert player in team.players
|
||||||
|
|
||||||
|
assert player not in self.players
|
||||||
|
self.players.append(player)
|
||||||
|
assert player in self.players
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.on_player_join(player)
|
||||||
|
except Exception:
|
||||||
|
print_exception('Error in on_player_join for', self)
|
||||||
|
|
||||||
|
def remove_player(self, sessionplayer: ba.SessionPlayer) -> None:
|
||||||
|
"""(internal)"""
|
||||||
|
|
||||||
|
# This should only be called on unexpired activities
|
||||||
|
# the player has been added to.
|
||||||
|
assert not self.expired
|
||||||
|
|
||||||
|
player: Any = sessionplayer.gameplayer
|
||||||
|
assert isinstance(player, self._playertype)
|
||||||
|
team: Any = sessionplayer.team.gameteam
|
||||||
|
assert isinstance(team, self._teamtype)
|
||||||
|
|
||||||
|
assert player in team.players
|
||||||
|
team.players.remove(player)
|
||||||
|
assert player not in team.players
|
||||||
|
|
||||||
|
assert player in self.players
|
||||||
|
self.players.remove(player)
|
||||||
|
assert player not in self.players
|
||||||
|
|
||||||
|
with _ba.Context(self):
|
||||||
|
# Make a decent attempt to persevere if user code breaks.
|
||||||
|
try:
|
||||||
|
self.on_player_leave(player)
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error in on_player_leave for {self}')
|
||||||
|
try:
|
||||||
|
sessionplayer.reset()
|
||||||
|
sessionplayer.set_node(None)
|
||||||
|
sessionplayer.set_activity(None)
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error resetting player for {self}')
|
||||||
|
|
||||||
|
def add_team(self, sessionteam: ba.SessionTeam) -> None:
|
||||||
|
"""(internal)"""
|
||||||
|
assert not self.expired
|
||||||
|
|
||||||
|
with _ba.Context(self):
|
||||||
|
sessionteam.gameteam = team = self.create_team(sessionteam)
|
||||||
|
team.postinit(sessionteam)
|
||||||
|
self.teams.append(team)
|
||||||
|
try:
|
||||||
|
self.on_team_join(team)
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error in on_team_join for {self}')
|
||||||
|
|
||||||
|
def remove_team(self, sessionteam: ba.SessionTeam) -> None:
|
||||||
|
"""(internal)"""
|
||||||
|
|
||||||
|
# This should only be called on unexpired activities the team has
|
||||||
|
# been added to.
|
||||||
|
assert not self.expired
|
||||||
|
assert sessionteam.gameteam is not None
|
||||||
|
assert sessionteam.gameteam in self.teams
|
||||||
|
|
||||||
|
team = sessionteam.gameteam
|
||||||
|
assert isinstance(team, self._teamtype)
|
||||||
|
|
||||||
|
assert team in self.teams
|
||||||
|
self.teams.remove(team)
|
||||||
|
assert team not in self.teams
|
||||||
|
|
||||||
|
with _ba.Context(self):
|
||||||
|
# Make a decent attempt to persevere if user code breaks.
|
||||||
|
try:
|
||||||
|
self.on_team_leave(team)
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error in on_team_leave for {self}')
|
||||||
|
try:
|
||||||
|
sessionteam.reset_gamedata()
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error in reset_gamedata for {self}')
|
||||||
|
sessionteam.gameteam = None
|
||||||
|
|
||||||
|
def _sanity_check_begin_call(self) -> None:
|
||||||
|
# Make sure ba.Activity.on_transition_in() got called at some point.
|
||||||
|
if not self._called_activity_on_transition_in:
|
||||||
|
print_error(
|
||||||
|
'ba.Activity.on_transition_in() never got called for ' +
|
||||||
|
str(self) + '; did you forget to call it'
|
||||||
|
' in your on_transition_in override?')
|
||||||
|
# Make sure that ba.Activity.on_begin() got called at some point.
|
||||||
|
if not self._called_activity_on_begin:
|
||||||
|
print_error(
|
||||||
|
'ba.Activity.on_begin() never got called for ' + str(self) +
|
||||||
|
'; did you forget to call it in your on_begin override?')
|
||||||
|
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
def _setup_player_and_team_types(self) -> None:
|
def _setup_player_and_team_types(self) -> None:
|
||||||
"""Pull player and team types from our typing.Generic params."""
|
"""Pull player and team types from our typing.Generic params."""
|
||||||
@ -805,3 +724,104 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
|||||||
f' if you do not want to override it.')
|
f' if you do not want to override it.')
|
||||||
assert issubclass(self._playertype, Player)
|
assert issubclass(self._playertype, Player)
|
||||||
assert issubclass(self._teamtype, Team)
|
assert issubclass(self._teamtype, Team)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _check_activity_death(cls, activity_ref: ReferenceType[Activity],
|
||||||
|
counter: List[int]) -> None:
|
||||||
|
"""Sanity check to make sure an Activity was destroyed properly.
|
||||||
|
|
||||||
|
Receives a weakref to a ba.Activity which should have torn itself
|
||||||
|
down due to no longer being referenced anywhere. Will complain
|
||||||
|
and/or print debugging info if the Activity still exists.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import gc
|
||||||
|
import types
|
||||||
|
activity = activity_ref()
|
||||||
|
print('ERROR: Activity is not dying when expected:', activity,
|
||||||
|
'(warning ' + str(counter[0] + 1) + ')')
|
||||||
|
print('This means something is still strong-referencing it.')
|
||||||
|
counter[0] += 1
|
||||||
|
|
||||||
|
# FIXME: Running the code below shows us references but winds up
|
||||||
|
# keeping the object alive; need to figure out why.
|
||||||
|
# For now we just print refs if the count gets to 3, and then we
|
||||||
|
# kill the app at 4 so it doesn't matter anyway.
|
||||||
|
if counter[0] == 3:
|
||||||
|
print('Activity references for', activity, ':')
|
||||||
|
refs = list(gc.get_referrers(activity))
|
||||||
|
i = 1
|
||||||
|
for ref in refs:
|
||||||
|
if isinstance(ref, types.FrameType):
|
||||||
|
continue
|
||||||
|
print(' reference', i, ':', ref)
|
||||||
|
i += 1
|
||||||
|
if counter[0] == 4:
|
||||||
|
print('Killing app due to stuck activity... :-(')
|
||||||
|
_ba.quit()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
print_exception('exception on _check_activity_death:')
|
||||||
|
|
||||||
|
def _expire(self) -> None:
|
||||||
|
"""Put the activity in a state where it can be garbage-collected.
|
||||||
|
|
||||||
|
This involves clearing anything that might be holding a reference
|
||||||
|
to it, etc.
|
||||||
|
"""
|
||||||
|
assert not self._expired
|
||||||
|
self._expired = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.on_expire()
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error in Activity on_expire() for {self}')
|
||||||
|
|
||||||
|
# Send expire notices to all remaining actors.
|
||||||
|
for actor_ref in self._actor_weak_refs:
|
||||||
|
try:
|
||||||
|
actor = actor_ref()
|
||||||
|
if actor is not None:
|
||||||
|
actor.on_expire()
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error expiring Actor {actor_ref()}')
|
||||||
|
|
||||||
|
# Reset all Players.
|
||||||
|
# (releases any attached actors, clears game-data, etc)
|
||||||
|
for player in self.players:
|
||||||
|
if player:
|
||||||
|
try:
|
||||||
|
sessionplayer = player.sessionplayer
|
||||||
|
sessionplayer.set_node(None)
|
||||||
|
sessionplayer.set_activity(None)
|
||||||
|
sessionplayer.gameplayer = None
|
||||||
|
sessionplayer.reset()
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error resetting Player {player}')
|
||||||
|
|
||||||
|
# Ditto with Teams.
|
||||||
|
for team in self.teams:
|
||||||
|
try:
|
||||||
|
sessionteam = team.sessionteam
|
||||||
|
sessionteam.gameteam = None
|
||||||
|
sessionteam.reset_gamedata()
|
||||||
|
except SessionTeamNotFoundError:
|
||||||
|
pass
|
||||||
|
# print_exception(f'Error resetting Team {team}')
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error resetting Team {team}')
|
||||||
|
|
||||||
|
# Regardless of what happened here, we want to destroy our data, as
|
||||||
|
# our activity might not go down if we don't. This will kill all
|
||||||
|
# Timers, Nodes, etc, which should clear up any remaining refs to our
|
||||||
|
# Actors and Activity and allow us to die peacefully.
|
||||||
|
try:
|
||||||
|
self._activity_data.destroy()
|
||||||
|
except Exception:
|
||||||
|
print_exception(
|
||||||
|
'Exception during ba.Activity._expire() destroying data:')
|
||||||
|
|
||||||
|
def _prune_dead_actors(self) -> None:
|
||||||
|
self._actor_refs = [a for a in self._actor_refs if a]
|
||||||
|
self._actor_weak_refs = [a for a in self._actor_weak_refs if a()]
|
||||||
|
self._last_prune_dead_actors_time = _ba.time()
|
||||||
|
|||||||
@ -77,7 +77,7 @@ class TeamGameResults:
|
|||||||
self._score_type = score_info.scoretype
|
self._score_type = score_info.scoretype
|
||||||
|
|
||||||
def set_team_score(self, team: Union[ba.SessionTeam, ba.Team],
|
def set_team_score(self, team: Union[ba.SessionTeam, ba.Team],
|
||||||
score: int) -> None:
|
score: Optional[int]) -> None:
|
||||||
"""Set the score for a given ba.Team.
|
"""Set the score for a given ba.Team.
|
||||||
|
|
||||||
This can be a number or None.
|
This can be a number or None.
|
||||||
|
|||||||
@ -59,6 +59,18 @@ class Player(Generic[TeamType]):
|
|||||||
"""
|
"""
|
||||||
from ba._nodeactor import NodeActor
|
from ba._nodeactor import NodeActor
|
||||||
import _ba
|
import _ba
|
||||||
|
|
||||||
|
# Sanity check; if a dataclass is created that inherits from us,
|
||||||
|
# it will define an equality operator by default which will break
|
||||||
|
# internal game logic. So complain loudly if we find one.
|
||||||
|
if type(self).__eq__ is not object.__eq__:
|
||||||
|
raise RuntimeError(
|
||||||
|
f'Player class {type(self)} defines an equality'
|
||||||
|
f' operator (__eq__) which will break internal'
|
||||||
|
f' logic. Please remove it.\n'
|
||||||
|
f'For dataclasses you can do "dataclass(eq=False)"'
|
||||||
|
f' in the class decorator.')
|
||||||
|
|
||||||
self.actor = None
|
self.actor = None
|
||||||
self.character = ''
|
self.character = ''
|
||||||
self._nodeactor: Optional[ba.NodeActor] = None
|
self._nodeactor: Optional[ba.NodeActor] = None
|
||||||
|
|||||||
@ -126,6 +126,18 @@ class Team(Generic[PlayerType]):
|
|||||||
|
|
||||||
(internal)
|
(internal)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Sanity check; if a dataclass is created that inherits from us,
|
||||||
|
# it will define an equality operator by default which will break
|
||||||
|
# internal game logic. So complain loudly if we find one.
|
||||||
|
if type(self).__eq__ is not object.__eq__:
|
||||||
|
raise RuntimeError(
|
||||||
|
f'Team class {type(self)} defines an equality'
|
||||||
|
f' operator (__eq__) which will break internal'
|
||||||
|
f' logic. Please remove it.\n'
|
||||||
|
f'For dataclasses you can do "dataclass(eq=False)"'
|
||||||
|
f' in the class decorator.')
|
||||||
|
|
||||||
self.players = []
|
self.players = []
|
||||||
self._sessionteam = weakref.ref(sessionteam)
|
self._sessionteam = weakref.ref(sessionteam)
|
||||||
self.id = sessionteam.id
|
self.id = sessionteam.id
|
||||||
@ -134,6 +146,15 @@ class Team(Generic[PlayerType]):
|
|||||||
self.gamedata = sessionteam.gamedata
|
self.gamedata = sessionteam.gamedata
|
||||||
self.sessiondata = sessionteam.sessiondata
|
self.sessiondata = sessionteam.sessiondata
|
||||||
|
|
||||||
|
def manual_init(self, team_id: int, name: Union[ba.Lstr, str],
|
||||||
|
color: Tuple[float, ...]) -> None:
|
||||||
|
"""Manually init a team for uses such as bots."""
|
||||||
|
self.id = team_id
|
||||||
|
self.name = name
|
||||||
|
self.color = color
|
||||||
|
self.gamedata = {}
|
||||||
|
self.sessiondata = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sessionteam(self) -> SessionTeam:
|
def sessionteam(self) -> SessionTeam:
|
||||||
"""Return the ba.SessionTeam corresponding to this Team.
|
"""Return the ba.SessionTeam corresponding to this Team.
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import weakref
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
from bastd.actor import spaz as basespaz
|
from bastd.actor.spaz import Spaz
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Optional, List, Tuple, Sequence, Type, Callable
|
from typing import Any, Optional, List, Tuple, Sequence, Type, Callable
|
||||||
@ -87,7 +87,7 @@ class SpazBotDeathMessage:
|
|||||||
self.how = how
|
self.how = how
|
||||||
|
|
||||||
|
|
||||||
class SpazBot(basespaz.Spaz):
|
class SpazBot(Spaz):
|
||||||
"""A really dumb AI version of ba.Spaz.
|
"""A really dumb AI version of ba.Spaz.
|
||||||
|
|
||||||
category: Bot Classes
|
category: Bot Classes
|
||||||
@ -127,13 +127,12 @@ class SpazBot(basespaz.Spaz):
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Instantiate a spaz-bot."""
|
"""Instantiate a spaz-bot."""
|
||||||
basespaz.Spaz.__init__(self,
|
super().__init__(color=self.color,
|
||||||
color=self.color,
|
highlight=self.highlight,
|
||||||
highlight=self.highlight,
|
character=self.character,
|
||||||
character=self.character,
|
source_player=None,
|
||||||
source_player=None,
|
start_invincible=False,
|
||||||
start_invincible=False,
|
can_accept_powerups=False)
|
||||||
can_accept_powerups=False)
|
|
||||||
|
|
||||||
# If you need to add custom behavior to a bot, set this to a callable
|
# If you need to add custom behavior to a bot, set this to a callable
|
||||||
# which takes one arg (the bot) and returns False if the bot's normal
|
# which takes one arg (the bot) and returns False if the bot's normal
|
||||||
@ -503,7 +502,7 @@ class SpazBot(basespaz.Spaz):
|
|||||||
ba.getactivity().handlemessage(SpazBotPunchedMessage(self, damage))
|
ba.getactivity().handlemessage(SpazBotPunchedMessage(self, damage))
|
||||||
|
|
||||||
def on_expire(self) -> None:
|
def on_expire(self) -> None:
|
||||||
basespaz.Spaz.on_expire(self)
|
super().on_expire()
|
||||||
|
|
||||||
# We're being torn down; release our callback(s) so there's
|
# We're being torn down; release our callback(s) so there's
|
||||||
# no chance of them keeping activities or other things alive.
|
# no chance of them keeping activities or other things alive.
|
||||||
@ -980,9 +979,10 @@ class BotSet:
|
|||||||
# 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:
|
||||||
|
assert isinstance(player, ba.Player)
|
||||||
try:
|
try:
|
||||||
if player.is_alive():
|
if player.is_alive():
|
||||||
assert isinstance(player.actor, basespaz.Spaz)
|
assert isinstance(player.actor, Spaz)
|
||||||
assert player.actor.node
|
assert player.actor.node
|
||||||
player_pts.append((ba.Vec3(player.actor.node.position),
|
player_pts.append((ba.Vec3(player.actor.node.position),
|
||||||
ba.Vec3(player.actor.node.velocity)))
|
ba.Vec3(player.actor.node.velocity)))
|
||||||
|
|||||||
@ -37,12 +37,12 @@ if TYPE_CHECKING:
|
|||||||
from typing import Any, Type, List, Dict, Tuple, Sequence, Union
|
from typing import Any, Type, List, Dict, Tuple, Sequence, Union
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(eq=False)
|
||||||
class Player(ba.Player['Team']):
|
class Player(ba.Player['Team']):
|
||||||
"""Our player type for this game."""
|
"""Our player type for this game."""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(eq=False)
|
||||||
class Team(ba.Team[Player]):
|
class Team(ba.Team[Player]):
|
||||||
"""Our team type for this game."""
|
"""Our team type for this game."""
|
||||||
base_pos: Sequence[float]
|
base_pos: Sequence[float]
|
||||||
|
|||||||
@ -81,13 +81,13 @@ class CTFFlag(stdflag.Flag):
|
|||||||
return delegate if isinstance(delegate, CTFFlag) else None
|
return delegate if isinstance(delegate, CTFFlag) else None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(eq=False)
|
||||||
class Player(ba.Player['Team']):
|
class Player(ba.Player['Team']):
|
||||||
"""Our player type for this game."""
|
"""Our player type for this game."""
|
||||||
touching_own_flag: int = 0
|
touching_own_flag: int = 0
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(eq=False)
|
||||||
class Team(ba.Team[Player]):
|
class Team(ba.Team[Player]):
|
||||||
"""Our team type for this game."""
|
"""Our team type for this game."""
|
||||||
base_pos: Sequence[float]
|
base_pos: Sequence[float]
|
||||||
|
|||||||
@ -25,19 +25,31 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
from bastd.actor import flag
|
from bastd.actor.flag import Flag
|
||||||
from bastd.actor import playerspaz
|
from bastd.actor.playerspaz import PlayerSpaz, PlayerSpazDeathMessage
|
||||||
from bastd.actor import spaz
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import (Any, Type, List, Dict, Tuple, Optional, Sequence,
|
from typing import (Any, Type, List, Dict, Tuple, Optional, Sequence,
|
||||||
Union)
|
Union)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Player(ba.Player['Team']):
|
||||||
|
"""Our player type for this game."""
|
||||||
|
chosen_light: Optional[ba.NodeActor] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
time_remaining: int
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class ChosenOneGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
|
||||||
"""
|
"""
|
||||||
Game involving trying to remain the one 'chosen one'
|
Game involving trying to remain the one 'chosen one'
|
||||||
for a set length of time while everyone else tries to
|
for a set length of time while everyone else tries to
|
||||||
@ -96,10 +108,8 @@ class ChosenOneGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
def __init__(self, settings: Dict[str, Any]):
|
def __init__(self, settings: Dict[str, Any]):
|
||||||
from bastd.actor.scoreboard import Scoreboard
|
from bastd.actor.scoreboard import Scoreboard
|
||||||
super().__init__(settings)
|
super().__init__(settings)
|
||||||
if self.settings_raw['Epic Mode']:
|
|
||||||
self.slow_motion = True
|
|
||||||
self._scoreboard = Scoreboard()
|
self._scoreboard = Scoreboard()
|
||||||
self._chosen_one_player: Optional[ba.Player] = None
|
self._chosen_one_player: Optional[Player] = None
|
||||||
self._swipsound = ba.getsound('swip')
|
self._swipsound = ba.getsound('swip')
|
||||||
self._countdownsounds: Dict[int, ba.Sound] = {
|
self._countdownsounds: Dict[int, ba.Sound] = {
|
||||||
10: ba.getsound('announceTen'),
|
10: ba.getsound('announceTen'),
|
||||||
@ -115,30 +125,36 @@ class ChosenOneGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
}
|
}
|
||||||
self._flag_spawn_pos: Optional[Sequence[float]] = None
|
self._flag_spawn_pos: Optional[Sequence[float]] = None
|
||||||
self._reset_region_material: Optional[ba.Material] = None
|
self._reset_region_material: Optional[ba.Material] = None
|
||||||
self._flag: Optional[flag.Flag] = None
|
self._flag: Optional[Flag] = None
|
||||||
self._reset_region: Optional[ba.Node] = None
|
self._reset_region: Optional[ba.Node] = None
|
||||||
|
self._epic_mode = bool(settings['Epic Mode'])
|
||||||
|
self._chosen_one_time = int(settings['Chosen One Time'])
|
||||||
|
self._time_limit = float(settings['Time Limit'])
|
||||||
|
self._chosen_one_gets_shield = bool(settings['Chosen One Gets Shield'])
|
||||||
|
self._chosen_one_gets_gloves = bool(settings['Chosen One Gets Gloves'])
|
||||||
|
|
||||||
|
# Base class overrides
|
||||||
|
self.slow_motion = self._epic_mode
|
||||||
|
self.default_music = (ba.MusicType.EPIC
|
||||||
|
if self._epic_mode else ba.MusicType.CHOSEN_ONE)
|
||||||
|
|
||||||
def get_instance_description(self) -> Union[str, Sequence]:
|
def get_instance_description(self) -> Union[str, Sequence]:
|
||||||
return 'There can be only one.'
|
return 'There can be only one.'
|
||||||
|
|
||||||
def on_transition_in(self) -> None:
|
def create_team(self, sessionteam: ba.SessionTeam) -> Team:
|
||||||
self.default_music = (ba.MusicType.EPIC
|
return Team(time_remaining=self._chosen_one_time)
|
||||||
if self.settings_raw['Epic Mode'] else
|
|
||||||
ba.MusicType.CHOSEN_ONE)
|
|
||||||
super().on_transition_in()
|
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
def on_team_join(self, team: Team) -> None:
|
||||||
team.gamedata['time_remaining'] = self.settings_raw['Chosen One Time']
|
|
||||||
self._update_scoreboard()
|
self._update_scoreboard()
|
||||||
|
|
||||||
def on_player_leave(self, player: ba.Player) -> None:
|
def on_player_leave(self, player: Player) -> None:
|
||||||
super().on_player_leave(player)
|
super().on_player_leave(player)
|
||||||
if self._get_chosen_one_player() is player:
|
if self._get_chosen_one_player() is player:
|
||||||
self._set_chosen_one_player(None)
|
self._set_chosen_one_player(None)
|
||||||
|
|
||||||
def on_begin(self) -> None:
|
def on_begin(self) -> None:
|
||||||
super().on_begin()
|
super().on_begin()
|
||||||
self.setup_standard_time_limit(self.settings_raw['Time Limit'])
|
self.setup_standard_time_limit(self._time_limit)
|
||||||
self.setup_standard_powerup_drops()
|
self.setup_standard_powerup_drops()
|
||||||
self._flag_spawn_pos = self.map.get_flag_position(None)
|
self._flag_spawn_pos = self.map.get_flag_position(None)
|
||||||
self.project_flag_stand(self._flag_spawn_pos)
|
self.project_flag_stand(self._flag_spawn_pos)
|
||||||
@ -164,7 +180,7 @@ class ChosenOneGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
'materials': [mat]
|
'materials': [mat]
|
||||||
})
|
})
|
||||||
|
|
||||||
def _get_chosen_one_player(self) -> Optional[ba.Player]:
|
def _get_chosen_one_player(self) -> Optional[Player]:
|
||||||
if self._chosen_one_player:
|
if self._chosen_one_player:
|
||||||
return self._chosen_one_player
|
return self._chosen_one_player
|
||||||
return None
|
return None
|
||||||
@ -173,13 +189,11 @@ class ChosenOneGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
# If we have a chosen one, ignore these.
|
# If we have a chosen one, ignore these.
|
||||||
if self._get_chosen_one_player() is not None:
|
if self._get_chosen_one_player() is not None:
|
||||||
return
|
return
|
||||||
try:
|
delegate = ba.get_collision_info('opposing_node').getdelegate()
|
||||||
player = (ba.get_collision_info(
|
if isinstance(delegate, PlayerSpaz):
|
||||||
'opposing_node').getdelegate().getplayer())
|
player = ba.playercast_o(Player, delegate.getplayer())
|
||||||
except Exception:
|
if player is not None and player.is_alive():
|
||||||
return
|
self._set_chosen_one_player(player)
|
||||||
if player is not None and player.is_alive():
|
|
||||||
self._set_chosen_one_player(player)
|
|
||||||
|
|
||||||
def _flash_flag_spawn(self) -> None:
|
def _flash_flag_spawn(self) -> None:
|
||||||
light = ba.newnode('light',
|
light = ba.newnode('light',
|
||||||
@ -210,29 +224,24 @@ class ChosenOneGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
screenmessage=False,
|
screenmessage=False,
|
||||||
display=False)
|
display=False)
|
||||||
|
|
||||||
scoring_team.gamedata['time_remaining'] = max(
|
scoring_team.time_remaining = max(
|
||||||
0, scoring_team.gamedata['time_remaining'] - 1)
|
0, scoring_team.time_remaining - 1)
|
||||||
|
|
||||||
# show the count over their head
|
# Show the count over their head
|
||||||
try:
|
if scoring_team.time_remaining > 0:
|
||||||
if scoring_team.gamedata['time_remaining'] > 0:
|
if isinstance(player.actor, PlayerSpaz) and player.actor:
|
||||||
if isinstance(player.actor, spaz.Spaz):
|
player.actor.set_score_text(
|
||||||
player.actor.set_score_text(
|
str(scoring_team.time_remaining))
|
||||||
str(scoring_team.gamedata['time_remaining']))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self._update_scoreboard()
|
self._update_scoreboard()
|
||||||
|
|
||||||
# announce numbers we have sounds for
|
# announce numbers we have sounds for
|
||||||
try:
|
if scoring_team.time_remaining in self._countdownsounds:
|
||||||
ba.playsound(self._countdownsounds[
|
ba.playsound(
|
||||||
scoring_team.gamedata['time_remaining']])
|
self._countdownsounds[scoring_team.time_remaining])
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Winner!
|
# Winner!
|
||||||
if scoring_team.gamedata['time_remaining'] <= 0:
|
if scoring_team.time_remaining <= 0:
|
||||||
self.end_game()
|
self.end_game()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -247,89 +256,81 @@ class ChosenOneGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
def end_game(self) -> None:
|
def end_game(self) -> None:
|
||||||
results = ba.TeamGameResults()
|
results = ba.TeamGameResults()
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
results.set_team_score(
|
results.set_team_score(team,
|
||||||
team, self.settings_raw['Chosen One Time'] -
|
self._chosen_one_time - team.time_remaining)
|
||||||
team.gamedata['time_remaining'])
|
|
||||||
self.end(results=results, announce_delay=0)
|
self.end(results=results, announce_delay=0)
|
||||||
|
|
||||||
def _set_chosen_one_player(self, player: Optional[ba.Player]) -> None:
|
def _set_chosen_one_player(self, player: Optional[Player]) -> None:
|
||||||
try:
|
for p_other in self.players:
|
||||||
for p_other in self.players:
|
p_other.chosen_light = None
|
||||||
p_other.gamedata['chosen_light'] = None
|
ba.playsound(self._swipsound)
|
||||||
ba.playsound(self._swipsound)
|
if not player:
|
||||||
if not player:
|
assert self._flag_spawn_pos is not None
|
||||||
assert self._flag_spawn_pos is not None
|
self._flag = Flag(color=(1, 0.9, 0.2),
|
||||||
self._flag = flag.Flag(color=(1, 0.9, 0.2),
|
position=self._flag_spawn_pos,
|
||||||
position=self._flag_spawn_pos,
|
touchable=False)
|
||||||
touchable=False)
|
self._chosen_one_player = None
|
||||||
self._chosen_one_player = None
|
|
||||||
|
|
||||||
# Create a light to highlight the flag;
|
# Create a light to highlight the flag;
|
||||||
# this will go away when the flag dies.
|
# this will go away when the flag dies.
|
||||||
ba.newnode('light',
|
ba.newnode('light',
|
||||||
owner=self._flag.node,
|
owner=self._flag.node,
|
||||||
attrs={
|
attrs={
|
||||||
'position': self._flag_spawn_pos,
|
'position': self._flag_spawn_pos,
|
||||||
'intensity': 0.6,
|
'intensity': 0.6,
|
||||||
'height_attenuated': False,
|
'height_attenuated': False,
|
||||||
'volume_intensity_scale': 0.1,
|
'volume_intensity_scale': 0.1,
|
||||||
'radius': 0.1,
|
'radius': 0.1,
|
||||||
'color': (1.2, 1.2, 0.4)
|
'color': (1.2, 1.2, 0.4)
|
||||||
})
|
})
|
||||||
|
|
||||||
# Also an extra momentary flash.
|
# Also an extra momentary flash.
|
||||||
self._flash_flag_spawn()
|
self._flash_flag_spawn()
|
||||||
else:
|
else:
|
||||||
if player.actor is not None:
|
if player.actor:
|
||||||
self._flag = None
|
self._flag = None
|
||||||
self._chosen_one_player = player
|
self._chosen_one_player = player
|
||||||
|
|
||||||
if player.actor:
|
if self._chosen_one_gets_shield:
|
||||||
if self.settings_raw['Chosen One Gets Shield']:
|
player.actor.handlemessage(ba.PowerupMessage('shield'))
|
||||||
player.actor.handlemessage(
|
if self._chosen_one_gets_gloves:
|
||||||
ba.PowerupMessage('shield'))
|
player.actor.handlemessage(ba.PowerupMessage('punch'))
|
||||||
if self.settings_raw['Chosen One Gets Gloves']:
|
|
||||||
player.actor.handlemessage(
|
|
||||||
ba.PowerupMessage('punch'))
|
|
||||||
|
|
||||||
# Use a color that's partway between their team color
|
# Use a color that's partway between their team color
|
||||||
# and white.
|
# and white.
|
||||||
color = [
|
color = [
|
||||||
0.3 + c * 0.7
|
0.3 + c * 0.7
|
||||||
for c in ba.normalized_color(player.team.color)
|
for c in ba.normalized_color(player.team.color)
|
||||||
]
|
]
|
||||||
light = player.gamedata['chosen_light'] = ba.NodeActor(
|
light = player.chosen_light = ba.NodeActor(
|
||||||
ba.newnode('light',
|
ba.newnode('light',
|
||||||
attrs={
|
attrs={
|
||||||
'intensity': 0.6,
|
'intensity': 0.6,
|
||||||
'height_attenuated': False,
|
'height_attenuated': False,
|
||||||
'volume_intensity_scale': 0.1,
|
'volume_intensity_scale': 0.1,
|
||||||
'radius': 0.13,
|
'radius': 0.13,
|
||||||
'color': color
|
'color': color
|
||||||
}))
|
}))
|
||||||
|
|
||||||
assert light.node
|
assert light.node
|
||||||
ba.animate(light.node,
|
ba.animate(light.node,
|
||||||
'intensity', {
|
'intensity', {
|
||||||
0: 1.0,
|
0: 1.0,
|
||||||
0.2: 0.4,
|
0.2: 0.4,
|
||||||
0.4: 1.0
|
0.4: 1.0
|
||||||
},
|
},
|
||||||
loop=True)
|
loop=True)
|
||||||
assert isinstance(player.actor, playerspaz.PlayerSpaz)
|
assert isinstance(player.actor, PlayerSpaz)
|
||||||
player.actor.node.connectattr('position', light.node,
|
player.actor.node.connectattr('position', light.node,
|
||||||
'position')
|
'position')
|
||||||
|
|
||||||
except Exception:
|
|
||||||
ba.print_exception('EXC in _set_chosen_one_player')
|
|
||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, PlayerSpazDeathMessage):
|
||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
player = msg.playerspaz(self).player
|
player = msg.playerspaz(self).player
|
||||||
if player is self._get_chosen_one_player():
|
if player is self._get_chosen_one_player():
|
||||||
killerplayer = msg.killerplayer
|
killerplayer = ba.playercast_o(Player, msg.killerplayer)
|
||||||
self._set_chosen_one_player(None if (
|
self._set_chosen_one_player(None if (
|
||||||
killerplayer is None or killerplayer is player
|
killerplayer is None or killerplayer is player
|
||||||
or not killerplayer.is_alive()) else killerplayer)
|
or not killerplayer.is_alive()) else killerplayer)
|
||||||
@ -339,8 +340,7 @@ class ChosenOneGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
|
|
||||||
def _update_scoreboard(self) -> None:
|
def _update_scoreboard(self) -> None:
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
self._scoreboard.set_team_value(
|
self._scoreboard.set_team_value(team,
|
||||||
team,
|
team.time_remaining,
|
||||||
team.gamedata['time_remaining'],
|
self._chosen_one_time,
|
||||||
self.settings_raw['Chosen One Time'],
|
countdown=True)
|
||||||
countdown=True)
|
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
@ -40,7 +41,7 @@ class Icon(ba.Actor):
|
|||||||
"""Creates in in-game icon on screen."""
|
"""Creates in in-game icon on screen."""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
player: ba.Player,
|
player: Player,
|
||||||
position: Tuple[float, float],
|
position: Tuple[float, float],
|
||||||
scale: float,
|
scale: float,
|
||||||
show_lives: bool = True,
|
show_lives: bool = True,
|
||||||
@ -117,7 +118,7 @@ class Icon(ba.Actor):
|
|||||||
def update_for_lives(self) -> None:
|
def update_for_lives(self) -> None:
|
||||||
"""Update for the target player's current lives."""
|
"""Update for the target player's current lives."""
|
||||||
if self._player:
|
if self._player:
|
||||||
lives = self._player.gamedata['lives']
|
lives = self._player.lives
|
||||||
else:
|
else:
|
||||||
lives = 0
|
lives = 0
|
||||||
if self._show_lives:
|
if self._show_lives:
|
||||||
@ -158,13 +159,27 @@ class Icon(ba.Actor):
|
|||||||
0.50: 1.0,
|
0.50: 1.0,
|
||||||
0.55: 0.2
|
0.55: 0.2
|
||||||
})
|
})
|
||||||
lives = self._player.gamedata['lives']
|
lives = self._player.lives
|
||||||
if lives == 0:
|
if lives == 0:
|
||||||
ba.timer(0.6, self.update_for_lives)
|
ba.timer(0.6, self.update_for_lives)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Player(ba.Player['Team']):
|
||||||
|
"""Our player type for this game."""
|
||||||
|
lives: int = 0
|
||||||
|
icons: List[Icon] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
survival_seconds: Optional[int] = None
|
||||||
|
spawn_order: List[Player] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
class EliminationGame(ba.TeamGameActivity[Player, Team]):
|
||||||
"""Game type where last player(s) left alive win."""
|
"""Game type where last player(s) left alive win."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -196,13 +211,15 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
|
sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
|
||||||
settings: List[Tuple[str, Dict[str, Any]]] = [
|
settings: List[Tuple[str, Dict[str, Any]]] = [
|
||||||
('Lives Per Player', {
|
('Lives Per Player', {
|
||||||
'default': 1, 'min_value': 1,
|
'default': 1,
|
||||||
'max_value': 10, 'increment': 1
|
'min_value': 1,
|
||||||
|
'max_value': 10,
|
||||||
|
'increment': 1
|
||||||
}),
|
}),
|
||||||
('Time Limit', {
|
('Time Limit', {
|
||||||
'choices': [('None', 0), ('1 Minute', 60),
|
'choices': [('None', 0), ('1 Minute', 60), ('2 Minutes', 120),
|
||||||
('2 Minutes', 120), ('5 Minutes', 300),
|
('5 Minutes', 300), ('10 Minutes', 600),
|
||||||
('10 Minutes', 600), ('20 Minutes', 1200)],
|
('20 Minutes', 1200)],
|
||||||
'default': 0
|
'default': 0
|
||||||
}),
|
}),
|
||||||
('Respawn Times', {
|
('Respawn Times', {
|
||||||
@ -210,7 +227,10 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
('Long', 2.0), ('Longer', 4.0)],
|
('Long', 2.0), ('Longer', 4.0)],
|
||||||
'default': 1.0
|
'default': 1.0
|
||||||
}),
|
}),
|
||||||
('Epic Mode', {'default': False})] # yapf: disable
|
('Epic Mode', {
|
||||||
|
'default': False
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
if issubclass(sessiontype, ba.DualTeamSession):
|
if issubclass(sessiontype, ba.DualTeamSession):
|
||||||
settings.append(('Solo Mode', {'default': False}))
|
settings.append(('Solo Mode', {'default': False}))
|
||||||
@ -221,17 +241,23 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
def __init__(self, settings: Dict[str, Any]):
|
def __init__(self, settings: Dict[str, Any]):
|
||||||
from bastd.actor.scoreboard import Scoreboard
|
from bastd.actor.scoreboard import Scoreboard
|
||||||
super().__init__(settings)
|
super().__init__(settings)
|
||||||
if self.settings_raw['Epic Mode']:
|
|
||||||
self.slow_motion = True
|
|
||||||
|
|
||||||
# Show messages when players die since it's meaningful here.
|
|
||||||
self.announce_player_deaths = True
|
|
||||||
|
|
||||||
self._solo_mode = settings.get('Solo Mode', False)
|
|
||||||
self._scoreboard = Scoreboard()
|
self._scoreboard = Scoreboard()
|
||||||
self._start_time: Optional[float] = None
|
self._start_time: Optional[float] = None
|
||||||
self._vs_text: Optional[ba.Actor] = None
|
self._vs_text: Optional[ba.Actor] = None
|
||||||
self._round_end_timer: Optional[ba.Timer] = None
|
self._round_end_timer: Optional[ba.Timer] = None
|
||||||
|
self._epic_mode = bool(settings['Epic Mode'])
|
||||||
|
self._lives_per_player = int(settings['Lives Per Player'])
|
||||||
|
self._time_limit = float(settings['Time Limit'])
|
||||||
|
self._balance_total_lives = bool(
|
||||||
|
settings.get('Balance Total Lives', False))
|
||||||
|
self._solo_mode = bool(settings.get('Solo Mode', False))
|
||||||
|
|
||||||
|
# Base class overrides:
|
||||||
|
# Show messages when players die since it's meaningful here.
|
||||||
|
self.announce_player_deaths = True
|
||||||
|
self.slow_motion = self._epic_mode
|
||||||
|
self.default_music = (ba.MusicType.EPIC
|
||||||
|
if self._epic_mode else ba.MusicType.SURVIVAL)
|
||||||
|
|
||||||
def get_instance_description(self) -> Union[str, Sequence]:
|
def get_instance_description(self) -> Union[str, Sequence]:
|
||||||
return 'Last team standing wins.' if isinstance(
|
return 'Last team standing wins.' if isinstance(
|
||||||
@ -241,65 +267,93 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
return 'last team standing wins' if isinstance(
|
return 'last team standing wins' if isinstance(
|
||||||
self.session, ba.DualTeamSession) else 'last one standing wins'
|
self.session, ba.DualTeamSession) else 'last one standing wins'
|
||||||
|
|
||||||
def on_transition_in(self) -> None:
|
def on_player_join(self, player: Player) -> None:
|
||||||
self.default_music = (ba.MusicType.EPIC
|
|
||||||
if self.settings_raw['Epic Mode'] else
|
|
||||||
ba.MusicType.SURVIVAL)
|
|
||||||
super().on_transition_in()
|
|
||||||
self._start_time = ba.time()
|
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
|
||||||
team.gamedata['survival_seconds'] = None
|
|
||||||
team.gamedata['spawn_order'] = []
|
|
||||||
|
|
||||||
def on_player_join(self, player: ba.Player) -> None:
|
|
||||||
|
|
||||||
# No longer allowing mid-game joiners here; too easy to exploit.
|
# No longer allowing mid-game joiners here; too easy to exploit.
|
||||||
if self.has_begun():
|
if self.has_begun():
|
||||||
player.gamedata['lives'] = 0
|
|
||||||
player.gamedata['icons'] = []
|
|
||||||
|
|
||||||
# Make sure our team has survival seconds set if they're all dead
|
# Make sure our team has survival seconds set if they're all dead
|
||||||
# (otherwise blocked new ffa players would be considered 'still
|
# (otherwise blocked new ffa players would be considered 'still
|
||||||
# alive' in score tallying).
|
# alive' in score tallying).
|
||||||
if self._get_total_team_lives(
|
if (self._get_total_team_lives(player.team) == 0
|
||||||
player.team
|
and player.team.survival_seconds is None):
|
||||||
) == 0 and player.team.gamedata['survival_seconds'] is None:
|
player.team.survival_seconds = 0
|
||||||
player.team.gamedata['survival_seconds'] = 0
|
ba.screenmessage(
|
||||||
ba.screenmessage(ba.Lstr(resource='playerDelayedJoinText',
|
ba.Lstr(resource='playerDelayedJoinText',
|
||||||
subs=[('${PLAYER}',
|
subs=[('${PLAYER}', player.get_name(full=True))]),
|
||||||
player.get_name(full=True))]),
|
color=(0, 1, 0),
|
||||||
color=(0, 1, 0))
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
player.gamedata['lives'] = self.settings_raw['Lives Per Player']
|
player.lives = self._lives_per_player
|
||||||
|
|
||||||
if self._solo_mode:
|
if self._solo_mode:
|
||||||
player.gamedata['icons'] = []
|
player.team.spawn_order.append(player)
|
||||||
player.team.gamedata['spawn_order'].append(player)
|
|
||||||
self._update_solo_mode()
|
self._update_solo_mode()
|
||||||
else:
|
else:
|
||||||
# Create our icon and spawn.
|
# Create our icon and spawn.
|
||||||
player.gamedata['icons'] = [
|
player.icons = [Icon(player, position=(0, 50), scale=0.8)]
|
||||||
Icon(player, position=(0, 50), scale=0.8)
|
if player.lives > 0:
|
||||||
]
|
|
||||||
if player.gamedata['lives'] > 0:
|
|
||||||
self.spawn_player(player)
|
self.spawn_player(player)
|
||||||
|
|
||||||
# Don't waste time doing this until begin.
|
# Don't waste time doing this until begin.
|
||||||
if self.has_begun():
|
if self.has_begun():
|
||||||
self._update_icons()
|
self._update_icons()
|
||||||
|
|
||||||
|
def on_begin(self) -> None:
|
||||||
|
super().on_begin()
|
||||||
|
self._start_time = ba.time()
|
||||||
|
self.setup_standard_time_limit(self._time_limit)
|
||||||
|
self.setup_standard_powerup_drops()
|
||||||
|
if self._solo_mode:
|
||||||
|
self._vs_text = ba.NodeActor(
|
||||||
|
ba.newnode('text',
|
||||||
|
attrs={
|
||||||
|
'position': (0, 105),
|
||||||
|
'h_attach': 'center',
|
||||||
|
'h_align': 'center',
|
||||||
|
'maxwidth': 200,
|
||||||
|
'shadow': 0.5,
|
||||||
|
'vr_depth': 390,
|
||||||
|
'scale': 0.6,
|
||||||
|
'v_attach': 'bottom',
|
||||||
|
'color': (0.8, 0.8, 0.3, 1.0),
|
||||||
|
'text': ba.Lstr(resource='vsText')
|
||||||
|
}))
|
||||||
|
|
||||||
|
# If balance-team-lives is on, add lives to the smaller team until
|
||||||
|
# total lives match.
|
||||||
|
if (isinstance(self.session, ba.DualTeamSession)
|
||||||
|
and self._balance_total_lives and self.teams[0].players
|
||||||
|
and self.teams[1].players):
|
||||||
|
if self._get_total_team_lives(
|
||||||
|
self.teams[0]) < self._get_total_team_lives(self.teams[1]):
|
||||||
|
lesser_team = self.teams[0]
|
||||||
|
greater_team = self.teams[1]
|
||||||
|
else:
|
||||||
|
lesser_team = self.teams[1]
|
||||||
|
greater_team = self.teams[0]
|
||||||
|
add_index = 0
|
||||||
|
while (self._get_total_team_lives(lesser_team) <
|
||||||
|
self._get_total_team_lives(greater_team)):
|
||||||
|
lesser_team.players[add_index].lives += 1
|
||||||
|
add_index = (add_index + 1) % len(lesser_team.players)
|
||||||
|
|
||||||
|
self._update_icons()
|
||||||
|
|
||||||
|
# We could check game-over conditions at explicit trigger points,
|
||||||
|
# but lets just do the simple thing and poll it.
|
||||||
|
ba.timer(1.0, self._update, repeat=True)
|
||||||
|
|
||||||
def _update_solo_mode(self) -> None:
|
def _update_solo_mode(self) -> None:
|
||||||
# For both teams, find the first player on the spawn order list with
|
# For both teams, find the first player on the spawn order list with
|
||||||
# lives remaining and spawn them if they're not alive.
|
# lives remaining and spawn them if they're not alive.
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
# Prune dead players from the spawn order.
|
# Prune dead players from the spawn order.
|
||||||
team.gamedata['spawn_order'] = [
|
team.spawn_order = [p for p in team.spawn_order if p]
|
||||||
p for p in team.gamedata['spawn_order'] if p
|
for player in team.spawn_order:
|
||||||
]
|
assert isinstance(player, Player)
|
||||||
for player in team.gamedata['spawn_order']:
|
if player.lives > 0:
|
||||||
if player.gamedata['lives'] > 0:
|
|
||||||
if not player.is_alive():
|
if not player.is_alive():
|
||||||
self.spawn_player(player)
|
self.spawn_player(player)
|
||||||
break
|
break
|
||||||
@ -315,7 +369,7 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
if len(team.players) == 1:
|
if len(team.players) == 1:
|
||||||
player = team.players[0]
|
player = team.players[0]
|
||||||
for icon in player.gamedata['icons']:
|
for icon in player.icons:
|
||||||
icon.set_position_and_scale((xval, 30), 0.7)
|
icon.set_position_and_scale((xval, 30), 0.7)
|
||||||
icon.update_for_lives()
|
icon.update_for_lives()
|
||||||
xval += x_offs
|
xval += x_offs
|
||||||
@ -325,7 +379,7 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
if self._solo_mode:
|
if self._solo_mode:
|
||||||
# First off, clear out all icons.
|
# First off, clear out all icons.
|
||||||
for player in self.players:
|
for player in self.players:
|
||||||
player.gamedata['icons'] = []
|
player.icons = []
|
||||||
|
|
||||||
# Now for each team, cycle through our available players
|
# Now for each team, cycle through our available players
|
||||||
# adding icons.
|
# adding icons.
|
||||||
@ -340,13 +394,13 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
test_lives = 1
|
test_lives = 1
|
||||||
while True:
|
while True:
|
||||||
players_with_lives = [
|
players_with_lives = [
|
||||||
p for p in team.gamedata['spawn_order']
|
p for p in team.spawn_order
|
||||||
if p and p.gamedata['lives'] >= test_lives
|
if p and p.lives >= test_lives
|
||||||
]
|
]
|
||||||
if not players_with_lives:
|
if not players_with_lives:
|
||||||
break
|
break
|
||||||
for player in players_with_lives:
|
for player in players_with_lives:
|
||||||
player.gamedata['icons'].append(
|
player.icons.append(
|
||||||
Icon(player,
|
Icon(player,
|
||||||
position=(xval, (40 if is_first else 25)),
|
position=(xval, (40 if is_first else 25)),
|
||||||
scale=1.0 if is_first else 0.5,
|
scale=1.0 if is_first else 0.5,
|
||||||
@ -369,12 +423,12 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
xval = 50
|
xval = 50
|
||||||
x_offs = 85
|
x_offs = 85
|
||||||
for player in team.players:
|
for player in team.players:
|
||||||
for icon in player.gamedata['icons']:
|
for icon in player.icons:
|
||||||
icon.set_position_and_scale((xval, 30), 0.7)
|
icon.set_position_and_scale((xval, 30), 0.7)
|
||||||
icon.update_for_lives()
|
icon.update_for_lives()
|
||||||
xval += x_offs
|
xval += x_offs
|
||||||
|
|
||||||
def _get_spawn_point(self, player: ba.Player) -> Optional[ba.Vec3]:
|
def _get_spawn_point(self, player: Player) -> Optional[ba.Vec3]:
|
||||||
del player # Unused.
|
del player # Unused.
|
||||||
|
|
||||||
# In solo-mode, if there's an existing live player on the map, spawn at
|
# In solo-mode, if there's an existing live player on the map, spawn at
|
||||||
@ -403,119 +457,76 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
return points[-1][1]
|
return points[-1][1]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def spawn_player(self, player: ba.Player) -> ba.Actor:
|
def spawn_player(self, player: Player) -> ba.Actor:
|
||||||
actor = self.spawn_player_spaz(player, self._get_spawn_point(player))
|
actor = self.spawn_player_spaz(player, self._get_spawn_point(player))
|
||||||
if not self._solo_mode:
|
if not self._solo_mode:
|
||||||
ba.timer(0.3, ba.Call(self._print_lives, player))
|
ba.timer(0.3, ba.Call(self._print_lives, player))
|
||||||
|
|
||||||
# If we have any icons, update their state.
|
# If we have any icons, update their state.
|
||||||
for icon in player.gamedata['icons']:
|
for icon in player.icons:
|
||||||
icon.handle_player_spawned()
|
icon.handle_player_spawned()
|
||||||
return actor
|
return actor
|
||||||
|
|
||||||
def _print_lives(self, player: ba.Player) -> None:
|
def _print_lives(self, player: Player) -> None:
|
||||||
from bastd.actor import popuptext
|
from bastd.actor import popuptext
|
||||||
assert player # Shouldn't be passing invalid refs around.
|
|
||||||
|
# We get called in a timer so it's possible our player has left/etc.
|
||||||
if not player or not player.is_alive() or not player.node:
|
if not player or not player.is_alive() or not player.node:
|
||||||
return
|
return
|
||||||
|
|
||||||
popuptext.PopupText('x' + str(player.gamedata['lives'] - 1),
|
popuptext.PopupText('x' + str(player.lives - 1),
|
||||||
color=(1, 1, 0, 1),
|
color=(1, 1, 0, 1),
|
||||||
offset=(0, -0.8, 0),
|
offset=(0, -0.8, 0),
|
||||||
random_offset=0.0,
|
random_offset=0.0,
|
||||||
scale=1.8,
|
scale=1.8,
|
||||||
position=player.node.position).autoretain()
|
position=player.node.position).autoretain()
|
||||||
|
|
||||||
def on_player_leave(self, player: ba.Player) -> None:
|
def on_player_leave(self, player: Player) -> None:
|
||||||
super().on_player_leave(player)
|
super().on_player_leave(player)
|
||||||
player.gamedata['icons'] = None
|
player.icons = []
|
||||||
|
|
||||||
# Remove us from spawn-order.
|
# Remove us from spawn-order.
|
||||||
if self._solo_mode:
|
if self._solo_mode:
|
||||||
if player in player.team.gamedata['spawn_order']:
|
if player in player.team.spawn_order:
|
||||||
player.team.gamedata['spawn_order'].remove(player)
|
player.team.spawn_order.remove(player)
|
||||||
|
|
||||||
# Update icons in a moment since our team will be gone from the
|
# Update icons in a moment since our team will be gone from the
|
||||||
# list then.
|
# list then.
|
||||||
ba.timer(0, self._update_icons)
|
ba.timer(0, self._update_icons)
|
||||||
|
|
||||||
def on_begin(self) -> None:
|
def _get_total_team_lives(self, team: Team) -> int:
|
||||||
super().on_begin()
|
return sum(player.lives for player in team.players)
|
||||||
self.setup_standard_time_limit(self.settings_raw['Time Limit'])
|
|
||||||
self.setup_standard_powerup_drops()
|
|
||||||
if self._solo_mode:
|
|
||||||
self._vs_text = ba.NodeActor(
|
|
||||||
ba.newnode('text',
|
|
||||||
attrs={
|
|
||||||
'position': (0, 105),
|
|
||||||
'h_attach': 'center',
|
|
||||||
'h_align': 'center',
|
|
||||||
'maxwidth': 200,
|
|
||||||
'shadow': 0.5,
|
|
||||||
'vr_depth': 390,
|
|
||||||
'scale': 0.6,
|
|
||||||
'v_attach': 'bottom',
|
|
||||||
'color': (0.8, 0.8, 0.3, 1.0),
|
|
||||||
'text': ba.Lstr(resource='vsText')
|
|
||||||
}))
|
|
||||||
|
|
||||||
# If balance-team-lives is on, add lives to the smaller team until
|
|
||||||
# total lives match.
|
|
||||||
if (isinstance(self.session, ba.DualTeamSession)
|
|
||||||
and self.settings_raw['Balance Total Lives']
|
|
||||||
and self.teams[0].players and self.teams[1].players):
|
|
||||||
if self._get_total_team_lives(
|
|
||||||
self.teams[0]) < self._get_total_team_lives(self.teams[1]):
|
|
||||||
lesser_team = self.teams[0]
|
|
||||||
greater_team = self.teams[1]
|
|
||||||
else:
|
|
||||||
lesser_team = self.teams[1]
|
|
||||||
greater_team = self.teams[0]
|
|
||||||
add_index = 0
|
|
||||||
while self._get_total_team_lives(
|
|
||||||
lesser_team) < self._get_total_team_lives(greater_team):
|
|
||||||
lesser_team.players[add_index].gamedata['lives'] += 1
|
|
||||||
add_index = (add_index + 1) % len(lesser_team.players)
|
|
||||||
|
|
||||||
self._update_icons()
|
|
||||||
|
|
||||||
# We could check game-over conditions at explicit trigger points,
|
|
||||||
# but lets just do the simple thing and poll it.
|
|
||||||
ba.timer(1.0, self._update, repeat=True)
|
|
||||||
|
|
||||||
def _get_total_team_lives(self, team: ba.Team) -> int:
|
|
||||||
return sum(player.gamedata['lives'] for player in team.players)
|
|
||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
||||||
|
|
||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
player = msg.playerspaz(self).player
|
player: Player = msg.playerspaz(self).player
|
||||||
|
|
||||||
player.gamedata['lives'] -= 1
|
player.lives -= 1
|
||||||
if player.gamedata['lives'] < 0:
|
if player.lives < 0:
|
||||||
ba.print_error(
|
ba.print_error(
|
||||||
"Got lives < 0 in Elim; this shouldn't happen. solo:" +
|
"Got lives < 0 in Elim; this shouldn't happen. solo:" +
|
||||||
str(self._solo_mode))
|
str(self._solo_mode))
|
||||||
player.gamedata['lives'] = 0
|
player.lives = 0
|
||||||
|
|
||||||
# If we have any icons, update their state.
|
# If we have any icons, update their state.
|
||||||
for icon in player.gamedata['icons']:
|
for icon in player.icons:
|
||||||
icon.handle_player_died()
|
icon.handle_player_died()
|
||||||
|
|
||||||
# Play big death sound on our last death
|
# Play big death sound on our last death
|
||||||
# or for every one in solo mode.
|
# or for every one in solo mode.
|
||||||
if self._solo_mode or player.gamedata['lives'] == 0:
|
if self._solo_mode or player.lives == 0:
|
||||||
ba.playsound(spaz.get_factory().single_player_death_sound)
|
ba.playsound(spaz.get_factory().single_player_death_sound)
|
||||||
|
|
||||||
# If we hit zero lives, we're dead (and our team might be too).
|
# If we hit zero lives, we're dead (and our team might be too).
|
||||||
if player.gamedata['lives'] == 0:
|
if player.lives == 0:
|
||||||
# If the whole team is now dead, mark their survival time.
|
# If the whole team is now dead, mark their survival time.
|
||||||
if self._get_total_team_lives(player.team) == 0:
|
if self._get_total_team_lives(player.team) == 0:
|
||||||
assert self._start_time is not None
|
assert self._start_time is not None
|
||||||
player.team.gamedata['survival_seconds'] = int(
|
player.team.survival_seconds = int(ba.time() -
|
||||||
ba.time() - self._start_time)
|
self._start_time)
|
||||||
else:
|
else:
|
||||||
# Otherwise, in regular mode, respawn.
|
# Otherwise, in regular mode, respawn.
|
||||||
if not self._solo_mode:
|
if not self._solo_mode:
|
||||||
@ -523,8 +534,8 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
|
|
||||||
# In solo, put ourself at the back of the spawn order.
|
# In solo, put ourself at the back of the spawn order.
|
||||||
if self._solo_mode:
|
if self._solo_mode:
|
||||||
player.team.gamedata['spawn_order'].remove(player)
|
player.team.spawn_order.remove(player)
|
||||||
player.team.gamedata['spawn_order'].append(player)
|
player.team.spawn_order.append(player)
|
||||||
|
|
||||||
def _update(self) -> None:
|
def _update(self) -> None:
|
||||||
if self._solo_mode:
|
if self._solo_mode:
|
||||||
@ -532,11 +543,10 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
# list with lives remaining and spawn them if they're not alive.
|
# list with lives remaining and spawn them if they're not alive.
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
# Prune dead players from the spawn order.
|
# Prune dead players from the spawn order.
|
||||||
team.gamedata['spawn_order'] = [
|
team.spawn_order = [p for p in team.spawn_order if p]
|
||||||
p for p in team.gamedata['spawn_order'] if p
|
for player in team.spawn_order:
|
||||||
]
|
assert isinstance(player, Player)
|
||||||
for player in team.gamedata['spawn_order']:
|
if player.lives > 0:
|
||||||
if player.gamedata['lives'] > 0:
|
|
||||||
if not player.is_alive():
|
if not player.is_alive():
|
||||||
self.spawn_player(player)
|
self.spawn_player(player)
|
||||||
self._update_icons()
|
self._update_icons()
|
||||||
@ -548,10 +558,10 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
if len(self._get_living_teams()) < 2:
|
if len(self._get_living_teams()) < 2:
|
||||||
self._round_end_timer = ba.Timer(0.5, self.end_game)
|
self._round_end_timer = ba.Timer(0.5, self.end_game)
|
||||||
|
|
||||||
def _get_living_teams(self) -> List[ba.Team]:
|
def _get_living_teams(self) -> List[Team]:
|
||||||
return [
|
return [
|
||||||
team for team in self.teams
|
team for team in self.teams
|
||||||
if len(team.players) > 0 and any(player.gamedata['lives'] > 0
|
if len(team.players) > 0 and any(player.lives > 0
|
||||||
for player in team.players)
|
for player in team.players)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -561,5 +571,5 @@ class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
results = ba.TeamGameResults()
|
results = ba.TeamGameResults()
|
||||||
self._vs_text = None # Kill our 'vs' if its there.
|
self._vs_text = None # Kill our 'vs' if its there.
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
results.set_team_score(team, team.gamedata['survival_seconds'])
|
results.set_team_score(team, team.survival_seconds)
|
||||||
self.end(results=results)
|
self.end(results=results)
|
||||||
|
|||||||
@ -26,14 +26,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
import math
|
import math
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
from bastd.actor import bomb as stdbomb
|
|
||||||
from bastd.actor import flag as stdflag
|
|
||||||
from bastd.actor import playerspaz
|
|
||||||
from bastd.actor import spazbot
|
from bastd.actor import spazbot
|
||||||
|
from bastd.actor import flag as stdflag
|
||||||
|
from bastd.actor.bomb import TNTSpawner
|
||||||
|
from bastd.actor.playerspaz import PlayerSpaz, PlayerSpazDeathMessage
|
||||||
from bastd.actor.scoreboard import Scoreboard
|
from bastd.actor.scoreboard import Scoreboard
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -66,8 +67,18 @@ class FootballFlag(stdflag.Flag):
|
|||||||
self.node.connectattr('position', self.light, 'position')
|
self.node.connectattr('position', self.light, 'position')
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Player(ba.Player['Team']):
|
||||||
|
"""Our player type for this game."""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class FootballTeamGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
|
||||||
"""Football game for teams mode."""
|
"""Football game for teams mode."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -183,7 +194,7 @@ class FootballTeamGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
self._update_scoreboard()
|
self._update_scoreboard()
|
||||||
ba.playsound(self._chant_sound)
|
ba.playsound(self._chant_sound)
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
def on_team_join(self, team: Team) -> None:
|
||||||
team.gamedata['score'] = 0
|
team.gamedata['score'] = 0
|
||||||
self._update_scoreboard()
|
self._update_scoreboard()
|
||||||
|
|
||||||
@ -271,7 +282,7 @@ class FootballTeamGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
msg.flag.held_count -= 1
|
msg.flag.held_count -= 1
|
||||||
|
|
||||||
# Respawn dead players if they're still in the game.
|
# Respawn dead players if they're still in the game.
|
||||||
elif isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
elif isinstance(msg, PlayerSpazDeathMessage):
|
||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
self.respawn_player(msg.playerspaz(self).player)
|
self.respawn_player(msg.playerspaz(self).player)
|
||||||
@ -320,7 +331,7 @@ class FootballTeamGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
self._flag = FootballFlag(position=self._flag_spawn_pos)
|
self._flag = FootballFlag(position=self._flag_spawn_pos)
|
||||||
|
|
||||||
|
|
||||||
class FootballCoopGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
|
||||||
"""
|
"""
|
||||||
Co-op variant of football
|
Co-op variant of football
|
||||||
"""
|
"""
|
||||||
@ -385,15 +396,15 @@ class FootballCoopGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
|||||||
self._bot_types_initial: Optional[List[Type[spazbot.SpazBot]]] = None
|
self._bot_types_initial: Optional[List[Type[spazbot.SpazBot]]] = None
|
||||||
self._bot_types_7: Optional[List[Type[spazbot.SpazBot]]] = None
|
self._bot_types_7: Optional[List[Type[spazbot.SpazBot]]] = None
|
||||||
self._bot_types_14: Optional[List[Type[spazbot.SpazBot]]] = None
|
self._bot_types_14: Optional[List[Type[spazbot.SpazBot]]] = None
|
||||||
self._bot_team: Optional[ba.Team] = None
|
self._bot_team: Optional[Team] = None
|
||||||
self._starttime_ms: Optional[int] = None
|
self._starttime_ms: Optional[int] = None
|
||||||
self._time_text: Optional[ba.NodeActor] = None
|
self._time_text: Optional[ba.NodeActor] = None
|
||||||
self._time_text_input: Optional[ba.NodeActor] = None
|
self._time_text_input: Optional[ba.NodeActor] = None
|
||||||
self._tntspawner: Optional[stdbomb.TNTSpawner] = None
|
self._tntspawner: Optional[TNTSpawner] = None
|
||||||
self._bots = spazbot.BotSet()
|
self._bots = spazbot.BotSet()
|
||||||
self._bot_spawn_timer: Optional[ba.Timer] = None
|
self._bot_spawn_timer: Optional[ba.Timer] = None
|
||||||
self._powerup_drop_timer: Optional[ba.Timer] = None
|
self._powerup_drop_timer: Optional[ba.Timer] = None
|
||||||
self.scoring_team: Optional[ba.Team] = None
|
self.scoring_team: Optional[Team] = None
|
||||||
self._final_time_ms: Optional[int] = None
|
self._final_time_ms: Optional[int] = None
|
||||||
self._time_text_timer: Optional[ba.Timer] = None
|
self._time_text_timer: Optional[ba.Timer] = None
|
||||||
self._flag_respawn_light: Optional[ba.Actor] = None
|
self._flag_respawn_light: Optional[ba.Actor] = None
|
||||||
@ -508,11 +519,10 @@ class FootballCoopGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
|||||||
|
|
||||||
# Make a bogus team for our bots.
|
# Make a bogus team for our bots.
|
||||||
bad_team_name = self.get_team_display_string('Bad Guys')
|
bad_team_name = self.get_team_display_string('Bad Guys')
|
||||||
# self._bot_team = ba.Team(1, bad_team_name, (0.5, 0.4, 0.4))
|
self._bot_team = Team()
|
||||||
self._bot_team = ba.Team()
|
self._bot_team.manual_init(team_id=1,
|
||||||
self._bot_team.id = 1
|
name=bad_team_name,
|
||||||
self._bot_team.name = bad_team_name
|
color=(0.5, 0.4, 0.4))
|
||||||
self._bot_team.color = (0.5, 0.4, 0.4)
|
|
||||||
|
|
||||||
for team in [self.teams[0], self._bot_team]:
|
for team in [self.teams[0], self._bot_team]:
|
||||||
team.gamedata['score'] = 0
|
team.gamedata['score'] = 0
|
||||||
@ -547,7 +557,7 @@ class FootballCoopGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
|||||||
|
|
||||||
# Our TNT spawner (if applicable).
|
# Our TNT spawner (if applicable).
|
||||||
if self._have_tnt:
|
if self._have_tnt:
|
||||||
self._tntspawner = stdbomb.TNTSpawner(position=(0, 1, -1))
|
self._tntspawner = TNTSpawner(position=(0, 1, -1))
|
||||||
|
|
||||||
self._bots = spazbot.BotSet()
|
self._bots = spazbot.BotSet()
|
||||||
self._bot_spawn_timer = ba.Timer(1.0, self._update_bots, repeat=True)
|
self._bot_spawn_timer = ba.Timer(1.0, self._update_bots, repeat=True)
|
||||||
@ -588,7 +598,7 @@ class FootballCoopGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
|||||||
if self._flag.node:
|
if self._flag.node:
|
||||||
for player in self.players:
|
for player in self.players:
|
||||||
if player.actor:
|
if player.actor:
|
||||||
assert isinstance(player.actor, playerspaz.PlayerSpaz)
|
assert isinstance(player.actor, PlayerSpaz)
|
||||||
if (player.actor.is_alive() and player.actor.node.hold_node
|
if (player.actor.is_alive() and player.actor.node.hold_node
|
||||||
== self._flag.node):
|
== self._flag.node):
|
||||||
return
|
return
|
||||||
@ -808,7 +818,7 @@ class FootballCoopGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
|||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
""" handle high-level game messages """
|
""" handle high-level game messages """
|
||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, PlayerSpazDeathMessage):
|
||||||
from bastd.actor import respawnicon
|
from bastd.actor import respawnicon
|
||||||
|
|
||||||
# Respawn dead players.
|
# Respawn dead players.
|
||||||
@ -872,7 +882,7 @@ class FootballCoopGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
|||||||
del player # Unused.
|
del player # Unused.
|
||||||
self._player_has_punched = True
|
self._player_has_punched = True
|
||||||
|
|
||||||
def spawn_player(self, player: ba.Player) -> ba.Actor:
|
def spawn_player(self, player: Player) -> ba.Actor:
|
||||||
spaz = self.spawn_player_spaz(player,
|
spaz = self.spawn_player_spaz(player,
|
||||||
position=self.map.get_start_position(
|
position=self.map.get_start_position(
|
||||||
player.team.id))
|
player.team.id))
|
||||||
|
|||||||
@ -25,19 +25,31 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
from bastd.actor import flag as stdflag
|
from bastd.actor.flag import (Flag, FlagDroppedMessage, FlagDeathMessage,
|
||||||
from bastd.actor import playerspaz
|
FlagPickedUpMessage)
|
||||||
|
from bastd.actor.playerspaz import PlayerSpaz, PlayerSpazDeathMessage
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import (Any, Type, List, Tuple, Dict, Optional, Sequence,
|
from typing import (Any, Type, List, Tuple, Dict, Optional, Sequence,
|
||||||
Union)
|
Union)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Player(ba.Player['Team']):
|
||||||
|
"""Our player type for this game."""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class KeepAwayGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
|
||||||
"""Game where you try to keep the flag away from your enemies."""
|
"""Game where you try to keep the flag away from your enemies."""
|
||||||
|
|
||||||
FLAG_NEW = 0
|
FLAG_NEW = 0
|
||||||
@ -109,11 +121,11 @@ class KeepAwayGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
}
|
}
|
||||||
self._flag_spawn_pos: Optional[Sequence[float]] = None
|
self._flag_spawn_pos: Optional[Sequence[float]] = None
|
||||||
self._update_timer: Optional[ba.Timer] = None
|
self._update_timer: Optional[ba.Timer] = None
|
||||||
self._holding_players: List[ba.Player] = []
|
self._holding_players: List[Player] = []
|
||||||
self._flag_state: Optional[int] = None
|
self._flag_state: Optional[int] = None
|
||||||
self._flag_light: Optional[ba.Node] = None
|
self._flag_light: Optional[ba.Node] = None
|
||||||
self._scoring_team: Optional[ba.Team] = None
|
self._scoring_team: Optional[Team] = None
|
||||||
self._flag: Optional[stdflag.Flag] = None
|
self._flag: Optional[Flag] = None
|
||||||
|
|
||||||
def get_instance_description(self) -> Union[str, Sequence]:
|
def get_instance_description(self) -> Union[str, Sequence]:
|
||||||
return ('Carry the flag for ${ARG1} seconds.',
|
return ('Carry the flag for ${ARG1} seconds.',
|
||||||
@ -127,7 +139,7 @@ class KeepAwayGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
self.default_music = ba.MusicType.KEEP_AWAY
|
self.default_music = ba.MusicType.KEEP_AWAY
|
||||||
super().on_transition_in()
|
super().on_transition_in()
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
def on_team_join(self, team: Team) -> None:
|
||||||
team.gamedata['time_remaining'] = self.settings_raw['Hold Time']
|
team.gamedata['time_remaining'] = self.settings_raw['Hold Time']
|
||||||
self._update_scoreboard()
|
self._update_scoreboard()
|
||||||
|
|
||||||
@ -194,8 +206,8 @@ class KeepAwayGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
for player in self.players:
|
for player in self.players:
|
||||||
holding_flag = False
|
holding_flag = False
|
||||||
try:
|
try:
|
||||||
assert isinstance(player.actor, playerspaz.PlayerSpaz)
|
assert isinstance(player.actor, (PlayerSpaz, type(None)))
|
||||||
if (player.actor.is_alive() and player.actor.node
|
if (player.actor and player.actor.node
|
||||||
and player.actor.node.hold_node):
|
and player.actor.node.hold_node):
|
||||||
holding_flag = (
|
holding_flag = (
|
||||||
player.actor.node.hold_node.getnodetype() == 'flag')
|
player.actor.node.hold_node.getnodetype() == 'flag')
|
||||||
@ -235,8 +247,7 @@ class KeepAwayGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
ba.playsound(self._swipsound)
|
ba.playsound(self._swipsound)
|
||||||
self._flash_flag_spawn()
|
self._flash_flag_spawn()
|
||||||
assert self._flag_spawn_pos is not None
|
assert self._flag_spawn_pos is not None
|
||||||
self._flag = stdflag.Flag(dropped_timeout=20,
|
self._flag = Flag(dropped_timeout=20, position=self._flag_spawn_pos)
|
||||||
position=self._flag_spawn_pos)
|
|
||||||
self._flag_state = self.FLAG_NEW
|
self._flag_state = self.FLAG_NEW
|
||||||
self._flag_light = ba.newnode('light',
|
self._flag_light = ba.newnode('light',
|
||||||
owner=self._flag.node,
|
owner=self._flag.node,
|
||||||
@ -268,15 +279,13 @@ class KeepAwayGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
countdown=True)
|
countdown=True)
|
||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, PlayerSpazDeathMessage):
|
||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
self.respawn_player(msg.playerspaz(self).player)
|
self.respawn_player(msg.playerspaz(self).player)
|
||||||
elif isinstance(msg, stdflag.FlagDeathMessage):
|
elif isinstance(msg, FlagDeathMessage):
|
||||||
self._spawn_flag()
|
self._spawn_flag()
|
||||||
elif isinstance(
|
elif isinstance(msg, (FlagDroppedMessage, FlagPickedUpMessage)):
|
||||||
msg,
|
|
||||||
(stdflag.FlagDroppedMessage, stdflag.FlagPickedUpMessage)):
|
|
||||||
self._update_flag_state()
|
self._update_flag_state()
|
||||||
else:
|
else:
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
|
|||||||
@ -26,11 +26,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import weakref
|
import weakref
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
from bastd.actor import flag as stdflag
|
from bastd.actor.flag import Flag
|
||||||
from bastd.actor import playerspaz
|
from bastd.actor.playerspaz import PlayerSpaz, PlayerSpazDeathMessage
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from weakref import ReferenceType
|
from weakref import ReferenceType
|
||||||
@ -38,8 +39,20 @@ if TYPE_CHECKING:
|
|||||||
Union)
|
Union)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Player(ba.Player['Team']):
|
||||||
|
"""Our player type for this game."""
|
||||||
|
time_at_flag: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
time_remaining: int
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class KingOfTheHillGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
|
||||||
"""Game where a team wins by holding a 'hill' for a set amount of time."""
|
"""Game where a team wins by holding a 'hill' for a set amount of time."""
|
||||||
|
|
||||||
FLAG_NEW = 0
|
FLAG_NEW = 0
|
||||||
@ -71,23 +84,24 @@ class KingOfTheHillGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
def get_settings(
|
def get_settings(
|
||||||
cls,
|
cls,
|
||||||
sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
|
sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
|
||||||
return [('Hold Time', {
|
return [
|
||||||
'min_value': 10,
|
('Hold Time', {
|
||||||
'default': 30,
|
'min_value': 10,
|
||||||
'increment': 10
|
'default': 30,
|
||||||
}),
|
'increment': 10
|
||||||
('Time Limit', {
|
}),
|
||||||
'choices': [('None', 0), ('1 Minute', 60),
|
('Time Limit', {
|
||||||
('2 Minutes', 120), ('5 Minutes', 300),
|
'choices': [('None', 0), ('1 Minute', 60), ('2 Minutes', 120),
|
||||||
('10 Minutes', 600), ('20 Minutes', 1200)],
|
('5 Minutes', 300), ('10 Minutes', 600),
|
||||||
'default': 0
|
('20 Minutes', 1200)],
|
||||||
}),
|
'default': 0
|
||||||
('Respawn Times', {
|
}),
|
||||||
'choices': [('Shorter', 0.25), ('Short', 0.5),
|
('Respawn Times', {
|
||||||
('Normal', 1.0), ('Long', 2.0),
|
'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
|
||||||
('Longer', 4.0)],
|
('Long', 2.0), ('Longer', 4.0)],
|
||||||
'default': 1.0
|
'default': 1.0
|
||||||
})]
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, settings: Dict[str, Any]):
|
def __init__(self, settings: Dict[str, Any]):
|
||||||
from bastd.actor.scoreboard import Scoreboard
|
from bastd.actor.scoreboard import Scoreboard
|
||||||
@ -109,9 +123,11 @@ class KingOfTheHillGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
}
|
}
|
||||||
self._flag_pos: Optional[Sequence[float]] = None
|
self._flag_pos: Optional[Sequence[float]] = None
|
||||||
self._flag_state: Optional[int] = None
|
self._flag_state: Optional[int] = None
|
||||||
self._flag: Optional[stdflag.Flag] = None
|
self._flag: Optional[Flag] = None
|
||||||
self._flag_light: Optional[ba.Node] = None
|
self._flag_light: Optional[ba.Node] = None
|
||||||
self._scoring_team: Optional[ReferenceType[ba.Team]] = None
|
self._scoring_team: Optional[ReferenceType[Team]] = None
|
||||||
|
self._hold_time = int(settings['Hold Time'])
|
||||||
|
self._time_limit = float(settings['Time Limit'])
|
||||||
|
|
||||||
self._flag_region_material = ba.Material()
|
self._flag_region_material = ba.Material()
|
||||||
self._flag_region_material.add_actions(
|
self._flag_region_material.add_actions(
|
||||||
@ -124,38 +140,30 @@ class KingOfTheHillGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
ba.Call(self._handle_player_flag_region_collide,
|
ba.Call(self._handle_player_flag_region_collide,
|
||||||
False))))
|
False))))
|
||||||
|
|
||||||
|
# Base class overrides.
|
||||||
|
self.default_music = ba.MusicType.SCARY
|
||||||
|
|
||||||
def get_instance_description(self) -> Union[str, Sequence]:
|
def get_instance_description(self) -> Union[str, Sequence]:
|
||||||
return ('Secure the flag for ${ARG1} seconds.',
|
return 'Secure the flag for ${ARG1} seconds.', self._hold_time
|
||||||
self.settings_raw['Hold Time'])
|
|
||||||
|
|
||||||
def get_instance_scoreboard_description(self) -> Union[str, Sequence]:
|
def get_instance_scoreboard_description(self) -> Union[str, Sequence]:
|
||||||
return ('secure the flag for ${ARG1} seconds',
|
return 'secure the flag for ${ARG1} seconds', self._hold_time
|
||||||
self.settings_raw['Hold Time'])
|
|
||||||
|
|
||||||
def on_transition_in(self) -> None:
|
def create_team(self, sessionteam: ba.SessionTeam) -> Team:
|
||||||
self.default_music = ba.MusicType.SCARY
|
return Team(time_remaining=self._hold_time)
|
||||||
super().on_transition_in()
|
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
|
||||||
team.gamedata['time_remaining'] = self.settings_raw['Hold Time']
|
|
||||||
self._update_scoreboard()
|
|
||||||
|
|
||||||
def on_player_join(self, player: ba.Player) -> None:
|
|
||||||
super().on_player_join(player)
|
|
||||||
player.gamedata['at_flag'] = 0
|
|
||||||
|
|
||||||
def on_begin(self) -> None:
|
def on_begin(self) -> None:
|
||||||
super().on_begin()
|
super().on_begin()
|
||||||
self.setup_standard_time_limit(self.settings_raw['Time Limit'])
|
self.setup_standard_time_limit(self._time_limit)
|
||||||
self.setup_standard_powerup_drops()
|
self.setup_standard_powerup_drops()
|
||||||
self._flag_pos = self.map.get_flag_position(None)
|
self._flag_pos = self.map.get_flag_position(None)
|
||||||
ba.timer(1.0, self._tick, repeat=True)
|
ba.timer(1.0, self._tick, repeat=True)
|
||||||
self._flag_state = self.FLAG_NEW
|
self._flag_state = self.FLAG_NEW
|
||||||
self.project_flag_stand(self._flag_pos)
|
self.project_flag_stand(self._flag_pos)
|
||||||
|
|
||||||
self._flag = stdflag.Flag(position=self._flag_pos,
|
self._flag = Flag(position=self._flag_pos,
|
||||||
touchable=False,
|
touchable=False,
|
||||||
color=(1, 1, 1))
|
color=(1, 1, 1))
|
||||||
self._flag_light = ba.newnode('light',
|
self._flag_light = ba.newnode('light',
|
||||||
attrs={
|
attrs={
|
||||||
'position': self._flag_pos,
|
'position': self._flag_pos,
|
||||||
@ -184,51 +192,47 @@ class KingOfTheHillGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
|
|
||||||
# Give holding players points.
|
# Give holding players points.
|
||||||
for player in self.players:
|
for player in self.players:
|
||||||
if player.gamedata['at_flag'] > 0:
|
if player.time_at_flag > 0:
|
||||||
self.stats.player_scored(player,
|
self.stats.player_scored(player,
|
||||||
3,
|
3,
|
||||||
screenmessage=False,
|
screenmessage=False,
|
||||||
display=False)
|
display=False)
|
||||||
|
|
||||||
if self._scoring_team is None:
|
if self._scoring_team is None:
|
||||||
scoring_team = None
|
scoring_team = None
|
||||||
else:
|
else:
|
||||||
scoring_team = self._scoring_team()
|
scoring_team = self._scoring_team()
|
||||||
if scoring_team:
|
if scoring_team:
|
||||||
|
|
||||||
if scoring_team.gamedata['time_remaining'] > 0:
|
if scoring_team.time_remaining > 0:
|
||||||
ba.playsound(self._tick_sound)
|
ba.playsound(self._tick_sound)
|
||||||
|
|
||||||
scoring_team.gamedata['time_remaining'] = max(
|
scoring_team.time_remaining = max(0,
|
||||||
0, scoring_team.gamedata['time_remaining'] - 1)
|
scoring_team.time_remaining - 1)
|
||||||
self._update_scoreboard()
|
self._update_scoreboard()
|
||||||
if scoring_team.gamedata['time_remaining'] > 0:
|
if scoring_team.time_remaining > 0:
|
||||||
assert self._flag is not None
|
assert self._flag is not None
|
||||||
self._flag.set_score_text(
|
self._flag.set_score_text(str(scoring_team.time_remaining))
|
||||||
str(scoring_team.gamedata['time_remaining']))
|
|
||||||
|
|
||||||
# Announce numbers we have sounds for.
|
# Announce numbers we have sounds for.
|
||||||
try:
|
try:
|
||||||
ba.playsound(self._countdownsounds[
|
ba.playsound(
|
||||||
scoring_team.gamedata['time_remaining']])
|
self._countdownsounds[scoring_team.time_remaining])
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# winner
|
# winner
|
||||||
if scoring_team.gamedata['time_remaining'] <= 0:
|
if scoring_team.time_remaining <= 0:
|
||||||
self.end_game()
|
self.end_game()
|
||||||
|
|
||||||
def end_game(self) -> None:
|
def end_game(self) -> None:
|
||||||
results = ba.TeamGameResults()
|
results = ba.TeamGameResults()
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
results.set_team_score(
|
results.set_team_score(team, self._hold_time - team.time_remaining)
|
||||||
team, self.settings_raw['Hold Time'] -
|
|
||||||
team.gamedata['time_remaining'])
|
|
||||||
self.end(results=results, announce_delay=0)
|
self.end(results=results, announce_delay=0)
|
||||||
|
|
||||||
def _update_flag_state(self) -> None:
|
def _update_flag_state(self) -> None:
|
||||||
holding_teams = set(player.team for player in self.players
|
holding_teams = set(player.team for player in self.players
|
||||||
if player.gamedata['at_flag'])
|
if player.time_at_flag)
|
||||||
prev_state = self._flag_state
|
prev_state = self._flag_state
|
||||||
assert self._flag_light
|
assert self._flag_light
|
||||||
assert self._flag is not None
|
assert self._flag is not None
|
||||||
@ -253,35 +257,36 @@ class KingOfTheHillGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
ba.playsound(self._swipsound)
|
ba.playsound(self._swipsound)
|
||||||
|
|
||||||
def _handle_player_flag_region_collide(self, colliding: bool) -> None:
|
def _handle_player_flag_region_collide(self, colliding: bool) -> None:
|
||||||
playernode = ba.get_collision_info('opposing_node')
|
delegate = ba.get_collision_info('opposing_node').getdelegate()
|
||||||
try:
|
if not isinstance(delegate, PlayerSpaz):
|
||||||
player = playernode.getdelegate().getplayer()
|
return
|
||||||
except Exception:
|
player = ba.playercast_o(Player, delegate.getplayer())
|
||||||
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Different parts of us can collide so a single value isn't enough
|
# Different parts of us can collide so a single value isn't enough
|
||||||
# also don't count it if we're dead (flying heads shouldn't be able to
|
# also don't count it if we're dead (flying heads shouldn't be able to
|
||||||
# win the game :-)
|
# win the game :-)
|
||||||
if colliding and player.is_alive():
|
if colliding and player.is_alive():
|
||||||
player.gamedata['at_flag'] += 1
|
player.time_at_flag += 1
|
||||||
else:
|
else:
|
||||||
player.gamedata['at_flag'] = max(0, player.gamedata['at_flag'] - 1)
|
player.time_at_flag = max(0, player.time_at_flag - 1)
|
||||||
|
|
||||||
self._update_flag_state()
|
self._update_flag_state()
|
||||||
|
|
||||||
def _update_scoreboard(self) -> None:
|
def _update_scoreboard(self) -> None:
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
self._scoreboard.set_team_value(team,
|
self._scoreboard.set_team_value(team,
|
||||||
team.gamedata['time_remaining'],
|
team.time_remaining,
|
||||||
self.settings_raw['Hold Time'],
|
self._hold_time,
|
||||||
countdown=True)
|
countdown=True)
|
||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, PlayerSpazDeathMessage):
|
||||||
super().handlemessage(msg) # Augment default.
|
super().handlemessage(msg) # Augment default.
|
||||||
|
|
||||||
# No longer can count as at_flag once dead.
|
# No longer can count as time_at_flag once dead.
|
||||||
player = msg.playerspaz(self).player
|
player = msg.playerspaz(self).player
|
||||||
player.gamedata['at_flag'] = 0
|
player.time_at_flag = 0
|
||||||
self._update_flag_state()
|
self._update_flag_state()
|
||||||
self.respawn_player(player)
|
self.respawn_player(player)
|
||||||
|
|||||||
@ -255,6 +255,10 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
|
|||||||
# (these per-player scores are only meaningful in team-games)
|
# (these per-player scores are only meaningful in team-games)
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
for player in team.players:
|
for player in team.players:
|
||||||
|
|
||||||
|
if not player:
|
||||||
|
print(f'GOT DEAD PLAYER {id(player)}')
|
||||||
|
|
||||||
survived = False
|
survived = False
|
||||||
|
|
||||||
# Throw an extra fudge factor in so teams that
|
# Throw an extra fudge factor in so teams that
|
||||||
|
|||||||
@ -27,6 +27,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
@ -38,7 +39,17 @@ if TYPE_CHECKING:
|
|||||||
from bastd.actor.scoreboard import Scoreboard
|
from bastd.actor.scoreboard import Scoreboard
|
||||||
|
|
||||||
|
|
||||||
class OnslaughtGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
@dataclass(eq=False)
|
||||||
|
class Player(ba.Player['Team']):
|
||||||
|
"""Our player type for this game."""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
|
||||||
|
|
||||||
|
class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
|
||||||
"""Co-op game where players try to survive attacking waves of enemies."""
|
"""Co-op game where players try to survive attacking waves of enemies."""
|
||||||
|
|
||||||
tips: List[Union[str, Dict[str, Any]]] = [
|
tips: List[Union[str, Dict[str, Any]]] = [
|
||||||
@ -622,7 +633,7 @@ class OnslaughtGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
|||||||
|
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
def spawn_player(self, player: ba.Player) -> ba.Actor:
|
def spawn_player(self, player: Player) -> ba.Actor:
|
||||||
|
|
||||||
# We keep track of who got hurt each wave for score purposes.
|
# We keep track of who got hurt each wave for score purposes.
|
||||||
player.gamedata['has_been_hurt'] = False
|
player.gamedata['has_been_hurt'] = False
|
||||||
@ -816,7 +827,7 @@ class OnslaughtGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
|||||||
self._score += self._time_bonus
|
self._score += self._time_bonus
|
||||||
self._update_scores()
|
self._update_scores()
|
||||||
|
|
||||||
def _award_flawless_bonus(self, player: ba.Player) -> None:
|
def _award_flawless_bonus(self, player: Player) -> None:
|
||||||
ba.playsound(self._cashregistersound)
|
ba.playsound(self._cashregistersound)
|
||||||
try:
|
try:
|
||||||
if player.is_alive():
|
if player.is_alive():
|
||||||
|
|||||||
@ -66,8 +66,19 @@ class RaceRegion(ba.Actor):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Player(ba.Player['Team']):
|
||||||
|
"""Our player type for this game."""
|
||||||
|
distance_txt: Optional[ba.Node] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class RaceGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
class RaceGame(ba.TeamGameActivity[Player, Team]):
|
||||||
"""Game of racing around a track."""
|
"""Game of racing around a track."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -186,7 +197,7 @@ class RaceGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
for rpt in pts:
|
for rpt in pts:
|
||||||
self._regions.append(RaceRegion(rpt, len(self._regions)))
|
self._regions.append(RaceRegion(rpt, len(self._regions)))
|
||||||
|
|
||||||
def _flash_player(self, player: ba.Player, scale: float) -> None:
|
def _flash_player(self, player: Player, scale: float) -> None:
|
||||||
assert isinstance(player.actor, PlayerSpaz)
|
assert isinstance(player.actor, PlayerSpaz)
|
||||||
assert player.actor.node
|
assert player.actor.node
|
||||||
pos = player.actor.node.position
|
pos = player.actor.node.position
|
||||||
@ -214,7 +225,7 @@ class RaceGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
region = region_node.getdelegate()
|
region = region_node.getdelegate()
|
||||||
if not player or not region:
|
if not player or not region:
|
||||||
return
|
return
|
||||||
assert isinstance(player, ba.Player)
|
assert isinstance(player, Player)
|
||||||
assert isinstance(region, RaceRegion)
|
assert isinstance(region, RaceRegion)
|
||||||
|
|
||||||
last_region = player.gamedata['last_region']
|
last_region = player.gamedata['last_region']
|
||||||
@ -342,13 +353,13 @@ class RaceGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print('Exception printing lap:', exc)
|
print('Exception printing lap:', exc)
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
def on_team_join(self, team: Team) -> None:
|
||||||
team.gamedata['time'] = None
|
team.gamedata['time'] = None
|
||||||
team.gamedata['lap'] = 0
|
team.gamedata['lap'] = 0
|
||||||
team.gamedata['finished'] = False
|
team.gamedata['finished'] = False
|
||||||
self._update_scoreboard()
|
self._update_scoreboard()
|
||||||
|
|
||||||
def on_player_join(self, player: ba.Player) -> None:
|
def on_player_join(self, player: Player) -> None:
|
||||||
player.gamedata['last_region'] = 0
|
player.gamedata['last_region'] = 0
|
||||||
player.gamedata['lap'] = 0
|
player.gamedata['lap'] = 0
|
||||||
player.gamedata['distance'] = 0.0
|
player.gamedata['distance'] = 0.0
|
||||||
@ -356,7 +367,7 @@ class RaceGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
player.gamedata['rank'] = None
|
player.gamedata['rank'] = None
|
||||||
super().on_player_join(player)
|
super().on_player_join(player)
|
||||||
|
|
||||||
def on_player_leave(self, player: ba.Player) -> None:
|
def on_player_leave(self, player: Player) -> None:
|
||||||
super().on_player_leave(player)
|
super().on_player_leave(player)
|
||||||
|
|
||||||
# A player leaving disqualifies the team if 'Entire Team Must Finish'
|
# A player leaving disqualifies the team if 'Entire Team Must Finish'
|
||||||
@ -550,16 +561,16 @@ class RaceGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
player.gamedata['distance'] = amt
|
player.gamedata['distance'] = amt
|
||||||
|
|
||||||
# Sort players by distance and update their ranks.
|
# Sort players by distance and update their ranks.
|
||||||
p_list = [[player.gamedata['distance'], player]
|
p_list = [(player.gamedata['distance'], player)
|
||||||
for player in self.players]
|
for player in self.players]
|
||||||
|
|
||||||
p_list.sort(reverse=True, key=lambda x: x[0])
|
p_list.sort(reverse=True, key=lambda x: x[0])
|
||||||
for i, plr in enumerate(p_list):
|
for i, plr in enumerate(p_list):
|
||||||
try:
|
try:
|
||||||
plr[1].gamedata['rank'] = i
|
plr[1].gamedata['rank'] = i
|
||||||
if plr[1].actor is not None:
|
if plr[1].actor:
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
node = plr[1].actor.distance_txt
|
node = plr[1].distance_txt
|
||||||
if node:
|
if node:
|
||||||
node.text = str(i + 1) if plr[1].is_alive() else ''
|
node.text = str(i + 1) if plr[1].is_alive() else ''
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -620,10 +631,11 @@ class RaceGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
self._flash_mine(m_index)
|
self._flash_mine(m_index)
|
||||||
ba.timer(0.95, ba.Call(self._make_mine, m_index))
|
ba.timer(0.95, ba.Call(self._make_mine, m_index))
|
||||||
|
|
||||||
def spawn_player(self, player: ba.Player) -> ba.Actor:
|
def spawn_player(self, player: Player) -> ba.Actor:
|
||||||
if player.team.gamedata['finished']:
|
if player.team.gamedata['finished']:
|
||||||
# FIXME: This is not type-safe
|
# FIXME: This is not type-safe!
|
||||||
# (this call is expected to return an Actor).
|
# This call is expected to always return an Actor!
|
||||||
|
# Perhaps we need something like can_spawn_player()...
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
return None # type: ignore
|
return None # type: ignore
|
||||||
pos = self._regions[player.gamedata['last_region']].pos
|
pos = self._regions[player.gamedata['last_region']].pos
|
||||||
@ -661,9 +673,7 @@ class RaceGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
'scale': 0.02,
|
'scale': 0.02,
|
||||||
'h_align': 'center'
|
'h_align': 'center'
|
||||||
})
|
})
|
||||||
# FIXME store this in a type-safe way
|
player.distance_txt = distance_txt
|
||||||
# noinspection PyTypeHints
|
|
||||||
spaz.distance_txt = distance_txt # type: ignore
|
|
||||||
mathnode.connectattr('output', distance_txt, 'position')
|
mathnode.connectattr('output', distance_txt, 'position')
|
||||||
return spaz
|
return spaz
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
@ -37,8 +38,20 @@ if TYPE_CHECKING:
|
|||||||
from bastd.actor.bomb import Bomb, Blast
|
from bastd.actor.bomb import Bomb, Blast
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Player(ba.Player['Team']):
|
||||||
|
"""Our player type for this game."""
|
||||||
|
streak: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
score: int = 0
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class TargetPracticeGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
class TargetPracticeGame(ba.TeamGameActivity[Player, Team]):
|
||||||
"""Game where players try to hit targets with bombs."""
|
"""Game where players try to hit targets with bombs."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -63,14 +76,18 @@ class TargetPracticeGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
def get_settings(
|
def get_settings(
|
||||||
cls,
|
cls,
|
||||||
sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
|
sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
|
||||||
return [('Target Count', {
|
return [
|
||||||
'min_value': 1,
|
('Target Count', {
|
||||||
'default': 3
|
'min_value': 1,
|
||||||
}), ('Enable Impact Bombs', {
|
'default': 3
|
||||||
'default': True
|
}),
|
||||||
}), ('Enable Triple Bombs', {
|
('Enable Impact Bombs', {
|
||||||
'default': True
|
'default': True
|
||||||
})]
|
}),
|
||||||
|
('Enable Triple Bombs', {
|
||||||
|
'default': True
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, settings: Dict[str, Any]):
|
def __init__(self, settings: Dict[str, Any]):
|
||||||
from bastd.actor.scoreboard import Scoreboard
|
from bastd.actor.scoreboard import Scoreboard
|
||||||
@ -79,13 +96,14 @@ class TargetPracticeGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
self._targets: List[Target] = []
|
self._targets: List[Target] = []
|
||||||
self._update_timer: Optional[ba.Timer] = None
|
self._update_timer: Optional[ba.Timer] = None
|
||||||
self._countdown: Optional[OnScreenCountdown] = None
|
self._countdown: Optional[OnScreenCountdown] = None
|
||||||
|
self._target_count = int(settings['Target Count'])
|
||||||
|
self._enable_impact_bombs = bool(settings['Enable Impact Bombs'])
|
||||||
|
self._enable_triple_bombs = bool(settings['Enable Triple Bombs'])
|
||||||
|
|
||||||
def on_transition_in(self) -> None:
|
# Base class overrides
|
||||||
self.default_music = ba.MusicType.FORWARD_MARCH
|
self.default_music = ba.MusicType.FORWARD_MARCH
|
||||||
super().on_transition_in()
|
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
def on_team_join(self, team: Team) -> None:
|
||||||
team.gamedata['score'] = 0
|
|
||||||
if self.has_begun():
|
if self.has_begun():
|
||||||
self.update_scoreboard()
|
self.update_scoreboard()
|
||||||
|
|
||||||
@ -95,28 +113,27 @@ class TargetPracticeGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
self.update_scoreboard()
|
self.update_scoreboard()
|
||||||
|
|
||||||
# Number of targets is based on player count.
|
# Number of targets is based on player count.
|
||||||
num_targets = self.settings_raw['Target Count']
|
for i in range(self._target_count):
|
||||||
for i in range(num_targets):
|
|
||||||
ba.timer(5.0 + i * 1.0, self._spawn_target)
|
ba.timer(5.0 + i * 1.0, self._spawn_target)
|
||||||
|
|
||||||
self._update_timer = ba.Timer(1.0, self._update, repeat=True)
|
self._update_timer = ba.Timer(1.0, self._update, repeat=True)
|
||||||
self._countdown = OnScreenCountdown(60, endcall=self.end_game)
|
self._countdown = OnScreenCountdown(60, endcall=self.end_game)
|
||||||
ba.timer(4.0, self._countdown.start)
|
ba.timer(4.0, self._countdown.start)
|
||||||
|
|
||||||
def spawn_player(self, player: ba.Player) -> ba.Actor:
|
def spawn_player(self, player: Player) -> ba.Actor:
|
||||||
spawn_center = (0, 3, -5)
|
spawn_center = (0, 3, -5)
|
||||||
pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1],
|
pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1],
|
||||||
spawn_center[2] + random.uniform(-1.5, 1.5))
|
spawn_center[2] + random.uniform(-1.5, 1.5))
|
||||||
|
|
||||||
# Reset their streak.
|
# Reset their streak.
|
||||||
player.gamedata['streak'] = 0
|
player.streak = 0
|
||||||
spaz = self.spawn_player_spaz(player, position=pos)
|
spaz = self.spawn_player_spaz(player, position=pos)
|
||||||
|
|
||||||
# Give players permanent triple impact bombs and wire them up
|
# Give players permanent triple impact bombs and wire them up
|
||||||
# to tell us when they drop a bomb.
|
# to tell us when they drop a bomb.
|
||||||
if self.settings_raw['Enable Impact Bombs']:
|
if self._enable_impact_bombs:
|
||||||
spaz.bomb_type = 'impact'
|
spaz.bomb_type = 'impact'
|
||||||
if self.settings_raw['Enable Triple Bombs']:
|
if self._enable_triple_bombs:
|
||||||
spaz.set_bomb_count(3)
|
spaz.set_bomb_count(3)
|
||||||
spaz.add_dropped_bomb_callback(self._on_spaz_dropped_bomb)
|
spaz.add_dropped_bomb_callback(self._on_spaz_dropped_bomb)
|
||||||
return spaz
|
return spaz
|
||||||
@ -166,7 +183,7 @@ class TargetPracticeGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
# Feed the explosion point to all our targets and get points in return.
|
# Feed the explosion point to all our targets and get points in return.
|
||||||
# Note: we operate on a copy of self._targets since the list may change
|
# Note: we operate on a copy of self._targets since the list may change
|
||||||
# under us if we hit stuff (don't wanna get points for new targets).
|
# under us if we hit stuff (don't wanna get points for new targets).
|
||||||
player = bomb.get_source_player()
|
player = ba.playercast_o(Player, bomb.get_source_player())
|
||||||
if not player:
|
if not player:
|
||||||
return # could happen if they leave after throwing a bomb..
|
return # could happen if they leave after throwing a bomb..
|
||||||
|
|
||||||
@ -174,9 +191,9 @@ class TargetPracticeGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
target.do_hit_at_position(pos, player)
|
target.do_hit_at_position(pos, player)
|
||||||
for target in list(self._targets))
|
for target in list(self._targets))
|
||||||
if bullseye:
|
if bullseye:
|
||||||
player.gamedata['streak'] += 1
|
player.streak += 1
|
||||||
else:
|
else:
|
||||||
player.gamedata['streak'] = 0
|
player.streak = 0
|
||||||
|
|
||||||
def _update(self) -> None:
|
def _update(self) -> None:
|
||||||
"""Misc. periodic updating."""
|
"""Misc. periodic updating."""
|
||||||
@ -200,12 +217,12 @@ class TargetPracticeGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
|||||||
def update_scoreboard(self) -> None:
|
def update_scoreboard(self) -> None:
|
||||||
"""Update the game scoreboard with current team values."""
|
"""Update the game scoreboard with current team values."""
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
self._scoreboard.set_team_value(team, team.gamedata['score'])
|
self._scoreboard.set_team_value(team, team.score)
|
||||||
|
|
||||||
def end_game(self) -> None:
|
def end_game(self) -> None:
|
||||||
results = ba.TeamGameResults()
|
results = ba.TeamGameResults()
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
results.set_team_score(team, team.gamedata['score'])
|
results.set_team_score(team, team.score)
|
||||||
self.end(results)
|
self.end(results)
|
||||||
|
|
||||||
|
|
||||||
@ -278,8 +295,7 @@ class Target(ba.Actor):
|
|||||||
"""Given a point, returns distance squared from it."""
|
"""Given a point, returns distance squared from it."""
|
||||||
return (ba.Vec3(pos) - self._position).length()
|
return (ba.Vec3(pos) - self._position).length()
|
||||||
|
|
||||||
def do_hit_at_position(self, pos: Sequence[float],
|
def do_hit_at_position(self, pos: Sequence[float], player: Player) -> bool:
|
||||||
player: ba.Player) -> bool:
|
|
||||||
"""Handle a bomb hit at the given position."""
|
"""Handle a bomb hit at the given position."""
|
||||||
# pylint: disable=too-many-statements
|
# pylint: disable=too-many-statements
|
||||||
from bastd.actor import popuptext
|
from bastd.actor import popuptext
|
||||||
@ -316,7 +332,7 @@ class Target(ba.Actor):
|
|||||||
ba.animate_array(self._nodes[0], 'color', 3, keys, loop=True)
|
ba.animate_array(self._nodes[0], 'color', 3, keys, loop=True)
|
||||||
popupscale = 1.8
|
popupscale = 1.8
|
||||||
popupcolor = (1, 1, 0, 1)
|
popupcolor = (1, 1, 0, 1)
|
||||||
streak = player.gamedata['streak']
|
streak = player.streak
|
||||||
points = 10 + min(20, streak * 2)
|
points = 10 + min(20, streak * 2)
|
||||||
ba.playsound(ba.getsound('bellHigh'))
|
ba.playsound(ba.getsound('bellHigh'))
|
||||||
if streak > 0:
|
if streak > 0:
|
||||||
@ -357,7 +373,7 @@ class Target(ba.Actor):
|
|||||||
scale=popupscale).autoretain()
|
scale=popupscale).autoretain()
|
||||||
|
|
||||||
# Give this player's team points and update the score-board.
|
# Give this player's team points and update the score-board.
|
||||||
player.team.gamedata['score'] += points
|
player.team.score += points
|
||||||
assert isinstance(activity, TargetPracticeGame)
|
assert isinstance(activity, TargetPracticeGame)
|
||||||
activity.update_scoreboard()
|
activity.update_scoreboard()
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
@ -35,7 +36,17 @@ if TYPE_CHECKING:
|
|||||||
from bastd.actor.scoreboard import Scoreboard
|
from bastd.actor.scoreboard import Scoreboard
|
||||||
|
|
||||||
|
|
||||||
class TheLastStandGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
@dataclass(eq=False)
|
||||||
|
class Player(ba.Player['Team']):
|
||||||
|
"""Our player type for this game."""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
|
||||||
|
|
||||||
|
class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
|
||||||
"""Slow motion how-long-can-you-last game."""
|
"""Slow motion how-long-can-you-last game."""
|
||||||
|
|
||||||
tips = [
|
tips = [
|
||||||
@ -118,7 +129,7 @@ class TheLastStandGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
|||||||
self._tntspawner = TNTSpawner(position=self._tntspawnpos,
|
self._tntspawner = TNTSpawner(position=self._tntspawnpos,
|
||||||
respawn_time=10.0)
|
respawn_time=10.0)
|
||||||
|
|
||||||
def spawn_player(self, player: ba.Player) -> ba.Actor:
|
def spawn_player(self, player: Player) -> ba.Actor:
|
||||||
pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5),
|
pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5),
|
||||||
self._spawn_center[1],
|
self._spawn_center[1],
|
||||||
self._spawn_center[2] + random.uniform(-1.5, 1.5))
|
self._spawn_center[2] + random.uniform(-1.5, 1.5))
|
||||||
|
|||||||
@ -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-05-18 for Ballistica version 1.5.0 build 20021</em></h4>
|
<h4><em>last updated on 2020-05-19 for Ballistica version 1.5.0 build 20021</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>
|
||||||
@ -4741,6 +4741,15 @@ of the session.</p>
|
|||||||
|
|
||||||
<p> Throws a <a href="#class_ba_SessionTeamNotFoundError">ba.SessionTeamNotFoundError</a> if there is none.</p>
|
<p> Throws a <a href="#class_ba_SessionTeamNotFoundError">ba.SessionTeamNotFoundError</a> if there is none.</p>
|
||||||
|
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<h3>Methods:</h3>
|
||||||
|
<dl>
|
||||||
|
<dt><h4><a name="method_ba_Team__manual_init">manual_init()</a></dt></h4><dd>
|
||||||
|
<p><span>manual_init(self, team_id: int, name: Union[<a href="#class_ba_Lstr">ba.Lstr</a>, str], color: Tuple[float, ...]) -> None</span></p>
|
||||||
|
|
||||||
|
<p>Manually init a team for uses such as bots.</p>
|
||||||
|
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<hr>
|
<hr>
|
||||||
@ -4949,7 +4958,7 @@ Results for a completed <a href="#class_ba_TeamGameActivity">ba.TeamGameActivity
|
|||||||
|
|
||||||
</dd>
|
</dd>
|
||||||
<dt><h4><a name="method_ba_TeamGameResults__set_team_score">set_team_score()</a></dt></h4><dd>
|
<dt><h4><a name="method_ba_TeamGameResults__set_team_score">set_team_score()</a></dt></h4><dd>
|
||||||
<p><span>set_team_score(self, team: Union[<a href="#class_ba_SessionTeam">ba.SessionTeam</a>, <a href="#class_ba_Team">ba.Team</a>], score: int) -> None</span></p>
|
<p><span>set_team_score(self, team: Union[<a href="#class_ba_SessionTeam">ba.SessionTeam</a>, <a href="#class_ba_Team">ba.Team</a>], score: Optional[int]) -> None</span></p>
|
||||||
|
|
||||||
<p>Set the score for a given <a href="#class_ba_Team">ba.Team</a>.</p>
|
<p>Set the score for a given <a href="#class_ba_Team">ba.Team</a>.</p>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user