diff --git a/plugins/minigames.json b/plugins/minigames.json index b262ccb..68702d7 100644 --- a/plugins/minigames.json +++ b/plugins/minigames.json @@ -40,6 +40,44 @@ "md5sum": "a7ad7f1ac908fd871bcbcf665d49828c" } } + }, + "memory_game": { + "description": "Memorise tiles to surive! Playable in teams/ffa/co-op", + "external_url": "", + "authors": [ + { + "name": "Freaku", + "email": "", + "discord": "[Just] Freak#4999" + } + ], + "versions": { + "1.0.0": { + "api_version": 7, + "commit_sha": "858030b", + "released_on": "01-09-2022", + "md5sum": "27de4d6a66f41367977812c2df307c24" + } + } + }, + "musical_flags": { + "description": "Musical chairs... but for bombsquad!?!! Playable in ffa/teams", + "external_url": "", + "authors": [ + { + "name": "Freaku", + "email": "", + "discord": "[Just] Freak#4999" + } + ], + "versions": { + "1.0.0": { + "api_version": 7, + "commit_sha": "858030b", + "released_on": "01-09-2022", + "md5sum": "c84b7f415de5d3e9189ee73fc0e3ce93" + } + } } } } \ No newline at end of file diff --git a/plugins/minigames/memory_game.py b/plugins/minigames/memory_game.py new file mode 100644 index 0000000..c5c0204 --- /dev/null +++ b/plugins/minigames/memory_game.py @@ -0,0 +1,1017 @@ +from __future__ import annotations + + +## Original creator: byANG3L ## +## Made by: Freaku / @[Just] Freak#4999 ## + +## From: BSWorld Modpack (https://youtu.be/1TN56NLlShE) ## + + +# Used in-game boxes and textures instead of external +# So it will run on server and randoms can play init ._. +# (& some improvements) + + +# incase someone is wondering how is map floating. Check out +# def spawnAllMap(self) + + +# ba_meta require api 7 +from typing import TYPE_CHECKING, overload +import _ba +import ba +import random +from bastd.gameutils import SharedObjects +if TYPE_CHECKING: + from typing import Any, Sequence, Optional, List, Dict, Type, Union, Any, Literal + + +class OnTimer(ba.Actor): + """Timer which counts but doesn't show on-screen""" + + def __init__(self) -> None: + super().__init__() + self._starttime_ms: Optional[int] = None + self.node = ba.newnode('text', attrs={'v_attach': 'top', 'h_attach': 'center', 'h_align': 'center', 'color': ( + 1, 1, 0.5, 1), 'flatness': 0.5, 'shadow': 0.5, 'position': (0, -70), 'scale': 0, 'text': ''}) + self.inputnode = ba.newnode('timedisplay', attrs={ + 'timemin': 0, 'showsubseconds': True}) + self.inputnode.connectattr('output', self.node, 'text') + + def start(self) -> None: + tval = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) + assert isinstance(tval, int) + self._starttime_ms = tval + self.inputnode.time1 = self._starttime_ms + ba.getactivity().globalsnode.connectattr('time', self.inputnode, 'time2') + + def has_started(self) -> bool: + return self._starttime_ms is not None + + def stop(self, + endtime: Union[int, float] = None, + timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> None: + if endtime is None: + endtime = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) + timeformat = ba.TimeFormat.MILLISECONDS + if self._starttime_ms is None: + print('Warning: OnTimer.stop() called without start() first') + else: + endtime_ms: int + if timeformat is ba.TimeFormat.SECONDS: + endtime_ms = int(endtime * 1000) + elif timeformat is ba.TimeFormat.MILLISECONDS: + assert isinstance(endtime, int) + endtime_ms = endtime + else: + raise ValueError(f'invalid timeformat: {timeformat}') + + self.inputnode.timemax = endtime_ms - self._starttime_ms + # Overloads so type checker knows our exact return type based in args. + + @overload + def getstarttime(self, timeformat: Literal[ba.TimeFormat.SECONDS] = ba.TimeFormat.SECONDS) -> float: + ... + + @overload + def getstarttime(self, + timeformat: Literal[ba.TimeFormat.MILLISECONDS]) -> int: + ... + + def getstarttime( + self, + timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS + ) -> Union[int, float]: + """Return the sim-time when start() was called. + + Time will be returned in seconds if timeformat is SECONDS or + milliseconds if it is MILLISECONDS. + """ + val_ms: Any + if self._starttime_ms is None: + print('WARNING: getstarttime() called on un-started timer') + val_ms = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) + else: + val_ms = self._starttime_ms + assert isinstance(val_ms, int) + if timeformat is ba.TimeFormat.SECONDS: + return 0.001 * val_ms + if timeformat is ba.TimeFormat.MILLISECONDS: + return val_ms + raise ValueError(f'invalid timeformat: {timeformat}') + + @property + def starttime(self) -> float: + """Shortcut for start time in seconds.""" + return self.getstarttime() + + def handlemessage(self, msg: Any) -> Any: + # if we're asked to die, just kill our node/timer + if isinstance(msg, ba.DieMessage): + if self.node: + self.node.delete() + + +class Player(ba.Player['Team']): + """Our player type for this game.""" + + def __init__(self) -> None: + super().__init__() + self.death_time: Optional[float] = None + + +class Team(ba.Team[Player]): + """Our team type for this game.""" + + +# ba_meta export game +class MGgame(ba.TeamGameActivity[Player, Team]): + + name = 'Memory Game' + description = 'Memories tiles and survive till the end!' + available_settings = [ba.BoolSetting( + 'Epic Mode', default=False), ba.BoolSetting('Enable Bottom Credits', True)] + scoreconfig = ba.ScoreConfig(label='Survived', scoretype=ba.ScoreType.MILLISECONDS, version='B') + + # Print messages when players die (since its meaningful in this game). + announce_player_deaths = True + + # we're currently hard-coded for one map.. + @classmethod + def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + return ['Sky Tiles'] + + # We support teams, free-for-all, and co-op sessions. + @classmethod + def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + return (issubclass(sessiontype, ba.DualTeamSession) + or issubclass(sessiontype, ba.FreeForAllSession) + or issubclass(sessiontype, ba.CoopSession)) + + def __init__(self, settings: dict): + super().__init__(settings) + + self._epic_mode = settings.get('Epic Mode', False) + self._last_player_death_time: Optional[float] = None + self._timer: Optional[OnTimer] = None + self.credit_text = bool(settings['Enable Bottom Credits']) + + # Some base class overrides: + self.default_music = (ba.MusicType.EPIC + if self._epic_mode else ba.MusicType.SURVIVAL) + if self._epic_mode: + self.slow_motion = True + shared = SharedObjects.get() + self._collide_with_player = ba.Material() + self._collide_with_player.add_actions(actions=(('modify_part_collision', 'collide', True))) + self.dont_collide = ba.Material() + self.dont_collide.add_actions(actions=(('modify_part_collision', 'collide', False))) + self._levelStage = 0 + + self.announcePlayerDeaths = True + self._lastPlayerDeathTime = None + self._spawnCenter = (-3.17358, 2.75764, -2.99124) + + self._mapFGPModel = ba.getmodel('buttonSquareOpaque') + self._mapFGPDefaultTex = ba.gettexture('achievementOffYouGo') + + self._mapFGCurseTex = ba.gettexture('powerupCurse') + self._mapFGHealthTex = ba.gettexture('powerupHealth') + self._mapFGIceTex = ba.gettexture('powerupIceBombs') + self._mapFGImpactTex = ba.gettexture('powerupImpactBombs') + self._mapFGMinesTex = ba.gettexture('powerupLandMines') + self._mapFGPunchTex = ba.gettexture('powerupPunch') + self._mapFGShieldTex = ba.gettexture('powerupShield') + self._mapFGStickyTex = ba.gettexture('powerupStickyBombs') + + self._mapFGSpaz = ba.gettexture('neoSpazIcon') + self._mapFGZoe = ba.gettexture('zoeIcon') + self._mapFGSnake = ba.gettexture('ninjaIcon') + self._mapFGKronk = ba.gettexture('kronkIcon') + self._mapFGMel = ba.gettexture('melIcon') + self._mapFGJack = ba.gettexture('jackIcon') + self._mapFGSanta = ba.gettexture('santaIcon') + self._mapFGFrosty = ba.gettexture('frostyIcon') + self._mapFGBones = ba.gettexture('bonesIcon') + self._mapFGBernard = ba.gettexture('bearIcon') + self._mapFGPascal = ba.gettexture('penguinIcon') + self._mapFGAli = ba.gettexture('aliIcon') + self._mapFGRobot = ba.gettexture('cyborgIcon') + self._mapFGAgent = ba.gettexture('agentIcon') + self._mapFGGrumbledorf = ba.gettexture('wizardIcon') + self._mapFGPixel = ba.gettexture('pixieIcon') + + self._imageTextDefault = ba.gettexture('bg') + self._circleTex = ba.gettexture('circleShadow') + + self._image = ba.newnode('image', + attrs={'texture': self._imageTextDefault, + 'position': (0, -100), + 'scale': (100, 100), + 'opacity': 0.0, + 'attach': 'topCenter'}) + + self._textCounter = ba.newnode('text', + attrs={'text': '10', + 'position': (0, -100), + 'scale': 2.3, + 'shadow': 1.0, + 'flatness': 1.0, + 'opacity': 0.0, + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'v_align': 'center'}) + + self._textLevel = ba.newnode('text', + attrs={'text': 'Level ' + str(self._levelStage), + 'position': (0, -28), + 'scale': 1.3, + 'shadow': 1.0, + 'flatness': 1.0, + 'color': (1.0, 0.0, 1.0), + 'opacity': 0.0, + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'v_align': 'center'}) + + self._imageCircle = ba.newnode('image', + attrs={'texture': self._circleTex, + 'position': (75, -75), + 'scale': (20, 20), + 'color': (0.2, 0.2, 0.2), + 'opacity': 0.0, + 'attach': 'topCenter'}) + self._imageCircle2 = ba.newnode('image', + attrs={'texture': self._circleTex, + 'position': (75, -100), + 'scale': (20, 20), + 'color': (0.2, 0.2, 0.2), + 'opacity': 0.0, + 'attach': 'topCenter'}) + self._imageCircle3 = ba.newnode('image', + attrs={'texture': self._circleTex, + 'position': (75, -125), + 'scale': (20, 20), + 'color': (0.2, 0.2, 0.2), + 'opacity': 0.0, + 'attach': 'topCenter'}) + + def on_transition_in(self) -> None: + super().on_transition_in() + self._bellLow = ba.getsound('bellLow') + self._bellMed = ba.getsound('bellMed') + self._bellHigh = ba.getsound('bellHigh') + self._tickSound = ba.getsound('tick') + self._tickFinal = ba.getsound('powerup01') + self._scoreSound = ba.getsound('score') + + self._image.opacity = 1 + self._textCounter.opacity = 1 + self._textLevel.opacity = 1 + self._imageCircle.opacity = 0.7 + self._imageCircle2.opacity = 0.7 + self._imageCircle3.opacity = 0.7 + + self._levelStage += 1 + + self._textLevel.text = 'Level ' + str(self._levelStage) + + self._image.texture = self._imageTextDefault + + if self._levelStage == 1: + timeStart = 6 + ba.timer(timeStart, self._randomPlatform) + ba.timer(timeStart, self.startCounter) + + def on_begin(self) -> None: + super().on_begin() + + self._timer = OnTimer() + self._timer.start() + + self.coldel = True + self.coldel2 = True + self.coldel3 = True + self.coldel4 = True + self.coldel5 = True + self.coldel6 = True + self.coldel7 = True + self.coldel8 = True + self.coldel9 = True + self.coldel10 = True + self.coldel11 = True + self.coldel12 = True + self.coldel13 = True + self.coldel14 = True + self.coldel15 = True + self.coldel16 = True + if self.credit_text: + t = ba.newnode('text', + attrs={'text': "Made by Freaku\nOriginally for 1.4: byANG3L", # 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.spawnAllMap() + self.flashHide() + + # Check for immediate end (if we've only got 1 player, etc). + ba.timer(5, self._check_end_game) + self._dingSound = ba.getsound('dingSmall') + self._dingSoundHigh = ba.getsound('dingSmallHigh') + + def startCounter(self): + self._textCounter.text = '10' + + def count9(): + def count8(): + def count7(): + def count6(): + def count5(): + def count4(): + def count3(): + def count2(): + def count1(): + def countFinal(): + self._textCounter.text = '' + ba.playsound(self._tickFinal) + self._stop() + self._textCounter.text = '1' + ba.playsound(self._tickSound) + ba.timer(1, countFinal) + self._textCounter.text = '2' + ba.playsound(self._tickSound) + ba.timer(1, count1) + self._textCounter.text = '3' + ba.playsound(self._tickSound) + ba.timer(1, count2) + self._textCounter.text = '4' + ba.playsound(self._tickSound) + ba.timer(1, count3) + self._textCounter.text = '5' + ba.playsound(self._tickSound) + ba.timer(1, count4) + self._textCounter.text = '6' + ba.playsound(self._tickSound) + ba.timer(1, count5) + self._textCounter.text = '7' + ba.playsound(self._tickSound) + ba.timer(1, count6) + self._textCounter.text = '8' + ba.playsound(self._tickSound) + ba.timer(1, count7) + self._textCounter.text = '9' + ba.playsound(self._tickSound) + ba.timer(1, count8) + ba.timer(1, count9) + + def on_player_join(self, player: Player) -> None: + # Don't allow joining after we start + # (would enable leave/rejoin tomfoolery). + if self.has_begun(): + ba.screenmessage( + ba.Lstr(resource='playerDelayedJoinText', + subs=[('${PLAYER}', player.getname(full=True))]), + color=(0, 1, 0), transient=True, clients=[player.sessionplayer.inputdevice.client_id]) + # For score purposes, mark them as having died right as the + # game started. + assert self._timer is not None + player.death_time = self._timer.getstarttime() + return + self.spawn_player(player) + + def on_player_leave(self, player: Player) -> None: + # Augment default behavior. + super().on_player_leave(player) + + # A departing player may trigger game-over. + self._check_end_game() + + # overriding the default character spawning.. + def spawn_player(self, player: Player) -> ba.Actor: + spaz = self.spawn_player_spaz(player) + pos = (self._spawnCenter[0] + random.uniform(-1.5, 2.5), + self._spawnCenter[1], self._spawnCenter[2] + random.uniform(-2.5, 1.5)) + spaz.connect_controls_to_player(enable_punch=False, enable_bomb=False, enable_pickup=False) + spaz.handlemessage(ba.StandMessage(pos)) + return spaz + + def _randomSelect(self): + if self._levelStage == 1: + self._textureSelected = random.choice([self._mapFGMinesTex, + self._mapFGStickyTex]) + self._image.texture = self._textureSelected + elif self._levelStage == 2: + self._textureSelected = random.choice([self._mapFGIceTex, + self._mapFGShieldTex]) + self._image.texture = self._textureSelected + elif self._levelStage in [3, 4, 5]: + self._textureSelected = random.choice([self._mapFGStickyTex, + self._mapFGIceTex, + self._mapFGImpactTex, + self._mapFGMinesTex]) + self._image.texture = self._textureSelected + elif self._levelStage in [6, 7, 8, 9]: + self._textureSelected = random.choice([self._mapFGCurseTex, + self._mapFGHealthTex, + self._mapFGIceTex, + self._mapFGImpactTex, + self._mapFGMinesTex, + self._mapFGPunchTex, + self._mapFGShieldTex]) + self._image.texture = self._textureSelected + elif self._levelStage >= 10: + self._textureSelected = random.choice([self._mapFGSpaz, + self._mapFGZoe, + self._mapFGSnake, + self._mapFGKronk, + self._mapFGMel, + self._mapFGJack, + self._mapFGSanta, + self._mapFGFrosty, + self._mapFGBones, + self._mapFGBernard, + self._mapFGPascal, + self._mapFGAli, + self._mapFGRobot, + self._mapFGAgent, + self._mapFGGrumbledorf, + self._mapFGPixel]) + self._image.texture = self._textureSelected + return self._textureSelected + + def _stop(self): + self._textureSelected = self._randomSelect() + + def circle(): + def circle2(): + def circle3(): + self._imageCircle3.color = (0.0, 1.0, 0.0) + self._imageCircle3.opacity = 1.0 + ba.playsound(self._bellHigh) + ba.timer(0.2, self._doDelete) + self._imageCircle2.color = (1.0, 1.0, 0.0) + self._imageCircle2.opacity = 1.0 + ba.playsound(self._bellMed) + ba.timer(1, circle3) + self._imageCircle.color = (1.0, 0.0, 0.0) + self._imageCircle.opacity = 1.0 + ba.playsound(self._bellLow) + ba.timer(1, circle2) + ba.timer(1, circle) + + def _randomPlatform(self): + if self._levelStage == 1: + randomTexture = [self._mapFGMinesTex, + self._mapFGMinesTex, + self._mapFGMinesTex, + self._mapFGMinesTex, + self._mapFGMinesTex, + self._mapFGMinesTex, + self._mapFGMinesTex, + self._mapFGMinesTex, + self._mapFGStickyTex, + self._mapFGStickyTex, + self._mapFGStickyTex, + self._mapFGStickyTex, + self._mapFGStickyTex, + self._mapFGStickyTex, + self._mapFGStickyTex, + self._mapFGStickyTex] + elif self._levelStage == 2: + randomTexture = [self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGShieldTex, + self._mapFGShieldTex, + self._mapFGShieldTex, + self._mapFGShieldTex, + self._mapFGShieldTex, + self._mapFGShieldTex, + self._mapFGShieldTex, + self._mapFGShieldTex] + elif self._levelStage in [3, 4, 5]: + randomTexture = [self._mapFGStickyTex, + self._mapFGStickyTex, + self._mapFGStickyTex, + self._mapFGStickyTex, + self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGImpactTex, + self._mapFGImpactTex, + self._mapFGImpactTex, + self._mapFGImpactTex, + self._mapFGMinesTex, + self._mapFGMinesTex, + self._mapFGMinesTex, + self._mapFGMinesTex] + elif self._levelStage in [6, 7, 8, 9]: + randomTexture = [self._mapFGHealthTex, + self._mapFGShieldTex, + self._mapFGCurseTex, + self._mapFGCurseTex, + self._mapFGHealthTex, + self._mapFGHealthTex, + self._mapFGIceTex, + self._mapFGIceTex, + self._mapFGImpactTex, + self._mapFGImpactTex, + self._mapFGMinesTex, + self._mapFGMinesTex, + self._mapFGPunchTex, + self._mapFGPunchTex, + self._mapFGShieldTex, + self._mapFGShieldTex] + elif self._levelStage >= 10: + randomTexture = [self._mapFGSpaz, + self._mapFGZoe, + self._mapFGSnake, + self._mapFGKronk, + self._mapFGMel, + self._mapFGJack, + self._mapFGSanta, + self._mapFGFrosty, + self._mapFGBones, + self._mapFGBernard, + self._mapFGPascal, + self._mapFGAli, + self._mapFGRobot, + self._mapFGAgent, + self._mapFGGrumbledorf, + self._mapFGPixel] + + (self.mapFGPTex, self.mapFGP2Tex, + self.mapFGP3Tex, self.mapFGP4Tex, + self.mapFGP5Tex, self.mapFGP6Tex, + self.mapFGP7Tex, self.mapFGP8Tex, + self.mapFGP9Tex, self.mapFGP10Tex, + self.mapFGP11Tex, self.mapFGP12Tex, + self.mapFGP13Tex, self.mapFGP14Tex, + self.mapFGP15Tex, self.mapFGP16Tex) = ( + random.sample(randomTexture, 16)) + self._mixPlatform() + + def _mixPlatform(self): + ba.timer(1, self.flashShow) + ba.timer(3, self.flashHide) + ba.timer(4, self.flashShow) + ba.timer(6, self.flashHide) + ba.timer(7, self.flashShow) + ba.timer(9, self.flashHide) + ba.timer(13.2, self.flashShow) + + def flashHide(self): + self.mapFGP.color_texture = self._mapFGPDefaultTex + self.mapFGP2.color_texture = self._mapFGPDefaultTex + self.mapFGP3.color_texture = self._mapFGPDefaultTex + self.mapFGP4.color_texture = self._mapFGPDefaultTex + self.mapFGP5.color_texture = self._mapFGPDefaultTex + self.mapFGP6.color_texture = self._mapFGPDefaultTex + self.mapFGP7.color_texture = self._mapFGPDefaultTex + self.mapFGP8.color_texture = self._mapFGPDefaultTex + self.mapFGP9.color_texture = self._mapFGPDefaultTex + self.mapFGP10.color_texture = self._mapFGPDefaultTex + self.mapFGP11.color_texture = self._mapFGPDefaultTex + self.mapFGP12.color_texture = self._mapFGPDefaultTex + self.mapFGP13.color_texture = self._mapFGPDefaultTex + self.mapFGP14.color_texture = self._mapFGPDefaultTex + self.mapFGP15.color_texture = self._mapFGPDefaultTex + self.mapFGP16.color_texture = self._mapFGPDefaultTex + + def flashShow(self): + self.mapFGP.color_texture = self.mapFGPTex + self.mapFGP2.color_texture = self.mapFGP2Tex + self.mapFGP3.color_texture = self.mapFGP3Tex + self.mapFGP4.color_texture = self.mapFGP4Tex + self.mapFGP5.color_texture = self.mapFGP5Tex + self.mapFGP6.color_texture = self.mapFGP6Tex + self.mapFGP7.color_texture = self.mapFGP7Tex + self.mapFGP8.color_texture = self.mapFGP8Tex + self.mapFGP9.color_texture = self.mapFGP9Tex + self.mapFGP10.color_texture = self.mapFGP10Tex + self.mapFGP11.color_texture = self.mapFGP11Tex + self.mapFGP12.color_texture = self.mapFGP12Tex + self.mapFGP13.color_texture = self.mapFGP13Tex + self.mapFGP14.color_texture = self.mapFGP14Tex + self.mapFGP15.color_texture = self.mapFGP15Tex + self.mapFGP16.color_texture = self.mapFGP16Tex + + def _doDelete(self): + if not self.mapFGPTex == self._textureSelected: + self.mapFGP.delete() + self.mapFGPcol.delete() + self.coldel = True + if not self.mapFGP2Tex == self._textureSelected: + self.mapFGP2.delete() + self.mapFGP2col.delete() + self.coldel2 = True + if not self.mapFGP3Tex == self._textureSelected: + self.mapFGP3.delete() + self.mapFGP3col.delete() + self.coldel3 = True + if not self.mapFGP4Tex == self._textureSelected: + self.mapFGP4.delete() + self.mapFGP4col.delete() + self.coldel4 = True + if not self.mapFGP5Tex == self._textureSelected: + self.mapFGP5.delete() + self.mapFGP5col.delete() + self.coldel5 = True + if not self.mapFGP6Tex == self._textureSelected: + self.mapFGP6.delete() + self.mapFGP6col.delete() + self.coldel6 = True + if not self.mapFGP7Tex == self._textureSelected: + self.mapFGP7.delete() + self.mapFGP7col.delete() + self.coldel7 = True + if not self.mapFGP8Tex == self._textureSelected: + self.mapFGP8.delete() + self.mapFGP8col.delete() + self.coldel8 = True + if not self.mapFGP9Tex == self._textureSelected: + self.mapFGP9.delete() + self.mapFGP9col.delete() + self.coldel9 = True + if not self.mapFGP10Tex == self._textureSelected: + self.mapFGP10.delete() + self.mapFGP10col.delete() + self.coldel10 = True + if not self.mapFGP11Tex == self._textureSelected: + self.mapFGP11.delete() + self.mapFGP11col.delete() + self.coldel11 = True + if not self.mapFGP12Tex == self._textureSelected: + self.mapFGP12.delete() + self.mapFGP12col.delete() + self.coldel12 = True + if not self.mapFGP13Tex == self._textureSelected: + self.mapFGP13.delete() + self.mapFGP13col.delete() + self.coldel13 = True + if not self.mapFGP14Tex == self._textureSelected: + self.mapFGP14.delete() + self.mapFGP14col.delete() + self.coldel14 = True + if not self.mapFGP15Tex == self._textureSelected: + self.mapFGP15.delete() + self.mapFGP15col.delete() + self.coldel15 = True + if not self.mapFGP16Tex == self._textureSelected: + self.mapFGP16.delete() + self.mapFGP16col.delete() + self.coldel16 = True + + ba.timer(3.3, self._platformTexDefault) + + def spawnAllMap(self): + """ + # Here's how it works: + # First, create prop with a gravity scale of 0 + # Then use a in-game model which will suit it (For this one I didn't chose box, since it will look kinda weird) Right? + # Instead I used a 2d model (which is nothing but a button in menu) + # This prop SHOULD NOT collide with anything, since it has gravity_scale of 0 if it'll get weight it will fall down :(( + # These are where we change those color-textures and is seen in-game + + # Now lets talk about the actual node on which we stand (sadly no-one realises it exists) + # A moment of silence for this node... + + # Alright, so this is a region node (the one used in hockey/football for scoring) + # Thanksfully these are just thicc boxes positioned on the map (so they are not moved neither they have gravity_scale) + # So we create this region node and place it to the same position of our prop node + # and give it collide_with_player and footing materials + # Thats it, now you have your own floating platforms :D + """ + shared = SharedObjects.get() + if self.coldel: + self.mapFGP = ba.newnode('prop', + attrs={'body': 'puck', 'position': (3, 2, -9), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGPTex = None + self.mapFGPcol = ba.newnode('region', attrs={'position': (3, 2, -9), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel = False + + if self.coldel2: + self.mapFGP2 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (3, 2, -6), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP2Tex = None + self.mapFGP2col = ba.newnode('region', attrs={'position': (3, 2, -6), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel2 = False + + if self.coldel3: + self.mapFGP3 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (3, 2, -3), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP3Tex = None + self.mapFGP3col = ba.newnode('region', attrs={'position': (3, 2, -3), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel3 = False + + if self.coldel4: + self.mapFGP4 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (3, 2, 0), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP4Tex = None + self.mapFGP4col = ba.newnode('region', attrs={'position': (3, 2, 0), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel4 = False + + if self.coldel5: + self.mapFGP5 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (0, 2, -9), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP5Tex = None + self.mapFGP5col = ba.newnode('region', attrs={'position': (0, 2, -9), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel5 = False + + if self.coldel6: + self.mapFGP6 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (0, 2, -6), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP6Tex = None + self.mapFGP6col = ba.newnode('region', attrs={'position': (0, 2, -6), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel6 = False + + if self.coldel7: + self.mapFGP7 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (0, 2, -3), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP7Tex = None + self.mapFGP7col = ba.newnode('region', attrs={'position': (0, 2, -3), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel7 = False + + if self.coldel8: + self.mapFGP8 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (0, 2, 0), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP8Tex = None + self.mapFGP8col = ba.newnode('region', attrs={'position': (0, 2, 0), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel8 = False + + if self.coldel9: + self.mapFGP9 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (-3, 2, -9), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP9Tex = None + self.mapFGP9col = ba.newnode('region', attrs={'position': (-3, 2, -9), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel9 = False + + if self.coldel10: + self.mapFGP10 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (-3, 2, -6), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP10Tex = None + self.mapFGP10col = ba.newnode('region', attrs={'position': (-3, 2, -6), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel10 = False + + if self.coldel11: + self.mapFGP11 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (-3, 2, -3), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP11Tex = None + self.mapFGP11col = ba.newnode('region', attrs={'position': (-3, 2, -3), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel11 = False + + if self.coldel12: + self.mapFGP12 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (-3, 2, 0), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP12Tex = None + self.mapFGP12col = ba.newnode('region', attrs={'position': (-3, 2, 0), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel12 = False + + if self.coldel13: + self.mapFGP13 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (-6, 2, -9), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP13Tex = None + self.mapFGP13col = ba.newnode('region', attrs={'position': (-6, 2, -9), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel13 = False + + if self.coldel14: + self.mapFGP14 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (-6, 2, -6), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP14Tex = None + self.mapFGP14col = ba.newnode('region', attrs={'position': (-6, 2, -6), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel14 = False + + if self.coldel15: + self.mapFGP15 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (-6, 2, -3), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP15Tex = None + self.mapFGP15col = ba.newnode('region', attrs={'position': (-6, 2, -3), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel15 = False + + if self.coldel16: + self.mapFGP16 = ba.newnode('prop', + attrs={'body': 'puck', 'position': (-6, 2, 0), 'model': self._mapFGPModel, 'model_scale': 3.8, 'body_scale': 3.8, 'shadow_size': 0.5, 'gravity_scale': 0.0, 'color_texture': self._mapFGPDefaultTex, 'reflection': 'soft', 'reflection_scale': [1.0], 'is_area_of_interest': True, 'materials': [self.dont_collide]}) + self.mapFGP16Tex = None + self.mapFGP16col = ba.newnode('region', attrs={'position': (-6, 2, 0), 'scale': ( + 3.5, 0.1, 3.5), 'type': 'box', 'materials': (self._collide_with_player, shared.footing_material)}) + self.coldel16 = False + + def _platformTexDefault(self): + self._textureSelected = None + + self._imageCircle.color = (0.2, 0.2, 0.2) + self._imageCircle.opacity = 0.7 + self._imageCircle2.color = (0.2, 0.2, 0.2) + self._imageCircle2.opacity = 0.7 + self._imageCircle3.color = (0.2, 0.2, 0.2) + self._imageCircle3.opacity = 0.7 + + self._levelStage += 1 + + self._textLevel.text = 'Level ' + str(self._levelStage) + + self._image.texture = self._imageTextDefault + + if self._levelStage == 1: + timeStart = 6 + else: + timeStart = 2 + ba.playsound(self._scoreSound) + activity = _ba.get_foreground_host_activity() + for i in activity.players: + try: + i.actor.node.handlemessage(ba.CelebrateMessage(2.0)) + except: + pass + ba.timer(timeStart, self._randomPlatform) + ba.timer(timeStart, self.startCounter) + + self.spawnAllMap() + self.flashHide() + + # Various high-level game events come through this method. + def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, ba.PlayerDiedMessage): + + # Augment standard behavior. + super().handlemessage(msg) + + curtime = ba.time() + + # Record the player's moment of death. + # assert isinstance(msg.spaz.player + msg.getplayer(Player).death_time = curtime + + # In co-op mode, end the game the instant everyone dies + # (more accurate looking). + # In teams/ffa, allow a one-second fudge-factor so we can + # get more draws if players die basically at the same time. + if isinstance(self.session, ba.CoopSession): + # Teams will still show up if we check now.. check in + # the next cycle. + ba.pushcall(self._check_end_game) + + # Also record this for a final setting of the clock. + self._last_player_death_time = curtime + else: + ba.timer(1.0, self._check_end_game) + else: + # Default handler: + return super().handlemessage(msg) + return None + + def _check_end_game(self) -> None: + living_team_count = 0 + for team in self.teams: + for player in team.players: + if player.is_alive(): + living_team_count += 1 + break + + # In co-op, we go till everyone is dead.. otherwise we go + # until one team remains. + if isinstance(self.session, ba.CoopSession): + if living_team_count <= 0: + self.end_game() + else: + if living_team_count <= 1: + self.end_game() + + def end_game(self) -> None: + cur_time = ba.time() + assert self._timer is not None + start_time = self._timer.getstarttime() + + # Mark death-time as now for any still-living players + # and award players points for how long they lasted. + # (these per-player scores are only meaningful in team-games) + for team in self.teams: + for player in team.players: + survived = False + + # Throw an extra fudge factor in so teams that + # didn't die come out ahead of teams that did. + if player.death_time is None: + survived = True + player.death_time = cur_time + 1 + + # Award a per-player score depending on how many seconds + # they lasted (per-player scores only affect teams mode; + # everywhere else just looks at the per-team score). + score = int(player.death_time - self._timer.getstarttime()) + if survived: + score += 50 # A bit extra for survivors. + self.stats.player_scored(player, score, screenmessage=False) + + # Stop updating our time text, and set the final time to match + # exactly when our last guy died. + self._timer.stop(endtime=self._last_player_death_time) + + # Ok now calc game results: set a score for each team and then tell + # the game to end. + results = ba.GameResults() + + # Remember that 'free-for-all' mode is simply a special form + # of 'teams' mode where each player gets their own team, so we can + # just always deal in teams and have all cases covered. + for team in self.teams: + + # Set the team score to the max time survived by any player on + # that team. + longest_life = 0.0 + for player in team.players: + assert player.death_time is not None + longest_life = max(longest_life, player.death_time - start_time) + + # Submit the score value in milliseconds. + results.set_team_score(team, int(1000.0 * longest_life)) + + self.end(results=results) + + +class MGdefs(): + points = {} + boxes = {} + boxes['area_of_interest_bounds'] = ( + 0.3544110667, 4.493562578, -2.518391331) + (0.0, 0.0, 0.0) + (16.64754831, 8.06138989, 18.5029888) + boxes['map_bounds'] = (0.2608783669, 4.899663734, -3.543675157) + \ + (0.0, 0.0, 0.0) + (29.23565494, 14.19991443, 29.92689344) + + +class MGmap(ba.Map): + defs = MGdefs() + name = 'Sky Tiles' + + @classmethod + def get_play_types(cls) -> List[str]: + """Return valid play types for this map.""" + return [] + + @classmethod + def get_preview_texture_name(cls) -> str: + return 'achievementOffYouGo' + + @classmethod + def on_preload(cls) -> Any: + data: Dict[str, Any] = { + 'bgtex': ba.gettexture('menuBG'), + 'bgmodel': ba.getmodel('thePadBG') + } + return data + + def __init__(self) -> None: + super().__init__() + shared = SharedObjects.get() + self.node = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bgmodel'], + 'lighting': False, + 'background': True, + 'color_texture': self.preloaddata['bgtex'] + }) + gnode = ba.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 + + +ba._map.register_map(MGmap) + + +# ba_meta export plugin +class byFreaku(ba.Plugin): + def __init__(self): + ## Campaign support ## + ba.app.add_coop_practice_level(ba.Level(name='Memory Game', displayname='${GAME}', gametype=MGgame, settings={ + }, preview_texture_name='achievementOffYouGo')) diff --git a/plugins/minigames/musical_flags.py b/plugins/minigames/musical_flags.py new file mode 100644 index 0000000..fbe9d2e --- /dev/null +++ b/plugins/minigames/musical_flags.py @@ -0,0 +1,252 @@ +# Made by MattZ45986 on GitHub +# Ported by: Freaku / @[Just] Freak#4999 + + +# Bug Fixes & Improvements as well... + +# Join BCS: +# https://discord.gg/ucyaesh + + +from __future__ import annotations +from typing import TYPE_CHECKING +import _ba +import ba +import random +import math +from bastd.actor.flag import Flag, FlagPickedUpMessage +from bastd.actor.playerspaz import PlayerSpaz +if TYPE_CHECKING: + from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional + + +class Player(ba.Player['Team']): + def __init__(self) -> None: + self.done: bool = False + self.survived: bool = True + + +class Team(ba.Team[Player]): + def __init__(self) -> None: + self.score = 0 + + +# ba_meta require api 7 +# ba_meta export game +class MFGame(ba.TeamGameActivity[Player, Team]): + name = 'Musical Flags' + description = "Don't be the one stuck without a flag!" + + @classmethod + def get_available_settings( + cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]: + settings = [ + 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.BoolSetting('Epic Mode', default=False), + ba.BoolSetting('Enable Running', default=True), + ba.BoolSetting('Enable Punching', default=False), + ba.BoolSetting('Enable Bottom Credit', True) + ] + 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 ['Doom Shroom'] + + def __init__(self, settings: dict): + super().__init__(settings) + self.nodes = [] + self._dingsound = ba.getsound('dingSmall') + self._epic_mode = bool(settings['Epic Mode']) + self.credit_text = bool(settings['Enable Bottom Credit']) + self._time_limit = float(settings['Time Limit']) + self.is_punch = bool(settings['Enable Punching']) + self.is_run = bool(settings['Enable Running']) + + self._textRound = ba.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.slow_motion = self._epic_mode + # A cool music, matching our gamemode theme + self.default_music = ba.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(): + ba.screenmessage( + ba.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. + self.checkEnd() + + def on_begin(self) -> None: + super().on_begin() + self.roundNum = 0 + self.numPickedUp = 0 + self.nodes = [] + self.flags = [] + self.spawned = [] + self.setup_standard_time_limit(self._time_limit) + if self.credit_text: + t = ba.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) + ba.timer(5, self.checkEnd) + + 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 = [] + 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)] + # 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(ba.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)) -> ba.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(ba.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, ba.PlayerDiedMessage): + super().handlemessage(msg) + player = msg.getplayer(Player) + ba.timer(0.1, ba.Call(self.check_respawn, player)) + ba.timer(0.5, self.checkEnd) + elif isinstance(msg, FlagPickedUpMessage): + self.numPickedUp += 1 + msg.node.getdelegate(PlayerSpaz, True).getplayer(Player, True).done = True + l = ba.newnode('light', + owner=None, + attrs={'color': msg.node.color, + 'position': (msg.node.position_center), + 'intensity': 1}) + self.nodes.append(l) + msg.flag.handlemessage(ba.DieMessage()) + msg.node.handlemessage(ba.DieMessage()) + msg.node.delete() + if self.numPickedUp == len(self.flags): + for player in self.spawned: + if not player.done: + try: + player.survived = False + ba.screenmessage("No Flag? "+player.getname()) + player.actor.handlemessage(ba.StandMessage((0, 3, -2))) + ba.timer(0.5, ba.Call(player.actor.handlemessage, ba.FreezeMessage())) + ba.timer(3, ba.Call(player.actor.handlemessage, ba.ShouldShatterMessage())) + except: + pass + ba.timer(3.5, self.killRound) + ba.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 + ba.timer(2.5, self.end_game) + + def end_game(self) -> None: + results = ba.GameResults() + for team in self.teams: + results.set_team_score(team, team.score) + self.end(results=results) diff --git a/plugins/utilities.json b/plugins/utilities.json index 41eda5e..fb1503e 100644 --- a/plugins/utilities.json +++ b/plugins/utilities.json @@ -76,6 +76,25 @@ "md5sum": "aac4edfcaeca1dc2910f97e739d67482" } } + }, + "floater": { + "description": "Calls a overpowered floater in any gamemode. Chat /floater to activate!", + "external_url": "", + "authors": [ + { + "name": "Freaku", + "email": "", + "discord": "[Just] Freak#4999" + } + ], + "versions": { + "1.0.0": { + "api_version": 7, + "commit_sha": "858030b", + "released_on": "01-09-2022", + "md5sum": "c024a0774f2e960dad7f633efdc3feb5" + } + } } } } \ No newline at end of file diff --git a/plugins/utilities/floater.py b/plugins/utilities/floater.py new file mode 100644 index 0000000..9106117 --- /dev/null +++ b/plugins/utilities/floater.py @@ -0,0 +1,276 @@ +# Ported by: Freaku / @[Just] Freak#4999 + +# Join BCS: +# https://discord.gg/ucyaesh + + +# My GitHub: +# https://github.com/Freaku17/BombSquad-Mods-byFreaku + + +# ba_meta require api 7 +from __future__ import annotations +from typing import TYPE_CHECKING +import _ba +import ba +import random +import math +from bastd.gameutils import SharedObjects +from bastd.actor.bomb import Bomb +from bastd.actor.popuptext import PopupText +if TYPE_CHECKING: + from typing import Optional + + +class Floater(ba.Actor): + def __init__(self, bounds): + super().__init__() + shared = SharedObjects.get() + self.controlled = False + self.source_player = None + self.floaterMaterial = ba.Material() + self.floaterMaterial.add_actions( + conditions=('they_have_material', + shared.player_material), + actions=(('modify_node_collision', 'collide', True), + ('modify_part_collision', 'physical', True))) + self.floaterMaterial.add_actions( + conditions=(('they_have_material', + shared.object_material), 'or', + ('they_have_material', + shared.footing_material), 'or', + ('they_have_material', + self.floaterMaterial)), + actions=('modify_part_collision', 'physical', False)) + + self.pos = bounds + self.px = "random.uniform(self.pos[0],self.pos[3])" + self.py = "random.uniform(self.pos[1],self.pos[4])" + self.pz = "random.uniform(self.pos[2],self.pos[5])" + + self.node = ba.newnode( + 'prop', + delegate=self, + owner=None, + attrs={ + 'position': (eval(self.px), eval(self.py), eval(self.pz)), + 'model': + ba.getmodel('landMine'), + 'light_model': + ba.getmodel('landMine'), + 'body': + 'landMine', + 'body_scale': + 3, + 'model_scale': + 3.1, + 'shadow_size': + 0.25, + 'density': + 999999, + 'gravity_scale': + 0.0, + 'color_texture': + ba.gettexture('achievementFlawlessVictory'), + 'reflection': + 'soft', + 'reflection_scale': [0.25], + 'materials': + [shared.footing_material, self.floaterMaterial] + }) + self.node2 = ba.newnode( + 'prop', + owner=self.node, + attrs={ + 'position': (0, 0, 0), + 'body': + 'sphere', + 'model': + None, + 'color_texture': + None, + 'body_scale': + 1.0, + 'reflection': + 'powerup', + 'density': + 999999, + 'reflection_scale': [1.0], + 'model_scale': + 1.0, + 'gravity_scale': + 0, + 'shadow_size': + 0.1, + 'is_area_of_interest': + True, + 'materials': + [shared.object_material, self.floaterMaterial] + }) + self.node.connectattr('position', self.node2, 'position') + + def pop(self): PopupText(text="Ported by \ue048Freaku", scale=1.3, position=( + self.node.position[0], self.node.position[1]-1, self.node.position[2]), color=(0, 1, 1)).autoretain() # Edit = YouNoob... + + def checkCanControl(self): + if not self.node.exists(): + return False + if not self.source_player.is_alive(): + self.dis() + return False + return True + + def con(self): + self.controlled = True + self.checkPlayerDie() + + def up(self): + if not self.checkCanControl(): + return + v = self.node.velocity + self.node.velocity = (v[0], 5, v[2]) + + def upR(self): + if not self.checkCanControl(): + return + v = self.node.velocity + self.node.velocity = (v[0], 0, v[2]) + + def down(self): + if not self.checkCanControl(): + return + v = self.node.velocity + self.node.velocity = (v[0], -5, v[2]) + + def downR(self): + if not self.checkCanControl(): + return + v = self.node.velocity + self.node.velocity = (v[0], 0, v[2]) + + def leftright(self, value): + if not self.checkCanControl(): + return + v = self.node.velocity + self.node.velocity = (5 * value, v[1], v[2]) + + def updown(self, value): + if not self.checkCanControl(): + return + v = self.node.velocity + self.node.velocity = (v[0], v[1], -5 * value) + + def dis(self): + if self.node.exists(): + self.controlled = False + self.node.velocity = (0, 0, 0) + self.move() + + def checkPlayerDie(self): + if not self.controlled: + return + if self.source_player is None: + return + if self.source_player.is_alive(): + ba.timer(1, self.checkPlayerDie) + return + else: + self.dis() + + def distance(self, x1, y1, z1, x2, y2, z2): + d = math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2) + math.pow(z2 - z1, 2)) + return d + + def drop(self): + try: + np = self.node.position + except: + np = (0, 0, 0) + self.b = Bomb(bomb_type=random.choice(['normal', 'ice', 'sticky', 'impact', 'land_mine', 'tnt']), + source_player=self.source_player, position=(np[0], np[1] - 1, np[2]), velocity=(0, -1, 0)).autoretain() + if self.b.bomb_type in ['impact', 'land_mine']: + self.b.arm() + + def move(self): + px = eval(self.px) + py = eval(self.py) + pz = eval(self.pz) + if self.node.exists() and not self.controlled: + pn = self.node.position + dist = self.distance(pn[0], pn[1], pn[2], px, py, pz) + self.node.velocity = ((px - pn[0]) / dist, (py - pn[1]) / dist, (pz - pn[2]) / dist) + ba.timer(dist-1, ba.WeakCall(self.move), suppress_format_warning=True) + + def handlemessage(self, msg): + if isinstance(msg, ba.DieMessage): + self.node.delete() + self.node2.delete() + self.controlled = False + elif isinstance(msg, ba.OutOfBoundsMessage): + self.handlemessage(ba.DieMessage()) + else: + super().handlemessage(msg) + + +def assignFloInputs(clientID: int): + with ba.Context(_ba.get_foreground_host_activity()): + activity = ba.getactivity() + if not hasattr(activity, 'flo') or not activity.flo.node.exists(): + try: + activity.flo = Floater(activity.map.get_def_bound_box('map_bounds')) + except: + return # Perhaps using in main-menu/score-screen + floater = activity.flo + if floater.controlled: + ba.screenmessage('Floater is already being controlled', + color=(1, 0, 0), transient=True, clients=[clientID]) + return + ba.screenmessage('You Gained Control Over The Floater!\n Press Bomb to Throw Bombs and Punch to leave!', clients=[ + clientID], transient=True, color=(0, 1, 1)) + + for i in _ba.get_foreground_host_activity().players: + if i.sessionplayer.inputdevice.client_id == clientID: + def dis(i, floater): + i.actor.node.invincible = False + i.resetinput() + i.actor.connect_controls_to_player() + floater.dis() + ps = i.actor.node.position + i.actor.node.invincible = True + floater.node.position = (ps[0], ps[1] + 1.0, ps[2]) + ba.timer(1, floater.pop) + i.actor.node.hold_node = ba.Node(None) + i.actor.node.hold_node = floater.node2 + i.actor.connect_controls_to_player() + i.actor.disconnect_controls_from_player() + i.resetinput() + floater.source_player = i + floater.con() + i.assigninput(ba.InputType.PICK_UP_PRESS, floater.up) + i.assigninput(ba.InputType.PICK_UP_RELEASE, floater.upR) + i.assigninput(ba.InputType.JUMP_PRESS, floater.down) + i.assigninput(ba.InputType.BOMB_PRESS, floater.drop) + i.assigninput(ba.InputType.PUNCH_PRESS, ba.Call(dis, i, floater)) + i.assigninput(ba.InputType.UP_DOWN, floater.updown) + i.assigninput(ba.InputType.LEFT_RIGHT, floater.leftright) + + +old_fcm = _ba.chatmessage + + +def new_chat_message(msg: Union[str, ba.Lstr], clients: Sequence[int] = None, sender_override: str = None): + old_fcm(msg, clients, sender_override) + if msg == '/floater': + try: + assignFloInputs(-1) + except: + pass + + +_ba.chatmessage = new_chat_message + +# ba_meta export plugin + + +class byFreaku(ba.Plugin): + def __init__(self): pass