class_name BaseMode extends Node # NOTE: The ball's queue_free is called higher up and after game mode handles # the ball. Never call it in a given game mode. A game mode should handle # these signals to handle game related rules regarding ball sinks. var turn_count: int = 1 var player_1_points: int = 0 var player_2_points: int = 0 # NOTE: Kinda a super tracker of balls. Not all entries # generated are used depending on the game type. var ball_states: Dictionary = {} # NOTE: Used to track handoff logic and is cleared after eah switch accordingly. var balls_sunk: Array = [] var balls_reaped: Array = [] # NOTE: Kinda a super tracker of the state of a ball. # Not all entries are used depending on the game type. var BALL_STATE: Dictionary = { "turn": 0, "player": "", "sunk": false, "reaped": false, "is_foul": false, "points": 0, } # NOTE: Override when a mode doesn't award points for a sunk balls. func score_sunk_balls() -> bool: return true # NOTE: Override when a mode shouldn't keep the player's turn after a legal sink. func keep_turn_after_sink() -> bool: return true func get_player() -> String: return ( "Player 1" if Globals.game_data.client1 == Globals.multiplayer_data.current_turn_player_id else "Player 2" ) # NOTE: Handled before 'handle_switch_user' for early bail out if invalid reap. func reap_ball(ball: Node3D) -> void: _record_ball(ball, "reaped", balls_reaped) on_ball_reaped(ball) # NOTE: Override when a mode has extra processing such as # checking if solid or striped ball. Or 8-ball not sunk last. func on_ball_reaped(_ball: Node3D) -> void: pass # NOTE: Handled in 'handle_switch_user' func _process_reaped_balls(player: String) -> void: for ball_name in balls_reaped: _add_points(player, -ball_states[ball_name]["points"]) balls_reaped.clear() # NOTE: Handled before 'handle_switch_user' for early bail out if invalid sink. func process_ball(ball: Node3D) -> void: _record_ball(ball, "sunk", balls_sunk) on_ball_sunk(ball) # NOTE: Override when a mode has extra processing such as # checking if solid or striped ball. Or 8-ball not sunk last. func on_ball_sunk(_ball: Node3D) -> void: pass # NOTE: Handled in 'handle_switch_user' func _process_sunk_balls(player: String) -> void: for ball_name in balls_sunk: _add_points(player, ball_states[ball_name]["points"]) balls_sunk.clear() func _add_points(player: String, amount: int) -> void: if player == "Player 1": player_1_points += amount else: player_2_points += amount func _record_ball( ball: Node3D, state_key: String, tracker: Array ) -> void: tracker.append(ball.name) ball_states[ball.name]["turn"] = turn_count ball_states[ball.name]["player"] = get_player() ball_states[ball.name][state_key] = true func generate_ball_states() -> Dictionary: var state: Dictionary = {} for i: int in range(1, 16): var ball_name = "%02d_ball" % i state[ball_name] = BALL_STATE.duplicate_deep() state[ball_name]["points"] = i return state # NOTE: For testing end state. Keep below commented out. #func generate_ball_states() -> Dictionary: #var state: Dictionary = {} # #for i: int in range(1, 16): #var ball_name = "%02d_ball" % i #state[ball_name] = BALL_STATE.duplicate_deep() #state[ball_name]["turn"] = i #state[ball_name]["player"] = "Player " + str( 1 if randi() % 2 else 2 ) #state[ball_name]["sunk"] = true #state[ball_name]["is_foul"] = (randi() % 10 == 0) #state[ball_name]["points"] = i # #return state # End for testing end state. Keep above commented out. func handle_switch_user() -> void: push_warning("Balls reaped: ", balls_reaped.size() ) push_warning("Balls sunk: ", balls_sunk.size() ) var player: String = get_player() var no_sunk_balls: bool = balls_sunk.is_empty() _process_reaped_balls(player) if score_sunk_balls(): _process_sunk_balls(player) if ball_states_finalized(): process_win_condition() return if no_sunk_balls or not keep_turn_after_sink(): Globals.game_state.switch_active_player() turn_count += 1 func ball_states_finalized() -> bool: for ball_state in ball_states.values(): if ball_state["reaped"] or ball_state["sunk"]: continue return false return true func process_win_condition() -> String: var sub_message: String = \ ( "Player " + str( 1 if player_1_points > player_2_points else 2 ) + " Won!" ) var message: String = \ ( "==================================================" + "\n" + Globals.game_data.get_mode_pretty() + " Game Over\n" + sub_message + "\n==================================================" ) push_warning(message) MessageBus.emit_propagation_clients_only.rpc("declare_win_condition", message) return message