Adding more api 8 games

This commit is contained in:
Vishal 2024-11-21 22:01:56 +05:30 committed by GitHub
parent c73d92665c
commit b22ea22865
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 12117 additions and 416 deletions

143
dist/ba_root/mods/games/avalanche.py vendored Normal file
View file

@ -0,0 +1,143 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
"""Avalancha mini-game."""
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.actor.bomb import Bomb
from bascenev1lib.actor.onscreentimer import OnScreenTimer
from bascenev1lib.game.meteorshower import *
from bascenev1lib.actor.spazbot import *
from bascenev1lib.actor.spaz import PunchHitMessage
from bascenev1lib.gameutils import SharedObjects
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 8
def ba_get_levels():
return [
bs._level.Level(
"Icy Emits",
gametype=IcyEmitsGame,
settings={},
preview_texture_name=random.choice(randomPic),
)
]
## MoreMinigames.py support ##
class PascalBot(BrawlerBot):
color = (0, 0, 3)
highlight = (0.2, 0.2, 1)
character = "Pascal"
bouncy = True
punchiness = 0.7
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.FreezeMessage):
return
else:
super().handlemessage(msg)
# ba_meta export bascenev1.GameActivity
class AvalanchaGame(MeteorShowerGame):
"""Minigame involving dodging falling bombs."""
name = "Avalanche"
description = "Dodge the ice-bombs."
available_settings = [
bs.BoolSetting("Epic Mode", default=False),
bs.IntSetting("Difficulty", default=1, min_value=1, max_value=3, increment=1),
]
scoreconfig = bs.ScoreConfig(
label="Survived", scoretype=bs.ScoreType.MILLISECONDS, version="B"
)
announce_player_deaths = True
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return ["Tip Top"]
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
if settings["Difficulty"] == 1:
self._min_delay = 0.4
elif settings["Difficulty"] == 2:
self._min_delay = 0.3
else:
self._min_delay = 0.1
self._timer: Optional[OnScreenTimer] = None
self._bots = SpazBotSet()
self.default_music = (
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SURVIVAL
)
if self._epic_mode:
self.slow_motion = True
def on_transition_in(self) -> None:
super().on_transition_in()
gnode = bs.getactivity().globalsnode
gnode.tint = (0.5, 0.5, 1)
act = bs.getactivity().map
shared = SharedObjects.get()
mat = bs.Material()
mat.add_actions(actions=("modify_part_collision", "friction", 0.18))
act.node.color = act.bottom.color = (1, 1, 1.2)
act.node.reflection = act.bottom.reflection = "soft"
act.node.materials = [shared.footing_material, mat]
def _set_meteor_timer(self) -> None:
bs.timer(
(1.0 + 0.2 * random.random()) * self._meteor_time, self._drop_bomb_cluster
)
def _drop_bomb_cluster(self) -> None:
defs = self.map.defs
delay = 0.0
for _i in range(random.randrange(1, 3)):
pos = defs.points["flag_default"]
pos = (pos[0], pos[1] + 0.4, pos[2])
dropdir = -1.0 if pos[0] > 0 else 1.0
vel = (random.randrange(-4, 4), 7.0, random.randrange(0, 4))
bs.timer(delay, babase.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:
if self._meteor_time < self._min_delay:
return
self._meteor_time = max(0.01, self._meteor_time * 0.9)
if random.choice([0, 0, 1]) == 1:
pos = self.map.defs.points["flag_default"]
self._bots.spawn_bot(PascalBot, pos=pos, spawn_time=2)

774
dist/ba_root/mods/games/basket_bomb.py vendored Normal file
View file

@ -0,0 +1,774 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# Released under the MIT License. See LICENSE for details.
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
import _babase
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.actor import playerspaz as ps
from bascenev1lib import maps
if TYPE_CHECKING:
from typing import Any, Sequence, Dict, Type, List, Optional, Union
bsuSpaz = None
def getlanguage(text, sub: str = ''):
lang = bs.app.lang.language
translate = {
"Name":
{"Spanish": "Baloncesto",
"English": "Basketbomb",
"Portuguese": "Basketbomb"},
"Info":
{"Spanish": "Anota todas las canastas y sé el MVP.",
"English": "Score all the baskets and be the MVP.",
"Portuguese": "Marque cada cesta e seja o MVP."},
"Info-Short":
{"Spanish": f"Anota {sub} canasta(s) para ganar",
"English": f"Score {sub} baskets to win",
"Portuguese": f"Cestas de {sub} pontos para ganhar"},
"S: Powerups":
{"Spanish": "Aparecer Potenciadores",
"English": "Powerups Spawn",
"Portuguese": "Habilitar Potenciadores"},
"S: Velocity":
{"Spanish": "Activar velocidad",
"English": "Enable speed",
"Portuguese": "Ativar velocidade"},
}
languages = ['Spanish', 'Portuguese', 'English']
if lang not in languages:
lang = 'English'
if text not in translate:
return text
return translate[text][lang]
class BallDiedMessage:
def __init__(self, ball: Ball):
self.ball = ball
class Ball(bs.Actor):
def __init__(self, position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
shared = SharedObjects.get()
activity = self.getactivity()
velocty = (0.0, 8.0, 0.0)
_scale = 1.2
self._spawn_pos = (position[0], position[1] + 0.5, position[2])
self.last_players_to_touch: Dict[int, Player] = {}
self.scored = False
assert activity is not None
assert isinstance(activity, BasketGame)
pmats = [shared.object_material, activity.ball_material]
self.node = bs.newnode('prop',
delegate=self,
attrs={
'mesh': activity.ball_mesh,
'color_texture': activity.ball_tex,
'body': 'sphere',
'reflection': 'soft',
'body_scale': 1.0 * _scale,
'reflection_scale': [1.3],
'shadow_size': 1.0,
'gravity_scale': 0.92,
'density': max(0.4 * _scale, 0.3),
'position': self._spawn_pos,
'velocity': velocty,
'materials': pmats})
self.scale = scale = 0.25 * _scale
bs.animate(self.node, 'mesh_scale', {0: 0, 0.2: scale*1.3, 0.26: scale})
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
assert self.node
self.node.delete()
activity = self._activity()
if activity and not msg.immediate:
activity.handlemessage(BallDiedMessage(self))
elif isinstance(msg, bs.OutOfBoundsMessage):
assert self.node
self.node.position = self._spawn_pos
self.node.velocity = (0.0, 0.0, 0.0)
elif isinstance(msg, bs.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])
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(bs.Player['Team']):
"""Our player type for this game."""
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
class Points:
postes = dict()
# 10.736066818237305, 0.3002409040927887, 0.5281256437301636
postes['pal_0'] = (10.64702320098877, 0.0000000000000000, 0.0000000000000000)
postes['pal_1'] = (-10.64702320098877, 0.0000000000000000, 0.0000000000000000)
# ba_meta export bascenev1.GameActivity
class BasketGame(bs.TeamGameActivity[Player, Team]):
name = getlanguage('Name')
description = getlanguage('Info')
available_settings = [
bs.IntSetting(
'Score to Win',
min_value=1,
default=1,
increment=1,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
bs.BoolSetting(getlanguage('S: Powerups'), default=True),
bs.BoolSetting(getlanguage('S: Velocity'), default=False),
bs.BoolSetting('Epic Mode', default=False),
]
default_music = bs.MusicType.HOCKEY
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return ['BasketBall Stadium', 'BasketBall Stadium V2']
def __init__(self, settings: dict):
super().__init__(settings)
shared = SharedObjects.get()
self._scoreboard = Scoreboard()
self._cheer_sound = bs.getsound('cheer')
self._chant_sound = bs.getsound('crowdChant')
self._foghorn_sound = bs.getsound('foghorn')
self._swipsound = bs.getsound('swip')
self._whistle_sound = bs.getsound('refWhistle')
self.ball_mesh = bs.getmesh('shield')
self.ball_tex = bs.gettexture('fontExtras3')
self._ball_sound = bs.getsound('bunnyJump')
self._powerups = bool(settings[getlanguage('S: Powerups')])
self._speed = bool(settings[getlanguage('S: Velocity')])
self._epic_mode = bool(settings['Epic Mode'])
self.slow_motion = self._epic_mode
self.ball_material = bs.Material()
self.ball_material.add_actions(actions=(('modify_part_collision',
'friction', 0.5)))
self.ball_material.add_actions(conditions=('they_have_material',
shared.pickup_material),
actions=('modify_part_collision',
'collide', True))
self.ball_material.add_actions(
conditions=(
('we_are_younger_than', 100),
'and',
('they_have_material', shared.object_material),
),
actions=('modify_node_collision', 'collide', False),
)
self.ball_material.add_actions(conditions=('they_have_material',
shared.footing_material),
actions=('impact_sound',
self._ball_sound, 0.2, 5))
# Keep track of which player last touched the ball
self.ball_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('call', 'at_connect',
self._handle_ball_player_collide), ))
self._score_region_material = bs.Material()
self._score_region_material.add_actions(
conditions=('they_have_material', self.ball_material),
actions=(('modify_part_collision', 'collide',
True), ('modify_part_collision', 'physical', False),
('call', 'at_connect', self._handle_score)))
self._ball_spawn_pos: Optional[Sequence[float]] = None
self._score_regions: Optional[List[bs.NodeActor]] = None
self._ball: Optional[Ball] = 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]:
return getlanguage('Info-Short', sub=self._score_to_win)
def get_instance_description_short(self) -> Union[str, Sequence]:
return getlanguage('Info-Short', sub=self._score_to_win)
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
if self._powerups:
self.setup_standard_powerup_drops()
self._ball_spawn_pos = self.map.get_flag_position(None)
self._spawn_ball()
defs = self.map.defs
self._score_regions = []
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': defs.boxes['goal1'][0:3],
'scale': defs.boxes['goal1'][6:9],
'type': 'box',
'materials': []
})))
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': defs.boxes['goal2'][0:3],
'scale': defs.boxes['goal2'][6:9],
'type': 'box',
'materials': []
})))
self._update_scoreboard()
self._chant_sound.play()
for id, team in enumerate(self.teams):
self.postes(id)
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
def _handle_ball_player_collide(self) -> None:
collision = bs.getcollision()
try:
ball = collision.sourcenode.getdelegate(Ball, True)
player = collision.opposingnode.getdelegate(PlayerSpaz,
True).getplayer(
Player, True)
except bs.NotFoundError:
return
ball.last_players_to_touch[player.team.id] = player
def _kill_ball(self) -> None:
self._ball = None
def _handle_score(self, team_index: int = None) -> None:
assert self._ball is not None
assert self._score_regions is not None
if self._ball.scored:
return
region = bs.getcollision().sourcenode
index = 0
for index in range(len(self._score_regions)):
if region == self._score_regions[index].node:
break
if team_index is not None:
index = team_index
for team in self.teams:
if team.id == index:
scoring_team = team
team.score += 1
for player in team.players:
if player.actor:
player.actor.handlemessage(bs.CelebrateMessage(2.0))
if (scoring_team.id in self._ball.last_players_to_touch
and self._ball.last_players_to_touch[scoring_team.id]):
self.stats.player_scored(
self._ball.last_players_to_touch[scoring_team.id],
100, big_message=True)
if team.score >= self._score_to_win:
self.end_game()
# self._foghorn_sound.play()
self._cheer_sound.play()
self._ball.scored = True
# Kill the ball (it'll respawn itself shortly).
bs.timer(1.0, self._kill_ball)
light = bs.newnode('light',
attrs={
'position': bs.getcollision().position,
'height_attenuated': False,
'color': (1, 0, 0)
})
bs.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True)
bs.timer(1.0, light.delete)
bs.cameraflash(duration=10.0)
self._update_scoreboard()
def end_game(self) -> None:
results = bs.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 id, team in enumerate(self.teams):
self._scoreboard.set_team_value(team, team.score, winscore)
# self.postes(id)
def spawn_player(self, player: Player) -> bs.Actor:
if bsuSpaz is None:
spaz = self.spawn_player_spaz(player)
else:
ps.PlayerSpaz = bsuSpaz.BskSpaz
spaz = self.spawn_player_spaz(player)
ps.PlayerSpaz = bsuSpaz.OldPlayerSpaz
if self._speed:
spaz.node.hockey = True
return spaz
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
super().handlemessage(msg)
self.respawn_player(msg.getplayer(Player))
elif isinstance(msg, BallDiedMessage):
if not self.has_ended():
bs.timer(3.0, self._spawn_ball)
else:
super().handlemessage(msg)
def postes(self, team_id: int):
if not hasattr(self._map, 'poste_'+str(team_id)):
setattr(self._map, 'poste_'+str(team_id),
Palos(team=team_id,
position=Points.postes['pal_' +
str(team_id)]).autoretain())
def _flash_ball_spawn(self) -> None:
light = bs.newnode('light',
attrs={
'position': self._ball_spawn_pos,
'height_attenuated': False,
'color': (1, 0, 0)
})
bs.animate(light, 'intensity', {0.0: 0, 0.25: 1, 0.5: 0}, loop=True)
bs.timer(1.0, light.delete)
def _spawn_ball(self) -> None:
self._swipsound.play()
self._whistle_sound.play()
self._flash_ball_spawn()
assert self._ball_spawn_pos is not None
self._ball = Ball(position=self._ball_spawn_pos)
class Aro(bs.Actor):
def __init__(self, team: int = 0,
position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
act = self.getactivity()
shared = SharedObjects.get()
setattr(self, 'team', team)
setattr(self, 'locs', [])
# Material Para; Traspasar Objetos
self.no_collision = bs.Material()
self.no_collision.add_actions(
actions=(('modify_part_collision', 'collide', False)))
self.collision = bs.Material()
self.collision.add_actions(
actions=(('modify_part_collision', 'collide', True)))
# Score
self._score_region_material = bs.Material()
self._score_region_material.add_actions(
conditions=('they_have_material', act.ball_material),
actions=(('modify_part_collision', 'collide',
True), ('modify_part_collision', 'physical', False),
('call', 'at_connect', self._annotation)))
self._spawn_pos = (position[0], position[1], position[2])
self._materials_region0 = [self.collision,
shared.footing_material]
mesh = None
tex = bs.gettexture('null')
pmats = [self.no_collision]
self.node = bs.newnode('prop',
delegate=self,
attrs={
'mesh': mesh,
'color_texture': tex,
'body': 'box',
'reflection': 'soft',
'reflection_scale': [1.5],
'shadow_size': 0.1,
'position': self._spawn_pos,
'materials': pmats})
self.scale = scale = 1.4
bs.animate(self.node, 'mesh_scale', {0: 0})
pos = (position[0], position[1]+0.6, position[2])
self.regions: List[bs.Node] = [
bs.newnode('region',
attrs={'position': position,
'scale': (0.6, 0.05, 0.6),
'type': 'box',
'materials': self._materials_region0}),
bs.newnode('region',
attrs={'position': pos,
'scale': (0.5, 0.3, 0.9),
'type': 'box',
'materials': [self._score_region_material]})
]
self.regions[0].connectattr('position', self.node, 'position')
# self.regions[0].connectattr('position', self.regions[1], 'position')
locs_count = 9
pos = list(position)
try:
id = 0 if team == 1 else 1
color = act.teams[id].color
except:
color = (1, 1, 1)
while locs_count > 1:
scale = (1.5 * 0.1 * locs_count) + 0.8
self.locs.append(bs.newnode('locator',
owner=self.node,
attrs={'shape': 'circleOutline',
'position': pos,
'color': color,
'opacity': 1.0,
'size': [scale],
'draw_beauty': True,
'additive': False}))
pos[1] -= 0.1
locs_count -= 1
def _annotation(self):
assert len(self.regions) >= 2
ball = self.getactivity()._ball
if ball:
p = self.regions[0].position
ball.node.position = p
ball.node.velocity = (0.0, 0.0, 0.0)
act = self.getactivity()
act._handle_score(self.team)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
if self.node.exists():
self.node.delete()
else:
super().handlemessage(msg)
class Cuadro(bs.Actor):
def __init__(self, team: int = 0,
position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
act = self.getactivity()
shared = SharedObjects.get()
setattr(self, 'locs', [])
self.collision = bs.Material()
self.collision.add_actions(
actions=(('modify_part_collision', 'collide', True)))
pos = (position[0], position[1]+0.9, position[2]+1.5)
self.region: bs.Node = bs.newnode('region',
attrs={'position': pos,
'scale': (0.5, 2.7, 2.5),
'type': 'box',
'materials': [self.collision,
shared.footing_material]})
# self.shield = bs.newnode('shield', attrs={'radius': 1.0, 'color': (0,10,0)})
# self.region.connectattr('position', self.shield, 'position')
position = (position[0], position[1], position[2]+0.09)
pos = list(position)
oldpos = list(position)
old_count = 14
count = old_count
count_y = 9
try:
id = 0 if team == 1 else 1
color = act.teams[id].color
except:
color = (1, 1, 1)
while (count_y != 1):
while (count != 1):
pos[2] += 0.19
self.locs.append(
bs.newnode('locator',
owner=self.region,
attrs={'shape': 'circle',
'position': pos,
'size': [0.5],
'color': color,
'opacity': 1.0,
'draw_beauty': True,
'additive': False}))
count -= 1
count = old_count
pos[1] += 0.2
pos[2] = oldpos[2]
count_y -= 1
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
if self.node.exists():
self.node.delete()
else:
super().handlemessage(msg)
class Palos(bs.Actor):
def __init__(self, team: int = 0,
position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
shared = SharedObjects.get()
activity = self.getactivity()
self._pos = position
self.aro = None
self.cua = None
# Material Para; Traspasar Objetos
self.no_collision = bs.Material()
self.no_collision.add_actions(
actions=(('modify_part_collision', 'collide', False)))
#
self.collision = bs.Material()
self.collision.add_actions(
actions=(('modify_part_collision', 'collide', True)))
# Spawn just above the provided point.
self._spawn_pos = (position[0], position[2]+2.5, position[2])
mesh = bs.getmesh('flagPole')
tex = bs.gettexture('flagPoleColor')
pmats = [self.no_collision]
self.node = bs.newnode('prop',
delegate=self,
attrs={
'mesh': mesh,
'color_texture': tex,
'body': 'puck',
'reflection': 'soft',
'reflection_scale': [2.6],
'shadow_size': 0,
'is_area_of_interest': True,
'position': self._spawn_pos,
'materials': pmats
})
self.scale = scale = 4.0
bs.animate(self.node, 'mesh_scale', {0: scale})
self.loc = bs.newnode('locator',
owner=self.node,
attrs={'shape': 'circle',
'position': position,
'color': (1, 1, 0),
'opacity': 1.0,
'draw_beauty': False,
'additive': True})
self._y = _y = 0.30
_x = -0.25 if team == 1 else 0.25
_pos = (position[0]+_x, position[1]-1.5 + _y, position[2])
self.region = bs.newnode('region',
attrs={
'position': _pos,
'scale': (0.4, 8, 0.4),
'type': 'box',
'materials': [self.collision]})
self.region.connectattr('position', self.node, 'position')
_y = self._y
position = self._pos
if team == 0:
pos = (position[0]-0.8, position[1] + 2.0 + _y, position[2])
else:
pos = (position[0]+0.8, position[1] + 2.0 + _y, position[2])
if self.aro is None:
self.aro = Aro(team, pos).autoretain()
if self.cua is None:
pos = (position[0], position[1] + 1.8 + _y, position[2]-1.4)
self.cua = Cuadro(team, pos).autoretain()
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
if self.node.exists():
self.node.delete()
else:
super().handlemessage(msg)
class BasketMap(maps.FootballStadium):
name = 'BasketBall Stadium'
@classmethod
def get_play_types(cls) -> List[str]:
"""Return valid play types for this map."""
return []
def __init__(self) -> None:
super().__init__()
gnode = bs.getactivity().globalsnode
gnode.tint = [(0.806, 0.8, 1.0476), (1.3, 1.2, 1.0)][0]
gnode.ambient_color = (1.3, 1.2, 1.0)
gnode.vignette_outer = (0.57, 0.57, 0.57)
gnode.vignette_inner = (0.9, 0.9, 0.9)
gnode.vr_camera_offset = (0, -0.8, -1.1)
gnode.vr_near_clip = 0.5
class BasketMapV2(maps.HockeyStadium):
name = 'BasketBall Stadium V2'
def __init__(self) -> None:
super().__init__()
shared = SharedObjects.get()
self.node.materials = [shared.footing_material]
self.node.collision_mesh = bs.getcollisionmesh('footballStadiumCollide')
self.node.mesh = None
self.stands.mesh = None
self.floor.reflection = 'soft'
self.floor.reflection_scale = [1.6]
self.floor.color = (1.1, 0.05, 0.8)
self.background = bs.newnode('terrain',
attrs={'mesh': bs.getmesh('thePadBG'),
'lighting': False,
'background': True,
'color': (1.0, 0.2, 1.0),
'color_texture': bs.gettexture('menuBG')})
gnode = bs.getactivity().globalsnode
gnode.floor_reflection = True
gnode.debris_friction = 0.3
gnode.debris_kill_height = -0.3
gnode.tint = [(1.2, 1.3, 1.33), (0.7, 0.9, 1.0)][1]
gnode.ambient_color = (1.15, 1.25, 1.6)
gnode.vignette_outer = (0.66, 0.67, 0.73)
gnode.vignette_inner = (0.93, 0.93, 0.95)
gnode.vr_camera_offset = (0, -0.8, -1.1)
gnode.vr_near_clip = 0.5
self.is_hockey = False
##################
self.collision = bs.Material()
self.collision.add_actions(
actions=(('modify_part_collision', 'collide', True)))
self.regions: List[bs.Node] = [
bs.newnode('region',
attrs={'position': (12.676897048950195, 0.2997918128967285, 5.583303928375244),
'scale': (1.01, 12, 28),
'type': 'box',
'materials': [self.collision]}),
bs.newnode('region',
attrs={'position': (11.871315956115723, 0.29975247383117676, 5.711406707763672),
'scale': (50, 12, 0.9),
'type': 'box',
'materials': [self.collision]}),
bs.newnode('region',
attrs={'position': (-12.776557922363281, 0.30036890506744385, 4.96237850189209),
'scale': (1.01, 12, 28),
'type': 'box',
'materials': [self.collision]}),
]
bs._map.register_map(BasketMap)
bs._map.register_map(BasketMapV2)

View file

@ -0,0 +1,269 @@
# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport)
# BetterDeathMatch
# Made by your friend: @[Just] Freak#4999
"""Defines a very-customisable DeathMatch mini-game"""
# ba_meta require api 8
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional
class Player(bs.Player['Team']):
"""Our player type for this game."""
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export bascenev1.GameActivity
class BetterDeathMatchGame(bs.TeamGameActivity[Player, Team]):
"""A game type based on acquiring kills."""
name = 'Btrr Death Match'
description = 'Kill a set number of enemies to win.\nbyFREAK'
# Print messages when players die since it matters here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]:
settings = [
bs.IntSetting(
'Kills to Win Per Player',
min_value=1,
default=5,
increment=1,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
bs.BoolSetting('Epic Mode', default=False),
## Add settings ##
bs.BoolSetting('Enable Gloves', False),
bs.BoolSetting('Enable Powerups', True),
bs.BoolSetting('Night Mode', False),
bs.BoolSetting('Icy Floor', False),
bs.BoolSetting('One Punch Kill', False),
bs.BoolSetting('Spawn with Shield', False),
bs.BoolSetting('Punching Only', False),
## Add settings ##
]
# In teams mode, a suicide gives a point to the other team, but in
# free-for-all it subtracts from your own score. By default we clamp
# this at zero to benefit new players, but pro players might like to
# be able to go negative. (to avoid a strategy of just
# suiciding until you get a good drop)
if issubclass(sessiontype, bs.FreeForAllSession):
settings.append(
bs.BoolSetting('Allow Negative Scores', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.DualTeamSession)
or issubclass(sessiontype, bs.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return bs.app.classic.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_to_win: Optional[int] = None
self._dingsound = bui.getsound('dingSmall')
## Take applied settings ##
self._boxing_gloves = bool(settings['Enable Gloves'])
self._enable_powerups = bool(settings['Enable Powerups'])
self._night_mode = bool(settings['Night Mode'])
self._icy_floor = bool(settings['Icy Floor'])
self._one_punch_kill = bool(settings['One Punch Kill'])
self._shield_ = bool(settings['Spawn with Shield'])
self._only_punch = bool(settings['Punching Only'])
## Take applied settings ##
self._epic_mode = bool(settings['Epic Mode'])
self._kills_to_win_per_player = int(
settings['Kills to Win Per Player'])
self._time_limit = float(settings['Time Limit'])
self._allow_negative_scores = bool(
settings.get('Allow Negative Scores', False))
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC if self._epic_mode else
bs.MusicType.TO_THE_DEATH)
def get_instance_description(self) -> Union[str, Sequence]:
return 'Crush ${ARG1} of your enemies. byFREAK', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'kill ${ARG1} enemies. byFREAK', self._score_to_win
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scoreboard()
## Run settings related: IcyFloor ##
def on_transition_in(self) -> None:
super().on_transition_in()
activity = bs.getactivity()
if self._icy_floor:
activity.map.is_hockey = True
else:
return
## Run settings related: IcyFloor ##
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
## Run settings related: NightMode,Powerups ##
if self._night_mode:
bs.getactivity().globalsnode.tint = (0.5, 0.7, 1)
else:
pass
# -# Tried return here, pfft. Took me 30mins to figure out why pwps spawning only on NightMode
# -# Now its fixed :)
if self._enable_powerups:
self.setup_standard_powerup_drops()
else:
pass
## Run settings related: NightMode,Powerups ##
# 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 handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
self.respawn_player(player)
killer = msg.getkillerplayer(Player)
if killer is None:
return None
# Handle team-kills.
if killer.team is player.team:
# In free-for-all, killing yourself loses you a point.
if isinstance(self.session, bs.FreeForAllSession):
new_score = player.team.score - 1
if not self._allow_negative_scores:
new_score = max(0, new_score)
player.team.score = new_score
# In teams-mode it gives a point to the other team.
else:
self._dingsound.play()
for team in self.teams:
if team is not killer.team:
team.score += 1
# Killing someone on another team nets a kill.
else:
killer.team.score += 1
self._dingsound.play()
# In FFA show scores since its hard to find on the scoreboard.
if isinstance(killer.actor, PlayerSpaz) and killer.actor:
killer.actor.set_score_text(str(killer.team.score) + '/' +
str(self._score_to_win),
color=killer.team.color,
flash=True)
self._update_scoreboard()
# If someone has won, set a timer to end shortly.
# (allows the dust to clear and draws to occur if deaths are
# close enough)
assert self._score_to_win is not None
if any(team.score >= self._score_to_win for team in self.teams):
bs.timer(0.5, self.end_game)
else:
return super().handlemessage(msg)
return None
## Run settings related: Spaz ##
def spawn_player(self, player: Player) -> bs.Actor:
spaz = self.spawn_player_spaz(player)
if self._boxing_gloves:
spaz.equip_boxing_gloves()
if self._one_punch_kill:
spaz._punch_power_scale = 15
if self._shield_:
spaz.equip_shields()
if self._only_punch:
spaz.connect_controls_to_player(enable_bomb=False, enable_pickup=False)
return spaz
## Run settings related: Spaz ##
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(team, team.score,
self._score_to_win)
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

View file

@ -0,0 +1,659 @@
# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport)
# BetterElimination
# Made by your friend: @[Just] Freak#4999
# Huge Thx to Nippy for "Live Team Balance"
"""Defines a very-customisable Elimination mini-game"""
# ba_meta require api 8
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.actor.spazfactory import SpazFactory
from bascenev1lib.actor.scoreboard import Scoreboard
if TYPE_CHECKING:
from typing import (Any, Tuple, Dict, Type, List, Sequence, Optional,
Union)
class Icon(bs.Actor):
"""Creates in in-game icon on screen."""
def __init__(self,
player: Player,
position: Tuple[float, float],
scale: float,
show_lives: bool = True,
show_death: bool = True,
name_scale: float = 1.0,
name_maxwidth: float = 115.0,
flatness: float = 1.0,
shadow: float = 1.0):
super().__init__()
self._player = player
self._show_lives = show_lives
self._show_death = show_death
self._name_scale = name_scale
self._outline_tex = bs.gettexture('characterIconMask')
icon = player.get_icon()
self.node = bs.newnode('image',
delegate=self,
attrs={
'texture': icon['texture'],
'tint_texture': icon['tint_texture'],
'tint_color': icon['tint_color'],
'vr_depth': 400,
'tint2_color': icon['tint2_color'],
'mask_texture': self._outline_tex,
'opacity': 1.0,
'absolute_scale': True,
'attach': 'bottomCenter'
})
self._name_text = bs.newnode(
'text',
owner=self.node,
attrs={
'text': babase.Lstr(value=player.getname()),
'color': babase.safecolor(player.team.color),
'h_align': 'center',
'v_align': 'center',
'vr_depth': 410,
'maxwidth': name_maxwidth,
'shadow': shadow,
'flatness': flatness,
'h_attach': 'center',
'v_attach': 'bottom'
})
if self._show_lives:
self._lives_text = bs.newnode('text',
owner=self.node,
attrs={
'text': 'x0',
'color': (1, 1, 0.5),
'h_align': 'left',
'vr_depth': 430,
'shadow': 1.0,
'flatness': 1.0,
'h_attach': 'center',
'v_attach': 'bottom'
})
self.set_position_and_scale(position, scale)
def set_position_and_scale(self, position: Tuple[float, float],
scale: float) -> None:
"""(Re)position the icon."""
assert self.node
self.node.position = position
self.node.scale = [70.0 * scale]
self._name_text.position = (position[0], position[1] + scale * 52.0)
self._name_text.scale = 1.0 * scale * self._name_scale
if self._show_lives:
self._lives_text.position = (position[0] + scale * 10.0,
position[1] - scale * 43.0)
self._lives_text.scale = 1.0 * scale
def update_for_lives(self) -> None:
"""Update for the target player's current lives."""
if self._player:
lives = self._player.lives
else:
lives = 0
if self._show_lives:
if lives > 0:
self._lives_text.text = 'x' + str(lives - 1)
else:
self._lives_text.text = ''
if lives == 0:
self._name_text.opacity = 0.2
assert self.node
self.node.color = (0.7, 0.3, 0.3)
self.node.opacity = 0.2
def handle_player_spawned(self) -> None:
"""Our player spawned; hooray!"""
if not self.node:
return
self.node.opacity = 1.0
self.update_for_lives()
def handle_player_died(self) -> None:
"""Well poo; our player died."""
if not self.node:
return
if self._show_death:
bs.animate(
self.node, 'opacity', {
0.00: 1.0,
0.05: 0.0,
0.10: 1.0,
0.15: 0.0,
0.20: 1.0,
0.25: 0.0,
0.30: 1.0,
0.35: 0.0,
0.40: 1.0,
0.45: 0.0,
0.50: 1.0,
0.55: 0.2
})
lives = self._player.lives
if lives == 0:
bs.timer(0.6, self.update_for_lives)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
self.node.delete()
return None
return super().handlemessage(msg)
class Player(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.lives = 0
self.icons: List[Icon] = []
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.survival_seconds: Optional[int] = None
self.spawn_order: List[Player] = []
# ba_meta export bascenev1.GameActivity
class BetterEliminationGame(bs.TeamGameActivity[Player, Team]):
"""Game type where last player(s) left alive win."""
name = 'Bttr Elimination'
description = 'Last remaining alive wins.\nbyFREAK'
scoreconfig = bs.ScoreConfig(label='Survived',
scoretype=bs.ScoreType.SECONDS,
none_is_winner=True)
# Show messages when players die since it's meaningful here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]:
settings = [
bs.IntSetting(
'Life\'s Per Player',
default=1,
min_value=1,
max_value=10,
increment=1,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
bs.BoolSetting('Epic Mode', default=False),
## Add settings ##
bs.BoolSetting('Live Team Balance (by Nippy#2677)', True),
bs.BoolSetting('Enable Gloves', False),
bs.BoolSetting('Enable Powerups', True),
bs.BoolSetting('Night Mode', False),
bs.BoolSetting('Icy Floor', False),
bs.BoolSetting('One Punch Kill', False),
bs.BoolSetting('Spawn with Shield', False),
bs.BoolSetting('Punching Only', False),
## Add settings ##
]
if issubclass(sessiontype, bs.DualTeamSession):
settings.append(bs.BoolSetting('Solo Mode', default=False))
settings.append(
bs.BoolSetting('Balance Total Life\'s (on spawn only)', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.DualTeamSession)
or issubclass(sessiontype, bs.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return bs.app.classic.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._start_time: Optional[float] = None
self._vs_text: Optional[bs.Actor] = None
self._round_end_timer: Optional[bs.Timer] = None
## Take applied settings ##
self._live_team_balance = bool(settings['Live Team Balance (by Nippy#2677)'])
self._boxing_gloves = bool(settings['Enable Gloves'])
self._enable_powerups = bool(settings['Enable Powerups'])
self._night_mode = bool(settings['Night Mode'])
self._icy_floor = bool(settings['Icy Floor'])
self._one_punch_kill = bool(settings['One Punch Kill'])
self._shield_ = bool(settings['Spawn with Shield'])
self._only_punch = bool(settings['Punching Only'])
## Take applied settings ##
self._epic_mode = bool(settings['Epic Mode'])
self._lives_per_player = int(settings['Life\'s Per Player'])
self._time_limit = float(settings['Time Limit'])
self._balance_total_lives = bool(
settings.get('Balance Total Life\'s (on spawn only)', False))
self._solo_mode = bool(settings.get('Solo Mode', False))
# Base class overrides:
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC
if self._epic_mode else bs.MusicType.SURVIVAL)
def get_instance_description(self) -> Union[str, Sequence]:
return 'Last team standing wins. byFREAK' if isinstance(
self.session, bs.DualTeamSession) else 'Last one standing wins.'
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'last team standing wins. byFREAK' if isinstance(
self.session, bs.DualTeamSession) else 'last one standing wins'
def on_player_join(self, player: Player) -> None:
# No longer allowing mid-game joiners here; too easy to exploit.
if self.has_begun():
# Make sure their team has survival seconds set if they're all dead
# (otherwise blocked new ffa players are considered 'still alive'
# in score tallying).
if (self._get_total_team_lives(player.team) == 0
and player.team.survival_seconds is None):
player.team.survival_seconds = 0
bui.screenmessage(
babase.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
player.lives = self._lives_per_player
if self._solo_mode:
player.team.spawn_order.append(player)
self._update_solo_mode()
else:
# Create our icon and spawn.
player.icons = [Icon(player, position=(0, 50), scale=0.8)]
if player.lives > 0:
self.spawn_player(player)
# Don't waste time doing this until begin.
if self.has_begun():
self._update_icons()
## Run settings related: IcyFloor ##
def on_transition_in(self) -> None:
super().on_transition_in()
activity = bs.getactivity()
if self._icy_floor:
activity.map.is_hockey = True
else:
return
## Run settings related: IcyFloor ##
def on_begin(self) -> None:
super().on_begin()
self._start_time = bs.time()
self.setup_standard_time_limit(self._time_limit)
## Run settings related: NightMode,Powerups ##
if self._night_mode:
bs.getactivity().globalsnode.tint = (0.5, 0.7, 1)
else:
pass
# -# Tried return here, pfft. Took me 30mins to figure out why pwps spawning only on NightMode
# -# Now its fixed :)
if self._enable_powerups:
self.setup_standard_powerup_drops()
else:
pass
## Run settings related: NightMode,Powerups ##
if self._solo_mode:
self._vs_text = bs.NodeActor(
bs.newnode('text',
attrs={
'position': (0, 105),
'h_attach': 'center',
'h_align': 'center',
'maxwidth': 200,
'shadow': 0.5,
'vr_depth': 390,
'scale': 0.6,
'v_attach': 'bottom',
'color': (0.8, 0.8, 0.3, 1.0),
'text': babase.Lstr(resource='vsText')
}))
# If balance-team-lives is on, add lives to the smaller team until
# total lives match.
if (isinstance(self.session, bs.DualTeamSession)
and self._balance_total_lives and self.teams[0].players
and self.teams[1].players):
if self._get_total_team_lives(
self.teams[0]) < self._get_total_team_lives(self.teams[1]):
lesser_team = self.teams[0]
greater_team = self.teams[1]
else:
lesser_team = self.teams[1]
greater_team = self.teams[0]
add_index = 0
while (self._get_total_team_lives(lesser_team) <
self._get_total_team_lives(greater_team)):
lesser_team.players[add_index].lives += 1
add_index = (add_index + 1) % len(lesser_team.players)
self._update_icons()
# We could check game-over conditions at explicit trigger points,
# but lets just do the simple thing and poll it.
bs.timer(1.0, self._update, repeat=True)
def _update_solo_mode(self) -> None:
# For both teams, find the first player on the spawn order list with
# lives remaining and spawn them if they're not alive.
for team in self.teams:
# Prune dead players from the spawn order.
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
break
def _update_icons(self) -> None:
# pylint: disable=too-many-branches
# In free-for-all mode, everyone is just lined up along the bottom.
if isinstance(self.session, bs.FreeForAllSession):
count = len(self.teams)
x_offs = 85
xval = x_offs * (count - 1) * -0.5
for team in self.teams:
if len(team.players) == 1:
player = team.players[0]
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
# In teams mode we split up teams.
else:
if self._solo_mode:
# First off, clear out all icons.
for player in self.players:
player.icons = []
# Now for each team, cycle through our available players
# adding icons.
for team in self.teams:
if team.id == 0:
xval = -60
x_offs = -78
else:
xval = 60
x_offs = 78
is_first = True
test_lives = 1
while True:
players_with_lives = [
p for p in team.spawn_order
if p and p.lives >= test_lives
]
if not players_with_lives:
break
for player in players_with_lives:
player.icons.append(
Icon(player,
position=(xval, (40 if is_first else 25)),
scale=1.0 if is_first else 0.5,
name_maxwidth=130 if is_first else 75,
name_scale=0.8 if is_first else 1.0,
flatness=0.0 if is_first else 1.0,
shadow=0.5 if is_first else 1.0,
show_death=is_first,
show_lives=False))
xval += x_offs * (0.8 if is_first else 0.56)
is_first = False
test_lives += 1
# Non-solo mode.
else:
for team in self.teams:
if team.id == 0:
xval = -50
x_offs = -85
else:
xval = 50
x_offs = 85
for player in team.players:
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
def _get_spawn_point(self, player: Player) -> Optional[babase.Vec3]:
del player # Unused.
# In solo-mode, if there's an existing live player on the map, spawn at
# whichever spot is farthest from them (keeps the action spread out).
if self._solo_mode:
living_player = None
living_player_pos = None
for team in self.teams:
for tplayer in team.players:
if tplayer.is_alive():
assert tplayer.node
ppos = tplayer.node.position
living_player = tplayer
living_player_pos = ppos
break
if living_player:
assert living_player_pos is not None
player_pos = babase.Vec3(living_player_pos)
points: List[Tuple[float, babase.Vec3]] = []
for team in self.teams:
start_pos = babase.Vec3(self.map.get_start_position(team.id))
points.append(
((start_pos - player_pos).length(), start_pos))
# Hmm.. we need to sorting vectors too?
points.sort(key=lambda x: x[0])
return points[-1][1]
return None
def spawn_player(self, player: Player) -> bs.Actor:
actor = self.spawn_player_spaz(player, self._get_spawn_point(player))
if not self._solo_mode:
bs.timer(0.3, babase.Call(self._print_lives, player))
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_spawned()
## Run settings related: Spaz ##
if self._boxing_gloves:
actor.equip_boxing_gloves()
if self._one_punch_kill:
actor._punch_power_scale = 15
if self._shield_:
actor.equip_shields()
if self._only_punch:
actor.connect_controls_to_player(enable_bomb=False, enable_pickup=False)
return actor
## Run settings related: Spaz ##
def _print_lives(self, player: Player) -> None:
from bascenev1lib.actor import popuptext
# We get called in a timer so it's possible our player has left/etc.
if not player or not player.is_alive() or not player.node:
return
popuptext.PopupText('x' + str(player.lives - 1),
color=(1, 1, 0, 1),
offset=(0, -0.8, 0),
random_offset=0.0,
scale=1.8,
position=player.node.position).autoretain()
def on_player_leave(self, player: Player) -> None:
# Nippy#2677
team_count = 1 # Just initiating
if player.lives > 0 and self._live_team_balance:
team_mem = []
for teamer in player.team.players:
if player != teamer:
team_mem.append(teamer) # Got Dead players Team
live = player.lives
team_count = len(team_mem)
# Extending Player List for Sorted Players
for i in range(int((live if live % 2 == 0 else live+1)/2)):
team_mem.extend(team_mem)
if team_count > 0:
for i in range(live):
team_mem[i].lives += 1
if team_count <= 0: # Draw if Player Leaves
self.end_game()
# Nippy#2677
super().on_player_leave(player)
player.icons = []
# Remove us from spawn-order.
if self._solo_mode:
if player in player.team.spawn_order:
player.team.spawn_order.remove(player)
# Update icons in a moment since our team will be gone from the
# list then.
bs.timer(0, self._update_icons)
# If the player to leave was the last in spawn order and had
# their final turn currently in-progress, mark the survival time
# for their team.
if self._get_total_team_lives(player.team) == 0:
assert self._start_time is not None
player.team.survival_seconds = int(bs.time() - self._start_time)
def _get_total_team_lives(self, team: Team) -> int:
return sum(player.lives for player in team.players)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player: Player = msg.getplayer(Player)
player.lives -= 1
if player.lives < 0:
babase.print_error(
"Got lives < 0 in Elim; this shouldn't happen. solo:" +
str(self._solo_mode))
player.lives = 0
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_died()
# Play big death sound on our last death
# or for every one in solo mode.
if self._solo_mode or player.lives == 0:
SpazFactory.get().single_player_death_sound.play()
# If we hit zero lives, we're dead (and our team might be too).
if player.lives == 0:
# If the whole team is now dead, mark their survival time.
if self._get_total_team_lives(player.team) == 0:
assert self._start_time is not None
player.team.survival_seconds = int(bs.time() -
self._start_time)
else:
# Otherwise, in regular mode, respawn.
if not self._solo_mode:
self.respawn_player(player)
# In solo, put ourself at the back of the spawn order.
if self._solo_mode:
player.team.spawn_order.remove(player)
player.team.spawn_order.append(player)
def _update(self) -> None:
if self._solo_mode:
# For both teams, find the first player on the spawn order
# list with lives remaining and spawn them if they're not alive.
for team in self.teams:
# Prune dead players from the spawn order.
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
self._update_icons()
break
# If we're down to 1 or fewer living teams, start a timer to end
# the game (allows the dust to settle and draws to occur if deaths
# are close enough).
if len(self._get_living_teams()) < 2:
self._round_end_timer = bs.Timer(0.5, self.end_game)
def _get_living_teams(self) -> List[Team]:
return [
team for team in self.teams
if len(team.players) > 0 and any(player.lives > 0
for player in team.players)
]
def end_game(self) -> None:
if self.has_ended():
return
results = bs.GameResults()
self._vs_text = None # Kill our 'vs' if its there.
for team in self.teams:
results.set_team_score(team, team.survival_seconds)
self.end(results=results)

View file

@ -0,0 +1,339 @@
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
import random
from bascenev1lib.actor.onscreentimer import OnScreenTimer
from bascenev1lib.actor.spaz import BombDiedMessage
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor import bomb as stdbomb
if TYPE_CHECKING:
from typing import Any, Sequence
lang = bs.app.lang.language
if lang == 'Spanish':
name = 'Bomba en mi Cabeza'
description = ('Siempre tendrás una bomba en la cabeza.\n'
'¡Sobrevive tanto como puedas!')
description_ingame = '¡Sobrevive tanto como puedas!'
# description_short = 'Elimina {} Jugadores para ganar'
maxbomblimit = 'Límite Máximo de Bombas'
mbltwo = 'Dos'
mblthree = 'Tres'
mblfour = 'Cuatro'
else:
name = 'Bomb on my Head'
description = ('You\'ll always have a bomb on your head.\n'
'Survive as long as you can!')
description_ingame = 'Survive as long as you can!'
# description_short = 'Kill {} Players to win'
maxbomblimit = 'Max Bomb Limit'
mbltwo = 'Two'
mblthree = 'Three'
mblfour = 'Four'
class NewPlayerSpaz(PlayerSpaz):
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, BombDiedMessage):
self.bomb_count += 1
self.check_avalible_bombs()
else:
return super().handlemessage(msg)
def check_avalible_bombs(self) -> None:
if not self.node:
return
if self.bomb_count <= 0:
return
if not self.node.hold_node:
self.on_bomb_press()
self.on_bomb_release()
def start_bomb_checking(self) -> None:
self.check_avalible_bombs()
self._bomb_check_timer = bs.timer(
0.5,
bs.WeakCall(self.check_avalible_bombs),
repeat=True)
def drop_bomb(self) -> stdbomb.Bomb | None:
lifespan = 3.0
if self.bomb_count <= 0 or self.frozen:
return None
assert self.node
pos = self.node.position_forward
vel = self.node.velocity
bomb_type = 'normal'
bomb = stdbomb.Bomb(
position=(pos[0], pos[1] - 0.0, pos[2]),
velocity=(vel[0], vel[1], vel[2]),
bomb_type=bomb_type,
blast_radius=self.blast_radius,
source_player=self.source_player,
owner=self.node,
).autoretain()
bs.animate(bomb.node, 'mesh_scale', {
0.0: 0.0,
lifespan*0.1: 1.5,
lifespan*0.5: 1.0
})
self.bomb_count -= 1
bomb.node.add_death_action(
bs.WeakCall(self.handlemessage, BombDiedMessage())
)
self._pick_up(bomb.node)
for clb in self._dropped_bomb_callbacks:
clb(self, bomb)
return bomb
class Player(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
super().__init__()
self.death_time: float | None = None
class Team(bs.Team[Player]):
"""Our team type for this game."""
# ba_meta export bascenev1.GameActivity
class BombOnMyHeadGame(bs.TeamGameActivity[Player, Team]):
name = name
description = description
scoreconfig = bs.ScoreConfig(
label='Survived', scoretype=bs.ScoreType.MILLISECONDS, version='B'
)
# Show messages when players die since it's meaningful here.
announce_player_deaths = True
allow_mid_activity_joins = False
@classmethod
def get_available_settings(
cls, sessiontype: type[bs.Session]
) -> list[babase.Setting]:
settings = [
bs.IntChoiceSetting(
maxbomblimit,
choices=[
('Normal', 1),
(mbltwo, 2),
(mblthree, 3),
(mblfour, 4),
],
default=1,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.BoolSetting('Epic Mode', default=False),
]
return settings
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession) or issubclass(
sessiontype, bs.FreeForAllSession
)
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return bs.app.classic.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._max_bomb_limit = int(settings[maxbomblimit])
self._epic_mode = bool(settings['Epic Mode'])
self._time_limit = float(settings['Time Limit'])
self._last_player_death_time: float | None = None
self._timer: OnScreenTimer | None = None
# Some base class overrides:
self.default_music = (
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SURVIVAL
)
if self._epic_mode:
self.slow_motion = True
def get_instance_description(self) -> str | Sequence:
return description_ingame
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self._timer = OnScreenTimer()
self._timer.start()
def spawn_player(self, player: Player) -> bs.Actor:
from babase import _math
from bascenev1._gameutils import animate
from bascenev1._coopsession import CoopSession
if isinstance(self.session, bs.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()
color = player.color
highlight = player.highlight
light_color = _math.normalized_color(color)
display_color = babase.safecolor(color, target_intensity=0.75)
spaz = NewPlayerSpaz(color=color,
highlight=highlight,
character=player.character,
player=player)
player.actor = spaz
assert spaz.node
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(
bs.StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
self._spawn_sound.play(1, position=spaz.node.position)
light = bs.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
bs.timer(0.5, light.delete)
bs.timer(1.0, bs.WeakCall(spaz.start_bomb_checking))
spaz.set_bomb_count(self._max_bomb_limit)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
curtime = bs.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, bs.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
babase.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = curtime
else:
bs.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, bs.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def end_game(self) -> None:
cur_time = bs.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 = bs.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)

View file

@ -0,0 +1,790 @@
# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport)
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
import _babase
import random
from bascenev1._map import register_map
from bascenev1lib.actor.spaz import PickupMessage
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.spazfactory import SpazFactory
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.actor.spazbot import SpazBotSet, ChargerBotPro, TriggerBotPro
from bascenev1lib.actor.bomb import Blast
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
from bascenev1lib.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
from typing import Any, Sequence
lang = bs.app.lang.language
if lang == 'Spanish':
name = 'Abajo en el Abismo'
description = 'Sobrevive tanto como puedas'
help = 'El mapa es 3D, ¡ten cuidado!'
author = 'Autor: Deva'
github = 'GitHub: spdv123'
blog = 'Blog: superdeva.info'
peaceTime = 'Tiempo de Paz'
npcDensity = 'Densidad de Enemigos'
hint_use_punch = '¡Ahora puedes golpear a los enemigos!'
elif lang == 'Chinese':
name = '无尽深渊'
description = '在无穷尽的坠落中存活更长时间'
help = ''
author = '作者: Deva'
github = 'GitHub: spdv123'
blog = '博客: superdeva.info'
peaceTime = '和平时间'
npcDensity = 'NPC密度'
hint_use_punch = u'现在可以使用拳头痛扁你的敌人了'
else:
name = 'Down Into The Abyss'
description = 'Survive as long as you can'
help = 'The map is 3D, be careful!'
author = 'Author: Deva'
github = 'GitHub: spdv123'
blog = 'Blog: superdeva.info'
peaceTime = 'Peace Time'
npcDensity = 'NPC Density'
hint_use_punch = 'You can punch your enemies now!'
class AbyssMap(bs.Map):
from bascenev1lib.mapdata import happy_thoughts as defs
# Add the y-dimension space for players
defs.boxes['map_bounds'] = (-0.8748348681, 9.212941713, -9.729538885) \
+ (0.0, 0.0, 0.0) \
+ (36.09666006, 26.19950145, 20.89541168)
name = 'Abyss Unhappy'
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['abyss']
@classmethod
def get_preview_texture_name(cls) -> str:
return 'alwaysLandPreview'
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
'mesh': bs.getmesh('alwaysLandLevel'),
'bottom_mesh': bs.getmesh('alwaysLandLevelBottom'),
'bgmesh': bs.getmesh('alwaysLandBG'),
'collision_mesh': bs.getcollisionmesh('alwaysLandLevelCollide'),
'tex': bs.gettexture('alwaysLandLevelColor'),
'bgtex': bs.gettexture('alwaysLandBGColor'),
'vr_fill_mound_mesh': bs.getmesh('alwaysLandVRFillMound'),
'vr_fill_mound_tex': bs.gettexture('vrFillMound')
}
return data
@classmethod
def get_music_type(cls) -> bs.MusicType:
return bs.MusicType.FLYING
def __init__(self) -> None:
super().__init__(vr_overlay_offset=(0, -3.7, 2.5))
self.background = bs.newnode(
'terrain',
attrs={
'mesh': self.preloaddata['bgmesh'],
'lighting': False,
'background': True,
'color_texture': self.preloaddata['bgtex']
})
bs.newnode('terrain',
attrs={
'mesh': self.preloaddata['vr_fill_mound_mesh'],
'lighting': False,
'vr_only': True,
'color': (0.2, 0.25, 0.2),
'background': True,
'color_texture': self.preloaddata['vr_fill_mound_tex']
})
gnode = bs.getactivity().globalsnode
gnode.happy_thoughts_mode = True
gnode.shadow_offset = (0.0, 8.0, 5.0)
gnode.tint = (1.3, 1.23, 1.0)
gnode.ambient_color = (1.3, 1.23, 1.0)
gnode.vignette_outer = (0.64, 0.59, 0.69)
gnode.vignette_inner = (0.95, 0.95, 0.93)
gnode.vr_near_clip = 1.0
self.is_flying = True
register_map(AbyssMap)
class SpazTouchFoothold:
pass
class BombToDieMessage:
pass
class Foothold(bs.Actor):
def __init__(self,
position: Sequence[float] = (0.0, 1.0, 0.0),
power: str = 'random',
size: float = 6.0,
breakable: bool = True,
moving: bool = False):
super().__init__()
shared = SharedObjects.get()
powerup = PowerupBoxFactory.get()
fmesh = bs.getmesh('landMine')
fmeshs = bs.getmesh('powerupSimple')
self.died = False
self.breakable = breakable
self.moving = moving # move right and left
self.lrSig = 1 # left or right signal
self.lrSpeedPlus = random.uniform(1 / 2.0, 1 / 0.7)
self._npcBots = SpazBotSet()
self.foothold_material = bs.Material()
self.impact_sound = bui.getsound('impactMedium')
self.foothold_material.add_actions(
conditions=(('they_dont_have_material', shared.player_material),
'and',
('they_have_material', shared.object_material),
'or',
('they_have_material', shared.footing_material)),
actions=(('modify_node_collision', 'collide', True),
))
self.foothold_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('modify_part_collision', 'physical', True),
('modify_part_collision', 'stiffness', 0.05),
('message', 'our_node', 'at_connect', SpazTouchFoothold()),
))
self.foothold_material.add_actions(
conditions=('they_have_material', self.foothold_material),
actions=('modify_node_collision', 'collide', False),
)
tex = {
'punch': powerup.tex_punch,
'sticky_bombs': powerup.tex_sticky_bombs,
'ice_bombs': powerup.tex_ice_bombs,
'impact_bombs': powerup.tex_impact_bombs,
'health': powerup.tex_health,
'curse': powerup.tex_curse,
'shield': powerup.tex_shield,
'land_mines': powerup.tex_land_mines,
'tnt': bs.gettexture('tnt'),
}.get(power, bs.gettexture('tnt'))
powerupdist = {
powerup.tex_bomb: 3,
powerup.tex_ice_bombs: 2,
powerup.tex_punch: 3,
powerup.tex_impact_bombs: 3,
powerup.tex_land_mines: 3,
powerup.tex_sticky_bombs: 4,
powerup.tex_shield: 4,
powerup.tex_health: 3,
powerup.tex_curse: 1,
bs.gettexture('tnt'): 2
}
self.randtex = []
for keyTex in powerupdist:
for i in range(powerupdist[keyTex]):
self.randtex.append(keyTex)
if power == 'random':
random.seed()
tex = random.choice(self.randtex)
self.tex = tex
self.powerup_type = {
powerup.tex_punch: 'punch',
powerup.tex_bomb: 'triple_bombs',
powerup.tex_ice_bombs: 'ice_bombs',
powerup.tex_impact_bombs: 'impact_bombs',
powerup.tex_land_mines: 'land_mines',
powerup.tex_sticky_bombs: 'sticky_bombs',
powerup.tex_shield: 'shield',
powerup.tex_health: 'health',
powerup.tex_curse: 'curse',
bs.gettexture('tnt'): 'tnt'
}.get(self.tex, '')
self._spawn_pos = (position[0], position[1], position[2])
self.node = bs.newnode(
'prop',
delegate=self,
attrs={
'body': 'landMine',
'position': self._spawn_pos,
'mesh': fmesh,
'light_mesh': fmeshs,
'shadow_size': 0.5,
'velocity': (0, 0, 0),
'density': 90000000000,
'sticky': False,
'body_scale': size,
'mesh_scale': size,
'color_texture': tex,
'reflection': 'powerup',
'is_area_of_interest': True,
'gravity_scale': 0.0,
'reflection_scale': [0],
'materials': [self.foothold_material,
shared.object_material,
shared.footing_material]
})
self.touchedSpazs = set()
self.keep_vel()
def keep_vel(self) -> None:
if self.node and not self.died:
speed = bs.getactivity().cur_speed
if self.moving:
if abs(self.node.position[0]) > 10:
self.lrSig *= -1
self.node.velocity = (
self.lrSig * speed * self.lrSpeedPlus, speed, 0)
bs.timer(0.1, bs.WeakCall(self.keep_vel))
else:
self.node.velocity = (0, speed, 0)
# self.node.extraacceleration = (0, self.speed, 0)
bs.timer(0.1, bs.WeakCall(self.keep_vel))
def tnt_explode(self) -> None:
pos = self.node.position
Blast(position=pos,
blast_radius=6.0,
blast_type='tnt',
source_player=None).autoretain()
def spawn_npc(self) -> None:
if not self.breakable:
return
if self._npcBots.have_living_bots():
return
if random.randint(0, 3) >= bs.getactivity().npc_density:
return
pos = self.node.position
pos = (pos[0], pos[1] + 1, pos[2])
self._npcBots.spawn_bot(
bot_type=random.choice([ChargerBotPro, TriggerBotPro]),
pos=pos,
spawn_time=10)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
if self.node:
self.node.delete()
self.died = True
elif isinstance(msg, bs.OutOfBoundsMessage):
self.handlemessage(bs.DieMessage())
elif isinstance(msg, BombToDieMessage):
if self.powerup_type == 'tnt':
self.tnt_explode()
self.handlemessage(bs.DieMessage())
elif isinstance(msg, bs.HitMessage):
ispunched = (msg.srcnode and msg.srcnode.getnodetype() == 'spaz')
if not ispunched:
if self.breakable:
self.handlemessage(BombToDieMessage())
elif isinstance(msg, SpazTouchFoothold):
node = bs.getcollision().opposingnode
if node is not None and node:
try:
spaz = node.getdelegate(object)
if not isinstance(spaz, AbyssPlayerSpaz):
return
if spaz in self.touchedSpazs:
return
self.touchedSpazs.add(spaz)
self.spawn_npc()
spaz.fix_2D_position()
if self.powerup_type not in ['', 'tnt']:
node.handlemessage(
bs.PowerupMessage(self.powerup_type))
except Exception as e:
print(e)
pass
class AbyssPlayerSpaz(PlayerSpaz):
def __init__(self,
player: bs.Player,
color: Sequence[float] = (1.0, 1.0, 1.0),
highlight: Sequence[float] = (0.5, 0.5, 0.5),
character: str = 'Spaz',
powerups_expire: bool = True):
super().__init__(player=player,
color=color,
highlight=highlight,
character=character,
powerups_expire=powerups_expire)
self.node.fly = False
self.node.hockey = True
self.hitpoints_max = self.hitpoints = 1500 # more HP to handle drop
bs.timer(bs.getactivity().peace_time,
bs.WeakCall(self.safe_connect_controls_to_player))
def safe_connect_controls_to_player(self) -> None:
try:
self.connect_controls_to_player()
except:
pass
def on_move_up_down(self, value: float) -> None:
"""
Called to set the up/down joystick amount on this spaz;
used for player or AI connections.
value will be between -32768 to 32767
WARNING: deprecated; use on_move instead.
"""
if not self.node:
return
if self.node.run > 0.1:
self.node.move_up_down = value
else:
self.node.move_up_down = value / 3.
def on_move_left_right(self, value: float) -> None:
"""
Called to set the left/right joystick amount on this spaz;
used for player or AI connections.
value will be between -32768 to 32767
WARNING: deprecated; use on_move instead.
"""
if not self.node:
return
if self.node.run > 0.1:
self.node.move_left_right = value
else:
self.node.move_left_right = value / 1.5
def fix_2D_position(self) -> None:
self.node.fly = True
bs.timer(0.02, bs.WeakCall(self.disable_fly))
def disable_fly(self) -> None:
if self.node:
self.node.fly = False
def curse(self) -> None:
"""
Give this poor spaz a curse;
he will explode in 5 seconds.
"""
if not self._cursed:
factory = SpazFactory.get()
self._cursed = True
# Add the curse material.
for attr in ['materials', 'roller_materials']:
materials = getattr(self.node, attr)
if factory.curse_material not in materials:
setattr(self.node, attr,
materials + (factory.curse_material, ))
# None specifies no time limit
assert self.node
if self.curse_time == -1:
self.node.curse_death_time = -1
else:
# Note: curse-death-time takes milliseconds.
tval = bs.time()
assert isinstance(tval, (float, int))
self.node.curse_death_time = bs.time() + 15
bs.timer(15, bs.WeakCall(self.curse_explode))
def handlemessage(self, msg: Any) -> Any:
dontUp = False
if isinstance(msg, PickupMessage):
dontUp = True
collision = bs.getcollision()
opposingnode = collision.opposingnode
opposingbody = collision.opposingbody
if opposingnode is None or not opposingnode:
return True
opposingdelegate = opposingnode.getdelegate(object)
# Don't pick up the foothold
if isinstance(opposingdelegate, Foothold):
return True
# dont allow picking up of invincible dudes
try:
if opposingnode.invincible:
return True
except Exception:
pass
# if we're grabbing the pelvis of a non-shattered spaz,
# we wanna grab the torso instead
if (opposingnode.getnodetype() == 'spaz'
and not opposingnode.shattered and opposingbody == 4):
opposingbody = 1
# Special case - if we're holding a flag, don't replace it
# (hmm - should make this customizable or more low level).
held = self.node.hold_node
if held and held.getnodetype() == 'flag':
return True
# Note: hold_body needs to be set before hold_node.
self.node.hold_body = opposingbody
self.node.hold_node = opposingnode
if not dontUp:
PlayerSpaz.handlemessage(self, msg)
class Player(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
super().__init__()
self.death_time: float | None = None
self.notIn: bool = None
class Team(bs.Team[Player]):
"""Our team type for this game."""
# ba_meta export bascenev1.GameActivity
class AbyssGame(bs.TeamGameActivity[Player, Team]):
name = name
description = description
scoreconfig = bs.ScoreConfig(label='Survived',
scoretype=bs.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[bs.Session]) -> list[str]:
return ['Abyss Unhappy']
@classmethod
def get_available_settings(
cls, sessiontype: type[bs.Session]) -> list[babase.Setting]:
settings = [
bs.FloatChoiceSetting(
peaceTime,
choices=[
('None', 0.0),
('Shorter', 2.5),
('Short', 5.0),
('Normal', 10.0),
('Long', 15.0),
('Longer', 20.0),
],
default=10.0,
),
bs.FloatChoiceSetting(
npcDensity,
choices=[
('0%', 0),
('25%', 1),
('50%', 2),
('75%', 3),
('100%', 4),
],
default=2,
),
bs.BoolSetting('Epic Mode', default=False),
]
return settings
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.DualTeamSession)
or issubclass(sessiontype, bs.FreeForAllSession)
or issubclass(sessiontype, bs.CoopSession))
def __init__(self, settings: dict):
super().__init__(settings)
self._epic_mode = settings.get('Epic Mode', False)
self._last_player_death_time: float | None = None
self._timer: OnScreenTimer | None = None
self.fix_y = -5.614479365
self.start_z = 0
self.init_position = (0, self.start_z, self.fix_y)
self.team_init_positions = [(-5, self.start_z, self.fix_y),
(5, self.start_z, self.fix_y)]
self.cur_speed = 2.5
# TODO: The variable below should be set in settings
self.peace_time = float(settings[peaceTime])
self.npc_density = float(settings[npcDensity])
# Some base class overrides:
self.default_music = (bs.MusicType.EPIC
if self._epic_mode else bs.MusicType.SURVIVAL)
if self._epic_mode:
self.slow_motion = True
self._game_credit = bs.NodeActor(
bs.newnode(
'text',
attrs={
'v_attach': 'bottom',
'h_align': 'center',
'vr_depth': 0,
'color': (0.0, 0.7, 1.0),
'shadow': 1.0 if True else 0.5,
'flatness': 1.0 if True else 0.5,
'position': (0, 0),
'scale': 0.8,
'text': ' | '.join([author, github, blog])
}))
def get_instance_description(self) -> str | Sequence:
return description
def get_instance_description_short(self) -> str | Sequence:
return self.get_instance_description() + '\n' + help
def on_player_join(self, player: Player) -> None:
if self.has_begun():
player.notIn = True
bs.broadcastmessage(babase.Lstr(
resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0))
self.spawn_player(player)
def on_begin(self) -> None:
super().on_begin()
self._timer = OnScreenTimer()
self._timer.start()
self.level_cnt = 1
if self.teams_or_ffa() == 'teams':
ip0 = self.team_init_positions[0]
ip1 = self.team_init_positions[1]
Foothold(
(ip0[0], ip0[1] - 2, ip0[2]),
power='shield', breakable=False).autoretain()
Foothold(
(ip1[0], ip1[1] - 2, ip1[2]),
power='shield', breakable=False).autoretain()
else:
ip = self.init_position
Foothold(
(ip[0], ip[1] - 2, ip[2]),
power='shield', breakable=False).autoretain()
bs.timer(int(5.0 / self.cur_speed),
bs.WeakCall(self.add_foothold), repeat=True)
# Repeat check game end
bs.timer(1.0, self._check_end_game, repeat=True)
bs.timer(self.peace_time + 0.1,
bs.WeakCall(self.tip_hint, hint_use_punch))
bs.timer(6.0, bs.WeakCall(self.faster_speed), repeat=True)
def tip_hint(self, text: str) -> None:
bs.broadcastmessage(text, color=(0.2, 0.2, 1))
def faster_speed(self) -> None:
self.cur_speed *= 1.15
def add_foothold(self) -> None:
ip = self.init_position
ip_1 = (ip[0] - 7, ip[1], ip[2])
ip_2 = (ip[0] + 7, ip[1], ip[2])
ru = random.uniform
self.level_cnt += 1
if self.level_cnt % 3:
Foothold((
ip_1[0] + ru(-5, 5),
ip[1] - 2,
ip[2] + ru(-0.0, 0.0))).autoretain()
Foothold((
ip_2[0] + ru(-5, 5),
ip[1] - 2,
ip[2] + ru(-0.0, 0.0))).autoretain()
else:
Foothold((
ip[0] + ru(-8, 8),
ip[1] - 2,
ip[2]), moving=True).autoretain()
def teams_or_ffa(self) -> None:
if isinstance(self.session, bs.DualTeamSession):
return 'teams'
return 'ffa'
def spawn_player_spaz(self,
player: Player,
position: Sequence[float] = (0, 0, 0),
angle: float | None = None) -> PlayerSpaz:
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
from babase import _math
from bascenev1._gameutils import animate
position = self.init_position
if self.teams_or_ffa() == 'teams':
position = self.team_init_positions[player.team.id % 2]
angle = None
name = player.getname()
color = player.color
highlight = player.highlight
light_color = _math.normalized_color(color)
display_color = _babase.safecolor(color, target_intensity=0.75)
spaz = AbyssPlayerSpaz(color=color,
highlight=highlight,
character=player.character,
player=player)
player.actor = spaz
assert spaz.node
spaz.node.name = name
spaz.node.name_color = display_color
spaz.connect_controls_to_player(enable_punch=False,
enable_bomb=True,
enable_pickup=False)
# Move to the stand position and add a flash of light.
spaz.handlemessage(
bs.StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
self._spawn_sound.play(1, position=spaz.node.position)
light = bs.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
bs.timer(0.5, light.delete)
return spaz
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
curtime = bs.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, bs.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
babase.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = curtime
else:
bs.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, bs.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 0:
self.end_game()
def end_game(self) -> None:
cur_time = bs.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
if player.notIn:
player.death_time = 0
# 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 = bs.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)

445
dist/ba_root/mods/games/drone_war.py vendored Normal file
View file

@ -0,0 +1,445 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# Released under the MIT License. See LICENSE for details.
#
"""
DroneWar - Attack enemies with drone, Fly with drone and fire rocket launcher.
Author: Mr.Smoothy
Discord: https://discord.gg/ucyaesh
Youtube: https://www.youtube.com/c/HeySmoothy
Website: https://bombsquad-community.web.app
Github: https://github.com/bombsquad-community
"""
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bascenev1 as bs
from babase._mgen.enums import InputType
from bascenev1lib.actor.bomb import Blast
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.actor.playerspaz import PlayerSpaz, PlayerT
from bascenev1lib.game.deathmatch import DeathMatchGame, Player
from bascenev1lib.actor import spaz
if TYPE_CHECKING:
from typing import Any, Sequence, Dict, Type, List, Optional, Union
STORAGE_ATTR_NAME = f'_shared_{__name__}_factory'
# SMoothy's Drone (fixed version of floater) with rocket launcher
# use drone as long as you want , unlike floater which dies after being idle.
class Drone(bs.Actor):
def __init__(self, spaz):
super().__init__()
shared = SharedObjects.get()
self._drone_material = bs.Material()
self.loop_ascend = None
self.loop_descend = None
self.loop_lr = None
self.loop_ud = None
self.rocket_launcher = None
self.x_direction = 0
self.z_direction = 0
self.spaz = spaz
self._drone_material.add_actions(
conditions=('they_have_material',
shared.player_material),
actions=(('modify_node_collision', 'collide', True),
('modify_part_collision', 'physical', True)))
self._drone_material.add_actions(
conditions=(('they_have_material',
shared.object_material), 'or',
('they_have_material',
shared.footing_material), 'or',
('they_have_material',
self._drone_material)),
actions=('modify_part_collision', 'physical', False))
self.node = bs.newnode(
'prop',
delegate=self,
owner=None,
attrs={
'position': spaz.node.position,
'mesh': bs.getmesh('landMine'),
'light_mesh': bs.getmesh('landMine'),
'body': 'landMine',
'body_scale': 1,
'mesh_scale': 1,
'shadow_size': 0.25,
'density': 999999,
'gravity_scale': 0.0,
'color_texture': bs.gettexture('achievementCrossHair'),
'reflection': 'soft',
'reflection_scale': [0.25],
'materials': [shared.footing_material, self._drone_material]
})
self.grab_node = bs.newnode(
'prop',
owner=self.node,
attrs={
'position': (0, 0, 0),
'body': 'sphere',
'mesh': None,
'color_texture': None,
'body_scale': 0.2,
'reflection': 'powerup',
'density': 999999,
'reflection_scale': [1.0],
'mesh_scale': 0.2,
'gravity_scale': 0,
'shadow_size': 0.1,
'is_area_of_interest': True,
'materials': [shared.object_material, self._drone_material]
})
self.node.connectattr('position', self.grab_node, 'position')
self._rcombine = bs.newnode('combine',
owner=self.node,
attrs={
'input0': self.spaz.node.position[0],
'input1': self.spaz.node.position[1]+3,
'input2': self.spaz.node.position[2],
'size': 3
})
self._rcombine.connectattr('output', self.node, 'position')
def set_rocket_launcher(self, launcher: RocketLauncher):
self.rocket_launcher = launcher
def fire(self):
if hasattr(self.grab_node, "position"):
self.rocket_launcher.shot(self.spaz, self.x_direction, self.z_direction, (
self.grab_node.position[0], self.grab_node.position[1] - 1, self.grab_node.position[2]))
def ascend(self):
def loop():
if self.node.exists():
bs.animate(self._rcombine, 'input1', {
0: self.node.position[1],
1: self.node.position[1] + 2
})
loop()
self.loop_ascend = bs.Timer(1, loop, repeat=True)
def pause_movement(self):
self.loop_ascend = None
def decend(self):
def loop():
if self.node.exists():
bs.animate(self._rcombine, 'input1', {
0: self.node.position[1],
1: self.node.position[1] - 2
})
loop()
self.loop_ascend = bs.Timer(1, loop, repeat=True)
def pause_lr(self):
self.loop_lr = None
def pause_ud(self):
self.loop_ud = None
def left_(self, value=-1):
def loop():
if self.node.exists():
bs.animate(self._rcombine, 'input0', {
0: self.node.position[0],
1: self.node.position[0] + 2 * value
})
if value == 0.0:
self.loop_lr = None
else:
self.x_direction = value
self.z_direction = 0
loop()
self.loop_lr = bs.Timer(1, loop, repeat=True)
def right_(self, value=1):
def loop():
if self.node.exists():
bs.animate(self._rcombine, 'input0', {
0: self.node.position[0],
1: self.node.position[0] + 2 * value
})
if value == 0.0:
self.loop_lr = None
else:
self.x_direction = value
self.z_direction = 0
loop()
self.loop_lr = bs.Timer(1, loop, repeat=True)
def up_(self, value=1):
def loop():
if self.node.exists():
bs.animate(self._rcombine, 'input2', {
0: self.node.position[2],
1: self.node.position[2] - 2 * value
})
if value == 0.0:
self.loop_ud = None
else:
self.x_direction = 0
self.z_direction = - value
loop()
self.loop_ud = bs.Timer(1, loop, repeat=True)
def down_(self, value=-1):
def loop():
if self.node.exists():
bs.animate(self._rcombine, 'input2', {
0: self.node.position[2],
1: self.node.position[2] - 2 * value
})
if value == 0.0:
self.loop_ud = None
else:
self.x_direction = 0
self.z_direction = - value
loop()
self.loop_ud = bs.Timer(1, loop, repeat=True)
def handlemessage(self, msg):
if isinstance(msg, bs.DieMessage):
self.node.delete()
self.grab_node.delete()
self.loop_ascend = None
self.loop_ud = None
self.loop_lr = None
elif isinstance(msg, bs.OutOfBoundsMessage):
self.handlemessage(bs.DieMessage())
else:
super().handlemessage(msg)
# =============================================Copied from Quake Game - Dliwk =====================================================================
class RocketFactory:
"""Quake Rocket factory"""
def __init__(self) -> None:
self.ball_material = bs.Material()
self.ball_material.add_actions(
conditions=((('we_are_younger_than', 5), 'or',
('they_are_younger_than', 5)), 'and',
('they_have_material',
SharedObjects.get().object_material)),
actions=('modify_node_collision', 'collide', False))
self.ball_material.add_actions(
conditions=('they_have_material',
SharedObjects.get().pickup_material),
actions=('modify_part_collision', 'use_node_collide', False))
self.ball_material.add_actions(actions=('modify_part_collision',
'friction', 0))
self.ball_material.add_actions(
conditions=(('they_have_material',
SharedObjects.get().footing_material), 'or',
('they_have_material',
SharedObjects.get().object_material)),
actions=('message', 'our_node', 'at_connect', ImpactMessage()))
@classmethod
def get(cls):
"""Get factory if exists else create new"""
activity = bs.getactivity()
if hasattr(activity, STORAGE_ATTR_NAME):
return getattr(activity, STORAGE_ATTR_NAME)
factory = cls()
setattr(activity, STORAGE_ATTR_NAME, factory)
return factory
class RocketLauncher:
"""Very dangerous weapon"""
def __init__(self):
self.last_shot = bs.time()
def give(self, spaz: spaz.Spaz) -> None:
"""Give spaz a rocket launcher"""
spaz.punch_callback = self.shot
self.last_shot = bs.time()
# FIXME
# noinspection PyUnresolvedReferences
def shot(self, spaz, x, z, position) -> None:
"""Release a rocket"""
time = bs.time()
if time - self.last_shot > 0.6:
self.last_shot = time
direction = [x, 0, z]
direction[1] = 0.0
mag = 10.0 / \
1 if babase.Vec3(*direction).length() == 0 else babase.Vec3(*direction).length()
vel = [v * mag for v in direction]
Rocket(position=position,
velocity=vel,
owner=spaz.getplayer(bs.Player),
source_player=spaz.getplayer(bs.Player),
color=spaz.node.color).autoretain()
class ImpactMessage:
"""Rocket touched something"""
class Rocket(bs.Actor):
"""Epic rocket from rocket launcher"""
def __init__(self,
position=(0, 5, 0),
velocity=(1, 0, 0),
source_player=None,
owner=None,
color=(1.0, 0.2, 0.2)) -> None:
super().__init__()
self.source_player = source_player
self.owner = owner
self._color = color
factory = RocketFactory.get()
self.node = bs.newnode('prop',
delegate=self,
attrs={
'position': position,
'velocity': velocity,
'mesh': bs.getmesh('impactBomb'),
'body': 'sphere',
'color_texture': bs.gettexture(
'bunnyColor'),
'mesh_scale': 0.2,
'is_area_of_interest': True,
'body_scale': 0.8,
'materials': [
SharedObjects.get().object_material,
factory.ball_material]
}) # yapf: disable
self.node.extra_acceleration = (self.node.velocity[0] * 200, 0,
self.node.velocity[2] * 200)
self._life_timer = bs.Timer(
5, bs.WeakCall(self.handlemessage, bs.DieMessage()))
self._emit_timer = bs.Timer(0.001, bs.WeakCall(self.emit), repeat=True)
self.base_pos_y = self.node.position[1]
bs.camerashake(5.0)
def emit(self) -> None:
"""Emit a trace after rocket"""
bs.emitfx(position=self.node.position,
scale=0.4,
spread=0.01,
chunk_type='spark')
if not self.node:
return
self.node.position = (self.node.position[0], self.base_pos_y,
self.node.position[2]) # ignore y
bs.newnode('explosion',
owner=self.node,
attrs={
'position': self.node.position,
'radius': 0.2,
'color': self._color
})
def handlemessage(self, msg: Any) -> Any:
"""Message handling for rocket"""
super().handlemessage(msg)
if isinstance(msg, ImpactMessage):
self.node.handlemessage(bs.DieMessage())
elif isinstance(msg, bs.DieMessage):
if self.node:
Blast(position=self.node.position,
blast_radius=2,
source_player=self.source_player)
self.node.delete()
self._emit_timer = None
elif isinstance(msg, bs.OutOfBoundsMessage):
self.handlemessage(bs.DieMessage())
# ba_meta export bascenev1.GameActivity
class ChooseQueen(DeathMatchGame):
name = 'Drone War'
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return ['Football Stadium']
def spawn_player_spaz(
self,
player: PlayerT,
position: Sequence[float] | None = None,
angle: float | None = None,
) -> PlayerSpaz:
spaz = super().spawn_player_spaz(player, position, angle)
self.spawn_drone(spaz)
return spaz
def on_begin(self):
super().on_begin()
shared = SharedObjects.get()
self.ground_material = bs.Material()
self.ground_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(
('modify_part_collision', 'collide', True),
('call', 'at_connect', babase.Call(self._handle_player_collide)),
),
)
pos = (0, 0.1, -5)
self.main_region = bs.newnode('region', attrs={'position': pos, 'scale': (
30, 0.001, 23), 'type': 'box', 'materials': [shared.footing_material, self.ground_material]})
def _handle_player_collide(self):
try:
player = bs.getcollision().opposingnode.getdelegate(
PlayerSpaz, True)
except bs.NotFoundError:
return
if player.is_alive():
player.shatter(True)
def spawn_drone(self, spaz):
with bs.get_foreground_host_activity().context:
drone = Drone(spaz)
r_launcher = RocketLauncher()
drone.set_rocket_launcher(r_launcher)
player = spaz.getplayer(Player, True)
spaz.node.hold_node = drone.grab_node
player.actor.disconnect_controls_from_player()
player.resetinput()
player.assigninput(InputType.PICK_UP_PRESS, drone.ascend)
player.assigninput(InputType.PICK_UP_RELEASE, drone.pause_movement)
player.assigninput(InputType.JUMP_PRESS, drone.decend)
player.assigninput(InputType.PUNCH_PRESS, drone.fire)
player.assigninput(InputType.LEFT_PRESS, drone.left_)
player.assigninput(InputType.RIGHT_PRESS, drone.right_)
player.assigninput(InputType.LEFT_RELEASE, drone.pause_lr)
player.assigninput(InputType.RIGHT_RELEASE, drone.pause_lr)
player.assigninput(InputType.UP_PRESS, drone.up_)
player.assigninput(InputType.DOWN_PRESS, drone.down_)
player.assigninput(InputType.UP_RELEASE, drone.pause_ud)
player.assigninput(InputType.DOWN_RELEASE, drone.pause_ud)

493
dist/ba_root/mods/games/egg_game.py vendored Normal file
View file

@ -0,0 +1,493 @@
# Ported by brostos to api 8
# Tool used to make porting easier.(https://github.com/bombsquad-community/baport)
# Released under the MIT License. See LICENSE for details.
"""Egg game and support classes."""
# The Egg Game - throw egg as far as you can
# created in BCS (Bombsquad Consultancy Service) - opensource bombsquad mods for all
# discord.gg/ucyaesh join now and give your contribution
# The Egg game by mr.smoothy
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.actor.flag import Flag
import math
import random
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(bs.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 = None
self.scored = False
self.egg_mesh = bs.getmesh('egg')
self.egg_tex_1 = bs.gettexture('eggTex1')
self.egg_tex_2 = bs.gettexture('eggTex2')
self.egg_tex_3 = bs.gettexture('eggTex3')
self.eggtx = [self.egg_tex_1, self.egg_tex_2, self.egg_tex_3]
regg = random.randrange(0, 3)
assert activity is not None
assert isinstance(activity, EggGame)
pmats = [shared.object_material, activity.puck_material]
self.node = bs.newnode('prop',
delegate=self,
attrs={
'mesh': self.egg_mesh,
'color_texture': self.eggtx[regg],
'body': 'capsule',
'reflection': 'soft',
'reflection_scale': [0.2],
'shadow_size': 0.5,
'body_scale': 0.7,
'is_area_of_interest': True,
'position': self._spawn_pos,
'materials': pmats
})
bs.animate(self.node, 'mesh_scale', {0: 0, 0.2: 0.7, 0.26: 0.6})
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.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, bs.OutOfBoundsMessage):
assert self.node
self.node.position = self._spawn_pos
elif isinstance(msg, bs.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
else:
super().handlemessage(msg)
class Player(bs.Player['Team']):
"""Our player type for this game."""
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export bascenev1.GameActivity
class EggGame(bs.TeamGameActivity[Player, Team]):
"""Egg game."""
name = 'Epic Egg Game'
description = 'Score some goals.'
available_settings = [
bs.IntSetting(
'Score to Win',
min_value=1,
default=1,
increment=1,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('40 Seconds', 40),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.1),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
]
default_music = bs.MusicType.HOCKEY
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return bs.app.classic.getmaps('football')
def __init__(self, settings: dict):
super().__init__(settings)
shared = SharedObjects.get()
self.slow_motion = True
self._scoreboard = Scoreboard()
self._cheer_sound = bui.getsound('cheer')
self._chant_sound = bui.getsound('crowdChant')
self._foghorn_sound = bui.getsound('foghorn')
self._swipsound = bui.getsound('swip')
self._whistle_sound = bui.getsound('refWhistle')
self.puck_mesh = bs.getmesh('bomb')
self.puck_tex = bs.gettexture('landMine')
self.puck_scored_tex = bs.gettexture('landMineLit')
self._puck_sound = bui.getsound('metalHit')
self.puck_material = bs.Material()
self._fake_wall_material = bs.Material()
self.HIGHEST = 0
self._fake_wall_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', True)
))
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', bs.DieMessage())))
# self.puck_material.add_actions(
# conditions=('they_have_material',shared.footing_material)
# actions=(('modify_part_collision', 'collide',
# True), ('modify_part_collision', 'physical', True),
# ('call', 'at_connect', self._handle_egg_collision))
# )
self._score_region_material = bs.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.main_ground_material = bs.Material()
self.main_ground_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_egg_collision)))
self._puck_spawn_pos: Optional[Sequence[float]] = None
self._score_regions: Optional[List[bs.NodeActor]] = None
self._puck: Optional[Puck] = None
self._pucks = []
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
def get_instance_description(self) -> Union[str, Sequence]:
return "Throw Egg as far u can"
def get_instance_description_short(self) -> Union[str, Sequence]:
return "Throw Egg as far u can"
def on_begin(self) -> None:
super().on_begin()
if self._time_limit == 0.0:
self._time_limit = 60
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()
self._spawn_puck()
self._spawn_puck()
self._spawn_puck()
self._spawn_puck()
# Set up the two score regions.
defs = self.map.defs
self._score_regions = []
pos = (11.88630542755127, 0.3009839951992035, 1.33331298828125)
# mat=bs.Material()
# mat.add_actions(
# actions=( ('modify_part_collision','physical',True),
# ('modify_part_collision','collide',True))
# )
# self._score_regions.append(
# bs.NodeActor(
# bs.newnode('region',
# attrs={
# 'position': pos,
# 'scale': (2,3,5),
# 'type': 'box',
# 'materials': [self._score_region_material]
# })))
# pos=(-11.88630542755127, 0.3009839951992035, 1.33331298828125)
# self._score_regions.append(
# bs.NodeActor(
# bs.newnode('region',
# attrs={
# 'position': pos,
# 'scale': (2,3,5),
# 'type': 'box',
# 'materials': [self._score_region_material]
# })))
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': (-9.21, defs.boxes['goal2'][0:3][1], defs.boxes['goal2'][0:3][2]),
'scale': defs.boxes['goal2'][6:9],
'type': 'box',
'materials': (self._fake_wall_material, )
})))
pos = (0, 0.1, -5)
self.main_ground = bs.newnode('region', attrs={'position': pos, 'scale': (
25, 0.001, 22), 'type': 'box', 'materials': [self.main_ground_material]})
self._update_scoreboard()
self._chant_sound.play()
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
def _handle_puck_player_collide(self) -> None:
collision = bs.getcollision()
try:
puck = collision.sourcenode.getdelegate(Puck, True)
player = collision.opposingnode.getdelegate(PlayerSpaz,
True).getplayer(
Player, True)
except bs.NotFoundError:
return
puck.last_players_to_touch = player
def _kill_puck(self) -> None:
self._puck = None
def _handle_egg_collision(self) -> None:
no = bs.getcollision().opposingnode
pos = no.position
egg = no.getdelegate(Puck)
source_player = egg.last_players_to_touch
if source_player == None or pos[0] < -8 or not source_player.node.exists():
return
try:
col = source_player.team.color
self.flagg = Flag(pos, touchable=False, color=col).autoretain()
self.flagg.is_area_of_interest = True
player_pos = source_player.node.position
distance = math.sqrt(pow(player_pos[0]-pos[0], 2) + pow(player_pos[2]-pos[2], 2))
dis_mark = bs.newnode('text',
attrs={
'text': str(round(distance, 2))+"m",
'in_world': True,
'scale': 0.02,
'h_align': 'center',
'position': (pos[0], 1.6, pos[2]),
'color': col
})
bs.animate(dis_mark, 'scale', {
0.0: 0, 0.5: 0.01
})
if distance > self.HIGHEST:
self.HIGHEST = distance
self.stats.player_scored(
source_player,
10,
big_message=False)
no.delete()
bs.timer(2, self._spawn_puck)
source_player.team.score = int(distance)
except ():
pass
def spawn_player(self, player: Player) -> bs.Actor:
zoo = random.randrange(-4, 5)
pos = (-11.204887390136719, 0.2998693287372589, zoo)
spaz = self.spawn_player_spaz(
player, position=pos, angle=90)
assert spaz.node
# Prevent controlling of characters before the start of the race.
return spaz
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 = bs.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(bs.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],
20,
big_message=True)
# End game if we won.
if team.score >= self._score_to_win:
self.end_game()
self._foghorn_sound.play()
self._cheer_sound.play()
# self._puck.scored = True
# Change puck texture to something cool
# self._puck.node.color_texture = self.puck_scored_tex
# Kill the puck (it'll respawn itself shortly).
bs.timer(1.0, self._kill_puck)
# light = bs.newnode('light',
# attrs={
# 'position': bs.getcollision().position,
# 'height_attenuated': False,
# 'color': (1, 0, 0)
# })
# bs.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True)
# bs.timer(1.0, light.delete)
bs.cameraflash(duration=10.0)
self._update_scoreboard()
def end_game(self) -> None:
results = bs.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, bs.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():
bs.timer(3.0, self._spawn_puck)
else:
super().handlemessage(msg)
def _flash_puck_spawn(self) -> None:
# light = bs.newnode('light',
# attrs={
# 'position': self._puck_spawn_pos,
# 'height_attenuated': False,
# 'color': (1, 0, 0)
# })
# bs.animate(light, 'intensity', {0.0: 0, 0.25: 1, 0.5: 0}, loop=True)
# bs.timer(1.0, light.delete)
pass
def _spawn_puck(self) -> None:
# self._swipsound.play()
# self._whistle_sound.play()
self._flash_puck_spawn()
assert self._puck_spawn_pos is not None
zoo = random.randrange(-5, 6)
pos = (-11.204887390136719, 0.2998693287372589, zoo)
self._pucks.append(Puck(position=pos))

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

