Syncing latest changes between public/private.

This commit is contained in:
Eric Froemling 2019-11-11 01:03:00 -08:00
parent 29b588f5f5
commit 068806facc
38 changed files with 541 additions and 323 deletions

View File

@ -1021,6 +1021,7 @@
<w>nntplib</w>
<w>nodepos</w>
<w>nodpi</w>
<w>nofiles</w>
<w>noinspect</w>
<w>noninfringement</w>
<w>nonmultipart</w>

View File

@ -41,9 +41,9 @@ help:
# Prerequisites that should be in place before running most any other build;
# things like tool config files, etc.
PREREQS = .dir-locals.el .mypy.ini .pycheckers \
.pylintrc .style.yapf .clang-format \
.projectile .editorconfig .cache/checkenv
PREREQS = .cache/checkenv .dir-locals.el \
.mypy.ini .pycheckers .pylintrc .style.yapf .clang-format \
.projectile .editorconfig
prereqs: ${PREREQS}
@ -264,43 +264,43 @@ formatmakefile: prereqs
# These are useful, but can take significantly longer and/or be a bit flaky.
check: updatecheck
@$(MAKE) -j3 cpplintcode pylintscripts mypyscripts
@$(MAKE) -j3 cpplint pylint mypy
@echo ALL CHECKS PASSED!
check2: updatecheck
@$(MAKE) -j4 cpplintcode pylintscripts mypyscripts pycharmscripts
@$(MAKE) -j4 cpplint pylint mypy pycharmscripts
@echo ALL CHECKS PASSED!
checkfast: updatecheck
@$(MAKE) -j3 cpplintcode pylintscriptsfast mypyscripts
@$(MAKE) -j3 cpplint pylintfast mypy
@echo ALL CHECKS PASSED!
checkfast2: updatecheck
@$(MAKE) -j4 cpplintcode pylintscriptsfast mypyscripts pycharmscripts
@$(MAKE) -j4 cpplint pylintfast mypy pycharmscripts
@echo ALL CHECKS PASSED!
checkfull: updatecheck
@$(MAKE) -j3 cpplintcodefull pylintscriptsfull mypyscriptsfull
@$(MAKE) -j3 cpplintfull pylintfull mypyfull
@echo ALL CHECKS PASSED!
checkfull2: updatecheck
@$(MAKE) -j4 cpplintcodefull pylintscriptsfull mypyscriptsfull pycharmscriptsfull
@$(MAKE) -j4 cpplintfull pylintfull mypyfull pycharmscriptsfull
@echo ALL CHECKS PASSED!
cpplintcode: prereqs
@tools/snippets cpplintcode
cpplint: prereqs
@tools/snippets cpplint
cpplintcodefull: prereqs
@tools/snippets cpplintcode -full
cpplintfull: prereqs
@tools/snippets cpplint -full
pylintscripts: prereqs
@tools/snippets pylintscripts
pylint: prereqs
@tools/snippets pylint
pylintscriptsfull: prereqs
@tools/snippets pylintscripts -full
pylintfull: prereqs
@tools/snippets pylint -full
mypyscripts: prereqs
@tools/snippets mypyscripts
mypy: prereqs
@tools/snippets mypy
mypyscriptsfull: prereqs
@tools/snippets mypyscripts -full
mypyfull: prereqs
@tools/snippets mypy -full
pycharmscripts: prereqs
@tools/snippets pycharmscripts
@ -311,14 +311,14 @@ pycharmscriptsfull: prereqs
# 'Fast' script checking using dependency recursion limits.
# This can require much less re-checking but may miss problems in rare cases.
# Its not a bad idea to run a non-fast check every so often or before pushing.
pylintscriptsfast: prereqs
@tools/snippets pylintscripts -fast
pylintfast: prereqs
@tools/snippets pylint -fast
# Tell make which of these targets don't represent files.
.PHONY: format formatfull formatcode formatcodefull formatscripts \
formatscriptsfull check check2 checkfast checkfast2 checkfull checkfull2 \
cpplintcode cpplintcodefull pylintscripts pylintscriptsfull mypyscripts \
mypyscriptsfull pycharmscripts pycharmscriptsfull
cpplint cpplintfull pylint pylintfull mypy \
mypyfull pycharmscripts pycharmscriptsfull
################################################################################
@ -338,24 +338,24 @@ updatecheck: prereqs
# Run an update and check together; handy while iterating.
# (slightly more efficient than running update/check separately).
updatethencheck: update
@$(MAKE) -j3 cpplintcode pylintscripts mypyscripts
@$(MAKE) -j3 cpplint pylint mypy
@echo ALL CHECKS PASSED!
updatethencheck2: update
@$(MAKE) -j4 cpplintcode pylintscripts mypyscripts pycharmscripts
@$(MAKE) -j4 cpplint pylint mypy pycharmscripts
@echo ALL CHECKS PASSED!
updatethencheckfast: update
@$(MAKE) -j3 cpplintcode pylintscriptsfast mypyscripts
@$(MAKE) -j3 cpplint pylintfast mypy
@echo ALL CHECKS PASSED!
updatethencheckfast2: update
@$(MAKE) -j4 cpplintcode pylintscriptsfast mypyscripts pycharmscripts
@$(MAKE) -j4 cpplint pylintfast mypy pycharmscripts
@echo ALL CHECKS PASSED!
updatethencheckfull: update
@$(MAKE) -j3 cpplintcodefull pylintscriptsfull mypyscriptsfull
@$(MAKE) -j3 cpplintfull pylintfull mypyfull
@echo ALL CHECKS PASSED!
updatethencheckfull2: update
@$(MAKE) -j4 cpplintcodefull pylintscriptsfull mypyscriptsfull pycharmscriptsfull
@$(MAKE) -j4 cpplintfull pylintfull mypyfull pycharmscriptsfull
@echo ALL CHECKS PASSED!
# Run a format, an update, and then a check.

View File

