bombsquad-plugin-manager/plugins/utilities/auto_stunt.py
brostosjoined bfb5d25467 ^->8
2024-01-22 12:06:37 +03:00

562 lines
21 KiB
Python

# Porting to api 8 made easier by baport.(https://github.com/bombsquad-community/baport)
# ba_meta require api 8
# AutoStunt mod by - Mr.Smoothy x Rikko
# https://discord.gg/ucyaesh
# https://bombsquad-community.web.app/home
# Dont modify redistribute this plugin , if want to use features of this plugin in your mod write logic in seprate file
# and import this as module.
# If want to contribute in this original module, raise PR on github https://github.com/bombsquad-community/plugin-manager
import babase
import bauiv1 as bui
import bascenev1 as bs
import _babase
import bascenev1lib
from bascenev1lib.actor.text import Text
from bascenev1lib.actor.image import Image
from bascenev1lib.actor import spaz
from bascenev1lib.actor import playerspaz
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
from bascenev1lib.actor.spazfactory import SpazFactory
from bascenev1lib.game.elimination import EliminationGame
from bauiv1lib import mainmenu
import math
import json
import os
from typing import Optional
CONTROLS_CENTER = (0, 0)
CONTROLS_SCALE = 1
BASE_STUNTS_DIRECTORY = os.path.join(_babase.env()["python_directory_user"], "CustomStunts")
PLAYERS_STUNT_INFO = {}
STUNT_CACHE = {}
original_on_begin = bs._activity.Activity.on_begin
original_chatmessage = bs.chatmessage
class ControlsUI:
def on_jump_press(activity):
activity._jump_image.node.color = list(
channel * 2 for channel in activity._jump_image.node.color[:3]) + [1]
def on_jump_release(activity):
activity._jump_image.node.color = list(
channel * 0.5 for channel in activity._jump_image.node.color[:3]) + [1]
def on_pickup_press(activity):
activity._pickup_image.node.color = list(
channel * 2 for channel in activity._pickup_image.node.color[:3]) + [1]
def on_pickup_release(activity):
activity._pickup_image.node.color = list(
channel * 0.5 for channel in activity._pickup_image.node.color[:3]) + [1]
def on_punch_press(activity):
activity._punch_image.node.color = list(
channel * 2 for channel in activity._punch_image.node.color[:3]) + [1]
def on_punch_release(activity):
activity._punch_image.node.color = list(
channel * 0.5 for channel in activity._punch_image.node.color[:3]) + [1]
def on_bomb_press(activity):
activity._bomb_image.node.color = list(
channel * 2 for channel in activity._bomb_image.node.color[:3]) + [1]
def on_bomb_release(activity):
activity._bomb_image.node.color = list(
channel * 0.5 for channel in activity._bomb_image.node.color[:3]) + [1]
def on_move_ud(activity, value):
activity.set_stick_image_position(activity, x=activity.stick_image_position_x, y=value)
def on_move_lr(activity, value):
activity.set_stick_image_position(activity, x=value, y=activity.stick_image_position_y)
def display(activity):
activity._jump_image.node.color = list(activity._jump_image.node.color[:3]) + [1]
activity._pickup_image.node.color = list(activity._pickup_image.node.color[:3]) + [1]
activity._punch_image.node.color = list(activity._punch_image.node.color[:3]) + [1]
activity._bomb_image.node.color = list(activity._bomb_image.node.color[:3]) + [1]
activity._stick_base_image.opacity = 1.0
activity._stick_nub_image.opacity = 1.0
def hide(activity):
activity._jump_image.node.color = list(activity._jump_image.node.color[:3]) + [0]
activity._pickup_image.node.color = list(activity._pickup_image.node.color[:3]) + [0]
activity._punch_image.node.color = list(activity._punch_image.node.color[:3]) + [0]
activity._bomb_image.node.color = list(activity._bomb_image.node.color[:3]) + [0]
activity._stick_base_image.opacity = 0.0
activity._stick_nub_image.opacity = 0.0
CONTROLS_UI_MAP = {
"JUMP_PRESS": ControlsUI.on_jump_press,
"JUMP_RELEASE": ControlsUI.on_jump_release,
"PICKUP_PRESS": ControlsUI.on_pickup_press,
"PICKUP_RELEASE": ControlsUI.on_pickup_release,
"PUNCH_PRESS": ControlsUI.on_punch_press,
"PUNCH_RELEASE": ControlsUI.on_punch_release,
"BOMB_PRESS": ControlsUI.on_bomb_press,
"BOMB_RELEASE": ControlsUI.on_bomb_release,
"UP_DOWN": ControlsUI.on_move_ud,
"LEFT_RIGHT": ControlsUI.on_move_lr
}
class NewSpaz(bascenev1lib.actor.spaz.Spaz):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.move_map = {
"UP_DOWN": self.on_move_up_down,
"LEFT_RIGHT": self.on_move_left_right,
"HOLD_POSITION": self.on_hold_position_press,
"HOLD_RELEASE": self.on_hold_position_release,
"JUMP_PRESS": self.on_jump_press,
"JUMP_RELEASE": self.on_jump_release,
"PICKUP_PRESS": self.on_pickup_press,
"PICKUP_RELEASE": self.on_pickup_release,
"PUNCH_PRESS": self.on_punch_press,
"PUNCH_RELEASE": self.on_punch_release,
"BOMB_PRESS": self.on_bomb_press,
"BOMB_RELEASE": self.on_bomb_release,
"RUN": self.on_run,
}
class NewPlayerSpaz(bascenev1lib.actor.playerspaz.PlayerSpaz):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.move_map = {
"UP_DOWN": self.on_move_up_down,
"LEFT_RIGHT": self.on_move_left_right,
"HOLD_POSITION": self.on_hold_position_press,
"HOLD_RELEASE": self.on_hold_position_release,
"JUMP_PRESS": self.on_jump_press,
"JUMP_RELEASE": self.on_jump_release,
"PICKUP_PRESS": self.on_pickup_press,
"PICKUP_RELEASE": self.on_pickup_release,
"PUNCH_PRESS": self.on_punch_press,
"PUNCH_RELEASE": self.on_punch_release,
"BOMB_PRESS": self.on_bomb_press,
"BOMB_RELEASE": self.on_bomb_release,
"RUN": self.on_run,
}
self.mirror_spaz = []
self.source_player.in_replay = False
self.source_player.mirror_mode = False
def _handle_action(self, action, value: Optional[float] = None) -> None:
if self.source_player.sessionplayer in PLAYERS_STUNT_INFO:
PLAYERS_STUNT_INFO[self.source_player.sessionplayer].append({
"time": bs.time() - self.source_player.recording_start_time,
"move": {
"action": action,
"value": value,
}
})
elif self.source_player.in_replay:
ui_activation = CONTROLS_UI_MAP.get(action)
if ui_activation:
if action in ["UP_DOWN", "LEFT_RIGHT"]:
ui_activation(self.source_player.actor._activity(), value)
else:
ui_activation(self.source_player.actor._activity())
elif self.source_player.mirror_mode:
for mspaz in self.mirror_spaz:
if mspaz and mspaz.node.exists():
if action in ["UP_DOWN", "LEFT_RIGHT", "RUN"]:
mspaz.move_map[action](value)
else:
mspaz.move_map[action]()
def on_move_up_down(self, value: float, *args, **kwargs) -> None:
self._handle_action("UP_DOWN", value)
super().on_move_up_down(value, *args, **kwargs)
def on_move_left_right(self, value: float, *args, **kwargs) -> None:
self._handle_action("LEFT_RIGHT", value)
super().on_move_left_right(value, *args, **kwargs)
def on_hold_position_press(self, *args, **kwargs) -> None:
self._handle_action("HOLD_POSITION")
super().on_hold_position_press(*args, **kwargs)
def on_hold_position_release(self, *args, **kwargs) -> None:
self._handle_action("HOLD_RELEASE")
super().on_hold_position_release(*args, **kwargs)
def on_jump_press(self, *args, **kwargs) -> None:
self._handle_action("JUMP_PRESS")
super().on_jump_press(*args, **kwargs)
def on_jump_release(self, *args, **kwargs) -> None:
self._handle_action("JUMP_RELEASE")
super().on_jump_release(*args, **kwargs)
def on_pickup_press(self, *args, **kwargs) -> None:
self._handle_action("PICKUP_PRESS")
super().on_pickup_press(*args, **kwargs)
def on_pickup_release(self, *args, **kwargs) -> None:
self._handle_action("PICKUP_RELEASE")
super().on_pickup_release(*args, **kwargs)
def on_punch_press(self, *args, **kwargs) -> None:
self._handle_action("PUNCH_PRESS")
super().on_punch_press(*args, **kwargs)
def on_punch_release(self, *args, **kwargs) -> None:
self._handle_action("PUNCH_RELEASE")
super().on_punch_release(*args, **kwargs)
def on_bomb_press(self, *args, **kwargs) -> None:
self._handle_action("BOMB_PRESS")
super().on_bomb_press(*args, **kwargs)
def on_bomb_release(self, *args, **kwargs) -> None:
self._handle_action("BOMB_RELEASE")
super().on_bomb_release(*args, **kwargs)
def on_run(self, value: float, *args, **kwargs) -> None:
self._handle_action("RUN", value)
super().on_run(value, *args, **kwargs)
def handle_player_replay_end(player):
player.in_replay = False
ControlsUI.hide(player.actor._activity())
def get_player_from_client_id(client_id, activity=None):
activity = activity or bs.get_foreground_host_activity()
for player in activity.players:
if player.sessionplayer.inputdevice.client_id == client_id:
return player
raise bs.SessionPlayerNotFound()
def mirror(clieid):
player = get_player_from_client_id(clieid)
spawn_mirror_spaz(player)
def capture(player):
with player.actor._activity().context:
player.recording_start_time = bs.time()
PLAYERS_STUNT_INFO[player.sessionplayer] = []
def save(player, stunt_name):
stunt_path = f"{os.path.join(BASE_STUNTS_DIRECTORY, stunt_name)}.json"
os.makedirs(BASE_STUNTS_DIRECTORY, exist_ok=True)
with open(stunt_path, "w") as fout:
json.dump(PLAYERS_STUNT_INFO[player.sessionplayer], fout, indent=2)
del PLAYERS_STUNT_INFO[player.sessionplayer]
def replay(player, stunt_name):
stunt_path = f"{os.path.join(BASE_STUNTS_DIRECTORY, stunt_name)}.json"
if stunt_name in STUNT_CACHE:
stunt = STUNT_CACHE[stunt_name]
else:
try:
with open(stunt_path, "r") as fin:
stunt = json.load(fin)
STUNT_CACHE[stunt_name] = stunt
except:
bui.screenmessage(f"{stunt_name} doesn't exists")
return
player.in_replay = True
with player.actor._activity().context:
ControlsUI.display(player.actor._activity())
for move in stunt:
value = move["move"]["value"]
if value is None:
bs.timer(
move["time"],
babase.Call(player.actor.move_map[move["move"]["action"]])
)
else:
bs.timer(
move["time"],
babase.Call(player.actor.move_map[move["move"]
["action"]], move["move"]["value"])
)
last_move_time = move["time"]
time_to_hide_controls = last_move_time + 1
bs.timer(time_to_hide_controls, babase.Call(handle_player_replay_end, player))
def spawn_mirror_spaz(player):
player.mirror_mode = True
with player.actor._activity().context:
bot = spaz.Spaz(player.color, player.highlight, character=player.character).autoretain()
bot.handlemessage(bs.StandMessage(
(player.actor.node.position[0], player.actor.node.position[1], player.actor.node.position[2]+1), 93))
bot.node.name = player.actor.node.name
bot.node.name_color = player.actor.node.name_color
player.actor.mirror_spaz.append(bot)
def ghost(player, stunt_name):
stunt_path = f"{os.path.join(BASE_STUNTS_DIRECTORY, stunt_name)}.json"
if stunt_name in STUNT_CACHE:
stunt = STUNT_CACHE[stunt_name]
else:
try:
with open(stunt_path, "r") as fin:
stunt = json.load(fin)
STUNT_CACHE[stunt_name] = stunt
except:
bui.screenmessage(f"{stunt_name} doesn't exists")
return
player.in_replay = True
with player.actor._activity().context:
bot = spaz.Spaz((1, 0, 0), character="Spaz").autoretain()
bot.handlemessage(bs.StandMessage(player.actor.node.position, 93))
give_ghost_power(bot)
ControlsUI.display(player.actor._activity())
for move in stunt:
value = move["move"]["value"]
if value is None:
bs.timer(
move["time"],
babase.Call(bot.move_map[move["move"]["action"]])
)
ui_activation = CONTROLS_UI_MAP.get(move["move"]["action"])
if ui_activation:
bs.timer(
move["time"],
babase.Call(ui_activation, player.actor._activity())
)
else:
bs.timer(
move["time"],
babase.Call(bot.move_map[move["move"]["action"]], move["move"]["value"])
)
ui_activation = CONTROLS_UI_MAP.get(move["move"]["action"])
if ui_activation:
bs.timer(
move["time"],
babase.Call(ui_activation, player.actor._activity(), move["move"]["value"])
)
last_move_time = move["time"]
time_to_hide_controls = last_move_time + 1
bs.timer(time_to_hide_controls, babase.Call(handle_player_replay_end, player))
bs.timer(time_to_hide_controls, babase.Call(bot.node.delete))
def give_ghost_power(spaz):
spaz.node.invincible = True
shared = SharedObjects.get()
factory = SpazFactory.get()
ghost = bs.Material()
# smoothy hecks
ghost.add_actions(
conditions=(('they_have_material', factory.spaz_material), 'or',
('they_have_material', shared.player_material), 'or',
('they_have_material', shared.attack_material), 'or',
('they_have_material', shared.pickup_material), 'or',
('they_have_material', PowerupBoxFactory.get().powerup_accept_material)),
actions=(
('modify_part_collision', 'collide', False),
('modify_part_collision', 'physical', False)
))
mats = list(spaz.node.materials)
roller = list(spaz.node.roller_materials)
ext = list(spaz.node.extras_material)
pick = list(spaz.node.pickup_materials)
punch = list(spaz.node.punch_materials)
mats.append(ghost)
roller.append(ghost)
ext.append(ghost)
pick.append(ghost)
punch.append(ghost)
spaz.node.materials = tuple(mats)
spaz.node.roller_materials = tuple(roller)
spaz.node.extras_material = tuple(ext)
spaz.node.pickup_materials = tuple(pick)
spaz.node.punch_materials = tuple(pick)
def new_chatmessage(msg):
#! Fix here to make it work with other mods modifying chat message
if not msg.startswith("*"):
return original_chatmessage(msg)
stripped_msg = msg[1:]
msg_splits = stripped_msg.split(maxsplit=3)
command = msg_splits[0]
client_id = -1
try:
player = get_player_from_client_id(client_id)
except AttributeError:
bui.screenmessage("Start a game to use", color=(0, 1, 0))
return
if command == "start":
capture(player)
bs.chatmessage("Recording started for {}.".format(
player.getname(),
))
return original_chatmessage(msg)
stunt_name = " ".join(msg_splits[1:])
if command == "save":
if len(msg_splits) < 2:
bui.screenmessage("Enter name of stunt eg : *save bombjump")
return original_chatmessage(msg)
save(player, stunt_name)
bs.chatmessage('Recording "{}" by {} saved.'.format(
stunt_name,
player.getname(),
))
elif command == "stunt":
if len(msg_splits) < 2:
bui.screenmessage("Enter name of stunt eg : *stunt bombjump")
return original_chatmessage(msg)
replay(player, stunt_name)
bs.chatmessage('Replaying "{}" on {}.'.format(
stunt_name,
player.getname(),
))
elif command == "learn":
if len(msg_splits) < 2:
bui.screenmessage("Enter name of stunt eg : *learn bombjump")
return original_chatmessage(msg)
ghost(player, stunt_name)
bs.chatmessage('Replaying "{}" on {}.'.format(
stunt_name,
player.getname(),
))
elif command == "mirror":
spawn_mirror_spaz(player)
return original_chatmessage(msg)
def set_stick_image_position(self, x: float, y: float) -> None:
# Clamp this to a circle.
len_squared = x * x + y * y
if len_squared > 1.0:
length = math.sqrt(len_squared)
mult = 1.0 / length
x *= mult
y *= mult
self.stick_image_position_x = x
self.stick_image_position_y = y
offs = 50.0
assert self._scale is not None
p = [
self._stick_nub_position[0] + x * offs * 0.6,
self._stick_nub_position[1] + y * offs * 0.6
]
c = list(self._stick_nub_image_color)
if abs(x) > 0.1 or abs(y) > 0.1:
c[0] *= 2.0
c[1] *= 4.0
c[2] *= 2.0
assert self._stick_nub_image is not None
self._stick_nub_image.position = p
self._stick_nub_image.color = c
c = list(self._stick_base_image_color)
if abs(x) > 0.1 or abs(y) > 0.1:
c[0] *= 1.5
c[1] *= 1.5
c[2] *= 1.5
assert self._stick_base_image is not None
self._stick_base_image.color = c
def on_begin(self, *args, **kwargs) -> None:
self._jump_image = Image(
bs.gettexture('buttonJump'),
position=(385, 160),
scale=(50, 50),
color=[0.1, 0.45, 0.1, 0]
)
self._pickup_image = Image(
bs.gettexture('buttonPickUp'),
position=(385, 240),
scale=(50, 50),
color=[0, 0.35, 0, 0]
)
self._punch_image = Image(
bs.gettexture('buttonPunch'),
position=(345, 200),
scale=(50, 50),
color=[0.45, 0.45, 0, 0]
)
self._bomb_image = Image(
bs.gettexture('buttonBomb'),
position=(425, 200),
scale=(50, 50),
color=[0.45, 0.1, 0.1, 0]
)
self.stick_image_position_x = self.stick_image_position_y = 0.0
self._stick_base_position = p = (-328, 200)
self._stick_base_image_color = c2 = (0.25, 0.25, 0.25, 1.0)
self._stick_base_image = bs.newnode(
'image',
attrs={
'texture': bs.gettexture('nub'),
'absolute_scale': True,
'vr_depth': -40,
'position': p,
'scale': (220.0*0.6, 220.0*0.6),
'color': c2
})
self._stick_nub_position = p = (-328, 200)
self._stick_nub_image_color = c3 = (0.4, 0.4, 0.4, 1.0)
self._stick_nub_image = bs.newnode('image',
attrs={
'texture': bs.gettexture('nub'),
'absolute_scale': True,
'position': p,
'scale': (110*0.6, 110*0.66),
'color': c3
})
self._stick_base_image.opacity = 0.0
self._stick_nub_image.opacity = 0.0
self.set_stick_image_position = set_stick_image_position
return original_on_begin(self, *args, **kwargs)
class NewMainMenuWindow(mainmenu.MainMenuWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Display chat icon, but if user open/close gather it may disappear
bui.set_party_icon_always_visible(True)
# ba_meta export plugin
class byHeySmoothy(babase.Plugin):
def on_app_running(self):
mainmenu.MainMenuWindow = NewMainMenuWindow
bs._activity.Activity.on_begin = on_begin
bs.chatmessage = new_chatmessage
bascenev1lib.actor.playerspaz.PlayerSpaz = NewPlayerSpaz
bascenev1lib.actor.spaz.Spaz = NewSpaz
# lets define a sample elimination game that can use super power of this plugin
# ba_meta export bascenev1.GameActivity
class BroEliminaition(EliminationGame):
name = 'BroElimination'
description = 'Elimination Game with dual character control'
def spawn_player(self, player) -> bs.Actor:
super().spawn_player(player)
spawn_mirror_spaz(player)