Adding barracks
This commit is contained in:
@@ -2,7 +2,9 @@ extends Control
|
|||||||
|
|
||||||
const SESSION_PATH := "user://auth_session.json"
|
const SESSION_PATH := "user://auth_session.json"
|
||||||
const LOCATIONS_BASE_URL := "https://tloc.ranaze.com"
|
const LOCATIONS_BASE_URL := "https://tloc.ranaze.com"
|
||||||
|
const AUTH_BASE_URL := "https://tauth.ranaze.com"
|
||||||
const RESOURCE_REFRESH_SECONDS := 15.0
|
const RESOURCE_REFRESH_SECONDS := 15.0
|
||||||
|
const TOKEN_REFRESH_PROMPT_SECONDS := 120.0
|
||||||
const HexGridView := preload("res://scenes/game/hex_grid_view.gd")
|
const HexGridView := preload("res://scenes/game/hex_grid_view.gd")
|
||||||
|
|
||||||
var coordinate_label: Label
|
var coordinate_label: Label
|
||||||
@@ -15,21 +17,29 @@ var units_request: HTTPRequest
|
|||||||
var train_unit_request: HTTPRequest
|
var train_unit_request: HTTPRequest
|
||||||
var claim_request: HTTPRequest
|
var claim_request: HTTPRequest
|
||||||
var build_request: HTTPRequest
|
var build_request: HTTPRequest
|
||||||
|
var refresh_request: HTTPRequest
|
||||||
var resources_timer: Timer
|
var resources_timer: Timer
|
||||||
|
var auth_check_timer: Timer
|
||||||
|
var refresh_dialog: ConfirmationDialog
|
||||||
var session: Dictionary = {}
|
var session: Dictionary = {}
|
||||||
var escape_overlay: Control
|
var escape_overlay: Control
|
||||||
var escape_panel: PanelContainer
|
var escape_panel: PanelContainer
|
||||||
var gold_label: Label
|
var gold_label: Label
|
||||||
var tick_bar: ProgressBar
|
var tick_bar: ProgressBar
|
||||||
var tick_label: Label
|
var tick_label: Label
|
||||||
|
var resource_text := "Gold: --\nWood: --\nOre: --"
|
||||||
var gold_tick_anchor_unix := 0.0
|
var gold_tick_anchor_unix := 0.0
|
||||||
var gold_tick_seconds := 60.0
|
var gold_tick_seconds := 60.0
|
||||||
var train_button: Button
|
var train_button: Button
|
||||||
var unit_label: Label
|
var unit_label: Label
|
||||||
|
var training_box: VBoxContainer
|
||||||
var training_label: Label
|
var training_label: Label
|
||||||
var training_bar: ProgressBar
|
var training_bar: ProgressBar
|
||||||
|
var building_box: VBoxContainer
|
||||||
var building_label: Label
|
var building_label: Label
|
||||||
var building_bar: ProgressBar
|
var building_bar: ProgressBar
|
||||||
|
var progress_panel: PanelContainer
|
||||||
|
var action_buttons: HBoxContainer
|
||||||
var training_started_unix := 0.0
|
var training_started_unix := 0.0
|
||||||
var training_completes_unix := 0.0
|
var training_completes_unix := 0.0
|
||||||
var training_completion_refresh_requested := false
|
var training_completion_refresh_requested := false
|
||||||
@@ -40,19 +50,24 @@ var active_building_label := "Build"
|
|||||||
var claim_button: Button
|
var claim_button: Button
|
||||||
var build_log_camp_button: Button
|
var build_log_camp_button: Button
|
||||||
var build_mine_button: Button
|
var build_mine_button: Button
|
||||||
|
var build_barracks_button: Button
|
||||||
var selected_q := 0
|
var selected_q := 0
|
||||||
var selected_r := 0
|
var selected_r := 0
|
||||||
var selected_can_claim := false
|
var selected_can_claim := false
|
||||||
var selected_can_build_log_camp := false
|
var selected_can_build_log_camp := false
|
||||||
var selected_can_build_mine := false
|
var selected_can_build_mine := false
|
||||||
|
var selected_can_build_barracks := false
|
||||||
var selected_is_home := false
|
var selected_is_home := false
|
||||||
var completed_scout_count := 0
|
var completed_scout_count := 0
|
||||||
|
var refresh_prompt_active := false
|
||||||
|
var refresh_request_active := false
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
session = _load_session()
|
session = _load_session()
|
||||||
_build_ui()
|
_build_ui()
|
||||||
_assign_home_location()
|
_assign_home_location()
|
||||||
|
_start_auth_watch()
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
@@ -100,10 +115,10 @@ func _build_ui() -> void:
|
|||||||
top_margin.add_theme_constant_override("margin_bottom", 12)
|
top_margin.add_theme_constant_override("margin_bottom", 12)
|
||||||
top_bar.add_child(top_margin)
|
top_bar.add_child(top_margin)
|
||||||
|
|
||||||
var top_content := HBoxContainer.new()
|
var title_row := HBoxContainer.new()
|
||||||
top_content.alignment = BoxContainer.ALIGNMENT_CENTER
|
title_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
top_content.add_theme_constant_override("separation", 16)
|
title_row.add_theme_constant_override("separation", 16)
|
||||||
top_margin.add_child(top_content)
|
top_margin.add_child(title_row)
|
||||||
|
|
||||||
var title := Label.new()
|
var title := Label.new()
|
||||||
title.text = "Territory Map"
|
title.text = "Territory Map"
|
||||||
@@ -112,11 +127,11 @@ func _build_ui() -> void:
|
|||||||
title.add_theme_color_override("font_color", Color(0.94, 0.96, 0.9))
|
title.add_theme_color_override("font_color", Color(0.94, 0.96, 0.9))
|
||||||
if title_font:
|
if title_font:
|
||||||
title.add_theme_font_override("font", title_font)
|
title.add_theme_font_override("font", title_font)
|
||||||
top_content.add_child(title)
|
title_row.add_child(title)
|
||||||
|
|
||||||
var legend := HBoxContainer.new()
|
var legend := HBoxContainer.new()
|
||||||
legend.add_theme_constant_override("separation", 12)
|
legend.add_theme_constant_override("separation", 12)
|
||||||
top_content.add_child(legend)
|
title_row.add_child(legend)
|
||||||
|
|
||||||
legend.add_child(_legend_item(Color(1.0, 0.76, 0.16), "Home"))
|
legend.add_child(_legend_item(Color(1.0, 0.76, 0.16), "Home"))
|
||||||
legend.add_child(_legend_item(Color(0.16, 0.86, 0.92), "Yours"))
|
legend.add_child(_legend_item(Color(0.16, 0.86, 0.92), "Yours"))
|
||||||
@@ -124,38 +139,11 @@ func _build_ui() -> void:
|
|||||||
legend.add_child(_legend_item(Color(0.38, 0.56, 0.36), "Open"))
|
legend.add_child(_legend_item(Color(0.38, 0.56, 0.36), "Open"))
|
||||||
legend.add_child(_legend_item(Color(0.045, 0.065, 0.065), "Unknown"))
|
legend.add_child(_legend_item(Color(0.045, 0.065, 0.065), "Unknown"))
|
||||||
|
|
||||||
gold_label = Label.new()
|
|
||||||
gold_label.text = "Gold: --"
|
|
||||||
gold_label.add_theme_font_size_override("font_size", 15)
|
|
||||||
gold_label.add_theme_color_override("font_color", Color(0.95, 0.78, 0.32))
|
|
||||||
top_content.add_child(gold_label)
|
|
||||||
|
|
||||||
var tick_box := VBoxContainer.new()
|
|
||||||
tick_box.custom_minimum_size = Vector2(130, 0)
|
|
||||||
tick_box.add_theme_constant_override("separation", 4)
|
|
||||||
top_content.add_child(tick_box)
|
|
||||||
|
|
||||||
tick_label = Label.new()
|
|
||||||
tick_label.text = "Next: --"
|
|
||||||
tick_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
||||||
tick_label.add_theme_font_size_override("font_size", 12)
|
|
||||||
tick_label.add_theme_color_override("font_color", Color(0.72, 0.82, 0.78))
|
|
||||||
tick_box.add_child(tick_label)
|
|
||||||
|
|
||||||
tick_bar = ProgressBar.new()
|
|
||||||
tick_bar.custom_minimum_size = Vector2(130, 8)
|
|
||||||
tick_bar.max_value = 1.0
|
|
||||||
tick_bar.value = 0.0
|
|
||||||
tick_bar.show_percentage = false
|
|
||||||
tick_bar.add_theme_stylebox_override("background", _progress_style(Color(0.035, 0.055, 0.055), Color(0.18, 0.28, 0.26)))
|
|
||||||
tick_bar.add_theme_stylebox_override("fill", _progress_style(Color(0.82, 0.65, 0.2), Color(0.9, 0.78, 0.36)))
|
|
||||||
tick_box.add_child(tick_bar)
|
|
||||||
|
|
||||||
var user_label := Label.new()
|
var user_label := Label.new()
|
||||||
user_label.text = _session_name()
|
user_label.text = _session_name()
|
||||||
user_label.add_theme_font_size_override("font_size", 15)
|
user_label.add_theme_font_size_override("font_size", 15)
|
||||||
user_label.add_theme_color_override("font_color", Color(0.63, 0.78, 0.75))
|
user_label.add_theme_color_override("font_color", Color(0.63, 0.78, 0.75))
|
||||||
top_content.add_child(user_label)
|
title_row.add_child(user_label)
|
||||||
|
|
||||||
var map_frame := PanelContainer.new()
|
var map_frame := PanelContainer.new()
|
||||||
map_frame.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
map_frame.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
@@ -163,13 +151,20 @@ func _build_ui() -> void:
|
|||||||
map_frame.add_theme_stylebox_override("panel", _panel_style(Color(0.065, 0.09, 0.09, 0.92), Color(0.19, 0.28, 0.27), 1, 0))
|
map_frame.add_theme_stylebox_override("panel", _panel_style(Color(0.065, 0.09, 0.09, 0.92), Color(0.19, 0.28, 0.27), 1, 0))
|
||||||
layout.add_child(map_frame)
|
layout.add_child(map_frame)
|
||||||
|
|
||||||
|
var map_layer := Control.new()
|
||||||
|
map_layer.clip_contents = true
|
||||||
|
map_layer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
map_layer.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
map_frame.add_child(map_layer)
|
||||||
|
|
||||||
grid_view = HexGridView.new()
|
grid_view = HexGridView.new()
|
||||||
grid_view.clip_contents = true
|
grid_view.clip_contents = true
|
||||||
grid_view.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
grid_view.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
grid_view.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
||||||
grid_view.tile_hovered.connect(_on_tile_hovered)
|
grid_view.tile_hovered.connect(_on_tile_hovered)
|
||||||
grid_view.tile_selected.connect(_on_tile_selected)
|
grid_view.tile_selected.connect(_on_tile_selected)
|
||||||
map_frame.add_child(grid_view)
|
map_layer.add_child(grid_view)
|
||||||
|
|
||||||
|
_build_map_overlays(map_layer)
|
||||||
|
|
||||||
var bottom_bar := PanelContainer.new()
|
var bottom_bar := PanelContainer.new()
|
||||||
bottom_bar.custom_minimum_size = Vector2(0, 46)
|
bottom_bar.custom_minimum_size = Vector2(0, 46)
|
||||||
@@ -183,85 +178,56 @@ func _build_ui() -> void:
|
|||||||
bottom_margin.add_theme_constant_override("margin_bottom", 8)
|
bottom_margin.add_theme_constant_override("margin_bottom", 8)
|
||||||
bottom_bar.add_child(bottom_margin)
|
bottom_bar.add_child(bottom_margin)
|
||||||
|
|
||||||
var bottom_content := HBoxContainer.new()
|
var status_row := HBoxContainer.new()
|
||||||
bottom_content.add_theme_constant_override("separation", 16)
|
status_row.add_theme_constant_override("separation", 10)
|
||||||
bottom_margin.add_child(bottom_content)
|
bottom_margin.add_child(status_row)
|
||||||
|
|
||||||
coordinate_label = Label.new()
|
coordinate_label = Label.new()
|
||||||
coordinate_label.text = "Drag to pan. Mouse wheel zooms. Select a tile to inspect territory."
|
coordinate_label.text = "Drag to pan. Mouse wheel zooms. Select a tile to inspect territory."
|
||||||
coordinate_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
coordinate_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
coordinate_label.clip_text = true
|
||||||
|
coordinate_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||||
coordinate_label.add_theme_font_size_override("font_size", 14)
|
coordinate_label.add_theme_font_size_override("font_size", 14)
|
||||||
coordinate_label.add_theme_color_override("font_color", Color(0.72, 0.8, 0.78))
|
coordinate_label.add_theme_color_override("font_color", Color(0.72, 0.8, 0.78))
|
||||||
bottom_content.add_child(coordinate_label)
|
status_row.add_child(coordinate_label)
|
||||||
|
|
||||||
unit_label = Label.new()
|
action_buttons = HBoxContainer.new()
|
||||||
unit_label.text = "Scouts: -- | Queue: --"
|
action_buttons.custom_minimum_size = Vector2(112, 0)
|
||||||
unit_label.custom_minimum_size = Vector2(142, 0)
|
action_buttons.size_flags_horizontal = Control.SIZE_SHRINK_END
|
||||||
unit_label.add_theme_font_size_override("font_size", 14)
|
action_buttons.add_theme_constant_override("separation", 6)
|
||||||
unit_label.add_theme_color_override("font_color", Color(0.72, 0.82, 0.78))
|
status_row.add_child(action_buttons)
|
||||||
bottom_content.add_child(unit_label)
|
|
||||||
|
|
||||||
var training_box := VBoxContainer.new()
|
|
||||||
training_box.custom_minimum_size = Vector2(150, 0)
|
|
||||||
training_box.add_theme_constant_override("separation", 4)
|
|
||||||
bottom_content.add_child(training_box)
|
|
||||||
|
|
||||||
training_label = Label.new()
|
|
||||||
training_label.text = "Training: idle"
|
|
||||||
training_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
||||||
training_label.add_theme_font_size_override("font_size", 12)
|
|
||||||
training_label.add_theme_color_override("font_color", Color(0.72, 0.82, 0.78))
|
|
||||||
training_box.add_child(training_label)
|
|
||||||
|
|
||||||
training_bar = ProgressBar.new()
|
|
||||||
training_bar.custom_minimum_size = Vector2(150, 8)
|
|
||||||
training_bar.max_value = 1.0
|
|
||||||
training_bar.value = 0.0
|
|
||||||
training_bar.show_percentage = false
|
|
||||||
training_bar.add_theme_stylebox_override("background", _progress_style(Color(0.035, 0.055, 0.055), Color(0.18, 0.28, 0.26)))
|
|
||||||
training_bar.add_theme_stylebox_override("fill", _progress_style(Color(0.28, 0.62, 0.72), Color(0.43, 0.82, 0.92)))
|
|
||||||
training_box.add_child(training_bar)
|
|
||||||
|
|
||||||
var building_box := VBoxContainer.new()
|
|
||||||
building_box.custom_minimum_size = Vector2(150, 0)
|
|
||||||
building_box.add_theme_constant_override("separation", 4)
|
|
||||||
bottom_content.add_child(building_box)
|
|
||||||
|
|
||||||
building_label = Label.new()
|
|
||||||
building_label.text = "Build: idle"
|
|
||||||
building_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
||||||
building_label.add_theme_font_size_override("font_size", 12)
|
|
||||||
building_label.add_theme_color_override("font_color", Color(0.72, 0.82, 0.78))
|
|
||||||
building_box.add_child(building_label)
|
|
||||||
|
|
||||||
building_bar = ProgressBar.new()
|
|
||||||
building_bar.custom_minimum_size = Vector2(150, 8)
|
|
||||||
building_bar.max_value = 1.0
|
|
||||||
building_bar.value = 0.0
|
|
||||||
building_bar.show_percentage = false
|
|
||||||
building_bar.add_theme_stylebox_override("background", _progress_style(Color(0.035, 0.055, 0.055), Color(0.18, 0.28, 0.26)))
|
|
||||||
building_bar.add_theme_stylebox_override("fill", _progress_style(Color(0.62, 0.42, 0.22), Color(0.86, 0.62, 0.34)))
|
|
||||||
building_box.add_child(building_bar)
|
|
||||||
|
|
||||||
train_button = _small_button("Train Scout 50g")
|
train_button = _small_button("Train Scout 50g")
|
||||||
train_button.visible = false
|
train_button.visible = false
|
||||||
train_button.pressed.connect(_train_scout)
|
train_button.pressed.connect(_train_scout)
|
||||||
bottom_content.add_child(train_button)
|
action_buttons.add_child(train_button)
|
||||||
|
|
||||||
claim_button = _small_button("Claim 1 Scout")
|
claim_button = _small_button("Claim 1 Scout")
|
||||||
claim_button.disabled = true
|
claim_button.visible = false
|
||||||
claim_button.pressed.connect(_claim_selected_tile)
|
claim_button.pressed.connect(_claim_selected_tile)
|
||||||
bottom_content.add_child(claim_button)
|
action_buttons.add_child(claim_button)
|
||||||
|
|
||||||
build_log_camp_button = _small_button("Build Log Camp")
|
build_log_camp_button = _small_button("Build Log Camp")
|
||||||
build_log_camp_button.disabled = true
|
build_log_camp_button.visible = false
|
||||||
build_log_camp_button.pressed.connect(_build_log_camp)
|
build_log_camp_button.pressed.connect(_build_log_camp)
|
||||||
bottom_content.add_child(build_log_camp_button)
|
action_buttons.add_child(build_log_camp_button)
|
||||||
|
|
||||||
build_mine_button = _small_button("Build Mine")
|
build_mine_button = _small_button("Build Mine")
|
||||||
build_mine_button.disabled = true
|
build_mine_button.visible = false
|
||||||
build_mine_button.pressed.connect(_build_mine)
|
build_mine_button.pressed.connect(_build_mine)
|
||||||
bottom_content.add_child(build_mine_button)
|
action_buttons.add_child(build_mine_button)
|
||||||
|
|
||||||
|
build_barracks_button = _small_button("Build Barracks")
|
||||||
|
build_barracks_button.visible = false
|
||||||
|
build_barracks_button.pressed.connect(_build_barracks)
|
||||||
|
action_buttons.add_child(build_barracks_button)
|
||||||
|
|
||||||
|
unit_label = Label.new()
|
||||||
|
unit_label.text = "Scouts: -- | Queue: --"
|
||||||
|
unit_label.custom_minimum_size = Vector2(126, 0)
|
||||||
|
unit_label.add_theme_font_size_override("font_size", 14)
|
||||||
|
unit_label.add_theme_color_override("font_color", Color(0.72, 0.82, 0.78))
|
||||||
|
status_row.add_child(unit_label)
|
||||||
|
|
||||||
http_request = HTTPRequest.new()
|
http_request = HTTPRequest.new()
|
||||||
http_request.request_completed.connect(_on_home_request_completed)
|
http_request.request_completed.connect(_on_home_request_completed)
|
||||||
@@ -291,15 +257,144 @@ func _build_ui() -> void:
|
|||||||
build_request.request_completed.connect(_on_build_request_completed)
|
build_request.request_completed.connect(_on_build_request_completed)
|
||||||
add_child(build_request)
|
add_child(build_request)
|
||||||
|
|
||||||
|
refresh_request = HTTPRequest.new()
|
||||||
|
refresh_request.request_completed.connect(_on_refresh_request_completed)
|
||||||
|
add_child(refresh_request)
|
||||||
|
|
||||||
|
auth_check_timer = Timer.new()
|
||||||
|
auth_check_timer.wait_time = 15.0
|
||||||
|
auth_check_timer.autostart = false
|
||||||
|
auth_check_timer.timeout.connect(_check_auth_expiry)
|
||||||
|
add_child(auth_check_timer)
|
||||||
|
|
||||||
resources_timer = Timer.new()
|
resources_timer = Timer.new()
|
||||||
resources_timer.wait_time = RESOURCE_REFRESH_SECONDS
|
resources_timer.wait_time = RESOURCE_REFRESH_SECONDS
|
||||||
resources_timer.autostart = false
|
resources_timer.autostart = false
|
||||||
resources_timer.timeout.connect(_refresh_player_state)
|
resources_timer.timeout.connect(_refresh_player_state)
|
||||||
add_child(resources_timer)
|
add_child(resources_timer)
|
||||||
|
|
||||||
|
_build_refresh_dialog()
|
||||||
_build_escape_menu()
|
_build_escape_menu()
|
||||||
|
|
||||||
|
|
||||||
|
func _build_map_overlays(map_layer: Control) -> void:
|
||||||
|
var resource_panel := PanelContainer.new()
|
||||||
|
resource_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
resource_panel.anchor_left = 1.0
|
||||||
|
resource_panel.anchor_right = 1.0
|
||||||
|
resource_panel.anchor_top = 0.0
|
||||||
|
resource_panel.anchor_bottom = 0.0
|
||||||
|
resource_panel.offset_left = -230
|
||||||
|
resource_panel.offset_right = -16
|
||||||
|
resource_panel.offset_top = 16
|
||||||
|
resource_panel.offset_bottom = 128
|
||||||
|
resource_panel.add_theme_stylebox_override("panel", _panel_style(Color(0.045, 0.07, 0.07, 0.78), Color(0.18, 0.28, 0.26), 1, 4))
|
||||||
|
map_layer.add_child(resource_panel)
|
||||||
|
|
||||||
|
var resource_margin := MarginContainer.new()
|
||||||
|
resource_margin.add_theme_constant_override("margin_left", 12)
|
||||||
|
resource_margin.add_theme_constant_override("margin_right", 12)
|
||||||
|
resource_margin.add_theme_constant_override("margin_top", 10)
|
||||||
|
resource_margin.add_theme_constant_override("margin_bottom", 10)
|
||||||
|
resource_panel.add_child(resource_margin)
|
||||||
|
|
||||||
|
var resource_stack := VBoxContainer.new()
|
||||||
|
resource_stack.add_theme_constant_override("separation", 6)
|
||||||
|
resource_margin.add_child(resource_stack)
|
||||||
|
|
||||||
|
gold_label = Label.new()
|
||||||
|
gold_label.text = resource_text
|
||||||
|
gold_label.add_theme_font_size_override("font_size", 14)
|
||||||
|
gold_label.add_theme_color_override("font_color", Color(0.95, 0.78, 0.32))
|
||||||
|
resource_stack.add_child(gold_label)
|
||||||
|
|
||||||
|
tick_label = Label.new()
|
||||||
|
tick_label.text = "Next: --"
|
||||||
|
tick_label.add_theme_font_size_override("font_size", 12)
|
||||||
|
tick_label.add_theme_color_override("font_color", Color(0.72, 0.82, 0.78))
|
||||||
|
resource_stack.add_child(tick_label)
|
||||||
|
|
||||||
|
tick_bar = ProgressBar.new()
|
||||||
|
tick_bar.custom_minimum_size = Vector2(180, 8)
|
||||||
|
tick_bar.max_value = 1.0
|
||||||
|
tick_bar.value = 0.0
|
||||||
|
tick_bar.show_percentage = false
|
||||||
|
tick_bar.add_theme_stylebox_override("background", _progress_style(Color(0.035, 0.055, 0.055), Color(0.18, 0.28, 0.26)))
|
||||||
|
tick_bar.add_theme_stylebox_override("fill", _progress_style(Color(0.82, 0.65, 0.2), Color(0.9, 0.78, 0.36)))
|
||||||
|
resource_stack.add_child(tick_bar)
|
||||||
|
|
||||||
|
progress_panel = PanelContainer.new()
|
||||||
|
progress_panel.visible = false
|
||||||
|
progress_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
progress_panel.anchor_left = 0.0
|
||||||
|
progress_panel.anchor_right = 0.0
|
||||||
|
progress_panel.anchor_top = 0.0
|
||||||
|
progress_panel.anchor_bottom = 0.0
|
||||||
|
progress_panel.offset_left = 16
|
||||||
|
progress_panel.offset_right = 220
|
||||||
|
progress_panel.offset_top = 16
|
||||||
|
progress_panel.offset_bottom = 130
|
||||||
|
progress_panel.add_theme_stylebox_override("panel", _panel_style(Color(0.045, 0.07, 0.07, 0.78), Color(0.18, 0.28, 0.26), 1, 4))
|
||||||
|
map_layer.add_child(progress_panel)
|
||||||
|
|
||||||
|
var progress_margin := MarginContainer.new()
|
||||||
|
progress_margin.add_theme_constant_override("margin_left", 12)
|
||||||
|
progress_margin.add_theme_constant_override("margin_right", 12)
|
||||||
|
progress_margin.add_theme_constant_override("margin_top", 10)
|
||||||
|
progress_margin.add_theme_constant_override("margin_bottom", 10)
|
||||||
|
progress_panel.add_child(progress_margin)
|
||||||
|
|
||||||
|
var progress_stack := VBoxContainer.new()
|
||||||
|
progress_stack.add_theme_constant_override("separation", 8)
|
||||||
|
progress_margin.add_child(progress_stack)
|
||||||
|
|
||||||
|
training_box = _progress_overlay_item("Training")
|
||||||
|
progress_stack.add_child(training_box)
|
||||||
|
training_label = training_box.get_child(0) as Label
|
||||||
|
training_bar = training_box.get_child(1) as ProgressBar
|
||||||
|
|
||||||
|
building_box = _progress_overlay_item("Build")
|
||||||
|
progress_stack.add_child(building_box)
|
||||||
|
building_label = building_box.get_child(0) as Label
|
||||||
|
building_bar = building_box.get_child(1) as ProgressBar
|
||||||
|
building_bar.add_theme_stylebox_override("fill", _progress_style(Color(0.62, 0.42, 0.22), Color(0.86, 0.62, 0.34)))
|
||||||
|
|
||||||
|
|
||||||
|
func _progress_overlay_item(label_text: String) -> VBoxContainer:
|
||||||
|
var box := VBoxContainer.new()
|
||||||
|
box.visible = false
|
||||||
|
box.custom_minimum_size = Vector2(180, 0)
|
||||||
|
box.add_theme_constant_override("separation", 4)
|
||||||
|
|
||||||
|
var label := Label.new()
|
||||||
|
label.text = label_text
|
||||||
|
label.add_theme_font_size_override("font_size", 12)
|
||||||
|
label.add_theme_color_override("font_color", Color(0.72, 0.82, 0.78))
|
||||||
|
box.add_child(label)
|
||||||
|
|
||||||
|
var bar := ProgressBar.new()
|
||||||
|
bar.custom_minimum_size = Vector2(180, 8)
|
||||||
|
bar.max_value = 1.0
|
||||||
|
bar.value = 0.0
|
||||||
|
bar.show_percentage = false
|
||||||
|
bar.add_theme_stylebox_override("background", _progress_style(Color(0.035, 0.055, 0.055), Color(0.18, 0.28, 0.26)))
|
||||||
|
bar.add_theme_stylebox_override("fill", _progress_style(Color(0.28, 0.62, 0.72), Color(0.43, 0.82, 0.92)))
|
||||||
|
box.add_child(bar)
|
||||||
|
|
||||||
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
func _build_refresh_dialog() -> void:
|
||||||
|
refresh_dialog = ConfirmationDialog.new()
|
||||||
|
refresh_dialog.title = "Session expiring"
|
||||||
|
refresh_dialog.dialog_text = "Your session is about to expire. Refresh it now?"
|
||||||
|
refresh_dialog.ok_button_text = "Refresh"
|
||||||
|
refresh_dialog.cancel_button_text = "Log Out"
|
||||||
|
refresh_dialog.confirmed.connect(_refresh_auth_token)
|
||||||
|
refresh_dialog.canceled.connect(_logout)
|
||||||
|
add_child(refresh_dialog)
|
||||||
|
|
||||||
|
|
||||||
func _on_tile_hovered(q: int, r: int, terrain: String) -> void:
|
func _on_tile_hovered(q: int, r: int, terrain: String) -> void:
|
||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
coordinate_label.text = "Hex q:%d r:%d | %s" % [q, r, terrain]
|
coordinate_label.text = "Hex q:%d r:%d | %s" % [q, r, terrain]
|
||||||
@@ -312,14 +407,8 @@ func _on_tile_selected(q: int, r: int, terrain: String) -> void:
|
|||||||
selected_is_home = _selected_tile_is_home()
|
selected_is_home = _selected_tile_is_home()
|
||||||
selected_can_build_log_camp = _can_build_log_camp_on_selected_tile()
|
selected_can_build_log_camp = _can_build_log_camp_on_selected_tile()
|
||||||
selected_can_build_mine = _can_build_mine_on_selected_tile()
|
selected_can_build_mine = _can_build_mine_on_selected_tile()
|
||||||
if train_button:
|
selected_can_build_barracks = _can_build_barracks_on_selected_tile()
|
||||||
train_button.visible = selected_is_home
|
_update_action_buttons()
|
||||||
if claim_button:
|
|
||||||
claim_button.disabled = not _can_claim_selected_tile()
|
|
||||||
if build_log_camp_button:
|
|
||||||
build_log_camp_button.disabled = not selected_can_build_log_camp
|
|
||||||
if build_mine_button:
|
|
||||||
build_mine_button.disabled = not selected_can_build_mine
|
|
||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
coordinate_label.text = "Selected q:%d r:%d | %s territory" % [q, r, terrain]
|
coordinate_label.text = "Selected q:%d r:%d | %s territory" % [q, r, terrain]
|
||||||
|
|
||||||
@@ -332,6 +421,10 @@ func _can_build_mine_on_selected_tile() -> bool:
|
|||||||
return _can_build_on_selected_tile("Ore")
|
return _can_build_on_selected_tile("Ore")
|
||||||
|
|
||||||
|
|
||||||
|
func _can_build_barracks_on_selected_tile() -> bool:
|
||||||
|
return _can_build_on_selected_tile("")
|
||||||
|
|
||||||
|
|
||||||
func _can_build_on_selected_tile(required_terrain: String) -> bool:
|
func _can_build_on_selected_tile(required_terrain: String) -> bool:
|
||||||
if not grid_view:
|
if not grid_view:
|
||||||
return false
|
return false
|
||||||
@@ -343,13 +436,31 @@ func _can_build_on_selected_tile(required_terrain: String) -> bool:
|
|||||||
var username: String = str(session.get("username", ""))
|
var username: String = str(session.get("username", ""))
|
||||||
var owner_username: String = str(location.get("ownerUsername", ""))
|
var owner_username: String = str(location.get("ownerUsername", ""))
|
||||||
var terrain: String = str(location.get("terrain", ""))
|
var terrain: String = str(location.get("terrain", ""))
|
||||||
var building_type: String = str(location.get("buildingType", ""))
|
var building_type: String = _nullable_string(location.get("buildingType", ""))
|
||||||
|
|
||||||
return owner_username == username and terrain == required_terrain and building_type.is_empty()
|
return owner_username == username and building_type.is_empty() and (required_terrain.is_empty() or terrain == required_terrain)
|
||||||
|
|
||||||
|
|
||||||
func _can_claim_selected_tile() -> bool:
|
func _can_claim_selected_tile() -> bool:
|
||||||
return selected_can_claim and completed_scout_count > 0
|
return selected_can_claim and completed_scout_count > 0 and grid_view and grid_view.has_owned_neighbor(selected_q, selected_r)
|
||||||
|
|
||||||
|
|
||||||
|
func _update_action_buttons() -> void:
|
||||||
|
if train_button:
|
||||||
|
train_button.visible = selected_is_home
|
||||||
|
train_button.disabled = false
|
||||||
|
if claim_button:
|
||||||
|
claim_button.visible = _can_claim_selected_tile()
|
||||||
|
claim_button.disabled = false
|
||||||
|
if build_log_camp_button:
|
||||||
|
build_log_camp_button.visible = selected_can_build_log_camp
|
||||||
|
build_log_camp_button.disabled = false
|
||||||
|
if build_mine_button:
|
||||||
|
build_mine_button.visible = selected_can_build_mine
|
||||||
|
build_mine_button.disabled = false
|
||||||
|
if build_barracks_button:
|
||||||
|
build_barracks_button.visible = selected_can_build_barracks
|
||||||
|
build_barracks_button.disabled = false
|
||||||
|
|
||||||
|
|
||||||
func _selected_tile_is_home() -> bool:
|
func _selected_tile_is_home() -> bool:
|
||||||
@@ -391,6 +502,97 @@ func _load_session() -> Dictionary:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
func _start_auth_watch() -> void:
|
||||||
|
_check_auth_expiry()
|
||||||
|
if auth_check_timer:
|
||||||
|
auth_check_timer.start()
|
||||||
|
|
||||||
|
|
||||||
|
func _check_auth_expiry() -> void:
|
||||||
|
if session.is_empty() or refresh_prompt_active or refresh_request_active:
|
||||||
|
return
|
||||||
|
|
||||||
|
var expires_unix := _parse_utc_timestamp(str(session.get("exp", "")))
|
||||||
|
if expires_unix <= 0.0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var seconds_remaining := expires_unix - Time.get_unix_time_from_system()
|
||||||
|
if seconds_remaining <= 0.0:
|
||||||
|
_logout_deferred()
|
||||||
|
return
|
||||||
|
|
||||||
|
if seconds_remaining <= TOKEN_REFRESH_PROMPT_SECONDS:
|
||||||
|
_prompt_refresh_or_logout("Your session expires in %d seconds. Refresh it now?" % int(ceil(seconds_remaining)))
|
||||||
|
|
||||||
|
|
||||||
|
func _prompt_refresh_or_logout(message: String) -> void:
|
||||||
|
if refresh_prompt_active or refresh_request_active:
|
||||||
|
return
|
||||||
|
|
||||||
|
refresh_prompt_active = true
|
||||||
|
if refresh_dialog:
|
||||||
|
refresh_dialog.dialog_text = message
|
||||||
|
refresh_dialog.popup_centered()
|
||||||
|
|
||||||
|
|
||||||
|
func _refresh_auth_token() -> void:
|
||||||
|
refresh_prompt_active = false
|
||||||
|
var username := str(session.get("username", ""))
|
||||||
|
var refresh_token := str(session.get("refreshToken", ""))
|
||||||
|
if username.is_empty() or refresh_token.is_empty():
|
||||||
|
_logout()
|
||||||
|
return
|
||||||
|
|
||||||
|
refresh_request_active = true
|
||||||
|
var error: int = refresh_request.request(
|
||||||
|
AUTH_BASE_URL + "/api/auth/refresh",
|
||||||
|
PackedStringArray(["Content-Type: application/json"]),
|
||||||
|
HTTPClient.METHOD_POST,
|
||||||
|
JSON.stringify({
|
||||||
|
"username": username,
|
||||||
|
"refreshToken": refresh_token
|
||||||
|
})
|
||||||
|
)
|
||||||
|
if error != OK:
|
||||||
|
refresh_request_active = false
|
||||||
|
_logout()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_refresh_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||||
|
refresh_request_active = false
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
||||||
|
_logout()
|
||||||
|
return
|
||||||
|
|
||||||
|
var parsed = JSON.parse_string(body.get_string_from_utf8())
|
||||||
|
if typeof(parsed) != TYPE_DICTIONARY:
|
||||||
|
_logout()
|
||||||
|
return
|
||||||
|
|
||||||
|
var refreshed: Dictionary = parsed
|
||||||
|
session["accessToken"] = refreshed.get("accessToken", session.get("accessToken", ""))
|
||||||
|
session["exp"] = refreshed.get("exp", session.get("exp", ""))
|
||||||
|
get_tree().set_meta("auth_session", session)
|
||||||
|
_save_session_if_present(session)
|
||||||
|
coordinate_label.text = "Session refreshed."
|
||||||
|
_check_auth_expiry()
|
||||||
|
|
||||||
|
|
||||||
|
func _save_session(next_session: Dictionary) -> void:
|
||||||
|
var file := FileAccess.open(SESSION_PATH, FileAccess.WRITE)
|
||||||
|
if file:
|
||||||
|
file.store_string(JSON.stringify(next_session))
|
||||||
|
|
||||||
|
|
||||||
|
func _save_session_if_present(next_session: Dictionary) -> void:
|
||||||
|
if FileAccess.file_exists(SESSION_PATH):
|
||||||
|
_save_session(next_session)
|
||||||
|
|
||||||
|
|
||||||
|
func _logout_deferred() -> void:
|
||||||
|
call_deferred("_logout")
|
||||||
|
|
||||||
|
|
||||||
func _assign_home_location() -> void:
|
func _assign_home_location() -> void:
|
||||||
var access_token: String = str(session.get("accessToken", ""))
|
var access_token: String = str(session.get("accessToken", ""))
|
||||||
if access_token.is_empty():
|
if access_token.is_empty():
|
||||||
@@ -484,7 +686,8 @@ func _refresh_player_state() -> void:
|
|||||||
func _load_resources() -> void:
|
func _load_resources() -> void:
|
||||||
var access_token: String = str(session.get("accessToken", ""))
|
var access_token: String = str(session.get("accessToken", ""))
|
||||||
if access_token.is_empty():
|
if access_token.is_empty():
|
||||||
gold_label.text = "Gold: --"
|
resource_text = "Gold: --\nWood: --\nOre: --"
|
||||||
|
gold_label.text = resource_text
|
||||||
return
|
return
|
||||||
|
|
||||||
if resources_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
if resources_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
||||||
@@ -498,17 +701,20 @@ func _load_resources() -> void:
|
|||||||
HTTPClient.METHOD_GET
|
HTTPClient.METHOD_GET
|
||||||
)
|
)
|
||||||
if error != OK:
|
if error != OK:
|
||||||
gold_label.text = "Gold: ?"
|
coordinate_label.text = "Resource request could not start. Error: %d" % error
|
||||||
|
|
||||||
|
|
||||||
func _on_resources_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
func _on_resources_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||||
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
||||||
gold_label.text = "Gold: ?"
|
if response_code == 401:
|
||||||
|
_prompt_refresh_or_logout("Your session expired. Refresh it now?")
|
||||||
|
return
|
||||||
|
coordinate_label.text = "Resource request failed. result=%d status=%d body=%s" % [result, response_code, body.get_string_from_utf8()]
|
||||||
return
|
return
|
||||||
|
|
||||||
var parsed = JSON.parse_string(body.get_string_from_utf8())
|
var parsed = JSON.parse_string(body.get_string_from_utf8())
|
||||||
if typeof(parsed) != TYPE_DICTIONARY:
|
if typeof(parsed) != TYPE_DICTIONARY:
|
||||||
gold_label.text = "Gold: ?"
|
coordinate_label.text = "Resource service returned invalid JSON."
|
||||||
return
|
return
|
||||||
|
|
||||||
var resources: Dictionary = parsed
|
var resources: Dictionary = parsed
|
||||||
@@ -518,7 +724,8 @@ func _on_resources_request_completed(result: int, response_code: int, _headers:
|
|||||||
var wood_per_minute: int = int(resources.get("woodPerMinute", 0))
|
var wood_per_minute: int = int(resources.get("woodPerMinute", 0))
|
||||||
var ore: int = int(resources.get("ore", 0))
|
var ore: int = int(resources.get("ore", 0))
|
||||||
var ore_per_minute: int = int(resources.get("orePerMinute", 0))
|
var ore_per_minute: int = int(resources.get("orePerMinute", 0))
|
||||||
gold_label.text = "Gold: %d (+%d/min) | Wood: %d (+%d/min) | Ore: %d (+%d/min)" % [gold, gold_per_minute, wood, wood_per_minute, ore, ore_per_minute]
|
resource_text = "Gold: %d (+%d/min)\nWood: %d (+%d/min)\nOre: %d (+%d/min)" % [gold, gold_per_minute, wood, wood_per_minute, ore, ore_per_minute]
|
||||||
|
gold_label.text = resource_text
|
||||||
|
|
||||||
var anchor_text: String = str(resources.get("lastGoldGeneratedAtUtc", ""))
|
var anchor_text: String = str(resources.get("lastGoldGeneratedAtUtc", ""))
|
||||||
gold_tick_anchor_unix = _parse_utc_timestamp(anchor_text)
|
gold_tick_anchor_unix = _parse_utc_timestamp(anchor_text)
|
||||||
@@ -565,8 +772,7 @@ func _on_units_request_completed(result: int, response_code: int, _headers: Pack
|
|||||||
|
|
||||||
completed_scout_count = int(units.get("completedScouts", 0))
|
completed_scout_count = int(units.get("completedScouts", 0))
|
||||||
unit_label.text = "Scouts: %d | Queue: %d" % [completed_scout_count, training.size()]
|
unit_label.text = "Scouts: %d | Queue: %d" % [completed_scout_count, training.size()]
|
||||||
if claim_button:
|
_update_action_buttons()
|
||||||
claim_button.disabled = not _can_claim_selected_tile()
|
|
||||||
|
|
||||||
if training.is_empty():
|
if training.is_empty():
|
||||||
training_started_unix = 0.0
|
training_started_unix = 0.0
|
||||||
@@ -640,6 +846,9 @@ func _claim_selected_tile() -> void:
|
|||||||
if completed_scout_count <= 0:
|
if completed_scout_count <= 0:
|
||||||
coordinate_label.text = "Train a scout before claiming territory."
|
coordinate_label.text = "Train a scout before claiming territory."
|
||||||
return
|
return
|
||||||
|
if not grid_view.has_owned_neighbor(selected_q, selected_r):
|
||||||
|
coordinate_label.text = "Claims must be adjacent to controlled territory."
|
||||||
|
return
|
||||||
|
|
||||||
var access_token: String = str(session.get("accessToken", ""))
|
var access_token: String = str(session.get("accessToken", ""))
|
||||||
if access_token.is_empty():
|
if access_token.is_empty():
|
||||||
@@ -661,7 +870,7 @@ func _claim_selected_tile() -> void:
|
|||||||
JSON.stringify({ "q": selected_q, "r": selected_r })
|
JSON.stringify({ "q": selected_q, "r": selected_r })
|
||||||
)
|
)
|
||||||
if error != OK:
|
if error != OK:
|
||||||
claim_button.disabled = false
|
_update_action_buttons()
|
||||||
coordinate_label.text = "Could not contact the locations service."
|
coordinate_label.text = "Could not contact the locations service."
|
||||||
|
|
||||||
|
|
||||||
@@ -669,17 +878,17 @@ func _on_claim_request_completed(result: int, response_code: int, _headers: Pack
|
|||||||
var text: String = body.get_string_from_utf8()
|
var text: String = body.get_string_from_utf8()
|
||||||
if result != HTTPRequest.RESULT_SUCCESS:
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
coordinate_label.text = "Scout claim request failed."
|
coordinate_label.text = "Scout claim request failed."
|
||||||
claim_button.disabled = not selected_can_claim
|
_update_action_buttons()
|
||||||
return
|
return
|
||||||
|
|
||||||
if response_code < 200 or response_code >= 300:
|
if response_code < 200 or response_code >= 300:
|
||||||
coordinate_label.text = "Scout claim failed: %s" % text
|
coordinate_label.text = "Scout claim failed: %s" % text
|
||||||
claim_button.disabled = not selected_can_claim
|
_update_action_buttons()
|
||||||
_refresh_player_state()
|
_refresh_player_state()
|
||||||
return
|
return
|
||||||
|
|
||||||
selected_can_claim = false
|
selected_can_claim = false
|
||||||
claim_button.disabled = true
|
_update_action_buttons()
|
||||||
coordinate_label.text = "Scout claimed q:%d r:%d. Territory expanded." % [selected_q, selected_r]
|
coordinate_label.text = "Scout claimed q:%d r:%d. Territory expanded." % [selected_q, selected_r]
|
||||||
_load_visible_locations()
|
_load_visible_locations()
|
||||||
_refresh_player_state()
|
_refresh_player_state()
|
||||||
@@ -701,6 +910,14 @@ func _build_mine() -> void:
|
|||||||
_request_building("Mine")
|
_request_building("Mine")
|
||||||
|
|
||||||
|
|
||||||
|
func _build_barracks() -> void:
|
||||||
|
if not selected_can_build_barracks:
|
||||||
|
coordinate_label.text = "Select controlled territory without a building."
|
||||||
|
return
|
||||||
|
|
||||||
|
_request_building("Barracks")
|
||||||
|
|
||||||
|
|
||||||
func _request_building(building_type: String) -> void:
|
func _request_building(building_type: String) -> void:
|
||||||
var access_token: String = str(session.get("accessToken", ""))
|
var access_token: String = str(session.get("accessToken", ""))
|
||||||
if access_token.is_empty():
|
if access_token.is_empty():
|
||||||
@@ -710,9 +927,12 @@ func _request_building(building_type: String) -> void:
|
|||||||
if build_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
if build_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
||||||
return
|
return
|
||||||
|
|
||||||
if building_type == "Mine":
|
match building_type:
|
||||||
|
"Mine":
|
||||||
build_mine_button.disabled = true
|
build_mine_button.disabled = true
|
||||||
else:
|
"Barracks":
|
||||||
|
build_barracks_button.disabled = true
|
||||||
|
_:
|
||||||
build_log_camp_button.disabled = true
|
build_log_camp_button.disabled = true
|
||||||
|
|
||||||
coordinate_label.text = "Building %s at q:%d r:%d..." % [building_type, selected_q, selected_r]
|
coordinate_label.text = "Building %s at q:%d r:%d..." % [building_type, selected_q, selected_r]
|
||||||
@@ -726,8 +946,7 @@ func _request_building(building_type: String) -> void:
|
|||||||
JSON.stringify({ "q": selected_q, "r": selected_r, "buildingType": building_type })
|
JSON.stringify({ "q": selected_q, "r": selected_r, "buildingType": building_type })
|
||||||
)
|
)
|
||||||
if error != OK:
|
if error != OK:
|
||||||
build_log_camp_button.disabled = not selected_can_build_log_camp
|
_update_action_buttons()
|
||||||
build_mine_button.disabled = not selected_can_build_mine
|
|
||||||
coordinate_label.text = "Could not contact the locations service."
|
coordinate_label.text = "Could not contact the locations service."
|
||||||
|
|
||||||
|
|
||||||
@@ -735,25 +954,23 @@ func _on_build_request_completed(result: int, response_code: int, _headers: Pack
|
|||||||
var text: String = body.get_string_from_utf8()
|
var text: String = body.get_string_from_utf8()
|
||||||
if result != HTTPRequest.RESULT_SUCCESS:
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
coordinate_label.text = "Build request failed."
|
coordinate_label.text = "Build request failed."
|
||||||
build_log_camp_button.disabled = not selected_can_build_log_camp
|
_update_action_buttons()
|
||||||
build_mine_button.disabled = not selected_can_build_mine
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if response_code < 200 or response_code >= 300:
|
if response_code < 200 or response_code >= 300:
|
||||||
coordinate_label.text = "Build failed: %s" % text
|
coordinate_label.text = "Build failed: %s" % text
|
||||||
build_log_camp_button.disabled = not selected_can_build_log_camp
|
_update_action_buttons()
|
||||||
build_mine_button.disabled = not selected_can_build_mine
|
|
||||||
_refresh_player_state()
|
_refresh_player_state()
|
||||||
return
|
return
|
||||||
|
|
||||||
selected_can_build_log_camp = false
|
selected_can_build_log_camp = false
|
||||||
selected_can_build_mine = false
|
selected_can_build_mine = false
|
||||||
build_log_camp_button.disabled = true
|
selected_can_build_barracks = false
|
||||||
build_mine_button.disabled = true
|
_update_action_buttons()
|
||||||
var parsed = JSON.parse_string(text)
|
var parsed = JSON.parse_string(text)
|
||||||
if typeof(parsed) == TYPE_DICTIONARY:
|
if typeof(parsed) == TYPE_DICTIONARY:
|
||||||
var location: Dictionary = parsed
|
var location: Dictionary = parsed
|
||||||
active_building_label = _building_display_name(str(location.get("buildingType", "Building")))
|
active_building_label = _building_display_name(_nullable_string(location.get("buildingType", "Building")))
|
||||||
building_started_unix = _parse_utc_timestamp(str(location.get("buildingStartedAtUtc", "")))
|
building_started_unix = _parse_utc_timestamp(str(location.get("buildingStartedAtUtc", "")))
|
||||||
building_completes_unix = _parse_utc_timestamp(str(location.get("buildingCompletesAtUtc", "")))
|
building_completes_unix = _parse_utc_timestamp(str(location.get("buildingCompletesAtUtc", "")))
|
||||||
building_completion_refresh_requested = false
|
building_completion_refresh_requested = false
|
||||||
@@ -787,10 +1004,13 @@ func _update_training_bar() -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if training_started_unix <= 0.0 or training_completes_unix <= 0.0:
|
if training_started_unix <= 0.0 or training_completes_unix <= 0.0:
|
||||||
|
training_box.visible = false
|
||||||
training_bar.value = 0.0
|
training_bar.value = 0.0
|
||||||
training_label.text = "Training: idle"
|
training_label.text = "Training: idle"
|
||||||
|
_update_progress_panel_visibility()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
training_box.visible = true
|
||||||
var now: float = Time.get_unix_time_from_system()
|
var now: float = Time.get_unix_time_from_system()
|
||||||
var duration: float = max(1.0, training_completes_unix - training_started_unix)
|
var duration: float = max(1.0, training_completes_unix - training_started_unix)
|
||||||
var elapsed: float = clamp(now - training_started_unix, 0.0, duration)
|
var elapsed: float = clamp(now - training_started_unix, 0.0, duration)
|
||||||
@@ -804,6 +1024,7 @@ func _update_training_bar() -> void:
|
|||||||
_load_units()
|
_load_units()
|
||||||
else:
|
else:
|
||||||
training_label.text = "Training: %ds" % remaining
|
training_label.text = "Training: %ds" % remaining
|
||||||
|
_update_progress_panel_visibility()
|
||||||
|
|
||||||
|
|
||||||
func _update_building_timer() -> void:
|
func _update_building_timer() -> void:
|
||||||
@@ -811,10 +1032,13 @@ func _update_building_timer() -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if building_started_unix <= 0.0 or building_completes_unix <= 0.0:
|
if building_started_unix <= 0.0 or building_completes_unix <= 0.0:
|
||||||
|
building_box.visible = false
|
||||||
building_bar.value = 0.0
|
building_bar.value = 0.0
|
||||||
building_label.text = "Build: idle"
|
building_label.text = "Build: idle"
|
||||||
|
_update_progress_panel_visibility()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
building_box.visible = true
|
||||||
var now: float = Time.get_unix_time_from_system()
|
var now: float = Time.get_unix_time_from_system()
|
||||||
var duration: float = max(1.0, building_completes_unix - building_started_unix)
|
var duration: float = max(1.0, building_completes_unix - building_started_unix)
|
||||||
var elapsed: float = clamp(now - building_started_unix, 0.0, duration)
|
var elapsed: float = clamp(now - building_started_unix, 0.0, duration)
|
||||||
@@ -830,6 +1054,12 @@ func _update_building_timer() -> void:
|
|||||||
building_completion_refresh_requested = true
|
building_completion_refresh_requested = true
|
||||||
_load_visible_locations()
|
_load_visible_locations()
|
||||||
_refresh_player_state()
|
_refresh_player_state()
|
||||||
|
_update_progress_panel_visibility()
|
||||||
|
|
||||||
|
|
||||||
|
func _update_progress_panel_visibility() -> void:
|
||||||
|
if progress_panel:
|
||||||
|
progress_panel.visible = (training_box and training_box.visible) or (building_box and building_box.visible)
|
||||||
|
|
||||||
|
|
||||||
func _sync_active_building_timer(locations: Array, username: String) -> void:
|
func _sync_active_building_timer(locations: Array, username: String) -> void:
|
||||||
@@ -844,7 +1074,7 @@ func _sync_active_building_timer(locations: Array, username: String) -> void:
|
|||||||
var location: Dictionary = item
|
var location: Dictionary = item
|
||||||
if str(location.get("ownerUsername", "")) != username:
|
if str(location.get("ownerUsername", "")) != username:
|
||||||
continue
|
continue
|
||||||
var building_type: String = str(location.get("buildingType", ""))
|
var building_type: String = _nullable_string(location.get("buildingType", ""))
|
||||||
if building_type.is_empty():
|
if building_type.is_empty():
|
||||||
continue
|
continue
|
||||||
if str(location.get("buildingStatus", "")) != "Building":
|
if str(location.get("buildingStatus", "")) != "Building":
|
||||||
@@ -872,6 +1102,8 @@ func _building_display_name(building_type: String) -> String:
|
|||||||
return "Log Camp"
|
return "Log Camp"
|
||||||
"Mine":
|
"Mine":
|
||||||
return "Mine"
|
return "Mine"
|
||||||
|
"Barracks":
|
||||||
|
return "Barracks"
|
||||||
_:
|
_:
|
||||||
return "Build"
|
return "Build"
|
||||||
|
|
||||||
@@ -895,6 +1127,12 @@ func _parse_utc_timestamp(timestamp: String) -> float:
|
|||||||
return Time.get_unix_time_from_datetime_string(normalized)
|
return Time.get_unix_time_from_datetime_string(normalized)
|
||||||
|
|
||||||
|
|
||||||
|
func _nullable_string(value: Variant) -> String:
|
||||||
|
if value == null:
|
||||||
|
return ""
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
func _build_escape_menu() -> void:
|
func _build_escape_menu() -> void:
|
||||||
escape_overlay = Control.new()
|
escape_overlay = Control.new()
|
||||||
escape_overlay.visible = false
|
escape_overlay.visible = false
|
||||||
@@ -995,8 +1233,10 @@ func _menu_button(text_value: String) -> Button:
|
|||||||
|
|
||||||
func _small_button(text_value: String) -> Button:
|
func _small_button(text_value: String) -> Button:
|
||||||
var button := _menu_button(text_value)
|
var button := _menu_button(text_value)
|
||||||
button.custom_minimum_size = Vector2(128, 32)
|
button.custom_minimum_size = Vector2(108, 32)
|
||||||
button.add_theme_font_size_override("font_size", 13)
|
button.add_theme_font_size_override("font_size", 11)
|
||||||
|
button.clip_text = true
|
||||||
|
button.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||||
return button
|
return button
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -161,14 +161,16 @@ func _draw_tile(tile: Dictionary) -> void:
|
|||||||
draw_polyline(_closed_points(points), Color(1.0, 0.88, 0.38), 3.0)
|
draw_polyline(_closed_points(points), Color(1.0, 0.88, 0.38), 3.0)
|
||||||
|
|
||||||
var visible_location: Dictionary = visible_tiles.get(key, {})
|
var visible_location: Dictionary = visible_tiles.get(key, {})
|
||||||
var building_type: String = str(visible_location.get("buildingType", ""))
|
var building_type: String = _nullable_string(visible_location.get("buildingType", ""))
|
||||||
var building_status: String = str(visible_location.get("buildingStatus", ""))
|
var building_status: String = _nullable_string(visible_location.get("buildingStatus", ""))
|
||||||
if coord == home_tile:
|
if coord == home_tile:
|
||||||
_draw_town_hall_marker(center, height)
|
_draw_town_hall_marker(center, height)
|
||||||
elif building_type == "LogCamp" and (building_status == "Built" or building_status.is_empty()):
|
elif building_type == "LogCamp" and (building_status == "Built" or building_status.is_empty()):
|
||||||
_draw_log_camp_marker(center, height)
|
_draw_log_camp_marker(center, height)
|
||||||
elif building_type == "Mine" and (building_status == "Built" or building_status.is_empty()):
|
elif building_type == "Mine" and (building_status == "Built" or building_status.is_empty()):
|
||||||
_draw_mine_marker(center, height)
|
_draw_mine_marker(center, height)
|
||||||
|
elif building_type == "Barracks" and (building_status == "Built" or building_status.is_empty()):
|
||||||
|
_draw_barracks_marker(center, height)
|
||||||
elif not building_type.is_empty():
|
elif not building_type.is_empty():
|
||||||
_draw_building_marker(center, height)
|
_draw_building_marker(center, height)
|
||||||
|
|
||||||
@@ -329,6 +331,37 @@ func _draw_mine_marker(center: Vector2, height: float) -> void:
|
|||||||
draw_circle(marker_center + Vector2(width * 0.34, -marker_height * 0.1), 2.0 * zoom, ore_color)
|
draw_circle(marker_center + Vector2(width * 0.34, -marker_height * 0.1), 2.0 * zoom, ore_color)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_barracks_marker(center: Vector2, height: float) -> void:
|
||||||
|
var marker_center: Vector2 = center + Vector2(0, -height * zoom - 3.0)
|
||||||
|
var width: float = 22.0 * zoom
|
||||||
|
var marker_height: float = 15.0 * zoom
|
||||||
|
var wall_color := Color(0.52, 0.36, 0.24)
|
||||||
|
var roof_color := Color(0.25, 0.18, 0.13)
|
||||||
|
var banner_color := Color(0.82, 0.18, 0.16)
|
||||||
|
var trim_color := Color(0.78, 0.58, 0.34)
|
||||||
|
var shadow_color := Color(0.09, 0.06, 0.04)
|
||||||
|
|
||||||
|
var body_rect := Rect2(marker_center + Vector2(-width * 0.42, -marker_height * 0.05), Vector2(width * 0.84, marker_height * 0.62))
|
||||||
|
var roof := PackedVector2Array([
|
||||||
|
marker_center + Vector2(-width * 0.52, -marker_height * 0.05),
|
||||||
|
marker_center + Vector2(0, -marker_height * 0.58),
|
||||||
|
marker_center + Vector2(width * 0.52, -marker_height * 0.05)
|
||||||
|
])
|
||||||
|
var banner := PackedVector2Array([
|
||||||
|
marker_center + Vector2(width * 0.16, -marker_height * 0.7),
|
||||||
|
marker_center + Vector2(width * 0.42, -marker_height * 0.62),
|
||||||
|
marker_center + Vector2(width * 0.16, -marker_height * 0.46)
|
||||||
|
])
|
||||||
|
|
||||||
|
draw_rect(Rect2(body_rect.position + Vector2(1, 1) * zoom, body_rect.size), shadow_color, true)
|
||||||
|
draw_colored_polygon(roof, roof_color)
|
||||||
|
draw_rect(body_rect, wall_color, true)
|
||||||
|
draw_line(marker_center + Vector2(-width * 0.52, marker_height * 0.58), marker_center + Vector2(width * 0.52, marker_height * 0.58), trim_color, 2.0 * zoom)
|
||||||
|
draw_line(marker_center + Vector2(width * 0.16, -marker_height * 0.78), marker_center + Vector2(width * 0.16, marker_height * 0.1), trim_color.darkened(0.25), 2.0 * zoom)
|
||||||
|
draw_colored_polygon(banner, banner_color)
|
||||||
|
draw_rect(Rect2(marker_center + Vector2(-width * 0.1, marker_height * 0.24), Vector2(width * 0.2, marker_height * 0.33)), shadow_color.lightened(0.2), true)
|
||||||
|
|
||||||
|
|
||||||
func _draw_town_hall_marker(center: Vector2, height: float) -> void:
|
func _draw_town_hall_marker(center: Vector2, height: float) -> void:
|
||||||
var marker_center: Vector2 = center + Vector2(0, -height * zoom - 3.0)
|
var marker_center: Vector2 = center + Vector2(0, -height * zoom - 3.0)
|
||||||
var width: float = 24.0 * zoom
|
var width: float = 24.0 * zoom
|
||||||
@@ -434,6 +467,13 @@ func get_visible_location(q: int, r: int) -> Dictionary:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
func has_owned_neighbor(q: int, r: int) -> bool:
|
||||||
|
for neighbor in _neighbor_coordinates(q, r):
|
||||||
|
if owned_tiles.has(_tile_key(neighbor.x, neighbor.y)):
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
func _tile_key(q: int, r: int) -> String:
|
func _tile_key(q: int, r: int) -> String:
|
||||||
return "%d,%d" % [q, r]
|
return "%d,%d" % [q, r]
|
||||||
|
|
||||||
@@ -481,13 +521,15 @@ func _neighbor_coordinates(q: int, r: int) -> Array[Vector2i]:
|
|||||||
|
|
||||||
func _tile_display_label(key: String, data: Dictionary) -> String:
|
func _tile_display_label(key: String, data: Dictionary) -> String:
|
||||||
var terrain: String = str(data.get("terrain", "Open"))
|
var terrain: String = str(data.get("terrain", "Open"))
|
||||||
var building_type: String = str(data.get("buildingType", ""))
|
var building_type: String = _nullable_string(data.get("buildingType", ""))
|
||||||
var building_status: String = str(data.get("buildingStatus", ""))
|
var building_status: String = _nullable_string(data.get("buildingStatus", ""))
|
||||||
var building_label: String = ""
|
var building_label: String = ""
|
||||||
if building_type == "LogCamp":
|
if building_type == "LogCamp":
|
||||||
building_label = " | Log Camp" if building_status == "Built" or building_status.is_empty() else " | Log Camp building"
|
building_label = " | Log Camp" if building_status == "Built" or building_status.is_empty() else " | Log Camp building"
|
||||||
elif building_type == "Mine":
|
elif building_type == "Mine":
|
||||||
building_label = " | Mine" if building_status == "Built" or building_status.is_empty() else " | Mine building"
|
building_label = " | Mine" if building_status == "Built" or building_status.is_empty() else " | Mine building"
|
||||||
|
elif building_type == "Barracks":
|
||||||
|
building_label = " | Barracks" if building_status == "Built" or building_status.is_empty() else " | Barracks building"
|
||||||
|
|
||||||
if home_tile == Vector2i(int(data.get("q", 0)), int(data.get("r", 0))):
|
if home_tile == Vector2i(int(data.get("q", 0)), int(data.get("r", 0))):
|
||||||
return "%s | Your home%s" % [terrain, building_label]
|
return "%s | Your home%s" % [terrain, building_label]
|
||||||
@@ -509,6 +551,12 @@ func _owner_username(location: Dictionary) -> String:
|
|||||||
return str(owner_value)
|
return str(owner_value)
|
||||||
|
|
||||||
|
|
||||||
|
func _nullable_string(value: Variant) -> String:
|
||||||
|
if value == null:
|
||||||
|
return ""
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
func _tile_height(q: int, r: int) -> float:
|
func _tile_height(q: int, r: int) -> float:
|
||||||
var distance: int = max(max(abs(q), abs(r)), abs(-q - r))
|
var distance: int = max(max(abs(q), abs(r)), abs(-q - r))
|
||||||
if distance <= 1:
|
if distance <= 1:
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ public class LocationsController : ControllerBase
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!User.IsInRole("SUPER") && !await _locations.IsAdjacentToOwnedAsync(username, request.Q, request.R))
|
||||||
|
{
|
||||||
|
return BadRequest("Territory claims must be adjacent to controlled territory.");
|
||||||
|
}
|
||||||
|
|
||||||
var existingLocation = await _locations.GetAsync(request.Q, request.R);
|
var existingLocation = await _locations.GetAsync(request.Q, request.R);
|
||||||
if (existingLocation is not null && !string.IsNullOrWhiteSpace(existingLocation.OwnerUsername))
|
if (existingLocation is not null && !string.IsNullOrWhiteSpace(existingLocation.OwnerUsername))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class LocationResponse
|
|||||||
Label = location.Label,
|
Label = location.Label,
|
||||||
Level = location.Level,
|
Level = location.Level,
|
||||||
BuildingType = location.BuildingType,
|
BuildingType = location.BuildingType,
|
||||||
BuildingStatus = (location.BuildingType == "LogCamp" || location.BuildingType == "Mine") && string.IsNullOrWhiteSpace(location.BuildingStatus)
|
BuildingStatus = (location.BuildingType == "LogCamp" || location.BuildingType == "Mine" || location.BuildingType == "Barracks") && string.IsNullOrWhiteSpace(location.BuildingStatus)
|
||||||
? "Built"
|
? "Built"
|
||||||
: location.BuildingStatus,
|
: location.BuildingStatus,
|
||||||
BuildingStartedAtUtc = location.BuildingStartedAtUtc,
|
BuildingStartedAtUtc = location.BuildingStartedAtUtc,
|
||||||
|
|||||||
@@ -44,8 +44,10 @@ training record.
|
|||||||
Buildings are stored on location records. `LogCamp` can be built on controlled
|
Buildings are stored on location records. `LogCamp` can be built on controlled
|
||||||
Forest territory, takes 60 seconds to build, and adds 5 wood per minute after it
|
Forest territory, takes 60 seconds to build, and adds 5 wood per minute after it
|
||||||
finishes. `Mine` can be built on controlled Ore territory, takes 60 seconds to
|
finishes. `Mine` can be built on controlled Ore territory, takes 60 seconds to
|
||||||
build, and adds 5 ore per minute after it finishes. Each location has one
|
build, and adds 5 ore per minute after it finishes. `Barracks` can be built on
|
||||||
building slot; a building under construction occupies that slot.
|
any controlled territory, costs 500 gold, 100 wood, and 100 ore, and takes 60
|
||||||
|
seconds to build. Each location has one building slot; a building under
|
||||||
|
construction occupies that slot.
|
||||||
|
|
||||||
Timer settings are stored in the MongoDB `GameSettings` collection and are
|
Timer settings are stored in the MongoDB `GameSettings` collection and are
|
||||||
seeded on first read:
|
seeded on first read:
|
||||||
@@ -53,3 +55,4 @@ seeded on first read:
|
|||||||
- `ScoutTrainingSeconds`
|
- `ScoutTrainingSeconds`
|
||||||
- `LogCampBuildSeconds`
|
- `LogCampBuildSeconds`
|
||||||
- `MineBuildSeconds`
|
- `MineBuildSeconds`
|
||||||
|
- `BarracksBuildSeconds`
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public class GameSettingsService
|
|||||||
public const string ScoutTrainingSeconds = "ScoutTrainingSeconds";
|
public const string ScoutTrainingSeconds = "ScoutTrainingSeconds";
|
||||||
public const string LogCampBuildSeconds = "LogCampBuildSeconds";
|
public const string LogCampBuildSeconds = "LogCampBuildSeconds";
|
||||||
public const string MineBuildSeconds = "MineBuildSeconds";
|
public const string MineBuildSeconds = "MineBuildSeconds";
|
||||||
|
public const string BarracksBuildSeconds = "BarracksBuildSeconds";
|
||||||
|
|
||||||
private readonly IMongoCollection<GameSetting> _settings;
|
private readonly IMongoCollection<GameSetting> _settings;
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ public class LocationService
|
|||||||
private readonly int _mineOrePerMinute;
|
private readonly int _mineOrePerMinute;
|
||||||
private readonly int _defaultLogCampBuildSeconds;
|
private readonly int _defaultLogCampBuildSeconds;
|
||||||
private readonly int _defaultMineBuildSeconds;
|
private readonly int _defaultMineBuildSeconds;
|
||||||
|
private readonly int _defaultBarracksBuildSeconds;
|
||||||
|
private readonly int _barracksGoldCost;
|
||||||
|
private readonly int _barracksWoodCost;
|
||||||
|
private readonly int _barracksOreCost;
|
||||||
|
|
||||||
public LocationService(IConfiguration cfg, ResourceService resources, GameSettingsService settings)
|
public LocationService(IConfiguration cfg, ResourceService resources, GameSettingsService settings)
|
||||||
{
|
{
|
||||||
@@ -40,6 +44,18 @@ public class LocationService
|
|||||||
_defaultMineBuildSeconds = int.TryParse(cfg["Buildings:MineBuildSeconds"], out var mineBuildSeconds)
|
_defaultMineBuildSeconds = int.TryParse(cfg["Buildings:MineBuildSeconds"], out var mineBuildSeconds)
|
||||||
? Math.Max(1, mineBuildSeconds)
|
? Math.Max(1, mineBuildSeconds)
|
||||||
: 60;
|
: 60;
|
||||||
|
_defaultBarracksBuildSeconds = int.TryParse(cfg["Buildings:BarracksBuildSeconds"], out var barracksBuildSeconds)
|
||||||
|
? Math.Max(1, barracksBuildSeconds)
|
||||||
|
: 60;
|
||||||
|
_barracksGoldCost = int.TryParse(cfg["Buildings:BarracksGoldCost"], out var barracksGoldCost)
|
||||||
|
? Math.Max(0, barracksGoldCost)
|
||||||
|
: 500;
|
||||||
|
_barracksWoodCost = int.TryParse(cfg["Buildings:BarracksWoodCost"], out var barracksWoodCost)
|
||||||
|
? Math.Max(0, barracksWoodCost)
|
||||||
|
: 100;
|
||||||
|
_barracksOreCost = int.TryParse(cfg["Buildings:BarracksOreCost"], out var barracksOreCost)
|
||||||
|
? Math.Max(0, barracksOreCost)
|
||||||
|
: 100;
|
||||||
|
|
||||||
var coordinateIndex = Builders<Location>.IndexKeys
|
var coordinateIndex = Builders<Location>.IndexKeys
|
||||||
.Ascending(x => x.Q)
|
.Ascending(x => x.Q)
|
||||||
@@ -149,8 +165,8 @@ public class LocationService
|
|||||||
throw new InvalidOperationException("This location already has a building or construction in progress.");
|
throw new InvalidOperationException("This location already has a building or construction in progress.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var requiredTerrain = buildingType == "LogCamp" ? "Forest" : "Ore";
|
var requiredTerrain = RequiredTerrainForBuilding(buildingType);
|
||||||
if (!string.Equals(location.Terrain, requiredTerrain, StringComparison.OrdinalIgnoreCase))
|
if (requiredTerrain is not null && !string.Equals(location.Terrain, requiredTerrain, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"{buildingType} can only be built on {requiredTerrain} territory.");
|
throw new InvalidOperationException($"{buildingType} can only be built on {requiredTerrain} territory.");
|
||||||
}
|
}
|
||||||
@@ -158,12 +174,8 @@ public class LocationService
|
|||||||
await CompleteFinishedBuildingsAsync(username);
|
await CompleteFinishedBuildingsAsync(username);
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var settingKey = buildingType == "LogCamp"
|
var settingKey = BuildSecondsSettingKey(buildingType);
|
||||||
? GameSettingsService.LogCampBuildSeconds
|
var defaultBuildSeconds = DefaultBuildSeconds(buildingType);
|
||||||
: GameSettingsService.MineBuildSeconds;
|
|
||||||
var defaultBuildSeconds = buildingType == "LogCamp"
|
|
||||||
? _defaultLogCampBuildSeconds
|
|
||||||
: _defaultMineBuildSeconds;
|
|
||||||
var buildSeconds = await _settings.GetIntAsync(settingKey, defaultBuildSeconds);
|
var buildSeconds = await _settings.GetIntAsync(settingKey, defaultBuildSeconds);
|
||||||
var update = Builders<Location>.Update
|
var update = Builders<Location>.Update
|
||||||
.Set(x => x.BuildingType, buildingType)
|
.Set(x => x.BuildingType, buildingType)
|
||||||
@@ -173,7 +185,7 @@ public class LocationService
|
|||||||
.Set(x => x.UpdatedAtUtc, now);
|
.Set(x => x.UpdatedAtUtc, now);
|
||||||
|
|
||||||
var updated = await _locations.FindOneAndUpdateAsync(
|
var updated = await _locations.FindOneAndUpdateAsync(
|
||||||
x => x.Id == location.Id && x.OwnerUsername == username && x.BuildingType == null,
|
x => x.Id == location.Id && x.OwnerUsername == username && (x.BuildingType == null || x.BuildingType == ""),
|
||||||
update,
|
update,
|
||||||
new FindOneAndUpdateOptions<Location>
|
new FindOneAndUpdateOptions<Location>
|
||||||
{
|
{
|
||||||
@@ -185,6 +197,27 @@ public class LocationService
|
|||||||
throw new InvalidOperationException("This location already has a building or construction in progress.");
|
throw new InvalidOperationException("This location already has a building or construction in progress.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buildingType == "Barracks")
|
||||||
|
{
|
||||||
|
var spent = await _resources.TrySpendResourcesAsync(username, _barracksGoldCost, _barracksWoodCost, _barracksOreCost);
|
||||||
|
if (!spent)
|
||||||
|
{
|
||||||
|
var rollback = Builders<Location>.Update
|
||||||
|
.Unset(x => x.BuildingType)
|
||||||
|
.Unset(x => x.BuildingStatus)
|
||||||
|
.Unset(x => x.BuildingStartedAtUtc)
|
||||||
|
.Unset(x => x.BuildingCompletesAtUtc)
|
||||||
|
.Unset(x => x.BuildingBuiltAtUtc)
|
||||||
|
.Set(x => x.UpdatedAtUtc, DateTime.UtcNow);
|
||||||
|
|
||||||
|
await _locations.UpdateOneAsync(
|
||||||
|
x => x.Id == updated.Id && x.BuildingType == "Barracks" && x.BuildingStatus == "Building" && x.BuildingStartedAtUtc == now,
|
||||||
|
rollback);
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"Not enough resources to build Barracks. Required: {_barracksGoldCost} gold, {_barracksWoodCost} wood, {_barracksOreCost} ore.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +226,7 @@ public class LocationService
|
|||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var readyBuildings = await _locations.Find(x =>
|
var readyBuildings = await _locations.Find(x =>
|
||||||
x.OwnerUsername == username &&
|
x.OwnerUsername == username &&
|
||||||
(x.BuildingType == "LogCamp" || x.BuildingType == "Mine") &&
|
(x.BuildingType == "LogCamp" || x.BuildingType == "Mine" || x.BuildingType == "Barracks") &&
|
||||||
x.BuildingStatus == "Building" &&
|
x.BuildingStatus == "Building" &&
|
||||||
x.BuildingCompletesAtUtc <= now)
|
x.BuildingCompletesAtUtc <= now)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
@@ -353,6 +386,15 @@ public class LocationService
|
|||||||
return ownedLocations.Any(owned => HexDistance(owned.Q, owned.R, q, r) <= _visibilityDepth);
|
return ownedLocations.Any(owned => HexDistance(owned.Q, owned.R, q, r) <= _visibilityDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsAdjacentToOwnedAsync(string username, int q, int r)
|
||||||
|
{
|
||||||
|
var neighbors = NeighborCoordinates(q, r).ToList();
|
||||||
|
return await _locations.Find(x =>
|
||||||
|
x.OwnerUsername == username &&
|
||||||
|
neighbors.Any(n => n.Q == x.Q && n.R == x.R))
|
||||||
|
.AnyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Location?> TryCreateHomeAsync(string username, int q, int r)
|
private async Task<Location?> TryCreateHomeAsync(string username, int q, int r)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
@@ -582,7 +624,35 @@ public class LocationService
|
|||||||
{
|
{
|
||||||
"logcamp" or "log camp" => "LogCamp",
|
"logcamp" or "log camp" => "LogCamp",
|
||||||
"mine" => "Mine",
|
"mine" => "Mine",
|
||||||
|
"barracks" => "Barracks",
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? RequiredTerrainForBuilding(string buildingType) =>
|
||||||
|
buildingType switch
|
||||||
|
{
|
||||||
|
"LogCamp" => "Forest",
|
||||||
|
"Mine" => "Ore",
|
||||||
|
"Barracks" => null,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string BuildSecondsSettingKey(string buildingType) =>
|
||||||
|
buildingType switch
|
||||||
|
{
|
||||||
|
"LogCamp" => GameSettingsService.LogCampBuildSeconds,
|
||||||
|
"Mine" => GameSettingsService.MineBuildSeconds,
|
||||||
|
"Barracks" => GameSettingsService.BarracksBuildSeconds,
|
||||||
|
_ => GameSettingsService.LogCampBuildSeconds
|
||||||
|
};
|
||||||
|
|
||||||
|
private int DefaultBuildSeconds(string buildingType) =>
|
||||||
|
buildingType switch
|
||||||
|
{
|
||||||
|
"LogCamp" => _defaultLogCampBuildSeconds,
|
||||||
|
"Mine" => _defaultMineBuildSeconds,
|
||||||
|
"Barracks" => _defaultBarracksBuildSeconds,
|
||||||
|
_ => 60
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,35 @@ public class ResourceService
|
|||||||
return updated is not null;
|
return updated is not null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TrySpendResourcesAsync(string username, int gold, int wood, int ore)
|
||||||
|
{
|
||||||
|
gold = Math.Max(0, gold);
|
||||||
|
wood = Math.Max(0, wood);
|
||||||
|
ore = Math.Max(0, ore);
|
||||||
|
if (gold == 0 && wood == 0 && ore == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await EnsureAsync(username);
|
||||||
|
|
||||||
|
var update = Builders<PlayerResources>.Update
|
||||||
|
.Inc(x => x.Gold, -gold)
|
||||||
|
.Inc(x => x.Wood, -wood)
|
||||||
|
.Inc(x => x.Ore, -ore)
|
||||||
|
.Set(x => x.UpdatedAtUtc, DateTime.UtcNow);
|
||||||
|
|
||||||
|
var updated = await _resources.FindOneAndUpdateAsync(
|
||||||
|
x => x.Username == username && x.Gold >= gold && x.Wood >= wood && x.Ore >= ore,
|
||||||
|
update,
|
||||||
|
new FindOneAndUpdateOptions<PlayerResources>
|
||||||
|
{
|
||||||
|
ReturnDocument = ReturnDocument.After
|
||||||
|
});
|
||||||
|
|
||||||
|
return updated is not null;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task AddWoodGenerationAsync(string username, int woodPerMinute)
|
public async Task AddWoodGenerationAsync(string username, int woodPerMinute)
|
||||||
{
|
{
|
||||||
if (woodPerMinute <= 0)
|
if (woodPerMinute <= 0)
|
||||||
|
|||||||
@@ -31,7 +31,11 @@
|
|||||||
"LogCampWoodPerMinute": 5,
|
"LogCampWoodPerMinute": 5,
|
||||||
"LogCampBuildSeconds": 60,
|
"LogCampBuildSeconds": 60,
|
||||||
"MineOrePerMinute": 5,
|
"MineOrePerMinute": 5,
|
||||||
"MineBuildSeconds": 60
|
"MineBuildSeconds": 60,
|
||||||
|
"BarracksBuildSeconds": 60,
|
||||||
|
"BarracksGoldCost": 500,
|
||||||
|
"BarracksWoodCost": 100,
|
||||||
|
"BarracksOreCost": 100
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,3 +19,7 @@ data:
|
|||||||
Buildings__LogCampBuildSeconds: "60"
|
Buildings__LogCampBuildSeconds: "60"
|
||||||
Buildings__MineOrePerMinute: "5"
|
Buildings__MineOrePerMinute: "5"
|
||||||
Buildings__MineBuildSeconds: "60"
|
Buildings__MineBuildSeconds: "60"
|
||||||
|
Buildings__BarracksBuildSeconds: "60"
|
||||||
|
Buildings__BarracksGoldCost: "500"
|
||||||
|
Buildings__BarracksWoodCost: "100"
|
||||||
|
Buildings__BarracksOreCost: "100"
|
||||||
|
|||||||
Reference in New Issue
Block a user