@ -23,11 +23,11 @@ The Ballistica project is the foundation for the next generation of [BombSquad](
* Migrate modding documentation to this repo's wiki, allowing other modders to add their own bits of wisdom
### Frequently Asked Questions
* **Q: What's with the new name? Is BombSquad getting renamed?**
* **Q: What's with this new name? Is BombSquad getting renamed?**
* A: No, BombSquad is still BombSquad. 'Ballistica' is simply the new name for the engine/app-framework. This way it can be used for other game/app projects without causing confusion (though that is mostly theoretical at this point). As a modder, the biggest change you will notice is a lot of 'ba' prefixes in the APIs as opposed to 'bs'. You may also see the word 'BallisticaCore' show up various places, which in actual releases gets replaced by 'BombSquad'.
* **Q: Does this mean BombSquad is open source?**
* A: Yes and no. All code contained in this repo is MIT licensed and free for use anywhere. This includes game scripts, pipeline tools, etc. In the future I hope to expand this to include at least some binary sources. Anything not included here, however, even if automatically downloaded by build scripts, is still copyrighted and cannot be redistributed without explicit consent. This includes assets and game binaries. So in a nutshell: create and share mods to your heart's content, but please don't distribute your own complete copy of the game. Please email support@froemling.net if you have any questions.
* A: Yes and no. All code contained in this repo is MIT licensed and free for use anywhere. This includes game scripts, pipeline tools, etc. In the future I hope to expand this to include at least some binary sources. Anything not included here, however, even if automatically downloaded by build scripts, is still proprietary and cannot be redistributed without explicit consent. This includes assets and game binaries. So in a nutshell: create and share mods to your heart's content, but please don't distribute your own complete copy of the game. Please email support@froemling.net if you have any questions.
* **Q: Will my existing BombSquad 1.4.x mods still work?**
* A: No. All mods will need to be explicitly updated to work with the new ballistica apis in 1.5+. This may or may not be a significant amount of work depending on the mod. I would highly suggest tinkering around with some of the new features in 1.5 such as type-safe Python and dynamic assets before attempting to port any old mods, as some things are done significantly differently now. You may also want to consider simply sticking with 1.4 builds for a while longer, especially for server duties, since they will remain fully compatible with clients running 1.5. The new ballistica apis may still be in significant flux for at least a while until the dust settles, but they will be worth switching to eventually, I promise!

View File

@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand.
"""
# (hash we can use to see if this file is out of date)
# SOURCES_HASH=171913046369526584263358640346000279030
# SOURCES_HASH=120456495529909322688635129785556212748
# I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression
@ -3483,10 +3483,20 @@ def set_ui_input_device(input_device: Optional[ba.InputDevice]) -> None:
def show_ad(purpose: str,
on_completion_call: Callable[[], None] = None,
pass_actually_showed: bool = False) -> None:
"""show_ad(purpose: str, on_completion_call: Callable[[], None] = None,
pass_actually_showed: bool = False) -> None
on_completion_call: Callable[[], None] = None) -> None:
"""show_ad(purpose: str, on_completion_call: Callable[[], None] = None)
-> None
(internal)
"""
return None
def show_ad_2(purpose: str,
on_completion_call: Callable[[bool], None] = None) -> None:
"""show_ad_2(purpose: str,
on_completion_call: Callable[[bool], None] = None)
-> None
(internal)
"""

View File

@ -667,7 +667,7 @@ class Achievement:
from ba import _gameutils
from bastd.actor.text import Text
from bastd.actor.image import Image
from ba._general import Call, WeakCall
from ba._general import WeakCall
from ba._lang import Lstr
from ba._messages import DieMessage
from ba._enums import TimeType, SpecialChar
@ -692,7 +692,8 @@ class Achievement:
_ba.playsound(_ba.getsound('achievement'), host_only=True)
else:
_ba.timer(
0.5, Call(_ba.playsound, _ba.getsound('ding'), host_only=True))
0.5,
lambda: _ba.playsound(_ba.getsound('ding'), host_only=True))
in_time = 0.300
out_time = 3.5

View File

@ -247,27 +247,9 @@ class App:
import urllib.request
try:
val = urllib.request.urlopen('https://example.com').read()
print("HTTPS SUCCESS", len(val))
print("HTTPS TEST SUCCESS", len(val))
except Exception as exc:
print("GOT EXC", exc)
try:
import sqlite3
print("GOT SQLITE", sqlite3)
except Exception as exc:
print("EXC IMPORTING SQLITE", exc)
try:
import csv
print("GOT CSV", csv)
except Exception as exc:
print("EXC IMPORTING CSV", exc)
try:
import lzma
print("GOT LZMA", lzma)
except Exception as exc:
print("EXC IMPORTING LZMA", exc)
print("HTTPS TEST FAIL:", exc)
# Config.
self.config_file_healthy = False
@ -436,7 +418,7 @@ class App:
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
from ba import _apputils
from ba._general import Call
# from ba._general import Call
from ba import _appconfig
from ba import ui as bsui
from ba import _achievement
@ -493,31 +475,23 @@ class App:
self.small_ui = True
self.med_ui = False
with _ba.Context('ui'):
_ba.pushcall(
Call(_ba.screenmessage,
'FORCING SMALL UI FOR TESTING',
color=(1, 0, 1),
log=True))
_ba.pushcall(lambda: _ba.screenmessage(
'FORCING SMALL UI FOR TESTING', color=(1, 0, 1), log=True))
# noinspection PyUnreachableCode
if 0: # force-test medium UI
self.small_ui = False
self.med_ui = True
with _ba.Context('ui'):
_ba.pushcall(
Call(_ba.screenmessage,
'FORCING MEDIUM UI FOR TESTING',
color=(1, 0, 1),
log=True))
_ba.pushcall(lambda: _ba.screenmessage(
'FORCING MEDIUM UI FOR TESTING', color=(1, 0, 1
), log=True))
# noinspection PyUnreachableCode
if 0: # force-test large UI
self.small_ui = False
self.med_ui = False
with _ba.Context('ui'):
_ba.pushcall(
Call(_ba.screenmessage,
'FORCING LARGE UI FOR TESTING',
color=(1, 0, 1),
log=True))
_ba.pushcall(lambda: _ba.screenmessage(
'FORCING LARGE UI FOR TESTING', color=(1, 0, 1), log=True))
# pylint: enable=using-constant-test
# If there's a leftover log file, attempt to upload
@ -572,16 +546,17 @@ class App:
server_addr = _ba.get_master_server_address()
if 'localhost' in server_addr:
_ba.timer(2.0,
Call(_ba.screenmessage,
"Note: using local server", (1, 1, 0),
log=True),
lambda: _ba.screenmessage("Note: using local server",
(1, 1, 0),
log=True),
timetype=TimeType.REAL)
elif 'test' in server_addr:
_ba.timer(2.0,
Call(_ba.screenmessage,
"Note: using test server-module", (1, 1, 0),
log=True),
timetype=TimeType.REAL)
_ba.timer(
2.0,
lambda: _ba.screenmessage("Note: using test server-module",
(1, 1, 0),
log=True),
timetype=TimeType.REAL)
cfg['launchCount'] = launch_count
cfg.commit()
@ -786,7 +761,6 @@ class App:
def do_remove_in_game_ads_message(self) -> None:
"""(internal)"""
from ba._lang import Lstr
from ba._general import Call
from ba._enums import TimeType
# Print this message once every 10 minutes at most.
@ -797,15 +771,12 @@ class App:
with _ba.Context('ui'):
_ba.timer(
1.0,
Call(_ba.screenmessage,
Lstr(
resource='removeInGameAdsText',
subs=[
('${PRO}',
Lstr(resource='store.bombSquadProNameText')),
('${APP_NAME}', Lstr(resource='titleText'))
]),
color=(1, 1, 0)),
lambda: _ba.screenmessage(Lstr(
resource='removeInGameAdsText',
subs=[('${PRO}',
Lstr(resource='store.bombSquadProNameText')),
('${APP_NAME}', Lstr(resource='titleText'))]),
color=(1, 1, 0)),
timetype=TimeType.REAL)
def shutdown(self) -> None:

View File