@ -0,0 +1,339 @@
# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport)
# ba_meta require api 8
# - - - - - - - - - - - - - - - - - - - - -
# - Fat-Pigs! by Zacker Tz || Zacker#5505 -
# - Version 0.01 :v -
# - - - - - - - - - - - - - - - - - - - - -
from __future__ import annotations
from typing import TYPE_CHECKING
import random
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.actor.bomb import Bomb
from bascenev1lib.actor.onscreentimer import OnScreenTimer
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
if TYPE_CHECKING:
from typing import Any, Union, Sequence, Optional
# - - - - - - - Mini - Settings - - - - - - - - - - - - - - - - #
zkBombs_limit = 3 # Number of bombs you can use | Default = 3
zkPunch = False # Enable/Disable punchs | Default = False
zkPickup = False # Enable/Disable pickup | Default = False
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
class Player(bs.Player['Team']):
"""Our player type for this game."""
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export bascenev1.GameActivity
class FatPigs(bs.TeamGameActivity[Player, Team]):
"""A game type based on acquiring kills."""
name = 'Fat-Pigs!'
description = 'Survive...'
# Print messages when players die since it matters here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: type[bs.Session]) -> list[babase.Setting]:
settings = [
bs.IntSetting(
'Kills to Win Per Player',
min_value=1,
default=5,
increment=1,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=0.25,
),
bs.BoolSetting('Epic Mode', default=False),
]
# In teams mode, a suicide gives a point to the other team, but in
# free-for-all it subtracts from your own score. By default we clamp
# this at zero to benefit new players, but pro players might like to
# be able to go negative. (to avoid a strategy of just
# suiciding until you get a good drop)
if issubclass(sessiontype, bs.FreeForAllSession):
settings.append(
bs.BoolSetting('Allow Negative Scores', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.DualTeamSession)
or issubclass(sessiontype, bs.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return ['Courtyard', 'Rampage', 'Monkey Face', 'Lake Frigid', 'Step Right Up']
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._meteor_time = 2.0
self._score_to_win: Optional[int] = None
self._dingsound = bs.getsound('dingSmall')
self._epic_mode = bool(settings['Epic Mode'])
# self._text_credit = bool(settings['Credits'])
self._kills_to_win_per_player = int(
settings['Kills to Win Per Player'])
self._time_limit = float(settings['Time Limit'])
self._allow_negative_scores = bool(
settings.get('Allow Negative Scores', False))
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC if self._epic_mode else
bs.MusicType.TO_THE_DEATH)
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)
# self.setup_standard_powerup_drops()
# Ambiente
gnode = bs.getactivity().globalsnode
gnode.tint = (0.8, 1.2, 0.8)
gnode.ambient_color = (0.7, 1.0, 0.6)
gnode.vignette_outer = (0.4, 0.6, 0.4) # C
# gnode.vignette_inner = (0.9, 0.9, 0.9)
# 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()
delay = 5.0 if len(self.players) > 2 else 2.5
if self._epic_mode:
delay *= 0.25
bs.timer(delay, self._decrement_meteor_time, repeat=False)
# Kick off the first wave in a few seconds.
delay = 3.0
if self._epic_mode:
delay *= 0.25
bs.timer(delay, self._set_meteor_timer)
# self._timer = OnScreenTimer()
# self._timer.start()
# Check for immediate end (if we've only got 1 player, etc).
bs.timer(5.0, self._check_end_game)
t = bs.newnode('text',
attrs={'text': "Minigame by Zacker Tz",
'scale': 0.7,
'position': (0.001, 625),
'shadow': 0.5,
'opacity': 0.7,
'flatness': 1.2,
'color': (0.6, 1, 0.6),
'h_align': 'center',
'v_attach': 'bottom'})
def spawn_player(self, player: Player) -> bs.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=zkPunch,
enable_bomb=True,
enable_pickup=zkPickup)
spaz.bomb_count = zkBombs_limit
spaz._max_bomb_count = zkBombs_limit
spaz.bomb_type_default = 'sticky'
spaz.bomb_type = 'sticky'
# cerdo gordo
spaz.node.color_mask_texture = bs.gettexture('melColorMask')
spaz.node.color_texture = bs.gettexture('melColor')
spaz.node.head_mesh = bs.getmesh('melHead')
spaz.node.hand_mesh = bs.getmesh('melHand')
spaz.node.torso_mesh = bs.getmesh('melTorso')
spaz.node.pelvis_mesh = bs.getmesh('kronkPelvis')
spaz.node.upper_arm_mesh = bs.getmesh('melUpperArm')
spaz.node.forearm_mesh = bs.getmesh('melForeArm')
spaz.node.upper_leg_mesh = bs.getmesh('melUpperLeg')
spaz.node.lower_leg_mesh = bs.getmesh('melLowerLeg')
spaz.node.toes_mesh = bs.getmesh('melToes')
spaz.node.style = 'mel'
# Sounds cerdo gordo
mel_sounds = [bs.getsound('mel01'), bs.getsound('mel02'), bs.getsound('mel03'), bs.getsound('mel04'), bs.getsound('mel05'),
bs.getsound('mel06'), bs.getsound('mel07'), bs.getsound('mel08'), bs.getsound('mel09'), bs.getsound('mel10')]
spaz.node.jump_sounds = mel_sounds
spaz.node.attack_sounds = mel_sounds
spaz.node.impact_sounds = mel_sounds
spaz.node.pickup_sounds = mel_sounds
spaz.node.death_sounds = [bs.getsound('melDeath01')]
spaz.node.fall_sounds = [bs.getsound('melFall01')]
def _set_meteor_timer(self) -> None:
bs.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:
bs.newnode('locator', attrs={'position': (8, 6, -5.5)})
bs.newnode('locator', attrs={'position': (8, 6, -2.3)})
bs.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
bs.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)
bs.timer(delay, babase.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='sticky').autoretain()
def _decrement_meteor_time(self) -> None:
self._meteor_time = max(0.01, self._meteor_time * 0.9)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
self.respawn_player(player)
killer = msg.getkillerplayer(Player)
if killer is None:
return None
# Handle team-kills.
if killer.team is player.team:
# In free-for-all, killing yourself loses you a point.
if isinstance(self.session, bs.FreeForAllSession):
new_score = player.team.score - 1
if not self._allow_negative_scores:
new_score = max(0, new_score)
player.team.score = new_score
# In teams-mode it gives a point to the other team.
else:
self._dingsound.play()
for team in self.teams:
if team is not killer.team:
team.score += 1
# Killing someone on another team nets a kill.
else:
killer.team.score += 1
self._dingsound.play()
# In FFA show scores since its hard to find on the scoreboard.
if isinstance(killer.actor, PlayerSpaz) and killer.actor:
killer.actor.set_score_text(str(killer.team.score) + '/' +
str(self._score_to_win),
color=killer.team.color,
flash=True)
self._update_scoreboard()
# If someone has won, set a timer to end shortly.
# (allows the dust to clear and draws to occur if deaths are
# close enough)
assert self._score_to_win is not None
if any(team.score >= self._score_to_win for team in self.teams):
bs.timer(0.5, self.end_game)
else:
return super().handlemessage(msg)
return None
def _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, bs.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(team, team.score,
self._score_to_win)
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

