diff --git a/plugins/minigames.json b/plugins/minigames.json index 66ba30d..909e72d 100644 --- a/plugins/minigames.json +++ b/plugins/minigames.json @@ -293,6 +293,62 @@ } } }, + "demolition_war": { + "description": "BombFight on wooden floor flying in air.", + "external_url": "https://www.youtube.com/channel/UCaQajfKHrTPgiOhuias5iPg", + "authors": [ + { + "name": "Mr.Smoothy", + "email": "smoothy@bombsquad.ga", + "discord": "mr.smoothy#5824" + } + ], + "versions": { + "1.0.0": null + } + }, + "castel_queen": { + "description": "Carry the queen for some duration in her room.", + "external_url": "https://www.youtube.com/channel/UCaQajfKHrTPgiOhuias5iPg", + "authors": [ + { + "name": "Mr.Smoothy", + "email": "smoothy@bombsquad.ga", + "discord": "mr.smoothy#5824" + } + ], + "versions": { + "1.0.0": null + } + }, + "drone_war": { + "description": "Fly with Drone and attack with rocket launcher", + "external_url": "https://www.youtube.com/channel/UCaQajfKHrTPgiOhuias5iPg", + "authors": [ + { + "name": "Mr.Smoothy", + "email": "smoothy@bombsquad.ga", + "discord": "mr.smoothy#5824" + } + ], + "versions": { + "1.0.0": null + } + }, + "the_spaz_game": { + "description": "Enemy Spaz Amoung us, kill correct person.", + "external_url": "https://www.youtube.com/channel/UCaQajfKHrTPgiOhuias5iPg", + "authors": [ + { + "name": "Mr.Smoothy", + "email": "smoothy@bombsquad.ga", + "discord": "mr.smoothy#5824" + } + ], + "versions": { + "1.0.0": null + } + }, "air_soccer": { "description": "Play soccer while flying in air", "external_url": "https://youtu.be/j6FFk7E6W_U", diff --git a/plugins/minigames/castel_queen.py b/plugins/minigames/castel_queen.py new file mode 100644 index 0000000..02986ce --- /dev/null +++ b/plugins/minigames/castel_queen.py @@ -0,0 +1,407 @@ +# Released under the MIT License. See LICENSE for details. +# +""" +CastelQueen - Carry the Queen alone or with your team. +Author: Mr.Smoothy +Discord: https://discord.gg/ucyaesh +Youtube: https://www.youtube.com/c/HeySmoothy +Website: https://bombsquad-community.web.app +Github: https://github.com/bombsquad-community +""" +# ba_meta require api 7 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import ba +import _ba + +from bastd.gameutils import SharedObjects +from bastd.actor.playerspaz import PlayerSpaz +from bastd.game.keepaway import KeepAwayGame, FlagState, Player +from bastd.actor import spaz +if TYPE_CHECKING: + from typing import Any, Sequence, Dict, Type, List, Optional, Union + +# ba_meta export game +class ChooseQueen(KeepAwayGame): + name = 'FCUK The Queen' + description = 'Carry the queen for a set length of time' + @classmethod + def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + return issubclass(sessiontype, ba.DualTeamSession) + + @classmethod + def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + return ['Creative Thoughts'] + + def get_instance_description(self) -> str | Sequence: + return 'FCUK the queen for ${ARG1} seconds.', self._hold_time + + def get_instance_description_short(self) -> str | Sequence: + return 'FCUK the queen for ${ARG1} seconds', self._hold_time + + def __init__(self, settings: dict): + super().__init__(settings) + shared = SharedObjects.get() + self.lifts = {} + self._room_wall_material = ba.Material() + self._room_wall_material.add_actions( + actions=( + ('modify_part_collision', 'collide', False), + ('modify_part_collision', 'physical', False) + )) + self._queen_material = ba.Material() + self._queen_material.add_actions( + conditions=('they_have_material', self._room_wall_material), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', True) + )) + self._queen_material.add_actions( + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', True) + )) + self._room_wall_material.add_actions( + conditions=('they_have_material', self._queen_material), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', True) + )) + self._real_wall_material = ba.Material() + self._real_wall_material.add_actions( + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', True) + )) + + self._real_wall_material.add_actions( + conditions=('they_have_material', shared.player_material), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', True) + + )) + + def on_begin(self): + ba.getactivity().globalsnode.happy_thoughts_mode = True + super().on_begin() + self.make_map() + + def _spawn_flag(self) -> None: + ba.playsound(self._swipsound) + self._flash_flag_spawn() + assert self._flag_spawn_pos is not None + shared = SharedObjects.get() + self._flag = spaz.Spaz((0, 0, 0), character="Pixel").autoretain() + self._flag.handlemessage(ba.StandMessage((0, 14.63, -5.52), 93)) + self._flag.node.hold_position_pressed = True + self._flag.node.materials = (self._queen_material,shared.object_material) + # self._flag.node.extras_material= tuple(list(self._flag.node.extras_material).append(self._queen_materia)) + self._flag.hitpoints = 5000 + self._flag.hitpoints_max = 5000 + + self._flag_state = FlagState.NEW + self._flag_light = ba.newnode( + 'light', + owner=self._flag.node, + attrs={'intensity': 0.2, 'radius': 0.3, 'color': (0.2, 0.2, 0.2)}, + ) + assert self._flag.node + self._flag.node.connectattr('position', self._flag_light, 'position') + self._update_flag_state() + + def _update_flag_state(self) -> None: + if not self._flag.node.exists(): + self._spawn_flag() + for team in self.teams: + team.holdingflag = False + self._holding_players = [] + for player in self.players: + holdingflag = False + try: + assert isinstance(player.actor, (PlayerSpaz, type(None))) + if ( + player.actor + and player.actor.node + and player.actor.node.hold_node + ): + holdingflag = ( + player.actor.node.hold_node == self._flag.node + ) + except Exception: + ba.print_exception('Error checking hold flag.') + if holdingflag: + self._holding_players.append(player) + player.team.holdingflag = True + + holdingteams = set(t for t in self.teams if t.holdingflag) + prevstate = self._flag_state + assert self._flag is not None + assert self._flag_light + assert self._flag.node + if len(holdingteams) > 1: + self._flag_state = FlagState.CONTESTED + self._scoring_team = None + elif len(holdingteams) == 1: + holdingteam = list(holdingteams)[0] + self._flag_state = FlagState.HELD + self._scoring_team = holdingteam + else: + self._flag_state = FlagState.UNCONTESTED + self._scoring_team = None + + if self._flag_state != prevstate: + ba.playsound(self._swipsound) + + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, ba.PlayerDiedMessage): + # Augment standard behavior. + super().handlemessage(msg) + self.respawn_player(msg.getplayer(Player)) + elif isinstance(msg, (ba.PickedUpMessage, ba.DroppedMessage)): + self._update_flag_state() + else: + super().handlemessage(msg) + + def make_map(self): + shared = SharedObjects.get() + _ba.get_foreground_host_activity()._map.leftwall.materials = [ + shared.footing_material, self._real_wall_material] + + _ba.get_foreground_host_activity()._map.rightwall.materials = [ + shared.footing_material, self._real_wall_material] + + _ba.get_foreground_host_activity()._map.topwall.materials = [ + shared.footing_material, self._real_wall_material] + + self.floorwall1 = ba.newnode('region', attrs={'position': (-10, 5, -5.52), 'scale': + (15, 0.2, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]}) + self.floorwall2 = ba.newnode('region', attrs={'position': (10, 5, -5.52), 'scale': ( + 15, 0.2, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]}) + + self.wall1 = ba.newnode('region', attrs={'position': (0, 11, -6.90), 'scale': ( + 35.4, 20, 1), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]}) + self.wall2 = ba.newnode('region', attrs={'position': (0, 11, -4.14), 'scale': ( + 35.4, 20, 1), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]}) + + ba.newnode('locator', attrs={'shape': 'box', 'position': (-10, 5, -5.52), 'color': ( + 0, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (15, 0.2, 2)}) + + ba.newnode('locator', attrs={'shape': 'box', 'position': (10, 5, -5.52), 'color': ( + 0, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (15, 0.2, 2)}) + + self.create_static_step(0, 14.29) + # upper right + self.create_static_step(11, 16) + self.create_slope(8, 16, False) + self.create_static_step(3, 18) + + # lower right + self.create_static_step(11, 12) + self.create_slope(6, 10, True) + self.create_static_step(3, 10) + + # upper left + self.create_static_step(-11, 16) + self.create_slope(-8, 16, True) + self.create_static_step(-3, 18) + + # lower left + self.create_static_step(-11, 12) + self.create_slope(-6, 10, False) + self.create_static_step(-3, 10) + + # create queen personal room + self.room_wall_left = ba.newnode('region', attrs={'position': (-3.633, 16.63, -5.52), 'scale': + (2, 4, 5), 'type': 'box', 'materials': [shared.footing_material, self._room_wall_material]}) + self.room_wall_right = ba.newnode('region', attrs={'position': (3.533, 16.63, -5.52), 'scale': + (2, 4, 5), 'type': 'box', 'materials': [shared.footing_material, self._room_wall_material]}) + + def create_static_step(self, x, y): + shared = SharedObjects.get() + ba.newnode('region', attrs={'position': (x, y, -5.52), 'scale': (5.5, 0.1, 6), + 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]}) + ba.newnode('locator', attrs={'shape': 'box', 'position': (x, y, -5.52), 'color': ( + 1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (5.5, 0.1, 2)}) + + def create_slope(self, x, y, backslash): + shared = SharedObjects.get() + + for _ in range(0, 21): + ba.newnode('region', attrs={'position': (x, y, -5.52), 'scale': (0.2, 0.1, 6), + 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]}) + ba.newnode('locator', attrs={'shape': 'box', 'position': (x, y, -5.52), 'color': ( + 1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (0.2, 0.1, 2)}) + if backslash: + x = x + 0.1 + y = y + 0.1 + else: + x = x - 0.1 + y = y + 0.1 + + +class mapdefs: + points = {} + # noinspection PyDictCreation + boxes = {} + boxes['area_of_interest_bounds'] = (-1.045859963, 12.67722855, + -5.401537075) + (0.0, 0.0, 0.0) + ( + 42.46156851, 20.94044653, 0.6931564611) + points['ffa_spawn1'] = (-9.295167711, 8.010664315, + -5.44451005) + (1.555840357, 1.453808816, 0.1165648888) + points['ffa_spawn2'] = (7.484707127, 8.172681752, -5.614479365) + ( + 1.553861796, 1.453808816, 0.04419853907) + points['ffa_spawn3'] = (9.55724115, 11.30789446, -5.614479365) + ( + 1.337925849, 1.453808816, 0.04419853907) + points['ffa_spawn4'] = (-11.55747023, 10.99170684, -5.614479365) + ( + 1.337925849, 1.453808816, 0.04419853907) + points['ffa_spawn5'] = (-1.878892369, 9.46490571, -5.614479365) + ( + 1.337925849, 1.453808816, 0.04419853907) + points['ffa_spawn6'] = (-0.4912812943, 5.077006397, -5.521672101) + ( + 1.878332089, 1.453808816, 0.007578097856) + points['flag1'] = (-11.75152479, 8.057427485, -5.52) + points['flag2'] = (9.840909039, 8.188634282, -5.52) + points['flag3'] = (-0.2195258696, 5.010273907, -5.52) + points['flag4'] = (-0.04605809154, 12.73369108, -5.52) + points['flag_default'] = (-0.04201942896, 12.72374492, -5.52) + boxes['map_bounds'] = (-0.8748348681, 9.212941713, -5.729538885) + ( + 0.0, 0.0, 0.0) + (42.09666006, 26.19950145, 7.89541168) + points['powerup_spawn1'] = (1.160232442, 6.745963662, -5.469115985) + points['powerup_spawn2'] = (-1.899700206, 10.56447241, -5.505721177) + points['powerup_spawn3'] = (10.56098871, 12.25165669, -5.576232453) + points['powerup_spawn4'] = (-12.33530337, 12.25165669, -5.576232453) + points['spawn1'] = (-9.295167711, 8.010664315, + -5.44451005) + (1.555840357, 1.453808816, 0.1165648888) + points['spawn2'] = (7.484707127, 8.172681752, + -5.614479365) + (1.553861796, 1.453808816, 0.04419853907) + points['spawn_by_flag1'] = (-9.295167711, 8.010664315, -5.44451005) + ( + 1.555840357, 1.453808816, 0.1165648888) + points['spawn_by_flag2'] = (7.484707127, 8.172681752, -5.614479365) + ( + 1.553861796, 1.453808816, 0.04419853907) + points['spawn_by_flag3'] = (-1.45994593, 5.038762459, -5.535288724) + ( + 0.9516389866, 0.6666414677, 0.08607244075) + points['spawn_by_flag4'] = (0.4932087091, 12.74493212, -5.598987003) + ( + 0.5245740665, 0.5245740665, 0.01941146064) + + +class CreativeThoughts(ba.Map): + """Freaking map by smoothy.""" + + defs = mapdefs + + name = 'Creative Thoughts' + + @classmethod + def get_play_types(cls) -> List[str]: + """Return valid play types for this map.""" + return [ + 'melee', 'keep_away', 'team_flag' + ] + + @classmethod + def get_preview_texture_name(cls) -> str: + return 'alwaysLandPreview' + + @classmethod + def on_preload(cls) -> Any: + data: Dict[str, Any] = { + 'model': ba.getmodel('alwaysLandLevel'), + 'bottom_model': ba.getmodel('alwaysLandLevelBottom'), + 'bgmodel': ba.getmodel('alwaysLandBG'), + 'collide_model': ba.getcollidemodel('alwaysLandLevelCollide'), + 'tex': ba.gettexture('alwaysLandLevelColor'), + 'bgtex': ba.gettexture('alwaysLandBGColor'), + 'vr_fill_mound_model': ba.getmodel('alwaysLandVRFillMound'), + 'vr_fill_mound_tex': ba.gettexture('vrFillMound') + } + return data + + @classmethod + def get_music_type(cls) -> ba.MusicType: + return ba.MusicType.FLYING + + def __init__(self) -> None: + super().__init__(vr_overlay_offset=(0, -3.7, 2.5)) + shared = SharedObjects.get() + self._fake_wall_material = ba.Material() + self._real_wall_material = ba.Material() + self._fake_wall_material.add_actions( + conditions=(('they_are_younger_than', 9000), 'and', + ('they_have_material', shared.player_material)), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', True) + + )) + self._real_wall_material.add_actions( + conditions=('they_have_material', shared.player_material), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', True) + + )) + self.background = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bgmodel'], + 'lighting': False, + 'background': True, + 'color_texture': ba.gettexture("rampageBGColor") + }) + + self.leftwall = ba.newnode('region', attrs={'position': (-17.75152479, 13, -5.52), 'scale': ( + 0.1, 15.5, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]}) + self.rightwall = ba.newnode('region', attrs={'position': (17.75, 13, -5.52), 'scale': ( + 0.1, 15.5, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]}) + self.topwall = ba.newnode('region', attrs={'position': (0, 21.0, -5.52), 'scale': ( + 35.4, 0.2, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]}) + ba.newnode('locator', attrs={'shape': 'box', 'position': (-17.75152479, 13, -5.52), 'color': ( + 0, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (0.1, 15.5, 2)}) + ba.newnode('locator', attrs={'shape': 'box', 'position': (17.75, 13, -5.52), 'color': ( + 0, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (0.1, 15.5, 2)}) + ba.newnode('locator', attrs={'shape': 'box', 'position': (0, 21.0, -5.52), 'color': ( + 0, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (35.4, 0.2, 2)}) + + gnode = ba.getactivity().globalsnode + gnode.happy_thoughts_mode = True + gnode.shadow_offset = (0.0, 8.0, 5.0) + gnode.tint = (1.3, 1.23, 1.0) + gnode.ambient_color = (1.3, 1.23, 1.0) + gnode.vignette_outer = (0.64, 0.59, 0.69) + gnode.vignette_inner = (0.95, 0.95, 0.93) + gnode.vr_near_clip = 1.0 + self.is_flying = True + + # throw out some tips on flying + txt = ba.newnode('text', + attrs={ + 'text': ba.Lstr(resource='pressJumpToFlyText'), + 'scale': 1.2, + 'maxwidth': 800, + 'position': (0, 200), + 'shadow': 0.5, + 'flatness': 0.5, + 'h_align': 'center', + 'v_attach': 'bottom' + }) + cmb = ba.newnode('combine', + owner=txt, + attrs={ + 'size': 4, + 'input0': 0.3, + 'input1': 0.9, + 'input2': 0.0 + }) + ba.animate(cmb, 'input3', {3.0: 0, 4.0: 1, 9.0: 1, 10.0: 0}) + cmb.connectattr('output', txt, 'color') + ba.timer(10.0, txt.delete) + + +try: + ba._map.register_map(CreativeThoughts) +except: + pass diff --git a/plugins/minigames/demolition_war.py b/plugins/minigames/demolition_war.py new file mode 100644 index 0000000..31f2abe --- /dev/null +++ b/plugins/minigames/demolition_war.py @@ -0,0 +1,303 @@ + +# ba_meta require api 7 +""" +DemolitionWar - BombFight on wooden floor flying in air. +Author: Mr.Smoothy +Discord: https://discord.gg/ucyaesh +Youtube: https://www.youtube.com/c/HeySmoothy +Website: https://bombsquad-community.web.app +Github: https://github.com/bombsquad-community +""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +import ba +from bastd.game.elimination import EliminationGame, Player +from bastd.gameutils import SharedObjects +from bastd.actor.bomb import BombFactory +import random +from bastd.actor.playerspaz import PlayerSpaz +if TYPE_CHECKING: + from typing import Any, Sequence + +# ba_meta export game +class DemolitionWar(EliminationGame): + name = 'DemolitionWar' + description = 'Last remaining alive wins.' + scoreconfig = ba.ScoreConfig( + label='Survived', scoretype=ba.ScoreType.SECONDS, none_is_winner=True + ) + # Show messages when players die since it's meaningful here. + announce_player_deaths = True + + allow_mid_activity_joins = False + + @classmethod + def get_available_settings( + cls, sessiontype: type[ba.Session] + ) -> list[ba.Setting]: + settings = [ + ba.IntSetting( + 'Lives Per Player', + default=1, + min_value=1, + max_value=10, + increment=1, + ), + ba.IntChoiceSetting( + 'Time Limit', + choices=[ + ('None', 0), + ('1 Minute', 60), + ('2 Minutes', 120), + ('5 Minutes', 300), + ('10 Minutes', 600), + ('20 Minutes', 1200), + ], + default=0, + ), + ba.FloatChoiceSetting( + 'Respawn Times', + choices=[ + ('Shorter', 0.25), + ('Short', 0.5), + ('Normal', 1.0), + ('Long', 2.0), + ('Longer', 4.0), + ], + default=1.0, + ), + ba.BoolSetting('Epic Mode', default=False), + ] + if issubclass(sessiontype, ba.DualTeamSession): + settings.append(ba.BoolSetting('Solo Mode', default=False)) + settings.append( + ba.BoolSetting('Balance Total Lives', default=False) + ) + return settings + + @classmethod + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: + return issubclass(sessiontype, ba.DualTeamSession) or issubclass( + sessiontype, ba.FreeForAllSession + ) + + @classmethod + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: + return ['Wooden Floor'] + + def __init__(self, settings: dict): + super().__init__(settings) + self._lives_per_player = 1 + self._solo_mode = False + self._balance_total_lives = False + + def spawn_player(self, player: Player) -> ba.Actor: + p = [-6, -4.3, -2.6, -0.9, 0.8, 2.5, 4.2, 5.9] + q = [-4, -2.3, -0.6, 1.1, 2.8, 4.5] + + x = random.randrange(0, len(p)) + y = random.randrange(0, len(q)) + spaz = self.spawn_player_spaz(player, position=(p[x], 1.8, q[y])) + spaz.bomb_type = 'impact' + # Let's reconnect this player's controls to this + # spaz but *without* the ability to attack or pick stuff up. + spaz.connect_controls_to_player(enable_punch=False, + enable_bomb=True, + enable_pickup=True) + + # Also lets have them make some noise when they die. + spaz.play_big_death_sound = True + return spaz + + def on_begin(self) -> None: + super().on_begin() + self.map_extend() + + def on_blast(self): + node = ba.getcollision().sourcenode + ba.emitfx((node.position[0], 0.9, node.position[2]), + (0, 2, 0), 30, 1, spread=1, chunk_type='splinter') + ba.timer(0.1, ba.Call(node.delete)) + + def map_extend(self): + # TODO need to improve here , so we can increase size of map easily with settings + p = [-6, -4.3, -2.6, -0.9, 0.8, 2.5, 4.2, 5.9] + q = [-4, -2.3, -0.6, 1.1, 2.8, 4.5] + factory = BombFactory.get() + self.ramp_bomb = ba.Material() + self.ramp_bomb.add_actions( + conditions=('they_have_material', factory.bomb_material), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', True), + ('call', 'at_connect', ba.Call(self.on_blast)) + )) + self.ramps = [] + for i in p: + for j in q: + self.ramps.append(self.create_ramp(i, j)) + + def create_ramp(self, x, z): + + shared = SharedObjects.get() + self._real_collied_material = ba.Material() + + self._real_collied_material.add_actions( + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', True) + + )) + self.mat = ba.Material() + self.mat.add_actions( + actions=(('modify_part_collision', 'physical', False), + ('modify_part_collision', 'collide', False)) + ) + pos = (x, 0, z) + ud_1_r = ba.newnode('region', attrs={'position': pos, 'scale': (1.5, 1, 1.5), 'type': 'box', 'materials': [ + shared.footing_material, self._real_collied_material, self.ramp_bomb]}) + + node = ba.newnode('prop', + owner=ud_1_r, + attrs={ + 'model': ba.getmodel('image1x1'), + 'light_model': ba.getmodel('powerupSimple'), + 'position': (2, 7, 2), + 'body': 'puck', + 'shadow_size': 0.0, + 'velocity': (0, 0, 0), + 'color_texture': ba.gettexture('tnt'), + 'model_scale': 1.5, + 'reflection_scale': [1.5], + 'materials': [self.mat, shared.object_material, shared.footing_material], + 'density': 9000000000 + }) + node.changerotation(1, 0, 0) + mnode = ba.newnode('math', + owner=ud_1_r, + attrs={ + 'input1': (0, 0.6, 0), + 'operation': 'add' + }) + ud_1_r.connectattr('position', mnode, 'input2') + mnode.connectattr('output', node, 'position') + return ud_1_r + + + +class mapdefs: + points = {} + # noinspection PyDictCreation + boxes = {} + boxes['area_of_interest_bounds'] = (0.0, 1.185751251, 0.4326226188) + ( + 0.0, 0.0, 0.0) + (29.8180273, 11.57249038, 18.89134176) + boxes['edge_box'] = (-0.103873591, 0.4133341891, 0.4294651013) + ( + 0.0, 0.0, 0.0) + (22.48295719, 1.290242794, 8.990252454) + points['ffa_spawn1'] = (-0.08015551329, 0.02275111462, + -4.373674593) + (8.895057015, 1.0, 0.444350722) + points['ffa_spawn2'] = (-0.08015551329, 0.02275111462, + 4.076288941) + (8.895057015, 1.0, 0.444350722) + points['flag1'] = (-10.99027878, 0.05744967453, 0.1095578275) + points['flag2'] = (11.01486398, 0.03986567039, 0.1095578275) + points['flag_default'] = (-0.1001374046, 0.04180340146, 0.1095578275) + boxes['goal1'] = (12.22454533, 1.0, + 0.1087926362) + (0.0, 0.0, 0.0) + (2.0, 2.0, 12.97466313) + boxes['goal2'] = (-12.15961605, 1.0, + 0.1097860203) + (0.0, 0.0, 0.0) + (2.0, 2.0, 13.11856424) + boxes['map_bounds'] = (0.0, 1.185751251, 0.4326226188) + (0.0, 0.0, 0.0) + ( + 42.09506485, 22.81173179, 29.76723155) + points['powerup_spawn1'] = (5.414681236, 0.9515026107, -5.037912441) + points['powerup_spawn2'] = (-5.555402285, 0.9515026107, -5.037912441) + points['powerup_spawn3'] = (5.414681236, 0.9515026107, 5.148223181) + points['powerup_spawn4'] = (-5.737266365, 0.9515026107, 5.148223181) + points['spawn1'] = (-10.03866341, 0.02275111462, 0.0) + (0.5, 1.0, 4.0) + points['spawn2'] = (9.823107149, 0.01092306765, 0.0) + (0.5, 1.0, 4.0) + points['tnt1'] = (-0.08421587483, 0.9515026107, -0.7762602271) +class WoodenFloor(ba.Map): + """Stadium map for football games.""" + defs = mapdefs + defs.points['spawn1'] = (-12.03866341, 0.02275111462, 0.0) + (0.5, 1.0, 4.0) + defs.points['spawn2'] = (12.823107149, 0.01092306765, 0.0) + (0.5, 1.0, 4.0) + name = 'Wooden Floor' + + @classmethod + def get_play_types(cls) -> list[str]: + """Return valid play types for this map.""" + return ['melee', 'football', 'team_flag', 'keep_away'] + + @classmethod + def get_preview_texture_name(cls) -> str: + return 'footballStadiumPreview' + + @classmethod + def on_preload(cls) -> Any: + data: dict[str, Any] = { + + 'model_bg': ba.getmodel('doomShroomBG'), + 'bg_vr_fill_model': ba.getmodel('natureBackgroundVRFill'), + 'collide_model': ba.getcollidemodel('bridgitLevelCollide'), + 'tex': ba.gettexture('bridgitLevelColor'), + 'model_bg_tex': ba.gettexture('doomShroomBGColor'), + 'collide_bg': ba.getcollidemodel('natureBackgroundCollide'), + 'railing_collide_model': + (ba.getcollidemodel('bridgitLevelRailingCollide')), + 'bg_material': ba.Material() + } + data['bg_material'].add_actions(actions=('modify_part_collision', + 'friction', 10.0)) + return data + + def __init__(self) -> None: + super().__init__() + shared = SharedObjects.get() + self.background = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['model_bg'], + 'lighting': False, + 'background': True, + 'color_texture': self.preloaddata['model_bg_tex'] + }) + self.vr = ba.newnode('terrain', + attrs={ + 'model': self.preloaddata['bg_vr_fill_model'], + 'lighting': False, + 'vr_only': True, + 'background': True, + 'color_texture': self.preloaddata['model_bg_tex'] + }) + gnode = ba.getactivity().globalsnode + gnode.tint = (1.3, 1.2, 1.0) + gnode.ambient_color = (1.3, 1.2, 1.0) + gnode.vignette_outer = (0.57, 0.57, 0.57) + gnode.vignette_inner = (0.9, 0.9, 0.9) + gnode.vr_camera_offset = (0, -0.8, -1.1) + gnode.vr_near_clip = 0.5 + + + def is_point_near_edge(self, + point: ba.Vec3, + running: bool = False) -> bool: + box_position = self.defs.boxes['edge_box'][0:3] + box_scale = self.defs.boxes['edge_box'][6:9] + xpos = (point.x - box_position[0]) / box_scale[0] + zpos = (point.z - box_position[2]) / box_scale[2] + return xpos < -0.5 or xpos > 0.5 or zpos < -0.5 or zpos > 0.5 + + def _handle_player_collide(self): + try: + player = ba.getcollision().opposingnode.getdelegate( + PlayerSpaz, True) + except ba.NotFoundError: + return + if player.is_alive(): + player.shatter(True) + + + +try: + ba._map.register_map(WoodenFloor) +except: + pass \ No newline at end of file diff --git a/plugins/minigames/drone_war.py b/plugins/minigames/drone_war.py new file mode 100644 index 0000000..b27276a --- /dev/null +++ b/plugins/minigames/drone_war.py @@ -0,0 +1,426 @@ +# Released under the MIT License. See LICENSE for details. +# +""" +DroneWar - Attack enemies with drone, Fly with drone and fire rocket launcher. +Author: Mr.Smoothy +Discord: https://discord.gg/ucyaesh +Youtube: https://www.youtube.com/c/HeySmoothy +Website: https://bombsquad-community.web.app +Github: https://github.com/bombsquad-community +""" +# ba_meta require api 7 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import ba +import _ba +from ba._generated.enums import InputType +from bastd.actor.bomb import Blast + +from bastd.gameutils import SharedObjects +from bastd.actor.playerspaz import PlayerSpaz, PlayerType +from bastd.game.deathmatch import DeathMatchGame, Player +from bastd.actor import spaz +if TYPE_CHECKING: + from typing import Any, Sequence, Dict, Type, List, Optional, Union + +STORAGE_ATTR_NAME = f'_shared_{__name__}_factory' + +# SMoothy's Drone (fixed version of floater) with rocket launcher +# use drone as long as you want , unlike floater which dies after being idle. +class Drone(ba.Actor): + def __init__(self, spaz): + super().__init__() + shared = SharedObjects.get() + self._drone_material = ba.Material() + self.loop_ascend = None + self.loop_descend = None + self.loop_lr = None + self.loop_ud = None + self.rocket_launcher = None + self.x_direction = 0 + self.z_direction = 0 + self.spaz = spaz + self._drone_material.add_actions( + conditions=('they_have_material', + shared.player_material), + actions=(('modify_node_collision', 'collide', True), + ('modify_part_collision', 'physical', True))) + self._drone_material.add_actions( + conditions=(('they_have_material', + shared.object_material), 'or', + ('they_have_material', + shared.footing_material), 'or', + ('they_have_material', + self._drone_material)), + actions=('modify_part_collision', 'physical', False)) + self.node = ba.newnode( + 'prop', + delegate=self, + owner = None, + attrs={ + 'position': spaz.node.position, + 'model':ba.getmodel('landMine'), + 'light_model':ba.getmodel('landMine'), + 'body':'landMine', + 'body_scale':1, + 'model_scale':1, + 'shadow_size':0.25, + 'density':999999, + 'gravity_scale':0.0, + 'color_texture':ba.gettexture('achievementCrossHair'), + 'reflection':'soft', + 'reflection_scale': [0.25], + 'materials':[shared.footing_material, self._drone_material] + }) + self.grab_node = ba.newnode( + 'prop', + owner=self.node, + attrs={ + 'position': (0, 0, 0), + 'body':'sphere', + 'model': None, + 'color_texture':None, + 'body_scale':0.2, + 'reflection':'powerup', + 'density':999999, + 'reflection_scale': [1.0], + 'model_scale':0.2, + 'gravity_scale':0, + 'shadow_size':0.1, + 'is_area_of_interest':True, + 'materials':[shared.object_material, self._drone_material] + }) + self.node.connectattr('position', self.grab_node, 'position') + self._rcombine=ba.newnode('combine', + owner=self.node, + attrs={ + 'input0':self.spaz.node.position[0], + 'input1':self.spaz.node.position[1]+3, + 'input2':self.spaz.node.position[2], + 'size':3 + }) + + self._rcombine.connectattr('output',self.node,'position') + def set_rocket_launcher(self, launcher: RocketLauncher): + self.rocket_launcher = launcher + def fire(self): + if hasattr(self.grab_node,"position"): + self.rocket_launcher.shot(self.spaz, self.x_direction, self.z_direction , (self.grab_node.position[0], self.grab_node.position[1] -1, self.grab_node.position[2])) + def ascend(self): + def loop(): + if self.node.exists(): + ba.animate( self._rcombine,'input1',{ + 0:self.node.position[1], + 1:self.node.position[1] + 2 + }) + loop() + self.loop_ascend = ba.Timer(1, loop , repeat = True) + + def pause_movement(self): + self.loop_ascend = None + def decend(self): + def loop(): + if self.node.exists(): + ba.animate( self._rcombine,'input1',{ + 0:self.node.position[1], + 1:self.node.position[1] - 2 + }) + loop() + self.loop_ascend = ba.Timer(1, loop , repeat = True ) + def pause_lr(self): + self.loop_lr = None + def pause_ud(self): + self.loop_ud = None + def left_(self, value = -1): + def loop(): + if self.node.exists(): + ba.animate( self._rcombine,'input0',{ + 0:self.node.position[0], + 1:self.node.position[0] + 2 * value + }) + if value == 0.0: + self.loop_lr = None + else: + self.x_direction = value + self.z_direction = 0 + loop() + self.loop_lr = ba.Timer(1, loop , repeat = True ) + def right_(self, value = 1): + def loop(): + if self.node.exists(): + ba.animate( self._rcombine,'input0',{ + 0:self.node.position[0], + 1:self.node.position[0] + 2 * value + }) + if value == 0.0: + self.loop_lr = None + else: + self.x_direction = value + self.z_direction = 0 + loop() + self.loop_lr = ba.Timer(1, loop , repeat = True ) + def up_(self, value=1): + def loop(): + if self.node.exists(): + ba.animate( self._rcombine,'input2',{ + 0:self.node.position[2], + 1:self.node.position[2] - 2 * value + }) + if value == 0.0: + self.loop_ud = None + else: + self.x_direction = 0 + self.z_direction = - value + loop() + self.loop_ud = ba.Timer(1, loop , repeat = True ) + def down_(self, value=-1): + def loop(): + if self.node.exists(): + ba.animate( self._rcombine,'input2',{ + 0:self.node.position[2], + 1:self.node.position[2] - 2 * value + }) + if value == 0.0: + self.loop_ud = None + else: + self.x_direction = 0 + self.z_direction = - value + loop() + self.loop_ud = ba.Timer(1, loop , repeat = True ) + def handlemessage(self, msg): + if isinstance(msg, ba.DieMessage): + self.node.delete() + self.grab_node.delete() + self.loop_ascend = None + self.loop_ud = None + self.loop_lr = None + elif isinstance(msg, ba.OutOfBoundsMessage): + self.handlemessage(ba.DieMessage()) + else: + super().handlemessage(msg) + +# =============================================Copied from Quake Game - Dliwk ===================================================================== +class RocketFactory: + """Quake Rocket factory""" + + def __init__(self) -> None: + self.ball_material = ba.Material() + + self.ball_material.add_actions( + conditions=((('we_are_younger_than', 5), 'or', + ('they_are_younger_than', 5)), 'and', + ('they_have_material', + SharedObjects.get().object_material)), + actions=('modify_node_collision', 'collide', False)) + + self.ball_material.add_actions( + conditions=('they_have_material', + SharedObjects.get().pickup_material), + actions=('modify_part_collision', 'use_node_collide', False)) + + self.ball_material.add_actions(actions=('modify_part_collision', + 'friction', 0)) + + self.ball_material.add_actions( + conditions=(('they_have_material', + SharedObjects.get().footing_material), 'or', + ('they_have_material', + SharedObjects.get().object_material)), + actions=('message', 'our_node', 'at_connect', ImpactMessage())) + + @classmethod + def get(cls): + """Get factory if exists else create new""" + activity = ba.getactivity() + if hasattr(activity, STORAGE_ATTR_NAME): + return getattr(activity, STORAGE_ATTR_NAME) + factory = cls() + setattr(activity, STORAGE_ATTR_NAME, factory) + return factory + + +class RocketLauncher: + """Very dangerous weapon""" + + def __init__(self): + self.last_shot = ba.time() + + def give(self, spaz: spaz.Spaz) -> None: + """Give spaz a rocket launcher""" + spaz.punch_callback = self.shot + self.last_shot = ba.time() + + # FIXME + # noinspection PyUnresolvedReferences + def shot(self, spaz, x, z, position) -> None: + """Release a rocket""" + time = ba.time() + if time - self.last_shot > 0.6: + self.last_shot = time + + direction = [x, 0, z] + direction[1] = 0.0 + + mag = 10.0 / 1 if ba.Vec3(*direction).length() == 0 else ba.Vec3(*direction).length() + vel = [v * mag for v in direction] + Rocket(position=position, + velocity=vel, + owner=spaz.getplayer(ba.Player), + source_player=spaz.getplayer(ba.Player), + color=spaz.node.color).autoretain() + + +class ImpactMessage: + """Rocket touched something""" + + +class Rocket(ba.Actor): + """Epic rocket from rocket launcher""" + + def __init__(self, + position=(0, 5, 0), + velocity=(1, 0, 0), + source_player=None, + owner=None, + color=(1.0, 0.2, 0.2)) -> None: + super().__init__() + self.source_player = source_player + self.owner = owner + self._color = color + factory = RocketFactory.get() + + self.node = ba.newnode('prop', + delegate=self, + attrs={ + 'position': position, + 'velocity': velocity, + 'model': ba.getmodel('impactBomb'), + 'body': 'sphere', + 'color_texture': ba.gettexture( + 'bunnyColor'), + 'model_scale': 0.2, + 'is_area_of_interest': True, + 'body_scale': 0.8, + 'materials': [ + SharedObjects.get().object_material, + factory.ball_material] + }) # yapf: disable + self.node.extra_acceleration = (self.node.velocity[0] * 200, 0, + self.node.velocity[2] * 200) + + self._life_timer = ba.Timer( + 5, ba.WeakCall(self.handlemessage, ba.DieMessage())) + + self._emit_timer = ba.Timer(0.001, ba.WeakCall(self.emit), repeat=True) + self.base_pos_y = self.node.position[1] + + ba.camerashake(5.0) + + def emit(self) -> None: + """Emit a trace after rocket""" + ba.emitfx(position=self.node.position, + scale=0.4, + spread=0.01, + chunk_type='spark') + if not self.node: + return + self.node.position = (self.node.position[0], self.base_pos_y, + self.node.position[2]) # ignore y + ba.newnode('explosion', + owner=self.node, + attrs={ + 'position': self.node.position, + 'radius': 0.2, + 'color': self._color + }) + + def handlemessage(self, msg: Any) -> Any: + """Message handling for rocket""" + super().handlemessage(msg) + if isinstance(msg, ImpactMessage): + self.node.handlemessage(ba.DieMessage()) + + elif isinstance(msg, ba.DieMessage): + if self.node: + Blast(position=self.node.position, + blast_radius=2, + source_player=self.source_player) + + self.node.delete() + self._emit_timer = None + + elif isinstance(msg, ba.OutOfBoundsMessage): + self.handlemessage(ba.DieMessage()) + + +# ba_meta export game +class ChooseQueen(DeathMatchGame): + name = 'Drone War' + + @classmethod + def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + return issubclass(sessiontype, ba.DualTeamSession) + + @classmethod + def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + return ['Football Stadium'] + + def spawn_player_spaz( + self, + player: PlayerType, + position: Sequence[float] | None = None, + angle: float | None = None, + ) -> PlayerSpaz: + spaz = super().spawn_player_spaz(player, position, angle) + self.spawn_drone(spaz) + return spaz + + def on_begin(self): + super().on_begin() + shared = SharedObjects.get() + self.ground_material = ba.Material() + self.ground_material.add_actions( + conditions=('they_have_material',shared.player_material), + actions=( + ('modify_part_collision', 'collide', True), + ( 'call','at_connect',ba.Call(self._handle_player_collide )), + ), + ) + pos=(0,0.1,-5) + self.main_region=ba.newnode('region',attrs={'position': pos,'scale': (30,0.001,23),'type': 'box','materials': [shared.footing_material,self.ground_material]}) + + def _handle_player_collide(self): + try: + player = ba.getcollision().opposingnode.getdelegate( + PlayerSpaz, True) + except ba.NotFoundError: + return + if player.is_alive(): + player.shatter(True) + + def spawn_drone(self, spaz): + with ba.Context(_ba.get_foreground_host_activity()): + + drone = Drone(spaz) + r_launcher = RocketLauncher() + drone.set_rocket_launcher(r_launcher) + player = spaz.getplayer(Player, True) + spaz.node.hold_node = drone.grab_node + player.actor.disconnect_controls_from_player() + player.resetinput() + player.assigninput(InputType.PICK_UP_PRESS, drone.ascend) + player.assigninput(InputType.PICK_UP_RELEASE, drone.pause_movement) + player.assigninput(InputType.JUMP_PRESS, drone.decend) + player.assigninput(InputType.PUNCH_PRESS, drone.fire) + player.assigninput(InputType.LEFT_PRESS, drone.left_) + player.assigninput(InputType.RIGHT_PRESS, drone.right_) + player.assigninput(InputType.LEFT_RELEASE, drone.pause_lr) + player.assigninput(InputType.RIGHT_RELEASE, drone.pause_lr) + player.assigninput(InputType.UP_PRESS, drone.up_) + player.assigninput(InputType.DOWN_PRESS, drone.down_) + player.assigninput(InputType.UP_RELEASE, drone.pause_ud) + player.assigninput(InputType.DOWN_RELEASE, drone.pause_ud) \ No newline at end of file diff --git a/plugins/minigames/the_spaz_game.py b/plugins/minigames/the_spaz_game.py new file mode 100644 index 0000000..5bbebac --- /dev/null +++ b/plugins/minigames/the_spaz_game.py @@ -0,0 +1,122 @@ + +# ba_meta require api 7 +""" +TheSpazGame - Mini game where all characters looks identical , identify enemies and kill them. +Author: Mr.Smoothy +Discord: https://discord.gg/ucyaesh +Youtube: https://www.youtube.com/c/HeySmoothy +Website: https://bombsquad-community.web.app +Github: https://github.com/bombsquad-community +""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +import ba +from bastd.game.elimination import EliminationGame, Player +from bastd.actor.spazfactory import SpazFactory +import random + +if TYPE_CHECKING: + from typing import Any, Sequence + + +CHARACTER = 'Spaz' + +# ba_meta export game +class TheSpazGame(EliminationGame): + name = 'TheSpazGame' + description = 'Enemy Spaz AmongUs. Kill them all' + scoreconfig = ba.ScoreConfig( + label='Survived', scoretype=ba.ScoreType.SECONDS, none_is_winner=True + ) + + announce_player_deaths = False + + allow_mid_activity_joins = False + + @classmethod + def get_available_settings( + cls, sessiontype: type[ba.Session] + ) -> list[ba.Setting]: + settings = [ + ba.IntSetting( + 'Lives Per Player', + default=1, + min_value=1, + max_value=10, + increment=1, + ), + ba.IntChoiceSetting( + 'Time Limit', + choices=[ + ('None', 0), + ('1 Minute', 60), + ('2 Minutes', 120), + ('5 Minutes', 300), + ('10 Minutes', 600), + ('20 Minutes', 1200), + ], + default=0, + ), + ba.FloatChoiceSetting( + 'Respawn Times', + choices=[ + ('Shorter', 0.15) + ], + default=1.0, + ), + ba.BoolSetting('Epic Mode', default=False), + ] + if issubclass(sessiontype, ba.DualTeamSession): + settings.append(ba.BoolSetting('Solo Mode', default=False)) + settings.append( + ba.BoolSetting('Balance Total Lives', default=False) + ) + return settings + + @classmethod + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: + return issubclass(sessiontype, ba.DualTeamSession) or issubclass( + sessiontype, ba.FreeForAllSession + ) + + @classmethod + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: + return ba.getmaps('melee') + + def get_instance_description(self) -> str | Sequence: + return ( + 'Enemy Spaz AmongUs. Kill them all' + ) + + def get_instance_description_short(self) -> str | Sequence: + return ( + 'Enemy Spaz AmongUs. Kill them all' + ) + def __init__(self, settings: dict): + super().__init__(settings) + self._solo_mode = False + + def spawn_player(self, player: Player) -> ba.Actor: + p = [-6, -4.3, -2.6, -0.9, 0.8, 2.5, 4.2, 5.9] + q = [-4, -2.3, -0.6, 1.1, 2.8, 4.5] + + x = random.randrange(0, len(p)) + y = random.randrange(0, len(q)) + spaz = self.spawn_player_spaz(player, position=(p[x], 1.8, q[y])) + spaz.node.color = (1,1,1) + spaz.node.highlight = (1,0.4,1) + self.update_appearance(spaz, character=CHARACTER) + # Also lets have them make some noise when they die. + spaz.play_big_death_sound = True + return spaz + + def update_appearance(self, spaz, character): + factory = SpazFactory.get() + media = factory.get_media(character) + for field, value in media.items(): + setattr(spaz.node, field, value) + spaz.node.style = factory.get_style(character) + spaz.node.name = '' +