vh-bombsquad-modded-server-.../dist/ba_data/python/bastd/actor/powerupbox.py
2024-02-20 23:04:51 +05:30

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