diff --git a/plugins/minigames.json b/plugins/minigames.json index 9513a6d..4cdc9ce 100644 --- a/plugins/minigames.json +++ b/plugins/minigames.json @@ -1203,6 +1203,104 @@ "versions": { "1.0.0": null } + }, + "down_into_the_abyss": { + "description": "Survive as long as you can but dont miss a step", + "external_url": "", + "authors": [ + { + "name": "", + "email": "", + "discord": "" + } + ], + "versions": { + "1.0.0": null + } + }, + "better_deathmatch": { + "description": "A very-customisable DeathMatch mini-game", + "external_url": "", + "authors": [ + { + "name": "Freaku", + "email": "", + "discord": "freakyyyy" + } + ], + "versions": { + "1.0.0": null + } + }, + "better_elimination": { + "description": "A very-customisable Elimination mini-game", + "external_url": "", + "authors": [ + { + "name": "Freaku", + "email": "", + "discord": "freakyyyy" + } + ], + "versions": { + "1.0.0": null + } + }, + "bot_shower": { + "description": "Survive from the bots.", + "external_url": "", + "authors": [ + { + "name": "", + "email": "", + "discord": "" + } + ], + "versions": { + "1.0.0": null + } + }, + "explodo_run": { + "description": "Run For Your Life :))", + "external_url": "", + "authors": [ + { + "name": "", + "email": "", + "discord": "" + } + ], + "versions": { + "1.0.0": null + } + }, + "extinction_run": { + "description": "Survive the Extinction.", + "external_url": "", + "authors": [ + { + "name": "", + "email": "", + "discord": "" + } + ], + "versions": { + "1.0.0": null + } + }, + "fat_pigs": { + "description": "Survive the Extinction.", + "external_url": "Survive the pigs...", + "authors": [ + { + "name": "Zacker Tz", + "email": "", + "discord": "zacker_tz" + } + ], + "versions": { + "1.0.0": null + } } } } \ No newline at end of file diff --git a/plugins/minigames/better_deathmatch.py b/plugins/minigames/better_deathmatch.py new file mode 100644 index 0000000..34116a5 --- /dev/null +++ b/plugins/minigames/better_deathmatch.py @@ -0,0 +1,271 @@ +# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport) +#BetterDeathMatch +#Made by your friend: @[Just] Freak#4999 + +"""Defines a very-customisable DeathMatch mini-game""" + +# ba_meta require api 8 + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import babase +import bauiv1 as bui +import bascenev1 as bs +from bascenev1lib.actor.playerspaz import PlayerSpaz +from bascenev1lib.actor.scoreboard import Scoreboard + +if TYPE_CHECKING: + from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional + + +class Player(bs.Player['Team']): + """Our player type for this game.""" + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + def __init__(self) -> None: + self.score = 0 + + +# ba_meta export bascenev1.GameActivity +class BetterDeathMatchGame(bs.TeamGameActivity[Player, Team]): + """A game type based on acquiring kills.""" + + name = 'Btrr Death Match' + description = 'Kill a set number of enemies to win.\nbyFREAK' + + # Print messages when players die since it matters here. + announce_player_deaths = True + + @classmethod + def get_available_settings( + cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]: + settings = [ + bs.IntSetting( + 'Kills to Win Per Player', + min_value=1, + default=5, + increment=1, + ), + bs.IntChoiceSetting( + 'Time Limit', + choices=[ + ('None', 0), + ('1 Minute', 60), + ('2 Minutes', 120), + ('5 Minutes', 300), + ('10 Minutes', 600), + ('20 Minutes', 1200), + ], + default=0, + ), + bs.FloatChoiceSetting( + 'Respawn Times', + choices=[ + ('Shorter', 0.25), + ('Short', 0.5), + ('Normal', 1.0), + ('Long', 2.0), + ('Longer', 4.0), + ], + default=1.0, + ), + bs.BoolSetting('Epic Mode', default=False), + + +## Add settings ## + bs.BoolSetting('Enable Gloves', False), + bs.BoolSetting('Enable Powerups', True), + bs.BoolSetting('Night Mode', False), + bs.BoolSetting('Icy Floor', False), + bs.BoolSetting('One Punch Kill', False), + bs.BoolSetting('Spawn with Shield', False), + bs.BoolSetting('Punching Only', False), +## Add settings ## + ] + + + # In teams mode, a suicide gives a point to the other team, but in + # free-for-all it subtracts from your own score. By default we clamp + # this at zero to benefit new players, but pro players might like to + # be able to go negative. (to avoid a strategy of just + # suiciding until you get a good drop) + if issubclass(sessiontype, bs.FreeForAllSession): + settings.append( + bs.BoolSetting('Allow Negative Scores', default=False)) + + return settings + + @classmethod + def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool: + return (issubclass(sessiontype, bs.DualTeamSession) + or issubclass(sessiontype, bs.FreeForAllSession)) + + @classmethod + def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]: + return bs.app.classic.getmaps('melee') + + def __init__(self, settings: dict): + super().__init__(settings) + self._scoreboard = Scoreboard() + self._score_to_win: Optional[int] = None + self._dingsound = bui.getsound('dingSmall') + + +## Take applied settings ## + self._boxing_gloves = bool(settings['Enable Gloves']) + self._enable_powerups = bool(settings['Enable Powerups']) + self._night_mode = bool(settings['Night Mode']) + self._icy_floor = bool(settings['Icy Floor']) + self._one_punch_kill = bool(settings['One Punch Kill']) + self._shield_ = bool(settings['Spawn with Shield']) + self._only_punch = bool(settings['Punching Only']) +## Take applied settings ## + + + self._epic_mode = bool(settings['Epic Mode']) + self._kills_to_win_per_player = int( + settings['Kills to Win Per Player']) + self._time_limit = float(settings['Time Limit']) + self._allow_negative_scores = bool( + settings.get('Allow Negative Scores', False)) + + # Base class overrides. + self.slow_motion = self._epic_mode + self.default_music = (bs.MusicType.EPIC if self._epic_mode else + bs.MusicType.TO_THE_DEATH) + + def get_instance_description(self) -> Union[str, Sequence]: + return 'Crush ${ARG1} of your enemies. byFREAK', self._score_to_win + + def get_instance_description_short(self) -> Union[str, Sequence]: + return 'kill ${ARG1} enemies. byFREAK', self._score_to_win + + def on_team_join(self, team: Team) -> None: + if self.has_begun(): + self._update_scoreboard() + + +## Run settings related: IcyFloor ## + def on_transition_in(self) -> None: + super().on_transition_in() + activity = bs.getactivity() + if self._icy_floor: + activity.map.is_hockey = True + else: + return +## Run settings related: IcyFloor ## + + + + def on_begin(self) -> None: + super().on_begin() + self.setup_standard_time_limit(self._time_limit) + + +## Run settings related: NightMode,Powerups ## + if self._night_mode: + bs.getactivity().globalsnode.tint = (0.5, 0.7, 1) + else: + pass +#-# Tried return here, pfft. Took me 30mins to figure out why pwps spawning only on NightMode +#-# Now its fixed :) + if self._enable_powerups: + self.setup_standard_powerup_drops() + else: + pass +## Run settings related: NightMode,Powerups ## + + + # Base kills needed to win on the size of the largest team. + self._score_to_win = (self._kills_to_win_per_player * + max(1, max(len(t.players) for t in self.teams))) + self._update_scoreboard() + + def handlemessage(self, msg: Any) -> Any: + + if isinstance(msg, bs.PlayerDiedMessage): + + # Augment standard behavior. + super().handlemessage(msg) + + player = msg.getplayer(Player) + self.respawn_player(player) + + killer = msg.getkillerplayer(Player) + if killer is None: + return None + + # Handle team-kills. + if killer.team is player.team: + + # In free-for-all, killing yourself loses you a point. + if isinstance(self.session, bs.FreeForAllSession): + new_score = player.team.score - 1 + if not self._allow_negative_scores: + new_score = max(0, new_score) + player.team.score = new_score + + # In teams-mode it gives a point to the other team. + else: + self._dingsound.play() + for team in self.teams: + if team is not killer.team: + team.score += 1 + + # Killing someone on another team nets a kill. + else: + killer.team.score += 1 + self._dingsound.play() + + # In FFA show scores since its hard to find on the scoreboard. + if isinstance(killer.actor, PlayerSpaz) and killer.actor: + killer.actor.set_score_text(str(killer.team.score) + '/' + + str(self._score_to_win), + color=killer.team.color, + flash=True) + + self._update_scoreboard() + + # If someone has won, set a timer to end shortly. + # (allows the dust to clear and draws to occur if deaths are + # close enough) + assert self._score_to_win is not None + if any(team.score >= self._score_to_win for team in self.teams): + bs.timer(0.5, self.end_game) + + else: + return super().handlemessage(msg) + return None + + +## Run settings related: Spaz ## + def spawn_player(self, player: Player) -> bs.Actor: + spaz = self.spawn_player_spaz(player) + if self._boxing_gloves: + spaz.equip_boxing_gloves() + if self._one_punch_kill: + spaz._punch_power_scale = 15 + if self._shield_: + spaz.equip_shields() + if self._only_punch: + spaz.connect_controls_to_player(enable_bomb=False, enable_pickup=False) + + return spaz +## Run settings related: Spaz ## + + + def _update_scoreboard(self) -> None: + for team in self.teams: + self._scoreboard.set_team_value(team, team.score, + self._score_to_win) + + def end_game(self) -> None: + results = bs.GameResults() + for team in self.teams: + results.set_team_score(team, team.score) + self.end(results=results) diff --git a/plugins/minigames/better_elimination.py b/plugins/minigames/better_elimination.py new file mode 100644 index 0000000..6ff2910 --- /dev/null +++ b/plugins/minigames/better_elimination.py @@ -0,0 +1,660 @@ +# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport) +#BetterElimination +#Made by your friend: @[Just] Freak#4999 + +#Huge Thx to Nippy for "Live Team Balance" + + +"""Defines a very-customisable Elimination mini-game""" + +# ba_meta require api 8 + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import babase +import bauiv1 as bui +import bascenev1 as bs +from bascenev1lib.actor.spazfactory import SpazFactory +from bascenev1lib.actor.scoreboard import Scoreboard + +if TYPE_CHECKING: + from typing import (Any, Tuple, Dict, Type, List, Sequence, Optional, + Union) + + +class Icon(bs.Actor): + """Creates in in-game icon on screen.""" + + def __init__(self, + player: Player, + position: Tuple[float, float], + scale: float, + show_lives: bool = True, + show_death: bool = True, + name_scale: float = 1.0, + name_maxwidth: float = 115.0, + flatness: float = 1.0, + shadow: float = 1.0): + super().__init__() + + self._player = player + self._show_lives = show_lives + self._show_death = show_death + self._name_scale = name_scale + self._outline_tex = bs.gettexture('characterIconMask') + + icon = player.get_icon() + self.node = bs.newnode('image', + delegate=self, + attrs={ + 'texture': icon['texture'], + 'tint_texture': icon['tint_texture'], + 'tint_color': icon['tint_color'], + 'vr_depth': 400, + 'tint2_color': icon['tint2_color'], + 'mask_texture': self._outline_tex, + 'opacity': 1.0, + 'absolute_scale': True, + 'attach': 'bottomCenter' + }) + self._name_text = bs.newnode( + 'text', + owner=self.node, + attrs={ + 'text': babase.Lstr(value=player.getname()), + 'color': babase.safecolor(player.team.color), + 'h_align': 'center', + 'v_align': 'center', + 'vr_depth': 410, + 'maxwidth': name_maxwidth, + 'shadow': shadow, + 'flatness': flatness, + 'h_attach': 'center', + 'v_attach': 'bottom' + }) + if self._show_lives: + self._lives_text = bs.newnode('text', + owner=self.node, + attrs={ + 'text': 'x0', + 'color': (1, 1, 0.5), + 'h_align': 'left', + 'vr_depth': 430, + 'shadow': 1.0, + 'flatness': 1.0, + 'h_attach': 'center', + 'v_attach': 'bottom' + }) + self.set_position_and_scale(position, scale) + + def set_position_and_scale(self, position: Tuple[float, float], + scale: float) -> None: + """(Re)position the icon.""" + assert self.node + self.node.position = position + self.node.scale = [70.0 * scale] + self._name_text.position = (position[0], position[1] + scale * 52.0) + self._name_text.scale = 1.0 * scale * self._name_scale + if self._show_lives: + self._lives_text.position = (position[0] + scale * 10.0, + position[1] - scale * 43.0) + self._lives_text.scale = 1.0 * scale + + def update_for_lives(self) -> None: + """Update for the target player's current lives.""" + if self._player: + lives = self._player.lives + else: + lives = 0 + if self._show_lives: + if lives > 0: + self._lives_text.text = 'x' + str(lives - 1) + else: + self._lives_text.text = '' + if lives == 0: + self._name_text.opacity = 0.2 + assert self.node + self.node.color = (0.7, 0.3, 0.3) + self.node.opacity = 0.2 + + def handle_player_spawned(self) -> None: + """Our player spawned; hooray!""" + if not self.node: + return + self.node.opacity = 1.0 + self.update_for_lives() + + def handle_player_died(self) -> None: + """Well poo; our player died.""" + if not self.node: + return + if self._show_death: + bs.animate( + self.node, 'opacity', { + 0.00: 1.0, + 0.05: 0.0, + 0.10: 1.0, + 0.15: 0.0, + 0.20: 1.0, + 0.25: 0.0, + 0.30: 1.0, + 0.35: 0.0, + 0.40: 1.0, + 0.45: 0.0, + 0.50: 1.0, + 0.55: 0.2 + }) + lives = self._player.lives + if lives == 0: + bs.timer(0.6, self.update_for_lives) + + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, bs.DieMessage): + self.node.delete() + return None + return super().handlemessage(msg) + + +class Player(bs.Player['Team']): + """Our player type for this game.""" + + def __init__(self) -> None: + self.lives = 0 + self.icons: List[Icon] = [] + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + def __init__(self) -> None: + self.survival_seconds: Optional[int] = None + self.spawn_order: List[Player] = [] + + +# ba_meta export bascenev1.GameActivity +class BetterEliminationGame(bs.TeamGameActivity[Player, Team]): + """Game type where last player(s) left alive win.""" + + name = 'Bttr Elimination' + description = 'Last remaining alive wins.\nbyFREAK' + scoreconfig = bs.ScoreConfig(label='Survived', + scoretype=bs.ScoreType.SECONDS, + none_is_winner=True) + # Show messages when players die since it's meaningful here. + announce_player_deaths = True + + @classmethod + def get_available_settings( + cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]: + settings = [ + bs.IntSetting( + 'Life\'s Per Player', + default=1, + min_value=1, + max_value=10, + increment=1, + ), + bs.IntChoiceSetting( + 'Time Limit', + choices=[ + ('None', 0), + ('1 Minute', 60), + ('2 Minutes', 120), + ('5 Minutes', 300), + ('10 Minutes', 600), + ('20 Minutes', 1200), + ], + default=0, + ), + bs.FloatChoiceSetting( + 'Respawn Times', + choices=[ + ('Shorter', 0.25), + ('Short', 0.5), + ('Normal', 1.0), + ('Long', 2.0), + ('Longer', 4.0), + ], + default=1.0, + ), + bs.BoolSetting('Epic Mode', default=False), + + +## Add settings ## + bs.BoolSetting('Live Team Balance (by Nippy#2677)', True), + bs.BoolSetting('Enable Gloves', False), + bs.BoolSetting('Enable Powerups', True), + bs.BoolSetting('Night Mode', False), + bs.BoolSetting('Icy Floor', False), + bs.BoolSetting('One Punch Kill', False), + bs.BoolSetting('Spawn with Shield', False), + bs.BoolSetting('Punching Only', False), +## Add settings ## + ] + if issubclass(sessiontype, bs.DualTeamSession): + settings.append(bs.BoolSetting('Solo Mode', default=False)) + settings.append( + bs.BoolSetting('Balance Total Life\'s (on spawn only)', default=False)) + return settings + + @classmethod + def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool: + return (issubclass(sessiontype, bs.DualTeamSession) + or issubclass(sessiontype, bs.FreeForAllSession)) + + @classmethod + def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]: + return bs.app.classic.getmaps('melee') + + def __init__(self, settings: dict): + super().__init__(settings) + self._scoreboard = Scoreboard() + self._start_time: Optional[float] = None + self._vs_text: Optional[bs.Actor] = None + self._round_end_timer: Optional[bs.Timer] = None + +## Take applied settings ## + self._live_team_balance = bool(settings['Live Team Balance (by Nippy#2677)']) + self._boxing_gloves = bool(settings['Enable Gloves']) + self._enable_powerups = bool(settings['Enable Powerups']) + self._night_mode = bool(settings['Night Mode']) + self._icy_floor = bool(settings['Icy Floor']) + self._one_punch_kill = bool(settings['One Punch Kill']) + self._shield_ = bool(settings['Spawn with Shield']) + self._only_punch = bool(settings['Punching Only']) +## Take applied settings ## + + self._epic_mode = bool(settings['Epic Mode']) + self._lives_per_player = int(settings['Life\'s Per Player']) + self._time_limit = float(settings['Time Limit']) + self._balance_total_lives = bool( + settings.get('Balance Total Life\'s (on spawn only)', False)) + self._solo_mode = bool(settings.get('Solo Mode', False)) + + # Base class overrides: + self.slow_motion = self._epic_mode + self.default_music = (bs.MusicType.EPIC + if self._epic_mode else bs.MusicType.SURVIVAL) + + def get_instance_description(self) -> Union[str, Sequence]: + return 'Last team standing wins. byFREAK' if isinstance( + self.session, bs.DualTeamSession) else 'Last one standing wins.' + + def get_instance_description_short(self) -> Union[str, Sequence]: + return 'last team standing wins. byFREAK' if isinstance( + self.session, bs.DualTeamSession) else 'last one standing wins' + + def on_player_join(self, player: Player) -> None: + + # No longer allowing mid-game joiners here; too easy to exploit. + if self.has_begun(): + + # Make sure their team has survival seconds set if they're all dead + # (otherwise blocked new ffa players are considered 'still alive' + # in score tallying). + if (self._get_total_team_lives(player.team) == 0 + and player.team.survival_seconds is None): + player.team.survival_seconds = 0 + bui.screenmessage( + babase.Lstr(resource='playerDelayedJoinText', + subs=[('${PLAYER}', player.getname(full=True))]), + color=(0, 1, 0), + ) + return + + player.lives = self._lives_per_player + + if self._solo_mode: + player.team.spawn_order.append(player) + self._update_solo_mode() + else: + # Create our icon and spawn. + player.icons = [Icon(player, position=(0, 50), scale=0.8)] + if player.lives > 0: + self.spawn_player(player) + + # Don't waste time doing this until begin. + if self.has_begun(): + self._update_icons() + + +## Run settings related: IcyFloor ## + def on_transition_in(self) -> None: + super().on_transition_in() + activity = bs.getactivity() + if self._icy_floor: + activity.map.is_hockey = True + else: + return +## Run settings related: IcyFloor ## + + + + def on_begin(self) -> None: + super().on_begin() + self._start_time = bs.time() + self.setup_standard_time_limit(self._time_limit) + + +## Run settings related: NightMode,Powerups ## + if self._night_mode: + bs.getactivity().globalsnode.tint = (0.5, 0.7, 1) + else: + pass +#-# Tried return here, pfft. Took me 30mins to figure out why pwps spawning only on NightMode +#-# Now its fixed :) + if self._enable_powerups: + self.setup_standard_powerup_drops() + else: + pass +## Run settings related: NightMode,Powerups ## + + + if self._solo_mode: + self._vs_text = bs.NodeActor( + bs.newnode('text', + attrs={ + 'position': (0, 105), + 'h_attach': 'center', + 'h_align': 'center', + 'maxwidth': 200, + 'shadow': 0.5, + 'vr_depth': 390, + 'scale': 0.6, + 'v_attach': 'bottom', + 'color': (0.8, 0.8, 0.3, 1.0), + 'text': babase.Lstr(resource='vsText') + })) + + # If balance-team-lives is on, add lives to the smaller team until + # total lives match. + if (isinstance(self.session, bs.DualTeamSession) + and self._balance_total_lives and self.teams[0].players + and self.teams[1].players): + if self._get_total_team_lives( + self.teams[0]) < self._get_total_team_lives(self.teams[1]): + lesser_team = self.teams[0] + greater_team = self.teams[1] + else: + lesser_team = self.teams[1] + greater_team = self.teams[0] + add_index = 0 + while (self._get_total_team_lives(lesser_team) < + self._get_total_team_lives(greater_team)): + lesser_team.players[add_index].lives += 1 + add_index = (add_index + 1) % len(lesser_team.players) + + self._update_icons() + + # We could check game-over conditions at explicit trigger points, + # but lets just do the simple thing and poll it. + bs.timer(1.0, self._update, repeat=True) + + def _update_solo_mode(self) -> None: + # For both teams, find the first player on the spawn order list with + # lives remaining and spawn them if they're not alive. + for team in self.teams: + # Prune dead players from the spawn order. + team.spawn_order = [p for p in team.spawn_order if p] + for player in team.spawn_order: + assert isinstance(player, Player) + if player.lives > 0: + if not player.is_alive(): + self.spawn_player(player) + break + + def _update_icons(self) -> None: + # pylint: disable=too-many-branches + + # In free-for-all mode, everyone is just lined up along the bottom. + if isinstance(self.session, bs.FreeForAllSession): + count = len(self.teams) + x_offs = 85 + xval = x_offs * (count - 1) * -0.5 + for team in self.teams: + if len(team.players) == 1: + player = team.players[0] + for icon in player.icons: + icon.set_position_and_scale((xval, 30), 0.7) + icon.update_for_lives() + xval += x_offs + + # In teams mode we split up teams. + else: + if self._solo_mode: + # First off, clear out all icons. + for player in self.players: + player.icons = [] + + # Now for each team, cycle through our available players + # adding icons. + for team in self.teams: + if team.id == 0: + xval = -60 + x_offs = -78 + else: + xval = 60 + x_offs = 78 + is_first = True + test_lives = 1 + while True: + players_with_lives = [ + p for p in team.spawn_order + if p and p.lives >= test_lives + ] + if not players_with_lives: + break + for player in players_with_lives: + player.icons.append( + Icon(player, + position=(xval, (40 if is_first else 25)), + scale=1.0 if is_first else 0.5, + name_maxwidth=130 if is_first else 75, + name_scale=0.8 if is_first else 1.0, + flatness=0.0 if is_first else 1.0, + shadow=0.5 if is_first else 1.0, + show_death=is_first, + show_lives=False)) + xval += x_offs * (0.8 if is_first else 0.56) + is_first = False + test_lives += 1 + # Non-solo mode. + else: + for team in self.teams: + if team.id == 0: + xval = -50 + x_offs = -85 + else: + xval = 50 + x_offs = 85 + for player in team.players: + for icon in player.icons: + icon.set_position_and_scale((xval, 30), 0.7) + icon.update_for_lives() + xval += x_offs + + def _get_spawn_point(self, player: Player) -> Optional[babase.Vec3]: + del player # Unused. + + # In solo-mode, if there's an existing live player on the map, spawn at + # whichever spot is farthest from them (keeps the action spread out). + if self._solo_mode: + living_player = None + living_player_pos = None + for team in self.teams: + for tplayer in team.players: + if tplayer.is_alive(): + assert tplayer.node + ppos = tplayer.node.position + living_player = tplayer + living_player_pos = ppos + break + if living_player: + assert living_player_pos is not None + player_pos = babase.Vec3(living_player_pos) + points: List[Tuple[float, babase.Vec3]] = [] + for team in self.teams: + start_pos = babase.Vec3(self.map.get_start_position(team.id)) + points.append( + ((start_pos - player_pos).length(), start_pos)) + # Hmm.. we need to sorting vectors too? + points.sort(key=lambda x: x[0]) + return points[-1][1] + return None + + def spawn_player(self, player: Player) -> bs.Actor: + actor = self.spawn_player_spaz(player, self._get_spawn_point(player)) + if not self._solo_mode: + bs.timer(0.3, babase.Call(self._print_lives, player)) + + # If we have any icons, update their state. + for icon in player.icons: + icon.handle_player_spawned() + +## Run settings related: Spaz ## + if self._boxing_gloves: + actor.equip_boxing_gloves() + if self._one_punch_kill: + actor._punch_power_scale = 15 + if self._shield_: + actor.equip_shields() + if self._only_punch: + actor.connect_controls_to_player(enable_bomb=False, enable_pickup=False) + + return actor +## Run settings related: Spaz ## + + + def _print_lives(self, player: Player) -> None: + from bascenev1lib.actor import popuptext + + # We get called in a timer so it's possible our player has left/etc. + if not player or not player.is_alive() or not player.node: + return + + popuptext.PopupText('x' + str(player.lives - 1), + color=(1, 1, 0, 1), + offset=(0, -0.8, 0), + random_offset=0.0, + scale=1.8, + position=player.node.position).autoretain() + + def on_player_leave(self, player: Player) -> None: + ########################################################Nippy#2677 + team_count=1 #Just initiating + if player.lives>0 and self._live_team_balance: + team_mem=[] + for teamer in player.team.players: + if player!=teamer: + team_mem.append(teamer) #Got Dead players Team + live=player.lives + team_count=len(team_mem) + for i in range(int((live if live%2==0 else live+1)/2)): #Extending Player List for Sorted Players + team_mem.extend(team_mem) + if team_count>0: + for i in range(live): + team_mem[i].lives+=1 + + if team_count<=0 : #Draw if Player Leaves + self.end_game() + ########################################################Nippy#2677 + super().on_player_leave(player) + player.icons = [] + + # Remove us from spawn-order. + if self._solo_mode: + if player in player.team.spawn_order: + player.team.spawn_order.remove(player) + + # Update icons in a moment since our team will be gone from the + # list then. + bs.timer(0, self._update_icons) + + # If the player to leave was the last in spawn order and had + # their final turn currently in-progress, mark the survival time + # for their team. + if self._get_total_team_lives(player.team) == 0: + assert self._start_time is not None + player.team.survival_seconds = int(bs.time() - self._start_time) + + def _get_total_team_lives(self, team: Team) -> int: + return sum(player.lives for player in team.players) + + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, bs.PlayerDiedMessage): + + # Augment standard behavior. + super().handlemessage(msg) + player: Player = msg.getplayer(Player) + + player.lives -= 1 + if player.lives < 0: + babase.print_error( + "Got lives < 0 in Elim; this shouldn't happen. solo:" + + str(self._solo_mode)) + player.lives = 0 + + # If we have any icons, update their state. + for icon in player.icons: + icon.handle_player_died() + + # Play big death sound on our last death + # or for every one in solo mode. + if self._solo_mode or player.lives == 0: + SpazFactory.get().single_player_death_sound.play() + + # If we hit zero lives, we're dead (and our team might be too). + if player.lives == 0: + # If the whole team is now dead, mark their survival time. + if self._get_total_team_lives(player.team) == 0: + assert self._start_time is not None + player.team.survival_seconds = int(bs.time() - + self._start_time) + else: + # Otherwise, in regular mode, respawn. + if not self._solo_mode: + self.respawn_player(player) + + # In solo, put ourself at the back of the spawn order. + if self._solo_mode: + player.team.spawn_order.remove(player) + player.team.spawn_order.append(player) + + def _update(self) -> None: + if self._solo_mode: + # For both teams, find the first player on the spawn order + # list with lives remaining and spawn them if they're not alive. + for team in self.teams: + # Prune dead players from the spawn order. + team.spawn_order = [p for p in team.spawn_order if p] + for player in team.spawn_order: + assert isinstance(player, Player) + if player.lives > 0: + if not player.is_alive(): + self.spawn_player(player) + self._update_icons() + break + + # If we're down to 1 or fewer living teams, start a timer to end + # the game (allows the dust to settle and draws to occur if deaths + # are close enough). + if len(self._get_living_teams()) < 2: + self._round_end_timer = bs.Timer(0.5, self.end_game) + + def _get_living_teams(self) -> List[Team]: + return [ + team for team in self.teams + if len(team.players) > 0 and any(player.lives > 0 + for player in team.players) + ] + + def end_game(self) -> None: + if self.has_ended(): + return + results = bs.GameResults() + self._vs_text = None # Kill our 'vs' if its there. + for team in self.teams: + results.set_team_score(team, team.survival_seconds) + self.end(results=results) diff --git a/plugins/minigames/bot_shower.py b/plugins/minigames/bot_shower.py new file mode 100644 index 0000000..ebbd14b --- /dev/null +++ b/plugins/minigames/bot_shower.py @@ -0,0 +1,195 @@ +# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport) + +# ba_meta require api 8 + +from __future__ import annotations +from typing import TYPE_CHECKING + +import babase +import bauiv1 as bui +import bascenev1 as bs +import random +from bascenev1lib.actor.onscreentimer import OnScreenTimer +from bascenev1lib.actor.spazbot import ( + SpazBot, SpazBotSet, + BomberBot, BrawlerBot, BouncyBot, + ChargerBot, StickyBot, TriggerBot, + ExplodeyBot) + +if TYPE_CHECKING: + from typing import Any, List, Type, Optional + + +class Player(bs.Player['Team']): + """Our player type for this game.""" + + def __init__(self) -> None: + super().__init__() + self.death_time: Optional[float] = None + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + +# ba_meta export bascenev1.GameActivity +class BotShowerGame(bs.TeamGameActivity[Player, Team]): + """A babase.MeteorShowerGame but replaced with bots.""" + + name = 'Bot Shower' + description = 'Survive from the bots.' + available_settings = [ + bs.BoolSetting('Spaz', default=True), + bs.BoolSetting('Zoe', default=True), + bs.BoolSetting('Kronk', default=True), + bs.BoolSetting('Snake Shadow', default=True), + bs.BoolSetting('Mel', default=True), + bs.BoolSetting('Jack Morgan', default=True), + bs.BoolSetting('Easter Bunny', default=True), + bs.BoolSetting('Epic Mode', default=False), + ] + + announce_player_deaths = True + + @classmethod + def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]: + return ['Football Stadium', 'Hockey Stadium'] + + def __init__(self, settings: dict) -> None: + super().__init__(settings) + self._epic_mode = settings['Epic Mode'] + self._last_player_death_time: Optional[float] = None + self._timer: Optional[OnScreenTimer] = None + self._bots: Optional[SpazBotSet] = None + self._bot_type: List[SpazBot] = [] + + if bool(settings['Spaz']) == True: + self._bot_type.append(BomberBot) + else: + if BomberBot in self._bot_type: + self._bot_type.remove(BomberBot) + if bool(settings['Zoe']) == True: + self._bot_type.append(TriggerBot) + else: + if TriggerBot in self._bot_type: + self._bot_type.remove(TriggerBot) + if bool(settings['Kronk']) == True: + self._bot_type.append(BrawlerBot) + else: + if BrawlerBot in self._bot_type: + self._bot_type.remove(BrawlerBot) + if bool(settings['Snake Shadow']) == True: + self._bot_type.append(ChargerBot) + else: + if ChargerBot in self._bot_type: + self._bot_type.remove(ChargerBot) + if bool(settings['Jack Morgan']) == True: + self._bot_type.append(ExplodeyBot) + else: + if ExplodeyBot in self._bot_type: + self._bot_type.remove(ExplodeyBot) + if bool(settings['Easter Bunny']) == True: + self._bot_type.append(BouncyBot) + else: + if BouncyBot in self._bot_type: + self._bot_type.remove(BouncyBot) + + self.slow_motion = self._epic_mode + self.default_music = (bs.MusicType.EPIC + if self._epic_mode else bs.MusicType.SURVIVAL) + + def on_begin(self) -> None: + super().on_begin() + self._bots = SpazBotSet() + self._timer = OnScreenTimer() + self._timer.start() + + if self._epic_mode: + bs.timer(1.0, self._start_spawning_bots) + else: + bs.timer(5.0, self._start_spawning_bots) + + bs.timer(5.0, self._check_end_game) + + def spawn_player(self, player: Player) -> None: + spaz = self.spawn_player_spaz(player) + spaz.connect_controls_to_player( + enable_punch=False, + enable_bomb=False, + enable_pickup=False) + return spaz + + def on_player_join(self, player: Player) -> None: + if self.has_begun(): + bui.screenmessage( + babase.Lstr(resource='playerDelayedJoinText', + subs=[('${PLAYER}', player.getname(full=True))]), + color=(1, 1, 0), + ) + + assert self._timer is not None + player.death_time = self._timer.getstarttime() + return + self.spawn_player(player) + + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, bs.PlayerDiedMessage): + curtime = bs.time() + msg.getplayer(Player).death_time = curtime + + bs.timer(1.0, self._check_end_game) + else: + super().handlemessage(msg) + + def _start_spawning_bots(self) -> None: + bs.timer(1.2, self._spawn_bot, repeat=True) + bs.timer(2.2, self._spawn_bot, repeat=True) + + def _spawn_bot(self) -> None: + assert self._bots is not None + self._bots.spawn_bot(random.choice(self._bot_type), pos=(random.uniform(-11, 11), (9.8 if self.map.getname() == 'Football Stadium' else 5.0), random.uniform(-5, 5))) + + def _check_end_game(self) -> None: + living_team_count = 0 + for team in self.teams: + for player in team.players: + if player.is_alive(): + living_team_count += 1 + break + + if living_team_count <= 1: + self.end_game() + + def end_game(self) -> None: + cur_time = bs.time() + assert self._timer is not None + start_time = self._timer.getstarttime() + + for team in self.teams: + for player in team.players: + survived = False + + if player.death_time is None: + survived = True + player.death_time = cur_time + 1 + + score = int(player.death_time - self._timer.getstarttime()) + if survived: + score += 50 + self.stats.player_scored(player, score, screenmessage=False) + + self._timer.stop(endtime=self._last_player_death_time) + + results = bs.GameResults() + + for team in self.teams: + + longest_life = 0.0 + for player in team.players: + assert player.death_time is not None + longest_life = max(longest_life, + player.death_time - start_time) + + results.set_team_score(team, int(1000.0 * longest_life)) + + self.end(results=results) \ No newline at end of file diff --git a/plugins/minigames/down_into_the_abyss.py b/plugins/minigames/down_into_the_abyss.py new file mode 100644 index 0000000..2ecac4e --- /dev/null +++ b/plugins/minigames/down_into_the_abyss.py @@ -0,0 +1,789 @@ +# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport) +# ba_meta require api 8 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import babase +import bauiv1 as bui +import bascenev1 as bs +import _babase +import random +from bascenev1._map import register_map +from bascenev1lib.actor.spaz import PickupMessage +from bascenev1lib.actor.playerspaz import PlayerSpaz +from bascenev1lib.actor.spazfactory import SpazFactory +from bascenev1lib.gameutils import SharedObjects +from bascenev1lib.actor.spazbot import SpazBotSet, ChargerBotPro, TriggerBotPro +from bascenev1lib.actor.bomb import Blast +from bascenev1lib.actor.powerupbox import PowerupBoxFactory +from bascenev1lib.actor.onscreentimer import OnScreenTimer + +if TYPE_CHECKING: + from typing import Any, Sequence + + +lang = bs.app.lang.language + +if lang == 'Spanish': + name = 'Abajo en el Abismo' + description = 'Sobrevive tanto como puedas' + help = 'El mapa es 3D, ¡ten cuidado!' + author = 'Autor: Deva' + github = 'GitHub: spdv123' + blog = 'Blog: superdeva.info' + peaceTime = 'Tiempo de Paz' + npcDensity = 'Densidad de Enemigos' + hint_use_punch = '¡Ahora puedes golpear a los enemigos!' +elif lang == 'Chinese': + name = '无尽深渊' + description = '在无穷尽的坠落中存活更长时间' + help = '' + author = '作者: Deva' + github = 'GitHub: spdv123' + blog = '博客: superdeva.info' + peaceTime = '和平时间' + npcDensity = 'NPC密度' + hint_use_punch = u'现在可以使用拳头痛扁你的敌人了' +else: + name = 'Down Into The Abyss' + description = 'Survive as long as you can' + help = 'The map is 3D, be careful!' + author = 'Author: Deva' + github = 'GitHub: spdv123' + blog = 'Blog: superdeva.info' + peaceTime = 'Peace Time' + npcDensity = 'NPC Density' + hint_use_punch = 'You can punch your enemies now!' + + +class AbyssMap(bs.Map): + from bascenev1lib.mapdata import happy_thoughts as defs + # Add the y-dimension space for players + defs.boxes['map_bounds'] = (-0.8748348681, 9.212941713, -9.729538885) \ + + (0.0, 0.0, 0.0) \ + + (36.09666006, 26.19950145, 20.89541168) + name = 'Abyss Unhappy' + + @classmethod + def get_play_types(cls) -> list[str]: + """Return valid play types for this map.""" + return ['abyss'] + + @classmethod + def get_preview_texture_name(cls) -> str: + return 'alwaysLandPreview' + + @classmethod + def on_preload(cls) -> Any: + data: dict[str, Any] = { + 'mesh': bs.getmesh('alwaysLandLevel'), + 'bottom_mesh': bs.getmesh('alwaysLandLevelBottom'), + 'bgmesh': bs.getmesh('alwaysLandBG'), + 'collision_mesh': bs.getcollisionmesh('alwaysLandLevelCollide'), + 'tex': bs.gettexture('alwaysLandLevelColor'), + 'bgtex': bs.gettexture('alwaysLandBGColor'), + 'vr_fill_mound_mesh': bs.getmesh('alwaysLandVRFillMound'), + 'vr_fill_mound_tex': bs.gettexture('vrFillMound') + } + return data + + @classmethod + def get_music_type(cls) -> bs.MusicType: + return bs.MusicType.FLYING + + def __init__(self) -> None: + super().__init__(vr_overlay_offset=(0, -3.7, 2.5)) + self.background = bs.newnode( + 'terrain', + attrs={ + 'mesh': self.preloaddata['bgmesh'], + 'lighting': False, + 'background': True, + 'color_texture': self.preloaddata['bgtex'] + }) + bs.newnode('terrain', + attrs={ + 'mesh': self.preloaddata['vr_fill_mound_mesh'], + 'lighting': False, + 'vr_only': True, + 'color': (0.2, 0.25, 0.2), + 'background': True, + 'color_texture': self.preloaddata['vr_fill_mound_tex'] + }) + gnode = bs.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 + +register_map(AbyssMap) + + +class SpazTouchFoothold: + pass + +class BombToDieMessage: + pass + + +class Foothold(bs.Actor): + + def __init__(self, + position: Sequence[float] = (0.0, 1.0, 0.0), + power: str = 'random', + size: float = 6.0, + breakable: bool = True, + moving: bool = False): + super().__init__() + shared = SharedObjects.get() + powerup = PowerupBoxFactory.get() + + fmesh = bs.getmesh('landMine') + fmeshs = bs.getmesh('powerupSimple') + self.died = False + self.breakable = breakable + self.moving = moving # move right and left + self.lrSig = 1 # left or right signal + self.lrSpeedPlus = random.uniform(1 / 2.0, 1 / 0.7) + self._npcBots = SpazBotSet() + + self.foothold_material = bs.Material() + self.impact_sound = bui.getsound('impactMedium') + + self.foothold_material.add_actions( + conditions=(('they_dont_have_material', shared.player_material), + 'and', + ('they_have_material', shared.object_material), + 'or', + ('they_have_material', shared.footing_material)), + actions=(('modify_node_collision', 'collide', True), + )) + + self.foothold_material.add_actions( + conditions=('they_have_material', shared.player_material), + actions=(('modify_part_collision', 'physical', True), + ('modify_part_collision', 'stiffness', 0.05), + ('message', 'our_node', 'at_connect', SpazTouchFoothold()), + )) + + self.foothold_material.add_actions( + conditions=('they_have_material', self.foothold_material), + actions=('modify_node_collision', 'collide', False), + ) + + tex = { + 'punch': powerup.tex_punch, + 'sticky_bombs': powerup.tex_sticky_bombs, + 'ice_bombs': powerup.tex_ice_bombs, + 'impact_bombs': powerup.tex_impact_bombs, + 'health': powerup.tex_health, + 'curse': powerup.tex_curse, + 'shield': powerup.tex_shield, + 'land_mines': powerup.tex_land_mines, + 'tnt': bs.gettexture('tnt'), + }.get(power, bs.gettexture('tnt')) + + powerupdist = { + powerup.tex_bomb: 3, + powerup.tex_ice_bombs: 2, + powerup.tex_punch: 3, + powerup.tex_impact_bombs: 3, + powerup.tex_land_mines: 3, + powerup.tex_sticky_bombs: 4, + powerup.tex_shield: 4, + powerup.tex_health: 3, + powerup.tex_curse: 1, + bs.gettexture('tnt'): 2 + } + + self.randtex = [] + + for keyTex in powerupdist: + for i in range(powerupdist[keyTex]): + self.randtex.append(keyTex) + + if power == 'random': + random.seed() + tex = random.choice(self.randtex) + + self.tex = tex + self.powerup_type = { + powerup.tex_punch: 'punch', + powerup.tex_bomb: 'triple_bombs', + powerup.tex_ice_bombs: 'ice_bombs', + powerup.tex_impact_bombs: 'impact_bombs', + powerup.tex_land_mines: 'land_mines', + powerup.tex_sticky_bombs: 'sticky_bombs', + powerup.tex_shield: 'shield', + powerup.tex_health: 'health', + powerup.tex_curse: 'curse', + bs.gettexture('tnt'): 'tnt' + }.get(self.tex, '') + + self._spawn_pos = (position[0], position[1], position[2]) + + self.node = bs.newnode( + 'prop', + delegate=self, + attrs={ + 'body': 'landMine', + 'position': self._spawn_pos, + 'mesh': fmesh, + 'light_mesh': fmeshs, + 'shadow_size': 0.5, + 'velocity': (0, 0, 0), + 'density': 90000000000, + 'sticky': False, + 'body_scale': size, + 'mesh_scale': size, + 'color_texture': tex, + 'reflection': 'powerup', + 'is_area_of_interest': True, + 'gravity_scale': 0.0, + 'reflection_scale': [0], + 'materials': [self.foothold_material, + shared.object_material, + shared.footing_material] + }) + self.touchedSpazs = set() + self.keep_vel() + + def keep_vel(self) -> None: + if self.node and not self.died: + speed = bs.getactivity().cur_speed + if self.moving: + if abs(self.node.position[0]) > 10: + self.lrSig *= -1 + self.node.velocity = ( + self.lrSig * speed * self.lrSpeedPlus,speed, 0) + bs.timer(0.1, bs.WeakCall(self.keep_vel)) + else: + self.node.velocity = (0, speed, 0) + # self.node.extraacceleration = (0, self.speed, 0) + bs.timer(0.1, bs.WeakCall(self.keep_vel)) + + def tnt_explode(self) -> None: + pos = self.node.position + Blast(position=pos, + blast_radius=6.0, + blast_type='tnt', + source_player=None).autoretain() + + def spawn_npc(self) -> None: + if not self.breakable: + return + if self._npcBots.have_living_bots(): + return + if random.randint(0, 3) >= bs.getactivity().npc_density: + return + pos = self.node.position + pos = (pos[0], pos[1] + 1, pos[2]) + self._npcBots.spawn_bot( + bot_type=random.choice([ChargerBotPro, TriggerBotPro]), + pos=pos, + spawn_time=10) + + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, bs.DieMessage): + if self.node: + self.node.delete() + self.died = True + elif isinstance(msg, bs.OutOfBoundsMessage): + self.handlemessage(bs.DieMessage()) + elif isinstance(msg, BombToDieMessage): + if self.powerup_type == 'tnt': + self.tnt_explode() + self.handlemessage(bs.DieMessage()) + elif isinstance(msg, bs.HitMessage): + ispunched = (msg.srcnode and msg.srcnode.getnodetype() == 'spaz') + if not ispunched: + if self.breakable: + self.handlemessage(BombToDieMessage()) + elif isinstance(msg, SpazTouchFoothold): + node = bs.getcollision().opposingnode + if node is not None and node: + try: + spaz = node.getdelegate(object) + if not isinstance(spaz, AbyssPlayerSpaz): + return + if spaz in self.touchedSpazs: + return + self.touchedSpazs.add(spaz) + self.spawn_npc() + spaz.fix_2D_position() + if self.powerup_type not in ['', 'tnt']: + node.handlemessage( + bs.PowerupMessage(self.powerup_type)) + except Exception as e: + print(e) + pass + + +class AbyssPlayerSpaz(PlayerSpaz): + + def __init__(self, + player: bs.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): + super().__init__(player=player, + color=color, + highlight=highlight, + character=character, + powerups_expire=powerups_expire) + self.node.fly = False + self.node.hockey = True + self.hitpoints_max = self.hitpoints = 1500 # more HP to handle drop + bs.timer(bs.getactivity().peace_time, + bs.WeakCall(self.safe_connect_controls_to_player)) + + def safe_connect_controls_to_player(self) -> None: + try: + self.connect_controls_to_player() + except: + pass + + def on_move_up_down(self, value: float) -> None: + """ + Called to set the up/down joystick amount on this spaz; + used for player or AI connections. + value will be between -32768 to 32767 + WARNING: deprecated; use on_move instead. + """ + if not self.node: + return + if self.node.run > 0.1: + self.node.move_up_down = value + else: + self.node.move_up_down = value / 3. + + def on_move_left_right(self, value: float) -> None: + """ + Called to set the left/right joystick amount on this spaz; + used for player or AI connections. + value will be between -32768 to 32767 + WARNING: deprecated; use on_move instead. + """ + if not self.node: + return + if self.node.run > 0.1: + self.node.move_left_right = value + else: + self.node.move_left_right = value / 1.5 + + def fix_2D_position(self) -> None: + self.node.fly = True + bs.timer(0.02, bs.WeakCall(self.disable_fly)) + + def disable_fly(self) -> None: + if self.node: + self.node.fly = False + + def curse(self) -> None: + """ + Give this poor spaz a curse; + he will explode in 5 seconds. + """ + if not self._cursed: + factory = SpazFactory.get() + self._cursed = True + + # Add the curse material. + for attr in ['materials', 'roller_materials']: + materials = getattr(self.node, attr) + if factory.curse_material not in materials: + setattr(self.node, attr, + materials + (factory.curse_material, )) + + # None specifies no time limit + assert self.node + if self.curse_time == -1: + self.node.curse_death_time = -1 + else: + # Note: curse-death-time takes milliseconds. + tval = bs.time() + assert isinstance(tval, (float, int)) + self.node.curse_death_time = bs.time() + 15 + bs.timer(15, bs.WeakCall(self.curse_explode)) + + def handlemessage(self, msg: Any) -> Any: + dontUp = False + + if isinstance(msg, PickupMessage): + dontUp = True + collision = bs.getcollision() + opposingnode = collision.opposingnode + opposingbody = collision.opposingbody + + if opposingnode is None or not opposingnode: + return True + opposingdelegate = opposingnode.getdelegate(object) + # Don't pick up the foothold + if isinstance(opposingdelegate, Foothold): + return True + + # dont allow picking up of invincible dudes + try: + if opposingnode.invincible: + return True + except Exception: + pass + + # if we're grabbing the pelvis of a non-shattered spaz, + # we wanna grab the torso instead + if (opposingnode.getnodetype() == 'spaz' + and not opposingnode.shattered and opposingbody == 4): + opposingbody = 1 + + + # Special case - if we're holding a flag, don't replace it + # (hmm - should make this customizable or more low level). + held = self.node.hold_node + if held and held.getnodetype() == 'flag': + return True + + # Note: hold_body needs to be set before hold_node. + self.node.hold_body = opposingbody + self.node.hold_node = opposingnode + + if not dontUp: + PlayerSpaz.handlemessage(self, msg) + + +class Player(bs.Player['Team']): + """Our player type for this game.""" + + def __init__(self) -> None: + super().__init__() + self.death_time: float | None = None + self.notIn: bool = None + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + +# ba_meta export bascenev1.GameActivity +class AbyssGame(bs.TeamGameActivity[Player, Team]): + + name = name + description = description + scoreconfig = bs.ScoreConfig(label='Survived', + scoretype=bs.ScoreType.MILLISECONDS, + version='B') + + # Print messages when players die (since its meaningful in this game). + announce_player_deaths = True + + # We're currently hard-coded for one map. + @classmethod + def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: + return ['Abyss Unhappy'] + + @classmethod + def get_available_settings( + cls, sessiontype: type[bs.Session]) -> list[babase.Setting]: + settings = [ + bs.FloatChoiceSetting( + peaceTime, + choices=[ + ('None', 0.0), + ('Shorter', 2.5), + ('Short', 5.0), + ('Normal', 10.0), + ('Long', 15.0), + ('Longer', 20.0), + ], + default=10.0, + ), + bs.FloatChoiceSetting( + npcDensity, + choices=[ + ('0%', 0), + ('25%', 1), + ('50%', 2), + ('75%', 3), + ('100%', 4), + ], + default=2, + ), + bs.BoolSetting('Epic Mode', default=False), + ] + return settings + + # We support teams, free-for-all, and co-op sessions. + @classmethod + def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: + return (issubclass(sessiontype, bs.DualTeamSession) + or issubclass(sessiontype, bs.FreeForAllSession) + or issubclass(sessiontype, bs.CoopSession)) + + def __init__(self, settings: dict): + super().__init__(settings) + self._epic_mode = settings.get('Epic Mode', False) + self._last_player_death_time: float | None = None + self._timer: OnScreenTimer | None = None + self.fix_y = -5.614479365 + self.start_z = 0 + self.init_position = (0, self.start_z, self.fix_y) + self.team_init_positions = [(-5, self.start_z, self.fix_y), + (5, self.start_z, self.fix_y)] + self.cur_speed = 2.5 + # TODO: The variable below should be set in settings + self.peace_time = float(settings[peaceTime]) + self.npc_density = float(settings[npcDensity]) + + # Some base class overrides: + self.default_music = (bs.MusicType.EPIC + if self._epic_mode else bs.MusicType.SURVIVAL) + if self._epic_mode: + self.slow_motion = True + + self._game_credit = bs.NodeActor( + bs.newnode( + 'text', + attrs={ + 'v_attach': 'bottom', + 'h_align': 'center', + 'vr_depth': 0, + 'color': (0.0, 0.7, 1.0), + 'shadow': 1.0 if True else 0.5, + 'flatness': 1.0 if True else 0.5, + 'position': (0, 0), + 'scale': 0.8, + 'text': ' | '.join([author, github, blog]) + })) + + def get_instance_description(self) -> str | Sequence: + return description + + def get_instance_description_short(self) -> str | Sequence: + return self.get_instance_description() + '\n' + help + + def on_player_join(self, player: Player) -> None: + if self.has_begun(): + player.notIn = True + bs.broadcastmessage(babase.Lstr( + resource='playerDelayedJoinText', + subs=[('${PLAYER}', player.getname(full=True))]), + color=(0, 1, 0)) + self.spawn_player(player) + + def on_begin(self) -> None: + super().on_begin() + self._timer = OnScreenTimer() + self._timer.start() + + self.level_cnt = 1 + + if self.teams_or_ffa() == 'teams': + ip0 = self.team_init_positions[0] + ip1 = self.team_init_positions[1] + Foothold( + (ip0[0], ip0[1] - 2, ip0[2]), + power='shield', breakable=False).autoretain() + Foothold( + (ip1[0], ip1[1] - 2, ip1[2]), + power='shield', breakable=False).autoretain() + else: + ip = self.init_position + Foothold( + (ip[0], ip[1] - 2, ip[2]), + power='shield', breakable=False).autoretain() + + bs.timer(int(5.0 / self.cur_speed), + bs.WeakCall(self.add_foothold), repeat=True) + + # Repeat check game end + bs.timer(1.0, self._check_end_game, repeat=True) + bs.timer(self.peace_time + 0.1, + bs.WeakCall(self.tip_hint, hint_use_punch)) + bs.timer(6.0, bs.WeakCall(self.faster_speed), repeat=True) + + def tip_hint(self, text: str) -> None: + bs.broadcastmessage(text, color=(0.2, 0.2, 1)) + + def faster_speed(self) -> None: + self.cur_speed *= 1.15 + + def add_foothold(self) -> None: + ip = self.init_position + ip_1 = (ip[0] - 7, ip[1], ip[2]) + ip_2 = (ip[0] + 7, ip[1], ip[2]) + ru = random.uniform + self.level_cnt += 1 + if self.level_cnt % 3: + Foothold(( + ip_1[0] + ru(-5, 5), + ip[1] - 2, + ip[2] + ru(-0.0, 0.0))).autoretain() + Foothold(( + ip_2[0] + ru(-5, 5), + ip[1] - 2, + ip[2] + ru(-0.0, 0.0))).autoretain() + else: + Foothold(( + ip[0] + ru(-8, 8), + ip[1] - 2, + ip[2]), moving=True).autoretain() + + def teams_or_ffa(self) -> None: + if isinstance(self.session, bs.DualTeamSession): + return 'teams' + return 'ffa' + + def spawn_player_spaz(self, + player: Player, + position: Sequence[float] = (0, 0, 0), + angle: float | None = None) -> PlayerSpaz: + # pylint: disable=too-many-locals + # pylint: disable=cyclic-import + from babase import _math + from bascenev1._gameutils import animate + + position = self.init_position + if self.teams_or_ffa() == 'teams': + position = self.team_init_positions[player.team.id % 2] + angle = None + + name = player.getname() + color = player.color + highlight = player.highlight + + light_color = _math.normalized_color(color) + display_color = _babase.safecolor(color, target_intensity=0.75) + spaz = AbyssPlayerSpaz(color=color, + highlight=highlight, + character=player.character, + player=player) + + player.actor = spaz + assert spaz.node + + spaz.node.name = name + spaz.node.name_color = display_color + spaz.connect_controls_to_player(enable_punch=False, + enable_bomb=True, + enable_pickup=False) + + # Move to the stand position and add a flash of light. + spaz.handlemessage( + bs.StandMessage( + position, + angle if angle is not None else random.uniform(0, 360))) + self._spawn_sound.play(1, position=spaz.node.position) + light = bs.newnode('light', attrs={'color': light_color}) + spaz.node.connectattr('position', light, 'position') + animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}) + bs.timer(0.5, light.delete) + return spaz + + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, bs.PlayerDiedMessage): + + # Augment standard behavior. + super().handlemessage(msg) + + curtime = bs.time() + + # Record the player's moment of death. + # assert isinstance(msg.spaz.player + msg.getplayer(Player).death_time = curtime + + # In co-op mode, end the game the instant everyone dies + # (more accurate looking). + # In teams/ffa, allow a one-second fudge-factor so we can + # get more draws if players die basically at the same time. + if isinstance(self.session, bs.CoopSession): + # Teams will still show up if we check now.. check in + # the next cycle. + babase.pushcall(self._check_end_game) + + # Also record this for a final setting of the clock. + self._last_player_death_time = curtime + else: + bs.timer(1.0, self._check_end_game) + + else: + # Default handler: + return super().handlemessage(msg) + return None + + def _check_end_game(self) -> None: + living_team_count = 0 + for team in self.teams: + for player in team.players: + if player.is_alive(): + living_team_count += 1 + break + + # In co-op, we go till everyone is dead.. otherwise we go + # until one team remains. + if isinstance(self.session, bs.CoopSession): + if living_team_count <= 0: + self.end_game() + else: + if living_team_count <= 0: + self.end_game() + + def end_game(self) -> None: + cur_time = bs.time() + assert self._timer is not None + start_time = self._timer.getstarttime() + + # Mark death-time as now for any still-living players + # and award players points for how long they lasted. + # (these per-player scores are only meaningful in team-games) + for team in self.teams: + for player in team.players: + survived = False + if player.notIn: + player.death_time = 0 + + # Throw an extra fudge factor in so teams that + # didn't die come out ahead of teams that did. + if player.death_time is None: + survived = True + player.death_time = cur_time + 1 + + # Award a per-player score depending on how many seconds + # they lasted (per-player scores only affect teams mode; + # everywhere else just looks at the per-team score). + score = int(player.death_time - self._timer.getstarttime()) + if survived: + score += 50 # A bit extra for survivors. + self.stats.player_scored(player, score, screenmessage=False) + + # Stop updating our time text, and set the final time to match + # exactly when our last guy died. + self._timer.stop(endtime=self._last_player_death_time) + + # Ok now calc game results: set a score for each team and then tell + # the game to end. + results = bs.GameResults() + + # Remember that 'free-for-all' mode is simply a special form + # of 'teams' mode where each player gets their own team, so we can + # just always deal in teams and have all cases covered. + for team in self.teams: + + # Set the team score to the max time survived by any player on + # that team. + longest_life = 0.0 + for player in team.players: + assert player.death_time is not None + longest_life = max(longest_life, + player.death_time - start_time) + + # Submit the score value in milliseconds. + results.set_team_score(team, int(1000.0 * longest_life)) + + self.end(results=results) diff --git a/plugins/minigames/explodo_run.py b/plugins/minigames/explodo_run.py new file mode 100644 index 0000000..be013c7 --- /dev/null +++ b/plugins/minigames/explodo_run.py @@ -0,0 +1,132 @@ +# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport) + +# ba_meta require api 8 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +import random +from typing import TYPE_CHECKING + +import babase +import bauiv1 as bui +import bascenev1 as bs +from bascenev1lib.actor.spazbot import SpazBotSet, ExplodeyBot, SpazBotDiedMessage +from bascenev1lib.actor.onscreentimer import OnScreenTimer + +if TYPE_CHECKING: + from typing import Any, Type, Dict, List, Optional + +def ba_get_api_version(): + return 8 + +def ba_get_levels(): + return [bs._level.Level( + 'Explodo Run', + gametype=ExplodoRunGame, + settings={}, + preview_texture_name='rampagePreview'),bs._level.Level( + 'Epic Explodo Run', + gametype=ExplodoRunGame, + settings={'Epic Mode':True}, + preview_texture_name='rampagePreview')] + +class Player(bs.Player['Team']): + """Our player type for this game.""" + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + +# ba_meta export bascenev1.GameActivity +class ExplodoRunGame(bs.TeamGameActivity[Player, Team]): + name = "Explodo Run" + description = "Run For Your Life :))" + available_settings = [bs.BoolSetting('Epic Mode', default=False)] + scoreconfig = bs.ScoreConfig(label='Time', + scoretype=bs.ScoreType.MILLISECONDS, + lower_is_better=False) + default_music = bs.MusicType.TO_THE_DEATH + + def __init__(self, settings:dict): + settings['map'] = "Rampage" + self._epic_mode = settings.get('Epic Mode', False) + if self._epic_mode: + self.slow_motion = True + super().__init__(settings) + self._timer: Optional[OnScreenTimer] = None + self._winsound = bs.getsound('score') + self._won = False + self._bots = SpazBotSet() + self.wave = 1 + + def on_begin(self) -> None: + super().on_begin() + + self._timer = OnScreenTimer() + bs.timer(2.5, self._timer.start) + + #Bots Hehe + bs.timer(2.5,self.street) + + def street(self): + for a in range(self.wave): + p1 = random.choice([-5,-2.5,0,2.5,5]) + p3 = random.choice([-4.5,-4.14,-5,-3]) + time = random.choice([1,1.5,2.5,2]) + self._bots.spawn_bot(ExplodeyBot, pos=(p1,5.5,p3),spawn_time = time) + self.wave += 1 + + def botrespawn(self): + if not self._bots.have_living_bots(): + self.street() + def handlemessage(self, msg: Any) -> Any: + + # A player has died. + if isinstance(msg, bs.PlayerDiedMessage): + super().handlemessage(msg) # Augment standard behavior. + self._won = True + self.end_game() + + # A spaz-bot has died. + elif isinstance(msg, SpazBotDiedMessage): + # Unfortunately the bot-set will always tell us there are living + # bots if we ask here (the currently-dying bot isn't officially + # marked dead yet) ..so lets push a call into the event loop to + # check once this guy has finished dying. + babase.pushcall(self.botrespawn) + + # Let the base class handle anything we don't. + else: + return super().handlemessage(msg) + return None + + # When this is called, we should fill out results and end the game + # *regardless* of whether is has been won. (this may be called due + # to a tournament ending or other external reason). + def end_game(self) -> None: + + # Stop our on-screen timer so players can see what they got. + assert self._timer is not None + self._timer.stop() + + results = bs.GameResults() + + # If we won, set our score to the elapsed time in milliseconds. + # (there should just be 1 team here since this is co-op). + # ..if we didn't win, leave scores as default (None) which means + # we lost. + if self._won: + elapsed_time_ms = int((bs.time() - self._timer.starttime) * 1000.0) + bs.cameraflash() + self._winsound.play() + for team in self.teams: + for player in team.players: + if player.actor: + player.actor.handlemessage(bs.CelebrateMessage()) + results.set_team_score(team, elapsed_time_ms) + + # Ends the activity. + self.end(results) + + \ No newline at end of file diff --git a/plugins/minigames/extinction.py b/plugins/minigames/extinction.py new file mode 100644 index 0000000..39266e5 --- /dev/null +++ b/plugins/minigames/extinction.py @@ -0,0 +1,254 @@ +# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport) +"""For 1.7.33""" + +# ba_meta require api 8 + +from __future__ import annotations +from typing import TYPE_CHECKING + +import babase +import bauiv1 as bui +import bascenev1 as bs +import random +from bascenev1lib.actor.bomb import BombFactory, Blast, ImpactMessage +from bascenev1lib.actor.onscreentimer import OnScreenTimer +from bascenev1lib.gameutils import SharedObjects + +if TYPE_CHECKING: + from typing import Any, Sequence, Optional, Type + + +def ba_get_api_version(): + return 8 + +def ba_get_levels(): + return [babase._level.Level( + 'Extinction', + gametype=NewMeteorShowerGame, + settings={'Epic Mode': False}, + preview_texture_name='footballStadiumPreview'), + babase._level.Level( + 'Epic Extinction', + gametype=NewMeteorShowerGame, + settings={'Epic Mode': True}, + preview_texture_name='footballStadiumPreview')] + +class Meteor(bs.Actor): + """A giant meteor instead of bombs.""" + + def __init__(self, + pos: Sequence[float] = (0.0, 1.0, 0.0), + velocity: Sequence[float] = (0.0, 0.0, 0.0)): + super().__init__() + + shared = SharedObjects.get() + factory = BombFactory.get() + + materials = (shared.object_material, + factory.impact_blast_material) + + self.pos = (pos[0], pos[1], pos[2]) + self.velocity = (velocity[0], velocity[1], velocity[2]) + + self.node = bs.newnode( + 'prop', + delegate=self, + attrs={ + 'position': self.pos, + 'velocity': self.velocity, + 'mesh': factory.sticky_bomb_mesh, + 'color_texture': factory.tnt_tex, + 'mesh_scale': 3.0, + 'body_scale': 2.99, + 'body': 'sphere', + 'shadow_size': 0.5, + 'reflection': 'soft', + 'reflection_scale': [0.45], + 'materials': materials + }) + + def explode(self) -> None: + Blast(position=self.node.position, + velocity=self.node.velocity, + blast_type='tnt', + blast_radius=2.0) + + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, bs.DieMessage): + if self.node: + self.node.delete() + elif isinstance(msg, ImpactMessage): + self.explode() + self.handlemessage(bs.DieMessage()) + else: + super().handlemessage(msg) + + +class Player(bs.Player['Team']): + """Our player type for this game.""" + + def __init__(self): + super().__init__() + self.death_time: Optional[float] = None + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + +# ba_meta export bascenev1.GameActivity +class NewMeteorShowerGame(bs.TeamGameActivity[Player, Team]): + """Minigame by Jetz.""" + + name = 'Extinction' + description = 'Survive the Extinction.' + available_settings = [ + bs.BoolSetting('Epic Mode', default=False)] + + announce_player_deaths = True + + @classmethod + def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]: + return ['Football Stadium'] + + @classmethod + def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool: + return (issubclass(sessiontype, bs.FreeForAllSession) + or issubclass(sessiontype, bs.DualTeamSession)) + + def __init__(self, settings: dict): + super().__init__(settings) + + self._epic_mode = bool(settings['Epic Mode']) + self._last_player_death_time: Optiobal[float] = None + self._meteor_time = 2.0 + self._timer: Optional[OnScreenTimer] = None + + self.default_music = (bs.MusicType.EPIC + if self._epic_mode else bs.MusicType.SURVIVAL) + + if self._epic_mode: + self.slow_motion = True + + def on_begin(self) -> None: + super().on_begin() + + delay = 5.0 if len(self.players) > 2 else 2.5 + if self._epic_mode: + delay *= 0.25 + bs.timer(delay, self._decrement_meteor_time, repeat=True) + + delay = 3.0 + if self._epic_mode: + delay *= 0.25 + bs.timer(delay, self._set_meteor_timer) + + self._timer = OnScreenTimer() + self._timer.start() + self._check_end_game() + + def on_player_join(self, player: Player) -> None: + if self.has_begun(): + bs.broadcastmessage( + babase.Lstr(resource='playerDelayedJoinText', + subs=[('${PLAYER}', player.getname(full=True))]), + color=(0, 1, 0), + ) + assert self._timer is not None + player.death_time = self._timer.getstarttime() + return + self.spawn_player(player) + + def spawn_player(self, player: Player) -> None: + spaz = self.spawn_player_spaz(player) + + spaz.connect_controls_to_player(enable_punch=False, + enable_pickup=False, + enable_bomb=False, + enable_jump=False) + spaz.play_big_death_sound = True + + return spaz + + def on_player_leave(self, player: Player) -> None: + super().on_player_leave(player) + + self._check_end_game() + + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, bs.PlayerDiedMessage): + curtime = bs.time() + + msg.getplayer(Player).death_time = curtime + bs.timer(1.0, self._check_end_game) + else: + return super().handlemessage(msg) + + def _spawn_meteors(self) -> None: + pos = (random.randint(-6, 7), 12, + random.uniform(-2, 1)) + velocity = (random.randint(-11, 11), 0, + random.uniform(-5, 5)) + Meteor(pos=pos, velocity=velocity).autoretain() + + def _spawn_meteors_cluster(self) -> None: + delay = 0.0 + for _i in range(random.randrange(1, 3)): + bs.timer(delay, self._spawn_meteors) + delay += 1 + self._set_meteor_timer() + + def _decrement_meteor_time(self) -> None: + self._meteor_time = max(0.01, self._meteor_time * 0.9) + + def _set_meteor_timer(self) -> None: + bs.timer((1.0 + 0.2 * random.random()) * self._meteor_time, + self._spawn_meteors_cluster) + + def _check_end_game(self) -> None: + living_team_count = 0 + for team in self.teams: + for player in team.players: + if player.is_alive(): + living_team_count += 1 + break + + if isinstance(self.session, bs.CoopSession): + if living_team_count <= 0: + self.end_game() + else: + if living_team_count <= 1: + self.end_game() + + def end_game(self) -> None: + cur_time = bs.time() + assert self._timer is not None + start_time = self._timer.getstarttime() + + for team in self.teams: + for player in team.players: + survived = False + + if player.death_time is None: + survived = True + player.death_time = cur_time + 1 + + score = int(player.death_time - self._timer.getstarttime()) + if survived: + score += 50 + self.stats.player_scored(player, score, screenmessage=False) + + self._timer.stop(endtime=self._last_player_death_time) + + results = bs.GameResults() + + for team in self.teams: + + longest_life = 0.0 + for player in team.players: + assert player.death_time is not None + longest_life = max(longest_life, + player.death_time - start_time) + + results.set_team_score(team, int(1000.0 * longest_life)) + + self.end(results=results) \ No newline at end of file diff --git a/plugins/minigames/fat_pigs.py b/plugins/minigames/fat_pigs.py new file mode 100644 index 0000000..aed1a69 --- /dev/null +++ b/plugins/minigames/fat_pigs.py @@ -0,0 +1,340 @@ +# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport) +# ba_meta require api 8 + +# - - - - - - - - - - - - - - - - - - - - - +# - Fat-Pigs! by Zacker Tz || Zacker#5505 - +# - Version 0.01 :v - +# - - - - - - - - - - - - - - - - - - - - - + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import random +import babase +import bauiv1 as bui +import bascenev1 as bs +from bascenev1lib.actor.bomb import Bomb +from bascenev1lib.actor.onscreentimer import OnScreenTimer +from bascenev1lib.actor.playerspaz import PlayerSpaz +from bascenev1lib.actor.scoreboard import Scoreboard + +if TYPE_CHECKING: + from typing import Any, Union, Sequence, Optional + +# - - - - - - - Mini - Settings - - - - - - - - - - - - - - - - # + +zkBombs_limit = 3 # Number of bombs you can use | Default = 3 +zkPunch = False # Enable/Disable punchs | Default = False +zkPickup = False # Enable/Disable pickup | Default = False + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # + +class Player(bs.Player['Team']): + """Our player type for this game.""" + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + def __init__(self) -> None: + self.score = 0 + +# ba_meta export bascenev1.GameActivity +class FatPigs(bs.TeamGameActivity[Player, Team]): + """A game type based on acquiring kills.""" + + name = 'Fat-Pigs!' + description = 'Survive...' + + # Print messages when players die since it matters here. + announce_player_deaths = True + + @classmethod + def get_available_settings( + cls, sessiontype: type[bs.Session]) -> list[babase.Setting]: + settings = [ + bs.IntSetting( + 'Kills to Win Per Player', + min_value=1, + default=5, + increment=1, + ), + bs.IntChoiceSetting( + 'Time Limit', + choices=[ + ('None', 0), + ('1 Minute', 60), + ('2 Minutes', 120), + ('5 Minutes', 300), + ('10 Minutes', 600), + ('20 Minutes', 1200), + ], + default=0, + ), + bs.FloatChoiceSetting( + 'Respawn Times', + choices=[ + ('Shorter', 0.25), + ('Short', 0.5), + ('Normal', 1.0), + ('Long', 2.0), + ('Longer', 4.0), + ], + default=0.25, + ), + bs.BoolSetting('Epic Mode', default=False), + ] + + # In teams mode, a suicide gives a point to the other team, but in + # free-for-all it subtracts from your own score. By default we clamp + # this at zero to benefit new players, but pro players might like to + # be able to go negative. (to avoid a strategy of just + # suiciding until you get a good drop) + if issubclass(sessiontype, bs.FreeForAllSession): + settings.append( + bs.BoolSetting('Allow Negative Scores', default=False)) + + return settings + + @classmethod + def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: + return (issubclass(sessiontype, bs.DualTeamSession) + or issubclass(sessiontype, bs.FreeForAllSession)) + + @classmethod + def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: + return ['Courtyard', 'Rampage', 'Monkey Face', 'Lake Frigid', 'Step Right Up'] + + def __init__(self, settings: dict): + super().__init__(settings) + self._scoreboard = Scoreboard() + self._meteor_time = 2.0 + self._score_to_win: Optional[int] = None + self._dingsound = bs.getsound('dingSmall') + self._epic_mode = bool(settings['Epic Mode']) + # self._text_credit = bool(settings['Credits']) + self._kills_to_win_per_player = int( + settings['Kills to Win Per Player']) + self._time_limit = float(settings['Time Limit']) + self._allow_negative_scores = bool( + settings.get('Allow Negative Scores', False)) + + # Base class overrides. + self.slow_motion = self._epic_mode + self.default_music = (bs.MusicType.EPIC if self._epic_mode else + bs.MusicType.TO_THE_DEATH) + + def get_instance_description(self) -> Union[str, Sequence]: + return 'Crush ${ARG1} of your enemies.', self._score_to_win + + def get_instance_description_short(self) -> Union[str, Sequence]: + return 'kill ${ARG1} enemies', self._score_to_win + + def on_team_join(self, team: Team) -> None: + if self.has_begun(): + self._update_scoreboard() + + def on_begin(self) -> None: + super().on_begin() + self.setup_standard_time_limit(self._time_limit) + # self.setup_standard_powerup_drops() + #Ambiente + gnode = bs.getactivity().globalsnode + gnode.tint = (0.8, 1.2, 0.8) + gnode.ambient_color = (0.7, 1.0, 0.6) + gnode.vignette_outer = (0.4, 0.6, 0.4) #C + # gnode.vignette_inner = (0.9, 0.9, 0.9) + + + + # Base kills needed to win on the size of the largest team. + self._score_to_win = (self._kills_to_win_per_player * + max(1, max(len(t.players) for t in self.teams))) + self._update_scoreboard() + + delay = 5.0 if len(self.players) > 2 else 2.5 + if self._epic_mode: + delay *= 0.25 + bs.timer(delay, self._decrement_meteor_time, repeat=False) + + # Kick off the first wave in a few seconds. + delay = 3.0 + if self._epic_mode: + delay *= 0.25 + bs.timer(delay, self._set_meteor_timer) + + # self._timer = OnScreenTimer() + # self._timer.start() + + # Check for immediate end (if we've only got 1 player, etc). + bs.timer(5.0, self._check_end_game) + + t = bs.newnode('text', + attrs={ 'text':"Minigame by Zacker Tz", + 'scale':0.7, + 'position':(0.001,625), + 'shadow':0.5, + 'opacity':0.7, + 'flatness':1.2, + 'color':(0.6, 1, 0.6), + 'h_align':'center', + 'v_attach':'bottom'}) + + + def spawn_player(self, player: Player) -> bs.Actor: + spaz = self.spawn_player_spaz(player) + + # 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=zkPunch, + enable_bomb=True, + enable_pickup=zkPickup) + + spaz.bomb_count = zkBombs_limit + spaz._max_bomb_count = zkBombs_limit + spaz.bomb_type_default = 'sticky' + spaz.bomb_type = 'sticky' + + #cerdo gordo + spaz.node.color_mask_texture = bs.gettexture('melColorMask') + spaz.node.color_texture = bs.gettexture('melColor') + spaz.node.head_mesh = bs.getmesh('melHead') + spaz.node.hand_mesh = bs.getmesh('melHand') + spaz.node.torso_mesh = bs.getmesh('melTorso') + spaz.node.pelvis_mesh = bs.getmesh('kronkPelvis') + spaz.node.upper_arm_mesh = bs.getmesh('melUpperArm') + spaz.node.forearm_mesh = bs.getmesh('melForeArm') + spaz.node.upper_leg_mesh = bs.getmesh('melUpperLeg') + spaz.node.lower_leg_mesh = bs.getmesh('melLowerLeg') + spaz.node.toes_mesh = bs.getmesh('melToes') + spaz.node.style = 'mel' + # Sounds cerdo gordo + mel_sounds = [bs.getsound('mel01'), bs.getsound('mel02'),bs.getsound('mel03'),bs.getsound('mel04'),bs.getsound('mel05'), + bs.getsound('mel06'),bs.getsound('mel07'),bs.getsound('mel08'),bs.getsound('mel09'),bs.getsound('mel10')] + spaz.node.jump_sounds = mel_sounds + spaz.node.attack_sounds = mel_sounds + spaz.node.impact_sounds = mel_sounds + spaz.node.pickup_sounds = mel_sounds + spaz.node.death_sounds = [bs.getsound('melDeath01')] + spaz.node.fall_sounds = [bs.getsound('melFall01')] + + def _set_meteor_timer(self) -> None: + bs.timer((1.0 + 0.2 * random.random()) * self._meteor_time, + self._drop_bomb_cluster) + + def _drop_bomb_cluster(self) -> None: + + # Random note: code like this is a handy way to plot out extents + # and debug things. + loc_test = False + if loc_test: + bs.newnode('locator', attrs={'position': (8, 6, -5.5)}) + bs.newnode('locator', attrs={'position': (8, 6, -2.3)}) + bs.newnode('locator', attrs={'position': (-7.3, 6, -5.5)}) + bs.newnode('locator', attrs={'position': (-7.3, 6, -2.3)}) + + # Drop several bombs in series. + delay = 0.0 + for _i in range(random.randrange(1, 3)): + # Drop them somewhere within our bounds with velocity pointing + # toward the opposite side. + pos = (-7.3 + 15.3 * random.random(), 11, + -5.5 + 2.1 * random.random()) + dropdir = (-1.0 if pos[0] > 0 else 1.0) + vel = ((-5.0 + random.random() * 30.0) * dropdir, -4.0, 0) + bs.timer(delay, babase.Call(self._drop_bomb, pos, vel)) + delay += 0.1 + self._set_meteor_timer() + + def _drop_bomb(self, position: Sequence[float], + velocity: Sequence[float]) -> None: + Bomb(position=position, velocity=velocity,bomb_type='sticky').autoretain() + + def _decrement_meteor_time(self) -> None: + self._meteor_time = max(0.01, self._meteor_time * 0.9) + + + def handlemessage(self, msg: Any) -> Any: + + if isinstance(msg, bs.PlayerDiedMessage): + + # Augment standard behavior. + super().handlemessage(msg) + + player = msg.getplayer(Player) + self.respawn_player(player) + + killer = msg.getkillerplayer(Player) + if killer is None: + return None + + # Handle team-kills. + if killer.team is player.team: + + # In free-for-all, killing yourself loses you a point. + if isinstance(self.session, bs.FreeForAllSession): + new_score = player.team.score - 1 + if not self._allow_negative_scores: + new_score = max(0, new_score) + player.team.score = new_score + + # In teams-mode it gives a point to the other team. + else: + self._dingsound.play() + for team in self.teams: + if team is not killer.team: + team.score += 1 + + # Killing someone on another team nets a kill. + else: + killer.team.score += 1 + self._dingsound.play() + + # In FFA show scores since its hard to find on the scoreboard. + if isinstance(killer.actor, PlayerSpaz) and killer.actor: + killer.actor.set_score_text(str(killer.team.score) + '/' + + str(self._score_to_win), + color=killer.team.color, + flash=True) + + self._update_scoreboard() + + # If someone has won, set a timer to end shortly. + # (allows the dust to clear and draws to occur if deaths are + # close enough) + assert self._score_to_win is not None + if any(team.score >= self._score_to_win for team in self.teams): + bs.timer(0.5, self.end_game) + + else: + return super().handlemessage(msg) + return None + + def _check_end_game(self) -> None: + living_team_count = 0 + for team in self.teams: + for player in team.players: + if player.is_alive(): + living_team_count += 1 + break + + # In co-op, we go till everyone is dead.. otherwise we go + # until one team remains. + if isinstance(self.session, bs.CoopSession): + if living_team_count <= 0: + self.end_game() + else: + if living_team_count <= 1: + self.end_game() + + def _update_scoreboard(self) -> None: + for team in self.teams: + self._scoreboard.set_team_value(team, team.score, + self._score_to_win) + + def end_game(self) -> None: + results = bs.GameResults() + for team in self.teams: + results.set_team_score(team, team.score) + self.end(results=results) diff --git a/plugins/utilities.json b/plugins/utilities.json index db809c6..0a5c094 100644 --- a/plugins/utilities.json +++ b/plugins/utilities.json @@ -1149,7 +1149,7 @@ } }, "ba_colours": { - "description": "Try to survive from bots!", + "description": "Colourful bots and more", "external_url": "", "authors": [ { @@ -1161,6 +1161,20 @@ "versions": { "1.0.0": null } + }, + "xyz_tool": { + "description": "Punch to save the co-ordinates", + "external_url": "", + "authors": [ + { + "name": "", + "email": "", + "discord": "" + } + ], + "versions": { + "1.0.0": null + } } } } \ No newline at end of file diff --git a/plugins/utilities/xyz_tool.py b/plugins/utilities/xyz_tool.py new file mode 100644 index 0000000..2ad1efd --- /dev/null +++ b/plugins/utilities/xyz_tool.py @@ -0,0 +1,85 @@ +# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport) +# Released under the MIT License. See LICENSE for details. +# ba_meta require api 8 + +from __future__ import annotations +from typing import TYPE_CHECKING +from bascenev1lib.actor.playerspaz import PlayerSpaz +from bascenev1lib.actor.spazfactory import SpazFactory +import babase +import bauiv1 as bui +import bascenev1 as bs +import math +import os +import _babase +import shutil +if TYPE_CHECKING: + pass + +DECIMAL_LIMIT = 7 + + +PlayerSpaz.supershit = PlayerSpaz.__init__ +def ShitInit(self, + player: bs.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) -> None: + self.supershit(player, color, highlight, character, powerups_expire) + self.offt = bs.newnode('math', owner=self.node, attrs={'input1': (1.2, 1.8, -0.7),'operation': 'add'}) + self.node.connectattr('torso_position', self.offt, 'input2') + self.txt = bs.newnode('text', owner=self.node, attrs={'text': '3.0','in_world': True,'text':'0','shadow': 1.0,'color': (1,0,0),'flatness': 0.5,'scale': 0.01,'h_align': 'right'}) + p = self.node.position + self.xyz = 0 + self.txt.text = "X: " + str(p[0]) + "\nY: " + str(p[1]) + "\nZ: " + str(p[2]) + self.offt.connectattr('output', self.txt, 'position') + def update(): + p = self.node.position + is_moving = abs(self.node.move_up_down) >= 0.01 or abs(self.node.move_left_right) >= 0.01 + if is_moving: + self.xyz = (p[0],p[1],p[2]) + self.txt.text = "X: " + str(round(self.xyz[0],DECIMAL_LIMIT)) + "\nY: " + str(round(self.xyz[1],DECIMAL_LIMIT)) + "\nZ: " + str(round(self.xyz[2],DECIMAL_LIMIT)) + bs.timer(0.1,update,repeat=True) + +def replaceable_punch(self) -> None: + """ + Called to 'press punch' on this spaz; + used for player or AI connections. + """ + if not self.node or self.frozen or self.node.knockout > 0.0: + return + index = 0 + path_aid = _babase.env()['python_directory_user'] + '/Saved XYZ' + path, dirs, files = next(os.walk(path_aid)) + index += len(files) + c27 = str(index + 1) + with open(path_aid + '/coords' + c27 + '.txt', 'w') as gg: + gg.write("X: " + str(round(self.xyz[0],DECIMAL_LIMIT)) + "\nY: " + str(round(self.xyz[1],DECIMAL_LIMIT)) + "\nZ: " + str(round(self.xyz[2],DECIMAL_LIMIT)) + '\n\n' + '(' + str(round(self.xyz[0],DECIMAL_LIMIT)) + ', ' + str(round(self.xyz[1],DECIMAL_LIMIT)) + ', ' + str(round(self.xyz[2],DECIMAL_LIMIT)) + ')') + bui.screenmessage("Coordinates saved in: " + "BombSquad/Saved XYZ/" + "coords" + c27) + if _babase.app.classic.platform == 'android': + _babase.android_media_scan_file(path_aid) + t_ms = bs.time() * 1000 + assert isinstance(t_ms, int) + if t_ms - self.last_punch_time_ms >= self._punch_cooldown: + if self.punch_callback is not None: + self.punch_callback(self) + self._punched_nodes = set() # Reset this. + self.last_punch_time_ms = t_ms + self.node.punch_pressed = True + if not self.node.hold_node: + bs.timer( + 0.1, + bs.WeakCall(self._safe_play_sound, + SpazFactory.get().swish_sound, 0.8)) + self._turbo_filter_add_press('punch') + +# ba_meta export plugin +class ragingspeedhorn(babase.Plugin): + try: + oath = _babase.env()['python_directory_user'] + '/Saved XYZ' + os.makedirs(oath,exist_ok=False) + except: pass + PlayerSpaz.on_punch_press = replaceable_punch + PlayerSpaz.__init__ = ShitInit + PlayerSpaz.xyz = 0 \ No newline at end of file