Source code for bascenev1lib.game.ninjafight

# Released under the MIT License. See LICENSE for details.
#
"""Provides Ninja Fight mini-game."""

# ba_meta require api 9
# (see https://ballistica.net/wiki/meta-tag-system)

from __future__ import annotations

import random
from typing import TYPE_CHECKING, override

import bascenev1 as bs

from bascenev1lib.actor.spazbot import (
    SpazBotSet,
    ChargerBot,
    SpazBotDiedMessage,
)
from bascenev1lib.actor.onscreentimer import OnScreenTimer

if TYPE_CHECKING:
    from typing import Any


[docs] class Player(bs.Player['Team']): """Our player type for this game."""
[docs] class Team(bs.Team[Player]): """Our team type for this game."""
# ba_meta export bascenev1.GameActivity
[docs] class NinjaFightGame(bs.TeamGameActivity[Player, Team]): """ A co-op game where you try to defeat a group of Ninjas as fast as possible """ name = 'Ninja Fight' description = 'How fast can you defeat the ninjas?' scoreconfig = bs.ScoreConfig( label='Time', scoretype=bs.ScoreType.MILLISECONDS, lower_is_better=True ) default_music = bs.MusicType.TO_THE_DEATH
[docs] @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: # For now we're hard-coding spawn positions and whatnot # so we need to be sure to specify that we only support # a specific map. return ['Courtyard']
[docs] @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: # We currently support Co-Op only. return issubclass(sessiontype, bs.CoopSession)
# In the constructor we should load any media we need/etc. # ...but not actually create anything yet. def __init__(self, settings: dict): super().__init__(settings) self._winsound = bs.getsound('score') self._won = False self._timer: OnScreenTimer | None = None self._bots = SpazBotSet() self._preset = str(settings['preset']) # Called when our game actually begins.
[docs] @override def on_begin(self) -> None: super().on_begin() is_pro = self._preset == 'pro' # In pro mode there's no powerups. if not is_pro: self.setup_standard_powerup_drops() # Make our on-screen timer and start it roughly when our bots appear. self._timer = OnScreenTimer() bs.timer(4.0, self._timer.start) # Spawn some baddies. bs.timer( 1.0, lambda: self._bots.spawn_bot( ChargerBot, pos=(3, 3, -2), spawn_time=3.0 ), ) bs.timer( 2.0, lambda: self._bots.spawn_bot( ChargerBot, pos=(-3, 3, -2), spawn_time=3.0 ), ) bs.timer( 3.0, lambda: self._bots.spawn_bot( ChargerBot, pos=(5, 3, -2), spawn_time=3.0 ), ) bs.timer( 4.0, lambda: self._bots.spawn_bot( ChargerBot, pos=(-5, 3, -2), spawn_time=3.0 ), ) # Add some extras for multiplayer or pro mode. assert self.initialplayerinfos is not None if len(self.initialplayerinfos) > 2 or is_pro: bs.timer( 5.0, lambda: self._bots.spawn_bot( ChargerBot, pos=(0, 3, -5), spawn_time=3.0 ), ) if len(self.initialplayerinfos) > 3 or is_pro: bs.timer( 6.0, lambda: self._bots.spawn_bot( ChargerBot, pos=(0, 3, 1), spawn_time=3.0 ), )
# Called for each spawning player.
[docs] @override def spawn_player(self, player: Player) -> bs.Actor: # Let's spawn close to the center. spawn_center = (0, 3, -2) pos = ( spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1], spawn_center[2] + random.uniform(-1.5, 1.5), ) return self.spawn_player_spaz(player, position=pos)
def _check_if_won(self) -> None: # Simply end the game if there's no living bots. # FIXME: Should also make sure all bots have been spawned; # if spawning is spread out enough that we're able to kill # all living bots before the next spawns, it would incorrectly # count as a win. if not self._bots.have_living_bots(): self._won = True self.end_game() # Called for miscellaneous messages.
[docs] @override def handlemessage(self, msg: Any) -> Any: # A player has died. if isinstance(msg, bs.PlayerDiedMessage): super().handlemessage(msg) # Augment standard behavior. self.respawn_player(msg.getplayer(Player)) # A spaz-bot has died. elif isinstance(msg, SpazBotDiedMessage): # Unfortunately the bot-set will always tell us there are living # bots if we ask here (the currently-dying bot isn't officially # marked dead yet) ..so lets push a call into the event loop to # check once this guy has finished dying. bs.pushcall(self._check_if_won) # Let the base class handle anything we don't. else: return super().handlemessage(msg) return None
# When this is called, we should fill out results and end the game # *regardless* of whether is has been won. (this may be called due # to a tournament ending or other external reason).
[docs] @override def end_game(self) -> None: # Stop our on-screen timer so players can see what they got. assert self._timer is not None self._timer.stop() results = bs.GameResults() # If we won, set our score to the elapsed time in milliseconds. # (there should just be 1 team here since this is co-op). # ..if we didn't win, leave scores as default (None) which means # we lost. if self._won: elapsed_time_ms = int((bs.time() - self._timer.starttime) * 1000.0) bs.cameraflash() self._winsound.play() for team in self.teams: for player in team.players: if player.actor: player.actor.handlemessage(bs.CelebrateMessage()) results.set_team_score(team, elapsed_time_ms) # Ends the activity. self.end(results)