From 7aed70dfb534073fba53702e92e61c2765687a7a Mon Sep 17 00:00:00 2001
From: Eric Froemling
Date: Thu, 7 May 2020 15:24:07 -0700
Subject: [PATCH] Modernized meteor shower code
---
.efrocachemap | 24 +++---
.idea/dictionaries/ericf.xml | 1 +
assets/.asset_manifest_public.json | 2 +
assets/Makefile | 7 ++
assets/src/ba_data/python/ba/__init__.py | 1 +
assets/src/ba_data/python/ba/_player.py | 54 ++++++++++++
.../ba_data/python/bastd/game/meteorshower.py | 83 +++++++++----------
docs/ba_module.md | 26 ++++++
8 files changed, 142 insertions(+), 56 deletions(-)
create mode 100644 assets/src/ba_data/python/ba/_player.py
diff --git a/.efrocachemap b/.efrocachemap
index 9281a2bb..ee0cbcbd 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -4132,16 +4132,16 @@
"assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c",
"assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb",
"assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe",
- "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/32/b0/12e2602010f316e3c26da125bf25",
- "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/50/12/5a87ef4e3b502f1e62f5f2c65985",
- "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/04/0d/5e5e5783775b70d06de5113904d7",
- "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ab/46/fd8487f22aaaf68d310e66c8b5de",
- "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c8/39/04b55a4b918602fbf06dccc4f3c3",
- "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/87/ab/5b265ac08141fae86f6102fb362c",
- "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/13/a7/c2f6454984d5639c39abd5bd871f",
- "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/cd/3a/f67ce2222af11d3ee614ba55e1f0",
- "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/4c/01/f33e921238d1bded473de5964ce4",
- "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/ec/1b/16fa91c7ccba5b20ea9a4ca3e0a4",
- "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/5c/6a/8e947d1317c67dc84856c5649fee",
- "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/7d/aa/901e45f5991e3907f0d9033ccdcf"
+ "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/22/a3/c6b38e5c76464077f400415eef8f",
+ "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/35/42/3802bdafafffea388132ccb27197",
+ "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/90/fb/bbb656b6d0b56ff92d30f13fcb10",
+ "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3b/75/2eee67afef4e0f446cba5a466521",
+ "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b6/31/fe8c9e100c0f99daaf1d11d702c0",
+ "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/85/27/7ddbe58f02af52884fff485c98dd",
+ "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d4/11/0f9806ff32156d9af2fb5c88e49c",
+ "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9a/55/2cd36310f00e3a187a70b3f122ee",
+ "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/30/8e/b4d365f9aa49cf247d6a73dfd5ee",
+ "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/b0/1a/20b48edf6830ad53a1c0049c129b",
+ "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/00/52/42d85a6502daf27271ce48634b5b",
+ "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/b2/1c/1afeac6051ad9973e06f945f98ce"
}
\ No newline at end of file
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index c56af62a..a1274851 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -1319,6 +1319,7 @@
pipname
pkey
pkgutil
+ playerdata
playerlostspaz
playernode
playerpt
diff --git a/assets/.asset_manifest_public.json b/assets/.asset_manifest_public.json
index f90691dc..767104c3 100644
--- a/assets/.asset_manifest_public.json
+++ b/assets/.asset_manifest_public.json
@@ -38,6 +38,7 @@
"ba_data/python/ba/__pycache__/_music.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_netutils.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_nodeactor.cpython-37.opt-1.pyc",
+ "ba_data/python/ba/__pycache__/_player.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_playlist.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_powerup.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_profile.cpython-37.opt-1.pyc",
@@ -90,6 +91,7 @@
"ba_data/python/ba/_music.py",
"ba_data/python/ba/_netutils.py",
"ba_data/python/ba/_nodeactor.py",
+ "ba_data/python/ba/_player.py",
"ba_data/python/ba/_playlist.py",
"ba_data/python/ba/_powerup.py",
"ba_data/python/ba/_profile.py",
diff --git a/assets/Makefile b/assets/Makefile
index 0aeb2198..1d135437 100644
--- a/assets/Makefile
+++ b/assets/Makefile
@@ -167,6 +167,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
build/ba_data/python/ba/_tips.py \
build/ba_data/python/ba/_store.py \
build/ba_data/python/ba/_activitytypes.py \
+ build/ba_data/python/ba/_player.py \
build/ba_data/python/ba/__init__.py \
build/ba_data/python/ba/_assetmanager.py \
build/ba_data/python/ba/_session.py \
@@ -392,6 +393,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
build/ba_data/python/ba/__pycache__/_tips.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_store.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_activitytypes.cpython-37.opt-1.pyc \
+ build/ba_data/python/ba/__pycache__/_player.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/__init__.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_assetmanager.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_session.cpython-37.opt-1.pyc \
@@ -729,6 +731,11 @@ build/ba_data/python/ba/__pycache__/_activitytypes.cpython-37.opt-1.pyc: \
@echo Compiling script: $^
@rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@
+build/ba_data/python/ba/__pycache__/_player.cpython-37.opt-1.pyc: \
+ build/ba_data/python/ba/_player.py
+ @echo Compiling script: $^
+ @rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@
+
build/ba_data/python/ba/__pycache__/__init__.cpython-37.opt-1.pyc: \
build/ba_data/python/ba/__init__.py
@echo Compiling script: $^
diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py
index 887e2415..201b4cb3 100644
--- a/assets/src/ba_data/python/ba/__init__.py
+++ b/assets/src/ba_data/python/ba/__init__.py
@@ -40,6 +40,7 @@ from _ba import (CollideModel, Context, ContextCall, Data, InputDevice,
charstr, textwidget, time, timer, open_url, widget)
from ba._activity import Activity
from ba._actor import Actor
+from ba._player import BasePlayerData
from ba._nodeactor import NodeActor
from ba._app import App
from ba._coopgame import CoopGameActivity
diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py
new file mode 100644
index 00000000..9d3100d1
--- /dev/null
+++ b/assets/src/ba_data/python/ba/_player.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2011-2020 Eric Froemling
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# -----------------------------------------------------------------------------
+"""Player related functionality."""
+from __future__ import annotations
+from typing import TYPE_CHECKING, TypeVar
+
+if TYPE_CHECKING:
+ from typing import Type
+ import ba
+
+T = TypeVar('T')
+
+
+class BasePlayerData:
+ """Base class for custom player data.
+
+ Category: Gameplay Classes
+
+ A convenience class that can be used as a base class for custom
+ per-game player data. It simply provides the ability to easily fetch
+ an instance of itself for a given ba.Player.
+ """
+
+ @classmethod
+ def get(cls: Type[T], player: ba.Player) -> T:
+ """Return the custom player data associated with a player.
+
+ If one does not exist, it will be created.
+ """
+
+ # Store/return an instance of ourself in the player's per-game dict.
+ data = player.gamedata.get('playerdata')
+ if data is None:
+ player.gamedata['playerdata'] = data = cls()
+ assert isinstance(data, cls)
+ return data
diff --git a/assets/src/ba_data/python/bastd/game/meteorshower.py b/assets/src/ba_data/python/bastd/game/meteorshower.py
index 4b5ac4d2..27c17d60 100644
--- a/assets/src/ba_data/python/bastd/game/meteorshower.py
+++ b/assets/src/ba_data/python/bastd/game/meteorshower.py
@@ -30,23 +30,18 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING
import ba
-from bastd.actor import bomb
-from bastd.actor import playerspaz
+from bastd.actor.bomb import Bomb
+from bastd.actor.playerspaz import PlayerSpazDeathMessage
if TYPE_CHECKING:
- from typing import Any, Tuple, Sequence, Optional, List, Dict, Type
+ from typing import Any, Tuple, Sequence, Optional, List, Dict, Type, Type
from bastd.actor.onscreentimer import OnScreenTimer
@dataclass
-class GameSettings:
- """Configurable settings for our game."""
- epic_mode: bool
-
-
-@dataclass
-class PlayerData:
+class PlayerData(ba.BasePlayerData):
"""Data we store per player."""
+ death_time: Optional[float] = None
# ba_meta export game
@@ -90,23 +85,19 @@ class MeteorShowerGame(ba.TeamGameActivity):
def __init__(self, settings: Dict[str, Any]):
super().__init__(settings)
- if self.settings_raw['Epic Mode']:
- self.slow_motion = True
-
- # Print messages when players die (since its meaningful in this game).
- self.announce_player_deaths = True
-
+ self._epic_mode = settings.get('Epic Mode', False)
self._last_player_death_time: Optional[float] = None
self._meteor_time = 2.0
self._timer: Optional[OnScreenTimer] = None
- # Called when our game is transitioning in but not ready to start;
- # ..we can go ahead and set our music and whatnot.
- def on_transition_in(self) -> None:
+ # Some base class overrides:
self.default_music = (ba.MusicType.EPIC
- if self.settings_raw['Epic Mode'] else
- ba.MusicType.SURVIVAL)
- super().on_transition_in()
+ if self._epic_mode else ba.MusicType.SURVIVAL)
+ if self._epic_mode:
+ self.slow_motion = True
+
+ # Print messages when players die (since its meaningful in this game).
+ self.announce_player_deaths = True
# Called when our game actually starts.
def on_begin(self) -> None:
@@ -118,13 +109,13 @@ class MeteorShowerGame(ba.TeamGameActivity):
# between waves ..lets have things increase faster if we have fewer
# players.
delay = 5.0 if len(self.players) > 2 else 2.5
- if self.settings_raw['Epic Mode']:
+ if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._decrement_meteor_time, repeat=True)
# Kick off the first wave in a few seconds.
delay = 3.0
- if self.settings_raw['Epic Mode']:
+ if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._set_meteor_timer)
@@ -145,7 +136,7 @@ class MeteorShowerGame(ba.TeamGameActivity):
# For score purposes, mark them as having died right as the
# game started.
assert self._timer is not None
- player.gamedata['death_time'] = self._timer.getstarttime()
+ PlayerData.get(player).death_time = self._timer.getstarttime()
return
self.spawn_player(player)
@@ -172,15 +163,15 @@ class MeteorShowerGame(ba.TeamGameActivity):
# Various high-level game events come through this method.
def handlemessage(self, msg: Any) -> Any:
- if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
+ if isinstance(msg, PlayerSpazDeathMessage):
# Augment standard behavior.
super().handlemessage(msg)
- death_time = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
+ curtime = ba.time()
# Record the player's moment of death.
- msg.spaz.player.gamedata['death_time'] = death_time
+ PlayerData.get(msg.spaz.player).death_time = curtime
# In co-op mode, end the game the instant everyone dies
# (more accurate looking).
@@ -192,7 +183,7 @@ class MeteorShowerGame(ba.TeamGameActivity):
ba.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
- self._last_player_death_time = death_time
+ self._last_player_death_time = curtime
else:
ba.timer(1.0, self._check_end_game)
@@ -247,36 +238,36 @@ class MeteorShowerGame(ba.TeamGameActivity):
def _drop_bomb(self, position: Sequence[float],
velocity: Sequence[float]) -> None:
- bomb.Bomb(position=position, velocity=velocity).autoretain()
+ Bomb(position=position, velocity=velocity).autoretain()
def _decrement_meteor_time(self) -> None:
self._meteor_time = max(0.01, self._meteor_time * 0.9)
def end_game(self) -> None:
- cur_time = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
+ cur_time = ba.time()
assert self._timer is not None
- start_time = self._timer.getstarttime(
- timeformat=ba.TimeFormat.MILLISECONDS)
+ start_time = self._timer.getstarttime()
- # Mark 'death-time' as now for any still-living players
+ # Mark death-time as now for any still-living players
# and award players points for how long they lasted.
# (these per-player scores are only meaningful in team-games)
for team in self.teams:
for player in team.players:
+ playerdata = PlayerData.get(player)
+ survived = False
# Throw an extra fudge factor in so teams that
# didn't die come out ahead of teams that did.
- if 'death_time' not in player.gamedata:
- player.gamedata['death_time'] = cur_time + 1
+ if playerdata.death_time is None:
+ survived = True
+ playerdata.death_time = cur_time + 1
# Award a per-player score depending on how many seconds
# they lasted (per-player scores only affect teams mode;
# everywhere else just looks at the per-team score).
- score = int(player.gamedata['death_time'] -
- self._timer.getstarttime(
- timeformat=ba.TimeFormat.MILLISECONDS))
- if 'death_time' not in player.gamedata:
- score += 50 # a bit extra for survivors
+ score = int(playerdata.death_time - self._timer.getstarttime())
+ if survived:
+ score += 50 # A bit extra for survivors.
self.stats.player_scored(player, score, screenmessage=False)
# Stop updating our time text, and set the final time to match
@@ -294,10 +285,14 @@ class MeteorShowerGame(ba.TeamGameActivity):
# Set the team score to the max time survived by any player on
# that team.
- longest_life = 0
+ longest_life = 0.0
for player in team.players:
+ playerdata = PlayerData.get(player)
+ assert playerdata.death_time is not None
longest_life = max(longest_life,
- player.gamedata['death_time'] - start_time)
- results.set_team_score(team, longest_life)
+ playerdata.death_time - start_time)
+
+ # Submit the score value in milliseconds.
+ results.set_team_score(team, int(1000.0 * longest_life))
self.end(results=results)
diff --git a/docs/ba_module.md b/docs/ba_module.md
index c7bd31af..2647f24b 100644
--- a/docs/ba_module.md
+++ b/docs/ba_module.md
@@ -19,6 +19,7 @@
ba.Map
ba.NodeActor
+ ba.BasePlayerData
ba.Chooser
ba.InputDevice
ba.Level
@@ -1083,6 +1084,31 @@ when done.
Behavior is similar to ba.gettexture()
+
+
+
+<top level class>
+
+Base class for custom player data.
+
+Category: Gameplay Classes
+
+ A convenience class that can be used as a base class for custom
+ per-game player data. It simply provides the ability to easily fetch
+ an instance of itself for a given ba.Player.
+
+
+Methods:
+
+-
+
<class method>
+get(player: ba.Player) -> T
+
+Return the custom player data associated with a player.
+
+If one does not exist, it will be created.
+