added mini games

This commit is contained in:
Ayush Saini 2023-08-14 00:59:21 +05:30
parent 985fe8c738
commit d119302b6e
22 changed files with 10312 additions and 6 deletions

View file

@ -143,6 +143,9 @@
"ladoo",
"barfi"
],
"Default Player Profiles": {
"Client Input Device #1": "__account__"
},
"Fleet Zone Pings": {
"prod": {
"delhi": 16.50450974109344,

View file

@ -145,12 +145,10 @@
],
"Fleet Zone Pings": {
"prod": {
"bangkok": 179.38869999852614,
"delhi": 21.102889599649643,
"dubai": 162.18640000079176,
"hyderabad": 51.26196139941021,
"kolkata": 38.122235600676504,
"mumbai": 36.901065400630614
"delhi": 16.50450974109344,
"hyderabad": 40.392026402187184,
"kolkata": 41.73070900011953,
"mumbai": 42.654999799975485
}
},
"Free-for-All Playlist Randomize": true,

View file

@ -0,0 +1,514 @@
# Released under the MIT License. See LICENSE for details.
#
"""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
if TYPE_CHECKING:
from typing import (Any, Tuple, 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 AllianceEliminationGame(bs.TeamGameActivity[Player, Team]):
"""Game type where last player(s) left alive win."""
name = 'Alliance Elimination'
description = 'Fight in groups of duo, trio, or more.\nLast remaining alive wins.'
scoreconfig = 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.IntSetting(
'Players Per Team In Arena',
default=2,
min_value=2,
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('Balance Total Lives', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return 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
self._epic_mode = bool(settings['Epic Mode'])
self._lives_per_player = int(settings['Lives Per Player'])
self._time_limit = float(settings['Time Limit'])
self._balance_total_lives = bool(
settings.get('Balance Total Lives', False))
self._players_per_team_in_arena = int(
settings['Players Per Team In Arena'])
# Base class overrides:
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC
if self._epic_mode else bs.MusicType.SURVIVAL)
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:
# 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
bs.broadcastmessage(
babase.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
player.lives = self._lives_per_player
player.team.spawn_order.append(player)
self._update_alliance_mode()
# Don't waste time doing this until begin.
if self.has_begun():
self._update_icons()
def on_begin(self) -> None:
super().on_begin()
self._start_time = bs.time()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._vs_text = bs.NodeActor(
bs.newnode('text',
attrs={
'position': (0, 92),
'h_attach': 'center',
'h_align': 'center',
'maxwidth': 200,
'shadow': 0.5,
'vr_depth': 390,
'scale': 0.6,
'v_attach': 'bottom',
'color': (0.8, 0.8, 0.3, 1.0),
'text': 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_alliance_mode(self) -> None:
# For both teams, find the first player on the spawn order list with
# lives remaining and spawn them if they're not alive.
for team in self.teams:
# Prune dead players from the spawn order.
players_spawned = 0
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
self._update_icons()
players_spawned += 1
if players_spawned >= self._players_per_team_in_arena:
break
def _update_icons(self) -> None:
# pylint: disable=too-many-branches
# First off, clear out all icons.
for player in self.players:
player.icons = []
# Now for each team, cycle through our available players
# adding icons.
for team in self.teams:
if team.id == 0:
xval = -60
x_offs = -78
else:
xval = 60
x_offs = 78
nplayers = self._players_per_team_in_arena
test_lives = 1
while True:
players_with_lives = [
p for p in team.spawn_order
if p and p.lives >= test_lives
]
if not players_with_lives:
break
for player in players_with_lives:
player.icons.append(
Icon(player,
position=(xval, (36 if nplayers > 0 else 25)),
scale=0.9 if nplayers > 0 else 0.5,
name_maxwidth=85 if nplayers > 0 else 75,
name_scale=0.8 if nplayers > 0 else 1.0,
flatness=0.0 if nplayers > 0 else 1.0,
shadow=0.5 if nplayers > 0 else 1.0,
show_death=True if nplayers > 0 else False,
show_lives=False))
xval += x_offs * (0.85 if nplayers > 0 else 0.56)
nplayers -= 1
test_lives += 1
def _get_spawn_point(self, player: Player) -> Optional[babase.Vec3]:
return None
def spawn_player(self, player: Player) -> bs.Actor:
actor = self.spawn_player_spaz(player, self._get_spawn_point(player))
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_spawned()
return actor
def _print_lives(self, player: Player) -> None:
from 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 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 Alliance Elimination; this shouldn't happen.")
player.lives = 0
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_died()
# Play big death sound on our last death
# or for every one.
if player.lives == 0:
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)
# Put ourself at the back of the spawn order.
player.team.spawn_order.remove(player)
player.team.spawn_order.append(player)
def _update(self) -> None:
# For both teams, find the first player on the spawn order
# list with lives remaining and spawn them if they're not alive.
for team in self.teams:
# Prune dead players from the spawn order.
team.spawn_order = [p for p in team.spawn_order if p]
players_spawned = 0
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
self._update_icons()
players_spawned += 1
if players_spawned >= self._players_per_team_in_arena:
break
# If we're down to 1 or fewer living teams, start a timer to end
# the game (allows the dust to settle and draws to occur if deaths
# are close enough).
if len(self._get_living_teams()) < 2:
self._round_end_timer = 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)

195
dist/ba_root/mods/games/arms_race.py vendored Normal file
View file

@ -0,0 +1,195 @@
# Ported by your friend: Freaku
# Join BCS:
# https://discord.gg/ucyaesh
# ba_meta require api 8
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional
class State:
def __init__(self, bomb=None, grab=False, punch=False, curse=False, required=False, final=False, name=''):
self.bomb = bomb
self.grab = grab
self.punch = punch
self.pickup = False
self.curse = curse
self.required = required or final
self.final = final
self.name = name
self.next = None
self.index = None
def apply(self, spaz):
spaz.disconnect_controls_from_player()
spaz.connect_controls_to_player(enable_punch=self.punch,
enable_bomb=self.bomb,
enable_pickup=self.grab)
if self.curse:
spaz.curse_time = -1
spaz.curse()
if self.bomb:
spaz.bomb_type = self.bomb
spaz.set_score_text(self.name)
def get_setting(self):
return (self.name)
states = [State(bomb='normal', name='Basic Bombs'),
State(bomb='ice', name='Frozen Bombs'),
State(bomb='sticky', name='Sticky Bombs'),
State(bomb='impact', name='Impact Bombs'),
State(grab=True, name='Grabbing only'),
State(punch=True, name='Punching only'),
State(curse=True, name='Cursed', final=True)]
class Player(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self):
self.state = None
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export bascenev1.GameActivity
class ArmsRaceGame(bs.TeamGameActivity[Player, Team]):
"""A game type based on acquiring kills."""
name = 'Arms Race'
description = 'Upgrade your weapon by eliminating enemies.\nWin the match by being the first player\nto get a kill while cursed.'
# 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.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)]
for state in states:
if not state.required:
settings.append(bs.BoolSetting(state.get_setting(), default=True))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.DualTeamSession)
or issubclass(sessiontype, bs.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return bs.app.classic.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self.states = [s for s in states if settings.get(s.name, True)]
for i, state in enumerate(self.states):
if i < len(self.states) and not state.final:
state.next = self.states[i + 1]
state.index = i
self._dingsound = bs.getsound('dingSmall')
self._epic_mode = bool(settings['Epic Mode'])
self._time_limit = float(settings['Time Limit'])
# 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 'Upgrade your weapon by eliminating enemies.'
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'kill ${ARG1} enemies', len(self.states)
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
# self.setup_standard_powerup_drops()
def on_player_join(self, player):
if player.state is None:
player.state = self.states[0]
self.spawn_player(player)
# overriding the default character spawning..
def spawn_player(self, player):
if player.state is None:
player.state = self.states[0]
super().spawn_player(player)
player.state.apply(player.actor)
def isValidKill(self, m):
if m.getkillerplayer(Player) is None:
return False
if m.getkillerplayer(Player).team is m.getplayer(Player).team:
return False
return True
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
if self.isValidKill(msg):
self.stats.player_scored(msg.getkillerplayer(Player), 10, kill=True)
if not msg.getkillerplayer(Player).state.final:
msg.getkillerplayer(Player).state = msg.getkillerplayer(Player).state.next
msg.getkillerplayer(Player).state.apply(msg.getkillerplayer(Player).actor)
else:
msg.getkillerplayer(Player).team.score += 1
self.end_game()
self.respawn_player(msg.getplayer(Player))
else:
return super().handlemessage(msg)
return None
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

524
dist/ba_root/mods/games/big_ball.py vendored Normal file
View file

@ -0,0 +1,524 @@
# Made by MythB
# Ported by: MysteriousBoi
# ba_meta require api 8
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
import random
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
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
# goalpost
class FlagKale(bs.Actor):
def __init__(self, position=(0, 2.5, 0), color=(1, 1, 1)):
super().__init__()
activity = self.getactivity()
shared = SharedObjects.get()
self.node = bs.newnode('flag',
attrs={'position': (position[0], position[1]+0.75, position[2]),
'color_texture': activity._flagKaleTex,
'color': color,
'materials': [shared.object_material, activity._kaleMaterial],
},
delegate=self)
def handleMessage(self, m):
if isinstance(m, bs.DieMessage):
if self.node.exists():
self.node.delete()
elif isinstance(m, bs.OutOfBoundsMessage):
self.handlemessage(bs.DieMessage())
else:
super().handlemessage(msg)
class Puck(bs.Actor):
def __init__(self, position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
shared = SharedObjects.get()
activity = self.getactivity()
# Spawn just above the provided point.
self._spawn_pos = (position[0], position[1] + 1.0, position[2])
self.last_players_to_touch: Dict[int, Player] = {}
self.scored = False
assert activity is not None
assert isinstance(activity, BBGame)
pmats = [shared.object_material, activity.puck_material]
self.node = bs.newnode('prop',
delegate=self,
attrs={
'mesh': activity._ballModel,
'color_texture': activity._ballTex,
'body': 'sphere',
'reflection': 'soft',
'reflection_scale': [0.2],
'shadow_size': 0.8,
'is_area_of_interest': True,
'position': self._spawn_pos,
'materials': pmats,
'body_scale': 4,
'mesh_scale': 1,
'density': 0.02})
bs.animate(self.node, 'mesh_scale', {0: 0, 0.2: 1.3, 0.26: 1})
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.team.id] = s_player
else:
super().handlemessage(msg)
# for night mode: using a actor with large shadow and little mesh scale. Better then tint i think, players and objects more visible
class NightMod(bs.Actor):
def __init__(self, position=(0, 0, 0)):
super().__init__()
shared = SharedObjects.get()
activity = self.getactivity()
# spawn just above the provided point
self._spawnPos = (position[0], position[1], position[2])
self.node = bs.newnode("prop",
attrs={'mesh': activity._nightModel,
'color_texture': activity._nightTex,
'body': 'sphere',
'reflection': 'soft',
'body_scale': 0.1,
'mesh_scale': 0.001,
'density': 0.010,
'reflection_scale': [0.23],
'shadow_size': 999999.0,
'is_area_of_interest': True,
'position': self._spawnPos,
'materials': [activity._nightMaterial]
},
delegate=self)
def handlemssage(self, m):
super().handlemessage(m)
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 BBGame(bs.TeamGameActivity[Player, Team]):
name = 'Big Ball'
description = 'Score some goals.\nFlags are goalposts.\nScored team players get boxing gloves,\nNon-scored team players getting shield (if Grant Powers on Score).\nYou can also set Night Mode!'
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('Epic Mode', True),
bs.BoolSetting('Night Mode', False),
bs.BoolSetting('Grant Powers on Score', 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 ['Football Stadium']
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._ballModel = bs.getmesh("shield")
self._ballTex = bs.gettexture("eggTex1")
self._ballSound = bs.getsound("impactMedium2")
self._flagKaleTex = bs.gettexture("star")
self._kaleSound = bs.getsound("metalHit")
self._nightModel = bs.getmesh("shield")
self._nightTex = bs.gettexture("black")
self._kaleMaterial = bs.Material()
# add friction to flags for standing our position (as far as)
self._kaleMaterial.add_actions(conditions=("they_have_material", shared.footing_material),
actions=(("modify_part_collision", "friction", 9999.5)))
self._kaleMaterial.add_actions(conditions=(("we_are_younger_than", 1), 'and',
("they_have_material", shared.object_material)),
actions=(("modify_part_collision", "collide", False)))
self._kaleMaterial.add_actions(conditions=("they_have_material", shared.pickup_material),
actions=(("modify_part_collision", "collide", False)))
self._kaleMaterial.add_actions(
conditions=('they_have_material', shared.object_material),
actions=(('impact_sound', self._kaleSound, 2, 5)))
# we dont wanna hit the night so
self._nightMaterial = bs.Material()
self._nightMaterial.add_actions(conditions=(('they_have_material', shared.pickup_material), 'or',
('they_have_material', shared.attack_material)),
actions=(('modify_part_collision', 'collide', False)))
# we also dont want anything moving it
self._nightMaterial.add_actions(
conditions=(('they_have_material', shared.object_material), 'or',
('they_dont_have_material', shared.footing_material)),
actions=(('modify_part_collision', 'collide', False),
('modify_part_collision', 'physical', False)))
self.puck_material = bs.Material()
self.puck_material.add_actions(actions=(('modify_part_collision',
'friction', 0.5)))
self.puck_material.add_actions(conditions=('they_have_material',
shared.pickup_material),
actions=('modify_part_collision',
'collide', False))
self.puck_material.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._ballSound, 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._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._puck_spawn_pos: Optional[Sequence[float]] = None
self._score_regions: Optional[List[bs.NodeActor]] = None
self._puck: Optional[Puck] = None
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
self._nm = bool(settings['Night Mode'])
self._grant_power = bool(settings['Grant Powers on Score'])
self._epic_mode = bool(settings['Epic Mode'])
# Base class overrides.
self.slow_motion = self._epic_mode
def get_instance_description(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'Score a goal.'
return 'Score ${ARG1} goals.', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'score a goal'
return 'score ${ARG1} goals', self._score_to_win
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._puck_spawn_pos = self.map.get_flag_position(None)
self._spawn_puck()
# for night mode we need night actor. And same goodies for nigh mode
if self._nm:
self._nightSpawny(), self._flagKaleFlash()
# Set up the two score regions.
defs = self.map.defs
self._score_regions = []
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': (13.75, 0.85744967453, 0.1095578275),
'scale': (1.05, 1.1, 3.8),
'type': 'box',
'materials': [self._score_region_material]
})))
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': (-13.55, 0.85744967453, 0.1095578275),
'scale': (1.05, 1.1, 3.8),
'type': 'box',
'materials': [self._score_region_material]
})))
self._update_scoreboard()
self._chant_sound.play()
def _nightSpawny(self):
self.MythBrk = NightMod(position=(0, 0.05744967453, 0))
# spawn some goodies on nightmode for pretty visuals
def _flagKaleFlash(self):
# flags positions
kale1 = (-12.45, 0.05744967453, -2.075)
kale2 = (-12.45, 0.05744967453, 2.075)
kale3 = (12.66, 0.03986567039, 2.075)
kale4 = (12.66, 0.03986567039, -2.075)
flash = bs.newnode("light",
attrs={'position': kale1,
'radius': 0.15,
'color': (1.0, 1.0, 0.7)})
flash = bs.newnode("light",
attrs={'position': kale2,
'radius': 0.15,
'color': (1.0, 1.0, 0.7)})
flash = bs.newnode("light",
attrs={'position': kale3,
'radius': 0.15,
'color': (0.7, 1.0, 1.0)})
flash = bs.newnode("light",
attrs={'position': kale4,
'radius': 0.15,
'color': (0.7, 1.0, 1.0)})
# flags positions
def _flagKalesSpawn(self):
for team in self.teams:
if team.id == 0:
_colorTeam0 = team.color
if team.id == 1:
_colorTeam1 = team.color
self._MythB = FlagKale(position=(-12.45, 0.05744967453, -2.075), color=_colorTeam0)
self._MythB2 = FlagKale(position=(-12.45, 0.05744967453, 2.075), color=_colorTeam0)
self._MythB3 = FlagKale(position=(12.66, 0.03986567039, 2.075), color=_colorTeam1)
self._MythB4 = FlagKale(position=(12.66, 0.03986567039, -2.075), color=_colorTeam1)
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.team.id] = player
def _kill_puck(self) -> None:
self._puck = None
def _handle_score(self) -> None:
"""A point has been scored."""
assert self._puck is not None
assert self._score_regions is not None
# Our puck might stick around for a second or two
# we don't want it to be able to score again.
if self._puck.scored:
return
region = 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 scored team players to celebrate and give them to boxing gloves
if self._grant_power:
for player in team.players:
try:
player.actor.node.handlemessage(bs.PowerupMessage('punch'))
except:
pass
# 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],
100,
big_message=True)
# End game if we won.
if team.score >= self._score_to_win:
self.end_game()
else:
if self._grant_power:
for player in team.players:
try:
player.actor.node.handlemessage(bs.PowerupMessage('shield'))
except:
pass
self._foghorn_sound.play()
self._cheer_sound.play()
self._puck.scored = True
# 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)
def _spawn_puck(self) -> None:
self._swipsound.play()
self._whistle_sound.play()
self._flagKalesSpawn()
self._flash_puck_spawn()
assert self._puck_spawn_pos is not None
self._puck = Puck(position=self._puck_spawn_pos)
self._puck.light = bs.newnode('light',
owner=self._puck.node,
attrs={'intensity': 0.3,
'height_attenuated': False,
'radius': 0.2,
'color': (0.9, 0.2, 0.9)})
self._puck.node.connectattr('position', self._puck.light, 'position')

242
dist/ba_root/mods/games/boxing.py vendored Normal file
View file

@ -0,0 +1,242 @@
# 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.game.deathmatch import DeathMatchGame
if TYPE_CHECKING:
from typing import Any, Sequence
lang = bs.app.lang.language
if lang == 'Spanish':
name = 'Super Boxeo'
description = ('¡Sin bombas!\n'
'¡Noquea a los enemigos con tus propias manos!\n')
super_jump_text = 'Super Salto'
enable_powerups = 'Habilitar Potenciadores'
else:
name = 'Super Boxing'
description = ('No bombs!\n'
'Knock out your enemies using your bare hands!\n')
super_jump_text = 'Super Jump'
enable_powerups = 'Enable Powerups'
class NewPlayerSpaz(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_jump: bool = False):
super().__init__(player=player,
color=color,
highlight=highlight,
character=character,
powerups_expire=powerups_expire)
from bascenev1lib.gameutils import SharedObjects
shared = SharedObjects.get()
self._super_jump = super_jump
self.jump_mode = False
self.super_jump_material = bs.Material()
self.super_jump_material.add_actions(
conditions=('they_have_material', shared.footing_material),
actions=(
('call', 'at_connect', babase.Call(self.jump_state, True)),
('call', 'at_disconnect', babase.Call(self.jump_state, False))
),
)
self.node.roller_materials += (self.super_jump_material, )
def jump_state(self, mode: bool) -> None:
self.jump_mode = mode
def on_jump_press(self) -> None:
"""
Called to 'press jump' on this spaz;
used by player or AI connections.
"""
if not self.node:
return
t_ms = int(bs.time() * 1000.0)
assert isinstance(t_ms, int)
if t_ms - self.last_jump_time_ms >= self._jump_cooldown:
self.node.jump_pressed = True
self.last_jump_time_ms = t_ms
if self._player.is_alive() and self.jump_mode and (
self._super_jump):
def do_jump():
self.node.handlemessage(
'impulse',
self.node.position[0],
self.node.position[1],
self.node.position[2],
0, 0, 0, 95, 95, 0, 0, 0, 1, 0
)
bs.timer(0.0, do_jump)
bs.timer(0.1, do_jump)
bs.timer(0.2, do_jump)
self._turbo_filter_add_press('jump')
# ba_meta export bascenev1.GameActivity
class BoxingGame(DeathMatchGame):
name = name
description = description
@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(super_jump_text, default=False),
bs.BoolSetting(enable_powerups, default=False),
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
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._super_jump = bool(settings[super_jump_text])
self._enable_powerups = bool(settings[enable_powerups])
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.TO_THE_DEATH
)
def on_begin(self) -> None:
bs.TeamGameActivity.on_begin(self)
self.setup_standard_time_limit(self._time_limit)
if self._enable_powerups:
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 _standard_drop_powerup(self, index: int, expire: bool = True) -> None:
# pylint: disable=cyclic-import
from bascenev1lib.actor.powerupbox import PowerupBox, PowerupBoxFactory
PowerupBox(
position=self.map.powerup_spawn_points[index],
poweruptype=PowerupBoxFactory.get().get_random_powerup_type(
excludetypes=['triple_bombs', 'ice_bombs', 'impact_bombs',
'land_mines', 'sticky_bombs', 'punch']
),
expire=expire,
).autoretain()
def spawn_player(self, player: Player) -> bs.Actor:
import random
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,
super_jump=self._super_jump)
player.actor = spaz
assert spaz.node
spaz.node.name = name
spaz.node.name_color = display_color
# 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.connect_controls_to_player(enable_bomb=False)
spaz.equip_boxing_gloves()
return spaz

636
dist/ba_root/mods/games/collector.py vendored Normal file
View file

@ -0,0 +1,636 @@
# ba_meta require api 8
# (see https://ballistica.net/wiki/meta-tag-system)
'''
Gamemode: Collector
Creator: TheMikirog
Website: https://bombsquadjoyride.blogspot.com/
This is a gamemode purely made by me just to spite unchallenged modders
out there that put out crap to the market.
We don't want gamemodes that are just the existing ones
with some novelties! Gamers deserve more!
In this gamemode you have to kill others in order to get their Capsules.
Capsules can be collected and staked in your inventory,
how many as you please.
After you kill an enemy that carries some of them,
they drop a respective amount of Capsules they carried + two more.
Your task is to collect these Capsules,
get to the flag and score them KOTH style.
You can't score if you don't have any Capsules with you.
The first player or team to get to the required ammount wins.
This is a gamemode all about trying to stay alive
and picking your battles in order to win.
A rare skill in BombSquad, where everyone is overly aggressive.
'''
from __future__ import annotations
import weakref
from enum import Enum
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
import bascenev1 as bs
import random
from bascenev1lib.actor.flag import Flag
from bascenev1lib.actor.popuptext import PopupText
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import Any, Sequence
lang = bs.app.lang.language
if lang == 'Spanish':
name = 'Coleccionista'
description = ('Elimina a tus oponentes para robar sus cápsulas.\n'
'¡Recolecta y anota en el punto de depósito!')
description_ingame = 'Obtén ${ARG1} cápsulas de tus enemigos.'
description_short = 'colecciona ${ARG1} cápsulas'
tips = [(
'¡Si tu oponente cae fuera del mapa, sus cápsulas desapareceran!\n'
'No intestes matar a tus enemigos arrojándolos al vacio.'),
'No te apresures. ¡Puedes perder tus cápsulas rápidamente!',
('¡No dejes que el jugador con más cápsulas anote!\n'
'¡Intenta atraparlo si puedes!'),
('¡Las Capsulas de la Suerte te dan 4 cápsulas en lugar de 2'
'y tienen un 8% de probabilidad de aparecer después de matar'),
('¡No te quedes en un solo lugar! Muevete más rapido que tu enemigo, '
'¡con suerte conseguirás algunas cápsulas!'),
]
capsules_to_win = 'Cápsulas para Ganar'
capsules_death = 'Cápsulas al Morir'
lucky_capsules = 'Cápsulas de la Suerte'
bonus = '¡BONUS!'
full_capacity = '¡Capacidad Completa!'
else:
name = 'Collector'
description = ('Kill your opponents to steal their Capsules.\n'
'Collect them and score at the Deposit point!')
description_ingame = 'Score ${ARG1} capsules from your enemies.'
description_short = 'collect ${ARG1} capsules'
tips = [(
'Making you opponent fall down the pit makes his Capsules wasted!\n'
'Try not to kill enemies by throwing them off the cliff.'),
'Don\'t be too reckless. You can lose your loot quite quickly!',
('Don\'t let the leading player score his Capsules '
'at the Deposit Point!\nTry to catch him if you can!'),
('Lucky Capsules give 4 to your inventory and they have 8% chance '
'of spawning after kill!'),
('Don\'t camp in one place! Make your move first, '
'so hopefully you get some dough!'),
]
capsules_to_win = 'Capsules to Win'
capsules_death = 'Capsules on Death'
lucky_capsules = 'Allow Lucky Capsules'
bonus = 'BONUS!'
full_capacity = 'Full Capacity!'
class FlagState(Enum):
"""States our single flag can be in."""
NEW = 0
UNCONTESTED = 1
CONTESTED = 2
HELD = 3
class Player(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.time_at_flag = 0
self.capsules = 0
self.light = None
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export bascenev1.GameActivity
class CollectorGame(bs.TeamGameActivity[Player, Team]):
name = name
description = description
tips = tips
# 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(
capsules_to_win,
min_value=1,
default=10,
increment=1,
),
bs.IntSetting(
capsules_death,
min_value=1,
max_value=10,
default=2,
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(lucky_capsules, default=True),
bs.BoolSetting('Epic Mode', default=False),
]
return settings
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession) or issubclass(
sessiontype, bs.FreeForAllSession
)
@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)
shared = SharedObjects.get()
self._scoreboard = Scoreboard()
self._score_to_win: int | None = None
self._swipsound = bs.getsound('swip')
self._lucky_sound = bs.getsound('ding')
self._flag_pos: Sequence[float] | None = None
self._flag_state: FlagState | None = None
self._flag: Flag | None = None
self._flag_light: bs.Node | None = None
self._scoring_team: weakref.ref[Team] | None = None
self._time_limit = float(settings['Time Limit'])
self._epic_mode = bool(settings['Epic Mode'])
self._capsules_to_win = int(settings[capsules_to_win])
self._capsules_death = int(settings[capsules_death])
self._lucky_capsules = bool(settings[lucky_capsules])
self._capsules: list[Any] = []
self._capsule_mesh = bs.getmesh('bomb')
self._capsule_tex = bs.gettexture('bombColor')
self._capsule_lucky_tex = bs.gettexture('bombStickyColor')
self._collect_sound = bs.getsound('powerup01')
self._lucky_collect_sound = bs.getsound('cashRegister2')
self._capsule_material = bs.Material()
self._capsule_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=('call', 'at_connect', self._on_capsule_player_collide),
)
self._flag_region_material = bs.Material()
self._flag_region_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
(
'call',
'at_connect',
babase.Call(self._handle_player_flag_region_collide, True),
),
(
'call',
'at_disconnect',
babase.Call(self._handle_player_flag_region_collide, False),
),
),
)
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SCARY
)
def get_instance_description(self) -> str | Sequence:
return description_ingame, self._score_to_win
def get_instance_description_short(self) -> str | Sequence:
return description_short, self._score_to_win
def create_team(self, sessionteam: bs.SessionTeam) -> Team:
return Team()
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
def on_begin(self) -> None:
super().on_begin()
shared = SharedObjects.get()
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._capsules_to_win * max(
1, max(len(t.players) for t in self.teams)
)
self._update_scoreboard()
if isinstance(self.session, bs.FreeForAllSession):
self._flag_pos = self.map.get_flag_position(random.randint(0, 1))
else:
self._flag_pos = self.map.get_flag_position(None)
bs.timer(1.0, self._tick, repeat=True)
self._flag_state = FlagState.NEW
Flag.project_stand(self._flag_pos)
self._flag = Flag(
position=self._flag_pos, touchable=False, color=(1, 1, 1)
)
self._flag_light = bs.newnode(
'light',
attrs={
'position': self._flag_pos,
'intensity': 0.2,
'height_attenuated': False,
'radius': 0.4,
'color': (0.2, 0.2, 0.2),
},
)
# Flag region.
flagmats = [self._flag_region_material, shared.region_material]
bs.newnode(
'region',
attrs={
'position': self._flag_pos,
'scale': (1.8, 1.8, 1.8),
'type': 'sphere',
'materials': flagmats,
},
)
self._update_flag_state()
def _tick(self) -> None:
self._update_flag_state()
if self._scoring_team is None:
scoring_team = None
else:
scoring_team = self._scoring_team()
if not scoring_team:
return
if isinstance(self.session, bs.FreeForAllSession):
players = self.players
else:
players = scoring_team.players
for player in players:
if player.time_at_flag > 0:
self.stats.player_scored(
player, 3, screenmessage=False, display=False
)
if player.capsules > 0:
if self._flag_state != FlagState.HELD:
return
if scoring_team.score >= self._score_to_win:
return
player.capsules -= 1
scoring_team.score += 1
self._handle_capsule_storage((
self._flag_pos[0],
self._flag_pos[1]+1,
self._flag_pos[2]
), player)
self._collect_sound.play(0.8, position=self._flag_pos)
self._update_scoreboard()
if player.capsules > 0:
assert self._flag is not None
self._flag.set_score_text(
str(self._score_to_win - scoring_team.score))
# winner
if scoring_team.score >= self._score_to_win:
self.end_game()
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results, announce_delay=0)
def _update_flag_state(self) -> None:
holding_teams = set(
player.team for player in self.players if player.time_at_flag
)
prev_state = self._flag_state
assert self._flag_light
assert self._flag is not None
assert self._flag.node
if len(holding_teams) > 1:
self._flag_state = FlagState.CONTESTED
self._scoring_team = None
self._flag_light.color = (0.6, 0.6, 0.1)
self._flag.node.color = (1.0, 1.0, 0.4)
elif len(holding_teams) == 1:
holding_team = list(holding_teams)[0]
self._flag_state = FlagState.HELD
self._scoring_team = weakref.ref(holding_team)
self._flag_light.color = babase.normalized_color(holding_team.color)
self._flag.node.color = holding_team.color
else:
self._flag_state = FlagState.UNCONTESTED
self._scoring_team = None
self._flag_light.color = (0.2, 0.2, 0.2)
self._flag.node.color = (1, 1, 1)
if self._flag_state != prev_state:
self._swipsound.play()
def _handle_player_flag_region_collide(self, colliding: bool) -> None:
try:
spaz = bs.getcollision().opposingnode.getdelegate(PlayerSpaz, True)
except bs.NotFoundError:
return
if not spaz.is_alive():
return
player = spaz.getplayer(Player, True)
# Different parts of us can collide so a single value isn't enough
# also don't count it if we're dead (flying heads shouldn't be able to
# win the game :-)
if colliding and player.is_alive():
player.time_at_flag += 1
else:
player.time_at_flag = max(0, player.time_at_flag - 1)
self._update_flag_state()
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(
team, team.score, self._score_to_win
)
def _drop_capsule(self, player: Player) -> None:
pt = player.node.position
# Throw out capsules that the victim has + 2 more to keep the game running
for i in range(player.capsules + self._capsules_death):
# How far from each other these capsules should spawn
w = 0.6
# How much these capsules should fly after spawning
s = 0.005 - (player.capsules * 0.01)
self._capsules.append(
Capsule(
position=(pt[0] + random.uniform(-w, w),
pt[1] + 0.75 + random.uniform(-w, w),
pt[2]),
velocity=(random.uniform(-s, s),
random.uniform(-s, s),
random.uniform(-s, s)),
lucky=False))
if random.randint(1, 12) == 1 and self._lucky_capsules:
# How far from each other these capsules should spawn
w = 0.6
# How much these capsules should fly after spawning
s = 0.005
self._capsules.append(
Capsule(
position=(pt[0] + random.uniform(-w, w),
pt[1] + 0.75 + random.uniform(-w, w),
pt[2]),
velocity=(random.uniform(-s, s),
random.uniform(-s, s),
random.uniform(-s, s)),
lucky=True))
def _on_capsule_player_collide(self) -> None:
if self.has_ended():
return
collision = bs.getcollision()
# Be defensive here; we could be hitting the corpse of a player
# who just left/etc.
try:
capsule = collision.sourcenode.getdelegate(Capsule, True)
player = collision.opposingnode.getdelegate(
PlayerSpaz, True
).getplayer(Player, True)
except bs.NotFoundError:
return
if not player.is_alive():
return
if capsule.node.color_texture == self._capsule_lucky_tex:
player.capsules += 4
PopupText(
bonus,
color=(1, 1, 0),
scale=1.5,
position=capsule.node.position
).autoretain()
self._lucky_collect_sound.play(1.0, position=capsule.node.position)
bs.emitfx(
position=capsule.node.position,
velocity=(0, 0, 0),
count=int(6.4+random.random()*24),
scale=1.2,
spread=2.0,
chunk_type='spark')
bs.emitfx(
position=capsule.node.position,
velocity=(0, 0, 0),
count=int(4.0+random.random()*6),
emit_type='tendrils')
else:
player.capsules += 1
self._collect_sound.play(0.6, position=capsule.node.position)
# create a flash
light = bs.newnode(
'light',
attrs={
'position': capsule.node.position,
'height_attenuated': False,
'radius': 0.1,
'color': (1, 1, 0)})
# Create a short text informing about your inventory
self._handle_capsule_storage(player.position, player)
bs.animate(light, 'intensity', {
0: 0,
0.1: 0.5,
0.2: 0
}, loop=False)
bs.timer(0.2, light.delete)
capsule.handlemessage(bs.DieMessage())
def _update_player_light(self, player: Player, capsules: int) -> None:
if player.light:
intensity = 0.04 * capsules
bs.animate(player.light, 'intensity', {
0.0: player.light.intensity,
0.1: intensity
})
def newintensity():
player.light.intensity = intensity
bs.timer(0.1, newintensity)
else:
player.light = bs.newnode(
'light',
attrs={
'height_attenuated': False,
'radius': 0.2,
'intensity': 0.0,
'color': (0.2, 1, 0.2)
})
player.node.connectattr('position', player.light, 'position')
def _handle_capsule_storage(self, pos: float, player: Player) -> None:
capsules = player.capsules
text = str(capsules)
scale = 1.75 + (0.02 * capsules)
if capsules > 10:
player.capsules = 10
text = full_capacity
color = (1, 0.85, 0)
elif capsules > 7:
color = (1, 0, 0)
scale = 2.4
elif capsules > 5:
color = (1, 0.4, 0.4)
scale = 2.1
elif capsules > 3:
color = (1, 1, 0.4)
scale = 2.0
else:
color = (1, 1, 1)
scale = 1.9
PopupText(
text,
color=color,
scale=scale,
position=(pos[0], pos[1]-1, pos[2])
).autoretain()
self._update_player_light(player, capsules)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
super().handlemessage(msg) # Augment default.
# No longer can count as time_at_flag once dead.
player = msg.getplayer(Player)
player.time_at_flag = 0
self._update_flag_state()
self._drop_capsule(player)
player.capsules = 0
self._update_player_light(player, 0)
self.respawn_player(player)
else:
return super().handlemessage(msg)
class Capsule(bs.Actor):
def __init__(self,
position: Sequence[float] = (0.0, 1.0, 0.0),
velocity: Sequence[float] = (0.0, 0.5, 0.0),
lucky: bool = False):
super().__init__()
shared = SharedObjects.get()
activity = self.getactivity()
# spawn just above the provided point
self._spawn_pos = (position[0], position[1], position[2])
if lucky:
activity._lucky_sound.play(1.0, self._spawn_pos)
self.node = bs.newnode(
'prop',
attrs={
'mesh': activity._capsule_mesh,
'color_texture': activity._capsule_lucky_tex if lucky else (
activity._capsule_tex),
'body': 'crate' if lucky else 'capsule',
'reflection': 'powerup' if lucky else 'soft',
'body_scale': 0.65 if lucky else 0.3,
'density': 6.0 if lucky else 4.0,
'reflection_scale': [0.15],
'shadow_size': 0.65 if lucky else 0.6,
'position': self._spawn_pos,
'velocity': velocity,
'materials': [
shared.object_material, activity._capsule_material]
},
delegate=self)
bs.animate(self.node, 'mesh_scale', {
0.0: 0.0,
0.1: 0.9 if lucky else 0.6,
0.16: 0.8 if lucky else 0.5
})
self._light_capsule = bs.newnode(
'light',
attrs={
'position': self._spawn_pos,
'height_attenuated': False,
'radius': 0.5 if lucky else 0.1,
'color': (0.2, 0.2, 0) if lucky else (0.2, 1, 0.2)
})
self.node.connectattr('position', self._light_capsule, 'position')
def handlemessage(self, msg: Any):
if isinstance(msg, bs.DieMessage):
self.node.delete()
bs.animate(self._light_capsule, 'intensity', {
0: 1.0,
0.05: 0.0
}, loop=False)
bs.timer(0.05, self._light_capsule.delete)
elif isinstance(msg, bs.OutOfBoundsMessage):
self.handlemessage(bs.DieMessage())
elif isinstance(msg, bs.HitMessage):
self.node.handlemessage(
'impulse',
msg.pos[0], msg.pos[1], msg.pos[2],
msg.velocity[0]/8, msg.velocity[1]/8, msg.velocity[2]/8,
1.0*msg.magnitude, 1.0*msg.velocity_magnitude, msg.radius, 0,
msg.force_direction[0], msg.force_direction[1],
msg.force_direction[2])
else:
return super().handlemessage(msg)

View file

@ -0,0 +1,307 @@
# ba_meta require api 8
"""
DemolitionWar - BombFight on wooden floor flying in air.
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 bascenev1 import _map
from bascenev1lib.game.elimination import EliminationGame, Player
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.actor.bomb import BombFactory
import random
from bascenev1lib.actor.playerspaz import PlayerSpaz
if TYPE_CHECKING:
from typing import Any, Sequence
# ba_meta export bascenev1.GameActivity
class DemolitionWar(EliminationGame):
name = 'DemolitionWar'
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 ['Wooden Floor']
def __init__(self, settings: dict):
super().__init__(settings)
self._lives_per_player = 1
self._solo_mode = False
self._balance_total_lives = 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.bomb_type = 'impact'
# 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=True,
enable_pickup=True)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
return spaz
def on_begin(self) -> None:
super().on_begin()
self.map_extend()
def on_blast(self):
node = bs.getcollision().sourcenode
bs.emitfx((node.position[0], 0.9, node.position[2]),
(0, 2, 0), 30, 1, spread=1, chunk_type='splinter')
bs.timer(0.1, babase.Call(node.delete))
def map_extend(self):
# TODO need to improve here , so we can increase size of map easily with settings
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]
factory = BombFactory.get()
self.ramp_bomb = bs.Material()
self.ramp_bomb.add_actions(
conditions=('they_have_material', factory.bomb_material),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', True),
('call', 'at_connect', babase.Call(self.on_blast))
))
self.ramps = []
for i in p:
for j in q:
self.ramps.append(self.create_ramp(i, j))
def create_ramp(self, x, z):
shared = SharedObjects.get()
self._real_collied_material = bs.Material()
self._real_collied_material.add_actions(
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', True)
))
self.mat = bs.Material()
self.mat.add_actions(
actions=(('modify_part_collision', 'physical', False),
('modify_part_collision', 'collide', False))
)
pos = (x, 0, z)
ud_1_r = bs.newnode('region', attrs={'position': pos, 'scale': (1.5, 1, 1.5), 'type': 'box', 'materials': [
shared.footing_material, self._real_collied_material, self.ramp_bomb]})
node = bs.newnode('prop',
owner=ud_1_r,
attrs={
'mesh': bs.getmesh('image1x1'),
'light_mesh': bs.getmesh('powerupSimple'),
'position': (2, 7, 2),
'body': 'puck',
'shadow_size': 0.0,
'velocity': (0, 0, 0),
'color_texture': bs.gettexture('tnt'),
'mesh_scale': 1.5,
'reflection_scale': [1.5],
'materials': [self.mat, shared.object_material, shared.footing_material],
'density': 9000000000
})
# node.changerotation(1, 0, 0)
mnode = bs.newnode('math',
owner=ud_1_r,
attrs={
'input1': (0, 0.6, 0),
'operation': 'add'
})
ud_1_r.connectattr('position', mnode, 'input2')
mnode.connectattr('output', node, 'position')
return ud_1_r
class mapdefs:
points = {}
# noinspection PyDictCreation
boxes = {}
boxes['area_of_interest_bounds'] = (0.0, 1.185751251, 0.4326226188) + (
0.0, 0.0, 0.0) + (29.8180273, 11.57249038, 18.89134176)
boxes['edge_box'] = (-0.103873591, 0.4133341891, 0.4294651013) + (
0.0, 0.0, 0.0) + (22.48295719, 1.290242794, 8.990252454)
points['ffa_spawn1'] = (-0.08015551329, 0.02275111462,
-4.373674593) + (8.895057015, 1.0, 0.444350722)
points['ffa_spawn2'] = (-0.08015551329, 0.02275111462,
4.076288941) + (8.895057015, 1.0, 0.444350722)
points['flag1'] = (-10.99027878, 0.05744967453, 0.1095578275)
points['flag2'] = (11.01486398, 0.03986567039, 0.1095578275)
points['flag_default'] = (-0.1001374046, 0.04180340146, 0.1095578275)
boxes['goal1'] = (12.22454533, 1.0,
0.1087926362) + (0.0, 0.0, 0.0) + (2.0, 2.0, 12.97466313)
boxes['goal2'] = (-12.15961605, 1.0,
0.1097860203) + (0.0, 0.0, 0.0) + (2.0, 2.0, 13.11856424)
boxes['map_bounds'] = (0.0, 1.185751251, 0.4326226188) + (0.0, 0.0, 0.0) + (
42.09506485, 22.81173179, 29.76723155)
points['powerup_spawn1'] = (5.414681236, 0.9515026107, -5.037912441)
points['powerup_spawn2'] = (-5.555402285, 0.9515026107, -5.037912441)
points['powerup_spawn3'] = (5.414681236, 0.9515026107, 5.148223181)
points['powerup_spawn4'] = (-5.737266365, 0.9515026107, 5.148223181)
points['spawn1'] = (-10.03866341, 0.02275111462, 0.0) + (0.5, 1.0, 4.0)
points['spawn2'] = (9.823107149, 0.01092306765, 0.0) + (0.5, 1.0, 4.0)
points['tnt1'] = (-0.08421587483, 0.9515026107, -0.7762602271)
class WoodenFloor(bs._map.Map): # ahdunno if this is correct way, change if u find better way
"""Stadium map for football games."""
defs = mapdefs
defs.points['spawn1'] = (-12.03866341, 0.02275111462, 0.0) + (0.5, 1.0, 4.0)
defs.points['spawn2'] = (12.823107149, 0.01092306765, 0.0) + (0.5, 1.0, 4.0)
name = 'Wooden Floor'
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'football', 'team_flag', 'keep_away']
@classmethod
def get_preview_texture_name(cls) -> str:
return 'footballStadiumPreview'
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
'mesh_bg': bs.getmesh('doomShroomBG'),
'bg_vr_fill_mesh': bs.getmesh('natureBackgroundVRFill'),
'collide_mesh': bs.getcollisionmesh('bridgitLevelCollide'),
'tex': bs.gettexture('bridgitLevelColor'),
'mesh_bg_tex': bs.gettexture('doomShroomBGColor'),
'collide_bg': bs.getcollisionmesh('natureBackgroundCollide'),
'railing_collide_mesh':
(bs.getcollisionmesh('bridgitLevelRailingCollide')),
'bg_material': bs.Material()
}
data['bg_material'].add_actions(actions=('modify_part_collision',
'friction', 10.0))
return data
def __init__(self) -> None:
super().__init__()
shared = SharedObjects.get()
self.background = bs.newnode(
'terrain',
attrs={
'mesh': self.preloaddata['mesh_bg'],
'lighting': False,
'background': True,
'color_texture': self.preloaddata['mesh_bg_tex']
})
self.vr = bs.newnode('terrain',
attrs={
'mesh': self.preloaddata['bg_vr_fill_mesh'],
'lighting': False,
'vr_only': True,
'background': True,
'color_texture': self.preloaddata['mesh_bg_tex']
})
gnode = bs.getactivity().globalsnode
gnode.tint = (1.3, 1.2, 1.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
def is_point_near_edge(self,
point: babase.Vec3,
running: bool = False) -> bool:
box_position = self.defs.boxes['edge_box'][0:3]
box_scale = self.defs.boxes['edge_box'][6:9]
xpos = (point.x - box_position[0]) / box_scale[0]
zpos = (point.z - box_position[2]) / box_scale[2]
return xpos < -0.5 or xpos > 0.5 or zpos < -0.5 or zpos > 0.5
def _handle_player_collide(self):
try:
player = bs.getcollision().opposingnode.getdelegate(
PlayerSpaz, True)
except bs.NotFoundError:
return
if player.is_alive():
player.shatter(True)
try:
bs._map.register_map(WoodenFloor)
except:
pass

View file

@ -0,0 +1,768 @@
"""
DondgeTheBall minigame by EmperoR#4098
"""
# Feel free to edit.
# 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 random import choice
from enum import Enum
from bascenev1lib.actor.bomb import Blast
from bascenev1lib.actor.popuptext import PopupText
from bascenev1lib.actor.powerupbox import PowerupBox
from bascenev1lib.actor.onscreencountdown import OnScreenCountdown
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import NoReturn, Sequence, Any
# Type of ball in this game
class BallType(Enum):
""" Types of ball """
EASY = 0
# Decrease the next ball shooting speed(not ball speed).
# Ball color is yellow.
MEDIUM = 1
# increase the next ball shooting speed(not ball speed).
# target the head of player.
# Ball color is purple.
HARD = 2
# Target player according to player movement (not very accurate).
# Taget: player head.
# increase the next ball speed but less than MEDIUM.
# Ball color is crimson(purple+red = pinky color type).
# this dict decide the ball_type spawning rate like powerup box
ball_type_dict: dict[BallType, int] = {
BallType.EASY: 3,
BallType.MEDIUM: 2,
BallType.HARD: 1,
}
class Ball(bs.Actor):
""" Shooting Ball """
def __init__(self,
position: Sequence[float],
velocity: Sequence[float],
texture: babase.Texture,
body_scale: float = 1.0,
gravity_scale: float = 1.0,
) -> NoReturn:
super().__init__()
shared = SharedObjects.get()
ball_material = bs.Material()
ball_material.add_actions(
conditions=(
(
('we_are_younger_than', 100),
'or',
('they_are_younger_than', 100),
),
'and',
('they_have_material', shared.object_material),
),
actions=('modify_node_collision', 'collide', False),
)
self.node = bs.newnode(
'prop',
delegate=self,
attrs={
'body': 'sphere',
'position': position,
'velocity': velocity,
'body_scale': body_scale,
'mesh': bs.getmesh('frostyPelvis'),
'mesh_scale': body_scale,
'color_texture': texture,
'gravity_scale': gravity_scale,
'density': 4.0, # increase density of ball so ball collide with player with heavy force. # ammm very bad grammer
'materials': (ball_material,),
},
)
# die the ball manually incase the ball doesn't fall the outside of the map
bs.timer(2.5, bs.WeakCall(self.handlemessage, bs.DieMessage()))
# i am not handling anything in this ball Class(except for diemessage).
# all game things and logics going to be in the box class
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
self.node.delete()
else:
super().handlemessage(msg)
class Box(bs.Actor):
""" A box that spawn midle of map as a decoration perpose """
def __init__(self,
position: Sequence[float],
velocity: Sequence[float],
) -> NoReturn:
super().__init__()
shared = SharedObjects.get()
# self.ball_jump = 0.0;
no_hit_material = bs.Material()
# we don't need that the box was move and collide with objects.
no_hit_material.add_actions(
conditions=(
('they_have_material', shared.pickup_material),
'or',
('they_have_material', shared.attack_material),
),
actions=('modify_part_collision', 'collide', False),
)
no_hit_material.add_actions(
conditions=(
('they_have_material', shared.object_material),
'or',
('they_dont_have_material', shared.footing_material),
),
actions=(
('modify_part_collision', 'collide', False),
('modify_part_collision', 'physical', False),
),
)
self.node = bs.newnode(
'prop',
delegate=self,
attrs={
'body': 'box',
'position': position,
'mesh': bs.getmesh('powerup'),
'light_mesh': bs.getmesh('powerupSimple'),
'shadow_size': 0.5,
'body_scale': 1.4,
'mesh_scale': 1.4,
'color_texture': bs.gettexture('landMineLit'),
'reflection': 'powerup',
'reflection_scale': [1.0],
'materials': (no_hit_material,),
},
)
# light
self.light = bs.newnode(
"light",
owner=self.node,
attrs={
'radius': 0.2,
'intensity': 0.8,
'color': (0.0, 1.0, 0.0),
}
)
self.node.connectattr("position", self.light, "position")
# Drawing circle and circleOutline in radius of 3,
# so player can see that how close he is to the box.
# If player is inside this circle the ball speed will increase.
circle = bs.newnode(
"locator",
owner=self.node,
attrs={
'shape': 'circle',
'color': (1.0, 0.0, 0.0),
'opacity': 0.1,
'size': (6.0, 0.0, 6.0),
'draw_beauty': False,
'additive': True,
},
)
self.node.connectattr("position", circle, "position")
# also adding a outline cause its look nice.
circle_outline = bs.newnode(
"locator",
owner=self.node,
attrs={
'shape': 'circleOutline',
'color': (1.0, 1.0, 0.0),
'opacity': 0.1,
'size': (6.0, 0.0, 6.0),
'draw_beauty': False,
'additive': True,
},
)
self.node.connectattr("position", circle_outline, "position")
# all ball attribute that we need.
self.ball_type: BallType = BallType.EASY
self.shoot_timer: bs.Timer | None = None
self.shoot_speed: float = 0.0
# this force the shoot if player is inside the red circle.
self.force_shoot_speed: float = 0.0
self.ball_mag = 3000
self.ball_gravity: float = 1.0
self.ball_tex: babase.Texture | None = None
# only for Hard ball_type
self.player_facing_direction: list[float, float] = [0.0, 0.0]
# ball shoot soound.
self.shoot_sound = bs.getsound('laserReverse')
# same as "powerupdist"
self.ball_type_dist: list[BallType] = []
for ball in ball_type_dict:
for _ in range(ball_type_dict[ball]):
self.ball_type_dist.append(ball)
# Here main logic of game goes here.
# like shoot balls, shoot speed, anything we want goes here(except for some thing).
def start_shoot(self) -> NoReturn:
# getting all allive players in a list.
alive_players_list = self.activity.get_alive_players()
# make sure that list is not Empty.
if len(alive_players_list) > 0:
# choosing a random player from list.
target_player = choice(alive_players_list)
# highlight the target player
self.highlight_target_player(target_player)
# to finding difference between player and box.
# we just need to subtract player pos and ball pos.
# Same logic as eric applied in Target Practice Gamemode.
difference = babase.Vec3(target_player.position) - babase.Vec3(self.node.position)
# discard Y position so ball shoot more straight.
difference[1] = 0.0
# and now, this length method returns distance in float.
# we're gonna use this value for calculating player analog stick
distance = difference.length()
# shoot a random BallType
self.upgrade_ball_type(choice(self.ball_type_dist))
# and check the ball_type and upgrade it gravity_scale, texture, next ball speed.
self.check_ball_type(self.ball_type)
# For HARD ball i am just focusing on player analog stick facing direction.
# Not very accurate and that's we need.
if self.ball_type == BallType.HARD:
self.calculate_player_analog_stick(target_player, distance)
else:
self.player_facing_direction = [0.0, 0.0]
pos = self.node.position
if self.ball_type == BallType.MEDIUM or self.ball_type == BallType.HARD:
# Target head by increasing Y pos.
# How this work? cause ball gravity_scale is ......
pos = (pos[0], pos[1]+.25, pos[2])
# ball is generating..
ball = Ball(
position=pos,
velocity=(0.0, 0.0, 0.0),
texture=self.ball_tex,
gravity_scale=self.ball_gravity,
body_scale=1.0,
).autoretain()
# shoot Animation and sound.
self.shoot_animation()
# force the shoot speed if player try to go inside the red circle.
if self.force_shoot_speed != 0.0:
self.shoot_speed = self.force_shoot_speed
# push the ball to the player
ball.node.handlemessage(
'impulse',
self.node.position[0], # ball spawn position X
self.node.position[1], # Y
self.node.position[2], # Z
0, 0, 0, # velocity x,y,z
self.ball_mag, # magnetude
0.000, # magnetude velocity
0.000, # radius
0.000, # idk
difference[0] + self.player_facing_direction[0], # force direction X
difference[1], # force direction Y
difference[2] + self.player_facing_direction[1], # force direction Z
)
# creating our timer and shoot the ball again.(and we create a loop)
self.shoot_timer = bs.Timer(self.shoot_speed, self.start_shoot)
def upgrade_ball_type(self, ball_type: BallType) -> NoReturn:
self.ball_type = ball_type
def check_ball_type(self, ball_type: BallType) -> NoReturn:
if ball_type == BallType.EASY:
self.shoot_speed = 0.8
self.ball_gravity = 1.0
# next ball shoot speed
self.ball_mag = 3000
# box light color and ball tex
self.light.color = (1.0, 1.0, 0.0)
self.ball_tex = bs.gettexture('egg4')
elif ball_type == BallType.MEDIUM:
self.ball_mag = 3000
# decrease the gravity scale so, ball shoot without falling and straight.
self.ball_gravity = 0.0
# next ball shoot speed.
self.shoot_speed = 0.4
# box light color and ball tex.
self.light.color = (1.0, 0.0, 1.0)
self.ball_tex = bs.gettexture('egg3')
elif ball_type == BallType.HARD:
self.ball_mag = 2500
self.ball_gravity = 0.0
# next ball shoot speed.
self.shoot_speed = 0.6
# box light color and ball tex.
self.light.color = (1.0, 0.2, 1.0)
self.ball_tex = bs.gettexture('egg1')
def shoot_animation(self) -> NoReturn:
bs.animate(
self.node,
"mesh_scale", {
0.00: 1.4,
0.05: 1.7,
0.10: 1.4,
}
)
# playing shoot sound.
# self.shoot_sound, position = self.node.position.play();
self.shoot_sound.play()
def highlight_target_player(self, player: bs.Player) -> NoReturn:
# adding light
light = bs.newnode(
"light",
owner=self.node,
attrs={
'radius': 0.0,
'intensity': 1.0,
'color': (1.0, 0.0, 0.0),
}
)
bs.animate(
light,
"radius", {
0.05: 0.02,
0.10: 0.07,
0.15: 0.15,
0.20: 0.13,
0.25: 0.10,
0.30: 0.05,
0.35: 0.02,
0.40: 0.00,
}
)
# And a circle outline with ugly animation.
circle_outline = bs.newnode(
"locator",
owner=player.actor.node,
attrs={
'shape': 'circleOutline',
'color': (1.0, 0.0, 0.0),
'opacity': 1.0,
'draw_beauty': False,
'additive': True,
},
)
bs.animate_array(
circle_outline,
'size',
1, {
0.05: [0.5],
0.10: [0.8],
0.15: [1.5],
0.20: [2.0],
0.25: [1.8],
0.30: [1.3],
0.35: [0.6],
0.40: [0.0],
}
)
# coonect it and...
player.actor.node.connectattr("position", light, "position")
player.actor.node.connectattr("position", circle_outline, "position")
# immediately delete the node after another player has been targeted.
self.shoot_speed = 0.5 if self.shoot_speed == 0.0 else self.shoot_speed
bs.timer(self.shoot_speed, light.delete)
bs.timer(self.shoot_speed, circle_outline.delete)
def calculate_player_analog_stick(self, player: bs.Player, distance: float) -> NoReturn:
# at first i was very confused how i can read the player analog stick \
# then i saw TheMikirog#1984 autorun plugin code.
# and i got it how analog stick values are works.
# just need to store analog stick facing direction and need some calculation according how far player pushed analog stick.
# Notice that how vertical direction is inverted, so we need to put a minus infront of veriable.(so ball isn't shoot at wrong direction).
self.player_facing_direction[0] = player.actor.node.move_left_right
self.player_facing_direction[1] = -player.actor.node.move_up_down
# if player is too close and the player pushing his analog stick fully the ball shoot's too far away to player.
# so, we need to reduce the value of "self.player_facing_direction" to fix this problem.
if distance <= 3:
self.player_facing_direction[0] = 0.4 if self.player_facing_direction[0] > 0 else -0.4
self.player_facing_direction[1] = 0.4 if self.player_facing_direction[0] > 0 else -0.4
# same problem to long distance but in reverse, the ball can't reach to the player,
# its because player analog stick value is between 1 and -1,
# and this value is low to shoot ball forward to Player if player is too far from the box.
# so. let's increase to 1.5 if player pushed analog stick fully.
elif distance > 6.5:
# So many calculation according to how analog stick pushed by player.
# Horizontal(left-right) calculation
if self.player_facing_direction[0] > 0.4:
self.player_facing_direction[0] = 1.5
elif self.player_facing_direction[0] < -0.4:
self.player_facing_direction[0] = -1.5
else:
if self.player_facing_direction[0] > 0.0:
self.player_facing_direction[0] = 0.2
elif self.player_facing_direction[0] < 0.0:
self.player_facing_direction[0] = -0.2
else:
self.player_facing_direction[0] = 0.0
# Vertical(up-down) calculation.
if self.player_facing_direction[1] > 0.4:
self.player_facing_direction[1] = 1.5
elif self.player_facing_direction[1] < -0.4:
self.player_facing_direction[1] = -1.5
else:
if self.player_facing_direction[1] > 0.0:
self.player_facing_direction[1] = 0.2
elif self.player_facing_direction[1] < 0.0:
self.player_facing_direction[1] = -0.2
else:
self.player_facing_direction[1] = -0.0
# if we want stop the ball shootes
def stop_shoot(self) -> NoReturn:
# Kill the timer.
self.shoot_timer = None
class Player(bs.Player['Team']):
"""Our player type for this game."""
class Team(bs.Team[Player]):
"""Our team type for this game."""
# almost 80 % for game we done in box class.
# now remain things, like name, seetings, scoring, cooldonw,
# and main thing don't allow player to camp inside of box are going in this class.
# ba_meta export bascenev1.GameActivity
class DodgeTheBall(bs.TeamGameActivity[Player, Team]):
# defining name, description and settings..
name = 'Dodge the ball'
description = 'Survive from shooting balls'
available_settings = [
bs.IntSetting(
'Cooldown',
min_value=20,
default=45,
increment=5,
),
bs.BoolSetting('Epic Mode', default=False)
]
# Don't allow joining after we start.
allow_mid_activity_joins = False
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
# We support team and ffa sessions.
return issubclass(sessiontype, bs.FreeForAllSession) or issubclass(
sessiontype, bs.DualTeamSession,
)
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
# This Game mode need a flat and perfect shape map where can player fall outside map.
# bombsquad have "Doom Shroom" map.
# Not perfect map for this game mode but its fine for this gamemode.
# the problem is that Doom Shroom is not a perfect circle and not flat also.
return ['Doom Shroom']
def __init__(self, settings: dict):
super().__init__(settings)
self._epic_mode = bool(settings['Epic Mode'])
self.countdown_time = int(settings['Cooldown'])
self.check_player_pos_timer: bs.Timer | None = None
self.shield_drop_timer: bs.Timer | None = None
# cooldown and Box
self._countdown: OnScreenCountdown | None = None
self.box: Box | None = None
# this lists for scoring.
self.joined_player_list: list[bs.Player] = []
self.dead_player_list: list[bs.Player] = []
# normally play RUN AWAY music cause is match with our gamemode at.. my point,
# but in epic switch to EPIC.
self.slow_motion = self._epic_mode
self.default_music = (
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.RUN_AWAY
)
def get_instance_description(self) -> str | Sequence:
return 'Keep away as possible as you can'
# add a tiny text under our game name.
def get_instance_description_short(self) -> str | Sequence:
return 'Dodge the shooting balls'
def on_begin(self) -> NoReturn:
super().on_begin()
# spawn our box at middle of the map
self.box = Box(
position=(0.5, 2.7, -3.9),
velocity=(0.0, 0.0, 0.0),
).autoretain()
# create our cooldown
self._countdown = OnScreenCountdown(
duration=self.countdown_time,
endcall=self.play_victory_sound_and_end,
)
# and starts the cooldown and shootes.
bs.timer(5.0, self._countdown.start)
bs.timer(5.0, self.box.start_shoot)
# start checking all player pos.
bs.timer(5.0, self.check_player_pos)
# drop shield every ten Seconds
# need five seconds delay Because shootes start after 5 seconds.
bs.timer(15.0, self.drop_shield)
# This function returns all alive players in game.
# i thinck you see this function in Box class.
def get_alive_players(self) -> Sequence[bs.Player]:
alive_players = []
for team in self.teams:
for player in team.players:
if player.is_alive():
alive_players.append(player)
return alive_players
# let's disallowed camping inside of box by doing a blast and increasing ball shoot speed.
def check_player_pos(self):
for player in self.get_alive_players():
# same logic as applied for the ball
difference = babase.Vec3(player.position) - babase.Vec3(self.box.node.position)
distance = difference.length()
if distance < 3:
self.box.force_shoot_speed = 0.2
else:
self.box.force_shoot_speed = 0.0
if distance < 0.5:
Blast(
position=self.box.node.position,
velocity=self.box.node.velocity,
blast_type='normal',
blast_radius=1.0,
).autoretain()
PopupText(
position=self.box.node.position,
text='Keep away from me',
random_offset=0.0,
scale=2.0,
color=self.box.light.color,
).autoretain()
# create our timer and start looping it
self.check_player_pos_timer = bs.Timer(0.1, self.check_player_pos)
# drop useless shield's too give player temptation.
def drop_shield(self) -> NoReturn:
pos = self.box.node.position
PowerupBox(
position=(pos[0] + 4.0, pos[1] + 3.0, pos[2]),
poweruptype='shield',
).autoretain()
PowerupBox(
position=(pos[0] - 4.0, pos[1] + 3.0, pos[2]),
poweruptype='shield',
).autoretain()
self.shield_drop_timer = bs.Timer(10.0, self.drop_shield)
# when cooldown time up i don't want that the game end immediately.
def play_victory_sound_and_end(self) -> NoReturn:
# kill timers
self.box.stop_shoot()
self.check_player_pos_timer = None
self.shield_drop_timer = None
bs.timer(2.0, self.end_game)
# this function runs when A player spawn in map
def spawn_player(self, player: Player) -> NoReturn:
spaz = self.spawn_player_spaz(player)
# reconnect this player's controls.
# without bomb, punch and pickup.
spaz.connect_controls_to_player(
enable_punch=False, enable_bomb=False, enable_pickup=False,
)
# storing all players for ScorinG.
self.joined_player_list.append(player)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
# very helpful function to check end game when player dead or leav.
def _check_end_game(self) -> bool:
living_team_count = 0
for team in self.teams:
for player in team.players:
if player.is_alive():
living_team_count += 1
break
if living_team_count <= 0:
# kill the coutdown timer incase the all players dead before game is about to going to be end.
# so, countdown won't call the function.
# FIXE ME: it's that ok to kill this timer?
# self._countdown._timer = None;
self.end_game()
# this function called when player leave.
def on_player_leave(self, player: Player) -> NoReturn:
# Augment default behavior.
super().on_player_leave(player)
# checking end game.
self._check_end_game()
# this gamemode needs to handle only one msg "PlayerDiedMessage".
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
# and storing the dead player records in our dead_player_list.
self.dead_player_list.append(msg.getplayer(Player))
# check the end game.
bs.timer(1.0, self._check_end_game)
def end_game(self):
# kill timers
self.box.stop_shoot()
self.check_player_pos_timer = None
self.shield_drop_timer = None
# here the player_dead_list and joined_player_list gonna be very helpful.
for team in self.teams:
for player in team.players:
# for scoring i am just following the index of the player_dead_list.
# for dead list...
# 0th index player dead first.
# 1st index player dead second.
# and so on...
# i think you got it... maybe
# sometime we also got a empty list
# if we got a empty list that means all players are survived or maybe only one player playing and he/she survived.
if len(self.dead_player_list) > 0:
for index, dead_player in enumerate(self.dead_player_list):
# if this condition is true we find the dead player \
# and his index with enumerate function.
if player == dead_player:
# updating with one, because i don't want to give 0 score to first dead player.
index += 1
break
# and if this statement is true we just find a survived player.
# for survived player i am giving the highest score according to how many players are joined.
elif index == len(self.dead_player_list) - 1:
index = len(self.joined_player_list)
# for survived player i am giving the highest score according to how many players are joined.
else:
index = len(self.joined_player_list)
# and here i am following Table of 10 for scoring.
# very lazY.
score = int(10 * index)
self.stats.player_scored(player, score, screenmessage=False)
# 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.
# hmmm... some eric comments might be helpful to you.
for team in self.teams:
max_index = 0
for player in team.players:
# for the team, we choose only one player who survived longest.
# same logic..
if len(self.dead_player_list) > 0:
for index, dead_player in enumerate(self.dead_player_list):
if player == dead_player:
index += 1
break
elif index == len(self.dead_player_list) - 1:
index = len(self.joined_player_list)
else:
index = len(self.joined_player_list)
max_index = max(max_index, index)
# set the team score
results.set_team_score(team, int(10 * max_index))
# and end the game
self.end(results=results)

18
dist/ba_root/mods/games/frozen_one.py vendored Normal file
View file

@ -0,0 +1,18 @@
# Ported by your friend: Freaku
import babase
import bascenev1 as bs
from bascenev1lib.game.chosenone import Player, ChosenOneGame
# ba_meta require api 8
# ba_meta export bascenev1.GameActivity
class FrozenOneGame(ChosenOneGame):
name = 'Frozen One'
def _set_chosen_one_player(self, player: Player) -> None:
super()._set_chosen_one_player(player)
if hasattr(player, 'actor'):
player.actor.frozen = True
player.actor.node.frozen = 1

383
dist/ba_root/mods/games/handball.py vendored Normal file
View file

@ -0,0 +1,383 @@
# Released under the MIT License. See LICENSE for details.
#
"""Hockey 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 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
if TYPE_CHECKING:
from typing import Any, Sequence, 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: dict[int, Player] = {}
self.scored = False
assert activity is not None
assert isinstance(activity, HockeyGame)
pmats = [shared.object_material, activity.puck_material]
self.node = bs.newnode('prop',
delegate=self,
attrs={
'mesh': activity.puck_mesh,
'color_texture': activity.puck_tex,
'body': 'sphere',
'reflection': 'soft',
'reflection_scale': [0.2],
'shadow_size': 0.8,
'is_area_of_interest': True,
'position': self._spawn_pos,
'materials': pmats
})
bs.animate(self.node, 'mesh_scale', {0: 0, 0.2: 1.3, 0.26: 1})
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.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
# ba_meta export bascenev1.GameActivity
class HockeyGame(bs.TeamGameActivity[Player, Team]):
"""Ice hockey game."""
name = 'Handball'
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),
('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),
]
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('hockey')
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.puck_mesh = bs.getmesh('bomb')
self.puck_tex = bs.gettexture('bonesColor')
self._puck_sound = bs.getsound('metalHit')
self._epic_mode = bool(settings['Epic Mode'])
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC
if self._epic_mode else bs.MusicType.FOOTBALL)
self.puck_material = bs.Material()
self.puck_material.add_actions(actions=(('modify_part_collision',
'friction', 0.5)))
self.puck_material.add_actions(conditions=('they_have_material',
shared.pickup_material),
actions=('modify_part_collision',
'collide', False))
self.puck_material = bs.Material()
self.puck_material.add_actions(actions=(('modify_part_collision',
'friction', 0.5)))
self.puck_material.add_actions(conditions=('they_have_material',
shared.pickup_material),
actions=('modify_part_collision',
'collide', True))
self.puck_material.add_actions(
conditions=(
('we_are_younger_than', 100),
'and',
('they_have_material', shared.object_material),
),
actions=('modify_node_collision', 'collide', False),
)
self.puck_material.add_actions(conditions=('they_have_material',
shared.footing_material),
actions=('impact_sound',
self._puck_sound, 0.2, 5))
# Keep track of which player last touched the puck
self.puck_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('call', 'at_connect',
self._handle_puck_player_collide), ))
# We want the puck to kill powerups; not get stopped by them
self.puck_material.add_actions(
conditions=('they_have_material',
PowerupBoxFactory.get().powerup_material),
actions=(('modify_part_collision', 'physical', False),
('message', 'their_node', 'at_connect', bs.DieMessage())))
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._puck_spawn_pos: Optional[Sequence[float]] = None
self._score_regions: Optional[list[bs.NodeActor]] = None
self._puck: Optional[Puck] = None
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
def get_instance_description(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'Score a goal.'
return 'Score ${ARG1} goals.', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'score a goal'
return 'score ${ARG1} goals', self._score_to_win
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._puck_spawn_pos = self.map.get_flag_position(None)
self._spawn_puck()
# Set up the two score regions.
defs = self.map.defs
self._score_regions = []
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': defs.boxes['goal1'][0:3],
'scale': defs.boxes['goal1'][6:9],
'type': 'box',
'materials': [self._score_region_material]
})))
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': defs.boxes['goal2'][0:3],
'scale': defs.boxes['goal2'][6:9],
'type': 'box',
'materials': [self._score_region_material]
})))
self._update_scoreboard()
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.team.id] = player
def _kill_puck(self) -> None:
self._puck = None
def _handle_score(self) -> None:
"""A point has been scored."""
assert self._puck is not None
assert self._score_regions is not None
# Our puck might stick around for a second or two
# we don't want it to be able to score again.
if self._puck.scored:
return
region = bs.getcollision().sourcenode
index = 0
for index, score_region in enumerate(self._score_regions):
if region == score_region.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],
100,
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
# 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)
def _spawn_puck(self) -> None:
self._swipsound.play()
self._whistle_sound.play()
self._flash_puck_spawn()
assert self._puck_spawn_pos is not None
self._puck = Puck(position=self._puck_spawn_pos)

1668
dist/ba_root/mods/games/hot_bomb.py vendored Normal file

File diff suppressed because it is too large Load diff

48
dist/ba_root/mods/games/icy_emits.py vendored Normal file
View file

@ -0,0 +1,48 @@
# Made by your friend: Freaku
import babase
import bascenev1 as bs
import random
from bascenev1lib.actor.bomb import Bomb
from bascenev1lib.game.meteorshower import Player, MeteorShowerGame
# ba_meta require api 8
# ba_meta export bascenev1.GameActivity
class IcyEmitsGame(MeteorShowerGame):
name = 'Icy Emits'
@classmethod
def get_supported_maps(cls, sessiontype):
return ['Lake Frigid', 'Hockey Stadium']
def _drop_bomb_cluster(self) -> None:
delay = 0.0
for _i in range(random.randrange(1, 3)):
# Drop them somewhere within our bounds with velocity pointing
# toward the opposite side.
pos = (-7.3 + 15.3 * random.random(), 5.3,
-5.5 + 2.1 * random.random())
dropdir = (-1.0 if pos[0] > 0 else 1.0)
vel = (0, 10, 0)
bs.timer(delay, babase.Call(self._drop_bomb, pos, vel))
delay += 0.1
self._set_meteor_timer()
def _drop_bomb(self, position, velocity):
random_xpositions = [-10, -9, -8, -7, -6, -5, -
4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
random_zpositions = [-5, -4.5, -4, -3.5, -3, -2.5, -2, -
1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5]
bomb_position = (random.choice(random_xpositions), 0.2, random.choice(random_zpositions))
Bomb(position=bomb_position, velocity=velocity, bomb_type='ice').autoretain()
# ba_meta export plugin
class byFreaku(babase.Plugin):
def __init__(self):
## Campaign support ##
randomPic = ['lakeFrigidPreview', 'hockeyStadiumPreview']
babase.app.classic.add_coop_practice_level(bs.Level(
name='Icy Emits', displayname='${GAME}', gametype=IcyEmitsGame, settings={}, preview_texture_name=random.choice(randomPic)))

1003
dist/ba_root/mods/games/memory_game.py vendored Normal file

File diff suppressed because it is too large Load diff

287
dist/ba_root/mods/games/musical_flags.py vendored Normal file
View file

@ -0,0 +1,287 @@
# Made by MattZ45986 on GitHub
# Ported by your friend: Freaku
# Bug Fixes & Improvements as well...
# Join BCS:
# https://discord.gg/ucyaesh
from __future__ import annotations
from typing import TYPE_CHECKING
import _babase
import random
import math
import bascenev1 as bs
from bascenev1lib.actor.flag import Flag, FlagPickedUpMessage
from bascenev1lib.actor.playerspaz import PlayerSpaz
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional
class Player(bs.Player['Team']):
def __init__(self) -> None:
self.done: bool = False
self.survived: bool = True
class Team(bs.Team[Player]):
def __init__(self) -> None:
self.score = 0
# ba_meta require api 8
# ba_meta export bascenev1.GameActivity
class MFGame(bs.TeamGameActivity[Player, Team]):
name = 'Musical Flags'
description = "Don't be the one stuck without a flag!"
@classmethod
def get_available_settings(
cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]:
settings = [
bs.IntSetting(
'Max Round Time',
min_value=15,
default=25,
increment=5,
),
bs.BoolSetting('Epic Mode', default=False),
bs.BoolSetting('Enable Running', default=True),
bs.BoolSetting('Enable Punching', default=False),
bs.BoolSetting('Enable Bottom Credit', True)
]
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.DualTeamSession)
or issubclass(sessiontype, bs.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return ['Doom Shroom']
def __init__(self, settings: dict):
super().__init__(settings)
self.nodes = []
self._dingsound = bs.getsound('dingSmall')
self._epic_mode = bool(settings['Epic Mode'])
self.credit_text = bool(settings['Enable Bottom Credit'])
self.is_punch = bool(settings['Enable Punching'])
self.is_run = bool(settings['Enable Running'])
self._textRound = bs.newnode('text',
attrs={'text': '',
'position': (0, -38),
'scale': 1,
'shadow': 1.0,
'flatness': 1.0,
'color': (1.0, 0.0, 1.0),
'opacity': 1,
'v_attach': 'top',
'h_attach': 'center',
'h_align': 'center',
'v_align': 'center'})
self.round_time = int(settings['Max Round Time'])
self.reset_round_time = int(settings['Max Round Time'])
self.should_die_occur = True
self.round_time_textnode = bs.newnode('text',
attrs={
'text': "", 'flatness': 1.0, 'h_align': 'center', 'h_attach': 'center', 'v_attach': 'top', 'v_align': 'center', 'position': (0, -15), 'scale': 0.9, 'color': (1, 0.7, 0.9)})
self.slow_motion = self._epic_mode
# A cool music, matching our gamemode theme
self.default_music = bs.MusicType.FLAG_CATCHER
def get_instance_description(self) -> Union[str, Sequence]:
return 'Catch Flag for yourself'
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'Catch Flag for yourself'
def on_player_join(self, player: Player) -> None:
if self.has_begun():
bs.broadcastmessage(
bs.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0), transient=True)
player.survived = False
return
self.spawn_player(player)
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
# A departing player may trigger game-over.
bs.timer(0, self.checkEnd)
def on_begin(self) -> None:
super().on_begin()
self.roundNum = 0
self.numPickedUp = 0
self.nodes = []
self.flags = []
self.spawned = []
if self.credit_text:
t = bs.newnode('text',
attrs={'text': "Ported by Freaku\nMade by MattZ45986", # Disable 'Enable Bottom Credits' when making playlist, No need to edit this lovely...
'scale': 0.7,
'position': (0, 0),
'shadow': 0.5,
'flatness': 1.2,
'color': (1, 1, 1),
'h_align': 'center',
'v_attach': 'bottom'})
self.makeRound()
self._textRound.text = 'Round ' + str(self.roundNum)
bs.timer(3, self.checkEnd)
self.keepcalling = bs.timer(1, self._timeround, True)
def _timeround(self):
if self.round_time == 0 and self.should_die_occur:
self.should_die_occur = False
self.round_time_textnode.opacity = 0
bs.broadcastmessage('Proceeding Round...')
for player in self.spawned:
if not player.done:
try:
player.survived = False
player.actor.handlemessage(bs.StandMessage((0, 3, -2)))
bs.timer(0.5, bs.Call(player.actor.handlemessage, bs.FreezeMessage()))
bs.timer(1.5, bs.Call(player.actor.handlemessage, bs.FreezeMessage()))
bs.timer(2.5, bs.Call(player.actor.handlemessage, bs.FreezeMessage()))
bs.timer(3, bs.Call(player.actor.handlemessage, bs.ShouldShatterMessage()))
except:
pass
bs.timer(3.5, self.killRound)
bs.timer(3.55, self.makeRound)
self.round_time_textnode.opacity = 0
self.round_time = self.reset_round_time
else:
self.round_time_textnode.text = "Time: " + str(self.round_time)
self.round_time -= 1
def makeRound(self):
for player in self.players:
if player.survived:
player.team.score += 1
self.roundNum += 1
self._textRound.text = 'Round ' + str(self.roundNum)
self.flags = []
self.spawned = []
self.should_die_occur = True
self.round_time = self.reset_round_time
self.round_time_textnode.opacity = 1
angle = random.randint(0, 359)
c = 0
for player in self.players:
if player.survived:
c += 1
spacing = 10
for player in self.players:
player.done = False
if player.survived:
if not player.is_alive():
self.spawn_player(player, (.5, 5, -4))
self.spawned.append(player)
try:
spacing = 360 // (c)
except:
self.checkEnd()
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), (1, 0, 1), (0, 1, 1), (0, 0, 0),
(0.5, 0.8, 0), (0, 0.8, 0.5), (0.8, 0.25, 0.7), (0, 0.27, 0.55), (2, 2, 0.6), (0.4, 3, 0.85)]
# Add support for more than 13 players
if c > 12:
for i in range(c-12):
colors.append((random.uniform(0.1, 1), random.uniform(
0.1, 1), random.uniform(0.1, 1)))
# Smart Mathematics:
# All Flags spawn same distance from the players
for i in range(c-1):
angle += spacing
angle %= 360
x = 6 * math.sin(math.degrees(angle))
z = 6 * math.cos(math.degrees(angle))
flag = Flag(position=(x+.5, 5, z-4), color=colors[i]).autoretain()
self.flags.append(flag)
def killRound(self):
self.numPickedUp = 0
for player in self.players:
if player.is_alive():
player.actor.handlemessage(bs.DieMessage())
for flag in self.flags:
flag.node.delete()
for light in self.nodes:
light.delete()
def spawn_player(self, player: Player, pos: tuple = (0, 0, 0)) -> bs.Actor:
spaz = self.spawn_player_spaz(player)
if pos == (0, 0, 0):
pos = (-.5+random.random()*2, 3+random.random()*2, -5+random.random()*2)
spaz.connect_controls_to_player(enable_punch=self.is_punch,
enable_bomb=False, enable_run=self.is_run)
spaz.handlemessage(bs.StandMessage(pos))
return spaz
def check_respawn(self, player):
if not player.done and player.survived:
self.respawn_player(player, 2.5)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
super().handlemessage(msg)
player = msg.getplayer(Player)
bs.timer(0.1, bs.Call(self.check_respawn, player))
bs.timer(0.5, self.checkEnd)
elif isinstance(msg, FlagPickedUpMessage):
self.numPickedUp += 1
msg.node.getdelegate(PlayerSpaz, True).getplayer(Player, True).done = True
l = bs.newnode('light',
owner=None,
attrs={'color': msg.node.color,
'position': (msg.node.position_center),
'intensity': 1})
self.nodes.append(l)
msg.flag.handlemessage(bs.DieMessage())
msg.node.handlemessage(bs.DieMessage())
msg.node.delete()
if self.numPickedUp == len(self.flags):
self.round_time_textnode.opacity = 0
self.round_time = self.reset_round_time
for player in self.spawned:
if not player.done:
try:
player.survived = False
bs.broadcastmessage("No Flag? "+player.getname())
player.actor.handlemessage(bs.StandMessage((0, 3, -2)))
bs.timer(0.5, bs.Call(player.actor.handlemessage, bs.FreezeMessage()))
bs.timer(3, bs.Call(player.actor.handlemessage, bs.ShouldShatterMessage()))
except:
pass
bs.timer(3.5, self.killRound)
bs.timer(3.55, self.makeRound)
else:
return super().handlemessage(msg)
return None
def checkEnd(self):
i = 0
for player in self.players:
if player.survived:
i += 1
if i <= 1:
for player in self.players:
if player.survived:
player.team.score += 10
bs.timer(2.5, self.end_game)
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,624 @@
# Created By Idk
# Ported to 1.7 by Yan
# ba_meta require api 8
from __future__ import annotations
from typing import TYPE_CHECKING
from bascenev1lib.actor.powerupbox import PowerupBox as Powerup
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.gameutils import SharedObjects
import bascenev1lib.actor.bomb
import bascenev1lib.actor.spaz
import weakref
import random
import math
import babase
import bauiv1 as bui
import bascenev1 as bs
if TYPE_CHECKING:
pass
class TouchedToSpaz(object):
pass
class TouchedToAnything(object):
pass
class TouchedToFootingMaterial(object):
pass
class QuakeBallFactory(object):
"""Components used by QuakeBall stuff
category: Game Classes
"""
_STORENAME = babase.storagename()
@classmethod
def get(cls) -> QuakeBallFactory:
"""Get/create a shared bascenev1lib.actor.bomb.BombFactory object."""
activity = bs.getactivity()
factory = activity.customdata.get(cls._STORENAME)
if factory is None:
factory = QuakeBallFactory()
activity.customdata[cls._STORENAME] = factory
assert isinstance(factory, QuakeBallFactory)
return factory
def __init__(self):
shared = SharedObjects.get()
self.ball_material = bs.Material()
self.ball_material.add_actions(
conditions=((('we_are_younger_than', 5), 'or', ('they_are_younger_than', 50)),
'and', ('they_have_material', shared.object_material)),
actions=(('modify_node_collision', 'collide', False)))
self.ball_material.add_actions(
conditions=('they_have_material', shared.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', shared.player_material),
actions=(('modify_part_collision', 'physical', False),
('message', 'our_node', 'at_connect', TouchedToSpaz())))
self.ball_material.add_actions(
conditions=(('they_dont_have_material', shared.player_material), 'and',
('they_have_material', shared.object_material)),
actions=('message', 'our_node', 'at_connect', TouchedToAnything()))
self.ball_material.add_actions(
conditions=(('they_dont_have_material', shared.player_material), 'and',
('they_have_material', shared.footing_material)),
actions=('message', 'our_node', 'at_connect', TouchedToFootingMaterial()))
def give(self, spaz):
spaz.punch_callback = self.shot
self.last_shot = int(bs.time() * 1000)
def shot(self, spaz):
time = int(bs.time() * 1000)
if time - self.last_shot > 0.6:
self.last_shot = time
p1 = spaz.node.position_center
p2 = spaz.node.position_forward
direction = [p1[0]-p2[0], p2[1]-p1[1], p1[2]-p2[2]]
direction[1] = 0.0
mag = 10.0/babase.Vec3(*direction).length()
vel = [v * mag for v in direction]
QuakeBall(
position=spaz.node.position,
velocity=(vel[0]*2, vel[1]*2, vel[2]*2),
owner=spaz._player,
source_player=spaz._player,
color=spaz.node.color).autoretain()
class QuakeBall(bs.Actor):
def __init__(self,
position=(0, 5, 0),
velocity=(0, 2, 0),
source_player=None,
owner=None,
color=(random.random(), random.random(), random.random()),
light_radius=0
):
super().__init__()
shared = SharedObjects.get()
b_shared = QuakeBallFactory.get()
self.source_player = source_player
self.owner = owner
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': [shared.object_material,
b_shared.ball_material]})
self.light_node = bs.newnode('light', attrs={
'position': position,
'color': color,
'radius': 0.1+light_radius,
'volume_intensity_scale': 15.0})
self.node.connectattr('position', self.light_node, 'position')
self.emit_time = bs.Timer(0.015, bs.WeakCall(self.emit), repeat=True)
self.life_time = bs.Timer(5.0, bs.WeakCall(self.handlemessage, bs.DieMessage()))
def emit(self):
bs.emitfx(
position=self.node.position,
velocity=self.node.velocity,
count=10,
scale=0.4,
spread=0.01,
chunk_type='spark')
def handlemessage(self, m):
if isinstance(m, TouchedToAnything):
node = bs.getcollision().opposingnode
if node is not None and node.exists():
v = self.node.velocity
t = self.node.position
hitdir = self.node.velocity
m = self.node
node.handlemessage(
bs.HitMessage(
pos=t,
velocity=v,
magnitude=babase.Vec3(*v).length()*40,
velocity_magnitude=babase.Vec3(*v).length()*40,
radius=0,
srcnode=self.node,
source_player=self.source_player,
force_direction=hitdir))
self.node.handlemessage(bs.DieMessage())
elif isinstance(m, bs.DieMessage):
if self.node.exists():
velocity = self.node.velocity
explosion = bs.newnode('explosion', attrs={
'position': self.node.position,
'velocity': (velocity[0], max(-1.0, velocity[1]), velocity[2]),
'radius': 1,
'big': False})
bs.getsound(random.choice(['impactHard', 'impactHard2', 'impactHard3'])).play(),
position = self.node.position
self.emit_time = None
self.light_node.delete()
self.node.delete()
elif isinstance(m, bs.OutOfBoundsMessage):
self.handlemessage(bs.DieMessage())
elif isinstance(m, bs.HitMessage):
self.node.handlemessage('impulse', m.pos[0], m.pos[1], m.pos[2],
m.velocity[0], m.velocity[1], m.velocity[2],
1.0*m.magnitude, 1.0*m.velocity_magnitude, m.radius, 0,
m.force_direction[0], m.force_direction[1], m.force_direction[2])
elif isinstance(m, TouchedToSpaz):
node = bs.getcollision() .opposingnode
if node is not None and node.exists() and node != self.owner \
and node.getdelegate(object)._player.team != self.owner.team:
node.handlemessage(bs.FreezeMessage())
v = self.node.velocity
t = self.node.position
hitdir = self.node.velocity
node.handlemessage(
bs.HitMessage(
pos=t,
velocity=(10, 10, 10),
magnitude=50,
velocity_magnitude=50,
radius=0,
srcnode=self.node,
source_player=self.source_player,
force_direction=hitdir))
self.node.handlemessage(bs.DieMessage())
elif isinstance(m, TouchedToFootingMaterial):
bs.getsound('blip').play(),
position = self.node.position
else:
super().handlemessage(m)
class Player(bs.Player['Team']):
...
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export bascenev1.GameActivity
class QuakeGame(bs.TeamGameActivity[Player, Team]):
"""A game type based on acquiring kills."""
name = 'Quake'
description = 'Kill a set number of enemies to win.'
# Print messages when players die since it matters here.
announce_player_deaths = True
@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 ['Doom Shroom', 'Monkey Face', 'Football Stadium']
@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(
'Graphics',
choices=[
('Normal', 1),
('High', 2)
],
default=1),
bs.BoolSetting('Fast Movespeed', default=True),
bs.BoolSetting('Enable Jump', default=False),
bs.BoolSetting('Enable Pickup', default=False),
bs.BoolSetting('Enable Bomb', default=False),
bs.BoolSetting('Obstacles', default=False),
bs.IntChoiceSetting(
'Obstacles Shape',
choices=[
('Cube', 1),
('Sphere', 2),
('Puck', 3),
('Egg', 4),
('Random', 5),
],
default=1),
bs.BoolSetting('Obstacles Bounces Shots', default=False),
bs.IntSetting(
'Obstacle Count',
min_value=1,
default=16,
increment=1,
),
bs.BoolSetting('Random Obstacle Color', default=True),
bs.BoolSetting('Epic Mode', default=False),
]
return settings
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_to_win: int | None = None
self._dingsound = bs.getsound('dingSmall')
self._epic_mode = bool(settings['Epic Mode'])
self._kills_to_win_per_player = int(settings['Kills to Win Per Player'])
self._time_limit = float(settings['Time Limit'])
self._allow_negative_scores = bool(
settings.get('Allow Negative Scores', False)
)
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.TO_THE_DEATH
)
self.settings = settings
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_begin(self) -> None:
super().on_begin()
self.dingsound = bs.getsound('dingSmall')
self.setup_standard_time_limit(self._time_limit)
self.drop_shield()
self.drop_shield_timer = bs.Timer(8.001, bs.WeakCall(self.drop_shield), repeat=True)
shared = SharedObjects.get()
if self.settings['Obstacles']:
count = self.settings['Obstacle Count']
map = bs.getactivity()._map.getname()
for i in range(count):
if map == 'Football Stadium':
radius = (random.uniform(-10, 1),
6,
random.uniform(-4.5, 4.5)) \
if i > count/2 else (random.uniform(10, 1), 6, random.uniform(-4.5, 4.5))
else:
radius = (random.uniform(-10, 1),
6,
random.uniform(-8, 8)) \
if i > count/2 else (random.uniform(10, 1), 6, random.uniform(-8, 8))
Obstacle(
position=radius,
graphics=self.settings['Graphics'],
random_color=self.settings['Random Obstacle Color'],
rebound=self.settings['Obstacles Bounces Shots'],
shape=int(self.settings['Obstacles Shape'])).autoretain()
if self.settings['Graphics'] == 2:
bs.getactivity().globalsnode.tint = (bs.getactivity(
).globalsnode.tint[0]-0.6, bs.getactivity().globalsnode.tint[1]-0.6, bs.getactivity().globalsnode.tint[2]-0.6)
light = bs.newnode('light', attrs={
'position': (9, 10, 0) if map == 'Football Stadium' else (6, 7, -2)
if not map == 'Rampage' else (6, 11, -2) if not map == 'The Pad' else (6, 8.5, -2),
'color': (0.4, 0.4, 0.45),
'radius': 1,
'intensity': 6,
'volume_intensity_scale': 10.0})
light2 = bs.newnode('light', attrs={
'position': (-9, 10, 0) if map == 'Football Stadium' else (-6, 7, -2)
if not map == 'Rampage' else (-6, 11, -2) if not map == 'The Pad' else (-6, 8.5, -2),
'color': (0.4, 0.4, 0.45),
'radius': 1,
'intensity': 6,
'volume_intensity_scale': 10.0})
if len(self.teams) > 0:
self._score_to_win = self.settings['Kills to Win Per Player'] * \
max(1, max(len(t.players) for t in self.teams))
else:
self._score_to_win = self.settings['Kills to Win Per Player']
self._update_scoreboard()
def drop_shield(self):
p = Powerup(
poweruptype='shield',
position=(random.uniform(-10, 10), 6, random.uniform(-5, 5))).autoretain()
bs.getsound('dingSmall').play()
p_light = bs.newnode('light', attrs={
'position': (0, 0, 0),
'color': (0.3, 0.0, 0.4),
'radius': 0.3,
'intensity': 2,
'volume_intensity_scale': 10.0})
p.node.connectattr('position', p_light, 'position')
bs.animate(p_light, 'intensity', {0: 2, 8000: 0})
def check_exists():
if p is None or p.node.exists() == False:
delete_light()
del_checker()
self._checker = bs.Timer(0.1, babase.Call(check_exists), repeat=True)
def del_checker():
if self._checker is not None:
self._checker = None
def delete_light():
if p_light.exists():
p_light.delete()
bs.timer(6.9, babase.Call(del_checker))
bs.timer(7.0, babase.Call(delete_light))
def spawn_player(self, player: bs.Player):
spaz = self.spawn_player_spaz(player)
QuakeBallFactory().give(spaz)
spaz.connect_controls_to_player(
enable_jump=self.settings['Enable Jump'],
enable_punch=True,
enable_pickup=self.settings['Enable Pickup'],
enable_bomb=self.settings['Enable Bomb'],
enable_run=True,
enable_fly=False)
if self.settings['Fast Movespeed']:
spaz.node.hockey = True
spaz.spaz_light = bs.newnode('light', attrs={
'position': (0, 0, 0),
'color': spaz.node.color,
'radius': 0.12,
'intensity': 1,
'volume_intensity_scale': 10.0})
spaz.node.connectattr('position', spaz.spaz_light, 'position')
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 hasattr(player.actor, 'spaz_light'):
player.actor.spaz_light.delete()
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)
class Obstacle(bs.Actor):
def __init__(self,
position: tuple(float, float, float),
graphics: bool,
random_color: bool,
rebound: bool,
shape: int) -> None:
super().__init__()
shared = SharedObjects.get()
if shape == 1:
mesh = 'tnt'
body = 'crate'
elif shape == 2:
mesh = 'bomb'
body = 'sphere'
elif shape == 3:
mesh = 'puck'
body = 'puck'
elif shape == 4:
mesh = 'egg'
body = 'capsule'
elif shape == 5:
pair = random.choice([
{'mesh': 'tnt', 'body': 'crate'},
{'mesh': 'bomb', 'body': 'sphere'},
{'mesh': 'puckModel', 'body': 'puck'},
{'mesh': 'egg', 'body': 'capsule'}
])
mesh = pair['mesh']
body = pair['body']
self.node = bs.newnode('prop', delegate=self, attrs={
'position': position,
'mesh': bs.getmesh(mesh),
'body': body,
'body_scale': 1.3,
'mesh_scale': 1.3,
'reflection': 'powerup',
'reflection_scale': [0.7],
'color_texture': bs.gettexture('bunnyColor'),
'materials': [shared.footing_material if rebound else shared.object_material,
shared.footing_material]})
if graphics == 2:
self.light_node = bs.newnode('light', attrs={
'position': (0, 0, 0),
'color': ((0.8, 0.2, 0.2) if i < count/2 else (0.2, 0.2, 0.8))
if not random_color else ((random.uniform(0, 1.1), random.uniform(0, 1.1), random.uniform(0, 1.1))),
'radius': 0.2,
'intensity': 1,
'volume_intensity_scale': 10.0})
self.node.connectattr('position', self.light_node, 'position')
def handlemessage(self, m):
if isinstance(m, bs.DieMessage):
if self.node.exists():
if hasattr(self, 'light_node'):
self.light_node.delete()
self.node.delete()
elif isinstance(m, bs.OutOfBoundsMessage):
if self.node.exists():
self.handlemessage(bs.DieMessage())
elif isinstance(m, bs.HitMessage):
self.node.handlemessage('impulse', m.pos[0], m.pos[1], m.pos[2],
m.velocity[0], m.velocity[1], m.velocity[2],
m.magnitude, m.velocity_magnitude, m.radius, 0,
m.velocity[0], m.velocity[1], m.velocity[2])

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

@ -0,0 +1,375 @@
# Released under the MIT License. See LICENSE for details.
# BY Stary_Agent
"""Hockey 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 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
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: Dict[int, Player] = {}
self.scored = False
assert activity is not None
assert isinstance(activity, HockeyGame)
pmats = [shared.object_material, activity.puck_material]
self.node = bs.newnode('prop',
delegate=self,
attrs={
'mesh': activity.puck_model,
'color_texture': activity.puck_tex,
'body': 'sphere',
'reflection': 'soft',
'reflection_scale': [0.2],
'shadow_size': 0.5,
'is_area_of_interest': True,
'position': self._spawn_pos,
'materials': pmats
})
bs.animate(self.node, 'mesh_scale', {0: 0, 0.2: 1.3, 0.26: 1})
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.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
# ba_meta export bascenev1.GameActivity
class HockeyGame(bs.TeamGameActivity[Player, Team]):
"""Ice hockey game."""
name = 'Epic Soccer'
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),
('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]:
assert babase.app.classic is not None
return babase.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_model = bs.getmesh('bomb')
self.puck_tex = bs.gettexture('landMine')
self.puck_scored_tex = bs.gettexture('landMineLit')
self._puck_sound = bs.getsound('metalHit')
self.puck_material = bs.Material()
self.puck_material.add_actions(actions=(('modify_part_collision',
'friction', 0.5)))
self.puck_material.add_actions(conditions=('they_have_material',
shared.pickup_material),
actions=('modify_part_collision',
'collide', True))
self.puck_material.add_actions(
conditions=(
('we_are_younger_than', 100),
'and',
('they_have_material', shared.object_material),
),
actions=('modify_node_collision', 'collide', False),
)
self.puck_material.add_actions(conditions=('they_have_material',
shared.footing_material),
actions=('impact_sound',
self._puck_sound, 0.2, 5))
# Keep track of which player last touched the puck
self.puck_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('call', 'at_connect',
self._handle_puck_player_collide), ))
# We want the puck to kill powerups; not get stopped by them
self.puck_material.add_actions(
conditions=('they_have_material',
PowerupBoxFactory.get().powerup_material),
actions=(('modify_part_collision', 'physical', False),
('message', 'their_node', 'at_connect', bs.DieMessage())))
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._puck_spawn_pos: Optional[Sequence[float]] = None
self._score_regions: Optional[List[bs.NodeActor]] = None
self._puck: Optional[Puck] = None
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
def get_instance_description(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'Score a goal.'
return 'Score ${ARG1} goals.', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'score a goal'
return 'score ${ARG1} goals', self._score_to_win
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._puck_spawn_pos = self.map.get_flag_position(None)
self._spawn_puck()
# Set up the two score regions.
defs = self.map.defs
self._score_regions = []
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': defs.boxes['goal1'][0:3],
'scale': defs.boxes['goal1'][6:9],
'type': 'box',
'materials': [self._score_region_material]
})))
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': defs.boxes['goal2'][0:3],
'scale': defs.boxes['goal2'][6:9],
'type': 'box',
'materials': [self._score_region_material]
})))
self._update_scoreboard()
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.team.id] = player
def _kill_puck(self) -> None:
self._puck = None
def _handle_score(self) -> None:
"""A point has been scored."""
assert self._puck is not None
assert self._score_regions is not None
# Our puck might stick around for a second or two
# we don't want it to be able to score again.
if self._puck.scored:
return
region = 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)
def _spawn_puck(self) -> None:
self._swipsound.play()
self._whistle_sound.play()
self._flash_puck_spawn()
assert self._puck_spawn_pos is not None
self._puck = Puck(position=self._puck_spawn_pos)

