mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-06 23:59:18 +08:00
Added per-game team and player types
This commit is contained in:
parent
b1d7d96386
commit
1ccf33e41d
19
.idea/dictionaries/ericf.xml
generated
19
.idea/dictionaries/ericf.xml
generated
@ -319,6 +319,7 @@
|
|||||||
<w>cnode</w>
|
<w>cnode</w>
|
||||||
<w>codecsmodule</w>
|
<w>codecsmodule</w>
|
||||||
<w>codefilenames</w>
|
<w>codefilenames</w>
|
||||||
|
<w>codefiles</w>
|
||||||
<w>codehash</w>
|
<w>codehash</w>
|
||||||
<w>codeop</w>
|
<w>codeop</w>
|
||||||
<w>collapsable</w>
|
<w>collapsable</w>
|
||||||
@ -727,8 +728,10 @@
|
|||||||
<w>gamepads</w>
|
<w>gamepads</w>
|
||||||
<w>gamepadselect</w>
|
<w>gamepadselect</w>
|
||||||
<w>gameplay</w>
|
<w>gameplay</w>
|
||||||
|
<w>gameplayer</w>
|
||||||
<w>gameport</w>
|
<w>gameport</w>
|
||||||
<w>gameresults</w>
|
<w>gameresults</w>
|
||||||
|
<w>gameteam</w>
|
||||||
<w>gametype</w>
|
<w>gametype</w>
|
||||||
<w>gametypes</w>
|
<w>gametypes</w>
|
||||||
<w>gameutils</w>
|
<w>gameutils</w>
|
||||||
@ -762,6 +765,7 @@
|
|||||||
<w>getsession</w>
|
<w>getsession</w>
|
||||||
<w>getsockname</w>
|
<w>getsockname</w>
|
||||||
<w>getsound</w>
|
<w>getsound</w>
|
||||||
|
<w>getspaz</w>
|
||||||
<w>getstarttime</w>
|
<w>getstarttime</w>
|
||||||
<w>gettext</w>
|
<w>gettext</w>
|
||||||
<w>gettexture</w>
|
<w>gettexture</w>
|
||||||
@ -893,6 +897,7 @@
|
|||||||
<w>inputfiles</w>
|
<w>inputfiles</w>
|
||||||
<w>inputhash</w>
|
<w>inputhash</w>
|
||||||
<w>inputnode</w>
|
<w>inputnode</w>
|
||||||
|
<w>inputtype</w>
|
||||||
<w>inpututils</w>
|
<w>inpututils</w>
|
||||||
<w>inspectdir</w>
|
<w>inspectdir</w>
|
||||||
<w>insta</w>
|
<w>insta</w>
|
||||||
@ -1319,6 +1324,7 @@
|
|||||||
<w>pipname</w>
|
<w>pipname</w>
|
||||||
<w>pkey</w>
|
<w>pkey</w>
|
||||||
<w>pkgutil</w>
|
<w>pkgutil</w>
|
||||||
|
<w>playercast</w>
|
||||||
<w>playerdata</w>
|
<w>playerdata</w>
|
||||||
<w>playerlostspaz</w>
|
<w>playerlostspaz</w>
|
||||||
<w>playernode</w>
|
<w>playernode</w>
|
||||||
@ -1326,6 +1332,8 @@
|
|||||||
<w>playerpts</w>
|
<w>playerpts</w>
|
||||||
<w>playerrec</w>
|
<w>playerrec</w>
|
||||||
<w>playerspaz</w>
|
<w>playerspaz</w>
|
||||||
|
<w>playerteamdata</w>
|
||||||
|
<w>playertype</w>
|
||||||
<w>playerval</w>
|
<w>playerval</w>
|
||||||
<w>playlistui</w>
|
<w>playlistui</w>
|
||||||
<w>playmode</w>
|
<w>playmode</w>
|
||||||
@ -1358,6 +1366,7 @@
|
|||||||
<w>positionadjusted</w>
|
<w>positionadjusted</w>
|
||||||
<w>posixpath</w>
|
<w>posixpath</w>
|
||||||
<w>posixsubprocess</w>
|
<w>posixsubprocess</w>
|
||||||
|
<w>postinit</w>
|
||||||
<w>poststr</w>
|
<w>poststr</w>
|
||||||
<w>powerdown</w>
|
<w>powerdown</w>
|
||||||
<w>powersgiven</w>
|
<w>powersgiven</w>
|
||||||
@ -1368,6 +1377,7 @@
|
|||||||
<w>poweruptype</w>
|
<w>poweruptype</w>
|
||||||
<w>powervr</w>
|
<w>powervr</w>
|
||||||
<w>ppos</w>
|
<w>ppos</w>
|
||||||
|
<w>pproxy</w>
|
||||||
<w>pragmas</w>
|
<w>pragmas</w>
|
||||||
<w>prch</w>
|
<w>prch</w>
|
||||||
<w>prec</w>
|
<w>prec</w>
|
||||||
@ -1547,6 +1557,7 @@
|
|||||||
<w>runmypy</w>
|
<w>runmypy</w>
|
||||||
<w>runonly</w>
|
<w>runonly</w>
|
||||||
<w>runpy</w>
|
<w>runpy</w>
|
||||||
|
<w>runpylint</w>
|
||||||
<w>runswindows</w>
|
<w>runswindows</w>
|
||||||
<w>rval</w>
|
<w>rval</w>
|
||||||
<w>safecolor</w>
|
<w>safecolor</w>
|
||||||
@ -1610,6 +1621,8 @@
|
|||||||
<w>sessiondata</w>
|
<w>sessiondata</w>
|
||||||
<w>sessionglobals</w>
|
<w>sessionglobals</w>
|
||||||
<w>sessionname</w>
|
<w>sessionname</w>
|
||||||
|
<w>sessionplayer</w>
|
||||||
|
<w>sessionteam</w>
|
||||||
<w>sessiontype</w>
|
<w>sessiontype</w>
|
||||||
<w>setalpha</w>
|
<w>setalpha</w>
|
||||||
<w>setbuild</w>
|
<w>setbuild</w>
|
||||||
@ -1685,6 +1698,7 @@
|
|||||||
<w>srcnode</w>
|
<w>srcnode</w>
|
||||||
<w>srcpath</w>
|
<w>srcpath</w>
|
||||||
<w>srcpathfull</w>
|
<w>srcpathfull</w>
|
||||||
|
<w>srcplayer</w>
|
||||||
<w>srcpy</w>
|
<w>srcpy</w>
|
||||||
<w>srcpydata</w>
|
<w>srcpydata</w>
|
||||||
<w>srcstr</w>
|
<w>srcstr</w>
|
||||||
@ -1720,6 +1734,7 @@
|
|||||||
<w>strftime</w>
|
<w>strftime</w>
|
||||||
<w>stringprep</w>
|
<w>stringprep</w>
|
||||||
<w>stringptr</w>
|
<w>stringptr</w>
|
||||||
|
<w>strippable</w>
|
||||||
<w>strobing</w>
|
<w>strobing</w>
|
||||||
<w>strptime</w>
|
<w>strptime</w>
|
||||||
<w>strt</w>
|
<w>strt</w>
|
||||||
@ -1785,11 +1800,13 @@
|
|||||||
<w>tdelay</w>
|
<w>tdelay</w>
|
||||||
<w>tdval</w>
|
<w>tdval</w>
|
||||||
<w>teambasesession</w>
|
<w>teambasesession</w>
|
||||||
|
<w>teamdata</w>
|
||||||
<w>teamgame</w>
|
<w>teamgame</w>
|
||||||
<w>teamnamescolors</w>
|
<w>teamnamescolors</w>
|
||||||
<w>teamsize</w>
|
<w>teamsize</w>
|
||||||
<w>teamsscorescreen</w>
|
<w>teamsscorescreen</w>
|
||||||
<w>teamssession</w>
|
<w>teamssession</w>
|
||||||
|
<w>teamtype</w>
|
||||||
<w>teeeeeeeesssssttttdddddddd</w>
|
<w>teeeeeeeesssssttttdddddddd</w>
|
||||||
<w>teehee</w>
|
<w>teehee</w>
|
||||||
<w>teleporting</w>
|
<w>teleporting</w>
|
||||||
@ -1867,11 +1884,13 @@
|
|||||||
<w>toplevel</w>
|
<w>toplevel</w>
|
||||||
<w>totaldudes</w>
|
<w>totaldudes</w>
|
||||||
<w>totalpts</w>
|
<w>totalpts</w>
|
||||||
|
<w>totype</w>
|
||||||
<w>touchpad</w>
|
<w>touchpad</w>
|
||||||
<w>tournamententry</w>
|
<w>tournamententry</w>
|
||||||
<w>tournamentscores</w>
|
<w>tournamentscores</w>
|
||||||
<w>tplayer</w>
|
<w>tplayer</w>
|
||||||
<w>tpos</w>
|
<w>tpos</w>
|
||||||
|
<w>tproxy</w>
|
||||||
<w>tracebacks</w>
|
<w>tracebacks</w>
|
||||||
<w>tracemalloc</w>
|
<w>tracemalloc</w>
|
||||||
<w>tradeoff</w>
|
<w>tradeoff</w>
|
||||||
|
|||||||
@ -50,6 +50,9 @@
|
|||||||
- The bs.Vector class is no more; in its place is a shiny new ba.Vec3 which is implemented internally in C++ so its nice and speedy. Will probably update certain things like vector node attrs to support this class in the future since it makes vector math nice and convenient.
|
- The bs.Vector class is no more; in its place is a shiny new ba.Vec3 which is implemented internally in C++ so its nice and speedy. Will probably update certain things like vector node attrs to support this class in the future since it makes vector math nice and convenient.
|
||||||
- Ok you get the point..
|
- Ok you get the point..
|
||||||
|
|
||||||
|
### 1.4.155 (14377)
|
||||||
|
- Added protection against a repeated-input attack in lobbies.
|
||||||
|
|
||||||
### 1.4.151 (14371)
|
### 1.4.151 (14371)
|
||||||
- Added Chinese-Traditional language and improved translations for others.
|
- Added Chinese-Traditional language and improved translations for others.
|
||||||
|
|
||||||
|
|||||||
39
Makefile
39
Makefile
@ -25,7 +25,7 @@
|
|||||||
# Targets in this top level Makefile do not expect -jX to be passed to them
|
# Targets in this top level Makefile do not expect -jX to be passed to them
|
||||||
# and generally handle spawning an appropriate number of child jobs themselves.
|
# and generally handle spawning an appropriate number of child jobs themselves.
|
||||||
|
|
||||||
# Prefix used for output of docs/changelogs/etc targets for use in webpages.
|
# Prefix used for output of docs/changelogs/etc. targets for use in webpages.
|
||||||
DOCPREFIX = "ballisticacore_"
|
DOCPREFIX = "ballisticacore_"
|
||||||
|
|
||||||
|
|
||||||
@ -475,12 +475,12 @@ update-check: prereqs
|
|||||||
# Run formatting on all files in the project considered 'dirty'.
|
# Run formatting on all files in the project considered 'dirty'.
|
||||||
format:
|
format:
|
||||||
@${MAKE} -j3 format-code format-scripts format-makefile
|
@${MAKE} -j3 format-code format-scripts format-makefile
|
||||||
@echo Formatting complete!
|
@tools/snippets echo GRN Formatting complete!
|
||||||
|
|
||||||
# Same but always formats; ignores dirty state.
|
# Same but always formats; ignores dirty state.
|
||||||
format-full:
|
format-full:
|
||||||
@${MAKE} -j3 format-code-full format-scripts-full format-makefile
|
@${MAKE} -j3 format-code-full format-scripts-full format-makefile
|
||||||
@echo Formatting complete!
|
@tools/snippets echo GRN Formatting complete!
|
||||||
|
|
||||||
# Run formatting for compiled code sources (.cc, .h, etc.).
|
# Run formatting for compiled code sources (.cc, .h, etc.).
|
||||||
format-code: prereqs
|
format-code: prereqs
|
||||||
@ -515,22 +515,22 @@ format-makefile: prereqs
|
|||||||
# Run all project checks. (static analysis)
|
# Run all project checks. (static analysis)
|
||||||
check: update-check
|
check: update-check
|
||||||
@${MAKE} -j3 cpplint pylint mypy
|
@${MAKE} -j3 cpplint pylint mypy
|
||||||
@echo ALL CHECKS PASSED!
|
@tools/snippets echo GRN ALL CHECKS PASSED!
|
||||||
|
|
||||||
# Same as check but no caching (all files are checked).
|
# Same as check but no caching (all files are checked).
|
||||||
check-full: update-check
|
check-full: update-check
|
||||||
@${MAKE} -j3 cpplint-full pylint-full mypy-full
|
@${MAKE} -j3 cpplint-full pylint-full mypy-full
|
||||||
@echo ALL CHECKS PASSED!
|
@tools/snippets echo GRN ALL CHECKS PASSED!
|
||||||
|
|
||||||
# Same as 'check' plus optional/slow extra checks.
|
# Same as 'check' plus optional/slow extra checks.
|
||||||
check2: update-check
|
check2: update-check
|
||||||
@${MAKE} -j4 cpplint pylint mypy pycharm
|
@${MAKE} -j4 cpplint pylint mypy pycharm
|
||||||
@echo ALL CHECKS PASSED!
|
@tools/snippets echo GRN ALL CHECKS PASSED!
|
||||||
|
|
||||||
# Same as check2 but no caching (all files are checked).
|
# Same as check2 but no caching (all files are checked).
|
||||||
check2-full: update-check
|
check2-full: update-check
|
||||||
@${MAKE} -j4 cpplint-full pylint-full mypy-full pycharm-full
|
@${MAKE} -j4 cpplint-full pylint-full mypy-full pycharm-full
|
||||||
@echo ALL CHECKS PASSED!
|
@tools/snippets echo GRN ALL CHECKS PASSED!
|
||||||
|
|
||||||
# Run Cpplint checks on all C/C++ code.
|
# Run Cpplint checks on all C/C++ code.
|
||||||
cpplint: prereqs
|
cpplint: prereqs
|
||||||
@ -586,6 +586,7 @@ pycharm-full: prereqs
|
|||||||
|
|
||||||
# Run all tests. (live execution verification)
|
# Run all tests. (live execution verification)
|
||||||
test: prereqs
|
test: prereqs
|
||||||
|
@tools/snippets echo BLU Running all tests...
|
||||||
@tools/snippets pytest -v tests
|
@tools/snippets pytest -v tests
|
||||||
|
|
||||||
# Run tests with any caching disabled.
|
# Run tests with any caching disabled.
|
||||||
@ -615,28 +616,28 @@ preflight:
|
|||||||
@${MAKE} format
|
@${MAKE} format
|
||||||
@${MAKE} update
|
@${MAKE} update
|
||||||
@${MAKE} -j4 cpplint pylint mypy test
|
@${MAKE} -j4 cpplint pylint mypy test
|
||||||
@echo PREFLIGHT SUCCESSFUL!
|
@tools/snippets echo SGRN BLD PREFLIGHT SUCCESSFUL!
|
||||||
|
|
||||||
# Same as 'preflight' without caching (all files are visited).
|
# Same as 'preflight' without caching (all files are visited).
|
||||||
preflight-full:
|
preflight-full:
|
||||||
@${MAKE} format-full
|
@${MAKE} format-full
|
||||||
@${MAKE} update
|
@${MAKE} update
|
||||||
@${MAKE} -j4 cpplint-full pylint-full mypy-full test-full
|
@${MAKE} -j4 cpplint-full pylint-full mypy-full test-full
|
||||||
@echo PREFLIGHT SUCCESSFUL!
|
@tools/snippets echo SGRN BLD PREFLIGHT SUCCESSFUL!
|
||||||
|
|
||||||
# Same as 'preflight' plus optional/slow extra checks.
|
# Same as 'preflight' plus optional/slow extra checks.
|
||||||
preflight2:
|
preflight2:
|
||||||
@${MAKE} format
|
@${MAKE} format
|
||||||
@${MAKE} update
|
@${MAKE} update
|
||||||
@${MAKE} -j5 cpplint pylint mypy pycharm test
|
@${MAKE} -j5 cpplint pylint mypy pycharm test
|
||||||
@echo PREFLIGHT SUCCESSFUL!
|
@tools/snippets echo SGRN BLD PREFLIGHT SUCCESSFUL!
|
||||||
|
|
||||||
# Same as 'preflight2' but without caching (all files visited).
|
# Same as 'preflight2' but without caching (all files visited).
|
||||||
preflight2-full:
|
preflight2-full:
|
||||||
@${MAKE} format-full
|
@${MAKE} format-full
|
||||||
@${MAKE} update
|
@${MAKE} update
|
||||||
@${MAKE} -j5 cpplint-full pylint-full mypy-full pycharm-full test-full
|
@${MAKE} -j5 cpplint-full pylint-full mypy-full pycharm-full test-full
|
||||||
@echo PREFLIGHT SUCCESSFUL!
|
@tools/snippets echo SGRN BLD PREFLIGHT SUCCESSFUL!
|
||||||
|
|
||||||
# Tell make which of these targets don't represent files.
|
# Tell make which of these targets don't represent files.
|
||||||
.PHONY: preflight preflight-full preflight2 preflight2-full
|
.PHONY: preflight preflight-full preflight2 preflight2-full
|
||||||
@ -675,28 +676,28 @@ TOOL_CFG_SRC = tools/efrotools/snippets.py config/config.json
|
|||||||
ENV_SRC = tools/snippets tools/batools/build.py
|
ENV_SRC = tools/snippets tools/batools/build.py
|
||||||
|
|
||||||
.clang-format: config/toolconfigsrc/clang-format ${TOOL_CFG_SRC}
|
.clang-format: config/toolconfigsrc/clang-format ${TOOL_CFG_SRC}
|
||||||
${TOOL_CFG_INST} $< $@
|
@${TOOL_CFG_INST} $< $@
|
||||||
|
|
||||||
.style.yapf: config/toolconfigsrc/style.yapf ${TOOL_CFG_SRC}
|
.style.yapf: config/toolconfigsrc/style.yapf ${TOOL_CFG_SRC}
|
||||||
${TOOL_CFG_INST} $< $@
|
@${TOOL_CFG_INST} $< $@
|
||||||
|
|
||||||
.pylintrc: config/toolconfigsrc/pylintrc ${TOOL_CFG_SRC}
|
.pylintrc: config/toolconfigsrc/pylintrc ${TOOL_CFG_SRC}
|
||||||
${TOOL_CFG_INST} $< $@
|
@${TOOL_CFG_INST} $< $@
|
||||||
|
|
||||||
.projectile: config/toolconfigsrc/projectile ${TOOL_CFG_SRC}
|
.projectile: config/toolconfigsrc/projectile ${TOOL_CFG_SRC}
|
||||||
${TOOL_CFG_INST} $< $@
|
@${TOOL_CFG_INST} $< $@
|
||||||
|
|
||||||
.editorconfig: config/toolconfigsrc/editorconfig ${TOOL_CFG_SRC}
|
.editorconfig: config/toolconfigsrc/editorconfig ${TOOL_CFG_SRC}
|
||||||
${TOOL_CFG_INST} $< $@
|
@${TOOL_CFG_INST} $< $@
|
||||||
|
|
||||||
.dir-locals.el: config/toolconfigsrc/dir-locals.el ${TOOL_CFG_SRC}
|
.dir-locals.el: config/toolconfigsrc/dir-locals.el ${TOOL_CFG_SRC}
|
||||||
${TOOL_CFG_INST} $< $@
|
@${TOOL_CFG_INST} $< $@
|
||||||
|
|
||||||
.mypy.ini: config/toolconfigsrc/mypy.ini ${TOOL_CFG_SRC}
|
.mypy.ini: config/toolconfigsrc/mypy.ini ${TOOL_CFG_SRC}
|
||||||
${TOOL_CFG_INST} $< $@
|
@${TOOL_CFG_INST} $< $@
|
||||||
|
|
||||||
.pycheckers: config/toolconfigsrc/pycheckers ${TOOL_CFG_SRC}
|
.pycheckers: config/toolconfigsrc/pycheckers ${TOOL_CFG_SRC}
|
||||||
${TOOL_CFG_INST} $< $@
|
@${TOOL_CFG_INST} $< $@
|
||||||
|
|
||||||
# Include anything as sources here that should require
|
# Include anything as sources here that should require
|
||||||
.cache/checkenv: ${ENV_SRC}
|
.cache/checkenv: ${ENV_SRC}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# (hash we can use to see if this file is out of date)
|
# (hash we can use to see if this file is out of date)
|
||||||
# SOURCES_HASH=233656876767477563471907026700832716822
|
# SOURCES_HASH=266649817838802754126771358652920545389
|
||||||
|
|
||||||
# I'm sorry Pylint. I know this file saddens you. Be strong.
|
# I'm sorry Pylint. I know this file saddens you. Be strong.
|
||||||
# pylint: disable=useless-suppression
|
# pylint: disable=useless-suppression
|
||||||
@ -259,7 +259,7 @@ class InputDevice:
|
|||||||
allows_configuring: bool
|
allows_configuring: bool
|
||||||
Whether the input-device can be configured.
|
Whether the input-device can be configured.
|
||||||
|
|
||||||
player: Optional[ba.Player]
|
player: Optional[ba.SessionPlayer]
|
||||||
The player associated with this input device.
|
The player associated with this input device.
|
||||||
|
|
||||||
client_id: int
|
client_id: int
|
||||||
@ -292,7 +292,7 @@ class InputDevice:
|
|||||||
"""
|
"""
|
||||||
exists: bool
|
exists: bool
|
||||||
allows_configuring: bool
|
allows_configuring: bool
|
||||||
player: Optional[ba.Player]
|
player: Optional[ba.SessionPlayer]
|
||||||
client_id: int
|
client_id: int
|
||||||
name: str
|
name: str
|
||||||
unique_identifier: str
|
unique_identifier: str
|
||||||
@ -648,6 +648,14 @@ class Node:
|
|||||||
billboard_texture: Optional[ba.Texture] = None
|
billboard_texture: Optional[ba.Texture] = None
|
||||||
billboard_cross_out: bool = False
|
billboard_cross_out: bool = False
|
||||||
billboard_opacity: float = 0.0
|
billboard_opacity: float = 0.0
|
||||||
|
slow_motion: bool = False
|
||||||
|
music: str = ''
|
||||||
|
vr_camera_offset: Sequence[float] = (0.0, 0.0, 0.0)
|
||||||
|
vr_overlay_center: Sequence[float] = (0.0, 0.0, 0.0)
|
||||||
|
vr_overlay_center_enabled: bool = False
|
||||||
|
vignette_outer: Sequence[float] = (0.0, 0.0)
|
||||||
|
vignette_inner: Sequence[float] = (0.0, 0.0)
|
||||||
|
tint: Sequence[float] = (1.0, 1.0, 1.0)
|
||||||
|
|
||||||
def add_death_action(self, action: Callable[[], None]) -> None:
|
def add_death_action(self, action: Callable[[], None]) -> None:
|
||||||
"""add_death_action(action: Callable[[], None]) -> None
|
"""add_death_action(action: Callable[[], None]) -> None
|
||||||
@ -737,31 +745,36 @@ class Node:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Player:
|
class SessionData:
|
||||||
"""A reference to a player in the game.
|
"""(internal)"""
|
||||||
|
|
||||||
|
def exists(self) -> bool:
|
||||||
|
"""exists() -> bool
|
||||||
|
|
||||||
|
Returns whether the SessionData still exists.
|
||||||
|
Most functionality will fail on a nonexistent instance.
|
||||||
|
"""
|
||||||
|
return bool()
|
||||||
|
|
||||||
|
|
||||||
|
class SessionPlayer:
|
||||||
|
"""A reference to a player in the ba.Session.
|
||||||
|
|
||||||
Category: Gameplay Classes
|
Category: Gameplay Classes
|
||||||
|
|
||||||
These are created and managed internally and
|
These are created and managed internally and
|
||||||
provided to your Session/Activity instances.
|
provided to your Session/Activity instances.
|
||||||
Be aware that, like ba.Nodes, ba.Player objects are 'weak'
|
Be aware that, like ba.Nodes, ba.SessionPlayer objects are 'weak'
|
||||||
references under-the-hood; a player can leave the game at
|
references under-the-hood; a player can leave the game at
|
||||||
any point. For this reason, you should make judicious use of the
|
any point. For this reason, you should make judicious use of the
|
||||||
ba.Player.exists attribute (or boolean operator) to ensure that a
|
ba.SessionPlayer.exists attribute (or boolean operator) to ensure
|
||||||
Player is still present if retaining references to one for any
|
that a SessionPlayer is still present if retaining references to one
|
||||||
length of time.
|
for any length of time.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
|
||||||
actor: Optional[ba.Actor]
|
id: int
|
||||||
The current ba.Actor associated with this Player.
|
The unique numeric ID of the Player.
|
||||||
This may be None
|
|
||||||
|
|
||||||
node: Optional[ba.Node]
|
|
||||||
A ba.Node of type 'player' associated with this Player.
|
|
||||||
This Node exists in the currently active game and can be used
|
|
||||||
to get a generic player position/etc.
|
|
||||||
This will be None if the Player is not in a game.
|
|
||||||
|
|
||||||
exists: bool
|
exists: bool
|
||||||
Whether the player still exists.
|
Whether the player still exists.
|
||||||
@ -775,10 +788,10 @@ class Player:
|
|||||||
This bool value will be True once the Player has completed
|
This bool value will be True once the Player has completed
|
||||||
any lobby character/team selection.
|
any lobby character/team selection.
|
||||||
|
|
||||||
team: ba.Team
|
team: ba.SessionTeam
|
||||||
The ba.Team this Player is on. If the Player is
|
The ba.SessionTeam this Player is on. If the SessionPlayer
|
||||||
still in its lobby selecting a team/etc. then a
|
is still in its lobby selecting a team/etc. then a
|
||||||
ba.TeamNotFoundError will be raised.
|
ba.SessionTeamNotFoundError will be raised.
|
||||||
|
|
||||||
sessiondata: Dict
|
sessiondata: Dict
|
||||||
A dict for use by the current ba.Session for
|
A dict for use by the current ba.Session for
|
||||||
@ -792,7 +805,7 @@ class Player:
|
|||||||
|
|
||||||
color: Sequence[float]
|
color: Sequence[float]
|
||||||
The base color for this Player.
|
The base color for this Player.
|
||||||
In team games this will match the ba.Team's color.
|
In team games this will match the ba.SessionTeam's color.
|
||||||
|
|
||||||
highlight: Sequence[float]
|
highlight: Sequence[float]
|
||||||
A secondary color for this player.
|
A secondary color for this player.
|
||||||
@ -802,17 +815,20 @@ class Player:
|
|||||||
|
|
||||||
character: str
|
character: str
|
||||||
The character this player has selected in their profile.
|
The character this player has selected in their profile.
|
||||||
|
|
||||||
|
gameplayer: Optional[ba.Player]
|
||||||
|
The current game-specific instance for this player.
|
||||||
"""
|
"""
|
||||||
actor: Optional[ba.Actor]
|
id: int
|
||||||
node: Optional[ba.Node]
|
|
||||||
exists: bool
|
exists: bool
|
||||||
in_game: bool
|
in_game: bool
|
||||||
team: ba.Team
|
team: ba.SessionTeam
|
||||||
sessiondata: Dict
|
sessiondata: Dict
|
||||||
gamedata: Dict
|
gamedata: Dict
|
||||||
color: Sequence[float]
|
color: Sequence[float]
|
||||||
highlight: Sequence[float]
|
highlight: Sequence[float]
|
||||||
character: str
|
character: str
|
||||||
|
gameplayer: Optional[ba.Player]
|
||||||
|
|
||||||
def assign_input_call(self, type: Union[str, Tuple[str, ...]],
|
def assign_input_call(self, type: Union[str, Tuple[str, ...]],
|
||||||
call: Callable) -> None:
|
call: Callable) -> None:
|
||||||
@ -855,13 +871,6 @@ class Player:
|
|||||||
"""
|
"""
|
||||||
return {'foo': 'bar'}
|
return {'foo': 'bar'}
|
||||||
|
|
||||||
def get_id(self) -> int:
|
|
||||||
"""get_id() -> int
|
|
||||||
|
|
||||||
Returns the unique numeric player ID for this player.
|
|
||||||
"""
|
|
||||||
return int()
|
|
||||||
|
|
||||||
def get_input_device(self) -> ba.InputDevice:
|
def get_input_device(self) -> ba.InputDevice:
|
||||||
"""get_input_device() -> ba.InputDevice
|
"""get_input_device() -> ba.InputDevice
|
||||||
|
|
||||||
@ -878,14 +887,6 @@ class Player:
|
|||||||
"""
|
"""
|
||||||
return str()
|
return str()
|
||||||
|
|
||||||
def is_alive(self) -> bool:
|
|
||||||
"""is_alive() -> bool
|
|
||||||
|
|
||||||
Returns True if the player has a ba.Actor assigned and its
|
|
||||||
is_alive() method return True. False is returned otherwise.
|
|
||||||
"""
|
|
||||||
return bool()
|
|
||||||
|
|
||||||
def remove_from_game(self) -> None:
|
def remove_from_game(self) -> None:
|
||||||
"""remove_from_game() -> None
|
"""remove_from_game() -> None
|
||||||
|
|
||||||
@ -914,17 +915,10 @@ class Player:
|
|||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_actor(self, actor: Optional[ba.Actor]) -> None:
|
def set_data(self, team: ba.SessionTeam, character: str,
|
||||||
"""set_actor(actor: Optional[ba.Actor]) -> None
|
color: Sequence[float], highlight: Sequence[float]) -> None:
|
||||||
|
"""set_data(team: ba.SessionTeam, character: str,
|
||||||
Set the player's associated ba.Actor.
|
color: Sequence[float], highlight: Sequence[float]) -> None
|
||||||
"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
def set_data(self, team: ba.Team, character: str, color: Sequence[float],
|
|
||||||
highlight: Sequence[float]) -> None:
|
|
||||||
"""set_data(team: ba.Team, character: str, color: Sequence[float],
|
|
||||||
highlight: Sequence[float]) -> None
|
|
||||||
|
|
||||||
(internal)
|
(internal)
|
||||||
"""
|
"""
|
||||||
@ -961,18 +955,6 @@ class Player:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class SessionData:
|
|
||||||
"""(internal)"""
|
|
||||||
|
|
||||||
def exists(self) -> bool:
|
|
||||||
"""exists() -> bool
|
|
||||||
|
|
||||||
Returns whether the SessionData still exists.
|
|
||||||
Most functionality will fail on a nonexistent instance.
|
|
||||||
"""
|
|
||||||
return bool()
|
|
||||||
|
|
||||||
|
|
||||||
class Sound:
|
class Sound:
|
||||||
"""A reference to a sound.
|
"""A reference to a sound.
|
||||||
|
|
||||||
@ -2011,7 +1993,7 @@ def get_foreground_host_activity() -> Optional[ba.Activity]:
|
|||||||
is none.
|
is none.
|
||||||
"""
|
"""
|
||||||
import ba # pylint: disable=cyclic-import
|
import ba # pylint: disable=cyclic-import
|
||||||
return ba.Activity({})
|
return ba.Activity(settings={})
|
||||||
|
|
||||||
|
|
||||||
def get_foreground_host_session() -> Optional[ba.Session]:
|
def get_foreground_host_session() -> Optional[ba.Session]:
|
||||||
@ -2348,7 +2330,7 @@ def getactivity(doraise: bool = True) -> ba.Activity:
|
|||||||
False then None is returned instead.
|
False then None is returned instead.
|
||||||
"""
|
"""
|
||||||
import ba # pylint: disable=cyclic-import
|
import ba # pylint: disable=cyclic-import
|
||||||
return ba.Activity({})
|
return ba.Activity(settings={})
|
||||||
|
|
||||||
|
|
||||||
def getcollidemodel(name: str) -> ba.CollideModel:
|
def getcollidemodel(name: str) -> ba.CollideModel:
|
||||||
@ -2905,7 +2887,7 @@ def new_activity(activity_type: Type[ba.Activity],
|
|||||||
instantiated; You must go through this function.
|
instantiated; You must go through this function.
|
||||||
"""
|
"""
|
||||||
import ba # pylint: disable=cyclic-import
|
import ba # pylint: disable=cyclic-import
|
||||||
return ba.Activity({})
|
return ba.Activity(settings={})
|
||||||
|
|
||||||
|
|
||||||
def new_host_session(sessiontype: Type[ba.Session],
|
def new_host_session(sessiontype: Type[ba.Session],
|
||||||
|
|||||||
@ -29,8 +29,8 @@ In some specific cases you may need to pull in individual submodules instead.
|
|||||||
# pylint: disable=redefined-builtin
|
# pylint: disable=redefined-builtin
|
||||||
|
|
||||||
from _ba import (CollideModel, Context, ContextCall, Data, InputDevice,
|
from _ba import (CollideModel, Context, ContextCall, Data, InputDevice,
|
||||||
Material, Model, Node, Player, Sound, Texture, Timer, Vec3,
|
Material, Model, Node, SessionPlayer, Sound, Texture, Timer,
|
||||||
Widget, buttonwidget, camerashake, checkboxwidget,
|
Vec3, Widget, buttonwidget, camerashake, checkboxwidget,
|
||||||
columnwidget, containerwidget, do_once, emitfx,
|
columnwidget, containerwidget, do_once, emitfx,
|
||||||
get_collision_info, getactivity, getcollidemodel, getmodel,
|
get_collision_info, getactivity, getcollidemodel, getmodel,
|
||||||
getnodes, getsession, getsound, gettexture, hscrollwidget,
|
getnodes, getsession, getsound, gettexture, hscrollwidget,
|
||||||
@ -40,7 +40,7 @@ from _ba import (CollideModel, Context, ContextCall, Data, InputDevice,
|
|||||||
charstr, textwidget, time, timer, open_url, widget)
|
charstr, textwidget, time, timer, open_url, widget)
|
||||||
from ba._activity import Activity
|
from ba._activity import Activity
|
||||||
from ba._actor import Actor
|
from ba._actor import Actor
|
||||||
from ba._player import BasePlayerData
|
from ba._player import Player, playercast, playercast_o
|
||||||
from ba._nodeactor import NodeActor
|
from ba._nodeactor import NodeActor
|
||||||
from ba._app import App
|
from ba._app import App
|
||||||
from ba._coopgame import CoopGameActivity
|
from ba._coopgame import CoopGameActivity
|
||||||
@ -48,11 +48,12 @@ from ba._coopsession import CoopSession
|
|||||||
from ba._dependency import (Dependency, DependencyComponent, DependencySet,
|
from ba._dependency import (Dependency, DependencyComponent, DependencySet,
|
||||||
AssetPackage)
|
AssetPackage)
|
||||||
from ba._enums import TimeType, Permission, TimeFormat, SpecialChar
|
from ba._enums import TimeType, Permission, TimeFormat, SpecialChar
|
||||||
from ba._error import (UNHANDLED, print_exception, print_error, NotFoundError,
|
from ba._error import (print_exception, print_error, NotFoundError,
|
||||||
PlayerNotFoundError, NodeNotFoundError,
|
PlayerNotFoundError, SessionPlayerNotFoundError,
|
||||||
ActorNotFoundError, InputDeviceNotFoundError,
|
NodeNotFoundError, ActorNotFoundError,
|
||||||
WidgetNotFoundError, ActivityNotFoundError,
|
InputDeviceNotFoundError, WidgetNotFoundError,
|
||||||
TeamNotFoundError, SessionNotFoundError,
|
ActivityNotFoundError, TeamNotFoundError,
|
||||||
|
SessionTeamNotFoundError, SessionNotFoundError,
|
||||||
DependencyError)
|
DependencyError)
|
||||||
from ba._freeforallsession import FreeForAllSession
|
from ba._freeforallsession import FreeForAllSession
|
||||||
from ba._gameactivity import GameActivity
|
from ba._gameactivity import GameActivity
|
||||||
@ -63,7 +64,7 @@ from ba._session import Session
|
|||||||
from ba._servermode import ServerController
|
from ba._servermode import ServerController
|
||||||
from ba._score import ScoreType, ScoreInfo
|
from ba._score import ScoreType, ScoreInfo
|
||||||
from ba._stats import PlayerScoredMessage, PlayerRecord, Stats
|
from ba._stats import PlayerScoredMessage, PlayerRecord, Stats
|
||||||
from ba._team import Team
|
from ba._team import SessionTeam, Team
|
||||||
from ba._teamgame import TeamGameActivity
|
from ba._teamgame import TeamGameActivity
|
||||||
from ba._dualteamsession import DualTeamSession
|
from ba._dualteamsession import DualTeamSession
|
||||||
from ba._achievement import Achievement
|
from ba._achievement import Achievement
|
||||||
@ -77,7 +78,7 @@ from ba._general import WeakCall, Call
|
|||||||
from ba._level import Level
|
from ba._level import Level
|
||||||
from ba._lobby import Lobby, Chooser
|
from ba._lobby import Lobby, Chooser
|
||||||
from ba._math import normalized_color, is_point_in_box, vec3validate
|
from ba._math import normalized_color, is_point_in_box, vec3validate
|
||||||
from ba._messages import (OutOfBoundsMessage, DeathType, DieMessage,
|
from ba._messages import (UNHANDLED, OutOfBoundsMessage, DeathType, DieMessage,
|
||||||
StandMessage, PickUpMessage, DropMessage,
|
StandMessage, PickUpMessage, DropMessage,
|
||||||
PickedUpMessage, DroppedMessage,
|
PickedUpMessage, DroppedMessage,
|
||||||
ShouldShatterMessage, ImpactDamageMessage,
|
ShouldShatterMessage, ImpactDamageMessage,
|
||||||
|
|||||||
@ -22,10 +22,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import weakref
|
import weakref
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Generic, TypeVar
|
||||||
|
|
||||||
import _ba
|
from ba._team import Team
|
||||||
|
from ba._player import Player
|
||||||
|
from ba._error import print_exception, print_error, SessionTeamNotFoundError
|
||||||
from ba._dependency import DependencyComponent
|
from ba._dependency import DependencyComponent
|
||||||
|
import _ba
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from weakref import ReferenceType
|
from weakref import ReferenceType
|
||||||
@ -33,8 +36,11 @@ if TYPE_CHECKING:
|
|||||||
import ba
|
import ba
|
||||||
from bastd.actor.respawnicon import RespawnIcon
|
from bastd.actor.respawnicon import RespawnIcon
|
||||||
|
|
||||||
|
PlayerType = TypeVar('PlayerType', bound=Player)
|
||||||
|
TeamType = TypeVar('TeamType', bound=Team)
|
||||||
|
|
||||||
class Activity(DependencyComponent):
|
|
||||||
|
class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||||
"""Units of execution wrangled by a ba.Session.
|
"""Units of execution wrangled by a ba.Session.
|
||||||
|
|
||||||
Category: Gameplay Classes
|
Category: Gameplay Classes
|
||||||
@ -66,13 +72,73 @@ class Activity(DependencyComponent):
|
|||||||
|
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
# Annotating attr types at the class level lets us introspect them.
|
# Annotating attr types at the class level lets us introspect at runtime.
|
||||||
settings_raw: Dict[str, Any]
|
settings_raw: Dict[str, Any]
|
||||||
teams: List[ba.Team]
|
teams: List[TeamType]
|
||||||
players: List[ba.Player]
|
players: List[PlayerType]
|
||||||
|
|
||||||
|
# Whether to print every time a player dies. This can be pertinent
|
||||||
|
# in games such as Death-Match but can be annoying in games where it
|
||||||
|
# doesn't matter.
|
||||||
|
announce_player_deaths = False
|
||||||
|
|
||||||
|
# Joining activities are for waiting for initial player joins.
|
||||||
|
# They are treated slightly differently than regular activities,
|
||||||
|
# mainly in that all players are passed to the activity at once
|
||||||
|
# instead of as each joins.
|
||||||
|
is_joining_activity = False
|
||||||
|
|
||||||
|
# Whether game-time should still progress when in menus/etc.
|
||||||
|
allow_pausing = False
|
||||||
|
|
||||||
|
# Whether idle players can potentially be kicked (should not happen in
|
||||||
|
# menus/etc).
|
||||||
|
allow_kick_idle_players = True
|
||||||
|
|
||||||
|
# In vr mode, this determines whether overlay nodes (text, images, etc)
|
||||||
|
# are created at a fixed position in space or one that moves based on
|
||||||
|
# the current map. Generally this should be on for games and off for
|
||||||
|
# transitions/score-screens/etc. that persist between maps.
|
||||||
|
use_fixed_vr_overlay = False
|
||||||
|
|
||||||
|
# If True, runs in slow motion and turns down sound pitch.
|
||||||
|
slow_motion = False
|
||||||
|
|
||||||
|
# Set this to True to inherit slow motion setting from previous
|
||||||
|
# activity (useful for transitions to avoid hitches).
|
||||||
|
inherits_slow_motion = False
|
||||||
|
|
||||||
|
# Set this to True to keep playing the music from the previous activity
|
||||||
|
# (without even restarting it).
|
||||||
|
inherits_music = False
|
||||||
|
|
||||||
|
# Set this to true to inherit VR camera offsets from the previous
|
||||||
|
# activity (useful for preventing sporadic camera movement
|
||||||
|
# during transitions).
|
||||||
|
inherits_camera_vr_offset = False
|
||||||
|
|
||||||
|
# Set this to true to inherit (non-fixed) VR overlay positioning from
|
||||||
|
# the previous activity (useful for prevent sporadic overlay jostling
|
||||||
|
# during transitions).
|
||||||
|
inherits_vr_overlay_center = False
|
||||||
|
|
||||||
|
# Set this to true to inherit screen tint/vignette colors from the
|
||||||
|
# previous activity (useful to prevent sudden color changes during
|
||||||
|
# transitions).
|
||||||
|
inherits_tint = False
|
||||||
|
|
||||||
|
# If the activity fades or transitions in, it should set the length of
|
||||||
|
# time here so that previous activities will be kept alive for that
|
||||||
|
# long (avoiding 'holes' in the screen)
|
||||||
|
# This value is given in real-time seconds.
|
||||||
|
transition_time = 0.0
|
||||||
|
|
||||||
|
# Is it ok to show an ad after this activity ends before showing
|
||||||
|
# the next activity?
|
||||||
|
can_show_ad_on_death = False
|
||||||
|
|
||||||
def __init__(self, settings: Dict[str, Any]):
|
def __init__(self, settings: Dict[str, Any]):
|
||||||
"""Creates an activity in the current ba.Session.
|
"""Creates an Activity in the current ba.Session.
|
||||||
|
|
||||||
The activity will not be actually run until ba.Session.set_activity()
|
The activity will not be actually run until ba.Session.set_activity()
|
||||||
is called. 'settings' should be a dict of key/value pairs specific
|
is called. 'settings' should be a dict of key/value pairs specific
|
||||||
@ -84,14 +150,20 @@ class Activity(DependencyComponent):
|
|||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
# FIXME: Relocate this stuff.
|
# Create our internal engine data.
|
||||||
|
self._activity_data = _ba.register_activity(self)
|
||||||
|
|
||||||
|
# Player/Team types should have been specified as type args;
|
||||||
|
# grab those.
|
||||||
|
self._playertype: Type[PlayerType]
|
||||||
|
self._teamtype: Type[TeamType]
|
||||||
|
self._setup_player_and_team_types()
|
||||||
|
|
||||||
|
# FIXME: Relocate or remove the need for this stuff.
|
||||||
self.sharedobjs: Dict[str, Any] = {}
|
self.sharedobjs: Dict[str, Any] = {}
|
||||||
self.paused_text: Optional[ba.Actor] = None
|
self.paused_text: Optional[ba.Actor] = None
|
||||||
self.spaz_respawn_icons_right: Dict[int, RespawnIcon]
|
self.spaz_respawn_icons_right: Dict[int, RespawnIcon]
|
||||||
|
|
||||||
# Create our internal engine data.
|
|
||||||
self._activity_data = _ba.register_activity(self)
|
|
||||||
|
|
||||||
session = _ba.getsession()
|
session = _ba.getsession()
|
||||||
if session is None:
|
if session is None:
|
||||||
raise Exception('No current session')
|
raise Exception('No current session')
|
||||||
@ -105,8 +177,9 @@ class Activity(DependencyComponent):
|
|||||||
if _ba.getactivity(doraise=False) is not self:
|
if _ba.getactivity(doraise=False) is not self:
|
||||||
raise Exception('invalid context state')
|
raise Exception('invalid context state')
|
||||||
|
|
||||||
# Should perhaps kill this; activities should validate/store whatever
|
# Hopefully can eventually kill this; activities should
|
||||||
# settings they need at init time (in a more type-safe way).
|
# validate/store whatever settings they need at init time
|
||||||
|
# (in a more type-safe way).
|
||||||
self.settings_raw = settings
|
self.settings_raw = settings
|
||||||
|
|
||||||
self._has_transitioned_in = False
|
self._has_transitioned_in = False
|
||||||
@ -122,66 +195,6 @@ class Activity(DependencyComponent):
|
|||||||
self._activity_death_check_timer: Optional[ba.Timer] = None
|
self._activity_death_check_timer: Optional[ba.Timer] = None
|
||||||
self._expired = False
|
self._expired = False
|
||||||
|
|
||||||
# Whether to print every time a player dies. This can be pertinent
|
|
||||||
# in games such as Death-Match but can be annoying in games where it
|
|
||||||
# doesn't matter.
|
|
||||||
self.announce_player_deaths = False
|
|
||||||
|
|
||||||
# Joining activities are for waiting for initial player joins.
|
|
||||||
# They are treated slightly differently than regular activities,
|
|
||||||
# mainly in that all players are passed to the activity at once
|
|
||||||
# instead of as each joins.
|
|
||||||
self.is_joining_activity = False
|
|
||||||
|
|
||||||
# Whether game-time should still progress when in menus/etc.
|
|
||||||
self.allow_pausing = False
|
|
||||||
|
|
||||||
# Whether idle players can potentially be kicked (should not happen in
|
|
||||||
# menus/etc).
|
|
||||||
self.allow_kick_idle_players = True
|
|
||||||
|
|
||||||
# In vr mode, this determines whether overlay nodes (text, images, etc)
|
|
||||||
# are created at a fixed position in space or one that moves based on
|
|
||||||
# the current map. Generally this should be on for games and off for
|
|
||||||
# transitions/score-screens/etc. that persist between maps.
|
|
||||||
self.use_fixed_vr_overlay = False
|
|
||||||
|
|
||||||
# If True, runs in slow motion and turns down sound pitch.
|
|
||||||
self.slow_motion = False
|
|
||||||
|
|
||||||
# Set this to True to inherit slow motion setting from previous
|
|
||||||
# activity (useful for transitions to avoid hitches).
|
|
||||||
self.inherits_slow_motion = False
|
|
||||||
|
|
||||||
# Set this to True to keep playing the music from the previous activity
|
|
||||||
# (without even restarting it).
|
|
||||||
self.inherits_music = False
|
|
||||||
|
|
||||||
# Set this to true to inherit VR camera offsets from the previous
|
|
||||||
# activity (useful for preventing sporadic camera movement
|
|
||||||
# during transitions).
|
|
||||||
self.inherits_camera_vr_offset = False
|
|
||||||
|
|
||||||
# Set this to true to inherit (non-fixed) VR overlay positioning from
|
|
||||||
# the previous activity (useful for prevent sporadic overlay jostling
|
|
||||||
# during transitions).
|
|
||||||
self.inherits_vr_overlay_center = False
|
|
||||||
|
|
||||||
# Set this to true to inherit screen tint/vignette colors from the
|
|
||||||
# previous activity (useful to prevent sudden color changes during
|
|
||||||
# transitions).
|
|
||||||
self.inherits_tint = False
|
|
||||||
|
|
||||||
# If the activity fades or transitions in, it should set the length of
|
|
||||||
# time here so that previous activities will be kept alive for that
|
|
||||||
# long (avoiding 'holes' in the screen)
|
|
||||||
# This value is given in real-time seconds.
|
|
||||||
self.transition_time = 0.0
|
|
||||||
|
|
||||||
# Is it ok to show an ad after this activity ends before showing
|
|
||||||
# the next activity?
|
|
||||||
self.can_show_ad_on_death = False
|
|
||||||
|
|
||||||
# This gets set once another activity has begun transitioning in but
|
# This gets set once another activity has begun transitioning in but
|
||||||
# before this one is killed. The on_transition_out() method is also
|
# before this one is killed. The on_transition_out() method is also
|
||||||
# called at this time. Make sure to not assign player inputs,
|
# called at this time. Make sure to not assign player inputs,
|
||||||
@ -194,48 +207,16 @@ class Activity(DependencyComponent):
|
|||||||
# is dying.
|
# is dying.
|
||||||
self._actor_refs: List[ba.Actor] = []
|
self._actor_refs: List[ba.Actor] = []
|
||||||
self._actor_weak_refs: List[ReferenceType[ba.Actor]] = []
|
self._actor_weak_refs: List[ReferenceType[ba.Actor]] = []
|
||||||
self._last_dead_object_prune_time = _ba.time()
|
self._last_prune_dead_actors_time = _ba.time()
|
||||||
|
self._prune_dead_actors_timer: Optional[ba.Timer] = None
|
||||||
|
|
||||||
# This stuff gets filled in just before on_begin() is called.
|
# This stuff gets filled in just before on_begin() is called.
|
||||||
self.teams = []
|
self.teams = []
|
||||||
self.players = []
|
self.players = []
|
||||||
|
self.lobby = None
|
||||||
self._stats: Optional[ba.Stats] = None
|
self._stats: Optional[ba.Stats] = None
|
||||||
|
|
||||||
self.lobby = None
|
|
||||||
self._prune_dead_objects_timer: Optional[ba.Timer] = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stats(self) -> ba.Stats:
|
|
||||||
"""The stats instance accessible while the activity is running.
|
|
||||||
|
|
||||||
If access is attempted before or after, raises a ba.NotFoundError.
|
|
||||||
"""
|
|
||||||
if self._stats is None:
|
|
||||||
from ba._error import NotFoundError
|
|
||||||
raise NotFoundError()
|
|
||||||
return self._stats
|
|
||||||
|
|
||||||
def on_expire(self) -> None:
|
|
||||||
"""Called when your activity is being expired.
|
|
||||||
|
|
||||||
If your activity has created anything explicitly that may be retaining
|
|
||||||
a strong reference to the activity and preventing it from dying, you
|
|
||||||
should clear that out here. From this point on your activity's sole
|
|
||||||
purpose in life is to hit zero references and die so the next activity
|
|
||||||
can begin.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def is_expired(self) -> bool:
|
|
||||||
"""Return whether the activity is expired.
|
|
||||||
|
|
||||||
An activity is set as expired when shutting down.
|
|
||||||
At this point no new nodes, timers, etc should be made,
|
|
||||||
run, etc, and the activity should be considered a 'zombie'.
|
|
||||||
"""
|
|
||||||
return self._expired
|
|
||||||
|
|
||||||
def __del__(self) -> None:
|
def __del__(self) -> None:
|
||||||
|
|
||||||
from ba._apputils import garbage_collect, call_after_ad
|
from ba._apputils import garbage_collect, call_after_ad
|
||||||
|
|
||||||
# If the activity has been run then we should have already cleaned
|
# If the activity has been run then we should have already cleaned
|
||||||
@ -259,6 +240,47 @@ class Activity(DependencyComponent):
|
|||||||
else:
|
else:
|
||||||
_ba.pushcall(session.begin_next_activity)
|
_ba.pushcall(session.begin_next_activity)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stats(self) -> ba.Stats:
|
||||||
|
"""The stats instance accessible while the activity is running.
|
||||||
|
|
||||||
|
If access is attempted before or after, raises a ba.NotFoundError.
|
||||||
|
"""
|
||||||
|
if self._stats is None:
|
||||||
|
from ba._error import NotFoundError
|
||||||
|
raise NotFoundError()
|
||||||
|
return self._stats
|
||||||
|
|
||||||
|
def on_expire(self) -> None:
|
||||||
|
"""Called when your activity is being expired.
|
||||||
|
|
||||||
|
If your activity has created anything explicitly that may be retaining
|
||||||
|
a strong reference to the activity and preventing it from dying, you
|
||||||
|
should clear that out here. From this point on your activity's sole
|
||||||
|
purpose in life is to hit zero references and die so the next activity
|
||||||
|
can begin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expired(self) -> bool:
|
||||||
|
"""Whether the activity is expired.
|
||||||
|
|
||||||
|
An activity is set as expired when shutting down.
|
||||||
|
At this point no new nodes, timers, etc should be made,
|
||||||
|
run, etc, and the activity should be considered a 'zombie'.
|
||||||
|
"""
|
||||||
|
return self._expired
|
||||||
|
|
||||||
|
@property
|
||||||
|
def playertype(self) -> Type[PlayerType]:
|
||||||
|
"""The type of ba.Player this Activity is using."""
|
||||||
|
return self._playertype
|
||||||
|
|
||||||
|
@property
|
||||||
|
def teamtype(self) -> Type[TeamType]:
|
||||||
|
"""The type of ba.Team this Activity is using."""
|
||||||
|
return self._teamtype
|
||||||
|
|
||||||
def set_has_ended(self, val: bool) -> None:
|
def set_has_ended(self, val: bool) -> None:
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
self._has_ended = val
|
self._has_ended = val
|
||||||
@ -277,18 +299,11 @@ class Activity(DependencyComponent):
|
|||||||
self._should_end_immediately_results = results
|
self._should_end_immediately_results = results
|
||||||
self._should_end_immediately_delay = delay
|
self._should_end_immediately_delay = delay
|
||||||
|
|
||||||
def _get_player_icon(self, player: ba.Player) -> Dict[str, Any]:
|
def destroy(self) -> None:
|
||||||
|
"""Begin the process of tearing down the activity.
|
||||||
|
|
||||||
# Do we want to cache these somehow?
|
(internal)
|
||||||
info = player.get_icon_info()
|
"""
|
||||||
return {
|
|
||||||
'texture': _ba.gettexture(info['texture']),
|
|
||||||
'tint_texture': _ba.gettexture(info['tint_texture']),
|
|
||||||
'tint_color': info['tint_color'],
|
|
||||||
'tint2_color': info['tint2_color']
|
|
||||||
}
|
|
||||||
|
|
||||||
def _destroy(self) -> None:
|
|
||||||
from ba._general import Call
|
from ba._general import Call
|
||||||
from ba._enums import TimeType
|
from ba._enums import TimeType
|
||||||
|
|
||||||
@ -313,7 +328,8 @@ class Activity(DependencyComponent):
|
|||||||
with _ba.Context('empty'):
|
with _ba.Context('empty'):
|
||||||
self._expire()
|
self._expire()
|
||||||
else:
|
else:
|
||||||
raise Exception('_destroy() called multiple times')
|
raise RuntimeError(f'destroy() called when'
|
||||||
|
f' already expired for {self}')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_activity_death(cls, activity_ref: ReferenceType[Activity],
|
def _check_activity_death(cls, activity_ref: ReferenceType[Activity],
|
||||||
@ -351,54 +367,54 @@ class Activity(DependencyComponent):
|
|||||||
_ba.quit()
|
_ba.quit()
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
from ba import _error
|
print_exception('exception on _check_activity_death:')
|
||||||
_error.print_exception('exception on _check_activity_death:')
|
|
||||||
|
|
||||||
def _expire(self) -> None:
|
def _expire(self) -> None:
|
||||||
from ba import _error
|
"""Put the activity in a state where it can be garbage-collected.
|
||||||
|
|
||||||
|
This involves clearing anything that might be holding a reference
|
||||||
|
to it, etc.
|
||||||
|
"""
|
||||||
|
assert not self._expired
|
||||||
self._expired = True
|
self._expired = True
|
||||||
|
|
||||||
# Do some default cleanup.
|
|
||||||
try:
|
try:
|
||||||
try:
|
self.on_expire()
|
||||||
self.on_expire()
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception('Error in activity on_expire()', self)
|
|
||||||
|
|
||||||
# Send finalize notices to all remaining actors.
|
|
||||||
for actor_ref in self._actor_weak_refs:
|
|
||||||
try:
|
|
||||||
actor = actor_ref()
|
|
||||||
if actor is not None:
|
|
||||||
actor.on_expire()
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
'Exception on ba.Activity._expire()'
|
|
||||||
' in actor on_expire():', actor_ref())
|
|
||||||
|
|
||||||
# Reset all players.
|
|
||||||
# (releases any attached actors, clears game-data, etc)
|
|
||||||
for player in self.players:
|
|
||||||
if player:
|
|
||||||
try:
|
|
||||||
player.reset()
|
|
||||||
player.set_activity(None)
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
'Exception on ba.Activity._expire()'
|
|
||||||
' resetting player:', player)
|
|
||||||
|
|
||||||
# Ditto with teams.
|
|
||||||
for team in self.teams:
|
|
||||||
try:
|
|
||||||
team.reset()
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
'Exception on ba.Activity._expire() resetting team:',
|
|
||||||
team)
|
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
_error.print_exception('Exception during ba.Activity._expire():')
|
print_exception(f'Error in Activity on_expire() for {self}')
|
||||||
|
|
||||||
|
# Send expire notices to all remaining actors.
|
||||||
|
for actor_ref in self._actor_weak_refs:
|
||||||
|
try:
|
||||||
|
actor = actor_ref()
|
||||||
|
if actor is not None:
|
||||||
|
actor.on_expire()
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error expiring Actor {actor_ref()}')
|
||||||
|
|
||||||
|
# Reset all Players.
|
||||||
|
# (releases any attached actors, clears game-data, etc)
|
||||||
|
for player in self.players:
|
||||||
|
if player:
|
||||||
|
try:
|
||||||
|
sessionplayer = player.sessionplayer
|
||||||
|
sessionplayer.set_node(None)
|
||||||
|
sessionplayer.set_activity(None)
|
||||||
|
sessionplayer.gameplayer = None
|
||||||
|
sessionplayer.reset()
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error resetting Player {player}')
|
||||||
|
|
||||||
|
# Ditto with Teams.
|
||||||
|
for team in self.teams:
|
||||||
|
try:
|
||||||
|
sessionteam = team.sessionteam
|
||||||
|
sessionteam.gameteam = None
|
||||||
|
sessionteam.reset_gamedata()
|
||||||
|
except SessionTeamNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error resetting Team {team}')
|
||||||
|
|
||||||
# Regardless of what happened here, we want to destroy our data, as
|
# Regardless of what happened here, we want to destroy our data, as
|
||||||
# our activity might not go down if we don't. This will kill all
|
# our activity might not go down if we don't. This will kill all
|
||||||
@ -407,13 +423,13 @@ class Activity(DependencyComponent):
|
|||||||
try:
|
try:
|
||||||
self._activity_data.destroy()
|
self._activity_data.destroy()
|
||||||
except Exception:
|
except Exception:
|
||||||
_error.print_exception(
|
print_exception(
|
||||||
'Exception during ba.Activity._expire() destroying data:')
|
'Exception during ba.Activity._expire() destroying data:')
|
||||||
|
|
||||||
def _prune_dead_objects(self) -> None:
|
def _prune_dead_actors(self) -> None:
|
||||||
self._actor_refs = [a for a in self._actor_refs if a]
|
self._actor_refs = [a for a in self._actor_refs if a]
|
||||||
self._actor_weak_refs = [a for a in self._actor_weak_refs if a()]
|
self._actor_weak_refs = [a for a in self._actor_weak_refs if a()]
|
||||||
self._last_dead_object_prune_time = _ba.time()
|
self._last_prune_dead_actors_time = _ba.time()
|
||||||
|
|
||||||
def retain_actor(self, actor: ba.Actor) -> None:
|
def retain_actor(self, actor: ba.Actor) -> None:
|
||||||
"""Add a strong-reference to a ba.Actor to this Activity.
|
"""Add a strong-reference to a ba.Actor to this Activity.
|
||||||
@ -423,16 +439,14 @@ class Activity(DependencyComponent):
|
|||||||
is a convenient way to access this same functionality.
|
is a convenient way to access this same functionality.
|
||||||
"""
|
"""
|
||||||
from ba import _actor as bsactor
|
from ba import _actor as bsactor
|
||||||
from ba import _error
|
|
||||||
if not isinstance(actor, bsactor.Actor):
|
if not isinstance(actor, bsactor.Actor):
|
||||||
raise Exception('non-actor passed to _retain_actor')
|
raise Exception('non-actor passed to _retain_actor')
|
||||||
if (self.has_transitioned_in()
|
if (self.has_transitioned_in()
|
||||||
and _ba.time() - self._last_dead_object_prune_time > 10.0):
|
and _ba.time() - self._last_prune_dead_actors_time > 10.0):
|
||||||
_error.print_error('it looks like nodes/actors are not'
|
print_error('it looks like nodes/actors are not'
|
||||||
' being pruned in your activity;'
|
' being pruned in your activity;'
|
||||||
' did you call Activity.on_transition_in()'
|
' did you call Activity.on_transition_in()'
|
||||||
' from your subclass?; ' + str(self) +
|
' from your subclass?; ' + str(self) + ' (loc. a)')
|
||||||
' (loc. a)')
|
|
||||||
self._actor_refs.append(actor)
|
self._actor_refs.append(actor)
|
||||||
|
|
||||||
def add_actor_weak_ref(self, actor: ba.Actor) -> None:
|
def add_actor_weak_ref(self, actor: ba.Actor) -> None:
|
||||||
@ -441,16 +455,14 @@ class Activity(DependencyComponent):
|
|||||||
(called by the ba.Actor base class)
|
(called by the ba.Actor base class)
|
||||||
"""
|
"""
|
||||||
from ba import _actor as bsactor
|
from ba import _actor as bsactor
|
||||||
from ba import _error
|
|
||||||
if not isinstance(actor, bsactor.Actor):
|
if not isinstance(actor, bsactor.Actor):
|
||||||
raise Exception('non-actor passed to _add_actor_weak_ref')
|
raise Exception('non-actor passed to _add_actor_weak_ref')
|
||||||
if (self.has_transitioned_in()
|
if (self.has_transitioned_in()
|
||||||
and _ba.time() - self._last_dead_object_prune_time > 10.0):
|
and _ba.time() - self._last_prune_dead_actors_time > 10.0):
|
||||||
_error.print_error('it looks like nodes/actors are '
|
print_error('it looks like nodes/actors are '
|
||||||
'not being pruned in your activity;'
|
'not being pruned in your activity;'
|
||||||
' did you call Activity.on_transition_in()'
|
' did you call Activity.on_transition_in()'
|
||||||
' from your subclass?; ' + str(self) +
|
' from your subclass?; ' + str(self) + ' (loc. b)')
|
||||||
' (loc. b)')
|
|
||||||
self._actor_weak_refs.append(weakref.ref(actor))
|
self._actor_weak_refs.append(weakref.ref(actor))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -465,48 +477,38 @@ class Activity(DependencyComponent):
|
|||||||
raise SessionNotFoundError()
|
raise SessionNotFoundError()
|
||||||
return session
|
return session
|
||||||
|
|
||||||
def on_player_join(self, player: ba.Player) -> None:
|
def on_player_join(self, player: PlayerType) -> None:
|
||||||
"""Called when a new ba.Player has joined the Activity.
|
"""Called when a new ba.Player has joined the Activity.
|
||||||
|
|
||||||
(including the initial set of Players)
|
(including the initial set of Players)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_player_leave(self, player: ba.Player) -> None:
|
def on_player_leave(self, player: PlayerType) -> None:
|
||||||
"""Called when a ba.Player is leaving the Activity."""
|
"""Called when a ba.Player is leaving the Activity."""
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
def on_team_join(self, team: TeamType) -> None:
|
||||||
"""Called when a new ba.Team joins the Activity.
|
"""Called when a new ba.Team joins the Activity.
|
||||||
|
|
||||||
(including the initial set of Teams)
|
(including the initial set of Teams)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_team_leave(self, team: ba.Team) -> None:
|
def on_team_leave(self, team: TeamType) -> None:
|
||||||
"""Called when a ba.Team leaves the Activity."""
|
"""Called when a ba.Team leaves the Activity."""
|
||||||
|
|
||||||
def on_transition_in(self) -> None:
|
def on_transition_in(self) -> None:
|
||||||
"""Called when the Activity is first becoming visible.
|
"""Called when the Activity is first becoming visible.
|
||||||
|
|
||||||
Upon this call, the Activity should fade in backgrounds,
|
Upon this call, the Activity should fade in backgrounds,
|
||||||
start playing music, etc. It does not yet have access to ba.Players
|
start playing music, etc. It does not yet have access to players
|
||||||
or ba.Teams, however. They remain owned by the previous Activity
|
or teams, however. They remain owned by the previous Activity
|
||||||
up until ba.Activity.on_begin() is called.
|
up until ba.Activity.on_begin() is called.
|
||||||
"""
|
"""
|
||||||
from ba._general import WeakCall
|
|
||||||
|
|
||||||
self._called_activity_on_transition_in = True
|
self._called_activity_on_transition_in = True
|
||||||
|
|
||||||
# Start pruning our transient actors periodically.
|
|
||||||
self._prune_dead_objects_timer = _ba.Timer(
|
|
||||||
5.17, WeakCall(self._prune_dead_objects), repeat=True)
|
|
||||||
self._prune_dead_objects()
|
|
||||||
|
|
||||||
# Also start our low-level scene-graph running.
|
|
||||||
self._activity_data.start()
|
|
||||||
|
|
||||||
def on_transition_out(self) -> None:
|
def on_transition_out(self) -> None:
|
||||||
"""Called when your activity begins transitioning out.
|
"""Called when your activity begins transitioning out.
|
||||||
|
|
||||||
Note that this may happen at any time even if finish() has not been
|
Note that this may happen at any time even if end() has not been
|
||||||
called.
|
called.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -552,110 +554,223 @@ class Activity(DependencyComponent):
|
|||||||
"""Return whether on_transition_out() has been called."""
|
"""Return whether on_transition_out() has been called."""
|
||||||
return self._transitioning_out
|
return self._transitioning_out
|
||||||
|
|
||||||
def start_transition_in(self) -> None:
|
def transition_in(self, prev_globals: Optional[ba.Node]) -> None:
|
||||||
"""Called by Session to kick of transition-in.
|
"""Called by Session to kick off transition-in.
|
||||||
|
|
||||||
(internal)
|
(internal)
|
||||||
"""
|
"""
|
||||||
|
from ba._general import WeakCall
|
||||||
|
from ba._gameutils import sharedobj
|
||||||
assert not self._has_transitioned_in
|
assert not self._has_transitioned_in
|
||||||
self._has_transitioned_in = True
|
self._has_transitioned_in = True
|
||||||
self.on_transition_in()
|
|
||||||
|
|
||||||
def create_player_node(self, player: ba.Player) -> ba.Node:
|
# Set up the globals node based on our settings.
|
||||||
"""Create the 'player' node associated with the provided ba.Player."""
|
|
||||||
from ba._nodeactor import NodeActor
|
|
||||||
with _ba.Context(self):
|
with _ba.Context(self):
|
||||||
node = _ba.newnode('player', attrs={'playerID': player.get_id()})
|
# Now that it's going to be front and center,
|
||||||
# FIXME: Should add a dedicated slot for this on ba.Player
|
# set some global values based on what the activity wants.
|
||||||
# instead of cluttering up their gamedata dict.
|
glb = sharedobj('globals')
|
||||||
player.gamedata['_playernode'] = NodeActor(node)
|
glb.use_fixed_vr_overlay = self.use_fixed_vr_overlay
|
||||||
return node
|
glb.allow_kick_idle_players = self.allow_kick_idle_players
|
||||||
|
if self.inherits_slow_motion and prev_globals is not None:
|
||||||
def begin(self, session: ba.Session) -> None:
|
glb.slow_motion = prev_globals.slow_motion
|
||||||
"""Begin the activity. (should only be called by Session).
|
|
||||||
|
|
||||||
(internal)"""
|
|
||||||
|
|
||||||
# pylint: disable=too-many-branches
|
|
||||||
from ba import _error
|
|
||||||
|
|
||||||
if self._has_begun:
|
|
||||||
_error.print_error("_begin called twice; this shouldn't happen")
|
|
||||||
return
|
|
||||||
|
|
||||||
self._stats = session.stats
|
|
||||||
|
|
||||||
# Operate on the subset of session players who have passed team/char
|
|
||||||
# selection.
|
|
||||||
players = []
|
|
||||||
chooser_players = []
|
|
||||||
for player in session.players:
|
|
||||||
assert player # should we ever have invalid players?..
|
|
||||||
if player:
|
|
||||||
try:
|
|
||||||
team: Optional[ba.Team] = player.team
|
|
||||||
except _error.TeamNotFoundError:
|
|
||||||
team = None
|
|
||||||
|
|
||||||
if team is not None:
|
|
||||||
player.reset_input()
|
|
||||||
players.append(player)
|
|
||||||
else:
|
|
||||||
# Simply ignore players sitting in the lobby.
|
|
||||||
# (though this technically shouldn't happen anymore since
|
|
||||||
# choosers now get cleared when starting new activities.)
|
|
||||||
print('unexpected: got no-team player in _begin')
|
|
||||||
chooser_players.append(player)
|
|
||||||
else:
|
else:
|
||||||
_error.print_error(
|
glb.slow_motion = self.slow_motion
|
||||||
'got nonexistent player in Activity._begin()')
|
if self.inherits_music and prev_globals is not None:
|
||||||
|
glb.music_continuous = True # Prevent restarting same music.
|
||||||
|
glb.music = prev_globals.music
|
||||||
|
glb.music_count += 1
|
||||||
|
if self.inherits_camera_vr_offset and prev_globals is not None:
|
||||||
|
glb.vr_camera_offset = prev_globals.vr_camera_offset
|
||||||
|
if self.inherits_vr_overlay_center and prev_globals is not None:
|
||||||
|
glb.vr_overlay_center = prev_globals.vr_overlay_center
|
||||||
|
glb.vr_overlay_center_enabled = (
|
||||||
|
prev_globals.vr_overlay_center_enabled)
|
||||||
|
|
||||||
# Add teams in one by one and send team-joined messages for each.
|
# If they want to inherit tint from the previous self.
|
||||||
for team in session.teams:
|
if self.inherits_tint and prev_globals is not None:
|
||||||
if team in self.teams:
|
glb.tint = prev_globals.tint
|
||||||
raise Exception('Duplicate Team Entry')
|
glb.vignette_outer = prev_globals.vignette_outer
|
||||||
|
glb.vignette_inner = prev_globals.vignette_inner
|
||||||
|
|
||||||
|
# Start pruning our transient actors periodically.
|
||||||
|
self._prune_dead_actors_timer = _ba.Timer(
|
||||||
|
5.17, WeakCall(self._prune_dead_actors), repeat=True)
|
||||||
|
self._prune_dead_actors()
|
||||||
|
|
||||||
|
# Also start our low-level scene running.
|
||||||
|
self._activity_data.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.on_transition_in()
|
||||||
|
except Exception:
|
||||||
|
print_exception('Error in on_transition_in for', self)
|
||||||
|
|
||||||
|
# Tell the C++ layer that this activity is the main one, so it uses
|
||||||
|
# settings from our globals, directs various events to us, etc.
|
||||||
|
self._activity_data.make_foreground()
|
||||||
|
|
||||||
|
def transition_out(self) -> None:
|
||||||
|
"""Called by the Session to start us transitioning out."""
|
||||||
|
assert not self._transitioning_out
|
||||||
|
self._transitioning_out = True
|
||||||
|
with _ba.Context(self):
|
||||||
|
try:
|
||||||
|
self.on_transition_out()
|
||||||
|
except Exception:
|
||||||
|
print_exception('Error in on_transition_out for', self)
|
||||||
|
|
||||||
|
def create_player(self, sessionplayer: ba.SessionPlayer) -> PlayerType:
|
||||||
|
"""Create the Player instance for this Activity.
|
||||||
|
|
||||||
|
Subclasses can override this if the activity's player class
|
||||||
|
requires a custom constructor; otherwise it will be called with
|
||||||
|
no args. Note that the player object should not be used at this
|
||||||
|
point as it is not yet fully wired up; wait for on_player_join()
|
||||||
|
for that.
|
||||||
|
"""
|
||||||
|
del sessionplayer # Unused
|
||||||
|
player = self._playertype()
|
||||||
|
return player
|
||||||
|
|
||||||
|
def create_team(self, sessionteam: ba.SessionTeam) -> TeamType:
|
||||||
|
"""Create the Team instance for this Activity.
|
||||||
|
|
||||||
|
Subclasses can override this if the activity's team class
|
||||||
|
requires a custom constructor; otherwise it will be called with
|
||||||
|
no args. Note that the team object should not be used at this
|
||||||
|
point as it is not yet fully wired up; wait for on_team_join()
|
||||||
|
for that.
|
||||||
|
"""
|
||||||
|
del sessionteam # Unused.
|
||||||
|
team = self._teamtype()
|
||||||
|
return team
|
||||||
|
|
||||||
|
def add_player(self, sessionplayer: ba.SessionPlayer) -> None:
|
||||||
|
"""(internal)"""
|
||||||
|
assert sessionplayer.team is not None
|
||||||
|
|
||||||
|
sessionplayer.reset_input()
|
||||||
|
sessionteam = sessionplayer.team
|
||||||
|
assert sessionplayer in sessionteam.players
|
||||||
|
team = sessionteam.gameteam
|
||||||
|
assert team is not None
|
||||||
|
sessionplayer.set_activity(self)
|
||||||
|
with _ba.Context(self):
|
||||||
|
sessionplayer.gameplayer = player = self.create_player(
|
||||||
|
sessionplayer)
|
||||||
|
player.postinit(sessionplayer)
|
||||||
|
team.players.append(player)
|
||||||
|
self.players.append(player)
|
||||||
|
try:
|
||||||
|
self.on_player_join(player)
|
||||||
|
except Exception:
|
||||||
|
print_exception('Error in on_player_join for', self)
|
||||||
|
|
||||||
|
def remove_player(self, sessionplayer: ba.SessionPlayer) -> None:
|
||||||
|
"""(internal)"""
|
||||||
|
|
||||||
|
# This should only be called on unexpired activities
|
||||||
|
# the player has been added to.
|
||||||
|
assert not self.expired
|
||||||
|
player = sessionplayer.gameplayer
|
||||||
|
assert isinstance(player, self._playertype)
|
||||||
|
assert player in self.players
|
||||||
|
|
||||||
|
self.players.remove(player)
|
||||||
|
with _ba.Context(self):
|
||||||
|
# Make a decent attempt to persevere if user code breaks.
|
||||||
|
try:
|
||||||
|
self.on_player_leave(player)
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error in on_player_leave for {self}')
|
||||||
|
try:
|
||||||
|
sessionplayer.reset()
|
||||||
|
sessionplayer.set_node(None)
|
||||||
|
sessionplayer.set_activity(None)
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error resetting player for {self}')
|
||||||
|
|
||||||
|
def add_team(self, sessionteam: ba.SessionTeam) -> None:
|
||||||
|
"""(internal)"""
|
||||||
|
assert not self.expired
|
||||||
|
|
||||||
|
with _ba.Context(self):
|
||||||
|
sessionteam.gameteam = team = self.create_team(sessionteam)
|
||||||
|
team.postinit(sessionteam)
|
||||||
self.teams.append(team)
|
self.teams.append(team)
|
||||||
try:
|
try:
|
||||||
with _ba.Context(self):
|
self.on_team_join(team)
|
||||||
self.on_team_join(team)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
_error.print_exception('Error in on_team_join for', self)
|
print_exception(f'Error in on_team_join for {self}')
|
||||||
|
|
||||||
# Now add each player to the activity and to its team's list,
|
def remove_team(self, sessionteam: ba.SessionTeam) -> None:
|
||||||
# and send player-joined messages for each.
|
"""(internal)"""
|
||||||
for player in players:
|
|
||||||
self.players.append(player)
|
|
||||||
player.team.players.append(player)
|
|
||||||
player.set_activity(self)
|
|
||||||
pnode = self.create_player_node(player)
|
|
||||||
player.set_node(pnode)
|
|
||||||
try:
|
|
||||||
with _ba.Context(self):
|
|
||||||
self.on_player_join(player)
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception('Error in on_player_join for', self)
|
|
||||||
|
|
||||||
|
# This should only be called on unexpired activities the team has
|
||||||
|
# been added to.
|
||||||
|
assert not self.expired
|
||||||
|
assert sessionteam.gameteam is not None
|
||||||
|
assert sessionteam.gameteam in self.teams
|
||||||
|
|
||||||
|
team = sessionteam.gameteam
|
||||||
|
assert isinstance(team, self._teamtype)
|
||||||
|
self.teams.remove(team)
|
||||||
with _ba.Context(self):
|
with _ba.Context(self):
|
||||||
# And finally tell the game to start.
|
# Make a decent attempt to persevere if user code breaks.
|
||||||
self._has_begun = True
|
try:
|
||||||
self.on_begin()
|
self.on_team_leave(team)
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error in on_team_leave for {self}')
|
||||||
|
try:
|
||||||
|
sessionteam.reset_gamedata()
|
||||||
|
except Exception:
|
||||||
|
print_exception(f'Error in reset_gamedata for {self}')
|
||||||
|
sessionteam.gameteam = None
|
||||||
|
|
||||||
# Make sure that ba.Activity.on_transition_in() got called
|
def _sanity_check_begin_call(self) -> None:
|
||||||
# at some point.
|
# Make sure ba.Activity.on_transition_in() got called at some point.
|
||||||
if not self._called_activity_on_transition_in:
|
if not self._called_activity_on_transition_in:
|
||||||
_error.print_error(
|
print_error(
|
||||||
'ba.Activity.on_transition_in() never got called for ' +
|
'ba.Activity.on_transition_in() never got called for ' +
|
||||||
str(self) + '; did you forget to call it'
|
str(self) + '; did you forget to call it'
|
||||||
' in your on_transition_in override?')
|
' in your on_transition_in override?')
|
||||||
|
|
||||||
# Make sure that ba.Activity.on_begin() got called at some point.
|
# Make sure that ba.Activity.on_begin() got called at some point.
|
||||||
if not self._called_activity_on_begin:
|
if not self._called_activity_on_begin:
|
||||||
_error.print_error(
|
print_error(
|
||||||
'ba.Activity.on_begin() never got called for ' + str(self) +
|
'ba.Activity.on_begin() never got called for ' + str(self) +
|
||||||
'; did you forget to call it in your on_begin override?')
|
'; did you forget to call it in your on_begin override?')
|
||||||
|
|
||||||
# If the whole session wants to die and was waiting on us, can get
|
def begin(self, session: ba.Session) -> None:
|
||||||
# that going now.
|
"""Begin the activity.
|
||||||
|
|
||||||
|
(internal)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Is this ever still happening?...
|
||||||
|
if self._has_begun:
|
||||||
|
print_error("_begin called twice; this shouldn't happen")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Inherit stats from the session.
|
||||||
|
self._stats = session.stats
|
||||||
|
|
||||||
|
# Add session's teams in.
|
||||||
|
for team in session.teams:
|
||||||
|
self.add_team(team)
|
||||||
|
|
||||||
|
# Add session's players in.
|
||||||
|
for player in session.players:
|
||||||
|
self.add_player(player)
|
||||||
|
|
||||||
|
# And finally tell the game to start.
|
||||||
|
with _ba.Context(self):
|
||||||
|
self._has_begun = True
|
||||||
|
self.on_begin()
|
||||||
|
|
||||||
|
self._sanity_check_begin_call()
|
||||||
|
|
||||||
|
# If the whole session wants to die and was waiting on us,
|
||||||
|
# can kick off that process now.
|
||||||
if session.wants_to_end:
|
if session.wants_to_end:
|
||||||
session.launch_end_session_activity()
|
session.launch_end_session_activity()
|
||||||
else:
|
else:
|
||||||
@ -663,3 +778,30 @@ class Activity(DependencyComponent):
|
|||||||
if self._should_end_immediately:
|
if self._should_end_immediately:
|
||||||
self.end(self._should_end_immediately_results,
|
self.end(self._should_end_immediately_results,
|
||||||
self._should_end_immediately_delay)
|
self._should_end_immediately_delay)
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
def _setup_player_and_team_types(self) -> None:
|
||||||
|
"""Pull player and team types from our typing.Generic params."""
|
||||||
|
|
||||||
|
# TODO: There are proper calls for pulling these in Python 3.8;
|
||||||
|
# should update this code when we adopt that.
|
||||||
|
# NOTE: If we get Any as PlayerType or TeamType (generally due
|
||||||
|
# to no generic params being passed) we automatically use the
|
||||||
|
# base class types, but also warn the user since this will mean
|
||||||
|
# less type safety for that class. (its better to pass the base
|
||||||
|
# types explicitly vs. having them be Any)
|
||||||
|
if not TYPE_CHECKING:
|
||||||
|
self._playertype = type(self).__orig_bases__[-1].__args__[0]
|
||||||
|
if not isinstance(self._playertype, type):
|
||||||
|
self._playertype = Player
|
||||||
|
print(f'ERROR: {type(self)} was not passed a Player'
|
||||||
|
f' type argument; please explicitly pass ba.Player'
|
||||||
|
f' if you do not want to override it.')
|
||||||
|
self._teamtype = type(self).__orig_bases__[-1].__args__[1]
|
||||||
|
if not isinstance(self._teamtype, type):
|
||||||
|
self._teamtype = Team
|
||||||
|
print(f'ERROR: {type(self)} was not passed a Team'
|
||||||
|
f' type argument; please explicitly pass ba.Team'
|
||||||
|
f' if you do not want to override it.')
|
||||||
|
assert issubclass(self._playertype, Player)
|
||||||
|
assert issubclass(self._teamtype, Team)
|
||||||
|
|||||||
@ -26,6 +26,9 @@ from typing import TYPE_CHECKING
|
|||||||
import _ba
|
import _ba
|
||||||
from ba._activity import Activity
|
from ba._activity import Activity
|
||||||
from ba._music import setmusic, MusicType
|
from ba._music import setmusic, MusicType
|
||||||
|
# False positive due to our class_generics_filter custom pylint filter.
|
||||||
|
from ba._player import Player # pylint: disable=W0611
|
||||||
|
from ba._team import Team # pylint: disable=W0611
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
@ -33,7 +36,7 @@ if TYPE_CHECKING:
|
|||||||
from ba._lobby import JoinInfo
|
from ba._lobby import JoinInfo
|
||||||
|
|
||||||
|
|
||||||
class EndSessionActivity(Activity):
|
class EndSessionActivity(Activity[Player, Team]):
|
||||||
"""Special ba.Activity to fade out and end the current ba.Session."""
|
"""Special ba.Activity to fade out and end the current ba.Session."""
|
||||||
|
|
||||||
def __init__(self, settings: Dict[str, Any]):
|
def __init__(self, settings: Dict[str, Any]):
|
||||||
@ -61,7 +64,7 @@ class EndSessionActivity(Activity):
|
|||||||
call_after_ad(Call(_ba.new_host_session, MainMenuSession))
|
call_after_ad(Call(_ba.new_host_session, MainMenuSession))
|
||||||
|
|
||||||
|
|
||||||
class JoinActivity(Activity):
|
class JoinActivity(Activity[Player, Team]):
|
||||||
"""Standard activity for waiting for players to join.
|
"""Standard activity for waiting for players to join.
|
||||||
|
|
||||||
It shows tips and other info and waits for all players to check ready.
|
It shows tips and other info and waits for all players to check ready.
|
||||||
@ -98,7 +101,7 @@ class JoinActivity(Activity):
|
|||||||
_ba.set_analytics_screen('Joining Screen')
|
_ba.set_analytics_screen('Joining Screen')
|
||||||
|
|
||||||
|
|
||||||
class TransitionActivity(Activity):
|
class TransitionActivity(Activity[Player, Team]):
|
||||||
"""A simple overlay fade out/in.
|
"""A simple overlay fade out/in.
|
||||||
|
|
||||||
Useful as a bare minimum transition between two level based activities.
|
Useful as a bare minimum transition between two level based activities.
|
||||||
@ -131,7 +134,7 @@ class TransitionActivity(Activity):
|
|||||||
_ba.timer(0.1, self.end)
|
_ba.timer(0.1, self.end)
|
||||||
|
|
||||||
|
|
||||||
class ScoreScreenActivity(Activity):
|
class ScoreScreenActivity(Activity[Player, Team]):
|
||||||
"""A standard score screen that fades in and shows stuff for a while.
|
"""A standard score screen that fades in and shows stuff for a while.
|
||||||
|
|
||||||
After a specified delay, player input is assigned to end the activity.
|
After a specified delay, player input is assigned to end the activity.
|
||||||
|
|||||||
@ -25,8 +25,8 @@ from __future__ import annotations
|
|||||||
import weakref
|
import weakref
|
||||||
from typing import TYPE_CHECKING, TypeVar
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
|
|
||||||
from ba._messages import DieMessage, DeathType, OutOfBoundsMessage
|
from ba._messages import DieMessage, DeathType, OutOfBoundsMessage, UNHANDLED
|
||||||
from ba import _error
|
from ba._error import print_error, print_exception, ActivityNotFoundError
|
||||||
import _ba
|
import _ba
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -97,10 +97,10 @@ class Actor:
|
|||||||
# Non-expired Actors send themselves a DieMessage when going down.
|
# Non-expired Actors send themselves a DieMessage when going down.
|
||||||
# That way we can treat DieMessage handling as the single
|
# That way we can treat DieMessage handling as the single
|
||||||
# point-of-action for death.
|
# point-of-action for death.
|
||||||
if not self.is_expired():
|
if not self.expired:
|
||||||
self.handlemessage(DieMessage())
|
self.handlemessage(DieMessage())
|
||||||
except Exception:
|
except Exception:
|
||||||
_error.print_exception('exception in ba.Actor.__del__() for', self)
|
print_exception('exception in ba.Actor.__del__() for', self)
|
||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
"""General message handling; can be passed any message object."""
|
"""General message handling; can be passed any message object."""
|
||||||
@ -111,7 +111,7 @@ class Actor:
|
|||||||
if isinstance(msg, OutOfBoundsMessage):
|
if isinstance(msg, OutOfBoundsMessage):
|
||||||
return self.handlemessage(DieMessage(how=DeathType.OUT_OF_BOUNDS))
|
return self.handlemessage(DieMessage(how=DeathType.OUT_OF_BOUNDS))
|
||||||
|
|
||||||
return _error.UNHANDLED
|
return UNHANDLED
|
||||||
|
|
||||||
def autoretain(self: T) -> T:
|
def autoretain(self: T) -> T:
|
||||||
"""Keep this Actor alive without needing to hold a reference to it.
|
"""Keep this Actor alive without needing to hold a reference to it.
|
||||||
@ -126,7 +126,7 @@ class Actor:
|
|||||||
"""
|
"""
|
||||||
activity = self._activity()
|
activity = self._activity()
|
||||||
if activity is None:
|
if activity is None:
|
||||||
raise _error.ActivityNotFoundError()
|
raise ActivityNotFoundError()
|
||||||
activity.retain_actor(self)
|
activity.retain_actor(self)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -144,13 +144,14 @@ class Actor:
|
|||||||
likely result in errors.
|
likely result in errors.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def is_expired(self) -> bool:
|
@property
|
||||||
"""Returns whether the Actor is expired.
|
def expired(self) -> bool:
|
||||||
|
"""Whether the Actor is expired.
|
||||||
|
|
||||||
(see ba.Actor.on_expire())
|
(see ba.Actor.on_expire())
|
||||||
"""
|
"""
|
||||||
activity = self.getactivity(doraise=False)
|
activity = self.getactivity(doraise=False)
|
||||||
return True if activity is None else activity.is_expired()
|
return True if activity is None else activity.expired
|
||||||
|
|
||||||
def exists(self) -> bool:
|
def exists(self) -> bool:
|
||||||
"""Returns whether the Actor is still present in a meaningful way.
|
"""Returns whether the Actor is still present in a meaningful way.
|
||||||
@ -195,13 +196,12 @@ class Actor:
|
|||||||
avoided.
|
avoided.
|
||||||
"""
|
"""
|
||||||
if not __debug__:
|
if not __debug__:
|
||||||
_error.print_error('This should only be called in __debug__ mode.',
|
print_error('This should only be called in __debug__ mode.',
|
||||||
once=True)
|
once=True)
|
||||||
if not getattr(self, '_root_actor_init_called', False):
|
if not getattr(self, '_root_actor_init_called', False):
|
||||||
_error.print_error('Root Actor __init__() not called.')
|
print_error('Root Actor __init__() not called.')
|
||||||
if self.is_expired():
|
if self.expired:
|
||||||
_error.print_error(
|
print_error(f'handlemessage() called on expired actor: {self}')
|
||||||
f'handlemessage() called on expired actor: {self}')
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity(self) -> ba.Activity:
|
def activity(self) -> ba.Activity:
|
||||||
@ -211,7 +211,7 @@ class Actor:
|
|||||||
"""
|
"""
|
||||||
activity = self._activity()
|
activity = self._activity()
|
||||||
if activity is None:
|
if activity is None:
|
||||||
raise _error.ActivityNotFoundError()
|
raise ActivityNotFoundError()
|
||||||
return activity
|
return activity
|
||||||
|
|
||||||
def getactivity(self, doraise: bool = True) -> Optional[ba.Activity]:
|
def getactivity(self, doraise: bool = True) -> Optional[ba.Activity]:
|
||||||
@ -222,5 +222,5 @@ class Actor:
|
|||||||
"""
|
"""
|
||||||
activity = self._activity()
|
activity = self._activity()
|
||||||
if activity is None and doraise:
|
if activity is None and doraise:
|
||||||
raise _error.ActivityNotFoundError()
|
raise ActivityNotFoundError()
|
||||||
return activity
|
return activity
|
||||||
|
|||||||
@ -62,7 +62,7 @@ def run_cpu_benchmark() -> None:
|
|||||||
cfg['Graphics Quality'] = self._old_quality
|
cfg['Graphics Quality'] = self._old_quality
|
||||||
cfg.apply()
|
cfg.apply()
|
||||||
|
|
||||||
def on_player_request(self, player: ba.Player) -> bool:
|
def on_player_request(self, player: ba.SessionPlayer) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
_ba.new_host_session(BenchmarkSession, benchmark_type='cpu')
|
_ba.new_host_session(BenchmarkSession, benchmark_type='cpu')
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
"""Functionality related to co-op games."""
|
"""Functionality related to co-op games."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
|
|
||||||
import _ba
|
import _ba
|
||||||
from ba._gameactivity import GameActivity
|
from ba._gameactivity import GameActivity
|
||||||
@ -31,8 +31,11 @@ if TYPE_CHECKING:
|
|||||||
from bastd.actor.playerspaz import PlayerSpaz
|
from bastd.actor.playerspaz import PlayerSpaz
|
||||||
import ba
|
import ba
|
||||||
|
|
||||||
|
PlayerType = TypeVar('PlayerType', bound='ba.Player')
|
||||||
|
TeamType = TypeVar('TeamType', bound='ba.Team')
|
||||||
|
|
||||||
class CoopGameActivity(GameActivity):
|
|
||||||
|
class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||||
"""Base class for cooperative-mode games.
|
"""Base class for cooperative-mode games.
|
||||||
|
|
||||||
Category: Gameplay Classes
|
Category: Gameplay Classes
|
||||||
@ -187,7 +190,7 @@ class CoopGameActivity(GameActivity):
|
|||||||
vval -= 55
|
vval -= 55
|
||||||
|
|
||||||
def spawn_player_spaz(self,
|
def spawn_player_spaz(self,
|
||||||
player: ba.Player,
|
player: PlayerType,
|
||||||
position: Sequence[float] = (0.0, 0.0, 0.0),
|
position: Sequence[float] = (0.0, 0.0, 0.0),
|
||||||
angle: float = None) -> PlayerSpaz:
|
angle: float = None) -> PlayerSpaz:
|
||||||
"""Spawn and wire up a standard player spaz."""
|
"""Spawn and wire up a standard player spaz."""
|
||||||
|
|||||||
@ -173,9 +173,9 @@ class CoopSession(Session):
|
|||||||
def get_custom_menu_entries(self) -> List[Dict[str, Any]]:
|
def get_custom_menu_entries(self) -> List[Dict[str, Any]]:
|
||||||
return self._custom_menu_ui
|
return self._custom_menu_ui
|
||||||
|
|
||||||
def on_player_leave(self, player: ba.Player) -> None:
|
def on_player_leave(self, sessionplayer: ba.SessionPlayer) -> None:
|
||||||
from ba._general import WeakCall
|
from ba._general import WeakCall
|
||||||
super().on_player_leave(player)
|
super().on_player_leave(sessionplayer)
|
||||||
|
|
||||||
# If all our players leave we wanna quit out of the session.
|
# If all our players leave we wanna quit out of the session.
|
||||||
_ba.timer(2.0, WeakCall(self._end_session_if_empty))
|
_ba.timer(2.0, WeakCall(self._end_session_if_empty))
|
||||||
@ -213,7 +213,7 @@ class CoopSession(Session):
|
|||||||
from bastd.ui.tournamententry import TournamentEntryWindow
|
from bastd.ui.tournamententry import TournamentEntryWindow
|
||||||
from ba._gameactivity import GameActivity
|
from ba._gameactivity import GameActivity
|
||||||
activity = self.getactivity()
|
activity = self.getactivity()
|
||||||
if activity is not None and not activity.is_expired():
|
if activity is not None and not activity.expired:
|
||||||
assert self.tournament_id is not None
|
assert self.tournament_id is not None
|
||||||
assert isinstance(activity, GameActivity)
|
assert isinstance(activity, GameActivity)
|
||||||
TournamentEntryWindow(tournament_id=self.tournament_id,
|
TournamentEntryWindow(tournament_id=self.tournament_id,
|
||||||
@ -235,7 +235,7 @@ class CoopSession(Session):
|
|||||||
# This method may get called from the UI context so make sure we
|
# This method may get called from the UI context so make sure we
|
||||||
# explicitly run in the activity's context.
|
# explicitly run in the activity's context.
|
||||||
activity = self.getactivity()
|
activity = self.getactivity()
|
||||||
if activity is not None and not activity.is_expired():
|
if activity is not None and not activity.expired:
|
||||||
activity.can_show_ad_on_death = True
|
activity.can_show_ad_on_death = True
|
||||||
with _ba.Context(activity):
|
with _ba.Context(activity):
|
||||||
activity.end(results={'outcome': 'restart'}, force=True)
|
activity.end(results={'outcome': 'restart'}, force=True)
|
||||||
|
|||||||
@ -31,16 +31,6 @@ if TYPE_CHECKING:
|
|||||||
import ba
|
import ba
|
||||||
|
|
||||||
|
|
||||||
class _UnhandledType:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# A special value that should be returned from handlemessage()
|
|
||||||
# functions for unhandled message types. This may result
|
|
||||||
# in fallback message types being attempted/etc.
|
|
||||||
UNHANDLED = _UnhandledType()
|
|
||||||
|
|
||||||
|
|
||||||
class DependencyError(Exception):
|
class DependencyError(Exception):
|
||||||
"""Exception raised when one or more ba.Dependency items are missing.
|
"""Exception raised when one or more ba.Dependency items are missing.
|
||||||
|
|
||||||
@ -73,6 +63,13 @@ class PlayerNotFoundError(NotFoundError):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class SessionPlayerNotFoundError(NotFoundError):
|
||||||
|
"""Exception raised when an expected ba.SessionPlayer does not exist.
|
||||||
|
|
||||||
|
category: Exception Classes
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class TeamNotFoundError(NotFoundError):
|
class TeamNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected ba.Team does not exist.
|
"""Exception raised when an expected ba.Team does not exist.
|
||||||
|
|
||||||
@ -80,6 +77,13 @@ class TeamNotFoundError(NotFoundError):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class SessionTeamNotFoundError(NotFoundError):
|
||||||
|
"""Exception raised when an expected ba.SessionTeam does not exist.
|
||||||
|
|
||||||
|
category: Exception Classes
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class NodeNotFoundError(NotFoundError):
|
class NodeNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected ba.Node does not exist.
|
"""Exception raised when an expected ba.Node does not exist.
|
||||||
|
|
||||||
|
|||||||
@ -24,12 +24,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
|
|
||||||
import _ba
|
|
||||||
from ba._activity import Activity
|
from ba._activity import Activity
|
||||||
from ba._score import ScoreInfo
|
from ba._score import ScoreInfo
|
||||||
from ba._lang import Lstr
|
from ba._lang import Lstr
|
||||||
|
import _ba
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import (List, Optional, Dict, Type, Any, Callable, Sequence,
|
from typing import (List, Optional, Dict, Type, Any, Callable, Sequence,
|
||||||
@ -38,8 +38,11 @@ if TYPE_CHECKING:
|
|||||||
from bastd.actor.bomb import TNTSpawner
|
from bastd.actor.bomb import TNTSpawner
|
||||||
import ba
|
import ba
|
||||||
|
|
||||||
|
PlayerType = TypeVar('PlayerType', bound='ba.Player')
|
||||||
|
TeamType = TypeVar('TeamType', bound='ba.Team')
|
||||||
|
|
||||||
class GameActivity(Activity):
|
|
||||||
|
class GameActivity(Activity[PlayerType, TeamType]):
|
||||||
"""Common base class for all game ba.Activities.
|
"""Common base class for all game ba.Activities.
|
||||||
|
|
||||||
category: Gameplay Classes
|
category: Gameplay Classes
|
||||||
@ -606,13 +609,13 @@ class GameActivity(Activity):
|
|||||||
self._setup_tournament_time_limit(
|
self._setup_tournament_time_limit(
|
||||||
max(5, data_t[0]['timeRemaining']))
|
max(5, data_t[0]['timeRemaining']))
|
||||||
|
|
||||||
def on_player_join(self, player: ba.Player) -> None:
|
def on_player_join(self, player: PlayerType) -> None:
|
||||||
super().on_player_join(player)
|
super().on_player_join(player)
|
||||||
|
|
||||||
# By default, just spawn a dude.
|
# By default, just spawn a dude.
|
||||||
self.spawn_player(player)
|
self.spawn_player(player)
|
||||||
|
|
||||||
def on_player_leave(self, player: ba.Player) -> None:
|
def on_player_leave(self, player: PlayerType) -> None:
|
||||||
from ba._general import Call
|
from ba._general import Call
|
||||||
from ba._messages import DieMessage, DeathType
|
from ba._messages import DieMessage, DeathType
|
||||||
|
|
||||||
@ -632,7 +635,7 @@ class GameActivity(Activity):
|
|||||||
from bastd.actor.playerspaz import PlayerSpazDeathMessage
|
from bastd.actor.playerspaz import PlayerSpazDeathMessage
|
||||||
if isinstance(msg, PlayerSpazDeathMessage):
|
if isinstance(msg, PlayerSpazDeathMessage):
|
||||||
|
|
||||||
player = msg.spaz.player
|
player = msg.getspaz(self).player
|
||||||
killer = msg.killerplayer
|
killer = msg.killerplayer
|
||||||
|
|
||||||
# Inform our score-set of the demise.
|
# Inform our score-set of the demise.
|
||||||
@ -642,7 +645,7 @@ class GameActivity(Activity):
|
|||||||
|
|
||||||
# Award the killer points if he's on a different team.
|
# Award the killer points if he's on a different team.
|
||||||
if killer and killer.team is not player.team:
|
if killer and killer.team is not player.team:
|
||||||
pts, importance = msg.spaz.get_death_points(msg.how)
|
pts, importance = msg.getspaz(self).get_death_points(msg.how)
|
||||||
if not self.has_ended():
|
if not self.has_ended():
|
||||||
self.stats.player_scored(killer,
|
self.stats.player_scored(killer,
|
||||||
pts,
|
pts,
|
||||||
@ -928,7 +931,7 @@ class GameActivity(Activity):
|
|||||||
print('WARNING: default end_game() implementation called;'
|
print('WARNING: default end_game() implementation called;'
|
||||||
' your game should override this.')
|
' your game should override this.')
|
||||||
|
|
||||||
def spawn_player_if_exists(self, player: ba.Player) -> None:
|
def spawn_player_if_exists(self, player: PlayerType) -> None:
|
||||||
"""
|
"""
|
||||||
A utility method which calls self.spawn_player() *only* if the
|
A utility method which calls self.spawn_player() *only* if the
|
||||||
ba.Player provided still exists; handy for use in timers and whatnot.
|
ba.Player provided still exists; handy for use in timers and whatnot.
|
||||||
@ -938,7 +941,7 @@ class GameActivity(Activity):
|
|||||||
if player:
|
if player:
|
||||||
self.spawn_player(player)
|
self.spawn_player(player)
|
||||||
|
|
||||||
def spawn_player(self, player: ba.Player) -> ba.Actor:
|
def spawn_player(self, player: PlayerType) -> ba.Actor:
|
||||||
"""Spawn *something* for the provided ba.Player.
|
"""Spawn *something* for the provided ba.Player.
|
||||||
|
|
||||||
The default implementation simply calls spawn_player_spaz().
|
The default implementation simply calls spawn_player_spaz().
|
||||||
@ -949,7 +952,7 @@ class GameActivity(Activity):
|
|||||||
return self.spawn_player_spaz(player)
|
return self.spawn_player_spaz(player)
|
||||||
|
|
||||||
def respawn_player(self,
|
def respawn_player(self,
|
||||||
player: ba.Player,
|
player: PlayerType,
|
||||||
respawn_time: Optional[float] = None) -> None:
|
respawn_time: Optional[float] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Given a ba.Player, sets up a standard respawn timer,
|
Given a ba.Player, sets up a standard respawn timer,
|
||||||
@ -989,7 +992,7 @@ class GameActivity(Activity):
|
|||||||
player.gamedata['respawn_icon'] = RespawnIcon(player, respawn_time)
|
player.gamedata['respawn_icon'] = RespawnIcon(player, respawn_time)
|
||||||
|
|
||||||
def spawn_player_spaz(self,
|
def spawn_player_spaz(self,
|
||||||
player: ba.Player,
|
player: PlayerType,
|
||||||
position: Sequence[float] = (0, 0, 0),
|
position: Sequence[float] = (0, 0, 0),
|
||||||
angle: float = None) -> PlayerSpaz:
|
angle: float = None) -> PlayerSpaz:
|
||||||
"""Create and wire up a ba.PlayerSpaz for the provided ba.Player."""
|
"""Create and wire up a ba.PlayerSpaz for the provided ba.Player."""
|
||||||
|
|||||||
@ -26,9 +26,11 @@ import weakref
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ba._team import Team
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from weakref import ReferenceType
|
from weakref import ReferenceType
|
||||||
from typing import Sequence, Tuple, Any, Optional, Dict, List
|
from typing import Sequence, Tuple, Any, Optional, Dict, List, Union
|
||||||
import ba
|
import ba
|
||||||
|
|
||||||
|
|
||||||
@ -36,7 +38,7 @@ if TYPE_CHECKING:
|
|||||||
class WinnerGroup:
|
class WinnerGroup:
|
||||||
"""Entry for a winning team or teams calculated by game-results."""
|
"""Entry for a winning team or teams calculated by game-results."""
|
||||||
score: Optional[int]
|
score: Optional[int]
|
||||||
teams: Sequence[ba.Team]
|
teams: Sequence[ba.SessionTeam]
|
||||||
|
|
||||||
|
|
||||||
class TeamGameResults:
|
class TeamGameResults:
|
||||||
@ -52,9 +54,9 @@ class TeamGameResults:
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Instantiate a results instance."""
|
"""Instantiate a results instance."""
|
||||||
self._game_set = False
|
self._game_set = False
|
||||||
self._scores: Dict[int, Tuple[ReferenceType[ba.Team],
|
self._scores: Dict[int, Tuple[ReferenceType[ba.SessionTeam],
|
||||||
Optional[int]]] = {}
|
Optional[int]]] = {}
|
||||||
self._teams: Optional[List[ReferenceType[ba.Team]]] = None
|
self._teams: Optional[List[ReferenceType[ba.SessionTeam]]] = None
|
||||||
self._player_info: Optional[List[Dict[str, Any]]] = None
|
self._player_info: Optional[List[Dict[str, Any]]] = None
|
||||||
self._lower_is_better: Optional[bool] = None
|
self._lower_is_better: Optional[bool] = None
|
||||||
self._score_label: Optional[str] = None
|
self._score_label: Optional[str] = None
|
||||||
@ -74,16 +76,22 @@ class TeamGameResults:
|
|||||||
self._none_is_winner = score_info.none_is_winner
|
self._none_is_winner = score_info.none_is_winner
|
||||||
self._score_type = score_info.scoretype
|
self._score_type = score_info.scoretype
|
||||||
|
|
||||||
def set_team_score(self, team: ba.Team, score: int) -> None:
|
def set_team_score(self, team: Union[ba.SessionTeam, ba.Team],
|
||||||
|
score: int) -> None:
|
||||||
"""Set the score for a given ba.Team.
|
"""Set the score for a given ba.Team.
|
||||||
|
|
||||||
This can be a number or None.
|
This can be a number or None.
|
||||||
(see the none_is_winner arg in the constructor)
|
(see the none_is_winner arg in the constructor)
|
||||||
"""
|
"""
|
||||||
self._scores[team.get_id()] = (weakref.ref(team), score)
|
if isinstance(team, Team):
|
||||||
|
team = team.sessionteam
|
||||||
|
self._scores[team.id] = (weakref.ref(team), score)
|
||||||
|
|
||||||
def get_team_score(self, team: ba.Team) -> Optional[int]:
|
def get_team_score(self, team: Union[ba.SessionTeam,
|
||||||
|
ba.Team]) -> Optional[int]:
|
||||||
"""Return the score for a given team."""
|
"""Return the score for a given team."""
|
||||||
|
if isinstance(team, Team):
|
||||||
|
team = team.sessionteam
|
||||||
for score in list(self._scores.values()):
|
for score in list(self._scores.values()):
|
||||||
if score[0]() is team:
|
if score[0]() is team:
|
||||||
return score[1]
|
return score[1]
|
||||||
@ -91,8 +99,8 @@ class TeamGameResults:
|
|||||||
# If we have no score value, assume None.
|
# If we have no score value, assume None.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_teams(self) -> List[ba.Team]:
|
def get_teams(self) -> List[ba.SessionTeam]:
|
||||||
"""Return all ba.Teams in the results."""
|
"""Return all ba.SessionTeams in the results."""
|
||||||
if not self._game_set:
|
if not self._game_set:
|
||||||
raise RuntimeError("Can't get teams until game is set.")
|
raise RuntimeError("Can't get teams until game is set.")
|
||||||
teams = []
|
teams = []
|
||||||
@ -103,12 +111,9 @@ class TeamGameResults:
|
|||||||
teams.append(team)
|
teams.append(team)
|
||||||
return teams
|
return teams
|
||||||
|
|
||||||
def has_score_for_team(self, team: ba.Team) -> bool:
|
def has_score_for_team(self, sessionteam: ba.SessionTeam) -> bool:
|
||||||
"""Return whether there is a score for a given team."""
|
"""Return whether there is a score for a given team."""
|
||||||
for score in list(self._scores.values()):
|
return any(s[0]() is sessionteam for s in self._scores.values())
|
||||||
if score[0]() is team:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_team_score_str(self, team: ba.Team) -> ba.Lstr:
|
def get_team_score_str(self, team: ba.Team) -> ba.Lstr:
|
||||||
"""Return the score for the given ba.Team as an Lstr.
|
"""Return the score for the given ba.Team as an Lstr.
|
||||||
@ -122,7 +127,7 @@ class TeamGameResults:
|
|||||||
if not self._game_set:
|
if not self._game_set:
|
||||||
raise RuntimeError("Can't get team-score-str until game is set.")
|
raise RuntimeError("Can't get team-score-str until game is set.")
|
||||||
for score in list(self._scores.values()):
|
for score in list(self._scores.values()):
|
||||||
if score[0]() is team:
|
if score[0]() is team.sessionteam:
|
||||||
if score[1] is None:
|
if score[1] is None:
|
||||||
return Lstr(value='-')
|
return Lstr(value='-')
|
||||||
if self._score_type is ScoreType.SECONDS:
|
if self._score_type is ScoreType.SECONDS:
|
||||||
@ -164,7 +169,7 @@ class TeamGameResults:
|
|||||||
assert self._lower_is_better is not None
|
assert self._lower_is_better is not None
|
||||||
return self._lower_is_better
|
return self._lower_is_better
|
||||||
|
|
||||||
def get_winning_team(self) -> Optional[ba.Team]:
|
def get_winning_team(self) -> Optional[ba.SessionTeam]:
|
||||||
"""Get the winning ba.Team if there is exactly one; None otherwise."""
|
"""Get the winning ba.Team if there is exactly one; None otherwise."""
|
||||||
if not self._game_set:
|
if not self._game_set:
|
||||||
raise RuntimeError("Can't get winners until game is set.")
|
raise RuntimeError("Can't get winners until game is set.")
|
||||||
@ -179,7 +184,7 @@ class TeamGameResults:
|
|||||||
raise RuntimeError("Can't get winners until game is set.")
|
raise RuntimeError("Can't get winners until game is set.")
|
||||||
|
|
||||||
# Group by best scoring teams.
|
# Group by best scoring teams.
|
||||||
winners: Dict[int, List[ba.Team]] = {}
|
winners: Dict[int, List[ba.SessionTeam]] = {}
|
||||||
scores = [
|
scores = [
|
||||||
score for score in self._scores.values()
|
score for score in self._scores.values()
|
||||||
if score[0]() is not None and score[1] is not None
|
if score[0]() is not None and score[1] is not None
|
||||||
@ -191,11 +196,11 @@ class TeamGameResults:
|
|||||||
assert team is not None
|
assert team is not None
|
||||||
sval.append(team)
|
sval.append(team)
|
||||||
results: List[Tuple[Optional[int],
|
results: List[Tuple[Optional[int],
|
||||||
List[ba.Team]]] = list(winners.items())
|
List[ba.SessionTeam]]] = list(winners.items())
|
||||||
results.sort(reverse=not self._lower_is_better, key=lambda x: x[0])
|
results.sort(reverse=not self._lower_is_better, key=lambda x: x[0])
|
||||||
|
|
||||||
# Also group the 'None' scores.
|
# Also group the 'None' scores.
|
||||||
none_teams: List[ba.Team] = []
|
none_teams: List[ba.SessionTeam] = []
|
||||||
for score in self._scores.values():
|
for score in self._scores.values():
|
||||||
scoreteam = score[0]()
|
scoreteam = score[0]()
|
||||||
if scoreteam is not None and score[1] is None:
|
if scoreteam is not None and score[1] is None:
|
||||||
@ -205,7 +210,7 @@ class TeamGameResults:
|
|||||||
# depending on the rules).
|
# depending on the rules).
|
||||||
if none_teams:
|
if none_teams:
|
||||||
nones: List[Tuple[Optional[int],
|
nones: List[Tuple[Optional[int],
|
||||||
List[ba.Team]]] = [(None, none_teams)]
|
List[ba.SessionTeam]]] = [(None, none_teams)]
|
||||||
if self._none_is_winner:
|
if self._none_is_winner:
|
||||||
results = nones + results
|
results = nones + results
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import _ba
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Type
|
from typing import Any, Type
|
||||||
from efro.call import Call
|
from efro.call import Call as Call # 'as Call' so we re-export.
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ from typing import TYPE_CHECKING
|
|||||||
import _ba
|
import _ba
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import List, Sequence, Optional
|
from typing import List, Sequence, Optional, Dict, Any
|
||||||
import ba
|
import ba
|
||||||
|
|
||||||
|
|
||||||
@ -346,3 +346,13 @@ def local_chat_message(msg: str) -> None:
|
|||||||
def handle_remote_achievement_list(completed_achievements: List[str]) -> None:
|
def handle_remote_achievement_list(completed_achievements: List[str]) -> None:
|
||||||
from ba import _achievement
|
from ba import _achievement
|
||||||
_achievement.set_completed_achievements(completed_achievements)
|
_achievement.set_completed_achievements(completed_achievements)
|
||||||
|
|
||||||
|
|
||||||
|
def get_player_icon(sessionplayer: ba.SessionPlayer) -> Dict[str, Any]:
|
||||||
|
info = sessionplayer.get_icon_info()
|
||||||
|
return {
|
||||||
|
'texture': _ba.gettexture(info['texture']),
|
||||||
|
'tint_texture': _ba.gettexture(info['tint_texture']),
|
||||||
|
'tint_color': info['tint_color'],
|
||||||
|
'tint2_color': info['tint2_color']
|
||||||
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
MAX_QUICK_CHANGE_COUNT = 30
|
MAX_QUICK_CHANGE_COUNT = 30
|
||||||
QUICK_CHANGE_INTERVAL = 0.05
|
QUICK_CHANGE_INTERVAL = 0.05
|
||||||
|
QUICK_CHANGE_RESET_INTERVAL = 1.0
|
||||||
|
|
||||||
|
|
||||||
# Hmm should we move this to actors?..
|
# Hmm should we move this to actors?..
|
||||||
@ -147,7 +148,7 @@ class Chooser:
|
|||||||
if self._text_node:
|
if self._text_node:
|
||||||
self._text_node.delete()
|
self._text_node.delete()
|
||||||
|
|
||||||
def __init__(self, vpos: float, player: _ba.Player,
|
def __init__(self, vpos: float, player: _ba.SessionPlayer,
|
||||||
lobby: 'Lobby') -> None:
|
lobby: 'Lobby') -> None:
|
||||||
# FIXME: Tidy up around here.
|
# FIXME: Tidy up around here.
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
@ -326,7 +327,7 @@ class Chooser:
|
|||||||
self._inited = True
|
self._inited = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def player(self) -> ba.Player:
|
def player(self) -> ba.SessionPlayer:
|
||||||
"""The ba.Player associated with this chooser."""
|
"""The ba.Player associated with this chooser."""
|
||||||
return self._player
|
return self._player
|
||||||
|
|
||||||
@ -343,7 +344,7 @@ class Chooser:
|
|||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
self._dead = val
|
self._dead = val
|
||||||
|
|
||||||
def get_team(self) -> ba.Team:
|
def get_team(self) -> ba.SessionTeam:
|
||||||
"""Return this chooser's selected ba.Team."""
|
"""Return this chooser's selected ba.Team."""
|
||||||
return self.lobby.teams[self._selected_team_index]
|
return self.lobby.teams[self._selected_team_index]
|
||||||
|
|
||||||
@ -641,18 +642,17 @@ class Chooser:
|
|||||||
# choosers that have been marked as ready.
|
# choosers that have been marked as ready.
|
||||||
team_player_counts = {}
|
team_player_counts = {}
|
||||||
for team in teams:
|
for team in teams:
|
||||||
team_player_counts[team.get_id()] = (len(team.players))
|
team_player_counts[team.id] = (len(team.players))
|
||||||
for chooser in lobby.choosers:
|
for chooser in lobby.choosers:
|
||||||
if chooser.ready:
|
if chooser.ready:
|
||||||
team_player_counts[
|
team_player_counts[chooser.get_team().id] += 1
|
||||||
chooser.get_team().get_id()] += 1
|
|
||||||
largest_team_size = max(team_player_counts.values())
|
largest_team_size = max(team_player_counts.values())
|
||||||
smallest_team_size = (min(team_player_counts.values()))
|
smallest_team_size = (min(team_player_counts.values()))
|
||||||
|
|
||||||
# Force switch if we're on the biggest team
|
# Force switch if we're on the biggest team
|
||||||
# and there's a smaller one available.
|
# and there's a smaller one available.
|
||||||
if (largest_team_size != smallest_team_size
|
if (largest_team_size != smallest_team_size
|
||||||
and team_player_counts[self.get_team().get_id()] >=
|
and team_player_counts[self.get_team().id] >=
|
||||||
largest_team_size):
|
largest_team_size):
|
||||||
force_team_switch = True
|
force_team_switch = True
|
||||||
|
|
||||||
@ -664,17 +664,24 @@ class Chooser:
|
|||||||
_ba.playsound(self._punchsound)
|
_ba.playsound(self._punchsound)
|
||||||
self._set_ready(ready)
|
self._set_ready(ready)
|
||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
# TODO: should handle this at the engine layer so this is unnecessary.
|
||||||
"""Standard generic message handler."""
|
def _handle_repeat_message_attack(self) -> None:
|
||||||
if isinstance(msg, ChangeMessage):
|
now = _ba.time()
|
||||||
now = _ba.time()
|
count = self.last_change[1]
|
||||||
count = self.last_change[1] + 1
|
if now - self.last_change[0] < QUICK_CHANGE_INTERVAL:
|
||||||
if (now - self.last_change[0] < QUICK_CHANGE_INTERVAL
|
count += 1
|
||||||
and count > MAX_QUICK_CHANGE_COUNT):
|
if count > MAX_QUICK_CHANGE_COUNT:
|
||||||
# Hmm maybe we should notify client?
|
|
||||||
_ba.disconnect_client(
|
_ba.disconnect_client(
|
||||||
self._player.get_input_device().client_id)
|
self._player.get_input_device().client_id)
|
||||||
self.last_change = (now, count)
|
elif now - self.last_change[0] > QUICK_CHANGE_RESET_INTERVAL:
|
||||||
|
count = 0
|
||||||
|
self.last_change = (now, count)
|
||||||
|
|
||||||
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
|
"""Standard generic message handler."""
|
||||||
|
|
||||||
|
if isinstance(msg, ChangeMessage):
|
||||||
|
self._handle_repeat_message_attack()
|
||||||
|
|
||||||
# If we've been removed from the lobby, ignore this stuff.
|
# If we've been removed from the lobby, ignore this stuff.
|
||||||
if self._dead:
|
if self._dead:
|
||||||
@ -806,7 +813,7 @@ class Chooser:
|
|||||||
highlight[(max_index + 2) % 3] += diff * 0.2
|
highlight[(max_index + 2) % 3] += diff * 0.2
|
||||||
return highlight
|
return highlight
|
||||||
|
|
||||||
def getplayer(self) -> ba.Player:
|
def getplayer(self) -> ba.SessionPlayer:
|
||||||
"""Return the player associated with this chooser."""
|
"""Return the player associated with this chooser."""
|
||||||
return self._player
|
return self._player
|
||||||
|
|
||||||
@ -890,7 +897,7 @@ class Lobby:
|
|||||||
if teams is not None:
|
if teams is not None:
|
||||||
self._teams = [weakref.ref(team) for team in teams]
|
self._teams = [weakref.ref(team) for team in teams]
|
||||||
else:
|
else:
|
||||||
self._dummy_teams = bs_team.Team()
|
self._dummy_teams = bs_team.SessionTeam()
|
||||||
self._teams = [weakref.ref(self._dummy_teams)]
|
self._teams = [weakref.ref(self._dummy_teams)]
|
||||||
v_offset = (-150
|
v_offset = (-150
|
||||||
if isinstance(session, _coopsession.CoopSession) else -50)
|
if isinstance(session, _coopsession.CoopSession) else -50)
|
||||||
@ -920,7 +927,7 @@ class Lobby:
|
|||||||
return self._use_team_colors
|
return self._use_team_colors
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def teams(self) -> List[ba.Team]:
|
def teams(self) -> List[ba.SessionTeam]:
|
||||||
"""Teams available in this lobby."""
|
"""Teams available in this lobby."""
|
||||||
allteams = []
|
allteams = []
|
||||||
for tref in self._teams:
|
for tref in self._teams:
|
||||||
@ -974,14 +981,14 @@ class Lobby:
|
|||||||
"""Return whether all choosers are marked ready."""
|
"""Return whether all choosers are marked ready."""
|
||||||
return all(chooser.ready for chooser in self.choosers)
|
return all(chooser.ready for chooser in self.choosers)
|
||||||
|
|
||||||
def add_chooser(self, player: ba.Player) -> None:
|
def add_chooser(self, player: ba.SessionPlayer) -> None:
|
||||||
"""Add a chooser to the lobby for the provided player."""
|
"""Add a chooser to the lobby for the provided player."""
|
||||||
self.choosers.append(
|
self.choosers.append(
|
||||||
Chooser(vpos=self._vpos, player=player, lobby=self))
|
Chooser(vpos=self._vpos, player=player, lobby=self))
|
||||||
self._next_add_team = (self._next_add_team + 1) % len(self._teams)
|
self._next_add_team = (self._next_add_team + 1) % len(self._teams)
|
||||||
self._vpos -= 48
|
self._vpos -= 48
|
||||||
|
|
||||||
def remove_chooser(self, player: ba.Player) -> None:
|
def remove_chooser(self, player: ba.SessionPlayer) -> None:
|
||||||
"""Remove a single player's chooser; does not kick him.
|
"""Remove a single player's chooser; does not kick him.
|
||||||
|
|
||||||
This is used when a player enters the game and no longer
|
This is used when a player enters the game and no longer
|
||||||
|
|||||||
@ -33,6 +33,16 @@ if TYPE_CHECKING:
|
|||||||
import ba
|
import ba
|
||||||
|
|
||||||
|
|
||||||
|
class _UnhandledType:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# A special value that should be returned from handlemessage()
|
||||||
|
# functions for unhandled message types. This may result
|
||||||
|
# in fallback message types being attempted/etc.
|
||||||
|
UNHANDLED = _UnhandledType()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OutOfBoundsMessage:
|
class OutOfBoundsMessage:
|
||||||
"""A message telling an object that it is out of bounds.
|
"""A message telling an object that it is out of bounds.
|
||||||
|
|||||||
@ -27,6 +27,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import _ba
|
import _ba
|
||||||
from ba._session import Session
|
from ba._session import Session
|
||||||
|
from ba._error import NotFoundError, print_error
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Optional, Any, Dict, List, Type, Sequence
|
from typing import Optional, Any, Dict, List, Type, Sequence
|
||||||
@ -155,7 +156,7 @@ class MultiTeamSession(Session):
|
|||||||
"""Returns which game in the series is currently being played."""
|
"""Returns which game in the series is currently being played."""
|
||||||
return self._game_number
|
return self._game_number
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
def on_team_join(self, team: ba.SessionTeam) -> None:
|
||||||
team.sessiondata['previous_score'] = team.sessiondata['score'] = 0
|
team.sessiondata['previous_score'] = team.sessiondata['score'] = 0
|
||||||
|
|
||||||
def get_max_players(self) -> int:
|
def get_max_players(self) -> int:
|
||||||
@ -171,7 +172,6 @@ class MultiTeamSession(Session):
|
|||||||
|
|
||||||
def on_activity_end(self, activity: ba.Activity, results: Any) -> None:
|
def on_activity_end(self, activity: ba.Activity, results: Any) -> None:
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
from ba import _error
|
|
||||||
from bastd.tutorial import TutorialActivity
|
from bastd.tutorial import TutorialActivity
|
||||||
from bastd.activity.multiteamvictory import (
|
from bastd.activity.multiteamvictory import (
|
||||||
TeamSeriesVictoryScoreScreenActivity)
|
TeamSeriesVictoryScoreScreenActivity)
|
||||||
@ -223,7 +223,7 @@ class MultiTeamSession(Session):
|
|||||||
# (ie: no longer sitting in the lobby).
|
# (ie: no longer sitting in the lobby).
|
||||||
try:
|
try:
|
||||||
has_team = (player.team is not None)
|
has_team = (player.team is not None)
|
||||||
except _error.TeamNotFoundError:
|
except NotFoundError:
|
||||||
has_team = False
|
has_team = False
|
||||||
if has_team:
|
if has_team:
|
||||||
self.stats.register_player(player)
|
self.stats.register_player(player)
|
||||||
@ -238,9 +238,8 @@ class MultiTeamSession(Session):
|
|||||||
|
|
||||||
def _switch_to_score_screen(self, results: Any) -> None:
|
def _switch_to_score_screen(self, results: Any) -> None:
|
||||||
"""Switch to a score screen after leaving a round."""
|
"""Switch to a score screen after leaving a round."""
|
||||||
from ba import _error
|
|
||||||
del results # Unused arg.
|
del results # Unused arg.
|
||||||
_error.print_error('this should be overridden')
|
print_error('this should be overridden')
|
||||||
|
|
||||||
def announce_game_results(self,
|
def announce_game_results(self,
|
||||||
activity: ba.GameActivity,
|
activity: ba.GameActivity,
|
||||||
@ -269,7 +268,8 @@ class MultiTeamSession(Session):
|
|||||||
if winning_team is not None:
|
if winning_team is not None:
|
||||||
# Have all players celebrate.
|
# Have all players celebrate.
|
||||||
celebrate_msg = CelebrateMessage(duration=10.0)
|
celebrate_msg = CelebrateMessage(duration=10.0)
|
||||||
for player in winning_team.players:
|
assert winning_team.gameteam is not None
|
||||||
|
for player in winning_team.gameteam.players:
|
||||||
if player.actor:
|
if player.actor:
|
||||||
player.actor.handlemessage(celebrate_msg)
|
player.actor.handlemessage(celebrate_msg)
|
||||||
cameraflash()
|
cameraflash()
|
||||||
|
|||||||
@ -94,7 +94,7 @@ class ServerCallThread(threading.Thread):
|
|||||||
# this check manually?
|
# this check manually?
|
||||||
if self._activity is not None:
|
if self._activity is not None:
|
||||||
activity = self._activity()
|
activity = self._activity()
|
||||||
if activity is None or activity.is_expired():
|
if activity is None or activity.expired:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Technically we could do the same check for session contexts,
|
# Technically we could do the same check for session contexts,
|
||||||
|
|||||||
@ -20,35 +20,178 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
"""Player related functionality."""
|
"""Player related functionality."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import TYPE_CHECKING, TypeVar
|
|
||||||
|
from typing import TYPE_CHECKING, TypeVar, Generic
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Type
|
from typing import (Type, Optional, Sequence, Dict, Any, Union, Tuple,
|
||||||
|
Callable)
|
||||||
import ba
|
import ba
|
||||||
|
|
||||||
T = TypeVar('T')
|
TeamType = TypeVar('TeamType', bound='ba.Team')
|
||||||
|
|
||||||
|
|
||||||
class BasePlayerData:
|
class Player(Generic[TeamType]):
|
||||||
"""Base class for custom player data.
|
"""Testing."""
|
||||||
|
|
||||||
Category: Gameplay Classes
|
# Defining these types at the class level instead of in __init__ so
|
||||||
|
# that types are introspectable (these are still instance attrs).
|
||||||
|
team: TeamType
|
||||||
|
character: str
|
||||||
|
actor: Optional[ba.Actor]
|
||||||
|
color: Sequence[float]
|
||||||
|
highlight: Sequence[float]
|
||||||
|
_sessionplayer: ba.SessionPlayer
|
||||||
|
_nodeactor: Optional[ba.NodeActor]
|
||||||
|
|
||||||
A convenience class that can be used as a base class for custom
|
# Should aim to kill this eventually (at least gamedata).
|
||||||
per-game player data. It simply provides the ability to easily fetch
|
# Game-specific data can be tacked on to the per-game player class.
|
||||||
an instance of itself for a given ba.Player.
|
sessiondata: Dict
|
||||||
"""
|
gamedata: Dict
|
||||||
|
|
||||||
@classmethod
|
# NOTE: avoiding having any __init__() here since it seems to not
|
||||||
def get(cls: Type[T], player: ba.Player) -> T:
|
# get called by default if a dataclass inherits from us.
|
||||||
"""Return the custom player data associated with a player.
|
|
||||||
|
|
||||||
If one does not exist, it will be created.
|
def postinit(self, sessionplayer: ba.SessionPlayer) -> None:
|
||||||
|
"""Wire up a newly created player.
|
||||||
|
|
||||||
|
(internal)
|
||||||
"""
|
"""
|
||||||
|
from ba._nodeactor import NodeActor
|
||||||
|
import _ba
|
||||||
|
self.actor = None
|
||||||
|
self.character = ''
|
||||||
|
self._nodeactor: Optional[ba.NodeActor] = None
|
||||||
|
self._sessionplayer = sessionplayer
|
||||||
|
self.character = sessionplayer.character
|
||||||
|
self.color = sessionplayer.color
|
||||||
|
self.highlight = sessionplayer.highlight
|
||||||
|
self.team = sessionplayer.team.gameteam # type: ignore
|
||||||
|
assert self.team is not None
|
||||||
|
self.sessiondata = sessionplayer.sessiondata
|
||||||
|
self.gamedata = sessionplayer.gamedata
|
||||||
|
|
||||||
# Store/return an instance of ourself in the player's per-game dict.
|
# Create our player node in the current activity.
|
||||||
data = player.gamedata.get('playerdata')
|
node = _ba.newnode('player', attrs={'playerID': sessionplayer.id})
|
||||||
if data is None:
|
self._nodeactor = NodeActor(node)
|
||||||
player.gamedata['playerdata'] = data = cls()
|
sessionplayer.set_node(node)
|
||||||
assert isinstance(data, cls)
|
|
||||||
return data
|
@property
|
||||||
|
def sessionplayer(self) -> ba.SessionPlayer:
|
||||||
|
"""Return the ba.SessionPlayer corresponding to this Player.
|
||||||
|
|
||||||
|
Throws a ba.SessionPlayerNotFoundError if it does not exist.
|
||||||
|
"""
|
||||||
|
if bool(self._sessionplayer):
|
||||||
|
return self._sessionplayer
|
||||||
|
from ba import _error
|
||||||
|
raise _error.SessionPlayerNotFoundError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node(self) -> ba.Node:
|
||||||
|
"""A ba.Node of type 'player' associated with this Player.
|
||||||
|
|
||||||
|
This node can be used to get a generic player position/etc.
|
||||||
|
"""
|
||||||
|
if not self._nodeactor:
|
||||||
|
from ba import _error
|
||||||
|
raise _error.NodeNotFoundError
|
||||||
|
return self._nodeactor.node
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exists(self) -> bool:
|
||||||
|
"""Whether the player still exists.
|
||||||
|
|
||||||
|
Most functionality will fail on a nonexistent player.
|
||||||
|
Note that you can also use the boolean operator for this same
|
||||||
|
functionality, so a statement such as "if player" will do
|
||||||
|
the right thing both for Player objects and values of None.
|
||||||
|
"""
|
||||||
|
return bool(self._sessionplayer)
|
||||||
|
|
||||||
|
def get_name(self, full: bool = False, icon: bool = True) -> str:
|
||||||
|
"""get_name(full: bool = False, icon: bool = True) -> str
|
||||||
|
|
||||||
|
Returns the player's name. If icon is True, the long version of the
|
||||||
|
name may include an icon.
|
||||||
|
"""
|
||||||
|
return self._sessionplayer.get_name(full=full, icon=icon)
|
||||||
|
|
||||||
|
def set_actor(self, actor: Optional[ba.Actor]) -> None:
|
||||||
|
"""set_actor(actor: Optional[ba.Actor]) -> None
|
||||||
|
|
||||||
|
Set the player's associated ba.Actor.
|
||||||
|
"""
|
||||||
|
self.actor = actor
|
||||||
|
|
||||||
|
def is_alive(self) -> bool:
|
||||||
|
"""is_alive() -> bool
|
||||||
|
|
||||||
|
Returns True if the player has a ba.Actor assigned and its
|
||||||
|
is_alive() method return True. False is returned otherwise.
|
||||||
|
"""
|
||||||
|
return self.actor is not None and self.actor.is_alive()
|
||||||
|
|
||||||
|
def get_icon(self) -> Dict[str, Any]:
|
||||||
|
"""get_icon() -> Dict[str, Any]
|
||||||
|
|
||||||
|
Returns the character's icon (images, colors, etc contained in a dict)
|
||||||
|
"""
|
||||||
|
return self._sessionplayer.get_icon()
|
||||||
|
|
||||||
|
def assign_input_call(self, inputtype: Union[str, Tuple[str, ...]],
|
||||||
|
call: Callable) -> None:
|
||||||
|
"""assign_input_call(type: Union[str, Tuple[str, ...]],
|
||||||
|
call: Callable) -> None
|
||||||
|
|
||||||
|
Set the python callable to be run for one or more types of input.
|
||||||
|
Valid type values are: 'jumpPress', 'jumpRelease', 'punchPress',
|
||||||
|
'punchRelease','bombPress', 'bombRelease', 'pickUpPress',
|
||||||
|
'pickUpRelease', 'upDown','leftRight','upPress', 'upRelease',
|
||||||
|
'downPress', 'downRelease', 'leftPress','leftRelease','rightPress',
|
||||||
|
'rightRelease', 'run', 'flyPress', 'flyRelease', 'startPress',
|
||||||
|
'startRelease'
|
||||||
|
"""
|
||||||
|
return self._sessionplayer.assign_input_call(type=inputtype, call=call)
|
||||||
|
|
||||||
|
def reset_input(self) -> None:
|
||||||
|
"""reset_input() -> None
|
||||||
|
|
||||||
|
Clears out the player's assigned input actions.
|
||||||
|
"""
|
||||||
|
self._sessionplayer.reset_input()
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return bool(self._sessionplayer)
|
||||||
|
|
||||||
|
|
||||||
|
PlayerType = TypeVar('PlayerType', bound='ba.Player')
|
||||||
|
|
||||||
|
|
||||||
|
def playercast(totype: Type[PlayerType], player: ba.Player) -> PlayerType:
|
||||||
|
"""Cast a ba.Player to a specific ba.Player subclass.
|
||||||
|
|
||||||
|
Category: Gameplay Functions
|
||||||
|
|
||||||
|
When writing type-checked code, sometimes code will deal with raw
|
||||||
|
ba.Player objects which need to be cast back to the game's actual
|
||||||
|
player type so that access can be properly type-checked. This function
|
||||||
|
is a safe way to do so. It ensures that Optional values are not cast
|
||||||
|
into Non-Optional, etc.
|
||||||
|
"""
|
||||||
|
assert isinstance(player, totype)
|
||||||
|
return player
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: ideally we should have a single playercast() call and use overloads
|
||||||
|
# for the optional variety, but that currently seems to not be working.
|
||||||
|
# See: https://github.com/python/mypy/issues/8800
|
||||||
|
def playercast_o(totype: Type[PlayerType],
|
||||||
|
player: Optional[ba.Player]) -> Optional[PlayerType]:
|
||||||
|
"""A variant of ba.playercast() for use with optional ba.Player values.
|
||||||
|
|
||||||
|
Category: Gameplay Functions
|
||||||
|
"""
|
||||||
|
# noinspection PyTypeHints
|
||||||
|
assert isinstance(player, (totype, type(None)))
|
||||||
|
return player
|
||||||
|
|||||||
@ -25,11 +25,12 @@ import weakref
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import _ba
|
import _ba
|
||||||
|
from ba._error import print_error, print_exception
|
||||||
|
from ba._lang import Lstr
|
||||||
|
from ba._player import Player
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from weakref import ReferenceType
|
|
||||||
from typing import Sequence, List, Dict, Any, Optional, Set
|
from typing import Sequence, List, Dict, Any, Optional, Set
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
|
|
||||||
|
|
||||||
@ -81,8 +82,8 @@ class Session:
|
|||||||
lobby: ba.Lobby
|
lobby: ba.Lobby
|
||||||
max_players: int
|
max_players: int
|
||||||
min_players: int
|
min_players: int
|
||||||
players: List[ba.Player]
|
players: List[ba.SessionPlayer]
|
||||||
teams: List[ba.Team]
|
teams: List[ba.SessionTeam]
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
depsets: Sequence[ba.DependencySet],
|
depsets: Sequence[ba.DependencySet],
|
||||||
@ -104,9 +105,11 @@ class Session:
|
|||||||
from ba._stats import Stats
|
from ba._stats import Stats
|
||||||
from ba._gameutils import sharedobj
|
from ba._gameutils import sharedobj
|
||||||
from ba._gameactivity import GameActivity
|
from ba._gameactivity import GameActivity
|
||||||
from ba._team import Team
|
from ba._activity import Activity
|
||||||
|
from ba._team import SessionTeam
|
||||||
from ba._error import DependencyError
|
from ba._error import DependencyError
|
||||||
from ba._dependency import Dependency, AssetPackage
|
from ba._dependency import Dependency, AssetPackage
|
||||||
|
from efro.util import empty_weakref
|
||||||
|
|
||||||
# First off, resolve all dependency-sets we were passed.
|
# First off, resolve all dependency-sets we were passed.
|
||||||
# If things are missing, we'll try to gather them into a single
|
# If things are missing, we'll try to gather them into a single
|
||||||
@ -143,21 +146,12 @@ class Session:
|
|||||||
# print('Would set host-session asset-reqs to:',
|
# print('Would set host-session asset-reqs to:',
|
||||||
# required_asset_packages)
|
# required_asset_packages)
|
||||||
|
|
||||||
# First thing, wire up our internal engine data.
|
# Stuff in this section should be removed from this class if possible.
|
||||||
self._sessiondata = _ba.register_session(self)
|
self._sessiondata = _ba.register_session(self)
|
||||||
|
|
||||||
self.tournament_id: Optional[str] = None
|
self.tournament_id: Optional[str] = None
|
||||||
|
|
||||||
# FIXME: This stuff shouldn't be here.
|
|
||||||
self.sharedobjs: Dict[str, Any] = {}
|
self.sharedobjs: Dict[str, Any] = {}
|
||||||
|
|
||||||
# TeamGameActivity uses this to display a help overlay on the first
|
|
||||||
# activity only.
|
|
||||||
self.have_shown_controls_help_overlay = False
|
self.have_shown_controls_help_overlay = False
|
||||||
|
|
||||||
self.campaign = None
|
self.campaign = None
|
||||||
|
|
||||||
# FIXME: Should be able to kill this I think.
|
|
||||||
self.campaign_state: Dict[str, str] = {}
|
self.campaign_state: Dict[str, str] = {}
|
||||||
|
|
||||||
self._use_teams = (team_names is not None)
|
self._use_teams = (team_names is not None)
|
||||||
@ -171,13 +165,7 @@ class Session:
|
|||||||
self._activity_retained: Optional[ba.Activity] = None
|
self._activity_retained: Optional[ba.Activity] = None
|
||||||
self.launch_end_session_activity_time: Optional[float] = None
|
self.launch_end_session_activity_time: Optional[float] = None
|
||||||
self._activity_end_timer: Optional[ba.Timer] = None
|
self._activity_end_timer: Optional[ba.Timer] = None
|
||||||
|
self._activity_weak = empty_weakref(Activity)
|
||||||
# Hacky way to create empty weak ref; must be a better way.
|
|
||||||
class _EmptyObj:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self._activity_weak: ReferenceType[ba.Activity]
|
|
||||||
self._activity_weak = weakref.ref(_EmptyObj()) # type: ignore
|
|
||||||
if self._activity_weak() is not None:
|
if self._activity_weak() is not None:
|
||||||
raise Exception('Error creating empty activity weak ref.')
|
raise Exception('Error creating empty activity weak ref.')
|
||||||
|
|
||||||
@ -192,10 +180,10 @@ class Session:
|
|||||||
assert team_names is not None
|
assert team_names is not None
|
||||||
assert team_colors is not None
|
assert team_colors is not None
|
||||||
for i, color in enumerate(team_colors):
|
for i, color in enumerate(team_colors):
|
||||||
team = Team(team_id=self._next_team_id,
|
team = SessionTeam(team_id=self._next_team_id,
|
||||||
name=GameActivity.get_team_display_string(
|
name=GameActivity.get_team_display_string(
|
||||||
team_names[i]),
|
team_names[i]),
|
||||||
color=color)
|
color=color)
|
||||||
self.teams.append(team)
|
self.teams.append(team)
|
||||||
self._next_team_id += 1
|
self._next_team_id += 1
|
||||||
|
|
||||||
@ -203,15 +191,12 @@ class Session:
|
|||||||
with _ba.Context(self):
|
with _ba.Context(self):
|
||||||
self.on_team_join(team)
|
self.on_team_join(team)
|
||||||
except Exception:
|
except Exception:
|
||||||
from ba import _error
|
print_exception(f'Error in on_team_join for {self}.')
|
||||||
_error.print_exception(
|
|
||||||
f'Error in on_team_join for {self}.')
|
|
||||||
|
|
||||||
self.lobby = Lobby()
|
self.lobby = Lobby()
|
||||||
self.stats = Stats()
|
self.stats = Stats()
|
||||||
|
|
||||||
# Instantiate our session globals node
|
# Instantiate our session globals node which will apply its settings.
|
||||||
# (so it can apply default settings).
|
|
||||||
sharedobj('globals')
|
sharedobj('globals')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -224,12 +209,11 @@ class Session:
|
|||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
return self._use_team_colors
|
return self._use_team_colors
|
||||||
|
|
||||||
def on_player_request(self, player: ba.Player) -> bool:
|
def on_player_request(self, player: ba.SessionPlayer) -> bool:
|
||||||
"""Called when a new ba.Player wants to join the Session.
|
"""Called when a new ba.Player wants to join the Session.
|
||||||
|
|
||||||
This should return True or False to accept/reject.
|
This should return True or False to accept/reject.
|
||||||
"""
|
"""
|
||||||
from ba._lang import Lstr
|
|
||||||
|
|
||||||
# Limit player counts *unless* we're in a stress test.
|
# Limit player counts *unless* we're in a stress test.
|
||||||
if _ba.app.stress_test_reset_timer is None:
|
if _ba.app.stress_test_reset_timer is None:
|
||||||
@ -250,144 +234,88 @@ class Session:
|
|||||||
_ba.playsound(_ba.getsound('dripity'))
|
_ba.playsound(_ba.getsound('dripity'))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def on_player_leave(self, player: ba.Player) -> None:
|
def on_player_leave(self, sessionplayer: ba.SessionPlayer) -> None:
|
||||||
"""Called when a previously-accepted ba.Player leaves the session."""
|
"""Called when a previously-accepted ba.SessionPlayer leaves."""
|
||||||
# pylint: disable=too-many-statements
|
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
# pylint: disable=cyclic-import
|
|
||||||
from ba._freeforallsession import FreeForAllSession
|
|
||||||
from ba._lang import Lstr
|
|
||||||
from ba import _error
|
|
||||||
|
|
||||||
# Remove them from the game rosters.
|
if sessionplayer not in self.players:
|
||||||
if player in self.players:
|
|
||||||
|
|
||||||
_ba.playsound(_ba.getsound('playerLeft'))
|
|
||||||
|
|
||||||
team: Optional[ba.Team]
|
|
||||||
|
|
||||||
# The player will have no team if they are still in the lobby.
|
|
||||||
try:
|
|
||||||
team = player.team
|
|
||||||
except _error.TeamNotFoundError:
|
|
||||||
team = None
|
|
||||||
|
|
||||||
activity = self._activity_weak()
|
|
||||||
|
|
||||||
# If he had no team, he's in the lobby.
|
|
||||||
# If we have a current activity with a lobby, ask them to
|
|
||||||
# remove him.
|
|
||||||
if team is None:
|
|
||||||
with _ba.Context(self):
|
|
||||||
try:
|
|
||||||
self.lobby.remove_chooser(player)
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
'Error in Lobby.remove_chooser()')
|
|
||||||
|
|
||||||
# *If* they were actually in the game, announce their departure.
|
|
||||||
if team is not None:
|
|
||||||
_ba.screenmessage(
|
|
||||||
Lstr(resource='playerLeftText',
|
|
||||||
subs=[('${PLAYER}', player.get_name(full=True))]))
|
|
||||||
|
|
||||||
# Remove him from his team and session lists.
|
|
||||||
# (he may not be on the team list since player are re-added to
|
|
||||||
# team lists every activity)
|
|
||||||
if team is not None and player in team.players:
|
|
||||||
|
|
||||||
# Testing; can remove this eventually.
|
|
||||||
if isinstance(self, FreeForAllSession):
|
|
||||||
if len(team.players) != 1:
|
|
||||||
_error.print_error('expected 1 player in FFA team')
|
|
||||||
team.players.remove(player)
|
|
||||||
|
|
||||||
# Remove player from any current activity.
|
|
||||||
if activity is not None and player in activity.players:
|
|
||||||
activity.players.remove(player)
|
|
||||||
|
|
||||||
# Run the activity callback unless its been expired.
|
|
||||||
if not activity.is_expired():
|
|
||||||
try:
|
|
||||||
with _ba.Context(activity):
|
|
||||||
activity.on_player_leave(player)
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
'exception in on_player_leave for activity',
|
|
||||||
activity)
|
|
||||||
else:
|
|
||||||
_error.print_error('expired activity in on_player_leave;'
|
|
||||||
" shouldn't happen")
|
|
||||||
|
|
||||||
player.set_activity(None)
|
|
||||||
player.set_node(None)
|
|
||||||
|
|
||||||
# Reset the player; this will remove its actor-ref and clear
|
|
||||||
# its calls/etc
|
|
||||||
try:
|
|
||||||
with _ba.Context(activity):
|
|
||||||
player.reset()
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
'exception in player.reset in'
|
|
||||||
' on_player_leave for player', player)
|
|
||||||
|
|
||||||
# If we're a non-team session, remove the player's team completely.
|
|
||||||
if not self._use_teams and team is not None:
|
|
||||||
|
|
||||||
# If the team's in an activity, call its on_team_leave
|
|
||||||
# callback.
|
|
||||||
if activity is not None and team in activity.teams:
|
|
||||||
activity.teams.remove(team)
|
|
||||||
|
|
||||||
if not activity.is_expired():
|
|
||||||
try:
|
|
||||||
with _ba.Context(activity):
|
|
||||||
activity.on_team_leave(team)
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
'exception in on_team_leave for activity',
|
|
||||||
activity)
|
|
||||||
else:
|
|
||||||
_error.print_error(
|
|
||||||
'expired activity in on_player_leave p2'
|
|
||||||
"; shouldn't happen")
|
|
||||||
|
|
||||||
# Clear the team's game-data (so dying stuff will
|
|
||||||
# have proper context).
|
|
||||||
try:
|
|
||||||
with _ba.Context(activity):
|
|
||||||
team.reset_gamedata()
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
'exception clearing gamedata for team:', team,
|
|
||||||
'for player:', player, 'in activity:', activity)
|
|
||||||
|
|
||||||
# Remove the team from the session.
|
|
||||||
self.teams.remove(team)
|
|
||||||
try:
|
|
||||||
with _ba.Context(self):
|
|
||||||
self.on_team_leave(team)
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
'exception in on_team_leave for session', self)
|
|
||||||
|
|
||||||
# Clear the team's session-data (so dying stuff will
|
|
||||||
# have proper context).
|
|
||||||
try:
|
|
||||||
with _ba.Context(self):
|
|
||||||
team.reset_sessiondata()
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
'exception clearing sessiondata for team:', team,
|
|
||||||
'in session:', self)
|
|
||||||
|
|
||||||
# Now remove them from the session list.
|
|
||||||
self.players.remove(player)
|
|
||||||
|
|
||||||
else:
|
|
||||||
print('ERROR: Session.on_player_leave called'
|
print('ERROR: Session.on_player_leave called'
|
||||||
' for player not in our list.')
|
' for player not in our list.')
|
||||||
|
return
|
||||||
|
|
||||||
|
_ba.playsound(_ba.getsound('playerLeft'))
|
||||||
|
|
||||||
|
activity = self._activity_weak()
|
||||||
|
|
||||||
|
if not sessionplayer.in_game:
|
||||||
|
# Ok, the player's still in the lobby. Simply remove them from it.
|
||||||
|
with _ba.Context(self):
|
||||||
|
try:
|
||||||
|
self.lobby.remove_chooser(sessionplayer)
|
||||||
|
except Exception:
|
||||||
|
print_exception('Error in Lobby.remove_chooser().')
|
||||||
|
else:
|
||||||
|
# Ok, they've already entered the game. Remove them from
|
||||||
|
# teams/activities/etc.
|
||||||
|
sessionteam = sessionplayer.team
|
||||||
|
assert sessionteam is not None
|
||||||
|
assert sessionplayer in sessionteam.players
|
||||||
|
|
||||||
|
_ba.screenmessage(
|
||||||
|
Lstr(resource='playerLeftText',
|
||||||
|
subs=[('${PLAYER}', sessionplayer.get_name(full=True))]))
|
||||||
|
|
||||||
|
# Remove them from their SessionTeam.
|
||||||
|
if sessionplayer in sessionteam.players:
|
||||||
|
sessionteam.players.remove(sessionplayer)
|
||||||
|
else:
|
||||||
|
print('SessionPlayer not found in SessionTeam'
|
||||||
|
' in on_player_leave.')
|
||||||
|
|
||||||
|
# Grab their activity-specific player instance.
|
||||||
|
player = sessionplayer.gameplayer
|
||||||
|
assert isinstance(player, (Player, type(None)))
|
||||||
|
|
||||||
|
# Remove them from any current Activity.
|
||||||
|
if activity is not None:
|
||||||
|
if player in activity.players:
|
||||||
|
activity.remove_player(sessionplayer)
|
||||||
|
else:
|
||||||
|
print('Player not found in Activity in on_player_leave.')
|
||||||
|
|
||||||
|
# If we're a non-team session, remove their team too.
|
||||||
|
if not self._use_teams:
|
||||||
|
|
||||||
|
# They should have been the only one on their team.
|
||||||
|
assert not sessionteam.players
|
||||||
|
|
||||||
|
# Remove their Team from the Activity.
|
||||||
|
if activity is not None:
|
||||||
|
if sessionteam.gameteam in activity.teams:
|
||||||
|
activity.remove_team(sessionteam)
|
||||||
|
else:
|
||||||
|
print('Team not found in Activity in on_player_leave.')
|
||||||
|
|
||||||
|
# And then from the Session.
|
||||||
|
with _ba.Context(self):
|
||||||
|
if sessionteam in self.teams:
|
||||||
|
try:
|
||||||
|
self.teams.remove(sessionteam)
|
||||||
|
self.on_team_leave(sessionteam)
|
||||||
|
except Exception:
|
||||||
|
print_exception(
|
||||||
|
f'Error in on_team_leave for Session {self}.')
|
||||||
|
else:
|
||||||
|
print('Team no in Session teams in on_player_leave.')
|
||||||
|
try:
|
||||||
|
sessionteam.reset_sessiondata()
|
||||||
|
except Exception:
|
||||||
|
print_exception(
|
||||||
|
f'Error clearing sessiondata'
|
||||||
|
f' for team {sessionteam} in session {self}.')
|
||||||
|
|
||||||
|
# Now remove them from the session list.
|
||||||
|
self.players.remove(sessionplayer)
|
||||||
|
|
||||||
def end(self) -> None:
|
def end(self) -> None:
|
||||||
"""Initiates an end to the session and a return to the main menu.
|
"""Initiates an end to the session and a return to the main menu.
|
||||||
@ -401,7 +329,6 @@ class Session:
|
|||||||
|
|
||||||
def launch_end_session_activity(self) -> None:
|
def launch_end_session_activity(self) -> None:
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
from ba import _error
|
|
||||||
from ba._activitytypes import EndSessionActivity
|
from ba._activitytypes import EndSessionActivity
|
||||||
from ba._enums import TimeType
|
from ba._enums import TimeType
|
||||||
with _ba.Context(self):
|
with _ba.Context(self):
|
||||||
@ -412,18 +339,18 @@ class Session:
|
|||||||
since_last = (curtime - self.launch_end_session_activity_time)
|
since_last = (curtime - self.launch_end_session_activity_time)
|
||||||
if since_last < 30.0:
|
if since_last < 30.0:
|
||||||
return
|
return
|
||||||
_error.print_error(
|
print_error(
|
||||||
'launch_end_session_activity called twice (since_last=' +
|
'launch_end_session_activity called twice (since_last=' +
|
||||||
str(since_last) + ')')
|
str(since_last) + ')')
|
||||||
self.launch_end_session_activity_time = curtime
|
self.launch_end_session_activity_time = curtime
|
||||||
self.set_activity(_ba.new_activity(EndSessionActivity))
|
self.set_activity(_ba.new_activity(EndSessionActivity))
|
||||||
self.wants_to_end = False
|
self.wants_to_end = False
|
||||||
self._ending = True # Prevent further activity-mucking.
|
self._ending = True # Prevent further actions.
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
def on_team_join(self, team: ba.SessionTeam) -> None:
|
||||||
"""Called when a new ba.Team joins the session."""
|
"""Called when a new ba.Team joins the session."""
|
||||||
|
|
||||||
def on_team_leave(self, team: ba.Team) -> None:
|
def on_team_leave(self, team: ba.SessionTeam) -> None:
|
||||||
"""Called when a ba.Team is leaving the session."""
|
"""Called when a ba.Team is leaving the session."""
|
||||||
|
|
||||||
def _complete_end_activity(self, activity: ba.Activity,
|
def _complete_end_activity(self, activity: ba.Activity,
|
||||||
@ -433,10 +360,8 @@ class Session:
|
|||||||
with _ba.Context(self):
|
with _ba.Context(self):
|
||||||
self.on_activity_end(activity, results)
|
self.on_activity_end(activity, results)
|
||||||
except Exception:
|
except Exception:
|
||||||
from ba import _error
|
print_exception('exception in on_activity_end() for session', self,
|
||||||
_error.print_exception(
|
'activity', activity, 'with results', results)
|
||||||
'exception in on_activity_end() for session', self, 'activity',
|
|
||||||
activity, 'with results', results)
|
|
||||||
|
|
||||||
def end_activity(self, activity: ba.Activity, results: Any, delay: float,
|
def end_activity(self, activity: ba.Activity, results: Any, delay: float,
|
||||||
force: bool) -> None:
|
force: bool) -> None:
|
||||||
@ -473,8 +398,7 @@ class Session:
|
|||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
"""General message handling; can be passed any message object."""
|
"""General message handling; can be passed any message object."""
|
||||||
from ba._lobby import PlayerReadyMessage
|
from ba._lobby import PlayerReadyMessage
|
||||||
from ba._error import UNHANDLED
|
from ba._messages import PlayerProfilesChangedMessage, UNHANDLED
|
||||||
from ba._messages import PlayerProfilesChangedMessage
|
|
||||||
if isinstance(msg, PlayerReadyMessage):
|
if isinstance(msg, PlayerReadyMessage):
|
||||||
self._on_player_ready(msg.chooser)
|
self._on_player_ready(msg.chooser)
|
||||||
return None
|
return None
|
||||||
@ -496,84 +420,46 @@ class Session:
|
|||||||
(on_transition_in, etc) to get it. (so you can't do
|
(on_transition_in, etc) to get it. (so you can't do
|
||||||
session.set_activity(foo) and then ba.newnode() to add a node to foo)
|
session.set_activity(foo) and then ba.newnode() to add a node to foo)
|
||||||
"""
|
"""
|
||||||
# pylint: disable=too-many-statements
|
|
||||||
# pylint: disable=too-many-branches
|
|
||||||
from ba import _error
|
|
||||||
from ba._gameutils import sharedobj
|
from ba._gameutils import sharedobj
|
||||||
from ba._enums import TimeType
|
from ba._enums import TimeType
|
||||||
|
|
||||||
# Sanity test: make sure this doesn't get called recursively.
|
# Sanity test: make sure this doesn't get called recursively.
|
||||||
if self._in_set_activity:
|
if self._in_set_activity:
|
||||||
raise Exception(
|
raise RuntimeError(
|
||||||
'Session.set_activity() cannot be called recursively.')
|
'Session.set_activity() cannot be called recursively.')
|
||||||
|
self._in_set_activity = True
|
||||||
|
|
||||||
if activity.session is not _ba.getsession():
|
if activity.session is not _ba.getsession():
|
||||||
raise Exception("Provided Activity's Session is not current.")
|
raise RuntimeError("Provided Activity's Session is not current.")
|
||||||
|
|
||||||
# Quietly ignore this if the whole session is going down.
|
# Quietly ignore this if the whole session is going down.
|
||||||
if self._ending:
|
if self._ending:
|
||||||
return
|
return
|
||||||
|
|
||||||
if activity is self._activity_retained:
|
if activity is self._activity_retained:
|
||||||
_error.print_error('activity set to already-current activity')
|
print_error('activity set to already-current activity')
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._next_activity is not None:
|
if self._next_activity is not None:
|
||||||
raise Exception('Activity switch already in progress (to ' +
|
raise RuntimeError('Activity switch already in progress (to ' +
|
||||||
str(self._next_activity) + ')')
|
str(self._next_activity) + ')')
|
||||||
|
|
||||||
self._in_set_activity = True
|
|
||||||
|
|
||||||
prev_activity = self._activity_retained
|
prev_activity = self._activity_retained
|
||||||
|
|
||||||
if prev_activity is not None:
|
if prev_activity is not None:
|
||||||
with _ba.Context(prev_activity):
|
with _ba.Context(prev_activity):
|
||||||
gprev = sharedobj('globals')
|
prev_globals = sharedobj('globals')
|
||||||
else:
|
else:
|
||||||
gprev = None
|
prev_globals = None
|
||||||
|
|
||||||
with _ba.Context(activity):
|
# Let the activity do its thing.
|
||||||
|
activity.transition_in(prev_globals)
|
||||||
# Now that it's going to be front and center,
|
|
||||||
# set some global values based on what the activity wants.
|
|
||||||
glb = sharedobj('globals')
|
|
||||||
glb.use_fixed_vr_overlay = activity.use_fixed_vr_overlay
|
|
||||||
glb.allow_kick_idle_players = activity.allow_kick_idle_players
|
|
||||||
if activity.inherits_slow_motion and gprev is not None:
|
|
||||||
glb.slow_motion = gprev.slow_motion
|
|
||||||
else:
|
|
||||||
glb.slow_motion = activity.slow_motion
|
|
||||||
if activity.inherits_music and gprev is not None:
|
|
||||||
glb.music_continuous = True # Prevent restarting same music.
|
|
||||||
glb.music = gprev.music
|
|
||||||
glb.music_count += 1
|
|
||||||
if activity.inherits_camera_vr_offset and gprev is not None:
|
|
||||||
glb.vr_camera_offset = gprev.vr_camera_offset
|
|
||||||
if activity.inherits_vr_overlay_center and gprev is not None:
|
|
||||||
glb.vr_overlay_center = gprev.vr_overlay_center
|
|
||||||
glb.vr_overlay_center_enabled = gprev.vr_overlay_center_enabled
|
|
||||||
|
|
||||||
# If they want to inherit tint from the previous activity.
|
|
||||||
if activity.inherits_tint and gprev is not None:
|
|
||||||
glb.tint = gprev.tint
|
|
||||||
glb.vignette_outer = gprev.vignette_outer
|
|
||||||
glb.vignette_inner = gprev.vignette_inner
|
|
||||||
|
|
||||||
# Let the activity do its thing.
|
|
||||||
activity.start_transition_in()
|
|
||||||
|
|
||||||
self._next_activity = activity
|
self._next_activity = activity
|
||||||
|
|
||||||
# If we have a current activity, tell it it's transitioning out;
|
# If we have a current activity, tell it it's transitioning out;
|
||||||
# the next one will become current once this one dies.
|
# the next one will become current once this one dies.
|
||||||
if prev_activity is not None:
|
if prev_activity is not None:
|
||||||
# pylint: disable=protected-access
|
prev_activity.transition_out()
|
||||||
prev_activity._transitioning_out = True
|
|
||||||
# pylint: enable=protected-access
|
|
||||||
|
|
||||||
# Activity will be None until the next one begins.
|
|
||||||
with _ba.Context(prev_activity):
|
|
||||||
prev_activity.on_transition_out()
|
|
||||||
|
|
||||||
# Setting this to None should free up the old activity to die,
|
# Setting this to None should free up the old activity to die,
|
||||||
# which will call begin_next_activity.
|
# which will call begin_next_activity.
|
||||||
@ -586,35 +472,15 @@ class Session:
|
|||||||
else:
|
else:
|
||||||
self.begin_next_activity()
|
self.begin_next_activity()
|
||||||
|
|
||||||
# Tell the C layer that this new activity is now 'foregrounded'.
|
# We want to call destroy() for the previous activity once it should
|
||||||
# This means that its globals node controls global stuff and stuff
|
# tear itself down, clear out any self-refs, etc. After this call
|
||||||
# like console operations, keyboard shortcuts, etc will run in it.
|
# the activity should have no refs left to it and should die (which
|
||||||
# pylint: disable=protected-access
|
# will trigger the next activity to run).
|
||||||
# noinspection PyProtectedMember
|
|
||||||
activity._activity_data.make_foreground()
|
|
||||||
# pylint: enable=protected-access
|
|
||||||
|
|
||||||
# We want to call _destroy() for the previous activity once it should
|
|
||||||
# tear itself down, clear out any self-refs, etc. If the new activity
|
|
||||||
# has a transition-time, set it up to be called after that passes;
|
|
||||||
# otherwise call it immediately. After this call the activity should
|
|
||||||
# have no refs left to it and should die (which will trigger the next
|
|
||||||
# activity to run).
|
|
||||||
if prev_activity is not None:
|
if prev_activity is not None:
|
||||||
if activity.transition_time > 0.0:
|
with _ba.Context('ui'):
|
||||||
# FIXME: We should tweak the activity to not allow
|
_ba.timer(max(0.0, activity.transition_time),
|
||||||
# node-creation/etc when we call _destroy (or after).
|
prev_activity.destroy,
|
||||||
with _ba.Context('ui'):
|
timetype=TimeType.REAL)
|
||||||
# pylint: disable=protected-access
|
|
||||||
# noinspection PyProtectedMember
|
|
||||||
_ba.timer(activity.transition_time,
|
|
||||||
prev_activity._destroy,
|
|
||||||
timetype=TimeType.REAL)
|
|
||||||
|
|
||||||
# Just run immediately.
|
|
||||||
else:
|
|
||||||
# noinspection PyProtectedMember
|
|
||||||
prev_activity._destroy() # pylint: disable=protected-access
|
|
||||||
self._in_set_activity = False
|
self._in_set_activity = False
|
||||||
|
|
||||||
def getactivity(self) -> Optional[ba.Activity]:
|
def getactivity(self) -> Optional[ba.Activity]:
|
||||||
@ -631,34 +497,29 @@ class Session:
|
|||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _request_player(self, player: ba.Player) -> bool:
|
def _request_player(self, sessionplayer: ba.SessionPlayer) -> bool:
|
||||||
|
"""Called by the C++ layer when players want to join."""
|
||||||
|
|
||||||
# If we're ending, allow no new players.
|
# If we're ending, allow no new players.
|
||||||
if self._ending:
|
if self._ending:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Ask the user.
|
# Ask the session subclass to approve/deny this request.
|
||||||
try:
|
try:
|
||||||
with _ba.Context(self):
|
with _ba.Context(self):
|
||||||
result = self.on_player_request(player)
|
result = self.on_player_request(sessionplayer)
|
||||||
except Exception:
|
except Exception:
|
||||||
from ba import _error
|
print_exception('error in on_player_request call for', self)
|
||||||
_error.print_exception('error in on_player_request call for', self)
|
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
# If the user said yes, add the player to the session list.
|
# If the user said yes, add the player to the session list.
|
||||||
if result:
|
if result:
|
||||||
self.players.append(player)
|
self.players.append(sessionplayer)
|
||||||
|
|
||||||
# If we have a current activity with a lobby,
|
|
||||||
# ask it to bring up a chooser for this player.
|
|
||||||
# otherwise they'll have to wait around for the next activity.
|
|
||||||
with _ba.Context(self):
|
with _ba.Context(self):
|
||||||
try:
|
try:
|
||||||
self.lobby.add_chooser(player)
|
self.lobby.add_chooser(sessionplayer)
|
||||||
except Exception:
|
except Exception:
|
||||||
from ba import _error
|
print_exception('exception in lobby.add_chooser()')
|
||||||
_error.print_exception('exception in lobby.add_chooser()')
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -683,17 +544,12 @@ class Session:
|
|||||||
self._activity_weak = weakref.ref(self._next_activity)
|
self._activity_weak = weakref.ref(self._next_activity)
|
||||||
self._next_activity = None
|
self._next_activity = None
|
||||||
|
|
||||||
# Lets kick out any players sitting in the lobby since
|
# Kick out anyone loitering in the lobby.
|
||||||
# new activities such as score screens could cover them up;
|
|
||||||
# better to have them rejoin.
|
|
||||||
self.lobby.remove_all_choosers_and_kick_players()
|
self.lobby.remove_all_choosers_and_kick_players()
|
||||||
activity = self._activity_weak()
|
self._activity_retained.begin(self)
|
||||||
assert activity is not None
|
|
||||||
activity.begin(self)
|
|
||||||
|
|
||||||
def _on_player_ready(self, chooser: ba.Chooser) -> None:
|
def _on_player_ready(self, chooser: ba.Chooser) -> None:
|
||||||
"""Called when a ba.Player has checked themself ready."""
|
"""Called when a ba.Player has checked themself ready."""
|
||||||
from ba._lang import Lstr
|
|
||||||
lobby = chooser.lobby
|
lobby = chooser.lobby
|
||||||
activity = self._activity_weak()
|
activity = self._activity_weak()
|
||||||
|
|
||||||
@ -711,10 +567,11 @@ class Session:
|
|||||||
# Get our next activity going.
|
# Get our next activity going.
|
||||||
self._complete_end_activity(activity, {})
|
self._complete_end_activity(activity, {})
|
||||||
else:
|
else:
|
||||||
_ba.screenmessage(Lstr(resource='notEnoughPlayersText',
|
_ba.screenmessage(
|
||||||
subs=[('${COUNT}', str(min_players))
|
Lstr(resource='notEnoughPlayersText',
|
||||||
]),
|
subs=[('${COUNT}', str(min_players))]),
|
||||||
color=(1, 1, 0))
|
color=(1, 1, 0),
|
||||||
|
)
|
||||||
_ba.playsound(_ba.getsound('error'))
|
_ba.playsound(_ba.getsound('error'))
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
@ -724,24 +581,19 @@ class Session:
|
|||||||
self._add_chosen_player(chooser)
|
self._add_chosen_player(chooser)
|
||||||
lobby.remove_chooser(chooser.getplayer())
|
lobby.remove_chooser(chooser.getplayer())
|
||||||
|
|
||||||
def _add_chosen_player(self, chooser: ba.Chooser) -> ba.Player:
|
def _add_chosen_player(self, chooser: ba.Chooser) -> ba.SessionPlayer:
|
||||||
# pylint: disable=too-many-statements
|
from ba._team import SessionTeam
|
||||||
# pylint: disable=too-many-branches
|
sessionplayer = chooser.getplayer()
|
||||||
from ba import _error
|
assert sessionplayer in self.players, (
|
||||||
from ba._lang import Lstr
|
'SessionPlayer not found in session '
|
||||||
from ba._team import Team
|
'player-list after chooser selection.')
|
||||||
from ba import _freeforallsession
|
|
||||||
player = chooser.getplayer()
|
|
||||||
if player not in self.players:
|
|
||||||
_error.print_error('player not found in session '
|
|
||||||
'player-list after chooser selection')
|
|
||||||
|
|
||||||
activity = self._activity_weak()
|
activity = self._activity_weak()
|
||||||
assert activity is not None
|
assert activity is not None
|
||||||
|
|
||||||
# We need to reset the player's input here, as it is currently
|
# Reset the player's input here, as it is probably
|
||||||
# referencing the chooser which could inadvertently keep it alive.
|
# referencing the chooser which could inadvertently keep it alive.
|
||||||
player.reset_input()
|
sessionplayer.reset_input()
|
||||||
|
|
||||||
# Pass it to the current activity if it has already begun
|
# Pass it to the current activity if it has already begun
|
||||||
# (otherwise it'll get passed once begin is called).
|
# (otherwise it'll get passed once begin is called).
|
||||||
@ -749,74 +601,51 @@ class Session:
|
|||||||
and not activity.is_joining_activity)
|
and not activity.is_joining_activity)
|
||||||
|
|
||||||
# If we're not allowing mid-game joins, don't pass; just announce
|
# If we're not allowing mid-game joins, don't pass; just announce
|
||||||
# the arrival.
|
# the arrival and say they'll partake next round.
|
||||||
if pass_to_activity:
|
if pass_to_activity:
|
||||||
if not self._allow_mid_activity_joins:
|
if not self._allow_mid_activity_joins:
|
||||||
pass_to_activity = False
|
pass_to_activity = False
|
||||||
with _ba.Context(self):
|
with _ba.Context(self):
|
||||||
_ba.screenmessage(Lstr(resource='playerDelayedJoinText',
|
_ba.screenmessage(
|
||||||
subs=[('${PLAYER}',
|
Lstr(resource='playerDelayedJoinText',
|
||||||
player.get_name(full=True))
|
subs=[('${PLAYER}',
|
||||||
]),
|
sessionplayer.get_name(full=True))]),
|
||||||
color=(0, 1, 0))
|
color=(0, 1, 0),
|
||||||
|
)
|
||||||
|
|
||||||
# If we're a non-team game, each player gets their own team
|
# If we're a non-team session, each player gets their own team.
|
||||||
# (keeps mini-game coding simpler if we can always deal with teams).
|
# (keeps mini-game coding simpler if we can always deal with teams).
|
||||||
if self._use_teams:
|
if self._use_teams:
|
||||||
team = chooser.get_team()
|
sessionteam = chooser.get_team()
|
||||||
else:
|
else:
|
||||||
our_team_id = self._next_team_id
|
our_team_id = self._next_team_id
|
||||||
team = Team(team_id=our_team_id,
|
|
||||||
name=chooser.getplayer().get_name(full=True,
|
|
||||||
icon=False),
|
|
||||||
color=chooser.get_color())
|
|
||||||
self.teams.append(team)
|
|
||||||
self._next_team_id += 1
|
self._next_team_id += 1
|
||||||
|
sessionteam = SessionTeam(
|
||||||
|
team_id=our_team_id,
|
||||||
|
color=chooser.get_color(),
|
||||||
|
name=chooser.getplayer().get_name(full=True, icon=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add player's team to the Session.
|
||||||
|
self.teams.append(sessionteam)
|
||||||
try:
|
try:
|
||||||
with _ba.Context(self):
|
with _ba.Context(self):
|
||||||
self.on_team_join(team)
|
self.on_team_join(sessionteam)
|
||||||
except Exception:
|
except Exception:
|
||||||
_error.print_exception(f'exception in on_team_join for {self}')
|
print_exception(f'exception in on_team_join for {self}')
|
||||||
|
|
||||||
|
# Add player's team to the Activity.
|
||||||
if pass_to_activity:
|
if pass_to_activity:
|
||||||
if team in activity.teams:
|
activity.add_team(sessionteam)
|
||||||
_error.print_error(
|
|
||||||
'Duplicate team ID in ba.Session._add_chosen_player')
|
|
||||||
activity.teams.append(team)
|
|
||||||
try:
|
|
||||||
with _ba.Context(activity):
|
|
||||||
activity.on_team_join(team)
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
f'ERROR: exception in on_team_join for {activity}')
|
|
||||||
|
|
||||||
player.set_data(team=team,
|
assert sessionplayer not in sessionteam.players
|
||||||
character=chooser.get_character_name(),
|
sessionteam.players.append(sessionplayer)
|
||||||
color=chooser.get_color(),
|
sessionplayer.set_data(team=sessionteam,
|
||||||
highlight=chooser.get_highlight())
|
character=chooser.get_character_name(),
|
||||||
|
color=chooser.get_color(),
|
||||||
|
highlight=chooser.get_highlight())
|
||||||
|
|
||||||
self.stats.register_player(player)
|
self.stats.register_player(sessionplayer)
|
||||||
if pass_to_activity:
|
if pass_to_activity:
|
||||||
if isinstance(self, _freeforallsession.FreeForAllSession):
|
activity.add_player(sessionplayer)
|
||||||
if player.team.players:
|
return sessionplayer
|
||||||
_error.print_error('expected 0 players in FFA team')
|
|
||||||
|
|
||||||
# Don't actually add the player to their team list if we're not
|
|
||||||
# in an activity. (players get (re)added to their team lists
|
|
||||||
# when the activity begins).
|
|
||||||
player.team.players.append(player)
|
|
||||||
if player in activity.players:
|
|
||||||
_error.print_exception(
|
|
||||||
f'Dup player in ba.Session._add_chosen_player: {player}')
|
|
||||||
else:
|
|
||||||
activity.players.append(player)
|
|
||||||
player.set_activity(activity)
|
|
||||||
pnode = activity.create_player_node(player)
|
|
||||||
player.set_node(pnode)
|
|
||||||
try:
|
|
||||||
with _ba.Context(activity):
|
|
||||||
activity.on_player_join(player)
|
|
||||||
except Exception:
|
|
||||||
_error.print_exception(
|
|
||||||
f'Error on on_player_join for {activity}')
|
|
||||||
return player
|
|
||||||
|
|||||||
@ -19,7 +19,6 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
"""Functionality related to scores and statistics."""
|
"""Functionality related to scores and statistics."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
@ -28,6 +27,8 @@ from typing import TYPE_CHECKING
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import _ba
|
import _ba
|
||||||
|
from ba._error import (print_exception, print_error, SessionTeamNotFoundError,
|
||||||
|
SessionPlayerNotFoundError)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import ba
|
import ba
|
||||||
@ -37,12 +38,11 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PlayerScoredMessage:
|
class PlayerScoredMessage:
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
"""Informs something that a ba.Player scored.
|
"""Informs something that a ba.Player scored.
|
||||||
|
|
||||||
Category: Message Classes
|
Category: Message Classes
|
||||||
|
|
||||||
Attributes:
|
Attrs:
|
||||||
|
|
||||||
score
|
score
|
||||||
The score value.
|
The score value.
|
||||||
@ -61,7 +61,7 @@ class PlayerRecord:
|
|||||||
"""
|
"""
|
||||||
character: str
|
character: str
|
||||||
|
|
||||||
def __init__(self, name: str, name_full: str, player: ba.Player,
|
def __init__(self, name: str, name_full: str, player: ba.SessionPlayer,
|
||||||
stats: ba.Stats):
|
stats: ba.Stats):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.name_full = name_full
|
self.name_full = name_full
|
||||||
@ -74,34 +74,34 @@ class PlayerRecord:
|
|||||||
self._multi_kill_timer: Optional[ba.Timer] = None
|
self._multi_kill_timer: Optional[ba.Timer] = None
|
||||||
self._multi_kill_count = 0
|
self._multi_kill_count = 0
|
||||||
self._stats = weakref.ref(stats)
|
self._stats = weakref.ref(stats)
|
||||||
self._last_player: Optional[ba.Player] = None
|
self._last_player: Optional[ba.SessionPlayer] = None
|
||||||
self._player: Optional[ba.Player] = None
|
self._player: Optional[ba.SessionPlayer] = None
|
||||||
self._team: Optional[ReferenceType[ba.Team]] = None
|
self._team: Optional[ReferenceType[ba.SessionTeam]] = None
|
||||||
self.streak = 0
|
self.streak = 0
|
||||||
self.associate_with_player(player)
|
self.associate_with_player(player)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def team(self) -> ba.Team:
|
def team(self) -> ba.SessionTeam:
|
||||||
"""The ba.Team the last associated player was last on.
|
"""The ba.SessionTeam the last associated player was last on.
|
||||||
|
|
||||||
This can still return a valid result even if the player is gone.
|
This can still return a valid result even if the player is gone.
|
||||||
Raises a ba.TeamNotFoundError if the team no longer exists.
|
Raises a ba.SessionTeamNotFoundError if the team no longer exists.
|
||||||
"""
|
"""
|
||||||
assert self._team is not None
|
assert self._team is not None
|
||||||
team = self._team()
|
team = self._team()
|
||||||
if team is None:
|
if team is None:
|
||||||
from ba._error import TeamNotFoundError
|
raise SessionTeamNotFoundError()
|
||||||
raise TeamNotFoundError()
|
|
||||||
return team
|
return team
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def player(self) -> ba.Player:
|
def player(self) -> ba.SessionPlayer:
|
||||||
"""Return the instance's associated ba.Player.
|
"""Return the instance's associated ba.SessionPlayer.
|
||||||
|
|
||||||
Raises a ba.PlayerNotFoundError if the player no longer exists."""
|
Raises a ba.SessionPlayerNotFoundError if the player
|
||||||
|
no longer exists.
|
||||||
|
"""
|
||||||
if not self._player:
|
if not self._player:
|
||||||
from ba._error import PlayerNotFoundError
|
raise SessionPlayerNotFoundError()
|
||||||
raise PlayerNotFoundError()
|
|
||||||
return self._player
|
return self._player
|
||||||
|
|
||||||
def get_name(self, full: bool = False) -> str:
|
def get_name(self, full: bool = False) -> str:
|
||||||
@ -127,7 +127,7 @@ class PlayerRecord:
|
|||||||
return stats.getactivity()
|
return stats.getactivity()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def associate_with_player(self, player: ba.Player) -> None:
|
def associate_with_player(self, player: ba.SessionPlayer) -> None:
|
||||||
"""Associate this entry with a ba.Player."""
|
"""Associate this entry with a ba.Player."""
|
||||||
self._team = weakref.ref(player.team)
|
self._team = weakref.ref(player.team)
|
||||||
self.character = player.character
|
self.character = player.character
|
||||||
@ -139,7 +139,7 @@ class PlayerRecord:
|
|||||||
self._multi_kill_timer = None
|
self._multi_kill_timer = None
|
||||||
self._multi_kill_count = 0
|
self._multi_kill_count = 0
|
||||||
|
|
||||||
def get_last_player(self) -> ba.Player:
|
def get_last_player(self) -> ba.SessionPlayer:
|
||||||
"""Return the last ba.Player we were associated with."""
|
"""Return the last ba.Player we were associated with."""
|
||||||
assert self._last_player is not None
|
assert self._last_player is not None
|
||||||
return self._last_player
|
return self._last_player
|
||||||
@ -203,10 +203,13 @@ class PlayerRecord:
|
|||||||
from bastd.actor.popuptext import PopupText
|
from bastd.actor.popuptext import PopupText
|
||||||
|
|
||||||
# Only award this if they're still alive and we can get
|
# Only award this if they're still alive and we can get
|
||||||
# their pos.
|
# a current position for them.
|
||||||
if self._player is not None and self._player.node:
|
our_pos: Optional[Sequence[float]] = None
|
||||||
our_pos = self._player.node.position
|
if self._player is not None:
|
||||||
else:
|
if self._player.gameplayer is not None:
|
||||||
|
if self._player.gameplayer.node:
|
||||||
|
our_pos = self._player.gameplayer.node.position
|
||||||
|
if our_pos is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Jitter position a bit since these often come in clusters.
|
# Jitter position a bit since these often come in clusters.
|
||||||
@ -263,9 +266,8 @@ class Stats:
|
|||||||
|
|
||||||
# Load our media into this activity's context.
|
# Load our media into this activity's context.
|
||||||
if activity is not None:
|
if activity is not None:
|
||||||
if activity.is_expired():
|
if activity.expired:
|
||||||
from ba import _error
|
print_error('unexpected finalized activity')
|
||||||
_error.print_error('unexpected finalized activity')
|
|
||||||
else:
|
else:
|
||||||
with _ba.Context(activity):
|
with _ba.Context(activity):
|
||||||
self._load_activity_media()
|
self._load_activity_media()
|
||||||
@ -303,7 +305,7 @@ class Stats:
|
|||||||
s_player.accum_killed_count = 0
|
s_player.accum_killed_count = 0
|
||||||
s_player.streak = 0
|
s_player.streak = 0
|
||||||
|
|
||||||
def register_player(self, player: ba.Player) -> None:
|
def register_player(self, player: ba.SessionPlayer) -> None:
|
||||||
"""Register a player with this score-set."""
|
"""Register a player with this score-set."""
|
||||||
name = player.get_name()
|
name = player.get_name()
|
||||||
name_full = player.get_name(full=True)
|
name_full = player.get_name(full=True)
|
||||||
@ -329,7 +331,7 @@ class Stats:
|
|||||||
records[record_id] = record
|
records[record_id] = record
|
||||||
return records
|
return records
|
||||||
|
|
||||||
def player_got_hit(self, player: ba.Player) -> None:
|
def player_got_hit(self, player: ba.SessionPlayer) -> None:
|
||||||
"""Call this when a player got hit."""
|
"""Call this when a player got hit."""
|
||||||
s_player = self._player_records[player.get_name()]
|
s_player = self._player_records[player.get_name()]
|
||||||
s_player.streak = 0
|
s_player.streak = 0
|
||||||
@ -388,8 +390,7 @@ class Stats:
|
|||||||
subs=[('${NAME}', name_full)]),
|
subs=[('${NAME}', name_full)]),
|
||||||
color=_math.normalized_color(player.team.color))
|
color=_math.normalized_color(player.team.color))
|
||||||
except Exception:
|
except Exception:
|
||||||
from ba import _error
|
print_exception('error showing big_message')
|
||||||
_error.print_exception('error showing big_message')
|
|
||||||
|
|
||||||
# If we currently have a actor, pop up a score over it.
|
# If we currently have a actor, pop up a score over it.
|
||||||
if display and showpoints:
|
if display and showpoints:
|
||||||
@ -430,8 +431,7 @@ class Stats:
|
|||||||
color=player.color,
|
color=player.color,
|
||||||
image=player.get_icon())
|
image=player.get_icon())
|
||||||
except Exception:
|
except Exception:
|
||||||
from ba import _error
|
print_exception('error announcing score')
|
||||||
_error.print_exception('error announcing score')
|
|
||||||
|
|
||||||
s_player.score += points
|
s_player.score += points
|
||||||
s_player.accumscore += points
|
s_player.accumscore += points
|
||||||
@ -458,14 +458,14 @@ class Stats:
|
|||||||
prec.killed_count += 1
|
prec.killed_count += 1
|
||||||
try:
|
try:
|
||||||
if killed and _ba.getactivity().announce_player_deaths:
|
if killed and _ba.getactivity().announce_player_deaths:
|
||||||
if killer == player:
|
if killer is player:
|
||||||
_ba.screenmessage(Lstr(resource='nameSuicideText',
|
_ba.screenmessage(Lstr(resource='nameSuicideText',
|
||||||
subs=[('${NAME}', name)]),
|
subs=[('${NAME}', name)]),
|
||||||
top=True,
|
top=True,
|
||||||
color=player.color,
|
color=player.color,
|
||||||
image=player.get_icon())
|
image=player.get_icon())
|
||||||
elif killer is not None:
|
elif killer is not None:
|
||||||
if killer.team == player.team:
|
if killer.team is player.team:
|
||||||
_ba.screenmessage(Lstr(resource='nameBetrayedText',
|
_ba.screenmessage(Lstr(resource='nameBetrayedText',
|
||||||
subs=[('${NAME}',
|
subs=[('${NAME}',
|
||||||
killer.get_name()),
|
killer.get_name()),
|
||||||
@ -488,5 +488,4 @@ class Stats:
|
|||||||
color=player.color,
|
color=player.color,
|
||||||
image=player.get_icon())
|
image=player.get_icon())
|
||||||
except Exception:
|
except Exception:
|
||||||
from ba import _error
|
print_exception('error announcing kill')
|
||||||
_error.print_exception('error announcing kill')
|
|
||||||
|
|||||||
@ -21,15 +21,17 @@
|
|||||||
"""Defines Team class."""
|
"""Defines Team class."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
import weakref
|
||||||
|
from typing import TYPE_CHECKING, TypeVar, Generic
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Dict, List, Sequence, Any, Tuple, Union
|
from weakref import ReferenceType
|
||||||
|
from typing import Dict, List, Sequence, Tuple, Union, Optional
|
||||||
import ba
|
import ba
|
||||||
|
|
||||||
|
|
||||||
class Team:
|
class SessionTeam:
|
||||||
"""A team of one or more ba.Players.
|
"""A team of one or more ba.SessionPlayers.
|
||||||
|
|
||||||
Category: Gameplay Classes
|
Category: Gameplay Classes
|
||||||
|
|
||||||
@ -42,11 +44,14 @@ class Team:
|
|||||||
name
|
name
|
||||||
The team's name.
|
The team's name.
|
||||||
|
|
||||||
|
id
|
||||||
|
The unique numeric id of the team.
|
||||||
|
|
||||||
color
|
color
|
||||||
The team's color.
|
The team's color.
|
||||||
|
|
||||||
players
|
players
|
||||||
The list of ba.Players on the team.
|
The list of ba.SessionPlayers on the team.
|
||||||
|
|
||||||
gamedata
|
gamedata
|
||||||
A dict for use by the current ba.Activity
|
A dict for use by the current ba.Activity
|
||||||
@ -62,56 +67,82 @@ class Team:
|
|||||||
|
|
||||||
# Annotate our attr types at the class level so they're introspectable.
|
# Annotate our attr types at the class level so they're introspectable.
|
||||||
name: Union[ba.Lstr, str]
|
name: Union[ba.Lstr, str]
|
||||||
color: Tuple[float, ...]
|
color: Tuple[float, ...] # FIXME: can't we make this fixed len?
|
||||||
players: List[ba.Player]
|
players: List[ba.SessionPlayer]
|
||||||
gamedata: Dict
|
gamedata: Dict
|
||||||
sessiondata: Dict
|
sessiondata: Dict
|
||||||
|
id: int
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
team_id: int = 0,
|
team_id: int = 0,
|
||||||
name: Union[ba.Lstr, str] = '',
|
name: Union[ba.Lstr, str] = '',
|
||||||
color: Sequence[float] = (1.0, 1.0, 1.0)):
|
color: Sequence[float] = (1.0, 1.0, 1.0)):
|
||||||
"""Instantiate a ba.Team.
|
"""Instantiate a ba.SessionTeam.
|
||||||
|
|
||||||
In most cases, all teams are provided to you by the ba.Session,
|
In most cases, all teams are provided to you by the ba.Session,
|
||||||
ba.Session, so calling this shouldn't be necessary.
|
ba.Session, so calling this shouldn't be necessary.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: Once we spin off team copies for each activity, we don't
|
self.id = team_id
|
||||||
# need to bother with trying to lock things down, since it won't
|
|
||||||
# matter at that point if the activity mucks with them.
|
|
||||||
|
|
||||||
# Temporarily allow us to set our own attrs
|
|
||||||
# (keeps pylint happier than using __setattr__ explicitly for all).
|
|
||||||
object.__setattr__(self, '_locked', False)
|
|
||||||
self._team_id: int = team_id
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.color = tuple(color)
|
self.color = tuple(color)
|
||||||
self.players = []
|
self.players = []
|
||||||
self.gamedata = {}
|
self.gamedata = {}
|
||||||
self.sessiondata = {}
|
self.sessiondata = {}
|
||||||
|
self.gameteam: Optional[Team] = None
|
||||||
# Now prevent further attr sets.
|
|
||||||
self._locked = True
|
|
||||||
|
|
||||||
def get_id(self) -> int:
|
|
||||||
"""Returns the numeric team ID."""
|
|
||||||
return self._team_id
|
|
||||||
|
|
||||||
def reset(self) -> None:
|
|
||||||
"""(internal)"""
|
|
||||||
self.reset_gamedata()
|
|
||||||
object.__setattr__(self, 'players', [])
|
|
||||||
|
|
||||||
def reset_gamedata(self) -> None:
|
def reset_gamedata(self) -> None:
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
object.__setattr__(self, 'gamedata', {})
|
self.gamedata = {}
|
||||||
|
|
||||||
def reset_sessiondata(self) -> None:
|
def reset_sessiondata(self) -> None:
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
object.__setattr__(self, 'sessiondata', {})
|
self.sessiondata = {}
|
||||||
|
|
||||||
def __setattr__(self, name: str, value: Any) -> None:
|
|
||||||
if self._locked:
|
PlayerType = TypeVar('PlayerType', bound='ba.Player')
|
||||||
raise Exception("can't set attrs on ba.Team objects")
|
|
||||||
object.__setattr__(self, name, value)
|
|
||||||
|
class Team(Generic[PlayerType]):
|
||||||
|
"""Testing."""
|
||||||
|
|
||||||
|
# Defining these types at the class level instead of in __init__ so
|
||||||
|
# that types are introspectable (these are still instance attrs).
|
||||||
|
players: List[PlayerType]
|
||||||
|
id: int
|
||||||
|
name: Union[ba.Lstr, str]
|
||||||
|
color: Tuple[float, ...] # FIXME: can't we make this fixed len?
|
||||||
|
_sessionteam: ReferenceType[SessionTeam]
|
||||||
|
|
||||||
|
# TODO: kill these.
|
||||||
|
gamedata: Dict
|
||||||
|
sessiondata: Dict
|
||||||
|
|
||||||
|
# NOTE: avoiding having any __init__() here since it seems to not
|
||||||
|
# get called by default if a dataclass inherits from us.
|
||||||
|
|
||||||
|
def postinit(self, sessionteam: SessionTeam) -> None:
|
||||||
|
"""Wire up a newly created SessionTeam.
|
||||||
|
|
||||||
|
(internal)
|
||||||
|
"""
|
||||||
|
self.players = []
|
||||||
|
self._sessionteam = weakref.ref(sessionteam)
|
||||||
|
self.id = sessionteam.id
|
||||||
|
self.name = sessionteam.name
|
||||||
|
self.color = sessionteam.color
|
||||||
|
self.gamedata = sessionteam.gamedata
|
||||||
|
self.sessiondata = sessionteam.sessiondata
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sessionteam(self) -> SessionTeam:
|
||||||
|
"""Return the ba.SessionTeam corresponding to this Team.
|
||||||
|
|
||||||
|
Throws a ba.SessionTeamNotFoundError if there is none.
|
||||||
|
"""
|
||||||
|
if self._sessionteam is not None:
|
||||||
|
sessionteam = self._sessionteam()
|
||||||
|
if sessionteam is not None:
|
||||||
|
return sessionteam
|
||||||
|
from ba import _error
|
||||||
|
raise _error.SessionTeamNotFoundError()
|
||||||
|
|||||||
@ -22,21 +22,24 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
|
|
||||||
import _ba
|
|
||||||
from ba._freeforallsession import FreeForAllSession
|
from ba._freeforallsession import FreeForAllSession
|
||||||
from ba._gameactivity import GameActivity
|
from ba._gameactivity import GameActivity
|
||||||
from ba._gameresults import TeamGameResults
|
from ba._gameresults import TeamGameResults
|
||||||
from ba._dualteamsession import DualTeamSession
|
from ba._dualteamsession import DualTeamSession
|
||||||
|
import _ba
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Dict, Type, Sequence
|
from typing import Any, Dict, Type, Sequence
|
||||||
from bastd.actor.playerspaz import PlayerSpaz
|
from bastd.actor.playerspaz import PlayerSpaz
|
||||||
import ba
|
import ba
|
||||||
|
|
||||||
|
PlayerType = TypeVar('PlayerType', bound='ba.Player')
|
||||||
|
TeamType = TypeVar('TeamType', bound='ba.Team')
|
||||||
|
|
||||||
class TeamGameActivity(GameActivity):
|
|
||||||
|
class TeamGameActivity(GameActivity[PlayerType, TeamType]):
|
||||||
"""Base class for teams and free-for-all mode games.
|
"""Base class for teams and free-for-all mode games.
|
||||||
|
|
||||||
Category: Gameplay Classes
|
Category: Gameplay Classes
|
||||||
@ -56,6 +59,7 @@ class TeamGameActivity(GameActivity):
|
|||||||
or issubclass(sessiontype, FreeForAllSession))
|
or issubclass(sessiontype, FreeForAllSession))
|
||||||
|
|
||||||
def __init__(self, settings: Dict[str, Any]):
|
def __init__(self, settings: Dict[str, Any]):
|
||||||
|
|
||||||
super().__init__(settings)
|
super().__init__(settings)
|
||||||
|
|
||||||
# By default we don't show kill-points in free-for-all.
|
# By default we don't show kill-points in free-for-all.
|
||||||
@ -104,7 +108,7 @@ class TeamGameActivity(GameActivity):
|
|||||||
_error.print_exception()
|
_error.print_exception()
|
||||||
|
|
||||||
def spawn_player_spaz(self,
|
def spawn_player_spaz(self,
|
||||||
player: ba.Player,
|
player: PlayerType,
|
||||||
position: Sequence[float] = None,
|
position: Sequence[float] = None,
|
||||||
angle: float = None) -> PlayerSpaz:
|
angle: float = None) -> PlayerSpaz:
|
||||||
"""
|
"""
|
||||||
@ -117,7 +121,7 @@ class TeamGameActivity(GameActivity):
|
|||||||
if position is None:
|
if position is None:
|
||||||
# In teams-mode get our team-start-location.
|
# In teams-mode get our team-start-location.
|
||||||
if isinstance(self.session, DualTeamSession):
|
if isinstance(self.session, DualTeamSession):
|
||||||
position = (self.map.get_start_position(player.team.get_id()))
|
position = (self.map.get_start_position(player.team.id))
|
||||||
else:
|
else:
|
||||||
# Otherwise do free-for-all spawn locations.
|
# Otherwise do free-for-all spawn locations.
|
||||||
position = self.map.get_ffa_start_position(self.players)
|
position = self.map.get_ffa_start_position(self.players)
|
||||||
|
|||||||
@ -38,12 +38,12 @@ if TYPE_CHECKING:
|
|||||||
from bastd.ui.league.rankbutton import LeagueRankButton
|
from bastd.ui.league.rankbutton import LeagueRankButton
|
||||||
|
|
||||||
|
|
||||||
class CoopScoreScreen(ba.Activity):
|
class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
|
||||||
"""Score screen showing the results of a cooperative game."""
|
"""Score screen showing the results of a cooperative game."""
|
||||||
|
|
||||||
def __init__(self, settings: Dict[str, Any]):
|
def __init__(self, settings: Dict[str, Any]):
|
||||||
# pylint: disable=too-many-statements
|
# pylint: disable=too-many-statements
|
||||||
super().__init__(settings=settings)
|
super().__init__(settings)
|
||||||
|
|
||||||
# Keep prev activity alive while we fade in
|
# Keep prev activity alive while we fade in
|
||||||
self.transition_time = 0.5
|
self.transition_time = 0.5
|
||||||
@ -1008,7 +1008,7 @@ class CoopScoreScreen(ba.Activity):
|
|||||||
# We need to manually run this in the context of our activity
|
# We need to manually run this in the context of our activity
|
||||||
# and only if we aren't shutting down.
|
# and only if we aren't shutting down.
|
||||||
# (really should make the submit_score call handle that stuff itself)
|
# (really should make the submit_score call handle that stuff itself)
|
||||||
if self.is_expired():
|
if self.expired:
|
||||||
return
|
return
|
||||||
with ba.Context(self):
|
with ba.Context(self):
|
||||||
# Delay a bit if results come in too fast.
|
# Delay a bit if results come in too fast.
|
||||||
|
|||||||
@ -73,7 +73,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
|||||||
scale=0.25,
|
scale=0.25,
|
||||||
color=(0.5, 0.5, 0.5, 1.0),
|
color=(0.5, 0.5, 0.5, 1.0),
|
||||||
jitter=3.0).autoretain()
|
jitter=3.0).autoretain()
|
||||||
for team in self.teams:
|
for team in self.session.teams:
|
||||||
ba.timer(
|
ba.timer(
|
||||||
i * 0.15 + 0.15,
|
i * 0.15 + 0.15,
|
||||||
ba.WeakCall(self._show_team_name, vval - i * height, team,
|
ba.WeakCall(self._show_team_name, vval - i * height, team,
|
||||||
@ -99,8 +99,8 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
|||||||
i += 1
|
i += 1
|
||||||
self.show_player_scores()
|
self.show_player_scores()
|
||||||
|
|
||||||
def _show_team_name(self, pos_v: float, team: ba.Team, kill_delay: float,
|
def _show_team_name(self, pos_v: float, team: ba.SessionTeam,
|
||||||
shiftdelay: float) -> None:
|
kill_delay: float, shiftdelay: float) -> None:
|
||||||
del kill_delay # unused arg
|
del kill_delay # unused arg
|
||||||
ZoomText(ba.Lstr(value='${A}:', subs=[('${A}', team.name)]),
|
ZoomText(ba.Lstr(value='${A}:', subs=[('${A}', team.name)]),
|
||||||
position=(100, pos_v),
|
position=(100, pos_v),
|
||||||
@ -113,7 +113,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
|||||||
color=team.color,
|
color=team.color,
|
||||||
jitter=1.0).autoretain()
|
jitter=1.0).autoretain()
|
||||||
|
|
||||||
def _show_team_old_score(self, pos_v: float, team: ba.Team,
|
def _show_team_old_score(self, pos_v: float, team: ba.SessionTeam,
|
||||||
shiftdelay: float) -> None:
|
shiftdelay: float) -> None:
|
||||||
ZoomText(str(team.sessiondata['score'] - 1),
|
ZoomText(str(team.sessiondata['score'] - 1),
|
||||||
position=(150, pos_v),
|
position=(150, pos_v),
|
||||||
@ -127,8 +127,9 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
|||||||
h_align='left',
|
h_align='left',
|
||||||
jitter=1.0).autoretain()
|
jitter=1.0).autoretain()
|
||||||
|
|
||||||
def _show_team_score(self, pos_v: float, team: ba.Team, scored: bool,
|
def _show_team_score(self, pos_v: float, team: ba.SessionTeam,
|
||||||
kill_delay: float, shiftdelay: float) -> None:
|
scored: bool, kill_delay: float,
|
||||||
|
shiftdelay: float) -> None:
|
||||||
del kill_delay # unused arg
|
del kill_delay # unused arg
|
||||||
ZoomText(str(team.sessiondata['score']),
|
ZoomText(str(team.sessiondata['score']),
|
||||||
position=(150, pos_v),
|
position=(150, pos_v),
|
||||||
|
|||||||
@ -19,13 +19,14 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
"""Functionality related to teams mode score screen."""
|
"""Functionality related to teams mode score screen."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
from ba.internal import ScoreScreenActivity
|
from ba.internal import ScoreScreenActivity
|
||||||
|
from bastd.actor.text import Text
|
||||||
|
from bastd.actor.image import Image
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Dict, Optional, Union
|
from typing import Any, Dict, Optional, Union
|
||||||
@ -42,7 +43,6 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
|
|||||||
self._show_up_next: bool = True
|
self._show_up_next: bool = True
|
||||||
|
|
||||||
def on_begin(self) -> None:
|
def on_begin(self) -> None:
|
||||||
from bastd.actor.text import Text
|
|
||||||
super().on_begin()
|
super().on_begin()
|
||||||
session = self.session
|
session = self.session
|
||||||
if self._show_up_next and isinstance(session, ba.MultiTeamSession):
|
if self._show_up_next and isinstance(session, ba.MultiTeamSession):
|
||||||
@ -77,8 +77,6 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
|
|||||||
"""Show scores for individual players."""
|
"""Show scores for individual players."""
|
||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
# pylint: disable=too-many-statements
|
# pylint: disable=too-many-statements
|
||||||
from bastd.actor.text import Text
|
|
||||||
from bastd.actor.image import Image
|
|
||||||
|
|
||||||
ts_v_offset = 150.0 + y_offset
|
ts_v_offset = 150.0 + y_offset
|
||||||
ts_h_offs = 80.0 + x_offset
|
ts_h_offs = 80.0 + x_offset
|
||||||
@ -90,6 +88,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
|
|||||||
def _get_prec_score(p_rec: ba.PlayerRecord) -> Optional[int]:
|
def _get_prec_score(p_rec: ba.PlayerRecord) -> Optional[int]:
|
||||||
if is_free_for_all and results is not None:
|
if is_free_for_all and results is not None:
|
||||||
assert isinstance(results, ba.TeamGameResults)
|
assert isinstance(results, ba.TeamGameResults)
|
||||||
|
assert p_rec.team.gameteam is not None
|
||||||
val = results.get_team_score(p_rec.team)
|
val = results.get_team_score(p_rec.team)
|
||||||
return val
|
return val
|
||||||
return p_rec.accumscore
|
return p_rec.accumscore
|
||||||
@ -97,7 +96,8 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
|
|||||||
def _get_prec_score_str(p_rec: ba.PlayerRecord) -> Union[str, ba.Lstr]:
|
def _get_prec_score_str(p_rec: ba.PlayerRecord) -> Union[str, ba.Lstr]:
|
||||||
if is_free_for_all and results is not None:
|
if is_free_for_all and results is not None:
|
||||||
assert isinstance(results, ba.TeamGameResults)
|
assert isinstance(results, ba.TeamGameResults)
|
||||||
val = results.get_team_score_str(p_rec.team)
|
assert p_rec.team.gameteam is not None
|
||||||
|
val = results.get_team_score_str(p_rec.team.gameteam)
|
||||||
assert val is not None
|
assert val is not None
|
||||||
return val
|
return val
|
||||||
return str(p_rec.accumscore)
|
return str(p_rec.accumscore)
|
||||||
@ -113,7 +113,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
|
|||||||
valid_players = list(self.stats.get_records().items())
|
valid_players = list(self.stats.get_records().items())
|
||||||
|
|
||||||
def _get_player_score_set_entry(
|
def _get_player_score_set_entry(
|
||||||
player: ba.Player) -> Optional[ba.PlayerRecord]:
|
player: ba.SessionPlayer) -> Optional[ba.PlayerRecord]:
|
||||||
for p_rec in valid_players:
|
for p_rec in valid_players:
|
||||||
# PyCharm incorrectly thinks valid_players is a List[str]
|
# PyCharm incorrectly thinks valid_players is a List[str]
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
|
|||||||
@ -142,7 +142,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
|||||||
h_align=Text.HAlign.CENTER,
|
h_align=Text.HAlign.CENTER,
|
||||||
transition_delay=t_incr * 4).autoretain()
|
transition_delay=t_incr * 4).autoretain()
|
||||||
|
|
||||||
win_score = (session.get_series_length() - 1) / 2 + 1
|
win_score = (session.get_series_length() - 1) // 2 + 1
|
||||||
lose_score = 0
|
lose_score = 0
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
if team.sessiondata['score'] != win_score:
|
if team.sessiondata['score'] != win_score:
|
||||||
@ -344,7 +344,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
|||||||
if not self.is_transitioning_out():
|
if not self.is_transitioning_out():
|
||||||
ba.setmusic(ba.MusicType.VICTORY)
|
ba.setmusic(ba.MusicType.VICTORY)
|
||||||
|
|
||||||
def _show_winner(self, team: ba.Team) -> None:
|
def _show_winner(self, team: ba.SessionTeam) -> None:
|
||||||
from bastd.actor.image import Image
|
from bastd.actor.image import Image
|
||||||
from bastd.actor.zoomtext import ZoomText
|
from bastd.actor.zoomtext import ZoomText
|
||||||
if not self._is_ffa:
|
if not self._is_ffa:
|
||||||
|
|||||||
@ -22,13 +22,16 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Generic, TypeVar
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
from bastd.actor.spaz import Spaz
|
from bastd.actor.spaz import Spaz
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Optional, Sequence, Tuple
|
from typing import Any, Sequence, Tuple, Optional
|
||||||
|
|
||||||
|
PlayerType = TypeVar('PlayerType', bound=ba.Player)
|
||||||
|
TeamType = TypeVar('TeamType', bound=ba.Team)
|
||||||
|
|
||||||
|
|
||||||
class PlayerSpazDeathMessage:
|
class PlayerSpazDeathMessage:
|
||||||
@ -38,9 +41,6 @@ class PlayerSpazDeathMessage:
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
|
||||||
spaz
|
|
||||||
The ba.PlayerSpaz that died.
|
|
||||||
|
|
||||||
killed
|
killed
|
||||||
If True, the spaz was killed;
|
If True, the spaz was killed;
|
||||||
If False, they left the game or the round ended.
|
If False, they left the game or the round ended.
|
||||||
@ -55,11 +55,22 @@ class PlayerSpazDeathMessage:
|
|||||||
def __init__(self, spaz: PlayerSpaz, was_killed: bool,
|
def __init__(self, spaz: PlayerSpaz, was_killed: bool,
|
||||||
killerplayer: Optional[ba.Player], how: ba.DeathType):
|
killerplayer: Optional[ba.Player], how: ba.DeathType):
|
||||||
"""Instantiate a message with the given values."""
|
"""Instantiate a message with the given values."""
|
||||||
self.spaz = spaz
|
self._spaz = spaz
|
||||||
self.killed = was_killed
|
self.killed = was_killed
|
||||||
self.killerplayer = killerplayer
|
self.killerplayer = killerplayer
|
||||||
self.how = how
|
self.how = how
|
||||||
|
|
||||||
|
def getspaz(
|
||||||
|
self, activity: ba.Activity[PlayerType,
|
||||||
|
TeamType]) -> PlayerSpaz[PlayerType]:
|
||||||
|
"""Return the spaz that died.
|
||||||
|
|
||||||
|
The current activity is required as an argument so the exact type of
|
||||||
|
PlayerSpaz can be determined by the type checker.
|
||||||
|
"""
|
||||||
|
del activity # Unused
|
||||||
|
return self._spaz
|
||||||
|
|
||||||
|
|
||||||
class PlayerSpazHurtMessage:
|
class PlayerSpazHurtMessage:
|
||||||
"""A message saying a ba.PlayerSpaz was hurt.
|
"""A message saying a ba.PlayerSpaz was hurt.
|
||||||
@ -77,7 +88,7 @@ class PlayerSpazHurtMessage:
|
|||||||
self.spaz = spaz
|
self.spaz = spaz
|
||||||
|
|
||||||
|
|
||||||
class PlayerSpaz(Spaz):
|
class PlayerSpaz(Spaz, Generic[PlayerType]):
|
||||||
"""A ba.Spaz subclass meant to be controlled by a ba.Player.
|
"""A ba.Spaz subclass meant to be controlled by a ba.Player.
|
||||||
|
|
||||||
category: Gameplay Classes
|
category: Gameplay Classes
|
||||||
@ -91,10 +102,10 @@ class PlayerSpaz(Spaz):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
player: PlayerType,
|
||||||
color: Sequence[float] = (1.0, 1.0, 1.0),
|
color: Sequence[float] = (1.0, 1.0, 1.0),
|
||||||
highlight: Sequence[float] = (0.5, 0.5, 0.5),
|
highlight: Sequence[float] = (0.5, 0.5, 0.5),
|
||||||
character: str = 'Spaz',
|
character: str = 'Spaz',
|
||||||
player: ba.Player = None,
|
|
||||||
powerups_expire: bool = True):
|
powerups_expire: bool = True):
|
||||||
"""Create a spaz for the provided ba.Player.
|
"""Create a spaz for the provided ba.Player.
|
||||||
|
|
||||||
@ -108,12 +119,13 @@ class PlayerSpaz(Spaz):
|
|||||||
source_player=player,
|
source_player=player,
|
||||||
start_invincible=True,
|
start_invincible=True,
|
||||||
powerups_expire=powerups_expire)
|
powerups_expire=powerups_expire)
|
||||||
self.last_player_attacked_by: Optional[ba.Player] = None
|
self.last_player_attacked_by: Optional[PlayerType] = None
|
||||||
self.last_attacked_time = 0.0
|
self.last_attacked_time = 0.0
|
||||||
self.last_attacked_type: Optional[Tuple[str, str]] = None
|
self.last_attacked_type: Optional[Tuple[str, str]] = None
|
||||||
self.held_count = 0
|
self.held_count = 0
|
||||||
self.last_player_held_by: Optional[ba.Player] = None
|
self.last_player_held_by: Optional[PlayerType] = None
|
||||||
self._player = player
|
self._player = player
|
||||||
|
self.playertype = type(player)
|
||||||
|
|
||||||
# Grab the node for this player and wire it to follow our spaz
|
# Grab the node for this player and wire it to follow our spaz
|
||||||
# (so players' controllers know where to draw their guides, etc).
|
# (so players' controllers know where to draw their guides, etc).
|
||||||
@ -123,7 +135,7 @@ class PlayerSpaz(Spaz):
|
|||||||
self.node.connectattr('torso_position', player.node, 'position')
|
self.node.connectattr('torso_position', player.node, 'position')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def player(self) -> ba.Player:
|
def player(self) -> PlayerType:
|
||||||
"""The ba.Player associated with this Spaz.
|
"""The ba.Player associated with this Spaz.
|
||||||
|
|
||||||
If the player no longer exists, raises an ba.PlayerNotFoundError.
|
If the player no longer exists, raises an ba.PlayerNotFoundError.
|
||||||
@ -132,7 +144,7 @@ class PlayerSpaz(Spaz):
|
|||||||
raise ba.PlayerNotFoundError()
|
raise ba.PlayerNotFoundError()
|
||||||
return self._player
|
return self._player
|
||||||
|
|
||||||
def getplayer(self) -> Optional[ba.Player]:
|
def getplayer(self) -> Optional[PlayerType]:
|
||||||
"""Get the ba.Player associated with this Spaz.
|
"""Get the ba.Player associated with this Spaz.
|
||||||
|
|
||||||
Note that this may return None if the player has left.
|
Note that this may return None if the player has left.
|
||||||
@ -226,7 +238,8 @@ class PlayerSpaz(Spaz):
|
|||||||
if isinstance(msg, ba.PickedUpMessage):
|
if isinstance(msg, ba.PickedUpMessage):
|
||||||
super().handlemessage(msg) # Augment standard behavior.
|
super().handlemessage(msg) # Augment standard behavior.
|
||||||
self.held_count += 1
|
self.held_count += 1
|
||||||
picked_up_by = msg.node.source_player
|
picked_up_by = ba.playercast_o(self.playertype,
|
||||||
|
msg.node.source_player)
|
||||||
if picked_up_by:
|
if picked_up_by:
|
||||||
self.last_player_held_by = picked_up_by
|
self.last_player_held_by = picked_up_by
|
||||||
elif isinstance(msg, ba.DroppedMessage):
|
elif isinstance(msg, ba.DroppedMessage):
|
||||||
@ -237,11 +250,12 @@ class PlayerSpaz(Spaz):
|
|||||||
|
|
||||||
# Let's count someone dropping us as an attack.
|
# Let's count someone dropping us as an attack.
|
||||||
try:
|
try:
|
||||||
picked_up_by = msg.node.source_player
|
picked_up_by_2 = ba.playercast_o(self.playertype,
|
||||||
|
msg.node.source_player)
|
||||||
except Exception:
|
except Exception:
|
||||||
picked_up_by = None
|
picked_up_by_2 = None
|
||||||
if picked_up_by:
|
if picked_up_by_2:
|
||||||
self.last_player_attacked_by = picked_up_by
|
self.last_player_attacked_by = picked_up_by_2
|
||||||
self.last_attacked_time = ba.time()
|
self.last_attacked_time = ba.time()
|
||||||
self.last_attacked_type = ('picked_up', 'default')
|
self.last_attacked_type = ('picked_up', 'default')
|
||||||
elif isinstance(msg, ba.DieMessage):
|
elif isinstance(msg, ba.DieMessage):
|
||||||
@ -296,7 +310,8 @@ class PlayerSpaz(Spaz):
|
|||||||
# Keep track of the player who last hit us for point rewarding.
|
# Keep track of the player who last hit us for point rewarding.
|
||||||
elif isinstance(msg, ba.HitMessage):
|
elif isinstance(msg, ba.HitMessage):
|
||||||
if msg.source_player:
|
if msg.source_player:
|
||||||
self.last_player_attacked_by = msg.source_player
|
srcplayer = ba.playercast_o(self.playertype, msg.source_player)
|
||||||
|
self.last_player_attacked_by = srcplayer
|
||||||
self.last_attacked_time = ba.time()
|
self.last_attacked_time = ba.time()
|
||||||
self.last_attacked_type = (msg.hit_type, msg.hit_subtype)
|
self.last_attacked_type = (msg.hit_type, msg.hit_subtype)
|
||||||
super().handlemessage(msg) # Augment standard behavior.
|
super().handlemessage(msg) # Augment standard behavior.
|
||||||
|
|||||||
@ -136,7 +136,7 @@ class RespawnIcon:
|
|||||||
"""Return info on where we should be shown and stored."""
|
"""Return info on where we should be shown and stored."""
|
||||||
activity = ba.getactivity()
|
activity = ba.getactivity()
|
||||||
if isinstance(ba.getsession(), ba.DualTeamSession):
|
if isinstance(ba.getsession(), ba.DualTeamSession):
|
||||||
on_right = player.team.get_id() % 2 == 1
|
on_right = player.team.id % 2 == 1
|
||||||
|
|
||||||
# Store a list of icons in the team.
|
# Store a list of icons in the team.
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -317,7 +317,7 @@ class _EntryProxy:
|
|||||||
self._scoreboard = weakref.ref(scoreboard)
|
self._scoreboard = weakref.ref(scoreboard)
|
||||||
# have to store ID here instead of a weak-ref since the team will be
|
# have to store ID here instead of a weak-ref since the team will be
|
||||||
# dead when we die and need to remove it
|
# dead when we die and need to remove it
|
||||||
self._team_id = team.get_id()
|
self._team_id = team.id
|
||||||
|
|
||||||
def __del__(self) -> None:
|
def __del__(self) -> None:
|
||||||
scoreboard = self._scoreboard()
|
scoreboard = self._scoreboard()
|
||||||
@ -366,7 +366,7 @@ class Scoreboard:
|
|||||||
flash: bool = True,
|
flash: bool = True,
|
||||||
show_value: bool = True) -> None:
|
show_value: bool = True) -> None:
|
||||||
"""Update the score-board display for the given ba.Team."""
|
"""Update the score-board display for the given ba.Team."""
|
||||||
if not team.get_id() in self._entries:
|
if not team.id in self._entries:
|
||||||
self._add_team(team)
|
self._add_team(team)
|
||||||
# create a proxy in the team which will kill
|
# create a proxy in the team which will kill
|
||||||
# our entry when it dies (for convenience)
|
# our entry when it dies (for convenience)
|
||||||
@ -374,21 +374,21 @@ class Scoreboard:
|
|||||||
raise Exception('existing _EntryProxy found')
|
raise Exception('existing _EntryProxy found')
|
||||||
team.gamedata['_scoreboard_entry'] = _EntryProxy(self, team)
|
team.gamedata['_scoreboard_entry'] = _EntryProxy(self, team)
|
||||||
# now set the entry..
|
# now set the entry..
|
||||||
self._entries[team.get_id()].set_value(score=score,
|
self._entries[team.id].set_value(score=score,
|
||||||
max_score=max_score,
|
max_score=max_score,
|
||||||
countdown=countdown,
|
countdown=countdown,
|
||||||
flash=flash,
|
flash=flash,
|
||||||
show_value=show_value)
|
show_value=show_value)
|
||||||
|
|
||||||
def _add_team(self, team: ba.Team) -> None:
|
def _add_team(self, team: ba.Team) -> None:
|
||||||
if team.get_id() in self._entries:
|
if team.id in self._entries:
|
||||||
raise Exception('Duplicate team add')
|
raise Exception('Duplicate team add')
|
||||||
self._entries[team.get_id()] = _Entry(self,
|
self._entries[team.id] = _Entry(self,
|
||||||
team,
|
team,
|
||||||
do_cover=self._do_cover,
|
do_cover=self._do_cover,
|
||||||
scale=self._scale,
|
scale=self._scale,
|
||||||
label=self._label,
|
label=self._label,
|
||||||
flash_length=self._flash_length)
|
flash_length=self._flash_length)
|
||||||
self._update_teams()
|
self._update_teams()
|
||||||
|
|
||||||
def remove_team(self, team_id: int) -> None:
|
def remove_team(self, team_id: int) -> None:
|
||||||
|
|||||||
@ -998,7 +998,7 @@ class BotSet:
|
|||||||
|
|
||||||
# Don't do this if the activity is shutting down or dead.
|
# Don't do this if the activity is shutting down or dead.
|
||||||
activity: Optional[ba.Activity] = ba.getactivity(doraise=False)
|
activity: Optional[ba.Activity] = ba.getactivity(doraise=False)
|
||||||
if activity is None or activity.is_expired():
|
if activity is None or activity.expired:
|
||||||
return
|
return
|
||||||
|
|
||||||
for i in range(len(self._bot_lists)):
|
for i in range(len(self._bot_lists)):
|
||||||
|
|||||||
@ -29,14 +29,14 @@ import random
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
from bastd.actor import playerspaz
|
from bastd.actor.playerspaz import PlayerSpaz, PlayerSpazDeathMessage
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Type, List, Dict, Tuple, Sequence, Union
|
from typing import Any, Type, List, Dict, Tuple, Sequence, Union
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class AssaultGame(ba.TeamGameActivity):
|
class AssaultGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""Game where you score by touching the other team's flag."""
|
"""Game where you score by touching the other team's flag."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -109,7 +109,7 @@ class AssaultGame(ba.TeamGameActivity):
|
|||||||
self.setup_standard_time_limit(self.settings_raw['Time Limit'])
|
self.setup_standard_time_limit(self.settings_raw['Time Limit'])
|
||||||
self.setup_standard_powerup_drops()
|
self.setup_standard_powerup_drops()
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
mat = self._base_region_materials[team.get_id()] = ba.Material()
|
mat = self._base_region_materials[team.id] = ba.Material()
|
||||||
mat.add_actions(conditions=('they_have_material',
|
mat.add_actions(conditions=('they_have_material',
|
||||||
ba.sharedobj('player_material')),
|
ba.sharedobj('player_material')),
|
||||||
actions=(('modify_part_collision', 'collide',
|
actions=(('modify_part_collision', 'collide',
|
||||||
@ -121,8 +121,7 @@ class AssaultGame(ba.TeamGameActivity):
|
|||||||
|
|
||||||
# Create a score region and flag for each team.
|
# Create a score region and flag for each team.
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
team.gamedata['base_pos'] = self.map.get_flag_position(
|
team.gamedata['base_pos'] = self.map.get_flag_position(team.id)
|
||||||
team.get_id())
|
|
||||||
|
|
||||||
ba.newnode('light',
|
ba.newnode('light',
|
||||||
attrs={
|
attrs={
|
||||||
@ -139,20 +138,20 @@ class AssaultGame(ba.TeamGameActivity):
|
|||||||
position=team.gamedata['base_pos'],
|
position=team.gamedata['base_pos'],
|
||||||
color=team.color)
|
color=team.color)
|
||||||
basepos = team.gamedata['base_pos']
|
basepos = team.gamedata['base_pos']
|
||||||
ba.newnode(
|
ba.newnode('region',
|
||||||
'region',
|
owner=team.gamedata['flag'].node,
|
||||||
owner=team.gamedata['flag'].node,
|
attrs={
|
||||||
attrs={
|
'position':
|
||||||
'position': (basepos[0], basepos[1] + 0.75, basepos[2]),
|
(basepos[0], basepos[1] + 0.75, basepos[2]),
|
||||||
'scale': (0.5, 0.5, 0.5),
|
'scale': (0.5, 0.5, 0.5),
|
||||||
'type': 'sphere',
|
'type': 'sphere',
|
||||||
'materials': [self._base_region_materials[team.get_id()]]
|
'materials': [self._base_region_materials[team.id]]
|
||||||
})
|
})
|
||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, PlayerSpazDeathMessage):
|
||||||
super().handlemessage(msg) # Augment standard.
|
super().handlemessage(msg) # Augment standard.
|
||||||
self.respawn_player(msg.spaz.player)
|
self.respawn_player(msg.getspaz(self).player)
|
||||||
else:
|
else:
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
|
|
||||||
@ -173,14 +172,15 @@ class AssaultGame(ba.TeamGameActivity):
|
|||||||
cnode = ba.get_collision_info('opposing_node')
|
cnode = ba.get_collision_info('opposing_node')
|
||||||
assert isinstance(cnode, ba.Node)
|
assert isinstance(cnode, ba.Node)
|
||||||
actor = cnode.getdelegate()
|
actor = cnode.getdelegate()
|
||||||
if not isinstance(actor, playerspaz.PlayerSpaz):
|
if not isinstance(actor, PlayerSpaz):
|
||||||
return
|
return
|
||||||
|
|
||||||
player = actor.getplayer()
|
player = actor.getplayer()
|
||||||
if not player or not player.is_alive():
|
if not player or not player.actor:
|
||||||
return
|
return
|
||||||
|
|
||||||
# If its another team's player, they scored.
|
# If its another team's player, they scored.
|
||||||
player_team = player.team
|
player_team: ba.Team[ba.Player] = player.team
|
||||||
if player_team is not team:
|
if player_team is not team:
|
||||||
|
|
||||||
# Prevent multiple simultaneous scores.
|
# Prevent multiple simultaneous scores.
|
||||||
@ -194,24 +194,22 @@ class AssaultGame(ba.TeamGameActivity):
|
|||||||
# and add flashes of light so its noticeable.
|
# and add flashes of light so its noticeable.
|
||||||
for player in player_team.players:
|
for player in player_team.players:
|
||||||
if player.is_alive():
|
if player.is_alive():
|
||||||
if player.node:
|
pos = player.node.position
|
||||||
pos = player.node.position
|
light = ba.newnode('light',
|
||||||
light = ba.newnode('light',
|
attrs={
|
||||||
attrs={
|
'position': pos,
|
||||||
'position': pos,
|
'color': player_team.color,
|
||||||
'color': player_team.color,
|
'height_attenuated': False,
|
||||||
'height_attenuated': False,
|
'radius': 0.4
|
||||||
'radius': 0.4
|
})
|
||||||
})
|
ba.timer(0.5, light.delete)
|
||||||
ba.timer(0.5, light.delete)
|
ba.animate(light, 'intensity', {
|
||||||
ba.animate(light, 'intensity', {
|
0: 0,
|
||||||
0: 0,
|
0.1: 1.0,
|
||||||
0.1: 1.0,
|
0.5: 0
|
||||||
0.5: 0
|
})
|
||||||
})
|
|
||||||
|
|
||||||
new_pos = (self.map.get_start_position(
|
new_pos = (self.map.get_start_position(player_team.id))
|
||||||
player_team.get_id()))
|
|
||||||
light = ba.newnode('light',
|
light = ba.newnode('light',
|
||||||
attrs={
|
attrs={
|
||||||
'position': new_pos,
|
'position': new_pos,
|
||||||
|
|||||||
@ -25,11 +25,13 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
from bastd.actor import flag as stdflag
|
from bastd.actor import flag as stdflag
|
||||||
from bastd.actor.playerspaz import PlayerSpaz, PlayerSpazDeathMessage
|
from bastd.actor.playerspaz import PlayerSpaz, PlayerSpazDeathMessage
|
||||||
|
from bastd.actor.scoreboard import Scoreboard
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Type, List, Dict, Tuple, Sequence, Union, Optional
|
from typing import Any, Type, List, Dict, Tuple, Sequence, Union, Optional
|
||||||
@ -38,9 +40,10 @@ if TYPE_CHECKING:
|
|||||||
class CTFFlag(stdflag.Flag):
|
class CTFFlag(stdflag.Flag):
|
||||||
"""Special flag type for ctf games."""
|
"""Special flag type for ctf games."""
|
||||||
|
|
||||||
def __init__(self, team: ba.Team):
|
def __init__(self, team: Team):
|
||||||
super().__init__(materials=[team.gamedata['flagmaterial']],
|
assert team.flagmaterial is not None
|
||||||
position=team.gamedata['base_pos'],
|
super().__init__(materials=[team.flagmaterial],
|
||||||
|
position=team.base_pos,
|
||||||
color=team.color)
|
color=team.color)
|
||||||
self._team = team
|
self._team = team
|
||||||
self.held_count = 0
|
self.held_count = 0
|
||||||
@ -52,7 +55,7 @@ class CTFFlag(stdflag.Flag):
|
|||||||
'h_align': 'center'
|
'h_align': 'center'
|
||||||
})
|
})
|
||||||
self.reset_return_times()
|
self.reset_return_times()
|
||||||
self.last_player_to_hold: Optional[ba.Player] = None
|
self.last_player_to_hold: Optional[Player] = None
|
||||||
self.time_out_respawn_time: Optional[int] = None
|
self.time_out_respawn_time: Optional[int] = None
|
||||||
self.touch_return_time: Optional[float] = None
|
self.touch_return_time: Optional[float] = None
|
||||||
|
|
||||||
@ -64,7 +67,7 @@ class CTFFlag(stdflag.Flag):
|
|||||||
self.activity.settings_raw['Flag Touch Return Time'])
|
self.activity.settings_raw['Flag Touch Return Time'])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def team(self) -> ba.Team:
|
def team(self) -> Team:
|
||||||
"""return the flag's team."""
|
"""return the flag's team."""
|
||||||
return self._team
|
return self._team
|
||||||
|
|
||||||
@ -77,8 +80,33 @@ class CTFFlag(stdflag.Flag):
|
|||||||
return delegate if isinstance(delegate, CTFFlag) else None
|
return delegate if isinstance(delegate, CTFFlag) else None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Player(ba.Player['Team']):
|
||||||
|
"""Our player type for this game."""
|
||||||
|
touching_own_flag: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
base_pos: Sequence[float]
|
||||||
|
base_region_material: ba.Material
|
||||||
|
base_region: ba.Node
|
||||||
|
spaz_material_no_flag_physical: ba.Material
|
||||||
|
spaz_material_no_flag_collide: ba.Material
|
||||||
|
flagmaterial: ba.Material
|
||||||
|
score: int = 0
|
||||||
|
flag_return_touches: int = 0
|
||||||
|
home_flag_at_base: bool = True
|
||||||
|
touch_return_timer: Optional[ba.Timer] = None
|
||||||
|
enemy_flag_at_base: bool = False
|
||||||
|
flag: Optional[CTFFlag] = None
|
||||||
|
last_flag_leave_time: Optional[float] = None
|
||||||
|
touch_return_timer_ticking: Optional[ba.NodeActor] = None
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class CaptureTheFlagGame(ba.TeamGameActivity):
|
class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||||
"""Game of stealing other team's flag and returning it to your base."""
|
"""Game of stealing other team's flag and returning it to your base."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -102,24 +130,37 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
cls,
|
cls,
|
||||||
sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
|
sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
|
||||||
return [
|
return [
|
||||||
('Score to Win', {'min_value': 1, 'default': 3}),
|
('Score to Win', {
|
||||||
|
'min_value': 1,
|
||||||
|
'default': 3
|
||||||
|
}),
|
||||||
('Flag Touch Return Time', {
|
('Flag Touch Return Time', {
|
||||||
'min_value': 0, 'default': 0, 'increment': 1}),
|
'min_value': 0,
|
||||||
|
'default': 0,
|
||||||
|
'increment': 1
|
||||||
|
}),
|
||||||
('Flag Idle Return Time', {
|
('Flag Idle Return Time', {
|
||||||
'min_value': 5, 'default': 30, 'increment': 5}),
|
'min_value': 5,
|
||||||
|
'default': 30,
|
||||||
|
'increment': 5
|
||||||
|
}),
|
||||||
('Time Limit', {
|
('Time Limit', {
|
||||||
'choices': [('None', 0), ('1 Minute', 60),
|
'choices': [('None', 0), ('1 Minute', 60), ('2 Minutes', 120),
|
||||||
('2 Minutes', 120), ('5 Minutes', 300),
|
('5 Minutes', 300), ('10 Minutes', 600),
|
||||||
('10 Minutes', 600), ('20 Minutes', 1200)],
|
('20 Minutes', 1200)],
|
||||||
'default': 0}),
|
'default': 0
|
||||||
|
}),
|
||||||
('Respawn Times', {
|
('Respawn Times', {
|
||||||
'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
|
'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
|
||||||
('Long', 2.0), ('Longer', 4.0)],
|
('Long', 2.0), ('Longer', 4.0)],
|
||||||
'default': 1.0}),
|
'default': 1.0
|
||||||
('Epic Mode', {'default': False})] # yapf: disable
|
}),
|
||||||
|
('Epic Mode', {
|
||||||
|
'default': False
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, settings: Dict[str, Any]):
|
def __init__(self, settings: Dict[str, Any]):
|
||||||
from bastd.actor.scoreboard import Scoreboard
|
|
||||||
super().__init__(settings)
|
super().__init__(settings)
|
||||||
self._scoreboard = Scoreboard()
|
self._scoreboard = Scoreboard()
|
||||||
if self.settings_raw['Epic Mode']:
|
if self.settings_raw['Epic Mode']:
|
||||||
@ -149,29 +190,24 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
ba.MusicType.FLAG_CATCHER)
|
ba.MusicType.FLAG_CATCHER)
|
||||||
super().on_transition_in()
|
super().on_transition_in()
|
||||||
|
|
||||||
def on_team_join(self, team: ba.Team) -> None:
|
def create_team(self, sessionteam: ba.SessionTeam) -> Team:
|
||||||
team.gamedata['score'] = 0
|
|
||||||
team.gamedata['flag_return_touches'] = 0
|
|
||||||
team.gamedata['home_flag_at_base'] = True
|
|
||||||
team.gamedata['touch_return_timer'] = None
|
|
||||||
team.gamedata['enemy_flag_at_base'] = False
|
|
||||||
team.gamedata['base_pos'] = (self.map.get_flag_position(team.get_id()))
|
|
||||||
|
|
||||||
self.project_flag_stand(team.gamedata['base_pos'])
|
base_pos = self.map.get_flag_position(sessionteam.id)
|
||||||
|
self.project_flag_stand(base_pos)
|
||||||
|
|
||||||
ba.newnode('light',
|
ba.newnode('light',
|
||||||
attrs={
|
attrs={
|
||||||
'position': team.gamedata['base_pos'],
|
'position': base_pos,
|
||||||
'intensity': 0.6,
|
'intensity': 0.6,
|
||||||
'height_attenuated': False,
|
'height_attenuated': False,
|
||||||
'volume_intensity_scale': 0.1,
|
'volume_intensity_scale': 0.1,
|
||||||
'radius': 0.1,
|
'radius': 0.1,
|
||||||
'color': team.color
|
'color': sessionteam.color
|
||||||
})
|
})
|
||||||
|
|
||||||
base_region_mat = team.gamedata['base_region_material'] = ba.Material()
|
base_region_mat = ba.Material()
|
||||||
pos = team.gamedata['base_pos']
|
pos = base_pos
|
||||||
team.gamedata['base_region'] = ba.newnode(
|
base_region = ba.newnode(
|
||||||
'region',
|
'region',
|
||||||
attrs={
|
attrs={
|
||||||
'position': (pos[0], pos[1] + 0.75, pos[2]),
|
'position': (pos[0], pos[1] + 0.75, pos[2]),
|
||||||
@ -180,22 +216,28 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
'materials': [base_region_mat, self._all_bases_material]
|
'materials': [base_region_mat, self._all_bases_material]
|
||||||
})
|
})
|
||||||
|
|
||||||
# create some materials for this team
|
spaz_mat_no_flag_physical = ba.Material()
|
||||||
spaz_mat_no_flag_physical = team.gamedata[
|
spaz_mat_no_flag_collide = ba.Material()
|
||||||
'spaz_material_no_flag_physical'] = ba.Material()
|
flagmat = ba.Material()
|
||||||
spaz_mat_no_flag_collide = team.gamedata[
|
|
||||||
'spaz_material_no_flag_collide'] = ba.Material()
|
team = Team(base_pos=base_pos,
|
||||||
flagmat = team.gamedata['flagmaterial'] = ba.Material()
|
base_region_material=base_region_mat,
|
||||||
|
base_region=base_region,
|
||||||
|
spaz_material_no_flag_physical=spaz_mat_no_flag_physical,
|
||||||
|
spaz_material_no_flag_collide=spaz_mat_no_flag_collide,
|
||||||
|
flagmaterial=flagmat)
|
||||||
|
|
||||||
# Some parts of our spazzes don't collide physically with our
|
# Some parts of our spazzes don't collide physically with our
|
||||||
# flags but generate callbacks.
|
# flags but generate callbacks.
|
||||||
spaz_mat_no_flag_physical.add_actions(
|
spaz_mat_no_flag_physical.add_actions(
|
||||||
conditions=('they_have_material', flagmat),
|
conditions=('they_have_material', flagmat),
|
||||||
actions=(('modify_part_collision', 'physical',
|
actions=(
|
||||||
False), ('call', 'at_connect',
|
('modify_part_collision', 'physical', False),
|
||||||
lambda: self._handle_hit_own_flag(team, 1)),
|
('call', 'at_connect',
|
||||||
('call', 'at_disconnect',
|
lambda: self._handle_hit_own_flag(team, 1)),
|
||||||
lambda: self._handle_hit_own_flag(team, 0))))
|
('call', 'at_disconnect',
|
||||||
|
lambda: self._handle_hit_own_flag(team, 0)),
|
||||||
|
))
|
||||||
|
|
||||||
# Other parts of our spazzes don't collide with our flags at all.
|
# Other parts of our spazzes don't collide with our flags at all.
|
||||||
spaz_mat_no_flag_collide.add_actions(conditions=('they_have_material',
|
spaz_mat_no_flag_collide.add_actions(conditions=('they_have_material',
|
||||||
@ -214,6 +256,9 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
('call', 'at_disconnect',
|
('call', 'at_disconnect',
|
||||||
lambda: self._handle_flag_left_base(team))))
|
lambda: self._handle_flag_left_base(team))))
|
||||||
|
|
||||||
|
return team
|
||||||
|
|
||||||
|
def on_team_join(self, team: Team) -> None:
|
||||||
self._spawn_flag_for_team(team)
|
self._spawn_flag_for_team(team)
|
||||||
self._update_scoreboard()
|
self._update_scoreboard()
|
||||||
|
|
||||||
@ -223,14 +268,14 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
self.setup_standard_powerup_drops()
|
self.setup_standard_powerup_drops()
|
||||||
ba.timer(1.0, call=self._tick, repeat=True)
|
ba.timer(1.0, call=self._tick, repeat=True)
|
||||||
|
|
||||||
def _spawn_flag_for_team(self, team: ba.Team) -> None:
|
def _spawn_flag_for_team(self, team: Team) -> None:
|
||||||
flag = team.gamedata['flag'] = CTFFlag(team)
|
team.flag = CTFFlag(team)
|
||||||
team.gamedata['flag_return_touches'] = 0
|
team.flag_return_touches = 0
|
||||||
self._flash_base(team, length=1.0)
|
self._flash_base(team, length=1.0)
|
||||||
assert flag.node
|
assert team.flag.node
|
||||||
ba.playsound(self._swipsound, position=flag.node.position)
|
ba.playsound(self._swipsound, position=team.flag.node.position)
|
||||||
|
|
||||||
def _handle_flag_entered_base(self, team: ba.Team) -> None:
|
def _handle_flag_entered_base(self, team: Team) -> None:
|
||||||
node = ba.get_collision_info('opposing_node')
|
node = ba.get_collision_info('opposing_node')
|
||||||
assert isinstance(node, (ba.Node, type(None)))
|
assert isinstance(node, (ba.Node, type(None)))
|
||||||
flag = CTFFlag.from_node(node)
|
flag = CTFFlag.from_node(node)
|
||||||
@ -239,14 +284,14 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if flag.team is team:
|
if flag.team is team:
|
||||||
team.gamedata['home_flag_at_base'] = True
|
team.home_flag_at_base = True
|
||||||
|
|
||||||
# If the enemy flag is already here, score!
|
# If the enemy flag is already here, score!
|
||||||
if team.gamedata['enemy_flag_at_base']:
|
if team.enemy_flag_at_base:
|
||||||
self._score(team)
|
self._score(team)
|
||||||
else:
|
else:
|
||||||
team.gamedata['enemy_flag_at_base'] = True
|
team.enemy_flag_at_base = True
|
||||||
if team.gamedata['home_flag_at_base']:
|
if team.home_flag_at_base:
|
||||||
# Award points to whoever was carrying the enemy flag.
|
# Award points to whoever was carrying the enemy flag.
|
||||||
player = flag.last_player_to_hold
|
player = flag.last_player_to_hold
|
||||||
if player and player.team is team:
|
if player and player.team is team:
|
||||||
@ -262,7 +307,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
curtime = ba.time(ba.TimeType.BASE)
|
curtime = ba.time(ba.TimeType.BASE)
|
||||||
if curtime - self._last_home_flag_notice_print_time > 5.0:
|
if curtime - self._last_home_flag_notice_print_time > 5.0:
|
||||||
self._last_home_flag_notice_print_time = curtime
|
self._last_home_flag_notice_print_time = curtime
|
||||||
bpos = team.gamedata['base_pos']
|
bpos = team.base_pos
|
||||||
tval = ba.Lstr(resource='ownFlagAtYourBaseWarning')
|
tval = ba.Lstr(resource='ownFlagAtYourBaseWarning')
|
||||||
tnode = ba.newnode(
|
tnode = ba.newnode(
|
||||||
'text',
|
'text',
|
||||||
@ -286,10 +331,10 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
# If either flag is away from base and not being held, tick down its
|
# If either flag is away from base and not being held, tick down its
|
||||||
# respawn timer.
|
# respawn timer.
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
flag = team.gamedata['flag']
|
flag = team.flag
|
||||||
|
assert flag is not None
|
||||||
|
|
||||||
if (not team.gamedata['home_flag_at_base']
|
if not team.home_flag_at_base and flag.held_count == 0:
|
||||||
and flag.held_count == 0):
|
|
||||||
time_out_counting_down = True
|
time_out_counting_down = True
|
||||||
if flag.time_out_respawn_time is None:
|
if flag.time_out_respawn_time is None:
|
||||||
flag.reset_return_times()
|
flag.reset_return_times()
|
||||||
@ -307,16 +352,16 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
# If there's no self-touches on this flag, set its text
|
# If there's no self-touches on this flag, set its text
|
||||||
# to show its auto-return counter. (if there's self-touches
|
# to show its auto-return counter. (if there's self-touches
|
||||||
# its showing that time).
|
# its showing that time).
|
||||||
if team.gamedata['flag_return_touches'] == 0:
|
if team.flag_return_touches == 0:
|
||||||
flag.counter.text = (str(flag.time_out_respawn_time) if
|
flag.counter.text = (str(flag.time_out_respawn_time) if (
|
||||||
(time_out_counting_down
|
time_out_counting_down
|
||||||
and flag.time_out_respawn_time <= 10)
|
and flag.time_out_respawn_time is not None
|
||||||
else '')
|
and flag.time_out_respawn_time <= 10) else '')
|
||||||
flag.counter.color = (1, 1, 1, 0.5)
|
flag.counter.color = (1, 1, 1, 0.5)
|
||||||
flag.counter.scale = 0.014
|
flag.counter.scale = 0.014
|
||||||
|
|
||||||
def _score(self, team: ba.Team) -> None:
|
def _score(self, team: Team) -> None:
|
||||||
team.gamedata['score'] += 1
|
team.score += 1
|
||||||
ba.playsound(self._score_sound)
|
ba.playsound(self._score_sound)
|
||||||
self._flash_base(team)
|
self._flash_base(team)
|
||||||
self._update_scoreboard()
|
self._update_scoreboard()
|
||||||
@ -328,58 +373,57 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
|
|
||||||
# Reset all flags/state.
|
# Reset all flags/state.
|
||||||
for reset_team in self.teams:
|
for reset_team in self.teams:
|
||||||
if not reset_team.gamedata['home_flag_at_base']:
|
if not reset_team.home_flag_at_base:
|
||||||
reset_team.gamedata['flag'].handlemessage(ba.DieMessage())
|
assert reset_team.flag is not None
|
||||||
reset_team.gamedata['enemy_flag_at_base'] = False
|
reset_team.flag.handlemessage(ba.DieMessage())
|
||||||
if team.gamedata['score'] >= self.settings_raw['Score to Win']:
|
reset_team.enemy_flag_at_base = False
|
||||||
|
if team.score >= self.settings_raw['Score to Win']:
|
||||||
self.end_game()
|
self.end_game()
|
||||||
|
|
||||||
def end_game(self) -> None:
|
def end_game(self) -> None:
|
||||||
results = ba.TeamGameResults()
|
results = ba.TeamGameResults()
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
results.set_team_score(team, team.gamedata['score'])
|
results.set_team_score(team, team.score)
|
||||||
self.end(results=results, announce_delay=0.8)
|
self.end(results=results, announce_delay=0.8)
|
||||||
|
|
||||||
def _handle_flag_left_base(self, team: ba.Team) -> None:
|
def _handle_flag_left_base(self, team: Team) -> None:
|
||||||
cur_time = ba.time()
|
cur_time = ba.time()
|
||||||
op_node = ba.get_collision_info('opposing_node')
|
op_node = ba.get_collision_info('opposing_node')
|
||||||
assert isinstance(op_node, (ba.Node, type(None)))
|
assert isinstance(op_node, (ba.Node, type(None)))
|
||||||
flag = CTFFlag.from_node(op_node)
|
flag = CTFFlag.from_node(op_node)
|
||||||
if not flag:
|
if not flag:
|
||||||
return
|
return
|
||||||
|
|
||||||
if flag.team is team:
|
if flag.team is team:
|
||||||
|
|
||||||
# Check times here to prevent too much flashing.
|
# Check times here to prevent too much flashing.
|
||||||
if ('last_flag_leave_time' not in team.gamedata
|
if (team.last_flag_leave_time is None
|
||||||
or cur_time - team.gamedata['last_flag_leave_time'] > 3.0):
|
or cur_time - team.last_flag_leave_time > 3.0):
|
||||||
ba.playsound(self._alarmsound,
|
ba.playsound(self._alarmsound, position=team.base_pos)
|
||||||
position=team.gamedata['base_pos'])
|
|
||||||
self._flash_base(team)
|
self._flash_base(team)
|
||||||
team.gamedata['last_flag_leave_time'] = cur_time
|
team.last_flag_leave_time = cur_time
|
||||||
team.gamedata['home_flag_at_base'] = False
|
team.home_flag_at_base = False
|
||||||
else:
|
else:
|
||||||
team.gamedata['enemy_flag_at_base'] = False
|
team.enemy_flag_at_base = False
|
||||||
|
|
||||||
def _touch_return_update(self, team: ba.Team) -> None:
|
|
||||||
|
|
||||||
|
def _touch_return_update(self, team: Team) -> None:
|
||||||
# Count down only while its away from base and not being held.
|
# Count down only while its away from base and not being held.
|
||||||
if (team.gamedata['home_flag_at_base']
|
assert team.flag is not None
|
||||||
or team.gamedata['flag'].held_count > 0):
|
if team.home_flag_at_base or team.flag.held_count > 0:
|
||||||
team.gamedata['touch_return_timer_ticking'] = None
|
team.touch_return_timer_ticking = None
|
||||||
return # No need to return when its at home.
|
return # No need to return when its at home.
|
||||||
if team.gamedata['touch_return_timer_ticking'] is None:
|
if team.touch_return_timer_ticking is None:
|
||||||
team.gamedata['touch_return_timer_ticking'] = ba.NodeActor(
|
team.touch_return_timer_ticking = ba.NodeActor(
|
||||||
ba.newnode('sound',
|
ba.newnode('sound',
|
||||||
attrs={
|
attrs={
|
||||||
'sound': self._ticking_sound,
|
'sound': self._ticking_sound,
|
||||||
'positional': False,
|
'positional': False,
|
||||||
'loop': True
|
'loop': True
|
||||||
}))
|
}))
|
||||||
flag = team.gamedata['flag']
|
flag = team.flag
|
||||||
|
assert flag.touch_return_time is not None
|
||||||
flag.touch_return_time -= 0.1
|
flag.touch_return_time -= 0.1
|
||||||
if flag.counter:
|
if flag.counter:
|
||||||
flag.counter.text = '%.1f' % flag.touch_return_time
|
flag.counter.text = f'{flag.touch_return_time:.1f}'
|
||||||
flag.counter.color = (1, 1, 0, 1)
|
flag.counter.color = (1, 1, 0, 1)
|
||||||
flag.counter.scale = 0.02
|
flag.counter.scale = 0.02
|
||||||
|
|
||||||
@ -387,9 +431,9 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
self._award_players_touching_own_flag(team)
|
self._award_players_touching_own_flag(team)
|
||||||
flag.handlemessage(ba.DieMessage())
|
flag.handlemessage(ba.DieMessage())
|
||||||
|
|
||||||
def _award_players_touching_own_flag(self, team: ba.Team) -> None:
|
def _award_players_touching_own_flag(self, team: Team) -> None:
|
||||||
for player in team.players:
|
for player in team.players:
|
||||||
if player.gamedata['touching_own_flag'] > 0:
|
if player.touching_own_flag > 0:
|
||||||
return_score = 10 + 5 * int(
|
return_score = 10 + 5 * int(
|
||||||
self.settings_raw['Flag Touch Return Time'])
|
self.settings_raw['Flag Touch Return Time'])
|
||||||
self.stats.player_scored(player,
|
self.stats.player_scored(player,
|
||||||
@ -397,7 +441,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
screenmessage=False)
|
screenmessage=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _player_from_node(node: Optional[ba.Node]) -> Optional[ba.Player]:
|
def _player_from_node(node: Optional[ba.Node]) -> Optional[Player]:
|
||||||
"""Return a player if given a node that is part of one's actor."""
|
"""Return a player if given a node that is part of one's actor."""
|
||||||
if not node:
|
if not node:
|
||||||
return None
|
return None
|
||||||
@ -406,7 +450,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
return None
|
return None
|
||||||
return delegate.getplayer()
|
return delegate.getplayer()
|
||||||
|
|
||||||
def _handle_hit_own_flag(self, team: ba.Team, val: int) -> None:
|
def _handle_hit_own_flag(self, team: Team, val: int) -> None:
|
||||||
"""
|
"""
|
||||||
keep track of when each player is touching their
|
keep track of when each player is touching their
|
||||||
own flag so we can award points when returned
|
own flag so we can award points when returned
|
||||||
@ -415,13 +459,13 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
assert isinstance(srcnode, (ba.Node, type(None)))
|
assert isinstance(srcnode, (ba.Node, type(None)))
|
||||||
player = self._player_from_node(srcnode)
|
player = self._player_from_node(srcnode)
|
||||||
if player:
|
if player:
|
||||||
player.gamedata['touching_own_flag'] += (1 if val else -1)
|
player.touching_own_flag += (1 if val else -1)
|
||||||
|
|
||||||
# If return-time is zero, just kill it immediately.. otherwise keep
|
# If return-time is zero, just kill it immediately.. otherwise keep
|
||||||
# track of touches and count down.
|
# track of touches and count down.
|
||||||
if float(self.settings_raw['Flag Touch Return Time']) <= 0.0:
|
if float(self.settings_raw['Flag Touch Return Time']) <= 0.0:
|
||||||
if (not team.gamedata['home_flag_at_base']
|
assert team.flag is not None
|
||||||
and team.gamedata['flag'].held_count == 0):
|
if not team.home_flag_at_base and team.flag.held_count == 0:
|
||||||
|
|
||||||
# Use a node message to kill the flag instead of just killing
|
# Use a node message to kill the flag instead of just killing
|
||||||
# our team's. (avoids redundantly killing new flags if
|
# our team's. (avoids redundantly killing new flags if
|
||||||
@ -434,26 +478,26 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
# Takes a non-zero amount of time to return.
|
# Takes a non-zero amount of time to return.
|
||||||
else:
|
else:
|
||||||
if val:
|
if val:
|
||||||
team.gamedata['flag_return_touches'] += 1
|
team.flag_return_touches += 1
|
||||||
if team.gamedata['flag_return_touches'] == 1:
|
if team.flag_return_touches == 1:
|
||||||
team.gamedata['touch_return_timer'] = ba.Timer(
|
team.touch_return_timer = ba.Timer(
|
||||||
0.1,
|
0.1,
|
||||||
call=ba.Call(self._touch_return_update, team),
|
call=ba.Call(self._touch_return_update, team),
|
||||||
repeat=True)
|
repeat=True)
|
||||||
team.gamedata['touch_return_timer_ticking'] = None
|
team.touch_return_timer_ticking = None
|
||||||
else:
|
else:
|
||||||
team.gamedata['flag_return_touches'] -= 1
|
team.flag_return_touches -= 1
|
||||||
if team.gamedata['flag_return_touches'] == 0:
|
if team.flag_return_touches == 0:
|
||||||
team.gamedata['touch_return_timer'] = None
|
team.touch_return_timer = None
|
||||||
team.gamedata['touch_return_timer_ticking'] = None
|
team.touch_return_timer_ticking = None
|
||||||
if team.gamedata['flag_return_touches'] < 0:
|
if team.flag_return_touches < 0:
|
||||||
ba.print_error(
|
ba.print_error(
|
||||||
"CTF: flag_return_touches < 0; this shouldn't happen.")
|
"CTF: flag_return_touches < 0; this shouldn't happen.")
|
||||||
|
|
||||||
def _flash_base(self, team: ba.Team, length: float = 2.0) -> None:
|
def _flash_base(self, team: Team, length: float = 2.0) -> None:
|
||||||
light = ba.newnode('light',
|
light = ba.newnode('light',
|
||||||
attrs={
|
attrs={
|
||||||
'position': team.gamedata['base_pos'],
|
'position': team.base_pos,
|
||||||
'height_attenuated': False,
|
'height_attenuated': False,
|
||||||
'radius': 0.3,
|
'radius': 0.3,
|
||||||
'color': team.color
|
'color': team.color
|
||||||
@ -461,22 +505,21 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
ba.animate(light, 'intensity', {0.0: 0, 0.25: 2.0, 0.5: 0}, loop=True)
|
ba.animate(light, 'intensity', {0.0: 0, 0.25: 2.0, 0.5: 0}, loop=True)
|
||||||
ba.timer(length, light.delete)
|
ba.timer(length, light.delete)
|
||||||
|
|
||||||
def spawn_player_spaz(self, *args: Any, **keywds: Any) -> Any:
|
def spawn_player_spaz(self,
|
||||||
|
player: Player,
|
||||||
|
position: Sequence[float] = None,
|
||||||
|
angle: float = None) -> PlayerSpaz:
|
||||||
"""Intercept new spazzes and add our team material for them."""
|
"""Intercept new spazzes and add our team material for them."""
|
||||||
# (chill pylint; we're passing our exact args to parent call)
|
spaz = super().spawn_player_spaz(player, position, angle)
|
||||||
# pylint: disable=signature-differs
|
|
||||||
spaz = super().spawn_player_spaz(*args, **keywds)
|
|
||||||
player = spaz.player
|
player = spaz.player
|
||||||
player.gamedata['touching_own_flag'] = 0
|
team: Team = player.team
|
||||||
|
player.touching_own_flag = 0
|
||||||
# Ignore false alarm for gamedata member.
|
no_physical_mats: List[ba.Material] = [
|
||||||
no_physical_mats = [
|
team.spaz_material_no_flag_physical
|
||||||
player.team.gamedata['spaz_material_no_flag_physical']
|
|
||||||
]
|
]
|
||||||
no_collide_mats = [
|
no_collide_mats: List[ba.Material] = [
|
||||||
player.team.gamedata['spaz_material_no_flag_collide']
|
team.spaz_material_no_flag_collide
|
||||||
]
|
]
|
||||||
# pylint: enable=arguments-differ
|
|
||||||
|
|
||||||
# Our normal parts should still collide; just not physically
|
# Our normal parts should still collide; just not physically
|
||||||
# (so we can calc restores).
|
# (so we can calc restores).
|
||||||
@ -496,14 +539,14 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
|
|||||||
|
|
||||||
def _update_scoreboard(self) -> None:
|
def _update_scoreboard(self) -> None:
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
self._scoreboard.set_team_value(team, team.gamedata['score'],
|
self._scoreboard.set_team_value(team, team.score,
|
||||||
self.settings_raw['Score to Win'])
|
self.settings_raw['Score to Win'])
|
||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
if isinstance(msg, PlayerSpazDeathMessage):
|
if isinstance(msg, PlayerSpazDeathMessage):
|
||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
self.respawn_player(msg.spaz.player)
|
self.respawn_player(msg.getspaz(self).player)
|
||||||
elif isinstance(msg, stdflag.FlagDeathMessage):
|
elif isinstance(msg, stdflag.FlagDeathMessage):
|
||||||
assert isinstance(msg.flag, CTFFlag)
|
assert isinstance(msg.flag, CTFFlag)
|
||||||
ba.timer(0.1, ba.Call(self._spawn_flag_for_team, msg.flag.team))
|
ba.timer(0.1, ba.Call(self._spawn_flag_for_team, msg.flag.team))
|
||||||
|
|||||||
@ -37,7 +37,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class ChosenOneGame(ba.TeamGameActivity):
|
class ChosenOneGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""
|
"""
|
||||||
Game involving trying to remain the one 'chosen one'
|
Game involving trying to remain the one 'chosen one'
|
||||||
for a set length of time while everyone else tries to
|
for a set length of time while everyone else tries to
|
||||||
@ -327,7 +327,7 @@ class ChosenOneGame(ba.TeamGameActivity):
|
|||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
player = msg.spaz.player
|
player = msg.getspaz(self).player
|
||||||
if player is self._get_chosen_one_player():
|
if player is self._get_chosen_one_player():
|
||||||
killerplayer = msg.killerplayer
|
killerplayer = msg.killerplayer
|
||||||
self._set_chosen_one_player(None if (
|
self._set_chosen_one_player(None if (
|
||||||
|
|||||||
@ -57,7 +57,7 @@ class ConquestFlag(Flag):
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class ConquestGame(ba.TeamGameActivity):
|
class ConquestGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""A game where teams try to claim all flags on the map."""
|
"""A game where teams try to claim all flags on the map."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -254,7 +254,7 @@ class ConquestGame(ba.TeamGameActivity):
|
|||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
|
|
||||||
# Respawn only if this team has a flag.
|
# Respawn only if this team has a flag.
|
||||||
player = msg.spaz.player
|
player = msg.getspaz(self).player
|
||||||
if player.team.gamedata['flags_held'] > 0:
|
if player.team.gamedata['flags_held'] > 0:
|
||||||
self.respawn_player(player)
|
self.respawn_player(player)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -35,7 +35,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class DeathMatchGame(ba.TeamGameActivity):
|
class DeathMatchGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""A game type based on acquiring kills."""
|
"""A game type based on acquiring kills."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -145,7 +145,7 @@ class DeathMatchGame(ba.TeamGameActivity):
|
|||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
|
|
||||||
player = msg.spaz.player
|
player = msg.getspaz(self).player
|
||||||
self.respawn_player(player)
|
self.respawn_player(player)
|
||||||
|
|
||||||
killer = msg.killerplayer
|
killer = msg.killerplayer
|
||||||
|
|||||||
@ -39,7 +39,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class EasterEggHuntGame(ba.TeamGameActivity):
|
class EasterEggHuntGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""A game where score is based on collecting eggs."""
|
"""A game where score is based on collecting eggs."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -212,7 +212,7 @@ class EasterEggHuntGame(ba.TeamGameActivity):
|
|||||||
|
|
||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
player = msg.spaz.getplayer()
|
player = msg.getspaz(self).getplayer()
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
self.stats.player_was_killed(player)
|
self.stats.player_was_killed(player)
|
||||||
|
|||||||
@ -164,7 +164,7 @@ class Icon(ba.Actor):
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class EliminationGame(ba.TeamGameActivity):
|
class EliminationGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""Game type where last player(s) left alive win."""
|
"""Game type where last player(s) left alive win."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -330,7 +330,7 @@ class EliminationGame(ba.TeamGameActivity):
|
|||||||
# Now for each team, cycle through our available players
|
# Now for each team, cycle through our available players
|
||||||
# adding icons.
|
# adding icons.
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
if team.get_id() == 0:
|
if team.id == 0:
|
||||||
xval = -60
|
xval = -60
|
||||||
x_offs = -78
|
x_offs = -78
|
||||||
else:
|
else:
|
||||||
@ -362,7 +362,7 @@ class EliminationGame(ba.TeamGameActivity):
|
|||||||
# Non-solo mode.
|
# Non-solo mode.
|
||||||
else:
|
else:
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
if team.get_id() == 0:
|
if team.id == 0:
|
||||||
xval = -50
|
xval = -50
|
||||||
x_offs = -85
|
x_offs = -85
|
||||||
else:
|
else:
|
||||||
@ -395,8 +395,7 @@ class EliminationGame(ba.TeamGameActivity):
|
|||||||
player_pos = ba.Vec3(living_player_pos)
|
player_pos = ba.Vec3(living_player_pos)
|
||||||
points: List[Tuple[float, ba.Vec3]] = []
|
points: List[Tuple[float, ba.Vec3]] = []
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
start_pos = ba.Vec3(
|
start_pos = ba.Vec3(self.map.get_start_position(team.id))
|
||||||
self.map.get_start_position(team.get_id()))
|
|
||||||
points.append(
|
points.append(
|
||||||
((start_pos - player_pos).length(), start_pos))
|
((start_pos - player_pos).length(), start_pos))
|
||||||
# Hmm.. we need to sorting vectors too?
|
# Hmm.. we need to sorting vectors too?
|
||||||
@ -492,7 +491,7 @@ class EliminationGame(ba.TeamGameActivity):
|
|||||||
|
|
||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
player = msg.spaz.player
|
player = msg.getspaz(self).player
|
||||||
|
|
||||||
player.gamedata['lives'] -= 1
|
player.gamedata['lives'] -= 1
|
||||||
if player.gamedata['lives'] < 0:
|
if player.gamedata['lives'] < 0:
|
||||||
|
|||||||
@ -67,7 +67,7 @@ class FootballFlag(stdflag.Flag):
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class FootballTeamGame(ba.TeamGameActivity):
|
class FootballTeamGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""Football game for teams mode."""
|
"""Football game for teams mode."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -204,7 +204,7 @@ class FootballTeamGame(ba.TeamGameActivity):
|
|||||||
if region == self._score_regions[i].node:
|
if region == self._score_regions[i].node:
|
||||||
break
|
break
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
if team.get_id() == i:
|
if team.id == i:
|
||||||
team.gamedata['score'] += 7
|
team.gamedata['score'] += 7
|
||||||
|
|
||||||
# Tell all players to celebrate.
|
# Tell all players to celebrate.
|
||||||
@ -274,7 +274,7 @@ class FootballTeamGame(ba.TeamGameActivity):
|
|||||||
elif isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
elif isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
self.respawn_player(msg.spaz.player)
|
self.respawn_player(msg.getspaz(self).player)
|
||||||
|
|
||||||
# Respawn dead flags.
|
# Respawn dead flags.
|
||||||
elif isinstance(msg, stdflag.FlagDeathMessage):
|
elif isinstance(msg, stdflag.FlagDeathMessage):
|
||||||
@ -320,7 +320,7 @@ class FootballTeamGame(ba.TeamGameActivity):
|
|||||||
self._flag = FootballFlag(position=self._flag_spawn_pos)
|
self._flag = FootballFlag(position=self._flag_spawn_pos)
|
||||||
|
|
||||||
|
|
||||||
class FootballCoopGame(ba.CoopGameActivity):
|
class FootballCoopGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
||||||
"""
|
"""
|
||||||
Co-op variant of football
|
Co-op variant of football
|
||||||
"""
|
"""
|
||||||
@ -508,7 +508,11 @@ class FootballCoopGame(ba.CoopGameActivity):
|
|||||||
|
|
||||||
# Make a bogus team for our bots.
|
# Make a bogus team for our bots.
|
||||||
bad_team_name = self.get_team_display_string('Bad Guys')
|
bad_team_name = self.get_team_display_string('Bad Guys')
|
||||||
self._bot_team = ba.Team(1, bad_team_name, (0.5, 0.4, 0.4))
|
# self._bot_team = ba.Team(1, bad_team_name, (0.5, 0.4, 0.4))
|
||||||
|
self._bot_team = ba.Team()
|
||||||
|
self._bot_team.id = 1
|
||||||
|
self._bot_team.name = bad_team_name
|
||||||
|
self._bot_team.color = (0.5, 0.4, 0.4)
|
||||||
|
|
||||||
for team in [self.teams[0], self._bot_team]:
|
for team in [self.teams[0], self._bot_team]:
|
||||||
team.gamedata['score'] = 0
|
team.gamedata['score'] = 0
|
||||||
@ -562,7 +566,7 @@ class FootballCoopGame(ba.CoopGameActivity):
|
|||||||
spaz_type: Type[spazbot.SpazBot],
|
spaz_type: Type[spazbot.SpazBot],
|
||||||
immediate: bool = False) -> None:
|
immediate: bool = False) -> None:
|
||||||
assert self._bot_team is not None
|
assert self._bot_team is not None
|
||||||
pos = self.map.get_start_position(self._bot_team.get_id())
|
pos = self.map.get_start_position(self._bot_team.id)
|
||||||
self._bots.spawn_bot(spaz_type,
|
self._bots.spawn_bot(spaz_type,
|
||||||
pos=pos,
|
pos=pos,
|
||||||
spawn_time=0.001 if immediate else 3.0,
|
spawn_time=0.001 if immediate else 3.0,
|
||||||
@ -668,7 +672,7 @@ class FootballCoopGame(ba.CoopGameActivity):
|
|||||||
|
|
||||||
for team in [self.teams[0], self._bot_team]:
|
for team in [self.teams[0], self._bot_team]:
|
||||||
assert team is not None
|
assert team is not None
|
||||||
if team.get_id() == i:
|
if team.id == i:
|
||||||
team.gamedata['score'] += 7
|
team.gamedata['score'] += 7
|
||||||
|
|
||||||
# Tell all players (or bots) to celebrate.
|
# Tell all players (or bots) to celebrate.
|
||||||
@ -808,7 +812,7 @@ class FootballCoopGame(ba.CoopGameActivity):
|
|||||||
from bastd.actor import respawnicon
|
from bastd.actor import respawnicon
|
||||||
|
|
||||||
# Respawn dead players.
|
# Respawn dead players.
|
||||||
player = msg.spaz.player
|
player = msg.getspaz(self).player
|
||||||
self.stats.player_was_killed(player)
|
self.stats.player_was_killed(player)
|
||||||
assert self.initial_player_info is not None
|
assert self.initial_player_info is not None
|
||||||
respawn_time = 2.0 + len(self.initial_player_info) * 1.0
|
respawn_time = 2.0 + len(self.initial_player_info) * 1.0
|
||||||
@ -871,7 +875,7 @@ class FootballCoopGame(ba.CoopGameActivity):
|
|||||||
def spawn_player(self, player: ba.Player) -> ba.Actor:
|
def spawn_player(self, player: ba.Player) -> ba.Actor:
|
||||||
spaz = self.spawn_player_spaz(player,
|
spaz = self.spawn_player_spaz(player,
|
||||||
position=self.map.get_start_position(
|
position=self.map.get_start_position(
|
||||||
player.team.get_id()))
|
player.team.id))
|
||||||
if self._preset in ['rookie_easy', 'pro_easy', 'uber_easy']:
|
if self._preset in ['rookie_easy', 'pro_easy', 'uber_easy']:
|
||||||
spaz.impact_scale = 0.25
|
spaz.impact_scale = 0.25
|
||||||
spaz.add_dropped_bomb_callback(self._handle_player_dropped_bomb)
|
spaz.add_dropped_bomb_callback(self._handle_player_dropped_bomb)
|
||||||
|
|||||||
@ -100,14 +100,13 @@ class Puck(ba.Actor):
|
|||||||
if activity:
|
if activity:
|
||||||
if msg.source_player in activity.players:
|
if msg.source_player in activity.players:
|
||||||
self.last_players_to_touch[
|
self.last_players_to_touch[
|
||||||
msg.source_player.team.get_id(
|
msg.source_player.team.id] = msg.source_player
|
||||||
)] = msg.source_player
|
|
||||||
else:
|
else:
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class HockeyGame(ba.TeamGameActivity):
|
class HockeyGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""Ice hockey game."""
|
"""Ice hockey game."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -255,8 +254,10 @@ class HockeyGame(ba.TeamGameActivity):
|
|||||||
player = playernode.getdelegate().getplayer()
|
player = playernode.getdelegate().getplayer()
|
||||||
except Exception:
|
except Exception:
|
||||||
player = puck = None
|
player = puck = None
|
||||||
|
assert isinstance(player, ba.Player)
|
||||||
|
assert isinstance(puck, Puck)
|
||||||
if player and puck:
|
if player and puck:
|
||||||
puck.last_players_to_touch[player.team.get_id()] = player
|
puck.last_players_to_touch[player.team.id] = player
|
||||||
|
|
||||||
def _kill_puck(self) -> None:
|
def _kill_puck(self) -> None:
|
||||||
self._puck = None
|
self._puck = None
|
||||||
@ -279,7 +280,7 @@ class HockeyGame(ba.TeamGameActivity):
|
|||||||
break
|
break
|
||||||
|
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
if team.get_id() == index:
|
if team.id == index:
|
||||||
scoring_team = team
|
scoring_team = team
|
||||||
team.gamedata['score'] += 1
|
team.gamedata['score'] += 1
|
||||||
|
|
||||||
@ -290,13 +291,12 @@ class HockeyGame(ba.TeamGameActivity):
|
|||||||
|
|
||||||
# If we've got the player from the scoring team that last
|
# If we've got the player from the scoring team that last
|
||||||
# touched us, give them points.
|
# touched us, give them points.
|
||||||
if (scoring_team.get_id() in self._puck.last_players_to_touch
|
if (scoring_team.id in self._puck.last_players_to_touch
|
||||||
and self._puck.last_players_to_touch[
|
and self._puck.last_players_to_touch[scoring_team.id]):
|
||||||
scoring_team.get_id()]):
|
self.stats.player_scored(
|
||||||
self.stats.player_scored(self._puck.last_players_to_touch[
|
self._puck.last_players_to_touch[scoring_team.id],
|
||||||
scoring_team.get_id()],
|
100,
|
||||||
100,
|
big_message=True)
|
||||||
big_message=True)
|
|
||||||
|
|
||||||
# End game if we won.
|
# End game if we won.
|
||||||
if team.gamedata['score'] >= self.settings_raw['Score to Win']:
|
if team.gamedata['score'] >= self.settings_raw['Score to Win']:
|
||||||
@ -341,7 +341,7 @@ class HockeyGame(ba.TeamGameActivity):
|
|||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
||||||
# Augment standard behavior...
|
# Augment standard behavior...
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
self.respawn_player(msg.spaz.player)
|
self.respawn_player(msg.getspaz(self).player)
|
||||||
|
|
||||||
# Respawn dead pucks.
|
# Respawn dead pucks.
|
||||||
elif isinstance(msg, PuckDeathMessage):
|
elif isinstance(msg, PuckDeathMessage):
|
||||||
|
|||||||
@ -37,7 +37,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class KeepAwayGame(ba.TeamGameActivity):
|
class KeepAwayGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""Game where you try to keep the flag away from your enemies."""
|
"""Game where you try to keep the flag away from your enemies."""
|
||||||
|
|
||||||
FLAG_NEW = 0
|
FLAG_NEW = 0
|
||||||
@ -271,7 +271,7 @@ class KeepAwayGame(ba.TeamGameActivity):
|
|||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
||||||
# Augment standard behavior.
|
# Augment standard behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
self.respawn_player(msg.spaz.player)
|
self.respawn_player(msg.getspaz(self).player)
|
||||||
elif isinstance(msg, stdflag.FlagDeathMessage):
|
elif isinstance(msg, stdflag.FlagDeathMessage):
|
||||||
self._spawn_flag()
|
self._spawn_flag()
|
||||||
elif isinstance(
|
elif isinstance(
|
||||||
|
|||||||
@ -39,7 +39,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class KingOfTheHillGame(ba.TeamGameActivity):
|
class KingOfTheHillGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""Game where a team wins by holding a 'hill' for a set amount of time."""
|
"""Game where a team wins by holding a 'hill' for a set amount of time."""
|
||||||
|
|
||||||
FLAG_NEW = 0
|
FLAG_NEW = 0
|
||||||
@ -281,7 +281,7 @@ class KingOfTheHillGame(ba.TeamGameActivity):
|
|||||||
super().handlemessage(msg) # Augment default.
|
super().handlemessage(msg) # Augment default.
|
||||||
|
|
||||||
# No longer can count as at_flag once dead.
|
# No longer can count as at_flag once dead.
|
||||||
player = msg.spaz.player
|
player = msg.getspaz(self).player
|
||||||
player.gamedata['at_flag'] = 0
|
player.gamedata['at_flag'] = 0
|
||||||
self._update_flag_state()
|
self._update_flag_state()
|
||||||
self.respawn_player(player)
|
self.respawn_player(player)
|
||||||
|
|||||||
@ -26,26 +26,31 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import ba
|
import ba
|
||||||
from bastd.actor.bomb import Bomb
|
from bastd.actor.bomb import Bomb
|
||||||
from bastd.actor.playerspaz import PlayerSpazDeathMessage
|
from bastd.actor.playerspaz import PlayerSpazDeathMessage
|
||||||
|
from bastd.actor.onscreentimer import OnScreenTimer
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Tuple, Sequence, Optional, List, Dict, Type, Type
|
from typing import Any, Tuple, Sequence, Optional, List, Dict, Type, Type
|
||||||
from bastd.actor.onscreentimer import OnScreenTimer
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class Player(ba.Player['Team']):
|
||||||
class PlayerData(ba.BasePlayerData):
|
"""Our player type for this game."""
|
||||||
"""Data we store per player."""
|
|
||||||
death_time: Optional[float] = None
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.death_time: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Team(ba.Team[Player]):
|
||||||
|
"""Our team type for this game."""
|
||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class MeteorShowerGame(ba.TeamGameActivity):
|
class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
|
||||||
"""Minigame involving dodging falling bombs."""
|
"""Minigame involving dodging falling bombs."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -80,6 +85,9 @@ class MeteorShowerGame(ba.TeamGameActivity):
|
|||||||
or issubclass(sessiontype, ba.FreeForAllSession)
|
or issubclass(sessiontype, ba.FreeForAllSession)
|
||||||
or issubclass(sessiontype, ba.CoopSession))
|
or issubclass(sessiontype, ba.CoopSession))
|
||||||
|
|
||||||
|
# Print messages when players die (since its meaningful in this game).
|
||||||
|
announce_player_deaths = True
|
||||||
|
|
||||||
def __init__(self, settings: Dict[str, Any]):
|
def __init__(self, settings: Dict[str, Any]):
|
||||||
super().__init__(settings)
|
super().__init__(settings)
|
||||||
|
|
||||||
@ -94,12 +102,7 @@ class MeteorShowerGame(ba.TeamGameActivity):
|
|||||||
if self._epic_mode:
|
if self._epic_mode:
|
||||||
self.slow_motion = True
|
self.slow_motion = True
|
||||||
|
|
||||||
# Print messages when players die (since its meaningful in this game).
|
|
||||||
self.announce_player_deaths = True
|
|
||||||
|
|
||||||
def on_begin(self) -> None:
|
def on_begin(self) -> None:
|
||||||
from bastd.actor.onscreentimer import OnScreenTimer
|
|
||||||
|
|
||||||
super().on_begin()
|
super().on_begin()
|
||||||
|
|
||||||
# Drop a wave every few seconds.. and every so often drop the time
|
# Drop a wave every few seconds.. and every so often drop the time
|
||||||
@ -122,22 +125,23 @@ class MeteorShowerGame(ba.TeamGameActivity):
|
|||||||
# Check for immediate end (if we've only got 1 player, etc).
|
# Check for immediate end (if we've only got 1 player, etc).
|
||||||
ba.timer(5.0, self._check_end_game)
|
ba.timer(5.0, self._check_end_game)
|
||||||
|
|
||||||
def on_player_join(self, player: ba.Player) -> None:
|
def on_player_join(self, player: Player) -> None:
|
||||||
# Don't allow joining after we start
|
# Don't allow joining after we start
|
||||||
# (would enable leave/rejoin tomfoolery).
|
# (would enable leave/rejoin tomfoolery).
|
||||||
if self.has_begun():
|
if self.has_begun():
|
||||||
ba.screenmessage(ba.Lstr(resource='playerDelayedJoinText',
|
ba.screenmessage(
|
||||||
subs=[('${PLAYER}',
|
ba.Lstr(resource='playerDelayedJoinText',
|
||||||
player.get_name(full=True))]),
|
subs=[('${PLAYER}', player.get_name(full=True))]),
|
||||||
color=(0, 1, 0))
|
color=(0, 1, 0),
|
||||||
|
)
|
||||||
# For score purposes, mark them as having died right as the
|
# For score purposes, mark them as having died right as the
|
||||||
# game started.
|
# game started.
|
||||||
assert self._timer is not None
|
assert self._timer is not None
|
||||||
PlayerData.get(player).death_time = self._timer.getstarttime()
|
player.death_time = self._timer.getstarttime()
|
||||||
return
|
return
|
||||||
self.spawn_player(player)
|
self.spawn_player(player)
|
||||||
|
|
||||||
def on_player_leave(self, player: ba.Player) -> None:
|
def on_player_leave(self, player: Player) -> None:
|
||||||
# Augment default behavior.
|
# Augment default behavior.
|
||||||
super().on_player_leave(player)
|
super().on_player_leave(player)
|
||||||
|
|
||||||
@ -145,7 +149,7 @@ class MeteorShowerGame(ba.TeamGameActivity):
|
|||||||
self._check_end_game()
|
self._check_end_game()
|
||||||
|
|
||||||
# overriding the default character spawning..
|
# overriding the default character spawning..
|
||||||
def spawn_player(self, player: ba.Player) -> ba.Actor:
|
def spawn_player(self, player: Player) -> ba.Actor:
|
||||||
spaz = self.spawn_player_spaz(player)
|
spaz = self.spawn_player_spaz(player)
|
||||||
|
|
||||||
# Let's reconnect this player's controls to this
|
# Let's reconnect this player's controls to this
|
||||||
@ -168,7 +172,8 @@ class MeteorShowerGame(ba.TeamGameActivity):
|
|||||||
curtime = ba.time()
|
curtime = ba.time()
|
||||||
|
|
||||||
# Record the player's moment of death.
|
# Record the player's moment of death.
|
||||||
PlayerData.get(msg.spaz.player).death_time = curtime
|
# assert isinstance(msg.spaz.player
|
||||||
|
msg.getspaz(self).player.death_time = curtime
|
||||||
|
|
||||||
# In co-op mode, end the game the instant everyone dies
|
# In co-op mode, end the game the instant everyone dies
|
||||||
# (more accurate looking).
|
# (more accurate looking).
|
||||||
@ -250,19 +255,18 @@ class MeteorShowerGame(ba.TeamGameActivity):
|
|||||||
# (these per-player scores are only meaningful in team-games)
|
# (these per-player scores are only meaningful in team-games)
|
||||||
for team in self.teams:
|
for team in self.teams:
|
||||||
for player in team.players:
|
for player in team.players:
|
||||||
playerdata = PlayerData.get(player)
|
|
||||||
survived = False
|
survived = False
|
||||||
|
|
||||||
# Throw an extra fudge factor in so teams that
|
# Throw an extra fudge factor in so teams that
|
||||||
# didn't die come out ahead of teams that did.
|
# didn't die come out ahead of teams that did.
|
||||||
if playerdata.death_time is None:
|
if player.death_time is None:
|
||||||
survived = True
|
survived = True
|
||||||
playerdata.death_time = cur_time + 1
|
player.death_time = cur_time + 1
|
||||||
|
|
||||||
# Award a per-player score depending on how many seconds
|
# Award a per-player score depending on how many seconds
|
||||||
# they lasted (per-player scores only affect teams mode;
|
# they lasted (per-player scores only affect teams mode;
|
||||||
# everywhere else just looks at the per-team score).
|
# everywhere else just looks at the per-team score).
|
||||||
score = int(playerdata.death_time - self._timer.getstarttime())
|
score = int(player.death_time - self._timer.getstarttime())
|
||||||
if survived:
|
if survived:
|
||||||
score += 50 # A bit extra for survivors.
|
score += 50 # A bit extra for survivors.
|
||||||
self.stats.player_scored(player, score, screenmessage=False)
|
self.stats.player_scored(player, score, screenmessage=False)
|
||||||
@ -284,10 +288,9 @@ class MeteorShowerGame(ba.TeamGameActivity):
|
|||||||
# that team.
|
# that team.
|
||||||
longest_life = 0.0
|
longest_life = 0.0
|
||||||
for player in team.players:
|
for player in team.players:
|
||||||
playerdata = PlayerData.get(player)
|
assert player.death_time is not None
|
||||||
assert playerdata.death_time is not None
|
|
||||||
longest_life = max(longest_life,
|
longest_life = max(longest_life,
|
||||||
playerdata.death_time - start_time)
|
player.death_time - start_time)
|
||||||
|
|
||||||
# Submit the score value in milliseconds.
|
# Submit the score value in milliseconds.
|
||||||
results.set_team_score(team, int(1000.0 * longest_life))
|
results.set_team_score(team, int(1000.0 * longest_life))
|
||||||
|
|||||||
@ -38,7 +38,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class NinjaFightGame(ba.TeamGameActivity):
|
class NinjaFightGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""
|
"""
|
||||||
A co-op game where you try to defeat a group
|
A co-op game where you try to defeat a group
|
||||||
of Ninjas as fast as possible
|
of Ninjas as fast as possible
|
||||||
@ -148,7 +148,7 @@ class NinjaFightGame(ba.TeamGameActivity):
|
|||||||
# A player has died.
|
# A player has died.
|
||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
||||||
super().handlemessage(msg) # do standard stuff
|
super().handlemessage(msg) # do standard stuff
|
||||||
self.respawn_player(msg.spaz.player) # kick off a respawn
|
self.respawn_player(msg.getspaz(self).player) # kick off a respawn
|
||||||
|
|
||||||
# A spaz-bot has died.
|
# A spaz-bot has died.
|
||||||
elif isinstance(msg, spazbot.SpazBotDeathMessage):
|
elif isinstance(msg, spazbot.SpazBotDeathMessage):
|
||||||
|
|||||||
@ -38,7 +38,7 @@ if TYPE_CHECKING:
|
|||||||
from bastd.actor.scoreboard import Scoreboard
|
from bastd.actor.scoreboard import Scoreboard
|
||||||
|
|
||||||
|
|
||||||
class OnslaughtGame(ba.CoopGameActivity):
|
class OnslaughtGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
||||||
"""Co-op game where players try to survive attacking waves of enemies."""
|
"""Co-op game where players try to survive attacking waves of enemies."""
|
||||||
|
|
||||||
tips: List[Union[str, Dict[str, Any]]] = [
|
tips: List[Union[str, Dict[str, Any]]] = [
|
||||||
@ -1163,7 +1163,7 @@ class OnslaughtGame(ba.CoopGameActivity):
|
|||||||
|
|
||||||
elif isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
elif isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
||||||
super().handlemessage(msg) # Augment standard behavior.
|
super().handlemessage(msg) # Augment standard behavior.
|
||||||
player = msg.spaz.getplayer()
|
player = msg.getspaz(self).getplayer()
|
||||||
assert player is not None
|
assert player is not None
|
||||||
self._a_player_has_been_hurt = True
|
self._a_player_has_been_hurt = True
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,7 @@ class RaceRegion(ba.Actor):
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class RaceGame(ba.TeamGameActivity):
|
class RaceGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""Game of racing around a track."""
|
"""Game of racing around a track."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -733,7 +733,7 @@ class RaceGame(ba.TeamGameActivity):
|
|||||||
if isinstance(msg, PlayerSpazDeathMessage):
|
if isinstance(msg, PlayerSpazDeathMessage):
|
||||||
# Augment default behavior.
|
# Augment default behavior.
|
||||||
super().handlemessage(msg)
|
super().handlemessage(msg)
|
||||||
player = msg.spaz.getplayer()
|
player = msg.getspaz(self).getplayer()
|
||||||
if not player:
|
if not player:
|
||||||
ba.print_error('got no player in PlayerSpazDeathMessage')
|
ba.print_error('got no player in PlayerSpazDeathMessage')
|
||||||
return
|
return
|
||||||
|
|||||||
@ -38,7 +38,7 @@ if TYPE_CHECKING:
|
|||||||
from typing import Type, Any, List, Dict, Tuple, Sequence, Optional
|
from typing import Type, Any, List, Dict, Tuple, Sequence, Optional
|
||||||
|
|
||||||
|
|
||||||
class RunaroundGame(ba.CoopGameActivity):
|
class RunaroundGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
||||||
"""Game involving trying to bomb bots as they walk through the map."""
|
"""Game involving trying to bomb bots as they walk through the map."""
|
||||||
|
|
||||||
tips = [
|
tips = [
|
||||||
@ -48,7 +48,7 @@ class RunaroundGame(ba.CoopGameActivity):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# How fast our various bot types walk.
|
# How fast our various bot types walk.
|
||||||
_bot_speed_map = {
|
_bot_speed_map: Dict[Type[spazbot.SpazBot], float] = {
|
||||||
spazbot.BomberBot: 0.48,
|
spazbot.BomberBot: 0.48,
|
||||||
spazbot.BomberBotPro: 0.48,
|
spazbot.BomberBotPro: 0.48,
|
||||||
spazbot.BomberBotProShielded: 0.48,
|
spazbot.BomberBotProShielded: 0.48,
|
||||||
@ -1127,7 +1127,7 @@ class RunaroundGame(ba.CoopGameActivity):
|
|||||||
elif isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
elif isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
||||||
from bastd.actor import respawnicon
|
from bastd.actor import respawnicon
|
||||||
self._a_player_has_been_killed = True
|
self._a_player_has_been_killed = True
|
||||||
player = msg.spaz.getplayer()
|
player = msg.getspaz(self).getplayer()
|
||||||
if player is None:
|
if player is None:
|
||||||
ba.print_error('FIXME: getplayer() should no'
|
ba.print_error('FIXME: getplayer() should no'
|
||||||
' longer ever be returning None')
|
' longer ever be returning None')
|
||||||
|
|||||||
@ -38,7 +38,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
# ba_meta export game
|
# ba_meta export game
|
||||||
class TargetPracticeGame(ba.TeamGameActivity):
|
class TargetPracticeGame(ba.TeamGameActivity[ba.Player, ba.Team]):
|
||||||
"""Game where players try to hit targets with bombs."""
|
"""Game where players try to hit targets with bombs."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -187,7 +187,7 @@ class TargetPracticeGame(ba.TeamGameActivity):
|
|||||||
# When players die, respawn them.
|
# When players die, respawn them.
|
||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
||||||
super().handlemessage(msg) # Do standard stuff.
|
super().handlemessage(msg) # Do standard stuff.
|
||||||
player = msg.spaz.getplayer()
|
player = msg.getspaz(self).getplayer()
|
||||||
assert player is not None
|
assert player is not None
|
||||||
self.respawn_player(player) # Kick off a respawn.
|
self.respawn_player(player) # Kick off a respawn.
|
||||||
elif isinstance(msg, Target.TargetHitMessage):
|
elif isinstance(msg, Target.TargetHitMessage):
|
||||||
|
|||||||
@ -35,7 +35,7 @@ if TYPE_CHECKING:
|
|||||||
from bastd.actor.scoreboard import Scoreboard
|
from bastd.actor.scoreboard import Scoreboard
|
||||||
|
|
||||||
|
|
||||||
class TheLastStandGame(ba.CoopGameActivity):
|
class TheLastStandGame(ba.CoopGameActivity[ba.Player, ba.Team]):
|
||||||
"""Slow motion how-long-can-you-last game."""
|
"""Slow motion how-long-can-you-last game."""
|
||||||
|
|
||||||
tips = [
|
tips = [
|
||||||
@ -257,7 +257,7 @@ class TheLastStandGame(ba.CoopGameActivity):
|
|||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
|
||||||
player = msg.spaz.getplayer()
|
player = msg.getspaz(self).getplayer()
|
||||||
if player is None:
|
if player is None:
|
||||||
ba.print_error('FIXME: getplayer() should no longer '
|
ba.print_error('FIXME: getplayer() should no longer '
|
||||||
'ever be returning None.')
|
'ever be returning None.')
|
||||||
|
|||||||
@ -27,9 +27,9 @@ import time
|
|||||||
import weakref
|
import weakref
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import _ba
|
|
||||||
import ba
|
import ba
|
||||||
from bastd.actor import spaz
|
from bastd.actor import spaz
|
||||||
|
import _ba
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
@ -43,7 +43,7 @@ if TYPE_CHECKING:
|
|||||||
# noinspection PyAttributeOutsideInit
|
# noinspection PyAttributeOutsideInit
|
||||||
|
|
||||||
|
|
||||||
class MainMenuActivity(ba.Activity):
|
class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
|
||||||
"""Activity showing the rotating main menu bg stuff."""
|
"""Activity showing the rotating main menu bg stuff."""
|
||||||
|
|
||||||
_stdassets = ba.Dependency(ba.AssetPackage, 'stdassets@1')
|
_stdassets = ba.Dependency(ba.AssetPackage, 'stdassets@1')
|
||||||
@ -358,7 +358,7 @@ class MainMenuActivity(ba.Activity):
|
|||||||
# need to make nodes and stuff.. should fix the serverget
|
# need to make nodes and stuff.. should fix the serverget
|
||||||
# call so it.
|
# call so it.
|
||||||
activity = self._activity()
|
activity = self._activity()
|
||||||
if activity is None or activity.is_expired():
|
if activity is None or activity.expired:
|
||||||
return
|
return
|
||||||
with ba.Context(activity):
|
with ba.Context(activity):
|
||||||
|
|
||||||
@ -830,7 +830,7 @@ class MainMenuActivity(ba.Activity):
|
|||||||
def _start_preloads(self) -> None:
|
def _start_preloads(self) -> None:
|
||||||
# FIXME: The func that calls us back doesn't save/restore state
|
# FIXME: The func that calls us back doesn't save/restore state
|
||||||
# or check for a dead activity so we have to do that ourself.
|
# or check for a dead activity so we have to do that ourself.
|
||||||
if self.is_expired():
|
if self.expired:
|
||||||
return
|
return
|
||||||
with ba.Context(self):
|
with ba.Context(self):
|
||||||
_preload1()
|
_preload1()
|
||||||
@ -929,6 +929,6 @@ class MainMenuSession(ba.Session):
|
|||||||
# Any ending activity leads us into the main menu one.
|
# Any ending activity leads us into the main menu one.
|
||||||
self.set_activity(ba.new_activity(MainMenuActivity))
|
self.set_activity(ba.new_activity(MainMenuActivity))
|
||||||
|
|
||||||
def on_player_request(self, player: ba.Player) -> bool:
|
def on_player_request(self, player: ba.SessionPlayer) -> bool:
|
||||||
# Reject all player requests.
|
# Reject all player requests.
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -181,7 +181,7 @@ class ButtonRelease:
|
|||||||
timeformat=ba.TimeFormat.MILLISECONDS)
|
timeformat=ba.TimeFormat.MILLISECONDS)
|
||||||
|
|
||||||
|
|
||||||
class TutorialActivity(ba.Activity):
|
class TutorialActivity(ba.Activity[ba.Player, ba.Team]):
|
||||||
|
|
||||||
def __init__(self, settings: Dict[str, Any] = None):
|
def __init__(self, settings: Dict[str, Any] = None):
|
||||||
from bastd.maps import Rampage
|
from bastd.maps import Rampage
|
||||||
|
|||||||
@ -31,22 +31,19 @@ from pathlib import Path
|
|||||||
from threading import Lock, Thread, current_thread
|
from threading import Lock, Thread, current_thread
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
# We make use of the bacommon and efro packages as well as site-packages
|
||||||
|
# included with our bundled Ballistica dist, so we need to add those paths
|
||||||
|
# before we import them.
|
||||||
|
sys.path += [
|
||||||
|
str(Path(Path(__file__).parent, 'dist', 'ba_data', 'python')),
|
||||||
|
str(Path(Path(__file__).parent, 'dist', 'ba_data', 'python-site-packages'))
|
||||||
|
]
|
||||||
|
|
||||||
from bacommon.servermanager import ServerConfig, StartServerModeCommand
|
from bacommon.servermanager import ServerConfig, StartServerModeCommand
|
||||||
from efro.dataclasses import dataclass_assign, dataclass_validate
|
from efro.dataclasses import dataclass_assign, dataclass_validate
|
||||||
from efro.error import CleanError
|
from efro.error import CleanError
|
||||||
from efro.terminal import Clr
|
from efro.terminal import Clr
|
||||||
|
|
||||||
# We change our working directory according to file's path
|
|
||||||
# so that the script can be properly executed from anywhere
|
|
||||||
os.chdir(os.path.abspath(os.path.dirname(__file__)))
|
|
||||||
|
|
||||||
# We make use of the bacommon and efro packages as well as site-packages
|
|
||||||
# included with our bundled Ballistica dist.
|
|
||||||
sys.path += [
|
|
||||||
str(Path(os.getcwd(), 'dist', 'ba_data', 'python')),
|
|
||||||
str(Path(os.getcwd(), 'dist', 'ba_data', 'python-site-packages'))
|
|
||||||
]
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Optional, List, Dict, Union, Tuple
|
from typing import Optional, List, Dict, Union, Tuple
|
||||||
from types import FrameType
|
from types import FrameType
|
||||||
@ -54,7 +51,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
# Not sure how much versioning we'll do with this, but this will get
|
# Not sure how much versioning we'll do with this, but this will get
|
||||||
# printed at startup in case we need it.
|
# printed at startup in case we need it.
|
||||||
VERSION_STR = '1.0'
|
VERSION_STR = '1.0.1'
|
||||||
|
|
||||||
|
|
||||||
class ServerManagerApp:
|
class ServerManagerApp:
|
||||||
@ -432,6 +429,10 @@ class ServerManagerApp:
|
|||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Run a BallisticaCore server manager in interactive mode."""
|
"""Run a BallisticaCore server manager in interactive mode."""
|
||||||
try:
|
try:
|
||||||
|
# Change our working directory according to file's path
|
||||||
|
# so that this script can be run from anywhere.
|
||||||
|
os.chdir(os.path.abspath(os.path.dirname(__file__)))
|
||||||
|
|
||||||
ServerManagerApp().run_interactive()
|
ServerManagerApp().run_interactive()
|
||||||
except CleanError as exc:
|
except CleanError as exc:
|
||||||
# For clean errors, do a simple print and fail; no tracebacks/etc.
|
# For clean errors, do a simple print and fail; no tracebacks/etc.
|
||||||
|
|||||||
@ -3,6 +3,21 @@ mypy_path = __EFRO_PROJECT_ROOT__/tools:__EFRO_PROJECT_ROOT__/assets/src/ba_data
|
|||||||
|
|
||||||
__EFRO_MYPY_STANDARD_SETTINGS__
|
__EFRO_MYPY_STANDARD_SETTINGS__
|
||||||
|
|
||||||
|
# We have mypy alert us if we use any vars that have been imported
|
||||||
|
# by other modules; we want to import everything directly from its
|
||||||
|
# source. However there are some modules that are explicitly exist
|
||||||
|
# to reexport things, so let's let those pass.
|
||||||
|
# (we could also set __all__ in those modules, but that's a lot of
|
||||||
|
# repeating ourself)
|
||||||
|
[mypy-ba]
|
||||||
|
no_implicit_reexport = False
|
||||||
|
[mypy-efro.entity]
|
||||||
|
no_implicit_reexport = False
|
||||||
|
[mypy-ba.internal]
|
||||||
|
no_implicit_reexport = False
|
||||||
|
[mypy-ba.deprecated]
|
||||||
|
no_implicit_reexport = False
|
||||||
|
|
||||||
[mypy-pylint.*]
|
[mypy-pylint.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
|||||||
@ -79,6 +79,7 @@ good-names=i,
|
|||||||
v2,
|
v2,
|
||||||
ex,
|
ex,
|
||||||
Run,
|
Run,
|
||||||
|
id,
|
||||||
T,
|
T,
|
||||||
S,
|
S,
|
||||||
U,
|
U,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -237,7 +237,7 @@ class App:
|
|||||||
with open(self._state_data_path, 'r') as infile:
|
with open(self._state_data_path, 'r') as infile:
|
||||||
self._state = StateData(**json.loads(infile.read()))
|
self._state = StateData(**json.loads(infile.read()))
|
||||||
except Exception:
|
except Exception:
|
||||||
print(f'{Clr.SRED}Error loading {TOOL_NAME} data;'
|
print(f'{Clr.RED}Error loading {TOOL_NAME} data;'
|
||||||
f' resetting to defaults.{Clr.RST}')
|
f' resetting to defaults.{Clr.RST}')
|
||||||
|
|
||||||
def _save_state(self) -> None:
|
def _save_state(self) -> None:
|
||||||
@ -285,7 +285,7 @@ class App:
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
def _upload_file(self, filename: str, call: str, args: Dict) -> None:
|
def _upload_file(self, filename: str, call: str, args: Dict) -> None:
|
||||||
print(f'{Clr.SBLU}Uploading {filename}{Clr.RST}', flush=True)
|
print(f'{Clr.BLU}Uploading {filename}{Clr.RST}', flush=True)
|
||||||
with tempfile.TemporaryDirectory() as tempdir:
|
with tempfile.TemporaryDirectory() as tempdir:
|
||||||
srcpath = Path(filename)
|
srcpath = Path(filename)
|
||||||
gzpath = Path(tempdir, 'file.gz')
|
gzpath = Path(tempdir, 'file.gz')
|
||||||
|
|||||||
@ -112,7 +112,7 @@ def _lazybuild_check_paths(inpaths: List[str], category: SourceCategory,
|
|||||||
# Now see this path is newer than our target..
|
# Now see this path is newer than our target..
|
||||||
if mtime is None or os.path.getmtime(path) >= mtime:
|
if mtime is None or os.path.getmtime(path) >= mtime:
|
||||||
print(f'{Clr.SMAG}Build of {tnamepretty} triggered by'
|
print(f'{Clr.SMAG}Build of {tnamepretty} triggered by'
|
||||||
f' {path}{Clr.RST}')
|
f" '{path}'{Clr.RST}")
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -483,7 +483,7 @@ def _vstr(nums: Sequence[int]) -> str:
|
|||||||
def checkenv() -> None:
|
def checkenv() -> None:
|
||||||
"""Check for tools necessary to build and run the app."""
|
"""Check for tools necessary to build and run the app."""
|
||||||
from efrotools import PYTHON_BIN
|
from efrotools import PYTHON_BIN
|
||||||
print('Checking environment...', flush=True)
|
print(f'{Clr.BLD}Checking environment...{Clr.RST}', flush=True)
|
||||||
|
|
||||||
# Make sure they've got curl.
|
# Make sure they've got curl.
|
||||||
if subprocess.run(['which', 'curl'], check=False,
|
if subprocess.run(['which', 'curl'], check=False,
|
||||||
@ -539,7 +539,7 @@ def checkenv() -> None:
|
|||||||
'Alternately, "tools/snippets install_pip_reqs"'
|
'Alternately, "tools/snippets install_pip_reqs"'
|
||||||
' will update all pip requirements.')
|
' will update all pip requirements.')
|
||||||
|
|
||||||
print('Environment ok.', flush=True)
|
print(f'{Clr.BLD}Environment ok.{Clr.RST}', flush=True)
|
||||||
|
|
||||||
|
|
||||||
def get_pip_reqs() -> List[str]:
|
def get_pip_reqs() -> List[str]:
|
||||||
|
|||||||
@ -26,9 +26,9 @@ import copy
|
|||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Generic, TypeVar, overload
|
from typing import TYPE_CHECKING, Generic, TypeVar, overload
|
||||||
|
|
||||||
from efro.entity._support import (BaseField, BoundCompoundValue,
|
from efro.entity._base import BaseField
|
||||||
BoundListField, BoundDictField,
|
from efro.entity._support import (BoundCompoundValue, BoundListField,
|
||||||
BoundCompoundListField,
|
BoundDictField, BoundCompoundListField,
|
||||||
BoundCompoundDictField)
|
BoundCompoundDictField)
|
||||||
from efro.entity.util import have_matching_fields
|
from efro.entity.util import have_matching_fields
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,9 @@ import inspect
|
|||||||
import logging
|
import logging
|
||||||
from collections import abc
|
from collections import abc
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import TYPE_CHECKING, TypeVar, Tuple, Optional, Generic
|
from typing import TYPE_CHECKING, TypeVar, Generic
|
||||||
|
# Our Pylint class_generics_filter gives us a false-positive unused-import.
|
||||||
|
from typing import Tuple, Optional # pylint: disable=W0611
|
||||||
|
|
||||||
from efro.entity._base import DataHandler, BaseField
|
from efro.entity._base import DataHandler, BaseField
|
||||||
from efro.entity.util import compound_eq
|
from efro.entity.util import compound_eq
|
||||||
|
|||||||
@ -24,17 +24,24 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
import weakref
|
||||||
from typing import TYPE_CHECKING, cast, TypeVar, Generic
|
from typing import TYPE_CHECKING, cast, TypeVar, Generic
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any, Dict, Callable, Optional
|
from weakref import ReferenceType
|
||||||
|
from typing import Any, Dict, Callable, Optional, Type
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
TVAL = TypeVar('TVAL')
|
TVAL = TypeVar('TVAL')
|
||||||
TARG = TypeVar('TARG')
|
TARG = TypeVar('TARG')
|
||||||
TRET = TypeVar('TRET')
|
TRET = TypeVar('TRET')
|
||||||
|
|
||||||
|
|
||||||
|
class _EmptyObj:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def utc_now() -> datetime.datetime:
|
def utc_now() -> datetime.datetime:
|
||||||
"""Get offset-aware current utc time.
|
"""Get offset-aware current utc time.
|
||||||
|
|
||||||
@ -46,6 +53,15 @@ def utc_now() -> datetime.datetime:
|
|||||||
return datetime.datetime.now(datetime.timezone.utc)
|
return datetime.datetime.now(datetime.timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def empty_weakref(objtype: Type[T]) -> ReferenceType[T]:
|
||||||
|
"""Return an invalidated weak-reference for the specified type."""
|
||||||
|
# At runtime, all weakrefs are the same; our type arg is just
|
||||||
|
# for the static type checker.
|
||||||
|
del objtype # Unused.
|
||||||
|
# Just create an object and let it die. Is there a cleaner way to do this?
|
||||||
|
return weakref.ref(_EmptyObj()) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def data_size_str(bytecount: int) -> str:
|
def data_size_str(bytecount: int) -> str:
|
||||||
"""Given a size in bytes, returns a short human readable string.
|
"""Given a size in bytes, returns a short human readable string.
|
||||||
|
|
||||||
|
|||||||
@ -187,7 +187,7 @@ def _py_symbol_at_column(line: str, col: int) -> str:
|
|||||||
return line[start:end]
|
return line[start:end]
|
||||||
|
|
||||||
|
|
||||||
def py_examine(filename: Path, line: int, column: int,
|
def py_examine(projroot: Path, filename: Path, line: int, column: int,
|
||||||
selection: Optional[str], operation: str) -> None:
|
selection: Optional[str], operation: str) -> None:
|
||||||
"""Given file position info, performs some code inspection."""
|
"""Given file position info, performs some code inspection."""
|
||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
@ -242,7 +242,7 @@ def py_examine(filename: Path, line: int, column: int,
|
|||||||
with tmppath.open('w') as outfile:
|
with tmppath.open('w') as outfile:
|
||||||
outfile.write('\n'.join(flines))
|
outfile.write('\n'.join(flines))
|
||||||
try:
|
try:
|
||||||
code.runmypy([str(tmppath)], check=False)
|
code.runmypy(projroot, [str(tmppath)], check=False)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print('error running mypy:', exc)
|
print('error running mypy:', exc)
|
||||||
tmppath.unlink()
|
tmppath.unlink()
|
||||||
|
|||||||
@ -38,8 +38,8 @@ def formatcode(projroot: Path, full: bool) -> None:
|
|||||||
"""Run clang-format on all of our source code (multithreaded)."""
|
"""Run clang-format on all of our source code (multithreaded)."""
|
||||||
import time
|
import time
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from efrotools import get_files_hash
|
|
||||||
from multiprocessing import cpu_count
|
from multiprocessing import cpu_count
|
||||||
|
from efrotools import get_files_hash
|
||||||
os.chdir(projroot)
|
os.chdir(projroot)
|
||||||
cachepath = Path(projroot, 'config/.cache-formatcode')
|
cachepath = Path(projroot, 'config/.cache-formatcode')
|
||||||
if full and cachepath.exists():
|
if full and cachepath.exists():
|
||||||
@ -66,13 +66,8 @@ def formatcode(projroot: Path, full: bool) -> None:
|
|||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
return {'f': filename, 't': duration}
|
return {'f': filename, 't': duration}
|
||||||
|
|
||||||
# NOTE: using fewer workers than we have logical procs for now;
|
with concurrent.futures.ThreadPoolExecutor(
|
||||||
# we're bottlenecked by one or two long running instances
|
max_workers=cpu_count()) as executor:
|
||||||
# so it actually helps to lighten the load around them.
|
|
||||||
# may want to revisit later when we have everything chopped up
|
|
||||||
# better
|
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=cpu_count() //
|
|
||||||
2) as executor:
|
|
||||||
# Converting this to a list will propagate any errors.
|
# Converting this to a list will propagate any errors.
|
||||||
list(executor.map(format_file, dirtyfiles))
|
list(executor.map(format_file, dirtyfiles))
|
||||||
|
|
||||||
@ -88,8 +83,9 @@ def formatcode(projroot: Path, full: bool) -> None:
|
|||||||
def cpplint(projroot: Path, full: bool) -> None:
|
def cpplint(projroot: Path, full: bool) -> None:
|
||||||
"""Run lint-checking on all code deemed lint-able."""
|
"""Run lint-checking on all code deemed lint-able."""
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from efrotools import get_config
|
|
||||||
from multiprocessing import cpu_count
|
from multiprocessing import cpu_count
|
||||||
|
from efrotools import get_config
|
||||||
|
from efro.terminal import Clr
|
||||||
|
|
||||||
os.chdir(projroot)
|
os.chdir(projroot)
|
||||||
filenames = get_code_filenames(projroot)
|
filenames = get_code_filenames(projroot)
|
||||||
@ -115,21 +111,24 @@ def cpplint(projroot: Path, full: bool) -> None:
|
|||||||
dirtyfiles = cache.get_dirty_files()
|
dirtyfiles = cache.get_dirty_files()
|
||||||
|
|
||||||
if dirtyfiles:
|
if dirtyfiles:
|
||||||
print(f'CppLint checking {len(dirtyfiles)} file(s)...')
|
print(f'{Clr.BLU}CppLint checking'
|
||||||
|
f' {len(dirtyfiles)} file(s)...{Clr.RST}')
|
||||||
|
|
||||||
def lint_file(filename: str) -> None:
|
def lint_file(filename: str) -> None:
|
||||||
result = subprocess.call(['cpplint', '--root=src', filename])
|
result = subprocess.call(['cpplint', '--root=src', filename])
|
||||||
if result != 0:
|
if result != 0:
|
||||||
raise Exception(f'Linting failed for {filename}')
|
raise Exception(f'Linting failed for {filename}')
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=cpu_count() // 2) as executor:
|
with ThreadPoolExecutor(max_workers=cpu_count()) as executor:
|
||||||
# Converting this to a list will propagate any errors.
|
# Converting this to a list will propagate any errors.
|
||||||
list(executor.map(lint_file, dirtyfiles))
|
list(executor.map(lint_file, dirtyfiles))
|
||||||
|
|
||||||
if dirtyfiles:
|
if dirtyfiles:
|
||||||
cache.mark_clean(filenames)
|
cache.mark_clean(filenames)
|
||||||
cache.write()
|
cache.write()
|
||||||
print(f'CppLint: all {len(filenames)} files are passing.', flush=True)
|
print(
|
||||||
|
f'{Clr.GRN}CppLint: all {len(filenames)} files are passing.{Clr.RST}',
|
||||||
|
flush=True)
|
||||||
|
|
||||||
|
|
||||||
def get_code_filenames(projroot: Path) -> List[str]:
|
def get_code_filenames(projroot: Path) -> List[str]:
|
||||||
@ -153,8 +152,8 @@ def formatscripts(projroot: Path, full: bool) -> None:
|
|||||||
"""Runs yapf on all our scripts (multithreaded)."""
|
"""Runs yapf on all our scripts (multithreaded)."""
|
||||||
import time
|
import time
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from efrotools import get_files_hash
|
|
||||||
from multiprocessing import cpu_count
|
from multiprocessing import cpu_count
|
||||||
|
from efrotools import get_files_hash
|
||||||
os.chdir(projroot)
|
os.chdir(projroot)
|
||||||
cachepath = Path(projroot, 'config/.cache-formatscripts')
|
cachepath = Path(projroot, 'config/.cache-formatscripts')
|
||||||
if full and cachepath.exists():
|
if full and cachepath.exists():
|
||||||
@ -178,12 +177,7 @@ def formatscripts(projroot: Path, full: bool) -> None:
|
|||||||
print(f'Formatted {filename} in {duration:.2f} seconds.')
|
print(f'Formatted {filename} in {duration:.2f} seconds.')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
# NOTE: using fewer workers than we have logical procs for now;
|
with ThreadPoolExecutor(max_workers=cpu_count()) as executor:
|
||||||
# we're bottlenecked by one or two long running instances
|
|
||||||
# so it actually helps to lighten the load around them.
|
|
||||||
# may want to revisit later when we have everything chopped up
|
|
||||||
# better
|
|
||||||
with ThreadPoolExecutor(max_workers=cpu_count() // 2) as executor:
|
|
||||||
# Convert the futures to a list to propagate any errors even
|
# Convert the futures to a list to propagate any errors even
|
||||||
# though there are no return values we use.
|
# though there are no return values we use.
|
||||||
list(executor.map(format_file, dirtyfiles))
|
list(executor.map(format_file, dirtyfiles))
|
||||||
@ -235,9 +229,27 @@ def get_script_filenames(projroot: Path) -> List[str]:
|
|||||||
return sorted(list(f for f in filenames if 'flycheck_' not in f))
|
return sorted(list(f for f in filenames if 'flycheck_' not in f))
|
||||||
|
|
||||||
|
|
||||||
|
def runpylint(projroot: Path, filenames: List[str]) -> None:
|
||||||
|
"""Run Pylint explicitly on files."""
|
||||||
|
|
||||||
|
pylintrc = Path(projroot, '.pylintrc')
|
||||||
|
if not os.path.isfile(pylintrc):
|
||||||
|
raise Exception('pylintrc not found where expected')
|
||||||
|
|
||||||
|
# Technically we could just run pylint standalone via command line here,
|
||||||
|
# but let's go ahead and run it inline so we're consistent with our cached
|
||||||
|
# full-project version.
|
||||||
|
_run_pylint(projroot,
|
||||||
|
pylintrc,
|
||||||
|
cache=None,
|
||||||
|
dirtyfiles=filenames,
|
||||||
|
allfiles=None)
|
||||||
|
|
||||||
|
|
||||||
def pylint(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."""
|
"""Run Pylint on all scripts in our project (with smart dep tracking)."""
|
||||||
from efrotools import get_files_hash
|
from efrotools import get_files_hash
|
||||||
|
from efro.terminal import Clr
|
||||||
pylintrc = Path(projroot, '.pylintrc')
|
pylintrc = Path(projroot, '.pylintrc')
|
||||||
if not os.path.isfile(pylintrc):
|
if not os.path.isfile(pylintrc):
|
||||||
raise Exception('pylintrc not found where expected')
|
raise Exception('pylintrc not found where expected')
|
||||||
@ -270,22 +282,17 @@ def pylint(projroot: Path, full: bool, fast: bool) -> None:
|
|||||||
dirtyfiles.sort(reverse=True, key=lambda f: os.stat(f).st_mtime)
|
dirtyfiles.sort(reverse=True, key=lambda f: os.stat(f).st_mtime)
|
||||||
|
|
||||||
if dirtyfiles:
|
if dirtyfiles:
|
||||||
print(f'Pylint checking {len(dirtyfiles)} file(s)...', flush=True)
|
print(
|
||||||
|
f'{Clr.BLU}Pylint checking {len(dirtyfiles)} file(s)...{Clr.RST}',
|
||||||
|
flush=True)
|
||||||
try:
|
try:
|
||||||
_run_script_lint(projroot, pylintrc, cache, dirtyfiles, filenames)
|
_run_pylint(projroot, pylintrc, cache, dirtyfiles, filenames)
|
||||||
except Exception:
|
finally:
|
||||||
# Note: even if we fail here, we still want to
|
# No matter what happens, we still want to
|
||||||
# update our disk cache (since some lints may have passed).
|
# update our disk cache (since some lints may have passed).
|
||||||
print('Pylint failed.', flush=True)
|
|
||||||
|
|
||||||
# Hmm; this can be handy sometimes; perhaps should add an env
|
|
||||||
# var to control it?
|
|
||||||
if bool(False):
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
cache.write()
|
cache.write()
|
||||||
sys.exit(255)
|
print(f'{Clr.GRN}Pylint: all {len(filenames)} files are passing.{Clr.RST}',
|
||||||
print(f'Pylint: all {len(filenames)} files are passing.', flush=True)
|
flush=True)
|
||||||
|
|
||||||
cache.write()
|
cache.write()
|
||||||
|
|
||||||
@ -349,32 +356,39 @@ def _dirty_dep_check(fname: str, filestates: Dict[str, bool], cache: FileCache,
|
|||||||
return dirty
|
return dirty
|
||||||
|
|
||||||
|
|
||||||
def _run_script_lint(projroot: Path, pylintrc: Union[Path, str],
|
def _run_pylint(projroot: Path, pylintrc: Union[Path, str],
|
||||||
cache: FileCache, dirtyfiles: List[str],
|
cache: Optional[FileCache], dirtyfiles: List[str],
|
||||||
allfiles: List[str]) -> Dict[str, Any]:
|
allfiles: Optional[List[str]]) -> Dict[str, Any]:
|
||||||
import time
|
import time
|
||||||
from pylint import lint
|
from pylint import lint
|
||||||
|
from efro.error import CleanError
|
||||||
|
from efro.terminal import Clr
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
args = ['--rcfile', str(pylintrc)]
|
args = ['--rcfile', str(pylintrc), '--output-format=colorized']
|
||||||
|
|
||||||
args += dirtyfiles
|
args += dirtyfiles
|
||||||
name = f'{len(dirtyfiles)} file(s)'
|
name = f'{len(dirtyfiles)} file(s)'
|
||||||
run = lint.Run(args, do_exit=False)
|
run = lint.Run(args, do_exit=False)
|
||||||
result = _apply_pylint_run_to_cache(projroot, run, dirtyfiles, allfiles,
|
if cache is not None:
|
||||||
cache)
|
assert allfiles is not None
|
||||||
if result != 0:
|
result = _apply_pylint_run_to_cache(projroot, run, dirtyfiles,
|
||||||
raise Exception(f'Linting failed for {result} file(s).')
|
allfiles, cache)
|
||||||
|
if result != 0:
|
||||||
|
raise CleanError(f'Linting failed for {result} file(s).')
|
||||||
|
|
||||||
|
# Sanity check: when the linter fails we should always be failing too.
|
||||||
|
# If not, it means we're probably missing something and incorrectly
|
||||||
|
# marking a failed file as clean.
|
||||||
|
if run.linter.msg_status != 0 and result == 0:
|
||||||
|
raise RuntimeError('Pylint linter returned non-zero result'
|
||||||
|
' but we did not; this is probably a bug.')
|
||||||
|
else:
|
||||||
|
if run.linter.msg_status != 0:
|
||||||
|
raise CleanError('Pylint failed.')
|
||||||
|
|
||||||
# Sanity check: when the linter fails we should always be failing too.
|
|
||||||
# If not, it means we're probably missing something and incorrectly
|
|
||||||
# marking a failed file as clean.
|
|
||||||
if run.linter.msg_status != 0 and result == 0:
|
|
||||||
raise Exception('linter returned non-zero result but we did not;'
|
|
||||||
' this is probably a bug.')
|
|
||||||
# result = run.linter.msg_status
|
|
||||||
# we can run
|
|
||||||
duration = time.time() - start_time
|
duration = time.time() - start_time
|
||||||
print(f'Pylint passed for {name} in {duration:.1f} seconds.')
|
print(f'{Clr.GRN}Pylint passed for {name}'
|
||||||
|
f' in {duration:.1f} seconds.{Clr.RST}')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
return {'f': dirtyfiles, 't': duration}
|
return {'f': dirtyfiles, 't': duration}
|
||||||
|
|
||||||
@ -415,7 +429,7 @@ def _apply_pylint_run_to_cache(projroot: Path, run: Any, dirtyfiles: List[str],
|
|||||||
|
|
||||||
# Update dependencies for what we just ran.
|
# Update dependencies for what we just ran.
|
||||||
# A run leaves us with a map of modules to a list of the modules that
|
# A run leaves us with a map of modules to a list of the modules that
|
||||||
# imports them. We want the opposite though: for each of our modules
|
# imports them. We want the opposite though: for each of our modules
|
||||||
# we want a list of the modules it imports.
|
# we want a list of the modules it imports.
|
||||||
reversedeps = {}
|
reversedeps = {}
|
||||||
|
|
||||||
@ -453,10 +467,8 @@ def _apply_pylint_run_to_cache(projroot: Path, run: Any, dirtyfiles: List[str],
|
|||||||
for fname in dirtyfiles:
|
for fname in dirtyfiles:
|
||||||
fmod = paths_to_names[fname]
|
fmod = paths_to_names[fname]
|
||||||
if fmod not in deps:
|
if fmod not in deps:
|
||||||
|
# Since this code is a bit flaky, lets always announce when we
|
||||||
# Since this code is a bit flaky, lets always announce when
|
# come up empty and keep a whitelist of expected values to ignore.
|
||||||
# we come up empty and keep a whitelist of expected values to
|
|
||||||
# ignore.
|
|
||||||
no_deps_modules.add(fmod)
|
no_deps_modules.add(fmod)
|
||||||
depsval: List[str] = []
|
depsval: List[str] = []
|
||||||
else:
|
else:
|
||||||
@ -512,14 +524,16 @@ def _filter_module_name(mpath: str) -> str:
|
|||||||
return mpath[:-9] if mpath.endswith('.__init__') else mpath
|
return mpath[:-9] if mpath.endswith('.__init__') else mpath
|
||||||
|
|
||||||
|
|
||||||
def runmypy(filenames: List[str],
|
def runmypy(projroot: Path,
|
||||||
|
filenames: List[str],
|
||||||
full: bool = False,
|
full: bool = False,
|
||||||
check: bool = True) -> None:
|
check: bool = True) -> None:
|
||||||
"""Run MyPy on provided filenames."""
|
"""Run MyPy on provided filenames."""
|
||||||
from efrotools import PYTHON_BIN
|
from efrotools import PYTHON_BIN
|
||||||
args = [
|
args = [
|
||||||
PYTHON_BIN, '-m', 'mypy', '--pretty', '--no-error-summary',
|
PYTHON_BIN, '-m', 'mypy', '--pretty', '--no-error-summary',
|
||||||
'--config-file', '.mypy.ini'
|
'--config-file',
|
||||||
|
str(Path(projroot, '.mypy.ini'))
|
||||||
] + filenames
|
] + filenames
|
||||||
if full:
|
if full:
|
||||||
args.insert(args.index('mypy') + 1, '--no-incremental')
|
args.insert(args.index('mypy') + 1, '--no-incremental')
|
||||||
@ -529,22 +543,26 @@ def runmypy(filenames: List[str],
|
|||||||
def mypy(projroot: Path, full: bool) -> None:
|
def mypy(projroot: Path, full: bool) -> None:
|
||||||
"""Type check all of our scripts using mypy."""
|
"""Type check all of our scripts using mypy."""
|
||||||
import time
|
import time
|
||||||
|
from efro.terminal import Clr
|
||||||
|
from efro.error import CleanError
|
||||||
filenames = get_script_filenames(projroot)
|
filenames = get_script_filenames(projroot)
|
||||||
print('Running Mypy ' + ('(full)' if full else '(incremental)') + '...',
|
desc = '(full)' if full else '(incremental)'
|
||||||
flush=True)
|
print(f'{Clr.BLU}Running Mypy {desc}...{Clr.RST}', flush=True)
|
||||||
starttime = time.time()
|
starttime = time.time()
|
||||||
try:
|
try:
|
||||||
runmypy(filenames, full)
|
runmypy(projroot, filenames, full)
|
||||||
except Exception:
|
except Exception:
|
||||||
print('Mypy: fail.')
|
raise CleanError('Mypy: fail.')
|
||||||
sys.exit(255)
|
|
||||||
duration = time.time() - starttime
|
duration = time.time() - starttime
|
||||||
print(f'Mypy passed in {duration:.1f} seconds.', flush=True)
|
print(f'{Clr.GRN}Mypy passed in {duration:.1f} seconds.{Clr.RST}',
|
||||||
|
flush=True)
|
||||||
|
|
||||||
|
|
||||||
def dmypy(projroot: Path) -> None:
|
def dmypy(projroot: Path) -> None:
|
||||||
"""Type check all of our scripts using mypy in daemon mode."""
|
"""Type check all of our scripts using mypy in daemon mode."""
|
||||||
import time
|
import time
|
||||||
|
from efro.terminal import Clr
|
||||||
|
from efro.error import CleanError
|
||||||
filenames = get_script_filenames(projroot)
|
filenames = get_script_filenames(projroot)
|
||||||
|
|
||||||
# Special case; explicitly kill the daemon.
|
# Special case; explicitly kill the daemon.
|
||||||
@ -561,10 +579,10 @@ def dmypy(projroot: Path) -> None:
|
|||||||
] + filenames
|
] + filenames
|
||||||
subprocess.run(args, check=True)
|
subprocess.run(args, check=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
print('Mypy: fail.')
|
raise CleanError('Mypy daemon: fail.')
|
||||||
sys.exit(255)
|
|
||||||
duration = time.time() - starttime
|
duration = time.time() - starttime
|
||||||
print(f'Mypy daemon passed in {duration:.1f} seconds.', flush=True)
|
print(f'{Clr.GRN}Mypy daemon passed in {duration:.1f} seconds.{Clr.RST}',
|
||||||
|
flush=True)
|
||||||
|
|
||||||
|
|
||||||
def _parse_idea_results(path: Path) -> int:
|
def _parse_idea_results(path: Path) -> int:
|
||||||
@ -615,8 +633,13 @@ def _run_idea_inspections(projroot: Path,
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import datetime
|
import datetime
|
||||||
|
from efro.error import CleanError
|
||||||
|
from efro.terminal import Clr
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
print(f'{displayname} checking', len(scripts), 'file(s)...', flush=True)
|
print(
|
||||||
|
f'{Clr.BLU}{displayname} checking'
|
||||||
|
f' {len(scripts)} file(s)...{Clr.RST}',
|
||||||
|
flush=True)
|
||||||
tmpdir = tempfile.TemporaryDirectory()
|
tmpdir = tempfile.TemporaryDirectory()
|
||||||
iprof = Path(projroot, '.idea/inspectionProfiles/Default.xml')
|
iprof = Path(projroot, '.idea/inspectionProfiles/Default.xml')
|
||||||
if not iprof.exists():
|
if not iprof.exists():
|
||||||
@ -659,13 +682,12 @@ def _run_idea_inspections(projroot: Path,
|
|||||||
for fname in files:
|
for fname in files:
|
||||||
total_errors += _parse_idea_results(Path(tmpdir.name, fname))
|
total_errors += _parse_idea_results(Path(tmpdir.name, fname))
|
||||||
if total_errors > 0:
|
if total_errors > 0:
|
||||||
raise RuntimeError(
|
raise CleanError(f'{Clr.SRED}{displayname} inspection'
|
||||||
f'{displayname} inspection found {total_errors} error(s).')
|
f' found {total_errors} error(s).{Clr.RST}')
|
||||||
duration = time.time() - start_time
|
duration = time.time() - start_time
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f'{displayname} passed for {len(scripts)} files'
|
f'{Clr.GRN}{displayname} passed for {len(scripts)} files'
|
||||||
f' in {duration:.1f} seconds.',
|
f' in {duration:.1f} seconds.{Clr.RST}',
|
||||||
flush=True)
|
flush=True)
|
||||||
|
|
||||||
|
|
||||||
@ -680,6 +702,7 @@ def _run_idea_inspections_cached(cachepath: Path,
|
|||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
from efro.terminal import Clr
|
||||||
md5 = hashlib.md5()
|
md5 = hashlib.md5()
|
||||||
|
|
||||||
# Let's calc a single hash from the contents of all script files and only
|
# Let's calc a single hash from the contents of all script files and only
|
||||||
@ -717,8 +740,10 @@ def _run_idea_inspections_cached(cachepath: Path,
|
|||||||
inspectdir=inspectdir)
|
inspectdir=inspectdir)
|
||||||
with open(cachepath, 'w') as outfile:
|
with open(cachepath, 'w') as outfile:
|
||||||
outfile.write(json.dumps({'hash': current_hash}))
|
outfile.write(json.dumps({'hash': current_hash}))
|
||||||
print(f'{displayname}: all {len(filenames)} files are passing.',
|
print(
|
||||||
flush=True)
|
f'{Clr.GRN}{displayname}: all {len(filenames)}'
|
||||||
|
f' files are passing.{Clr.RST}',
|
||||||
|
flush=True)
|
||||||
|
|
||||||
|
|
||||||
def pycharm(projroot: Path, full: bool, verbose: bool) -> None:
|
def pycharm(projroot: Path, full: bool, verbose: bool) -> None:
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import astroid
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from astroid import node_classes as nc
|
from astroid import node_classes as nc
|
||||||
from typing import Set, Dict, Any
|
from typing import Set, Dict, Any, List
|
||||||
|
|
||||||
VERBOSE = False
|
VERBOSE = False
|
||||||
|
|
||||||
@ -199,9 +199,49 @@ def var_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
# Stripping subscripts on some generics seems to cause
|
||||||
|
# more harm than good, so we leave some intact.
|
||||||
|
ALLOWED_GENERICS = {'Sequence'}
|
||||||
|
|
||||||
|
|
||||||
|
def _is_strippable_subscript(node: nc.NodeNG) -> bool:
|
||||||
|
if isinstance(node, astroid.Subscript):
|
||||||
|
# We can strip if its not in our allowed list.
|
||||||
|
if not (isinstance(node.value, astroid.Name)
|
||||||
|
and node.value.name in ALLOWED_GENERICS):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def class_generics_filter(node: nc.NodeNG) -> nc.NodeNG:
|
||||||
|
"""Filter generics subscripts out of class declarations."""
|
||||||
|
|
||||||
|
# First, quick-out if nothing here should be filtered.
|
||||||
|
found = False
|
||||||
|
for base in node.bases:
|
||||||
|
if _is_strippable_subscript(base):
|
||||||
|
found = True
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
return node
|
||||||
|
|
||||||
|
# Now strip subscripts from base classes.
|
||||||
|
new_bases: List[nc.NodeNG] = []
|
||||||
|
for base in node.bases:
|
||||||
|
if _is_strippable_subscript(base):
|
||||||
|
new_bases.append(base.value)
|
||||||
|
base.value.parent = node
|
||||||
|
else:
|
||||||
|
new_bases.append(base)
|
||||||
|
node.bases = new_bases
|
||||||
|
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
def register_plugins(manager: astroid.Manager) -> None:
|
def register_plugins(manager: astroid.Manager) -> None:
|
||||||
"""Apply our transforms to a given astroid manager object."""
|
"""Apply our transforms to a given astroid manager object."""
|
||||||
|
|
||||||
|
# Hmm; is this still necessary?
|
||||||
if VERBOSE:
|
if VERBOSE:
|
||||||
manager.register_failed_import_hook(failed_import_hook)
|
manager.register_failed_import_hook(failed_import_hook)
|
||||||
|
|
||||||
@ -210,16 +250,30 @@ def register_plugins(manager: astroid.Manager) -> None:
|
|||||||
# check code as if it doesn't exist at all.
|
# check code as if it doesn't exist at all.
|
||||||
manager.register_transform(astroid.If, ignore_type_check_filter)
|
manager.register_transform(astroid.If, ignore_type_check_filter)
|
||||||
|
|
||||||
|
# We use 'reveal_type()' quite often, which tells mypy to print
|
||||||
|
# the type of an expression. Let's ignore it in Pylint's eyes so
|
||||||
|
# we don't see an ugly error there.
|
||||||
manager.register_transform(astroid.Call, ignore_reveal_type_call)
|
manager.register_transform(astroid.Call, ignore_reveal_type_call)
|
||||||
|
|
||||||
# Annotations on variables within a function are defer-eval'ed
|
# We make use of 'from __future__ import annotations' which causes Python
|
||||||
# in some cases, so lets replace them with simple strings in those
|
# to receive annotations as strings, and also 'if TYPE_CHECKING:' blocks,
|
||||||
# cases to avoid type complaints.
|
# which lets us do imports and whatnot that are limited to type-checking.
|
||||||
# (mypy will still properly alert us to type errors for them)
|
# Let's make Pylint understand these.
|
||||||
manager.register_transform(astroid.AnnAssign, var_annotations_filter)
|
manager.register_transform(astroid.AnnAssign, var_annotations_filter)
|
||||||
manager.register_transform(astroid.FunctionDef, func_annotations_filter)
|
manager.register_transform(astroid.FunctionDef, func_annotations_filter)
|
||||||
manager.register_transform(astroid.AsyncFunctionDef,
|
manager.register_transform(astroid.AsyncFunctionDef,
|
||||||
func_annotations_filter)
|
func_annotations_filter)
|
||||||
|
|
||||||
|
# Pylint doesn't seem to support Generics much right now, and it seems
|
||||||
|
# to lead to some buggy behavior and slowdowns. So let's filter them
|
||||||
|
# out. So instead of this:
|
||||||
|
# class MyClass(MyType[T]):
|
||||||
|
# Pylint will see this:
|
||||||
|
# class MyClass(MyType):
|
||||||
|
# I've opened a github issue related to the problems I was hitting,
|
||||||
|
# so we can revisit the need for this if that gets resolved.
|
||||||
|
# https://github.com/PyCQA/pylint/issues/3605
|
||||||
|
manager.register_transform(astroid.ClassDef, class_generics_filter)
|
||||||
|
|
||||||
|
|
||||||
register_plugins(astroid.MANAGER)
|
register_plugins(astroid.MANAGER)
|
||||||
|
|||||||
@ -54,7 +54,7 @@ def snippets_main(globs: Dict[str, Any]) -> None:
|
|||||||
show_help = False
|
show_help = False
|
||||||
retval = 0
|
retval = 0
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
print(f'{Clr.SRED}ERROR: command expected.{Clr.RST}')
|
print(f'{Clr.RED}ERROR: command expected.{Clr.RST}')
|
||||||
show_help = True
|
show_help = True
|
||||||
retval = 255
|
retval = 255
|
||||||
else:
|
else:
|
||||||
@ -67,13 +67,17 @@ def snippets_main(globs: Dict[str, Any]) -> None:
|
|||||||
else:
|
else:
|
||||||
docs = _trim_docstring(
|
docs = _trim_docstring(
|
||||||
getattr(funcs[sys.argv[2]], '__doc__', '<no docs>'))
|
getattr(funcs[sys.argv[2]], '__doc__', '<no docs>'))
|
||||||
print('\nsnippets ' + sys.argv[2] + ':\n' + docs + '\n')
|
print(f'\n{Clr.MAG}{Clr.BLD}snippets {sys.argv[2]}:{Clr.RST}\n'
|
||||||
|
f'{Clr.MAG}{docs}{Clr.RST}\n')
|
||||||
elif sys.argv[1] in funcs:
|
elif sys.argv[1] in funcs:
|
||||||
try:
|
try:
|
||||||
funcs[sys.argv[1]]()
|
funcs[sys.argv[1]]()
|
||||||
|
except KeyboardInterrupt as exc:
|
||||||
|
print(f'{Clr.RED}{exc}{Clr.RST}')
|
||||||
|
sys.exit(1)
|
||||||
except CleanError as exc:
|
except CleanError as exc:
|
||||||
exc.pretty_print()
|
exc.pretty_print()
|
||||||
sys.exit(-1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
print('Unknown snippets command: "' + sys.argv[1] + '"',
|
print('Unknown snippets command: "' + sys.argv[1] + '"',
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
@ -82,11 +86,12 @@ def snippets_main(globs: Dict[str, Any]) -> None:
|
|||||||
if show_help:
|
if show_help:
|
||||||
print('Snippets contains project related commands too small'
|
print('Snippets contains project related commands too small'
|
||||||
' to warrant full scripts.')
|
' to warrant full scripts.')
|
||||||
print("Run 'snippets help <COMMAND>' for full command documentation.")
|
print(f"Run {Clr.MAG}'snippets help {Clr.BLD}<COMMAND>'"
|
||||||
|
f'{Clr.RST} for full command documentation.')
|
||||||
print('Available commands:')
|
print('Available commands:')
|
||||||
for func, obj in sorted(funcs.items()):
|
for func, obj in sorted(funcs.items()):
|
||||||
doc = getattr(obj, '__doc__', '').splitlines()[0].strip()
|
doc = getattr(obj, '__doc__', '').splitlines()[0].strip()
|
||||||
print(f'{Clr.SMAG}{func}{Clr.SBLU} - {doc}{Clr.RST}')
|
print(f'{Clr.MAG}{func}{Clr.BLU} - {doc}{Clr.RST}')
|
||||||
sys.exit(retval)
|
sys.exit(retval)
|
||||||
|
|
||||||
|
|
||||||
@ -194,16 +199,16 @@ def check_clean_safety() -> None:
|
|||||||
|
|
||||||
def formatcode() -> None:
|
def formatcode() -> None:
|
||||||
"""Run clang-format on all of our source code (multithreaded)."""
|
"""Run clang-format on all of our source code (multithreaded)."""
|
||||||
from efrotools import code
|
import efrotools.code
|
||||||
full = '-full' in sys.argv
|
full = '-full' in sys.argv
|
||||||
code.formatcode(PROJROOT, full)
|
efrotools.code.formatcode(PROJROOT, full)
|
||||||
|
|
||||||
|
|
||||||
def formatscripts() -> None:
|
def formatscripts() -> None:
|
||||||
"""Run yapf on all our scripts (multithreaded)."""
|
"""Run yapf on all our scripts (multithreaded)."""
|
||||||
from efrotools import code
|
import efrotools.code
|
||||||
full = '-full' in sys.argv
|
full = '-full' in sys.argv
|
||||||
code.formatscripts(PROJROOT, full)
|
efrotools.code.formatscripts(PROJROOT, full)
|
||||||
|
|
||||||
|
|
||||||
def formatmakefile() -> None:
|
def formatmakefile() -> None:
|
||||||
@ -222,9 +227,9 @@ def formatmakefile() -> None:
|
|||||||
|
|
||||||
def cpplint() -> None:
|
def cpplint() -> None:
|
||||||
"""Run lint-checking on all code deemed lint-able."""
|
"""Run lint-checking on all code deemed lint-able."""
|
||||||
from efrotools import code
|
import efrotools.code
|
||||||
full = '-full' in sys.argv
|
full = '-full' in sys.argv
|
||||||
code.cpplint(PROJROOT, full)
|
efrotools.code.cpplint(PROJROOT, full)
|
||||||
|
|
||||||
|
|
||||||
def scriptfiles() -> None:
|
def scriptfiles() -> None:
|
||||||
@ -232,8 +237,8 @@ def scriptfiles() -> None:
|
|||||||
|
|
||||||
Pass -lines to use newlines as separators. The default is spaces.
|
Pass -lines to use newlines as separators. The default is spaces.
|
||||||
"""
|
"""
|
||||||
from efrotools import code
|
import efrotools.code
|
||||||
paths = code.get_script_filenames(projroot=PROJROOT)
|
paths = efrotools.code.get_script_filenames(projroot=PROJROOT)
|
||||||
assert not any(' ' in path for path in paths)
|
assert not any(' ' in path for path in paths)
|
||||||
if '-lines' in sys.argv:
|
if '-lines' in sys.argv:
|
||||||
print('\n'.join(paths))
|
print('\n'.join(paths))
|
||||||
@ -243,101 +248,102 @@ def scriptfiles() -> None:
|
|||||||
|
|
||||||
def pylint() -> None:
|
def pylint() -> None:
|
||||||
"""Run pylint checks on our scripts."""
|
"""Run pylint checks on our scripts."""
|
||||||
from efrotools import code
|
from efro.error import CleanError
|
||||||
|
import efrotools.code
|
||||||
full = ('-full' in sys.argv)
|
full = ('-full' in sys.argv)
|
||||||
fast = ('-fast' in sys.argv)
|
fast = ('-fast' in sys.argv)
|
||||||
code.pylint(PROJROOT, full, fast)
|
try:
|
||||||
|
efrotools.code.pylint(PROJROOT, full, fast)
|
||||||
|
except Exception:
|
||||||
|
raise CleanError('Pylint failed.')
|
||||||
|
|
||||||
|
|
||||||
|
def runpylint() -> None:
|
||||||
|
"""Run pylint checks on provided filenames."""
|
||||||
|
import os
|
||||||
|
from efro.terminal import Clr
|
||||||
|
from efro.error import CleanError
|
||||||
|
import efrotools.code
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
raise CleanError('Expected at least 1 filename arg.')
|
||||||
|
filenames = sys.argv[2:]
|
||||||
|
try:
|
||||||
|
efrotools.code.runpylint(PROJROOT, filenames)
|
||||||
|
print(f'{Clr.GRN}Pylint Passed.{Clr.RST}')
|
||||||
|
except Exception:
|
||||||
|
if os.environ.get('VERBOSE') == '1':
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
raise CleanError('Pylint Failed.')
|
||||||
|
|
||||||
|
|
||||||
def mypy() -> None:
|
def mypy() -> None:
|
||||||
"""Run mypy checks on our scripts."""
|
"""Run mypy checks on our scripts."""
|
||||||
from efrotools import code
|
import efrotools.code
|
||||||
full = ('-full' in sys.argv)
|
full = ('-full' in sys.argv)
|
||||||
code.mypy(PROJROOT, full)
|
efrotools.code.mypy(PROJROOT, full)
|
||||||
|
|
||||||
|
|
||||||
|
def runmypy() -> None:
|
||||||
|
"""Run mypy checks on provided filenames."""
|
||||||
|
from efro.terminal import Clr
|
||||||
|
from efro.error import CleanError
|
||||||
|
import efrotools.code
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
raise CleanError('Expected at least 1 filename arg.')
|
||||||
|
filenames = sys.argv[2:]
|
||||||
|
try:
|
||||||
|
efrotools.code.runmypy(PROJROOT, filenames)
|
||||||
|
print(f'{Clr.GRN}Mypy Passed.{Clr.RST}')
|
||||||
|
except Exception:
|
||||||
|
raise CleanError('Mypy Failed.')
|
||||||
|
|
||||||
|
|
||||||
def dmypy() -> None:
|
def dmypy() -> None:
|
||||||
"""Run mypy checks on our scripts using the mypy daemon."""
|
"""Run mypy checks on our scripts using the mypy daemon."""
|
||||||
from efrotools import code
|
import efrotools.code
|
||||||
code.dmypy(PROJROOT)
|
efrotools.code.dmypy(PROJROOT)
|
||||||
|
|
||||||
|
|
||||||
def pycharm() -> None:
|
def pycharm() -> None:
|
||||||
"""Run PyCharm checks on our scripts."""
|
"""Run PyCharm checks on our scripts."""
|
||||||
from efrotools import code
|
import efrotools.code
|
||||||
full = '-full' in sys.argv
|
full = '-full' in sys.argv
|
||||||
verbose = '-v' in sys.argv
|
verbose = '-v' in sys.argv
|
||||||
code.pycharm(PROJROOT, full, verbose)
|
efrotools.code.pycharm(PROJROOT, full, verbose)
|
||||||
|
|
||||||
|
|
||||||
def clioncode() -> None:
|
def clioncode() -> None:
|
||||||
"""Run CLion checks on our code."""
|
"""Run CLion checks on our code."""
|
||||||
from efrotools import code
|
import efrotools.code
|
||||||
full = '-full' in sys.argv
|
full = '-full' in sys.argv
|
||||||
verbose = '-v' in sys.argv
|
verbose = '-v' in sys.argv
|
||||||
code.clioncode(PROJROOT, full, verbose)
|
efrotools.code.clioncode(PROJROOT, full, verbose)
|
||||||
|
|
||||||
|
|
||||||
def androidstudiocode() -> None:
|
def androidstudiocode() -> None:
|
||||||
"""Run Android Studio checks on our code."""
|
"""Run Android Studio checks on our code."""
|
||||||
from efrotools import code
|
import efrotools.code
|
||||||
full = '-full' in sys.argv
|
full = '-full' in sys.argv
|
||||||
verbose = '-v' in sys.argv
|
verbose = '-v' in sys.argv
|
||||||
code.androidstudiocode(PROJROOT, full, verbose)
|
efrotools.code.androidstudiocode(PROJROOT, full, verbose)
|
||||||
|
|
||||||
|
|
||||||
def tool_config_install() -> None:
|
def tool_config_install() -> None:
|
||||||
"""Install a tool config file (with some filtering)."""
|
"""Install a tool config file (with some filtering)."""
|
||||||
from efrotools import get_config
|
from efro.terminal import Clr
|
||||||
import textwrap
|
|
||||||
if len(sys.argv) != 4:
|
if len(sys.argv) != 4:
|
||||||
raise Exception('expected 2 args')
|
raise Exception('expected 2 args')
|
||||||
src = Path(sys.argv[2])
|
src = Path(sys.argv[2])
|
||||||
dst = Path(sys.argv[3])
|
dst = Path(sys.argv[3])
|
||||||
|
|
||||||
|
print(f'Creating tool config: {Clr.BLD}{dst}{Clr.RST}')
|
||||||
|
|
||||||
with src.open() as infile:
|
with src.open() as infile:
|
||||||
cfg = infile.read()
|
cfg = infile.read()
|
||||||
|
|
||||||
# Do a bit of filtering.
|
# Rome substitutions, etc.
|
||||||
|
cfg = _filter_tool_config(cfg)
|
||||||
# Stick project-root wherever they want.
|
|
||||||
cfg = cfg.replace('__EFRO_PROJECT_ROOT__', str(PROJROOT))
|
|
||||||
|
|
||||||
# Short project name.
|
|
||||||
short_names = {'ballistica-internal': 'ba-int', 'ballistica': 'ba'}
|
|
||||||
shortname = short_names.get(PROJROOT.name, PROJROOT.name)
|
|
||||||
cfg = cfg.replace('__EFRO_PROJECT_SHORTNAME__', shortname)
|
|
||||||
|
|
||||||
stdsettings = textwrap.dedent("""
|
|
||||||
# We don't want all of our plain scripts complaining
|
|
||||||
# about __main__ being redefined.
|
|
||||||
scripts_are_modules = True
|
|
||||||
|
|
||||||
# Try to be as strict as we can about using types everywhere.
|
|
||||||
warn_unused_ignores = True
|
|
||||||
warn_return_any = True
|
|
||||||
warn_redundant_casts = True
|
|
||||||
warn_unreachable=True
|
|
||||||
disallow_incomplete_defs = True
|
|
||||||
disallow_untyped_defs = True
|
|
||||||
disallow_untyped_decorators = True
|
|
||||||
disallow_untyped_calls = True
|
|
||||||
disallow_any_unimported = True
|
|
||||||
strict_equality = True
|
|
||||||
""").strip()
|
|
||||||
|
|
||||||
cfg = cfg.replace('__EFRO_MYPY_STANDARD_SETTINGS__', stdsettings)
|
|
||||||
|
|
||||||
# Gen a pylint init to set up our python paths:
|
|
||||||
pylint_init_tag = '__EFRO_PYLINT_INIT__'
|
|
||||||
if pylint_init_tag in cfg:
|
|
||||||
pypaths = get_config(PROJROOT).get('python_paths')
|
|
||||||
if pypaths is None:
|
|
||||||
raise RuntimeError('python_paths not set in project config')
|
|
||||||
cstr = "init-hook='import sys;"
|
|
||||||
for path in pypaths:
|
|
||||||
cstr += f" sys.path.append('{PROJROOT}/{path}');"
|
|
||||||
cstr += "'"
|
|
||||||
cfg = cfg.replace(pylint_init_tag, cstr)
|
|
||||||
|
|
||||||
# Add an auto-generated notice.
|
# Add an auto-generated notice.
|
||||||
comment = None
|
comment = None
|
||||||
@ -356,6 +362,57 @@ def tool_config_install() -> None:
|
|||||||
outfile.write(cfg)
|
outfile.write(cfg)
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_tool_config(cfg: str) -> str:
|
||||||
|
import textwrap
|
||||||
|
from efrotools import get_config
|
||||||
|
|
||||||
|
# Stick project-root wherever they want.
|
||||||
|
cfg = cfg.replace('__EFRO_PROJECT_ROOT__', str(PROJROOT))
|
||||||
|
|
||||||
|
# Short project name.
|
||||||
|
short_names = {'ballistica-internal': 'ba-int', 'ballistica': 'ba'}
|
||||||
|
shortname = short_names.get(PROJROOT.name, PROJROOT.name)
|
||||||
|
cfg = cfg.replace('__EFRO_PROJECT_SHORTNAME__', shortname)
|
||||||
|
|
||||||
|
mypy_standard_settings = textwrap.dedent("""
|
||||||
|
# We don't want all of our plain scripts complaining
|
||||||
|
# about __main__ being redefined.
|
||||||
|
scripts_are_modules = True
|
||||||
|
|
||||||
|
# Try to be as strict as we can about using types everywhere.
|
||||||
|
warn_unused_ignores = True
|
||||||
|
warn_return_any = True
|
||||||
|
warn_redundant_casts = True
|
||||||
|
warn_unreachable = True
|
||||||
|
warn_unused_configs = True
|
||||||
|
disallow_incomplete_defs = True
|
||||||
|
disallow_untyped_defs = True
|
||||||
|
disallow_untyped_decorators = True
|
||||||
|
disallow_untyped_calls = True
|
||||||
|
disallow_any_unimported = True
|
||||||
|
disallow_subclassing_any = True
|
||||||
|
strict_equality = True
|
||||||
|
local_partial_types = True
|
||||||
|
no_implicit_reexport = True
|
||||||
|
""").strip()
|
||||||
|
|
||||||
|
cfg = cfg.replace('__EFRO_MYPY_STANDARD_SETTINGS__',
|
||||||
|
mypy_standard_settings)
|
||||||
|
|
||||||
|
# Gen a pylint init to set up our python paths:
|
||||||
|
pylint_init_tag = '__EFRO_PYLINT_INIT__'
|
||||||
|
if pylint_init_tag in cfg:
|
||||||
|
pypaths = get_config(PROJROOT).get('python_paths')
|
||||||
|
if pypaths is None:
|
||||||
|
raise RuntimeError('python_paths not set in project config')
|
||||||
|
cstr = "init-hook='import sys;"
|
||||||
|
for path in pypaths:
|
||||||
|
cstr += f" sys.path.append('{PROJROOT}/{path}');"
|
||||||
|
cstr += "'"
|
||||||
|
cfg = cfg.replace(pylint_init_tag, cstr)
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
def sync_all() -> None:
|
def sync_all() -> None:
|
||||||
"""Runs full syncs between all efrotools projects.
|
"""Runs full syncs between all efrotools projects.
|
||||||
|
|
||||||
@ -368,7 +425,7 @@ def sync_all() -> None:
|
|||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from efro.error import CleanError
|
from efro.error import CleanError
|
||||||
from efro.terminal import Clr
|
from efro.terminal import Clr
|
||||||
print(f'{Clr.SBLU}Updating formatting for all projects...{Clr.RST}')
|
print(f'{Clr.BLU}Updating formatting for all projects...{Clr.RST}')
|
||||||
projects_str = os.environ.get('EFROTOOLS_SYNC_PROJECTS')
|
projects_str = os.environ.get('EFROTOOLS_SYNC_PROJECTS')
|
||||||
if projects_str is None:
|
if projects_str is None:
|
||||||
raise CleanError('EFROTOOL_SYNC_PROJECTS is not defined.')
|
raise CleanError('EFROTOOL_SYNC_PROJECTS is not defined.')
|
||||||
@ -399,17 +456,17 @@ def sync_all() -> None:
|
|||||||
# Real mode
|
# Real mode
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
print(Clr.SBLU + 'Running sync pass 1:'
|
print(Clr.BLU + 'Running sync pass 1:'
|
||||||
' (ensures all changes at dsts are pushed to src)' +
|
' (ensures all changes at dsts are pushed to src)' +
|
||||||
Clr.RST)
|
Clr.RST)
|
||||||
else:
|
else:
|
||||||
print(Clr.SBLU + 'Running sync pass 2:'
|
print(Clr.BLU + 'Running sync pass 2:'
|
||||||
' (ensures latest src is pulled to all dsts)' + Clr.RST)
|
' (ensures latest src is pulled to all dsts)' + Clr.RST)
|
||||||
for project in projects_str.split(':'):
|
for project in projects_str.split(':'):
|
||||||
cmd = f'cd "{project}" && make sync-full'
|
cmd = f'cd "{project}" && make sync-full'
|
||||||
print(cmd)
|
print(cmd)
|
||||||
subprocess.run(cmd, shell=True, check=True)
|
subprocess.run(cmd, shell=True, check=True)
|
||||||
print(Clr.SBLU + 'Sync-all successful!' + Clr.RST)
|
print(Clr.BLU + 'Sync-all successful!' + Clr.RST)
|
||||||
|
|
||||||
|
|
||||||
def sync() -> None:
|
def sync() -> None:
|
||||||
@ -530,5 +587,26 @@ def makefile_target_list() -> None:
|
|||||||
continue
|
continue
|
||||||
print('\n' + entry.title + '\n' + '-' * len(entry.title))
|
print('\n' + entry.title + '\n' + '-' * len(entry.title))
|
||||||
elif entry.kind == 'target':
|
elif entry.kind == 'target':
|
||||||
print(Clr.SMAG + entry.title + Clr.SBLU +
|
print(Clr.MAG + entry.title + Clr.BLU +
|
||||||
_docstr(lines, entry.line) + Clr.RST)
|
_docstr(lines, entry.line) + Clr.RST)
|
||||||
|
|
||||||
|
|
||||||
|
def echo() -> None:
|
||||||
|
"""Echo with support for efro.terminal.Clr args (RED, GRN, BLU, etc).
|
||||||
|
|
||||||
|
Prints a Clr.RST at the end so that can be omitted.
|
||||||
|
"""
|
||||||
|
from efro.terminal import Clr
|
||||||
|
clrnames = {n for n in dir(Clr) if n.isupper() and not n.startswith('_')}
|
||||||
|
first = True
|
||||||
|
out: List[str] = []
|
||||||
|
for arg in sys.argv[2:]:
|
||||||
|
if arg in clrnames:
|
||||||
|
out.append(getattr(Clr, arg))
|
||||||
|
else:
|
||||||
|
if not first:
|
||||||
|
out.append(' ')
|
||||||
|
first = False
|
||||||
|
out.append(arg)
|
||||||
|
out.append(Clr.RST)
|
||||||
|
print(''.join(out))
|
||||||
|
|||||||
@ -40,9 +40,10 @@ from typing import TYPE_CHECKING
|
|||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from efrotools.snippets import (
|
from efrotools.snippets import (
|
||||||
PROJROOT, snippets_main, formatcode, formatscripts, formatmakefile,
|
PROJROOT, snippets_main, formatcode, formatscripts, formatmakefile,
|
||||||
cpplint, pylint, mypy, dmypy, tool_config_install, sync, sync_all,
|
cpplint, pylint, runpylint, mypy, runmypy, dmypy, tool_config_install,
|
||||||
scriptfiles, pycharm, clioncode, androidstudiocode, makefile_target_list,
|
sync, sync_all, scriptfiles, pycharm, clioncode, androidstudiocode,
|
||||||
spelling, spelling_all, compile_python_files, pytest)
|
makefile_target_list, spelling, spelling_all, compile_python_files, pytest,
|
||||||
|
echo)
|
||||||
# pylint: enable=unused-import
|
# pylint: enable=unused-import
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -259,7 +260,7 @@ def clean_orphaned_assets() -> None:
|
|||||||
# Operate from dist root..
|
# Operate from dist root..
|
||||||
os.chdir(PROJROOT)
|
os.chdir(PROJROOT)
|
||||||
|
|
||||||
# Our manifest is split into 2 files (public and non-public)
|
# Our manifest is split into 2 files (public and private)
|
||||||
with open('assets/.asset_manifest_public.json') as infile:
|
with open('assets/.asset_manifest_public.json') as infile:
|
||||||
manifest = set(json.loads(infile.read()))
|
manifest = set(json.loads(infile.read()))
|
||||||
with open('assets/.asset_manifest_private.json') as infile:
|
with open('assets/.asset_manifest_private.json') as infile:
|
||||||
@ -303,7 +304,8 @@ def py_examine() -> None:
|
|||||||
sys.path.append(scriptsdir)
|
sys.path.append(scriptsdir)
|
||||||
if toolsdir not in sys.path:
|
if toolsdir not in sys.path:
|
||||||
sys.path.append(toolsdir)
|
sys.path.append(toolsdir)
|
||||||
efrotools.py_examine(filename, line, column, selection, operation)
|
efrotools.py_examine(PROJROOT, filename, line, column, selection,
|
||||||
|
operation)
|
||||||
|
|
||||||
|
|
||||||
def push_ipa() -> None:
|
def push_ipa() -> None:
|
||||||
@ -553,6 +555,9 @@ def lazy_increment_build() -> None:
|
|||||||
"""Increment build number only if C++ sources have changed.
|
"""Increment build number only if C++ sources have changed.
|
||||||
|
|
||||||
This is convenient to place in automatic commit/push scripts.
|
This is convenient to place in automatic commit/push scripts.
|
||||||
|
It could make sense to auto update build number when scripts/assets
|
||||||
|
change too, but a build number change requires rebuilding all binaries
|
||||||
|
so I'll leave that as an explicit choice to save work.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|||||||
@ -133,9 +133,9 @@ class App:
|
|||||||
self._update_docs_md()
|
self._update_docs_md()
|
||||||
|
|
||||||
if self._check:
|
if self._check:
|
||||||
print('Check-Builds: Everything up to date.')
|
print(f'{Clr.BLU}Check-Builds: Everything up to date.{Clr.RST}')
|
||||||
else:
|
else:
|
||||||
print('Update-Builds: SUCCESS!')
|
print(f'{Clr.GRN}Update-Project: SUCCESS!{Clr.RST}')
|
||||||
|
|
||||||
def _update_dummy_module(self) -> None:
|
def _update_dummy_module(self) -> None:
|
||||||
# Update our dummy _ba module.
|
# Update our dummy _ba module.
|
||||||
@ -144,8 +144,8 @@ class App:
|
|||||||
# been updated.
|
# been updated.
|
||||||
if os.path.exists('tools/gendummymodule.py'):
|
if os.path.exists('tools/gendummymodule.py'):
|
||||||
if os.system('tools/gendummymodule.py' + self._checkarg) != 0:
|
if os.system('tools/gendummymodule.py' + self._checkarg) != 0:
|
||||||
print(Clr.SRED + 'Error checking/updating dummy module' +
|
print(
|
||||||
Clr.RST)
|
f'{Clr.RED}Error checking/updating dummy module{Clr.RST}')
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
|
|
||||||
def _update_docs_md(self) -> None:
|
def _update_docs_md(self) -> None:
|
||||||
@ -157,8 +157,8 @@ class App:
|
|||||||
if os.path.exists('tools/gendocs.py'):
|
if os.path.exists('tools/gendocs.py'):
|
||||||
if os.system('tools/snippets update_docs_md' +
|
if os.system('tools/snippets update_docs_md' +
|
||||||
self._checkarg) != 0:
|
self._checkarg) != 0:
|
||||||
print(Clr.SRED + 'Error checking/updating docs markdown.' +
|
print(f'{Clr.RED}Error checking/updating'
|
||||||
Clr.RST)
|
f' docs markdown.{Clr.RST}')
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
|
|
||||||
def _update_compile_commands_file(self) -> None:
|
def _update_compile_commands_file(self) -> None:
|
||||||
@ -166,7 +166,7 @@ class App:
|
|||||||
# our cmake stuff. Do this at end so cmake changes already happened.
|
# our cmake stuff. Do this at end so cmake changes already happened.
|
||||||
if not self._check and os.path.exists('ballisticacore-cmake'):
|
if not self._check and os.path.exists('ballisticacore-cmake'):
|
||||||
if os.system('make .irony/compile_commands.json') != 0:
|
if os.system('make .irony/compile_commands.json') != 0:
|
||||||
print(Clr.SRED + 'Error updating compile-commands.' + Clr.RST)
|
print(f'{Clr.RED}Error updating compile-commands.{Clr.RST}')
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
|
|
||||||
def _apply_file_changes(self) -> None:
|
def _apply_file_changes(self) -> None:
|
||||||
@ -184,11 +184,11 @@ class App:
|
|||||||
unchanged_project_count += 1
|
unchanged_project_count += 1
|
||||||
else:
|
else:
|
||||||
if self._check:
|
if self._check:
|
||||||
print(f'{Clr.SRED}ERROR: found out-of-date'
|
print(f'{Clr.RED}ERROR: found out-of-date'
|
||||||
f' project file: {fname}{Clr.RST}')
|
f' project file: {fname}{Clr.RST}')
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
|
|
||||||
print(f'{Clr.SBLU}Writing project file: {fname}{Clr.RST}')
|
print(f'{Clr.BLU}Writing project file: {fname}{Clr.RST}')
|
||||||
with open(fname, 'w') as outfile:
|
with open(fname, 'w') as outfile:
|
||||||
outfile.write(fcode)
|
outfile.write(fcode)
|
||||||
if unchanged_project_count > 0:
|
if unchanged_project_count > 0:
|
||||||
@ -211,16 +211,16 @@ class App:
|
|||||||
# If there are any manual-only entries, list then and bail.
|
# If there are any manual-only entries, list then and bail.
|
||||||
# (Don't wanna allow auto-apply unless it fixes everything)
|
# (Don't wanna allow auto-apply unless it fixes everything)
|
||||||
if manual_changes:
|
if manual_changes:
|
||||||
print(f'{Clr.SRED}Found erroneous lines '
|
print(f'{Clr.RED}Found erroneous lines '
|
||||||
f'requiring manual correction:{Clr.RST}')
|
f'requiring manual correction:{Clr.RST}')
|
||||||
for change in manual_changes:
|
for change in manual_changes:
|
||||||
print(
|
print(
|
||||||
f'{Clr.SRED}{change[0]}:{change[1].line_number + 1}:'
|
f'{Clr.RED}{change[0]}:{change[1].line_number + 1}:'
|
||||||
f' Expected line to be:\n {change[1].expected}{Clr.RST}')
|
f' Expected line to be:\n {change[1].expected}{Clr.RST}')
|
||||||
|
|
||||||
# Make a note on copyright lines that this can be disabled.
|
# Make a note on copyright lines that this can be disabled.
|
||||||
if 'Copyright' in change[1].expected:
|
if 'Copyright' in change[1].expected:
|
||||||
print(f'{Clr.SRED}NOTE: You can disable copyright'
|
print(f'{Clr.RED}NOTE: You can disable copyright'
|
||||||
f' checks by adding "copyright_checks": false\n'
|
f' checks by adding "copyright_checks": false\n'
|
||||||
f'to the root dict in config/localconfig.json.\n'
|
f'to the root dict in config/localconfig.json.\n'
|
||||||
f'see https://github.com/efroemling/ballistica/wiki'
|
f'see https://github.com/efroemling/ballistica/wiki'
|
||||||
@ -232,22 +232,21 @@ class App:
|
|||||||
if auto_changes:
|
if auto_changes:
|
||||||
if not self._fix:
|
if not self._fix:
|
||||||
for i, change in enumerate(auto_changes):
|
for i, change in enumerate(auto_changes):
|
||||||
print(f'{Clr.SRED}#{i}: {change[0]}:{Clr.RST}')
|
print(f'{Clr.RED}#{i}: {change[0]}:{Clr.RST}')
|
||||||
print(
|
print(
|
||||||
f'{Clr.SRED} Expected "{change[1].expected}"{Clr.RST}'
|
f'{Clr.RED} Expected "{change[1].expected}"{Clr.RST}')
|
||||||
)
|
|
||||||
with open(change[0]) as infile:
|
with open(change[0]) as infile:
|
||||||
lines = infile.read().splitlines()
|
lines = infile.read().splitlines()
|
||||||
line = lines[change[1].line_number]
|
line = lines[change[1].line_number]
|
||||||
print(f'{Clr.SRED} Found "{line}"{Clr.RST}')
|
print(f'{Clr.RED} Found "{line}"{Clr.RST}')
|
||||||
print(Clr.SRED +
|
print(Clr.RED +
|
||||||
f'All {len(auto_changes)} errors are auto-fixable;'
|
f'All {len(auto_changes)} errors are auto-fixable;'
|
||||||
' run tools/update_project --fix to apply corrections.' +
|
' run tools/update_project --fix to apply corrections.' +
|
||||||
Clr.RST)
|
Clr.RST)
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
else:
|
else:
|
||||||
for i, change in enumerate(auto_changes):
|
for i, change in enumerate(auto_changes):
|
||||||
print(f'{Clr.SBLU}Correcting file: {change[0]}{Clr.RST}')
|
print(f'{Clr.BLU}Correcting file: {change[0]}{Clr.RST}')
|
||||||
with open(change[0]) as infile:
|
with open(change[0]) as infile:
|
||||||
lines = infile.read().splitlines()
|
lines = infile.read().splitlines()
|
||||||
lines[change[1].line_number] = change[1].expected
|
lines[change[1].line_number] = change[1].expected
|
||||||
@ -269,7 +268,7 @@ class App:
|
|||||||
# Could just ignore these but it probably means I intended
|
# Could just ignore these but it probably means I intended
|
||||||
# to save something and forgot.
|
# to save something and forgot.
|
||||||
if '/.#' in fsrc:
|
if '/.#' in fsrc:
|
||||||
print(f'{Clr.SRED}'
|
print(f'{Clr.RED}'
|
||||||
f'ERROR: Found an unsaved emacs file: "{fsrc}"'
|
f'ERROR: Found an unsaved emacs file: "{fsrc}"'
|
||||||
f'{Clr.RST}')
|
f'{Clr.RST}')
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
@ -393,7 +392,7 @@ class App:
|
|||||||
'tools/devtool', 'tools/version_utils', 'tools/vmshell'
|
'tools/devtool', 'tools/version_utils', 'tools/vmshell'
|
||||||
]:
|
]:
|
||||||
if not contents.startswith('#!/usr/bin/env python3.7'):
|
if not contents.startswith('#!/usr/bin/env python3.7'):
|
||||||
print(f'{Clr.SRED}Incorrect shebang (first line) for '
|
print(f'{Clr.RED}Incorrect shebang (first line) for '
|
||||||
f'{fname}.{Clr.RST}')
|
f'{fname}.{Clr.RST}')
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
else:
|
else:
|
||||||
@ -482,7 +481,7 @@ class App:
|
|||||||
if ('__pycache__' not in root
|
if ('__pycache__' not in root
|
||||||
and os.path.basename(root) != '.vscode'):
|
and os.path.basename(root) != '.vscode'):
|
||||||
if '__init__.py' not in files:
|
if '__init__.py' not in files:
|
||||||
print(Clr.SRED +
|
print(Clr.RED +
|
||||||
'Error: no __init__.py in package dir: ' + root +
|
'Error: no __init__.py in package dir: ' + root +
|
||||||
Clr.RST)
|
Clr.RST)
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
@ -630,14 +629,14 @@ class App:
|
|||||||
# Make sure none of our sync targets have been mucked with since
|
# Make sure none of our sync targets have been mucked with since
|
||||||
# their last sync.
|
# their last sync.
|
||||||
if os.system('tools/snippets sync check') != 0:
|
if os.system('tools/snippets sync check') != 0:
|
||||||
print(Clr.SRED + 'Sync check failed; you may need to run "sync".' +
|
print(Clr.RED + 'Sync check failed; you may need to run "sync".' +
|
||||||
Clr.RST)
|
Clr.RST)
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
|
|
||||||
def _update_assets_makefile(self) -> None:
|
def _update_assets_makefile(self) -> None:
|
||||||
if os.path.exists('tools/update_assets_makefile'):
|
if os.path.exists('tools/update_assets_makefile'):
|
||||||
if os.system('tools/update_assets_makefile' + self._checkarg) != 0:
|
if os.system('tools/update_assets_makefile' + self._checkarg) != 0:
|
||||||
print(Clr.SRED + 'Error checking/updating assets Makefile' +
|
print(Clr.RED + 'Error checking/updating assets Makefile' +
|
||||||
Clr.RST)
|
Clr.RST)
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
|
|
||||||
@ -645,7 +644,7 @@ class App:
|
|||||||
if os.path.exists('tools/update_generated_code_makefile'):
|
if os.path.exists('tools/update_generated_code_makefile'):
|
||||||
if os.system('tools/update_generated_code_makefile' +
|
if os.system('tools/update_generated_code_makefile' +
|
||||||
self._checkarg) != 0:
|
self._checkarg) != 0:
|
||||||
print(Clr.SRED +
|
print(Clr.RED +
|
||||||
'Error checking/updating generated-code Makefile' +
|
'Error checking/updating generated-code Makefile' +
|
||||||
Clr.RST)
|
Clr.RST)
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
@ -654,7 +653,7 @@ class App:
|
|||||||
if os.path.exists('tools/update_resources_makefile'):
|
if os.path.exists('tools/update_resources_makefile'):
|
||||||
if os.system('tools/update_resources_makefile' +
|
if os.system('tools/update_resources_makefile' +
|
||||||
self._checkarg) != 0:
|
self._checkarg) != 0:
|
||||||
print(Clr.SRED + 'Error checking/updating resources Makefile' +
|
print(Clr.RED + 'Error checking/updating resources Makefile' +
|
||||||
Clr.RST)
|
Clr.RST)
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
|
|
||||||
@ -662,8 +661,8 @@ class App:
|
|||||||
if os.path.exists('tools/update_python_enums_module'):
|
if os.path.exists('tools/update_python_enums_module'):
|
||||||
if os.system('tools/update_python_enums_module' +
|
if os.system('tools/update_python_enums_module' +
|
||||||
self._checkarg) != 0:
|
self._checkarg) != 0:
|
||||||
print(Clr.SRED +
|
print(Clr.RED + 'Error checking/updating python enums module' +
|
||||||
'Error checking/updating python enums module' + Clr.RST)
|
Clr.RST)
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user