From 849bd117953db5072cb87dff7a82383e497d6581 Mon Sep 17 00:00:00 2001 From: Eric Froemling Date: Mon, 25 May 2020 23:01:24 -0700 Subject: [PATCH] Improved type checking on time functionality --- .efrocachemap | 24 ++++++------ .idea/dictionaries/ericf.xml | 1 + assets/src/ba_data/python/_ba.py | 32 ++++++++++++++-- assets/src/ba_data/python/ba/_store.py | 2 +- .../python/bastd/actor/onscreentimer.py | 37 ++++++++++++++----- .../ba_data/python/bastd/game/ninjafight.py | 18 ++++----- docs/ba_module.md | 4 +- 7 files changed, 80 insertions(+), 38 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index 26963766..9e595257 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/a1/97/fd0e5553917019236912a7010d38", - "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/43/32/c48a4da7fcc8c17132077da852d7", - "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/75/13/2bc3ae9026386d1b515ede107e17", - "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/1f/9a/b112f788f528ec9e3627622698ac", - "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a1/87/fdb3e925f3be1cc353db94d61c5a", - "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fa/ba/125457c69bc2940850ab84c4cc6a", - "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a1/d4/5efcef1f85b2bfea7bbb81800580", - "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fa/66/75a3a0f27d07131473e804a5b937", - "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/51/6c/911ba80ba913e6dac1cafa5c8a74", - "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/01/cc/cf896173a8d5731ada2aace59146", - "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a2/12/aa1f0ca5506f0b57f93ca9adf7be", - "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/e1/5f/f372d0ba9591b99284b9b50d7cdf" + "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ba/35/3b6bc5c5609b1dd37bd65c39df45", + "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a6/bc/c2c7231dc6bf085eda15d6198554", + "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/6b/fd/020ed9bb0e8c8a18b2d793fee8bd", + "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/43/0b/78c8bacb215abaf50dcb3284eef7", + "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f8/e1/e0dc64b5c00661cce19530c0e836", + "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e6/74/73a514993d626a6bc75717d185ef", + "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f0/7d/dbd2624759a1fdce2a20d53cab1a", + "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/1c/01/4833ec215cc6c53f7e4ebf850608", + "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/b1/57/b72500d2a568df5afa36556f89dd", + "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/48/ea/c83f97f44703b16eeec794d29da6", + "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a3/16/284b9953c7ef4a841ff907079cbd", + "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/12/43/d0513cf8f8dac0712cbf42d4b94b" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 1cf96c30..16a51b7c 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -1114,6 +1114,7 @@ mhsh microprotocols mikirog + millisecs mimetypes mimportedby mindepth diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py index f1f90179..3e0e0d1f 100644 --- a/assets/src/ba_data/python/_ba.py +++ b/assets/src/ba_data/python/_ba.py @@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand. """ # (hash we can use to see if this file is out of date) -# SOURCES_HASH=318889180473540875493180206824864665481 +# SOURCES_HASH=223083205204988067566025188831386474803 # I'm sorry Pylint. I know this file saddens you. Be strong. # pylint: disable=useless-suppression @@ -3729,11 +3729,35 @@ def textwidget(edit: Widget = None, return Widget() +# Overloads to return a type based on requested format. + + +@overload +def time( + timetype: ba.TimeType = TimeType.SIM, + timeformat: Literal[TimeFormat.SECONDS] = TimeFormat.SECONDS) -> float: + ... + + +# This "*" keyword-only hack lets us accept 1 arg (timeformat=MILLISECS) forms. +@overload def time(timetype: ba.TimeType = TimeType.SIM, - timeformat: ba.TimeFormat = TimeFormat.SECONDS) -> Union[float, int]: + *, + timeformat: Literal[TimeFormat.MILLISECONDS]) -> int: + ... + + +@overload +def time(timetype: ba.TimeType, + timeformat: Literal[TimeFormat.MILLISECONDS]) -> int: + ... + + +def time(timetype: ba.TimeType = TimeType.SIM, + timeformat: ba.TimeFormat = TimeFormat.SECONDS) -> Any: """time(timetype: ba.TimeType = TimeType.SIM, timeformat: ba.TimeFormat = TimeFormat.SECONDS) - -> Union[float, int] + -> Return the current time. @@ -3766,7 +3790,7 @@ def time(timetype: ba.TimeType = TimeType.SIM, Note: If you need pure unfiltered clock time, just use the standard Python functions such as time.time(). """ - return 0.0 + return None def time_format_check(time_format: ba.TimeFormat, length: Union[float, diff --git a/assets/src/ba_data/python/ba/_store.py b/assets/src/ba_data/python/ba/_store.py index 09e27e01..c68aa6f8 100644 --- a/assets/src/ba_data/python/ba/_store.py +++ b/assets/src/ba_data/python/ba/_store.py @@ -494,7 +494,7 @@ def get_available_sale_time(tab: str) -> Optional[int]: assert app.pro_sale_start_val is not None val: Optional[int] = max( 0, app.pro_sale_start_val - - (int(_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS)) - + (_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) - app.pro_sale_start_time)) # Keep the value in the config up to date. I suppose we should diff --git a/assets/src/ba_data/python/bastd/actor/onscreentimer.py b/assets/src/ba_data/python/bastd/actor/onscreentimer.py index 422a630c..8b5699b6 100644 --- a/assets/src/ba_data/python/bastd/actor/onscreentimer.py +++ b/assets/src/ba_data/python/bastd/actor/onscreentimer.py @@ -21,12 +21,13 @@ """Defines Actor(s).""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, overload import ba if TYPE_CHECKING: from typing import Optional, Union, Any + from typing_extensions import Literal class OnScreenTimer(ba.Actor): @@ -39,7 +40,7 @@ class OnScreenTimer(ba.Actor): def __init__(self) -> None: super().__init__() - self._starttime: Optional[int] = None + self._starttime_ms: Optional[int] = None self.node = ba.newnode('text', attrs={ 'v_attach': 'top', @@ -63,13 +64,13 @@ class OnScreenTimer(ba.Actor): """Start the timer.""" tval = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) assert isinstance(tval, int) - self._starttime = tval - self.inputnode.time1 = self._starttime + self._starttime_ms = tval + self.inputnode.time1 = self._starttime_ms ba.sharedobj('globals').connectattr('time', self.inputnode, 'time2') def has_started(self) -> bool: """Return whether this timer has started yet.""" - return self._starttime is not None + return self._starttime_ms is not None def stop(self, endtime: Union[int, float] = None, @@ -85,7 +86,7 @@ class OnScreenTimer(ba.Actor): endtime = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) timeformat = ba.TimeFormat.MILLISECONDS - if self._starttime is None: + if self._starttime_ms is None: print('Warning: OnScreenTimer.stop() called without start() first') else: endtime_ms: int @@ -97,7 +98,20 @@ class OnScreenTimer(ba.Actor): else: raise ValueError(f'invalid timeformat: {timeformat}') - self.inputnode.timemax = endtime_ms - self._starttime + self.inputnode.timemax = endtime_ms - self._starttime_ms + + # Overloads so type checker knows our exact return type based in args. + @overload + def getstarttime( + self, + timeformat: Literal[ba.TimeFormat.SECONDS] = ba.TimeFormat.SECONDS + ) -> float: + ... + + @overload + def getstarttime(self, + timeformat: Literal[ba.TimeFormat.MILLISECONDS]) -> int: + ... def getstarttime( self, @@ -109,11 +123,11 @@ class OnScreenTimer(ba.Actor): milliseconds if it is MILLISECONDS. """ val_ms: Any - if self._starttime is None: + if self._starttime_ms is None: print('WARNING: getstarttime() called on un-started timer') val_ms = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) else: - val_ms = self._starttime + val_ms = self._starttime_ms assert isinstance(val_ms, int) if timeformat is ba.TimeFormat.SECONDS: return 0.001 * val_ms @@ -121,6 +135,11 @@ class OnScreenTimer(ba.Actor): return val_ms raise ValueError(f'invalid timeformat: {timeformat}') + @property + def starttime(self) -> float: + """Shortcut for start time in seconds.""" + return self.getstarttime() + def handlemessage(self, msg: Any) -> Any: # if we're asked to die, just kill our node/timer if isinstance(msg, ba.DieMessage): diff --git a/assets/src/ba_data/python/bastd/game/ninjafight.py b/assets/src/ba_data/python/bastd/game/ninjafight.py index 439aab2a..42e73e51 100644 --- a/assets/src/ba_data/python/bastd/game/ninjafight.py +++ b/assets/src/ba_data/python/bastd/game/ninjafight.py @@ -77,6 +77,7 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): self._won = False self._timer: Optional[OnScreenTimer] = None self._bots = SpazBotSet() + self._preset = str(settings['preset']) # Called when our game is transitioning in but not ready to begin; # we can go ahead and start creating stuff, playing music, etc. @@ -87,7 +88,7 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): # Called when our game actually begins. def on_begin(self) -> None: super().on_begin() - is_pro = self.settings_raw.get('preset') == 'pro' + is_pro = self._preset == 'pro' # In pro mode there's no powerups. if not is_pro: @@ -156,9 +157,11 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): # marked dead yet) ..so lets push a call into the event loop to # check once this guy has finished dying. ba.pushcall(self._check_if_won) + + # Let the base class handle anything we don't. else: - # Let the base class handle anything we don't. - super().handlemessage(msg) + return super().handlemessage(msg) + return None # When this is called, we should fill out results and end the game # *regardless* of whether is has been won. (this may be called due @@ -171,17 +174,12 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): results = ba.TeamGameResults() - # If we won, set our score to the elapsed time + # If we won, set our score to the elapsed time in milliseconds. # (there should just be 1 team here since this is co-op). # ..if we didn't win, leave scores as default (None) which means # we lost. if self._won: - curtime = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) - assert isinstance(curtime, int) - starttime = self._timer.getstarttime( - timeformat=ba.TimeFormat.MILLISECONDS) - assert isinstance(starttime, int) - elapsed_time_ms = curtime - starttime + elapsed_time_ms = int((ba.time() - self._timer.starttime) * 1000.0) ba.cameraflash() ba.playsound(self._winsound) for team in self.teams: diff --git a/docs/ba_module.md b/docs/ba_module.md index c7da0733..d6b1b011 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2020-05-25 for Ballistica version 1.5.0 build 20028

+

last updated on 2020-05-25 for Ballistica version 1.5.0 build 20029

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 let me know. Happy modding!


@@ -6382,7 +6382,7 @@ are applied to the Widget.

ba.time()

time(timetype: ba.TimeType = TimeType.SIM, timeformat: ba.TimeFormat = TimeFormat.SECONDS) - -> Union[float, int]

+ -> <varies>

Return the current time.