@ -279,23 +279,29 @@ def print_corrupt_file_error() -> None:
from ba._lang import get_resource
from ba._general import Call
from ba._enums import TimeType
_ba.timer(2.0,
Call(_ba.screenmessage,
get_resource('internal.corruptFileText').replace(
'${EMAIL}', 'support@froemling.net'),
color=(1, 0, 0)),
timetype=TimeType.REAL)
_ba.timer(
2.0,
lambda: _ba.screenmessage(get_resource('internal.corruptFileText').
replace('${EMAIL}', 'support@froemling.net'),
color=(1, 0, 0)),
timetype=TimeType.REAL)
_ba.timer(2.0,
Call(_ba.playsound, _ba.getsound('error')),
timetype=TimeType.REAL)
def show_ad(purpose: str,
on_completion_call: Callable[[], Any] = None,
pass_actually_showed: bool = False) -> None:
on_completion_call: Callable[[], Any] = None) -> None:
"""(internal)"""
_ba.app.last_ad_purpose = purpose
_ba.show_ad(purpose, on_completion_call, pass_actually_showed)
_ba.show_ad(purpose, on_completion_call)
def show_ad_2(purpose: str,
on_completion_call: Callable[[bool], Any] = None) -> None:
"""(internal)"""
_ba.app.last_ad_purpose = purpose
_ba.show_ad_2(purpose, on_completion_call)
def call_after_ad(call: Callable[[], Any]) -> None:

View File