602
dist/ba_root/mods/games/super_duel.py vendored Normal file
View file

@ -0,0 +1,602 @@
"""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 random
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.game.elimination import Icon
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional
class SuperSpaz(PlayerSpaz):
def __init__(self,
player: bs.Player,
color: Sequence[float] = (1.0, 1.0, 1.0),
highlight: Sequence[float] = (0.5, 0.5, 0.5),
character: str = 'Spaz',
super_punch: bool = False,
powerups_expire: bool = True):
super().__init__(player=player,
color=color,
highlight=highlight,
character=character,
powerups_expire=powerups_expire)
self._super_punch = super_punch
def handlemessage(self, msg: Any) -> Any:
from bascenev1lib.actor.spaz import PunchHitMessage
from bascenev1lib.actor.bomb import Blast
if isinstance(msg, PunchHitMessage):
super().handlemessage(msg)
node = bs.getcollision().opposingnode
if self._super_punch:
if node.getnodetype() == 'spaz':
if not node.frozen:
node.frozen = True
node.handlemessage(babase.FreezeMessage())
bs.getsound('freeze').play()
bs.getsound('superPunch').play()
bs.getsound('punchStrong02').play()
Blast(position=node.position,
velocity=node.velocity,
blast_radius=0.0,
blast_type='normal').autoretain()
else:
return super().handlemessage(msg)
return None
class Player(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.icons: List[Icon] = []
self.in_game: bool = False
self.playervs1: bool = False
self.playervs2: bool = False
self.light: bool = False
class Team(bs.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
lang = bs.app.lang.language
if lang == 'Spanish':
enable_powerups = 'Habilitar Potenciadores'
night_mode = 'Modo Noche'
fight_delay = 'Tiempo entre Pelea'
very_fast = 'Muy Rápido'
fast = 'Rápido'
normal = 'Normal'
slow = 'Lento'
very_slow = 'Muy Lento'
none = 'Ninguno'
super_punch = 'Super Golpe'
box_mode = 'Modo Caja'
boxing_gloves = 'Guantes de Boxeo'
else:
enable_powerups = 'Enable Powerups'
night_mode = 'Night Mode'
fight_delay = 'Fight Delay'
very_fast = 'Very Fast'
fast = 'Fast'
normal = 'Normal'
slow = 'Slow'
very_slow = 'Very Slow'
super_punch = 'Super Punch'
box_mode = 'Box Mode'
boxing_gloves = 'Boxing Gloves'
# ba_meta export bascenev1.GameActivity
class NewDuelGame(bs.TeamGameActivity[Player, Team]):
"""A game type based on acquiring kills."""
name = 'Duel'
description = 'Kill a set number of enemies to win.'
# Print messages when players die since it matters here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]:
settings = [
bs.IntSetting(
'Kills to Win Per Player',
min_value=1,
default=5,
increment=1,
),
bs.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
bs.BoolSetting(enable_powerups, default=False),
bs.BoolSetting(boxing_gloves, default=False),
bs.BoolSetting(night_mode, default=False),
bs.BoolSetting(super_punch, default=False),
bs.BoolSetting(box_mode, default=False),
bs.BoolSetting('Epic Mode', default=False),
bs.BoolSetting('Allow Negative Scores', default=False),
]
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
return (issubclass(sessiontype, bs.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
return bs.app.classic.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_to_win: Optional[int] = None
self._dingsound = bs.getsound('dingSmall')
self._epic_mode = bool(settings['Epic Mode'])
self._kills_to_win_per_player = int(
settings['Kills to Win Per Player'])
self._enable_powerups = bool(settings[enable_powerups])
self._night_mode = bool(settings[night_mode])
self._fight_delay: float = 0
self._time_limit = float(settings['Time Limit'])
self._allow_negative_scores = bool(
settings.get('Allow Negative Scores', False))
self._super_punch = bool(settings[super_punch])
self._box_mode = bool(settings[box_mode])
self._boxing_gloves = bool(settings[boxing_gloves])
self._vs_text: Optional[bs.Actor] = None
self.spawn_order: List[Player] = []
self._players_vs_1: bool = False
self._players_vs_2: bool = False
self._first_countdown: bool = True
self._count_1 = bs.getsound('announceOne')
self._count_2 = bs.getsound('announceTwo')
self._count_3 = bs.getsound('announceThree')
self._boxing_bell = bs.getsound('boxingBell')
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (bs.MusicType.EPIC if self._epic_mode else
bs.MusicType.TO_THE_DEATH)
def get_instance_description(self) -> Union[str, Sequence]:
return 'Crush ${ARG1} of your enemies.', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
return 'kill ${ARG1} enemies', self._score_to_win
def on_player_join(self, player: Player) -> None:
self.spawn_order.append(player)
self._update_order()
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
player.icons = []
if player.playervs1:
player.playervs1 = False
self._players_vs_1 = False
player.in_game = False
elif player.playervs2:
player.playervs2 = False
self._players_vs_2 = False
player.in_game = False
if player in self.spawn_order:
self.spawn_order.remove(player)
bs.timer(0.2, self._update_order)
def on_transition_in(self) -> None:
super().on_transition_in()
if self._night_mode:
gnode = bs.getactivity().globalsnode
gnode.tint = (0.3, 0.3, 0.3)
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scoreboard()
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
if self._enable_powerups:
self.setup_standard_powerup_drops()
self._vs_text = bs.NodeActor(
bs.newnode('text',
attrs={
'position': (0, 105),
'h_attach': 'center',
'h_align': 'center',
'maxwidth': 200,
'shadow': 0.5,
'vr_depth': 390,
'scale': 0.6,
'v_attach': 'bottom',
'color': (0.8, 0.8, 0.3, 1.0),
'text': babase.Lstr(resource='vsText')
}))
# Base kills needed to win on the size of the largest team.
self._score_to_win = (self._kills_to_win_per_player *
max(1, max(len(t.players) for t in self.teams)))
self._update_scoreboard()
bs.timer(1.0, self._update, repeat=True)
def _update(self) -> None:
if len(self.players) == 1:
'self.end_game()'
def spawn_player(self, player: PlayerType) -> bs.Actor:
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
from babase import _math
from bascenev1._coopsession import CoopSession
from bascenev1lib.actor.spazfactory import SpazFactory
factory = SpazFactory.get()
name = player.getname()
color = player.color
highlight = player.highlight
light_color = _math.normalized_color(color)
display_color = babase.safecolor(color, target_intensity=0.75)
spaz = SuperSpaz(color=color,
highlight=highlight,
character=player.character,
player=player,
super_punch=True if self._super_punch else False)
player.actor = spaz
assert spaz.node
# If this is co-op and we're on Courtyard or Runaround, add the
# material that allows us to collide with the player-walls.
# FIXME: Need to generalize this.
if isinstance(self.session, CoopSession) and self.map.getname() in [
'Courtyard', 'Tower D'
]:
mat = self.map.preloaddata['collide_with_wall_material']
assert isinstance(spaz.node.materials, tuple)
assert isinstance(spaz.node.roller_materials, tuple)
spaz.node.materials += (mat, )
spaz.node.roller_materials += (mat, )
spaz.node.name = name
spaz.node.name_color = display_color
spaz.connect_controls_to_player()
self._spawn_sound.play(1, position=spaz.node.position)
light = bs.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
bs.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
bs.timer(0.5, light.delete)
pos1 = [self.map.get_start_position(0), 90]
pos2 = [self.map.get_start_position(1), 270]
pos3 = []
for x in self.players:
if x.is_alive():
if x is player:
continue
p = x.actor.node.position
if 0.0 not in (p[0], p[2]):
if p[0] <= 0:
pos3.append(pos2[0])
else:
pos3.append(pos1[0])
spaz.handlemessage(bs.StandMessage(pos1[0] if player.playervs1 else pos2[0],
pos1[1] if player.playervs1 else pos2[1]))
if any(pos3):
spaz.handlemessage(bs.StandMessage(pos3[0]))
if self._super_punch:
spaz._punch_power_scale = factory.punch_power_scale_gloves = 10
spaz.equip_boxing_gloves()
lfx = bs.newnode(
'light',
attrs={
'color': color,
'radius': 0.3,
'intensity': 0.3})
def sp_fx():
if not spaz.node:
lfx.delete()
return
bs.emitfx(position=spaz.node.position,
velocity=spaz.node.velocity,
count=5,
scale=0.5,
spread=0.5,
chunk_type='spark')
bs.emitfx(position=spaz.node.position,
velocity=spaz.node.velocity,
count=2,
scale=0.8,
spread=0.3,
chunk_type='spark')
if lfx:
spaz.node.connectattr('position', lfx, 'position')
bs.timer(0.1, sp_fx, repeat=True)
if self._box_mode:
spaz.node.color_texture = bs.gettexture('tnt')
spaz.node.color_mask_texture = bs.gettexture('tnt')
spaz.node.color = (1, 1, 1)
spaz.node.highlight = (1, 1, 1)
spaz.node.head_mesh = None
spaz.node.torso_mesh = bs.getmesh('tnt')
spaz.node.style = 'cyborg'
if self._boxing_gloves:
spaz.equip_boxing_gloves()
return spaz
def _update_spawn(self) -> None:
if self._players_vs_1 or self._players_vs_2:
for player in self.players:
if player.playervs1 or player.playervs2:
if not player.is_alive():
self.spawn_player(player)
# player.actor.disconnect_controls_from_player()
if self._night_mode:
if not player.light:
player.light = True
light = bs.newnode(
'light',
owner=player.node,
attrs={
'radius': 0.3,
'intensity': 0.6,
'height_attenuated': False,
'color': player.color
})
player.node.connectattr(
'position', light, 'position')
else:
player.actor.disconnect_controls_from_player()
bs.timer(0.0, self._countdown)
# bs.timer(0.1, self._clear_all_objects)
def _countdown(self) -> None:
self._first_countdown = False
if self._fight_delay == 0:
for player in self.players:
if player.playervs1 or player.playervs2:
if not player.is_alive():
return
else:
player.actor.connect_controls_to_player()
else:
bs.timer(self._fight_delay, self.count3)
def start(self) -> None:
self._count_text('FIGHT')
self._boxing_bell.play()
for player in self.players:
if player.playervs1 or player.playervs2:
if not player.is_alive():
return
else:
player.actor.connect_controls_to_player()
def count(self) -> None:
self._count_text('1')
self._count_1.play()
bs.timer(self._fight_delay, self.start)
def count2(self) -> None:
self._count_text('2')
self._count_2.play()
bs.timer(self._fight_delay, self.count)
def count3(self) -> None:
self._count_text('3')
self._count_3.play()
bs.timer(self._fight_delay, self.count2)
def _count_text(self, num: str) -> None:
self.node = bs.newnode('text',
attrs={
'v_attach': 'center',
'h_attach': 'center',
'h_align': 'center',
'color': (1, 1, 0.5, 1),
'flatness': 0.5,
'shadow': 0.5,
'position': (0, 18),
'text': num
})
if self._fight_delay == 0.7:
bs.animate(self.node, 'scale',
{0: 0, 0.1: 3.9, 0.64: 4.3, 0.68: 0})
elif self._fight_delay == 0.4:
bs.animate(self.node, 'scale',
{0: 0, 0.1: 3.9, 0.34: 4.3, 0.38: 0})
else:
bs.animate(self.node, 'scale',
{0: 0, 0.1: 3.9, 0.92: 4.3, 0.96: 0})
cmb = bs.newnode('combine', owner=self.node, attrs={'size': 4})
cmb.connectattr('output', self.node, 'color')
bs.animate(cmb, 'input0', {0: 1.0, 0.15: 1.0}, loop=True)
bs.animate(cmb, 'input1', {0: 1.0, 0.15: 0.5}, loop=True)
bs.animate(cmb, 'input2', {0: 0.1, 0.15: 0.0}, loop=True)
cmb.input3 = 1.0
bs.timer(self._fight_delay, self.node.delete)
def _update_order(self) -> None:
for player in self.spawn_order:
assert isinstance(player, Player)
if not player.is_alive():
if not self._players_vs_1:
self._players_vs_1 = True
player.playervs1 = True
player.in_game = True
self.spawn_order.remove(player)
self._update_spawn()
elif not self._players_vs_2:
self._players_vs_2 = True
player.playervs2 = True
player.in_game = True
self.spawn_order.remove(player)
self._update_spawn()
self._update_icons()
def _update_icons(self) -> None:
# pylint: disable=too-many-branches
for player in self.players:
player.icons = []
if player.in_game:
if player.playervs1:
xval = -60
x_offs = -78
elif player.playervs2:
xval = 60
x_offs = 78
player.icons.append(
Icon(player,
position=(xval, 40),
scale=1.0,
name_maxwidth=130,
name_scale=0.8,
flatness=0.0,
shadow=0.5,
show_death=True,
show_lives=False))
else:
xval = 125
xval2 = -125
x_offs = 78
for player in self.spawn_order:
player.icons.append(
Icon(player,
position=(xval, 25),
scale=0.5,
name_maxwidth=75,
name_scale=1.0,
flatness=1.0,
shadow=1.0,
show_death=False,
show_lives=False))
xval += x_offs * 0.56
player.icons.append(
Icon(player,
position=(xval2, 25),
scale=0.5,
name_maxwidth=75,
name_scale=1.0,
flatness=1.0,
shadow=1.0,
show_death=False,
show_lives=False))
xval2 -= x_offs * 0.56
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player = msg.getplayer(Player)
if player.playervs1:
player.playervs1 = False
self._players_vs_1 = False
player.in_game = False
self.spawn_order.append(player)
elif player.playervs2:
player.playervs2 = False
self._players_vs_2 = False
player.in_game = False
self.spawn_order.append(player)
bs.timer(0.2, self._update_order)
killer = msg.getkillerplayer(Player)
if killer is None:
return None
# Handle team-kills.
if killer.team is player.team:
# In free-for-all, killing yourself loses you a point.
if isinstance(self.session, bs.FreeForAllSession):
new_score = player.team.score - 1
if not self._allow_negative_scores:
new_score = max(0, new_score)
player.team.score = new_score
# In teams-mode it gives a point to the other team.
else:
self._dingsound.play()
for team in self.teams:
if team is not killer.team:
team.score += 1
# Killing someone on another team nets a kill.
else:
killer.team.score += 1
self._dingsound.play()
# In FFA show scores since its hard to find on the scoreboard.
if isinstance(killer.actor, PlayerSpaz) and killer.actor:
killer.actor.set_score_text(str(killer.team.score) + '/' +
str(self._score_to_win),
color=killer.team.color,
flash=True)
self._update_scoreboard()
# If someone has won, set a timer to end shortly.
# (allows the dust to clear and draws to occur if deaths are
# close enough)
assert self._score_to_win is not None
if any(team.score >= self._score_to_win for team in self.teams):
bs.timer(0.5, self.end_game)
else:
return super().handlemessage(msg)
return None
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(team, team.score,
self._score_to_win)
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)

956
dist/ba_root/mods/games/supersmash.py vendored Normal file
View file

@ -0,0 +1,956 @@
# To learn more, see https://ballistica.net/wiki/meta-tag-system
# ba_meta require api 8
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import random
import bauiv1 as bui
import bascenev1 as bs
from babase import _math
from bascenev1lib.actor.spaz import Spaz
from bascenev1lib.actor.spazfactory import SpazFactory
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.game import elimination
from bascenev1lib.game.elimination import Icon, Player, Team
from bascenev1lib.actor.bomb import Bomb, Blast
from bascenev1lib.actor.playerspaz import PlayerSpaz, PlayerSpazHurtMessage
if TYPE_CHECKING:
from typing import Any, Type, List, Sequence, Optional
class Icon(Icon):
def update_for_lives(self) -> None:
"""Update for the target player's current lives."""
if self._player:
lives = self._player.lives
else:
lives = 0
if self._show_lives:
if lives > 1:
self._lives_text.text = 'x' + str(lives - 1)
else:
self._lives_text.text = ''
if lives == 0:
self._name_text.opacity = 0.2
assert self.node
self.node.color = (0.7, 0.3, 0.3)
self.node.opacity = 0.2
class PowBox(Bomb):
def __init__(self,
position: Sequence[float] = (0.0, 1.0, 0.0),
velocity: Sequence[float] = (0.0, 0.0, 0.0)) -> None:
Bomb.__init__(self,
position,
velocity,
bomb_type='tnt',
blast_radius=2.5,
source_player=None,
owner=None)
self.set_pow_text()
def set_pow_text(self) -> None:
m = bs.newnode('math',
owner=self.node,
attrs={'input1': (0, 0.7, 0),
'operation': 'add'})
self.node.connectattr('position', m, 'input2')
self._pow_text = bs.newnode('text',
owner=self.node,
attrs={'text': 'POW!',
'in_world': True,
'shadow': 1.0,
'flatness': 1.0,
'color': (1, 1, 0.4),
'scale': 0.0,
'h_align': 'center'})
m.connectattr('output', self._pow_text, 'position')
bs.animate(self._pow_text, 'scale', {0: 0.0, 1.0: 0.01})
def pow(self) -> None:
self.explode()
def handlemessage(self, m: Any) -> Any:
if isinstance(m, babase.PickedUpMessage):
self._heldBy = m.node
elif isinstance(m, bs.DroppedMessage):
bs.timer(0.6, self.pow)
Bomb.handlemessage(self, m)
class SSPlayerSpaz(PlayerSpaz):
multiplyer = 1
is_dead = False
def oob_effect(self) -> None:
if self.is_dead:
return
self.is_dead = True
if self.multiplyer > 1.25:
blast_type = 'tnt'
radius = min(self.multiplyer * 5, 20)
else:
# penalty for killing people with low multiplyer
blast_type = 'ice'
radius = 7.5
Blast(position=self.node.position,
blast_radius=radius,
blast_type=blast_type).autoretain()
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.HitMessage):
if not self.node:
return None
if self.node.invincible:
SpazFactory.get().block_sound.play(1.0, position=self.node.position)
return True
# If we were recently hit, don't count this as another.
# (so punch flurries and bomb pileups essentially count as 1 hit)
local_time = int(bs.time() * 1000)
assert isinstance(local_time, int)
if (self._last_hit_time is None
or local_time - self._last_hit_time > 1000):
self._num_times_hit += 1
self._last_hit_time = local_time
mag = msg.magnitude * self.impact_scale
velocity_mag = msg.velocity_magnitude * self.impact_scale
damage_scale = 0.22
# If they've got a shield, deliver it to that instead.
if self.shield:
if msg.flat_damage:
damage = msg.flat_damage * self.impact_scale
else:
# Hit our spaz with an impulse but tell it to only return
# theoretical damage; not apply the impulse.
assert msg.force_direction is not None
self.node.handlemessage(
'impulse', msg.pos[0], msg.pos[1], msg.pos[2],
msg.velocity[0], msg.velocity[1], msg.velocity[2], mag,
velocity_mag, msg.radius, 1, msg.force_direction[0],
msg.force_direction[1], msg.force_direction[2])
damage = damage_scale * self.node.damage
assert self.shield_hitpoints is not None
self.shield_hitpoints -= int(damage)
self.shield.hurt = (
1.0 -
float(self.shield_hitpoints) / self.shield_hitpoints_max)
# Its a cleaner event if a hit just kills the shield
# without damaging the player.
# However, massive damage events should still be able to
# damage the player. This hopefully gives us a happy medium.
max_spillover = SpazFactory.get().max_shield_spillover_damage
if self.shield_hitpoints <= 0:
# FIXME: Transition out perhaps?
self.shield.delete()
self.shield = None
SpazFactory.get().shield_down_sound.play(1.0, position=self.node.position)
# Emit some cool looking sparks when the shield dies.
npos = self.node.position
bs.emitfx(position=(npos[0], npos[1] + 0.9, npos[2]),
velocity=self.node.velocity,
count=random.randrange(20, 30),
scale=1.0,
spread=0.6,
chunk_type='spark')
else:
SpazFactory.get().shield_hit_sound.play(0.5, position=self.node.position)
# Emit some cool looking sparks on shield hit.
assert msg.force_direction is not None
bs.emitfx(position=msg.pos,
velocity=(msg.force_direction[0] * 1.0,
msg.force_direction[1] * 1.0,
msg.force_direction[2] * 1.0),
count=min(30, 5 + int(damage * 0.005)),
scale=0.5,
spread=0.3,
chunk_type='spark')
# If they passed our spillover threshold,
# pass damage along to spaz.
if self.shield_hitpoints <= -max_spillover:
leftover_damage = -max_spillover - self.shield_hitpoints
shield_leftover_ratio = leftover_damage / damage
# Scale down the magnitudes applied to spaz accordingly.
mag *= shield_leftover_ratio
velocity_mag *= shield_leftover_ratio
else:
return True # Good job shield!
else:
shield_leftover_ratio = 1.0
if msg.flat_damage:
damage = int(msg.flat_damage * self.impact_scale *
shield_leftover_ratio)
else:
# Hit it with an impulse and get the resulting damage.
assert msg.force_direction is not None
self.node.handlemessage(
'impulse', msg.pos[0], msg.pos[1], msg.pos[2],
msg.velocity[0], msg.velocity[1], msg.velocity[2], mag,
velocity_mag, msg.radius, 0, msg.force_direction[0],
msg.force_direction[1], msg.force_direction[2])
damage = int(damage_scale * self.node.damage)
self.node.handlemessage('hurt_sound')
# Play punch impact sound based on damage if it was a punch.
if msg.hit_type == 'punch':
self.on_punched(damage)
# If damage was significant, lets show it.
# if damage > 350:
# assert msg.force_direction is not None
# babase.show_damage_count('-' + str(int(damage / 10)) + '%',
# msg.pos, msg.force_direction)
# Let's always add in a super-punch sound with boxing
# gloves just to differentiate them.
if msg.hit_subtype == 'super_punch':
SpazFactory.get().punch_sound_stronger.play(1.0, position=self.node.position)
if damage > 500:
sounds = SpazFactory.get().punch_sound_strong
sound = sounds[random.randrange(len(sounds))]
else:
sound = SpazFactory.get().punch_sound
sound.play(1.0, position=self.node.position)
# Throw up some chunks.
assert msg.force_direction is not None
bs.emitfx(position=msg.pos,
velocity=(msg.force_direction[0] * 0.5,
msg.force_direction[1] * 0.5,
msg.force_direction[2] * 0.5),
count=min(10, 1 + int(damage * 0.0025)),
scale=0.3,
spread=0.03)
bs.emitfx(position=msg.pos,
chunk_type='sweat',
velocity=(msg.force_direction[0] * 1.3,
msg.force_direction[1] * 1.3 + 5.0,
msg.force_direction[2] * 1.3),
count=min(30, 1 + int(damage * 0.04)),
scale=0.9,
spread=0.28)
# Momentary flash.
hurtiness = damage * 0.003
punchpos = (msg.pos[0] + msg.force_direction[0] * 0.02,
msg.pos[1] + msg.force_direction[1] * 0.02,
msg.pos[2] + msg.force_direction[2] * 0.02)
flash_color = (1.0, 0.8, 0.4)
light = bs.newnode(
'light',
attrs={
'position': punchpos,
'radius': 0.12 + hurtiness * 0.12,
'intensity': 0.3 * (1.0 + 1.0 * hurtiness),
'height_attenuated': False,
'color': flash_color
})
bs.timer(0.06, light.delete)
flash = bs.newnode('flash',
attrs={
'position': punchpos,
'size': 0.17 + 0.17 * hurtiness,
'color': flash_color
})
bs.timer(0.06, flash.delete)
if msg.hit_type == 'impact':
assert msg.force_direction is not None
bs.emitfx(position=msg.pos,
velocity=(msg.force_direction[0] * 2.0,
msg.force_direction[1] * 2.0,
msg.force_direction[2] * 2.0),
count=min(10, 1 + int(damage * 0.01)),
scale=0.4,
spread=0.1)
if self.hitpoints > 0:
# It's kinda crappy to die from impacts, so lets reduce
# impact damage by a reasonable amount *if* it'll keep us alive
if msg.hit_type == 'impact' and damage > self.hitpoints:
# Drop damage to whatever puts us at 10 hit points,
# or 200 less than it used to be whichever is greater
# (so it *can* still kill us if its high enough)
newdamage = max(damage - 200, self.hitpoints - 10)
damage = newdamage
self.node.handlemessage('flash')
# If we're holding something, drop it.
if damage > 0.0 and self.node.hold_node:
self.node.hold_node = None
# self.hitpoints -= damage
self.multiplyer += min(damage / 2000, 0.15)
if damage/2000 > 0.05:
self.set_score_text(str(int((self.multiplyer-1)*100))+'%')
# self.node.hurt = 1.0 - float(
# self.hitpoints) / self.hitpoints_max
self.node.hurt = 0.0
# If we're cursed, *any* damage blows us up.
if self._cursed and damage > 0:
bs.timer(
0.05,
bs.WeakCall(self.curse_explode,
msg.get_source_player(bs.Player)))
# If we're frozen, shatter.. otherwise die if we hit zero
# if self.frozen and (damage > 200 or self.hitpoints <= 0):
# self.shatter()
# elif self.hitpoints <= 0:
# self.node.handlemessage(
# bs.DieMessage(how=babase.DeathType.IMPACT))
# If we're dead, take a look at the smoothed damage value
# (which gives us a smoothed average of recent damage) and shatter
# us if its grown high enough.
# if self.hitpoints <= 0:
# damage_avg = self.node.damage_smoothed * damage_scale
# if damage_avg > 1000:
# self.shatter()
source_player = msg.get_source_player(type(self._player))
if source_player:
self.last_player_attacked_by = source_player
self.last_attacked_time = bs.time()
self.last_attacked_type = (msg.hit_type, msg.hit_subtype)
Spaz.handlemessage(self, bs.HitMessage) # Augment standard behavior.
activity = self._activity()
if activity is not None and self._player.exists():
activity.handlemessage(PlayerSpazHurtMessage(self))
elif isinstance(msg, bs.DieMessage):
self.oob_effect()
super().handlemessage(msg)
elif isinstance(msg, bs.PowerupMessage):
if msg.poweruptype == 'health':
if self.multiplyer > 2:
self.multiplyer *= 0.5
else:
self.multiplyer *= 0.75
self.multiplyer = max(1, self.multiplyer)
self.set_score_text(str(int((self.multiplyer-1)*100))+"%")
super().handlemessage(msg)
else:
super().handlemessage(msg)
class Player(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 SuperSmash(bs.TeamGameActivity[Player, Team]):
name = 'Super Smash'
description = 'Knock everyone off the map.'
# 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('Boxing Gloves', default=False),
bs.BoolSetting('Epic Mode', default=False),
]
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]:
maps = bs.app.classic.getmaps('melee')
for m in ['Lake Frigid', 'Hockey Stadium', 'Football Stadium']:
# remove maps without bounds
maps.remove(m)
return maps
def __init__(self, settings: dict):
super().__init__(settings)
self._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._boxing_gloves = bool(settings['Boxing Gloves'])
# 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) -> str | Sequence:
return 'Knock everyone off the map.'
def get_instance_description_short(self) -> str | Sequence:
return 'Knock off the map.'
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(enable_tnt=False)
self._pow = None
self._tnt_drop_timer = bs.timer(1.0 * 0.30,
bs.WeakCall(self._drop_pow_box),
repeat=True)
# 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 _drop_pow_box(self) -> None:
if self._pow is not None and self._pow:
return
if len(self.map.tnt_points) == 0:
return
pos = random.choice(self.map.tnt_points)
pos = (pos[0], pos[1] + 1, pos[2])
self._pow = PowBox(position=pos, velocity=(0.0, 1.0, 0.0))
def spawn_player(self, player: Player) -> bs.Actor:
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()
light_color = _math.normalized_color(player.color)
display_color = babase.safecolor(player.color, target_intensity=0.75)
spaz = SSPlayerSpaz(color=player.color,
highlight=player.highlight,
character=player.character,
player=player)
player.actor = spaz
assert spaz.node
# If this is co-op and we're on Courtyard or Runaround, add the
# material that allows us to collide with the player-walls.
# FIXME: Need to generalize this.
if isinstance(self.session, 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)
if self._boxing_gloves:
spaz.equip_boxing_gloves()
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, SSPlayerSpaz) 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)
class Player2(bs.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.lives = 0
self.icons: List[Icon] = []
class Team2(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 SuperSmashElimination(bs.TeamGameActivity[Player2, Team2]):
name = 'Super Smash Elimination'
description = 'Knock everyone off the map.'
scoreconfig = bs.ScoreConfig(label='Survived',
scoretype=bs.ScoreType.SECONDS,
none_is_winner=True)
# Print messages when players die since it matters here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: Type[bs.Session]) -> List[babase.Setting]:
settings = [
bs.IntSetting(
'Lives (0 = Unlimited)',
min_value=0,
default=3,
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('Boxing Gloves', default=False),
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]:
maps = bs.app.classic.getmaps('melee')
for m in ['Lake Frigid', 'Hockey Stadium', 'Football Stadium']:
# remove maps without bounds
maps.remove(m)
return maps
def __init__(self, settings: dict):
super().__init__(settings)
self.lives = int(settings['Lives (0 = Unlimited)'])
self.time_limit_only = (self.lives == 0)
if self.time_limit_only:
settings['Time Limit'] = max(60, settings['Time Limit'])
self._epic_mode = bool(settings['Epic Mode'])
self._time_limit = float(settings['Time Limit'])
self._start_time: Optional[float] = 1.0
self._boxing_gloves = bool(settings['Boxing Gloves'])
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) -> str | Sequence:
return 'Knock everyone off the map.'
def get_instance_description_short(self) -> str | Sequence:
return 'Knock off the map.'
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(enable_tnt=False)
self._pow = None
self._tnt_drop_timer = bs.timer(1.0 * 0.30,
bs.WeakCall(self._drop_pow_box),
repeat=True)
self._update_icons()
bs.timer(1.0, self.check_end_game, repeat=True)
def _drop_pow_box(self) -> None:
if self._pow is not None and self._pow:
return
if len(self.map.tnt_points) == 0:
return
pos = random.choice(self.map.tnt_points)
pos = (pos[0], pos[1] + 1, pos[2])
self._pow = PowBox(position=pos, velocity=(0.0, 1.0, 0.0))
def on_player_join(self, player: Player) -> None:
if self.has_begun():
if (all(teammate.lives == 0 for teammate in player.team.players)
and player.team.survival_seconds is None):
player.team.survival_seconds = 0
bs.broadcastmessage(
babase.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
player.lives = self.lives
# create our icon and spawn
player.icons = [Icon(player,
position=(0.0, 50),
scale=0.8)]
if player.lives > 0 or self.time_limit_only:
self.spawn_player(player)
# dont waste time doing this until begin
if self.has_begun():
self._update_icons()
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
player.icons = None
# update icons in a moment since our team
# will be gone from the list then
bs.timer(0.0, self._update_icons)
bs.timer(0.1, self.check_end_game, 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:
print('WTF have', len(team.players), 'players in ffa team')
elif len(team.players) == 1:
player = team.players[0]
if len(player.icons) != 1:
print(
'WTF have',
len(player.icons),
'icons in non-solo elim')
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
# In teams mode we split up teams.
else:
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:
if len(player.icons) != 1:
print(
'WTF have',
len(player.icons),
'icons in non-solo elim')
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
# overriding the default character spawning..
def spawn_player(self, player: Player) -> bs.Actor:
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()
light_color = _math.normalized_color(player.color)
display_color = babase.safecolor(player.color, target_intensity=0.75)
spaz = SSPlayerSpaz(color=player.color,
highlight=player.highlight,
character=player.character,
player=player)
player.actor = spaz
assert spaz.node
# If this is co-op and we're on Courtyard or Runaround, add the
# material that allows us to collide with the player-walls.
# FIXME: Need to generalize this.
if isinstance(self.session, 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)
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_spawned()
if self._boxing_gloves:
spaz.equip_boxing_gloves()
return spaz
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:
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 the game might be over
if player.lives == 0 and not self.time_limit_only:
# 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)
# we still have lives; yay!
else:
self.respawn_player(player)
bs.timer(0.1, self.check_end_game, repeat=True)
else:
return super().handlemessage(msg)
return None
def check_end_game(self) -> None:
if len(self._get_living_teams()) < 2:
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)

762
dist/ba_root/mods/games/volleyball.py vendored Normal file
View file

@ -0,0 +1,762 @@
# Volley Ball (final)
# Made by your friend: Freaku
# Join BCS:
# https://discord.gg/ucyaesh
# My GitHub:
# https://github.com/Freaku17/BombSquad-Mods-byFreaku
# CHANGELOG:
"""
## 2021
- Fixed Puck's mass/size/positions/texture/effects
- Fixed Goal positions
- Better center wall
- Added 1 more map
- Added more customisable options
- Map lights locators are now looped (thus reducing the size of the file and lengthy work...)
- Merged map & minigame in one file
- Puck spawns according to scored team
- Also puck now spawns in airrr
- Server support added :)
- Fixed **LOTS** of errors/bugs
## 2022
- Code cleanup
- More accurate Goal positions
"""
# ba_meta require api 8
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import random
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
from bascenev1lib.actor.bomb import BombFactory
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import Any, Sequence, Dict, Type, List, Optional, Union
class PuckDiedMessage:
"""Inform something that a puck has died."""
def __init__(self, puck: Puck):
self.puck = puck
class Puck(bs.Actor):
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.05, position[2])
self.last_players_to_touch: Dict[int, Player] = {}
self.scored = False
assert activity is not None
assert isinstance(activity, VolleyBallGame)
pmats = [shared.object_material, activity.puck_material]
self.node = bs.newnode('prop',
delegate=self,
attrs={
'mesh': activity.puck_mesh,
'color_texture': activity.puck_tex,
'body': 'sphere',
'reflection': 'soft',
'reflection_scale': [0.2],
'shadow_size': 0.6,
'mesh_scale': 0.4,
'body_scale': 1.07,
'is_area_of_interest': True,
'position': self._spawn_pos,
'materials': pmats
})
# Since it rolls on spawn, lets make gravity
# to 0, and when another node (bomb/spaz)
# touches it. It'll act back as our normie puck!
bs.animate(self.node, 'gravity_scale', {0: -0.1, 0.2: 1}, False)
# When other node touches, it realises its new gravity_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(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.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
# ba_meta export bascenev1.GameActivity
class VolleyBallGame(bs.TeamGameActivity[Player, Team]):
name = 'Volley Ball'
description = 'Score some goals.\nby \ue048Freaku'
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('Epic Mode', True),
bs.BoolSetting('Night Mode', False),
bs.BoolSetting('Icy Floor', True),
bs.BoolSetting('Disable Punch', False),
bs.BoolSetting('Disable Bombs', False),
bs.BoolSetting('Enable Bottom Credits', True),
]
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 ['Open Field', 'Closed Arena']
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.puck_mesh = bs.getmesh('shield')
self.puck_tex = bs.gettexture('gameCircleIcon')
self._puck_sound = bs.getsound('metalHit')
self.puck_material = bs.Material()
self.puck_material.add_actions(actions=(('modify_part_collision',
'friction', 0.5)))
self.puck_material.add_actions(conditions=('they_have_material',
shared.pickup_material),
actions=('modify_part_collision',
'collide', True))
self.puck_material.add_actions(
conditions=(
('we_are_younger_than', 100),
'and',
('they_have_material', shared.object_material),
),
actions=('modify_node_collision', 'collide', False),
)
self.puck_material.add_actions(conditions=('they_have_material',
shared.footing_material),
actions=('impact_sound',
self._puck_sound, 0.2, 5))
# Keep track of which player last touched the puck
self.puck_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('call', 'at_connect',
self._handle_puck_player_collide), ))
# We want the puck to kill powerups; not get stopped by them
self.puck_material.add_actions(
conditions=('they_have_material',
PowerupBoxFactory.get().powerup_material),
actions=(('modify_part_collision', 'physical', False),
('message', 'their_node', 'at_connect', bs.DieMessage())))
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._wall_material = bs.Material()
self._fake_wall_material = bs.Material()
self._wall_material.add_actions(
actions=(
('modify_part_collision', 'friction', 100000),
))
self._wall_material.add_actions(
conditions=('they_have_material', shared.pickup_material),
actions=(
('modify_part_collision', 'collide', False),
))
self._wall_material.add_actions(
conditions=(('we_are_younger_than', 100),
'and',
('they_have_material', shared.object_material)),
actions=(
('modify_part_collision', 'collide', False),
))
self._wall_material.add_actions(
conditions=('they_have_material', shared.footing_material),
actions=(
('modify_part_collision', 'friction', 9999.5),
))
self._wall_material.add_actions(
conditions=('they_have_material', BombFactory.get().blast_material),
actions=(
('modify_part_collision', 'collide', False),
('modify_part_collision', 'physical', False)
))
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.blocks = []
self._net_wall_material = bs.Material()
self._net_wall_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', True)
))
self._net_wall_material.add_actions(
conditions=('they_have_material', shared.object_material),
actions=(
('modify_part_collision', 'collide', True),
))
self._net_wall_material.add_actions(
conditions=('they_have_material', self.puck_material),
actions=(
('modify_part_collision', 'collide', True),
))
self._net_wall_material.add_actions(
conditions=('we_are_older_than', 1),
actions=(
('modify_part_collision', 'collide', True),
))
self.net_blocc = []
self._puck_spawn_pos: Optional[Sequence[float]] = None
self._score_regions: Optional[List[bs.NodeActor]] = None
self._puck: Optional[Puck] = None
self._score_to_win = int(settings['Score to Win'])
self._punchie_ = bool(settings['Disable Punch'])
self._night_mode = bool(settings['Night Mode'])
self._bombies_ = bool(settings['Disable Bombs'])
self._time_limit = float(settings['Time Limit'])
self._icy_flooor = bool(settings['Icy Floor'])
self.credit_text = bool(settings['Enable Bottom Credits'])
self._epic_mode = bool(settings['Epic Mode'])
# 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]:
if self._score_to_win == 1:
return 'Score a goal.'
return 'Score ${ARG1} goals.', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]:
if self._score_to_win == 1:
return 'score a goal'
return 'score ${ARG1} goals', self._score_to_win
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
if self._night_mode:
bs.getactivity().globalsnode.tint = (0.5, 0.7, 1)
self._puck_spawn_pos = self.map.get_flag_position(None)
self._spawn_puck()
# Set up the two score regions.
self._score_regions = []
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': (5.7, 0, -0.065),
'scale': (10.7, 0.001, 8),
'type': 'box',
'materials': [self._score_region_material]
})))
self._score_regions.append(
bs.NodeActor(
bs.newnode('region',
attrs={
'position': (-5.7, 0, -0.065),
'scale': (10.7, 0.001, 8),
'type': 'box',
'materials': [self._score_region_material]
})))
self._update_scoreboard()
self._chant_sound.play()
if self.credit_text:
t = bs.newnode('text',
attrs={'text': "Created by Freaku\nVolleyBall", # Disable 'Enable Bottom Credits' when making playlist, No need to edit this lovely...
'scale': 0.7,
'position': (0, 0),
'shadow': 0.5,
'flatness': 1.2,
'color': (1, 1, 1),
'h_align': 'center',
'v_attach': 'bottom'})
shared = SharedObjects.get()
self.blocks.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 2.4, 0), 'scale': (
0.8, 6, 20), 'type': 'box', 'materials': (self._fake_wall_material, )})))
self.net_blocc.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 0, 0), 'scale': (
0.6, 2.4, 20), 'type': 'box', 'materials': (self._net_wall_material, )})))
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.team.id] = player
def _kill_puck(self) -> None:
self._puck = None
def _handle_score(self) -> None:
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
# Change puck Spawn
if team.id == 0: # left side scored
self._puck_spawn_pos = (5, 0.42, 0)
elif team.id == 1: # right side scored
self._puck_spawn_pos = (-5, 0.42, 0)
else: # normally shouldn't occur
self._puck_spawn_pos = (0, 0.42, 0)
# Easy pizzy
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],
100,
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
# Kill the puck (it'll respawn itself shortly).
bs.emitfx(position=bs.getcollision().position, count=int(
6.0 + 7.0 * 12), scale=3, spread=0.5, chunk_type='spark')
bs.timer(0.7, self._kill_puck)
bs.cameraflash(duration=7.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 on_transition_in(self) -> None:
super().on_transition_in()
activity = bs.getactivity()
if self._icy_flooor:
activity.map.is_hockey = True
def _update_scoreboard(self) -> None:
winscore = self._score_to_win
for team in self.teams:
self._scoreboard.set_team_value(team, team.score, winscore)
# overriding the default character spawning..
def spawn_player(self, player: Player) -> bs.Actor:
spaz = self.spawn_player_spaz(player)
if self._bombies_:
# We want the button to work, just no bombs...
spaz.bomb_count = 0
# Imagine not being able to swipe those colorful buttons ;(
if self._punchie_:
spaz.connect_controls_to_player(enable_punch=False)
return spaz
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(2.2, self._spawn_puck)
else:
super().handlemessage(msg)
def _flash_puck_spawn(self) -> None:
# Effect >>>>>> Flashly
bs.emitfx(position=self._puck_spawn_pos, count=int(
6.0 + 7.0 * 12), scale=1.7, spread=0.4, chunk_type='spark')
def _spawn_puck(self) -> None:
self._swipsound.play()
self._whistle_sound.play()
self._flash_puck_spawn()
assert self._puck_spawn_pos is not None
self._puck = Puck(position=self._puck_spawn_pos)
class Pointzz:
points, boxes = {}, {}
points['spawn1'] = (-8.03866, 0.02275, 0.0) + (0.5, 0.05, 4.0)
points['spawn2'] = (8.82311, 0.01092, 0.0) + (0.5, 0.05, 4.0)
boxes['area_of_interest_bounds'] = (0.0, 1.18575, 0.43262) + \
(0, 0, 0) + (29.81803, 11.57249, 18.89134)
boxes['map_bounds'] = (0.0, 1.185751251, 0.4326226188) + (0.0, 0.0, 0.0) + (
42.09506485, 22.81173179, 29.76723155)
class PointzzforH:
points, boxes = {}, {}
boxes['area_of_interest_bounds'] = (0.0, 0.7956858119, 0.0) + \
(0.0, 0.0, 0.0) + (30.80223883, 0.5961646365, 13.88431707)
boxes['map_bounds'] = (0.0, 0.7956858119, -0.4689020853) + (0.0, 0.0, 0.0) + (
35.16182389, 12.18696164, 21.52869693)
points['spawn1'] = (-6.835352227, 0.02305323209, 0.0) + (1.0, 1.0, 3.0)
points['spawn2'] = (6.857415055, 0.03938567998, 0.0) + (1.0, 1.0, 3.0)
class VolleyBallMap(bs.Map):
defs = Pointzz()
name = "Open Field"
@classmethod
def get_play_types(cls) -> List[str]:
return []
@classmethod
def get_preview_texture_name(cls) -> str:
return 'footballStadiumPreview'
@classmethod
def on_preload(cls) -> Any:
data: Dict[str, Any] = {
'mesh': bs.getmesh('footballStadium'),
'vr_fill_mesh': bs.getmesh('footballStadiumVRFill'),
'collision_mesh': bs.getcollisionmesh('footballStadiumCollide'),
'tex': bs.gettexture('footballStadium')
}
return data
def __init__(self):
super().__init__()
shared = SharedObjects.get()
x = -5
while x < 5:
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (0, 0, x),
'color': (1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (0, .25, x),
'color': (1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (0, .5, x),
'color': (1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (0, .75, x),
'color': (1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (0, 1, x),
'color': (1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
x = x + 0.5
y = -1
while y > -11:
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (y, 0.01, 4),
'color': (0, 0, 1), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (y, 0.01, -4),
'color': (0, 0, 1), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (-y, 0.01, 4),
'color': (1, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (-y, 0.01, -4),
'color': (1, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
y -= 1
z = 0
while z < 5:
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (11, 0.01, z),
'color': (1, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (11, 0.01, -z),
'color': (1, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (-11, 0.01, z),
'color': (0, 0, 1), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (-11, 0.01, -z),
'color': (0, 0, 1), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
z += 1
self.node = bs.newnode(
'terrain',
delegate=self,
attrs={
'mesh': self.preloaddata['mesh'],
'collision_mesh': self.preloaddata['collision_mesh'],
'color_texture': self.preloaddata['tex'],
'materials': [shared.footing_material]
})
bs.newnode('terrain',
attrs={
'mesh': self.preloaddata['vr_fill_mesh'],
'lighting': False,
'vr_only': True,
'background': True,
'color_texture': self.preloaddata['tex']
})
gnode = bs.getactivity().globalsnode
gnode.tint = (1.3, 1.2, 1.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 VolleyBallMapH(bs.Map):
defs = PointzzforH()
name = 'Closed Arena'
@classmethod
def get_play_types(cls) -> List[str]:
return []
@classmethod
def get_preview_texture_name(cls) -> str:
return 'hockeyStadiumPreview'
@classmethod
def on_preload(cls) -> Any:
data: Dict[str, Any] = {
'meshs': (bs.getmesh('hockeyStadiumOuter'),
bs.getmesh('hockeyStadiumInner')),
'vr_fill_mesh': bs.getmesh('footballStadiumVRFill'),
'collision_mesh': bs.getcollisionmesh('hockeyStadiumCollide'),
'tex': bs.gettexture('hockeyStadium'),
}
mat = bs.Material()
mat.add_actions(actions=('modify_part_collision', 'friction', 0.01))
data['ice_material'] = mat
return data
def __init__(self) -> None:
super().__init__()
shared = SharedObjects.get()
x = -5
while x < 5:
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (0, 0, x),
'color': (1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (0, .25, x),
'color': (1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (0, .5, x),
'color': (1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (0, .75, x),
'color': (1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (0, 1, x),
'color': (1, 1, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
x = x + 0.5
y = -1
while y > -11:
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (y, 0.01, 4),
'color': (0, 0, 1), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (y, 0.01, -4),
'color': (0, 0, 1), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (-y, 0.01, 4),
'color': (1, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (-y, 0.01, -4),
'color': (1, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
y -= 1
z = 0
while z < 5:
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (11, 0.01, z),
'color': (1, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (11, 0.01, -z),
'color': (1, 0, 0), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (-11, 0.01, z),
'color': (0, 0, 1), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
self.zone = bs.newnode('locator', attrs={'shape': 'circle', 'position': (-11, 0.01, -z),
'color': (0, 0, 1), 'opacity': 1, 'draw_beauty': True, 'additive': False, 'size': [0.40]})
z += 1
self.node = bs.newnode('terrain',
delegate=self,
attrs={
'mesh':
None,
'collision_mesh':
# we dont want Goalposts...
bs.getcollisionmesh('footballStadiumCollide'),
'color_texture':
self.preloaddata['tex'],
'materials': [
shared.footing_material]
})
bs.newnode('terrain',
attrs={
'mesh': self.preloaddata['vr_fill_mesh'],
'vr_only': True,
'lighting': False,
'background': True,
})
mats = [shared.footing_material]
self.floor = bs.newnode('terrain',
attrs={
'mesh': self.preloaddata['meshs'][1],
'color_texture': self.preloaddata['tex'],
'opacity': 0.92,
'opacity_in_low_or_medium_quality': 1.0,
'materials': mats,
'color': (0.4, 0.9, 0)
})
self.background = bs.newnode(
'terrain',
attrs={
'mesh': bs.getmesh('natureBackground'),
'lighting': False,
'background': True,
'color': (0.5, 0.30, 0.4)
})
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)
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 = True
bs._map.register_map(VolleyBallMap)
bs._map.register_map(VolleyBallMapH)
# ba_meta export plugin
class byFreaku(babase.Plugin):
def __init__(self):
# Reason of plugin:
# To register maps.
#
# Then why not include function here?
# On server upon first launch, plugins are not activated,
# (same can be case for user if disabled auto-enable plugins)
pass

View file

@ -0,0 +1,33 @@
# Made by your friend: Freaku
import babase
import bascenev1 as bs
from bascenev1lib.game.deathmatch import Player, DeathMatchGame
# ba_meta require api 8
# ba_meta export bascenev1.GameActivity
class YeetingGame(DeathMatchGame):
"""A game of yeeting people out of map"""
name = 'Yeeting Party!'
description = 'Yeet your enemies out of the map'
@classmethod
def get_supported_maps(cls, sessiontype):
return ['Bridgit', 'Rampage', 'Monkey Face']
def get_instance_description(self):
return 'Yeet ${ARG1} enemies out of the map!', self._score_to_win
def get_instance_description_short(self):
return 'yeet ${ARG1} enemies', self._score_to_win
def setup_standard_powerup_drops(self, enable_tnt: bool = True) -> None:
pass
def spawn_player(self, player: Player):
spaz = self.spawn_player_spaz(player)
spaz.connect_controls_to_player(enable_punch=False, enable_bomb=False)
return spaz

View file

@ -0,0 +1,360 @@
# ba_meta require api 8
'''
Character Chooser by Mr.Smoothy
This plugin will let you choose your character from lobby.
Install this plugin on your Phone/PC or on Server
If installed on server :- this will also let players choose server specific custom characters . so no more sharing of character file with all players,
just install this plugin on server ...and players can pick character from lobby .
Use:-
> select your profile (focus on color and name)
> press ready (punch)
> now use UP/DOWN buttons to scroll character list
> Press ready again (punch) to join the game
> or press Bomb button to go back to profile choosing menu
> END
Watch : https://www.youtube.com/watch?v=hNmv2l-NahE
Join : https://discord.gg/ucyaesh
Contact : discord mr.smoothy#5824
Share this plugin with your server owner /admins to use it online
:)
'''
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 babase._error import print_exception, print_error, NotFoundError
from babase._language import Lstr
if TYPE_CHECKING:
from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional
import weakref
import os
import json
from bascenev1._lobby import ChangeMessage, PlayerReadyMessage
from bascenev1 import _lobby
from bascenev1lib.actor.spazappearance import *
def __init__(self, vpos: float, sessionplayer: bs.SessionPlayer,
lobby: 'Lobby') -> None:
self._deek_sound = bs.getsound('deek')
self._click_sound = bs.getsound('click01')
self._punchsound = bs.getsound('punch01')
self._swish_sound = bs.getsound('punchSwish')
self._errorsound = bs.getsound('error')
self._mask_texture = bs.gettexture('characterIconMask')
self._vpos = vpos
self._lobby = weakref.ref(lobby)
self._sessionplayer = sessionplayer
self._inited = False
self._dead = False
self._text_node: Optional[bs.Node] = None
self._profilename = ''
self._profilenames: List[str] = []
self._ready: bool = False
self._character_names: List[str] = []
self._last_change: Sequence[Union[float, int]] = (0, 0)
self._profiles: Dict[str, Dict[str, Any]] = {}
app = babase.app
self.bakwas_chars = ["Lee", "Todd McBurton", "Zola", "Butch", "Witch", "warrior",
"Middle-Man", "Alien", "OldLady", "Gladiator", "Wrestler", "Gretel", "Robot"]
# Load available player profiles either from the local config or
# from the remote device.
self.reload_profiles()
for name in bs.app.classic.spaz_appearances:
if name not in self._character_names and name not in self.bakwas_chars:
self._character_names.append(name)
# Note: this is just our local index out of available teams; *not*
# the team-id!
self._selected_team_index: int = self.lobby.next_add_team
# Store a persistent random character index and colors; we'll use this
# for the '_random' profile. Let's use their input_device id to seed
# it. This will give a persistent character for them between games
# and will distribute characters nicely if everyone is random.
self._random_color, self._random_highlight = (
bs.get_player_profile_colors(None))
# To calc our random character we pick a random one out of our
# unlocked list and then locate that character's index in the full
# list.
char_index_offset = app.classic.lobby_random_char_index_offset
self._random_character_index = (
(sessionplayer.inputdevice.id + char_index_offset) %
len(self._character_names))
# Attempt to set an initial profile based on what was used previously
# for this input-device, etc.
self._profileindex = self._select_initial_profile()
self._profilename = self._profilenames[self._profileindex]
self._text_node = bs.newnode('text',
delegate=self,
attrs={
'position': (-100, self._vpos),
'maxwidth': 190,
'shadow': 0.5,
'vr_depth': -20,
'h_align': 'left',
'v_align': 'center',
'v_attach': 'top'
})
bs.animate(self._text_node, 'scale', {0: 0, 0.1: 1.0})
self.icon = bs.newnode('image',
owner=self._text_node,
attrs={
'position': (-130, self._vpos + 20),
'mask_texture': self._mask_texture,
'vr_depth': -10,
'attach': 'topCenter'
})
bs.animate_array(self.icon, 'scale', 2, {0: (0, 0), 0.1: (45, 45)})
# Set our initial name to '<choosing player>' in case anyone asks.
self._sessionplayer.setname(
Lstr(resource='choosingPlayerText').evaluate(), real=False)
# Init these to our rando but they should get switched to the
# selected profile (if any) right after.
self._character_index = self._random_character_index
self._color = self._random_color
self._highlight = self._random_highlight
self.characterchooser = False
self.update_from_profile()
self.update_position()
self._inited = True
self._set_ready(False)
def _set_ready(self, ready: bool) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.profile import browser as pbrowser
from babase._general import Call
profilename = self._profilenames[self._profileindex]
# Handle '_edit' as a special case.
if profilename == '_edit' and ready:
with _babase.Context('ui'):
pbrowser.ProfileBrowserWindow(in_main_menu=False)
# Give their input-device UI ownership too
# (prevent someone else from snatching it in crowded games)
_babase.set_ui_input_device(self._sessionplayer.inputdevice)
return
if ready == False:
self._sessionplayer.assigninput(
babase.InputType.LEFT_PRESS,
Call(self.handlemessage, ChangeMessage('team', -1)))
self._sessionplayer.assigninput(
babase.InputType.RIGHT_PRESS,
Call(self.handlemessage, ChangeMessage('team', 1)))
self._sessionplayer.assigninput(
babase.InputType.BOMB_PRESS,
Call(self.handlemessage, ChangeMessage('character', 1)))
self._sessionplayer.assigninput(
babase.InputType.UP_PRESS,
Call(self.handlemessage, ChangeMessage('profileindex', -1)))
self._sessionplayer.assigninput(
babase.InputType.DOWN_PRESS,
Call(self.handlemessage, ChangeMessage('profileindex', 1)))
self._sessionplayer.assigninput(
(babase.InputType.JUMP_PRESS, babase.InputType.PICK_UP_PRESS,
babase.InputType.PUNCH_PRESS),
Call(self.handlemessage, ChangeMessage('ready', 1)))
self._ready = False
self._update_text()
self._sessionplayer.setname('untitled', real=False)
elif ready == True:
self.characterchooser = True
self._sessionplayer.assigninput(
(babase.InputType.LEFT_PRESS, babase.InputType.RIGHT_PRESS,
babase.InputType.UP_PRESS, babase.InputType.DOWN_PRESS,
babase.InputType.JUMP_PRESS, babase.InputType.BOMB_PRESS,
babase.InputType.PICK_UP_PRESS), self._do_nothing)
self._sessionplayer.assigninput(
(babase.InputType.UP_PRESS), Call(self.handlemessage, ChangeMessage('characterchooser', -1)))
self._sessionplayer.assigninput(
(babase.InputType.DOWN_PRESS), Call(self.handlemessage, ChangeMessage('characterchooser', 1)))
self._sessionplayer.assigninput(
(babase.InputType.BOMB_PRESS), Call(self.handlemessage, ChangeMessage('ready', 0)))
self._sessionplayer.assigninput(
(babase.InputType.JUMP_PRESS, babase.InputType.PICK_UP_PRESS, babase.InputType.PUNCH_PRESS),
Call(self.handlemessage, ChangeMessage('ready', 2)))
# Store the last profile picked by this input for reuse.
input_device = self._sessionplayer.inputdevice
name = input_device.name
unique_id = input_device.unique_identifier
device_profiles = _babase.app.config.setdefault(
'Default Player Profiles', {})
# Make an exception if we have no custom profiles and are set
# to random; in that case we'll want to start picking up custom
# profiles if/when one is made so keep our setting cleared.
special = ('_random', '_edit', '__account__')
have_custom_profiles = any(p not in special
for p in self._profiles)
profilekey = name + ' ' + unique_id
if profilename == '_random' and not have_custom_profiles:
if profilekey in device_profiles:
del device_profiles[profilekey]
else:
device_profiles[profilekey] = profilename
_babase.app.config.commit()
# Set this player's short and full name.
self._sessionplayer.setname(self._getname(),
self._getname(full=True),
real=True)
self._ready = True
self._update_text()
else:
# Inform the session that this player is ready.
bs.getsession().handlemessage(PlayerReadyMessage(self))
def handlemessage(self, msg: Any) -> Any:
"""Standard generic message handler."""
if isinstance(msg, ChangeMessage):
self._handle_repeat_message_attack()
# If we've been removed from the lobby, ignore this stuff.
if self._dead:
print_error('chooser got ChangeMessage after dying')
return
if not self._text_node:
print_error('got ChangeMessage after nodes died')
return
if msg.what == 'characterchooser':
self._click_sound.play()
# update our index in our local list of characters
self._character_index = ((self._character_index + msg.value) %
len(self._character_names))
self._update_text()
self._update_icon()
if msg.what == 'team':
sessionteams = self.lobby.sessionteams
if len(sessionteams) > 1:
self._swish_sound.play()
self._selected_team_index = (
(self._selected_team_index + msg.value) %
len(sessionteams))
self._update_text()
self.update_position()
self._update_icon()
elif msg.what == 'profileindex':
if len(self._profilenames) == 1:
# This should be pretty hard to hit now with
# automatic local accounts.
bui.getsound('error').play()
else:
# Pick the next player profile and assign our name
# and character based on that.
self._deek_sound.play()
self._profileindex = ((self._profileindex + msg.value) %
len(self._profilenames))
self.update_from_profile()
elif msg.what == 'character':
self._click_sound.play()
self.characterchooser = True
# update our index in our local list of characters
self._character_index = ((self._character_index + msg.value) %
len(self._character_names))
self._update_text()
self._update_icon()
elif msg.what == 'ready':
self._handle_ready_msg(msg.value)
def _update_text(self) -> None:
assert self._text_node is not None
if self._ready:
# Once we're ready, we've saved the name, so lets ask the system
# for it so we get appended numbers and stuff.
text = Lstr(value=self._sessionplayer.getname(full=True))
if self.characterchooser:
text = Lstr(value='${A}\n${B}',
subs=[('${A}', text),
('${B}', Lstr(value=""+self._character_names[self._character_index]))])
self._text_node.scale = 0.8
else:
text = Lstr(value='${A} (${B})',
subs=[('${A}', text),
('${B}', Lstr(resource='readyText'))])
else:
text = Lstr(value=self._getname(full=True))
self._text_node.scale = 1.0
can_switch_teams = len(self.lobby.sessionteams) > 1
# Flash as we're coming in.
fin_color = _babase.safecolor(self.get_color()) + (1, )
if not self._inited:
bs.animate_array(self._text_node, 'color', 4, {
0.15: fin_color,
0.25: (2, 2, 2, 1),
0.35: fin_color
})
else:
# Blend if we're in teams mode; switch instantly otherwise.
if can_switch_teams:
bs.animate_array(self._text_node, 'color', 4, {
0: self._text_node.color,
0.1: fin_color
})
else:
self._text_node.color = fin_color
self._text_node.text = text
# ba_meta export plugin
class HeySmoothy(babase.Plugin):
def __init__(self):
_lobby.Chooser.__init__ = __init__
_lobby.Chooser._set_ready = _set_ready
_lobby.Chooser._update_text = _update_text
_lobby.Chooser.handlemessage = handlemessage