mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2025-11-07 17:36:08 +00:00
Added new files
This commit is contained in:
parent
867634cc5c
commit
3a407868d4
1775 changed files with 550222 additions and 0 deletions
1
dist/ba_data/python/bastd/actor/__init__.py
vendored
Normal file
1
dist/ba_data/python/bastd/actor/__init__.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
BIN
dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/background.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/background.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/image.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/image.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/spawner.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/spawner.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/text.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/text.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
145
dist/ba_data/python/bastd/actor/background.py
vendored
Normal file
145
dist/ba_data/python/bastd/actor/background.py
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines Actor(s)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import weakref
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Background(ba.Actor):
|
||||
"""Simple Fading Background Actor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
fade_time: float = 0.5,
|
||||
start_faded: bool = False,
|
||||
show_logo: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self._dying = False
|
||||
self.fade_time = fade_time
|
||||
# We're special in that we create our node in the session
|
||||
# scene instead of the activity scene.
|
||||
# This way we can overlap multiple activities for fades
|
||||
# and whatnot.
|
||||
session = ba.getsession()
|
||||
self._session = weakref.ref(session)
|
||||
with ba.Context(session):
|
||||
self.node = ba.newnode(
|
||||
'image',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'fill_screen': True,
|
||||
'texture': ba.gettexture('bg'),
|
||||
'tilt_translate': -0.3,
|
||||
'has_alpha_channel': False,
|
||||
'color': (1, 1, 1),
|
||||
},
|
||||
)
|
||||
if not start_faded:
|
||||
ba.animate(
|
||||
self.node,
|
||||
'opacity',
|
||||
{0.0: 0.0, self.fade_time: 1.0},
|
||||
loop=False,
|
||||
)
|
||||
if show_logo:
|
||||
logo_texture = ba.gettexture('logo')
|
||||
logo_model = ba.getmodel('logo')
|
||||
logo_model_transparent = ba.getmodel('logoTransparent')
|
||||
self.logo = ba.newnode(
|
||||
'image',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'texture': logo_texture,
|
||||
'model_opaque': logo_model,
|
||||
'model_transparent': logo_model_transparent,
|
||||
'scale': (0.7, 0.7),
|
||||
'vr_depth': -250,
|
||||
'color': (0.15, 0.15, 0.15),
|
||||
'position': (0, 0),
|
||||
'tilt_translate': -0.05,
|
||||
'absolute_scale': False,
|
||||
},
|
||||
)
|
||||
self.node.connectattr('opacity', self.logo, 'opacity')
|
||||
# add jitter/pulse for a stop-motion-y look unless we're in VR
|
||||
# in which case stillness is better
|
||||
if not ba.app.vr_mode:
|
||||
self.cmb = ba.newnode(
|
||||
'combine', owner=self.node, attrs={'size': 2}
|
||||
)
|
||||
for attr in ['input0', 'input1']:
|
||||
ba.animate(
|
||||
self.cmb,
|
||||
attr,
|
||||
{0.0: 0.693, 0.05: 0.7, 0.5: 0.693},
|
||||
loop=True,
|
||||
)
|
||||
self.cmb.connectattr('output', self.logo, 'scale')
|
||||
cmb = ba.newnode(
|
||||
'combine', owner=self.node, attrs={'size': 2}
|
||||
)
|
||||
cmb.connectattr('output', self.logo, 'position')
|
||||
# Gen some random keys for that stop-motion-y look.
|
||||
keys = {}
|
||||
timeval = 0.0
|
||||
for _i in range(10):
|
||||
keys[timeval] = (random.random() - 0.5) * 0.0015
|
||||
timeval += random.random() * 0.1
|
||||
ba.animate(cmb, 'input0', keys, loop=True)
|
||||
keys = {}
|
||||
timeval = 0.0
|
||||
for _i in range(10):
|
||||
keys[timeval] = (random.random() - 0.5) * 0.0015 + 0.05
|
||||
timeval += random.random() * 0.1
|
||||
ba.animate(cmb, 'input1', keys, loop=True)
|
||||
|
||||
def __del__(self) -> None:
|
||||
# Normal actors don't get sent DieMessages when their
|
||||
# activity is shutting down, but we still need to do so
|
||||
# since our node lives in the session and it wouldn't die
|
||||
# otherwise.
|
||||
self._die()
|
||||
super().__del__()
|
||||
|
||||
def _die(self, immediate: bool = False) -> None:
|
||||
session = self._session()
|
||||
if session is None and self.node:
|
||||
# If session is gone, our node should be too,
|
||||
# since it was part of the session's scene.
|
||||
# Let's make sure that's the case.
|
||||
# (since otherwise we have no way to kill it)
|
||||
ba.print_error(
|
||||
'got None session on Background _die'
|
||||
' (and node still exists!)'
|
||||
)
|
||||
elif session is not None:
|
||||
with ba.Context(session):
|
||||
if not self._dying and self.node:
|
||||
self._dying = True
|
||||
if immediate:
|
||||
self.node.delete()
|
||||
else:
|
||||
ba.animate(
|
||||
self.node,
|
||||
'opacity',
|
||||
{0.0: 1.0, self.fade_time: 0.0},
|
||||
loop=False,
|
||||
)
|
||||
ba.timer(self.fade_time + 0.1, self.node.delete)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
if isinstance(msg, ba.DieMessage):
|
||||
self._die(msg.immediate)
|
||||
else:
|
||||
super().handlemessage(msg)
|
||||
1212
dist/ba_data/python/bastd/actor/bomb.py
vendored
Normal file
1212
dist/ba_data/python/bastd/actor/bomb.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
639
dist/ba_data/python/bastd/actor/controlsguide.py
vendored
Normal file
639
dist/ba_data/python/bastd/actor/controlsguide.py
vendored
Normal file
|
|
@ -0,0 +1,639 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines Actors related to controls guides."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
class ControlsGuide(ba.Actor):
|
||||
"""A screen overlay of game controls.
|
||||
|
||||
category: Gameplay Classes
|
||||
|
||||
Shows button mappings based on what controllers are connected.
|
||||
Handy to show at the start of a series or whenever there might
|
||||
be newbies watching.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
position: tuple[float, float] = (390.0, 120.0),
|
||||
scale: float = 1.0,
|
||||
delay: float = 0.0,
|
||||
lifespan: float | None = None,
|
||||
bright: bool = False,
|
||||
):
|
||||
"""Instantiate an overlay.
|
||||
|
||||
delay: is the time in seconds before the overlay fades in.
|
||||
|
||||
lifespan: if not None, the overlay will fade back out and die after
|
||||
that long (in milliseconds).
|
||||
|
||||
bright: if True, brighter colors will be used; handy when showing
|
||||
over gameplay but may be too bright for join-screens, etc.
|
||||
"""
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-locals
|
||||
super().__init__()
|
||||
show_title = True
|
||||
scale *= 0.75
|
||||
image_size = 90.0 * scale
|
||||
offs = 74.0 * scale
|
||||
offs5 = 43.0 * scale
|
||||
ouya = False
|
||||
maxw = 50
|
||||
self._lifespan = lifespan
|
||||
self._dead = False
|
||||
self._bright = bright
|
||||
self._cancel_timer: ba.Timer | None = None
|
||||
self._fade_in_timer: ba.Timer | None = None
|
||||
self._update_timer: ba.Timer | None = None
|
||||
self._title_text: ba.Node | None
|
||||
clr: Sequence[float]
|
||||
extra_pos_1: tuple[float, float] | None
|
||||
extra_pos_2: tuple[float, float] | None
|
||||
if ba.app.iircade_mode:
|
||||
xtweak = 0.2
|
||||
ytweak = 0.2
|
||||
jump_pos = (
|
||||
position[0] + offs * (-1.2 + xtweak),
|
||||
position[1] + offs * (0.1 + ytweak),
|
||||
)
|
||||
bomb_pos = (
|
||||
position[0] + offs * (0.0 + xtweak),
|
||||
position[1] + offs * (0.5 + ytweak),
|
||||
)
|
||||
punch_pos = (
|
||||
position[0] + offs * (1.2 + xtweak),
|
||||
position[1] + offs * (0.5 + ytweak),
|
||||
)
|
||||
|
||||
pickup_pos = (
|
||||
position[0] + offs * (-1.4 + xtweak),
|
||||
position[1] + offs * (-1.2 + ytweak),
|
||||
)
|
||||
extra_pos_1 = (
|
||||
position[0] + offs * (-0.2 + xtweak),
|
||||
position[1] + offs * (-0.8 + ytweak),
|
||||
)
|
||||
extra_pos_2 = (
|
||||
position[0] + offs * (1.0 + xtweak),
|
||||
position[1] + offs * (-0.8 + ytweak),
|
||||
)
|
||||
self._force_hide_button_names = True
|
||||
else:
|
||||
punch_pos = (position[0] - offs * 1.1, position[1])
|
||||
jump_pos = (position[0], position[1] - offs)
|
||||
bomb_pos = (position[0] + offs * 1.1, position[1])
|
||||
pickup_pos = (position[0], position[1] + offs)
|
||||
extra_pos_1 = None
|
||||
extra_pos_2 = None
|
||||
self._force_hide_button_names = False
|
||||
|
||||
if show_title:
|
||||
self._title_text_pos_top = (
|
||||
position[0],
|
||||
position[1] + 139.0 * scale,
|
||||
)
|
||||
self._title_text_pos_bottom = (
|
||||
position[0],
|
||||
position[1] + 139.0 * scale,
|
||||
)
|
||||
clr = (1, 1, 1) if bright else (0.7, 0.7, 0.7)
|
||||
tval = ba.Lstr(
|
||||
value='${A}:', subs=[('${A}', ba.Lstr(resource='controlsText'))]
|
||||
)
|
||||
self._title_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'text': tval,
|
||||
'host_only': True,
|
||||
'scale': 1.1 * scale,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'maxwidth': 480,
|
||||
'v_align': 'center',
|
||||
'h_align': 'center',
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._title_text = None
|
||||
pos = jump_pos
|
||||
clr = (0.4, 1, 0.4)
|
||||
self._jump_image = ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'texture': ba.gettexture('buttonJump'),
|
||||
'absolute_scale': True,
|
||||
'host_only': True,
|
||||
'vr_depth': 10,
|
||||
'position': pos,
|
||||
'scale': (image_size, image_size),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
self._jump_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
clr = (0.2, 0.6, 1) if ouya else (1, 0.7, 0.3)
|
||||
pos = punch_pos
|
||||
self._punch_image = ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'texture': ba.gettexture('buttonPunch'),
|
||||
'absolute_scale': True,
|
||||
'host_only': True,
|
||||
'vr_depth': 10,
|
||||
'position': pos,
|
||||
'scale': (image_size, image_size),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
self._punch_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
pos = bomb_pos
|
||||
clr = (1, 0.3, 0.3)
|
||||
self._bomb_image = ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'texture': ba.gettexture('buttonBomb'),
|
||||
'absolute_scale': True,
|
||||
'host_only': True,
|
||||
'vr_depth': 10,
|
||||
'position': pos,
|
||||
'scale': (image_size, image_size),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
self._bomb_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'h_align': 'center',
|
||||
'v_align': 'top',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
pos = pickup_pos
|
||||
clr = (1, 0.8, 0.3) if ouya else (0.8, 0.5, 1)
|
||||
self._pickup_image = ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'texture': ba.gettexture('buttonPickUp'),
|
||||
'absolute_scale': True,
|
||||
'host_only': True,
|
||||
'vr_depth': 10,
|
||||
'position': pos,
|
||||
'scale': (image_size, image_size),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
self._pick_up_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
clr = (0.9, 0.9, 2.0, 1.0) if bright else (0.8, 0.8, 2.0, 1.0)
|
||||
self._run_text_pos_top = (position[0], position[1] - 135.0 * scale)
|
||||
self._run_text_pos_bottom = (position[0], position[1] - 172.0 * scale)
|
||||
sval = 1.0 * scale if ba.app.vr_mode else 0.8 * scale
|
||||
self._run_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'scale': sval,
|
||||
'host_only': True,
|
||||
'shadow': 1.0 if ba.app.vr_mode else 0.5,
|
||||
'flatness': 1.0,
|
||||
'maxwidth': 380,
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
clr = (1, 1, 1) if bright else (0.7, 0.7, 0.7)
|
||||
self._extra_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'scale': 0.8 * scale,
|
||||
'host_only': True,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'maxwidth': 380,
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
|
||||
if extra_pos_1 is not None:
|
||||
self._extra_image_1: ba.Node | None = ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'texture': ba.gettexture('nub'),
|
||||
'absolute_scale': True,
|
||||
'host_only': True,
|
||||
'vr_depth': 10,
|
||||
'position': extra_pos_1,
|
||||
'scale': (image_size, image_size),
|
||||
'color': (0.5, 0.5, 0.5),
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._extra_image_1 = None
|
||||
if extra_pos_2 is not None:
|
||||
self._extra_image_2: ba.Node | None = ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'texture': ba.gettexture('nub'),
|
||||
'absolute_scale': True,
|
||||
'host_only': True,
|
||||
'vr_depth': 10,
|
||||
'position': extra_pos_2,
|
||||
'scale': (image_size, image_size),
|
||||
'color': (0.5, 0.5, 0.5),
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._extra_image_2 = None
|
||||
|
||||
self._nodes = [
|
||||
self._bomb_image,
|
||||
self._bomb_text,
|
||||
self._punch_image,
|
||||
self._punch_text,
|
||||
self._jump_image,
|
||||
self._jump_text,
|
||||
self._pickup_image,
|
||||
self._pick_up_text,
|
||||
self._run_text,
|
||||
self._extra_text,
|
||||
]
|
||||
if show_title:
|
||||
assert self._title_text
|
||||
self._nodes.append(self._title_text)
|
||||
if self._extra_image_1 is not None:
|
||||
self._nodes.append(self._extra_image_1)
|
||||
if self._extra_image_2 is not None:
|
||||
self._nodes.append(self._extra_image_2)
|
||||
|
||||
# Start everything invisible.
|
||||
for node in self._nodes:
|
||||
node.opacity = 0.0
|
||||
|
||||
# Don't do anything until our delay has passed.
|
||||
ba.timer(delay, ba.WeakCall(self._start_updating))
|
||||
|
||||
@staticmethod
|
||||
def _meaningful_button_name(device: ba.InputDevice, button: int) -> str:
|
||||
"""Return a flattened string button name; empty for non-meaningful."""
|
||||
if not device.has_meaningful_button_names:
|
||||
return ''
|
||||
return device.get_button_name(button).evaluate()
|
||||
|
||||
def _start_updating(self) -> None:
|
||||
|
||||
# Ok, our delay has passed. Now lets periodically see if we can fade
|
||||
# in (if a touch-screen is present we only want to show up if gamepads
|
||||
# are connected, etc).
|
||||
# Also set up a timer so if we haven't faded in by the end of our
|
||||
# duration, abort.
|
||||
if self._lifespan is not None:
|
||||
self._cancel_timer = ba.Timer(
|
||||
self._lifespan,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage(immediate=True)),
|
||||
)
|
||||
self._fade_in_timer = ba.Timer(
|
||||
1.0, ba.WeakCall(self._check_fade_in), repeat=True
|
||||
)
|
||||
self._check_fade_in() # Do one check immediately.
|
||||
|
||||
def _check_fade_in(self) -> None:
|
||||
from ba.internal import get_device_value
|
||||
|
||||
# If we have a touchscreen, we only fade in if we have a player with
|
||||
# an input device that is *not* the touchscreen.
|
||||
# (otherwise it is confusing to see the touchscreen buttons right
|
||||
# next to our display buttons)
|
||||
touchscreen: ba.InputDevice | None = ba.internal.getinputdevice(
|
||||
'TouchScreen', '#1', doraise=False
|
||||
)
|
||||
|
||||
if touchscreen is not None:
|
||||
# We look at the session's players; not the activity's.
|
||||
# We want to get ones who are still in the process of
|
||||
# selecting a character, etc.
|
||||
input_devices = [
|
||||
p.inputdevice for p in ba.getsession().sessionplayers
|
||||
]
|
||||
input_devices = [
|
||||
i for i in input_devices if i and i is not touchscreen
|
||||
]
|
||||
fade_in = False
|
||||
if input_devices:
|
||||
# Only count this one if it has non-empty button names
|
||||
# (filters out wiimotes, the remote-app, etc).
|
||||
for device in input_devices:
|
||||
for name in (
|
||||
'buttonPunch',
|
||||
'buttonJump',
|
||||
'buttonBomb',
|
||||
'buttonPickUp',
|
||||
):
|
||||
if (
|
||||
self._meaningful_button_name(
|
||||
device, get_device_value(device, name)
|
||||
)
|
||||
!= ''
|
||||
):
|
||||
fade_in = True
|
||||
break
|
||||
if fade_in:
|
||||
break # No need to keep looking.
|
||||
else:
|
||||
# No touch-screen; fade in immediately.
|
||||
fade_in = True
|
||||
if fade_in:
|
||||
self._cancel_timer = None # Didn't need this.
|
||||
self._fade_in_timer = None # Done with this.
|
||||
self._fade_in()
|
||||
|
||||
def _fade_in(self) -> None:
|
||||
for node in self._nodes:
|
||||
ba.animate(node, 'opacity', {0: 0.0, 2.0: 1.0})
|
||||
|
||||
# If we were given a lifespan, transition out after it.
|
||||
if self._lifespan is not None:
|
||||
ba.timer(
|
||||
self._lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage())
|
||||
)
|
||||
self._update()
|
||||
self._update_timer = ba.Timer(
|
||||
1.0, ba.WeakCall(self._update), repeat=True
|
||||
)
|
||||
|
||||
def _update(self) -> None:
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
from ba.internal import get_device_value, get_remote_app_name
|
||||
|
||||
if self._dead:
|
||||
return
|
||||
punch_button_names = set()
|
||||
jump_button_names = set()
|
||||
pickup_button_names = set()
|
||||
bomb_button_names = set()
|
||||
|
||||
# We look at the session's players; not the activity's - we want to
|
||||
# get ones who are still in the process of selecting a character, etc.
|
||||
input_devices = [p.inputdevice for p in ba.getsession().sessionplayers]
|
||||
input_devices = [i for i in input_devices if i]
|
||||
|
||||
# If there's no players with input devices yet, try to default to
|
||||
# showing keyboard controls.
|
||||
if not input_devices:
|
||||
kbd = ba.internal.getinputdevice('Keyboard', '#1', doraise=False)
|
||||
if kbd is not None:
|
||||
input_devices.append(kbd)
|
||||
|
||||
# We word things specially if we have nothing but keyboards.
|
||||
all_keyboards = input_devices and all(
|
||||
i.name == 'Keyboard' for i in input_devices
|
||||
)
|
||||
only_remote = len(input_devices) == 1 and all(
|
||||
i.name == 'Amazon Fire TV Remote' for i in input_devices
|
||||
)
|
||||
|
||||
right_button_names = set()
|
||||
left_button_names = set()
|
||||
up_button_names = set()
|
||||
down_button_names = set()
|
||||
|
||||
# For each player in the game with an input device,
|
||||
# get the name of the button for each of these 4 actions.
|
||||
# If any of them are uniform across all devices, display the name.
|
||||
for device in input_devices:
|
||||
# We only care about movement buttons in the case of keyboards.
|
||||
if all_keyboards:
|
||||
right_button_names.add(
|
||||
device.get_button_name(
|
||||
get_device_value(device, 'buttonRight')
|
||||
)
|
||||
)
|
||||
left_button_names.add(
|
||||
device.get_button_name(
|
||||
get_device_value(device, 'buttonLeft')
|
||||
)
|
||||
)
|
||||
down_button_names.add(
|
||||
device.get_button_name(
|
||||
get_device_value(device, 'buttonDown')
|
||||
)
|
||||
)
|
||||
up_button_names.add(
|
||||
device.get_button_name(get_device_value(device, 'buttonUp'))
|
||||
)
|
||||
|
||||
# Ignore empty values; things like the remote app or
|
||||
# wiimotes can return these.
|
||||
bname = self._meaningful_button_name(
|
||||
device, get_device_value(device, 'buttonPunch')
|
||||
)
|
||||
if bname != '':
|
||||
punch_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device, get_device_value(device, 'buttonJump')
|
||||
)
|
||||
if bname != '':
|
||||
jump_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device, get_device_value(device, 'buttonBomb')
|
||||
)
|
||||
if bname != '':
|
||||
bomb_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device, get_device_value(device, 'buttonPickUp')
|
||||
)
|
||||
if bname != '':
|
||||
pickup_button_names.add(bname)
|
||||
|
||||
# If we have no values yet, we may want to throw out some sane
|
||||
# defaults.
|
||||
if all(
|
||||
not lst
|
||||
for lst in (
|
||||
punch_button_names,
|
||||
jump_button_names,
|
||||
bomb_button_names,
|
||||
pickup_button_names,
|
||||
)
|
||||
):
|
||||
# Otherwise on android show standard buttons.
|
||||
if ba.app.platform == 'android':
|
||||
punch_button_names.add('X')
|
||||
jump_button_names.add('A')
|
||||
bomb_button_names.add('B')
|
||||
pickup_button_names.add('Y')
|
||||
|
||||
run_text = ba.Lstr(
|
||||
value='${R}: ${B}',
|
||||
subs=[
|
||||
('${R}', ba.Lstr(resource='runText')),
|
||||
(
|
||||
'${B}',
|
||||
ba.Lstr(
|
||||
resource='holdAnyKeyText'
|
||||
if all_keyboards
|
||||
else 'holdAnyButtonText'
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# If we're all keyboards, lets show move keys too.
|
||||
if (
|
||||
all_keyboards
|
||||
and len(up_button_names) == 1
|
||||
and len(down_button_names) == 1
|
||||
and len(left_button_names) == 1
|
||||
and len(right_button_names) == 1
|
||||
):
|
||||
up_text = list(up_button_names)[0]
|
||||
down_text = list(down_button_names)[0]
|
||||
left_text = list(left_button_names)[0]
|
||||
right_text = list(right_button_names)[0]
|
||||
run_text = ba.Lstr(
|
||||
value='${M}: ${U}, ${L}, ${D}, ${R}\n${RUN}',
|
||||
subs=[
|
||||
('${M}', ba.Lstr(resource='moveText')),
|
||||
('${U}', up_text),
|
||||
('${L}', left_text),
|
||||
('${D}', down_text),
|
||||
('${R}', right_text),
|
||||
('${RUN}', run_text),
|
||||
],
|
||||
)
|
||||
|
||||
if self._force_hide_button_names:
|
||||
jump_button_names.clear()
|
||||
punch_button_names.clear()
|
||||
bomb_button_names.clear()
|
||||
pickup_button_names.clear()
|
||||
|
||||
self._run_text.text = run_text
|
||||
w_text: ba.Lstr | str
|
||||
if only_remote and self._lifespan is None:
|
||||
w_text = ba.Lstr(
|
||||
resource='fireTVRemoteWarningText',
|
||||
subs=[('${REMOTE_APP_NAME}', get_remote_app_name())],
|
||||
)
|
||||
else:
|
||||
w_text = ''
|
||||
self._extra_text.text = w_text
|
||||
if len(punch_button_names) == 1:
|
||||
self._punch_text.text = list(punch_button_names)[0]
|
||||
else:
|
||||
self._punch_text.text = ''
|
||||
|
||||
if len(jump_button_names) == 1:
|
||||
tval = list(jump_button_names)[0]
|
||||
else:
|
||||
tval = ''
|
||||
self._jump_text.text = tval
|
||||
if tval == '':
|
||||
self._run_text.position = self._run_text_pos_top
|
||||
self._extra_text.position = (
|
||||
self._run_text_pos_top[0],
|
||||
self._run_text_pos_top[1] - 50,
|
||||
)
|
||||
else:
|
||||
self._run_text.position = self._run_text_pos_bottom
|
||||
self._extra_text.position = (
|
||||
self._run_text_pos_bottom[0],
|
||||
self._run_text_pos_bottom[1] - 50,
|
||||
)
|
||||
if len(bomb_button_names) == 1:
|
||||
self._bomb_text.text = list(bomb_button_names)[0]
|
||||
else:
|
||||
self._bomb_text.text = ''
|
||||
|
||||
# Also move our title up/down depending on if this is shown.
|
||||
if len(pickup_button_names) == 1:
|
||||
self._pick_up_text.text = list(pickup_button_names)[0]
|
||||
if self._title_text is not None:
|
||||
self._title_text.position = self._title_text_pos_top
|
||||
else:
|
||||
self._pick_up_text.text = ''
|
||||
if self._title_text is not None:
|
||||
self._title_text.position = self._title_text_pos_bottom
|
||||
|
||||
def _die(self) -> None:
|
||||
for node in self._nodes:
|
||||
node.delete()
|
||||
self._nodes = []
|
||||
self._update_timer = None
|
||||
self._dead = True
|
||||
|
||||
def exists(self) -> bool:
|
||||
return not self._dead
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
if isinstance(msg, ba.DieMessage):
|
||||
if msg.immediate:
|
||||
self._die()
|
||||
else:
|
||||
# If they don't need immediate,
|
||||
# fade out our nodes and die later.
|
||||
for node in self._nodes:
|
||||
ba.animate(node, 'opacity', {0: node.opacity, 3.0: 0.0})
|
||||
ba.timer(3.1, ba.WeakCall(self._die))
|
||||
return None
|
||||
return super().handlemessage(msg)
|
||||
380
dist/ba_data/python/bastd/actor/flag.py
vendored
Normal file
380
dist/ba_data/python/bastd/actor/flag.py
vendored
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Implements a flag used for marking bases, capture-the-flag games, etc."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
from bastd.gameutils import SharedObjects
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
class FlagFactory:
|
||||
"""Wraps up media and other resources used by `Flag`s.
|
||||
|
||||
Category: **Gameplay Classes**
|
||||
|
||||
A single instance of this is shared between all flags
|
||||
and can be retrieved via FlagFactory.get().
|
||||
"""
|
||||
|
||||
flagmaterial: ba.Material
|
||||
"""The ba.Material applied to all `Flag`s."""
|
||||
|
||||
impact_sound: ba.Sound
|
||||
"""The ba.Sound used when a `Flag` hits the ground."""
|
||||
|
||||
skid_sound: ba.Sound
|
||||
"""The ba.Sound used when a `Flag` skids along the ground."""
|
||||
|
||||
no_hit_material: ba.Material
|
||||
"""A ba.Material that prevents contact with most objects;
|
||||
applied to 'non-touchable' flags."""
|
||||
|
||||
flag_texture: ba.Texture
|
||||
"""The ba.Texture for flags."""
|
||||
|
||||
_STORENAME = ba.storagename()
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Instantiate a `FlagFactory`.
|
||||
|
||||
You shouldn't need to do this; call FlagFactory.get() to
|
||||
get a shared instance.
|
||||
"""
|
||||
shared = SharedObjects.get()
|
||||
self.flagmaterial = ba.Material()
|
||||
self.flagmaterial.add_actions(
|
||||
conditions=(
|
||||
('we_are_younger_than', 100),
|
||||
'and',
|
||||
('they_have_material', shared.object_material),
|
||||
),
|
||||
actions=('modify_node_collision', 'collide', False),
|
||||
)
|
||||
|
||||
self.flagmaterial.add_actions(
|
||||
conditions=(
|
||||
'they_have_material',
|
||||
shared.footing_material,
|
||||
),
|
||||
actions=(
|
||||
('message', 'our_node', 'at_connect', 'footing', 1),
|
||||
('message', 'our_node', 'at_disconnect', 'footing', -1),
|
||||
),
|
||||
)
|
||||
|
||||
self.impact_sound = ba.getsound('metalHit')
|
||||
self.skid_sound = ba.getsound('metalSkid')
|
||||
self.flagmaterial.add_actions(
|
||||
conditions=(
|
||||
'they_have_material',
|
||||
shared.footing_material,
|
||||
),
|
||||
actions=(
|
||||
('impact_sound', self.impact_sound, 2, 5),
|
||||
('skid_sound', self.skid_sound, 2, 5),
|
||||
),
|
||||
)
|
||||
|
||||
self.no_hit_material = ba.Material()
|
||||
self.no_hit_material.add_actions(
|
||||
conditions=(
|
||||
('they_have_material', shared.pickup_material),
|
||||
'or',
|
||||
('they_have_material', shared.attack_material),
|
||||
),
|
||||
actions=('modify_part_collision', 'collide', False),
|
||||
)
|
||||
|
||||
# We also don't want anything moving it.
|
||||
self.no_hit_material.add_actions(
|
||||
conditions=(
|
||||
('they_have_material', shared.object_material),
|
||||
'or',
|
||||
('they_dont_have_material', shared.footing_material),
|
||||
),
|
||||
actions=(
|
||||
('modify_part_collision', 'collide', False),
|
||||
('modify_part_collision', 'physical', False),
|
||||
),
|
||||
)
|
||||
|
||||
self.flag_texture = ba.gettexture("pixieIcon")
|
||||
|
||||
@classmethod
|
||||
def get(cls) -> FlagFactory:
|
||||
"""Get/create a shared `FlagFactory` instance."""
|
||||
activity = ba.getactivity()
|
||||
factory = activity.customdata.get(cls._STORENAME)
|
||||
if factory is None:
|
||||
factory = FlagFactory()
|
||||
activity.customdata[cls._STORENAME] = factory
|
||||
assert isinstance(factory, FlagFactory)
|
||||
return factory
|
||||
|
||||
|
||||
@dataclass
|
||||
class FlagPickedUpMessage:
|
||||
"""A message saying a `Flag` has been picked up.
|
||||
|
||||
Category: **Message Classes**
|
||||
"""
|
||||
|
||||
flag: Flag
|
||||
"""The `Flag` that has been picked up."""
|
||||
|
||||
node: ba.Node
|
||||
"""The ba.Node doing the picking up."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class FlagDiedMessage:
|
||||
"""A message saying a `Flag` has died.
|
||||
|
||||
Category: **Message Classes**
|
||||
"""
|
||||
|
||||
flag: Flag
|
||||
"""The `Flag` that died."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class FlagDroppedMessage:
|
||||
"""A message saying a `Flag` has been dropped.
|
||||
|
||||
Category: **Message Classes**
|
||||
"""
|
||||
|
||||
flag: Flag
|
||||
"""The `Flag` that was dropped."""
|
||||
|
||||
node: ba.Node
|
||||
"""The ba.Node that was holding it."""
|
||||
|
||||
|
||||
class Flag(ba.Actor):
|
||||
"""A flag; used in games such as capture-the-flag or king-of-the-hill.
|
||||
|
||||
Category: **Gameplay Classes**
|
||||
|
||||
Can be stationary or carry-able by players.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
position: Sequence[float] = (0.0, 1.0, 0.0),
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0),
|
||||
materials: Sequence[ba.Material] | None = None,
|
||||
touchable: bool = True,
|
||||
dropped_timeout: int | None = None,
|
||||
):
|
||||
"""Instantiate a flag.
|
||||
|
||||
If 'touchable' is False, the flag will only touch terrain;
|
||||
useful for things like king-of-the-hill where players should
|
||||
not be moving the flag around.
|
||||
|
||||
'materials can be a list of extra `ba.Material`s to apply to the flag.
|
||||
|
||||
If 'dropped_timeout' is provided (in seconds), the flag will die
|
||||
after remaining untouched for that long once it has been moved
|
||||
from its initial position.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
|
||||
self._initial_position: Sequence[float] | None = None
|
||||
self._has_moved = False
|
||||
shared = SharedObjects.get()
|
||||
factory = FlagFactory.get()
|
||||
|
||||
if materials is None:
|
||||
materials = []
|
||||
elif not isinstance(materials, list):
|
||||
# In case they passed a tuple or whatnot.
|
||||
materials = list(materials)
|
||||
if not touchable:
|
||||
materials = [factory.no_hit_material] + materials
|
||||
|
||||
finalmaterials = [
|
||||
shared.object_material,
|
||||
factory.flagmaterial,
|
||||
] + materials
|
||||
self.node = ba.newnode(
|
||||
'flag',
|
||||
attrs={
|
||||
'position': (position[0], position[1] + 0.75, position[2]),
|
||||
'color_texture': factory.flag_texture,
|
||||
'color': color,
|
||||
'materials': finalmaterials,
|
||||
},
|
||||
delegate=self,
|
||||
)
|
||||
|
||||
if dropped_timeout is not None:
|
||||
dropped_timeout = int(dropped_timeout)
|
||||
self._dropped_timeout = dropped_timeout
|
||||
self._counter: ba.Node | None
|
||||
if self._dropped_timeout is not None:
|
||||
self._count = self._dropped_timeout
|
||||
self._tick_timer = ba.Timer(
|
||||
1.0, call=ba.WeakCall(self._tick), repeat=True
|
||||
)
|
||||
self._counter = ba.newnode(
|
||||
'text',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'in_world': True,
|
||||
'color': (1, 1, 1, 0.7),
|
||||
'scale': 0.015,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center',
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._counter = None
|
||||
|
||||
self._held_count = 0
|
||||
self._score_text: ba.Node | None = None
|
||||
self._score_text_hide_timer: ba.Timer | None = None
|
||||
|
||||
def _tick(self) -> None:
|
||||
if self.node:
|
||||
|
||||
# Grab our initial position after one tick (in case we fall).
|
||||
if self._initial_position is None:
|
||||
self._initial_position = self.node.position
|
||||
|
||||
# Keep track of when we first move; we don't count down
|
||||
# until then.
|
||||
if not self._has_moved:
|
||||
nodepos = self.node.position
|
||||
if (
|
||||
max(
|
||||
abs(nodepos[i] - self._initial_position[i])
|
||||
for i in list(range(3))
|
||||
)
|
||||
> 1.0
|
||||
):
|
||||
self._has_moved = True
|
||||
|
||||
if self._held_count > 0 or not self._has_moved:
|
||||
assert self._dropped_timeout is not None
|
||||
assert self._counter
|
||||
self._count = self._dropped_timeout
|
||||
self._counter.text = ''
|
||||
else:
|
||||
self._count -= 1
|
||||
if self._count <= 10:
|
||||
nodepos = self.node.position
|
||||
assert self._counter
|
||||
self._counter.position = (
|
||||
nodepos[0],
|
||||
nodepos[1] + 1.3,
|
||||
nodepos[2],
|
||||
)
|
||||
self._counter.text = str(self._count)
|
||||
if self._count < 1:
|
||||
self.handlemessage(ba.DieMessage())
|
||||
else:
|
||||
assert self._counter
|
||||
self._counter.text = ''
|
||||
|
||||
def _hide_score_text(self) -> None:
|
||||
assert self._score_text is not None
|
||||
assert isinstance(self._score_text.scale, float)
|
||||
ba.animate(
|
||||
self._score_text, 'scale', {0: self._score_text.scale, 0.2: 0}
|
||||
)
|
||||
|
||||
def set_score_text(self, text: str) -> None:
|
||||
"""Show a message over the flag; handy for scores."""
|
||||
if not self.node:
|
||||
return
|
||||
if not self._score_text:
|
||||
start_scale = 0.0
|
||||
math = ba.newnode(
|
||||
'math',
|
||||
owner=self.node,
|
||||
attrs={'input1': (0, 1.4, 0), 'operation': 'add'},
|
||||
)
|
||||
self.node.connectattr('position', math, 'input2')
|
||||
self._score_text = ba.newnode(
|
||||
'text',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'text': text,
|
||||
'in_world': True,
|
||||
'scale': 0.02,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center',
|
||||
},
|
||||
)
|
||||
math.connectattr('output', self._score_text, 'position')
|
||||
else:
|
||||
assert isinstance(self._score_text.scale, float)
|
||||
start_scale = self._score_text.scale
|
||||
self._score_text.text = text
|
||||
self._score_text.color = ba.safecolor(self.node.color)
|
||||
ba.animate(self._score_text, 'scale', {0: start_scale, 0.2: 0.02})
|
||||
self._score_text_hide_timer = ba.Timer(
|
||||
1.0, ba.WeakCall(self._hide_score_text)
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
if isinstance(msg, ba.DieMessage):
|
||||
if self.node:
|
||||
self.node.delete()
|
||||
if not msg.immediate:
|
||||
self.activity.handlemessage(FlagDiedMessage(self))
|
||||
elif isinstance(msg, ba.HitMessage):
|
||||
assert self.node
|
||||
assert msg.force_direction is not None
|
||||
self.node.handlemessage(
|
||||
'impulse',
|
||||
msg.pos[0],
|
||||
msg.pos[1],
|
||||
msg.pos[2],
|
||||
msg.velocity[0],
|
||||
msg.velocity[1],
|
||||
msg.velocity[2],
|
||||
msg.magnitude,
|
||||
msg.velocity_magnitude,
|
||||
msg.radius,
|
||||
0,
|
||||
msg.force_direction[0],
|
||||
msg.force_direction[1],
|
||||
msg.force_direction[2],
|
||||
)
|
||||
elif isinstance(msg, ba.PickedUpMessage):
|
||||
self._held_count += 1
|
||||
if self._held_count == 1 and self._counter is not None:
|
||||
self._counter.text = ''
|
||||
self.activity.handlemessage(FlagPickedUpMessage(self, msg.node))
|
||||
elif isinstance(msg, ba.DroppedMessage):
|
||||
self._held_count -= 1
|
||||
if self._held_count < 0:
|
||||
print('Flag held count < 0.')
|
||||
self._held_count = 0
|
||||
self.activity.handlemessage(FlagDroppedMessage(self, msg.node))
|
||||
else:
|
||||
super().handlemessage(msg)
|
||||
|
||||
@staticmethod
|
||||
def project_stand(pos: Sequence[float]) -> None:
|
||||
"""Project a flag-stand onto the ground at the given position.
|
||||
|
||||
Useful for games such as capture-the-flag to show where a
|
||||
movable flag originated from.
|
||||
"""
|
||||
assert len(pos) == 3
|
||||
ba.emitfx(position=pos, emit_type='flag_stand')
|
||||
174
dist/ba_data/python/bastd/actor/image.py
vendored
Normal file
174
dist/ba_data/python/bastd/actor/image.py
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines Actor(s)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
class Image(ba.Actor):
|
||||
"""Just a wrapped up image node with a few tricks up its sleeve."""
|
||||
|
||||
class Transition(Enum):
|
||||
"""Transition types we support."""
|
||||
|
||||
FADE_IN = 'fade_in'
|
||||
IN_RIGHT = 'in_right'
|
||||
IN_LEFT = 'in_left'
|
||||
IN_BOTTOM = 'in_bottom'
|
||||
IN_BOTTOM_SLOW = 'in_bottom_slow'
|
||||
IN_TOP_SLOW = 'in_top_slow'
|
||||
|
||||
class Attach(Enum):
|
||||
"""Attach types we support."""
|
||||
|
||||
CENTER = 'center'
|
||||
TOP_CENTER = 'topCenter'
|
||||
TOP_LEFT = 'topLeft'
|
||||
BOTTOM_CENTER = 'bottomCenter'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
texture: ba.Texture | dict[str, Any],
|
||||
position: tuple[float, float] = (0, 0),
|
||||
transition: Transition | None = None,
|
||||
transition_delay: float = 0.0,
|
||||
attach: Attach = Attach.CENTER,
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
|
||||
scale: tuple[float, float] = (100.0, 100.0),
|
||||
transition_out_delay: float | None = None,
|
||||
model_opaque: ba.Model | None = None,
|
||||
model_transparent: ba.Model | None = None,
|
||||
vr_depth: float = 0.0,
|
||||
host_only: bool = False,
|
||||
front: bool = False,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
super().__init__()
|
||||
|
||||
# If they provided a dict as texture, assume its an icon.
|
||||
# otherwise its just a texture value itself.
|
||||
mask_texture: ba.Texture | None
|
||||
if isinstance(texture, dict):
|
||||
tint_color = texture['tint_color']
|
||||
tint2_color = texture['tint2_color']
|
||||
tint_texture = texture['tint_texture']
|
||||
texture = texture['texture']
|
||||
mask_texture = ba.gettexture('characterIconMask')
|
||||
else:
|
||||
tint_color = (1, 1, 1)
|
||||
tint2_color = None
|
||||
tint_texture = None
|
||||
mask_texture = None
|
||||
|
||||
self.node = ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'texture': texture,
|
||||
'tint_color': tint_color,
|
||||
'tint_texture': tint_texture,
|
||||
'position': position,
|
||||
'vr_depth': vr_depth,
|
||||
'scale': scale,
|
||||
'mask_texture': mask_texture,
|
||||
'color': color,
|
||||
'absolute_scale': True,
|
||||
'host_only': host_only,
|
||||
'front': front,
|
||||
'attach': attach.value,
|
||||
},
|
||||
delegate=self,
|
||||
)
|
||||
|
||||
if model_opaque is not None:
|
||||
self.node.model_opaque = model_opaque
|
||||
if model_transparent is not None:
|
||||
self.node.model_transparent = model_transparent
|
||||
if tint2_color is not None:
|
||||
self.node.tint2_color = tint2_color
|
||||
if transition is self.Transition.FADE_IN:
|
||||
keys = {transition_delay: 0, transition_delay + 0.5: color[3]}
|
||||
if transition_out_delay is not None:
|
||||
keys[transition_delay + transition_out_delay] = color[3]
|
||||
keys[transition_delay + transition_out_delay + 0.5] = 0
|
||||
ba.animate(self.node, 'opacity', keys)
|
||||
cmb = self.position_combine = ba.newnode(
|
||||
'combine', owner=self.node, attrs={'size': 2}
|
||||
)
|
||||
if transition is self.Transition.IN_RIGHT:
|
||||
keys = {
|
||||
transition_delay: position[0] + 1200,
|
||||
transition_delay + 0.2: position[0],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
ba.animate(cmb, 'input0', keys)
|
||||
cmb.input1 = position[1]
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
elif transition is self.Transition.IN_LEFT:
|
||||
keys = {
|
||||
transition_delay: position[0] - 1200,
|
||||
transition_delay + 0.2: position[0],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
if transition_out_delay is not None:
|
||||
keys[transition_delay + transition_out_delay] = position[0]
|
||||
keys[transition_delay + transition_out_delay + 200] = (
|
||||
-position[0] - 1200
|
||||
)
|
||||
o_keys[transition_delay + transition_out_delay + 0.15] = 1.0
|
||||
o_keys[transition_delay + transition_out_delay + 0.2] = 0.0
|
||||
ba.animate(cmb, 'input0', keys)
|
||||
cmb.input1 = position[1]
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
elif transition is self.Transition.IN_BOTTOM_SLOW:
|
||||
keys = {transition_delay: -400, transition_delay + 3.5: position[1]}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 2.0: 1.0}
|
||||
cmb.input0 = position[0]
|
||||
ba.animate(cmb, 'input1', keys)
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
elif transition is self.Transition.IN_BOTTOM:
|
||||
keys = {transition_delay: -400, transition_delay + 0.2: position[1]}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
if transition_out_delay is not None:
|
||||
keys[transition_delay + transition_out_delay] = position[1]
|
||||
keys[transition_delay + transition_out_delay + 0.2] = -400
|
||||
o_keys[transition_delay + transition_out_delay + 0.15] = 1.0
|
||||
o_keys[transition_delay + transition_out_delay + 0.2] = 0.0
|
||||
cmb.input0 = position[0]
|
||||
ba.animate(cmb, 'input1', keys)
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
elif transition is self.Transition.IN_TOP_SLOW:
|
||||
keys = {transition_delay: 400, transition_delay + 3.5: position[1]}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 1.0: 1.0}
|
||||
cmb.input0 = position[0]
|
||||
ba.animate(cmb, 'input1', keys)
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
else:
|
||||
assert transition is self.Transition.FADE_IN or transition is None
|
||||
cmb.input0 = position[0]
|
||||
cmb.input1 = position[1]
|
||||
cmb.connectattr('output', self.node, 'position')
|
||||
|
||||
# If we're transitioning out, die at the end of it.
|
||||
if transition_out_delay is not None:
|
||||
ba.timer(
|
||||
transition_delay + transition_out_delay + 1.0,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage()),
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
if isinstance(msg, ba.DieMessage):
|
||||
if self.node:
|
||||
self.node.delete()
|
||||
return None
|
||||
return super().handlemessage(msg)
|
||||
107
dist/ba_data/python/bastd/actor/onscreencountdown.py
vendored
Normal file
107
dist/ba_data/python/bastd/actor/onscreencountdown.py
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines Actor Type(s)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
class OnScreenCountdown(ba.Actor):
|
||||
"""A Handy On-Screen Timer.
|
||||
|
||||
category: Gameplay Classes
|
||||
|
||||
Useful for time-based games that count down to zero.
|
||||
"""
|
||||
|
||||
def __init__(self, duration: int, endcall: Callable[[], Any] | None = None):
|
||||
"""Duration is provided in seconds."""
|
||||
super().__init__()
|
||||
self._timeremaining = duration
|
||||
self._ended = False
|
||||
self._endcall = endcall
|
||||
self.node = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': 'center',
|
||||
'h_align': 'center',
|
||||
'color': (1, 1, 0.5, 1),
|
||||
'flatness': 0.5,
|
||||
'shadow': 0.5,
|
||||
'position': (0, -70),
|
||||
'scale': 1.4,
|
||||
'text': '',
|
||||
},
|
||||
)
|
||||
self.inputnode = ba.newnode(
|
||||
'timedisplay',
|
||||
attrs={
|
||||
'time2': duration * 1000,
|
||||
'timemax': duration * 1000,
|
||||
'timemin': 0,
|
||||
},
|
||||
)
|
||||
self.inputnode.connectattr('output', self.node, 'text')
|
||||
self._countdownsounds = {
|
||||
10: ba.getsound('announceTen'),
|
||||
9: ba.getsound('announceNine'),
|
||||
8: ba.getsound('announceEight'),
|
||||
7: ba.getsound('announceSeven'),
|
||||
6: ba.getsound('announceSix'),
|
||||
5: ba.getsound('announceFive'),
|
||||
4: ba.getsound('announceFour'),
|
||||
3: ba.getsound('announceThree'),
|
||||
2: ba.getsound('announceTwo'),
|
||||
1: ba.getsound('announceOne'),
|
||||
}
|
||||
self._timer: ba.Timer | None = None
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the timer."""
|
||||
globalsnode = ba.getactivity().globalsnode
|
||||
globalsnode.connectattr('time', self.inputnode, 'time1')
|
||||
self.inputnode.time2 = (
|
||||
globalsnode.time + (self._timeremaining + 1) * 1000
|
||||
)
|
||||
self._timer = ba.Timer(1.0, self._update, repeat=True)
|
||||
|
||||
def on_expire(self) -> None:
|
||||
super().on_expire()
|
||||
|
||||
# Release callbacks/refs.
|
||||
self._endcall = None
|
||||
|
||||
def _update(self, forcevalue: int | None = None) -> None:
|
||||
if forcevalue is not None:
|
||||
tval = forcevalue
|
||||
else:
|
||||
self._timeremaining = max(0, self._timeremaining - 1)
|
||||
tval = self._timeremaining
|
||||
|
||||
# if there's a countdown sound for this time that we
|
||||
# haven't played yet, play it
|
||||
if tval == 10:
|
||||
assert self.node
|
||||
assert isinstance(self.node.scale, float)
|
||||
self.node.scale *= 1.2
|
||||
cmb = ba.newnode('combine', owner=self.node, attrs={'size': 4})
|
||||
cmb.connectattr('output', self.node, 'color')
|
||||
ba.animate(cmb, 'input0', {0: 1.0, 0.15: 1.0}, loop=True)
|
||||
ba.animate(cmb, 'input1', {0: 1.0, 0.15: 0.5}, loop=True)
|
||||
ba.animate(cmb, 'input2', {0: 0.1, 0.15: 0.0}, loop=True)
|
||||
cmb.input3 = 1.0
|
||||
if tval <= 10 and not self._ended:
|
||||
ba.playsound(ba.getsound('tick'))
|
||||
if tval in self._countdownsounds:
|
||||
ba.playsound(self._countdownsounds[tval])
|
||||
if tval <= 0 and not self._ended:
|
||||
self._ended = True
|
||||
if self._endcall is not None:
|
||||
self._endcall()
|
||||
131
dist/ba_data/python/bastd/actor/onscreentimer.py
vendored
Normal file
131
dist/ba_data/python/bastd/actor/onscreentimer.py
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines Actor(s)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, overload
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Literal
|
||||
|
||||
|
||||
class OnScreenTimer(ba.Actor):
|
||||
"""A handy on-screen timer.
|
||||
|
||||
category: Gameplay Classes
|
||||
|
||||
Useful for time-based games where time increases.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._starttime_ms: int | None = None
|
||||
self.node = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': 'center',
|
||||
'h_align': 'center',
|
||||
'color': (1, 1, 0.5, 1),
|
||||
'flatness': 0.5,
|
||||
'shadow': 0.5,
|
||||
'position': (0, -70),
|
||||
'scale': 1.4,
|
||||
'text': '',
|
||||
},
|
||||
)
|
||||
self.inputnode = ba.newnode(
|
||||
'timedisplay', attrs={'timemin': 0, 'showsubseconds': True}
|
||||
)
|
||||
self.inputnode.connectattr('output', self.node, 'text')
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the timer."""
|
||||
tval = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
|
||||
assert isinstance(tval, int)
|
||||
self._starttime_ms = tval
|
||||
self.inputnode.time1 = self._starttime_ms
|
||||
ba.getactivity().globalsnode.connectattr(
|
||||
'time', self.inputnode, 'time2'
|
||||
)
|
||||
|
||||
def has_started(self) -> bool:
|
||||
"""Return whether this timer has started yet."""
|
||||
return self._starttime_ms is not None
|
||||
|
||||
def stop(
|
||||
self,
|
||||
endtime: int | float | None = None,
|
||||
timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS,
|
||||
) -> None:
|
||||
"""End the timer.
|
||||
|
||||
If 'endtime' is not None, it is used when calculating
|
||||
the final display time; otherwise the current time is used.
|
||||
|
||||
'timeformat' applies to endtime and can be SECONDS or MILLISECONDS
|
||||
"""
|
||||
if endtime is None:
|
||||
endtime = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
|
||||
timeformat = ba.TimeFormat.MILLISECONDS
|
||||
|
||||
if self._starttime_ms is None:
|
||||
print('Warning: OnScreenTimer.stop() called without start() first')
|
||||
else:
|
||||
endtime_ms: int
|
||||
if timeformat is ba.TimeFormat.SECONDS:
|
||||
endtime_ms = int(endtime * 1000)
|
||||
elif timeformat is ba.TimeFormat.MILLISECONDS:
|
||||
assert isinstance(endtime, int)
|
||||
endtime_ms = endtime
|
||||
else:
|
||||
raise ValueError(f'invalid timeformat: {timeformat}')
|
||||
|
||||
self.inputnode.timemax = endtime_ms - self._starttime_ms
|
||||
|
||||
# Overloads so type checker knows our exact return type based in args.
|
||||
@overload
|
||||
def getstarttime(
|
||||
self, timeformat: Literal[ba.TimeFormat.SECONDS] = ba.TimeFormat.SECONDS
|
||||
) -> float:
|
||||
...
|
||||
|
||||
@overload
|
||||
def getstarttime(
|
||||
self, timeformat: Literal[ba.TimeFormat.MILLISECONDS]
|
||||
) -> int:
|
||||
...
|
||||
|
||||
def getstarttime(
|
||||
self, timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS
|
||||
) -> int | float:
|
||||
"""Return the sim-time when start() was called.
|
||||
|
||||
Time will be returned in seconds if timeformat is SECONDS or
|
||||
milliseconds if it is MILLISECONDS.
|
||||
"""
|
||||
val_ms: Any
|
||||
if self._starttime_ms is None:
|
||||
print('WARNING: getstarttime() called on un-started timer')
|
||||
val_ms = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
|
||||
else:
|
||||
val_ms = self._starttime_ms
|
||||
assert isinstance(val_ms, int)
|
||||
if timeformat is ba.TimeFormat.SECONDS:
|
||||
return 0.001 * val_ms
|
||||
if timeformat is ba.TimeFormat.MILLISECONDS:
|
||||
return val_ms
|
||||
raise ValueError(f'invalid timeformat: {timeformat}')
|
||||
|
||||
@property
|
||||
def starttime(self) -> float:
|
||||
"""Shortcut for start time in seconds."""
|
||||
return self.getstarttime()
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
# if we're asked to die, just kill our node/timer
|
||||
if isinstance(msg, ba.DieMessage):
|
||||
if self.node:
|
||||
self.node.delete()
|
||||
309
dist/ba_data/python/bastd/actor/playerspaz.py
vendored
Normal file
309
dist/ba_data/python/bastd/actor/playerspaz.py
vendored
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to player-controlled Spazzes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, TypeVar, overload
|
||||
|
||||
import ba
|
||||
from bastd.actor.spaz import Spaz
|
||||
from spazmod import modifyspaz
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence, Literal
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
PlayerType = TypeVar('PlayerType', bound=ba.Player)
|
||||
TeamType = TypeVar('TeamType', bound=ba.Team)
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
|
||||
class PlayerSpazHurtMessage:
|
||||
"""A message saying a PlayerSpaz was hurt.
|
||||
|
||||
Category: **Message Classes**
|
||||
"""
|
||||
|
||||
spaz: PlayerSpaz
|
||||
"""The PlayerSpaz that was hurt"""
|
||||
|
||||
def __init__(self, spaz: PlayerSpaz):
|
||||
"""Instantiate with the given ba.Spaz value."""
|
||||
self.spaz = spaz
|
||||
|
||||
|
||||
class PlayerSpaz(Spaz):
|
||||
"""A Spaz subclass meant to be controlled by a ba.Player.
|
||||
|
||||
Category: **Gameplay Classes**
|
||||
|
||||
When a PlayerSpaz dies, it delivers a ba.PlayerDiedMessage
|
||||
to the current ba.Activity. (unless the death was the result of the
|
||||
player leaving the game, in which case no message is sent)
|
||||
|
||||
When a PlayerSpaz is hurt, it delivers a PlayerSpazHurtMessage
|
||||
to the current ba.Activity.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
player: ba.Player,
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0),
|
||||
highlight: Sequence[float] = (0.5, 0.5, 0.5),
|
||||
character: str = 'Spaz',
|
||||
powerups_expire: bool = True,
|
||||
):
|
||||
"""Create a spaz for the provided ba.Player.
|
||||
|
||||
Note: this does not wire up any controls;
|
||||
you must call connect_controls_to_player() to do so.
|
||||
"""
|
||||
character=modifyspaz.getCharacter(player,character)
|
||||
|
||||
super().__init__(
|
||||
color=color,
|
||||
highlight=highlight,
|
||||
character=character,
|
||||
source_player=player,
|
||||
start_invincible=True,
|
||||
powerups_expire=powerups_expire,
|
||||
)
|
||||
self.last_player_attacked_by: ba.Player | None = None
|
||||
self.last_attacked_time = 0.0
|
||||
self.last_attacked_type: tuple[str, str] | None = None
|
||||
self.held_count = 0
|
||||
self.last_player_held_by: ba.Player | None = None
|
||||
self._player = player
|
||||
self._drive_player_position()
|
||||
import custom_hooks
|
||||
custom_hooks.playerspaz_init(self, self.node, self._player)
|
||||
|
||||
# Overloads to tell the type system our return type based on doraise val.
|
||||
|
||||
@overload
|
||||
def getplayer(
|
||||
self, playertype: type[PlayerType], doraise: Literal[False] = False
|
||||
) -> PlayerType | None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def getplayer(
|
||||
self, playertype: type[PlayerType], doraise: Literal[True]
|
||||
) -> PlayerType:
|
||||
...
|
||||
|
||||
def getplayer(
|
||||
self, playertype: type[PlayerType], doraise: bool = False
|
||||
) -> PlayerType | None:
|
||||
"""Get the ba.Player associated with this Spaz.
|
||||
|
||||
By default this will return None if the Player no longer exists.
|
||||
If you are logically certain that the Player still exists, pass
|
||||
doraise=False to get a non-optional return type.
|
||||
"""
|
||||
player: Any = self._player
|
||||
assert isinstance(player, playertype)
|
||||
if not player.exists() and doraise:
|
||||
raise ba.PlayerNotFoundError()
|
||||
return player if player.exists() else None
|
||||
|
||||
def connect_controls_to_player(
|
||||
self,
|
||||
enable_jump: bool = True,
|
||||
enable_punch: bool = True,
|
||||
enable_pickup: bool = True,
|
||||
enable_bomb: bool = True,
|
||||
enable_run: bool = True,
|
||||
enable_fly: bool = True,
|
||||
) -> None:
|
||||
"""Wire this spaz up to the provided ba.Player.
|
||||
|
||||
Full control of the character is given by default
|
||||
but can be selectively limited by passing False
|
||||
to specific arguments.
|
||||
"""
|
||||
player = self.getplayer(ba.Player)
|
||||
assert player
|
||||
|
||||
# Reset any currently connected player and/or the player we're
|
||||
# wiring up.
|
||||
if self._connected_to_player:
|
||||
if player != self._connected_to_player:
|
||||
player.resetinput()
|
||||
self.disconnect_controls_from_player()
|
||||
else:
|
||||
player.resetinput()
|
||||
|
||||
player.assigninput(ba.InputType.UP_DOWN, self.on_move_up_down)
|
||||
player.assigninput(ba.InputType.LEFT_RIGHT, self.on_move_left_right)
|
||||
player.assigninput(
|
||||
ba.InputType.HOLD_POSITION_PRESS, self.on_hold_position_press
|
||||
)
|
||||
player.assigninput(
|
||||
ba.InputType.HOLD_POSITION_RELEASE, self.on_hold_position_release
|
||||
)
|
||||
intp = ba.InputType
|
||||
if enable_jump:
|
||||
player.assigninput(intp.JUMP_PRESS, self.on_jump_press)
|
||||
player.assigninput(intp.JUMP_RELEASE, self.on_jump_release)
|
||||
if enable_pickup:
|
||||
player.assigninput(intp.PICK_UP_PRESS, self.on_pickup_press)
|
||||
player.assigninput(intp.PICK_UP_RELEASE, self.on_pickup_release)
|
||||
if enable_punch:
|
||||
player.assigninput(intp.PUNCH_PRESS, self.on_punch_press)
|
||||
player.assigninput(intp.PUNCH_RELEASE, self.on_punch_release)
|
||||
if enable_bomb:
|
||||
player.assigninput(intp.BOMB_PRESS, self.on_bomb_press)
|
||||
player.assigninput(intp.BOMB_RELEASE, self.on_bomb_release)
|
||||
if enable_run:
|
||||
player.assigninput(intp.RUN, self.on_run)
|
||||
if enable_fly:
|
||||
player.assigninput(intp.FLY_PRESS, self.on_fly_press)
|
||||
player.assigninput(intp.FLY_RELEASE, self.on_fly_release)
|
||||
|
||||
self._connected_to_player = player
|
||||
|
||||
def disconnect_controls_from_player(self) -> None:
|
||||
"""
|
||||
Completely sever any previously connected
|
||||
ba.Player from control of this spaz.
|
||||
"""
|
||||
if self._connected_to_player:
|
||||
self._connected_to_player.resetinput()
|
||||
self._connected_to_player = None
|
||||
|
||||
# Send releases for anything in case its held.
|
||||
self.on_move_up_down(0)
|
||||
self.on_move_left_right(0)
|
||||
self.on_hold_position_release()
|
||||
self.on_jump_release()
|
||||
self.on_pickup_release()
|
||||
self.on_punch_release()
|
||||
self.on_bomb_release()
|
||||
self.on_run(0.0)
|
||||
self.on_fly_release()
|
||||
else:
|
||||
print(
|
||||
'WARNING: disconnect_controls_from_player() called for'
|
||||
' non-connected player'
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
# FIXME: Tidy this up.
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
assert not self.expired
|
||||
|
||||
# Keep track of if we're being held and by who most recently.
|
||||
if isinstance(msg, ba.PickedUpMessage):
|
||||
# Augment standard behavior.
|
||||
super().handlemessage(msg)
|
||||
self.held_count += 1
|
||||
picked_up_by = msg.node.source_player
|
||||
if picked_up_by:
|
||||
self.last_player_held_by = picked_up_by
|
||||
elif isinstance(msg, ba.DroppedMessage):
|
||||
# Augment standard behavior.
|
||||
super().handlemessage(msg)
|
||||
self.held_count -= 1
|
||||
if self.held_count < 0:
|
||||
print('ERROR: spaz held_count < 0')
|
||||
|
||||
# Let's count someone dropping us as an attack.
|
||||
picked_up_by = msg.node.source_player
|
||||
if picked_up_by:
|
||||
self.last_player_attacked_by = picked_up_by
|
||||
self.last_attacked_time = ba.time()
|
||||
self.last_attacked_type = ('picked_up', 'default')
|
||||
elif isinstance(msg, ba.StandMessage):
|
||||
super().handlemessage(msg) # Augment standard behavior.
|
||||
|
||||
# Our Spaz was just moved somewhere. Explicitly update
|
||||
# our associated player's position in case it is being used
|
||||
# for logic (otherwise it will be out of date until next step)
|
||||
self._drive_player_position()
|
||||
|
||||
elif isinstance(msg, ba.DieMessage):
|
||||
|
||||
# Report player deaths to the game.
|
||||
if not self._dead:
|
||||
|
||||
# Immediate-mode or left-game deaths don't count as 'kills'.
|
||||
killed = (
|
||||
not msg.immediate and msg.how is not ba.DeathType.LEFT_GAME
|
||||
)
|
||||
|
||||
activity = self._activity()
|
||||
|
||||
player = self.getplayer(ba.Player, False)
|
||||
if not killed:
|
||||
killerplayer = None
|
||||
else:
|
||||
# If this player was being held at the time of death,
|
||||
# the holder is the killer.
|
||||
if self.held_count > 0 and self.last_player_held_by:
|
||||
killerplayer = self.last_player_held_by
|
||||
else:
|
||||
# Otherwise, if they were attacked by someone in the
|
||||
# last few seconds, that person is the killer.
|
||||
# Otherwise it was a suicide.
|
||||
# FIXME: Currently disabling suicides in Co-Op since
|
||||
# all bot kills would register as suicides; need to
|
||||
# change this from last_player_attacked_by to
|
||||
# something like last_actor_attacked_by to fix that.
|
||||
if (
|
||||
self.last_player_attacked_by
|
||||
and ba.time() - self.last_attacked_time < 4.0
|
||||
):
|
||||
killerplayer = self.last_player_attacked_by
|
||||
else:
|
||||
# ok, call it a suicide unless we're in co-op
|
||||
if activity is not None and not isinstance(
|
||||
activity.session, ba.CoopSession
|
||||
):
|
||||
killerplayer = player
|
||||
else:
|
||||
killerplayer = None
|
||||
|
||||
# We should never wind up with a dead-reference here;
|
||||
# we want to use None in that case.
|
||||
assert killerplayer is None or killerplayer
|
||||
|
||||
# Only report if both the player and the activity still exist.
|
||||
if killed and activity is not None and player:
|
||||
activity.handlemessage(
|
||||
ba.PlayerDiedMessage(
|
||||
player, killed, killerplayer, msg.how
|
||||
)
|
||||
)
|
||||
|
||||
super().handlemessage(msg) # Augment standard behavior.
|
||||
|
||||
# Keep track of the player who last hit us for point rewarding.
|
||||
elif isinstance(msg, ba.HitMessage):
|
||||
source_player = msg.get_source_player(type(self._player))
|
||||
if source_player:
|
||||
self.last_player_attacked_by = source_player
|
||||
self.last_attacked_time = ba.time()
|
||||
self.last_attacked_type = (msg.hit_type, msg.hit_subtype)
|
||||
super().handlemessage(msg) # Augment standard behavior.
|
||||
activity = self._activity()
|
||||
if activity is not None and self._player.exists():
|
||||
activity.handlemessage(PlayerSpazHurtMessage(self))
|
||||
else:
|
||||
return super().handlemessage(msg)
|
||||
return None
|
||||
|
||||
def _drive_player_position(self) -> None:
|
||||
"""Drive our ba.Player's official position
|
||||
|
||||
If our position is changed explicitly, this should be called again
|
||||
to instantly update the player position (otherwise it would be out
|
||||
of date until the next sim step)
|
||||
"""
|
||||
player = self._player
|
||||
if player:
|
||||
assert self.node
|
||||
assert player.node
|
||||
self.node.connectattr('torso_position', player.node, 'position')
|
||||
127
dist/ba_data/python/bastd/actor/popuptext.py
vendored
Normal file
127
dist/ba_data/python/bastd/actor/popuptext.py
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines Actor(s)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
class PopupText(ba.Actor):
|
||||
"""Text that pops up above a position to denote something special.
|
||||
|
||||
category: Gameplay Classes
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: str | ba.Lstr,
|
||||
position: Sequence[float] = (0.0, 0.0, 0.0),
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
|
||||
random_offset: float = 0.5,
|
||||
offset: Sequence[float] = (0.0, 0.0, 0.0),
|
||||
scale: float = 1.0,
|
||||
):
|
||||
"""Instantiate with given values.
|
||||
|
||||
random_offset is the amount of random offset from the provided position
|
||||
that will be applied. This can help multiple achievements from
|
||||
overlapping too much.
|
||||
"""
|
||||
super().__init__()
|
||||
if len(color) == 3:
|
||||
color = (color[0], color[1], color[2], 1.0)
|
||||
pos = (
|
||||
position[0] + offset[0] + random_offset * (0.5 - random.random()),
|
||||
position[1] + offset[1] + random_offset * (0.5 - random.random()),
|
||||
position[2] + offset[2] + random_offset * (0.5 - random.random()),
|
||||
)
|
||||
|
||||
self.node = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'text': text,
|
||||
'in_world': True,
|
||||
'shadow': 1.0,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center',
|
||||
},
|
||||
delegate=self,
|
||||
)
|
||||
|
||||
lifespan = 1.5
|
||||
|
||||
# scale up
|
||||
ba.animate(
|
||||
self.node,
|
||||
'scale',
|
||||
{
|
||||
0: 0.0,
|
||||
lifespan * 0.11: 0.020 * 0.7 * scale,
|
||||
lifespan * 0.16: 0.013 * 0.7 * scale,
|
||||
lifespan * 0.25: 0.014 * 0.7 * scale,
|
||||
},
|
||||
)
|
||||
|
||||
# translate upward
|
||||
self._tcombine = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={'input0': pos[0], 'input2': pos[2], 'size': 3},
|
||||
)
|
||||
ba.animate(
|
||||
self._tcombine, 'input1', {0: pos[1] + 1.5, lifespan: pos[1] + 2.0}
|
||||
)
|
||||
self._tcombine.connectattr('output', self.node, 'position')
|
||||
|
||||
# fade our opacity in/out
|
||||
self._combine = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'input0': color[0],
|
||||
'input1': color[1],
|
||||
'input2': color[2],
|
||||
'size': 4,
|
||||
},
|
||||
)
|
||||
for i in range(4):
|
||||
ba.animate(
|
||||
self._combine,
|
||||
'input' + str(i),
|
||||
{
|
||||
0.13 * lifespan: color[i],
|
||||
0.18 * lifespan: 4.0 * color[i],
|
||||
0.22 * lifespan: color[i],
|
||||
},
|
||||
)
|
||||
ba.animate(
|
||||
self._combine,
|
||||
'input3',
|
||||
{
|
||||
0: 0,
|
||||
0.1 * lifespan: color[3],
|
||||
0.7 * lifespan: color[3],
|
||||
lifespan: 0,
|
||||
},
|
||||
)
|
||||
self._combine.connectattr('output', self.node, 'color')
|
||||
|
||||
# kill ourself
|
||||
self._die_timer = ba.Timer(
|
||||
lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage())
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
if isinstance(msg, ba.DieMessage):
|
||||
if self.node:
|
||||
self.node.delete()
|
||||
else:
|
||||
super().handlemessage(msg)
|
||||
319
dist/ba_data/python/bastd/actor/powerupbox.py
vendored
Normal file
319
dist/ba_data/python/bastd/actor/powerupbox.py
vendored
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines Actor(s)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
from bastd.gameutils import SharedObjects
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
DEFAULT_POWERUP_INTERVAL = 8.0
|
||||
|
||||
|
||||
class _TouchedMessage:
|
||||
pass
|
||||
|
||||
|
||||
class PowerupBoxFactory:
|
||||
"""A collection of media and other resources used by ba.Powerups.
|
||||
|
||||
Category: **Gameplay Classes**
|
||||
|
||||
A single instance of this is shared between all powerups
|
||||
and can be retrieved via ba.Powerup.get_factory().
|
||||
"""
|
||||
|
||||
model: ba.Model
|
||||
"""The ba.Model of the powerup box."""
|
||||
|
||||
model_simple: ba.Model
|
||||
"""A simpler ba.Model of the powerup box, for use in shadows, etc."""
|
||||
|
||||
tex_bomb: ba.Texture
|
||||
"""Triple-bomb powerup ba.Texture."""
|
||||
|
||||
tex_punch: ba.Texture
|
||||
"""Punch powerup ba.Texture."""
|
||||
|
||||
tex_ice_bombs: ba.Texture
|
||||
"""Ice bomb powerup ba.Texture."""
|
||||
|
||||
tex_sticky_bombs: ba.Texture
|
||||
"""Sticky bomb powerup ba.Texture."""
|
||||
|
||||
tex_shield: ba.Texture
|
||||
"""Shield powerup ba.Texture."""
|
||||
|
||||
tex_impact_bombs: ba.Texture
|
||||
"""Impact-bomb powerup ba.Texture."""
|
||||
|
||||
tex_health: ba.Texture
|
||||
"""Health powerup ba.Texture."""
|
||||
|
||||
tex_land_mines: ba.Texture
|
||||
"""Land-mine powerup ba.Texture."""
|
||||
|
||||
tex_curse: ba.Texture
|
||||
"""Curse powerup ba.Texture."""
|
||||
|
||||
health_powerup_sound: ba.Sound
|
||||
"""ba.Sound played when a health powerup is accepted."""
|
||||
|
||||
powerup_sound: ba.Sound
|
||||
"""ba.Sound played when a powerup is accepted."""
|
||||
|
||||
powerdown_sound: ba.Sound
|
||||
"""ba.Sound that can be used when powerups wear off."""
|
||||
|
||||
powerup_material: ba.Material
|
||||
"""ba.Material applied to powerup boxes."""
|
||||
|
||||
powerup_accept_material: ba.Material
|
||||
"""Powerups will send a ba.PowerupMessage to anything they touch
|
||||
that has this ba.Material applied."""
|
||||
|
||||
_STORENAME = ba.storagename()
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Instantiate a PowerupBoxFactory.
|
||||
|
||||
You shouldn't need to do this; call Powerup.get_factory()
|
||||
to get a shared instance.
|
||||
"""
|
||||
from ba.internal import get_default_powerup_distribution
|
||||
|
||||
shared = SharedObjects.get()
|
||||
self._lastpoweruptype: str | None = None
|
||||
self.model = ba.getmodel('powerup')
|
||||
self.model_simple = ba.getmodel('powerupSimple')
|
||||
self.tex_bomb = ba.gettexture('powerupBomb')
|
||||
self.tex_punch = ba.gettexture('powerupPunch')
|
||||
self.tex_ice_bombs = ba.gettexture('powerupIceBombs')
|
||||
self.tex_sticky_bombs = ba.gettexture('powerupStickyBombs')
|
||||
self.tex_shield = ba.gettexture('powerupShield')
|
||||
self.tex_impact_bombs = ba.gettexture('powerupImpactBombs')
|
||||
self.tex_health = ba.gettexture('powerupHealth')
|
||||
self.tex_land_mines = ba.gettexture('powerupLandMines')
|
||||
self.tex_curse = ba.gettexture('powerupCurse')
|
||||
self.health_powerup_sound = ba.getsound('healthPowerup')
|
||||
self.powerup_sound = ba.getsound('powerup01')
|
||||
self.powerdown_sound = ba.getsound('powerdown01')
|
||||
self.drop_sound = ba.getsound('boxDrop')
|
||||
|
||||
# Material for powerups.
|
||||
self.powerup_material = ba.Material()
|
||||
|
||||
# Material for anyone wanting to accept powerups.
|
||||
self.powerup_accept_material = ba.Material()
|
||||
|
||||
# Pass a powerup-touched message to applicable stuff.
|
||||
self.powerup_material.add_actions(
|
||||
conditions=('they_have_material', self.powerup_accept_material),
|
||||
actions=(
|
||||
('modify_part_collision', 'collide', True),
|
||||
('modify_part_collision', 'physical', False),
|
||||
('message', 'our_node', 'at_connect', _TouchedMessage()),
|
||||
),
|
||||
)
|
||||
|
||||
# We don't wanna be picked up.
|
||||
self.powerup_material.add_actions(
|
||||
conditions=('they_have_material', shared.pickup_material),
|
||||
actions=('modify_part_collision', 'collide', False),
|
||||
)
|
||||
|
||||
self.powerup_material.add_actions(
|
||||
conditions=('they_have_material', shared.footing_material),
|
||||
actions=('impact_sound', self.drop_sound, 0.5, 0.1),
|
||||
)
|
||||
|
||||
self._powerupdist: list[str] = []
|
||||
for powerup, freq in get_default_powerup_distribution():
|
||||
for _i in range(int(freq)):
|
||||
self._powerupdist.append(powerup)
|
||||
|
||||
def get_random_powerup_type(
|
||||
self,
|
||||
forcetype: str | None = None,
|
||||
excludetypes: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Returns a random powerup type (string).
|
||||
|
||||
See ba.Powerup.poweruptype for available type values.
|
||||
|
||||
There are certain non-random aspects to this; a 'curse' powerup,
|
||||
for instance, is always followed by a 'health' powerup (to keep things
|
||||
interesting). Passing 'forcetype' forces a given returned type while
|
||||
still properly interacting with the non-random aspects of the system
|
||||
(ie: forcing a 'curse' powerup will result
|
||||
in the next powerup being health).
|
||||
"""
|
||||
if excludetypes is None:
|
||||
excludetypes = []
|
||||
if forcetype:
|
||||
ptype = forcetype
|
||||
else:
|
||||
# If the last one was a curse, make this one a health to
|
||||
# provide some hope.
|
||||
if self._lastpoweruptype == 'curse':
|
||||
ptype = 'health'
|
||||
else:
|
||||
while True:
|
||||
ptype = self._powerupdist[
|
||||
random.randint(0, len(self._powerupdist) - 1)
|
||||
]
|
||||
if ptype not in excludetypes:
|
||||
break
|
||||
self._lastpoweruptype = ptype
|
||||
return ptype
|
||||
|
||||
@classmethod
|
||||
def get(cls) -> PowerupBoxFactory:
|
||||
"""Return a shared ba.PowerupBoxFactory object, creating if needed."""
|
||||
activity = ba.getactivity()
|
||||
if activity is None:
|
||||
raise ba.ContextError('No current activity.')
|
||||
factory = activity.customdata.get(cls._STORENAME)
|
||||
if factory is None:
|
||||
factory = activity.customdata[cls._STORENAME] = PowerupBoxFactory()
|
||||
assert isinstance(factory, PowerupBoxFactory)
|
||||
return factory
|
||||
|
||||
|
||||
class PowerupBox(ba.Actor):
|
||||
"""A box that grants a powerup.
|
||||
|
||||
category: Gameplay Classes
|
||||
|
||||
This will deliver a ba.PowerupMessage to anything that touches it
|
||||
which has the ba.PowerupBoxFactory.powerup_accept_material applied.
|
||||
"""
|
||||
|
||||
poweruptype: str
|
||||
"""The string powerup type. This can be 'triple_bombs', 'punch',
|
||||
'ice_bombs', 'impact_bombs', 'land_mines', 'sticky_bombs', 'shield',
|
||||
'health', or 'curse'."""
|
||||
|
||||
node: ba.Node
|
||||
"""The 'prop' ba.Node representing this box."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
position: Sequence[float] = (0.0, 1.0, 0.0),
|
||||
poweruptype: str = 'triple_bombs',
|
||||
expire: bool = True,
|
||||
):
|
||||
"""Create a powerup-box of the requested type at the given position.
|
||||
|
||||
see ba.Powerup.poweruptype for valid type strings.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
shared = SharedObjects.get()
|
||||
factory = PowerupBoxFactory.get()
|
||||
self.poweruptype = poweruptype
|
||||
self._powersgiven = False
|
||||
|
||||
if poweruptype == 'triple_bombs':
|
||||
tex = factory.tex_bomb
|
||||
elif poweruptype == 'punch':
|
||||
tex = factory.tex_punch
|
||||
elif poweruptype == 'ice_bombs':
|
||||
tex = factory.tex_ice_bombs
|
||||
elif poweruptype == 'impact_bombs':
|
||||
tex = factory.tex_impact_bombs
|
||||
elif poweruptype == 'land_mines':
|
||||
tex = factory.tex_land_mines
|
||||
elif poweruptype == 'sticky_bombs':
|
||||
tex = factory.tex_sticky_bombs
|
||||
elif poweruptype == 'shield':
|
||||
tex = factory.tex_shield
|
||||
elif poweruptype == 'health':
|
||||
tex = factory.tex_health
|
||||
elif poweruptype == 'curse':
|
||||
tex = factory.tex_curse
|
||||
else:
|
||||
raise ValueError('invalid poweruptype: ' + str(poweruptype))
|
||||
|
||||
if len(position) != 3:
|
||||
raise ValueError('expected 3 floats for position')
|
||||
|
||||
self.node = ba.newnode(
|
||||
'prop',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'body': 'box',
|
||||
'position': position,
|
||||
'model': factory.model,
|
||||
'light_model': factory.model_simple,
|
||||
'shadow_size': 0.5,
|
||||
'color_texture': tex,
|
||||
'reflection': 'powerup',
|
||||
'reflection_scale': [1.0],
|
||||
'materials': (factory.powerup_material, shared.object_material),
|
||||
},
|
||||
) # yapf: disable
|
||||
|
||||
# Animate in.
|
||||
curve = ba.animate(self.node, 'model_scale', {0: 0, 0.14: 1.6, 0.2: 1})
|
||||
ba.timer(0.2, curve.delete)
|
||||
|
||||
if expire:
|
||||
ba.timer(
|
||||
DEFAULT_POWERUP_INTERVAL - 2.5,
|
||||
ba.WeakCall(self._start_flashing),
|
||||
)
|
||||
ba.timer(
|
||||
DEFAULT_POWERUP_INTERVAL - 1.0,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage()),
|
||||
)
|
||||
|
||||
def _start_flashing(self) -> None:
|
||||
if self.node:
|
||||
self.node.flashing = True
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
|
||||
if isinstance(msg, ba.PowerupAcceptMessage):
|
||||
factory = PowerupBoxFactory.get()
|
||||
assert self.node
|
||||
if self.poweruptype == 'health':
|
||||
ba.playsound(
|
||||
factory.health_powerup_sound, 3, position=self.node.position
|
||||
)
|
||||
ba.playsound(factory.powerup_sound, 3, position=self.node.position)
|
||||
self._powersgiven = True
|
||||
self.handlemessage(ba.DieMessage())
|
||||
|
||||
elif isinstance(msg, _TouchedMessage):
|
||||
if not self._powersgiven:
|
||||
node = ba.getcollision().opposingnode
|
||||
node.handlemessage(
|
||||
ba.PowerupMessage(self.poweruptype, sourcenode=self.node)
|
||||
)
|
||||
|
||||
elif isinstance(msg, ba.DieMessage):
|
||||
if self.node:
|
||||
if msg.immediate:
|
||||
self.node.delete()
|
||||
else:
|
||||
ba.animate(self.node, 'model_scale', {0: 1, 0.1: 0})
|
||||
ba.timer(0.1, self.node.delete)
|
||||
|
||||
elif isinstance(msg, ba.OutOfBoundsMessage):
|
||||
self.handlemessage(ba.DieMessage())
|
||||
|
||||
elif isinstance(msg, ba.HitMessage):
|
||||
# Don't die on punches (that's annoying).
|
||||
if msg.hit_type != 'punch':
|
||||
self.handlemessage(ba.DieMessage())
|
||||
else:
|
||||
return super().handlemessage(msg)
|
||||
return None
|
||||
168
dist/ba_data/python/bastd/actor/respawnicon.py
vendored
Normal file
168
dist/ba_data/python/bastd/actor/respawnicon.py
vendored
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Implements respawn icon actor."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class RespawnIcon:
|
||||
"""An icon with a countdown that appears alongside the screen.
|
||||
|
||||
category: Gameplay Classes
|
||||
|
||||
This is used to indicate that a ba.Player is waiting to respawn.
|
||||
"""
|
||||
|
||||
_MASKTEXSTORENAME = ba.storagename('masktex')
|
||||
_ICONSSTORENAME = ba.storagename('icons')
|
||||
|
||||
def __init__(self, player: ba.Player, respawn_time: float):
|
||||
"""Instantiate with a ba.Player and respawn_time (in seconds)."""
|
||||
self._visible = True
|
||||
|
||||
on_right, offs_extra, respawn_icons = self._get_context(player)
|
||||
|
||||
# Cache our mask tex on the team for easy access.
|
||||
mask_tex = player.team.customdata.get(self._MASKTEXSTORENAME)
|
||||
if mask_tex is None:
|
||||
mask_tex = ba.gettexture('characterIconMask')
|
||||
player.team.customdata[self._MASKTEXSTORENAME] = mask_tex
|
||||
assert isinstance(mask_tex, ba.Texture)
|
||||
|
||||
# Now find the first unused slot and use that.
|
||||
index = 0
|
||||
while (
|
||||
index in respawn_icons
|
||||
and respawn_icons[index]() is not None
|
||||
and respawn_icons[index]().visible
|
||||
):
|
||||
index += 1
|
||||
respawn_icons[index] = weakref.ref(self)
|
||||
|
||||
offs = offs_extra + index * -53
|
||||
icon = player.get_icon()
|
||||
texture = icon['texture']
|
||||
h_offs = -10
|
||||
ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs)
|
||||
self._image: ba.NodeActor | None = ba.NodeActor(
|
||||
ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'texture': texture,
|
||||
'tint_texture': icon['tint_texture'],
|
||||
'tint_color': icon['tint_color'],
|
||||
'tint2_color': icon['tint2_color'],
|
||||
'mask_texture': mask_tex,
|
||||
'position': ipos,
|
||||
'scale': (32, 32),
|
||||
'opacity': 1.0,
|
||||
'absolute_scale': True,
|
||||
'attach': 'topRight' if on_right else 'topLeft',
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
assert self._image.node
|
||||
ba.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7})
|
||||
|
||||
npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs)
|
||||
self._name: ba.NodeActor | None = ba.NodeActor(
|
||||
ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': 'right' if on_right else 'left',
|
||||
'text': ba.Lstr(value=player.getname()),
|
||||
'maxwidth': 100,
|
||||
'h_align': 'center',
|
||||
'v_align': 'center',
|
||||
'shadow': 1.0,
|
||||
'flatness': 1.0,
|
||||
'color': ba.safecolor(icon['tint_color']),
|
||||
'scale': 0.5,
|
||||
'position': npos,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
assert self._name.node
|
||||
ba.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5})
|
||||
|
||||
tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs)
|
||||
self._text: ba.NodeActor | None = ba.NodeActor(
|
||||
ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'position': tpos,
|
||||
'h_attach': 'right' if on_right else 'left',
|
||||
'h_align': 'right' if on_right else 'left',
|
||||
'scale': 0.9,
|
||||
'shadow': 0.5,
|
||||
'flatness': 0.5,
|
||||
'v_attach': 'top',
|
||||
'color': ba.safecolor(icon['tint_color']),
|
||||
'text': '',
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
assert self._text.node
|
||||
ba.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9})
|
||||
|
||||
self._respawn_time = ba.time() + respawn_time
|
||||
self._update()
|
||||
self._timer: ba.Timer | None = ba.Timer(
|
||||
1.0, ba.WeakCall(self._update), repeat=True
|
||||
)
|
||||
|
||||
@property
|
||||
def visible(self) -> bool:
|
||||
"""Is this icon still visible?"""
|
||||
return self._visible
|
||||
|
||||
def _get_context(self, player: ba.Player) -> tuple[bool, float, dict]:
|
||||
"""Return info on where we should be shown and stored."""
|
||||
activity = ba.getactivity()
|
||||
|
||||
if isinstance(ba.getsession(), ba.DualTeamSession):
|
||||
on_right = player.team.id % 2 == 1
|
||||
|
||||
# Store a list of icons in the team.
|
||||
icons = player.team.customdata.get(self._ICONSSTORENAME)
|
||||
if icons is None:
|
||||
player.team.customdata[self._ICONSSTORENAME] = icons = {}
|
||||
assert isinstance(icons, dict)
|
||||
|
||||
offs_extra = -20
|
||||
else:
|
||||
on_right = False
|
||||
|
||||
# Store a list of icons in the activity.
|
||||
icons = activity.customdata.get(self._ICONSSTORENAME)
|
||||
if icons is None:
|
||||
activity.customdata[self._ICONSSTORENAME] = icons = {}
|
||||
assert isinstance(icons, dict)
|
||||
|
||||
if isinstance(activity.session, ba.FreeForAllSession):
|
||||
offs_extra = -150
|
||||
else:
|
||||
offs_extra = -20
|
||||
return on_right, offs_extra, icons
|
||||
|
||||
def _update(self) -> None:
|
||||
remaining = int(round(self._respawn_time - ba.time()))
|
||||
if remaining > 0:
|
||||
assert self._text is not None
|
||||
if self._text.node:
|
||||
self._text.node.text = str(remaining)
|
||||
else:
|
||||
self._visible = False
|
||||
self._image = self._text = self._timer = self._name = None
|
||||
447
dist/ba_data/python/bastd/actor/scoreboard.py
vendored
Normal file
447
dist/ba_data/python/bastd/actor/scoreboard.py
vendored
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines ScoreBoard Actor and related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
class _Entry:
|
||||
def __init__(
|
||||
self,
|
||||
scoreboard: Scoreboard,
|
||||
team: ba.Team,
|
||||
do_cover: bool,
|
||||
scale: float,
|
||||
label: ba.Lstr | None,
|
||||
flash_length: float,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
self._scoreboard = weakref.ref(scoreboard)
|
||||
self._do_cover = do_cover
|
||||
self._scale = scale
|
||||
self._flash_length = flash_length
|
||||
self._width = 140.0 * self._scale
|
||||
self._height = 32.0 * self._scale
|
||||
self._bar_width = 2.0 * self._scale
|
||||
self._bar_height = 32.0 * self._scale
|
||||
self._bar_tex = self._backing_tex = ba.gettexture('bar')
|
||||
self._cover_tex = ba.gettexture('uiAtlas')
|
||||
self._model = ba.getmodel('meterTransparent')
|
||||
self._pos: Sequence[float] | None = None
|
||||
self._flash_timer: ba.Timer | None = None
|
||||
self._flash_counter: int | None = None
|
||||
self._flash_colors: bool | None = None
|
||||
self._score: float | None = None
|
||||
|
||||
safe_team_color = ba.safecolor(team.color, target_intensity=1.0)
|
||||
|
||||
# FIXME: Should not do things conditionally for vr-mode, as there may
|
||||
# be non-vr clients connected which will also get these value.
|
||||
vrmode = ba.app.vr_mode
|
||||
|
||||
if self._do_cover:
|
||||
if vrmode:
|
||||
self._backing_color = [0.1 + c * 0.1 for c in safe_team_color]
|
||||
else:
|
||||
self._backing_color = [0.05 + c * 0.17 for c in safe_team_color]
|
||||
else:
|
||||
self._backing_color = [0.05 + c * 0.1 for c in safe_team_color]
|
||||
|
||||
opacity = (0.8 if vrmode else 0.8) if self._do_cover else 0.5
|
||||
self._backing = ba.NodeActor(
|
||||
ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'scale': (self._width, self._height),
|
||||
'opacity': opacity,
|
||||
'color': self._backing_color,
|
||||
'vr_depth': -3,
|
||||
'attach': 'topLeft',
|
||||
'texture': self._backing_tex,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
self._barcolor = safe_team_color
|
||||
self._bar = ba.NodeActor(
|
||||
ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'opacity': 0.7,
|
||||
'color': self._barcolor,
|
||||
'attach': 'topLeft',
|
||||
'texture': self._bar_tex,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
self._bar_scale = ba.newnode(
|
||||
'combine',
|
||||
owner=self._bar.node,
|
||||
attrs={
|
||||
'size': 2,
|
||||
'input0': self._bar_width,
|
||||
'input1': self._bar_height,
|
||||
},
|
||||
)
|
||||
assert self._bar.node
|
||||
self._bar_scale.connectattr('output', self._bar.node, 'scale')
|
||||
self._bar_position = ba.newnode(
|
||||
'combine',
|
||||
owner=self._bar.node,
|
||||
attrs={'size': 2, 'input0': 0, 'input1': 0},
|
||||
)
|
||||
self._bar_position.connectattr('output', self._bar.node, 'position')
|
||||
self._cover_color = safe_team_color
|
||||
if self._do_cover:
|
||||
self._cover = ba.NodeActor(
|
||||
ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'scale': (self._width * 1.15, self._height * 1.6),
|
||||
'opacity': 1.0,
|
||||
'color': self._cover_color,
|
||||
'vr_depth': 2,
|
||||
'attach': 'topLeft',
|
||||
'texture': self._cover_tex,
|
||||
'model_transparent': self._model,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
clr = safe_team_color
|
||||
maxwidth = 130.0 * (1.0 - scoreboard.score_split)
|
||||
flatness = (1.0 if vrmode else 0.5) if self._do_cover else 1.0
|
||||
self._score_text = ba.NodeActor(
|
||||
ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'h_attach': 'left',
|
||||
'v_attach': 'top',
|
||||
'h_align': 'right',
|
||||
'v_align': 'center',
|
||||
'maxwidth': maxwidth,
|
||||
'vr_depth': 2,
|
||||
'scale': self._scale * 0.9,
|
||||
'text': '',
|
||||
'shadow': 1.0 if vrmode else 0.5,
|
||||
'flatness': flatness,
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
clr = safe_team_color
|
||||
|
||||
team_name_label: str | ba.Lstr
|
||||
if label is not None:
|
||||
team_name_label = label
|
||||
else:
|
||||
team_name_label = team.name
|
||||
|
||||
# We do our own clipping here; should probably try to tap into some
|
||||
# existing functionality.
|
||||
if isinstance(team_name_label, ba.Lstr):
|
||||
|
||||
# Hmmm; if the team-name is a non-translatable value lets go
|
||||
# ahead and clip it otherwise we leave it as-is so
|
||||
# translation can occur..
|
||||
if team_name_label.is_flat_value():
|
||||
val = team_name_label.evaluate()
|
||||
if len(val) > 10:
|
||||
team_name_label = ba.Lstr(value=val[:10] + '...')
|
||||
else:
|
||||
if len(team_name_label) > 10:
|
||||
team_name_label = team_name_label[:10] + '...'
|
||||
team_name_label = ba.Lstr(value=team_name_label)
|
||||
|
||||
flatness = (1.0 if vrmode else 0.5) if self._do_cover else 1.0
|
||||
self._name_text = ba.NodeActor(
|
||||
ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'h_attach': 'left',
|
||||
'v_attach': 'top',
|
||||
'h_align': 'left',
|
||||
'v_align': 'center',
|
||||
'vr_depth': 2,
|
||||
'scale': self._scale * 0.9,
|
||||
'shadow': 1.0 if vrmode else 0.5,
|
||||
'flatness': flatness,
|
||||
'maxwidth': 130 * scoreboard.score_split,
|
||||
'text': team_name_label,
|
||||
'color': clr + (1.0,),
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def flash(self, countdown: bool, extra_flash: bool) -> None:
|
||||
"""Flash momentarily."""
|
||||
self._flash_timer = ba.Timer(
|
||||
0.1, ba.WeakCall(self._do_flash), repeat=True
|
||||
)
|
||||
if countdown:
|
||||
self._flash_counter = 10
|
||||
else:
|
||||
self._flash_counter = int(20.0 * self._flash_length)
|
||||
if extra_flash:
|
||||
self._flash_counter *= 4
|
||||
self._set_flash_colors(True)
|
||||
|
||||
def set_position(self, position: Sequence[float]) -> None:
|
||||
"""Set the entry's position."""
|
||||
|
||||
# Abort if we've been killed
|
||||
if not self._backing.node:
|
||||
return
|
||||
|
||||
self._pos = tuple(position)
|
||||
self._backing.node.position = (
|
||||
position[0] + self._width / 2,
|
||||
position[1] - self._height / 2,
|
||||
)
|
||||
if self._do_cover:
|
||||
assert self._cover.node
|
||||
self._cover.node.position = (
|
||||
position[0] + self._width / 2,
|
||||
position[1] - self._height / 2,
|
||||
)
|
||||
self._bar_position.input0 = self._pos[0] + self._bar_width / 2
|
||||
self._bar_position.input1 = self._pos[1] - self._bar_height / 2
|
||||
assert self._score_text.node
|
||||
self._score_text.node.position = (
|
||||
self._pos[0] + self._width - 7.0 * self._scale,
|
||||
self._pos[1] - self._bar_height + 16.0 * self._scale,
|
||||
)
|
||||
assert self._name_text.node
|
||||
self._name_text.node.position = (
|
||||
self._pos[0] + 7.0 * self._scale,
|
||||
self._pos[1] - self._bar_height + 16.0 * self._scale,
|
||||
)
|
||||
|
||||
def _set_flash_colors(self, flash: bool) -> None:
|
||||
self._flash_colors = flash
|
||||
|
||||
def _safesetcolor(node: ba.Node | None, val: Any) -> None:
|
||||
if node:
|
||||
node.color = val
|
||||
|
||||
if flash:
|
||||
scale = 2.0
|
||||
_safesetcolor(
|
||||
self._backing.node,
|
||||
(
|
||||
self._backing_color[0] * scale,
|
||||
self._backing_color[1] * scale,
|
||||
self._backing_color[2] * scale,
|
||||
),
|
||||
)
|
||||
_safesetcolor(
|
||||
self._bar.node,
|
||||
(
|
||||
self._barcolor[0] * scale,
|
||||
self._barcolor[1] * scale,
|
||||
self._barcolor[2] * scale,
|
||||
),
|
||||
)
|
||||
if self._do_cover:
|
||||
_safesetcolor(
|
||||
self._cover.node,
|
||||
(
|
||||
self._cover_color[0] * scale,
|
||||
self._cover_color[1] * scale,
|
||||
self._cover_color[2] * scale,
|
||||
),
|
||||
)
|
||||
else:
|
||||
_safesetcolor(self._backing.node, self._backing_color)
|
||||
_safesetcolor(self._bar.node, self._barcolor)
|
||||
if self._do_cover:
|
||||
_safesetcolor(self._cover.node, self._cover_color)
|
||||
|
||||
def _do_flash(self) -> None:
|
||||
assert self._flash_counter is not None
|
||||
if self._flash_counter <= 0:
|
||||
self._set_flash_colors(False)
|
||||
else:
|
||||
self._flash_counter -= 1
|
||||
self._set_flash_colors(not self._flash_colors)
|
||||
|
||||
def set_value(
|
||||
self,
|
||||
score: float,
|
||||
max_score: float | None = None,
|
||||
countdown: bool = False,
|
||||
flash: bool = True,
|
||||
show_value: bool = True,
|
||||
) -> None:
|
||||
"""Set the value for the scoreboard entry."""
|
||||
|
||||
# If we have no score yet, just set it.. otherwise compare
|
||||
# and see if we should flash.
|
||||
if self._score is None:
|
||||
self._score = score
|
||||
else:
|
||||
if score > self._score or (countdown and score < self._score):
|
||||
extra_flash = (
|
||||
max_score is not None
|
||||
and score >= max_score
|
||||
and not countdown
|
||||
) or (countdown and score == 0)
|
||||
if flash:
|
||||
self.flash(countdown, extra_flash)
|
||||
self._score = score
|
||||
|
||||
if max_score is None:
|
||||
self._bar_width = 0.0
|
||||
else:
|
||||
if countdown:
|
||||
self._bar_width = max(
|
||||
2.0 * self._scale,
|
||||
self._width * (1.0 - (float(score) / max_score)),
|
||||
)
|
||||
else:
|
||||
self._bar_width = max(
|
||||
2.0 * self._scale,
|
||||
self._width * (min(1.0, float(score) / max_score)),
|
||||
)
|
||||
|
||||
cur_width = self._bar_scale.input0
|
||||
ba.animate(
|
||||
self._bar_scale, 'input0', {0.0: cur_width, 0.25: self._bar_width}
|
||||
)
|
||||
self._bar_scale.input1 = self._bar_height
|
||||
cur_x = self._bar_position.input0
|
||||
assert self._pos is not None
|
||||
ba.animate(
|
||||
self._bar_position,
|
||||
'input0',
|
||||
{0.0: cur_x, 0.25: self._pos[0] + self._bar_width / 2},
|
||||
)
|
||||
self._bar_position.input1 = self._pos[1] - self._bar_height / 2
|
||||
assert self._score_text.node
|
||||
if show_value:
|
||||
self._score_text.node.text = str(score)
|
||||
else:
|
||||
self._score_text.node.text = ''
|
||||
|
||||
|
||||
class _EntryProxy:
|
||||
"""Encapsulates adding/removing of a scoreboard Entry."""
|
||||
|
||||
def __init__(self, scoreboard: Scoreboard, team: ba.Team):
|
||||
self._scoreboard = weakref.ref(scoreboard)
|
||||
|
||||
# Have to store ID here instead of a weak-ref since the team will be
|
||||
# dead when we die and need to remove it.
|
||||
self._team_id = team.id
|
||||
|
||||
def __del__(self) -> None:
|
||||
scoreboard = self._scoreboard()
|
||||
|
||||
# Remove our team from the scoreboard if its still around.
|
||||
# (but deferred, in case we die in a sim step or something where
|
||||
# its illegal to modify nodes)
|
||||
if scoreboard is None:
|
||||
return
|
||||
|
||||
try:
|
||||
ba.pushcall(ba.Call(scoreboard.remove_team, self._team_id))
|
||||
except ba.ContextError:
|
||||
# This happens if we fire after the activity expires.
|
||||
# In that case we don't need to do anything.
|
||||
pass
|
||||
|
||||
|
||||
class Scoreboard:
|
||||
"""A display for player or team scores during a game.
|
||||
|
||||
category: Gameplay Classes
|
||||
"""
|
||||
|
||||
_ENTRYSTORENAME = ba.storagename('entry')
|
||||
|
||||
def __init__(self, label: ba.Lstr | None = None, score_split: float = 0.7):
|
||||
"""Instantiate a scoreboard.
|
||||
|
||||
Label can be something like 'points' and will
|
||||
show up on boards if provided.
|
||||
"""
|
||||
self._flat_tex = ba.gettexture('null')
|
||||
self._entries: dict[int, _Entry] = {}
|
||||
self._label = label
|
||||
self.score_split = score_split
|
||||
|
||||
# For free-for-all we go simpler since we have one per player.
|
||||
self._pos: Sequence[float]
|
||||
if isinstance(ba.getsession(), ba.FreeForAllSession):
|
||||
self._do_cover = False
|
||||
self._spacing = 35.0
|
||||
self._pos = (17.0, -65.0)
|
||||
self._scale = 0.8
|
||||
self._flash_length = 0.5
|
||||
else:
|
||||
self._do_cover = True
|
||||
self._spacing = 50.0
|
||||
self._pos = (20.0, -70.0)
|
||||
self._scale = 1.0
|
||||
self._flash_length = 1.0
|
||||
|
||||
def set_team_value(
|
||||
self,
|
||||
team: ba.Team,
|
||||
score: float,
|
||||
max_score: float | None = None,
|
||||
countdown: bool = False,
|
||||
flash: bool = True,
|
||||
show_value: bool = True,
|
||||
) -> None:
|
||||
"""Update the score-board display for the given ba.Team."""
|
||||
if team.id not in self._entries:
|
||||
self._add_team(team)
|
||||
|
||||
# Create a proxy in the team which will kill
|
||||
# our entry when it dies (for convenience)
|
||||
assert self._ENTRYSTORENAME not in team.customdata
|
||||
team.customdata[self._ENTRYSTORENAME] = _EntryProxy(self, team)
|
||||
|
||||
# Now set the entry.
|
||||
self._entries[team.id].set_value(
|
||||
score=score,
|
||||
max_score=max_score,
|
||||
countdown=countdown,
|
||||
flash=flash,
|
||||
show_value=show_value,
|
||||
)
|
||||
|
||||
def _add_team(self, team: ba.Team) -> None:
|
||||
if team.id in self._entries:
|
||||
raise RuntimeError('Duplicate team add')
|
||||
self._entries[team.id] = _Entry(
|
||||
self,
|
||||
team,
|
||||
do_cover=self._do_cover,
|
||||
scale=self._scale,
|
||||
label=self._label,
|
||||
flash_length=self._flash_length,
|
||||
)
|
||||
self._update_teams()
|
||||
|
||||
def remove_team(self, team_id: int) -> None:
|
||||
"""Remove the team with the given id from the scoreboard."""
|
||||
del self._entries[team_id]
|
||||
self._update_teams()
|
||||
|
||||
def _update_teams(self) -> None:
|
||||
pos = list(self._pos)
|
||||
for entry in list(self._entries.values()):
|
||||
entry.set_position(pos)
|
||||
pos[1] -= self._spacing * self._scale
|
||||
118
dist/ba_data/python/bastd/actor/spawner.py
vendored
Normal file
118
dist/ba_data/python/bastd/actor/spawner.py
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines some lovely Actor(s)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence, Callable
|
||||
|
||||
|
||||
# FIXME: Should make this an Actor.
|
||||
class Spawner:
|
||||
"""Utility for delayed spawning of objects.
|
||||
|
||||
Category: **Gameplay Classes**
|
||||
|
||||
Creates a light flash and sends a Spawner.SpawnMessage
|
||||
to the current activity after a delay.
|
||||
"""
|
||||
|
||||
class SpawnMessage:
|
||||
"""Spawn message sent by a Spawner after its delay has passed.
|
||||
|
||||
Category: **Message Classes**
|
||||
"""
|
||||
|
||||
spawner: Spawner
|
||||
"""The ba.Spawner we came from."""
|
||||
|
||||
data: Any
|
||||
"""The data object passed by the user."""
|
||||
|
||||
pt: Sequence[float]
|
||||
"""The spawn position."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
spawner: Spawner,
|
||||
data: Any,
|
||||
pt: Sequence[float], # pylint: disable=invalid-name
|
||||
):
|
||||
"""Instantiate with the given values."""
|
||||
self.spawner = spawner
|
||||
self.data = data
|
||||
self.pt = pt # pylint: disable=invalid-name
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: Any = None,
|
||||
pt: Sequence[float] = (0, 0, 0), # pylint: disable=invalid-name
|
||||
spawn_time: float = 1.0,
|
||||
send_spawn_message: bool = True,
|
||||
spawn_callback: Callable[[], Any] | None = None,
|
||||
):
|
||||
"""Instantiate a Spawner.
|
||||
|
||||
Requires some custom data, a position,
|
||||
and a spawn-time in seconds.
|
||||
"""
|
||||
self._spawn_callback = spawn_callback
|
||||
self._send_spawn_message = send_spawn_message
|
||||
self._spawner_sound = ba.getsound('swip2')
|
||||
self._data = data
|
||||
self._pt = pt
|
||||
# create a light where the spawn will happen
|
||||
self._light = ba.newnode(
|
||||
'light',
|
||||
attrs={
|
||||
'position': tuple(pt),
|
||||
'radius': 0.1,
|
||||
'color': (1.0, 0.1, 0.1),
|
||||
'lights_volumes': False,
|
||||
},
|
||||
)
|
||||
scl = float(spawn_time) / 3.75
|
||||
min_val = 0.4
|
||||
max_val = 0.7
|
||||
ba.playsound(self._spawner_sound, position=self._light.position)
|
||||
ba.animate(
|
||||
self._light,
|
||||
'intensity',
|
||||
{
|
||||
0.0: 0.0,
|
||||
0.25 * scl: max_val,
|
||||
0.500 * scl: min_val,
|
||||
0.750 * scl: max_val,
|
||||
1.000 * scl: min_val,
|
||||
1.250 * scl: 1.1 * max_val,
|
||||
1.500 * scl: min_val,
|
||||
1.750 * scl: 1.2 * max_val,
|
||||
2.000 * scl: min_val,
|
||||
2.250 * scl: 1.3 * max_val,
|
||||
2.500 * scl: min_val,
|
||||
2.750 * scl: 1.4 * max_val,
|
||||
3.000 * scl: min_val,
|
||||
3.250 * scl: 1.5 * max_val,
|
||||
3.500 * scl: min_val,
|
||||
3.750 * scl: 2.0,
|
||||
4.000 * scl: 0.0,
|
||||
},
|
||||
)
|
||||
ba.timer(spawn_time, self._spawn)
|
||||
|
||||
def _spawn(self) -> None:
|
||||
ba.timer(1.0, self._light.delete)
|
||||
if self._spawn_callback is not None:
|
||||
self._spawn_callback()
|
||||
if self._send_spawn_message:
|
||||
# only run if our activity still exists
|
||||
activity = ba.getactivity()
|
||||
if activity is not None:
|
||||
activity.handlemessage(
|
||||
self.SpawnMessage(self, self._data, self._pt)
|
||||
)
|
||||
1633
dist/ba_data/python/bastd/actor/spaz.py
vendored
Normal file
1633
dist/ba_data/python/bastd/actor/spaz.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
988
dist/ba_data/python/bastd/actor/spazappearance.py
vendored
Normal file
988
dist/ba_data/python/bastd/actor/spazappearance.py
vendored
Normal file
|
|
@ -0,0 +1,988 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Appearance functionality for spazzes."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
def get_appearances(include_locked: bool = False) -> list[str]:
|
||||
"""Get the list of available spaz appearances."""
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
get_purchased = ba.internal.get_purchased
|
||||
disallowed = []
|
||||
if not include_locked:
|
||||
# hmm yeah this'll be tough to hack...
|
||||
if not get_purchased('characters.santa'):
|
||||
disallowed.append('Santa Claus')
|
||||
if not get_purchased('characters.frosty'):
|
||||
disallowed.append('Frosty')
|
||||
if not get_purchased('characters.bones'):
|
||||
disallowed.append('Bones')
|
||||
if not get_purchased('characters.bernard'):
|
||||
disallowed.append('Bernard')
|
||||
if not get_purchased('characters.pixie'):
|
||||
disallowed.append('Pixel')
|
||||
if not get_purchased('characters.pascal'):
|
||||
disallowed.append('Pascal')
|
||||
if not get_purchased('characters.actionhero'):
|
||||
disallowed.append('Todd McBurton')
|
||||
if not get_purchased('characters.taobaomascot'):
|
||||
disallowed.append('Taobao Mascot')
|
||||
if not get_purchased('characters.agent'):
|
||||
disallowed.append('Agent Johnson')
|
||||
if not get_purchased('characters.jumpsuit'):
|
||||
disallowed.append('Lee')
|
||||
if not get_purchased('characters.assassin'):
|
||||
disallowed.append('Zola')
|
||||
if not get_purchased('characters.wizard'):
|
||||
disallowed.append('Grumbledorf')
|
||||
if not get_purchased('characters.cowboy'):
|
||||
disallowed.append('Butch')
|
||||
if not get_purchased('characters.witch'):
|
||||
disallowed.append('Witch')
|
||||
if not get_purchased('characters.warrior'):
|
||||
disallowed.append('Warrior')
|
||||
if not get_purchased('characters.superhero'):
|
||||
disallowed.append('Middle-Man')
|
||||
if not get_purchased('characters.alien'):
|
||||
disallowed.append('Alien')
|
||||
if not get_purchased('characters.oldlady'):
|
||||
disallowed.append('OldLady')
|
||||
if not get_purchased('characters.gladiator'):
|
||||
disallowed.append('Gladiator')
|
||||
if not get_purchased('characters.wrestler'):
|
||||
disallowed.append('Wrestler')
|
||||
if not get_purchased('characters.operasinger'):
|
||||
disallowed.append('Gretel')
|
||||
if not get_purchased('characters.robot'):
|
||||
disallowed.append('Robot')
|
||||
if not get_purchased('characters.cyborg'):
|
||||
disallowed.append('B-9000')
|
||||
if not get_purchased('characters.bunny'):
|
||||
disallowed.append('Easter Bunny')
|
||||
if not get_purchased('characters.kronk'):
|
||||
disallowed.append('Kronk')
|
||||
if not get_purchased('characters.zoe'):
|
||||
disallowed.append('Zoe')
|
||||
if not get_purchased('characters.jackmorgan'):
|
||||
disallowed.append('Jack Morgan')
|
||||
if not get_purchased('characters.mel'):
|
||||
disallowed.append('Mel')
|
||||
if not get_purchased('characters.snakeshadow'):
|
||||
disallowed.append('Snake Shadow')
|
||||
return [
|
||||
s for s in list(ba.app.spaz_appearances.keys()) if s not in disallowed
|
||||
]
|
||||
|
||||
|
||||
class Appearance:
|
||||
"""Create and fill out one of these suckers to define a spaz appearance"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
if self.name in ba.app.spaz_appearances:
|
||||
raise Exception(
|
||||
'spaz appearance name "' + self.name + '" already exists.'
|
||||
)
|
||||
ba.app.spaz_appearances[self.name] = self
|
||||
self.color_texture = ''
|
||||
self.color_mask_texture = ''
|
||||
self.icon_texture = ''
|
||||
self.icon_mask_texture = ''
|
||||
self.head_model = ''
|
||||
self.torso_model = ''
|
||||
self.pelvis_model = ''
|
||||
self.upper_arm_model = ''
|
||||
self.forearm_model = ''
|
||||
self.hand_model = ''
|
||||
self.upper_leg_model = ''
|
||||
self.lower_leg_model = ''
|
||||
self.toes_model = ''
|
||||
self.jump_sounds: list[str] = []
|
||||
self.attack_sounds: list[str] = []
|
||||
self.impact_sounds: list[str] = []
|
||||
self.death_sounds: list[str] = []
|
||||
self.pickup_sounds: list[str] = []
|
||||
self.fall_sounds: list[str] = []
|
||||
self.style = 'spaz'
|
||||
self.default_color: tuple[float, float, float] | None = None
|
||||
self.default_highlight: tuple[float, float, float] | None = None
|
||||
|
||||
|
||||
def register_appearances() -> None:
|
||||
"""Register our builtin spaz appearances."""
|
||||
|
||||
# this is quite ugly but will be going away so not worth cleaning up
|
||||
# pylint: disable=invalid-name
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
|
||||
# Spaz #######################################
|
||||
t = Appearance('Spaz')
|
||||
t.color_texture = 'neoSpazColor'
|
||||
t.color_mask_texture = 'neoSpazColorMask'
|
||||
t.icon_texture = 'neoSpazIcon'
|
||||
t.icon_mask_texture = 'neoSpazIconColorMask'
|
||||
t.head_model = 'neoSpazHead'
|
||||
t.torso_model = 'neoSpazTorso'
|
||||
t.pelvis_model = 'neoSpazPelvis'
|
||||
t.upper_arm_model = 'neoSpazUpperArm'
|
||||
t.forearm_model = 'neoSpazForeArm'
|
||||
t.hand_model = 'neoSpazHand'
|
||||
t.upper_leg_model = 'neoSpazUpperLeg'
|
||||
t.lower_leg_model = 'neoSpazLowerLeg'
|
||||
t.toes_model = 'neoSpazToes'
|
||||
t.jump_sounds = ['spazJump01', 'spazJump02', 'spazJump03', 'spazJump04']
|
||||
t.attack_sounds = [
|
||||
'spazAttack01',
|
||||
'spazAttack02',
|
||||
'spazAttack03',
|
||||
'spazAttack04',
|
||||
]
|
||||
t.impact_sounds = [
|
||||
'spazImpact01',
|
||||
'spazImpact02',
|
||||
'spazImpact03',
|
||||
'spazImpact04',
|
||||
]
|
||||
t.death_sounds = ['spazDeath01']
|
||||
t.pickup_sounds = ['spazPickup01']
|
||||
t.fall_sounds = ['spazFall01']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Zoe #####################################
|
||||
t = Appearance('Zoe')
|
||||
t.color_texture = 'zoeColor'
|
||||
t.color_mask_texture = 'zoeColorMask'
|
||||
t.default_color = (0.6, 0.6, 0.6)
|
||||
t.default_highlight = (0, 1, 0)
|
||||
t.icon_texture = 'zoeIcon'
|
||||
t.icon_mask_texture = 'zoeIconColorMask'
|
||||
t.head_model = 'zoeHead'
|
||||
t.torso_model = 'zoeTorso'
|
||||
t.pelvis_model = 'zoePelvis'
|
||||
t.upper_arm_model = 'zoeUpperArm'
|
||||
t.forearm_model = 'zoeForeArm'
|
||||
t.hand_model = 'zoeHand'
|
||||
t.upper_leg_model = 'zoeUpperLeg'
|
||||
t.lower_leg_model = 'zoeLowerLeg'
|
||||
t.toes_model = 'zoeToes'
|
||||
t.jump_sounds = ['zoeJump01', 'zoeJump02', 'zoeJump03']
|
||||
t.attack_sounds = [
|
||||
'zoeAttack01',
|
||||
'zoeAttack02',
|
||||
'zoeAttack03',
|
||||
'zoeAttack04',
|
||||
]
|
||||
t.impact_sounds = [
|
||||
'zoeImpact01',
|
||||
'zoeImpact02',
|
||||
'zoeImpact03',
|
||||
'zoeImpact04',
|
||||
]
|
||||
t.death_sounds = ['zoeDeath01']
|
||||
t.pickup_sounds = ['zoePickup01']
|
||||
t.fall_sounds = ['zoeFall01']
|
||||
t.style = 'female'
|
||||
|
||||
# Ninja ##########################################
|
||||
t = Appearance('Snake Shadow')
|
||||
t.color_texture = 'ninjaColor'
|
||||
t.color_mask_texture = 'ninjaColorMask'
|
||||
t.default_color = (1, 1, 1)
|
||||
t.default_highlight = (0.55, 0.8, 0.55)
|
||||
t.icon_texture = 'ninjaIcon'
|
||||
t.icon_mask_texture = 'ninjaIconColorMask'
|
||||
t.head_model = 'ninjaHead'
|
||||
t.torso_model = 'ninjaTorso'
|
||||
t.pelvis_model = 'ninjaPelvis'
|
||||
t.upper_arm_model = 'ninjaUpperArm'
|
||||
t.forearm_model = 'ninjaForeArm'
|
||||
t.hand_model = 'ninjaHand'
|
||||
t.upper_leg_model = 'ninjaUpperLeg'
|
||||
t.lower_leg_model = 'ninjaLowerLeg'
|
||||
t.toes_model = 'ninjaToes'
|
||||
ninja_attacks = ['ninjaAttack' + str(i + 1) + '' for i in range(7)]
|
||||
ninja_hits = ['ninjaHit' + str(i + 1) + '' for i in range(8)]
|
||||
ninja_jumps = ['ninjaAttack' + str(i + 1) + '' for i in range(7)]
|
||||
t.jump_sounds = ninja_jumps
|
||||
t.attack_sounds = ninja_attacks
|
||||
t.impact_sounds = ninja_hits
|
||||
t.death_sounds = ['ninjaDeath1']
|
||||
t.pickup_sounds = ninja_attacks
|
||||
t.fall_sounds = ['ninjaFall1']
|
||||
t.style = 'ninja'
|
||||
|
||||
# Barbarian #####################################
|
||||
t = Appearance('Kronk')
|
||||
t.color_texture = 'kronk'
|
||||
t.color_mask_texture = 'kronkColorMask'
|
||||
t.default_color = (0.4, 0.5, 0.4)
|
||||
t.default_highlight = (1, 0.5, 0.3)
|
||||
t.icon_texture = 'kronkIcon'
|
||||
t.icon_mask_texture = 'kronkIconColorMask'
|
||||
t.head_model = 'kronkHead'
|
||||
t.torso_model = 'kronkTorso'
|
||||
t.pelvis_model = 'kronkPelvis'
|
||||
t.upper_arm_model = 'kronkUpperArm'
|
||||
t.forearm_model = 'kronkForeArm'
|
||||
t.hand_model = 'kronkHand'
|
||||
t.upper_leg_model = 'kronkUpperLeg'
|
||||
t.lower_leg_model = 'kronkLowerLeg'
|
||||
t.toes_model = 'kronkToes'
|
||||
kronk_sounds = [
|
||||
'kronk1',
|
||||
'kronk2',
|
||||
'kronk3',
|
||||
'kronk4',
|
||||
'kronk5',
|
||||
'kronk6',
|
||||
'kronk7',
|
||||
'kronk8',
|
||||
'kronk9',
|
||||
'kronk10',
|
||||
]
|
||||
t.jump_sounds = kronk_sounds
|
||||
t.attack_sounds = kronk_sounds
|
||||
t.impact_sounds = kronk_sounds
|
||||
t.death_sounds = ['kronkDeath']
|
||||
t.pickup_sounds = kronk_sounds
|
||||
t.fall_sounds = ['kronkFall']
|
||||
t.style = 'kronk'
|
||||
|
||||
# Chef ###########################################
|
||||
t = Appearance('Mel')
|
||||
t.color_texture = 'melColor'
|
||||
t.color_mask_texture = 'melColorMask'
|
||||
t.default_color = (1, 1, 1)
|
||||
t.default_highlight = (0.1, 0.6, 0.1)
|
||||
t.icon_texture = 'melIcon'
|
||||
t.icon_mask_texture = 'melIconColorMask'
|
||||
t.head_model = 'melHead'
|
||||
t.torso_model = 'melTorso'
|
||||
t.pelvis_model = 'kronkPelvis'
|
||||
t.upper_arm_model = 'melUpperArm'
|
||||
t.forearm_model = 'melForeArm'
|
||||
t.hand_model = 'melHand'
|
||||
t.upper_leg_model = 'melUpperLeg'
|
||||
t.lower_leg_model = 'melLowerLeg'
|
||||
t.toes_model = 'melToes'
|
||||
mel_sounds = [
|
||||
'mel01',
|
||||
'mel02',
|
||||
'mel03',
|
||||
'mel04',
|
||||
'mel05',
|
||||
'mel06',
|
||||
'mel07',
|
||||
'mel08',
|
||||
'mel09',
|
||||
'mel10',
|
||||
]
|
||||
t.attack_sounds = mel_sounds
|
||||
t.jump_sounds = mel_sounds
|
||||
t.impact_sounds = mel_sounds
|
||||
t.death_sounds = ['melDeath01']
|
||||
t.pickup_sounds = mel_sounds
|
||||
t.fall_sounds = ['melFall01']
|
||||
t.style = 'mel'
|
||||
|
||||
# Pirate #######################################
|
||||
t = Appearance('Jack Morgan')
|
||||
t.color_texture = 'jackColor'
|
||||
t.color_mask_texture = 'jackColorMask'
|
||||
t.default_color = (1, 0.2, 0.1)
|
||||
t.default_highlight = (1, 1, 0)
|
||||
t.icon_texture = 'jackIcon'
|
||||
t.icon_mask_texture = 'jackIconColorMask'
|
||||
t.head_model = 'jackHead'
|
||||
t.torso_model = 'jackTorso'
|
||||
t.pelvis_model = 'kronkPelvis'
|
||||
t.upper_arm_model = 'jackUpperArm'
|
||||
t.forearm_model = 'jackForeArm'
|
||||
t.hand_model = 'jackHand'
|
||||
t.upper_leg_model = 'jackUpperLeg'
|
||||
t.lower_leg_model = 'jackLowerLeg'
|
||||
t.toes_model = 'jackToes'
|
||||
hit_sounds = [
|
||||
'jackHit01',
|
||||
'jackHit02',
|
||||
'jackHit03',
|
||||
'jackHit04',
|
||||
'jackHit05',
|
||||
'jackHit06',
|
||||
'jackHit07',
|
||||
]
|
||||
sounds = ['jack01', 'jack02', 'jack03', 'jack04', 'jack05', 'jack06']
|
||||
t.attack_sounds = sounds
|
||||
t.jump_sounds = sounds
|
||||
t.impact_sounds = hit_sounds
|
||||
t.death_sounds = ['jackDeath01']
|
||||
t.pickup_sounds = sounds
|
||||
t.fall_sounds = ['jackFall01']
|
||||
t.style = 'pirate'
|
||||
|
||||
# Santa ######################################
|
||||
t = Appearance('Santa Claus')
|
||||
t.color_texture = 'santaColor'
|
||||
t.color_mask_texture = 'santaColorMask'
|
||||
t.default_color = (1, 0, 0)
|
||||
t.default_highlight = (1, 1, 1)
|
||||
t.icon_texture = 'santaIcon'
|
||||
t.icon_mask_texture = 'santaIconColorMask'
|
||||
t.head_model = 'santaHead'
|
||||
t.torso_model = 'santaTorso'
|
||||
t.pelvis_model = 'kronkPelvis'
|
||||
t.upper_arm_model = 'santaUpperArm'
|
||||
t.forearm_model = 'santaForeArm'
|
||||
t.hand_model = 'santaHand'
|
||||
t.upper_leg_model = 'santaUpperLeg'
|
||||
t.lower_leg_model = 'santaLowerLeg'
|
||||
t.toes_model = 'santaToes'
|
||||
hit_sounds = ['santaHit01', 'santaHit02', 'santaHit03', 'santaHit04']
|
||||
sounds = ['santa01', 'santa02', 'santa03', 'santa04', 'santa05']
|
||||
t.attack_sounds = sounds
|
||||
t.jump_sounds = sounds
|
||||
t.impact_sounds = hit_sounds
|
||||
t.death_sounds = ['santaDeath']
|
||||
t.pickup_sounds = sounds
|
||||
t.fall_sounds = ['santaFall']
|
||||
t.style = 'santa'
|
||||
|
||||
# Snowman ###################################
|
||||
t = Appearance('Frosty')
|
||||
t.color_texture = 'frostyColor'
|
||||
t.color_mask_texture = 'frostyColorMask'
|
||||
t.default_color = (0.5, 0.5, 1)
|
||||
t.default_highlight = (1, 0.5, 0)
|
||||
t.icon_texture = 'frostyIcon'
|
||||
t.icon_mask_texture = 'frostyIconColorMask'
|
||||
t.head_model = 'frostyHead'
|
||||
t.torso_model = 'frostyTorso'
|
||||
t.pelvis_model = 'frostyPelvis'
|
||||
t.upper_arm_model = 'frostyUpperArm'
|
||||
t.forearm_model = 'frostyForeArm'
|
||||
t.hand_model = 'frostyHand'
|
||||
t.upper_leg_model = 'frostyUpperLeg'
|
||||
t.lower_leg_model = 'frostyLowerLeg'
|
||||
t.toes_model = 'frostyToes'
|
||||
frosty_sounds = ['frosty01', 'frosty02', 'frosty03', 'frosty04', 'frosty05']
|
||||
frosty_hit_sounds = ['frostyHit01', 'frostyHit02', 'frostyHit03']
|
||||
t.attack_sounds = frosty_sounds
|
||||
t.jump_sounds = frosty_sounds
|
||||
t.impact_sounds = frosty_hit_sounds
|
||||
t.death_sounds = ['frostyDeath']
|
||||
t.pickup_sounds = frosty_sounds
|
||||
t.fall_sounds = ['frostyFall']
|
||||
t.style = 'frosty'
|
||||
|
||||
# Skeleton ################################
|
||||
t = Appearance('Bones')
|
||||
t.color_texture = 'bonesColor'
|
||||
t.color_mask_texture = 'bonesColorMask'
|
||||
t.default_color = (0.6, 0.9, 1)
|
||||
t.default_highlight = (0.6, 0.9, 1)
|
||||
t.icon_texture = 'bonesIcon'
|
||||
t.icon_mask_texture = 'bonesIconColorMask'
|
||||
t.head_model = 'bonesHead'
|
||||
t.torso_model = 'bonesTorso'
|
||||
t.pelvis_model = 'bonesPelvis'
|
||||
t.upper_arm_model = 'bonesUpperArm'
|
||||
t.forearm_model = 'bonesForeArm'
|
||||
t.hand_model = 'bonesHand'
|
||||
t.upper_leg_model = 'bonesUpperLeg'
|
||||
t.lower_leg_model = 'bonesLowerLeg'
|
||||
t.toes_model = 'bonesToes'
|
||||
bones_sounds = ['bones1', 'bones2', 'bones3']
|
||||
bones_hit_sounds = ['bones1', 'bones2', 'bones3']
|
||||
t.attack_sounds = bones_sounds
|
||||
t.jump_sounds = bones_sounds
|
||||
t.impact_sounds = bones_hit_sounds
|
||||
t.death_sounds = ['bonesDeath']
|
||||
t.pickup_sounds = bones_sounds
|
||||
t.fall_sounds = ['bonesFall']
|
||||
t.style = 'bones'
|
||||
|
||||
# Bear ###################################
|
||||
t = Appearance('Bernard')
|
||||
t.color_texture = 'bearColor'
|
||||
t.color_mask_texture = 'bearColorMask'
|
||||
t.default_color = (0.7, 0.5, 0.0)
|
||||
t.icon_texture = 'bearIcon'
|
||||
t.icon_mask_texture = 'bearIconColorMask'
|
||||
t.head_model = 'bearHead'
|
||||
t.torso_model = 'bearTorso'
|
||||
t.pelvis_model = 'bearPelvis'
|
||||
t.upper_arm_model = 'bearUpperArm'
|
||||
t.forearm_model = 'bearForeArm'
|
||||
t.hand_model = 'bearHand'
|
||||
t.upper_leg_model = 'bearUpperLeg'
|
||||
t.lower_leg_model = 'bearLowerLeg'
|
||||
t.toes_model = 'bearToes'
|
||||
bear_sounds = ['bear1', 'bear2', 'bear3', 'bear4']
|
||||
bear_hit_sounds = ['bearHit1', 'bearHit2']
|
||||
t.attack_sounds = bear_sounds
|
||||
t.jump_sounds = bear_sounds
|
||||
t.impact_sounds = bear_hit_sounds
|
||||
t.death_sounds = ['bearDeath']
|
||||
t.pickup_sounds = bear_sounds
|
||||
t.fall_sounds = ['bearFall']
|
||||
t.style = 'bear'
|
||||
|
||||
# Penguin ###################################
|
||||
t = Appearance('Pascal')
|
||||
t.color_texture = 'penguinColor'
|
||||
t.color_mask_texture = 'penguinColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'penguinIcon'
|
||||
t.icon_mask_texture = 'penguinIconColorMask'
|
||||
t.head_model = 'penguinHead'
|
||||
t.torso_model = 'penguinTorso'
|
||||
t.pelvis_model = 'penguinPelvis'
|
||||
t.upper_arm_model = 'penguinUpperArm'
|
||||
t.forearm_model = 'penguinForeArm'
|
||||
t.hand_model = 'penguinHand'
|
||||
t.upper_leg_model = 'penguinUpperLeg'
|
||||
t.lower_leg_model = 'penguinLowerLeg'
|
||||
t.toes_model = 'penguinToes'
|
||||
penguin_sounds = ['penguin1', 'penguin2', 'penguin3', 'penguin4']
|
||||
penguin_hit_sounds = ['penguinHit1', 'penguinHit2']
|
||||
t.attack_sounds = penguin_sounds
|
||||
t.jump_sounds = penguin_sounds
|
||||
t.impact_sounds = penguin_hit_sounds
|
||||
t.death_sounds = ['penguinDeath']
|
||||
t.pickup_sounds = penguin_sounds
|
||||
t.fall_sounds = ['penguinFall']
|
||||
t.style = 'penguin'
|
||||
|
||||
# Ali ###################################
|
||||
t = Appearance('Taobao Mascot')
|
||||
t.color_texture = 'aliColor'
|
||||
t.color_mask_texture = 'aliColorMask'
|
||||
t.default_color = (1, 0.5, 0)
|
||||
t.default_highlight = (1, 1, 1)
|
||||
t.icon_texture = 'aliIcon'
|
||||
t.icon_mask_texture = 'aliIconColorMask'
|
||||
t.head_model = 'aliHead'
|
||||
t.torso_model = 'aliTorso'
|
||||
t.pelvis_model = 'aliPelvis'
|
||||
t.upper_arm_model = 'aliUpperArm'
|
||||
t.forearm_model = 'aliForeArm'
|
||||
t.hand_model = 'aliHand'
|
||||
t.upper_leg_model = 'aliUpperLeg'
|
||||
t.lower_leg_model = 'aliLowerLeg'
|
||||
t.toes_model = 'aliToes'
|
||||
ali_sounds = ['ali1', 'ali2', 'ali3', 'ali4']
|
||||
ali_hit_sounds = ['aliHit1', 'aliHit2']
|
||||
t.attack_sounds = ali_sounds
|
||||
t.jump_sounds = ali_sounds
|
||||
t.impact_sounds = ali_hit_sounds
|
||||
t.death_sounds = ['aliDeath']
|
||||
t.pickup_sounds = ali_sounds
|
||||
t.fall_sounds = ['aliFall']
|
||||
t.style = 'ali'
|
||||
|
||||
# cyborg ###################################
|
||||
t = Appearance('B-9000')
|
||||
t.color_texture = 'cyborgColor'
|
||||
t.color_mask_texture = 'cyborgColorMask'
|
||||
t.default_color = (0.5, 0.5, 0.5)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'cyborgIcon'
|
||||
t.icon_mask_texture = 'cyborgIconColorMask'
|
||||
t.head_model = 'cyborgHead'
|
||||
t.torso_model = 'cyborgTorso'
|
||||
t.pelvis_model = 'cyborgPelvis'
|
||||
t.upper_arm_model = 'cyborgUpperArm'
|
||||
t.forearm_model = 'cyborgForeArm'
|
||||
t.hand_model = 'cyborgHand'
|
||||
t.upper_leg_model = 'cyborgUpperLeg'
|
||||
t.lower_leg_model = 'cyborgLowerLeg'
|
||||
t.toes_model = 'cyborgToes'
|
||||
cyborg_sounds = ['cyborg1', 'cyborg2', 'cyborg3', 'cyborg4']
|
||||
cyborg_hit_sounds = ['cyborgHit1', 'cyborgHit2']
|
||||
t.attack_sounds = cyborg_sounds
|
||||
t.jump_sounds = cyborg_sounds
|
||||
t.impact_sounds = cyborg_hit_sounds
|
||||
t.death_sounds = ['cyborgDeath']
|
||||
t.pickup_sounds = cyborg_sounds
|
||||
t.fall_sounds = ['cyborgFall']
|
||||
t.style = 'cyborg'
|
||||
|
||||
# Agent ###################################
|
||||
t = Appearance('Agent Johnson')
|
||||
t.color_texture = 'agentColor'
|
||||
t.color_mask_texture = 'agentColorMask'
|
||||
t.default_color = (0.3, 0.3, 0.33)
|
||||
t.default_highlight = (1, 0.5, 0.3)
|
||||
t.icon_texture = 'agentIcon'
|
||||
t.icon_mask_texture = 'agentIconColorMask'
|
||||
t.head_model = 'agentHead'
|
||||
t.torso_model = 'agentTorso'
|
||||
t.pelvis_model = 'agentPelvis'
|
||||
t.upper_arm_model = 'agentUpperArm'
|
||||
t.forearm_model = 'agentForeArm'
|
||||
t.hand_model = 'agentHand'
|
||||
t.upper_leg_model = 'agentUpperLeg'
|
||||
t.lower_leg_model = 'agentLowerLeg'
|
||||
t.toes_model = 'agentToes'
|
||||
agent_sounds = ['agent1', 'agent2', 'agent3', 'agent4']
|
||||
agent_hit_sounds = ['agentHit1', 'agentHit2']
|
||||
t.attack_sounds = agent_sounds
|
||||
t.jump_sounds = agent_sounds
|
||||
t.impact_sounds = agent_hit_sounds
|
||||
t.death_sounds = ['agentDeath']
|
||||
t.pickup_sounds = agent_sounds
|
||||
t.fall_sounds = ['agentFall']
|
||||
t.style = 'agent'
|
||||
|
||||
# Jumpsuit ###################################
|
||||
t = Appearance('Lee')
|
||||
t.color_texture = 'jumpsuitColor'
|
||||
t.color_mask_texture = 'jumpsuitColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'jumpsuitIcon'
|
||||
t.icon_mask_texture = 'jumpsuitIconColorMask'
|
||||
t.head_model = 'jumpsuitHead'
|
||||
t.torso_model = 'jumpsuitTorso'
|
||||
t.pelvis_model = 'jumpsuitPelvis'
|
||||
t.upper_arm_model = 'jumpsuitUpperArm'
|
||||
t.forearm_model = 'jumpsuitForeArm'
|
||||
t.hand_model = 'jumpsuitHand'
|
||||
t.upper_leg_model = 'jumpsuitUpperLeg'
|
||||
t.lower_leg_model = 'jumpsuitLowerLeg'
|
||||
t.toes_model = 'jumpsuitToes'
|
||||
jumpsuit_sounds = ['jumpsuit1', 'jumpsuit2', 'jumpsuit3', 'jumpsuit4']
|
||||
jumpsuit_hit_sounds = ['jumpsuitHit1', 'jumpsuitHit2']
|
||||
t.attack_sounds = jumpsuit_sounds
|
||||
t.jump_sounds = jumpsuit_sounds
|
||||
t.impact_sounds = jumpsuit_hit_sounds
|
||||
t.death_sounds = ['jumpsuitDeath']
|
||||
t.pickup_sounds = jumpsuit_sounds
|
||||
t.fall_sounds = ['jumpsuitFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# ActionHero ###################################
|
||||
t = Appearance('Todd McBurton')
|
||||
t.color_texture = 'actionHeroColor'
|
||||
t.color_mask_texture = 'actionHeroColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'actionHeroIcon'
|
||||
t.icon_mask_texture = 'actionHeroIconColorMask'
|
||||
t.head_model = 'actionHeroHead'
|
||||
t.torso_model = 'actionHeroTorso'
|
||||
t.pelvis_model = 'actionHeroPelvis'
|
||||
t.upper_arm_model = 'actionHeroUpperArm'
|
||||
t.forearm_model = 'actionHeroForeArm'
|
||||
t.hand_model = 'actionHeroHand'
|
||||
t.upper_leg_model = 'actionHeroUpperLeg'
|
||||
t.lower_leg_model = 'actionHeroLowerLeg'
|
||||
t.toes_model = 'actionHeroToes'
|
||||
action_hero_sounds = [
|
||||
'actionHero1',
|
||||
'actionHero2',
|
||||
'actionHero3',
|
||||
'actionHero4',
|
||||
]
|
||||
action_hero_hit_sounds = ['actionHeroHit1', 'actionHeroHit2']
|
||||
t.attack_sounds = action_hero_sounds
|
||||
t.jump_sounds = action_hero_sounds
|
||||
t.impact_sounds = action_hero_hit_sounds
|
||||
t.death_sounds = ['actionHeroDeath']
|
||||
t.pickup_sounds = action_hero_sounds
|
||||
t.fall_sounds = ['actionHeroFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Assassin ###################################
|
||||
t = Appearance('Zola')
|
||||
t.color_texture = 'assassinColor'
|
||||
t.color_mask_texture = 'assassinColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'assassinIcon'
|
||||
t.icon_mask_texture = 'assassinIconColorMask'
|
||||
t.head_model = 'assassinHead'
|
||||
t.torso_model = 'assassinTorso'
|
||||
t.pelvis_model = 'assassinPelvis'
|
||||
t.upper_arm_model = 'assassinUpperArm'
|
||||
t.forearm_model = 'assassinForeArm'
|
||||
t.hand_model = 'assassinHand'
|
||||
t.upper_leg_model = 'assassinUpperLeg'
|
||||
t.lower_leg_model = 'assassinLowerLeg'
|
||||
t.toes_model = 'assassinToes'
|
||||
assassin_sounds = ['assassin1', 'assassin2', 'assassin3', 'assassin4']
|
||||
assassin_hit_sounds = ['assassinHit1', 'assassinHit2']
|
||||
t.attack_sounds = assassin_sounds
|
||||
t.jump_sounds = assassin_sounds
|
||||
t.impact_sounds = assassin_hit_sounds
|
||||
t.death_sounds = ['assassinDeath']
|
||||
t.pickup_sounds = assassin_sounds
|
||||
t.fall_sounds = ['assassinFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Wizard ###################################
|
||||
t = Appearance('Grumbledorf')
|
||||
t.color_texture = 'wizardColor'
|
||||
t.color_mask_texture = 'wizardColorMask'
|
||||
t.default_color = (0.2, 0.4, 1.0)
|
||||
t.default_highlight = (0.06, 0.15, 0.4)
|
||||
t.icon_texture = 'wizardIcon'
|
||||
t.icon_mask_texture = 'wizardIconColorMask'
|
||||
t.head_model = 'wizardHead'
|
||||
t.torso_model = 'wizardTorso'
|
||||
t.pelvis_model = 'wizardPelvis'
|
||||
t.upper_arm_model = 'wizardUpperArm'
|
||||
t.forearm_model = 'wizardForeArm'
|
||||
t.hand_model = 'wizardHand'
|
||||
t.upper_leg_model = 'wizardUpperLeg'
|
||||
t.lower_leg_model = 'wizardLowerLeg'
|
||||
t.toes_model = 'wizardToes'
|
||||
wizard_sounds = ['wizard1', 'wizard2', 'wizard3', 'wizard4']
|
||||
wizard_hit_sounds = ['wizardHit1', 'wizardHit2']
|
||||
t.attack_sounds = wizard_sounds
|
||||
t.jump_sounds = wizard_sounds
|
||||
t.impact_sounds = wizard_hit_sounds
|
||||
t.death_sounds = ['wizardDeath']
|
||||
t.pickup_sounds = wizard_sounds
|
||||
t.fall_sounds = ['wizardFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Cowboy ###################################
|
||||
t = Appearance('Butch')
|
||||
t.color_texture = 'cowboyColor'
|
||||
t.color_mask_texture = 'cowboyColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'cowboyIcon'
|
||||
t.icon_mask_texture = 'cowboyIconColorMask'
|
||||
t.head_model = 'cowboyHead'
|
||||
t.torso_model = 'cowboyTorso'
|
||||
t.pelvis_model = 'cowboyPelvis'
|
||||
t.upper_arm_model = 'cowboyUpperArm'
|
||||
t.forearm_model = 'cowboyForeArm'
|
||||
t.hand_model = 'cowboyHand'
|
||||
t.upper_leg_model = 'cowboyUpperLeg'
|
||||
t.lower_leg_model = 'cowboyLowerLeg'
|
||||
t.toes_model = 'cowboyToes'
|
||||
cowboy_sounds = ['cowboy1', 'cowboy2', 'cowboy3', 'cowboy4']
|
||||
cowboy_hit_sounds = ['cowboyHit1', 'cowboyHit2']
|
||||
t.attack_sounds = cowboy_sounds
|
||||
t.jump_sounds = cowboy_sounds
|
||||
t.impact_sounds = cowboy_hit_sounds
|
||||
t.death_sounds = ['cowboyDeath']
|
||||
t.pickup_sounds = cowboy_sounds
|
||||
t.fall_sounds = ['cowboyFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Witch ###################################
|
||||
t = Appearance('Witch')
|
||||
t.color_texture = 'witchColor'
|
||||
t.color_mask_texture = 'witchColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'witchIcon'
|
||||
t.icon_mask_texture = 'witchIconColorMask'
|
||||
t.head_model = 'witchHead'
|
||||
t.torso_model = 'witchTorso'
|
||||
t.pelvis_model = 'witchPelvis'
|
||||
t.upper_arm_model = 'witchUpperArm'
|
||||
t.forearm_model = 'witchForeArm'
|
||||
t.hand_model = 'witchHand'
|
||||
t.upper_leg_model = 'witchUpperLeg'
|
||||
t.lower_leg_model = 'witchLowerLeg'
|
||||
t.toes_model = 'witchToes'
|
||||
witch_sounds = ['witch1', 'witch2', 'witch3', 'witch4']
|
||||
witch_hit_sounds = ['witchHit1', 'witchHit2']
|
||||
t.attack_sounds = witch_sounds
|
||||
t.jump_sounds = witch_sounds
|
||||
t.impact_sounds = witch_hit_sounds
|
||||
t.death_sounds = ['witchDeath']
|
||||
t.pickup_sounds = witch_sounds
|
||||
t.fall_sounds = ['witchFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Warrior ###################################
|
||||
t = Appearance('Warrior')
|
||||
t.color_texture = 'warriorColor'
|
||||
t.color_mask_texture = 'warriorColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'warriorIcon'
|
||||
t.icon_mask_texture = 'warriorIconColorMask'
|
||||
t.head_model = 'warriorHead'
|
||||
t.torso_model = 'warriorTorso'
|
||||
t.pelvis_model = 'warriorPelvis'
|
||||
t.upper_arm_model = 'warriorUpperArm'
|
||||
t.forearm_model = 'warriorForeArm'
|
||||
t.hand_model = 'warriorHand'
|
||||
t.upper_leg_model = 'warriorUpperLeg'
|
||||
t.lower_leg_model = 'warriorLowerLeg'
|
||||
t.toes_model = 'warriorToes'
|
||||
warrior_sounds = ['warrior1', 'warrior2', 'warrior3', 'warrior4']
|
||||
warrior_hit_sounds = ['warriorHit1', 'warriorHit2']
|
||||
t.attack_sounds = warrior_sounds
|
||||
t.jump_sounds = warrior_sounds
|
||||
t.impact_sounds = warrior_hit_sounds
|
||||
t.death_sounds = ['warriorDeath']
|
||||
t.pickup_sounds = warrior_sounds
|
||||
t.fall_sounds = ['warriorFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Superhero ###################################
|
||||
t = Appearance('Middle-Man')
|
||||
t.color_texture = 'superheroColor'
|
||||
t.color_mask_texture = 'superheroColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'superheroIcon'
|
||||
t.icon_mask_texture = 'superheroIconColorMask'
|
||||
t.head_model = 'superheroHead'
|
||||
t.torso_model = 'superheroTorso'
|
||||
t.pelvis_model = 'superheroPelvis'
|
||||
t.upper_arm_model = 'superheroUpperArm'
|
||||
t.forearm_model = 'superheroForeArm'
|
||||
t.hand_model = 'superheroHand'
|
||||
t.upper_leg_model = 'superheroUpperLeg'
|
||||
t.lower_leg_model = 'superheroLowerLeg'
|
||||
t.toes_model = 'superheroToes'
|
||||
superhero_sounds = ['superhero1', 'superhero2', 'superhero3', 'superhero4']
|
||||
superhero_hit_sounds = ['superheroHit1', 'superheroHit2']
|
||||
t.attack_sounds = superhero_sounds
|
||||
t.jump_sounds = superhero_sounds
|
||||
t.impact_sounds = superhero_hit_sounds
|
||||
t.death_sounds = ['superheroDeath']
|
||||
t.pickup_sounds = superhero_sounds
|
||||
t.fall_sounds = ['superheroFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Alien ###################################
|
||||
t = Appearance('Alien')
|
||||
t.color_texture = 'alienColor'
|
||||
t.color_mask_texture = 'alienColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'alienIcon'
|
||||
t.icon_mask_texture = 'alienIconColorMask'
|
||||
t.head_model = 'alienHead'
|
||||
t.torso_model = 'alienTorso'
|
||||
t.pelvis_model = 'alienPelvis'
|
||||
t.upper_arm_model = 'alienUpperArm'
|
||||
t.forearm_model = 'alienForeArm'
|
||||
t.hand_model = 'alienHand'
|
||||
t.upper_leg_model = 'alienUpperLeg'
|
||||
t.lower_leg_model = 'alienLowerLeg'
|
||||
t.toes_model = 'alienToes'
|
||||
alien_sounds = ['alien1', 'alien2', 'alien3', 'alien4']
|
||||
alien_hit_sounds = ['alienHit1', 'alienHit2']
|
||||
t.attack_sounds = alien_sounds
|
||||
t.jump_sounds = alien_sounds
|
||||
t.impact_sounds = alien_hit_sounds
|
||||
t.death_sounds = ['alienDeath']
|
||||
t.pickup_sounds = alien_sounds
|
||||
t.fall_sounds = ['alienFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# OldLady ###################################
|
||||
t = Appearance('OldLady')
|
||||
t.color_texture = 'oldLadyColor'
|
||||
t.color_mask_texture = 'oldLadyColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'oldLadyIcon'
|
||||
t.icon_mask_texture = 'oldLadyIconColorMask'
|
||||
t.head_model = 'oldLadyHead'
|
||||
t.torso_model = 'oldLadyTorso'
|
||||
t.pelvis_model = 'oldLadyPelvis'
|
||||
t.upper_arm_model = 'oldLadyUpperArm'
|
||||
t.forearm_model = 'oldLadyForeArm'
|
||||
t.hand_model = 'oldLadyHand'
|
||||
t.upper_leg_model = 'oldLadyUpperLeg'
|
||||
t.lower_leg_model = 'oldLadyLowerLeg'
|
||||
t.toes_model = 'oldLadyToes'
|
||||
old_lady_sounds = ['oldLady1', 'oldLady2', 'oldLady3', 'oldLady4']
|
||||
old_lady_hit_sounds = ['oldLadyHit1', 'oldLadyHit2']
|
||||
t.attack_sounds = old_lady_sounds
|
||||
t.jump_sounds = old_lady_sounds
|
||||
t.impact_sounds = old_lady_hit_sounds
|
||||
t.death_sounds = ['oldLadyDeath']
|
||||
t.pickup_sounds = old_lady_sounds
|
||||
t.fall_sounds = ['oldLadyFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Gladiator ###################################
|
||||
t = Appearance('Gladiator')
|
||||
t.color_texture = 'gladiatorColor'
|
||||
t.color_mask_texture = 'gladiatorColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'gladiatorIcon'
|
||||
t.icon_mask_texture = 'gladiatorIconColorMask'
|
||||
t.head_model = 'gladiatorHead'
|
||||
t.torso_model = 'gladiatorTorso'
|
||||
t.pelvis_model = 'gladiatorPelvis'
|
||||
t.upper_arm_model = 'gladiatorUpperArm'
|
||||
t.forearm_model = 'gladiatorForeArm'
|
||||
t.hand_model = 'gladiatorHand'
|
||||
t.upper_leg_model = 'gladiatorUpperLeg'
|
||||
t.lower_leg_model = 'gladiatorLowerLeg'
|
||||
t.toes_model = 'gladiatorToes'
|
||||
gladiator_sounds = ['gladiator1', 'gladiator2', 'gladiator3', 'gladiator4']
|
||||
gladiator_hit_sounds = ['gladiatorHit1', 'gladiatorHit2']
|
||||
t.attack_sounds = gladiator_sounds
|
||||
t.jump_sounds = gladiator_sounds
|
||||
t.impact_sounds = gladiator_hit_sounds
|
||||
t.death_sounds = ['gladiatorDeath']
|
||||
t.pickup_sounds = gladiator_sounds
|
||||
t.fall_sounds = ['gladiatorFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Wrestler ###################################
|
||||
t = Appearance('Wrestler')
|
||||
t.color_texture = 'wrestlerColor'
|
||||
t.color_mask_texture = 'wrestlerColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'wrestlerIcon'
|
||||
t.icon_mask_texture = 'wrestlerIconColorMask'
|
||||
t.head_model = 'wrestlerHead'
|
||||
t.torso_model = 'wrestlerTorso'
|
||||
t.pelvis_model = 'wrestlerPelvis'
|
||||
t.upper_arm_model = 'wrestlerUpperArm'
|
||||
t.forearm_model = 'wrestlerForeArm'
|
||||
t.hand_model = 'wrestlerHand'
|
||||
t.upper_leg_model = 'wrestlerUpperLeg'
|
||||
t.lower_leg_model = 'wrestlerLowerLeg'
|
||||
t.toes_model = 'wrestlerToes'
|
||||
wrestler_sounds = ['wrestler1', 'wrestler2', 'wrestler3', 'wrestler4']
|
||||
wrestler_hit_sounds = ['wrestlerHit1', 'wrestlerHit2']
|
||||
t.attack_sounds = wrestler_sounds
|
||||
t.jump_sounds = wrestler_sounds
|
||||
t.impact_sounds = wrestler_hit_sounds
|
||||
t.death_sounds = ['wrestlerDeath']
|
||||
t.pickup_sounds = wrestler_sounds
|
||||
t.fall_sounds = ['wrestlerFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# OperaSinger ###################################
|
||||
t = Appearance('Gretel')
|
||||
t.color_texture = 'operaSingerColor'
|
||||
t.color_mask_texture = 'operaSingerColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'operaSingerIcon'
|
||||
t.icon_mask_texture = 'operaSingerIconColorMask'
|
||||
t.head_model = 'operaSingerHead'
|
||||
t.torso_model = 'operaSingerTorso'
|
||||
t.pelvis_model = 'operaSingerPelvis'
|
||||
t.upper_arm_model = 'operaSingerUpperArm'
|
||||
t.forearm_model = 'operaSingerForeArm'
|
||||
t.hand_model = 'operaSingerHand'
|
||||
t.upper_leg_model = 'operaSingerUpperLeg'
|
||||
t.lower_leg_model = 'operaSingerLowerLeg'
|
||||
t.toes_model = 'operaSingerToes'
|
||||
opera_singer_sounds = [
|
||||
'operaSinger1',
|
||||
'operaSinger2',
|
||||
'operaSinger3',
|
||||
'operaSinger4',
|
||||
]
|
||||
opera_singer_hit_sounds = ['operaSingerHit1', 'operaSingerHit2']
|
||||
t.attack_sounds = opera_singer_sounds
|
||||
t.jump_sounds = opera_singer_sounds
|
||||
t.impact_sounds = opera_singer_hit_sounds
|
||||
t.death_sounds = ['operaSingerDeath']
|
||||
t.pickup_sounds = opera_singer_sounds
|
||||
t.fall_sounds = ['operaSingerFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Pixie ###################################
|
||||
t = Appearance('Pixel')
|
||||
t.color_texture = 'pixieColor'
|
||||
t.color_mask_texture = 'pixieColorMask'
|
||||
t.default_color = (0, 1, 0.7)
|
||||
t.default_highlight = (0.65, 0.35, 0.75)
|
||||
t.icon_texture = 'pixieIcon'
|
||||
t.icon_mask_texture = 'pixieIconColorMask'
|
||||
t.head_model = 'pixieHead'
|
||||
t.torso_model = 'pixieTorso'
|
||||
t.pelvis_model = 'pixiePelvis'
|
||||
t.upper_arm_model = 'pixieUpperArm'
|
||||
t.forearm_model = 'pixieForeArm'
|
||||
t.hand_model = 'pixieHand'
|
||||
t.upper_leg_model = 'pixieUpperLeg'
|
||||
t.lower_leg_model = 'pixieLowerLeg'
|
||||
t.toes_model = 'pixieToes'
|
||||
pixie_sounds = ['pixie1', 'pixie2', 'pixie3', 'pixie4']
|
||||
pixie_hit_sounds = ['pixieHit1', 'pixieHit2']
|
||||
t.attack_sounds = pixie_sounds
|
||||
t.jump_sounds = pixie_sounds
|
||||
t.impact_sounds = pixie_hit_sounds
|
||||
t.death_sounds = ['pixieDeath']
|
||||
t.pickup_sounds = pixie_sounds
|
||||
t.fall_sounds = ['pixieFall']
|
||||
t.style = 'pixie'
|
||||
|
||||
# Robot ###################################
|
||||
t = Appearance('Robot')
|
||||
t.color_texture = 'robotColor'
|
||||
t.color_mask_texture = 'robotColorMask'
|
||||
t.default_color = (0.3, 0.5, 0.8)
|
||||
t.default_highlight = (1, 0, 0)
|
||||
t.icon_texture = 'robotIcon'
|
||||
t.icon_mask_texture = 'robotIconColorMask'
|
||||
t.head_model = 'robotHead'
|
||||
t.torso_model = 'robotTorso'
|
||||
t.pelvis_model = 'robotPelvis'
|
||||
t.upper_arm_model = 'robotUpperArm'
|
||||
t.forearm_model = 'robotForeArm'
|
||||
t.hand_model = 'robotHand'
|
||||
t.upper_leg_model = 'robotUpperLeg'
|
||||
t.lower_leg_model = 'robotLowerLeg'
|
||||
t.toes_model = 'robotToes'
|
||||
robot_sounds = ['robot1', 'robot2', 'robot3', 'robot4']
|
||||
robot_hit_sounds = ['robotHit1', 'robotHit2']
|
||||
t.attack_sounds = robot_sounds
|
||||
t.jump_sounds = robot_sounds
|
||||
t.impact_sounds = robot_hit_sounds
|
||||
t.death_sounds = ['robotDeath']
|
||||
t.pickup_sounds = robot_sounds
|
||||
t.fall_sounds = ['robotFall']
|
||||
t.style = 'spaz'
|
||||
|
||||
# Bunny ###################################
|
||||
t = Appearance('Easter Bunny')
|
||||
t.color_texture = 'bunnyColor'
|
||||
t.color_mask_texture = 'bunnyColorMask'
|
||||
t.default_color = (1, 1, 1)
|
||||
t.default_highlight = (1, 0.5, 0.5)
|
||||
t.icon_texture = 'bunnyIcon'
|
||||
t.icon_mask_texture = 'bunnyIconColorMask'
|
||||
t.head_model = 'bunnyHead'
|
||||
t.torso_model = 'bunnyTorso'
|
||||
t.pelvis_model = 'bunnyPelvis'
|
||||
t.upper_arm_model = 'bunnyUpperArm'
|
||||
t.forearm_model = 'bunnyForeArm'
|
||||
t.hand_model = 'bunnyHand'
|
||||
t.upper_leg_model = 'bunnyUpperLeg'
|
||||
t.lower_leg_model = 'bunnyLowerLeg'
|
||||
t.toes_model = 'bunnyToes'
|
||||
bunny_sounds = ['bunny1', 'bunny2', 'bunny3', 'bunny4']
|
||||
bunny_hit_sounds = ['bunnyHit1', 'bunnyHit2']
|
||||
t.attack_sounds = bunny_sounds
|
||||
t.jump_sounds = ['bunnyJump']
|
||||
t.impact_sounds = bunny_hit_sounds
|
||||
t.death_sounds = ['bunnyDeath']
|
||||
t.pickup_sounds = bunny_sounds
|
||||
t.fall_sounds = ['bunnyFall']
|
||||
t.style = 'bunny'
|
||||
1120
dist/ba_data/python/bastd/actor/spazbot.py
vendored
Normal file
1120
dist/ba_data/python/bastd/actor/spazbot.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
307
dist/ba_data/python/bastd/actor/spazfactory.py
vendored
Normal file
307
dist/ba_data/python/bastd/actor/spazfactory.py
vendored
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides a factory object from creating Spazzes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import ba.internal
|
||||
from bastd.gameutils import SharedObjects
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
class SpazFactory:
|
||||
"""Wraps up media and other resources used by ba.Spaz instances.
|
||||
|
||||
Category: **Gameplay Classes**
|
||||
|
||||
Generally one of these is created per ba.Activity and shared
|
||||
between all spaz instances. Use ba.Spaz.get_factory() to return
|
||||
the shared factory for the current activity.
|
||||
"""
|
||||
|
||||
impact_sounds_medium: Sequence[ba.Sound]
|
||||
"""A tuple of ba.Sound-s for when a ba.Spaz hits something kinda hard."""
|
||||
|
||||
impact_sounds_hard: Sequence[ba.Sound]
|
||||
"""A tuple of ba.Sound-s for when a ba.Spaz hits something really hard."""
|
||||
|
||||
impact_sounds_harder: Sequence[ba.Sound]
|
||||
"""A tuple of ba.Sound-s for when a ba.Spaz hits something really
|
||||
really hard."""
|
||||
|
||||
single_player_death_sound: ba.Sound
|
||||
"""The sound that plays for an 'important' spaz death such as in
|
||||
co-op games."""
|
||||
|
||||
punch_sound_weak: ba.Sound
|
||||
"""A weak punch ba.Sound."""
|
||||
|
||||
punch_sound: ba.Sound
|
||||
"""A standard punch ba.Sound."""
|
||||
|
||||
punch_sound_strong: Sequence[ba.Sound]
|
||||
"""A tuple of stronger sounding punch ba.Sounds."""
|
||||
|
||||
punch_sound_stronger: ba.Sound
|
||||
"""A really really strong sounding punch ba.Sound."""
|
||||
|
||||
swish_sound: ba.Sound
|
||||
"""A punch swish ba.Sound."""
|
||||
|
||||
block_sound: ba.Sound
|
||||
"""A ba.Sound for when an attack is blocked by invincibility."""
|
||||
|
||||
shatter_sound: ba.Sound
|
||||
"""A ba.Sound for when a frozen ba.Spaz shatters."""
|
||||
|
||||
splatter_sound: ba.Sound
|
||||
"""A ba.Sound for when a ba.Spaz blows up via curse."""
|
||||
|
||||
spaz_material: ba.Material
|
||||
"""A ba.Material applied to all of parts of a ba.Spaz."""
|
||||
|
||||
roller_material: ba.Material
|
||||
"""A ba.Material applied to the invisible roller ball body that
|
||||
a ba.Spaz uses for locomotion."""
|
||||
|
||||
punch_material: ba.Material
|
||||
"""A ba.Material applied to the 'fist' of a ba.Spaz."""
|
||||
|
||||
pickup_material: ba.Material
|
||||
"""A ba.Material applied to the 'grabber' body of a ba.Spaz."""
|
||||
|
||||
curse_material: ba.Material
|
||||
"""A ba.Material applied to a cursed ba.Spaz that triggers an explosion."""
|
||||
|
||||
_STORENAME = ba.storagename()
|
||||
|
||||
def _preload(self, character: str) -> None:
|
||||
"""Preload media needed for a given character."""
|
||||
self.get_media(character)
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Instantiate a factory object."""
|
||||
# pylint: disable=cyclic-import
|
||||
# FIXME: should probably put these somewhere common so we don't
|
||||
# have to import them from a module that imports us.
|
||||
from bastd.actor.spaz import (
|
||||
PickupMessage,
|
||||
PunchHitMessage,
|
||||
CurseExplodeMessage,
|
||||
)
|
||||
|
||||
shared = SharedObjects.get()
|
||||
self.impact_sounds_medium = (
|
||||
ba.getsound('impactMedium'),
|
||||
ba.getsound('impactMedium2'),
|
||||
)
|
||||
self.impact_sounds_hard = (
|
||||
ba.getsound('impactHard'),
|
||||
ba.getsound('impactHard2'),
|
||||
ba.getsound('impactHard3'),
|
||||
)
|
||||
self.impact_sounds_harder = (
|
||||
ba.getsound('bigImpact'),
|
||||
ba.getsound('bigImpact2'),
|
||||
)
|
||||
self.single_player_death_sound = ba.getsound('playerDeath')
|
||||
self.punch_sound_weak = ba.getsound('punchWeak01')
|
||||
self.punch_sound = ba.getsound('punch01')
|
||||
self.punch_sound_strong = (
|
||||
ba.getsound('punchStrong01'),
|
||||
ba.getsound('punchStrong02'),
|
||||
)
|
||||
self.punch_sound_stronger = ba.getsound('superPunch')
|
||||
self.swish_sound = ba.getsound('punchSwish')
|
||||
self.block_sound = ba.getsound('block')
|
||||
self.shatter_sound = ba.getsound('shatter')
|
||||
self.splatter_sound = ba.getsound('splatter')
|
||||
self.spaz_material = ba.Material()
|
||||
self.roller_material = ba.Material()
|
||||
self.punch_material = ba.Material()
|
||||
self.pickup_material = ba.Material()
|
||||
self.curse_material = ba.Material()
|
||||
|
||||
footing_material = shared.footing_material
|
||||
object_material = shared.object_material
|
||||
player_material = shared.player_material
|
||||
region_material = shared.region_material
|
||||
|
||||
# Send footing messages to spazzes so they know when they're on
|
||||
# solid ground.
|
||||
# Eww; this probably should just be built into the spaz node.
|
||||
self.roller_material.add_actions(
|
||||
conditions=('they_have_material', footing_material),
|
||||
actions=(
|
||||
('message', 'our_node', 'at_connect', 'footing', 1),
|
||||
('message', 'our_node', 'at_disconnect', 'footing', -1),
|
||||
),
|
||||
)
|
||||
|
||||
self.spaz_material.add_actions(
|
||||
conditions=('they_have_material', footing_material),
|
||||
actions=(
|
||||
('message', 'our_node', 'at_connect', 'footing', 1),
|
||||
('message', 'our_node', 'at_disconnect', 'footing', -1),
|
||||
),
|
||||
)
|
||||
|
||||
# Punches.
|
||||
self.punch_material.add_actions(
|
||||
conditions=('they_are_different_node_than_us',),
|
||||
actions=(
|
||||
('modify_part_collision', 'collide', True),
|
||||
('modify_part_collision', 'physical', False),
|
||||
('message', 'our_node', 'at_connect', PunchHitMessage()),
|
||||
),
|
||||
)
|
||||
|
||||
# Pickups.
|
||||
self.pickup_material.add_actions(
|
||||
conditions=(
|
||||
('they_are_different_node_than_us',),
|
||||
'and',
|
||||
('they_have_material', object_material),
|
||||
),
|
||||
actions=(
|
||||
('modify_part_collision', 'collide', True),
|
||||
('modify_part_collision', 'physical', False),
|
||||
('message', 'our_node', 'at_connect', PickupMessage()),
|
||||
),
|
||||
)
|
||||
|
||||
# Curse.
|
||||
self.curse_material.add_actions(
|
||||
conditions=(
|
||||
('they_are_different_node_than_us',),
|
||||
'and',
|
||||
('they_have_material', player_material),
|
||||
),
|
||||
actions=(
|
||||
'message',
|
||||
'our_node',
|
||||
'at_connect',
|
||||
CurseExplodeMessage(),
|
||||
),
|
||||
)
|
||||
|
||||
self.foot_impact_sounds = (
|
||||
ba.getsound('footImpact01'),
|
||||
ba.getsound('footImpact02'),
|
||||
ba.getsound('footImpact03'),
|
||||
)
|
||||
|
||||
self.foot_skid_sound = ba.getsound('skid01')
|
||||
self.foot_roll_sound = ba.getsound('scamper01')
|
||||
|
||||
self.roller_material.add_actions(
|
||||
conditions=('they_have_material', footing_material),
|
||||
actions=(
|
||||
('impact_sound', self.foot_impact_sounds, 1, 0.2),
|
||||
('skid_sound', self.foot_skid_sound, 20, 0.3),
|
||||
('roll_sound', self.foot_roll_sound, 20, 3.0),
|
||||
),
|
||||
)
|
||||
|
||||
self.skid_sound = ba.getsound('gravelSkid')
|
||||
|
||||
self.spaz_material.add_actions(
|
||||
conditions=('they_have_material', footing_material),
|
||||
actions=(
|
||||
('impact_sound', self.foot_impact_sounds, 20, 6),
|
||||
('skid_sound', self.skid_sound, 2.0, 1),
|
||||
('roll_sound', self.skid_sound, 2.0, 1),
|
||||
),
|
||||
)
|
||||
|
||||
self.shield_up_sound = ba.getsound('shieldUp')
|
||||
self.shield_down_sound = ba.getsound('shieldDown')
|
||||
self.shield_hit_sound = ba.getsound('shieldHit')
|
||||
|
||||
# We don't want to collide with stuff we're initially overlapping
|
||||
# (unless its marked with a special region material).
|
||||
self.spaz_material.add_actions(
|
||||
conditions=(
|
||||
(
|
||||
('we_are_younger_than', 51),
|
||||
'and',
|
||||
('they_are_different_node_than_us',),
|
||||
),
|
||||
'and',
|
||||
('they_dont_have_material', region_material),
|
||||
),
|
||||
actions=('modify_node_collision', 'collide', False),
|
||||
)
|
||||
|
||||
self.spaz_media: dict[str, Any] = {}
|
||||
|
||||
# Lets load some basic rules.
|
||||
# (allows them to be tweaked from the master server)
|
||||
self.shield_decay_rate = ba.internal.get_v1_account_misc_read_val(
|
||||
'rsdr', 10.0
|
||||
)
|
||||
self.punch_cooldown = ba.internal.get_v1_account_misc_read_val(
|
||||
'rpc', 400
|
||||
)
|
||||
self.punch_cooldown_gloves = ba.internal.get_v1_account_misc_read_val(
|
||||
'rpcg', 300
|
||||
)
|
||||
self.punch_power_scale = ba.internal.get_v1_account_misc_read_val(
|
||||
'rpp', 1.2
|
||||
)
|
||||
self.punch_power_scale_gloves = (
|
||||
ba.internal.get_v1_account_misc_read_val('rppg', 1.4)
|
||||
)
|
||||
self.max_shield_spillover_damage = (
|
||||
ba.internal.get_v1_account_misc_read_val('rsms', 500)
|
||||
)
|
||||
|
||||
def get_style(self, character: str) -> str:
|
||||
"""Return the named style for this character.
|
||||
|
||||
(this influences subtle aspects of their appearance, etc)
|
||||
"""
|
||||
return ba.app.spaz_appearances[character].style
|
||||
|
||||
def get_media(self, character: str) -> dict[str, Any]:
|
||||
"""Return the set of media used by this variant of spaz."""
|
||||
char = ba.app.spaz_appearances[character]
|
||||
if character not in self.spaz_media:
|
||||
media = self.spaz_media[character] = {
|
||||
'jump_sounds': [ba.getsound(s) for s in char.jump_sounds],
|
||||
'attack_sounds': [ba.getsound(s) for s in char.attack_sounds],
|
||||
'impact_sounds': [ba.getsound(s) for s in char.impact_sounds],
|
||||
'death_sounds': [ba.getsound(s) for s in char.death_sounds],
|
||||
'pickup_sounds': [ba.getsound(s) for s in char.pickup_sounds],
|
||||
'fall_sounds': [ba.getsound(s) for s in char.fall_sounds],
|
||||
'color_texture': ba.gettexture(char.color_texture),
|
||||
'color_mask_texture': ba.gettexture(char.color_mask_texture),
|
||||
'head_model': ba.getmodel(char.head_model),
|
||||
'torso_model': ba.getmodel(char.torso_model),
|
||||
'pelvis_model': ba.getmodel(char.pelvis_model),
|
||||
'upper_arm_model': ba.getmodel(char.upper_arm_model),
|
||||
'forearm_model': ba.getmodel(char.forearm_model),
|
||||
'hand_model': ba.getmodel(char.hand_model),
|
||||
'upper_leg_model': ba.getmodel(char.upper_leg_model),
|
||||
'lower_leg_model': ba.getmodel(char.lower_leg_model),
|
||||
'toes_model': ba.getmodel(char.toes_model),
|
||||
}
|
||||
else:
|
||||
media = self.spaz_media[character]
|
||||
return media
|
||||
|
||||
@classmethod
|
||||
def get(cls) -> SpazFactory:
|
||||
"""Return the shared ba.SpazFactory, creating it if necessary."""
|
||||
# pylint: disable=cyclic-import
|
||||
activity = ba.getactivity()
|
||||
factory = activity.customdata.get(cls._STORENAME)
|
||||
if factory is None:
|
||||
factory = activity.customdata[cls._STORENAME] = SpazFactory()
|
||||
assert isinstance(factory, SpazFactory)
|
||||
return factory
|
||||
230
dist/ba_data/python/bastd/actor/text.py
vendored
Normal file
230
dist/ba_data/python/bastd/actor/text.py
vendored
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines Actor(s)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
class Text(ba.Actor):
|
||||
"""Text with some tricks."""
|
||||
|
||||
class Transition(Enum):
|
||||
"""Transition types for text."""
|
||||
|
||||
FADE_IN = 'fade_in'
|
||||
IN_RIGHT = 'in_right'
|
||||
IN_LEFT = 'in_left'
|
||||
IN_BOTTOM = 'in_bottom'
|
||||
IN_BOTTOM_SLOW = 'in_bottom_slow'
|
||||
IN_TOP_SLOW = 'in_top_slow'
|
||||
|
||||
class HAlign(Enum):
|
||||
"""Horizontal alignment type."""
|
||||
|
||||
LEFT = 'left'
|
||||
CENTER = 'center'
|
||||
RIGHT = 'right'
|
||||
|
||||
class VAlign(Enum):
|
||||
"""Vertical alignment type."""
|
||||
|
||||
NONE = 'none'
|
||||
CENTER = 'center'
|
||||
|
||||
class HAttach(Enum):
|
||||
"""Horizontal attach type."""
|
||||
|
||||
LEFT = 'left'
|
||||
CENTER = 'center'
|
||||
RIGHT = 'right'
|
||||
|
||||
class VAttach(Enum):
|
||||
"""Vertical attach type."""
|
||||
|
||||
BOTTOM = 'bottom'
|
||||
CENTER = 'center'
|
||||
TOP = 'top'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: str | ba.Lstr,
|
||||
position: tuple[float, float] = (0.0, 0.0),
|
||||
h_align: HAlign = HAlign.LEFT,
|
||||
v_align: VAlign = VAlign.NONE,
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
|
||||
transition: Transition | None = None,
|
||||
transition_delay: float = 0.0,
|
||||
flash: bool = False,
|
||||
v_attach: VAttach = VAttach.CENTER,
|
||||
h_attach: HAttach = HAttach.CENTER,
|
||||
scale: float = 1.0,
|
||||
transition_out_delay: float | None = None,
|
||||
maxwidth: float | None = None,
|
||||
shadow: float = 0.5,
|
||||
flatness: float = 0.0,
|
||||
vr_depth: float = 0.0,
|
||||
host_only: bool = False,
|
||||
front: bool = False,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
super().__init__()
|
||||
self.node = ba.newnode(
|
||||
'text',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'text': text,
|
||||
'color': color,
|
||||
'position': position,
|
||||
'h_align': h_align.value,
|
||||
'vr_depth': vr_depth,
|
||||
'v_align': v_align.value,
|
||||
'h_attach': h_attach.value,
|
||||
'v_attach': v_attach.value,
|
||||
'shadow': shadow,
|
||||
'flatness': flatness,
|
||||
'maxwidth': 0.0 if maxwidth is None else maxwidth,
|
||||
'host_only': host_only,
|
||||
'front': front,
|
||||
'scale': scale,
|
||||
},
|
||||
)
|
||||
|
||||
if transition is self.Transition.FADE_IN:
|
||||
if flash:
|
||||
raise RuntimeError(
|
||||
'fixme: flash and fade-in currently cant both be on'
|
||||
)
|
||||
cmb = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'input0': color[0],
|
||||
'input1': color[1],
|
||||
'input2': color[2],
|
||||
'size': 4,
|
||||
},
|
||||
)
|
||||
keys = {transition_delay: 0.0, transition_delay + 0.5: color[3]}
|
||||
if transition_out_delay is not None:
|
||||
keys[transition_delay + transition_out_delay] = color[3]
|
||||
keys[transition_delay + transition_out_delay + 0.5] = 0.0
|
||||
ba.animate(cmb, 'input3', keys)
|
||||
cmb.connectattr('output', self.node, 'color')
|
||||
|
||||
if flash:
|
||||
mult = 2.0
|
||||
tm1 = 0.15
|
||||
tm2 = 0.3
|
||||
cmb = ba.newnode('combine', owner=self.node, attrs={'size': 4})
|
||||
ba.animate(
|
||||
cmb,
|
||||
'input0',
|
||||
{0.0: color[0] * mult, tm1: color[0], tm2: color[0] * mult},
|
||||
loop=True,
|
||||
)
|
||||
ba.animate(
|
||||
cmb,
|
||||
'input1',
|
||||
{0.0: color[1] * mult, tm1: color[1], tm2: color[1] * mult},
|
||||
loop=True,
|
||||
)
|
||||
ba.animate(
|
||||
cmb,
|
||||
'input2',
|
||||
{0.0: color[2] * mult, tm1: color[2], tm2: color[2] * mult},
|
||||
loop=True,
|
||||
)
|
||||
cmb.input3 = color[3]
|
||||
cmb.connectattr('output', self.node, 'color')
|
||||
|
||||
cmb = self.position_combine = ba.newnode(
|
||||
'combine', owner=self.node, attrs={'size': 2}
|
||||
)
|
||||
|
||||
if transition is self.Transition.IN_RIGHT:
|
||||
keys = {
|
||||
transition_delay: position[0] + 1300,
|
||||
transition_delay + 0.2: position[0],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
ba.animate(cmb, 'input0', keys)
|
||||
cmb.input1 = position[1]
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
elif transition is self.Transition.IN_LEFT:
|
||||
keys = {
|
||||
transition_delay: position[0] - 1300,
|
||||
transition_delay + 0.2: position[0],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
if transition_out_delay is not None:
|
||||
keys[transition_delay + transition_out_delay] = position[0]
|
||||
keys[transition_delay + transition_out_delay + 0.2] = (
|
||||
position[0] - 1300.0
|
||||
)
|
||||
o_keys[transition_delay + transition_out_delay + 0.15] = 1.0
|
||||
o_keys[transition_delay + transition_out_delay + 0.2] = 0.0
|
||||
ba.animate(cmb, 'input0', keys)
|
||||
cmb.input1 = position[1]
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
elif transition is self.Transition.IN_BOTTOM_SLOW:
|
||||
keys = {
|
||||
transition_delay: -100.0,
|
||||
transition_delay + 1.0: position[1],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.2: 1.0}
|
||||
cmb.input0 = position[0]
|
||||
ba.animate(cmb, 'input1', keys)
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
elif transition is self.Transition.IN_BOTTOM:
|
||||
keys = {
|
||||
transition_delay: -100.0,
|
||||
transition_delay + 0.2: position[1],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
if transition_out_delay is not None:
|
||||
keys[transition_delay + transition_out_delay] = position[1]
|
||||
keys[transition_delay + transition_out_delay + 0.2] = -100.0
|
||||
o_keys[transition_delay + transition_out_delay + 0.15] = 1.0
|
||||
o_keys[transition_delay + transition_out_delay + 0.2] = 0.0
|
||||
cmb.input0 = position[0]
|
||||
ba.animate(cmb, 'input1', keys)
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
elif transition is self.Transition.IN_TOP_SLOW:
|
||||
keys = {
|
||||
transition_delay: 400.0,
|
||||
transition_delay + 3.5: position[1],
|
||||
}
|
||||
o_keys = {transition_delay: 0, transition_delay + 1.0: 1.0}
|
||||
cmb.input0 = position[0]
|
||||
ba.animate(cmb, 'input1', keys)
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
else:
|
||||
assert transition is self.Transition.FADE_IN or transition is None
|
||||
cmb.input0 = position[0]
|
||||
cmb.input1 = position[1]
|
||||
cmb.connectattr('output', self.node, 'position')
|
||||
|
||||
# If we're transitioning out, die at the end of it.
|
||||
if transition_out_delay is not None:
|
||||
ba.timer(
|
||||
transition_delay + transition_out_delay + 1.0,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage()),
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
if isinstance(msg, ba.DieMessage):
|
||||
if self.node:
|
||||
self.node.delete()
|
||||
return None
|
||||
return super().handlemessage(msg)
|
||||
101
dist/ba_data/python/bastd/actor/tipstext.py
vendored
Normal file
101
dist/ba_data/python/bastd/actor/tipstext.py
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides tip related Actor(s)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class TipsText(ba.Actor):
|
||||
"""A bit of text showing various helpful game tips."""
|
||||
|
||||
def __init__(self, offs_y: float = 100.0):
|
||||
super().__init__()
|
||||
self._tip_scale = 0.8
|
||||
self._tip_title_scale = 1.1
|
||||
self._offs_y = offs_y
|
||||
self.node = ba.newnode(
|
||||
'text',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'text': '',
|
||||
'scale': self._tip_scale,
|
||||
'h_align': 'left',
|
||||
'maxwidth': 800,
|
||||
'vr_depth': -20,
|
||||
'v_align': 'center',
|
||||
'v_attach': 'bottom',
|
||||
},
|
||||
)
|
||||
tval = ba.Lstr(
|
||||
value='${A}:', subs=[('${A}', ba.Lstr(resource='tipText'))]
|
||||
)
|
||||
self.title_node = ba.newnode(
|
||||
'text',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'text': tval,
|
||||
'scale': self._tip_title_scale,
|
||||
'maxwidth': 122,
|
||||
'h_align': 'right',
|
||||
'vr_depth': -20,
|
||||
'v_align': 'center',
|
||||
'v_attach': 'bottom',
|
||||
},
|
||||
)
|
||||
self._message_duration = 10000
|
||||
self._message_spacing = 3000
|
||||
self._change_timer = ba.Timer(
|
||||
0.001 * (self._message_duration + self._message_spacing),
|
||||
ba.WeakCall(self.change_phrase),
|
||||
repeat=True,
|
||||
)
|
||||
self._combine = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={'input0': 1.0, 'input1': 0.8, 'input2': 1.0, 'size': 4},
|
||||
)
|
||||
self._combine.connectattr('output', self.node, 'color')
|
||||
self._combine.connectattr('output', self.title_node, 'color')
|
||||
self.change_phrase()
|
||||
|
||||
def change_phrase(self) -> None:
|
||||
"""Switch the visible tip phrase."""
|
||||
from ba.internal import get_remote_app_name, get_next_tip
|
||||
|
||||
next_tip = ba.Lstr(
|
||||
translate=('tips', get_next_tip()),
|
||||
subs=[('${REMOTE_APP_NAME}', get_remote_app_name())],
|
||||
)
|
||||
spc = self._message_spacing
|
||||
assert self.node
|
||||
self.node.position = (-200, self._offs_y)
|
||||
self.title_node.position = (-220, self._offs_y + 3)
|
||||
keys = {
|
||||
spc: 0,
|
||||
spc + 1000: 1.0,
|
||||
spc + self._message_duration - 1000: 1.0,
|
||||
spc + self._message_duration: 0.0,
|
||||
}
|
||||
ba.animate(
|
||||
self._combine,
|
||||
'input3',
|
||||
{k: v * 0.5 for k, v in list(keys.items())},
|
||||
timeformat=ba.TimeFormat.MILLISECONDS,
|
||||
)
|
||||
self.node.text = next_tip
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
if isinstance(msg, ba.DieMessage):
|
||||
if self.node:
|
||||
self.node.delete()
|
||||
self.title_node.delete()
|
||||
return None
|
||||
return super().handlemessage(msg)
|
||||
209
dist/ba_data/python/bastd/actor/zoomtext.py
vendored
Normal file
209
dist/ba_data/python/bastd/actor/zoomtext.py
vendored
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defined Actor(s)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
class ZoomText(ba.Actor):
|
||||
"""Big Zooming Text.
|
||||
|
||||
Category: Gameplay Classes
|
||||
|
||||
Used for things such as the 'BOB WINS' victory messages.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: str | ba.Lstr,
|
||||
position: tuple[float, float] = (0.0, 0.0),
|
||||
shiftposition: tuple[float, float] | None = None,
|
||||
shiftdelay: float | None = None,
|
||||
lifespan: float | None = None,
|
||||
flash: bool = True,
|
||||
trail: bool = True,
|
||||
h_align: str = 'center',
|
||||
color: Sequence[float] = (0.9, 0.4, 0.0),
|
||||
jitter: float = 0.0,
|
||||
trailcolor: Sequence[float] = (1.0, 0.35, 0.1, 0.0),
|
||||
scale: float = 1.0,
|
||||
project_scale: float = 1.0,
|
||||
tilt_translate: float = 0.0,
|
||||
maxwidth: float | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
super().__init__()
|
||||
self._dying = False
|
||||
positionadjusted = (position[0], position[1] - 100)
|
||||
if shiftdelay is None:
|
||||
shiftdelay = 2.500
|
||||
if shiftdelay < 0.0:
|
||||
ba.print_error('got shiftdelay < 0')
|
||||
shiftdelay = 0.0
|
||||
self._project_scale = project_scale
|
||||
self.node = ba.newnode(
|
||||
'text',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'position': positionadjusted,
|
||||
'big': True,
|
||||
'text': text,
|
||||
'trail': trail,
|
||||
'vr_depth': 0,
|
||||
'shadow': 0.0 if trail else 0.3,
|
||||
'scale': scale,
|
||||
'maxwidth': maxwidth if maxwidth is not None else 0.0,
|
||||
'tilt_translate': tilt_translate,
|
||||
'h_align': h_align,
|
||||
'v_align': 'center',
|
||||
},
|
||||
)
|
||||
|
||||
# we never jitter in vr mode..
|
||||
if ba.app.vr_mode:
|
||||
jitter = 0.0
|
||||
|
||||
# if they want jitter, animate its position slightly...
|
||||
if jitter > 0.0:
|
||||
self._jitter(positionadjusted, jitter * scale)
|
||||
|
||||
# if they want shifting, move to the shift position and
|
||||
# then resume jittering
|
||||
if shiftposition is not None:
|
||||
positionadjusted2 = (shiftposition[0], shiftposition[1] - 100)
|
||||
ba.timer(
|
||||
shiftdelay,
|
||||
ba.WeakCall(self._shift, positionadjusted, positionadjusted2),
|
||||
)
|
||||
if jitter > 0.0:
|
||||
ba.timer(
|
||||
shiftdelay + 0.25,
|
||||
ba.WeakCall(
|
||||
self._jitter, positionadjusted2, jitter * scale
|
||||
),
|
||||
)
|
||||
color_combine = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={'input2': color[2], 'input3': 1.0, 'size': 4},
|
||||
)
|
||||
if trail:
|
||||
trailcolor_n = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'size': 3,
|
||||
'input0': trailcolor[0],
|
||||
'input1': trailcolor[1],
|
||||
'input2': trailcolor[2],
|
||||
},
|
||||
)
|
||||
trailcolor_n.connectattr('output', self.node, 'trailcolor')
|
||||
basemult = 0.85
|
||||
ba.animate(
|
||||
self.node,
|
||||
'trail_project_scale',
|
||||
{
|
||||
0: 0 * project_scale,
|
||||
basemult * 0.201: 0.6 * project_scale,
|
||||
basemult * 0.347: 0.8 * project_scale,
|
||||
basemult * 0.478: 0.9 * project_scale,
|
||||
basemult * 0.595: 0.93 * project_scale,
|
||||
basemult * 0.748: 0.95 * project_scale,
|
||||
basemult * 0.941: 0.95 * project_scale,
|
||||
},
|
||||
)
|
||||
if flash:
|
||||
mult = 2.0
|
||||
tm1 = 0.15
|
||||
tm2 = 0.3
|
||||
ba.animate(
|
||||
color_combine,
|
||||
'input0',
|
||||
{0: color[0] * mult, tm1: color[0], tm2: color[0] * mult},
|
||||
loop=True,
|
||||
)
|
||||
ba.animate(
|
||||
color_combine,
|
||||
'input1',
|
||||
{0: color[1] * mult, tm1: color[1], tm2: color[1] * mult},
|
||||
loop=True,
|
||||
)
|
||||
ba.animate(
|
||||
color_combine,
|
||||
'input2',
|
||||
{0: color[2] * mult, tm1: color[2], tm2: color[2] * mult},
|
||||
loop=True,
|
||||
)
|
||||
else:
|
||||
color_combine.input0 = color[0]
|
||||
color_combine.input1 = color[1]
|
||||
color_combine.connectattr('output', self.node, 'color')
|
||||
ba.animate(
|
||||
self.node,
|
||||
'project_scale',
|
||||
{0: 0, 0.27: 1.05 * project_scale, 0.3: 1 * project_scale},
|
||||
)
|
||||
|
||||
# if they give us a lifespan, kill ourself down the line
|
||||
if lifespan is not None:
|
||||
ba.timer(lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage()))
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
if isinstance(msg, ba.DieMessage):
|
||||
if not self._dying and self.node:
|
||||
self._dying = True
|
||||
if msg.immediate:
|
||||
self.node.delete()
|
||||
else:
|
||||
ba.animate(
|
||||
self.node,
|
||||
'project_scale',
|
||||
{
|
||||
0.0: 1 * self._project_scale,
|
||||
0.6: 1.2 * self._project_scale,
|
||||
},
|
||||
)
|
||||
ba.animate(self.node, 'opacity', {0.0: 1, 0.3: 0})
|
||||
ba.animate(self.node, 'trail_opacity', {0.0: 1, 0.6: 0})
|
||||
ba.timer(0.7, self.node.delete)
|
||||
return None
|
||||
return super().handlemessage(msg)
|
||||
|
||||
def _jitter(
|
||||
self, position: tuple[float, float], jitter_amount: float
|
||||
) -> None:
|
||||
if not self.node:
|
||||
return
|
||||
cmb = ba.newnode('combine', owner=self.node, attrs={'size': 2})
|
||||
for index, attr in enumerate(['input0', 'input1']):
|
||||
keys = {}
|
||||
timeval = 0.0
|
||||
# gen some random keys for that stop-motion-y look
|
||||
for _i in range(10):
|
||||
keys[timeval] = (
|
||||
position[index]
|
||||
+ (random.random() - 0.5) * jitter_amount * 1.6
|
||||
)
|
||||
timeval += random.random() * 0.1
|
||||
ba.animate(cmb, attr, keys, loop=True)
|
||||
cmb.connectattr('output', self.node, 'position')
|
||||
|
||||
def _shift(
|
||||
self, position1: tuple[float, float], position2: tuple[float, float]
|
||||
) -> None:
|
||||
if not self.node:
|
||||
return
|
||||
cmb = ba.newnode('combine', owner=self.node, attrs={'size': 2})
|
||||
ba.animate(cmb, 'input0', {0.0: position1[0], 0.25: position2[0]})
|
||||
ba.animate(cmb, 'input1', {0.0: position1[1], 0.25: position2[1]})
|
||||
cmb.connectattr('output', self.node, 'position')
|
||||
Loading…
Add table
Add a link
Reference in a new issue