@ -19,7 +19,6 @@
# SOFTWARE.
# -----------------------------------------------------------------------------
"""Functionality related to object/asset dependencies."""
# pylint: disable=redefined-builtin
from __future__ import annotations
@ -74,8 +73,7 @@ class Dependency(Generic[T]):
# return different things based on self's type and avoid the need for
# the fake dep classes below.
# See https://github.com/python/mypy/issues/5320
# noinspection PyShadowingBuiltins
def __get__(self, obj: Any, type: Any = None) -> Any:
def __get__(self, obj: Any, cls: Any = None) -> Any:
if obj is None:
raise TypeError("Dependency must be accessed through an instance.")
@ -114,15 +112,13 @@ else:
class _InstanceDep(Dependency[TI]):
"""Fake stub we use to tell the type system we provide a type."""
# noinspection PyShadowingBuiltins
def __get__(self, obj: Any, type: Any = None) -> Type[TI]:
def __get__(self, obj: Any, cls: Any = None) -> Type[TI]:
return cast(Type[TI], None)
class _StaticDep(Dependency[TS]):
"""Fake stub we use to tell the type system we provide an instance."""
# noinspection PyShadowingBuiltins
def __get__(self, obj: Any, type: Any = None) -> TS:
def __get__(self, obj: Any, cls: Any = None) -> TS:
return cast(TS, None)
# pylint: disable=invalid-name
@ -169,7 +165,7 @@ class BoundDepComponent:
class DepComponent:
"""Base class for all classes that can act as dependencies.
"""Base class for all classes that can act as or use dependencies.
category: Dependency Classes
"""
@ -373,7 +369,7 @@ class InstancedDepComponent(DepComponent):
class StaticDepComponent(DepComponent):
"""Base for DepComponents intended to be instanced once and shared."""
"""Base for DepComponents intended to be instantiated once and shared."""
@classmethod
def dep_get_payload(cls, depdata: DepData) -> Any:

View File

@ -30,6 +30,7 @@ import _ba
if TYPE_CHECKING:
from typing import Any, Type
from bafoundation import executils
T = TypeVar('T')
@ -121,7 +122,7 @@ def get_type_name(cls: Type) -> str:
return cls.__module__ + '.' + cls.__name__
class WeakCall:
class _WeakCall:
"""Wrap a callable and arguments into a single callable object.
Category: General Utility Classes
@ -159,11 +160,12 @@ class WeakCall:
Instantiate a WeakCall; pass a callable as the first
arg, followed by any number of arguments or keywords.
# example: wrap a method call with some positional and keyword args:
# example: wrap a method call with some positional and
# keyword args:
myweakcall = ba.WeakCall(myobj.dostuff, argval1, namedarg=argval2)
# Now we have a single callable to run that whole mess.
# This is the same as calling myobj.dostuff(argval1, namedarg=argval2)
# The same as calling myobj.dostuff(argval1, namedarg=argval2)
# (provided my_obj still exists; this will do nothing otherwise)
myweakcall()
"""
@ -191,16 +193,18 @@ class WeakCall:
str(self._args) + ' _keywds=' + str(self._keywds) + '>')
class Call:
class _Call:
"""Wraps a callable and arguments into a single callable object.
Category: General Utility Classes
The callable is strong-referenced so it won't die until this object does.
The callable is strong-referenced so it won't die until this
object does.
Note that a bound method (ex: myobj.dosomething) contains a reference
to 'self' (myobj in that case), so you will be keeping that object alive
too. Use ba.WeakCall if you want to pass a method to callback without
keeping its object alive.
to 'self' (myobj in that case), so you will be keeping that object
alive too. Use ba.WeakCall if you want to pass a method to callback
without keeping its object alive.
"""
def __init__(self, *args: Any, **keywds: Any):
@ -208,11 +212,11 @@ class Call:
Instantiate a Call; pass a callable as the first
arg, followed by any number of arguments or keywords.
# example: wrap a method call with 1 positional and 1 keyword arg
# Example: wrap a method call with 1 positional and 1 keyword arg.
mycall = ba.Call(myobj.dostuff, argval1, namedarg=argval2)
# now we have a single callable to run that whole mess
# this is the same as calling myobj.dostuff(argval1, namedarg=argval2)
# Now we have a single callable to run that whole mess.
# ..the same as calling myobj.dostuff(argval1, namedarg=argval2)
mycall()
"""
self._call = args[0]
@ -227,6 +231,16 @@ class Call:
str(self._args) + ' _keywds=' + str(self._keywds) + '>')
if TYPE_CHECKING:
WeakCall = executils.Call
Call = executils.Call
else:
WeakCall = _WeakCall
WeakCall.__name__ = 'WeakCall'
Call = _Call
Call.__name__ = 'Call'
class WeakMethod:
"""A weak-referenced bound method.

View File

@ -30,7 +30,7 @@ from typing import TYPE_CHECKING
import _ba
if TYPE_CHECKING:
from typing import Callable, Any, List, Optional, Dict
from typing import Callable, Any, List, Optional, Dict, Union
class MusicPlayer:
@ -141,7 +141,9 @@ class InternalMusicPlayer(MusicPlayer):
class _PickFolderSongThread(threading.Thread):
def __init__(self, path: str, callback: Callable):
def __init__(self, path: str,
callback: Callable[[Union[str, List[str]], Optional[str]],
None]):
super().__init__()
self._callback = callback
self._path = path
@ -166,7 +168,7 @@ class InternalMusicPlayer(MusicPlayer):
raise Exception(
_lang.Lstr(resource='internal.noMusicFilesInFolderText'
).evaluate())
_ba.pushcall(Call(self._callback, result=all_files),
_ba.pushcall(Call(self._callback, all_files, None),
from_other_thread=True)
except Exception as exc:
from ba import _error
@ -175,9 +177,7 @@ class InternalMusicPlayer(MusicPlayer):
err_str = str(exc)
except Exception:
err_str = '<ENCERR4523>'
_ba.pushcall(Call(self._callback,
result=self._path,
error=err_str),
_ba.pushcall(Call(self._callback, self._path, err_str),
from_other_thread=True)
def on_play(self, entry: Any) -> None:
@ -195,14 +195,19 @@ class InternalMusicPlayer(MusicPlayer):
self._actually_playing = False
self._PickFolderSongThread(name, self._on_play_folder_cb).start()
def _on_play_folder_cb(self, result: str,
def _on_play_folder_cb(self,
result: Union[str, List[str]],
error: Optional[str] = None) -> None:
from ba import _lang
if error is not None:
rstr = (_lang.Lstr(
resource='internal.errorPlayingMusicText').evaluate())
err_str = (rstr.replace('${MUSIC}', os.path.basename(result)) +
'; ' + str(error))
if isinstance(result, str):
err_str = (rstr.replace('${MUSIC}', os.path.basename(result)) +
'; ' + str(error))
else:
err_str = (rstr.replace('${MUSIC}', '<multiple>') + '; ' +
str(error))
_ba.screenmessage(err_str, color=(1, 0, 0))
return
@ -332,7 +337,8 @@ class ITunesThread(threading.Thread):
self._commands.append(['GET_PLAYLISTS', callback])
self._commands_available.set()
def _handle_get_playlists_command(self, target: str) -> None:
def _handle_get_playlists_command(self, target: Callable[[List[str]], None]
) -> None:
from ba._general import Call
try:
playlists = _ba.itunes_get_playlists()

View File

@ -32,7 +32,7 @@ import _ba
if TYPE_CHECKING:
import ba
from weakref import ReferenceType
from typing import Any, Dict, Optional, Sequence, Union
from typing import Any, Dict, Optional, Sequence, Union, Tuple
@dataclass
@ -210,9 +210,9 @@ class PlayerRecord:
delay = 1000
sound = stats.orchestrahitsound4
def _apply(name2: str, score2: int, showpoints2: bool,
color2: Sequence[float], scale2: float,
sound2: ba.Sound) -> None:
def _apply(name2: Lstr, score2: int, showpoints2: bool,
color2: Tuple[float, float, float, float], scale2: float,
sound2: Optional[ba.Sound]) -> None:
from bastd.actor.popuptext import PopupText
# Only award this if they're still alive and we can get
@ -238,7 +238,8 @@ class PlayerRecord:
color=color2,
scale=scale2,
position=our_pos).autoretain()
_ba.playsound(sound2)
if sound2:
_ba.playsound(sound2)
self.score += score2
self.accumscore += score2

View File

@ -45,7 +45,7 @@ from ba._achievement import (get_achievement, set_completed_achievements,
display_achievement_banner,
get_achievements_for_coop_level)
from ba._apputils import (is_browser_likely_available, get_remote_app_name,
should_submit_debug_info, show_ad)
should_submit_debug_info, show_ad, show_ad_2)
from ba._benchmark import (run_gpu_benchmark, run_cpu_benchmark,
run_media_reload_benchmark, run_stress_test)
from ba._campaign import get_campaign

View File

@ -25,20 +25,20 @@ from __future__ import annotations
from typing import TYPE_CHECKING, TypeVar, Generic, Callable, cast
if TYPE_CHECKING:
from typing import Any
from typing import Any, overload
T = TypeVar('T', bound=Callable)
CT = TypeVar('CT', bound=Callable)
class _CallbackCall(Generic[T]):
class _CallbackCall(Generic[CT]):
"""Descriptor for exposing a call with a type defined by a TypeVar."""
def __get__(self, obj: Any, type_in: Any = None) -> T:
return cast(T, None)
def __get__(self, obj: Any, type_in: Any = None) -> CT:
return cast(CT, None)
class CallbackSet(Generic[T]):
"""Wrangles callbacks for a particular event."""
class CallbackSet(Generic[CT]):
"""Wrangles callbacks for a particular event in a type-safe manner."""
# In the type-checker's eyes, our 'run' attr is a CallbackCall which
# returns a callable with the type we were created with. This lets us
@ -47,7 +47,7 @@ class CallbackSet(Generic[T]):
# At runtime, run() simply passes its args verbatim to its registered
# callbacks; no types are checked.
if TYPE_CHECKING:
run: _CallbackCall[T] = _CallbackCall()
run: _CallbackCall[CT] = _CallbackCall()
else:
def run(self, *args, **keywds):
@ -60,6 +60,221 @@ class CallbackSet(Generic[T]):
def __del__(self) -> None:
print("~CallbackSet()")
def add(self, call: T) -> None:
def add(self, call: CT) -> None:
"""Add a callback to be run."""
print("Would add call", call)
# Define Call() which can be used in type-checking call-wrappers that behave
# similarly to functools.partial (in that they take a callable and some
# positional arguments to be passed to it)
# We define several different _CallXArg classes corresponding to different
# argument counts and define Call() as an overloaded function which returns
# one of them based on how many args are passed.
# To use this, simply assign your call type to this Call for type checking:
# example:
# class _MyCallWrapper:
# <runtime class defined here>
# if TYPE_CHECKING:
# MyCallWrapper = bafoundation.executils.Call
# else:
# MyCallWrapper = _MyCallWrapper
# Note that this setup currently only works with positional arguments; if you
# would like to pass args via keyword you can wrap a lambda or local function
# which takes keyword args and converts to a call containing keywords.
if TYPE_CHECKING:
In1T = TypeVar('In1T')
In2T = TypeVar('In2T')
In3T = TypeVar('In3T')
In4T = TypeVar('In4T')
In5T = TypeVar('In5T')
In6T = TypeVar('In6T')
In7T = TypeVar('In7T')
OutT = TypeVar('OutT')
class _CallNoArgs(Generic[OutT]):
"""Single argument variant of call wrapper."""
def __init__(self, _call: Callable[[], OutT]):
...
def __call__(self) -> OutT:
...
class _Call1Arg(Generic[In1T, OutT]):
"""Single argument variant of call wrapper."""
def __init__(self, _call: Callable[[In1T], OutT]):
...
def __call__(self, _arg1: In1T) -> OutT:
...
class _Call2Args(Generic[In1T, In2T, OutT]):
"""Two argument variant of call wrapper"""
def __init__(self, _call: Callable[[In1T, In2T], OutT]):
...
def __call__(self, _arg1: In1T, _arg2: In2T) -> OutT:
...
class _Call3Args(Generic[In1T, In2T, In3T, OutT]):
"""Three argument variant of call wrapper"""
def __init__(self, _call: Callable[[In1T, In2T, In3T], OutT]):
...
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T) -> OutT:
...
class _Call4Args(Generic[In1T, In2T, In3T, In4T, OutT]):
"""Four argument variant of call wrapper"""
def __init__(self, _call: Callable[[In1T, In2T, In3T, In4T], OutT]):
...
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T,
_arg4: In4T) -> OutT:
...
class _Call5Args(Generic[In1T, In2T, In3T, In4T, In5T, OutT]):
"""Five argument variant of call wrapper"""
def __init__(self,
_call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT]):
...
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T,
_arg5: In5T) -> OutT:
...
class _Call6Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, OutT]):
"""Six argument variant of call wrapper"""
def __init__(
self,
_call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT]):
...
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T,
_arg5: In5T, _arg6: In6T) -> OutT:
...
class _Call7Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, In7T, OutT]):
"""Seven argument variant of call wrapper"""
def __init__(
self,
_call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T],
OutT]):
...
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T,
_arg5: In5T, _arg6: In6T, _arg7: In7T) -> OutT:
...
# No arg call; no args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[], OutT]) -> _CallNoArgs[OutT]:
...
# 1 arg call; 1 arg bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T], OutT], arg1: In1T) -> _CallNoArgs[OutT]:
...
# 1 arg call; no args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T], OutT]) -> _Call1Arg[In1T, OutT]:
...
# 2 arg call; 2 args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T], OutT], arg1: In1T,
arg2: In2T) -> _CallNoArgs[OutT]:
...
# 2 arg call; 1 arg bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T], OutT],
arg1: In1T) -> _Call1Arg[In2T, OutT]:
...
# 2 arg call; no args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T], OutT]) -> _CallNoArgs[OutT]:
...
# 3 arg call; 3 args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T, arg2: In2T,
arg3: In3T) -> _CallNoArgs[OutT]:
...
# 3 arg call; 2 args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T,
arg2: In2T) -> _Call1Arg[In3T, OutT]:
...
# 3 arg call; 1 arg bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T, In3T], OutT],
arg1: In1T) -> _Call2Args[In2T, In3T, OutT]:
...
# 3 arg call; no args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T, In3T], OutT]
) -> _Call3Args[In1T, In2T, In3T, OutT]:
...
# 4 arg call; 4 args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T, In3T, In4T], OutT], arg1: In1T,
arg2: In2T, arg3: In3T, arg4: In4T) -> _CallNoArgs[OutT]:
...
# 5 arg call; 5 args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT], arg1: In1T,
arg2: In2T, arg3: In3T, arg4: In4T,
arg5: In5T) -> _CallNoArgs[OutT]:
...
# 6 arg call; 6 args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT],
arg1: In1T, arg2: In2T, arg3: In3T, arg4: In4T, arg5: In5T,
arg6: In6T) -> _CallNoArgs[OutT]:
...
# 7 arg call; 7 args bundled.
# noinspection PyPep8Naming
@overload
def Call(call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT],
arg1: In1T, arg2: In2T, arg3: In3T, arg4: In4T, arg5: In5T,
arg6: In6T, arg7: In7T) -> _CallNoArgs[OutT]:
...
# noinspection PyPep8Naming
def Call(*_args: Any, **_keywds: Any) -> Any:
...

View File

@ -920,7 +920,7 @@ class BotSet:
self._spawning_count += 1
def _spawn_bot(self, bot_type: Type[SpazBot], pos: Sequence[float],
on_spawn_call: Callable[[SpazBot], Any]) -> None:
on_spawn_call: Optional[Callable[[SpazBot], Any]]) -> None:
spaz = bot_type()
ba.playsound(self._spawn_sound, position=pos)
assert spaz.node

View File

@ -118,9 +118,10 @@ class KingOfTheHillGame(ba.TeamGameActivity):
actions=(("modify_part_collision", "collide",
True), ("modify_part_collision", "physical", False),
("call", "at_connect",
ba.Call(self._handle_player_flag_region_collide, 1)),
ba.Call(self._handle_player_flag_region_collide, True)),
("call", "at_disconnect",
ba.Call(self._handle_player_flag_region_collide, 0))))
ba.Call(self._handle_player_flag_region_collide,
False))))
def get_instance_description(self) -> Union[str, Sequence]:
return ('Secure the flag for ${ARG1} seconds.',

View File

@ -104,46 +104,28 @@ class NinjaFightGame(ba.TeamGameActivity):
# Spawn some baddies.
ba.timer(
1.0,
ba.Call(self._bots.spawn_bot,
spazbot.ChargerBot,
pos=(3, 3, -2),
spawn_time=3.0))
1.0, lambda: self._bots.spawn_bot(
spazbot.ChargerBot, pos=(3, 3, -2), spawn_time=3.0))
ba.timer(
2.0,
ba.Call(self._bots.spawn_bot,
spazbot.ChargerBot,
pos=(-3, 3, -2),
spawn_time=3.0))
2.0, lambda: self._bots.spawn_bot(
spazbot.ChargerBot, pos=(-3, 3, -2), spawn_time=3.0))
ba.timer(
3.0,
ba.Call(self._bots.spawn_bot,
spazbot.ChargerBot,
pos=(5, 3, -2),
spawn_time=3.0))
3.0, lambda: self._bots.spawn_bot(
spazbot.ChargerBot, pos=(5, 3, -2), spawn_time=3.0))
ba.timer(
4.0,
ba.Call(self._bots.spawn_bot,
spazbot.ChargerBot,
pos=(-5, 3, -2),
spawn_time=3.0))
4.0, lambda: self._bots.spawn_bot(
spazbot.ChargerBot, pos=(-5, 3, -2), spawn_time=3.0))
# Add some extras for multiplayer or pro mode.
assert self.initial_player_info is not None
if len(self.initial_player_info) > 2 or is_pro:
ba.timer(
5.0,
ba.Call(self._bots.spawn_bot,
spazbot.ChargerBot,
pos=(0, 3, -5),
spawn_time=3.0))
5.0, lambda: self._bots.spawn_bot(
spazbot.ChargerBot, pos=(0, 3, -5), spawn_time=3.0))
if len(self.initial_player_info) > 3 or is_pro:
ba.timer(
6.0,
ba.Call(self._bots.spawn_bot,
spazbot.ChargerBot,
pos=(0, 3, 1),
spawn_time=3.0))
6.0, lambda: self._bots.spawn_bot(
spazbot.ChargerBot, pos=(0, 3, 1), spawn_time=3.0))
# Called for each spawning player.
def spawn_player(self, player: ba.Player) -> ba.Actor:

View File

@ -152,7 +152,7 @@ class TheLastStandGame(ba.CoopGameActivity):
def _drop_powerups(self,
standard_points: bool = False,
force_first: bool = None) -> None:
force_first: str = None) -> None:
"""Generic powerup drop."""
from bastd.actor import powerupbox
if standard_points:

View File

@ -40,9 +40,8 @@ def show_sign_in_prompt(account_type: str = None) -> None:
else:
confirm.ConfirmWindow(
ba.Lstr(resource='notSignedInErrorText'),
ba.Call(settings.AccountSettingsWindow,
modal=True,
close_once_signed_in=True),
lambda: settings.AccountSettingsWindow(modal=True,
close_once_signed_in=True),
ok_text=ba.Lstr(resource='accountSettingsWindow.signInText'),
width=460,
height=130)

View File

@ -774,11 +774,11 @@ class AccountSettingsWindow(ba.OldWindow):
autoselect=True,
size=(button_width, 60),
label=ba.Lstr(resource=self._r + '.resetProgressText'),
on_activate_call=ba.Call(confirm.ConfirmWindow,
text=confirm_text,
width=500,
height=200,
action=self._reset_progress))
on_activate_call=lambda: confirm.ConfirmWindow(
text=confirm_text,
width=500,
height=200,
action=self._reset_progress))
if first_selectable is None:
first_selectable = btn
if ba.app.toolbars:

View File

@ -147,7 +147,8 @@ class QuitWindow:
origin_widget: ba.Widget = None):
app = ba.app
self._back = back
# if there's already one of us up somewhere, kill it
# If there's already one of us up somewhere, kill it.
if app.quit_window is not None:
app.quit_window.delete()
app.quit_window = None
@ -164,9 +165,10 @@ class QuitWindow:
def _fade_and_quit(self) -> None:
_ba.fade_screen(False,
time=0.2,
endcall=ba.Call(ba.quit, soft=True, back=self._back))
endcall=lambda: ba.quit(soft=True, back=self._back))
_ba.lock_all_input()
# unlock and fade back in shortly.. just in case something goes wrong
# Unlock and fade back in shortly.. just in case something goes wrong
# (or on android where quit just backs out of our activity and
# we may come back)
ba.timer(0.3, _ba.unlock_all_input, timetype=ba.TimeType.REAL)

View File

@ -797,15 +797,15 @@ class CoopBrowserWindow(ba.OldWindow):
row_v_show_buffer = 100
v -= 198
h_scroll = ba.hscrollwidget(parent=w_parent,
size=(self._scroll_width - 10, 205),
position=(-5, v),
simple_culling_h=70,
highlight=False,
border_opacity=0.0,
color=(0.45, 0.4, 0.5),
on_select_call=ba.Call(
self._on_row_selected, 'campaign'))
h_scroll = ba.hscrollwidget(
parent=w_parent,
size=(self._scroll_width - 10, 205),
position=(-5, v),
simple_culling_h=70,
highlight=False,
border_opacity=0.0,
color=(0.45, 0.4, 0.5),
on_select_call=lambda: self._on_row_selected('campaign'))
self._campaign_h_scroll = h_scroll
ba.widget(edit=h_scroll,
show_buffer_top=row_v_show_buffer,
@ -1021,7 +1021,7 @@ class CoopBrowserWindow(ba.OldWindow):
# event queue.. we need to push ours too so we're enabled *after* them.
ba.pushcall(self._enable_selectable_callback)
def _on_row_selected(self, row: int) -> None:
def _on_row_selected(self, row: str) -> None:
if self._do_selection_callbacks:
if self._selected_row != row:
self._selected_row = row
@ -1039,16 +1039,14 @@ class CoopBrowserWindow(ba.OldWindow):
'has_time_remaining': False,
'leader': None
}
data['button'] = btn = ba.buttonwidget(parent=parent,
position=(x + 23, y + 4),
size=(sclx, scly),
label='',
button_type='square',
autoselect=True,
on_activate_call=ba.Call(
self.run,
None,
tournament_button=data))
data['button'] = btn = ba.buttonwidget(
parent=parent,
position=(x + 23, y + 4),
size=(sclx, scly),
label='',
button_type='square',
autoselect=True,
on_activate_call=lambda: self.run(None, tournament_button=data))
ba.widget(edit=btn,
show_buffer_bottom=50,
show_buffer_top=50,
@ -1353,7 +1351,7 @@ class CoopBrowserWindow(ba.OldWindow):
origin_widget=self._league_rank_button.get_button()).
get_root_widget())
def _switch_to_score(self, show_tab: str = 'extras') -> None:
def _switch_to_score(self, show_tab: Optional[str] = 'extras') -> None:
# pylint: disable=cyclic-import
from bastd.ui import account
from bastd.ui.store import browser
@ -1403,7 +1401,9 @@ class CoopBrowserWindow(ba.OldWindow):
"""Return whether our tourney data is up to date."""
return self._tourney_data_up_to_date
def run(self, game: str, tournament_button: Dict[str, Any] = None) -> None:
def run(self,
game: Optional[str],
tournament_button: Dict[str, Any] = None) -> None:
"""Run the provided game."""
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
@ -1515,6 +1515,7 @@ class CoopBrowserWindow(ba.OldWindow):
position=tournament_button['button'].get_screen_space_center())
else:
# Otherwise just dive right in.
assert game is not None
if ba.app.launch_coop_game(game, args=args):
ba.containerwidget(edit=self._root_widget,
transition='out_left')

View File

@ -108,7 +108,7 @@ class FileSelectorWindow(ba.OldWindow):
self._folder_color = (1.1, 0.8, 0.2)
self._file_tex = ba.gettexture('file')
self._file_color = (1, 1, 1)
self._use_folder_button = None
self._use_folder_button: Optional[ba.Widget] = None
self._folder_center = self._width * 0.5 + 15
self._folder_icon = ba.imagewidget(parent=self._root_widget,
size=(40, 40),
@ -189,7 +189,7 @@ class FileSelectorWindow(ba.OldWindow):
class _RefreshThread(threading.Thread):
def __init__(self, path: str,
callback: Callable[[List[str], str], Any]):
callback: Callable[[List[str], Optional[str]], Any]):
super().__init__()
self._callback = callback
self._path = path
@ -200,17 +200,19 @@ class FileSelectorWindow(ba.OldWindow):
files = os.listdir(self._path)
duration = time.time() - starttime
min_time = 0.1
# make sure this takes at least 1/10 second so the user
# has time to see the selection highlight
# Make sure this takes at least 1/10 second so the user
# has time to see the selection highlight.
if duration < min_time:
time.sleep(min_time - duration)
ba.pushcall(ba.Call(self._callback, file_names=files),
ba.pushcall(ba.Call(self._callback, files, None),
from_other_thread=True)
except Exception as exc:
# ignore permission-denied
# Ignore permission-denied.
if 'Errno 13' not in str(exc):
ba.print_exception()
ba.pushcall(ba.Call(self._callback, error=str(exc)),
nofiles: List[str] = []
ba.pushcall(ba.Call(self._callback, nofiles, str(exc)),
from_other_thread=True)
def _set_path(self, path: str, add_to_recent: bool = True) -> None:
@ -219,7 +221,7 @@ class FileSelectorWindow(ba.OldWindow):
self._recent_paths.append(path)
self._RefreshThread(path, self._refresh).start()
def _refresh(self, file_names: List[str], error: str) -> None:
def _refresh(self, file_names: List[str], error: Optional[str]) -> None:
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
@ -357,6 +359,7 @@ class FileSelectorWindow(ba.OldWindow):
if num == 0:
ba.widget(edit=cnt, up_widget=self._back_button)
is_valid_file_path = self._is_valid_file_path(entry)
assert self._path is not None
is_dir = os.path.isdir(self._path + '/' + entry)
if is_dir:
ba.imagewidget(parent=cnt,

View File

@ -218,17 +218,15 @@ class GatherWindow(ba.OldWindow):
# that they will get disconnected.. otherwise just go ahead..
google_player_count = (_ba.get_google_play_party_client_count())
if google_player_count > 0:
confirm.ConfirmWindow(ba.Lstr(
resource=self._r + '.googlePlayReInviteText',
subs=[('${COUNT}', str(google_player_count))]),
ba.Call(ba.timer,
0.2,
_ba.invite_players,
timetype='real'),
width=500,
height=150,
ok_text=ba.Lstr(resource=self._r +
'.googlePlayInviteText'))
confirm.ConfirmWindow(
ba.Lstr(resource=self._r + '.googlePlayReInviteText',
subs=[('${COUNT}', str(google_player_count))]),
lambda: ba.timer(
0.2, _ba.invite_players, timetype=ba.TimeType.REAL),
width=500,
height=150,
ok_text=ba.Lstr(resource=self._r +
'.googlePlayInviteText'))
else:
ba.timer(0.1, _ba.invite_players, timetype=ba.TimeType.REAL)
@ -411,19 +409,19 @@ class GatherWindow(ba.OldWindow):
h_align='center',
v_align='center')
v -= 180
ba.buttonwidget(parent=cnt,
label=ba.Lstr(resource=self._r +
'.googlePlaySeeInvitesText'),
color=(0.5, 0.5, 0.6),
textcolor=(0.75, 0.7, 0.8),
autoselect=True,
position=(c_width * 0.5 - b_width2 * 0.5, v),
size=(b_width2, 60),
on_activate_call=ba.Call(
ba.timer,
0.1,
self._on_google_play_show_invites_press,
timetype='real'))
ba.buttonwidget(
parent=cnt,
label=ba.Lstr(resource=self._r + '.googlePlaySeeInvitesText'),
color=(0.5, 0.5, 0.6),
textcolor=(0.75, 0.7, 0.8),
autoselect=True,
position=(c_width * 0.5 - b_width2 * 0.5, v),
size=(b_width2, 60),
on_activate_call=lambda: ba.timer(
0.1,
self._on_google_play_show_invites_press,
timetype=ba.TimeType.REAL),
)
elif tab == 'internet':
c_width = self._scroll_width
@ -448,9 +446,8 @@ class GatherWindow(ba.OldWindow):
click_activate=True,
selectable=True,
autoselect=True,
on_activate_call=ba.Call(self._set_internet_tab,
'join',
playsound=True),
on_activate_call=lambda: self._set_internet_tab(
'join', playsound=True),
text=ba.Lstr(resource=self._r +
'.joinPublicPartyDescriptionText'))
ba.widget(edit=txt, up_widget=self._tab_buttons[tab])
@ -466,9 +463,8 @@ class GatherWindow(ba.OldWindow):
click_activate=True,
selectable=True,
autoselect=True,
on_activate_call=ba.Call(self._set_internet_tab,
'host',
playsound=True),
on_activate_call=lambda: self._set_internet_tab(
'host', playsound=True),
text=ba.Lstr(resource=self._r +
'.hostPublicPartyDescriptionText'))
ba.widget(edit=txt,
@ -824,7 +820,8 @@ class GatherWindow(ba.OldWindow):
class HostAddrFetchThread(threading.Thread):
"""Thread to fetch an addr."""
def __init__(self, name: str, call: Callable[[str], Any]):
def __init__(self, name: str,
call: Callable[[Optional[str]], Any]):
super().__init__()
self._name = name
self._call = call
@ -839,7 +836,7 @@ class GatherWindow(ba.OldWindow):
ba.pushcall(ba.Call(self._call, None),
from_other_thread=True)
def do_it_2(addr2: str) -> None:
def do_it_2(addr2: Optional[str]) -> None:
if addr2 is None:
ba.screenmessage(ba.Lstr(
resource='internal.unableToResolveHostText'),
@ -1681,10 +1678,9 @@ class GatherWindow(ba.OldWindow):
class PingThread(threading.Thread):
"""Thread for sending out pings."""
def __init__(
self, address: str, port: int,
call: Callable[[str, int, Optional[int]], Any]
):
def __init__(self, address: str, port: int,
call: Callable[[str, int, Optional[int]],
Optional[int]]):
super().__init__()
# need utf8 here to avoid an error on our minimum
# bundled python
@ -1743,7 +1739,8 @@ class GatherWindow(ba.OldWindow):
PingThread(party['address'], party['port'],
ba.WeakCall(self._ping_callback)).start()
def _ping_callback(self, address: str, port: int, result: int) -> None:
def _ping_callback(self, address: str, port: Optional[int],
result: Optional[int]) -> None:
# Look for a widget corresponding to this target; if we find one,
# update our list.
party = self._public_parties.get(address + '_' + str(port))

View File

@ -609,7 +609,7 @@ def show_get_tickets_prompt() -> None:
confirm.ConfirmWindow(
ba.Lstr(translate=('serverResponses',
'You don\'t have enough tickets for this!')),
ba.Call(GetCurrencyWindow, modal=True),
lambda: GetCurrencyWindow(modal=True),
ok_text=ba.Lstr(resource='getTicketsWindow.titleText'),
width=460,
height=130)

View File

@ -39,12 +39,16 @@ class KioskWindow(ba.OldWindow):
from bastd.ui import confirm
self._width = 720.0
self._height = 340.0
super().__init__(root_widget=ba.containerwidget(
size=(self._width, self._height),
transition=transition,
on_cancel_call=ba.Call(confirm.QuitWindow, swish=True, back=True),
background=False,
stack_offset=(0, -130)))
def _do_cancel() -> None:
confirm.QuitWindow(swish=True, back=True)
super().__init__(
root_widget=ba.containerwidget(size=(self._width, self._height),
transition=transition,
on_cancel_call=_do_cancel,
background=False,
stack_offset=(0, -130)))
self._r = 'kioskWindow'

View File

@ -207,7 +207,9 @@ class LeagueRankButton:
if widget:
widget.delete()
ba.timer(2.0, ba.Call(safe_delete, diff_text, timetype='real'))
ba.timer(2.0,
ba.Call(safe_delete, diff_text),
timetype=ba.TimeType.REAL)
status_text: Union[str, ba.Lstr]
if self._rank is not None:
assert self._smooth_rank is not None

View File

@ -320,10 +320,12 @@ class MainMenuWindow(ba.OldWindow):
# we want back presses to quit our activity.
if (not self._in_game and not self._have_quit_button
and ba.app.platform == 'android'):
def _do_quit() -> None:
confirm.QuitWindow(swish=True, back=True)
ba.containerwidget(edit=self._root_widget,
on_cancel_call=ba.Call(confirm.QuitWindow,
swish=True,
back=True))
on_cancel_call=_do_quit)
# Add speed-up/slow-down buttons for replays.
# (ideally this should be part of a fading-out playback bar like most

View File

@ -123,7 +123,7 @@ class PartyQueueWindow(ba.OldWindow):
# need to push a deferred call to kill these as necessary instead.
# (should bulletproof internal widget code to give a clean error
# in this case)
def kill_widgets(widgets: Sequence[ba.Widget]) -> None:
def kill_widgets(widgets: Sequence[Optional[ba.Widget]]) -> None:
for widget in widgets:
if widget:
widget.delete()

View File

@ -152,22 +152,26 @@ class PlaylistAddGameWindow(ba.OldWindow):
gametypes.sort(key=lambda g: g.get_display_string().evaluate())
for i, gametype in enumerate(gametypes):
txt = ba.textwidget(
parent=self._column,
position=(0, 0),
size=(self._width - 88, 24),
text=gametype.get_display_string(),
h_align="left",
v_align="center",
color=(0.8, 0.8, 0.8, 1.0),
maxwidth=self._scroll_width * 0.8,
on_select_call=ba.Call(self._set_selected_game_type, gametype),
always_highlight=True,
selectable=True,
on_activate_call=ba.Call(ba.timer,
0.1,
self._select_button.activate,
timetype='real'))
def _doit() -> None:
if self._select_button:
ba.timer(0.1,
self._select_button.activate,
timetype=ba.TimeType.REAL)
txt = ba.textwidget(parent=self._column,
position=(0, 0),
size=(self._width - 88, 24),
text=gametype.get_display_string(),
h_align="left",
v_align="center",
color=(0.8, 0.8, 0.8, 1.0),
maxwidth=self._scroll_width * 0.8,
on_select_call=ba.Call(
self._set_selected_game_type, gametype),
always_highlight=True,
selectable=True,
on_activate_call=_doit)
if i == 0:
ba.widget(edit=txt, up_widget=self._back_button)

View File

@ -350,6 +350,7 @@ class PlaylistBrowserWindow(ba.OldWindow):
for y in range(rows):
for x in range(columns):
name = items[index][0]
assert name is not None
pos = (x * (button_width + 2 * button_buffer_h) +
button_buffer_h + 8 + h_offs, self._sub_height - 47 -
(y + 1) * (button_height + 2 * button_buffer_v))

View File

@ -225,9 +225,8 @@ class PlayOptionsWindow(popup.PopupWindow):
position=(h, v),
texture=ba.gettexture(tex_name if owned else 'empty'),
model_opaque=model_opaque if owned else None,
on_activate_call=ba.Call(ba.screenmessage,
desc,
color=desc_color),
on_activate_call=ba.Call(ba.screenmessage, desc,
desc_color),
label='',
color=(1, 1, 1),
autoselect=True,

View File

@ -27,7 +27,7 @@ from typing import TYPE_CHECKING
import ba
if TYPE_CHECKING:
from typing import Any, Callable, Dict, Tuple, List, Sequence
from typing import Any, Callable, Dict, Tuple, List, Sequence, Optional
def create_tab_buttons(parent_widget: ba.Widget,
@ -50,9 +50,11 @@ def create_tab_buttons(parent_widget: ba.Widget,
h = pos[0]
for _i, tab in enumerate(tabs):
def _tick_and_call(call: Callable[[Any], Any], arg: Any) -> None:
def _tick_and_call(call: Optional[Callable[[Any], Any]],
arg: Any) -> None:
ba.playsound(ba.getsound('click01'))
call(arg)
if call is not None:
call(arg)
pos = (h + tab_spacing * 0.5, tab_pos_v)
size = (tab_button_width - tab_spacing, 50.0)

View File

@ -541,7 +541,7 @@ class TournamentEntryWindow(popup.PopupWindow):
self._launch()
def _on_pay_with_ad_press(self) -> None:
from ba.internal import show_ad
from ba.internal import show_ad_2
# If we're already entering, ignore.
if self._entering:
@ -563,9 +563,8 @@ class TournamentEntryWindow(popup.PopupWindow):
cur_time = ba.time(ba.TimeType.REAL)
if cur_time - self._last_ad_press_time > 5.0:
self._last_ad_press_time = cur_time
show_ad('tournament_entry',
on_completion_call=ba.WeakCall(self._on_ad_complete),
pass_actually_showed=True)
show_ad_2('tournament_entry',
on_completion_call=ba.WeakCall(self._on_ad_complete))
def _on_ad_complete(self, actually_showed: bool) -> None:

View File

@ -333,15 +333,15 @@ class WatchWindow(ba.OldWindow):
autoselect=True,
maxwidth=c_width * 0.7,
max_chars=200)
cbtn = ba.buttonwidget(parent=cnt,
label=ba.Lstr(resource='cancelText'),
on_activate_call=ba.Call(
ba.containerwidget,
edit=cnt,
transition='out_scale'),
size=(180, 60),
position=(30, 30),
autoselect=True)
cbtn = ba.buttonwidget(
parent=cnt,
label=ba.Lstr(resource='cancelText'),
on_activate_call=ba.Call(
lambda c: ba.containerwidget(edit=c, transition='out_scale'),
cnt),
size=(180, 60),
position=(30, 30),
autoselect=True)
okb = ba.buttonwidget(parent=cnt,
label=ba.Lstr(resource=self._r + '.renameText'),
size=(180, 60),

View File

@ -85,7 +85,7 @@ def formatcode(projroot: Path, full: bool) -> None:
flush=True)
def cpplintcode(projroot: Path, full: bool) -> None:
def cpplint(projroot: Path, full: bool) -> None:
"""Run lint-checking on all code deemed lint-able."""
from concurrent.futures import ThreadPoolExecutor
from efrotools import get_config
@ -230,7 +230,7 @@ def get_script_filenames(projroot: Path) -> List[str]:
return sorted(list(f for f in filenames if 'flycheck_' not in f))
def pylintscripts(projroot: Path, full: bool, fast: bool) -> None:
def pylint(projroot: Path, full: bool, fast: bool) -> None:
"""Run lint-checking on all scripts deemed lint-able."""
from efrotools import get_files_hash
pylintrc = Path(projroot, '.pylintrc')
@ -516,7 +516,7 @@ def runmypy(filenames: List[str], full: bool = False,
subprocess.run(args, check=check)
def mypyscripts(projroot: Path, full: bool) -> None:
def mypy(projroot: Path, full: bool) -> None:
"""Run mypy on all of our scripts."""
import time
filenames = get_script_filenames(projroot)

View File

@ -137,9 +137,9 @@ def build_apple(arch: str, debug: bool = False) -> None:
txt = efrotools.replace_one(
txt, '&& PATH=$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/dist/bin:$(PATH) .',
'&& PATH="$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/dist/bin:$(PATH)" .')
txt = efrotools.replace_one(
txt, '&& PATH=$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/dist/bin:$(PATH) m',
'&& PATH="$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/dist/bin:$(PATH)" m')
# txt = efrotools.replace_one(
# txt, '&& PATH=$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/dist/bin:$(PATH) m',
# '&& PATH="$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/dist/bin:$(PATH)" m')
# Remove makefile dependencies so we don't build the
# libs we're not using.

View File

@ -226,11 +226,11 @@ def formatmakefile() -> None:
outfile.write(formatted)
def cpplintcode() -> None:
def cpplint() -> None:
"""Run lint-checking on all code deemed lint-able."""
from efrotools import code
full = '-full' in sys.argv
code.cpplintcode(PROJROOT, full)
code.cpplint(PROJROOT, full)
def scriptfiles() -> None:
@ -247,19 +247,19 @@ def scriptfiles() -> None:
print(' '.join(paths))
def pylintscripts() -> None:
def pylint() -> None:
"""Run pylint checks on our scripts."""
from efrotools import code
full = ('-full' in sys.argv)
fast = ('-fast' in sys.argv)
code.pylintscripts(PROJROOT, full, fast)
code.pylint(PROJROOT, full, fast)
def mypyscripts() -> None:
def mypy() -> None:
"""Run mypy checks on our scripts."""
from efrotools import code
full = ('-full' in sys.argv)
code.mypyscripts(PROJROOT, full)
code.mypy(PROJROOT, full)
def pycharmscripts() -> None:

View File

@ -43,10 +43,9 @@ import efrotools
# noinspection PyUnresolvedReferences
from efrotools.snippets import ( # pylint: disable=unused-import
PROJROOT, CleanError, snippets_main, formatcode, formatscripts,
formatmakefile, cpplintcode, pylintscripts, mypyscripts,
tool_config_install, sync, sync_all, scriptfiles, pycharmscripts,
clioncode, androidstudiocode, makefile_target_list, spelling, spelling_all,
compile_python_files)
formatmakefile, cpplint, pylint, mypy, tool_config_install, sync, sync_all,
scriptfiles, pycharmscripts, clioncode, androidstudiocode,
makefile_target_list, spelling, spelling_all, compile_python_files)
if TYPE_CHECKING:
from typing import Optional, List, Sequence