View file

@ -0,0 +1,35 @@
# Ported to api 8 by brostos using baport.(https://github.com/bombsquad-community/baport)
# Made by MattZ45986 on GitHub
# Ported by: Freaku / @[Just] Freak#4999
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.game.elimination import EliminationGame
# ba_meta require api 8
# ba_meta export bascenev1.GameActivity
class GFGame(EliminationGame):
name = 'Gravity Falls'
def spawn_player(self, player):
actor = self.spawn_player_spaz(player, (0, 5, 0))
if not self._solo_mode:
bs.timer(0.3, babase.Call(self._print_lives, player))
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_spawned()
bs.timer(1, babase.Call(self.raise_player, player))
return actor
def raise_player(self, player):
if player.is_alive():
try:
player.actor.node.handlemessage(
"impulse", player.actor.node.position[0], player.actor.node.position[1]+.5, player.actor.node.position[2], 0, 5, 0, 3, 10, 0, 0, 0, 5, 0)
except:
pass
bs.timer(0.05, babase.Call(self.raise_player, player))

1061
dist/ba_root/mods/games/hot_potato.py vendored Normal file

File diff suppressed because it is too large Load diff

1238
dist/ba_root/mods/games/hyper_race.py vendored Normal file

File diff suppressed because it is too large Load diff

