2026-04-20 13:03:39 -05:00
|
|
|
class_name BoardGame extends Node
|
|
|
|
|
|
2026-04-24 12:19:34 -05:00
|
|
|
signal all_players_ready_for_day_start
|
|
|
|
|
|
2026-04-20 13:03:39 -05:00
|
|
|
const HOME_SCENE = preload("uid://bto4vblqk2inb")
|
2026-04-24 12:19:34 -05:00
|
|
|
const HQ_SCENE = preload("uid://wdgig5aclnpx")
|
2026-04-20 13:03:39 -05:00
|
|
|
#const DOWN_SPAWN_SCENE = preload("uid://d4ltd1geg7s2p")
|
|
|
|
|
|
|
|
|
|
const BANK_SCENE = preload("uid://c06fsqdixer1c")
|
|
|
|
|
const BAR_SCENE = preload("uid://cfk268flnsbhb")
|
|
|
|
|
const CAFE_SCENE = preload("uid://bgtw051fiveeo")
|
|
|
|
|
const FORK_SCENE = preload("uid://cphy0vtj14ob0")
|
|
|
|
|
const GUN_SHOP_SCENE = preload("uid://c1kyedmrep0tu")
|
|
|
|
|
const HOSPITAL_SCENE = preload("uid://bytldu3y1jak3")
|
|
|
|
|
const OFFICE_SCENE = preload("uid://mixrqf035krk")
|
|
|
|
|
const SHOP_SCENE = preload("uid://dbn63mv0peqf")
|
2026-04-24 12:19:34 -05:00
|
|
|
const CHURCH_SCENE = preload("uid://brn0nbkela0m4")
|
|
|
|
|
const CITY_HALL_SCENE = preload("uid://dtnejoimqiu0o")
|
2026-04-20 13:03:39 -05:00
|
|
|
|
|
|
|
|
const BASE_DECK = [
|
|
|
|
|
BANK_SCENE,
|
|
|
|
|
BAR_SCENE,
|
|
|
|
|
CAFE_SCENE,
|
|
|
|
|
FORK_SCENE,
|
|
|
|
|
GUN_SHOP_SCENE,
|
|
|
|
|
HOSPITAL_SCENE,
|
|
|
|
|
OFFICE_SCENE,
|
2026-04-24 12:19:34 -05:00
|
|
|
SHOP_SCENE,
|
|
|
|
|
CHURCH_SCENE,
|
|
|
|
|
CITY_HALL_SCENE
|
2026-04-20 13:03:39 -05:00
|
|
|
]
|
2026-04-15 12:10:17 -05:00
|
|
|
|
|
|
|
|
var citizen_count: int = 0:
|
|
|
|
|
set(value):
|
2026-04-20 13:03:39 -05:00
|
|
|
#print(value)
|
2026-04-15 12:10:17 -05:00
|
|
|
citizen_count = value
|
|
|
|
|
if citizen_count == 0:
|
2026-04-20 13:03:39 -05:00
|
|
|
handle_citizens_finished()
|
|
|
|
|
|
|
|
|
|
var buildings_added_to_draft: int = 0
|
|
|
|
|
var spawn_placement_actions: int = 0
|
|
|
|
|
var draft_index_to_be_deleted: int = -1
|
2026-04-24 12:19:34 -05:00
|
|
|
var num_players_ready_for_day_start: int = 0
|
|
|
|
|
var is_playing_day: bool = false
|
2026-04-20 13:03:39 -05:00
|
|
|
|
|
|
|
|
var current_board_state: BoardState:
|
|
|
|
|
set(value):
|
|
|
|
|
current_board_state = value
|
2026-04-24 12:19:34 -05:00
|
|
|
#controls.check_controls_enabled()
|
|
|
|
|
#controls.set_info()
|
2026-04-20 13:03:39 -05:00
|
|
|
|
|
|
|
|
var pending_board_state: BoardState:
|
|
|
|
|
set(value):
|
|
|
|
|
pending_board_state = value
|
2026-04-24 12:19:34 -05:00
|
|
|
#controls.check_controls_enabled()
|
|
|
|
|
|
|
|
|
|
var original_board_state: BoardState
|
2026-04-15 12:10:17 -05:00
|
|
|
|
2026-04-20 13:03:39 -05:00
|
|
|
var deck: Array[Building] = []
|
2026-04-15 12:10:17 -05:00
|
|
|
|
|
|
|
|
@onready var board: Board = %Board
|
|
|
|
|
@onready var controls: Controls = %Controls
|
2026-04-20 13:03:39 -05:00
|
|
|
@onready var game_over: Control = %GameOver
|
2026-04-15 12:10:17 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func _init() -> void:
|
|
|
|
|
Globals.board_game = self
|
|
|
|
|
|
|
|
|
|
|
2026-04-20 13:03:39 -05:00
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
|
|
|
func setup_building_deck() -> void:
|
|
|
|
|
for i in range(current_board_state.players.size()):
|
|
|
|
|
for scene in BASE_DECK:
|
|
|
|
|
deck.append(scene.instantiate())
|
2026-04-15 12:10:17 -05:00
|
|
|
|
|
|
|
|
|
2026-04-20 13:03:39 -05:00
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
2026-04-24 12:19:34 -05:00
|
|
|
func advance_board_state(board_state: Dictionary) -> void:
|
|
|
|
|
pending_board_state = null
|
2026-04-20 13:03:39 -05:00
|
|
|
current_board_state = BoardState.deserialize(board_state)
|
|
|
|
|
board.reset()
|
|
|
|
|
board.set_board_state(current_board_state)
|
2026-04-24 12:19:34 -05:00
|
|
|
controls.set_board_state(current_board_state)
|
2026-04-20 13:03:39 -05:00
|
|
|
if current_board_state.state == BoardState.State.INITIAL_SETUP:
|
2026-04-24 12:19:34 -05:00
|
|
|
if current_board_state.turn >= 0:
|
|
|
|
|
current_board_state.players.reverse()
|
|
|
|
|
current_board_state.current_player = current_board_state.players[0]
|
|
|
|
|
controls.set_info()
|
|
|
|
|
start_day()
|
|
|
|
|
elif Globals.game.this_player.id == current_board_state.current_player.id:
|
|
|
|
|
if current_board_state.turn >= -current_board_state.players.size():
|
|
|
|
|
board.set_active_building(HQ_SCENE.instantiate())
|
|
|
|
|
else:
|
|
|
|
|
board.set_active_building(HOME_SCENE.instantiate())
|
|
|
|
|
elif current_board_state.state == BoardState.State.PLACING_SPAWNS:
|
2026-04-20 13:03:39 -05:00
|
|
|
start_day()
|
|
|
|
|
if current_board_state.players_passed == Globals.game.players.size():
|
2026-04-24 12:19:34 -05:00
|
|
|
current_board_state.players_passed = 0
|
2026-04-20 13:03:39 -05:00
|
|
|
end_day()
|
2026-04-24 12:19:34 -05:00
|
|
|
controls.check_controls_enabled()
|
|
|
|
|
controls.set_info()
|
2026-04-15 12:10:17 -05:00
|
|
|
|
|
|
|
|
|
2026-04-20 13:03:39 -05:00
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
2026-04-24 12:19:34 -05:00
|
|
|
func update_board_state(board_state: Dictionary) -> void:
|
|
|
|
|
current_board_state = BoardState.deserialize(board_state)
|
|
|
|
|
board.reset()
|
|
|
|
|
board.set_board_state(current_board_state)
|
|
|
|
|
controls.set_board_state(current_board_state)
|
|
|
|
|
controls.check_controls_enabled()
|
|
|
|
|
controls.set_info()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
|
|
|
func add_building_to_draft(building_data: Dictionary) -> void:
|
|
|
|
|
current_board_state.real_estate_market.append(Building.deserialize(building_data))
|
2026-04-20 13:03:39 -05:00
|
|
|
buildings_added_to_draft += 1
|
|
|
|
|
if buildings_added_to_draft >= Globals.game.players.size():
|
2026-04-24 12:19:34 -05:00
|
|
|
buildings_added_to_draft = 0
|
2026-04-20 13:03:39 -05:00
|
|
|
current_board_state.state = BoardState.State.PLAY
|
|
|
|
|
controls.check_controls_enabled()
|
2026-04-24 12:19:34 -05:00
|
|
|
controls.set_info()
|
|
|
|
|
original_board_state = BoardState.deserialize(current_board_state.serialize())
|
|
|
|
|
update_board_state.rpc(current_board_state.serialize())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
|
|
|
func ready_for_day_start() -> void:
|
|
|
|
|
num_players_ready_for_day_start += 1
|
|
|
|
|
if num_players_ready_for_day_start >= Globals.game.players.size():
|
|
|
|
|
all_players_ready_for_day_start.emit()
|
2026-04-15 12:10:17 -05:00
|
|
|
|
|
|
|
|
|
2026-04-20 13:03:39 -05:00
|
|
|
func select_tile(tile: Tile) -> void:
|
2026-04-24 12:19:34 -05:00
|
|
|
board.set_active_tile(Tile.deserialize(tile.serialize()))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func select_building(building: Building) -> void:
|
|
|
|
|
if building is Home:
|
|
|
|
|
board.remove_home(current_board_state.current_player)
|
|
|
|
|
if building is HQ:
|
|
|
|
|
board.remove_hq(current_board_state.current_player)
|
|
|
|
|
board.set_active_building(Building.deserialize(building.serialize()))
|
|
|
|
|
draft_index_to_be_deleted = current_board_state.real_estate_market.find_custom(
|
|
|
|
|
func(b: Building) -> bool:
|
|
|
|
|
return (
|
|
|
|
|
b.get_script() == building.get_script()
|
|
|
|
|
and b.cost == building.cost
|
|
|
|
|
and b.player == building.player
|
|
|
|
|
)
|
|
|
|
|
)
|
2026-04-15 12:10:17 -05:00
|
|
|
|
|
|
|
|
|
2026-04-24 12:19:34 -05:00
|
|
|
func cancel_placement() -> void:
|
|
|
|
|
if is_instance_valid(board.active_tile):
|
|
|
|
|
board.active_tile.queue_free()
|
|
|
|
|
board.active_tile = null
|
|
|
|
|
if is_instance_valid(board.active_building):
|
|
|
|
|
board.active_building.queue_free()
|
|
|
|
|
board.active_building = null
|
2026-04-15 12:10:17 -05:00
|
|
|
|
|
|
|
|
|
2026-04-20 13:03:39 -05:00
|
|
|
func start_day() -> void:
|
2026-04-24 12:19:34 -05:00
|
|
|
if current_board_state.day > 0 and current_board_state.state != BoardState.State.PLACING_SPAWNS:
|
|
|
|
|
current_board_state.players.sort_custom(
|
|
|
|
|
func(p1: Player, p2: Player) -> bool: return p1.money > p2.money
|
|
|
|
|
)
|
|
|
|
|
current_board_state.current_player = current_board_state.players[0]
|
|
|
|
|
|
|
|
|
|
await all_players_ready_for_day_start
|
|
|
|
|
|
|
|
|
|
if current_board_state.state == BoardState.State.PLACING_SPAWNS:
|
|
|
|
|
if current_board_state.spawn_placements > 0:
|
|
|
|
|
if current_board_state.current_player.id == Globals.game.this_player.id:
|
|
|
|
|
board.call_deferred(
|
|
|
|
|
"set_active_building", load("uid://d4ltd1geg7s2p").instantiate()
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
current_board_state.state = BoardState.State.DRAFT
|
2026-04-20 13:03:39 -05:00
|
|
|
current_board_state.day += 1
|
|
|
|
|
current_board_state.turn = 1
|
2026-04-24 12:19:34 -05:00
|
|
|
current_board_state.state = BoardState.State.DRAFT
|
|
|
|
|
for p in current_board_state.players:
|
|
|
|
|
p.build_actions_taken = 0
|
|
|
|
|
tally_votes()
|
|
|
|
|
reduce_draft_costs()
|
|
|
|
|
|
2026-04-20 13:03:39 -05:00
|
|
|
seed(Globals.game.sum_player_ids())
|
|
|
|
|
deck.shuffle()
|
|
|
|
|
var turn_index := current_board_state.get_this_player_index()
|
|
|
|
|
controls.give_hand(deck.slice(0 + (3 * turn_index), 3 + (3 * turn_index)))
|
|
|
|
|
controls.set_info()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func end_day() -> void:
|
|
|
|
|
controls.end_day()
|
|
|
|
|
get_tree().call_group("Pausable", "unpause")
|
2026-04-24 12:19:34 -05:00
|
|
|
is_playing_day = true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func update_player_votes(player: Player, votes: int) -> void:
|
|
|
|
|
var idx = current_board_state.get_player_index(player)
|
|
|
|
|
current_board_state.players[idx].votes = votes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func update_player_money(player: Player, money: int) -> void:
|
|
|
|
|
var idx = current_board_state.get_player_index(player)
|
|
|
|
|
current_board_state.players[idx].money = money
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func reduce_draft_costs() -> void:
|
|
|
|
|
for building in current_board_state.real_estate_market:
|
|
|
|
|
building.cost = ceili(building.cost / 2.0)
|
|
|
|
|
controls.set_board_state(current_board_state)
|
2026-04-15 12:10:17 -05:00
|
|
|
|
|
|
|
|
|
2026-04-20 13:03:39 -05:00
|
|
|
func handle_board_state_changed() -> void:
|
|
|
|
|
pending_board_state = BoardState.new()
|
|
|
|
|
pending_board_state.day = current_board_state.day
|
2026-04-24 12:19:34 -05:00
|
|
|
pending_board_state.spawn_placements = current_board_state.spawn_placements
|
|
|
|
|
if (
|
|
|
|
|
current_board_state.state != BoardState.State.DRAFT
|
|
|
|
|
and current_board_state.state != BoardState.State.PLACING_SPAWNS
|
|
|
|
|
):
|
|
|
|
|
pending_board_state.turn = current_board_state.turn + 1
|
|
|
|
|
pending_board_state.state = current_board_state.state
|
2026-04-20 13:03:39 -05:00
|
|
|
pending_board_state.players = current_board_state.players
|
|
|
|
|
pending_board_state.players.push_back(pending_board_state.players.pop_front())
|
2026-04-24 12:19:34 -05:00
|
|
|
if (
|
|
|
|
|
pending_board_state.state == BoardState.State.INITIAL_SETUP
|
|
|
|
|
and pending_board_state.turn == -pending_board_state.players.size()
|
|
|
|
|
):
|
|
|
|
|
pending_board_state.players.reverse()
|
2026-04-20 13:03:39 -05:00
|
|
|
pending_board_state.current_player = current_board_state.players[0]
|
|
|
|
|
pending_board_state.tiles = board.tiles.values()
|
|
|
|
|
for building in board.buildings.values():
|
|
|
|
|
if !pending_board_state.buildings.has(building):
|
|
|
|
|
pending_board_state.buildings.append(building)
|
2026-04-24 12:19:34 -05:00
|
|
|
pending_board_state.real_estate_market = current_board_state.real_estate_market
|
2026-04-20 13:03:39 -05:00
|
|
|
controls.check_controls_enabled()
|
2026-04-24 12:19:34 -05:00
|
|
|
controls.set_info()
|
2026-04-20 13:03:39 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_board_state_confirmed() -> void:
|
|
|
|
|
if draft_index_to_be_deleted != -1:
|
2026-04-24 12:19:34 -05:00
|
|
|
pending_board_state.real_estate_market.remove_at(draft_index_to_be_deleted)
|
2026-04-20 13:03:39 -05:00
|
|
|
draft_index_to_be_deleted = -1
|
2026-04-24 12:19:34 -05:00
|
|
|
advance_board_state.rpc(pending_board_state.serialize())
|
2026-04-20 13:03:39 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_pass() -> void:
|
|
|
|
|
current_board_state.players_passed += 1
|
|
|
|
|
current_board_state.turn += 1
|
|
|
|
|
current_board_state.players.push_back(current_board_state.players.pop_front())
|
|
|
|
|
current_board_state.current_player = current_board_state.players[0]
|
2026-04-24 12:19:34 -05:00
|
|
|
advance_board_state.rpc(current_board_state.serialize())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func reset_turn() -> void:
|
|
|
|
|
pending_board_state = null
|
|
|
|
|
update_board_state.rpc(original_board_state.serialize())
|
2026-04-20 13:03:39 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_citizens_finished() -> void:
|
|
|
|
|
for child in board.board_state.get_children():
|
|
|
|
|
if child.get_groups().has("PostTurnActions"):
|
|
|
|
|
child.handle_post_turn_actions()
|
2026-04-24 12:19:34 -05:00
|
|
|
is_playing_day = false
|
2026-04-20 13:03:39 -05:00
|
|
|
controls.reset_turn()
|
|
|
|
|
var winning_player_idx = current_board_state.players.find_custom(
|
|
|
|
|
func(p: Player) -> bool: return p.money >= 100
|
|
|
|
|
)
|
|
|
|
|
if winning_player_idx != -1:
|
|
|
|
|
game_over.set_winning_player(current_board_state.players[winning_player_idx])
|
|
|
|
|
else:
|
|
|
|
|
start_day()
|
2026-04-24 12:19:34 -05:00
|
|
|
ready_for_day_start.rpc()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func tally_votes() -> void:
|
|
|
|
|
var most_votes: int = (
|
|
|
|
|
current_board_state.players.map(func(p: Player) -> int: return p.votes).max()
|
|
|
|
|
)
|
|
|
|
|
var players_with_most_votes: Array[Player] = current_board_state.players.filter(
|
|
|
|
|
func(p: Player) -> bool: return p.votes == most_votes
|
|
|
|
|
)
|
|
|
|
|
if players_with_most_votes.size() == 1:
|
|
|
|
|
players_with_most_votes[0].building_permits += 1
|
2026-04-15 12:10:17 -05:00
|
|
|
|
|
|
|
|
|
2026-04-24 12:19:34 -05:00
|
|
|
func queue_spawn_placement(num_placements: int) -> void:
|
|
|
|
|
current_board_state.spawn_placements += num_placements * current_board_state.players.size()
|
|
|
|
|
current_board_state.state = BoardState.State.PLACING_SPAWNS
|