Merge pull request #36 from pranav-1711/public-server

Added new games.
This commit is contained in:
Ayush Saini 2022-04-02 14:05:25 +05:30 committed by GitHub
commit 48d85fa08e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 9965 additions and 0 deletions

206
dist/ba_root/mods/games/Bombers.py vendored Normal file
View file

@ -0,0 +1,206 @@
# ba_meta require api 6
#self._has_boxing_gloves = True
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
import random
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional
class Player(ba.Player['Team']):
"""Our player type for this game."""
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game
class BombersGame(ba.TeamGameActivity[Player, Team]):
"""A game type based on acquiring kills."""
name = 'Bombers'
description = 'Kill Enemies With Bombs'
# Print messages when players die since it matters here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting(
'Kills to Win Per Player',
min_value=1,
default=5,
increment=1,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.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, ba.FreeForAllSession):
settings.append(
ba.BoolSetting('Allow Negative Scores', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_to_win: Optional[int] = None
self._dingsound = ba.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 = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.TO_THE_DEATH)
def get_instance_description(self) -> Union[str, Sequence]:
return 'Crush ${ARG1} of your enemies.', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'kill ${ARG1} enemies', self._score_to_win
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scoreboard()
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
# Base kills needed to win on the size of the largest team.
self._score_to_win = (self._kills_to_win_per_player *
max(1, max(len(t.players) for t in self.teams)))
self._update_scoreboard()
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=False,
enable_jump=True,
enable_bomb=True,
enable_pickup=True)
spaz.bomb_type = random.choice(["impact","ice","normal","sticky"])
spaz.bomb_count = 2
return spaz
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.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, ba.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:
ba.playsound(self._dingsound)
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
ba.playsound(self._dingsound)
# 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):
ba.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 = ba.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

205
dist/ba_root/mods/games/Boxing.py vendored Normal file
View file

@ -0,0 +1,205 @@
# ba_meta require api 6
#self._has_boxing_gloves = True
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional
class Player(ba.Player['Team']):
"""Our player type for this game."""
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game
class BoxingGame(ba.TeamGameActivity[Player, Team]):
"""A game type based on acquiring kills."""
name = 'Boxing'
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[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting(
'Kills to Win Per Player',
min_value=1,
default=5,
increment=1,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.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, ba.FreeForAllSession):
settings.append(
ba.BoolSetting('Allow Negative Scores', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_to_win: Optional[int] = None
self._dingsound = ba.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 = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.TO_THE_DEATH)
def get_instance_description(self) -> Union[str, Sequence]:
return 'Crush ${ARG1} of your enemies.', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'kill ${ARG1} enemies', self._score_to_win
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scoreboard()
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
# Base kills needed to win on the size of the largest team.
self._score_to_win = (self._kills_to_win_per_player *
max(1, max(len(t.players) for t in self.teams)))
self._update_scoreboard()
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=True,
enable_jump=True,
enable_bomb=False,
enable_pickup=True)
spaz.equip_boxing_gloves()
return spaz
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.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, ba.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:
ba.playsound(self._dingsound)
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
ba.playsound(self._dingsound)
# 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):
ba.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 = ba.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

309
dist/ba_root/mods/games/BrainFreeze.py vendored Normal file
View file

@ -0,0 +1,309 @@
# Copyright (c) 2011-2020 Eric Froemling
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# -----------------------------------------------------------------------------
"""Defines a bomb-dodging mini-game."""
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random
from typing import TYPE_CHECKING
import ba
from bastd.actor.bomb import Bomb
from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
from typing import Any, Sequence, Optional, List, Dict, Type, Type
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
super().__init__()
self.death_time: Optional[float] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
# ba_meta export game
class BrainFreezeGame(ba.TeamGameActivity[Player, Team]):
"""Minigame involving dodging falling ice bombs."""
name = 'Brain Freeze'
description = 'Dodge the falling ice bombs.'
available_settings = [ba.BoolSetting('Epic Mode', default=False)]
scoreconfig = ba.ScoreConfig(label='Survived',
scoretype=ba.ScoreType.MILLISECONDS,
version='B')
# Print messages when players die (since its meaningful in this game).
announce_player_deaths = True
# we're currently hard-coded for one map..
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ['Rampage']
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession)
or issubclass(sessiontype, ba.CoopSession))
def __init__(self, settings: dict):
super().__init__(settings)
self._epic_mode = settings.get('Epic Mode', False)
self._last_player_death_time: Optional[float] = None
self._meteor_time = 2.0
self._timer: Optional[OnScreenTimer] = None
# Some base class overrides:
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL)
if self._epic_mode:
self.slow_motion = True
def on_begin(self) -> None:
super().on_begin()
# Drop a wave every few seconds.. and every so often drop the time
# between waves ..lets have things increase faster if we have fewer
# players.
delay = 5.0 if len(self.players) > 2 else 2.5
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._decrement_meteor_time, repeat=True)
# Kick off the first wave in a few seconds.
delay = 3.0
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._set_meteor_timer)
self._timer = OnScreenTimer()
self._timer.start()
# Check for immediate end (if we've only got 1 player, etc).
ba.timer(5.0, self._check_end_game)
def on_player_join(self, player: Player) -> None:
# Don't allow joining after we start
# (would enable leave/rejoin tomfoolery).
if self.has_begun():
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
# For score purposes, mark them as having died right as the
# game started.
assert self._timer is not None
player.death_time = self._timer.getstarttime()
return
self.spawn_player(player)
def on_player_leave(self, player: Player) -> None:
# Augment default behavior.
super().on_player_leave(player)
# A departing player may trigger game-over.
self._check_end_game()
# overriding the default character spawning..
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=False,
enable_bomb=False,
enable_pickup=False)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
return spaz
# Various high-level game events come through this method.
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
curtime = ba.time()
# Record the player's moment of death.
# assert isinstance(msg.spaz.player
msg.getplayer(Player).death_time = curtime
# In co-op mode, end the game the instant everyone dies
# (more accurate looking).
# In teams/ffa, allow a one-second fudge-factor so we can
# get more draws if players die basically at the same time.
if isinstance(self.session, ba.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
ba.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = curtime
else:
ba.timer(1.0, self._check_end_game)
else:
# Default handler:
return super().handlemessage(msg)
return None
def _check_end_game(self) -> None:
living_team_count = 0
for team in self.teams:
for player in team.players:
if player.is_alive():
living_team_count += 1
break
# In co-op, we go till everyone is dead.. otherwise we go
# until one team remains.
if isinstance(self.session, ba.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def _set_meteor_timer(self) -> None:
ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
self._drop_bomb_cluster)
def _drop_bomb_cluster(self) -> None:
# Random note: code like this is a handy way to plot out extents
# and debug things.
loc_test = False
if loc_test:
ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
# Drop several bombs in series.
delay = 0.0
for _i in range(random.randrange(1, 3)):
# Drop them somewhere within our bounds with velocity pointing
# toward the opposite side.
pos = (-7.3 + 15.3 * random.random(), 11,
-5.5 + 2.1 * random.random())
dropdir = (-1.0 if pos[0] > 0 else 1.0)
vel = ((-5.0 + random.random() * 30.0) * dropdir, -4.0, 0)
ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
delay += 0.1
self._set_meteor_timer()
def _drop_bomb(self, position: Sequence[float],
velocity: Sequence[float]) -> None:
Bomb(position=position, velocity=velocity, bomb_type = 'ice').autoretain()
def _decrement_meteor_time(self) -> None:
self._meteor_time = max(0.01, self._meteor_time * 0.9)
def end_game(self) -> None:
cur_time = ba.time()
assert self._timer is not None
start_time = self._timer.getstarttime()
# Mark death-time as now for any still-living players
# and award players points for how long they lasted.
# (these per-player scores are only meaningful in team-games)
for team in self.teams:
for player in team.players:
survived = False
# Throw an extra fudge factor in so teams that
# didn't die come out ahead of teams that did.
if player.death_time is None:
survived = True
player.death_time = cur_time + 1
# Award a per-player score depending on how many seconds
# they lasted (per-player scores only affect teams mode;
# everywhere else just looks at the per-team score).
score = int(player.death_time - self._timer.getstarttime())
if survived:
score += 50 # A bit extra for survivors.
self.stats.player_scored(player, score, screenmessage=False)
# Stop updating our time text, and set the final time to match
# exactly when our last guy died.
self._timer.stop(endtime=self._last_player_death_time)
# Ok now calc game results: set a score for each team and then tell
# the game to end.
results = ba.GameResults()
# Remember that 'free-for-all' mode is simply a special form
# of 'teams' mode where each player gets their own team, so we can
# just always deal in teams and have all cases covered.
for team in self.teams:
# Set the team score to the max time survived by any player on
# that team.
longest_life = 0.0
for player in team.players:
assert player.death_time is not None
longest_life = max(longest_life,
player.death_time - start_time)
# Submit the score value in milliseconds.
results.set_team_score(team, int(1000.0 * longest_life))
self.end(results=results)
# Copyright (c) Lifetime Benefit-Zebra
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this mod without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the mod.
#
# THE MOD IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE MOD OR THE USE OR OTHER DEALINGS IN THE
# MOD.
# --------------------------------------
#By Benefit-Zebra
#https://github.com/Benefit-Zebra

337
dist/ba_root/mods/games/CursedOne16.py vendored Normal file
View file

@ -0,0 +1,337 @@
#Cursed One for 1.6
#Made By your friend: @[Just] Freak#4999
"""Provides the Cursed-One mini-game."""
# ba_meta require api 6
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
from bastd.actor.flag import Flag
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Optional, Sequence, Union
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.chosen_light: Optional[ba.NodeActor] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self, time_remaining: int) -> None:
self.time_remaining = time_remaining
# ba_meta export game
class CursedOneGame(ba.TeamGameActivity[Player, Team]):
"""
Game involving trying to remain the one 'Cursed One'
for a set length of time while everyone else tries to
kill you and become the chosen one themselves.
"""
name = 'Cursed One'
description = ('Be the Cursed One for a length of time to win.\n'
'Kill the Cursed One to become it.')
available_settings = [
ba.IntSetting(
'Cursed One Time',
min_value=10,
default=30,
increment=10,
),
ba.BoolSetting('Cursed One Gets Gloves', default=True),
ba.BoolSetting('Cursed One Gets Shield', default=False),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
]
scoreconfig = ba.ScoreConfig(label='Time Held')
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('keep_away')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._chosen_one_player: Optional[Player] = None
self._swipsound = ba.getsound('swip')
self._countdownsounds: Dict[int, ba.Sound] = {
10: ba.getsound('announceTen'),
9: ba.getsound('announceNine'),
8: ba.getsound('announceEight'),
7: ba.getsound('announceSeven'),
6: ba.getsound('announceSix'),
5: ba.getsound('announceFive'),
4: ba.getsound('announceFour'),
3: ba.getsound('announceThree'),
2: ba.getsound('announceTwo'),
1: ba.getsound('announceOne')
}
self._flag_spawn_pos: Optional[Sequence[float]] = None
self._reset_region_material: Optional[ba.Material] = None
self._flag: Optional[Flag] = None
self._reset_region: Optional[ba.Node] = None
self._epic_mode = bool(settings['Epic Mode'])
self._chosen_one_time = int(settings['Cursed One Time'])
self._time_limit = float(settings['Time Limit'])
self._chosen_one_gets_shield = bool(settings['Cursed One Gets Shield'])
self._chosen_one_gets_gloves = bool(settings['Cursed One Gets Gloves'])
# Base class overrides
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.CHOSEN_ONE)
def get_instance_description(self) -> Union[str, Sequence]:
return 'There can be only one.'
def create_team(self, sessionteam: ba.SessionTeam) -> Team:
return Team(time_remaining=self._chosen_one_time)
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
if self._get_chosen_one_player() is player:
self._set_chosen_one_player(None)
def on_begin(self) -> None:
super().on_begin()
shared = SharedObjects.get()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._flag_spawn_pos = self.map.get_flag_position(None)
Flag.project_stand(self._flag_spawn_pos)
self._set_chosen_one_player(None)
pos = self._flag_spawn_pos
ba.timer(1.0, call=self._tick, repeat=True)
mat = self._reset_region_material = ba.Material()
mat.add_actions(
conditions=(
'they_have_material',
shared.player_material,
),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
('call', 'at_connect',
ba.WeakCall(self._handle_reset_collide)),
),
)
self._reset_region = ba.newnode('region',
attrs={
'position': (pos[0], pos[1] + 0.75,
pos[2]),
'scale': (0.5, 0.5, 0.5),
'type': 'sphere',
'materials': [mat]
})
def _get_chosen_one_player(self) -> Optional[Player]:
# Should never return invalid references; return None in that case.
if self._chosen_one_player:
return self._chosen_one_player
return None
def _handle_reset_collide(self) -> None:
# If we have a chosen one, ignore these.
if self._get_chosen_one_player() is not None:
return
# Attempt to get a Player controlling a Spaz that we hit.
try:
player = ba.getcollision().opposingnode.getdelegate(
PlayerSpaz, True).getplayer(Player, True)
except ba.NotFoundError:
return
if player.is_alive():
self._set_chosen_one_player(player)
def _flash_flag_spawn(self) -> None:
light = ba.newnode('light',
attrs={
'position': self._flag_spawn_pos,
'color': (1, 1, 1),
'radius': 0.3,
'height_attenuated': False
})
ba.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
ba.timer(1.0, light.delete)
def _tick(self) -> None:
# Give the chosen one points.
player = self._get_chosen_one_player()
if player is not None:
# This shouldn't happen, but just in case.
if not player.is_alive():
ba.print_error('got dead player as chosen one in _tick')
self._set_chosen_one_player(None)
else:
scoring_team = player.team
assert self.stats
self.stats.player_scored(player,
3,
screenmessage=False,
display=False)
scoring_team.time_remaining = max(
0, scoring_team.time_remaining - 1)
# Show the count over their head
if scoring_team.time_remaining > 0:
if isinstance(player.actor, PlayerSpaz) and player.actor:
player.actor.set_score_text(
str(scoring_team.time_remaining))
self._update_scoreboard()
# announce numbers we have sounds for
if scoring_team.time_remaining in self._countdownsounds:
ba.playsound(
self._countdownsounds[scoring_team.time_remaining])
# Winner!
if scoring_team.time_remaining <= 0:
self.end_game()
else:
# (player is None)
# This shouldn't happen, but just in case.
# (Chosen-one player ceasing to exist should
# trigger on_player_leave which resets chosen-one)
if self._chosen_one_player is not None:
ba.print_error('got nonexistent player as chosen one in _tick')
self._set_chosen_one_player(None)
def end_game(self) -> None:
results = ba.GameResults()
for team in self.teams:
results.set_team_score(team,
self._chosen_one_time - team.time_remaining)
self.end(results=results, announce_delay=0)
def _set_chosen_one_player(self, player: Optional[Player]) -> None:
existing = self._get_chosen_one_player()
if existing:
existing.chosen_light = None
ba.playsound(self._swipsound)
if not player:
assert self._flag_spawn_pos is not None
self._flag = Flag(color=(1, 0.9, 0.2),
position=self._flag_spawn_pos,
touchable=False)
self._chosen_one_player = None
# Create a light to highlight the flag;
# this will go away when the flag dies.
ba.newnode('light',
owner=self._flag.node,
attrs={
'position': self._flag_spawn_pos,
'intensity': 0.6,
'height_attenuated': False,
'volume_intensity_scale': 0.1,
'radius': 0.1,
'color': (1.2, 1.2, 0.4)
})
# Also an extra momentary flash.
self._flash_flag_spawn()
else:
if player.actor:
self._flag = None
self._chosen_one_player = player
player.actor.handlemessage(ba.PowerupMessage('curse'))
#above 1 line berryyyy importent!
if self._chosen_one_gets_shield:
player.actor.handlemessage(ba.PowerupMessage('shield'))
if self._chosen_one_gets_gloves:
player.actor.handlemessage(ba.PowerupMessage('punch'))
# Use a color that's partway between their team color
# and white.
color = [
0.3 + c * 0.7
for c in ba.normalized_color(player.team.color)
]
light = player.chosen_light = ba.NodeActor(
ba.newnode('light',
attrs={
'intensity': 0.6,
'height_attenuated': False,
'volume_intensity_scale': 0.1,
'radius': 0.13,
'color': color
}))
assert light.node
ba.animate(light.node,
'intensity', {
0: 1.0,
0.2: 0.4,
0.4: 1.0
},
loop=True)
assert isinstance(player.actor, PlayerSpaz)
player.actor.node.connectattr('position', light.node,
'position')
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
if player is self._get_chosen_one_player():
killerplayer = msg.getkillerplayer(Player)
self._set_chosen_one_player(None if (
killerplayer is None or killerplayer is player
or not killerplayer.is_alive()) else killerplayer)
self.respawn_player(player)
else:
super().handlemessage(msg)
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(team,
team.time_remaining,
self._chosen_one_time,
countdown=True)

129
dist/ba_root/mods/games/Explodo_Run.py vendored Normal file
View file

@ -0,0 +1,129 @@
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random
from typing import TYPE_CHECKING
import ba
from bastd.actor.spazbot import SpazBotSet, ExplodeyBot, SpazBotDiedMessage
from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
from typing import Any, Type, Dict, List, Optional
def ba_get_api_version():
return 6
def ba_get_levels():
return [ba._level.Level(
'Explodo Run',
gametype=ExplodoRunGame,
settings={},
preview_texture_name='rampagePreview'),ba._level.Level(
'Epic Explodo Run',
gametype=ExplodoRunGame,
settings={'Epic Mode':True},
preview_texture_name='rampagePreview')]
class Player(ba.Player['Team']):
"""Our player type for this game."""
class Team(ba.Team[Player]):
"""Our team type for this game."""
# ba_meta export game
class ExplodoRunGame(ba.TeamGameActivity[Player, Team]):
name = "Explodo Run"
description = "Run For Your Life :))"
available_settings = [ba.BoolSetting('Epic Mode', default=False)]
scoreconfig = ba.ScoreConfig(label='Time',
scoretype=ba.ScoreType.MILLISECONDS,
lower_is_better=False)
default_music = ba.MusicType.TO_THE_DEATH
def __init__(self, settings:dict):
settings['map'] = "Rampage"
self._epic_mode = settings.get('Epic Mode', False)
if self._epic_mode:
self.slow_motion = True
super().__init__(settings)
self._timer: Optional[OnScreenTimer] = None
self._winsound = ba.getsound('score')
self._won = False
self._bots = SpazBotSet()
self.wave = 1
def on_begin(self) -> None:
super().on_begin()
self._timer = OnScreenTimer()
ba.timer(2.5, self._timer.start)
#Bots Hehe
ba.timer(2.5,self.street)
def street(self):
for a in range(self.wave):
p1 = random.choice([-5,-2.5,0,2.5,5])
p3 = random.choice([-4.5,-4.14,-5,-3])
time = random.choice([1,1.5,2.5,2])
self._bots.spawn_bot(ExplodeyBot, pos=(p1,5.5,p3),spawn_time = time)
self.wave += 1
def botrespawn(self):
if not self._bots.have_living_bots():
self.street()
def handlemessage(self, msg: Any) -> Any:
# A player has died.
if isinstance(msg, ba.PlayerDiedMessage):
super().handlemessage(msg) # Augment standard behavior.
self._won = True
self.end_game()
# A spaz-bot has died.
elif isinstance(msg, SpazBotDiedMessage):
# Unfortunately the bot-set will always tell us there are living
# bots if we ask here (the currently-dying bot isn't officially
# marked dead yet) ..so lets push a call into the event loop to
# check once this guy has finished dying.
ba.pushcall(self.botrespawn)
# Let the base class handle anything we don't.
else:
return super().handlemessage(msg)
return None
# When this is called, we should fill out results and end the game
# *regardless* of whether is has been won. (this may be called due
# to a tournament ending or other external reason).
def end_game(self) -> None:
# Stop our on-screen timer so players can see what they got.
assert self._timer is not None
self._timer.stop()
results = ba.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((ba.time() - self._timer.starttime) * 1000.0)
ba.cameraflash()
ba.playsound(self._winsound)
for team in self.teams:
for player in team.players:
if player.actor:
player.actor.handlemessage(ba.CelebrateMessage())
results.set_team_score(team, elapsed_time_ms)
# Ends the activity.
self.end(results)

339
dist/ba_root/mods/games/FrozenOne16.py vendored Normal file
View file