526
dist/ba_root/mods/games/infection.py vendored Normal file
View file

@ -0,0 +1,526 @@
"""New Duel / Created by: byANG3L"""
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
import _babase
import random
from bascenev1lib.actor.bomb import Bomb
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
pass
lang = bs.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 8
def ba_get_levels():
return [bs._level.Level(
name,
gametype=Infection,
settings={},
preview_texture_name='footballStadiumPreview')]
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 = bs.newnode(
'locator',
attrs={
'shape': 'circle',
'position': self.node.position,
'color': (1, 0, 0),
'opacity': 0.5,
'draw_beauty': showInSpace,
'additive': True})
bs.animate_array(
self.zone,
'size',
1,
{0: [0.0], 0.05: [2*self.rad]})
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
if not self.died:
self.getactivity().mine_count -= 1
self.died = True
bs.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(bs.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(bs.Team[Player]):
"""Our team type for this game."""
# ba_meta export bascenev1.GameActivity
class Infection(bs.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
allow_mid_activity_joins = False
@classmethod
def get_available_settings(
cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]:
settings = [
bs.IntSetting(
mines,
min_value=5,
default=10,
increment=5,
),
bs.BoolSetting(enable_bombs, default=True),
bs.IntSetting(
extra_mines,
min_value=1,
default=10,
increment=1,
),
bs.IntSetting(
max_infected_size,
min_value=4,
default=6,
increment=1,
),
bs.IntChoiceSetting(
max_size_increases,
choices=[
('10s', 10),
('20s', 20),
('30s', 30),
('1 Minute', 60),
],
default=20,
),
bs.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,
),
bs.BoolSetting('Epic Mode', default=False),
]
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.CoopSession)
or issubclass(sessiontype, bs.MultiTeamSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.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 = (bs.MusicType.EPIC if self._epic_mode else
bs.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 = bs.time()
self.mine_count = 0
bs.timer(self._update_rate,
bs.WeakCall(self._mine_update),
repeat=True)
bs.timer(self._max_size_increases*1.0,
bs.WeakCall(self._max_size_update),
repeat=True)
bs.timer(self._extra_mines*1.0,
bs.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).
bs.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()
bs.broadcastmessage(
babase.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 = (babase.Vec3(p1[0]-p2[0],
0.0,
p1[2]-p2[2]))
dist = (diff.length())
if dist < m.rad:
player.actor.handlemessage(bs.DieMessage())
# Now tell the circle to grow to the new size
if m.rad < self._max_size:
bs.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)
bs.timer(0.95, babase.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 = bs.newnode('light',
attrs={
'position': pos,
'color': (1, 0.2, 0.2),
'radius': 0.1,
'height_attenuated': False
})
bs.animate(light, 'intensity', {0.0: 0, 0.1: 1.0, 0.2: 0}, loop=True)
bs.timer(1.0, light.delete)
def end_game(self) -> None:
results = bs.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 = bs.newnode('light',
attrs={
'position': pos,
'color': (1, 1, 0),
'height_attenuated': False,
'radius': 0.4
})
bs.timer(0.5, light.delete)
bs.animate(light, 'intensity', {0: 0, 0.1: 1.0 * scale, 0.5: 0})
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
death_time = bs.time()
msg.getplayer(Player).death_time = death_time
if isinstance(self.session, bs.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
babase.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = death_time
else:
bs.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, bs.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def spawn_player(self, player: PlayerType) -> bs.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 bs.PlayerSpaz for the provided bs.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 = babase._math.normalized_color(color)
display_color = _babase.safecolor(color, target_intensity=0.75)
spaz = PlayerSpaz(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, bs.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(
bs.StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
self._spawn_sound.play(1, position=spaz.node.position)
light = bs.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
bs.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
bs.timer(0.5, light.delete)
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 = bs.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 = bs.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)

333
dist/ba_root/mods/games/invisible_one.py vendored Normal file
View file

@ -0,0 +1,333 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# Released under the MIT License. See LICENSE for details.
#
# By itsre3
# =>3<=
# Don't mind my spelling. i realized that they were not correct after making last change and saving
# Besides that, enjoy.......!!
"""Provides the chosen-one mini-game."""
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.actor.flag import Flag
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Optional, Sequence, Union
class Player(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.chosen_light: Optional[bs.NodeActor] = None
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self, time_remaining: int) -> None:
self.time_remaining = time_remaining
# ba_meta export bascenev1.GameActivity
class InvicibleOneGame(bs.TeamGameActivity[Player, Team]):
"""
Game involving trying to remain the one 'invisible one'
for a set length of time while everyone else tries to
kill you and become the invisible one themselves.
"""
name = 'Invisible One'
description = ('Be the invisible one for a length of time to win.\n'
'Kill the invisible one to become it.')
available_settings = [
bs.IntSetting(
'Invicible One Time',
min_value=10,
default=30,
increment=10,
),
bs.BoolSetting('Invicible one is lazy', default=True),
bs.BoolSetting('Night mode', default=False),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
bs.BoolSetting('Epic Mode', default=False),
]
scoreconfig = bs.ScoreConfig(label='Time Held')
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return bs.app.classic.getmaps('keep_away')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._invicible_one_player: Optional[Player] = None
self._swipsound = bs.getsound('swip')
self._countdownsounds: Dict[int, babase.Sound] = {
10: bs.getsound('announceTen'),
9: bs.getsound('announceNine'),
8: bs.getsound('announceEight'),
7: bs.getsound('announceSeven'),
6: bs.getsound('announceSix'),
5: bs.getsound('announceFive'),
4: bs.getsound('announceFour'),
3: bs.getsound('announceThree'),
2: bs.getsound('announceTwo'),
1: bs.getsound('announceOne')
}
self._flag_spawn_pos: Optional[Sequence[float]] = None
self._reset_region_material: Optional[bs.Material] = None
self._flag: Optional[Flag] = None
self._reset_region: Optional[bs.Node] = None
self._epic_mode = bool(settings['Epic Mode'])
self._invicible_one_time = int(settings['Invicible One Time'])
self._time_limit = float(settings['Time Limit'])
self._invicible_one_is_lazy = bool(settings['Invicible one is lazy'])
self._night_mode = bool(settings['Night mode'])
# Base class overrides
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC
if self._epic_mode else bs.MusicType.CHOSEN_ONE)
def get_instance_description(self) -> Union[str, Sequence]:
return 'Show your invisibility powers.'
def create_team(self, sessionteam: bs.SessionTeam) -> Team:
return Team(time_remaining=self._invicible_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_invicible_one_player() is player:
self._set_invicible_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_invicible_one_player(None)
if self._night_mode:
gnode = bs.getactivity().globalsnode
gnode.tint = (0.4, 0.4, 0.4)
pos = self._flag_spawn_pos
bs.timer(1.0, call=self._tick, repeat=True)
mat = self._reset_region_material = bs.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',
bs.WeakCall(self._handle_reset_collide)),
),
)
self._reset_region = bs.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_invicible_one_player(self) -> Optional[Player]:
# Should never return invalid references; return None in that case.
if self._invicible_one_player:
return self._invicible_one_player
return None
def _handle_reset_collide(self) -> None:
# If we have a chosen one, ignore these.
if self._get_invicible_one_player() is not None:
return
# Attempt to get a Player controlling a Spaz that we hit.
try:
player = bs.getcollision().opposingnode.getdelegate(
PlayerSpaz, True).getplayer(Player, True)
except bs.NotFoundError:
return
if player.is_alive():
self._set_invicible_one_player(player)
def _flash_flag_spawn(self) -> None:
light = bs.newnode('light',
attrs={
'position': self._flag_spawn_pos,
'color': (1, 1, 1),
'radius': 0.3,
'height_attenuated': False
})
bs.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
bs.timer(1.0, light.delete)
def _tick(self) -> None:
# Give the chosen one points.
player = self._get_invicible_one_player()
if player is not None:
# This shouldn't happen, but just in case.
if not player.is_alive():
babase.print_error('got dead player as chosen one in _tick')
self._set_invicible_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)
self._update_scoreboard()
# announce numbers we have sounds for
if scoring_team.time_remaining in self._countdownsounds:
self._countdownsounds[scoring_team.time_remaining].play()
# 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._invicible_one_player is not None:
babase.print_error('got nonexistent player as chosen one in _tick')
self._set_invicible_one_player(None)
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
results.set_team_score(team,
self._invicible_one_time - team.time_remaining)
self.end(results=results, announce_delay=0)
def _set_invicible_one_player(self, player: Optional[Player]) -> None:
existing = self._get_invicible_one_player()
if existing:
existing.chosen_light = None
self._swipsound.play()
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._invicible_one_player = None
# Create a light to highlight the flag;
# this will go away when the flag dies.
bs.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._invicible_one_player = player
if self._invicible_one_is_lazy:
player.actor.connect_controls_to_player(
enable_punch=False, enable_pickup=False, enable_bomb=False)
if player.actor.node.torso_mesh != None:
player.actor.node.color_mask_texture = None
player.actor.node.color_texture = None
player.actor.node.head_mesh = None
player.actor.node.torso_mesh = None
player.actor.node.upper_arm_mesh = None
player.actor.node.forearm_mesh = None
player.actor.node.pelvis_mesh = None
player.actor.node.toes_mesh = None
player.actor.node.upper_leg_mesh = None
player.actor.node.lower_leg_mesh = None
player.actor.node.hand_mesh = None
player.actor.node.style = 'cyborg'
invi_sound = []
player.actor.node.jump_sounds = invi_sound
player.actor.attack_sounds = invi_sound
player.actor.impact_sounds = invi_sound
player.actor.pickup_sounds = invi_sound
player.actor.death_sounds = invi_sound
player.actor.fall_sounds = invi_sound
player.actor.node.name = ''
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
if player is self._get_invicible_one_player():
killerplayer = msg.getkillerplayer(Player)
self._set_invicible_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._invicible_one_time,
countdown=True)

