diff --git a/plugins/minigames.json b/plugins/minigames.json index f101bb1..11e9e23 100644 --- a/plugins/minigames.json +++ b/plugins/minigames.json @@ -1044,25 +1044,6 @@ } } }, - "safe_zone": { - "description": "Stay in the safe zone", - "external_url": "", - "authors": [ - { - "name": "SEBASTIAN2059", - "email": "", - "discord": "sebastian2059" - } - ], - "versions": { - "1.0.0": { - "api_version": 8, - "commit_sha": "718039b", - "released_on": "24-01-2024", - "md5sum": "862fab0c26947c70397542742fb82635" - } - } - }, "snow_ball_fight": { "description": "Throw snoballs and dominate", "external_url": "https://youtu.be/uXyb_meBjGI?si=D_N_OXZT5BFh8R5C", diff --git a/plugins/minigames/safe_zone.py b/plugins/minigames/safe_zone.py deleted file mode 100644 index 5680877..0000000 --- a/plugins/minigames/safe_zone.py +++ /dev/null @@ -1,737 +0,0 @@ -# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport) -# Released under the MIT License. See LICENSE for details. -# -"""Elimination mini-game.""" - -# Maded by Froshlee14 -# Update by SEBASTIAN2059 - -# 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 bascenev1lib.actor.spazfactory import SpazFactory -from bascenev1lib.actor.scoreboard import Scoreboard -from bascenev1lib.actor import spazbot as stdbot -from bascenev1lib.gameutils import SharedObjects as so - -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] = [] - - -lang = bs.app.lang.language -if lang == 'Spanish': - description = 'Mantente en la zona segura.' - join_description = 'Corre hacia la zona segura.' - kill_timer = 'Kill timer: ' -else: - description = 'Stay in the safe zone.' - join_description = 'Run into the safe zone' - kill_timer = 'Kill timer: ' - -# ba_meta export bascenev1.GameActivity - - -class SafeZoneGame(bs.TeamGameActivity[Player, Team]): - """Game type where last player(s) left alive win.""" - - name = 'Safe Zone' - description = description - 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( - 'Lives Per Player', - default=2, - 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=[ - ('Short', 0.25), - ('Normal', 0.5), - ], - default=0.5, - ), - bs.BoolSetting('Epic Mode', default=False), - ] - if issubclass(sessiontype, bs.DualTeamSession): - settings.append(bs.BoolSetting('Solo Mode', default=False)) - settings.append( - bs.BoolSetting('Balance Total Lives', 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 ['Football Stadium', 'Hockey Stadium'] - - 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 - self._epic_mode = bool(settings['Epic Mode']) - self._lives_per_player = int(settings['Lives Per Player']) - self._time_limit = float(settings['Time Limit']) - self._balance_total_lives = bool( - settings.get('Balance Total Lives', 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) - - self._tick_sound = bs.getsound('tick') - - def get_instance_description(self) -> Union[str, Sequence]: - return join_description - - def get_instance_description_short(self) -> Union[str, Sequence]: - return 'last team standing wins' 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 - bs.broadcastmessage( - 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() - - def on_begin(self) -> None: - super().on_begin() - self._start_time = bs.time() - self.setup_standard_time_limit(self._time_limit) - # self.setup_standard_powerup_drops() - - bs.timer(5, self.spawn_zone) - self._bots = stdbot.SpazBotSet() - bs.timer(3, babase.Call(self.add_bot, 'left')) - bs.timer(3, babase.Call(self.add_bot, 'right')) - if len(self.initialplayerinfos) > 4: - bs.timer(5, babase.Call(self.add_bot, 'right')) - bs.timer(5, babase.Call(self.add_bot, 'left')) - - 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 spawn_zone(self): - self.zone_pos = (random.randrange(-10, 10), 0.05, random.randrange(-5, 5)) - self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': self.zone_pos, 'color': ( - 1, 1, 0), 'opacity': 0.8, 'draw_beauty': True, 'additive': False, 'drawShadow': False}) - self.zone_limit = bs.newnode('locator', attrs={'shape': 'circleOutline', 'position': self.zone_pos, 'color': ( - 1, 0.2, 0.2), 'opacity': 0.8, 'draw_beauty': True, 'additive': False, 'drawShadow': False}) - bs.animate_array(self.zone, 'size', 1, {0: [0], 0.3: [ - self.get_players_count()*0.85], 0.35: [self.get_players_count()*0.8]}) - bs.animate_array(self.zone_limit, 'size', 1, {0: [0], 0.3: [ - self.get_players_count()*1.2], 0.35: [self.get_players_count()*0.95]}) - self.last_players_count = self.get_players_count() - bs.getsound('laserReverse').play() - self.start_timer() - self.move_zone() - - def delete_zone(self): - self.zone.delete() - self.zone = None - self.zone_limit.delete() - self.zone_limit = None - bs.getsound('shieldDown').play() - bs.timer(1, self.spawn_zone) - - def move_zone(self): - if self.zone_pos[0] > 0: - x = random.randrange(0, 10) - else: - x = random.randrange(-10, 0) - - if self.zone_pos[2] > 0: - y = random.randrange(0, 5) - else: - y = random.randrange(-5, 0) - - new_pos = (x, 0.05, y) - bs.animate_array(self.zone, 'position', 3, {0: self.zone.position, 8: new_pos}) - bs.animate_array(self.zone_limit, 'position', 3, {0: self.zone_limit.position, 8: new_pos}) - - def start_timer(self): - count = self.get_players_count() - self._time_remaining = 10 if count > 9 else count-1 if count > 6 else count if count > 2 else count*2 - self._timer_x = bs.Timer(1.0, bs.WeakCall(self.tick), repeat=True) - # gnode = bs.getactivity().globalsnode - # tint = gnode.tint - # bs.animate_array(gnode,'tint',3,{0:tint,self._time_remaining*1.5:(1.0,0.5,0.5),self._time_remaining*1.55:tint}) - - def stop_timer(self): - self._time = None - self._timer_x = None - - def tick(self): - self.check_players() - self._time = bs.NodeActor(bs.newnode('text', - attrs={'v_attach': 'top', 'h_attach': 'center', - 'text': kill_timer+str(self._time_remaining)+'s', - 'opacity': 0.8, 'maxwidth': 100, 'h_align': 'center', - 'v_align': 'center', 'shadow': 1.0, 'flatness': 1.0, - 'color': (1, 1, 1), 'scale': 1.5, 'position': (0, -50)} - ) - ) - self._time_remaining -= 1 - self._tick_sound.play() - - def check_players(self): - if self._time_remaining <= 0: - self.stop_timer() - bs.animate_array(self.zone, 'size', 1, { - 0: [self.last_players_count*0.8], 1.4: [self.last_players_count*0.8], 1.5: [0]}) - bs.animate_array(self.zone_limit, 'size', 1, { - 0: [self.last_players_count*0.95], 1.45: [self.last_players_count*0.95], 1.5: [0]}) - bs.timer(1.5, self.delete_zone) - for player in self.players: - if not player.actor is None: - if player.actor.is_alive(): - p1 = player.actor.node.position - p2 = self.zone.position - diff = (babase.Vec3(p1[0]-p2[0], 0.0, p1[2]-p2[2])) - dist = (diff.length()) - if dist > (self.get_players_count()*0.7): - player.actor.handlemessage(bs.DieMessage()) - - def get_players_count(self): - count = 0 - for player in self.players: - if not player.actor is None: - if player.actor.is_alive(): - count += 1 - return count - - 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)) - - # spaz but *without* the ability to attack or pick stuff up. - actor.connect_controls_to_player(enable_punch=False, - enable_bomb=False, - enable_pickup=False) - - # If we have any icons, update their state. - for icon in player.icons: - icon.handle_player_spawned() - return actor - - 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: - 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) - elif isinstance(msg, stdbot.SpazBotDiedMessage): - self._on_spaz_bot_died(msg) - - def _on_spaz_bot_died(self, die_msg): - bs.timer(1, babase.Call(self.add_bot, die_msg.spazbot.node.position)) - - def _on_bot_spawn(self, spaz): - spaz.update_callback = self.move_bot - spaz_type = type(spaz) - spaz._charge_speed = self._get_bot_speed(spaz_type) - - def add_bot(self, pos=None): - if pos == 'left': - position = (-11, 0, random.randrange(-5, 5)) - elif pos == 'right': - position = (11, 0, random.randrange(-5, 5)) - else: - position = pos - self._bots.spawn_bot(self.get_random_bot(), pos=position, spawn_time=1, - on_spawn_call=babase.Call(self._on_bot_spawn)) - - def move_bot(self, bot): - p = bot.node.position - speed = -bot._charge_speed if (p[0] >= -11 and p[0] < 0) else bot._charge_speed - - if (p[0] >= -11) and (p[0] <= 11): - bot.node.move_left_right = speed - bot.node.move_up_down = 0.0 - bot.node.run = 0.0 - return True - return False - - def get_random_bot(self): - bots = [stdbot.BomberBotStatic, stdbot.TriggerBotStatic] - return (random.choice(bots)) - - def _get_bot_speed(self, bot_type): - if bot_type == stdbot.BomberBotStatic: - return 0.48 - elif bot_type == stdbot.TriggerBotStatic: - return 0.73 - else: - raise Exception('Invalid bot type to _getBotSpeed(): '+str(bot_type)) - - 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)