@ -0,0 +1,339 @@
#Frozen One ported to 1.6
#By your friend: @[Just] Freak#4999
"""Provides the Frozen-One mini-game."""
# ba_meta require api 6
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
from bastd.actor.flag import Flag
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Optional, Sequence, Union
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.chosen_light: Optional[ba.NodeActor] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self, time_remaining: int) -> None:
self.time_remaining = time_remaining
# ba_meta export game
class FrozenOneGame(ba.TeamGameActivity[Player, Team]):
"""
Game involving trying to remain the one 'frozen one'
for a set length of time while everyone else tries to
kill you and become the chosen one themselves.
"""
name = 'Frozen One'
description = ('Be the frozen one for a length of time to win.\n'
'Kill the frozen one to become it.')
available_settings = [
ba.IntSetting(
'Frozen One Time',
min_value=10,
default=30,
increment=10,
),
ba.BoolSetting('Frozen One Gets Gloves', default=True),
ba.BoolSetting('Frozen One Gets Shield', default=False),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
]
scoreconfig = ba.ScoreConfig(label='Time Held')
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('keep_away')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._chosen_one_player: Optional[Player] = None
self._swipsound = ba.getsound('swip')
self._countdownsounds: Dict[int, ba.Sound] = {
10: ba.getsound('announceTen'),
9: ba.getsound('announceNine'),
8: ba.getsound('announceEight'),
7: ba.getsound('announceSeven'),
6: ba.getsound('announceSix'),
5: ba.getsound('announceFive'),
4: ba.getsound('announceFour'),
3: ba.getsound('announceThree'),
2: ba.getsound('announceTwo'),
1: ba.getsound('announceOne')
}
self._flag_spawn_pos: Optional[Sequence[float]] = None
self._reset_region_material: Optional[ba.Material] = None
self._flag: Optional[Flag] = None
self._reset_region: Optional[ba.Node] = None
self._epic_mode = bool(settings['Epic Mode'])
self._chosen_one_time = int(settings['Frozen One Time'])
self._time_limit = float(settings['Time Limit'])
self._chosen_one_gets_shield = bool(settings['Frozen One Gets Shield'])
self._chosen_one_gets_gloves = bool(settings['Frozen One Gets Gloves'])
# Base class overrides
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.CHOSEN_ONE)
def get_instance_description(self) -> Union[str, Sequence]:
return 'There can be only one.'
def create_team(self, sessionteam: ba.SessionTeam) -> Team:
return Team(time_remaining=self._chosen_one_time)
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
if self._get_chosen_one_player() is player:
self._set_chosen_one_player(None)
def on_begin(self) -> None:
super().on_begin()
shared = SharedObjects.get()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._flag_spawn_pos = self.map.get_flag_position(None)
Flag.project_stand(self._flag_spawn_pos)
self._set_chosen_one_player(None)
pos = self._flag_spawn_pos
ba.timer(1.0, call=self._tick, repeat=True)
mat = self._reset_region_material = ba.Material()
mat.add_actions(
conditions=(
'they_have_material',
shared.player_material,
),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
('call', 'at_connect',
ba.WeakCall(self._handle_reset_collide)),
),
)
self._reset_region = ba.newnode('region',
attrs={
'position': (pos[0], pos[1] + 0.75,
pos[2]),
'scale': (0.5, 0.5, 0.5),
'type': 'sphere',
'materials': [mat]
})
def _get_chosen_one_player(self) -> Optional[Player]:
# Should never return invalid references; return None in that case.
if self._chosen_one_player:
return self._chosen_one_player
return None
def _handle_reset_collide(self) -> None:
# If we have a chosen one, ignore these.
if self._get_chosen_one_player() is not None:
return
# Attempt to get a Player controlling a Spaz that we hit.
try:
player = ba.getcollision().opposingnode.getdelegate(
PlayerSpaz, True).getplayer(Player, True)
except ba.NotFoundError:
return
if player.is_alive():
self._set_chosen_one_player(player)
def _flash_flag_spawn(self) -> None:
light = ba.newnode('light',
attrs={
'position': self._flag_spawn_pos,
'color': (1, 1, 1),
'radius': 0.3,
'height_attenuated': False
})
ba.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
ba.timer(1.0, light.delete)
def _tick(self) -> None:
# Give the chosen one points.
player = self._get_chosen_one_player()
if player is not None:
# This shouldn't happen, but just in case.
if not player.is_alive():
ba.print_error('got dead player as chosen one in _tick')
self._set_chosen_one_player(None)
else:
scoring_team = player.team
assert self.stats
self.stats.player_scored(player,
3,
screenmessage=False,
display=False)
scoring_team.time_remaining = max(
0, scoring_team.time_remaining - 1)
# Show the count over their head
if scoring_team.time_remaining > 0:
if isinstance(player.actor, PlayerSpaz) and player.actor:
player.actor.set_score_text(
str(scoring_team.time_remaining))
self._update_scoreboard()
# announce numbers we have sounds for
if scoring_team.time_remaining in self._countdownsounds:
ba.playsound(
self._countdownsounds[scoring_team.time_remaining])
# Winner!
if scoring_team.time_remaining <= 0:
self.end_game()
else:
# (player is None)
# This shouldn't happen, but just in case.
# (Chosen-one player ceasing to exist should
# trigger on_player_leave which resets chosen-one)
if self._chosen_one_player is not None:
ba.print_error('got nonexistent player as chosen one in _tick')
self._set_chosen_one_player(None)
def end_game(self) -> None:
results = ba.GameResults()
for team in self.teams:
results.set_team_score(team,
self._chosen_one_time - team.time_remaining)
self.end(results=results, announce_delay=0)
def _set_chosen_one_player(self, player: Optional[Player]) -> None:
existing = self._get_chosen_one_player()
if existing:
existing.chosen_light = None
ba.playsound(self._swipsound)
if not player:
assert self._flag_spawn_pos is not None
self._flag = Flag(color=(1, 0.9, 0.2),
position=self._flag_spawn_pos,
touchable=False)
self._chosen_one_player = None
# Create a light to highlight the flag;
# this will go away when the flag dies.
ba.newnode('light',
owner=self._flag.node,
attrs={
'position': self._flag_spawn_pos,
'intensity': 0.6,
'height_attenuated': False,
'volume_intensity_scale': 0.1,
'radius': 0.1,
'color': (1.2, 1.2, 0.4)
})
# Also an extra momentary flash.
self._flash_flag_spawn()
else:
if player.actor:
self._flag = None
self._chosen_one_player = player
player.actor.frozen = True
player.actor.node.frozen = 1
#above 2 lines berryyyy importent!
if self._chosen_one_gets_shield:
player.actor.handlemessage(ba.PowerupMessage('shield'))
if self._chosen_one_gets_gloves:
player.actor.handlemessage(ba.PowerupMessage('punch'))
# Use a color that's partway between their team color
# and white.
color = [
0.3 + c * 0.7
for c in ba.normalized_color(player.team.color)
]
light = player.chosen_light = ba.NodeActor(
ba.newnode('light',
attrs={
'intensity': 0.6,
'height_attenuated': False,
'volume_intensity_scale': 0.1,
'radius': 0.13,
'color': color
}))
assert light.node
ba.animate(light.node,
'intensity', {
0: 1.0,
0.2: 0.4,
0.4: 1.0
},
loop=True)
assert isinstance(player.actor, PlayerSpaz)
player.actor.node.connectattr('position', light.node,
'position')
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
if player is self._get_chosen_one_player():
killerplayer = msg.getkillerplayer(Player)
self._set_chosen_one_player(None if (
killerplayer is None or killerplayer is player
or not killerplayer.is_alive()) else killerplayer)
self.respawn_player(player)
else:
super().handlemessage(msg)
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(team,
team.time_remaining,
self._chosen_one_time,
countdown=True)

282
dist/ba_root/mods/games/IcyEmits16.py vendored Normal file
View file

@ -0,0 +1,282 @@
#Icy Emits 1.6
#Made by your friend: @[Just] Freak#4999
"""Defines dodging icybombs mini-game which spawns below your feet"""
# ba_meta require api 6
from __future__ import annotations
import random
from typing import TYPE_CHECKING
import ba
from bastd.actor.bomb import Bomb
from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
from typing import Any, Sequence, Optional, List, Dict, Type, Type
## MoreMinigames.py support ##
randomPic = ['lakeFrigidPreview','hockeyStadiumPreview']
def ba_get_api_version():
return 6
def ba_get_levels():
return [ba._level.Level(
'Icy Emits',gametype=IcyEmitsGame,
settings={},
preview_texture_name = random.choice(randomPic))]
## MoreMinigames.py support ##
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
super().__init__()
self.death_time: Optional[float] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
# ba_meta export game
class IcyEmitsGame(ba.TeamGameActivity[Player, Team]):
"""Minigame involving dodging icy bombs"""
name = 'Icy Emits'
description = 'Beware of the cold platform'
available_settings = [ba.BoolSetting('Epic Mode', default=False)]
scoreconfig = ba.ScoreConfig(label='Survived',
scoretype=ba.ScoreType.MILLISECONDS,
version='B')
# Print messages when players die (since its meaningful in this game).
announce_player_deaths = True
# we're currently hard-coded for one map..
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ['Lake Frigid','Hockey Stadium']
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession)
or issubclass(sessiontype, ba.CoopSession))
def __init__(self, settings: dict):
super().__init__(settings)
self._epic_mode = settings.get('Epic Mode', False)
self._last_player_death_time: Optional[float] = None
self._meteor_time = 2.0
self._timer: Optional[OnScreenTimer] = None
# Some base class overrides:
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL)
if self._epic_mode:
self.slow_motion = True
def on_begin(self) -> None:
super().on_begin()
# Drop a wave every few seconds.. and every so often drop the time
# between waves ..lets have things increase faster if we have fewer
# players.
delay = 5.0 if len(self.players) > 2 else 2.5
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._decrement_meteor_time, repeat=True)
# Kick off the first wave in a few seconds.
delay = 3.0
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._set_meteor_timer)
self._timer = OnScreenTimer()
self._timer.start()
# Check for immediate end (if we've only got 1 player, etc).
ba.timer(5.0, self._check_end_game)
def on_player_join(self, player: Player) -> None:
# Don't allow joining after we start
# (would enable leave/rejoin tomfoolery).
if self.has_begun():
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
# For score purposes, mark them as having died right as the
# game started.
assert self._timer is not None
player.death_time = self._timer.getstarttime()
return
self.spawn_player(player)
def on_player_leave(self, player: Player) -> None:
# Augment default behavior.
super().on_player_leave(player)
# A departing player may trigger game-over.
self._check_end_game()
# overriding the default character spawning..
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=False,
enable_bomb=False,
enable_pickup=False)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
return spaz
# Various high-level game events come through this method.
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
curtime = ba.time()
# Record the player's moment of death.
# assert isinstance(msg.spaz.player
msg.getplayer(Player).death_time = curtime
# In co-op mode, end the game the instant everyone dies
# (more accurate looking).
# In teams/ffa, allow a one-second fudge-factor so we can
# get more draws if players die basically at the same time.
if isinstance(self.session, ba.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
ba.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = curtime
else:
ba.timer(1.0, self._check_end_game)
else:
# Default handler:
return super().handlemessage(msg)
return None
def _check_end_game(self) -> None:
living_team_count = 0
for team in self.teams:
for player in team.players:
if player.is_alive():
living_team_count += 2
break
# In co-op, we go till everyone is dead.. otherwise we go
# until one team remains.
if isinstance(self.session, ba.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def _set_meteor_timer(self) -> None:
ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
self._drop_bomb_cluster)
def _drop_bomb_cluster(self) -> None:
# Random note: code like this is a handy way to plot out extents
# and debug things.
loc_test = False
if loc_test:
ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
# Drop several bombs in series.
delay = 0.0
for _i in range(random.randrange(1, 3)):
# Drop them somewhere within our bounds with velocity pointing
# toward the opposite side.
pos = (-7.3 + 15.3 * random.random(), 5.3,
-5.5 + 2.1 * random.random())
dropdir = (-1.0 if pos[0] > 0 else 1.0)
vel = (0,10,0)
ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
delay += 0.1
self._set_meteor_timer()
def _drop_bomb(self, position: Sequence[float],
velocity: Sequence[float]) -> None:
random_xpositions = [-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10]
random_zpositions = [-5,-4.5,-4,-3.5,-3,-2.5,-2,-1.5,-1,-0.5,0,0.5,1,1.5,2,2.5,3,3.5,4,4.5,5]
bomb_position = (random.choice(random_xpositions), 0.2,random.choice(random_zpositions))
Bomb(position=bomb_position, velocity=velocity, bomb_type = 'ice').autoretain()
def _decrement_meteor_time(self) -> None:
self._meteor_time = max(0.01, self._meteor_time * 0.9)
def end_game(self) -> None:
cur_time = ba.time()
assert self._timer is not None
start_time = self._timer.getstarttime()
# Mark death-time as now for any still-living players
# and award players points for how long they lasted.
# (these per-player scores are only meaningful in team-games)
for team in self.teams:
for player in team.players:
survived = False
# Throw an extra fudge factor in so teams that
# didn't die come out ahead of teams that did.
if player.death_time is None:
survived = True
player.death_time = cur_time + 1
# Award a per-player score depending on how many seconds
# they lasted (per-player scores only affect teams mode;
# everywhere else just looks at the per-team score).
score = int(player.death_time - self._timer.getstarttime())
if survived:
score += 50 # A bit extra for survivors.
self.stats.player_scored(player, score, screenmessage=False)
# Stop updating our time text, and set the final time to match
# exactly when our last guy died.
self._timer.stop(endtime=self._last_player_death_time)
# Ok now calc game results: set a score for each team and then tell
# the game to end.
results = ba.GameResults()
# Remember that 'free-for-all' mode is simply a special form
# of 'teams' mode where each player gets their own team, so we can
# just always deal in teams and have all cases covered.
for team in self.teams:
# Set the team score to the max time survived by any player on
# that team.
longest_life = 0.0
for player in team.players:
assert player.death_time is not None
longest_life = max(longest_life,
player.death_time - start_time)
# Submit the score value in milliseconds.
results.set_team_score(team, int(1000.0 * longest_life))
self.end(results=results)

294
dist/ba_root/mods/games/ImpactTrigger.py vendored Normal file
View file

@ -0,0 +1,294 @@
"""Defines a Impact-Bomb-dodging mini-game."""
# ba_meta require api 6
from __future__ import annotations
import random
from typing import TYPE_CHECKING
import ba
import _ba
from ba._campaign import Campaign
from bastd.actor.bomb import Bomb
from bastd.actor.spaz import Spaz
from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
from typing import Any, Sequence, Optional, List, Dict, Type, Type
def ba_get_api_version():
return 6
def ba_get_levels():
return [ba._level.Level(
'Impact Trigger',
gametype=ImpactTriggerGame,
settings={},
preview_texture_name='rampagePreview'),ba._level.Level(
'Epic Impact Trigger',
gametype=ImpactTriggerGame,
settings={'Epic Mode':True},
preview_texture_name='rampagePreview')]
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
super().__init__()
self.death_time: Optional[float] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
# ba_meta export game
class ImpactTriggerGame(ba.TeamGameActivity[Player, Team]):
"""Minigame involving dodging falling impact bombs."""
name = 'Impact Trigger'
description = 'Dodge With Icy Speed'
available_settings = [ba.BoolSetting('Epic Mode', default=False)]
scoreconfig = ba.ScoreConfig(label='Survived',
scoretype=ba.ScoreType.MILLISECONDS,
version='B')
# Print messages when players die (since its meaningful in this game).
announce_player_deaths = True
# we're currently hard-coded for one map..
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ['Rampage']
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession)
or issubclass(sessiontype, ba.CoopSession))
def __init__(self, settings: dict):
super().__init__(settings)
self._epic_mode = settings.get('Epic Mode', False)
self._last_player_death_time: Optional[float] = None
self._meteor_time = 2.0
self._timer: Optional[OnScreenTimer] = None
# Some base class overrides:
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL)
if self._epic_mode:
self.slow_motion = True
def on_begin(self) -> None:
super().on_begin()
# Drop a wave every few seconds.. and every so often drop the time
# between waves ..lets have things increase faster if we have fewer
# players.
delay = 5.0 if len(self.players) > 2 else 2.5
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._decrement_meteor_time, repeat=True)
# Kick off the first wave in a few seconds.
delay = 3.0
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._set_meteor_timer)
self._timer = OnScreenTimer()
self._timer.start()
# Check for immediate end (if we've only got 1 player, etc).
ba.timer(5.0, self._check_end_game)
def on_player_join(self, player: Player) -> None:
# Don't allow joining after we start
# (would enable leave/rejoin tomfoolery).
if self.has_begun():
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
# For score purposes, mark them as having died right as the
# game started.
assert self._timer is not None
player.death_time = self._timer.getstarttime()
return
self.spawn_player(player)
def on_player_leave(self, player: Player) -> None:
# Augment default behavior.
super().on_player_leave(player)
# A departing player may trigger game-over.
self._check_end_game()
# overriding the default character spawning..
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=False,
enable_bomb=False,
enable_pickup=False)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
#Icy speed is cool right?
spaz.node.hockey = True
return spaz
# Various high-level game events come through this method.
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
curtime = ba.time()
# Record the player's moment of death.
# assert isinstance(msg.spaz.player
msg.getplayer(Player).death_time = curtime
# In co-op mode, end the game the instant everyone dies
# (more accurate looking).
# In teams/ffa, allow a one-second fudge-factor so we can
# get more draws if players die basically at the same time.
if isinstance(self.session, ba.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
ba.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = curtime
else:
ba.timer(1.0, self._check_end_game)
else:
# Default handler:
return super().handlemessage(msg)
return None
def _check_end_game(self) -> None:
living_team_count = 0
for team in self.teams:
for player in team.players:
if player.is_alive():
living_team_count += 1
break
# In co-op, we go till everyone is dead.. otherwise we go
# until one team remains.
if isinstance(self.session, ba.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def _set_meteor_timer(self) -> None:
ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
self._drop_bomb_cluster)
def _drop_bomb_cluster(self) -> None:
# Random note: code like this is a handy way to plot out extents
# and debug things.
loc_test = False
if loc_test:
ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
# Drop several bombs in series.
delay = 0.0
for _i in range(random.randrange(1, 3)):
# Drop them somewhere within our bounds with velocity pointing
# toward the opposite side.
pos = (-7.3 + 15.3 * random.random(), 11,
-5.5 + 2.1 * random.random())
dropdir = (-1.0 if pos[0] > 0 else 1.0)
vel = ((-5.0 + random.random() * 30.0) * dropdir, -4.0, 0)
ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
delay += 0.1
self._set_meteor_timer()
def _drop_bomb(self, position: Sequence[float],
velocity: Sequence[float]) -> None:
Bomb(position=position, velocity=velocity,bomb_type = random.choice(["impact"])).autoretain()
def _decrement_meteor_time(self) -> None:
self._meteor_time = max(0.01, self._meteor_time * 0.9)
def end_game(self) -> None:
cur_time = ba.time()
assert self._timer is not None
start_time = self._timer.getstarttime()
# Mark death-time as now for any still-living players
# and award players points for how long they lasted.
# (these per-player scores are only meaningful in team-games)
for team in self.teams:
for player in team.players:
survived = False
# Throw an extra fudge factor in so teams that
# didn't die come out ahead of teams that did.
if player.death_time is None:
survived = True
player.death_time = cur_time + 1
# Award a per-player score depending on how many seconds
# they lasted (per-player scores only affect teams mode;
# everywhere else just looks at the per-team score).
score = int(player.death_time - self._timer.getstarttime())
if survived:
score += 50 # A bit extra for survivors.
self.stats.player_scored(player, score, screenmessage=False)
# Stop updating our time text, and set the final time to match
# exactly when our last guy died.
self._timer.stop(endtime=self._last_player_death_time)
# Ok now calc game results: set a score for each team and then tell
# the game to end.
results = ba.GameResults()
# Remember that 'free-for-all' mode is simply a special form
# of 'teams' mode where each player gets their own team, so we can
# just always deal in teams and have all cases covered.
for team in self.teams:
# Set the team score to the max time survived by any player on
# that team.
longest_life = 0.0
for player in team.players:
assert player.death_time is not None
longest_life = max(longest_life,
player.death_time - start_time)
# Submit the score value in milliseconds.
results.set_team_score(team, int(1000.0 * longest_life))
self.end(results=results)
"""
from ba import _level
campaign = Campaign('Challenges', sequential=False)
campaign.addlevel(
_level.Level(name='Impact Trigger',
displayname='Sti',
gametype=impactTriggerGame,
settings={},
preview_texture_name='rampagePreview'))
ba._campaign.register_campaign(campaign)
"""

528
dist/ba_root/mods/games/Infection.py vendored Normal file
View file

@ -0,0 +1,528 @@
"""New Duel / Created by: byANG3L"""
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
import _ba
import random
from bastd.actor.bomb import Bomb
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
pass
lang = ba.app.lang.language
if lang == 'Spanish':
name = 'Infección'
description = '¡Se está extendiendo!'
instance_description = '¡Evite la propagación!'
mines = 'Minas'
enable_bombs = 'Habilitar Bombas'
extra_mines = 'Seg/Mina Extra'
max_infected_size = 'Tamaño Máx. de Infección'
max_size_increases = 'Incrementar Tamaño Cada'
infection_spread_rate = 'Velocidad de Infección'
faster = 'Muy Rápido'
fast = 'Rápido'
normal = 'Normal'
slow = 'Lento'
slowest = 'Muy Lento'
insane = 'Insano'
else:
name = 'Infection'
description = "It's spreading!"
instance_description = 'Avoid the spread!'
mines = 'Mines'
enable_bombs = 'Enable Bombs'
extra_mines = 'Sec/Extra Mine'
max_infected_size = 'Max Infected Size'
max_size_increases = 'Max Size Increases Every'
infection_spread_rate = 'Infection Spread Rate'
faster = 'Faster'
fast = 'Fast'
normal = 'Normal'
slow = 'Slow'
slowest = 'Slowest'
insane = 'Insane'
def ba_get_api_version():
return 6
def ba_get_levels():
return [ba._level.Level(
name,
gametype=Infection,
settings={},
preview_texture_name='footballStadiumPreview')]
class PlayerSpaz_Infection(PlayerSpaz):
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.HitMessage):
if not self.node:
return True
if msg._source_player != self.getplayer(Player):
return True
else:
super().handlemessage(msg)
else:
super().handlemessage(msg)
class myMine(Bomb):
#reason for the mine class is so we can add the death zone
def __init__(self,
pos: Sequence[float] = (0.0, 1.0, 0.0)):
Bomb.__init__(self, position=pos, bomb_type='land_mine')
showInSpace = False
self.died = False
self.rad = 0.3
self.zone = ba.newnode(
'locator',
attrs={
'shape':'circle',
'position':self.node.position,
'color':(1,0,0),
'opacity':0.5,
'draw_beauty':showInSpace,
'additive':True})
ba.animate_array(
self.zone,
'size',
1,
{0:[0.0], 0.05:[2*self.rad]})
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
if not self.died:
self.getactivity().mine_count -= 1
self.died = True
ba.animate_array(
self.zone,
'size',
1,
{0:[2*self.rad], 0.05:[0]})
self.zone = None
super().handlemessage(msg)
else:
super().handlemessage(msg)
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.survival_seconds: Optional[int] = None
self.death_time: Optional[float] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
# ba_meta export game
class Infection(ba.TeamGameActivity[Player, Team]):
"""A game type based on acquiring kills."""
name = name
description = description
# Print messages when players die since it matters here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting(
mines,
min_value=5,
default=10,
increment=5,
),
ba.BoolSetting(enable_bombs, default=True),
ba.IntSetting(
extra_mines,
min_value=1,
default=10,
increment=1,
),
ba.IntSetting(
max_infected_size,
min_value=4,
default=6,
increment=1,
),
ba.IntChoiceSetting(
max_size_increases,
choices=[
('10s', 10),
('20s', 20),
('30s',30),
('1 Minute', 60),
],
default=20,
),
ba.IntChoiceSetting(
infection_spread_rate,
choices=[
(slowest, 0.01),
(slow, 0.02),
(normal, 0.03),
(fast, 0.04),
(faster, 0.05),
(insane, 0.08),
],
default=0.03,
),
ba.BoolSetting('Epic Mode', default=False),
]
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.CoopSession)
or issubclass(sessiontype, ba.MultiTeamSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ['Doom Shroom', 'Rampage', 'Hockey Stadium',
'Crag Castle', 'Big G', 'Football Stadium']
def __init__(self, settings: dict):
super().__init__(settings)
self.mines: List = []
self._update_rate = 0.1
self._last_player_death_time = None
self._start_time: Optional[float] = None
self._timer: Optional[OnScreenTimer] = None
self._epic_mode = bool(settings['Epic Mode'])
self._max_mines = int(settings[mines])
self._extra_mines = int(settings[extra_mines])
self._enable_bombs = bool(settings[enable_bombs])
self._max_size = int(settings[max_infected_size])
self._max_size_increases = float(settings[max_size_increases])
self._growth_rate = float(settings[infection_spread_rate])
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.SURVIVAL)
def get_instance_description(self) -> Union[str, Sequence]:
return instance_description
def get_instance_description_short(self) -> Union[str, Sequence]:
return instance_description
def on_begin(self) -> None:
super().on_begin()
self._start_time = ba.time()
self.mine_count = 0
ba.timer(self._update_rate,
ba.WeakCall(self._mine_update),
repeat=True)
ba.timer(self._max_size_increases*1.0,
ba.WeakCall(self._max_size_update),
repeat=True)
ba.timer(self._extra_mines*1.0,
ba.WeakCall(self._max_mine_update),
repeat=True)
self._timer = OnScreenTimer()
self._timer.start()
# Check for immediate end (if we've only got 1 player, etc).
ba.timer(5.0, self._check_end_game)
def on_player_join(self, player: Player) -> None:
if self.has_begun():
assert self._timer is not None
player.survival_seconds = self._timer.getstarttime()
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
self.spawn_player(player)
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
self._check_end_game()
def _max_mine_update(self) -> None:
self._max_mines += 1
def _max_size_update(self) -> None:
self._max_size += 1
def _mine_update(self) -> None:
# print self.mineCount
# purge dead mines, update their animantion, check if players died
for m in self.mines:
if not m.node:
self.mines.remove(m)
else:
#First, check if any player is within the current death zone
for player in self.players:
if not player.actor is None:
if player.actor.is_alive():
p1 = player.actor.node.position
p2 = m.node.position
diff = (ba.Vec3(p1[0]-p2[0],
0.0,
p1[2]-p2[2]))
dist = (diff.length())
if dist < m.rad:
player.actor.handlemessage(ba.DieMessage())
#Now tell the circle to grow to the new size
if m.rad < self._max_size:
ba.animate_array(
m.zone, 'size' ,1,
{0:[m.rad*2],
self._update_rate:[(m.rad+self._growth_rate)*2]})
# Tell the circle to be the new size.
# This will be the new check radius next time.
m.rad += self._growth_rate
#make a new mine if needed.
if self.mine_count < self._max_mines:
pos = self.getRandomPowerupPoint()
self.mine_count += 1
self._flash_mine(pos)
ba.timer(0.95, ba.Call(self._make_mine, pos))
def _make_mine(self,posn: Sequence[float]) -> None:
m = myMine(pos=posn)
m.arm()
self.mines.append(m)
def _flash_mine(self, pos: Sequence[float]) -> None:
light = ba.newnode('light',
attrs={
'position': pos,
'color': (1, 0.2, 0.2),
'radius': 0.1,
'height_attenuated': False
})
ba.animate(light, 'intensity', {0.0: 0, 0.1: 1.0, 0.2: 0}, loop=True)
ba.timer(1.0, light.delete)
def end_game(self) -> None:
results = ba.GameResults()
for team in self.teams:
results.set_team_score(team, int(team.survival_seconds))
self.end(results=results, announce_delay=0.8)
def _flash_player(self, player: Player, scale: float) -> None:
assert isinstance(player.actor, PlayerSpaz)
assert player.actor.node
pos = player.actor.node.position
light = ba.newnode('light',
attrs={
'position': pos,
'color': (1, 1, 0),
'height_attenuated': False,
'radius': 0.4
})
ba.timer(0.5, light.delete)
ba.animate(light, 'intensity', {0: 0, 0.1: 1.0 * scale, 0.5: 0})
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
death_time = ba.time()
msg.getplayer(Player).death_time = death_time
if isinstance(self.session, ba.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
ba.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = death_time
else:
ba.timer(1.0, self._check_end_game)
else:
# Default handler:
return super().handlemessage(msg)
return None
def _check_end_game(self) -> None:
living_team_count = 0
for team in self.teams:
for player in team.players:
if player.is_alive():
living_team_count += 1
break
# In co-op, we go till everyone is dead.. otherwise we go
# until one team remains.
if isinstance(self.session, ba.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def spawn_player(self, player: PlayerType) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=False,
enable_bomb=self._enable_bombs,
enable_pickup=False)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
return spaz
def spawn_player_spaz(self,
player: PlayerType,
position: Sequence[float] = (0, 0, 0),
angle: float = None) -> PlayerSpaz:
"""Create and wire up a ba.PlayerSpaz for the provided ba.Player."""
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
position = self.map.get_ffa_start_position(self.players)
name = player.getname()
color = player.color
highlight = player.highlight
light_color = ba._math.normalized_color(color)
display_color = _ba.safecolor(color, target_intensity=0.75)
spaz = PlayerSpaz_Infection(color=color,
highlight=highlight,
character=player.character,
player=player)
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, ba.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()
# Move to the stand position and add a flash of light.
spaz.handlemessage(
ba.StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
_ba.playsound(self._spawn_sound, 1, position=spaz.node.position)
light = _ba.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
_ba.timer(0.5, light.delete)
return spaz
def getRandomPowerupPoint(self) -> None:
#So far, randomized points only figured out for mostly rectangular maps.
#Boxes will still fall through holes, but shouldn't be terrible problem (hopefully)
#If you add stuff here, need to add to "supported maps" above.
#['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium']
myMap = self.map.getname()
#print(myMap)
if myMap == 'Doom Shroom':
while True:
x = random.uniform(-1.0,1.0)
y = random.uniform(-1.0,1.0)
if x*x+y*y < 1.0: break
return ((8.0*x,2.5,-3.5+5.0*y))
elif myMap == 'Rampage':
x = random.uniform(-6.0,7.0)
y = random.uniform(-6.0,-2.5)
return ((x, 5.2, y))
elif myMap == 'Hockey Stadium':
x = random.uniform(-11.5,11.5)
y = random.uniform(-4.5,4.5)
return ((x, 0.2, y))
elif myMap == 'Courtyard':
x = random.uniform(-4.3,4.3)
y = random.uniform(-4.4,0.3)
return ((x, 3.0, y))
elif myMap == 'Crag Castle':
x = random.uniform(-6.7,8.0)
y = random.uniform(-6.0,0.0)
return ((x, 10.0, y))
elif myMap == 'Big G':
x = random.uniform(-8.7,8.0)
y = random.uniform(-7.5,6.5)
return ((x, 3.5, y))
elif myMap == 'Football Stadium':
x = random.uniform(-12.5,12.5)
y = random.uniform(-5.0,5.5)
return ((x, 0.32, y))
else:
x = random.uniform(-5.0,5.0)
y = random.uniform(-6.0,0.0)
return ((x, 8.0, y))
def end_game(self) -> None:
cur_time = ba.time()
assert self._timer is not None
start_time = self._timer.getstarttime()
# Mark death-time as now for any still-living players
# and award players points for how long they lasted.
# (these per-player scores are only meaningful in team-games)
for team in self.teams:
for player in team.players:
survived = False
# Throw an extra fudge factor in so teams that
# didn't die come out ahead of teams that did.
if player.death_time is None:
survived = True
player.death_time = cur_time + 1
# Award a per-player score depending on how many seconds
# they lasted (per-player scores only affect teams mode;
# everywhere else just looks at the per-team score).
score = int(player.death_time - self._timer.getstarttime())
if survived:
score += 50 # A bit extra for survivors.
self.stats.player_scored(player, score, screenmessage=False)
# Stop updating our time text, and set the final time to match
# exactly when our last guy died.
self._timer.stop(endtime=self._last_player_death_time)
# Ok now calc game results: set a score for each team and then tell
# the game to end.
results = ba.GameResults()
# Remember that 'free-for-all' mode is simply a special form
# of 'teams' mode where each player gets their own team, so we can
# just always deal in teams and have all cases covered.
for team in self.teams:
# Set the team score to the max time survived by any player on
# that team.
longest_life = 0.0
for player in team.players:
assert player.death_time is not None
longest_life = max(longest_life,
player.death_time - start_time)
# Submit the score value in milliseconds.
results.set_team_score(team, int(1000.0 * longest_life))
self.end(results=results)

155
dist/ba_root/mods/games/Lame_Fight.py vendored Normal file
View file

@ -0,0 +1,155 @@
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random
from typing import TYPE_CHECKING
import ba
from bastd.actor.spazbot import SpazBotSet, ChargerBot, BrawlerBotProShielded, TriggerBotProShielded, ExplodeyBot, BomberBotProShielded, SpazBotDiedMessage
from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
from typing import Any, Type, Dict, List, Optional
def ba_get_api_version():
return 6
def ba_get_levels():
return [ba._level.Level(
'Lame Fight',
gametype=LameFightGame,
settings={},
preview_texture_name='courtyardPreview')]
class Player(ba.Player['Team']):
"""Our player type for this game."""
class Team(ba.Team[Player]):
"""Our team type for this game."""
# ba_meta export game
class LameFightGame(ba.TeamGameActivity[Player, Team]):
name = "Lame Fight"
description = "Save World With Super Powers"
slow_motion = True
scoreconfig = ba.ScoreConfig(label='Time',
scoretype=ba.ScoreType.MILLISECONDS,
lower_is_better=True)
default_music = ba.MusicType.TO_THE_DEATH
def __init__(self, settings:dict):
settings['map'] = "Courtyard"
super().__init__(settings)
self._timer: Optional[OnScreenTimer] = None
self._winsound = ba.getsound('score')
self._won = False
self._bots = SpazBotSet()
def on_begin(self) -> None:
super().on_begin()
self._timer = OnScreenTimer()
ba.timer(4.0, self._timer.start)
#Bots Hehe
ba.timer(1.0, lambda: self._bots.spawn_bot(ChargerBot, pos=(3,3,-2),spawn_time = 3.0))
ba.timer(1.0, lambda: self._bots.spawn_bot(ChargerBot, pos=(-3,3,-2),spawn_time = 3.0))
ba.timer(1.0, lambda: self._bots.spawn_bot(ChargerBot, pos=(5,3,-2),spawn_time = 3.0))
ba.timer(1.0, lambda: self._bots.spawn_bot(ChargerBot, pos=(-5,3,-2),spawn_time = 3.0))
ba.timer(1.0, lambda: self._bots.spawn_bot(ChargerBot, pos=(0,3,1),spawn_time = 3.0))
ba.timer(1.0, lambda: self._bots.spawn_bot(ChargerBot, pos=(0,3,-5),spawn_time = 3.0))
ba.timer(9.0, lambda: self._bots.spawn_bot(BomberBotProShielded, pos=(-7,5,-7.5),spawn_time = 3.0))
ba.timer(9.0, lambda: self._bots.spawn_bot(BomberBotProShielded, pos=(7,5,-7.5),spawn_time = 3.0))
ba.timer(9.0, lambda: self._bots.spawn_bot(BomberBotProShielded, pos=(7,5,1.5),spawn_time = 3.0))
ba.timer(9.0, lambda: self._bots.spawn_bot(BomberBotProShielded, pos=(-7,5,1.5),spawn_time = 3.0))
ba.timer(12.0, lambda: self._bots.spawn_bot(TriggerBotProShielded, pos=(-1,7,-8),spawn_time = 3.0))
ba.timer(12.0, lambda: self._bots.spawn_bot(TriggerBotProShielded, pos=(1,7,-8),spawn_time = 3.0))
ba.timer(15.0, lambda: self._bots.spawn_bot(ExplodeyBot, pos=(0,3,-5),spawn_time = 3.0))
ba.timer(20.0, lambda: self._bots.spawn_bot(ExplodeyBot, pos=(0,3,1),spawn_time = 3.0))
ba.timer(20.0, lambda: self._bots.spawn_bot(ExplodeyBot, pos=(-5,3,-2),spawn_time = 3.0))
ba.timer(20.0, lambda: self._bots.spawn_bot(ExplodeyBot, pos=(5,3,-2),spawn_time = 3.0))
ba.timer(30,self.street)
def street(self):
ba.screenmessage("Lame Guys Are Here!",color = (1,0,0))
for a in range(-1,2):
for b in range(-3,0):
self._bots.spawn_bot(BrawlerBotProShielded, pos=(a,3,b),spawn_time = 3.0)
def spawn_player(self, player: Player) -> ba.Actor:
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))
spaz = self.spawn_player_spaz(player,position = pos)
p = ["Bigger Blast","Stronger Punch","Shield","Speed"]
Power = random.choice(p)
spaz.bomb_type = random.choice(["normal","sticky","ice","impact","normal","ice","sticky"])
ba.screenmessage(f"Now You Have {Power}")
if Power == p[0]:
spaz.bomb_count = 3
spaz.blast_radius = 2.5
if Power == p[1]:
spaz._punch_cooldown = 350
spaz._punch_power_scale = 2.0
if Power == p[2]:
spaz.equip_shields()
if Power == p[3]:
spaz.node.hockey = True
return spaz
def _check_if_won(self) -> None:
if not self._bots.have_living_bots():
self._won = True
self.end_game()
def handlemessage(self, msg: Any) -> Any:
# A player has died.
if isinstance(msg, ba.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.
ba.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).
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 = ba.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((ba.time() - self._timer.starttime) * 1000.0)
ba.cameraflash()
ba.playsound(self._winsound)
for team in self.teams:
for player in team.players:
if player.actor:
player.actor.handlemessage(ba.CelebrateMessage())
results.set_team_score(team, elapsed_time_ms)
# Ends the activity.
self.end(results)

