Source code for bascenev1lib.game.deathmatch

# Released under the MIT License. See LICENSE for details.
#
"""DeathMatch game and support classes."""

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

from __future__ import annotations

from typing import TYPE_CHECKING, override

import bascenev1 as bs

from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard

if TYPE_CHECKING:
    from typing import Any, Sequence


[docs] class Player(bs.Player['Team']): """Our player type for this game."""
[docs] class Team(bs.Team[Player]): """Our team type for this game.""" def __init__(self) -> None: self.score = 0
# ba_meta export bascenev1.GameActivity
[docs] class DeathMatchGame(bs.TeamGameActivity[Player, Team]): """A game type based on acquiring kills.""" name = 'Death Match' description = 'Kill a set number of enemies to win.' # Print messages when players die since it matters here. announce_player_deaths = True
[docs] @override @classmethod def get_available_settings( cls, sessiontype: type[bs.Session] ) -> list[bs.Setting]: settings = [ bs.IntSetting( 'Kills to Win Per Player', min_value=1, default=5, increment=1, ), bs.IntChoiceSetting( 'Time Limit', choices=[ ('None', 0), ('1 Minute', 60), ('2 Minutes', 120), ('5 Minutes', 300), ('10 Minutes', 600), ('20 Minutes', 1200), ], default=0, ), bs.FloatChoiceSetting( 'Respawn Times', choices=[ ('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0), ('Long', 2.0), ('Longer', 4.0), ], default=1.0, ), bs.BoolSetting('Epic Mode', default=False), ] # In teams mode, a suicide gives a point to the other team, but in # free-for-all it subtracts from your own score. By default we clamp # this at zero to benefit new players, but pro players might like to # be able to go negative. (to avoid a strategy of just # suiciding until you get a good drop) if issubclass(sessiontype, bs.FreeForAllSession): settings.append( bs.BoolSetting('Allow Negative Scores', default=False) ) return settings
[docs] @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: return issubclass(sessiontype, bs.DualTeamSession) or issubclass( sessiontype, bs.FreeForAllSession )
[docs] @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: assert bs.app.classic is not None return bs.app.classic.getmaps('melee')
def __init__(self, settings: dict): super().__init__(settings) self._scoreboard = Scoreboard() self._score_to_win: int | None = None self._dingsound = bs.getsound('dingSmall') self._epic_mode = bool(settings['Epic Mode']) self._kills_to_win_per_player = int(settings['Kills to Win Per Player']) self._time_limit = float(settings['Time Limit']) self._allow_negative_scores = bool( settings.get('Allow Negative Scores', False) ) # Base class overrides. self.slow_motion = self._epic_mode self.default_music = ( bs.MusicType.EPIC if self._epic_mode else bs.MusicType.TO_THE_DEATH )
[docs] @override def get_instance_description(self) -> str | Sequence: return 'Crush ${ARG1} of your enemies.', self._score_to_win
[docs] @override def get_instance_description_short(self) -> str | Sequence: return 'kill ${ARG1} enemies', self._score_to_win
[docs] @override def on_team_join(self, team: Team) -> None: if self.has_begun(): self._update_scoreboard()
[docs] @override def on_begin(self) -> None: super().on_begin() self.setup_standard_time_limit(self._time_limit) self.setup_standard_powerup_drops() # 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), default=0) ) self._update_scoreboard()
[docs] @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): # Augment standard behavior. super().handlemessage(msg) player = msg.getplayer(Player) self.respawn_player(player) killer = msg.getkillerplayer(Player) if killer is None: return None # Handle team-kills. if killer.team is player.team: # In free-for-all, killing yourself loses you a point. if isinstance(self.session, bs.FreeForAllSession): new_score = player.team.score - 1 if not self._allow_negative_scores: new_score = max(0, new_score) player.team.score = new_score # In teams-mode it gives a point to the other team. else: self._dingsound.play() for team in self.teams: if team is not killer.team: team.score += 1 # Killing someone on another team nets a kill. else: killer.team.score += 1 self._dingsound.play() # In FFA show scores since its hard to find on the scoreboard. if isinstance(killer.actor, PlayerSpaz) and killer.actor: killer.actor.set_score_text( str(killer.team.score) + '/' + str(self._score_to_win), color=killer.team.color, flash=True, ) self._update_scoreboard() # If someone has won, set a timer to end shortly. # (allows the dust to clear and draws to occur if deaths are # close enough) assert self._score_to_win is not None if any(team.score >= self._score_to_win for team in self.teams): bs.timer(0.5, self.end_game) else: return super().handlemessage(msg) return None
def _update_scoreboard(self) -> None: for team in self.teams: self._scoreboard.set_team_value( team, team.score, self._score_to_win )
[docs] @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: results.set_team_score(team, team.score) self.end(results=results)