# Released under the MIT License. See LICENSE for details. ''' Character Builder/Maker by Mr.Smoothy Plugin helps to mix character models and textures in interactive way. Watch tutorial : https://www.youtube.com/c/HeySmoothy Join discord: https://discord.gg/ucyaesh for help https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server/ > create team playlist and add character maker mini game > Use export command to save character > Character will be saved in CustomCharacter folder inside Bombsquad Mods folder *Only one player in that mini game supported ... Characters can be used offline or online for online you need to share character file with server owners. *For server owners:_ You might know what to do with that file, Still , refer code after line 455 in this file , add it as a plugin to import characters from json file. *For modders:- You can add more models and texture , check line near 400 and add asset names , you can also modify sounds and icon in json file (optional) . To share your character with friends , send them character .json file and tell them to put file in same location i.e Bombsquad/CustomCharacter or for PC appdata/Local/Bombsquad/Mods/CustomCharacter this plugin should be installed on their device too Dont forget to share your creativity with me , send your character screenshot discord: mr.smoothy#5824 https://discord.gg/ucyaesh Register your character in above discord server , so other server owners can add your characters. Released on 28 May 2021 Update 2 june : use import ''' # ba_meta require api 7 from __future__ import annotations from typing import TYPE_CHECKING import ba,_ba from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard if TYPE_CHECKING: from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional import os,json from bastd.actor.spazappearance import * spazoutfit={ "color_mask":"neoSpazColorMask", "color_texture":"neoSpazColor", "head":"neoSpazHead", "hand":"neoSpazHand", "torso":"neoSpazTorso", "pelvis":"neoSpazTorso", "upper_arm":"neoSpazUpperArm", "forearm":"neoSpazForeArm", "upper_leg":"neoSpazUpperLeg", "lower_leg":"neoSpazLowerLeg", "toes_model":"neoSpazToes", "jump_sounds":['spazJump01', 'spazJump02', 'spazJump03', 'spazJump04'], "attack_sounds":['spazAttack01', 'spazAttack02', 'spazAttack03', 'spazAttack04'], "impact_sounds":['spazImpact01', 'spazImpact02', 'spazImpact03', 'spazImpact04'], "death_sounds":['spazDeath01'], "pickup_sounds":['spazPickup01'], "fall_sounds":['spazFall01'], "icon_texture":"neoSpazIcon", "icon_mask_texture":"neoSpazIconColorMask", "style":"spaz" } character=None class Player(ba.Player['Team']): """Our player type for this game.""" class Team(ba.Team[Player]): """Our team type for this game.""" def __init__(self) -> None: self.score = 0 # ba_meta export game class CharacterBuilder(ba.TeamGameActivity[Player, Team]): """A game type based on acquiring kills.""" name = 'Character Maker' description = 'Create your own custom Characters' # Print messages when players die since it matters here. announce_player_deaths = True @classmethod def get_available_settings( cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]: settings = [ ba.IntSetting( 'Kills to Win Per Player', min_value=1, default=5, increment=1, ), ba.IntChoiceSetting( 'Time Limit', choices=[ ('None', 0), ('1 Minute', 60), ('2 Minutes', 120), ('5 Minutes', 300), ('10 Minutes', 600), ('20 Minutes', 1200), ], default=0, ), ba.FloatChoiceSetting( 'Respawn Times', choices=[ ('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0), ('Long', 2.0), ('Longer', 4.0), ], default=1.0, ), ba.BoolSetting('Epic Mode', default=False), ] if issubclass(sessiontype, ba.FreeForAllSession): settings.append( ba.BoolSetting('Allow Negative Scores', default=False)) return settings @classmethod def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: return (issubclass(sessiontype, ba.DualTeamSession) or issubclass(sessiontype, ba.FreeForAllSession)) @classmethod def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: return ['Rampage'] def __init__(self, settings: dict): super().__init__(settings) self.initdic() _ba.set_party_icon_always_visible(True) self._score_to_win: Optional[int] = None self._dingsound = ba.getsound('dingSmall') self._epic_mode = bool(settings['Epic Mode']) self._kills_to_win_per_player = int( settings['Kills to Win Per Player']) self._time_limit = float(settings['Time Limit']) self._allow_negative_scores = bool( settings.get('Allow Negative Scores', False)) self.bodyindex=0 self.modelindex=0 self.youtube= ba.newnode( 'text', attrs={ 'text': "youtube.com/c/HeySmoothy", 'in_world': True, 'scale': 0.02, 'color': (1, 0, 0, 0.4), 'h_align': 'center', 'position': (0,4,-1.9) }) self.discordservere= ba.newnode( 'text', attrs={ 'text': "discord.gg/ucyaesh", 'in_world': True, 'scale': 0.02, 'color': (0.12, 0.3, 0.6, 0.4), 'h_align': 'center', 'position': (-3,2.7,-1.9) }) # self.discord= ba.newnode( # 'text', # attrs={ # 'text': "mr.smoothy#5824", # 'in_world': True, # 'scale': 0.02, # 'color': (01.2, 0.3, 0.7, 0.4), # 'h_align': 'center', # 'position': (4,2.7,-1.9) # }) # Base class overrides. self.bodypart= ba.newnode( 'text', attrs={ 'text': "", 'in_world': True, 'scale': 0.02, 'color': (1, 1, 0, 1), 'h_align': 'center', 'position': (-4,6,-4) }) self.newmodel = ba.newnode( 'text', attrs={ 'text': "", 'in_world': True, 'scale': 0.02, 'color': (1, 1, 0, 1), 'h_align': 'center', 'position': (6,6,-4) }) self.slow_motion = self._epic_mode self.default_music = (ba.MusicType.EPIC if self._epic_mode else ba.MusicType.TO_THE_DEATH) def get_instance_description(self) -> Union[str, Sequence]: return '' def get_instance_description_short(self) -> Union[str, Sequence]: return '' def on_team_join(self, team: Team) -> None: if self.has_begun(): pass def on_begin(self) -> None: super().on_begin() def nextBodyPart(self): self.bodyindex =(self.bodyindex+1)%len(self.dic.keys()) self.bodypart.delete() PART=list(self.dic.keys())[self.bodyindex] self.bodypart=ba.newnode( 'text', attrs={ 'text': PART, 'in_world': True, 'scale': 0.02, 'color': (1, 1, 1, 1), 'h_align': 'center', 'position': (-4,6,-4) }) def prevBodyPart(self): self.bodyindex =(self.bodyindex-1)%len(self.dic.keys()) self.bodypart.delete() PART=list(self.dic.keys())[self.bodyindex] self.bodypart=ba.newnode( 'text', attrs={ 'text': PART, 'in_world': True, 'scale': 0.02, 'color': (1, 1, 1, 1), 'h_align': 'center', 'position': (-4,6,-4) }) def nextModel(self): self.newmodel.delete() PART=list(self.dic.keys())[self.bodyindex] self.modelindex =(self.modelindex+1)%len(self.dic[PART]) model=self.dic[PART][self.modelindex] self.newmodel=ba.newnode( 'text', attrs={ 'text': model, 'in_world': True, 'scale': 0.02, 'color': (1, 1, 1, 1), 'h_align': 'center', 'position': (6,6,-4) }) self.setModel(PART,model) def prevModel(self): self.newmodel.delete() PART=list(self.dic.keys())[self.bodyindex] self.modelindex =(self.modelindex-1)%len(self.dic[PART]) model=self.dic[PART][self.modelindex] self.newmodel=ba.newnode( 'text', attrs={ 'text': model, 'in_world': True, 'scale': 0.02, 'color': (1, 1, 1, 1), 'h_align': 'center', 'position': (6,6,-4) }) self.setModel(PART,model) def setModel(self,bodypart,modelname): global spazoutfit body=_ba.get_foreground_host_activity().players[0].actor.node if bodypart=='head': body.head_model=ba.getmodel(modelname) elif bodypart=='torso': body.torso_model=ba.getmodel(modelname) elif bodypart=='pelvis': body.pelvis_model=ba.getmodel(modelname) elif bodypart=='upper_arm': body.upper_arm_model=ba.getmodel(modelname) elif bodypart=='forearm': body.forearm_model=ba.getmodel(modelname) elif bodypart=='hand': body.hand_model=ba.getmodel(modelname) elif bodypart=='upper_leg': body.upper_leg_model=ba.getmodel(modelname) elif bodypart=='lower_leg': body.lower_leg_model=ba.getmodel(modelname) elif bodypart=='toes_model': body.toes_model=ba.getmodel(modelname) elif bodypart=='style': body.style=modelname elif bodypart=='color_texture': body.color_texture=ba.gettexture(modelname) elif bodypart=='color_mask': body.color_mask_texture=ba.gettexture(modelname) spazoutfit[bodypart]=modelname def spawn_player(self, player: Player) -> ba.Actor: global character if character!=None: player.character=character self.setcurrentcharacter(player.character) spaz = self.spawn_player_spaz(player) # Let's reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up. spaz.connect_controls_to_player(enable_punch=False, enable_jump=False, enable_bomb=False, enable_pickup=False) intp = ba.InputType player.assigninput(intp.JUMP_PRESS, self.nextBodyPart) player.assigninput(intp.PICK_UP_PRESS, self.prevBodyPart) player.assigninput(intp.PUNCH_PRESS, self.nextModel) player.assigninput(intp.BOMB_PRESS, self.prevModel) # Also lets have them make some noise when they die. spaz.play_big_death_sound = True return spaz def handlemessage(self, msg: Any) -> Any: if isinstance(msg, ba.PlayerDiedMessage): # Augment standard behavior. super().handlemessage(msg) player = msg.getplayer(Player) self.respawn_player(player) else: return super().handlemessage(msg) return None def setcurrentcharacter(self,charname): global spazoutfit char=ba.app.spaz_appearances[charname] spazoutfit['head']=char.head_model spazoutfit['hand']=char.hand_model spazoutfit['torso']=char.torso_model spazoutfit['pelvis']=char.pelvis_model spazoutfit['upper_arm']=char.upper_arm_model spazoutfit['forearm']=char.forearm_model spazoutfit['upper_leg']=char.upper_leg_model spazoutfit['lower_leg']=char.lower_leg_model spazoutfit['toes_model']=char.toes_model spazoutfit['style']=char.style spazoutfit['color_mask']=char.color_mask_texture spazoutfit['color_texture']=char.color_texture def _update_scoreboard(self) -> None: for team in self.teams: self._scoreboard.set_team_value(team, team.score, self._score_to_win) def end_game(self) -> None: results = ba.GameResults() for team in self.teams: results.set_team_score(team, team.score) self.end(results=results) def initdic(self): self.dic={"head":["bomb","landMine","trees","wing","eyeLid","impactBomb"], "hand":["hairTuft3","bomb","powerup"], "torso":["bomb","landMine","bomb"], "pelvis":["hairTuft4","bomb"], "upper_arm":["wing","locator","bomb"], "forearm":["flagPole","bomb"], "upper_leg":["bomb"], "lower_leg":["bomb"], "toes_model":["bomb"], "style":["spaz","female","ninja","kronk","mel","pirate","santa","frosty","bones","bear","penguin","ali","cyborg","agent","pixie","bunny"], "color_texture":["kronk","egg1","egg2","egg3","achievementGotTheMoves","bombColor","crossOut","explosion","rgbStripes","powerupCurse","powerupHealth","impactBombColorLit"], "color_mask":["egg1","egg2","egg3","bombColor","crossOutMask","fontExtras3"] } chars=["neoSpaz","zoe","ninja","kronk","mel","jack","santa","frosty","bones","bear","penguin","ali","cyborg","agent","wizard","pixie","bunny"] for char in chars: self.dic["head"].append(char+"Head") self.dic["hand"].append(char+"Hand") self.dic["torso"].append(char+"Torso") if char not in ['mel',"jack","santa"]: self.dic["pelvis"].append(char+"Pelvis") self.dic["upper_arm"].append(char+"UpperArm") self.dic["forearm"].append(char+"ForeArm") self.dic["upper_leg"].append(char+"UpperLeg") self.dic["lower_leg"].append(char+"LowerLeg") self.dic["toes_model"].append(char+"Toes") self.dic["color_mask"].append(char+"ColorMask") if char !="kronk": self.dic["color_texture"].append(char+"Color") cm=_ba.chatmessage def _new_chatmessage(msg): if msg.split(" ")[0]=="export": if len(msg.split(" "))>1: savecharacter(msg.split(" ")[1]) else: _ba.screenmessage("Enter name of character") elif msg.split(" ")[0]=="import": importcharacter(msg[7:]) else: cm(msg) _ba.chatmessage=_new_chatmessage def savecharacter(name): path=os.path.join(_ba.env()["python_directory_user"],"CustomCharacters" + os.sep) if not os.path.isdir(path): os.makedirs(path) if _ba.get_foreground_host_activity()!=None: with open(path+name+".json",'w') as f: json.dump(spazoutfit,f,indent=4) registercharacter(name,spazoutfit) ba.playsound(ba.getsound("gunCocking")) _ba.screenmessage("Character Saved") else: _ba.screenmessage("Works offline with Character Maker") def importcharacter(name): if name in ba.app.spaz_appearances: global character character=name try: _ba.get_foreground_host_activity().players[0].actor.node.handlemessage(ba.DieMessage()) _ba.screenmessage("Imported") except: _ba.screenmessage("works offline with character maker") else: _ba.screenmessage("invalid name check typo \n name is case sensitive") def registercharacter(name,char): t = Appearance(name.split(".")[0]) t.color_texture = char['color_texture'] t.color_mask_texture = char['color_mask'] t.default_color = (0.6, 0.6, 0.6) t.default_highlight = (0, 1, 0) t.icon_texture = char['icon_texture'] t.icon_mask_texture = char['icon_mask_texture'] t.head_model = char['head'] t.torso_model = char['torso'] t.pelvis_model = char['pelvis'] t.upper_arm_model = char['upper_arm'] t.forearm_model = char['forearm'] t.hand_model = char['hand'] t.upper_leg_model = char['upper_leg'] t.lower_leg_model = char['lower_leg'] t.toes_model = char['toes_model'] t.jump_sounds = char['jump_sounds'] t.attack_sounds = char['attack_sounds'] t.impact_sounds = char['impact_sounds'] t.death_sounds = char['death_sounds'] t.pickup_sounds = char['pickup_sounds'] t.fall_sounds = char['fall_sounds'] t.style = char['style'] # ba_meta export plugin class HeySmoothy(ba.Plugin): def __init__(self): _ba.set_party_icon_always_visible(True) path=os.path.join(_ba.env()["python_directory_user"],"CustomCharacters" + os.sep) if not os.path.isdir(path): os.makedirs(path) files=os.listdir(path) for file in files: with open(path+file, 'r') as f: character = json.load(f) registercharacter(file,character)