1000
dist/ba_root/mods/games/MemoryGame.py vendored Normal file

File diff suppressed because it is too large Load diff

949
dist/ba_root/mods/games/SquidRace.py vendored Normal file
View file

@ -0,0 +1,949 @@
# Released under the MIT License. See LICENSE for details.
#
"""Defines Race mini-game."""
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from dataclasses import dataclass
import ba
from bastd.actor.bomb import Bomb, Blast, ExplodeHitMessage
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import (Any, Type, Tuple, List, Sequence, Optional, Dict,
Union)
from bastd.actor.onscreentimer import OnScreenTimer
class NewBlast(Blast):
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ExplodeHitMessage):
pass
else:
return super().handlemessage(msg)
@dataclass
class RaceMine:
"""Holds info about a mine on the track."""
point: Sequence[float]
mine: Optional[Bomb]
class RaceRegion(ba.Actor):
"""Region used to track progress during a race."""
def __init__(self, pt: Sequence[float], index: int):
super().__init__()
activity = self.activity
assert isinstance(activity, RaceGame)
self.pos = pt
self.index = index
self.node = ba.newnode(
'region',
delegate=self,
attrs={
'position': pt[:3],
'scale': (pt[3] * 2.0, pt[4] * 2.0, pt[5] * 2.0),
'type': 'box',
'materials': [activity.race_region_material]
})
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.distance_txt: Optional[ba.Node] = None
self.last_region = 0
self.lap = 0
self.distance = 0.0
self.finished = False
self.rank: Optional[int] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.time: Optional[float] = None
self.lap = 0
self.finished = False
# ba_meta export game
class SquidRaceGame(ba.TeamGameActivity[Player, Team]):
"""Game of racing around a track."""
name = 'Squid Race'
description = 'Run real fast!'
scoreconfig = ba.ScoreConfig(label='Time',
lower_is_better=True,
scoretype=ba.ScoreType.MILLISECONDS)
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting('Laps', min_value=1, default=3, increment=1),
ba.IntChoiceSetting(
'Time Limit',
default=0,
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
),
ba.IntChoiceSetting(
'Mine Spawning',
default=4000,
choices=[
('No Mines', 0),
('8 Seconds', 8000),
('4 Seconds', 4000),
('2 Seconds', 2000),
],
),
ba.IntChoiceSetting(
'Bomb Spawning',
choices=[
('None', 0),
('8 Seconds', 8000),
('4 Seconds', 4000),
('2 Seconds', 2000),
('1 Second', 1000),
],
default=2000,
),
ba.BoolSetting('Epic Mode', default=False),
]
# We have some specific settings in teams mode.
if issubclass(sessiontype, ba.DualTeamSession):
settings.append(
ba.BoolSetting('Entire Team Must Finish', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return issubclass(sessiontype, ba.MultiTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('race')
def __init__(self, settings: dict):
self._race_started = False
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_sound = ba.getsound('score')
self._swipsound = ba.getsound('swip')
self._last_team_time: Optional[float] = None
self._front_race_region: Optional[int] = None
self._nub_tex = ba.gettexture('nub')
self._beep_1_sound = ba.getsound('raceBeep1')
self._beep_2_sound = ba.getsound('raceBeep2')
self.race_region_material: Optional[ba.Material] = None
self._regions: List[RaceRegion] = []
self._team_finish_pts: Optional[int] = None
self._time_text: Optional[ba.Actor] = None
self._timer: Optional[OnScreenTimer] = None
self._race_mines: Optional[List[RaceMine]] = None
self._race_mine_timer: Optional[ba.Timer] = None
self._scoreboard_timer: Optional[ba.Timer] = None
self._player_order_update_timer: Optional[ba.Timer] = None
self._start_lights: Optional[List[ba.Node]] = None
self._squid_lights: Optional[List[ba.Node]] = None
self._countdown_timer: int = 0
self._sq_mode: str = 'Easy'
self._tick_timer: Optional[ba.Timer] = None
self._bomb_spawn_timer: Optional[ba.Timer] = None
self._laps = int(settings['Laps'])
self._entire_team_must_finish = bool(
settings.get('Entire Team Must Finish', False))
self._time_limit = float(settings['Time Limit'])
self._mine_spawning = int(settings['Mine Spawning'])
self._bomb_spawning = int(settings['Bomb Spawning'])
self._epic_mode = bool(settings['Epic Mode'])
self._countdownsounds = {
10: ba.getsound('announceTen'),
9: ba.getsound('announceNine'),
8: ba.getsound('announceEight'),
7: ba.getsound('announceSeven'),
6: ba.getsound('announceSix'),
5: ba.getsound('announceFive'),
4: ba.getsound('announceFour'),
3: ba.getsound('announceThree'),
2: ba.getsound('announceTwo'),
1: ba.getsound('announceOne')
}
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC_RACE
if self._epic_mode else ba.MusicType.RACE)
def get_instance_description(self) -> Union[str, Sequence]:
if (isinstance(self.session, ba.DualTeamSession)
and self._entire_team_must_finish):
t_str = ' Your entire team has to finish.'
else:
t_str = ''
if self._laps > 1:
return 'Run ${ARG1} laps.' + t_str, self._laps
return 'Run 1 lap.' + t_str
def get_instance_description_short(self) -> Union[str, Sequence]:
if self._laps > 1:
return 'run ${ARG1} laps', self._laps
return 'run 1 lap'
def on_transition_in(self) -> None:
super().on_transition_in()
shared = SharedObjects.get()
pts = self.map.get_def_points('race_point')
mat = self.race_region_material = ba.Material()
mat.add_actions(conditions=('they_have_material',
shared.player_material),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
('call', 'at_connect',
self._handle_race_point_collide),
))
for rpt in pts:
self._regions.append(RaceRegion(rpt, len(self._regions)))
def _flash_player(self, player: Player, scale: float) -> None:
assert isinstance(player.actor, PlayerSpaz)
assert player.actor.node
pos = player.actor.node.position
light = ba.newnode('light',
attrs={
'position': pos,
'color': (1, 1, 0),
'height_attenuated': False,
'radius': 0.4
})
ba.timer(0.5, light.delete)
ba.animate(light, 'intensity', {0: 0, 0.1: 1.0 * scale, 0.5: 0})
def _handle_race_point_collide(self) -> None:
# FIXME: Tidy this up.
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-nested-blocks
collision = ba.getcollision()
try:
region = collision.sourcenode.getdelegate(RaceRegion, True)
player = collision.opposingnode.getdelegate(PlayerSpaz,
True).getplayer(
Player, True)
except ba.NotFoundError:
return
last_region = player.last_region
this_region = region.index
if last_region != this_region:
# If a player tries to skip regions, smite them.
# Allow a one region leeway though (its plausible players can get
# blown over a region, etc).
if this_region > last_region + 2:
if player.is_alive():
assert player.actor
player.actor.handlemessage(ba.DieMessage())
ba.screenmessage(ba.Lstr(
translate=('statements', 'Killing ${NAME} for'
' skipping part of the track!'),
subs=[('${NAME}', player.getname(full=True))]),
color=(1, 0, 0))
else:
# If this player is in first, note that this is the
# front-most race-point.
if player.rank == 0:
self._front_race_region = this_region
player.last_region = this_region
if last_region >= len(self._regions) - 2 and this_region == 0:
team = player.team
player.lap = min(self._laps, player.lap + 1)
# In teams mode with all-must-finish on, the team lap
# value is the min of all team players.
# Otherwise its the max.
if isinstance(self.session, ba.DualTeamSession
) and self._entire_team_must_finish:
team.lap = min([p.lap for p in team.players])
else:
team.lap = max([p.lap for p in team.players])
# A player is finishing.
if player.lap == self._laps:
# In teams mode, hand out points based on the order
# players come in.
if isinstance(self.session, ba.DualTeamSession):
assert self._team_finish_pts is not None
if self._team_finish_pts > 0:
self.stats.player_scored(player,
self._team_finish_pts,
screenmessage=False)
self._team_finish_pts -= 25
# Flash where the player is.
self._flash_player(player, 1.0)
player.finished = True
assert player.actor
player.actor.handlemessage(
ba.DieMessage(immediate=True))
# Makes sure noone behind them passes them in rank
# while finishing.
player.distance = 9999.0
# If the whole team has finished the race.
if team.lap == self._laps:
ba.playsound(self._score_sound)
player.team.finished = True
assert self._timer is not None
elapsed = ba.time() - self._timer.getstarttime()
self._last_team_time = player.team.time = elapsed
# Team has yet to finish.
else:
ba.playsound(self._swipsound)
# They've just finished a lap but not the race.
else:
ba.playsound(self._swipsound)
self._flash_player(player, 0.3)
# Print their lap number over their head.
try:
assert isinstance(player.actor, PlayerSpaz)
mathnode = ba.newnode('math',
owner=player.actor.node,
attrs={
'input1': (0, 1.9, 0),
'operation': 'add'
})
player.actor.node.connectattr(
'torso_position', mathnode, 'input2')
tstr = ba.Lstr(resource='lapNumberText',
subs=[('${CURRENT}',
str(player.lap + 1)),
('${TOTAL}', str(self._laps))
])
txtnode = ba.newnode('text',
owner=mathnode,
attrs={
'text': tstr,
'in_world': True,
'color': (1, 1, 0, 1),
'scale': 0.015,
'h_align': 'center'
})
mathnode.connectattr('output', txtnode, 'position')
ba.animate(txtnode, 'scale', {
0.0: 0,
0.2: 0.019,
2.0: 0.019,
2.2: 0
})
ba.timer(2.3, mathnode.delete)
except Exception:
ba.print_exception('Error printing lap.')
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
def on_player_join(self, player: Player) -> None:
# Don't allow joining after we start
# (would enable leave/rejoin tomfoolery).
if self.has_begun():
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
self.spawn_player(player)
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
# A player leaving disqualifies the team if 'Entire Team Must Finish'
# is on (otherwise in teams mode everyone could just leave except the
# leading player to win).
if (isinstance(self.session, ba.DualTeamSession)
and self._entire_team_must_finish):
ba.screenmessage(ba.Lstr(
translate=('statements',
'${TEAM} is disqualified because ${PLAYER} left'),
subs=[('${TEAM}', player.team.name),
('${PLAYER}', player.getname(full=True))]),
color=(1, 1, 0))
player.team.finished = True
player.team.time = None
player.team.lap = 0
ba.playsound(ba.getsound('boo'))
for otherplayer in player.team.players:
otherplayer.lap = 0
otherplayer.finished = True
try:
if otherplayer.actor is not None:
otherplayer.actor.handlemessage(ba.DieMessage())
except Exception:
ba.print_exception('Error sending DieMessage.')
# Defer so team/player lists will be updated.
ba.pushcall(self._check_end_game)
def _update_scoreboard(self) -> None:
for team in self.teams:
distances = [player.distance for player in team.players]
if not distances:
teams_dist = 0.0
else:
if (isinstance(self.session, ba.DualTeamSession)
and self._entire_team_must_finish):
teams_dist = min(distances)
else:
teams_dist = max(distances)
self._scoreboard.set_team_value(
team,
teams_dist,
self._laps,
flash=(teams_dist >= float(self._laps)),
show_value=False)
def on_begin(self) -> None:
from bastd.actor.onscreentimer import OnScreenTimer
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._team_finish_pts = 100
# Throw a timer up on-screen.
self._time_text = ba.NodeActor(
ba.newnode('text',
attrs={
'v_attach': 'top',
'h_attach': 'center',
'h_align': 'center',
'color': (1, 1, 0.5, 1),
'flatness': 0.5,
'shadow': 0.5,
'position': (0, -50),
'scale': 1.4,
'text': ''
}))
self._timer = OnScreenTimer()
if self._mine_spawning != 0:
self._race_mines = [
RaceMine(point=p, mine=None)
for p in self.map.get_def_points('race_mine')
]
if self._race_mines:
self._race_mine_timer = ba.Timer(0.001 * self._mine_spawning,
self._update_race_mine,
repeat=True)
self._scoreboard_timer = ba.Timer(0.25,
self._update_scoreboard,
repeat=True)
self._player_order_update_timer = ba.Timer(0.25,
self._update_player_order,
repeat=True)
if self.slow_motion:
t_scale = 0.4
light_y = 50
else:
t_scale = 1.0
light_y = 150
lstart = 7.1 * t_scale
inc = 1.25 * t_scale
ba.timer(lstart, self._do_light_1)
ba.timer(lstart + inc, self._do_light_2)
ba.timer(lstart + 2 * inc, self._do_light_3)
ba.timer(lstart + 3 * inc, self._start_race)
self._start_lights = []
for i in range(4):
lnub = ba.newnode('image',
attrs={
'texture': ba.gettexture('nub'),
'opacity': 1.0,
'absolute_scale': True,
'position': (-75 + i * 50, light_y),
'scale': (50, 50),
'attach': 'center'
})
ba.animate(
lnub, 'opacity', {
4.0 * t_scale: 0,
5.0 * t_scale: 1.0,
12.0 * t_scale: 1.0,
12.5 * t_scale: 0.0
})
ba.timer(13.0 * t_scale, lnub.delete)
self._start_lights.append(lnub)
self._start_lights[0].color = (0.2, 0, 0)
self._start_lights[1].color = (0.2, 0, 0)
self._start_lights[2].color = (0.2, 0.05, 0)
self._start_lights[3].color = (0.0, 0.3, 0)
self._squid_lights = []
for i in range(2):
lnub = ba.newnode('image',
attrs={
'texture': ba.gettexture('nub'),
'opacity': 1.0,
'absolute_scale': True,
'position': (-33 + i * 65, 220),
'scale': (60, 60),
'attach': 'center'
})
ba.animate(
lnub, 'opacity', {
4.0 * t_scale: 0,
5.0 * t_scale: 1.0})
self._squid_lights.append(lnub)
self._squid_lights[0].color = (0.2, 0, 0)
self._squid_lights[1].color = (0.0, 0.3, 0)
ba.timer(1.0, self._check_squid_end, repeat=True)
self._squidgame_countdown()
def _squidgame_countdown(self) -> None:
self._countdown_timer = 80 * self._laps # 80
ba.newnode(
'image',
attrs={
'opacity': 0.7,
'color': (0.2, 0.2, 0.2),
'attach': 'topCenter',
'position': (-220, -40),
'scale': (135, 45),
'texture': ba.gettexture('bar')})
ba.newnode(
'image',
attrs={
'opacity': 1.0,
'color': (1.0, 0.0, 0.0),
'attach': 'topCenter',
'position': (-220, -38),
'scale':(155, 65),
'texture': ba.gettexture('uiAtlas'),
'model_transparent': ba.getmodel('meterTransparent')})
self._sgcountdown_text = ba.newnode(
'text',
attrs={
'v_attach': 'top',
'h_attach': 'center',
'h_align': 'center',
'position': (-220, -57),
'shadow': 0.5,
'flatness': 0.5,
'scale': 1.1,
'text': str(self._countdown_timer)+"s"})
def _update_sgcountdown(self) -> None:
self._countdown_timer -= 1
self._countdown_timer
if self._countdown_timer <= 0:
self._countdown_timer = 0
self._squid_game_all_die()
if self._countdown_timer == 20:
self._sq_mode = 'Hard'
ba.playsound(ba.getsound('alarm'))
if self._countdown_timer == 40:
self._sq_mode = 'Normal'
if self._countdown_timer <= 20:
self._sgcountdown_text.color = (1.2, 0.0, 0.0)
self._sgcountdown_text.scale = 1.2
if self._countdown_timer in self._countdownsounds:
ba.playsound(self._countdownsounds[self._countdown_timer])
else:
self._sgcountdown_text.color = (1.0, 1.0, 1.0)
self._sgcountdown_text.text = str(self._countdown_timer)+"s"
def _squid_game_all_die(self) -> None:
for player in self.players:
if player.is_alive():
player.actor._cursed = True
player.actor.handlemessage(ba.DieMessage())
NewBlast(
position=player.actor.node.position,
velocity=player.actor.node.velocity,
blast_radius=3.0,
blast_type='normal').autoretain()
player.actor.handlemessage(
ba.HitMessage(
pos=player.actor.node.position,
velocity=player.actor.node.velocity,
magnitude=2000,
hit_type='explosion',
hit_subtype='normal',
radius=2.0,
source_player=None))
player.actor._cursed = False
def _do_ticks(self) -> None:
def do_ticks():
if self._ticks:
ba.playsound(ba.getsound('tick'))
self._tick_timer = ba.timer(1.0, do_ticks, repeat=True)
def _start_squid_game(self) -> None:
easy = [4.5,5,5.5,6]
normal = [4,4.5,5]
hard = [3,3.5,4]
random_number = random.choice(
hard if self._sq_mode == 'Hard' else
normal if self._sq_mode == 'Normal' else easy)
# if random_number == 6:
# ba.playsound(ba.getsound('lrlg_06s'))
# elif random_number == 5.5:
# ba.playsound(ba.getsound('lrlg_055s'))
# elif random_number == 5:
# ba.playsound(ba.getsound('lrlg_05s'))
# elif random_number == 4.5:
# ba.playsound(ba.getsound('lrlg_045s'))
# elif random_number == 4:
# ba.playsound(ba.getsound('lrlg_04s'))
# elif random_number == 3.5:
# ba.playsound(ba.getsound('lrlg_035s'))
# elif random_number == 3:
# ba.playsound(ba.getsound('lrlg_03s'))
self._squid_lights[0].color = (0.2, 0, 0)
self._squid_lights[1].color = (0.0, 1.0, 0)
self._do_delete = False
self._ticks = True
ba.timer(random_number, self._stop_squid_game)
def _stop_squid_game(self) -> None:
self._ticks = False
self._squid_lights[0].color = (1.0, 0, 0)
self._squid_lights[1].color = (0.0, 0.3, 0)
ba.timer(0.2, self._check_delete)
def _check_delete(self) -> None:
for player in self.players:
if player.is_alive():
player.customdata['position'] = None
player.customdata['position'] = player.actor.node.position
self._do_delete = True
ba.timer(3.0 if self._sq_mode == 'Hard' else 4.0,
self._start_squid_game)
def _start_delete(self) -> None:
for player in self.players:
if player.is_alive() and self._do_delete:
posx = float("%.1f" % player.customdata['position'][0])
posz = float("%.1f" % player.customdata['position'][1])
posy = float("%.1f" % player.customdata['position'][2])
posx_list = [
round(posx,1),round(posx+0.1,1),round(posx+0.2,1),
round(posx-0.1,1),round(posx-0.2,1)]
current_posx = float("%.1f" % player.actor.node.position[0])
posz_list = [
round(posz,1),round(posz+0.1,1),round(posz+0.2,1),
round(posz-0.1,1),round(posz-0.2,1)]
current_posz = float("%.1f" % player.actor.node.position[1])
posy_list = [
round(posy,1),round(posy+0.1,1),round(posy+0.2,1),
round(posy-0.1,1),round(posy-0.2,1)]
current_posy = float("%.1f" % player.actor.node.position[2])
if not (current_posx in posx_list) or not (
current_posz in posz_list) or not (
current_posy in posy_list):
player.actor._cursed = True
player.actor.handlemessage(ba.DieMessage())
NewBlast(
position=player.actor.node.position,
velocity=player.actor.node.velocity,
blast_radius=3.0,
blast_type='normal').autoretain()
player.actor.handlemessage(
ba.HitMessage(
pos=player.actor.node.position,
velocity=player.actor.node.velocity,
magnitude=2000,
hit_type='explosion',
hit_subtype='normal',
radius=2.0,
source_player=None))
player.actor._cursed = False
def _check_squid_end(self) -> None:
squid_player_alive = 0
for player in self.players:
if player.is_alive():
squid_player_alive += 1
break
if squid_player_alive < 1:
self.end_game()
def _do_light_1(self) -> None:
assert self._start_lights is not None
self._start_lights[0].color = (1.0, 0, 0)
ba.playsound(self._beep_1_sound)
def _do_light_2(self) -> None:
assert self._start_lights is not None
self._start_lights[1].color = (1.0, 0, 0)
ba.playsound(self._beep_1_sound)
def _do_light_3(self) -> None:
assert self._start_lights is not None
self._start_lights[2].color = (1.0, 0.3, 0)
ba.playsound(self._beep_1_sound)
def _start_race(self) -> None:
assert self._start_lights is not None
self._start_lights[3].color = (0.0, 1.0, 0)
ba.playsound(self._beep_2_sound)
for player in self.players:
if player.actor is not None:
try:
assert isinstance(player.actor, PlayerSpaz)
player.actor.connect_controls_to_player()
except Exception:
ba.print_exception('Error in race player connects.')
assert self._timer is not None
self._timer.start()
if self._bomb_spawning != 0:
self._bomb_spawn_timer = ba.Timer(0.001 * self._bomb_spawning,
self._spawn_bomb,
repeat=True)
self._race_started = True
self._squid_lights[1].color = (0.0, 1.0, 0)
self._start_squid_game()
self._do_ticks()
ba.timer(0.2, self._start_delete, repeat=True)
ba.timer(1.0, self._update_sgcountdown, repeat=True)
def _update_player_order(self) -> None:
# Calc all player distances.
for player in self.players:
pos: Optional[ba.Vec3]
try:
pos = player.position
except ba.NotFoundError:
pos = None
if pos is not None:
r_index = player.last_region
rg1 = self._regions[r_index]
r1pt = ba.Vec3(rg1.pos[:3])
rg2 = self._regions[0] if r_index == len(
self._regions) - 1 else self._regions[r_index + 1]
r2pt = ba.Vec3(rg2.pos[:3])
r2dist = (pos - r2pt).length()
amt = 1.0 - (r2dist / (r2pt - r1pt).length())
amt = player.lap + (r_index + amt) * (1.0 / len(self._regions))
player.distance = amt
# Sort players by distance and update their ranks.
p_list = [(player.distance, player) for player in self.players]
p_list.sort(reverse=True, key=lambda x: x[0])
for i, plr in enumerate(p_list):
plr[1].rank = i
if plr[1].actor:
node = plr[1].distance_txt
if node:
node.text = str(i + 1) if plr[1].is_alive() else ''
def _spawn_bomb(self) -> None:
if self._front_race_region is None:
return
region = (self._front_race_region + 3) % len(self._regions)
pos = self._regions[region].pos
# Don't use the full region so we're less likely to spawn off a cliff.
region_scale = 0.8
x_range = ((-0.5, 0.5) if pos[3] == 0 else
(-region_scale * pos[3], region_scale * pos[3]))
z_range = ((-0.5, 0.5) if pos[5] == 0 else
(-region_scale * pos[5], region_scale * pos[5]))
pos = (pos[0] + random.uniform(*x_range), pos[1] + 1.0,
pos[2] + random.uniform(*z_range))
ba.timer(random.uniform(0.0, 2.0),
ba.WeakCall(self._spawn_bomb_at_pos, pos))
def _spawn_bomb_at_pos(self, pos: Sequence[float]) -> None:
if self.has_ended():
return
Bomb(position=pos, bomb_type='normal').autoretain()
def _make_mine(self, i: int) -> None:
assert self._race_mines is not None
rmine = self._race_mines[i]
rmine.mine = Bomb(position=rmine.point[:3], bomb_type='land_mine')
rmine.mine.arm()
def _flash_mine(self, i: int) -> None:
assert self._race_mines is not None
rmine = self._race_mines[i]
light = ba.newnode('light',
attrs={
'position': rmine.point[:3],
'color': (1, 0.2, 0.2),
'radius': 0.1,
'height_attenuated': False
})
ba.animate(light, 'intensity', {0.0: 0, 0.1: 1.0, 0.2: 0}, loop=True)
ba.timer(1.0, light.delete)
def _update_race_mine(self) -> None:
assert self._race_mines is not None
m_index = -1
rmine = None
for _i in range(3):
m_index = random.randrange(len(self._race_mines))
rmine = self._race_mines[m_index]
if not rmine.mine:
break
assert rmine is not None
if not rmine.mine:
self._flash_mine(m_index)
ba.timer(0.95, ba.Call(self._make_mine, m_index))
def spawn_player(self, player: Player) -> ba.Actor:
if player.team.finished:
# FIXME: This is not type-safe!
# This call is expected to always return an Actor!
# Perhaps we need something like can_spawn_player()...
# noinspection PyTypeChecker
return None # type: ignore
pos = self._regions[player.last_region].pos
# Don't use the full region so we're less likely to spawn off a cliff.
region_scale = 0.8
x_range = ((-0.5, 0.5) if pos[3] == 0 else
(-region_scale * pos[3], region_scale * pos[3]))
z_range = ((-0.5, 0.5) if pos[5] == 0 else
(-region_scale * pos[5], region_scale * pos[5]))
pos = (pos[0] + random.uniform(*x_range), pos[1],
pos[2] + random.uniform(*z_range))
spaz = self.spawn_player_spaz(
player, position=pos, angle=90 if not self._race_started else None)
assert spaz.node
# Prevent controlling of characters before the start of the race.
if not self._race_started:
spaz.disconnect_controls_from_player()
mathnode = ba.newnode('math',
owner=spaz.node,
attrs={
'input1': (0, 1.4, 0),
'operation': 'add'
})
spaz.node.connectattr('torso_position', mathnode, 'input2')
distance_txt = ba.newnode('text',
owner=spaz.node,
attrs={
'text': '',
'in_world': True,
'color': (1, 1, 0.4),
'scale': 0.02,
'h_align': 'center'
})
player.distance_txt = distance_txt
mathnode.connectattr('output', distance_txt, 'position')
return spaz
def _check_end_game(self) -> None:
# If there's no teams left racing, finish.
teams_still_in = len([t for t in self.teams if not t.finished])
if teams_still_in == 0:
self.end_game()
return
# Count the number of teams that have completed the race.
teams_completed = len(
[t for t in self.teams if t.finished and t.time is not None])
if teams_completed > 0:
session = self.session
# In teams mode its over as soon as any team finishes the race
# FIXME: The get_ffa_point_awards code looks dangerous.
if isinstance(session, ba.DualTeamSession):
self.end_game()
else:
# In ffa we keep the race going while there's still any points
# to be handed out. Find out how many points we have to award
# and how many teams have finished, and once that matches
# we're done.
assert isinstance(session, ba.FreeForAllSession)
points_to_award = len(session.get_ffa_point_awards())
if teams_completed >= points_to_award - teams_completed:
self.end_game()
return
def end_game(self) -> None:
# Stop updating our time text, and set it to show the exact last
# finish time if we have one. (so users don't get upset if their
# final time differs from what they see onscreen by a tiny amount)
assert self._timer is not None
if self._timer.has_started():
self._timer.stop(
endtime=None if self._last_team_time is None else (
self._timer.getstarttime() + self._last_team_time))
results = ba.GameResults()
for team in self.teams:
if team.time is not None:
# We store time in seconds, but pass a score in milliseconds.
results.set_team_score(team, int(team.time * 1000.0))
else:
results.set_team_score(team, None)
# We don't announce a winner in ffa mode since its probably been a
# while since the first place guy crossed the finish line so it seems
# odd to be announcing that now.
self.end(results=results,
announce_winning_team=isinstance(self.session,
ba.DualTeamSession))
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment default behavior.
super().handlemessage(msg)
else:
super().handlemessage(msg)

291
dist/ba_root/mods/games/Sticky_Loads.py vendored Normal file
View file

@ -0,0 +1,291 @@
"""Defines a Sticky-Bomb-dodging mini-game."""
# ba_meta require api 6
from __future__ import annotations
import random
from typing import TYPE_CHECKING
import ba
import _ba
from ba._campaign import Campaign
from bastd.actor.bomb import Bomb
from bastd.actor.spaz import Spaz
from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
from typing import Any, Sequence, Optional, List, Dict, Type, Type
def ba_get_api_version():
return 6
def ba_get_levels():
return [ba._level.Level(
'Sticky Loads',
gametype=StickyLoadsGame,
settings={},
preview_texture_name='rampagePreview'), ba._level.Level(
'Epic Sticky Loads',
gametype=StickyLoadsGame,
settings={'Epic Mode':True},
preview_texture_name='rampagePreview')]
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
super().__init__()
self.death_time: Optional[float] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
# ba_meta export game
class StickyLoadsGame(ba.TeamGameActivity[Player, Team]):
"""Minigame involving dodging falling Sticky bombs."""
name = 'Sticky Lodes'
description = 'Dodge The Falling Sticky Bombs'
available_settings = [ba.BoolSetting('Epic Mode', default=False)]
scoreconfig = ba.ScoreConfig(label='Survived',
scoretype=ba.ScoreType.MILLISECONDS,
version='B')
# Print messages when players die (since its meaningful in this game).
announce_player_deaths = True
# we're currently hard-coded for one map..
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ['Rampage']
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession)
or issubclass(sessiontype, ba.CoopSession))
def __init__(self, settings: dict):
super().__init__(settings)
self._epic_mode = settings.get('Epic Mode', False)
self._last_player_death_time: Optional[float] = None
self._meteor_time = 2.0
self._timer: Optional[OnScreenTimer] = None
# Some base class overrides:
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL)
if self._epic_mode:
self.slow_motion = True
def on_begin(self) -> None:
super().on_begin()
# Drop a wave every few seconds.. and every so often drop the time
# between waves ..lets have things increase faster if we have fewer
# players.
delay = 5.0 if len(self.players) > 2 else 2.5
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._decrement_meteor_time, repeat=True)
# Kick off the first wave in a few seconds.
delay = 3.0
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._set_meteor_timer)
self._timer = OnScreenTimer()
self._timer.start()
# Check for immediate end (if we've only got 1 player, etc).
ba.timer(5.0, self._check_end_game)
def on_player_join(self, player: Player) -> None:
# Don't allow joining after we start
# (would enable leave/rejoin tomfoolery).
if self.has_begun():
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
# For score purposes, mark them as having died right as the
# game started.
assert self._timer is not None
player.death_time = self._timer.getstarttime()
return
self.spawn_player(player)
def on_player_leave(self, player: Player) -> None:
# Augment default behavior.
super().on_player_leave(player)
# A departing player may trigger game-over.
self._check_end_game()
# overriding the default character spawning..
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=False,
enable_bomb=False,
enable_pickup=False)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
return spaz
# Various high-level game events come through this method.
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
curtime = ba.time()
# Record the player's moment of death.
# assert isinstance(msg.spaz.player
msg.getplayer(Player).death_time = curtime
# In co-op mode, end the game the instant everyone dies
# (more accurate looking).
# In teams/ffa, allow a one-second fudge-factor so we can
# get more draws if players die basically at the same time.
if isinstance(self.session, ba.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
ba.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = curtime
else:
ba.timer(1.0, self._check_end_game)
else:
# Default handler:
return super().handlemessage(msg)
return None
def _check_end_game(self) -> None:
living_team_count = 0
for team in self.teams:
for player in team.players:
if player.is_alive():
living_team_count += 1
break
# In co-op, we go till everyone is dead.. otherwise we go
# until one team remains.
if isinstance(self.session, ba.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def _set_meteor_timer(self) -> None:
ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
self._drop_bomb_cluster)
def _drop_bomb_cluster(self) -> None:
# Random note: code like this is a handy way to plot out extents
# and debug things.
loc_test = False
if loc_test:
ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
# Drop several bombs in series.
delay = 0.0
for _i in range(random.randrange(1, 3)):
# Drop them somewhere within our bounds with velocity pointing
# toward the opposite side.
pos = (-7.3 + 15.3 * random.random(), 11,
-5.5 + 2.1 * random.random())
dropdir = (-1.0 if pos[0] > 0 else 1.0)
vel = ((-5.0 + random.random() * 30.0) * dropdir, -4.0, 0)
ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
delay += 0.1
self._set_meteor_timer()
def _drop_bomb(self, position: Sequence[float],
velocity: Sequence[float]) -> None:
Bomb(position=position, velocity=velocity,bomb_type = random.choice(["sticky"])).autoretain()
def _decrement_meteor_time(self) -> None:
self._meteor_time = max(0.01, self._meteor_time * 0.9)
def end_game(self) -> None:
cur_time = ba.time()
assert self._timer is not None
start_time = self._timer.getstarttime()
# Mark death-time as now for any still-living players
# and award players points for how long they lasted.
# (these per-player scores are only meaningful in team-games)
for team in self.teams:
for player in team.players:
survived = False
# Throw an extra fudge factor in so teams that
# didn't die come out ahead of teams that did.
if player.death_time is None:
survived = True
player.death_time = cur_time + 1
# Award a per-player score depending on how many seconds
# they lasted (per-player scores only affect teams mode;
# everywhere else just looks at the per-team score).
score = int(player.death_time - self._timer.getstarttime())
if survived:
score += 50 # A bit extra for survivors.
self.stats.player_scored(player, score, screenmessage=False)
# Stop updating our time text, and set the final time to match
# exactly when our last guy died.
self._timer.stop(endtime=self._last_player_death_time)
# Ok now calc game results: set a score for each team and then tell
# the game to end.
results = ba.GameResults()
# Remember that 'free-for-all' mode is simply a special form
# of 'teams' mode where each player gets their own team, so we can
# just always deal in teams and have all cases covered.
for team in self.teams:
# Set the team score to the max time survived by any player on
# that team.
longest_life = 0.0
for player in team.players:
assert player.death_time is not None
longest_life = max(longest_life,
player.death_time - start_time)
# Submit the score value in milliseconds.
results.set_team_score(team, int(1000.0 * longest_life))
self.end(results=results)
"""
from ba import _level
campaign = Campaign('Challenges', sequential=False)
campaign.addlevel(
_level.Level(name='Sticky Loads',
displayname='Sti',
gametype=StickyLoadesGame,
settings={},
preview_texture_name='rampagePreview'))
ba._campaign.register_campaign(campaign)
"""

677
dist/ba_root/mods/games/SuperSmash.py vendored Normal file
View file

@ -0,0 +1,677 @@
"""DeathMatch game and support classes."""
# Created by: byANG3L #
# YT: https://www.youtube.com/c/JoseANG3LYT #
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
import _ba
import random
from ba import _math
from bastd.actor.spazfactory import SpazFactory
from bastd.actor.scoreboard import Scoreboard
from bastd.game import elimination
from bastd.game.elimination import Icon, Player, Team
from bastd.actor.bomb import Bomb, Blast
from bastd.actor.playerspaz import PlayerSpaz
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional
class Icon(Icon):
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 > 1:
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
class PowBox(Bomb):
def __init__(self,
position: Sequence[float] = (0.0, 1.0, 0.0),
velocity: Sequence[float] = (0.0, 0.0, 0.0)) -> None:
Bomb.__init__(self,
position,
velocity,
bomb_type='tnt',
blast_radius=2.5,
source_player=None,
owner=None)
self.set_pow_text()
def set_pow_text(self) -> None:
m = ba.newnode('math',
owner=self.node,
attrs={'input1': (0, 0.7, 0),
'operation': 'add'})
self.node.connectattr('position', m, 'input2')
self._pow_text = ba.newnode('text',
owner=self.node,
attrs={'text':'POW!',
'in_world': True,
'shadow': 1.0,
'flatness': 1.0,
'color': (1, 1, 0.4),
'scale':0.0,
'h_align':'center'})
m.connectattr('output', self._pow_text, 'position')
ba.animate(self._pow_text, 'scale', {0: 0.0, 1.0: 0.01})
def pow(self) -> None:
self.explode()
def handlemessage(self, m: Any) -> Any:
if isinstance(m, ba.PickedUpMessage):
self._heldBy = m.node
elif isinstance(m, ba.DroppedMessage):
ba.timer(0.6, self.pow)
Bomb.handlemessage(self, m)
class PlayerSpaz_Smash(PlayerSpaz):
multiplyer = 1
is_dead = False
def oob_effect(self) -> None:
if self.is_dead:
return
self.is_dead = True
if self.multiplyer > 1.25:
blast_type = 'tnt'
radius = min(self.multiplyer * 5, 20)
else:
# penalty for killing people with low multiplyer
blast_type = 'ice'
radius = 7.5
Blast(position=self.node.position,
blast_radius=radius,
blast_type=blast_type).autoretain()
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.HitMessage):
if not self.node:
return None
if self.node.invincible:
ba.playsound(SpazFactory.get().block_sound,
1.0,
position=self.node.position)
return True
# If we were recently hit, don't count this as another.
# (so punch flurries and bomb pileups essentially count as 1 hit)
local_time = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
assert isinstance(local_time, int)
if (self._last_hit_time is None
or local_time - self._last_hit_time > 1000):
self._num_times_hit += 1
self._last_hit_time = local_time
mag = msg.magnitude * self.impact_scale
velocity_mag = msg.velocity_magnitude * self.impact_scale
damage_scale = 0.22
# If they've got a shield, deliver it to that instead.
if self.shield:
if msg.flat_damage:
damage = msg.flat_damage * self.impact_scale
else:
# Hit our spaz with an impulse but tell it to only return
# theoretical damage; not apply the impulse.
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], mag,
velocity_mag, msg.radius, 1, msg.force_direction[0],
msg.force_direction[1], msg.force_direction[2])
damage = damage_scale * self.node.damage
assert self.shield_hitpoints is not None
self.shield_hitpoints -= int(damage)
self.shield.hurt = (
1.0 -
float(self.shield_hitpoints) / self.shield_hitpoints_max)
# Its a cleaner event if a hit just kills the shield
# without damaging the player.
# However, massive damage events should still be able to
# damage the player. This hopefully gives us a happy medium.
max_spillover = SpazFactory.get().max_shield_spillover_damage
if self.shield_hitpoints <= 0:
# FIXME: Transition out perhaps?
self.shield.delete()
self.shield = None
ba.playsound(SpazFactory.get().shield_down_sound,
1.0,
position=self.node.position)
# Emit some cool looking sparks when the shield dies.
npos = self.node.position
ba.emitfx(position=(npos[0], npos[1] + 0.9, npos[2]),
velocity=self.node.velocity,
count=random.randrange(20, 30),
scale=1.0,
spread=0.6,
chunk_type='spark')
else:
ba.playsound(SpazFactory.get().shield_hit_sound,
0.5,
position=self.node.position)
# Emit some cool looking sparks on shield hit.
assert msg.force_direction is not None
ba.emitfx(position=msg.pos,
velocity=(msg.force_direction[0] * 1.0,
msg.force_direction[1] * 1.0,
msg.force_direction[2] * 1.0),
count=min(30, 5 + int(damage * 0.005)),
scale=0.5,
spread=0.3,
chunk_type='spark')
# If they passed our spillover threshold,
# pass damage along to spaz.
if self.shield_hitpoints <= -max_spillover:
leftover_damage = -max_spillover - self.shield_hitpoints
shield_leftover_ratio = leftover_damage / damage
# Scale down the magnitudes applied to spaz accordingly.
mag *= shield_leftover_ratio
velocity_mag *= shield_leftover_ratio
else:
return True # Good job shield!
else:
shield_leftover_ratio = 1.0
if msg.flat_damage:
damage = int(msg.flat_damage * self.impact_scale *
shield_leftover_ratio)
else:
# Hit it with an impulse and get the resulting damage.
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], mag,
velocity_mag, msg.radius, 0, msg.force_direction[0],
msg.force_direction[1], msg.force_direction[2])
damage = int(damage_scale * self.node.damage)
self.node.handlemessage('hurt_sound')
# Play punch impact sound based on damage if it was a punch.
if msg.hit_type == 'punch':
self.on_punched(damage)
# If damage was significant, lets show it.
# if damage > 350:
# assert msg.force_direction is not None
# ba.show_damage_count('-' + str(int(damage / 10)) + '%',
# msg.pos, msg.force_direction)
# Let's always add in a super-punch sound with boxing
# gloves just to differentiate them.
if msg.hit_subtype == 'super_punch':
ba.playsound(SpazFactory.get().punch_sound_stronger,
1.0,
position=self.node.position)
if damage > 500:
sounds = SpazFactory.get().punch_sound_strong
sound = sounds[random.randrange(len(sounds))]
else:
sound = SpazFactory.get().punch_sound
ba.playsound(sound, 1.0, position=self.node.position)
# Throw up some chunks.
assert msg.force_direction is not None
ba.emitfx(position=msg.pos,
velocity=(msg.force_direction[0] * 0.5,
msg.force_direction[1] * 0.5,
msg.force_direction[2] * 0.5),
count=min(10, 1 + int(damage * 0.0025)),
scale=0.3,
spread=0.03)
ba.emitfx(position=msg.pos,
chunk_type='sweat',
velocity=(msg.force_direction[0] * 1.3,
msg.force_direction[1] * 1.3 + 5.0,
msg.force_direction[2] * 1.3),
count=min(30, 1 + int(damage * 0.04)),
scale=0.9,
spread=0.28)
# Momentary flash.
hurtiness = damage * 0.003
punchpos = (msg.pos[0] + msg.force_direction[0] * 0.02,
msg.pos[1] + msg.force_direction[1] * 0.02,
msg.pos[2] + msg.force_direction[2] * 0.02)
flash_color = (1.0, 0.8, 0.4)
light = ba.newnode(
'light',
attrs={
'position': punchpos,
'radius': 0.12 + hurtiness * 0.12,
'intensity': 0.3 * (1.0 + 1.0 * hurtiness),
'height_attenuated': False,
'color': flash_color
})
ba.timer(0.06, light.delete)
flash = ba.newnode('flash',
attrs={
'position': punchpos,
'size': 0.17 + 0.17 * hurtiness,
'color': flash_color
})
ba.timer(0.06, flash.delete)
if msg.hit_type == 'impact':
assert msg.force_direction is not None
ba.emitfx(position=msg.pos,
velocity=(msg.force_direction[0] * 2.0,
msg.force_direction[1] * 2.0,
msg.force_direction[2] * 2.0),
count=min(10, 1 + int(damage * 0.01)),
scale=0.4,
spread=0.1)
if self.hitpoints > 0:
# It's kinda crappy to die from impacts, so lets reduce
# impact damage by a reasonable amount *if* it'll keep us alive
if msg.hit_type == 'impact' and damage > self.hitpoints:
# Drop damage to whatever puts us at 10 hit points,
# or 200 less than it used to be whichever is greater
# (so it *can* still kill us if its high enough)
newdamage = max(damage - 200, self.hitpoints - 10)
damage = newdamage
self.node.handlemessage('flash')
# If we're holding something, drop it.
if damage > 0.0 and self.node.hold_node:
self.node.hold_node = None
# self.hitpoints -= damage
self.multiplyer += min(damage / 2000, 0.15)
if damage/2000 > 0.05:
self.set_score_text(str(int((self.multiplyer-1)*100))+'%')
# self.node.hurt = 1.0 - float(
# self.hitpoints) / self.hitpoints_max
self.node.hurt = 0.0
# If we're cursed, *any* damage blows us up.
if self._cursed and damage > 0:
ba.timer(
0.05,
ba.WeakCall(self.curse_explode,
msg.get_source_player(ba.Player)))
# If we're frozen, shatter.. otherwise die if we hit zero
# if self.frozen and (damage > 200 or self.hitpoints <= 0):
# self.shatter()
# elif self.hitpoints <= 0:
# self.node.handlemessage(
# ba.DieMessage(how=ba.DeathType.IMPACT))
# If we're dead, take a look at the smoothed damage value
# (which gives us a smoothed average of recent damage) and shatter
# us if its grown high enough.
# if self.hitpoints <= 0:
# damage_avg = self.node.damage_smoothed * damage_scale
# if damage_avg > 1000:
# self.shatter()
elif isinstance(msg, ba.DieMessage):
self.oob_effect()
super().handlemessage(msg)
elif isinstance(msg, ba.PowerupMessage):
if msg.poweruptype == 'health':
if self.multiplyer > 2:
self.multiplyer *= 0.5
else:
self.multiplyer *= 0.75
self.multiplyer = max(1, self.multiplyer)
self.set_score_text(str(int((self.multiplyer-1)*100))+"%")
super().handlemessage(msg)
else:
super().handlemessage(msg)
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.lives = 0
self.icons: List[Icon] = []
class Team(ba.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 game
class SuperSmash(ba.TeamGameActivity[Player, Team]):
name = 'Super Smash'
description = 'Knock everyone off the map.'
scoreconfig = ba.ScoreConfig(label='Survived',
scoretype=ba.ScoreType.SECONDS,
none_is_winner=True)
# Print messages when players die since it matters here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting(
'Lives (0 = Unlimited)',
min_value=0,
default=3,
increment=1,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
]
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
maps = ba.getmaps('melee')
for m in ['Lake Frigid', 'Hockey Stadium', 'Football Stadium']:
# remove maps without bounds
maps.remove(m)
return maps
def __init__(self, settings: dict):
super().__init__(settings)
self.lives = int(settings['Lives (0 = Unlimited)'])
self.time_limit_only = (self.lives == 0)
if self.time_limit_only:
settings['Time Limit'] = max(60, settings['Time Limit'])
self._epic_mode = bool(settings['Epic Mode'])
self._time_limit = float(settings['Time Limit'])
self._start_time: Optional[float] = 1.0
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.SURVIVAL)
def get_instance_description(self) -> Union[str, Sequence]:
return 'Knock everyone off the map.'
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'Knock off the map.'
def on_begin(self) -> None:
super().on_begin()
self._start_time = ba.time()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops(enable_tnt=False)
self._pow = None
self._tnt_drop_timer = ba.timer(1.0 * 0.30,
ba.WeakCall(self._drop_pow_box),
repeat=True)
self._update_icons()
if len(self._get_living_teams()) < 2:
self._round_end_timer = ba.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 _drop_pow_box(self) -> None:
if self._pow is not None and self._pow:
return
if len(self.map.tnt_points) == 0:
return
pos = random.choice(self.map.tnt_points)
pos = (pos[0], pos[1] + 1, pos[2])
self._pow = PowBox(position=pos, velocity=(0.0, 1.0, 0.0))
def on_player_join(self, player: Player) -> None:
if self.has_begun():
if (all(teammate.lives == 0 for teammate in player.team.players)
and player.team.survival_seconds is None):
player.team.survival_seconds = 0
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
player.lives = self.lives
# create our icon and spawn
player.icons = [Icon(player,
position=(0.0, 50),
scale=0.8)]
if player.lives > 0 or self.time_limit_only:
self.spawn_player(player)
# dont waste time doing this until begin
if self.has_begun():
self._update_icons()
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
player.icons = None
# update icons in a moment since our team
# will be gone from the list then
ba.timer(0.0, self._update_icons)
if len(self._get_living_teams()) < 2:
self._round_end_timer = ba.Timer(0.5, self.end_game)
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, ba.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:
print('WTF have', len(team.players), 'players in ffa team')
elif len(team.players) == 1:
player = team.players[0]
if len(player.icons) != 1:
print(
'WTF have',
len(player.icons),
'icons in non-solo elim')
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:
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:
if len(player.icons) != 1:
print(
'WTF have',
len(player.icons),
'icons in non-solo elim')
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
# overriding the default character spawning..
def spawn_player(self, player: Player) -> ba.Actor:
if isinstance(self.session, ba.DualTeamSession):
position = self.map.get_start_position(player.team.id)
else:
# otherwise do free-for-all spawn locations
position = self.map.get_ffa_start_position(self.players)
angle = None
name = player.getname()
light_color = _math.normalized_color(player.color)
display_color = _ba.safecolor(player.color, target_intensity=0.75)
spaz = PlayerSpaz_Smash(color=player.color,
highlight=player.highlight,
character=player.character,
player=player)
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, ba.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()
spaz.equip_boxing_gloves()
# Move to the stand position and add a flash of light.
spaz.handlemessage(
ba.StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
_ba.playsound(self._spawn_sound, 1, position=spaz.node.position)
light = _ba.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
_ba.timer(0.5, light.delete)
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_spawned()
return spaz
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
player.lives -= 1
# 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 player.lives == 0:
ba.playsound(SpazFactory.get().single_player_death_sound)
# if we hit zero lives we're dead and the game might be over
if player.lives == 0 and not self.time_limit_only:
# if the whole team is dead, make note of how long they lasted
if all(
teammate.lives == 0 for teammate in player.team.players):
# log the team survival
# if we're the last player on the team
player.team.survival_seconds = int(ba.time() -
self._start_time)
# if someone has won, set a timer to end shortly
# (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 = ba.timer(1.0, self.end_game)
# we still have lives; yay!
else:
self.respawn_player(player)
else:
return super().handlemessage(msg)
return None
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 = ba.GameResults()
self._vs_text = None # Kill our 'vs' if its there.
for team in self.teams:
results.set_team_score(team, team.survival_seconds)
self.end(results=results)

279
dist/ba_root/mods/games/TnT_Error.py vendored Normal file
View file

@ -0,0 +1,279 @@
"""Defines a Tnt-dodging mini-game."""
# ba_meta require api 6
from __future__ import annotations
import random
from typing import TYPE_CHECKING
import ba
import _ba
from bastd.actor.bomb import Bomb
from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
from typing import Any, Sequence, Optional, List, Dict, Type, Type
from bastd.ui.coop.browser import CoopBrowserWindow
def ba_get_api_version():
return 6
def ba_get_levels():
return [ba._level.Level(
'TnT Error',
gametype=TntErrorGame,
settings={},
preview_texture_name='rampagePreview'), ba._level.Level(
'Epic TnT Error',
gametype=TntErrorGame,
settings={'Epic Mode':True},
preview_texture_name='rampagePreview')]
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
super().__init__()
self.death_time: Optional[float] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
# ba_meta export game
class TntErrorGame(ba.TeamGameActivity[Player, Team]):
"""Minigame involving dodging falling bombs."""
name = 'TnT Error'
description = 'Boom Goes The TNT :)'
available_settings = [ba.BoolSetting('Epic Mode', default=False)]
scoreconfig = ba.ScoreConfig(label='Survived',
scoretype=ba.ScoreType.MILLISECONDS,
version='B')
# Print messages when players die (since its meaningful in this game).
announce_player_deaths = True
# we're currently hard-coded for one map..
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ['Rampage']
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession)
or issubclass(sessiontype, ba.CoopSession))
def __init__(self, settings: dict):
super().__init__(settings)
self._epic_mode = settings.get('Epic Mode', False)
self._last_player_death_time: Optional[float] = None
self._meteor_time = 2.0
self._timer: Optional[OnScreenTimer] = None
# Some base class overrides:
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL)
if self._epic_mode:
self.slow_motion = True
def on_begin(self) -> None:
super().on_begin()
# Drop a wave every few seconds.. and every so often drop the time
# between waves ..lets have things increase faster if we have fewer
# players.
delay = 5.0 if len(self.players) > 2 else 2.5
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._decrement_meteor_time, repeat=True)
# Kick off the first wave in a few seconds.
delay = 3.0
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._set_meteor_timer)
self._timer = OnScreenTimer()
self._timer.start()
# Check for immediate end (if we've only got 1 player, etc).
ba.timer(5.0, self._check_end_game)
def on_player_join(self, player: Player) -> None:
# Don't allow joining after we start
# (would enable leave/rejoin tomfoolery).
if self.has_begun():
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
# For score purposes, mark them as having died right as the
# game started.
assert self._timer is not None
player.death_time = self._timer.getstarttime()
return
self.spawn_player(player)
def on_player_leave(self, player: Player) -> None:
# Augment default behavior.
super().on_player_leave(player)
# A departing player may trigger game-over.
self._check_end_game()
# overriding the default character spawning..
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=False,
enable_bomb=False,
enable_pickup=False)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
return spaz
# Various high-level game events come through this method.
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
curtime = ba.time()
# Record the player's moment of death.
# assert isinstance(msg.spaz.player
msg.getplayer(Player).death_time = curtime
# In co-op mode, end the game the instant everyone dies
# (more accurate looking).
# In teams/ffa, allow a one-second fudge-factor so we can
# get more draws if players die basically at the same time.
if isinstance(self.session, ba.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
ba.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = curtime
else:
ba.timer(1.0, self._check_end_game)
else:
# Default handler:
return super().handlemessage(msg)
return None
def _check_end_game(self) -> None:
living_team_count = 0
for team in self.teams:
for player in team.players:
if player.is_alive():
living_team_count += 1
break
# In co-op, we go till everyone is dead.. otherwise we go
# until one team remains.
if isinstance(self.session, ba.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def _set_meteor_timer(self) -> None:
ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
self._drop_bomb_cluster)
def _drop_bomb_cluster(self) -> None:
# Random note: code like this is a handy way to plot out extents
# and debug things.
loc_test = False
if loc_test:
ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
# Drop several bombs in series.
delay = 0.0
for _i in range(random.randrange(1, 3)):
# Drop them somewhere within our bounds with velocity pointing
# toward the opposite side.
pos = (-7.3 + 15.3 * random.random(), 11,
-5.5 + 2.1 * random.random())
dropdir = (-1.0 if pos[0] > 0 else 1.0)
vel = ((-5.0 + random.random() * 30.0) * dropdir, -4.0, 0)
ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
delay += 0.1
self._set_meteor_timer()
def _drop_bomb(self, position: Sequence[float],
velocity: Sequence[float]) -> None:
Bomb(position=position, velocity=velocity,bomb_type = random.choice(["tnt","tnt","impact"])).autoretain()
def _decrement_meteor_time(self) -> None:
self._meteor_time = max(0.01, self._meteor_time * 0.9)
def end_game(self) -> None:
cur_time = ba.time()
assert self._timer is not None
start_time = self._timer.getstarttime()
# Mark death-time as now for any still-living players
# and award players points for how long they lasted.
# (these per-player scores are only meaningful in team-games)
for team in self.teams:
for player in team.players:
survived = False
# Throw an extra fudge factor in so teams that
# didn't die come out ahead of teams that did.
if player.death_time is None:
survived = True
player.death_time = cur_time + 1
# Award a per-player score depending on how many seconds
# they lasted (per-player scores only affect teams mode;
# everywhere else just looks at the per-team score).
score = int(player.death_time - self._timer.getstarttime())
if survived:
score += 50 # A bit extra for survivors.
self.stats.player_scored(player, score, screenmessage=False)
# Stop updating our time text, and set the final time to match
# exactly when our last guy died.
self._timer.stop(endtime=self._last_player_death_time)
# Ok now calc game results: set a score for each team and then tell
# the game to end.
results = ba.GameResults()
# Remember that 'free-for-all' mode is simply a special form
# of 'teams' mode where each player gets their own team, so we can
# just always deal in teams and have all cases covered.
for team in self.teams:
# Set the team score to the max time survived by any player on
# that team.
longest_life = 0.0
for player in team.players:
assert player.death_time is not None
longest_life = max(longest_life,
player.death_time - start_time)
# Submit the score value in milliseconds.
results.set_team_score(team, int(1000.0 * longest_life))
self.end(results=results)

222
dist/ba_root/mods/games/Yeeting-party.py vendored Normal file
View file

@ -0,0 +1,222 @@
#Made by your friend: @[Just] Freak#4999
# ba_meta require api 6
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional
class Player(ba.Player['Team']):
"""Our player type for this game."""
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game
class BoxingGame(ba.TeamGameActivity[Player, Team]):
"""A game of yeeting people out of map"""
name = 'Yeeting Party!'
description = 'Yeet your enemies out of the map'
# Print messages when players die since it matters here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting(
'Kills to Win Per Player',
min_value=1,
default=5,
increment=1,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.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, ba.FreeForAllSession):
settings.append(
ba.BoolSetting('Allow Negative Scores', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ['Bridgit', 'Rampage', 'Monkey Face']
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_to_win: Optional[int] = None
self._dingsound = ba.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 = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.TO_THE_DEATH)
def get_instance_description(self) -> Union[str, Sequence]:
return 'Yeet ${ARG1} enemies out of the map!', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'yeet ${ARG1} enemies', self._score_to_win
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scoreboard()
def getRandomPowerupPoint(self) -> None:
myMap = self.map.getname()
if myMap == 'Doom Shroom':
while True:
x = random.uniform(-1.0,1.0)
y = random.uniform(-1.0,1.0)
if x*x+y*y < 1.0: break
return ((8.0*x,2.5,-3.5+5.0*y))
elif myMap == 'Rampage':
x = random.uniform(-6.0,7.0)
y = random.uniform(-6.0,-2.5)
return ((x, 5.2, y))
else:
x = random.uniform(-5.0,5.0)
y = random.uniform(-6.0,0.0)
return ((x, 8.0, y))
def on_begin(self) -> None:
super().on_begin()
ba.screenmessage("start Yeeting",color = (0.2,1,1))
self.setup_standard_time_limit(self._time_limit)
# Base kills needed to win on the size of the largest team.
self._score_to_win = (self._kills_to_win_per_player *
max(1, max(len(t.players) for t in self.teams)))
self._update_scoreboard()
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=False,
enable_jump=True,
enable_bomb=False,
enable_pickup=True)
return spaz
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.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, ba.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:
ba.playsound(self._dingsound)
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
ba.playsound(self._dingsound)
# 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):
ba.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 = ba.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

