mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2025-11-07 17:36:08 +00:00
319 lines
10 KiB
Python
319 lines
10 KiB
Python
# 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
|