From c73d92665c3406769b19ac1f759ba978f2a2dfba Mon Sep 17 00:00:00 2001 From: Vishal Date: Mon, 18 Nov 2024 20:23:04 +0530 Subject: [PATCH] few api 8 minigames --- dist/ba_root/mods/games/duel.py | 603 +++++++++++++++++ .../ba_root/mods/games/egg_hunt_in_the_sky.py | 309 +++++++++ dist/ba_root/mods/games/jaggernaut.py | 595 +++++++++++++++++ dist/ba_root/mods/games/kill_or_die.py | 625 ++++++++++++++++++ dist/ba_root/mods/games/last_punch_stand.py | 416 ++++++++++++ dist/ba_root/mods/games/tag.py | 410 ++++++++++++ 6 files changed, 2958 insertions(+) create mode 100644 dist/ba_root/mods/games/duel.py create mode 100644 dist/ba_root/mods/games/egg_hunt_in_the_sky.py create mode 100644 dist/ba_root/mods/games/jaggernaut.py create mode 100644 dist/ba_root/mods/games/kill_or_die.py create mode 100644 dist/ba_root/mods/games/last_punch_stand.py create mode 100644 dist/ba_root/mods/games/tag.py diff --git a/dist/ba_root/mods/games/duel.py b/dist/ba_root/mods/games/duel.py new file mode 100644 index 0000000..8f66947 --- /dev/null +++ b/dist/ba_root/mods/games/duel.py @@ -0,0 +1,603 @@ +# Porting to api 8 made easier by 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 bascenev1lib.actor.spaz import Spaz +from bascenev1lib.actor.playerspaz import PlayerSpaz +from bascenev1lib.actor.scoreboard import Scoreboard +from bascenev1lib.game.elimination import Icon +from bascenev1lib.game import elimination + + +if TYPE_CHECKING: + from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional + + +class SuperSpaz(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', + super_punch: bool = False, + powerups_expire: bool = True): + super().__init__(player=player, + color=color, + highlight=highlight, + character=character, + powerups_expire=powerups_expire) + self._super_punch = super_punch + + def handlemessage(self, msg: Any) -> Any: + from bascenev1lib.actor.spaz import PunchHitMessage + from bascenev1lib.actor.bomb import Blast + if isinstance(msg, PunchHitMessage): + super().handlemessage(msg) + node = bs.getcollision().opposingnode + if self._super_punch: + if node.getnodetype() == 'spaz': + if not node.frozen: + node.frozen = True + node.handlemessage(bs.FreezeMessage()) + bui.getsound('freeze').play() + bui.getsound('superPunch').play() + bui.getsound('punchStrong02').play() + Blast(position=node.position, + velocity=node.velocity, + blast_radius=0.0, + blast_type='normal').autoretain() + else: + return super().handlemessage(msg) + return None + + +class Player(bs.Player['Team']): + """Our player type for this game.""" + def __init__(self) -> None: + self.icons: List[Icon] = [] + self.in_game: bool = False + self.playervs1: bool = False + self.playervs2: bool = False + self.light: bool = False + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + def __init__(self) -> None: + self.score = 0 + + +lang = bs.app.lang.language +if lang == 'Spanish': + enable_powerups = 'Habilitar Potenciadores' + night_mode = 'Modo Noche' + fight_delay = 'Tiempo entre Pelea' + very_fast = 'Muy Rápido' + fast = 'Rápido' + normal = 'Normal' + slow = 'Lento' + very_slow = 'Muy Lento' + none = 'Ninguno' + super_punch = 'Super Golpe' + box_mode = 'Modo Caja' + boxing_gloves = 'Guantes de Boxeo' +else: + enable_powerups = 'Enable Powerups' + night_mode = 'Night Mode' + fight_delay = 'Fight Delay' + very_fast = 'Very Fast' + fast = 'Fast' + normal = 'Normal' + slow = 'Slow' + very_slow = 'Very Slow' + super_punch = 'Super Punch' + box_mode = 'Box Mode' + boxing_gloves = 'Boxing Gloves' + +# ba_meta export bascenev1.GameActivity + +class NewDuelGame(bs.TeamGameActivity[Player, Team]): + """A game type based on acquiring kills.""" + + name = 'Duel' + description = 'Kill a set number of enemies to win.' + + # 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.BoolSetting(enable_powerups, default=False), + bs.BoolSetting(boxing_gloves, default=False), + bs.BoolSetting(night_mode, default=False), + bs.BoolSetting(super_punch, default=False), + bs.BoolSetting(box_mode, default=False), + bs.BoolSetting('Epic Mode', default=False), + bs.BoolSetting('Allow Negative Scores', default=False), + ] + return settings + + @classmethod + def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool: + return (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') + self._epic_mode = bool(settings['Epic Mode']) + self._kills_to_win_per_player = int( + settings['Kills to Win Per Player']) + self._enable_powerups = bool(settings[enable_powerups]) + self._night_mode = bool(settings[night_mode]) + self._fight_delay: float = 0 + self._time_limit = float(settings['Time Limit']) + self._allow_negative_scores = bool( + settings.get('Allow Negative Scores', False)) + self._super_punch = bool(settings[super_punch]) + self._box_mode = bool(settings[box_mode]) + self._boxing_gloves = bool(settings[boxing_gloves]) + self._vs_text: Optional[bs.Actor] = None + self.spawn_order: List[Player] = [] + self._players_vs_1: bool = False + self._players_vs_2: bool = False + self._first_countdown: bool = True + self._count_1 = bui.getsound('announceOne') + self._count_2 = bui.getsound('announceTwo') + self._count_3 = bui.getsound('announceThree') + self._boxing_bell = bui.getsound('boxingBell') + + # 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_player_join(self, player: Player) -> None: + self.spawn_order.append(player) + self._update_order() + + def on_player_leave(self, player: Player) -> None: + super().on_player_leave(player) + player.icons = [] + if player.playervs1: + player.playervs1 = False + self._players_vs_1 = False + player.in_game = False + elif player.playervs2: + player.playervs2 = False + self._players_vs_2 = False + player.in_game = False + if player in self.spawn_order: + self.spawn_order.remove(player) + bs.timer(0.2, self._update_order) + + def on_transition_in(self) -> None: + super().on_transition_in() + if self._night_mode: + gnode = bs.getactivity().globalsnode + gnode.tint = (0.3, 0.3, 0.3) + + 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) + if self._enable_powerups: + self.setup_standard_powerup_drops() + 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') + })) + + # 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() + bs.timer(1.0, self._update, repeat=True) + + def _update(self) -> None: + if len(self.players) == 1: + 'self.end_game()' + + + + def spawn_player(self, player: PlayerT) -> bs.Actor: + # pylint: disable=too-many-locals + # pylint: disable=cyclic-import + from babase import _math + from bascenev1._coopsession import CoopSession + from bascenev1lib.actor.spazfactory import SpazFactory + factory = SpazFactory.get() + 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 = SuperSpaz(color=color, + highlight=highlight, + character=player.character, + player=player, + super_punch=True if self._super_punch else False) + + player.actor = spaz + assert spaz.node + + # If this is co-op and we're on Courtyard or Runaround, add the + # material that allows us to collide with the player-walls. + # FIXME: Need to generalize this. + if isinstance(self.session, CoopSession) and self.map.getname() in [ + 'Courtyard', 'Tower D' + ]: + mat = self.map.preloaddata['collide_with_wall_material'] + assert isinstance(spaz.node.materials, tuple) + assert isinstance(spaz.node.roller_materials, tuple) + spaz.node.materials += (mat, ) + spaz.node.roller_materials += (mat, ) + + spaz.node.name = name + spaz.node.name_color = display_color + spaz.connect_controls_to_player() + + self._spawn_sound.play(1, position=spaz.node.position) + light = bs.newnode('light', attrs={'color': light_color}) + spaz.node.connectattr('position', light, 'position') + bs.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}) + bs.timer(0.5, light.delete) + + pos1 = [self.map.get_start_position(0), 90] + pos2 = [self.map.get_start_position(1), 270] + pos3 = [] + + for x in self.players: + if x.is_alive(): + if x is player: continue + p = x.actor.node.position + if 0.0 not in (p[0], p[2]): + if p[0] <= 0: + pos3.append(pos2[0]) + else: pos3.append(pos1[0]) + + spaz.handlemessage(bs.StandMessage(pos1[0] if player.playervs1 else pos2[0], + pos1[1] if player.playervs1 else pos2[1])) + + if any(pos3): + spaz.handlemessage(bs.StandMessage(pos3[0])) + + if self._super_punch: + spaz._punch_power_scale = factory.punch_power_scale_gloves = 10 + spaz.equip_boxing_gloves() + lfx = bs.newnode( + 'light', + attrs={ + 'color': color, + 'radius': 0.3, + 'intensity': 0.3}) + def sp_fx(): + if not spaz.node: + lfx.delete() + return + bs.emitfx(position=spaz.node.position, + velocity=spaz.node.velocity, + count=5, + scale=0.5, + spread=0.5, + chunk_type='spark') + bs.emitfx(position=spaz.node.position, + velocity=spaz.node.velocity, + count=2, + scale=0.8, + spread=0.3, + chunk_type='spark') + if lfx: + spaz.node.connectattr('position', lfx, 'position') + bs.timer(0.1, sp_fx, repeat=True) + + if self._box_mode: + spaz.node.color_texture = bui.gettexture('tnt') + spaz.node.color_mask_texture = bui.gettexture('tnt') + spaz.node.color = (1, 1, 1) + spaz.node.highlight = (1, 1, 1) + spaz.node.head_mesh = None + spaz.node.torso_mesh = bs.getmesh('tnt') + spaz.node.style = 'cyborg' + + if self._boxing_gloves: + spaz.equip_boxing_gloves() + + return spaz + + def _update_spawn(self) -> None: + if self._players_vs_1 or self._players_vs_2: + for player in self.players: + if player.playervs1 or player.playervs2: + if not player.is_alive(): + self.spawn_player(player) + # player.actor.disconnect_controls_from_player() + + if self._night_mode: + if not player.light: + player.light = True + light = bs.newnode( + 'light', + owner=player.node, + attrs={ + 'radius': 0.3, + 'intensity': 0.6, + 'height_attenuated': False, + 'color': player.color + }) + player.node.connectattr( + 'position', light, 'position') + else: + player.actor.disconnect_controls_from_player() + + bs.timer(0.0, self._countdown) + # bs.timer(0.1, self._clear_all_objects) + + def _countdown(self) -> None: + self._first_countdown = False + if self._fight_delay == 0: + for player in self.players: + if player.playervs1 or player.playervs2: + if not player.is_alive(): + return + else: + player.actor.connect_controls_to_player() + else: + bs.timer(self._fight_delay, self.count3) + + def start(self) -> None: + self._count_text('FIGHT') + self._boxing_bell.play() + for player in self.players: + if player.playervs1 or player.playervs2: + if not player.is_alive(): + return + else: + player.actor.connect_controls_to_player() + + def count(self) -> None: + self._count_text('1') + self._count_1.play() + bs.timer(self._fight_delay, self.start) + + def count2(self) -> None: + self._count_text('2') + self._count_2.play() + bs.timer(self._fight_delay, self.count) + + def count3(self) -> None: + self._count_text('3') + self._count_3.play() + bs.timer(self._fight_delay, self.count2) + + def _count_text(self, num: str) -> None: + self.node = bs.newnode('text', + attrs={ + 'v_attach': 'center', + 'h_attach': 'center', + 'h_align': 'center', + 'color': (1, 1, 0.5, 1), + 'flatness': 0.5, + 'shadow': 0.5, + 'position': (0, 18), + 'text': num + }) + if self._fight_delay == 0.7: + bs.animate(self.node, 'scale', + {0: 0, 0.1: 3.9, 0.64: 4.3, 0.68: 0}) + elif self._fight_delay == 0.4: + bs.animate(self.node, 'scale', + {0: 0, 0.1: 3.9, 0.34: 4.3, 0.38: 0}) + else: + bs.animate(self.node, 'scale', + {0: 0, 0.1: 3.9, 0.92: 4.3, 0.96: 0}) + cmb = bs.newnode('combine', owner=self.node, attrs={'size': 4}) + cmb.connectattr('output', self.node, 'color') + bs.animate(cmb, 'input0', {0: 1.0, 0.15: 1.0}, loop=True) + bs.animate(cmb, 'input1', {0: 1.0, 0.15: 0.5}, loop=True) + bs.animate(cmb, 'input2', {0: 0.1, 0.15: 0.0}, loop=True) + cmb.input3 = 1.0 + bs.timer(self._fight_delay, self.node.delete) + + def _update_order(self) -> None: + for player in self.spawn_order: + assert isinstance(player, Player) + if not player.is_alive(): + if not self._players_vs_1: + self._players_vs_1 = True + player.playervs1 = True + player.in_game = True + self.spawn_order.remove(player) + self._update_spawn() + elif not self._players_vs_2: + self._players_vs_2 = True + player.playervs2 = True + player.in_game = True + self.spawn_order.remove(player) + self._update_spawn() + self._update_icons() + + def _update_icons(self) -> None: + # pylint: disable=too-many-branches + + for player in self.players: + player.icons = [] + + if player.in_game: + if player.playervs1: + xval = -60 + x_offs = -78 + elif player.playervs2: + xval = 60 + x_offs = 78 + player.icons.append( + Icon(player, + position=(xval, 40), + scale=1.0, + name_maxwidth=130, + name_scale=0.8, + flatness=0.0, + shadow=0.5, + show_death=True, + show_lives=False)) + else: + xval = 125 + xval2 = -125 + x_offs = 78 + for player in self.spawn_order: + player.icons.append( + Icon(player, + position=(xval, 25), + scale=0.5, + name_maxwidth=75, + name_scale=1.0, + flatness=1.0, + shadow=1.0, + show_death=False, + show_lives=False)) + xval += x_offs * 0.56 + player.icons.append( + Icon(player, + position=(xval2, 25), + scale=0.5, + name_maxwidth=75, + name_scale=1.0, + flatness=1.0, + shadow=1.0, + show_death=False, + show_lives=False)) + xval2 -= x_offs * 0.56 + + def handlemessage(self, msg: Any) -> Any: + + if isinstance(msg, bs.PlayerDiedMessage): + + # Augment standard behavior. + super().handlemessage(msg) + + player = msg.getplayer(Player) + + if player.playervs1: + player.playervs1 = False + self._players_vs_1 = False + player.in_game = False + self.spawn_order.append(player) + elif player.playervs2: + player.playervs2 = False + self._players_vs_2 = False + player.in_game = False + self.spawn_order.append(player) + bs.timer(0.2, self._update_order) + + 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 _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) \ No newline at end of file diff --git a/dist/ba_root/mods/games/egg_hunt_in_the_sky.py b/dist/ba_root/mods/games/egg_hunt_in_the_sky.py new file mode 100644 index 0000000..9477ede --- /dev/null +++ b/dist/ba_root/mods/games/egg_hunt_in_the_sky.py @@ -0,0 +1,309 @@ +# Released under AGPL-3.0-or-later. See LICENSE for details. +# +# This file incorporates work covered by the following permission notice: +# Released under the MIT License. See LICENSE for details. +# +"""Provides an easter egg hunt game; but on happy thoughts.""" + +# 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 bascenev1 as bs + +from bascenev1lib.actor.bomb import Bomb +from bascenev1lib.actor.playerspaz import PlayerSpaz +from bascenev1lib.actor.onscreencountdown import OnScreenCountdown +from bascenev1lib.actor.scoreboard import Scoreboard +from bascenev1lib.actor.respawnicon import RespawnIcon +from bascenev1lib.gameutils import SharedObjects + +if TYPE_CHECKING: + from typing import Any + + +class Player(bs.Player['Team']): + """Our player type for this game.""" + + def __init__(self) -> None: + self.respawn_timer: bs.Timer | None = None + self.respawn_icon: RespawnIcon | None = None + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + def __init__(self) -> None: + self.score = 0 + + +# ba_meta export bascenev1.GameActivity +class EggHuntInTheSkyGame(bs.TeamGameActivity[Player, Team]): + """A game where score is based on collecting eggs.""" + + name = 'Egg Hunt in the Sky' + description = 'Gather eggs!' + available_settings = [ + bs.BoolSetting('Pro Mode', default=False), + bs.BoolSetting('Epic Mode', default=False), + ] + scoreconfig = bs.ScoreConfig(label='Score', scoretype=bs.ScoreType.POINTS) + + # We're currently hard-coded for one map. + @classmethod + def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: + return ['Happy Thoughts'] + + # 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.CoopSession) + or issubclass(sessiontype, bs.DualTeamSession) + or issubclass(sessiontype, bs.FreeForAllSession) + ) + + def __init__(self, settings: dict): + super().__init__(settings) + shared = SharedObjects.get() + self._last_player_death_time = None + self._scoreboard = Scoreboard() + self.egg_mesh = bs.getmesh('egg') + self.egg_tex_1 = bs.gettexture('eggTex1') + self.egg_tex_2 = bs.gettexture('eggTex2') + self.egg_tex_3 = bs.gettexture('eggTex3') + self._collect_sound = bs.getsound('powerup01') + self._pro_mode = settings.get('Pro Mode', False) + self._epic_mode = settings.get('Epic Mode', False) + self._max_eggs = 4.0 + self.egg_material = bs.Material() + self.egg_material.add_actions( + conditions=('they_have_material', shared.player_material), + actions=(('call', 'at_connect', self._on_egg_player_collide),), + ) + self._eggs: list[Egg] = [] + self._update_timer: bs.Timer | None = None + self._countdown: OnScreenCountdown | None = None + + # Base class overrides + self.slow_motion = self._epic_mode + self.default_music = ( + bs.MusicType.EPIC if self._epic_mode else bs.MusicType.FORWARD_MARCH + ) + + def on_team_join(self, team: Team) -> None: + if self.has_begun(): + self._update_scoreboard() + + # Called when our game actually starts. + def on_begin(self) -> None: + super().on_begin() + self._update_scoreboard() + self._update_timer = bs.Timer(0.25, self._update, repeat=True) + self._countdown = OnScreenCountdown(120, endcall=self.end_game) + bs.timer(4.0, self._countdown.start) + + # Overriding the default character spawning. + def spawn_player(self, player: Player) -> bs.Actor: + spaz = self.spawn_player_spaz(player) + spaz.connect_controls_to_player() + return spaz + + def _on_egg_player_collide(self) -> None: + if self.has_ended(): + return + collision = bs.getcollision() + + # Be defensive here; we could be hitting the corpse of a player + # who just left/etc. + try: + egg = collision.sourcenode.getdelegate(Egg, True) + player = collision.opposingnode.getdelegate( + PlayerSpaz, True + ).getplayer(Player, True) + except bs.NotFoundError: + return + + player.team.score += 1 + + # Displays a +1 (and adds to individual player score in + # teams mode). + self.stats.player_scored(player, 1, screenmessage=False) + if self._max_eggs < 9: + self._max_eggs += 2.0 + elif self._max_eggs < 14: + self._max_eggs += 1.0 + elif self._max_eggs < 34: + self._max_eggs += 0.6 + self._update_scoreboard() + self._collect_sound.play(0.5, position=egg.node.position) + + # Create a flash. + light = bs.newnode( + 'light', + attrs={ + 'position': egg.node.position, + 'height_attenuated': False, + 'radius': 0.1, + 'color': (1, 1, 0), + }, + ) + bs.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.2: 0}, loop=False) + bs.timer(0.200, light.delete) + egg.handlemessage(bs.DieMessage()) + + def _update(self) -> None: + # Misc. periodic updating. + mb = self.map.get_def_bound_box('area_of_interest_bounds') + assert mb + xpos = random.uniform(mb[0], mb[3]) + ypos = random.uniform(mb[1], mb[4]) + zpos = random.uniform(mb[2], mb[5]) + + # Prune dead eggs from our list. + self._eggs = [e for e in self._eggs if e] + + # Spawn more eggs if we've got space. + if len(self._eggs) < int(self._max_eggs): + # Occasionally spawn a bomb in addition. + if self._pro_mode and random.random() < 0.4: + actor = Bomb( + position=(xpos, ypos, zpos), bomb_type='impact' + ).autoretain() + else: + actor = Egg(position=(xpos, ypos, zpos)) + self._eggs.append(actor) + + if actor.node: + # We want everything to float + actor.node.gravity_scale = 0.0 + # Some velocity so the game doesn't look stiff + nvelocity = bs.Vec3( + random.uniform(-1.0, 1.0), random.uniform(-1.0, 1.0), 0 + ).normalized() + s = random.uniform(-0.4, 0.4) + # Let's make it a bit more spicy since we don't have any bots + if self._pro_mode: + s *= 10 + actor.node.velocity = ( + nvelocity[0] * s, nvelocity[1] * s, nvelocity[2] * s + ) + + # Various high-level game events come through this method. + def handlemessage(self, msg: Any) -> Any: + # Respawn dead players. + if isinstance(msg, bs.PlayerDiedMessage): + # Augment standard behavior. + super().handlemessage(msg) + + # Respawn them shortly. + player = msg.getplayer(Player) + assert self.initialplayerinfos is not None + respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0 + player.respawn_timer = bs.Timer( + respawn_time, bs.Call(self.spawn_player_if_exists, player) + ) + player.respawn_icon = RespawnIcon(player, respawn_time) + else: + # Default handler. + return super().handlemessage(msg) + return None + + def _update_scoreboard(self) -> None: + for team in self.teams: + self._scoreboard.set_team_value(team, team.score) + + def end_game(self) -> None: + results = bs.GameResults() + for team in self.teams: + results.set_team_score(team, team.score) + self.end(results) + + +class Egg(bs.Actor): + """A lovely egg that can be picked up for points.""" + + def __init__(self, position: tuple[float, float, float] = (0.0, 1.0, 0.0)): + super().__init__() + activity = self.activity + assert isinstance(activity, EggHuntInTheSkyGame) + shared = SharedObjects.get() + + # Spawn just above the provided point. + self._spawn_pos = (position[0], position[1] + 1.0, position[2]) + ctex = (activity.egg_tex_1, activity.egg_tex_2, activity.egg_tex_3)[ + random.randrange(3) + ] + mats = [shared.object_material, activity.egg_material] + self.node = bs.newnode( + 'prop', + delegate=self, + attrs={ + 'mesh': activity.egg_mesh, + 'color_texture': ctex, + 'body': 'capsule', + 'reflection': 'soft', + 'mesh_scale': 0.5, + 'body_scale': 0.6, + 'density': 4.0, + 'reflection_scale': [0.15], + 'shadow_size': 0.6, + 'position': self._spawn_pos, + 'materials': mats, + 'is_area_of_interest': True, + }, + ) + + def exists(self) -> bool: + return bool(self.node) + + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, bs.DieMessage): + if self.node: + self.node.delete() + elif isinstance(msg, bs.HitMessage): + if self.node: + assert msg.force_direction is not None + self.node.handlemessage( + 'impulse', + msg.pos[0], + msg.pos[1], + msg.pos[2], + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + 1.0 * msg.magnitude, + 1.0 * msg.velocity_magnitude, + msg.radius, + 0, + msg.force_direction[0], + msg.force_direction[1], + msg.force_direction[2], + ) + else: + super().handlemessage(msg) + + +# ba_meta export plugin +class AddToCoopPracticeLevels(bs.Plugin): + def on_app_running(self) -> None: + bs.app.classic.add_coop_practice_level( + bs.Level( + name='Egg Hunt in the Sky', + gametype=EggHuntInTheSkyGame, + settings={}, + preview_texture_name='alwaysLandPreview', + ), + ) + bs.app.classic.add_coop_practice_level( + bs.Level( + name='Pro Egg Hunt in the Sky', + gametype=EggHuntInTheSkyGame, + settings={'Pro Mode': True}, + preview_texture_name='alwaysLandPreview', + ), + ) diff --git a/dist/ba_root/mods/games/jaggernaut.py b/dist/ba_root/mods/games/jaggernaut.py new file mode 100644 index 0000000..93cad45 --- /dev/null +++ b/dist/ba_root/mods/games/jaggernaut.py @@ -0,0 +1,595 @@ +"""Made by Sebaman2009""" + +# ba_meta require api 8 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing_extensions import override + +import bascenev1 as bs +import random +import _bascenev1 +from bascenev1lib.actor.playerspaz import PlayerSpazHurtMessage +from bascenev1lib.actor.playerspaz import PlayerSpaz +from bascenev1lib.actor.scoreboard import Scoreboard +from bascenev1lib.actor.powerupbox import PowerupBox +from bascenev1lib.actor import powerupbox +from bascenev1lib.game.elimination import Icon +import babase +import bascenev1lib +if TYPE_CHECKING: + from typing import Any, Sequence +session = "" +class LivesRemaining(bs.Actor): + def __init__(self,team): + super().__init__() + self.team = team + self.teamcolor = team.color + self.bar_posx = -100 - 120 if isinstance(session,bs.DualTeamSession) == True else 85 - 120 + self._height = 35 + self._width = 70 + self._bar_tex = self._backing_tex = bs.gettexture('bar') + self._cover_tex = bs.gettexture('uiAtlas') + self._mesh = bs.getmesh('meterTransparent') + self._backing = bs.NodeActor( + bs.newnode( + 'image', + attrs={ + 'position': (self.bar_posx + self._width / 2, -35) if self.team.id == 0 or isinstance(session,bs.DualTeamSession) == False else (-self.bar_posx + -self._width / 2, -35), + 'scale': (self._width, self._height), + 'opacity': 0.7, + 'color': ( + self.teamcolor[0] * 0.2, + self.teamcolor[1] * 0.2, + self.teamcolor[2] * 0.2, + ), + 'vr_depth': -3, + 'attach': 'topCenter', + 'texture': self._backing_tex + })) + self._cover = bs.NodeActor( + bs.newnode( + 'image', + attrs={ + 'position': (self.bar_posx + 35, -35) if self.team.id == 0 or isinstance(session,bs.DualTeamSession) == False else (-self.bar_posx - 35, -35), + 'scale': (self._width * 1.15, self._height * 1.6), + 'opacity': 1.0, + 'color': ( + self.teamcolor[0] * 1.1, + self.teamcolor[1] * 1.1, + self.teamcolor[2] * 1.1, + ), + 'vr_depth': 2, + 'attach': 'topCenter', + 'texture': self._cover_tex, + 'mesh_transparent': self._mesh})) + self._score_text = bs.NodeActor( + bs.newnode( + 'text', + attrs={ + 'position': (self.bar_posx +35 , -35) if self.team.id == 0 or isinstance(session,bs.DualTeamSession) == False else (-self.bar_posx - 35, -35), + 'h_attach': 'center', + 'v_attach': 'top', + 'h_align': 'center', + 'v_align': 'center', + 'maxwidth': 130, + 'scale': 0.9, + 'text': '', + 'shadow': 0.5, + 'flatness': 1.0, + 'color': (1.00,1.00,1.00,0.8) + })) + def update_text(self,num,color = (1,1,1,0.8) ): + text = bs.NodeActor(bs.newnode('text',attrs={'position': (self.bar_posx + 35 , -35) if self.team.id == 0 or isinstance(session,bs.DualTeamSession) == False else (-self.bar_posx - 35 , -35) ,'h_attach': 'center','v_attach': 'top','h_align': 'center','v_align': 'center','maxwidth': 130,'scale': 0.9,'text': str(num),'shadow': 0.5,'flatness': 1.0,'color': color})) + return text + def flick(self): + bs.animate( + self._score_text.node, + 'opacity', + { + 0.00: 1.0, + 0.05: 0.0, + 0.10: 1.0, + 0.15: 0.2, + 0.20: 1.0, + }, + ) + def flick_two(self): + bs.animate( + self._score_text.node, + 'opacity', + { + 0.00: 1.0, + 0.05: 0.0, + 0.10: 1.0, + }, + ) + +class Player(bs.Player['Team']): + """Our player type for this game.""" + def __init__(self) -> None: + self.jagg_light: bs.NodeActor | None = None + self.lives = 1 + self.icon = None + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + def __init__(self) -> None: + self.score = 0 + self.survival_seconds = int() +lista = [] +# ba_meta export bascenev1.GameActivity +class Jaggernaut(bs.TeamGameActivity[Player, Team]): + + name = 'Jaggernaut' + description = 'Kill the jagger!.' + scoreconfig = bs.ScoreConfig( + label='Survived', scoretype=bs.ScoreType.SECONDS + ) + + # Print messages when players die since it matters here. + announce_player_deaths = True + allow_mid_activity_joins = False + + @classmethod + def get_available_settings( + cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]: + settings = [ + bs.IntChoiceSetting( + "Jaggers's Health", + choices=[ + ('Normal (1x)', 1000), + ('Stronger (1.5x)',1500), + ('Much Stronger (2x)',2000), + ], + default=1000,), + + bs.IntChoiceSetting( + "Jaggers's Bombs", + choices=[ + ('Normal', 1), + ('Double',2), + ('Triple',3), + ], + default=3, + ), + bs.BoolSetting('Jagger has boxing gloves', default = False), + bs.BoolSetting('Spawn powerups', default = True), + + bs.BoolSetting('Epic Mode', default=False)] + + return settings + + @classmethod + def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool: + return 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._dingsound = bs.getsound('dingSmall') + self._epic_mode = bool(settings['Epic Mode']) + self.boxing = bool(settings['Jagger has boxing gloves']) + self.health = int(settings["Jaggers's Health"]) + self.bombs = int(settings["Jaggers's Bombs"]) + self.powerups = bool(settings["Spawn powerups"]) + + # 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) + self.player_count = len(bs.getactivity().players) + + + def get_instance_description(self) -> Union[str, Sequence]: + return 'Kill the titan!' if isinstance(self.session, bs.DualTeamSession) == False else "Kill the oposing titan!" + def get_instance_description_short(self) -> Union[str, Sequence]: + return f"Kill {self.jagg.team.name}!" if isinstance(self.session, bs.DualTeamSession) == False else "Kill the oposing titan!" + def tick(self) -> None: + if self.powerups: + factory = powerupbox.PowerupBoxFactory() + powerup = factory.get_random_powerup_type(excludetypes = ["ice_bombs","curse","health"]) + lugar = random.choice(self.map.powerup_spawn_points) + PowerupBox((lugar[0],lugar[1],lugar[2]),powerup).autoretain() + def dissapear(self): + if isinstance(self.session, bs.DualTeamSession) == True: + bs.animate( + self.jagg.icon.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: 0.2, + 0.55: 1.0, + 0.60: 0.0, + 0.65: 0.2, + 0.70: 1.0, + 0.75: 0.0, + 0.80: 0.2, + 0.85: 1.0, + 0.90: 0.0, + 0.95: 0.2, + 1.00: 0, + }, + ) + bs.animate( + self.jagg.icon._name_text, + '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: 0.2, + 0.55: 1.0, + 0.60: 0.0, + 0.65: 0.2, + 0.70: 1.0, + 0.75: 0.0, + 0.80: 0.2, + 0.85: 1.0, + 0.90: 0.0, + 0.95: 0.2, + 1.00: 0, + }, + ) + bs.animate( + self.jag.icon.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: 0.2, + 0.55: 1.0, + 0.60: 0.0, + 0.65: 0.2, + 0.70: 1.0, + 0.75: 0.0, + 0.80: 0.2, + 0.85: 1.0, + 0.90: 0.0, + 0.95: 0.2, + 1.00: 0, + }, + ) + bs.animate( + self.jag.icon._name_text, + '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: 0.2, + 0.55: 1.0, + 0.60: 0.0, + 0.65: 0.2, + 0.70: 1.0, + 0.75: 0.0, + 0.80: 0.2, + 0.85: 1.0, + 0.90: 0.0, + 0.95: 0.2, + 1.00: 0, + }, + ) + else: + + bs.animate( + self.jagg.icon.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: 0.2, + 0.55: 1.0, + 0.60: 0.0, + 0.65: 0.2, + 0.70: 1.0, + 0.75: 0.0, + 0.80: 0.2, + 0.85: 1.0, + 0.90: 0.0, + 0.95: 0.2, + 1.00: 0, + }, + ) + bs.animate( + self.jagg.icon._name_text, + '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: 0.2, + 0.55: 1.0, + 0.60: 0.0, + 0.65: 0.2, + 0.70: 1.0, + 0.75: 0.0, + 0.80: 0.2, + 0.85: 1.0, + 0.90: 0.0, + 0.95: 0.2, + 1.00: 0, + }, + ) + def on_begin(self): + super().on_begin() + global session + session = self.session + if isinstance(self.session, bs.DualTeamSession) == False: + powerupbox.DEFAULT_POWERUP_INTERVAL = 12.0 + self._start_time = bs.time() + jagg = random.choice(bs.getactivity().players) + self.player_count = len(bs.getactivity().players) + jaggy = jagg.actor + jaggy.hitpoints = self.health*len(bs.getactivity().players) + jaggy.hitpoints_max = self.health*len(bs.getactivity().players) + if self.boxing: + jaggy.equip_boxing_gloves() + jaggy.set_score_text("Jaggernaut") + jaggy.bomb_count = 3 + bs.timer(12.0,self.tick,True) + color = [ + 0.3 + c * 0.7 + for c in bs.normalized_color(jagg.team.color) + ] + light = jaggy.jagg_light = bs.NodeActor( + bs.newnode( + 'light', + attrs={ + 'intensity': 0.6, + 'height_attenuated': False, + 'volume_intensity_scale': 0.1, + 'radius': 0.13, + 'color': color}, + ) + ) + assert isinstance(jaggy, PlayerSpaz) + jaggy.node.connectattr( + 'position', light.node, 'position' + ) + self.jagg = jagg + self.jaggy = jaggy + self.jagg.icon = Icon(player = self.jagg,position = (0 ,550),scale = 1.0, show_lives = False) + self.text_jagg = LivesRemaining(self.jagg.team) + self.text_jagg.position = 0 - 120 + self.text_jagg._score_text = self.text_jagg.update_text(round(self.jaggy.hitpoints//10)) + bs.timer(8.0,self.dissapear) + return jaggy + else: + powerupbox.DEFAULT_POWERUP_INTERVAL = 12.0 + self._start_time = bs.time() + alive = self._get_living_teams() + print(alive) + self.jagg = random.choice(alive[0].players) + self.jag = random.choice(alive[1].players) + self.jaggy = self.jagg.actor + self.jagy = self.jag.actor + self.jaggy.hitpoints = self.health*len(bs.getactivity().players) + self.jaggy.hitpoints_max = self.health*len(bs.getactivity().players) + self.jagy.hitpoints = self.health*len(bs.getactivity().players) + self.jagy.hitpoints_max = self.health*len(bs.getactivity().players) + if self.boxing: + self.jaggy.equip_boxing_gloves() + self.jagy.equip_boxing_gloves() + self.jaggy.set_score_text("Jaggernaut",self.jagg.color) + self.jagy.set_score_text("Jaggernaut",self.jag.color) + self.jaggy.bomb_count = 3 + self.jagy.bomb_count = 3 + bs.timer(12.0,self.tick,True) + color1 = [ + 0.3 + c * 0.7 + for c in bs.normalized_color(self.jagg.team.color) + ] + light1 = self.jaggy.jagg_light = bs.NodeActor( + bs.newnode( + 'light', + attrs={ + 'intensity': 0.6, + 'height_attenuated': False, + 'volume_intensity_scale': 0.1, + 'radius': 0.13, + 'color': color1}, + ) + ) + assert isinstance(self.jaggy, PlayerSpaz) + self.jaggy.node.connectattr( + 'position', light1.node, 'position' + ) + color2 = [ + 0.3 + c * 0.7 + for c in bs.normalized_color(self.jag.team.color) + ] + light2 = self.jagy.jagg_light = bs.NodeActor( + bs.newnode( + 'light', + attrs={ + 'intensity': 0.6, + 'height_attenuated': False, + 'volume_intensity_scale': 0.1, + 'radius': 0.13, + 'color': color2}, + ) + ) + assert isinstance(self.jagy, PlayerSpaz) + self.jagy.node.connectattr( + 'position', light2.node, 'position' + ) + self.jagg.icon = Icon(player = self.jagg,position = ( -185 ,550),scale = 1.0, show_lives = False) + self.jag.icon = Icon(player = self.jag,position = ( 185 ,550),scale = 1.0, show_lives = False) + self.text_jagg = LivesRemaining(self.jagg.team) + self.text_jag = LivesRemaining(self.jag.team) + self.text_jagg._score_text = self.text_jagg.update_text(round(self.jaggy.hitpoints//10)) + self.text_jag._score_text = self.text_jag.update_text(round(self.jagy.hitpoints//10)) + bs.timer(8.0,self.dissapear) + + + def on_player_join(self, player): + self.spawn_player(player) + def on_player_leave(self,player): + self.player_count -= 1 + if player == self.jagg: + self.end_game() + alive_teams = self._get_living_teams() + if len(alive_teams) == 1: + for team in alive_teams: + team.survival_seconds += int(bs.time() - self._start_time) + 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 check_win(self): + alive_teams = self._get_living_teams() + if self.player_count == 0: + self.end_game() + elif self.msg.getkillerplayer(Player) == self.jagg and self.msg.getplayer(Player) == self.jagg: + self.msg.getkillerplayer(Player).team.survival_seconds = 0 + for team in alive_teams: + team.survival_seconds += int(bs.time() - self._start_time) + self.end_game() + elif self.msg.getplayer(Player) == self.jagg: + self.msg.getkillerplayer(Player).team.survival_seconds += 1 + self.jagg.team.survival_seconds = 0 + for team in alive_teams: + team.survival_seconds += int(bs.time() - self._start_time) + self.end_game() + elif self.player_count == 1: + self.jagg.team.survival_seconds += int(bs.time() - self._start_time) + self.end_game() + def check_win_teams(self): + if self.player_count == 0: + self.end_game() + elif self.jagy.is_alive() == False and self.jaggy.is_alive() == False: + self.end_game() + elif self.msg.getplayer(Player) == self.jagg: + self.msg.getplayer(Player).team.score -= 1 + self.end_game() + elif self.msg.getplayer(Player) == self.jag: + self.msg.getplayer(Player).team.score -= 1 + self.end_game() + + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, bs.PlayerDiedMessage): + super().handlemessage(msg) + msg.getplayer(Player).team.survival_seconds += int(bs.time() - self._start_time) + msg.getplayer(Player).lives -= 1 + self.player_count -= 1 + self.msg = msg + if msg.getplayer(Player).actor == self.jaggy: + self.jaggy.jagg_light = None + self.text_jagg._score_text = self.text_jagg.update_text(0,(1.00, 0.15, 0.15)) + if isinstance(self.session, bs.DualTeamSession) == True: + if msg.getplayer(Player).actor == self.jagy: + self.jagy.jagg_light = None + self.text_jag._score_text = self.text_jag.update_text(0,(1.00, 0.15, 0.15)) + if isinstance(self.session, bs.DualTeamSession) == False: + bs.timer(1.0,self.check_win) + else: + bs.timer(1.0,self.check_win_teams) + if isinstance(msg, PlayerSpazHurtMessage): + if isinstance(self.session, bs.DualTeamSession) == True: + if msg.spaz == self.jaggy: + self.text_jagg._score_text = self.text_jagg.update_text(round(self.jaggy.hitpoints//10)) + self.text_jagg.flick() + if self.jaggy.hitpoints == 0: + self.text_jagg._score_text = self.text_jagg.update_text(round(self.jaggy.hitpoints//10),(1.00, 0.15, 0.15)) + self.timertwo = None + elif self.jaggy.hitpoints <= (self.jaggy.hitpoints_max//4): + self.text_jagg._score_text = self.text_jagg.update_text(round(self.jaggy.hitpoints//10),(1.00, 0.15, 0.15)) + self.timertwo = None + self.timertwo = bs.Timer(0.1,self.text_jagg.flick_two,True) + elif self.jaggy.hitpoints <= (self.jaggy.hitpoints_max//2): + self.text_jagg._score_text = self.text_jagg.update_text(round(self.jaggy.hitpoints//10),(1.00, 0.50, 0.00)) + self.text_jagg.flick() + elif msg.spaz == self.jagy: + self.text_jag._score_text = self.text_jag.update_text(round(self.jagy.hitpoints//10)) + self.text_jag.flick() + if self.jagy.hitpoints == 0: + self.text_jag._score_text = self.text_jag.update_text(round(self.jagy.hitpoints//10),(1.00, 0.15, 0.15)) + self.timer = None + elif self.jagy.hitpoints <= (self.jagy.hitpoints_max//4): + self.text_jag._score_text = self.text_jag.update_text(round(self.jagy.hitpoints//10),(1.00, 0.15, 0.15)) + self.timer = None + self.timer = bs.Timer(0.1,self.text_jag.flick_two,True) + elif self.jagy.hitpoints <= (self.jagy.hitpoints_max//2): + self.text_jag._score_text = self.text_jag.update_text(round(self.jagy.hitpoints//10),(1.00, 0.50, 0.00)) + self.text_jag.flick() + elif msg.spaz == self.jaggy: + self.text_jagg._score_text = self.text_jagg.update_text(round(self.jaggy.hitpoints//10)) + self.text_jagg.flick() + if self.jaggy.hitpoints == 0: + self.text_jagg._score_text = self.text_jagg.update_text(round(self.jaggy.hitpoints//10),(1.00, 0.15, 0.15)) + self.timer = None + elif self.jaggy.hitpoints <= (self.jaggy.hitpoints_max//4): + self.text_jagg._score_text = self.text_jagg.update_text(round(self.jaggy.hitpoints//10),(1.00, 0.15, 0.15)) + self.timer = None + self.timer = bs.Timer(0.1,self.text_jagg.flick_two,True) + elif self.jaggy.hitpoints <= (self.jaggy.hitpoints_max//2): + self.text_jagg._score_text = self.text_jagg.update_text(round(self.jaggy.hitpoints//10),(1.00, 0.50, 0.00)) + self.text_jagg.flick() + + + def end_game(self) -> None: + results = bs.GameResults() + for team in self.teams: + if isinstance(self.session, bs.DualTeamSession) == False: + results.set_team_score(team, team.survival_seconds) + else: + results.set_team_score(team, team.score) + self.end(results=results) diff --git a/dist/ba_root/mods/games/kill_or_die.py b/dist/ba_root/mods/games/kill_or_die.py new file mode 100644 index 0000000..3660f15 --- /dev/null +++ b/dist/ba_root/mods/games/kill_or_die.py @@ -0,0 +1,625 @@ +# ba_meta require api 8 + +from __future__ import annotations + +# Made by Vishal (Discord id : 𝑽𝑰𝑺𝑯𝑼𝑼𝑼#2921) +from typing import TYPE_CHECKING +import logging + +import bascenev1 as bs +from bascenev1lib.actor.spazfactory import SpazFactory +from bascenev1lib.actor.scoreboard import Scoreboard +from bascenev1lib.gameutils import SharedObjects + +if TYPE_CHECKING: + from typing import Any, 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': bs.Lstr(value=player.getname()), + 'color': bs.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 KillOrDieGame(bs.TeamGameActivity[Player, Team]): + """Game type where last player(s) left alive win.""" + + name = 'Kill Or Die' + description = 'Kill the opponent or You Die.' + 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 + + allow_mid_activity_joins = False + + @classmethod + def get_available_settings( + cls, sessiontype: type[bs.Session]) -> list[bs.Setting]: + settings = [ + bs.IntSetting( + 'Lives 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.BoolSetting('Epic Mode', default=False), + ] + if issubclass(sessiontype, bs.DualTeamSession): + 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)) + + @classmethod + def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: + return ['The Pad'] + + def __init__(self, settings: dict): + super().__init__(settings) + shared = SharedObjects.get() + 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 = True + + self._collide_with_player = bs.Material() + self._collide_with_player.add_actions(conditions=('we_are_older_than', 1), actions=(('modify_part_collision', 'collide', True))) + + self.left_region = bs.newnode('region',attrs={ + 'position': (-6.35,5,-2.7), + 'scale': (2.1,5,14), + 'type': 'box', + 'materials': (self._collide_with_player, shared.footing_material)}) + + self.right_region = bs.newnode('region',attrs={ + 'position': (6.9,5,-2.7), + 'scale': (2.1,5,14), + 'type': 'box', + 'materials': (self._collide_with_player, shared.footing_material)}) + + self.bottom_region = bs.newnode('region',attrs={ + 'position': ((0.36072, 4.62957, 2.8769)), + 'scale': (14,5,2.1), + 'type': 'box', + 'materials': (self._collide_with_player, shared.footing_material)}) + self.front_region = bs.newnode('region',attrs={ + 'position': ((0.36072, 4.62957, -8.5)), + 'scale': (14,5,2.1), + 'type': 'box', + 'materials': (self._collide_with_player, shared.footing_material)}) + + self.test = bs.newnode('locator',attrs={ + 'shape':'circleOutline', + 'position':(0.3, 3.5, -2.58), + 'color':(1,1,1), + 'opacity':1, + 'draw_beauty':True, + 'additive':False, + 'size':[100, 0.001, 100] + }) + self.size = (5.45*2, 0.001, 5.45*2) + self.rad = 5.30 + + # Base class overrides: + self.slow_motion = self._epic_mode + self.default_music = (bs.MusicType.EPIC + if self._epic_mode else bs.MusicType.SURVIVAL) + self.tint = (1,1,1) + + def get_instance_description(self) -> Union[str, Sequence]: + return 'Last team standing wins.' 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' if isinstance( + self.session, bs.DualTeamSession) else 'last one standing wins' + + def on_player_join(self, player: Player) -> None: + 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) + 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': bs.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.tint = bs.get_foreground_host_activity().globalsnode.tint + bs.get_foreground_host_activity().globalsnode.tint = (0.4, 0.4, 0.4) + + 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) + bs.timer(0.2,self.red_zone,repeat=True) + + def red_zone(self) -> None: + try: + bs.animate_array( + self.test, + "size", + 3, + { + 0: self.size, + 0.2: ( + self.size[0] - (0.2 if self._epic_mode else 0.1), + self.size[1], + self.size[2] - (0.2 if self._epic_mode else 0.1) + ) + }, + loop=False + ) + self.size = ( + self.size[0] - (0.2 if self._epic_mode else 0.1), + self.size[1], + self.size[2] - (0.2 if self._epic_mode else 0.1) + ) + self.rad = self.rad - (0.1 if self._epic_mode else 0.05) + for player in self.players: + if not player.actor is None: + if player.actor.is_alive(): + p1 = player.actor.node.position + p2 = (0.3, 3.5, -2.58) + diff = (bs.Vec3(p1[0]-p2[0], 0.0, p1[2]-p2[2])) + dist = (diff.length()) + if dist > self.rad: + player.actor.handlemessage(bs.DieMessage()) + except Exception: + return + + 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[bs.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 = bs.Vec3(living_player_pos) + points: list[tuple[float, bs.Vec3]] = [] + for team in self.teams: + start_pos = bs.Vec3(self.map.get_start_position(team.id)) + points.append( + ((start_pos - player_pos).length(), start_pos)) + # Hmm.. we need to sort vectors too? + points.sort(key=lambda x: x[0]) + return points[-1][1] + return None + + def spawn_player(self, player: Player) -> bs.Actor: + if player.team.id == 0: + spaz = self.spawn_player_spaz(player, position=(-3.02237, 3.52416, -2.81289)) + return spaz + elif player.team.id == 1: + spaz = self.spawn_player_spaz(player, position=(3.67858, 3.52384, -2.77144)) + return spaz + actor = self.spawn_player_spaz(player, self._get_spawn_point(player)) + if not self._solo_mode: + bs.timer(0.3, bs.Call(self._print_lives, player)) + + # If we have any icons, update their state. + for icon in player.icons: + icon.handle_player_spawned() + + 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 = msg.getplayer(Player) + + player.lives -= 1 + if player.lives < 0: + logging.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.size = (5.45*2, 0.001, 5.45*2) + self.rad = 5.30 + 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) + bs.get_foreground_host_activity().globalsnode.tint = self.tint + self.end(results=results) diff --git a/dist/ba_root/mods/games/last_punch_stand.py b/dist/ba_root/mods/games/last_punch_stand.py new file mode 100644 index 0000000..bfd81e5 --- /dev/null +++ b/dist/ba_root/mods/games/last_punch_stand.py @@ -0,0 +1,416 @@ +# ba_meta require api 8 + +from typing import Sequence +import random +import bascenev1, babase, baclassic, baplus, bauiv1 +from bascenev1lib.actor.spaz import Spaz +from bascenev1lib.actor.scoreboard import Scoreboard +from bascenev1lib.gameutils import SharedObjects +from bascenev1lib.actor.powerupbox import PowerupBoxFactory + +class Player(bascenev1.Player['Team']): + """Our player type for this game.""" + +class Team(bascenev1.Team[Player]): + """Our team type for this game.""" + + def __init__(self) -> None: + super().__init__() + self.score = 1 + +class ChoosingThingHitMessage: + def __init__(self, hitter:Player) -> None: + self.hitter = hitter + +class ChoosingThingDieMessage: + def __init__(self, how:bascenev1.DeathType) -> None: + self.how = how + +class ChoosingThing(): + def __init__(self, pos, color) -> None: + pass + + def recolor(self, color:list[int | float], highlight:list[int, float] = (1,1,1)): + raise NotImplementedError() + + def is_dead(self): + raise NotImplementedError() + + def _is_dead(self): + return self.is_dead() + + def create_locator(self, node:bascenev1.Node, pos, color): + loc = bascenev1.newnode( + 'locator', + attrs={ + 'shape': 'circleOutline', + 'position': pos, + 'color': color, + 'opacity': 1, + 'draw_beauty': False, + 'additive': True, + }, + ) + node.connectattr("position", loc, "position") + bascenev1.animate_array(loc, "size", 1, keys={0:[0.5,], 1:[2,], 1.5:[0.5]}, loop=True) + + return loc + + dead = property(_is_dead) + +class ChoosingSpaz(Spaz, ChoosingThing): + def __init__( + self, + pos:Sequence[float], + color: Sequence[float] = (1.0, 1.0, 1.0), + highlight: Sequence[float] = (0.5, 0.5, 0.5), + ): + super().__init__(color, highlight, "Spaz", None, True, True, False, False) + self.stand(pos) + self.loc = self.create_locator(self.node, pos, color) + + def handlemessage(self, msg): + if isinstance(msg, bascenev1.FreezeMessage): + return + + if isinstance(msg, bascenev1.PowerupMessage): + if not(msg.poweruptype == "health"): + return + + super().handlemessage(msg) + + if isinstance(msg, bascenev1.HitMessage): + self.handlemessage(bascenev1.PowerupMessage("health")) + + player = msg.get_source_player(Player) + if self.is_alive(): + self.activity.handlemessage(ChoosingThingHitMessage(player)) + + elif isinstance(msg, bascenev1.DieMessage): + self._dead = True + self.activity.handlemessage(ChoosingThingDieMessage(msg.how)) + + self.loc.delete() + + def stand(self, pos = (0,0,0), angle = 0): + self.handlemessage(bascenev1.StandMessage(pos,angle)) + + def recolor(self, color, highlight = (1,1,1)): + self.node.color = color + self.node.highlight = highlight + self.loc.color = color + + def is_dead(self): + return self._dead + +class ChoosingBall(bascenev1.Actor, ChoosingThing): + def __init__(self, pos, color = (0.5,0.5,0.5)) -> None: + super().__init__() + shared = SharedObjects.get() + + pos = (pos[0], pos[1] + 2, pos[2]) + + # We want the puck to kill powerups; not get stopped by them + self.puck_material = bascenev1.Material() + self.puck_material.add_actions( + conditions=('they_have_material', + PowerupBoxFactory.get().powerup_material), + actions=(('modify_part_collision', 'physical', False), + ('message', 'their_node', 'at_connect', bascenev1.DieMessage()))) + + #fontSmall0 jumpsuitColor menuBG operaSingerColor rgbStripes zoeColor + self.node = bascenev1.newnode('prop', + delegate=self, + attrs={ + 'mesh': bascenev1.getmesh('frostyPelvis'), + 'color_texture': + bascenev1.gettexture('gameCenterIcon'), + 'body': 'sphere', + 'reflection': 'soft', + 'reflection_scale': [0.2], + 'shadow_size': 0.5, + 'is_area_of_interest': True, + 'position': pos, + "materials": [shared.object_material, self.puck_material] + }) + #seince this ball allways jumps a specefic direction when it spawned, we just jump it randomly + self.node.handlemessage( + 'impulse', + random.uniform(-10, 10), + random.uniform(-10, 10), + random.uniform(-10, 10), + random.uniform(-10, 10), + random.uniform(-10, 10), + random.uniform(-10, 10), + random.uniform(-10, 10), + random.uniform(-10, 10), + 0, + random.uniform(-10, 10), + random.uniform(-10, 10), + random.uniform(-10, 10), + random.uniform(-10, 10), + ) + + self.loc = self.create_locator(self.node, pos, color) + + self._died = False + + def handlemessage(self, msg): + if isinstance(msg, bascenev1.HitMessage): + player = msg.get_source_player(Player) + self.activity.handlemessage(ChoosingThingHitMessage(player)) + + mag = msg.magnitude + velocity_mag = msg.velocity_magnitude + + self.node.handlemessage( + 'impulse', + msg.pos[0], + msg.pos[1], + msg.pos[2], + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + mag, + velocity_mag, + msg.radius, + 1, + msg.force_direction[0], + msg.force_direction[1], + msg.force_direction[2], + ) + + elif isinstance(msg, bascenev1.DieMessage): + if self.node.exists(): + self.node.delete() + self.loc.delete() + + self._died = True + self.activity.handlemessage(ChoosingThingDieMessage(msg.how)) + + return super().handlemessage(msg) + + def exists(self) -> bool: + return not self.dead + + def is_alive(self) -> bool: + return not self.dead + + def recolor(self, color: list[int | float], highlight: list[int] = (1, 1, 1)): + self.loc.color = color + + def is_dead(self): + return self._died + +class ChooseBilbord(bascenev1.Actor): + def __init__(self, player:Player, delay = 0.1) -> None: + super().__init__() + + icon = player.get_icon() + self.scale = 100 + + self.node = bascenev1.newnode( + 'image', + delegate=self, + attrs={ + "position":(60,-125), + 'texture': icon['texture'], + 'tint_texture': icon['tint_texture'], + 'tint_color': icon['tint_color'], + 'tint2_color': icon['tint2_color'], + 'opacity': 1.0, + 'absolute_scale': True, + 'attach': "topLeft" + }, + ) + + self.name_node = bascenev1.newnode( + 'text', + owner=self.node, + attrs={ + 'position': (60,-185), + 'text': bascenev1.Lstr(value=player.getname()), + 'color': bascenev1.safecolor(player.team.color), + 'h_align': 'center', + 'v_align': 'center', + 'vr_depth': 410, + 'flatness': 1.0, + 'h_attach': 'left', + 'v_attach': 'top', + 'maxwidth':self.scale + }, + ) + + bascenev1.animate_array(self.node, "scale", keys={0 + delay:[0,0], 0.05 + delay:[self.scale, self.scale]}, size=1) + bascenev1.animate(self.name_node, "scale", {0 + delay:0, 0.07 + delay:1}) + + def handlemessage(self, msg): + super().handlemessage(msg) + if isinstance(msg, bascenev1.DieMessage): + bascenev1.animate_array(self.node, "scale", keys={0:self.node.scale, 0.05:[0,0]}, size=1) + bascenev1.animate(self.name_node, "scale", {0:self.name_node.scale, 0.07:0}) + + def __delete(): + self.node.delete() + self.name_node.delete() + + bascenev1.timer(0.2, __delete) + +# ba_meta export bascenev1.GameActivity +class LastPunchStand(bascenev1.TeamGameActivity[Player, Team]): + name = "Last Punch Stand" + description = "Last one punchs the choosing thing wins" + tips = [ + 'keep punching the choosing thing to be last punched player at times up!', + 'you can not frezz the choosing spaz', + "evry time you punch the choosing thing, you will get one point", + ] + + default_music = bascenev1.MusicType.TO_THE_DEATH + + def get_instance_display_string(self) -> bascenev1.Lstr: + name = self.name + if self.settings_raw["Ball Mode"]: + name += " Ball Mode" + + return name + + def get_instance_description_short(self) -> str | Sequence: + if self.settings_raw["Ball Mode"]: + return "Punch the Ball" + else: + return "Punch the Spaz" + + available_settings = [ + bascenev1.BoolSetting("Ball Mode", False), + bascenev1.FloatSetting("min time limit (in seconds)", 50.0, min_value=30.0), + bascenev1.FloatSetting("max time limit (in seconds)", 160.0, 60), + ] + + def __init__(self, settings: dict): + super().__init__(settings) + self._min_timelimit = settings["min time limit (in seconds)"] + self._max_timelimit = settings["max time limit (in seconds)"] + self.ball_mod:bool = settings["Ball Mode"] + if (self._min_timelimit > self._max_timelimit): + self._max_timelimit = self._min_timelimit + + self._choosing_thing_defcolor = (0.5,0.5,0.5) + self.choosing_thing:ChoosingThing = None + self.choosed_player = None + self.times_uped = False + self.scoreboard = Scoreboard() + + @classmethod + def get_supported_maps(cls, sessiontype: type[bascenev1.Session]) -> list[str]: + assert bascenev1.app.classic is not None + return bascenev1.app.classic.getmaps('team_flag') + + def times_up(self): + self.times_uped = True + self.end_game() + + for player in self.players: + try: + if self.choosed_player and player and (player.team.id != self.choosed_player.team.id): + player.actor._cursed = True + player.actor.curse_explode() + except AttributeError: + pass + + def __get_thing_spawn_point(self): + if len(self.map.flag_points_default) > 0: + return self.map.get_flag_position(None) + elif len(self.map.tnt_points) > 0: + return self.map.tnt_points[random.randint(0, len(self.map.tnt_points)-1)] + else: + return (0, 6, 0) + + def spaw_bot(self): + "spawns a choosing bot" + if self.ball_mod: + self.choosing_thing = ChoosingBall(self.__get_thing_spawn_point()) + else: + self.choosing_thing = ChoosingSpaz(self.__get_thing_spawn_point()) + self.choose_bilbord = None + + def on_begin(self) -> None: + super().on_begin() + time_limit = random.randint(self._min_timelimit, self._max_timelimit) + self.spaw_bot() + bascenev1.timer(time_limit, self.times_up) + + self.setup_standard_powerup_drops(False) + + def end_game(self) -> None: + results = bascenev1.GameResults() + total = 0 + + for team in self.teams: + total = team.score + + for team in self.teams: + if self.choosed_player and (team.id == self.choosed_player.team.id): team.score += total + results.set_team_score(team, team.score) + + self.end(results=results) + + def change_choosed_player(self, hitter:Player): + if hitter == self.choosed_player: + return + + if hitter: + self.choosing_thing.recolor(hitter.color, hitter.highlight) + self.choosed_player = hitter + hitter.team.score += 1 + self.choose_bilbord = ChooseBilbord(hitter) + self.hide_score_board() + else: + self.choosing_thing.recolor(self._choosing_thing_defcolor) + self.choosed_player = None + self.choose_bilbord = None + self.show_score_board() + + def show_score_board(self): + self.scoreboard = Scoreboard() + for team in self.teams: + self.scoreboard.set_team_value(team, team.score) + + def hide_score_board(self): + self.scoreboard = None + + def _watch_dog_(self): + "checks if choosing spaz exists" + #choosing spaz wont respawn if death type if generic + #this becuse we dont want to keep respawn him when he dies because of losing referce + #but sometimes "choosing spaz" dies naturaly and his death type is generic! so it wont respawn back again + #thats why we have this function; to check if spaz exits in the case that he didnt respawned + + if self.choosing_thing: + if self.choosing_thing.dead: + self.spaw_bot() + else: + self.spaw_bot() + + def handlemessage(self, msg): + super().handlemessage(msg) + + if isinstance(msg, ChoosingThingHitMessage): + hitter = msg.hitter + if hitter: + self.change_choosed_player(hitter) + + elif isinstance(msg, ChoosingThingDieMessage): + if msg.how.value != bascenev1.DeathType.GENERIC.value: + self.spaw_bot() + self.change_choosed_player(None) + + elif isinstance(msg, bascenev1.PlayerDiedMessage): + player = msg.getplayer(Player) + if not (self.has_ended() or self.times_uped): + self.respawn_player(player, 0) + + if self.choosed_player and (player.getname(True) == self.choosed_player.getname(True)): + self.change_choosed_player(None) + + self._watch_dog_() \ No newline at end of file diff --git a/dist/ba_root/mods/games/tag.py b/dist/ba_root/mods/games/tag.py new file mode 100644 index 0000000..a1cb5f5 --- /dev/null +++ b/dist/ba_root/mods/games/tag.py @@ -0,0 +1,410 @@ +"""Made by: Sebaman2009""" + +# ba_meta require api 8 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing_extensions import override + +import bascenev1 as bs +import random +from bascenev1lib.actor.playerspaz import PlayerSpazHurtMessage +from bascenev1lib.actor.playerspaz import PlayerSpaz +from bascenev1lib.actor.scoreboard import Scoreboard +import bascenev1lib +import babase +import _bascenev1 +from bascenev1lib.game.elimination import Icon + +if TYPE_CHECKING: + from typing import Any, Sequence + +class LivesRemaining(bs.Actor): + def __init__(self,team): + super().__init__() + self.team = team + self.teamcolor = team.color + self.bar_posx = -100 - 120 + self._height = 35 + self._width = 70 + self._bar_tex = self._backing_tex = bs.gettexture('bar') + self._cover_tex = bs.gettexture('uiAtlas') + self._mesh = bs.getmesh('meterTransparent') + self._backing = bs.NodeActor( + bs.newnode( + 'image', + attrs={ + 'position': (self.bar_posx + self._width / 2, -35) if team.id == 0 else (-self.bar_posx + -self._width / 2, -35), + 'scale': (self._width, self._height), + 'opacity': 0.7, + 'color': ( + self.teamcolor[0] * 0.2, + self.teamcolor[1] * 0.2, + self.teamcolor[2] * 0.2, + ), + 'vr_depth': -3, + 'attach': 'topCenter', + 'texture': self._backing_tex + })) + self._cover = bs.NodeActor( + bs.newnode( + 'image', + attrs={ + 'position': (self.bar_posx + 35, -35) if team.id == 0 else (-self.bar_posx - 35, -35), + 'scale': (self._width * 1.15, self._height * 1.6), + 'opacity': 1.0, + 'color': ( + self.teamcolor[0] * 1.1, + self.teamcolor[1] * 1.1, + self.teamcolor[2] * 1.1, + ), + 'vr_depth': 2, + 'attach': 'topCenter', + 'texture': self._cover_tex, + 'mesh_transparent': self._mesh})) + self._score_text = bs.NodeActor( + bs.newnode( + 'text', + attrs={ + 'position': (self.bar_posx +35 , -35) if team.id == 0 else (-self.bar_posx - 35, -35), + 'h_attach': 'center', + 'v_attach': 'top', + 'h_align': 'center', + 'v_align': 'center', + 'maxwidth': 130, + 'scale': 0.9, + 'text': str(len(self.team.players)), + 'shadow': 0.5, + 'flatness': 1.0, + 'color': (1,1,1,0.8) + })) + def update_text(self,num): + text = bs.NodeActor(bs.newnode('text',attrs={'position': (self.bar_posx + 35 , -35) if self.team.id == 0 else (-self.bar_posx - 35 , -35) ,'h_attach': 'center','v_attach': 'top','h_align': 'center','v_align': 'center','maxwidth': 130,'scale': 0.9,'text': str(num),'shadow': 0.5,'flatness': 1.0,'color': (1,1,1,0.8)})) + return text + def flick(self): + _bascenev1.Sound.play(bs.getsound("shieldDown")) + bs.animate( + self._score_text.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: 0.2, + 0.55: 1.0, + 0.60: 0.0, + 0.65: 0.2, + 0.70: 1.0, + 0.75: 0.0, + 0.80: 0.2, + 0.85: 1.0, + 0.90: 0.0, + 0.95: 0.2, + 1.00: 1.0, + }, + ) + + +class Player(bs.Player['Team']): + """Our player type for this game.""" + def __init__(self) -> None: + self.tag_light: bs.NodeActor | None = None + self.lives = 1 + self.time = 0 + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + def __init__(self) -> None: + self.score = 0 + self.survival_seconds = int() +lista = [] +# ba_meta export bascenev1.GameActivity +class Tag(bs.TeamGameActivity[Player, Team]): + """A game type based on acquiring kills.""" + + name = 'Tag' + description = "Don't get caught!" + scoreconfig = bs.ScoreConfig( + label='Survived', scoretype=bs.ScoreType.SECONDS + ) + + # Print messages when players die since it matters here. + allow_mid_activity_joins = False + announce_player_deaths = True + + @classmethod + def get_available_settings( + cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]: + settings = [ + bs.IntChoiceSetting( + "Tag time", + choices=[ + ('Shorter',15), + ('Normal', 20), + ('Longer',25), + ], + default=20,), + bs.BoolSetting('Epic Mode', default=False), + bs.BoolSetting('Disable Bombs', default=False), + bs.BoolSetting('Disable Pickup', default=False), + bs.BoolSetting('Tag explodes when time ends', default=True)] + + 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._dingsound = bs.getsound('dingSmall') + self.tag_length = int(settings["Tag time"]) + self._epic_mode = bool(settings['Epic Mode']) + self.bombs = bool(settings['Disable Bombs']) + self.grab = bool(settings['Disable Pickup']) + self.explode = bool(settings['Tag explodes when time ends']) + self.alive_teams = [team for team in self.teams if len(team.players) > 0 and any(player.lives > 0 for player in team.players)] + self.ended = False + # Base class overrides. + self.default_music = (bs.MusicType.EPIC if self._epic_mode else + bs.MusicType.RACE) + self.tag = "" + self.tag_act = "" + self.time = self.tag_length + self._countdownsounds: dict[int, bs.Sound] = { + 10: bs.getsound('announceTen'), + 9: bs.getsound('announceNine'), + 8: bs.getsound('announceEight'), + 7: bs.getsound('announceSeven'), + 6: bs.getsound('announceSix'), + 5: bs.getsound('announceFive'), + 4: bs.getsound('announceFour'), + 3: bs.getsound('announceThree'), + 2: bs.getsound('announceTwo'), + 1: bs.getsound('announceOne'), + } + self.slow_motion = self._epic_mode + + def get_instance_description(self) -> Union[str, Sequence]: + return "Don't get tagged!" + + def get_instance_description_short(self) -> Union[str, Sequence]: + return "Don't get tagged!" + + def tick(self): + + self.time -= 1 + if self.time == 0: + if self.explode == True: + self.tag_act.curse_time = 0.000000000000000000001 + self.tag_act.curse() + else: + self.tag_act.handlemessage(bs.DieMessage()) + if self.time in self._countdownsounds: + self._countdownsounds[self.time].play() + self.tag_act.set_score_text(f"{self.time}",self.color,True) + else: + self.tag_act.set_score_text(f"{self.time}",self.color) + + + + def select_tag(self): + alive = self._get_living_teams() + print(alive) + self.tag = random.choice(alive) if alive else self.end_game() + self.player_count = len(bs.getactivity().players) + self.tag_act = self.tag.actor + self.tag_act.invincible = True + self.tag_act.set_score_text("Tag") + self.color = [ + 0.3 + c * 0.7 + for c in bs.normalized_color(self.tag.team.color) + ] + light = self.tag_act.tag_light = bs.NodeActor( + bs.newnode( + 'light', + attrs={ + 'intensity': 0.6, + 'height_attenuated': False, + 'volume_intensity_scale': 0.1, + 'radius': 0.13, + 'color': self.color}, + ) + ) + assert isinstance(self.tag_act, PlayerSpaz) + self.tag_act.node.connectattr( + 'position', light.node, 'position' + ) + self.cuenta = bs.Timer(1.0,call= self.tick,repeat = True) + def flick(self): + bs.animate( + self.icon.node, + 'opacity', + { + 0.00: 1.0, + 0.05: 0.0, + 0.10: 1.0, + 0.15: 0.2, + 0.20: 1.0, + 0.25: 0.0, + 0.30: 1.0, + }, + ) + bs.animate( + self.icon._name_text, + 'opacity', + { + 0.00: 1.0, + 0.05: 0.0, + 0.10: 1.0, + 0.15: 0.2, + 0.20: 1.0, + 0.25: 0.0, + 0.30: 1.0, + }, + ) + def on_begin(self): + super().on_begin() + if len(bs.getactivity().players) == 1: + self.end_game() + if isinstance(self.session, bs.DualTeamSession) == True: + self.team_one_bar = LivesRemaining(self.teams[0]) + self.team_two_bar = LivesRemaining(self.teams[1]) + self._start_time = bs.time() + self.select_tag() + self.icon = Icon(player = self.tag,position = (0 ,600),scale = 1.0, show_lives = False) + players = self._get_living_teams() + for player in players: + actor = player.actor + actor.hitpoints = 20000000000000000000000000000000000 + actor.hitpoints_max = 20000000000000000000000000000000000 + if self.bombs == True and self.grab == True: + actor.disconnect_controls_from_player() + actor.connect_controls_to_player(enable_bomb = False,enable_pickup = False) + elif self.bombs == True and self.grab == False: + actor.disconnect_controls_from_player() + actor.connect_controls_to_player(enable_bomb = False) + elif self.bombs == False and self.grab == True: + actor.disconnect_controls_from_player() + actor.connect_controls_to_player(enable_grab = False) + else: + pass + def on_player_join(self, player): + self.spawn_player(player) + def on_player_leave(self,player): + self.player_count -= 1 + if player == self.tag: + self.select_tag() + if self.check_win() != None: + alive = self.check_win() + print(alive) + alive.survival_seconds += int(bs.time() - self._start_time) + self.end_game() + def _get_living_teams(self) -> list[Team]: + alive = [] + for team in self.teams: + for player in team.players: + if player.lives > 0: + alive.append(player) + return alive + def check_win(self): + alive_teams = [ + team for team in self.teams if len(team.players) > 0 and any(player.lives > 0 for player in team.players) + ] + print(alive_teams) + if len(alive_teams) == 1: + alive_teams[0].survival_seconds += int(bs.time() - self._start_time) + for player in alive_teams[0].players: + player.team.score += 1 + self.end_game() + def reselect_tag(self): + if self.ended == False: + self.select_tag() + self.icon.node.delete() + self.icon = Icon(player = self.tag,position = (0 ,600),scale = 1.0, show_lives = False) + self.flick() + def handlemessage(self, msg: Any) -> Any: + super().handlemessage(msg) + if isinstance(msg, bs.PlayerDiedMessage): + self.player_count -= 1 + msg.getplayer(Player).lives -= 1 + alive_teams = [team for team in self.teams if len(team.players) > 0 and any(player.lives > 0 for player in team.players)] + if msg.getplayer(Player).team not in alive_teams: + msg.getplayer(Player).team.survival_seconds += int(bs.time() - self._start_time) + if isinstance(self.session, bs.DualTeamSession): + if msg.getplayer(Player).team == self.teams[0]: + self.team_one_bar._score_text = self.team_one_bar.update_text(int(self.team_one_bar._score_text.node.text)-1) + self.team_one_bar.flick() + else: + self.team_two_bar._score_text = self.team_two_bar.update_text(int(self.team_two_bar._score_text.node.text)-1) + self.team_two_bar.flick() + bs.timer(1.0,self.check_win) + if msg.getplayer(Player).node == self.tag.node: + self.cuenta = None + if self.tag_act: + self.tag_act.tag_light = None + self.time = self.tag_length + bs.animate( + self.icon.node, + 'opacity', + {0.00: 1.0,0.05: 0.5,0.10: 0.3,0.15: 0.2,}) + bs.animate( + self.icon._name_text, + 'opacity', + {0.00: 1.0,0.05: 0.5,0.10: 0.3,0.15: 0.2,}) + self.tag_act = None + bs.timer(2.0,self.reselect_tag) + elif isinstance(msg, PlayerSpazHurtMessage): + player = msg.spaz + player_act = player.getplayer(playertype = Player) + if player.last_player_attacked_by == self.tag: + player.last_player_attacked_by = None + if self.tag_act: + self.tag_act.tag_light = None + self.tag_act = player + self.tag = self.tag_act.getplayer(playertype = Player) + self.color = [ + 0.3 + c * 0.7 + for c in bs.normalized_color(self.tag_act.getplayer(playertype = Player).color) + ] + light = self.tag_act.tag_light = bs.NodeActor( + bs.newnode( + 'light', + attrs={ + 'intensity': 0.6, + 'height_attenuated': False, + 'volume_intensity_scale': 0.1, + 'radius': 0.13, + 'color': self.color}, + ) + ) + assert isinstance(self.tag_act, PlayerSpaz) + self.tag_act.node.connectattr( + 'position', light.node, 'position' + ) + self.icon.node.delete() + self.icon = Icon(player = self.tag,position = (0 ,600),scale = 1.0, show_lives = False) + self.flick() + + def end_game(self) -> None: + self.ended = True + results = bs.GameResults() + for team in self.teams: + results.set_team_score(team, team.survival_seconds) + self.end(results=results)