bascenev1lib.game.kingofthehill
Defines the King of the Hill game.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Defines the King of the Hill game.""" 4 5# ba_meta require api 9 6# (see https://ballistica.net/wiki/meta-tag-system) 7 8from __future__ import annotations 9 10import weakref 11from enum import Enum 12from typing import TYPE_CHECKING, override 13 14import bascenev1 as bs 15 16from bascenev1lib.actor.flag import Flag 17from bascenev1lib.actor.playerspaz import PlayerSpaz 18from bascenev1lib.actor.scoreboard import Scoreboard 19from bascenev1lib.gameutils import SharedObjects 20 21if TYPE_CHECKING: 22 from typing import Any, Sequence 23 24 25class FlagState(Enum): 26 """States our single flag can be in.""" 27 28 NEW = 0 29 UNCONTESTED = 1 30 CONTESTED = 2 31 HELD = 3 32 33 34class Player(bs.Player['Team']): 35 """Our player type for this game.""" 36 37 def __init__(self) -> None: 38 self.time_at_flag = 0 39 40 41class Team(bs.Team[Player]): 42 """Our team type for this game.""" 43 44 def __init__(self, time_remaining: int) -> None: 45 self.time_remaining = time_remaining 46 47 48# ba_meta export bascenev1.GameActivity 49class KingOfTheHillGame(bs.TeamGameActivity[Player, Team]): 50 """Game where a team wins by holding a 'hill' for a set amount of time.""" 51 52 name = 'King of the Hill' 53 description = 'Secure the flag for a set length of time.' 54 available_settings = [ 55 bs.IntSetting( 56 'Hold Time', 57 min_value=10, 58 default=30, 59 increment=10, 60 ), 61 bs.IntChoiceSetting( 62 'Time Limit', 63 choices=[ 64 ('None', 0), 65 ('1 Minute', 60), 66 ('2 Minutes', 120), 67 ('5 Minutes', 300), 68 ('10 Minutes', 600), 69 ('20 Minutes', 1200), 70 ], 71 default=0, 72 ), 73 bs.FloatChoiceSetting( 74 'Respawn Times', 75 choices=[ 76 ('Shorter', 0.25), 77 ('Short', 0.5), 78 ('Normal', 1.0), 79 ('Long', 2.0), 80 ('Longer', 4.0), 81 ], 82 default=1.0, 83 ), 84 bs.BoolSetting('Epic Mode', default=False), 85 ] 86 scoreconfig = bs.ScoreConfig(label='Time Held') 87 88 @override 89 @classmethod 90 def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: 91 return issubclass(sessiontype, bs.MultiTeamSession) 92 93 @override 94 @classmethod 95 def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: 96 assert bs.app.classic is not None 97 return bs.app.classic.getmaps('king_of_the_hill') 98 99 def __init__(self, settings: dict): 100 super().__init__(settings) 101 shared = SharedObjects.get() 102 self._scoreboard = Scoreboard() 103 self._swipsound = bs.getsound('swip') 104 self._tick_sound = bs.getsound('tick') 105 self._countdownsounds = { 106 10: bs.getsound('announceTen'), 107 9: bs.getsound('announceNine'), 108 8: bs.getsound('announceEight'), 109 7: bs.getsound('announceSeven'), 110 6: bs.getsound('announceSix'), 111 5: bs.getsound('announceFive'), 112 4: bs.getsound('announceFour'), 113 3: bs.getsound('announceThree'), 114 2: bs.getsound('announceTwo'), 115 1: bs.getsound('announceOne'), 116 } 117 self._flag_pos: Sequence[float] | None = None 118 self._flag_state: FlagState | None = None 119 self._flag: Flag | None = None 120 self._flag_light: bs.Node | None = None 121 self._scoring_team: weakref.ref[Team] | None = None 122 self._hold_time = int(settings['Hold Time']) 123 self._time_limit = float(settings['Time Limit']) 124 self._epic_mode = bool(settings['Epic Mode']) 125 self._flag_region_material = bs.Material() 126 self._flag_region_material.add_actions( 127 conditions=('they_have_material', shared.player_material), 128 actions=( 129 ('modify_part_collision', 'collide', True), 130 ('modify_part_collision', 'physical', False), 131 ( 132 'call', 133 'at_connect', 134 bs.Call(self._handle_player_flag_region_collide, True), 135 ), 136 ( 137 'call', 138 'at_disconnect', 139 bs.Call(self._handle_player_flag_region_collide, False), 140 ), 141 ), 142 ) 143 144 # Base class overrides. 145 self.slow_motion = self._epic_mode 146 self.default_music = ( 147 bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SCARY 148 ) 149 150 @override 151 def get_instance_description(self) -> str | Sequence: 152 return 'Secure the flag for ${ARG1} seconds.', self._hold_time 153 154 @override 155 def get_instance_description_short(self) -> str | Sequence: 156 return 'secure the flag for ${ARG1} seconds', self._hold_time 157 158 @override 159 def create_team(self, sessionteam: bs.SessionTeam) -> Team: 160 return Team(time_remaining=self._hold_time) 161 162 @override 163 def on_begin(self) -> None: 164 super().on_begin() 165 shared = SharedObjects.get() 166 self.setup_standard_time_limit(self._time_limit) 167 self.setup_standard_powerup_drops() 168 self._flag_pos = self.map.get_flag_position(None) 169 bs.timer(1.0, self._tick, repeat=True) 170 self._flag_state = FlagState.NEW 171 Flag.project_stand(self._flag_pos) 172 self._flag = Flag( 173 position=self._flag_pos, touchable=False, color=(1, 1, 1) 174 ) 175 self._flag_light = bs.newnode( 176 'light', 177 attrs={ 178 'position': self._flag_pos, 179 'intensity': 0.2, 180 'height_attenuated': False, 181 'radius': 0.4, 182 'color': (0.2, 0.2, 0.2), 183 }, 184 ) 185 # Flag region. 186 flagmats = [self._flag_region_material, shared.region_material] 187 bs.newnode( 188 'region', 189 attrs={ 190 'position': self._flag_pos, 191 'scale': (1.8, 1.8, 1.8), 192 'type': 'sphere', 193 'materials': flagmats, 194 }, 195 ) 196 self._update_scoreboard() 197 self._update_flag_state() 198 199 def _tick(self) -> None: 200 self._update_flag_state() 201 202 # Give holding players points. 203 for player in self.players: 204 if player.time_at_flag > 0: 205 self.stats.player_scored( 206 player, 3, screenmessage=False, display=False 207 ) 208 if self._scoring_team is None: 209 scoring_team = None 210 else: 211 scoring_team = self._scoring_team() 212 if scoring_team: 213 if scoring_team.time_remaining > 0: 214 self._tick_sound.play() 215 216 scoring_team.time_remaining = max( 217 0, scoring_team.time_remaining - 1 218 ) 219 self._update_scoreboard() 220 if scoring_team.time_remaining > 0: 221 assert self._flag is not None 222 self._flag.set_score_text(str(scoring_team.time_remaining)) 223 224 # Announce numbers we have sounds for. 225 numsound = self._countdownsounds.get(scoring_team.time_remaining) 226 if numsound is not None: 227 numsound.play() 228 229 # winner 230 if scoring_team.time_remaining <= 0: 231 self.end_game() 232 233 @override 234 def end_game(self) -> None: 235 results = bs.GameResults() 236 for team in self.teams: 237 results.set_team_score(team, self._hold_time - team.time_remaining) 238 self.end(results=results, announce_delay=0) 239 240 def _update_flag_state(self) -> None: 241 holding_teams = set( 242 player.team for player in self.players if player.time_at_flag 243 ) 244 prev_state = self._flag_state 245 assert self._flag_light 246 assert self._flag is not None 247 assert self._flag.node 248 if len(holding_teams) > 1: 249 self._flag_state = FlagState.CONTESTED 250 self._scoring_team = None 251 self._flag_light.color = (0.6, 0.6, 0.1) 252 self._flag.node.color = (1.0, 1.0, 0.4) 253 elif len(holding_teams) == 1: 254 holding_team = list(holding_teams)[0] 255 self._flag_state = FlagState.HELD 256 self._scoring_team = weakref.ref(holding_team) 257 self._flag_light.color = bs.normalized_color(holding_team.color) 258 self._flag.node.color = holding_team.color 259 else: 260 self._flag_state = FlagState.UNCONTESTED 261 self._scoring_team = None 262 self._flag_light.color = (0.2, 0.2, 0.2) 263 self._flag.node.color = (1, 1, 1) 264 if self._flag_state != prev_state: 265 self._swipsound.play() 266 267 def _handle_player_flag_region_collide(self, colliding: bool) -> None: 268 try: 269 spaz = bs.getcollision().opposingnode.getdelegate(PlayerSpaz, True) 270 except bs.NotFoundError: 271 return 272 273 if not spaz.is_alive(): 274 return 275 276 player = spaz.getplayer(Player, True) 277 278 # Different parts of us can collide so a single value isn't enough 279 # also don't count it if we're dead (flying heads shouldn't be able to 280 # win the game :-) 281 if colliding and player.is_alive(): 282 player.time_at_flag += 1 283 else: 284 player.time_at_flag = max(0, player.time_at_flag - 1) 285 286 self._update_flag_state() 287 288 def _update_scoreboard(self) -> None: 289 for team in self.teams: 290 self._scoreboard.set_team_value( 291 team, team.time_remaining, self._hold_time, countdown=True 292 ) 293 294 @override 295 def handlemessage(self, msg: Any) -> Any: 296 if isinstance(msg, bs.PlayerDiedMessage): 297 super().handlemessage(msg) # Augment default. 298 299 # No longer can count as time_at_flag once dead. 300 player = msg.getplayer(Player) 301 player.time_at_flag = 0 302 self._update_flag_state() 303 self.respawn_player(player)
26class FlagState(Enum): 27 """States our single flag can be in.""" 28 29 NEW = 0 30 UNCONTESTED = 1 31 CONTESTED = 2 32 HELD = 3
States our single flag can be in.
35class Player(bs.Player['Team']): 36 """Our player type for this game.""" 37 38 def __init__(self) -> None: 39 self.time_at_flag = 0
Our player type for this game.
42class Team(bs.Team[Player]): 43 """Our team type for this game.""" 44 45 def __init__(self, time_remaining: int) -> None: 46 self.time_remaining = time_remaining
Our team type for this game.
50class KingOfTheHillGame(bs.TeamGameActivity[Player, Team]): 51 """Game where a team wins by holding a 'hill' for a set amount of time.""" 52 53 name = 'King of the Hill' 54 description = 'Secure the flag for a set length of time.' 55 available_settings = [ 56 bs.IntSetting( 57 'Hold Time', 58 min_value=10, 59 default=30, 60 increment=10, 61 ), 62 bs.IntChoiceSetting( 63 'Time Limit', 64 choices=[ 65 ('None', 0), 66 ('1 Minute', 60), 67 ('2 Minutes', 120), 68 ('5 Minutes', 300), 69 ('10 Minutes', 600), 70 ('20 Minutes', 1200), 71 ], 72 default=0, 73 ), 74 bs.FloatChoiceSetting( 75 'Respawn Times', 76 choices=[ 77 ('Shorter', 0.25), 78 ('Short', 0.5), 79 ('Normal', 1.0), 80 ('Long', 2.0), 81 ('Longer', 4.0), 82 ], 83 default=1.0, 84 ), 85 bs.BoolSetting('Epic Mode', default=False), 86 ] 87 scoreconfig = bs.ScoreConfig(label='Time Held') 88 89 @override 90 @classmethod 91 def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: 92 return issubclass(sessiontype, bs.MultiTeamSession) 93 94 @override 95 @classmethod 96 def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: 97 assert bs.app.classic is not None 98 return bs.app.classic.getmaps('king_of_the_hill') 99 100 def __init__(self, settings: dict): 101 super().__init__(settings) 102 shared = SharedObjects.get() 103 self._scoreboard = Scoreboard() 104 self._swipsound = bs.getsound('swip') 105 self._tick_sound = bs.getsound('tick') 106 self._countdownsounds = { 107 10: bs.getsound('announceTen'), 108 9: bs.getsound('announceNine'), 109 8: bs.getsound('announceEight'), 110 7: bs.getsound('announceSeven'), 111 6: bs.getsound('announceSix'), 112 5: bs.getsound('announceFive'), 113 4: bs.getsound('announceFour'), 114 3: bs.getsound('announceThree'), 115 2: bs.getsound('announceTwo'), 116 1: bs.getsound('announceOne'), 117 } 118 self._flag_pos: Sequence[float] | None = None 119 self._flag_state: FlagState | None = None 120 self._flag: Flag | None = None 121 self._flag_light: bs.Node | None = None 122 self._scoring_team: weakref.ref[Team] | None = None 123 self._hold_time = int(settings['Hold Time']) 124 self._time_limit = float(settings['Time Limit']) 125 self._epic_mode = bool(settings['Epic Mode']) 126 self._flag_region_material = bs.Material() 127 self._flag_region_material.add_actions( 128 conditions=('they_have_material', shared.player_material), 129 actions=( 130 ('modify_part_collision', 'collide', True), 131 ('modify_part_collision', 'physical', False), 132 ( 133 'call', 134 'at_connect', 135 bs.Call(self._handle_player_flag_region_collide, True), 136 ), 137 ( 138 'call', 139 'at_disconnect', 140 bs.Call(self._handle_player_flag_region_collide, False), 141 ), 142 ), 143 ) 144 145 # Base class overrides. 146 self.slow_motion = self._epic_mode 147 self.default_music = ( 148 bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SCARY 149 ) 150 151 @override 152 def get_instance_description(self) -> str | Sequence: 153 return 'Secure the flag for ${ARG1} seconds.', self._hold_time 154 155 @override 156 def get_instance_description_short(self) -> str | Sequence: 157 return 'secure the flag for ${ARG1} seconds', self._hold_time 158 159 @override 160 def create_team(self, sessionteam: bs.SessionTeam) -> Team: 161 return Team(time_remaining=self._hold_time) 162 163 @override 164 def on_begin(self) -> None: 165 super().on_begin() 166 shared = SharedObjects.get() 167 self.setup_standard_time_limit(self._time_limit) 168 self.setup_standard_powerup_drops() 169 self._flag_pos = self.map.get_flag_position(None) 170 bs.timer(1.0, self._tick, repeat=True) 171 self._flag_state = FlagState.NEW 172 Flag.project_stand(self._flag_pos) 173 self._flag = Flag( 174 position=self._flag_pos, touchable=False, color=(1, 1, 1) 175 ) 176 self._flag_light = bs.newnode( 177 'light', 178 attrs={ 179 'position': self._flag_pos, 180 'intensity': 0.2, 181 'height_attenuated': False, 182 'radius': 0.4, 183 'color': (0.2, 0.2, 0.2), 184 }, 185 ) 186 # Flag region. 187 flagmats = [self._flag_region_material, shared.region_material] 188 bs.newnode( 189 'region', 190 attrs={ 191 'position': self._flag_pos, 192 'scale': (1.8, 1.8, 1.8), 193 'type': 'sphere', 194 'materials': flagmats, 195 }, 196 ) 197 self._update_scoreboard() 198 self._update_flag_state() 199 200 def _tick(self) -> None: 201 self._update_flag_state() 202 203 # Give holding players points. 204 for player in self.players: 205 if player.time_at_flag > 0: 206 self.stats.player_scored( 207 player, 3, screenmessage=False, display=False 208 ) 209 if self._scoring_team is None: 210 scoring_team = None 211 else: 212 scoring_team = self._scoring_team() 213 if scoring_team: 214 if scoring_team.time_remaining > 0: 215 self._tick_sound.play() 216 217 scoring_team.time_remaining = max( 218 0, scoring_team.time_remaining - 1 219 ) 220 self._update_scoreboard() 221 if scoring_team.time_remaining > 0: 222 assert self._flag is not None 223 self._flag.set_score_text(str(scoring_team.time_remaining)) 224 225 # Announce numbers we have sounds for. 226 numsound = self._countdownsounds.get(scoring_team.time_remaining) 227 if numsound is not None: 228 numsound.play() 229 230 # winner 231 if scoring_team.time_remaining <= 0: 232 self.end_game() 233 234 @override 235 def end_game(self) -> None: 236 results = bs.GameResults() 237 for team in self.teams: 238 results.set_team_score(team, self._hold_time - team.time_remaining) 239 self.end(results=results, announce_delay=0) 240 241 def _update_flag_state(self) -> None: 242 holding_teams = set( 243 player.team for player in self.players if player.time_at_flag 244 ) 245 prev_state = self._flag_state 246 assert self._flag_light 247 assert self._flag is not None 248 assert self._flag.node 249 if len(holding_teams) > 1: 250 self._flag_state = FlagState.CONTESTED 251 self._scoring_team = None 252 self._flag_light.color = (0.6, 0.6, 0.1) 253 self._flag.node.color = (1.0, 1.0, 0.4) 254 elif len(holding_teams) == 1: 255 holding_team = list(holding_teams)[0] 256 self._flag_state = FlagState.HELD 257 self._scoring_team = weakref.ref(holding_team) 258 self._flag_light.color = bs.normalized_color(holding_team.color) 259 self._flag.node.color = holding_team.color 260 else: 261 self._flag_state = FlagState.UNCONTESTED 262 self._scoring_team = None 263 self._flag_light.color = (0.2, 0.2, 0.2) 264 self._flag.node.color = (1, 1, 1) 265 if self._flag_state != prev_state: 266 self._swipsound.play() 267 268 def _handle_player_flag_region_collide(self, colliding: bool) -> None: 269 try: 270 spaz = bs.getcollision().opposingnode.getdelegate(PlayerSpaz, True) 271 except bs.NotFoundError: 272 return 273 274 if not spaz.is_alive(): 275 return 276 277 player = spaz.getplayer(Player, True) 278 279 # Different parts of us can collide so a single value isn't enough 280 # also don't count it if we're dead (flying heads shouldn't be able to 281 # win the game :-) 282 if colliding and player.is_alive(): 283 player.time_at_flag += 1 284 else: 285 player.time_at_flag = max(0, player.time_at_flag - 1) 286 287 self._update_flag_state() 288 289 def _update_scoreboard(self) -> None: 290 for team in self.teams: 291 self._scoreboard.set_team_value( 292 team, team.time_remaining, self._hold_time, countdown=True 293 ) 294 295 @override 296 def handlemessage(self, msg: Any) -> Any: 297 if isinstance(msg, bs.PlayerDiedMessage): 298 super().handlemessage(msg) # Augment default. 299 300 # No longer can count as time_at_flag once dead. 301 player = msg.getplayer(Player) 302 player.time_at_flag = 0 303 self._update_flag_state() 304 self.respawn_player(player)
Game where a team wins by holding a 'hill' for a set amount of time.
100 def __init__(self, settings: dict): 101 super().__init__(settings) 102 shared = SharedObjects.get() 103 self._scoreboard = Scoreboard() 104 self._swipsound = bs.getsound('swip') 105 self._tick_sound = bs.getsound('tick') 106 self._countdownsounds = { 107 10: bs.getsound('announceTen'), 108 9: bs.getsound('announceNine'), 109 8: bs.getsound('announceEight'), 110 7: bs.getsound('announceSeven'), 111 6: bs.getsound('announceSix'), 112 5: bs.getsound('announceFive'), 113 4: bs.getsound('announceFour'), 114 3: bs.getsound('announceThree'), 115 2: bs.getsound('announceTwo'), 116 1: bs.getsound('announceOne'), 117 } 118 self._flag_pos: Sequence[float] | None = None 119 self._flag_state: FlagState | None = None 120 self._flag: Flag | None = None 121 self._flag_light: bs.Node | None = None 122 self._scoring_team: weakref.ref[Team] | None = None 123 self._hold_time = int(settings['Hold Time']) 124 self._time_limit = float(settings['Time Limit']) 125 self._epic_mode = bool(settings['Epic Mode']) 126 self._flag_region_material = bs.Material() 127 self._flag_region_material.add_actions( 128 conditions=('they_have_material', shared.player_material), 129 actions=( 130 ('modify_part_collision', 'collide', True), 131 ('modify_part_collision', 'physical', False), 132 ( 133 'call', 134 'at_connect', 135 bs.Call(self._handle_player_flag_region_collide, True), 136 ), 137 ( 138 'call', 139 'at_disconnect', 140 bs.Call(self._handle_player_flag_region_collide, False), 141 ), 142 ), 143 ) 144 145 # Base class overrides. 146 self.slow_motion = self._epic_mode 147 self.default_music = ( 148 bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SCARY 149 )
Instantiate the Activity.
89 @override 90 @classmethod 91 def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: 92 return issubclass(sessiontype, bs.MultiTeamSession)
Class method override; returns True for ba.DualTeamSessions and ba.FreeForAllSessions; False otherwise.
94 @override 95 @classmethod 96 def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: 97 assert bs.app.classic is not None 98 return bs.app.classic.getmaps('king_of_the_hill')
Called by the default bascenev1.GameActivity.create_settings_ui() implementation; should return a list of map names valid for this game-type for the given bascenev1.Session type.
151 @override 152 def get_instance_description(self) -> str | Sequence: 153 return 'Secure the flag for ${ARG1} seconds.', self._hold_time
Return a description for this game instance, in English.
This is shown in the center of the screen below the game name at the start of a game. It should start with a capital letter and end with a period, and can be a bit more verbose than the version returned by get_instance_description_short().
Note that translation is applied by looking up the specific returned value as a key, so the number of returned variations should be limited; ideally just one or two. To include arbitrary values in the description, you can return a sequence of values in the following form instead of just a string:
This will give us something like 'Score 3 goals.' in English
and can properly translate to 'Anota 3 goles.' in Spanish.
If we just returned the string 'Score 3 Goals' here, there would
have to be a translation entry for each specific number. ew.
return ['Score ${ARG1} goals.', self.settings_raw['Score to Win']]
This way the first string can be consistently translated, with any arg values then substituted into the result. ${ARG1} will be replaced with the first value, ${ARG2} with the second, etc.
155 @override 156 def get_instance_description_short(self) -> str | Sequence: 157 return 'secure the flag for ${ARG1} seconds', self._hold_time
Return a short description for this game instance in English.
This description is used above the game scoreboard in the corner of the screen, so it should be as concise as possible. It should be lowercase and should not contain periods or other punctuation.
Note that translation is applied by looking up the specific returned value as a key, so the number of returned variations should be limited; ideally just one or two. To include arbitrary values in the description, you can return a sequence of values in the following form instead of just a string:
This will give us something like 'score 3 goals' in English
and can properly translate to 'anota 3 goles' in Spanish.
If we just returned the string 'score 3 goals' here, there would
have to be a translation entry for each specific number. ew.
return ['score ${ARG1} goals', self.settings_raw['Score to Win']]
This way the first string can be consistently translated, with any arg values then substituted into the result. ${ARG1} will be replaced with the first value, ${ARG2} with the second, etc.
159 @override 160 def create_team(self, sessionteam: bs.SessionTeam) -> Team: 161 return Team(time_remaining=self._hold_time)
Create the Team instance for this Activity.
Subclasses can override this if the activity's team class requires a custom constructor; otherwise it will be called with no args. Note that the team object should not be used at this point as it is not yet fully wired up; wait for on_team_join() for that.
163 @override 164 def on_begin(self) -> None: 165 super().on_begin() 166 shared = SharedObjects.get() 167 self.setup_standard_time_limit(self._time_limit) 168 self.setup_standard_powerup_drops() 169 self._flag_pos = self.map.get_flag_position(None) 170 bs.timer(1.0, self._tick, repeat=True) 171 self._flag_state = FlagState.NEW 172 Flag.project_stand(self._flag_pos) 173 self._flag = Flag( 174 position=self._flag_pos, touchable=False, color=(1, 1, 1) 175 ) 176 self._flag_light = bs.newnode( 177 'light', 178 attrs={ 179 'position': self._flag_pos, 180 'intensity': 0.2, 181 'height_attenuated': False, 182 'radius': 0.4, 183 'color': (0.2, 0.2, 0.2), 184 }, 185 ) 186 # Flag region. 187 flagmats = [self._flag_region_material, shared.region_material] 188 bs.newnode( 189 'region', 190 attrs={ 191 'position': self._flag_pos, 192 'scale': (1.8, 1.8, 1.8), 193 'type': 'sphere', 194 'materials': flagmats, 195 }, 196 ) 197 self._update_scoreboard() 198 self._update_flag_state()
Called once the previous Activity has finished transitioning out.
At this point the activity's initial players and teams are filled in and it should begin its actual game logic.
234 @override 235 def end_game(self) -> None: 236 results = bs.GameResults() 237 for team in self.teams: 238 results.set_team_score(team, self._hold_time - team.time_remaining) 239 self.end(results=results, announce_delay=0)
Tell the game to wrap up and call bascenev1.Activity.end().
This method should be overridden by subclasses. A game should always be prepared to end and deliver results, even if there is no 'winner' yet; this way things like the standard time-limit (bascenev1.GameActivity.setup_standard_time_limit()) will work with the game.
295 @override 296 def handlemessage(self, msg: Any) -> Any: 297 if isinstance(msg, bs.PlayerDiedMessage): 298 super().handlemessage(msg) # Augment default. 299 300 # No longer can count as time_at_flag once dead. 301 player = msg.getplayer(Player) 302 player.time_at_flag = 0 303 self._update_flag_state() 304 self.respawn_player(player)
General message handling; can be passed any message object.