689
dist/ba_root/mods/games/laser_tracer.py vendored Normal file
View file

@ -0,0 +1,689 @@
# Released under the MIT License. See LICENSE for details.
# https://youtu.be/wTgwZKiykQw?si=Cr0ybDYAcKCUNFN4
# https://discord.gg/ucyaesh
# https://bombsquad-community.web.app/home
# by: Mr.Smoothy
"""Elimination mini-game."""
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.actor.spazfactory import SpazFactory
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import Any, Sequence, Optional, Union
import random
class Icon(bs.Actor):
"""Creates in in-game icon on screen."""
def __init__(self,
player: Player,
position: tuple[float, float],
scale: float,
show_lives: bool = True,
show_death: bool = True,
name_scale: float = 1.0,
name_maxwidth: float = 115.0,
flatness: float = 1.0,
shadow: float = 1.0):
super().__init__()
self._player = player
self._show_lives = show_lives
self._show_death = show_death
self._name_scale = name_scale
self._outline_tex = bs.gettexture('characterIconMask')
icon = player.get_icon()
self.node = bs.newnode('image',
delegate=self,
attrs={
'texture': icon['texture'],
'tint_texture': icon['tint_texture'],
'tint_color': icon['tint_color'],
'vr_depth': 400,
'tint2_color': icon['tint2_color'],
'mask_texture': self._outline_tex,
'opacity': 1.0,
'absolute_scale': True,
'attach': 'bottomCenter'
})
self._name_text = bs.newnode(
'text',
owner=self.node,
attrs={
'text': babase.Lstr(value=player.getname()),
'color': babase.safecolor(player.team.color),
'h_align': 'center',
'v_align': 'center',
'vr_depth': 410,
'maxwidth': name_maxwidth,
'shadow': shadow,
'flatness': flatness,
'h_attach': 'center',
'v_attach': 'bottom'
})
if self._show_lives:
self._lives_text = bs.newnode('text',
owner=self.node,
attrs={
'text': 'x0',
'color': (1, 1, 0.5),
'h_align': 'left',
'vr_depth': 430,
'shadow': 1.0,
'flatness': 1.0,
'h_attach': 'center',
'v_attach': 'bottom'
})
self.set_position_and_scale(position, scale)
def set_position_and_scale(self, position: tuple[float, float],
scale: float) -> None:
"""(Re)position the icon."""
assert self.node
self.node.position = position
self.node.scale = [70.0 * scale]
self._name_text.position = (position[0], position[1] + scale * 52.0)
self._name_text.scale = 1.0 * scale * self._name_scale
if self._show_lives:
self._lives_text.position = (position[0] + scale * 10.0,
position[1] - scale * 43.0)
self._lives_text.scale = 1.0 * scale
def update_for_lives(self) -> None:
"""Update for the target player's current lives."""
if self._player:
lives = self._player.lives
else:
lives = 0
if self._show_lives:
if lives > 0:
self._lives_text.text = 'x' + str(lives - 1)
else:
self._lives_text.text = ''
if lives == 0:
self._name_text.opacity = 0.2
assert self.node
self.node.color = (0.7, 0.3, 0.3)
self.node.opacity = 0.2
def handle_player_spawned(self) -> None:
"""Our player spawned; hooray!"""
if not self.node:
return
self.node.opacity = 1.0
self.update_for_lives()
def handle_player_died(self) -> None:
"""Well poo; our player died."""
if not self.node:
return
if self._show_death:
bs.animate(
self.node, 'opacity', {
0.00: 1.0,
0.05: 0.0,
0.10: 1.0,
0.15: 0.0,
0.20: 1.0,
0.25: 0.0,
0.30: 1.0,
0.35: 0.0,
0.40: 1.0,
0.45: 0.0,
0.50: 1.0,
0.55: 0.2
})
lives = self._player.lives
if lives == 0:
bs.timer(0.6, self.update_for_lives)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
self.node.delete()
return None
return super().handlemessage(msg)
class Player(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.lives = 0
self.icons: list[Icon] = []
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.survival_seconds: Optional[int] = None
self.spawn_order: list[Player] = []
# ba_meta export bascenev1.GameActivity
class LasorTracerGame(bs.TeamGameActivity[Player, Team]):
"""Game type where last player(s) left alive win."""
name = 'Laser Tracer'
description = 'Last remaining alive wins.'
scoreconfig = bs.ScoreConfig(label='Survived',
scoretype=bs.ScoreType.SECONDS,
none_is_winner=True)
# Show messages when players die since it's meaningful here.
announce_player_deaths = True
allow_mid_activity_joins = False
@classmethod
def get_available_settings(
cls, sessiontype: type[bs.Session]) -> list[babase.Setting]:
settings = [
bs.IntSetting(
'Lives Per Player',
default=1,
min_value=1,
max_value=10,
increment=1,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
bs.BoolSetting('Epic Mode', default=False),
]
if issubclass(sessiontype, bs.DualTeamSession):
settings.append(bs.BoolSetting('Solo Mode', default=False))
settings.append(
bs.BoolSetting('Balance Total Lives', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.DualTeamSession)
or issubclass(sessiontype, bs.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return ["Courtyard"]
def __init__(self, settings: dict):
super().__init__(settings)
shared = SharedObjects.get()
self._scoreboard = Scoreboard()
self._start_time: Optional[float] = None
self._vs_text: Optional[bs.Actor] = None
self._round_end_timer: Optional[bs.Timer] = None
self._epic_mode = bool(settings['Epic Mode'])
self._lives_per_player = 1
self._time_limit = float(settings['Time Limit'])
self._balance_total_lives = bool(
settings.get('Balance Total Lives', False))
self._solo_mode = bool(settings.get('Solo Mode', False))
# Base class overrides:
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC
if self._epic_mode else bs.MusicType.SURVIVAL)
self.laser_material = bs.Material()
self.laser_material.add_actions(
conditions=('they_have_material',
shared.player_material),
actions=(('modify_part_collision', 'collide', True),
('message', 'their_node', 'at_connect', bs.DieMessage()))
)
def get_instance_description(self) -> Union[str, Sequence]:
return 'Last team standing wins.' if isinstance(
self.session, bs.DualTeamSession) else 'Last one standing wins.'
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'last team standing wins' if isinstance(
self.session, bs.DualTeamSession) else 'last one standing wins'
def on_player_join(self, player: Player) -> None:
player.lives = self._lives_per_player
if self._solo_mode:
player.team.spawn_order.append(player)
self._update_solo_mode()
else:
# Create our icon and spawn.
# player.icons = [Icon(player, position=(0, 50), scale=0.8)]
if player.lives > 0:
self.spawn_player(player)
# Don't waste time doing this until begin.
if self.has_begun():
self._update_icons()
def on_begin(self) -> None:
super().on_begin()
self._start_time = bs.time()
self.setup_standard_time_limit(self._time_limit)
# self.setup_standard_powerup_drops()
self.add_wall()
self.create_laser()
if self._solo_mode:
self._vs_text = bs.NodeActor(
bs.newnode('text',
attrs={
'position': (0, 105),
'h_attach': 'center',
'h_align': 'center',
'maxwidth': 200,
'shadow': 0.5,
'vr_depth': 390,
'scale': 0.6,
'v_attach': 'bottom',
'color': (0.8, 0.8, 0.3, 1.0),
'text': babase.Lstr(resource='vsText')
}))
# If balance-team-lives is on, add lives to the smaller team until
# total lives match.
if (isinstance(self.session, bs.DualTeamSession)
and self._balance_total_lives and self.teams[0].players
and self.teams[1].players):
if self._get_total_team_lives(
self.teams[0]) < self._get_total_team_lives(self.teams[1]):
lesser_team = self.teams[0]
greater_team = self.teams[1]
else:
lesser_team = self.teams[1]
greater_team = self.teams[0]
add_index = 0
while (self._get_total_team_lives(lesser_team) <
self._get_total_team_lives(greater_team)):
lesser_team.players[add_index].lives += 1
add_index = (add_index + 1) % len(lesser_team.players)
self._update_icons()
# We could check game-over conditions at explicit trigger points,
# but lets just do the simple thing and poll it.
bs.timer(1.0, self._update, repeat=True)
def _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:
return
# lets do nothing ;Eat 5 Star
# pylint: disable=too-many-branches
# In free-for-all mode, everyone is just lined up along the bottom.
if isinstance(self.session, bs.FreeForAllSession):
count = len(self.teams)
x_offs = 85
xval = x_offs * (count - 1) * -0.5
for team in self.teams:
if len(team.players) == 1:
player = team.players[0]
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
# In teams mode we split up teams.
else:
if self._solo_mode:
# First off, clear out all icons.
for player in self.players:
player.icons = []
# Now for each team, cycle through our available players
# adding icons.
for team in self.teams:
if team.id == 0:
xval = -60
x_offs = -78
else:
xval = 60
x_offs = 78
is_first = True
test_lives = 1
while True:
players_with_lives = [
p for p in team.spawn_order
if p and p.lives >= test_lives
]
if not players_with_lives:
break
for player in players_with_lives:
player.icons.append(
Icon(player,
position=(xval, (40 if is_first else 25)),
scale=1.0 if is_first else 0.5,
name_maxwidth=130 if is_first else 75,
name_scale=0.8 if is_first else 1.0,
flatness=0.0 if is_first else 1.0,
shadow=0.5 if is_first else 1.0,
show_death=is_first,
show_lives=False))
xval += x_offs * (0.8 if is_first else 0.56)
is_first = False
test_lives += 1
# Non-solo mode.
else:
for team in self.teams:
if team.id == 0:
xval = -50
x_offs = -85
else:
xval = 50
x_offs = 85
for player in team.players:
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
def _get_spawn_point(self, player: Player) -> Optional[babase.Vec3]:
del player # Unused.
# In solo-mode, if there's an existing live player on the map, spawn at
# whichever spot is farthest from them (keeps the action spread out).
if self._solo_mode:
living_player = None
living_player_pos = None
for team in self.teams:
for tplayer in team.players:
if tplayer.is_alive():
assert tplayer.node
ppos = tplayer.node.position
living_player = tplayer
living_player_pos = ppos
break
if living_player:
assert living_player_pos is not None
player_pos = babase.Vec3(living_player_pos)
points: list[tuple[float, babase.Vec3]] = []
for team in self.teams:
start_pos = babase.Vec3(
self.map.get_start_position(team.id))
points.append(
((start_pos - player_pos).length(), start_pos))
# Hmm.. we need to sorting vectors too?
points.sort(key=lambda x: x[0])
return points[-1][1]
return None
def spawn_player(self, player: Player) -> bs.Actor:
actor = self.spawn_player_spaz(player, self._get_spawn_point(player))
actor.connect_controls_to_player(enable_punch=False,
enable_bomb=False,
enable_pickup=False)
if not self._solo_mode:
bs.timer(0.3, babase.Call(self._print_lives, 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 bascenev1lib.actor import popuptext
# We get called in a timer so it's possible our player has left/etc.
if not player or not player.is_alive() or not player.node:
return
popuptext.PopupText('x' + str(player.lives - 1),
color=(1, 1, 0, 1),
offset=(0, -0.8, 0),
random_offset=0.0,
scale=1.8,
position=player.node.position).autoretain()
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
player.icons = []
# Remove us from spawn-order.
if self._solo_mode:
if player in player.team.spawn_order:
player.team.spawn_order.remove(player)
# Update icons in a moment since our team will be gone from the
# list then.
bs.timer(0, self._update_icons)
# If the player to leave was the last in spawn order and had
# their final turn currently in-progress, mark the survival time
# for their team.
if self._get_total_team_lives(player.team) == 0:
assert self._start_time is not None
player.team.survival_seconds = int(bs.time() - self._start_time)
def _get_total_team_lives(self, team: Team) -> int:
return sum(player.lives for player in team.players)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player: Player = msg.getplayer(Player)
player.lives -= 1
if player.lives < 0:
babase.print_error(
"Got lives < 0 in Elim; this shouldn't happen. solo:" +
str(self._solo_mode))
player.lives = 0
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_died()
# Play big death sound on our last death
# or for every one in solo mode.
if self._solo_mode or player.lives == 0:
SpazFactory.get().single_player_death_sound.play()
# If we hit zero lives, we're dead (and our team might be too).
if player.lives == 0:
# If the whole team is now dead, mark their survival time.
if self._get_total_team_lives(player.team) == 0:
assert self._start_time is not None
player.team.survival_seconds = int(bs.time() -
self._start_time)
else:
# Otherwise, in regular mode, respawn.
if not self._solo_mode:
self.respawn_player(player)
# In solo, put ourself at the back of the spawn order.
if self._solo_mode:
player.team.spawn_order.remove(player)
player.team.spawn_order.append(player)
def _update(self) -> None:
if self._solo_mode:
# For both teams, find the first player on the spawn order
# list with lives remaining and spawn them if they're not alive.
for team in self.teams:
# Prune dead players from the spawn order.
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
self._update_icons()
break
# If we're down to 1 or fewer living teams, start a timer to end
# the game (allows the dust to settle and draws to occur if deaths
# are close enough).
if len(self._get_living_teams()) < 2:
self._round_end_timer = bs.Timer(0.5, self.end_game)
def _get_living_teams(self) -> list[Team]:
return [
team for team in self.teams
if len(team.players) > 0 and any(player.lives > 0
for player in team.players)
]
def end_game(self) -> None:
if self.has_ended():
return
results = bs.GameResults()
self._vs_text = None # Kill our 'vs' if its there.
for team in self.teams:
results.set_team_score(team, team.survival_seconds)
self.end(results=results)
def add_wall(self):
# FIXME: Chop this into vr and non-vr chunks.
shared = SharedObjects.get()
pwm = bs.Material()
cwwm = bs.Material()
pwm.add_actions(
actions=('modify_part_collision', 'friction', 0.0))
# anything that needs to hit the wall should apply this.
pwm.add_actions(
conditions=('they_have_material',
shared.player_material),
actions=('modify_part_collision', 'collide', True))
cmesh = bs.getcollisionmesh('courtyardPlayerWall')
self.player_wall = bs.newnode(
'terrain',
attrs={
'collision_mesh': cmesh,
'affect_bg_dynamics': False,
'materials': [pwm]
})
def create_laser(self) -> None:
bs.timer(6, babase.Call(self.LRlaser, True))
bs.timer(7, babase.Call(self.UDlaser, True))
bs.timer(30, babase.Call(self.create_laser))
def LRlaser(self, left):
ud_1_r = bs.newnode('region', attrs={'position': (-5, 2.6, 0), 'scale': (
0.1, 0.6, 15), 'type': 'box', 'materials': [self.laser_material]})
shields = []
x = -6
for i in range(0, 30):
x = x+0.4
node = bs.newnode('shield', owner=ud_1_r, attrs={
'color': (1, 0, 0), 'radius': 0.28})
mnode = bs.newnode('math',
owner=ud_1_r,
attrs={
'input1': (0, 0.0, x),
'operation': 'add'
})
ud_1_r.connectattr('position', mnode, 'input2')
mnode.connectattr('output', node, 'position')
_rcombine = bs.newnode('combine',
owner=ud_1_r,
attrs={
'input1': 2.6,
'input2': -2,
'size': 3
})
if left:
x1 = -10
x2 = 10
else:
x1 = 10
x2 = -10
bs.animate(_rcombine, 'input0', {
0: x1,
20: x2
})
_rcombine.connectattr('output', ud_1_r, 'position')
bs.timer(20, babase.Call(ud_1_r.delete))
t = random.randrange(7, 13)
bs.timer(t, babase.Call(self.LRlaser, random.randrange(0, 2)))
def UDlaser(self, up):
ud_2_r = bs.newnode('region', attrs={'position': (-3, 2.6, -6), 'scale': (
20, 0.6, 0.1), 'type': 'box', 'materials': [self.laser_material]})
shields = []
x = -6
for i in range(0, 40):
x = x+0.4
node = bs.newnode('shield', owner=ud_2_r, attrs={
'color': (1, 0, 0), 'radius': 0.28})
mnode = bs.newnode('math',
owner=ud_2_r,
attrs={
'input1': (x, 0.0, 0),
'operation': 'add'
})
ud_2_r.connectattr('position', mnode, 'input2')
mnode.connectattr('output', node, 'position')
_rcombine = bs.newnode('combine',
owner=ud_2_r,
attrs={
'input0': -2,
'input1': 2.6,
'size': 3
})
if up:
x1 = -9
x2 = 6
else:
x1 = 6
x2 = -9
bs.animate(_rcombine, 'input2', {
0: x1,
17: x2
})
_rcombine.connectattr('output', ud_2_r, 'position')
bs.timer(17, babase.Call(ud_2_r.delete))
t = random.randrange(6, 13)
bs.timer(t, babase.Call(self.UDlaser, random.randrange(0, 2)))

View file

@ -1,416 +1,275 @@
# ba_meta require api 8
from typing import Sequence
import random
import bascenev1, babase, baclassic, baplus, bauiv1
from bascenev1lib.actor.spaz import Spaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
class Player(bascenev1.Player['Team']):
"""Our player type for this game."""
class Team(bascenev1.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
super().__init__()
self.score = 1
class ChoosingThingHitMessage:
def __init__(self, hitter:Player) -> None:
self.hitter = hitter
class ChoosingThingDieMessage:
def __init__(self, how:bascenev1.DeathType) -> None:
self.how = how
class ChoosingThing():
def __init__(self, pos, color) -> None:
pass
def recolor(self, color:list[int | float], highlight:list[int, float] = (1,1,1)):
raise NotImplementedError()
def is_dead(self):
raise NotImplementedError()
def _is_dead(self):
return self.is_dead()
def create_locator(self, node:bascenev1.Node, pos, color):
loc = bascenev1.newnode(
'locator',
attrs={
'shape': 'circleOutline',
'position': pos,
'color': color,
'opacity': 1,
'draw_beauty': False,
'additive': True,
},
)
node.connectattr("position", loc, "position")
bascenev1.animate_array(loc, "size", 1, keys={0:[0.5,], 1:[2,], 1.5:[0.5]}, loop=True)
return loc
dead = property(_is_dead)
class ChoosingSpaz(Spaz, ChoosingThing):
def __init__(
self,
pos:Sequence[float],
color: Sequence[float] = (1.0, 1.0, 1.0),
highlight: Sequence[float] = (0.5, 0.5, 0.5),
):
super().__init__(color, highlight, "Spaz", None, True, True, False, False)
self.stand(pos)
self.loc = self.create_locator(self.node, pos, color)
def handlemessage(self, msg):
if isinstance(msg, bascenev1.FreezeMessage):
return
if isinstance(msg, bascenev1.PowerupMessage):
if not(msg.poweruptype == "health"):
return
super().handlemessage(msg)
if isinstance(msg, bascenev1.HitMessage):
self.handlemessage(bascenev1.PowerupMessage("health"))
player = msg.get_source_player(Player)
if self.is_alive():
self.activity.handlemessage(ChoosingThingHitMessage(player))
elif isinstance(msg, bascenev1.DieMessage):
self._dead = True
self.activity.handlemessage(ChoosingThingDieMessage(msg.how))
self.loc.delete()
def stand(self, pos = (0,0,0), angle = 0):
self.handlemessage(bascenev1.StandMessage(pos,angle))
def recolor(self, color, highlight = (1,1,1)):
self.node.color = color
self.node.highlight = highlight
self.loc.color = color
def is_dead(self):
return self._dead
class ChoosingBall(bascenev1.Actor, ChoosingThing):
def __init__(self, pos, color = (0.5,0.5,0.5)) -> None:
super().__init__()
shared = SharedObjects.get()
pos = (pos[0], pos[1] + 2, pos[2])
# We want the puck to kill powerups; not get stopped by them
self.puck_material = bascenev1.Material()
self.puck_material.add_actions(
conditions=('they_have_material',
PowerupBoxFactory.get().powerup_material),
actions=(('modify_part_collision', 'physical', False),
('message', 'their_node', 'at_connect', bascenev1.DieMessage())))
#fontSmall0 jumpsuitColor menuBG operaSingerColor rgbStripes zoeColor
self.node = bascenev1.newnode('prop',
delegate=self,
attrs={
'mesh': bascenev1.getmesh('frostyPelvis'),
'color_texture':
bascenev1.gettexture('gameCenterIcon'),
'body': 'sphere',
'reflection': 'soft',
'reflection_scale': [0.2],
'shadow_size': 0.5,
'is_area_of_interest': True,
'position': pos,
"materials": [shared.object_material, self.puck_material]
})
#seince this ball allways jumps a specefic direction when it spawned, we just jump it randomly
self.node.handlemessage(
'impulse',
random.uniform(-10, 10),
random.uniform(-10, 10),
random.uniform(-10, 10),
random.uniform(-10, 10),
random.uniform(-10, 10),
random.uniform(-10, 10),
random.uniform(-10, 10),
random.uniform(-10, 10),
0,
random.uniform(-10, 10),
random.uniform(-10, 10),
random.uniform(-10, 10),
random.uniform(-10, 10),
)
self.loc = self.create_locator(self.node, pos, color)
self._died = False
def handlemessage(self, msg):
if isinstance(msg, bascenev1.HitMessage):
player = msg.get_source_player(Player)
self.activity.handlemessage(ChoosingThingHitMessage(player))
mag = msg.magnitude
velocity_mag = msg.velocity_magnitude
self.node.handlemessage(
'impulse',
msg.pos[0],
msg.pos[1],
msg.pos[2],
msg.velocity[0],
msg.velocity[1],
msg.velocity[2],
mag,
velocity_mag,
msg.radius,
1,
msg.force_direction[0],
msg.force_direction[1],
msg.force_direction[2],
)
elif isinstance(msg, bascenev1.DieMessage):
if self.node.exists():
self.node.delete()
self.loc.delete()
self._died = True
self.activity.handlemessage(ChoosingThingDieMessage(msg.how))
return super().handlemessage(msg)
def exists(self) -> bool:
return not self.dead
def is_alive(self) -> bool:
return not self.dead
def recolor(self, color: list[int | float], highlight: list[int] = (1, 1, 1)):
self.loc.color = color
def is_dead(self):
return self._died
class ChooseBilbord(bascenev1.Actor):
def __init__(self, player:Player, delay = 0.1) -> None:
super().__init__()
icon = player.get_icon()
self.scale = 100
self.node = bascenev1.newnode(
'image',
delegate=self,
attrs={
"position":(60,-125),
'texture': icon['texture'],
'tint_texture': icon['tint_texture'],
'tint_color': icon['tint_color'],
'tint2_color': icon['tint2_color'],
'opacity': 1.0,
'absolute_scale': True,
'attach': "topLeft"
},
)
self.name_node = bascenev1.newnode(
'text',
owner=self.node,
attrs={
'position': (60,-185),
'text': bascenev1.Lstr(value=player.getname()),
'color': bascenev1.safecolor(player.team.color),
'h_align': 'center',
'v_align': 'center',
'vr_depth': 410,
'flatness': 1.0,
'h_attach': 'left',
'v_attach': 'top',
'maxwidth':self.scale
},
)
bascenev1.animate_array(self.node, "scale", keys={0 + delay:[0,0], 0.05 + delay:[self.scale, self.scale]}, size=1)
bascenev1.animate(self.name_node, "scale", {0 + delay:0, 0.07 + delay:1})
def handlemessage(self, msg):
super().handlemessage(msg)
if isinstance(msg, bascenev1.DieMessage):
bascenev1.animate_array(self.node, "scale", keys={0:self.node.scale, 0.05:[0,0]}, size=1)
bascenev1.animate(self.name_node, "scale", {0:self.name_node.scale, 0.07:0})
def __delete():
self.node.delete()
self.name_node.delete()
bascenev1.timer(0.2, __delete)
# ba_meta export bascenev1.GameActivity
class LastPunchStand(bascenev1.TeamGameActivity[Player, Team]):
name = "Last Punch Stand"
description = "Last one punchs the choosing thing wins"
tips = [
'keep punching the choosing thing to be last punched player at times up!',
'you can not frezz the choosing spaz',
"evry time you punch the choosing thing, you will get one point",
]
default_music = bascenev1.MusicType.TO_THE_DEATH
def get_instance_display_string(self) -> bascenev1.Lstr:
name = self.name
if self.settings_raw["Ball Mode"]:
name += " Ball Mode"
return name
def get_instance_description_short(self) -> str | Sequence:
if self.settings_raw["Ball Mode"]:
return "Punch the Ball"
else:
return "Punch the Spaz"
available_settings = [
bascenev1.BoolSetting("Ball Mode", False),
bascenev1.FloatSetting("min time limit (in seconds)", 50.0, min_value=30.0),
bascenev1.FloatSetting("max time limit (in seconds)", 160.0, 60),
]
def __init__(self, settings: dict):
super().__init__(settings)
self._min_timelimit = settings["min time limit (in seconds)"]
self._max_timelimit = settings["max time limit (in seconds)"]
self.ball_mod:bool = settings["Ball Mode"]
if (self._min_timelimit > self._max_timelimit):
self._max_timelimit = self._min_timelimit
self._choosing_thing_defcolor = (0.5,0.5,0.5)
self.choosing_thing:ChoosingThing = None
self.choosed_player = None
self.times_uped = False
self.scoreboard = Scoreboard()
@classmethod
def get_supported_maps(cls, sessiontype: type[bascenev1.Session]) -> list[str]:
assert bascenev1.app.classic is not None
return bascenev1.app.classic.getmaps('team_flag')
def times_up(self):
self.times_uped = True
self.end_game()
for player in self.players:
try:
if self.choosed_player and player and (player.team.id != self.choosed_player.team.id):
player.actor._cursed = True
player.actor.curse_explode()
except AttributeError:
pass
def __get_thing_spawn_point(self):
if len(self.map.flag_points_default) > 0:
return self.map.get_flag_position(None)
elif len(self.map.tnt_points) > 0:
return self.map.tnt_points[random.randint(0, len(self.map.tnt_points)-1)]
else:
return (0, 6, 0)
def spaw_bot(self):
"spawns a choosing bot"
if self.ball_mod:
self.choosing_thing = ChoosingBall(self.__get_thing_spawn_point())
else:
self.choosing_thing = ChoosingSpaz(self.__get_thing_spawn_point())
self.choose_bilbord = None
def on_begin(self) -> None:
super().on_begin()
time_limit = random.randint(self._min_timelimit, self._max_timelimit)
self.spaw_bot()
bascenev1.timer(time_limit, self.times_up)
self.setup_standard_powerup_drops(False)
def end_game(self) -> None:
results = bascenev1.GameResults()
total = 0
for team in self.teams:
total = team.score
for team in self.teams:
if self.choosed_player and (team.id == self.choosed_player.team.id): team.score += total
results.set_team_score(team, team.score)
self.end(results=results)
def change_choosed_player(self, hitter:Player):
if hitter == self.choosed_player:
return
if hitter:
self.choosing_thing.recolor(hitter.color, hitter.highlight)
self.choosed_player = hitter
hitter.team.score += 1
self.choose_bilbord = ChooseBilbord(hitter)
self.hide_score_board()
else:
self.choosing_thing.recolor(self._choosing_thing_defcolor)
self.choosed_player = None
self.choose_bilbord = None
self.show_score_board()
def show_score_board(self):
self.scoreboard = Scoreboard()
for team in self.teams:
self.scoreboard.set_team_value(team, team.score)
def hide_score_board(self):
self.scoreboard = None
def _watch_dog_(self):
"checks if choosing spaz exists"
#choosing spaz wont respawn if death type if generic
#this becuse we dont want to keep respawn him when he dies because of losing referce
#but sometimes "choosing spaz" dies naturaly and his death type is generic! so it wont respawn back again
#thats why we have this function; to check if spaz exits in the case that he didnt respawned
if self.choosing_thing:
if self.choosing_thing.dead:
self.spaw_bot()
else:
self.spaw_bot()
def handlemessage(self, msg):
super().handlemessage(msg)
if isinstance(msg, ChoosingThingHitMessage):
hitter = msg.hitter
if hitter:
self.change_choosed_player(hitter)
elif isinstance(msg, ChoosingThingDieMessage):
if msg.how.value != bascenev1.DeathType.GENERIC.value:
self.spaw_bot()
self.change_choosed_player(None)
elif isinstance(msg, bascenev1.PlayerDiedMessage):
player = msg.getplayer(Player)
if not (self.has_ended() or self.times_uped):
self.respawn_player(player, 0)
if self.choosed_player and (player.getname(True) == self.choosed_player.getname(True)):
self.change_choosed_player(None)
self._watch_dog_()
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# ba_meta require api 8
from typing import Sequence
import babase
import bauiv1 as bui
import bascenev1 as bs
import _babase
import random
from bascenev1lib.actor.spaz import Spaz
from bascenev1lib.actor.scoreboard import Scoreboard
class Player(bs.Player['Team']):
"""Our player type for this game."""
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
super().__init__()
self.score = 1
class ChooseingSpazHitMessage:
def __init__(self, hitter: Player) -> None:
self.hitter = hitter
class ChooseingSpazDieMessage:
def __init__(self, killer: Player) -> None:
self.killer = killer
class ChooseingSpaz(Spaz):
def __init__(
self,
pos: Sequence[float],
color: Sequence[float] = (1.0, 1.0, 1.0),
highlight: Sequence[float] = (0.5, 0.5, 0.5),
):
super().__init__(color, highlight, "Spaz", None, True, True, False, False)
self.last_player_attacked_by = None
self.stand(pos)
self.loc = bs.newnode(
'locator',
attrs={
'shape': 'circleOutline',
'position': pos,
'color': color,
'opacity': 1,
'draw_beauty': False,
'additive': True,
},
)
self.node.connectattr("position", self.loc, "position")
bs.animate_array(self.loc, "size", 1, keys={0: [0.5,], 1: [2,], 1.5: [0.5]}, loop=True)
def handlemessage(self, msg):
if isinstance(msg, bs.FreezeMessage):
return
if isinstance(msg, bs.PowerupMessage):
if not (msg.poweruptype == "health"):
return
super().handlemessage(msg)
if isinstance(msg, bs.HitMessage):
self.handlemessage(bs.PowerupMessage("health"))
player = msg.get_source_player(Player)
if self.is_alive():
self.activity.handlemessage(ChooseingSpazHitMessage(player))
self.last_player_attacked_by = player
elif isinstance(msg, bs.DieMessage):
player = self.last_player_attacked_by
if msg.how.value != bs.DeathType.GENERIC.value:
self._dead = True
self.activity.handlemessage(ChooseingSpazDieMessage(player))
self.loc.delete()
def stand(self, pos=(0, 0, 0), angle=0):
self.handlemessage(bs.StandMessage(pos, angle))
def recolor(self, color, highlight=(1, 1, 1)):
self.node.color = color
self.node.highlight = highlight
self.loc.color = color
class ChooseBilbord(bs.Actor):
def __init__(self, player: Player, delay=0.1) -> None:
super().__init__()
icon = player.get_icon()
self.scale = 100
self.node = bs.newnode(
'image',
delegate=self,
attrs={
"position": (60, -125),
'texture': icon['texture'],
'tint_texture': icon['tint_texture'],
'tint_color': icon['tint_color'],
'tint2_color': icon['tint2_color'],
'opacity': 1.0,
'absolute_scale': True,
'attach': "topLeft"
},
)
self.name_node = bs.newnode(
'text',
owner=self.node,
attrs={
'position': (60, -185),
'text': babase.Lstr(value=player.getname()),
'color': babase.safecolor(player.team.color),
'h_align': 'center',
'v_align': 'center',
'vr_depth': 410,
'flatness': 1.0,
'h_attach': 'left',
'v_attach': 'top',
'maxwidth': self.scale
},
)
bs.animate_array(self.node, "scale", keys={
0 + delay: [0, 0], 0.05 + delay: [self.scale, self.scale]}, size=1)
bs.animate(self.name_node, "scale", {0 + delay: 0, 0.07 + delay: 1})
def handlemessage(self, msg):
super().handlemessage(msg)
if isinstance(msg, bs.DieMessage):
bs.animate_array(self.node, "scale", keys={0: self.node.scale, 0.05: [0, 0]}, size=1)
bs.animate(self.name_node, "scale", {0: self.name_node.scale, 0.07: 0})
def __delete():
self.node.delete()
self.name_node.delete()
bs.timer(0.2, __delete)
# ba_meta export bascenev1.GameActivity
class LastPunchStand(bs.TeamGameActivity[Player, Team]):
name = "Last Punch Stand"
description = "Last one punchs the choosing spaz wins"
tips = [
'keep punching the choosing spaz to be last punched player at times up!',
'you can not frezz the choosing spaz',
"evry time you punch the choosing spaz, you will get one point",
]
default_music = bs.MusicType.TO_THE_DEATH
available_settings = [
bs.FloatSetting("min time limit (in seconds)", 50.0, min_value=30.0),
bs.FloatSetting("max time limit (in seconds)", 160.0, 60),
]
def __init__(self, settings: dict):
super().__init__(settings)
self._min_timelimit = settings["min time limit (in seconds)"]
self._max_timelimit = settings["max time limit (in seconds)"]
if (self._min_timelimit > self._max_timelimit):
self._max_timelimit = self._min_timelimit
self._choosing_spaz_defcolor = (0.5, 0.5, 0.5)
self.choosing_spaz = None
self.choosed_player = None
self.times_uped = False
self.scoreboard = Scoreboard()
def times_up(self):
self.times_uped = True
for player in self.players:
if self.choosed_player and (player.team.id != self.choosed_player.team.id):
player.actor._cursed = True
player.actor.curse_explode()
self.end_game()
def __get_spaz_bot_spawn_point(self):
if len(self.map.tnt_points) > 0:
return self.map.tnt_points[random.randint(0, len(self.map.tnt_points)-1)]
else:
return (0, 6, 0)
def spaw_bot(self):
"spawns a choosing bot"
self.choosing_spaz = ChooseingSpaz(self.__get_spaz_bot_spawn_point())
self.choose_bilbord = None
def on_begin(self) -> None:
super().on_begin()
time_limit = random.randint(self._min_timelimit, self._max_timelimit)
self.spaw_bot()
bs.timer(time_limit, self.times_up)
self.setup_standard_powerup_drops(False)
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
if self.choosed_player and (team.id == self.choosed_player.team.id):
team.score += 100
results.set_team_score(team, team.score)
self.end(results=results)
def change_choosed_player(self, hitter: Player):
if hitter:
self.choosing_spaz.recolor(hitter.color, hitter.highlight)
self.choosed_player = hitter
hitter.team.score += 1
self.choose_bilbord = ChooseBilbord(hitter)
self.hide_score_board()
else:
self.choosing_spaz.recolor(self._choosing_spaz_defcolor)
self.choosed_player = None
self.choose_bilbord = None
self.show_score_board()
def show_score_board(self):
self.scoreboard = Scoreboard()
for team in self.teams:
self.scoreboard.set_team_value(team, team.score)
def hide_score_board(self):
self.scoreboard = None
def _watch_dog_(self):
"checks if choosing spaz exists"
# choosing spaz wont respawn if death type if generic
# this becuse we dont want to keep respawn him when he dies because of losing referce
# but sometimes "choosing spaz" dies naturaly and his death type is generic! so it wont respawn back again
# thats why we have this function; to check if spaz exits in the case that he didnt respawned
if self.choosing_spaz:
if self.choosing_spaz._dead:
self.spaw_bot()
else:
self.spaw_bot()
def handlemessage(self, msg):
super().handlemessage(msg)
if isinstance(msg, ChooseingSpazHitMessage):
hitter = msg.hitter
if self.choosing_spaz.node and hitter:
self.change_choosed_player(hitter)
elif isinstance(msg, ChooseingSpazDieMessage):
self.spaw_bot()
self.change_choosed_player(None)
elif isinstance(msg, bs.PlayerDiedMessage):
player = msg.getplayer(Player)
if not (self.has_ended() or self.times_uped):
self.respawn_player(player, 0)
if self.choosed_player and (player.getname(True) == self.choosed_player.getname(True)):
self.change_choosed_player(None)
self._watch_dog_()

View file

@ -0,0 +1,67 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# ba_meta require api 8
"""
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <[1](https://fsf.org/)>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This license is designed to ensure cooperation with the community in the case of network server software. It is a free, copyleft license for software and other kinds of works. The license guarantees your freedom to share and change all versions of a program, to make sure it remains free software for all its users.
The license identifier refers to the choice to use code under AGPL-3.0-or-later (i.e., AGPL-3.0 or some later version), as distinguished from use of code under AGPL-3.0-only. The license notice states which of these applies the code in the file.
"""
import random
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.game.meteorshower import MeteorShowerGame
from bascenev1lib.actor.bomb import Bomb
class NewMeteorShowerGame(MeteorShowerGame):
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return bs.app.classic.getmaps("melee")
def _drop_bomb_cluster(self) -> None:
# Drop several bombs in series.
delay = 0.0
bounds = list(self._map.get_def_bound_box("map_bounds"))
for _i in range(random.randrange(1, 3)):
# Drop them somewhere within our bounds with velocity pointing
# toward the opposite side.
pos = (
random.uniform(bounds[0], bounds[3]),
bounds[4],
random.uniform(bounds[2], bounds[5]),
)
dropdirx = -1 if pos[0] > 0 else 1
dropdirz = -1 if pos[2] > 0 else 1
forcex = (
bounds[0] - bounds[3]
if bounds[0] - bounds[3] > 0
else -(bounds[0] - bounds[3])
)
forcez = (
bounds[2] - bounds[5]
if bounds[2] - bounds[5] > 0
else -(bounds[2] - bounds[5])
)
vel = (
(-5 + random.random() * forcex) * dropdirx,
random.uniform(-3.066, -4.12),
(-5 + random.random() * forcez) * dropdirz,
)
bs.timer(delay, babase.Call(self._drop_bomb, pos, vel))
delay += 0.1
self._set_meteor_timer()
# ba_meta export plugin
class byEra0S(babase.Plugin):
MeteorShowerGame.get_supported_maps = NewMeteorShowerGame.get_supported_maps
MeteorShowerGame._drop_bomb_cluster = NewMeteorShowerGame._drop_bomb_cluster

411
dist/ba_root/mods/games/shimla.py vendored Normal file
View file

@ -0,0 +1,411 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# Released under the MIT License. See LICENSE for details.
#
"""DeathMatch game and support classes."""
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.game.deathmatch import DeathMatchGame, Player
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import Any, Sequence, Dict, Type, List, Optional, Union
# ba_meta export bascenev1.GameActivity
class ShimlaGame(DeathMatchGame):
name = 'Shimla'
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return ['Creative Thoughts']
def __init__(self, settings: dict):
super().__init__(settings)
shared = SharedObjects.get()
self.lifts = {}
self._real_wall_material = bs.Material()
self._real_wall_material.add_actions(
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', True)
))
self._real_wall_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', True)
))
self._lift_material = bs.Material()
self._lift_material.add_actions(
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', True)
))
self._lift_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('call', 'at_connect', self._handle_lift),),
)
self._lift_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('call', 'at_disconnect', self._handle_lift_disconnect),),
)
def on_begin(self):
bs.getactivity().globalsnode.happy_thoughts_mode = False
super().on_begin()
self.make_map()
bs.timer(2, self.disable_fly)
def disable_fly(self):
activity = bs.get_foreground_host_activity()
for players in activity.players:
players.actor.node.fly = False
def spawn_player_spaz(
self,
player: Player,
position: Sequence[float] | None = None,
angle: float | None = None,
) -> PlayerSpaz:
"""Intercept new spazzes and add our team material for them."""
spaz = super().spawn_player_spaz(player, position, angle)
spaz.connect_controls_to_player(enable_punch=True,
enable_bomb=True,
enable_pickup=True,
enable_fly=False,
enable_jump=True)
spaz.fly = False
return spaz
def make_map(self):
shared = SharedObjects.get()
bs.get_foreground_host_activity()._map.leftwall.materials = [
shared.footing_material, self._real_wall_material]
bs.get_foreground_host_activity()._map.rightwall.materials = [
shared.footing_material, self._real_wall_material]
bs.get_foreground_host_activity()._map.topwall.materials = [
shared.footing_material, self._real_wall_material]
self.floorwall1 = bs.newnode('region', attrs={'position': (-10, 5, -5.52), 'scale':
(15, 0.2, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]})
self.floorwall2 = bs.newnode('region', attrs={'position': (10, 5, -5.52), 'scale': (
15, 0.2, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]})
self.wall1 = bs.newnode('region', attrs={'position': (0, 11, -6.90), 'scale': (
35.4, 20, 1), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]})
self.wall2 = bs.newnode('region', attrs={'position': (0, 11, -4.14), 'scale': (
35.4, 20, 1), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]})
bs.newnode('locator', attrs={'shape': 'box', 'position': (-10, 5, -5.52), 'color': (
0, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (15, 0.2, 2)})
bs.newnode('locator', attrs={'shape': 'box', 'position': (10, 5, -5.52), 'color': (
0, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (15, 0.2, 2)})
self.create_lift(-16.65, 8)
self.create_lift(16.65, 8)
self.create_static_step(0, 18.29)
self.create_static_step(0, 7)
self.create_static_step(13, 17)
self.create_static_step(-13, 17)
self.create_slope(8, 15, True)
self.create_slope(-8, 15, False)
self.create_static_step(5, 15)
self.create_static_step(-5, 15)
self.create_static_step(13, 12)
self.create_static_step(-13, 12)
self.create_slope(8, 10, True)
self.create_slope(-8, 10, False)
self.create_static_step(5, 10)
self.create_static_step(-5, 10)
def create_static_step(self, x, y):
shared = SharedObjects.get()
bs.newnode('region', attrs={'position': (x, y, -5.52), 'scale': (5.5, 0.1, 6),
'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]})
bs.newnode('locator', attrs={'shape': 'box', 'position': (x, y, -5.52), 'color': (
1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (5.5, 0.1, 2)})
def create_lift(self, x, y):
shared = SharedObjects.get()
color = (0.7, 0.6, 0.5)
floor = bs.newnode('region', attrs={'position': (x, y, -5.52), 'scale': (
1.8, 0.1, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material, self._lift_material]})
cleaner = bs.newnode('region', attrs={'position': (x, y, -5.52), 'scale': (
2, 0.3, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]})
lift = bs.newnode('locator', attrs={'shape': 'box', 'position': (
x, y, -5.52), 'color': color, 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (1.8, 3.7, 2)})
_tcombine = bs.newnode('combine',
owner=floor,
attrs={
'input0': x,
'input2': -5.5,
'size': 3
})
mnode = bs.newnode('math',
owner=lift,
attrs={
'input1': (0, 2, 0),
'operation': 'add'
})
_tcombine.connectattr('output', mnode, 'input2')
_cleaner_combine = bs.newnode('combine',
owner=cleaner,
attrs={
'input1': 5.6,
'input2': -5.5,
'size': 3
})
_cleaner_combine.connectattr('output', cleaner, 'position')
bs.animate(_tcombine, 'input1', {
0: 5.1,
})
bs.animate(_cleaner_combine, 'input0', {
0: -19 if x < 0 else 19,
})
_tcombine.connectattr('output', floor, 'position')
mnode.connectattr('output', lift, 'position')
self.lifts[floor] = {"state": "origin", "lift": _tcombine,
"cleaner": _cleaner_combine, 'leftLift': x < 0}
def _handle_lift(self):
region = bs.getcollision().sourcenode
lift = self.lifts[region]
def clean(lift):
bs.animate(lift["cleaner"], 'input0', {
0: -19 if lift["leftLift"] else 19,
2: -16 if lift["leftLift"] else 16,
4.3: -19 if lift["leftLift"] else 19
})
if lift["state"] == "origin":
lift["state"] = "transition"
bs.animate(lift["lift"], 'input1', {
0: 5.1,
1.3: 5.1,
6: 5+12,
9: 5+12,
15: 5.1
})
bs.timer(16, babase.Call(lambda lift: lift.update({'state': 'end'}), lift))
bs.timer(12, babase.Call(clean, lift))
def _handle_lift_disconnect(self):
region = bs.getcollision().sourcenode
lift = self.lifts[region]
if lift["state"] == 'end':
lift["state"] = "origin"
def create_slope(self, x, y, backslash):
shared = SharedObjects.get()
for i in range(0, 21):
bs.newnode('region', attrs={'position': (x, y, -5.52), 'scale': (0.2, 0.1, 6),
'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]})
bs.newnode('locator', attrs={'shape': 'box', 'position': (x, y, -5.52), 'color': (
1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (0.2, 0.1, 2)})
if backslash:
x = x+0.1
y = y+0.1
else:
x = x-0.1
y = y+0.1
class mapdefs:
points = {}
# noinspection PyDictCreation
boxes = {}
boxes['area_of_interest_bounds'] = (-1.045859963, 12.67722855,
-5.401537075) + (0.0, 0.0, 0.0) + (
42.46156851, 20.94044653, 0.6931564611)
points['ffa_spawn1'] = (-9.295167711, 8.010664315,
-5.44451005) + (1.555840357, 1.453808816, 0.1165648888)
points['ffa_spawn2'] = (7.484707127, 8.172681752, -5.614479365) + (
1.553861796, 1.453808816, 0.04419853907)
points['ffa_spawn3'] = (9.55724115, 11.30789446, -5.614479365) + (
1.337925849, 1.453808816, 0.04419853907)
points['ffa_spawn4'] = (-11.55747023, 10.99170684, -5.614479365) + (
1.337925849, 1.453808816, 0.04419853907)
points['ffa_spawn5'] = (-1.878892369, 9.46490571, -5.614479365) + (
1.337925849, 1.453808816, 0.04419853907)
points['ffa_spawn6'] = (-0.4912812943, 5.077006397, -5.521672101) + (
1.878332089, 1.453808816, 0.007578097856)
points['flag1'] = (-11.75152479, 8.057427485, -5.52)
points['flag2'] = (9.840909039, 8.188634282, -5.52)
points['flag3'] = (-0.2195258696, 5.010273907, -5.52)
points['flag4'] = (-0.04605809154, 12.73369108, -5.52)
points['flag_default'] = (-0.04201942896, 12.72374492, -5.52)
boxes['map_bounds'] = (-0.8748348681, 9.212941713, -5.729538885) + (
0.0, 0.0, 0.0) + (42.09666006, 26.19950145, 7.89541168)
points['powerup_spawn1'] = (1.160232442, 6.745963662, -5.469115985)
points['powerup_spawn2'] = (-1.899700206, 10.56447241, -5.505721177)
points['powerup_spawn3'] = (10.56098871, 12.25165669, -5.576232453)
points['powerup_spawn4'] = (-12.33530337, 12.25165669, -5.576232453)
points['spawn1'] = (-9.295167711, 8.010664315,
-5.44451005) + (1.555840357, 1.453808816, 0.1165648888)
points['spawn2'] = (7.484707127, 8.172681752,
-5.614479365) + (1.553861796, 1.453808816, 0.04419853907)
points['spawn_by_flag1'] = (-9.295167711, 8.010664315, -5.44451005) + (
1.555840357, 1.453808816, 0.1165648888)
points['spawn_by_flag2'] = (7.484707127, 8.172681752, -5.614479365) + (
1.553861796, 1.453808816, 0.04419853907)
points['spawn_by_flag3'] = (-1.45994593, 5.038762459, -5.535288724) + (
0.9516389866, 0.6666414677, 0.08607244075)
points['spawn_by_flag4'] = (0.4932087091, 12.74493212, -5.598987003) + (
0.5245740665, 0.5245740665, 0.01941146064)
class CreativeThoughts(bs.Map):
"""Freaking map by smoothy."""
defs = mapdefs
name = 'Creative Thoughts'
@classmethod
def get_play_types(cls) -> List[str]:
"""Return valid play types for this map."""
return [
'melee', 'keep_away', 'team_flag'
]
@classmethod
def get_preview_texture_name(cls) -> str:
return 'alwaysLandPreview'
@classmethod
def on_preload(cls) -> Any:
data: Dict[str, Any] = {
'mesh': bs.getmesh('alwaysLandLevel'),
'bottom_mesh': bs.getmesh('alwaysLandLevelBottom'),
'bgmesh': bs.getmesh('alwaysLandBG'),
'collision_mesh': bs.getcollisionmesh('alwaysLandLevelCollide'),
'tex': bs.gettexture('alwaysLandLevelColor'),
'bgtex': bs.gettexture('alwaysLandBGColor'),
'vr_fill_mound_mesh': bs.getmesh('alwaysLandVRFillMound'),
'vr_fill_mound_tex': bs.gettexture('vrFillMound')
}
return data
@classmethod
def get_music_type(cls) -> bs.MusicType:
return bs.MusicType.FLYING
def __init__(self) -> None:
super().__init__(vr_overlay_offset=(0, -3.7, 2.5))
shared = SharedObjects.get()
self._fake_wall_material = bs.Material()
self._real_wall_material = bs.Material()
self._fake_wall_material.add_actions(
conditions=(('they_are_younger_than', 9000), 'and',
('they_have_material', shared.player_material)),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', True)
))
self._real_wall_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', True)
))
self.background = bs.newnode(
'terrain',
attrs={
'mesh': self.preloaddata['bgmesh'],
'lighting': False,
'background': True,
'color_texture': bs.gettexture("rampageBGColor")
})
self.leftwall = bs.newnode('region', attrs={'position': (-17.75152479, 13, -5.52), 'scale': (
0.1, 15.5, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]})
self.rightwall = bs.newnode('region', attrs={'position': (17.75, 13, -5.52), 'scale': (
0.1, 15.5, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]})
self.topwall = bs.newnode('region', attrs={'position': (0, 21.0, -5.52), 'scale': (
35.4, 0.2, 2), 'type': 'box', 'materials': [shared.footing_material, self._real_wall_material]})
bs.newnode('locator', attrs={'shape': 'box', 'position': (-17.75152479, 13, -5.52), 'color': (
0, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (0.1, 15.5, 2)})
bs.newnode('locator', attrs={'shape': 'box', 'position': (17.75, 13, -5.52), 'color': (
0, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (0.1, 15.5, 2)})
bs.newnode('locator', attrs={'shape': 'box', 'position': (0, 21.0, -5.52), 'color': (
0, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': (35.4, 0.2, 2)})
gnode = bs.getactivity().globalsnode
gnode.happy_thoughts_mode = True
gnode.shadow_offset = (0.0, 8.0, 5.0)
gnode.tint = (1.3, 1.23, 1.0)
gnode.ambient_color = (1.3, 1.23, 1.0)
gnode.vignette_outer = (0.64, 0.59, 0.69)
gnode.vignette_inner = (0.95, 0.95, 0.93)
gnode.vr_near_clip = 1.0
self.is_flying = True
# throw out some tips on flying
txt = bs.newnode('text',
attrs={
'text': babase.Lstr(resource='pressJumpToFlyText'),
'scale': 1.2,
'maxwidth': 800,
'position': (0, 200),
'shadow': 0.5,
'flatness': 0.5,
'h_align': 'center',
'v_attach': 'bottom'
})
cmb = bs.newnode('combine',
owner=txt,
attrs={
'size': 4,
'input0': 0.3,
'input1': 0.9,
'input2': 0.0
})
bs.animate(cmb, 'input3', {3.0: 0, 4.0: 1, 9.0: 1, 10.0: 0})
cmb.connectattr('output', txt, 'color')
bs.timer(10.0, txt.delete)
try:
bs._map.register_map(CreativeThoughts)
except:
pass

View file

@ -0,0 +1,643 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
import random
from bascenev1lib.actor.bomb import Blast
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.actor.spaz import PunchHitMessage
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.spazfactory import SpazFactory
if TYPE_CHECKING:
from typing import Any, Sequence
lang = bs.app.lang.language
if lang == 'Spanish':
name = 'Guerra de Nieve'
snowball_rate = 'Intervalo de Ataque'
snowball_slowest = 'Más Lento'
snowball_slow = 'Lento'
snowball_fast = 'Rápido'
snowball_lagcity = 'Más Rápido'
snowball_scale = 'Tamaño de Bola de Nieve'
snowball_smallest = 'Más Pequeño'
snowball_small = 'Pequeño'
snowball_big = 'Grande'
snowball_biggest = 'Más Grande'
snowball_insane = 'Insano'
snowball_melt = 'Derretir Bola de Nieve'
snowball_bust = 'Rebotar Bola de Nieve'
snowball_explode = 'Explotar al Impactar'
snowball_snow = 'Modo Nieve'
else:
name = 'Snowball Fight'
snowball_rate = 'Snowball Rate'
snowball_slowest = 'Slowest'
snowball_slow = 'Slow'
snowball_fast = 'Fast'
snowball_lagcity = 'Lag City'
snowball_scale = 'Snowball Scale'
snowball_smallest = 'Smallest'
snowball_small = 'Small'
snowball_big = 'Big'
snowball_biggest = 'Biggest'
snowball_insane = 'Insane'
snowball_melt = 'Snowballs Melt'
snowball_bust = 'Snowballs Bust'
snowball_explode = 'Snowballs Explode'
snowball_snow = 'Snow Mode'
class Snowball(bs.Actor):
def __init__(self,
position: Sequence[float] = (0.0, 1.0, 0.0),
velocity: Sequence[float] = (0.0, 0.0, 0.0),
blast_radius: float = 0.7,
bomb_scale: float = 0.8,
source_player: bs.Player | None = None,
owner: bs.Node | None = None,
melt: bool = True,
bounce: bool = True,
explode: bool = False):
super().__init__()
shared = SharedObjects.get()
self._exploded = False
self.scale = bomb_scale
self.blast_radius = blast_radius
self._source_player = source_player
self.owner = owner
self._hit_nodes = set()
self.snowball_melt = melt
self.snowball_bounce = bounce
self.snowball_explode = explode
self.radius = bomb_scale * 0.1
if bomb_scale <= 1.0:
shadow_size = 0.6
elif bomb_scale <= 2.0:
shadow_size = 0.4
elif bomb_scale <= 3.0:
shadow_size = 0.2
else:
shadow_size = 0.1
self.snowball_material = bs.Material()
self.snowball_material.add_actions(
conditions=(
(
('we_are_younger_than', 5),
'or',
('they_are_younger_than', 100),
),
'and',
('they_have_material', shared.object_material),
),
actions=('modify_node_collision', 'collide', False),
)
self.snowball_material.add_actions(
conditions=('they_have_material', shared.pickup_material),
actions=('modify_part_collision', 'use_node_collide', False),
)
self.snowball_material.add_actions(actions=('modify_part_collision',
'friction', 0.3))
self.snowball_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('modify_part_collision', 'physical', False),
('call', 'at_connect', self.hit)))
self.snowball_material.add_actions(
conditions=(('they_dont_have_material', shared.player_material),
'and',
('they_have_material', shared.object_material),
'or',
('they_have_material', shared.footing_material)),
actions=('call', 'at_connect', self.bounce))
self.node = bs.newnode(
'prop',
delegate=self,
attrs={
'position': position,
'velocity': velocity,
'body': 'sphere',
'body_scale': self.scale,
'mesh': bs.getmesh('frostyPelvis'),
'shadow_size': shadow_size,
'color_texture': bs.gettexture('bunnyColor'),
'reflection': 'soft',
'reflection_scale': [0.15],
'density': 1.0,
'materials': [self.snowball_material]
})
self.light = bs.newnode(
'light',
owner=self.node,
attrs={
'color': (0.6, 0.6, 1.0),
'intensity': 0.8,
'radius': self.radius
})
self.node.connectattr('position', self.light, 'position')
bs.animate(self.node, 'mesh_scale', {
0: 0,
0.2: 1.3 * self.scale,
0.26: self.scale
})
bs.animate(self.light, 'radius', {
0: 0,
0.2: 1.3 * self.radius,
0.26: self.radius
})
if self.snowball_melt:
bs.timer(1.5, bs.WeakCall(self._disappear))
def hit(self) -> None:
if not self.node:
return
if self._exploded:
return
if self.snowball_explode:
self._exploded = True
self.do_explode()
bs.timer(0.001, bs.WeakCall(self.handlemessage, bs.DieMessage()))
else:
self.do_hit()
def do_hit(self) -> None:
v = self.node.velocity
if babase.Vec3(*v).length() > 5.0:
node = bs.getcollision().opposingnode
if node is not None and node and not (
node in self._hit_nodes):
t = self.node.position
hitdir = self.node.velocity
self._hit_nodes.add(node)
node.handlemessage(
bs.HitMessage(
pos=t,
velocity=v,
magnitude=babase.Vec3(*v).length()*0.5,
velocity_magnitude=babase.Vec3(*v).length()*0.5,
radius=0,
srcnode=self.node,
source_player=self._source_player,
force_direction=hitdir,
hit_type='snoBall',
hit_subtype='default'))
if not self.snowball_bounce:
bs.timer(0.05, bs.WeakCall(self.do_bounce))
def do_explode(self) -> None:
Blast(position=self.node.position,
velocity=self.node.velocity,
blast_radius=self.blast_radius,
source_player=babase.existing(self._source_player),
blast_type='impact',
hit_subtype='explode').autoretain()
def bounce(self) -> None:
if not self.node:
return
if self._exploded:
return
if not self.snowball_bounce:
vel = self.node.velocity
bs.timer(0.01, bs.WeakCall(self.calc_bounce, vel))
else:
return
def calc_bounce(self, vel) -> None:
if not self.node:
return
ospd = babase.Vec3(*vel).length()
dot = sum(x*y for x, y in zip(vel, self.node.velocity))
if ospd*ospd - dot > 50.0:
bs.timer(0.05, bs.WeakCall(self.do_bounce))
def do_bounce(self) -> None:
if not self.node:
return
if not self._exploded:
self.do_effect()
def do_effect(self) -> None:
self._exploded = True
bs.emitfx(position=self.node.position,
velocity=[v*0.1 for v in self.node.velocity],
count=10,
spread=0.1,
scale=0.4,
chunk_type='ice')
sound = bs.getsound('impactMedium')
sound.play(1.0, position=self.node.position)
scl = self.node.mesh_scale
bs.animate(self.node, 'mesh_scale', {
0.0: scl*1.0,
0.02: scl*0.5,
0.05: 0.0
})
lr = self.light.radius
bs.animate(self.light, 'radius', {
0.0: lr*1.0,
0.02: lr*0.5,
0.05: 0.0
})
bs.timer(0.08,
bs.WeakCall(self.handlemessage, bs.DieMessage()))
def _disappear(self) -> None:
self._exploded = True
if self.node:
scl = self.node.mesh_scale
bs.animate(self.node, 'mesh_scale', {
0.0: scl*1.0,
0.3: scl*0.5,
0.5: 0.0
})
lr = self.light.radius
bs.animate(self.light, 'radius', {
0.0: lr*1.0,
0.3: lr*0.5,
0.5: 0.0
})
bs.timer(0.55,
bs.WeakCall(self.handlemessage, bs.DieMessage()))
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
if self.node:
self.node.delete()
elif isinstance(msg, bs.OutOfBoundsMessage):
self.handlemessage(bs.DieMessage())
else:
super().handlemessage(msg)
class NewPlayerSpaz(PlayerSpaz):
def __init__(self, *args: Any, **kwds: Any):
super().__init__(*args, **kwds)
self.snowball_scale = 1.0
self.snowball_melt = True
self.snowball_bounce = True
self.snowball_explode = False
def on_punch_press(self) -> None:
if not self.node or self.frozen or self.node.knockout > 0.0:
return
t_ms = bs.time() * 1000
assert isinstance(t_ms, int)
if t_ms - self.last_punch_time_ms >= self._punch_cooldown:
if self.punch_callback is not None:
self.punch_callback(self)
# snowball
pos = self.node.position
p1 = self.node.position_center
p2 = self.node.position_forward
direction = [p1[0]-p2[0], p2[1]-p1[1], p1[2]-p2[2]]
direction[1] = 0.03
mag = 20.0/babase.Vec3(*direction).length()
vel = [v * mag for v in direction]
Snowball(position=(pos[0], pos[1] + 0.1, pos[2]),
velocity=vel,
blast_radius=self.blast_radius,
bomb_scale=self.snowball_scale,
source_player=self.source_player,
owner=self.node,
melt=self.snowball_melt,
bounce=self.snowball_bounce,
explode=self.snowball_explode).autoretain()
self._punched_nodes = set() # Reset this.
self.last_punch_time_ms = t_ms
self.node.punch_pressed = True
if not self.node.hold_node:
bs.timer(
0.1,
bs.WeakCall(self._safe_play_sound,
SpazFactory.get().swish_sound, 0.8))
self._turbo_filter_add_press('punch')
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, PunchHitMessage):
pass
else:
return super().handlemessage(msg)
return None
class Player(bs.Player['Team']):
"""Our player type for this game."""
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export bascenev1.GameActivity
class SnowballFightGame(bs.TeamGameActivity[Player, Team]):
"""A game type based on acquiring kills."""
name = name
description = 'Kill a set number of enemies to win.'
# Print messages when players die since it matters here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: type[bs.Session]) -> list[babase.Setting]:
settings = [
bs.IntSetting(
'Kills to Win Per Player',
min_value=1,
default=5,
increment=1,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
bs.IntChoiceSetting(
snowball_rate,
choices=[
(snowball_slowest, 500),
(snowball_slow, 400),
('Normal', 300),
(snowball_fast, 200),
(snowball_lagcity, 100),
],
default=300,
),
bs.FloatChoiceSetting(
snowball_scale,
choices=[
(snowball_smallest, 0.4),
(snowball_small, 0.6),
('Normal', 0.8),
(snowball_big, 1.4),
(snowball_biggest, 3.0),
(snowball_insane, 6.0),
],
default=0.8,
),
bs.BoolSetting(snowball_melt, default=True),
bs.BoolSetting(snowball_bust, default=True),
bs.BoolSetting(snowball_explode, default=False),
bs.BoolSetting(snowball_snow, default=True),
bs.BoolSetting('Epic Mode', default=False),
]
# In teams mode, a suicide gives a point to the other team, but in
# free-for-all it subtracts from your own score. By default we clamp
# this at zero to benefit new players, but pro players might like to
# be able to go negative. (to avoid a strategy of just
# suiciding until you get a good drop)
if issubclass(sessiontype, bs.FreeForAllSession):
settings.append(
bs.BoolSetting('Allow Negative Scores', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.DualTeamSession)
or issubclass(sessiontype, bs.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return bs.app.classic.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_to_win: int | None = None
self._dingsound = bs.getsound('dingSmall')
self._epic_mode = bool(settings['Epic Mode'])
self._kills_to_win_per_player = int(
settings['Kills to Win Per Player'])
self._time_limit = float(settings['Time Limit'])
self._allow_negative_scores = bool(
settings.get('Allow Negative Scores', False))
self._snowball_rate = int(settings[snowball_rate])
self._snowball_scale = float(settings[snowball_scale])
self._snowball_melt = bool(settings[snowball_melt])
self._snowball_bounce = bool(settings[snowball_bust])
self._snowball_explode = bool(settings[snowball_explode])
self._snow_mode = bool(settings[snowball_snow])
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC if self._epic_mode else
bs.MusicType.TO_THE_DEATH)
def get_instance_description(self) -> str | Sequence:
return 'Crush ${ARG1} of your enemies.', self._score_to_win
def get_instance_description_short(self) -> 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_transition_in(self) -> None:
super().on_transition_in()
if self._snow_mode:
gnode = bs.getactivity().globalsnode
gnode.tint = (0.8, 0.8, 1.3)
bs.timer(0.02, self.emit_snowball, repeat=True)
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()
def emit_snowball(self) -> None:
pos = (-10 + (random.random() * 30), 15,
-10 + (random.random() * 30))
vel = ((-5.0 + random.random() * 30.0) * (-1.0 if pos[0] > 0 else 1.0),
-50.0, (-5.0 + random.random() * 30.0) * (
-1.0 if pos[0] > 0 else 1.0))
bs.emitfx(position=pos,
velocity=vel,
count=10,
scale=1.0 + random.random(),
spread=0.0,
chunk_type='spark')
def spawn_player_spaz(self,
player: Player,
position: Sequence[float] = (0, 0, 0),
angle: float | None = None) -> PlayerSpaz:
from babase import _math
from bascenev1._gameutils import animate
from bascenev1._coopsession import CoopSession
if isinstance(self.session, bs.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)
name = player.getname()
color = player.color
highlight = player.highlight
light_color = _math.normalized_color(color)
display_color = babase.safecolor(color, target_intensity=0.75)
spaz = NewPlayerSpaz(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, 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_pickup=False, enable_bomb=False)
# Move to the stand position and add a flash of light.
spaz.handlemessage(
bs.StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
self._spawn_sound.play(1, position=spaz.node.position)
light = bs.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
bs.timer(0.5, light.delete)
# custom
spaz._punch_cooldown = self._snowball_rate
spaz.snowball_scale = self._snowball_scale
spaz.snowball_melt = self._snowball_melt
spaz.snowball_bounce = self._snowball_bounce
spaz.snowball_explode = self._snowball_explode
return spaz
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
self.respawn_player(player)
killer = msg.getkillerplayer(Player)
if killer is None:
return None
# Handle team-kills.
if killer.team is player.team:
# In free-for-all, killing yourself loses you a point.
if isinstance(self.session, bs.FreeForAllSession):
new_score = player.team.score - 1
if not self._allow_negative_scores:
new_score = max(0, new_score)
player.team.score = new_score
# In teams-mode it gives a point to the other team.
else:
self._dingsound.play()
for team in self.teams:
if team is not killer.team:
team.score += 1
# Killing someone on another team nets a kill.
else:
killer.team.score += 1
self._dingsound.play()
# In FFA show scores since its hard to find on the scoreboard.
if isinstance(killer.actor, PlayerSpaz) and killer.actor:
killer.actor.set_score_text(str(killer.team.score) + '/' +
str(self._score_to_win),
color=killer.team.color,
flash=True)
self._update_scoreboard()
# If someone has won, set a timer to end shortly.
# (allows the dust to clear and draws to occur if deaths are
# close enough)
assert self._score_to_win is not None
if any(team.score >= self._score_to_win for team in self.teams):
bs.timer(0.5, self.end_game)
else:
return super().handlemessage(msg)
return None
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(team, team.score,
self._score_to_win)
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

953
dist/ba_root/mods/games/squid_race.py vendored Normal file
View file

@ -0,0 +1,953 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# Released under the MIT License. See LICENSE for details.
#
"""Defines Race mini-game."""
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from dataclasses import dataclass
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.actor.bomb import Bomb, Blast, ExplodeHitMessage
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import (Any, Type, Tuple, List, Sequence, Optional, Dict,
Union)
from bascenev1lib.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(bs.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 = bs.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(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.distance_txt: Optional[bs.Node] = None
self.last_region = 0
self.lap = 0
self.distance = 0.0
self.finished = False
self.rank: Optional[int] = None
class Team(bs.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 bascenev1.GameActivity
class SquidRaceGame(bs.TeamGameActivity[Player, Team]):
"""Game of racing around a track."""
name = 'Squid Race'
description = 'Run real fast!'
scoreconfig = bs.ScoreConfig(label='Time',
lower_is_better=True,
scoretype=bs.ScoreType.MILLISECONDS)
@classmethod
def get_available_settings(
cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]:
settings = [
bs.IntSetting('Laps', min_value=1, default=3, increment=1),
bs.IntChoiceSetting(
'Time Limit',
default=0,
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
),
bs.IntChoiceSetting(
'Mine Spawning',
default=4000,
choices=[
('No Mines', 0),
('8 Seconds', 8000),
('4 Seconds', 4000),
('2 Seconds', 2000),
],
),
bs.IntChoiceSetting(
'Bomb Spawning',
choices=[
('None', 0),
('8 Seconds', 8000),
('4 Seconds', 4000),
('2 Seconds', 2000),
('1 Second', 1000),
],
default=2000,
),
bs.BoolSetting('Epic Mode', default=False),
]
# We have some specific settings in teams mode.
if issubclass(sessiontype, bs.DualTeamSession):
settings.append(
bs.BoolSetting('Entire Team Must Finish', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.MultiTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return bs.app.classic.getmaps('race')
def __init__(self, settings: dict):
self._race_started = False
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_sound = bs.getsound('score')
self._swipsound = bs.getsound('swip')
self._last_team_time: Optional[float] = None
self._front_race_region: Optional[int] = None
self._nub_tex = bs.gettexture('nub')
self._beep_1_sound = bs.getsound('raceBeep1')
self._beep_2_sound = bs.getsound('raceBeep2')
self.race_region_material: Optional[bs.Material] = None
self._regions: List[RaceRegion] = []
self._team_finish_pts: Optional[int] = None
self._time_text: Optional[bs.Actor] = None
self._timer: Optional[OnScreenTimer] = None
self._race_mines: Optional[List[RaceMine]] = None
self._race_mine_timer: Optional[bs.Timer] = None
self._scoreboard_timer: Optional[bs.Timer] = None
self._player_order_update_timer: Optional[bs.Timer] = None
self._start_lights: Optional[List[bs.Node]] = None
self._squid_lights: Optional[List[bs.Node]] = None
self._countdown_timer: int = 0
self._sq_mode: str = 'Easy'
self._tick_timer: Optional[bs.Timer] = None
self._bomb_spawn_timer: Optional[bs.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: bs.getsound('announceTen'),
9: bs.getsound('announceNine'),
8: bs.getsound('announceEight'),
7: bs.getsound('announceSeven'),
6: bs.getsound('announceSix'),
5: bs.getsound('announceFive'),
4: bs.getsound('announceFour'),
3: bs.getsound('announceThree'),
2: bs.getsound('announceTwo'),
1: bs.getsound('announceOne')
}
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC_RACE
if self._epic_mode else bs.MusicType.RACE)
def get_instance_description(self) -> Union[str, Sequence]:
if (isinstance(self.session, bs.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 = bs.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 = bs.newnode('light',
attrs={
'position': pos,
'color': (1, 1, 0),
'height_attenuated': False,
'radius': 0.4
})
bs.timer(0.5, light.delete)
bs.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 = bs.getcollision()
try:
region = collision.sourcenode.getdelegate(RaceRegion, True)
player = collision.opposingnode.getdelegate(PlayerSpaz,
True).getplayer(
Player, True)
except bs.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(bs.DieMessage())
bs.broadcastmessage(babase.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, bs.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, bs.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(
bs.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:
self._score_sound.play()
player.team.finished = True
assert self._timer is not None
elapsed = bs.time() - self._timer.getstarttime()
self._last_team_time = player.team.time = elapsed
# Team has yet to finish.
else:
self._swipsound.play()
# They've just finished a lap but not the race.
else:
self._swipsound.play()
self._flash_player(player, 0.3)
# Print their lap number over their head.
try:
assert isinstance(player.actor, PlayerSpaz)
mathnode = bs.newnode('math',
owner=player.actor.node,
attrs={
'input1': (0, 1.9, 0),
'operation': 'add'
})
player.actor.node.connectattr(
'torso_position', mathnode, 'input2')
tstr = babase.Lstr(resource='lapNumberText',
subs=[('${CURRENT}',
str(player.lap + 1)),
('${TOTAL}', str(self._laps))
])
txtnode = bs.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')
bs.animate(txtnode, 'scale', {
0.0: 0,
0.2: 0.019,
2.0: 0.019,
2.2: 0
})
bs.timer(2.3, mathnode.delete)
except Exception:
babase.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():
bs.broadcastmessage(
babase.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, bs.DualTeamSession)
and self._entire_team_must_finish):
bs.broadcastmessage(babase.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
bs.getsound('boo').play()
for otherplayer in player.team.players:
otherplayer.lap = 0
otherplayer.finished = True
try:
if otherplayer.actor is not None:
otherplayer.actor.handlemessage(bs.DieMessage())
except Exception:
babase.print_exception('Error sending DieMessage.')
# Defer so team/player lists will be updated.
babase.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, bs.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 bascenev1lib.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 = bs.NodeActor(
bs.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 = bs.Timer(0.001 * self._mine_spawning,
self._update_race_mine,
repeat=True)
self._scoreboard_timer = bs.Timer(0.25,
self._update_scoreboard,
repeat=True)
self._player_order_update_timer = bs.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
bs.timer(lstart, self._do_light_1)
bs.timer(lstart + inc, self._do_light_2)
bs.timer(lstart + 2 * inc, self._do_light_3)
bs.timer(lstart + 3 * inc, self._start_race)
self._start_lights = []
for i in range(4):
lnub = bs.newnode('image',
attrs={
'texture': bs.gettexture('nub'),
'opacity': 1.0,
'absolute_scale': True,
'position': (-75 + i * 50, light_y),
'scale': (50, 50),
'attach': 'center'
})
bs.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
})
bs.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 = bs.newnode('image',
attrs={
'texture': bs.gettexture('nub'),
'opacity': 1.0,
'absolute_scale': True,
'position': (-33 + i * 65, 220),
'scale': (60, 60),
'attach': 'center'
})
bs.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)
bs.timer(1.0, self._check_squid_end, repeat=True)
self._squidgame_countdown()
def _squidgame_countdown(self) -> None:
self._countdown_timer = 80 * self._laps # 80
bs.newnode(
'image',
attrs={
'opacity': 0.7,
'color': (0.2, 0.2, 0.2),
'attach': 'topCenter',
'position': (-220, -40),
'scale': (135, 45),
'texture': bs.gettexture('bar')})
bs.newnode(
'image',
attrs={
'opacity': 1.0,
'color': (1.0, 0.0, 0.0),
'attach': 'topCenter',
'position': (-220, -38),
'scale': (155, 65),
'texture': bs.gettexture('uiAtlas'),
'mesh_transparent': bs.getmesh('meterTransparent')})
self._sgcountdown_text = bs.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'
bs.getsound('alarm').play()
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:
self._countdownsounds[self._countdown_timer].play()
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(bs.DieMessage())
NewBlast(
position=player.actor.node.position,
velocity=player.actor.node.velocity,
blast_radius=3.0,
blast_type='normal').autoretain()
player.actor.handlemessage(
bs.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:
bs.getsound('tick').play()
self._tick_timer = bs.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:
# bs.getsound('lrlg_06s').play()
# elif random_number == 5.5:
# bs.getsound('lrlg_055s').play()
# elif random_number == 5:
# bs.getsound('lrlg_05s').play()
# elif random_number == 4.5:
# bs.getsound('lrlg_045s').play()
# elif random_number == 4:
# bs.getsound('lrlg_04s').play()
# elif random_number == 3.5:
# bs.getsound('lrlg_035s').play()
# elif random_number == 3:
# bs.getsound('lrlg_03s').play()
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
bs.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)
bs.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
bs.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(bs.DieMessage())
NewBlast(
position=player.actor.node.position,
velocity=player.actor.node.velocity,
blast_radius=3.0,
blast_type='normal').autoretain()
player.actor.handlemessage(
bs.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)
self._beep_1_sound.play()
def _do_light_2(self) -> None:
assert self._start_lights is not None
self._start_lights[1].color = (1.0, 0, 0)
self._beep_1_sound.play()
def _do_light_3(self) -> None:
assert self._start_lights is not None
self._start_lights[2].color = (1.0, 0.3, 0)
self._beep_1_sound.play()
def _start_race(self) -> None:
assert self._start_lights is not None
self._start_lights[3].color = (0.0, 1.0, 0)
self._beep_2_sound.play()
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:
babase.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 = bs.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()
bs.timer(0.2, self._start_delete, repeat=True)
bs.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[babase.Vec3]
try:
pos = player.position
except bs.NotFoundError:
pos = None
if pos is not None:
r_index = player.last_region
rg1 = self._regions[r_index]
r1pt = babase.Vec3(rg1.pos[:3])
rg2 = self._regions[0] if r_index == len(
self._regions) - 1 else self._regions[r_index + 1]
r2pt = babase.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))
bs.timer(random.uniform(0.0, 2.0),
bs.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 = bs.newnode('light',
attrs={
'position': rmine.point[:3],
'color': (1, 0.2, 0.2),
'radius': 0.1,
'height_attenuated': False
})
bs.animate(light, 'intensity', {0.0: 0, 0.1: 1.0, 0.2: 0}, loop=True)
bs.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)
bs.timer(0.95, babase.Call(self._make_mine, m_index))
def spawn_player(self, player: Player) -> bs.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 = bs.newnode('math',
owner=spaz.node,
attrs={
'input1': (0, 1.4, 0),
'operation': 'add'
})
spaz.node.connectattr('torso_position', mathnode, 'input2')
distance_txt = bs.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, bs.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, bs.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 = bs.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,
bs.DualTeamSession))
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment default behavior.
super().handlemessage(msg)
else:
super().handlemessage(msg)

127
dist/ba_root/mods/games/the_spaz_game.py vendored Normal file
View file

@ -0,0 +1,127 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# ba_meta require api 8
"""
TheSpazGame - Mini game where all characters looks identical , identify enemies and kill them.
Author: Mr.Smoothy
Discord: https://discord.gg/ucyaesh
Youtube: https://www.youtube.com/c/HeySmoothy
Website: https://bombsquad-community.web.app
Github: https://github.com/bombsquad-community
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.game.elimination import EliminationGame, Player
from bascenev1lib.actor.spazfactory import SpazFactory
import random
if TYPE_CHECKING:
from typing import Any, Sequence
CHARACTER = 'Spaz'
# ba_meta export bascenev1.GameActivity
class TheSpazGame(EliminationGame):
name = 'TheSpazGame'
description = 'Enemy Spaz AmongUs. Kill them all'
scoreconfig = bs.ScoreConfig(
label='Survived', scoretype=bs.ScoreType.SECONDS, none_is_winner=True
)
announce_player_deaths = False
allow_mid_activity_joins = False
@classmethod
def get_available_settings(
cls, sessiontype: type[bs.Session]
) -> list[babase.Setting]:
settings = [
bs.IntSetting(
'Lives Per Player',
default=1,
min_value=1,
max_value=10,
increment=1,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.15)
],
default=1.0,
),
bs.BoolSetting('Epic Mode', default=False),
]
if issubclass(sessiontype, bs.DualTeamSession):
settings.append(bs.BoolSetting('Solo Mode', default=False))
settings.append(
bs.BoolSetting('Balance Total Lives', default=False)
)
return settings
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession) or issubclass(
sessiontype, bs.FreeForAllSession
)
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return bs.app.classic.getmaps('melee')
def get_instance_description(self) -> str | Sequence:
return (
'Enemy Spaz AmongUs. Kill them all'
)
def get_instance_description_short(self) -> str | Sequence:
return (
'Enemy Spaz AmongUs. Kill them all'
)
def __init__(self, settings: dict):
super().__init__(settings)
self._solo_mode = False
def spawn_player(self, player: Player) -> bs.Actor:
p = [-6, -4.3, -2.6, -0.9, 0.8, 2.5, 4.2, 5.9]
q = [-4, -2.3, -0.6, 1.1, 2.8, 4.5]
x = random.randrange(0, len(p))
y = random.randrange(0, len(q))
spaz = self.spawn_player_spaz(player, position=(p[x], 1.8, q[y]))
spaz.node.color = (1, 1, 1)
spaz.node.highlight = (1, 0.4, 1)
self.update_appearance(spaz, character=CHARACTER)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
return spaz
def update_appearance(self, spaz, character):
factory = SpazFactory.get()
media = factory.get_media(character)
for field, value in media.items():
setattr(spaz.node, field, value)
spaz.node.style = factory.get_style(character)
spaz.node.name = ''

View file

@ -0,0 +1,623 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
"""Ultimate Last Stand V2:
Made by Cross Joy"""
# Anyone who wanna help me in giving suggestion/ fix bugs/ by creating PR,
# Can visit my github https://github.com/CrossJoy/Bombsquad-Modding
# You can contact me through discord:
# My Discord Id: Cross Joy#0721
# My BS Discord Server: https://discord.gg/JyBY6haARJ
# ----------------------------------------------------------------------------
# V2 What's new?
# - The "Player can't fight each other" system is removed,
# players exploiting the features and, I know ideas how to fix it especially
# the freeze handlemessage
# - Added new bot: Ice Bot
# - The bot spawn location will be more randomize rather than based on players
# position, I don't wanna players stay at the corner of the map.
# - Some codes clean up.
# ----------------------------------------------------------------------------
# ba_meta require api 8
from __future__ import annotations
import random
from dataclasses import dataclass
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.bomb import TNTSpawner
from bascenev1lib.actor.onscreentimer import OnScreenTimer
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.spazfactory import SpazFactory
from bascenev1lib.actor.spazbot import (SpazBot, SpazBotSet, BomberBot,
BomberBotPro, BomberBotProShielded,
BrawlerBot, BrawlerBotPro,
BrawlerBotProShielded, TriggerBot,
TriggerBotPro, TriggerBotProShielded,
ChargerBot, StickyBot, ExplodeyBot)
if TYPE_CHECKING:
from typing import Any, Sequence
from bascenev1lib.actor.spazbot import SpazBot
class IceBot(SpazBot):
"""A slow moving bot with ice bombs.
category: Bot Classes
"""
character = 'Pascal'
punchiness = 0.9
throwiness = 1
charge_speed_min = 1
charge_speed_max = 1
throw_dist_min = 5.0
throw_dist_max = 20
run = True
charge_dist_min = 10.0
charge_dist_max = 11.0
default_bomb_type = 'ice'
default_bomb_count = 1
points_mult = 3
class Icon(bs.Actor):
"""Creates in in-game icon on screen."""
def __init__(self,
player: Player,
position: tuple[float, float],
scale: float,
show_lives: bool = True,
show_death: bool = True,
name_scale: float = 1.0,
name_maxwidth: float = 115.0,
flatness: float = 1.0,
shadow: float = 1.0):
super().__init__()
self._player = player
self._show_lives = show_lives
self._show_death = show_death
self._name_scale = name_scale
self._outline_tex = bs.gettexture('characterIconMask')
icon = player.get_icon()
self.node = bs.newnode('image',
delegate=self,
attrs={
'texture': icon['texture'],
'tint_texture': icon['tint_texture'],
'tint_color': icon['tint_color'],
'vr_depth': 400,
'tint2_color': icon['tint2_color'],
'mask_texture': self._outline_tex,
'opacity': 1.0,
'absolute_scale': True,
'attach': 'bottomCenter'
})
self._name_text = bs.newnode(
'text',
owner=self.node,
attrs={
'text': babase.Lstr(value=player.getname()),
'color': babase.safecolor(player.team.color),
'h_align': 'center',
'v_align': 'center',
'vr_depth': 410,
'maxwidth': name_maxwidth,
'shadow': shadow,
'flatness': flatness,
'h_attach': 'center',
'v_attach': 'bottom'
})
if self._show_lives:
self._lives_text = bs.newnode('text',
owner=self.node,
attrs={
'text': 'x0',
'color': (1, 1, 0.5),
'h_align': 'left',
'vr_depth': 430,
'shadow': 1.0,
'flatness': 1.0,
'h_attach': 'center',
'v_attach': 'bottom'
})
self.set_position_and_scale(position, scale)
def set_position_and_scale(self, position: tuple[float, float],
scale: float) -> None:
"""(Re)position the icon."""
assert self.node
self.node.position = position
self.node.scale = [70.0 * scale]
self._name_text.position = (position[0], position[1] + scale * 52.0)
self._name_text.scale = 1.0 * scale * self._name_scale
if self._show_lives:
self._lives_text.position = (position[0] + scale * 10.0,
position[1] - scale * 43.0)
self._lives_text.scale = 1.0 * scale
def update_for_lives(self) -> None:
"""Update for the target player's current lives."""
if self._player:
lives = self._player.lives
else:
lives = 0
if self._show_lives:
if lives > 0:
self._lives_text.text = 'x' + str(lives - 1)
else:
self._lives_text.text = ''
if lives == 0:
self._name_text.opacity = 0.2
assert self.node
self.node.color = (0.7, 0.3, 0.3)
self.node.opacity = 0.2
def handle_player_spawned(self) -> None:
"""Our player spawned; hooray!"""
if not self.node:
return
self.node.opacity = 1.0
self.update_for_lives()
def handle_player_died(self) -> None:
"""Well poo; our player died."""
if not self.node:
return
if self._show_death:
bs.animate(
self.node, 'opacity', {
0.00: 1.0,
0.05: 0.0,
0.10: 1.0,
0.15: 0.0,
0.20: 1.0,
0.25: 0.0,
0.30: 1.0,
0.35: 0.0,
0.40: 1.0,
0.45: 0.0,
0.50: 1.0,
0.55: 0.2
})
lives = self._player.lives
if lives == 0:
bs.timer(0.6, self.update_for_lives)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
self.node.delete()
return None
return super().handlemessage(msg)
@dataclass
class SpawnInfo:
"""Spawning info for a particular bot type."""
spawnrate: float
increase: float
dincrease: float
class Player(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
super().__init__()
self.death_time: float | None = None
self.lives = 0
self.icons: list[Icon] = []
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.survival_seconds: int | None = None
self.spawn_order: list[Player] = []
# ba_meta export bascenev1.GameActivity
class UltimateLastStand(bs.TeamGameActivity[Player, Team]):
"""Minigame involving dodging falling bombs."""
name = 'Ultimate Last Stand'
description = 'Only the strongest will stand at the end.'
scoreconfig = bs.ScoreConfig(label='Survived',
scoretype=bs.ScoreType.SECONDS,
none_is_winner=True)
# Print messages when players die (since its meaningful in this game).
announce_player_deaths = True
# Don't allow joining after we start
# (would enable leave/rejoin tomfoolery).
allow_mid_activity_joins = False
@classmethod
def get_available_settings(
cls,
sessiontype: type[bs.Session]) -> list[babase.Setting]:
settings = [
bs.IntSetting(
'Lives Per Player',
default=1,
min_value=1,
max_value=10,
increment=1,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
bs.BoolSetting('Epic Mode', default=False),
]
if issubclass(sessiontype, bs.DualTeamSession):
settings.append(
bs.BoolSetting('Balance Total Lives', default=False))
return settings
# We're currently hard-coded for one map.
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return ['Rampage']
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.DualTeamSession)
or issubclass(sessiontype, bs.FreeForAllSession))
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._start_time: float | None = None
self._vs_text: bs.Actor | None = None
self._round_end_timer: bs.Timer | None = None
self._lives_per_player = int(settings['Lives Per Player'])
self._balance_total_lives = bool(
settings.get('Balance Total Lives', False))
self._epic_mode = settings.get('Epic Mode', True)
self._last_player_death_time: float | None = None
self._timer: OnScreenTimer | None = None
self._tntspawner: TNTSpawner | None = None
self._new_wave_sound = bs.getsound('scoreHit01')
self._bots = SpazBotSet()
self._tntspawnpos = (0, 5.5, -6)
self.spazList = []
# Base class overrides:
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC
if self._epic_mode else bs.MusicType.SURVIVAL)
self.node = bs.newnode('text',
attrs={
'v_attach': 'bottom',
'h_align': 'center',
'color': (0.83, 0.69, 0.21),
'flatness': 0.5,
'shadow': 0.5,
'position': (0, 75),
'scale': 0.7,
'text': 'By Cross Joy'
})
# For each bot type: [spawnrate, increase, d_increase]
self._bot_spawn_types = {
BomberBot: SpawnInfo(1.00, 0.00, 0.000),
BomberBotPro: SpawnInfo(0.00, 0.05, 0.001),
BomberBotProShielded: SpawnInfo(0.00, 0.02, 0.002),
BrawlerBot: SpawnInfo(1.00, 0.00, 0.000),
BrawlerBotPro: SpawnInfo(0.00, 0.05, 0.001),
BrawlerBotProShielded: SpawnInfo(0.00, 0.02, 0.002),
TriggerBot: SpawnInfo(0.30, 0.00, 0.000),
TriggerBotPro: SpawnInfo(0.00, 0.05, 0.001),
TriggerBotProShielded: SpawnInfo(0.00, 0.02, 0.002),
ChargerBot: SpawnInfo(0.30, 0.05, 0.000),
StickyBot: SpawnInfo(0.10, 0.03, 0.001),
IceBot: SpawnInfo(0.10, 0.03, 0.001),
ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002)
} # yapf: disable
# Some base class overrides:
self.default_music = (bs.MusicType.EPIC
if self._epic_mode else bs.MusicType.SURVIVAL)
if self._epic_mode:
self.slow_motion = True
def get_instance_description(self) -> str | Sequence:
return 'Only the strongest team will stand at the end.' if isinstance(
self.session,
bs.DualTeamSession) else 'Only the strongest will stand at the end.'
def get_instance_description_short(self) -> str | Sequence:
return 'Only the strongest team will stand at the end.' if isinstance(
self.session,
bs.DualTeamSession) else 'Only the strongest will stand at the end.'
def on_transition_in(self) -> None:
super().on_transition_in()
bs.timer(1.3, self._new_wave_sound.play)
def on_player_join(self, player: Player) -> None:
player.lives = self._lives_per_player
# Don't waste time doing this until begin.
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 on_begin(self) -> None:
super().on_begin()
bs.animate_array(node=self.node, attr='color', size=3, keys={
0.0: (0.5, 0.5, 0.5),
0.8: (0.83, 0.69, 0.21),
1.6: (0.5, 0.5, 0.5)
}, loop=True)
bs.timer(0.001, bs.WeakCall(self._start_bot_updates))
self._tntspawner = TNTSpawner(position=self._tntspawnpos,
respawn_time=10.0)
self._timer = OnScreenTimer()
self._timer.start()
self.setup_standard_powerup_drops()
# Check for immediate end (if we've only got 1 player, etc).
self._start_time = bs.time()
# If balance-team-lives is on, add lives to the smaller team until
# total lives match.
if (isinstance(self.session, bs.DualTeamSession)
and self._balance_total_lives and self.teams[0].players
and self.teams[1].players):
if self._get_total_team_lives(
self.teams[0]) < self._get_total_team_lives(self.teams[1]):
lesser_team = self.teams[0]
greater_team = self.teams[1]
else:
lesser_team = self.teams[1]
greater_team = self.teams[0]
add_index = 0
while (self._get_total_team_lives(lesser_team) <
self._get_total_team_lives(greater_team)):
lesser_team.players[add_index].lives += 1
add_index = (add_index + 1) % len(lesser_team.players)
bs.timer(1.0, self._update, repeat=True)
self._update_icons()
# We could check game-over conditions at explicit trigger points,
# but lets just do the simple thing and poll it.
def _update_icons(self) -> None:
# pylint: disable=too-many-branches
# In free-for-all mode, everyone is just lined up along the bottom.
if isinstance(self.session, bs.FreeForAllSession):
count = len(self.teams)
x_offs = 85
xval = x_offs * (count - 1) * -0.5
for team in self.teams:
if len(team.players) == 1:
player = team.players[0]
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
# In teams mode we split up teams.
else:
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 on_player_leave(self, player: Player) -> None:
# Augment default behavior.
super().on_player_leave(player)
player.icons = []
# Update icons in a moment since our team will be gone from the
# list then.
bs.timer(0, self._update_icons)
# If the player to leave was the last in spawn order and had
# their final turn currently in-progress, mark the survival time
# for their team.
if self._get_total_team_lives(player.team) == 0:
assert self._start_time is not None
player.team.survival_seconds = int(bs.time() - self._start_time)
# A departing player may trigger game-over.
# overriding the default character spawning..
def spawn_player(self, player: Player) -> bs.Actor:
actor = self.spawn_player_spaz(player)
bs.timer(0.3, babase.Call(self._print_lives, 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 bascenev1lib.actor import popuptext
# We get called in a timer so it's possible our player has left/etc.
if not player or not player.is_alive() or not player.node:
return
popuptext.PopupText('x' + str(player.lives - 1),
color=(1, 1, 0, 1),
offset=(0, -0.8, 0),
random_offset=0.0,
scale=1.8,
position=player.node.position).autoretain()
def _get_total_team_lives(self, team: Team) -> int:
return sum(player.lives for player in team.players)
def _start_bot_updates(self) -> None:
self._bot_update_interval = 3.3 - 0.3 * (len(self.players))
self._update_bots()
self._update_bots()
if len(self.players) > 2:
self._update_bots()
if len(self.players) > 3:
self._update_bots()
self._bot_update_timer = bs.Timer(self._bot_update_interval,
bs.WeakCall(self._update_bots))
def _update_bots(self) -> None:
assert self._bot_update_interval is not None
self._bot_update_interval = max(0.5, self._bot_update_interval * 0.98)
self._bot_update_timer = bs.Timer(self._bot_update_interval,
bs.WeakCall(self._update_bots))
botspawnpts: list[Sequence[float]] = [[-5.0, 5.5, -4.14],
[0.0, 5.5, -4.14],
[5.0, 5.5, -4.14]]
for player in self.players:
try:
if player.is_alive():
assert isinstance(player.actor, PlayerSpaz)
assert player.actor.node
except Exception:
babase.print_exception('Error updating bots.')
spawnpt = random.choice(
[botspawnpts[0], botspawnpts[1], botspawnpts[2]])
spawnpt = (spawnpt[0] + 3.0 * (random.random() - 0.5), spawnpt[1],
2.0 * (random.random() - 0.5) + spawnpt[2])
# Normalize our bot type total and find a random number within that.
total = 0.0
for spawninfo in self._bot_spawn_types.values():
total += spawninfo.spawnrate
randval = random.random() * total
# Now go back through and see where this value falls.
total = 0
bottype: type[SpazBot] | None = None
for spawntype, spawninfo in self._bot_spawn_types.items():
total += spawninfo.spawnrate
if randval <= total:
bottype = spawntype
break
spawn_time = 1.0
assert bottype is not None
self._bots.spawn_bot(bottype, pos=spawnpt, spawn_time=spawn_time)
# After every spawn we adjust our ratios slightly to get more
# difficult.
for spawninfo in self._bot_spawn_types.values():
spawninfo.spawnrate += spawninfo.increase
spawninfo.increase += spawninfo.dincrease
# Various high-level game events come through this method.
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
curtime = bs.time()
# Record the player's moment of death.
# assert isinstance(msg.spaz.player
msg.getplayer(Player).death_time = curtime
player: Player = msg.getplayer(Player)
player.lives -= 1
if player.lives < 0:
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 player.lives == 0:
SpazFactory.get().single_player_death_sound.play()
# If we hit zero lives, we're dead (and our team might be too).
if player.lives == 0:
# If the whole team is now dead, mark their survival time.
if self._get_total_team_lives(player.team) == 0:
assert self._start_time is not None
player.team.survival_seconds = int(bs.time() -
self._start_time)
else:
# Otherwise, in regular mode, respawn.
self.respawn_player(player)
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 _update(self) -> None:
# If we're down to 1 or fewer living teams, start a timer to end
# the game (allows the dust to settle and draws to occur if deaths
# are close enough).
if len(self._get_living_teams()) < 2:
self._round_end_timer = bs.Timer(0.5, self.end_game)
def end_game(self) -> None:
# 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 = bs.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:
# Submit the score value in milliseconds.
results.set_team_score(team, team.survival_seconds)
self.end(results=results)

885
dist/ba_root/mods/games/zombie_horde.py vendored Normal file
View file

@ -0,0 +1,885 @@
# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
import _babase
import copy
import random
from babase import _math
from bascenev1._coopsession import CoopSession
from bascenev1._messages import PlayerDiedMessage, StandMessage
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.game.elimination import Icon, Player
from bascenev1lib.actor.spaz import PickupMessage
from bascenev1lib.actor.spazbot import SpazBotSet, BrawlerBot, SpazBotDiedMessage
from bascenev1lib.actor.spazfactory import SpazFactory
if TYPE_CHECKING:
from typing import Any, Sequence
class PlayerSpaz_Zom(PlayerSpaz):
def handlemessage(self, m: Any) -> Any:
if isinstance(m, bs.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, bs.FreezeMessage):
pass
elif isinstance(m, PickupMessage):
if not self.node:
return None
try:
collision = bs.getcollision()
opposingnode = collision.opposingnode
opposingbody = collision.opposingbody
except bs.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, bs.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 = bs.Timer(0.05,
bs.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 = []
babase.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 bs.getactivity().players:
assert isinstance(player, bs.Player)
try:
if player.is_alive():
assert isinstance(player.actor, Spaz)
assert player.actor.node
if player.lives > 0:
player_pts.append(
(babase.Vec3(player.actor.node.position),
babase.Vec3(player.actor.node.velocity)))
except Exception:
babase.print_exception('Error on bot-set _update.')
for bot in bot_list:
bot.set_player_points(player_pts)
bot.update_ai()
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
self.spawn_order: list[Player] = []
# ba_meta export bascenev1.GameActivity
class ZombieHorde(bs.TeamGameActivity[Player, Team]):
name = 'Zombie Horde'
description = 'Kill walkers for points!'
scoreconfig = bs.ScoreConfig(label='Score',
scoretype=bs.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[bs.Session]) -> list[babase.Setting]:
settings = [
bs.IntSetting(
'Lives Per Player',
default=1,
min_value=1,
max_value=10,
increment=1,
),
bs.IntSetting(
'Max Zombies',
default=10,
min_value=5,
max_value=50,
increment=5,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
bs.BoolSetting('Epic Mode', default=False),
]
if issubclass(sessiontype, bs.DualTeamSession):
settings.append(bs.BoolSetting('Solo Mode', default=False))
settings.append(
bs.BoolSetting('Balance Total Lives', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.DualTeamSession)
or issubclass(sessiontype, bs.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return bs.app.classic.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._start_time: float | None = None
self._vs_text: bs.Actor | None = None
self._round_end_timer: bs.Timer | None = 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 = (bs.MusicType.EPIC
if self._epic_mode else bs.MusicType.SURVIVAL)
self.spazList = []
self.zombieQ = 0
activity = bs.getactivity()
my_factory = SpazFactory.get()
appears = ['Kronk', 'Zoe', 'Pixel', 'Agent Johnson',
'Bones', 'Frosty', 'Kronk2']
myAppear = copy.copy(babase.app.classic.spaz_appearances['Kronk'])
myAppear.name = 'Kronk2'
babase.app.classic.spaz_appearances['Kronk2'] = myAppear
for appear in appears:
my_factory.get_media(appear)
med = my_factory.spaz_media
med['Kronk2']['head_mesh'] = med['Zoe']['head_mesh']
med['Kronk2']['color_texture'] = med['Agent Johnson']['color_texture']
med['Kronk2']['color_mask_texture'] = med['Pixel']['color_mask_texture']
med['Kronk2']['torso_mesh'] = med['Bones']['torso_mesh']
med['Kronk2']['pelvis_mesh'] = med['Pixel']['pelvis_mesh']
med['Kronk2']['upper_arm_mesh'] = med['Frosty']['upper_arm_mesh']
med['Kronk2']['forearm_mesh'] = med['Frosty']['forearm_mesh']
med['Kronk2']['hand_mesh'] = med['Bones']['hand_mesh']
med['Kronk2']['upper_leg_mesh'] = med['Bones']['upper_leg_mesh']
med['Kronk2']['lower_leg_mesh'] = med['Pixel']['lower_leg_mesh']
med['Kronk2']['toes_mesh'] = med['Bones']['toes_mesh']
def get_instance_description(self) -> str | Sequence:
return ('Kill walkers for points! ',
'Dead player walker: 2 points!') if isinstance(
self.session, bs.DualTeamSession) else (
'Kill walkers for points! Dead player walker: 2 points!')
def get_instance_description_short(self) -> str | Sequence:
return ('Kill walkers for points! ',
'Dead player walker: 2 points!') if isinstance(
self.session, bs.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 = []
bs.broadcastmessage(
babase.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
player.lives = self._lives_per_player
if self._solo_mode:
player.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 = bs.time()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self.zombieQ = 1
if self._solo_mode:
self._vs_text = bs.NodeActor(
bs.newnode('text',
attrs={
'position': (0, 105),
'h_attach': 'center',
'h_align': 'center',
'maxwidth': 200,
'shadow': 0.5,
'vr_depth': 390,
'scale': 0.6,
'v_attach': 'bottom',
'color': (0.8, 0.8, 0.3, 1.0),
'text': babase.Lstr(resource='vsText')
}))
# If balance-team-lives is on, add lives to the smaller team until
# total lives match.
if (isinstance(self.session, bs.DualTeamSession)
and self._balance_total_lives and self.teams[0].players
and self.teams[1].players):
if self._get_total_team_lives(
self.teams[0]) < self._get_total_team_lives(self.teams[1]):
lesser_team = self.teams[0]
greater_team = self.teams[1]
else:
lesser_team = self.teams[1]
greater_team = self.teams[0]
add_index = 0
while (self._get_total_team_lives(lesser_team) <
self._get_total_team_lives(greater_team)):
lesser_team.players[add_index].lives += 1
add_index = (add_index + 1) % len(lesser_team.players)
self._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.
bs.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, bs.FreeForAllSession):
count = len(self.teams)
x_offs = 85
xval = x_offs * (count - 1) * -0.5
for team in self.teams:
if len(team.players) == 1:
player = team.players[0]
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
# In teams mode we split up teams.
else:
if self._solo_mode:
# First off, clear out all icons.
for player in self.players:
player.icons = []
# Now for each team, cycle through our available players
# adding icons.
for team in self.teams:
if team.id == 0:
xval = -60
x_offs = -78
else:
xval = 60
x_offs = 78
is_first = True
test_lives = 1
while True:
players_with_lives = [
p for p in team.spawn_order
if p and p.lives >= test_lives
]
if not players_with_lives:
break
for player in players_with_lives:
player.icons.append(
Icon(player,
position=(xval, (40 if is_first else 25)),
scale=1.0 if is_first else 0.5,
name_maxwidth=130 if is_first else 75,
name_scale=0.8 if is_first else 1.0,
flatness=0.0 if is_first else 1.0,
shadow=0.5 if is_first else 1.0,
show_death=is_first,
show_lives=False))
xval += x_offs * (0.8 if is_first else 0.56)
is_first = False
test_lives += 1
# Non-solo mode.
else:
for team in self.teams:
if team.id == 0:
xval = -50
x_offs = -85
else:
xval = 50
x_offs = 85
for player in team.players:
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
def _get_spawn_point(self, player: Player) -> babase.Vec3 | None:
del player # Unused.
# In solo-mode, if there's an existing live player on the map, spawn at
# whichever spot is farthest from them (keeps the action spread out).
if self._solo_mode:
living_player = None
living_player_pos = None
for team in self.teams:
for tplayer in team.players:
if tplayer.is_alive():
assert tplayer.node
ppos = tplayer.node.position
living_player = tplayer
living_player_pos = ppos
break
if living_player:
assert living_player_pos is not None
player_pos = babase.Vec3(living_player_pos)
points: list[tuple[float, babase.Vec3]] = []
for team in self.teams:
start_pos = babase.Vec3(self.map.get_start_position(team.id))
points.append(
((start_pos - player_pos).length(), start_pos))
# Hmm.. we need to sorting vectors too?
points.sort(key=lambda x: x[0])
return points[-1][1]
return None
def spawn_player(self, player: Player) -> bs.Actor:
position = self.map.get_ffa_start_position(self.players)
angle = 20
name = player.getname()
light_color = _math.normalized_color(player.color)
display_color = _babase.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)))
bs.Sound.play(self._spawn_sound, 1, position=spaz.node.position)
light = bs.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
bs.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
bs.timer(0.5, light.delete)
if not self._solo_mode:
bs.timer(0.3, babase.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: float | None = 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 bascenev1lib.actor.respawnicon import RespawnIcon
player.customdata['respawn_timer'] = bs.Timer(
respawn_time, bs.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: PlayerT) -> None:
"""
A utility method which calls self.spawn_player() *only* if the
bs.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: PlayerT) -> bs.Actor:
position = self.map.get_ffa_start_position(self.players)
angle = 20
name = player.getname()
light_color = _math.normalized_color(player.color)
display_color = _babase.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)))
bs.Sound.play(self._spawn_sound, 1, position=spaz.node.position)
light = bs.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
bs.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
bs.timer(0.5, light.delete)
if not self._solo_mode:
bs.timer(0.3, babase.Call(self._print_lives, player))
for icon in player.icons:
icon.handle_player_spawned()
return spaz
def _print_lives(self, player: Player) -> None:
from bascenev1lib.actor import popuptext
# We get called in a timer so it's possible our player has left/etc.
if not player or not player.is_alive() or not player.node:
return
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.
bs.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, bs.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:
babase.print_error(
"Got lives < 0 in Elim; this shouldn't happen. solo:" +
str(self._solo_mode))
player.lives = 0
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_died()
# Play big death sound on our last death
# or for every one in solo mode.
if self._solo_mode or player.lives == 0:
SpazFactory.get().single_player_death_sound.play()
# If we hit zero lives, we're dead (and our team might be too).
if player.lives == 0:
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)
# bs.PopupText("died",position=self._position,color=popupColor,scale=popupScale).autoRetain()
super().handlemessage(msg)
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 = bs.Timer(0.5, self.end_game)
else:
self._round_end_timer = bs.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]
bs.timer(0.1, babase.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 = bs.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)
# ba_meta export bascenev1.GameActivity
class ZombieHordeCoop(ZombieHorde):
name = 'Zombie Horde'
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return ['Football Stadium']
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.CoopSession))
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 not any(player.is_alive() for player in self.teams[0].players):
self._round_end_timer = bs.Timer(0.5, self.end_game)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
bs.TeamGameActivity.handlemessage(self, msg)
player: Player = msg.getplayer(Player)
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_died()
else:
super().handlemessage(msg)
# ba_meta export plugin
class ZombieHordeLevel(babase.Plugin):
def on_app_running(self) -> None:
babase.app.classic.add_coop_practice_level(
bs._level.Level(
'Zombie Horde',
gametype=ZombieHordeCoop,
settings={},
preview_texture_name='footballStadiumPreview',
)
)