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()

    + + +
    +

    ba.BasePlayerData

    +

    <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:

    +
    +

    get()

    +
    <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.

    +