822
dist/ba_root/mods/games/ZombieHorde.py vendored Normal file
View file

@ -0,0 +1,822 @@
"""ZombieHorde."""
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
import _ba
import copy
import random
from ba import _math
from ba._coopsession import CoopSession
from ba._messages import PlayerDiedMessage, StandMessage
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.game.elimination import Icon, Player
from bastd.actor.spaz import PickupMessage
from bastd.actor.spazbot import SpazBotSet, BrawlerBot, SpazBotDiedMessage
from bastd.actor.spazfactory import SpazFactory
if TYPE_CHECKING:
from typing import (Any, Tuple, Dict, Type, List, Sequence, Optional,
Union)
class PlayerSpaz_Zom(PlayerSpaz):
def handlemessage(self, m: Any) -> Any:
if isinstance(m, ba.HitMessage):
if not self.node:
return
if not m._source_player is None:
try:
playa = m._source_player.getname(True, False)
if not playa is None:
if m._source_player.lives < 1:
super().handlemessage(m)
except:
super().handlemessage(m)
else:
super().handlemessage(m)
elif isinstance(m, ba.FreezeMessage):
pass
elif isinstance(m, PickupMessage):
if not self.node:
return None
try:
collision = ba.getcollision()
opposingnode = collision.opposingnode
opposingbody = collision.opposingbody
except ba.NotFoundError:
return True
try:
if opposingnode.invincible:
return True
except Exception:
pass
try:
playa = opposingnode._source_player.getname(True, False)
if not playa is None:
if opposingnode._source_player.lives > 0:
return True
except Exception:
pass
if (opposingnode.getnodetype() == 'spaz'
and not opposingnode.shattered and opposingbody == 4):
opposingbody = 1
held = self.node.hold_node
if held and held.getnodetype() == 'flag':
return True
self.node.hold_body = opposingbody
self.node.hold_node = opposingnode
else:
return super().handlemessage(m)
return None
class PlayerZombie(PlayerSpaz):
def handlemessage(self, m: Any) -> Any:
if isinstance(m, ba.HitMessage):
if not self.node:
return None
if not m._source_player is None:
try:
playa = m._source_player.getname(True, False)
if playa is None:
pass
else:
super().handlemessage(m)
except:
super().handlemessage(m)
else:
super().handlemessage(m)
else:
super().handlemessage(m)
class zBotSet(SpazBotSet):
def start_moving(self) -> None:
"""Start processing bot AI updates so they start doing their thing."""
self._bot_update_timer = ba.Timer(0.05,
ba.WeakCall(self.zUpdate),
repeat=True)
def zUpdate(self) -> None:
try:
bot_list = self._bot_lists[self._bot_update_list] = ([
b for b in self._bot_lists[self._bot_update_list] if b
])
except Exception:
bot_list = []
ba.print_exception('Error updating bot list: ' +
str(self._bot_lists[self._bot_update_list]))
self._bot_update_list = (self._bot_update_list +
1) % self._bot_list_count
player_pts = []
for player in ba.getactivity().players:
assert isinstance(player, ba.Player)
try:
if player.is_alive():
assert isinstance(player.actor, Spaz)
assert player.actor.node
if player.lives > 0:
player_pts.append(
(ba.Vec3(player.actor.node.position),
ba.Vec3(player.actor.node.velocity)))
except Exception:
ba.print_exception('Error on bot-set _update.')
for bot in bot_list:
bot.set_player_points(player_pts)
bot.update_ai()
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
self.spawn_order: List[Player] = []
# ba_meta export game
class ZombieHorde(ba.TeamGameActivity[Player, Team]):
name = 'Zombie Horde'
description = 'Kill walkers for points!'
scoreconfig = ba.ScoreConfig(label='Score',
scoretype=ba.ScoreType.POINTS,
none_is_winner=False,
lower_is_better=False)
# Show messages when players die since it's meaningful here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting(
'Lives Per Player',
default=1,
min_value=1,
max_value=10,
increment=1,
),
ba.IntSetting(
'Max Zombies',
default=10,
min_value=5,
max_value=50,
increment=5,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
]
if issubclass(sessiontype, ba.DualTeamSession):
settings.append(ba.BoolSetting('Solo Mode', default=False))
settings.append(
ba.BoolSetting('Balance Total Lives', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._start_time: Optional[float] = None
self._vs_text: Optional[ba.Actor] = None
self._round_end_timer: Optional[ba.Timer] = None
self._epic_mode = bool(settings['Epic Mode'])
self._lives_per_player = int(settings['Lives Per Player'])
self._max_zombies = int(settings['Max Zombies'])
self._time_limit = float(settings['Time Limit'])
self._balance_total_lives = bool(
settings.get('Balance Total Lives', False))
self._solo_mode = bool(settings.get('Solo Mode', False))
# Base class overrides:
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL)
self.spazList = []
self.zombieQ = 0
activity = ba.getactivity()
my_factory = SpazFactory.get()
appears = ['Kronk','Zoe','Pixel','Agent Johnson',
'Bones','Frosty','Kronk2']
myAppear = copy.copy(ba.app.spaz_appearances['Kronk'])
myAppear.name = 'Kronk2'
ba.app.spaz_appearances['Kronk2'] = myAppear
for appear in appears:
my_factory.get_media(appear)
med = my_factory.spaz_media
med['Kronk2']['head_model'] = med['Zoe']['head_model']
med['Kronk2']['color_texture'] = med['Agent Johnson']['color_texture']
med['Kronk2']['color_mask_texture']=med['Pixel']['color_mask_texture']
med['Kronk2']['torso_model'] = med['Bones']['torso_model']
med['Kronk2']['pelvis_model'] = med['Pixel']['pelvis_model']
med['Kronk2']['upper_arm_model'] = med['Frosty']['upper_arm_model']
med['Kronk2']['forearm_model'] = med['Frosty']['forearm_model']
med['Kronk2']['hand_model'] = med['Bones']['hand_model']
med['Kronk2']['upper_leg_model'] = med['Bones']['upper_leg_model']
med['Kronk2']['lower_leg_model'] = med['Pixel']['lower_leg_model']
med['Kronk2']['toes_model'] = med['Bones']['toes_model']
def get_instance_description(self) -> Union[str, Sequence]:
return ('Kill walkers for points! ',
'Dead player walker: 2 points!') if isinstance(
self.session, ba.DualTeamSession) else (
'Kill walkers for points! Dead player walker: 2 points!')
def get_instance_description_short(self) -> Union[str, Sequence]:
return ('Kill walkers for points! ',
'Dead player walker: 2 points!') if isinstance(
self.session, ba.DualTeamSession) else (
'Kill walkers for points! Dead player walker: 2 points!')
def on_player_join(self, player: Player) -> None:
if self.has_begun():
player.lives = 0
player.icons = []
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
player.lives = self._lives_per_player
if self._solo_mode:
player.icons = []
player.team.spawn_order.append(player)
self._update_solo_mode()
else:
player.icons = [Icon(player, position=(0, 50), scale=0.8)]
if player.lives > 0:
self.spawn_player(player)
if self.has_begun():
self._update_icons()
def _update_solo_mode(self) -> None:
for team in self.teams:
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 on_begin(self) -> None:
super().on_begin()
self._start_time = ba.time()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self.zombieQ = 1
if self._solo_mode:
self._vs_text = ba.NodeActor(
ba.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': ba.Lstr(resource='vsText')
}))
# If balance-team-lives is on, add lives to the smaller team until
# total lives match.
if (isinstance(self.session, ba.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._bots = zBotSet()
#Set colors and character for ToughGuyBot to be zombie
setattr(BrawlerBot, 'color', (0.4,0.1,0.05))
setattr(BrawlerBot, 'highlight', (0.2,0.4,0.3))
setattr(BrawlerBot, 'character', 'Kronk2')
# start some timers to spawn bots
thePt = self.map.get_ffa_start_position(self.players)
self._update_icons()
# We could check game-over conditions at explicit trigger points,
# but lets just do the simple thing and poll it.
ba.timer(1.0, self._update, repeat=True)
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, ba.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[ba.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 = ba.Vec3(living_player_pos)
points: List[Tuple[float, ba.Vec3]] = []
for team in self.teams:
start_pos = ba.Vec3(self.map.get_start_position(team.id))
points.append(
((start_pos - player_pos).length(), start_pos))
# Hmm.. we need to sorting vectors too?
points.sort(key=lambda x: x[0])
return points[-1][1]
return None
def spawn_player(self, player: Player) -> ba.Actor:
position = self.map.get_ffa_start_position(self.players)
angle = 20
name = player.getname()
light_color = _math.normalized_color(player.color)
display_color = _ba.safecolor(player.color, target_intensity=0.75)
spaz = PlayerSpaz_Zom(color=player.color,
highlight=player.highlight,
character=player.character,
player=player)
player.actor = spaz
assert spaz.node
self.spazList.append(spaz)
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()
factory = SpazFactory()
# Move to the stand position and add a flash of light.
spaz.handlemessage(
StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
_ba.playsound(self._spawn_sound, 1, position=spaz.node.position)
light = _ba.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
_ba.timer(0.5, light.delete)
if not self._solo_mode:
ba.timer(0.3, ba.Call(self._print_lives, player))
for icon in player.icons:
icon.handle_player_spawned()
return spaz
def respawn_player_zombie(self,
player: Player,
respawn_time: Optional[float] = None) -> None:
# pylint: disable=cyclic-import
assert player
if respawn_time is None:
teamsize = len(player.team.players)
if teamsize == 1:
respawn_time = 3.0
elif teamsize == 2:
respawn_time = 5.0
elif teamsize == 3:
respawn_time = 6.0
else:
respawn_time = 7.0
# If this standard setting is present, factor it in.
if 'Respawn Times' in self.settings_raw:
respawn_time *= self.settings_raw['Respawn Times']
# We want whole seconds.
assert respawn_time is not None
respawn_time = round(max(1.0, respawn_time), 0)
if player.actor and not self.has_ended():
from bastd.actor.respawnicon import RespawnIcon
player.customdata['respawn_timer'] = _ba.Timer(
respawn_time, ba.WeakCall(
self.spawn_player_if_exists_as_zombie, player))
player.customdata['respawn_icon'] = RespawnIcon(
player, respawn_time)
def spawn_player_if_exists_as_zombie(self, player: PlayerType) -> None:
"""
A utility method which calls self.spawn_player() *only* if the
ba.Player provided still exists; handy for use in timers and whatnot.
There is no need to override this; just override spawn_player().
"""
if player:
self.spawn_player_zombie(player)
def spawn_player_zombie(self, player: PlayerType) -> ba.Actor:
position = self.map.get_ffa_start_position(self.players)
angle = 20
name = player.getname()
light_color = _math.normalized_color(player.color)
display_color = _ba.safecolor(player.color, target_intensity=0.75)
spaz = PlayerSpaz_Zom(color=player.color,
highlight=player.highlight,
character='Kronk2',
player=player)
player.actor = spaz
assert spaz.node
self.spazList.append(spaz)
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(enable_punch=True,
enable_bomb=False,
enable_pickup=False)
# Move to the stand position and add a flash of light.
spaz.handlemessage(
StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
_ba.playsound(self._spawn_sound, 1, position=spaz.node.position)
light = _ba.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
_ba.timer(0.5, light.delete)
if not self._solo_mode:
ba.timer(0.3, ba.Call(self._print_lives, player))
for icon in player.icons:
icon.handle_player_spawned()
return spaz
def _print_lives(self, player: Player) -> None:
from bastd.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
try:
pos = player.actor.node.position
except Exception as e:
print('EXC getting player pos in bsElim',e)
return
if player.lives > 0:
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=pos).autoretain()
else:
popuptext.PopupText('Dead!',
color=(1, 1, 0, 1),
offset=(0, -0.8, 0),
random_offset=0.0,
scale=1.8,
position=pos).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.
ba.timer(0, self._update_icons)
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, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player: Player = msg.getplayer(Player)
if player.lives > 0:
player.lives -= 1
else:
if msg._killerplayer:
if msg._killerplayer.lives > 0:
msg._killerplayer.team.score += 2
self._update_scoreboard()
if msg._player in self.spazList:
self.spazList.remove(msg._player)
if player.lives < 0:
ba.print_error(
"Got lives < 0 in Elim; this shouldn't happen. solo:" +
str(self._solo_mode))
player.lives = 0
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_died()
# Play big death sound on our last death
# or for every one in solo mode.
if self._solo_mode or player.lives == 0:
ba.playsound(SpazFactory.get().single_player_death_sound)
# If we hit zero lives, we're dead (and our team might be too).
if player.lives == 0:
self.respawn_player_zombie(player)
else:
# Otherwise, in regular mode, respawn.
if not self._solo_mode:
self.respawn_player(player)
# In solo, put ourself at the back of the spawn order.
if self._solo_mode:
player.team.spawn_order.remove(player)
player.team.spawn_order.append(player)
elif isinstance(msg, SpazBotDiedMessage):
self._onSpazBotDied(msg)
super().handlemessage(msg)#bs.PopupText("died",position=self._position,color=popupColor,scale=popupScale).autoRetain()
else:
super().handlemessage(msg)
def _update(self) -> None:
if self.zombieQ > 0:
self.zombieQ -= 1
self.spawn_zombie()
if self._solo_mode:
# For both teams, find the first player on the spawn order
# list with lives remaining and spawn them if they're not alive.
for team in self.teams:
# Prune dead players from the spawn order.
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
self._update_icons()
break
# If we're down to 1 or fewer living teams, start a timer to end
# the game (allows the dust to settle and draws to occur if deaths
# are close enough).
teamsRemain = self._get_living_teams()
if len(teamsRemain) < 2:
if len(teamsRemain) == 1:
theScores = []
for team in self.teams:
theScores.append(team.score)
if teamsRemain[0].score < max(theScores):
pass
elif teamsRemain[0].score == max(
theScores) and theScores.count(max(theScores)) > 1:
pass
else:
self._round_end_timer = ba.Timer(0.5, self.end_game)
else:
self._round_end_timer = ba.Timer(0.5, self.end_game)
def spawn_zombie(self) -> None:
#We need a Z height...
thePt = list(self.get_random_point_in_play())
thePt2 = self.map.get_ffa_start_position(self.players)
thePt[1] = thePt2[1]
ba.timer(0.1, ba.Call(
self._bots.spawn_bot, BrawlerBot, pos=thePt, spawn_time=1.0))
def _onSpazBotDied(self,DeathMsg) -> None:
#Just in case we are over max...
if len(self._bots.get_living_bots()) < self._max_zombies:
self.zombieQ += 1
if DeathMsg.killerplayer is None:
pass
else:
player = DeathMsg.killerplayer
if not player:
return
if player.lives < 1:
return
player.team.score += 1
self.zombieQ += 1
self._update_scoreboard()
def get_random_point_in_play(self) -> None:
myMap = self.map.getname()
if myMap == 'Doom Shroom':
while True:
x = random.uniform(-1.0,1.0)
y = random.uniform(-1.0,1.0)
if x*x+y*y < 1.0: break
return ((8.0*x,8.0,-3.5+5.0*y))
elif myMap == 'Rampage':
x = random.uniform(-6.0,7.0)
y = random.uniform(-6.0,-2.5)
return ((x, 8.0, y))
elif myMap == 'Hockey Stadium':
x = random.uniform(-11.5,11.5)
y = random.uniform(-4.5,4.5)
return ((x, 5.0, y))
elif myMap == 'Courtyard':
x = random.uniform(-4.3,4.3)
y = random.uniform(-4.4,0.3)
return ((x, 8.0, y))
elif myMap == 'Crag Castle':
x = random.uniform(-6.7,8.0)
y = random.uniform(-6.0,0.0)
return ((x, 12.0, y))
elif myMap == 'Big G':
x = random.uniform(-8.7,8.0)
y = random.uniform(-7.5,6.5)
return ((x, 8.0, y))
elif myMap == 'Football Stadium':
x = random.uniform(-12.5,12.5)
y = random.uniform(-5.0,5.5)
return ((x, 8.0, y))
else:
x = random.uniform(-5.0,5.0)
y = random.uniform(-6.0,0.0)
return ((x, 8.0, y))
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(team, team.score)
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
setattr(BrawlerBot, 'color', (0.6, 0.6, 0.6))
setattr(BrawlerBot, 'highlight', (0.6, 0.6, 0.6))
setattr(BrawlerBot, 'character', 'Kronk')
results = ba.GameResults()
self._vs_text = None # Kill our 'vs' if its there.
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

View file

@ -0,0 +1,510 @@
# Released under the MIT License. See LICENSE for details.
#
"""Elimination mini-game."""
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
from bastd.actor.spazfactory import SpazFactory
from bastd.actor.scoreboard import Scoreboard
if TYPE_CHECKING:
from typing import (Any, Tuple, Dict, Type, List, Sequence, Optional,
Union)
class Icon(ba.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 = ba.gettexture('characterIconMask')
icon = player.get_icon()
self.node = ba.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 = ba.newnode(
'text',
owner=self.node,
attrs={
'text': ba.Lstr(value=player.getname()),
'color': ba.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 = ba.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:
ba.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:
ba.timer(0.6, self.update_for_lives)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
self.node.delete()
return None
return super().handlemessage(msg)
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.lives = 0
self.icons: List[Icon] = []
class Team(ba.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 game
class AllianceEliminationGame(ba.TeamGameActivity[Player, Team]):
"""Game type where last player(s) left alive win."""
name = 'Alliance Elimination'
description = 'Fight in groups of duo, trio, or more.\nLast remaining alive wins.'
scoreconfig = ba.ScoreConfig(label='Survived',
scoretype=ba.ScoreType.SECONDS,
none_is_winner=True)
# Show messages when players die since it's meaningful here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting(
'Lives Per Player',
default=1,
min_value=1,
max_value=10,
increment=1,
),
ba.IntSetting(
'Players Per Team In Arena',
default=2,
min_value=2,
max_value=10,
increment=1,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
]
if issubclass(sessiontype, ba.DualTeamSession):
settings.append(
ba.BoolSetting('Balance Total Lives', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return issubclass(sessiontype, ba.DualTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._start_time: Optional[float] = None
self._vs_text: Optional[ba.Actor] = None
self._round_end_timer: Optional[ba.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._players_per_team_in_arena = int(
settings['Players Per Team In Arena'])
# Base class overrides:
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL)
def get_instance_description(self) -> Union[str, Sequence]:
return 'Last team standing wins.' if isinstance(
self.session, ba.DualTeamSession) else 'Last one standing wins.'
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'last team standing wins' if isinstance(
self.session, ba.DualTeamSession) else 'last one standing wins'
def on_player_join(self, player: Player) -> None:
# No longer allowing mid-game joiners here; too easy to exploit.
if self.has_begun():
# Make sure their team has survival seconds set if they're all dead
# (otherwise blocked new ffa players are considered 'still alive'
# in score tallying).
if (self._get_total_team_lives(player.team) == 0
and player.team.survival_seconds is None):
player.team.survival_seconds = 0
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
player.lives = self._lives_per_player
player.team.spawn_order.append(player)
self._update_alliance_mode()
# 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 = ba.time()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._vs_text = ba.NodeActor(
ba.newnode('text',
attrs={
'position': (0, 92),
'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': ba.Lstr(resource='vsText')
}))
# If balance-team-lives is on, add lives to the smaller team until
# total lives match.
if (isinstance(self.session, ba.DualTeamSession)
and self._balance_total_lives and self.teams[0].players
and self.teams[1].players):
if self._get_total_team_lives(
self.teams[0]) < self._get_total_team_lives(self.teams[1]):
lesser_team = self.teams[0]
greater_team = self.teams[1]
else:
lesser_team = self.teams[1]
greater_team = self.teams[0]
add_index = 0
while (self._get_total_team_lives(lesser_team) <
self._get_total_team_lives(greater_team)):
lesser_team.players[add_index].lives += 1
add_index = (add_index + 1) % len(lesser_team.players)
self._update_icons()
# We could check game-over conditions at explicit trigger points,
# but lets just do the simple thing and poll it.
ba.timer(1.0, self._update, repeat=True)
def _update_alliance_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.
players_spawned = 0
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
self._update_icons()
players_spawned += 1
if players_spawned >= self._players_per_team_in_arena:
break
def _update_icons(self) -> None:
# pylint: disable=too-many-branches
# 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
nplayers = self._players_per_team_in_arena
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, (36 if nplayers > 0 else 25)),
scale=0.9 if nplayers > 0 else 0.5,
name_maxwidth=85 if nplayers > 0 else 75,
name_scale=0.8 if nplayers > 0 else 1.0,
flatness=0.0 if nplayers > 0 else 1.0,
shadow=0.5 if nplayers > 0 else 1.0,
show_death=True if nplayers > 0 else False,
show_lives=False))
xval += x_offs * (0.85 if nplayers > 0 else 0.56)
nplayers -= 1
test_lives += 1
def _get_spawn_point(self, player: Player) -> Optional[ba.Vec3]:
return None
def spawn_player(self, player: Player) -> ba.Actor:
actor = self.spawn_player_spaz(player, self._get_spawn_point(player))
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_spawned()
return actor
def _print_lives(self, player: Player) -> None:
from bastd.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 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.
ba.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(ba.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, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player: Player = msg.getplayer(Player)
player.lives -= 1
if player.lives < 0:
ba.print_error(
"Got lives < 0 in Alliance Elimination; this shouldn't happen.")
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.
if player.lives == 0:
ba.playsound(SpazFactory.get().single_player_death_sound)
# 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(ba.time() -
self._start_time)
# Put ourself at the back of the spawn order.
player.team.spawn_order.remove(player)
player.team.spawn_order.append(player)
def _update(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]
players_spawned = 0
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
self._update_icons()
players_spawned += 1
if players_spawned >= self._players_per_team_in_arena:
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 = ba.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 = ba.GameResults()
self._vs_text = None # Kill our 'vs' if its there.
for team in self.teams:
results.set_team_score(team, team.survival_seconds)
self.end(results=results)

278
dist/ba_root/mods/games/baDarkFields.py vendored Normal file
View file

@ -0,0 +1,278 @@
"""Dark fields mini-game."""
# Minigame by Froshlee14
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random
from typing import TYPE_CHECKING
import _ba, ba
from bastd.actor import bomb
from ba._music import setmusic
from bastd.actor.scoreboard import Scoreboard
from ba._gameutils import animate_array
from bastd.gameutils import SharedObjects
from bastd.actor.playerspaz import PlayerSpaz
if TYPE_CHECKING:
from typing import Any, Sequence, Optional, List, Dict, Type, Type
class Player(ba.Player['Team']):
"""Our player type for this game."""
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game
class DarkFieldsGame(ba.TeamGameActivity[Player, Team]):
name = 'Dark Fields'
description = 'Get to the other side.'
available_settings = [
ba.IntSetting('Score to Win',
min_value=1,
default=3,
),
ba.IntChoiceSetting('Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting('Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
ba.BoolSetting('Players as center of interest', default=True),
]
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('football')
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession))
def __init__(self, settings: dict):
super().__init__(settings)
self._epic_mode = bool(settings['Epic Mode'])
self._center_of_interest = bool(settings['Players as center of interest'])
self._score_to_win_per_player = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
self._scoreboard = Scoreboard()
shared = SharedObjects.get()
self._scoreRegionMaterial = ba.Material()
self._scoreRegionMaterial.add_actions(
conditions=("they_have_material",shared.player_material),
actions=(("modify_part_collision","collide",True),
("modify_part_collision","physical",False),
("call","at_connect", self._onPlayerScores)))
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC if self._epic_mode else None)
def on_transition_in(self) -> None:
super().on_transition_in()
gnode = ba.getactivity().globalsnode
gnode.tint = (0.5,0.5,0.5)
a = ba.newnode('locator',attrs={'shape':'box','position':(12.2,0,.1087926362),
'color':(5,0,0),'opacity':1,'draw_beauty':True,'additive':False,'size':[2.5,0.1,12.8]})
b = ba.newnode('locator',attrs={'shape':'box','position':(-12.1,0,.1087926362),
'color':(0,0,5),'opacity':1,'draw_beauty':True,'additive':False,'size':[2.5,0.1,12.8]})
def on_begin(self) -> None:
self._has_begun = False
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self._score_to_win = (self._score_to_win_per_player *
max(1, max(len(t.players) for t in self.teams)))
self._update_scoreboard()
self.isUpdatingMines = False
self._scoreSound = ba.getsound('dingSmall')
for p in self.players:
if p.actor is not None:
try:
p.actor.disconnect_controls_from_player()
except Exception:
print('Can\'t connect to player')
self._scoreRegions = []
defs = ba.getactivity().map.defs
self._scoreRegions.append(ba.NodeActor(ba.newnode('region',
attrs={'position':defs.boxes['goal1'][0:3],
'scale':defs.boxes['goal1'][6:9],
'type': 'box',
'materials':(self._scoreRegionMaterial,)})))
self.mines = []
self.spawnMines()
ba.timer(0.8 if self.slow_motion else 1.7,self.start)
def start(self):
self._has_begun = True
self._show_info()
ba.timer(random.randrange(3,7),self.doRandomLighting)
if not self._epic_mode:
setmusic(ba.MusicType.SCARY)
animate_array(ba.getactivity().globalsnode,'tint',3,{0:(0.5,0.5,0.5),2:(0.2,0.2,0.2)})
for p in self.players:
self.doPlayer(p)
def spawn_player(self, player):
if not self._has_begun:
return
else:
self.doPlayer(player)
def doPlayer(self,player):
pos = (-12.4,1,random.randrange(-5,5))
player = self.spawn_player_spaz(player,pos)
player.connect_controls_to_player(enable_punch=False,enable_bomb=False)
player.node.is_area_of_interest = self._center_of_interest
def _show_info(self) -> None:
if self._has_begun:
super()._show_info()
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scoreboard()
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(team, team.score, self._score_to_win)
def doRandomLighting(self):
ba.timer(random.randrange(3,7),self.doRandomLighting)
if self.isUpdatingMines: return
delay = 0
for mine in self.mines:
if mine.node.exists():
pos = mine.node.position
ba.timer(delay,ba.Call(self.do_light,pos))
delay += 0.005 if self._epic_mode else 0.01
def do_light(self,pos):
light = ba.newnode('light',attrs={
'position': pos,
'volume_intensity_scale': 1.0,
'radius':0.1,
'color': (1,0,0)
})
ba.animate(light, 'intensity', { 0: 2.0, 3.0: 0.0})
ba.timer(3.0, light.delete)
def spawnMines(self):
delay = 0
h_range = [10,8,6,4,2,0,-2,-4,-6,-8,-10]
for h in h_range:
for i in range(random.randint(3,4)):
x = h+random.random()
y = random.randrange(-5,6)+(random.random())
pos = (x,1,y)
ba.timer(delay,ba.Call(self.doMine,pos))
delay += 0.015 if self._epic_mode else 0.04
ba.timer(5.0,self.stopUpdateMines)
def stopUpdateMines(self):
self.isUpdatingMines = False
def updateMines(self):
if self.isUpdatingMines: return
self.isUpdatingMines = True
for m in self.mines:
m.node.delete()
self.mines = []
self.spawnMines()
def doMine(self,pos):
b = bomb.Bomb(position=pos,bomb_type='land_mine').autoretain()
b.add_explode_callback(self._on_bomb_exploded)
b.arm()
self.mines.append(b)
def _on_bomb_exploded(self, bomb: Bomb, blast: Blast) -> None:
assert blast.node
p = blast.node.position
pos = (p[0],p[1]+1,p[2])
ba.timer(0.5,ba.Call(self.doMine,pos))
def _onPlayerScores(self):
player: Optional[Player]
try:
player = ba.getcollision().opposingnode.getdelegate(PlayerSpaz, True).getplayer(Player, True)
except ba.NotFoundError:
player = None
if player.exists() and player.is_alive():
player.team.score += 1
ba.playsound(self._scoreSound)
pos = player.actor.node.position
animate_array(ba.getactivity().globalsnode,'tint',3,{0:(0.5,0.5,0.5),2.8:(0.2,0.2,0.2)})
self._update_scoreboard()
light = ba.newnode('light',
attrs={
'position': pos,
'radius': 0.5,
'color': (1, 0, 0)
})
ba.animate(light, 'intensity', {0.0: 0, 0.1: 1, 0.5: 0}, loop=False)
ba.timer(1.0, light.delete)
player.actor.handlemessage(ba.DieMessage( how=ba.DeathType.REACHED_GOAL))
self.updateMines()
if any(team.score >= self._score_to_win for team in self.teams):
ba.timer(0.5, self.end_game)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
self.respawn_player(player)
else:
return super().handlemessage(msg)
return None
def end_game(self) -> None:
results = ba.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

319
dist/ba_root/mods/games/running_bombs.py vendored Normal file
View file

@ -0,0 +1,319 @@
# Released under the MIT License. See LICENSE for details.
#
"""Defines a bomb-dodging mini-game."""
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from bastd.gameutils import SharedObjects
from bastd.actor.playerspaz import PlayerSpaz
import ba
import bastd.actor.bomb
from bastd.actor.bomb import Bomb, BombFactory
from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
from typing import Any, Sequence, Optional, List, Dict, Type, Type
__version__ = "1.2"
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
super().__init__()
self.death_time: Optional[float] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
# ba_meta export game
class RunningBombsGame(ba.TeamGameActivity[Player, Team]):
"""Minigame involving dodging running bombs."""
name = 'Running Bombs'
description = 'Dodge the running bombs.'
available_settings = [ba.BoolSetting('Epic Mode', default=False)]
scoreconfig = ba.ScoreConfig(label='Survived',
scoretype=ba.ScoreType.MILLISECONDS,
version='B')
# Print messages when players die (since its meaningful in this game).
announce_player_deaths = True
# we're currently hard-coded for one map..
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ['Football Stadium']
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession)
or issubclass(sessiontype, ba.CoopSession))
def __init__(self, settings: dict):
super().__init__(settings)
self._timer: Optional[OnScreenTimer] = None
self._epic_mode = settings.get('Epic Mode', False)
self._score_regions: List[ba.NodeActor] = []
self._last_player_death_time: Optional[float] = None
self._meteor_time = 2.0
shared = SharedObjects.get()
self.kill_player_region_material = ba.Material()
self.kill_player_region_material.add_actions(
conditions = ("they_have_material", shared.player_material),
actions = (("modify_part_collision", "collide", True),
("modify_part_collision", "physical", False),
("call", "at_connect", self._kill_player)))
factory = BombFactory.get()
self.kill_bomb_region_material = ba.Material()
self.kill_bomb_region_material.add_actions(
conditions = ("they_have_material", factory.bomb_material),
actions = (("modify_part_collision", "collide", True),
("modify_part_collision", "physical", False),
("call", "at_connect", self._kill_bomb)))
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL)
if self._epic_mode:
self.slow_motion = True
def on_begin(self) -> None:
super().on_begin()
defs = self.map.defs
self._score_regions.append(
ba.NodeActor(
ba.newnode('region',
attrs={
'position': defs.boxes['goal1'][0:3],
'scale': defs.boxes['goal1'][6:9],
'type': 'box',
'materials': (self.kill_player_region_material, )
})))
self._score_regions.append(
ba.NodeActor(
ba.newnode('region',
attrs={
'position': defs.boxes['goal2'][0:3],
'scale': defs.boxes['goal2'][6:9],
'type': 'box',
'materials': (self.kill_player_region_material, self.kill_bomb_region_material)
})))
# Drop a wave every few seconds.. and every so often drop the time
# between waves ..lets have things increase faster if we have fewer
# players.
delay = 5.0 if len(self.players) > 2 else 2.5
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._decrement_meteor_time, repeat=True)
# Kick off the first wave in a few seconds.
delay = 3.0
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._set_meteor_timer)
self._timer = OnScreenTimer()
ba.timer(4, self._timer.start)
# Check for immediate end (if we've only got 1 player, etc).
# ba.timer(5.0, self._check_end_game)
def on_player_join(self, player: Player) -> None:
# Don't allow joining after we start
# (would enable leave/rejoin tomfoolery).
if self.has_begun():
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
# For score purposes, mark them as having died right as the
# game started.
assert self._timer is not None
player.death_time = self._timer.getstarttime()
return
self.spawn_player(player)
def on_player_leave(self, player: Player) -> None:
# Augment default behavior.
super().on_player_leave(player)
# A departing player may trigger game-over.
self._check_end_game()
def _kill_player(self):
try: player = ba.getcollision().opposingnode.getdelegate(PlayerSpaz, True).getplayer(Player, True)
except: return
if player.exists():
player.actor.handlemessage(ba.DieMessage())
player.actor.shatter()
def _kill_bomb(self):
try: bomb = ba.getcollision().opposingnode.getdelegate(Bomb, True)
except: return
if bomb.exists():
bomb.handlemessage(ba.DieMessage())
# overriding the default character spawning..
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=False,
enable_bomb=False,
enable_pickup=False)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
return spaz
# Various high-level game events come through this method.
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
curtime = ba.time()
# Record the player's moment of death.
# assert isinstance(msg.spaz.player
msg.getplayer(Player).death_time = curtime
# In co-op mode, end the game the instant everyone dies
# (more accurate looking).
# In teams/ffa, allow a one-second fudge-factor so we can
# get more draws if players die basically at the same time.
if isinstance(self.session, ba.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
ba.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = curtime
else:
ba.timer(1.0, self._check_end_game)
else:
# Default handler:
return super().handlemessage(msg)
return None
def _check_end_game(self) -> None:
living_team_count = 0
for team in self.teams:
for player in team.players:
if player.is_alive():
living_team_count += 1
break
# In co-op, we go till everyone is dead.. otherwise we go
# until one team remains.
if isinstance(self.session, ba.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def _set_meteor_timer(self) -> None:
ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
self._drop_bomb_cluster)
def _drop_bomb_cluster(self) -> None:
# Random note: code like this is a handy way to plot out extents
# and debug things.
loc_test = False
if loc_test:
ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
# Drop several bombs in series.
delay = 0.0
for _i in range(random.randrange(1, 3)):
# Drop them somewhere within our bounds with velocity pointing
# toward the opposite side.
pos = (-7.3 + 15.3 * random.random(), 11,
-5.5 + 2.1 * random.random())
dropdir = (-1.0 if pos[0] > 0 else 1.0)
vel = ((-5.0 + random.random() * 30.0) * dropdir, -4.0, 0)
ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
delay += 0.1
self._set_meteor_timer()
def _drop_bomb(self, position: Sequence[float],
velocity: Sequence[float]) -> None:
x = [-5,-4.5,-4,-3.5,-3,-2.5,-2,-1.5,-1,-0.5,0,0.5,1,1.5,2,2.5,3,3.5,4,4.5,5]
num = 0
for i in x:
num += 1
if num == 5: break
pos = (13.4,1,random.choice(x))
Bomb(position=pos, velocity=velocity).autoretain()
def _decrement_meteor_time(self) -> None:
self._meteor_time = max(0.01, self._meteor_time * 0.9)
def end_game(self) -> None:
cur_time = ba.time()
assert self._timer is not None
start_time = self._timer.getstarttime()
# Mark death-time as now for any still-living players
# and award players points for how long they lasted.
# (these per-player scores are only meaningful in team-games)
for team in self.teams:
for player in team.players:
survived = False
# Throw an extra fudge factor in so teams that
# didn't die come out ahead of teams that did.
if player.death_time is None:
survived = True
player.death_time = cur_time + 1
# Award a per-player score depending on how many seconds
# they lasted (per-player scores only affect teams mode;
# everywhere else just looks at the per-team score).
score = int(player.death_time - self._timer.getstarttime())
if survived:
score += 50 # A bit extra for survivors.
self.stats.player_scored(player, score, screenmessage=False)
# Stop updating our time text, and set the final time to match
# exactly when our last guy died.
self._timer.stop(endtime=self._last_player_death_time)
# Ok now calc game results: set a score for each team and then tell
# the game to end.
results = ba.GameResults()
# Remember that 'free-for-all' mode is simply a special form
# of 'teams' mode where each player gets their own team, so we can
# just always deal in teams and have all cases covered.
for team in self.teams:
# Set the team score to the max time survived by any player on
# that team.
longest_life = 0.0
for player in team.players:
assert player.death_time is not None
longest_life = max(longest_life,
player.death_time - start_time)
# Submit the score value in milliseconds.
results.set_team_score(team, int(1000.0 * longest_life))
self.end(results=results)

716
dist/ba_root/mods/games/safe_zone.py vendored Normal file
View file

@ -0,0 +1,716 @@
# Released under the MIT License. See LICENSE for details.
#
"""Elimination mini-game."""
# Maded by Froshlee14
# Update by SEBASTIAN2059
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba, _ba
import random
from bastd.actor.spazfactory import SpazFactory
from bastd.actor.scoreboard import Scoreboard
from bastd.actor import spazbot as stdbot
from bastd.gameutils import SharedObjects as so
if TYPE_CHECKING:
from typing import (Any, Tuple, Dict, Type, List, Sequence, Optional,
Union)
class Icon(ba.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 = ba.gettexture('characterIconMask')
icon = player.get_icon()
self.node = ba.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 = ba.newnode(
'text',
owner=self.node,
attrs={
'text': ba.Lstr(value=player.getname()),
'color': ba.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 = ba.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:
ba.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:
ba.timer(0.6, self.update_for_lives)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
self.node.delete()
return None
return super().handlemessage(msg)
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.lives = 0
self.icons: List[Icon] = []
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.survival_seconds: Optional[int] = None
self.spawn_order: List[Player] = []
lang = ba.app.lang.language
if lang == 'Spanish':
description = 'Mantente en la zona segura.'
join_description = 'Corre hacia la zona segura.'
kill_timer = 'Kill timer: '
else:
description = 'Stay in the safe zone.'
join_description = 'Run into the safe zone'
kill_timer = 'Kill timer: '
# ba_meta export game
class SafeZoneGame(ba.TeamGameActivity[Player, Team]):
"""Game type where last player(s) left alive win."""
name = 'Safe Zone'
description = description
scoreconfig = ba.ScoreConfig(label='Survived',
scoretype=ba.ScoreType.SECONDS,
none_is_winner=True)
# Show messages when players die since it's meaningful here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting(
'Lives Per Player',
default=2,
min_value=1,
max_value=10,
increment=1,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Short', 0.25),
('Normal', 0.5),
],
default=0.5,
),
ba.BoolSetting('Epic Mode', default=False),
]
if issubclass(sessiontype, ba.DualTeamSession):
settings.append(ba.BoolSetting('Solo Mode', default=False))
settings.append(
ba.BoolSetting('Balance Total Lives', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ['Football Stadium','Hockey Stadium']
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._start_time: Optional[float] = None
self._vs_text: Optional[ba.Actor] = None
self._round_end_timer: Optional[ba.Timer] = None
self._epic_mode = bool(settings['Epic Mode'])
self._lives_per_player = int(settings['Lives Per Player'])
self._time_limit = float(settings['Time Limit'])
self._balance_total_lives = bool(
settings.get('Balance Total Lives', False))
self._solo_mode = bool(settings.get('Solo Mode', False))
# Base class overrides:
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL)
self._tick_sound = ba.getsound('tick')
def get_instance_description(self) -> Union[str, Sequence]:
return join_description
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'last team standing wins' if isinstance(
self.session, ba.DualTeamSession) else 'last one standing wins'
def on_player_join(self, player: Player) -> None:
# No longer allowing mid-game joiners here; too easy to exploit.
if self.has_begun():
# Make sure their team has survival seconds set if they're all dead
# (otherwise blocked new ffa players are considered 'still alive'
# in score tallying).
if (self._get_total_team_lives(player.team) == 0
and player.team.survival_seconds is None):
player.team.survival_seconds = 0
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
player.lives = self._lives_per_player
if self._solo_mode:
player.team.spawn_order.append(player)
self._update_solo_mode()
else:
# Create our icon and spawn.
player.icons = [Icon(player, position=(0, 50), scale=0.8)]
if player.lives > 0:
self.spawn_player(player)
# Don't waste time doing this until begin.
if self.has_begun():
self._update_icons()
def on_begin(self) -> None:
super().on_begin()
self._start_time = ba.time()
self.setup_standard_time_limit(self._time_limit)
#self.setup_standard_powerup_drops()
ba.timer(5,self.spawn_zone)
self._bots = stdbot.SpazBotSet()
ba.timer(3,ba.Call(self.add_bot,'left'))
ba.timer(3,ba.Call(self.add_bot,'right'))
if len(self.initialplayerinfos) > 4:
ba.timer(5,ba.Call(self.add_bot,'right'))
ba.timer(5,ba.Call(self.add_bot,'left'))
if self._solo_mode:
self._vs_text = ba.NodeActor(
ba.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': ba.Lstr(resource='vsText')
}))
# If balance-team-lives is on, add lives to the smaller team until
# total lives match.
if (isinstance(self.session, ba.DualTeamSession)
and self._balance_total_lives and self.teams[0].players
and self.teams[1].players):
if self._get_total_team_lives(
self.teams[0]) < self._get_total_team_lives(self.teams[1]):
lesser_team = self.teams[0]
greater_team = self.teams[1]
else:
lesser_team = self.teams[1]
greater_team = self.teams[0]
add_index = 0
while (self._get_total_team_lives(lesser_team) <
self._get_total_team_lives(greater_team)):
lesser_team.players[add_index].lives += 1
add_index = (add_index + 1) % len(lesser_team.players)
self._update_icons()
# We could check game-over conditions at explicit trigger points,
# but lets just do the simple thing and poll it.
ba.timer(1.0, self._update, repeat=True)
def spawn_zone(self):
self.zone_pos = (random.randrange(-10,10),0.05,random.randrange(-5,5))
self.zone = ba.newnode('locator',attrs={'shape':'circle','position':self.zone_pos,'color':(1, 1, 0),'opacity':0.8,'draw_beauty':True,'additive':False,'drawShadow':False})
self.zone_limit = ba.newnode('locator',attrs={'shape':'circleOutline','position':self.zone_pos,'color':(1, 0.2, 0.2),'opacity':0.8,'draw_beauty':True,'additive':False,'drawShadow':False})
ba.animate_array(self.zone, 'size', 1,{0:[0], 0.3:[self.get_players_count()*0.85], 0.35:[self.get_players_count()*0.8]})
ba.animate_array(self.zone_limit, 'size', 1,{0:[0], 0.3:[self.get_players_count()*1.2], 0.35:[self.get_players_count()*0.95]})
self.last_players_count = self.get_players_count()
ba.playsound(ba.getsound('laserReverse'))
self.start_timer()
self.move_zone()
def delete_zone(self):
self.zone.delete()
self.zone = None
self.zone_limit.delete()
self.zone_limit = None
ba.playsound(ba.getsound('shieldDown'))
ba.timer(1,self.spawn_zone)
def move_zone(self):
if self.zone_pos[0] > 0: x = random.randrange(0,10)
else: x = random.randrange(-10,0)
if self.zone_pos[2] > 0: y = random.randrange(0,5)
else: y = random.randrange(-5,0)
new_pos = (x,0.05,y)
ba.animate_array(self.zone, 'position', 3,{0:self.zone.position, 8:new_pos})
ba.animate_array(self.zone_limit, 'position', 3,{0:self.zone_limit.position,8:new_pos})
def start_timer(self):
count = self.get_players_count()
self._time_remaining = 10 if count > 9 else count-1 if count > 6 else count if count > 2 else count*2
self._timer_x = ba.Timer(1.0,ba.WeakCall(self.tick),repeat=True)
# gnode = ba.getactivity().globalsnode
# tint = gnode.tint
# ba.animate_array(gnode,'tint',3,{0:tint,self._time_remaining*1.5:(1.0,0.5,0.5),self._time_remaining*1.55:tint})
def stop_timer(self):
self._time = None
self._timer_x = None
def tick(self):
self.check_players()
self._time = ba.NodeActor(ba.newnode('text',
attrs={'v_attach':'top','h_attach':'center',
'text':kill_timer+str(self._time_remaining)+'s',
'opacity':0.8,'maxwidth':100,'h_align':'center',
'v_align':'center','shadow':1.0,'flatness':1.0,
'color':(1,1,1),'scale':1.5,'position':(0,-50)}
)
)
self._time_remaining -= 1
ba.playsound(self._tick_sound)
def check_players(self):
if self._time_remaining <= 0:
self.stop_timer()
ba.animate_array(self.zone, 'size', 1,{0:[self.last_players_count*0.8], 1.4:[self.last_players_count*0.8],1.5:[0]})
ba.animate_array(self.zone_limit, 'size', 1,{0:[self.last_players_count*0.95], 1.45:[self.last_players_count*0.95],1.5:[0]})
ba.timer(1.5,self.delete_zone)
for player in self.players:
if not player.actor is None:
if player.actor.is_alive():
p1 = player.actor.node.position
p2 = self.zone.position
diff = (ba.Vec3(p1[0]-p2[0],0.0,p1[2]-p2[2]))
dist = (diff.length())
if dist > (self.get_players_count()*0.7):
player.actor.handlemessage(ba.DieMessage())
def get_players_count(self):
count = 0
for player in self.players:
if not player.actor is None:
if player.actor.is_alive():
count += 1
return count
def _update_solo_mode(self) -> None:
# For both teams, find the first player on the spawn order list with
# lives remaining and spawn them if they're not alive.
for team in self.teams:
# Prune dead players from the spawn order.
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
break
def _update_icons(self) -> None:
# pylint: disable=too-many-branches
# In free-for-all mode, everyone is just lined up along the bottom.
if isinstance(self.session, ba.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[ba.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 = ba.Vec3(living_player_pos)
points: List[Tuple[float, ba.Vec3]] = []
for team in self.teams:
start_pos = ba.Vec3(self.map.get_start_position(team.id))
points.append(
((start_pos - player_pos).length(), start_pos))
# Hmm.. we need to sorting vectors too?
points.sort(key=lambda x: x[0])
return points[-1][1]
return None
def spawn_player(self, player: Player) -> ba.Actor:
actor = self.spawn_player_spaz(player, self._get_spawn_point(player))
if not self._solo_mode:
ba.timer(0.3, ba.Call(self._print_lives, player))
# spaz but *without* the ability to attack or pick stuff up.
actor.connect_controls_to_player(enable_punch=False,
enable_bomb=False,
enable_pickup=False)
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_spawned()
return actor
def _print_lives(self, player: Player) -> None:
from bastd.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.
ba.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(ba.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, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player: Player = msg.getplayer(Player)
player.lives -= 1
if player.lives < 0:
ba.print_error(
"Got lives < 0 in Elim; this shouldn't happen. solo:" +
str(self._solo_mode))
player.lives = 0
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_died()
# Play big death sound on our last death
# or for every one in solo mode.
if self._solo_mode or player.lives == 0:
ba.playsound(SpazFactory.get().single_player_death_sound)
# 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(ba.time() -
self._start_time)
else:
# Otherwise, in regular mode, respawn.
if not self._solo_mode:
self.respawn_player(player)
# In solo, put ourself at the back of the spawn order.
if self._solo_mode:
player.team.spawn_order.remove(player)
player.team.spawn_order.append(player)
elif isinstance(msg,stdbot.SpazBotDiedMessage):
self._on_spaz_bot_died(msg)
def _on_spaz_bot_died(self,die_msg):
ba.timer(1,ba.Call(self.add_bot,die_msg.spazbot.node.position))
def _on_bot_spawn(self,spaz):
spaz.update_callback = self.move_bot
spaz_type = type(spaz)
spaz._charge_speed = self._get_bot_speed(spaz_type)
def add_bot(self,pos=None):
if pos == 'left': position = (-11,0,random.randrange(-5,5))
elif pos == 'right': position = (11,0,random.randrange(-5,5))
else: position = pos
self._bots.spawn_bot(self.get_random_bot(),pos=position,spawn_time=1,on_spawn_call=ba.Call(self._on_bot_spawn))
def move_bot(self,bot):
p = bot.node.position
speed = -bot._charge_speed if(p[0]>=-11 and p[0]<0) else bot._charge_speed
if (p[0]>=-11) and (p[0]<=11):
bot.node.move_left_right = speed
bot.node.move_up_down = 0.0
bot.node.run = 0.0
return True
return False
def get_random_bot(self):
bots = [stdbot.BomberBotStatic, stdbot.TriggerBotStatic]
return (random.choice(bots))
def _get_bot_speed(self, bot_type):
if bot_type == stdbot.BomberBotStatic:
return 0.48
elif bot_type == stdbot.TriggerBotStatic:
return 0.73
else:
raise Exception('Invalid bot type to _getBotSpeed(): '+str(bot_type))
def _update(self) -> None:
if self._solo_mode:
# For both teams, find the first player on the spawn order
# list with lives remaining and spawn them if they're not alive.
for team in self.teams:
# Prune dead players from the spawn order.
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
self._update_icons()
break
# If we're down to 1 or fewer living teams, start a timer to end
# the game (allows the dust to settle and draws to occur if deaths
# are close enough).
if len(self._get_living_teams()) < 2:
self._round_end_timer = ba.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 = ba.GameResults()
self._vs_text = None # Kill our 'vs' if its there.
for team in self.teams:
results.set_team_score(team, team.survival_seconds)
self.end(results=results)

314
dist/ba_root/mods/games/snake.py vendored Normal file
View file

@ -0,0 +1,314 @@
#snake
# Released under the MIT License. See LICENSE for details.
#
"""Snake game by SEBASTIAN2059"""
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.actor import bomb as stdbomb
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional
class ScoreMessage:
"""It will help us with the scores."""
def __init__(self,player: Player):
self.player = player
def getplayer(self):
return self.player
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.mines = []
self.actived = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
lang = ba.app.lang.language
if lang == 'Spanish':
description = 'Sobrevive a un número determinado de minas para ganar.'
join_description = 'Corre y no te dejes matar.'
view_description = 'sobrevive ${ARG1} minas'
else:
description = 'Survive a set number of mines to win.'
join_description = "Run and don't get killed."
view_description = 'survive ${ARG1} mines'
class Custom_Mine(stdbomb.Bomb):
"""Custom a mine :)"""
def __init__(self,position,source_player):
stdbomb.Bomb.__init__(self,position=position,bomb_type='land_mine',source_player=source_player)
def handlemessage(self,msg: Any) -> Any:
if isinstance(msg, ba.HitMessage):
return
else:
super().handlemessage(msg)
# ba_meta export game
class SnakeGame(ba.TeamGameActivity[Player, Team]):
"""A game type based on acquiring kills."""
name = 'Snake'
description = description
# Print messages when players die since it matters here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting(
'Score to Win',
min_value=40,
default=80,
increment=5,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
]
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_to_win: Optional[int] = None
self._dingsound = ba.getsound('dingSmall')
self._beep_1_sound = ba.getsound('raceBeep1')
self._beep_2_sound = ba.getsound('raceBeep2')
self._epic_mode = bool(settings['Epic Mode'])
self._kills_to_win_per_player = int(
settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
self._started = False
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC if self._epic_mode else
ba.MusicType.TO_THE_DEATH)
def get_instance_description(self) -> Union[str, Sequence]:
return join_description
def get_instance_description_short(self) -> Union[str, Sequence]:
return view_description, self._score_to_win
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scoreboard()
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
#self.setup_standard_powerup_drops()
# 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()
if self.slow_motion:
t_scale = 0.4
light_y = 50
else:
t_scale = 1.0
light_y = 150
lstart = 7.1 * t_scale
inc = 1.25 * t_scale
ba.timer(lstart, self._do_light_1)
ba.timer(lstart + inc, self._do_light_2)
ba.timer(lstart + 2 * inc, self._do_light_3)
ba.timer(lstart + 3 * inc, self._start_race)
self._start_lights = []
for i in range(4):
lnub = ba.newnode('image',
attrs={
'texture': ba.gettexture('nub'),
'opacity': 1.0,
'absolute_scale': True,
'position': (-75 + i * 50, light_y),
'scale': (50, 50),
'attach': 'center'
})
ba.animate(
lnub, 'opacity', {
4.0 * t_scale: 0,
5.0 * t_scale: 1.0,
12.0 * t_scale: 1.0,
12.5 * t_scale: 0.0
})
ba.timer(13.0 * t_scale, lnub.delete)
self._start_lights.append(lnub)
self._start_lights[0].color = (0.2, 0, 0)
self._start_lights[1].color = (0.2, 0, 0)
self._start_lights[2].color = (0.2, 0.05, 0)
self._start_lights[3].color = (0.0, 0.3, 0)
def _do_light_1(self) -> None:
assert self._start_lights is not None
self._start_lights[0].color = (1.0, 0, 0)
ba.playsound(self._beep_1_sound)
def _do_light_2(self) -> None:
assert self._start_lights is not None
self._start_lights[1].color = (1.0, 0, 0)
ba.playsound(self._beep_1_sound)
def _do_light_3(self) -> None:
assert self._start_lights is not None
self._start_lights[2].color = (1.0, 0.3, 0)
ba.playsound(self._beep_1_sound)
def _start_race(self) -> None:
assert self._start_lights is not None
self._start_lights[3].color = (0.0, 1.0, 0)
ba.playsound(self._beep_2_sound)
self._started = True
for player in self.players:
self.generate_mines(player)
# overriding the default character spawning..
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(enable_punch=False,
enable_bomb=False,
enable_pickup=False)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
if self._started:
self.generate_mines(player)
return spaz
def generate_mines(self,player: Player):
try:
player.actived = ba.Timer(0.5,ba.Call(self.spawn_mine, player),repeat=True)
except Exception as e:
print('Exception -> '+ str(e))
def spawn_mine(self,player: Player):
if player.team.score >= self._score_to_win:
return
pos = player.actor.node.position
# mine = stdbomb.Bomb(position=(pos[0], pos[1] + 2.0, pos[2]),
# velocity=(0, 0, 0),
# bomb_type='land_mine',
# #blast_radius=,
# source_player=player.actor.source_player,
# owner=player.actor.node).autoretain()
mine = Custom_Mine(position=(pos[0], pos[1] + 2.0, pos[2]),
source_player=player.actor.source_player)
def arm():
mine.arm()
ba.timer(0.5,arm)
player.mines.append(mine)
if len(player.mines) > 15:
for m in player.mines:
try:
m.node.delete()
except Exception:
pass
player.mines.remove(m)
break
self.handlemessage(ScoreMessage(player))
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
self.respawn_player(player)
player.actived = None
elif isinstance(msg, ScoreMessage):
player = msg.getplayer()
player.team.score += 1
self._update_scoreboard()
assert self._score_to_win is not None
if any(team.score >= self._score_to_win for team in self.teams):
self.end_game() #ba.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 = ba.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

397
dist/ba_root/mods/games/soccer.py vendored Normal file
View file

@ -0,0 +1,397 @@
# Released under the MIT License. See LICENSE for details.
# BY Stary_Agent
"""Hockey game and support classes."""
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.actor.powerupbox import PowerupBoxFactory
from bastd.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import Any, Sequence, Dict, Type, List, Optional, Union
class PuckDiedMessage:
"""Inform something that a puck has died."""
def __init__(self, puck: Puck):
self.puck = puck
class Puck(ba.Actor):
"""A lovely giant hockey puck."""
def __init__(self, position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
shared = SharedObjects.get()
activity = self.getactivity()
# Spawn just above the provided point.
self._spawn_pos = (position[0], position[1] + 1.0, position[2])
self.last_players_to_touch: Dict[int, Player] = {}
self.scored = False
assert activity is not None
assert isinstance(activity, HockeyGame)
pmats = [shared.object_material, activity.puck_material]
self.node = ba.newnode('prop',
delegate=self,
attrs={
'model': activity.puck_model,
'color_texture': activity.puck_tex,
'body': 'sphere',
'reflection': 'soft',
'reflection_scale': [0.2],
'shadow_size': 0.5,
'is_area_of_interest': True,
'position': self._spawn_pos,
'materials': pmats
})
ba.animate(self.node, 'model_scale', {0: 0, 0.2: 1.3, 0.26: 1})
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
assert self.node
self.node.delete()
activity = self._activity()
if activity and not msg.immediate:
activity.handlemessage(PuckDiedMessage(self))
# If we go out of bounds, move back to where we started.
elif isinstance(msg, ba.OutOfBoundsMessage):
assert self.node
self.node.position = self._spawn_pos
elif isinstance(msg, ba.HitMessage):
assert 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])
# If this hit came from a player, log them as the last to touch us.
s_player = msg.get_source_player(Player)
if s_player is not None:
activity = self._activity()
if activity:
if s_player in activity.players:
self.last_players_to_touch[s_player.team.id] = s_player
else:
super().handlemessage(msg)
class Player(ba.Player['Team']):
"""Our player type for this game."""
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game
class HockeyGame(ba.TeamGameActivity[Player, Team]):
"""Ice hockey game."""
name = 'Soccer'
description = 'Score some goals.'
available_settings = [
ba.IntSetting(
'Score to Win',
min_value=1,
default=1,
increment=1,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting('Boxing Gloves', default=False),
ba.BoolSetting('Icy Floor', default=True),
ba.BoolSetting('Epic Mode', default=False),
]
default_music = ba.MusicType.HOCKEY
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return issubclass(sessiontype, ba.DualTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('football')
def __init__(self, settings: dict):
super().__init__(settings)
shared = SharedObjects.get()
self._scoreboard = Scoreboard()
self._cheer_sound = ba.getsound('cheer')
self._chant_sound = ba.getsound('crowdChant')
self._foghorn_sound = ba.getsound('foghorn')
self._swipsound = ba.getsound('swip')
self._whistle_sound = ba.getsound('refWhistle')
self._boxing_gloves = bool(settings.get('Boxing Gloves', False))
self._icy_floor = bool(settings.get('Icy Floor', True))
self._epic_mode = bool(settings['Epic Mode'])
# Base class overrides:
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.FOOTBALL)
self.puck_model = ba.getmodel('bomb')
self.puck_tex = ba.gettexture('circleOutlineNoAlpha')
self._puck_sound = ba.getsound('metalHit')
self.puck_material = ba.Material()
self.puck_material.add_actions(actions=(('modify_part_collision',
'friction', 0.5)))
self.puck_material.add_actions(conditions=('they_have_material',
shared.pickup_material),
actions=('modify_part_collision',
'collide', True))
self.puck_material.add_actions(
conditions=(
('we_are_younger_than', 100),
'and',
('they_have_material', shared.object_material),
),
actions=('modify_node_collision', 'collide', False),
)
self.puck_material.add_actions(conditions=('they_have_material',
shared.footing_material),
actions=('impact_sound',
self._puck_sound, 0.2, 5))
# Keep track of which player last touched the puck
self.puck_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('call', 'at_connect',
self._handle_puck_player_collide), ))
# We want the puck to kill powerups; not get stopped by them
self.puck_material.add_actions(
conditions=('they_have_material',
PowerupBoxFactory.get().powerup_material),
actions=(('modify_part_collision', 'physical', False),
('message', 'their_node', 'at_connect', ba.DieMessage())))
self._score_region_material = ba.Material()
self._score_region_material.add_actions(
conditions=('they_have_material', self.puck_material),
actions=(('modify_part_collision', 'collide',
True), ('modify_part_collision', 'physical', False),
('call', 'at_connect', self._handle_score)))
self._puck_spawn_pos: Optional[Sequence[float]] = None
self._score_regions: Optional[List[ba.NodeActor]] = None
self._puck: Optional[Puck] = None
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
def get_instance_description(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'Score a goal.'
return 'Score ${ARG1} goals.', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'score a goal'
return 'score ${ARG1} goals', self._score_to_win
def on_transition_in(self) -> None:
super().on_transition_in()
shared = SharedObjects.get()
activity = ba.getactivity()
if self._icy_floor:
activity.map.is_hockey = True
else:
activity.map.is_hockey = False
activity.map.node.materials = [shared.footing_material]
activity.map.floor.materials = [shared.footing_material]
activity.map.floor.color = (0.2, 1.0, 0.2)
def on_begin(self) -> None:
super().on_begin()
ba.screenmessage("Run?",color = (0.2,1,1))
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._puck_spawn_pos = self.map.get_flag_position(None)
self._spawn_puck()
# Set up the two score regions.
defs = self.map.defs
self._score_regions = []
self._score_regions.append(
ba.NodeActor(
ba.newnode('region',
attrs={
'position': defs.boxes['goal1'][0:3],
'scale': defs.boxes['goal1'][6:9],
'type': 'box',
'materials': [self._score_region_material]
})))
self._score_regions.append(
ba.NodeActor(
ba.newnode('region',
attrs={
'position': defs.boxes['goal2'][0:3],
'scale': defs.boxes['goal2'][6:9],
'type': 'box',
'materials': [self._score_region_material]
})))
self._update_scoreboard()
ba.playsound(self._chant_sound)
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
def _handle_puck_player_collide(self) -> None:
collision = ba.getcollision()
try:
puck = collision.sourcenode.getdelegate(Puck, True)
player = collision.opposingnode.getdelegate(PlayerSpaz,
True).getplayer(
Player, True)
except ba.NotFoundError:
return
puck.last_players_to_touch[player.team.id] = player
def _kill_puck(self) -> None:
self._puck = None
def _handle_score(self) -> None:
"""A point has been scored."""
assert self._puck is not None
assert self._score_regions is not None
# Our puck might stick around for a second or two
# we don't want it to be able to score again.
if self._puck.scored:
return
region = ba.getcollision().sourcenode
index = 0
for index in range(len(self._score_regions)):
if region == self._score_regions[index].node:
break
for team in self.teams:
if team.id == index:
scoring_team = team
team.score += 1
# Tell all players to celebrate.
for player in team.players:
if player.actor:
player.actor.handlemessage(ba.CelebrateMessage(2.0))
# If we've got the player from the scoring team that last
# touched us, give them points.
if (scoring_team.id in self._puck.last_players_to_touch
and self._puck.last_players_to_touch[scoring_team.id]):
self.stats.player_scored(
self._puck.last_players_to_touch[scoring_team.id],
100,
big_message=True)
# End game if we won.
if team.score >= self._score_to_win:
self.end_game()
ba.playsound(self._foghorn_sound)
ba.playsound(self._cheer_sound)
self._puck.scored = True
# Kill the puck (it'll respawn itself shortly).
ba.timer(1.0, self._kill_puck)
light = ba.newnode('light',
attrs={
'position': ba.getcollision().position,
'height_attenuated': False,
'color': (1, 0, 0)
})
ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True)
ba.timer(1.0, light.delete)
ba.cameraflash(duration=10.0)
self._update_scoreboard()
def end_game(self) -> None:
results = ba.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)
def _update_scoreboard(self) -> None:
winscore = self._score_to_win
for team in self.teams:
self._scoreboard.set_team_value(team, team.score, winscore)
def handlemessage(self, msg: Any) -> Any:
# Respawn dead players if they're still in the game.
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior...
super().handlemessage(msg)
self.respawn_player(msg.getplayer(Player))
# Respawn dead pucks.
elif isinstance(msg, PuckDiedMessage):
if not self.has_ended():
ba.timer(3.0, self._spawn_puck)
else:
super().handlemessage(msg)
def _flash_puck_spawn(self) -> None:
light = ba.newnode('light',
attrs={
'position': self._puck_spawn_pos,
'height_attenuated': False,
'color': (1, 0, 0)
})
ba.animate(light, 'intensity', {0.0: 0, 0.25: 1, 0.5: 0}, loop=True)
ba.timer(1.0, light.delete)
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
if self._boxing_gloves:
spaz.equip_boxing_gloves()
else:
pass
return spaz
def _spawn_puck(self) -> None:
ba.playsound(self._swipsound)
ba.playsound(self._whistle_sound)
self._flash_puck_spawn()
assert self._puck_spawn_pos is not None
self._puck = Puck(position=self._puck_spawn_pos)

