# ba_meta require api 7 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations from typing import TYPE_CHECKING import ba from bastd.actor import playerspaz from bastd.actor.spaz import BombDiedMessage from bastd.actor.bomb import Blast, Bomb, BombFactory, ExplodeHitMessage if TYPE_CHECKING: from typing import Any, Sequence class NewBlast(Blast): def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, ba.DieMessage): if self.node: self.node.delete() elif isinstance(msg, ExplodeHitMessage): node = ba.getcollision().opposingnode assert self.node nodepos = self.node.position mag = 4000.0 if self.blast_type == 'ice': mag *= 0.8 elif self.blast_type == 'land_mine': mag *= 2.5 elif self.blast_type == 'tnt': mag *= 3.0 node.handlemessage( ba.HitMessage(pos=nodepos, velocity=(0, 0, 0), magnitude=mag, hit_type=self.hit_type, hit_subtype=self.hit_subtype, radius=self.radius, source_player=ba.existing(self._source_player))) if self.blast_type == 'ice': ba.playsound(BombFactory.get().freeze_sound, 10, position=nodepos) node.handlemessage(ba.FreezeMessage()) else: return super().handlemessage(msg) return None class NewBomb(Bomb): def __init__(self, position: Sequence[float] = (0.0, 1.0, 0.0), velocity: Sequence[float] = (0.0, 0.0, 0.0), bomb_type: str = 'normal', blast_radius: float = 12.0, bomb_scale: float = 3.4, source_player: ba.Player | None = None, owner: ba.Node | None = None): super().__init__(position, velocity, bomb_type, blast_radius, bomb_scale, source_player, owner) self.super_bomb_light = ba.newnode( 'light', owner=self.node, attrs={'radius': 0.9, 'color': (1, 0, 0), 'intensity': 0.9 }) self.super_bomb_shield = ba.newnode( 'shield', owner=self.node, attrs={ 'color': (1, 0, 0), 'radius': 2.6 }) self.node.connectattr('position', self.super_bomb_light, 'position') self.node.connectattr('position', self.super_bomb_shield, 'position') def explode(self) -> None: """Blows up the bomb if it has not yet done so.""" if self._exploded: return self._exploded = True if self.node: blast = NewBlast(position=self.node.position, velocity=self.node.velocity, blast_radius=self.blast_radius, blast_type=self.bomb_type, source_player=ba.existing(self._source_player), hit_type=self.hit_type, hit_subtype=self.hit_subtype).autoretain() for callback in self._explode_callbacks: callback(self, blast) # We blew up so we need to go away. # NOTE TO SELF: do we actually need this delay? ba.timer(0.001, ba.WeakCall(self.handlemessage, ba.DieMessage())) class NewPlayerSpaz(playerspaz.PlayerSpaz): def drop_bomb(self) -> NewBomb | None: """ Tell the spaz to drop one of his bombs, and returns the resulting bomb object. If the spaz has no bombs or is otherwise unable to drop a bomb, returns None. """ if (self.land_mine_count <= 0 and self.bomb_count <= 0) or self.frozen: return None assert self.node pos = self.node.position_forward vel = self.node.velocity if self.land_mine_count > 0: dropping_bomb = False self.set_land_mine_count(self.land_mine_count - 1) bomb_type = 'land_mine' else: dropping_bomb = True bomb_type = self.bomb_type bomb = NewBomb(position=(pos[0], pos[1] - 0.0, pos[2]), velocity=(vel[0], vel[1], vel[2]), bomb_type=bomb_type, blast_radius=self.blast_radius*12, source_player=self.source_player, owner=self.node).autoretain() assert bomb.node if dropping_bomb: self.bomb_count -= 1 bomb.node.add_death_action( ba.WeakCall(self.handlemessage, BombDiedMessage())) self._pick_up(bomb.node) for clb in self._dropped_bomb_callbacks: clb(self, bomb) return bomb # ba_meta export plugin class HyperMegaBombMod(ba.Plugin): playerspaz.PlayerSpaz = NewPlayerSpaz