407
dist/ba_root/mods/games/soccerhockey.py vendored Normal file
View file

@ -0,0 +1,407 @@
# Released under the MIT License. See LICENSE for details.
# BY Stary_Agent
"""Hockey game and support classes."""
# ba_meta require api 6
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.actor.powerupbox import PowerupBoxFactory
from bastd.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import Any, Sequence, Dict, Type, List, Optional, Union
class PuckDiedMessage:
"""Inform something that a puck has died."""
def __init__(self, puck: Puck):
self.puck = puck
class Puck(ba.Actor):
"""A lovely giant hockey puck."""
def __init__(self, position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
shared = SharedObjects.get()
activity = self.getactivity()
# Spawn just above the provided point.
self._spawn_pos = (position[0], position[1] + 1.0, position[2])
self.last_players_to_touch: Dict[int, Player] = {}
self.scored = False
assert activity is not None
assert isinstance(activity, HockeyGame)
pmats = [shared.object_material, activity.puck_material]
self.node = ba.newnode('prop',
delegate=self,
attrs={
'model': activity.puck_model,
'color_texture': activity.puck_tex,
'body': 'sphere',
'reflection': 'soft',
'reflection_scale': [0.2],
'shadow_size': 0.5,
'is_area_of_interest': True,
'position': self._spawn_pos,
'materials': pmats
})
ba.animate(self.node, 'model_scale', {0: 0, 0.2: 1.3, 0.26: 1})
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
assert self.node
self.node.delete()
activity = self._activity()
if activity and not msg.immediate:
activity.handlemessage(PuckDiedMessage(self))
# If we go out of bounds, move back to where we started.
elif isinstance(msg, ba.OutOfBoundsMessage):
assert self.node
self.node.position = self._spawn_pos
elif isinstance(msg, ba.HitMessage):
assert 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])
# If this hit came from a player, log them as the last to touch us.
s_player = msg.get_source_player(Player)
if s_player is not None:
activity = self._activity()
if activity:
if s_player in activity.players:
self.last_players_to_touch[s_player.team.id] = s_player
else:
super().handlemessage(msg)
class Player(ba.Player['Team']):
"""Our player type for this game."""
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game
class HockeyGame(ba.TeamGameActivity[Player, Team]):
"""Ice hockey game."""
name = 'Soccer Hockey'
description = 'Score some goals.'
available_settings = [
ba.IntSetting(
'Score to Win',
min_value=1,
default=1,
increment=1,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting('Boxing Gloves', default=False),
#ba.BoolSetting('Enable Powerups', default=True),
ba.BoolSetting('Ice Floor', default=True),
ba.BoolSetting('Epic Mode', default=False),
]
default_music = ba.MusicType.HOCKEY
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return issubclass(sessiontype, ba.DualTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('hockey')
def __init__(self, settings: dict):
super().__init__(settings)
shared = SharedObjects.get()
self.slow_motion = True
self._scoreboard = Scoreboard()
self._cheer_sound = ba.getsound('cheer')
self._chant_sound = ba.getsound('crowdChant')
self._foghorn_sound = ba.getsound('foghorn')
self._swipsound = ba.getsound('swip')
self._whistle_sound = ba.getsound('refWhistle')
self.puck_model = ba.getmodel('bomb')
self.puck_tex = ba.gettexture('circleOutlineNoAlpha')
self._puck_sound = ba.getsound('metalHit')
self._boxing_gloves = bool(settings.get('Boxing Gloves', False))
self._enable_powerups = bool(settings.get('Enable Powerups', True))
self._ice_floor = bool(settings.get('Ice Floor', True))
self._epic_mode = bool(settings['Epic Mode'])
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.FOOTBALL)
self.puck_material = ba.Material()
self.puck_material.add_actions(actions=(('modify_part_collision',
'friction', 0.5)))
self.puck_material.add_actions(conditions=('they_have_material',
shared.pickup_material),
actions=('modify_part_collision',
'collide', False))
self.puck_material = ba.Material()
self.puck_material.add_actions(actions=(('modify_part_collision',
'friction', 0.5)))
self.puck_material.add_actions(conditions=('they_have_material',
shared.pickup_material),
actions=('modify_part_collision',
'collide', True))
self.puck_material.add_actions(
conditions=(
('we_are_younger_than', 100),
'and',
('they_have_material', shared.object_material),
),
actions=('modify_node_collision', 'collide', False),
)
self.puck_material.add_actions(conditions=('they_have_material',
shared.footing_material),
actions=('impact_sound',
self._puck_sound, 0.2, 5))
# Keep track of which player last touched the puck
self.puck_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('call', 'at_connect',
self._handle_puck_player_collide), ))
# We want the puck to kill powerups; not get stopped by them
self.puck_material.add_actions(
conditions=('they_have_material',
PowerupBoxFactory.get().powerup_material),
actions=(('modify_part_collision', 'physical', False),
('message', 'their_node', 'at_connect', ba.DieMessage())))
self._score_region_material = ba.Material()
self._score_region_material.add_actions(
conditions=('they_have_material', self.puck_material),
actions=(('modify_part_collision', 'collide',
True), ('modify_part_collision', 'physical', False),
('call', 'at_connect', self._handle_score)))
self._puck_spawn_pos: Optional[Sequence[float]] = None
self._score_regions: Optional[List[ba.NodeActor]] = None
self._puck: Optional[Puck] = None
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
def get_instance_description(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'Score a goal.'
return 'Score ${ARG1} goals.', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'score a goal'
return 'score ${ARG1} goals', self._score_to_win
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._puck_spawn_pos = self.map.get_flag_position(None)
self._spawn_puck()
# Set up the two score regions.
defs = self.map.defs
self._score_regions = []
self._score_regions.append(
ba.NodeActor(
ba.newnode('region',
attrs={
'position': defs.boxes['goal1'][0:3],
'scale': defs.boxes['goal1'][6:9],
'type': 'box',
'materials': [self._score_region_material]
})))
self._score_regions.append(
ba.NodeActor(
ba.newnode('region',
attrs={
'position': defs.boxes['goal2'][0:3],
'scale': defs.boxes['goal2'][6:9],
'type': 'box',
'materials': [self._score_region_material]
})))
self._update_scoreboard()
ba.playsound(self._chant_sound)
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
def _handle_puck_player_collide(self) -> None:
collision = ba.getcollision()
try:
puck = collision.sourcenode.getdelegate(Puck, True)
player = collision.opposingnode.getdelegate(PlayerSpaz,
True).getplayer(
Player, True)
except ba.NotFoundError:
return
puck.last_players_to_touch[player.team.id] = player
def _kill_puck(self) -> None:
self._puck = None
def _handle_score(self) -> None:
"""A point has been scored."""
assert self._puck is not None
assert self._score_regions is not None
# Our puck might stick around for a second or two
# we don't want it to be able to score again.
if self._puck.scored:
return
region = ba.getcollision().sourcenode
index = 0
for index in range(len(self._score_regions)):
if region == self._score_regions[index].node:
break
for team in self.teams:
if team.id == index:
scoring_team = team
team.score += 1
# Tell all players to celebrate.
for player in team.players:
if player.actor:
player.actor.handlemessage(ba.CelebrateMessage(2.0))
# If we've got the player from the scoring team that last
# touched us, give them points.
if (scoring_team.id in self._puck.last_players_to_touch
and self._puck.last_players_to_touch[scoring_team.id]):
self.stats.player_scored(
self._puck.last_players_to_touch[scoring_team.id],
100,
big_message=True)
# End game if we won.
if team.score >= self._score_to_win:
self.end_game()
ba.playsound(self._foghorn_sound)
ba.playsound(self._cheer_sound)
self._puck.scored = True
# Kill the puck (it'll respawn itself shortly).
ba.timer(1.0, self._kill_puck)
light = ba.newnode('light',
attrs={
'position': ba.getcollision().position,
'height_attenuated': False,
'color': (1, 0, 0)
})
ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True)
ba.timer(1.0, light.delete)
ba.cameraflash(duration=10.0)
self._update_scoreboard()
def on_transition_in(self) -> None:
super().on_transition_in()
shared = SharedObjects.get()
activity = ba.getactivity()
if self._ice_floor:
activity.map.is_hockey = True
else:
activity.map.is_hockey = False
activity.map.node.materials = [shared.footing_material]
activity.map.floor.materials = [shared.footing_material]
activity.map.floor.color = (0.2, 1.0, 0.2)
def end_game(self) -> None:
results = ba.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)
def _update_scoreboard(self) -> None:
winscore = self._score_to_win
for team in self.teams:
self._scoreboard.set_team_value(team, team.score, winscore)
def handlemessage(self, msg: Any) -> Any:
# Respawn dead players if they're still in the game.
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior...
super().handlemessage(msg)
self.respawn_player(msg.getplayer(Player))
# Respawn dead pucks.
elif isinstance(msg, PuckDiedMessage):
if not self.has_ended():
ba.timer(3.0, self._spawn_puck)
else:
super().handlemessage(msg)
def _flash_puck_spawn(self) -> None:
light = ba.newnode('light',
attrs={
'position': self._puck_spawn_pos,
'height_attenuated': False,
'color': (1, 0, 0)
})
ba.animate(light, 'intensity', {0.0: 0, 0.25: 1, 0.5: 0}, loop=True)
ba.timer(1.0, light.delete)
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
if self._boxing_gloves:
spaz.equip_boxing_gloves()
else:
pass
return spaz
def _spawn_puck(self) -> None:
ba.playsound(self._swipsound)
ba.playsound(self._whistle_sound)
self._flash_puck_spawn()
assert self._puck_spawn_pos is not None
self._puck = Puck(position=self._puck_spawn_pos)