allowing territory to be abandoned, units to be dismissed, and buildings to be destroyed
This commit is contained in:
@@ -16,9 +16,11 @@ var resources_request: HTTPRequest
|
|||||||
var units_request: HTTPRequest
|
var units_request: HTTPRequest
|
||||||
var train_unit_request: HTTPRequest
|
var train_unit_request: HTTPRequest
|
||||||
var move_units_request: HTTPRequest
|
var move_units_request: HTTPRequest
|
||||||
|
var dismiss_units_request: HTTPRequest
|
||||||
var worker_request: HTTPRequest
|
var worker_request: HTTPRequest
|
||||||
var claim_request: HTTPRequest
|
var claim_request: HTTPRequest
|
||||||
var build_request: HTTPRequest
|
var build_request: HTTPRequest
|
||||||
|
var territory_action_request: HTTPRequest
|
||||||
var refresh_request: HTTPRequest
|
var refresh_request: HTTPRequest
|
||||||
var resources_timer: Timer
|
var resources_timer: Timer
|
||||||
var auth_check_timer: Timer
|
var auth_check_timer: Timer
|
||||||
@@ -58,10 +60,14 @@ var build_log_camp_button: Button
|
|||||||
var build_mine_button: Button
|
var build_mine_button: Button
|
||||||
var build_farm_button: Button
|
var build_farm_button: Button
|
||||||
var build_barracks_button: Button
|
var build_barracks_button: Button
|
||||||
|
var destroy_building_button: Button
|
||||||
|
var abandon_territory_button: Button
|
||||||
var unit_action_popup: PopupPanel
|
var unit_action_popup: PopupPanel
|
||||||
var unit_action_move_button: Button
|
var unit_action_move_button: Button
|
||||||
|
var unit_action_claim_button: Button
|
||||||
var unit_action_assign_button: Button
|
var unit_action_assign_button: Button
|
||||||
var unit_action_deassign_button: Button
|
var unit_action_deassign_button: Button
|
||||||
|
var unit_action_dismiss_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
|
||||||
@@ -69,6 +75,8 @@ var selected_can_build_log_camp := false
|
|||||||
var selected_can_build_mine := false
|
var selected_can_build_mine := false
|
||||||
var selected_can_build_farm := false
|
var selected_can_build_farm := false
|
||||||
var selected_can_build_barracks := false
|
var selected_can_build_barracks := false
|
||||||
|
var selected_can_destroy_building := false
|
||||||
|
var selected_can_abandon_territory := false
|
||||||
var selected_can_train_archer := false
|
var selected_can_train_archer := false
|
||||||
var selected_is_home := false
|
var selected_is_home := false
|
||||||
var completed_scout_count := 0
|
var completed_scout_count := 0
|
||||||
@@ -266,6 +274,16 @@ func _build_ui() -> void:
|
|||||||
build_barracks_button.pressed.connect(_build_barracks)
|
build_barracks_button.pressed.connect(_build_barracks)
|
||||||
action_buttons.add_child(build_barracks_button)
|
action_buttons.add_child(build_barracks_button)
|
||||||
|
|
||||||
|
destroy_building_button = _small_button("Destroy Building")
|
||||||
|
destroy_building_button.visible = false
|
||||||
|
destroy_building_button.pressed.connect(_destroy_selected_building)
|
||||||
|
action_buttons.add_child(destroy_building_button)
|
||||||
|
|
||||||
|
abandon_territory_button = _small_button("Give Up")
|
||||||
|
abandon_territory_button.visible = false
|
||||||
|
abandon_territory_button.pressed.connect(_abandon_selected_territory)
|
||||||
|
action_buttons.add_child(abandon_territory_button)
|
||||||
|
|
||||||
unit_label = Label.new()
|
unit_label = Label.new()
|
||||||
unit_label.text = "Scouts: -- | Archers: -- | Queue: --"
|
unit_label.text = "Scouts: -- | Archers: -- | Queue: --"
|
||||||
unit_label.custom_minimum_size = Vector2(180, 0)
|
unit_label.custom_minimum_size = Vector2(180, 0)
|
||||||
@@ -297,6 +315,10 @@ func _build_ui() -> void:
|
|||||||
move_units_request.request_completed.connect(_on_move_units_request_completed)
|
move_units_request.request_completed.connect(_on_move_units_request_completed)
|
||||||
add_child(move_units_request)
|
add_child(move_units_request)
|
||||||
|
|
||||||
|
dismiss_units_request = HTTPRequest.new()
|
||||||
|
dismiss_units_request.request_completed.connect(_on_dismiss_units_request_completed)
|
||||||
|
add_child(dismiss_units_request)
|
||||||
|
|
||||||
worker_request = HTTPRequest.new()
|
worker_request = HTTPRequest.new()
|
||||||
worker_request.request_completed.connect(_on_worker_request_completed)
|
worker_request.request_completed.connect(_on_worker_request_completed)
|
||||||
add_child(worker_request)
|
add_child(worker_request)
|
||||||
@@ -309,6 +331,10 @@ 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)
|
||||||
|
|
||||||
|
territory_action_request = HTTPRequest.new()
|
||||||
|
territory_action_request.request_completed.connect(_on_territory_action_request_completed)
|
||||||
|
add_child(territory_action_request)
|
||||||
|
|
||||||
refresh_request = HTTPRequest.new()
|
refresh_request = HTTPRequest.new()
|
||||||
refresh_request.request_completed.connect(_on_refresh_request_completed)
|
refresh_request.request_completed.connect(_on_refresh_request_completed)
|
||||||
add_child(refresh_request)
|
add_child(refresh_request)
|
||||||
@@ -482,6 +508,11 @@ func _build_unit_action_menu() -> void:
|
|||||||
unit_action_move_button.pressed.connect(_confirm_unit_move)
|
unit_action_move_button.pressed.connect(_confirm_unit_move)
|
||||||
actions.add_child(unit_action_move_button)
|
actions.add_child(unit_action_move_button)
|
||||||
|
|
||||||
|
unit_action_claim_button = _small_button("Claim")
|
||||||
|
unit_action_claim_button.custom_minimum_size = Vector2(92, 34)
|
||||||
|
unit_action_claim_button.pressed.connect(_confirm_scout_claim)
|
||||||
|
actions.add_child(unit_action_claim_button)
|
||||||
|
|
||||||
unit_action_assign_button = _small_button("Assign")
|
unit_action_assign_button = _small_button("Assign")
|
||||||
unit_action_assign_button.custom_minimum_size = Vector2(92, 34)
|
unit_action_assign_button.custom_minimum_size = Vector2(92, 34)
|
||||||
unit_action_assign_button.pressed.connect(_confirm_worker_assign)
|
unit_action_assign_button.pressed.connect(_confirm_worker_assign)
|
||||||
@@ -492,6 +523,11 @@ func _build_unit_action_menu() -> void:
|
|||||||
unit_action_deassign_button.pressed.connect(_confirm_worker_deassign)
|
unit_action_deassign_button.pressed.connect(_confirm_worker_deassign)
|
||||||
actions.add_child(unit_action_deassign_button)
|
actions.add_child(unit_action_deassign_button)
|
||||||
|
|
||||||
|
unit_action_dismiss_button = _small_button("Dismiss")
|
||||||
|
unit_action_dismiss_button.custom_minimum_size = Vector2(92, 34)
|
||||||
|
unit_action_dismiss_button.pressed.connect(_confirm_unit_dismiss)
|
||||||
|
actions.add_child(unit_action_dismiss_button)
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
@@ -510,6 +546,8 @@ func _on_tile_selected(q: int, r: int, terrain: String) -> void:
|
|||||||
selected_can_build_mine = _can_build_mine_on_selected_tile()
|
selected_can_build_mine = _can_build_mine_on_selected_tile()
|
||||||
selected_can_build_farm = _can_build_farm_on_selected_tile()
|
selected_can_build_farm = _can_build_farm_on_selected_tile()
|
||||||
selected_can_build_barracks = _can_build_barracks_on_selected_tile()
|
selected_can_build_barracks = _can_build_barracks_on_selected_tile()
|
||||||
|
selected_can_destroy_building = _can_destroy_building_on_selected_tile()
|
||||||
|
selected_can_abandon_territory = _can_abandon_selected_tile()
|
||||||
selected_can_train_archer = _selected_tile_has_built_barracks()
|
selected_can_train_archer = _selected_tile_has_built_barracks()
|
||||||
_update_action_buttons()
|
_update_action_buttons()
|
||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
@@ -577,27 +615,22 @@ func _on_tile_action_requested(q: int, r: int, _terrain: String, screen_position
|
|||||||
var has_worker := not _nullable_string(location.get("workerUnitId", "")).is_empty()
|
var has_worker := not _nullable_string(location.get("workerUnitId", "")).is_empty()
|
||||||
var selected_peasants := selected_peasant_count > 0
|
var selected_peasants := selected_peasant_count > 0
|
||||||
var selected_scouts := selected_scout_count > 0
|
var selected_scouts := selected_scout_count > 0
|
||||||
if selected_archer_count <= 0 and not selected_scouts and not selected_peasants and not has_worker:
|
var selected_archers := selected_archer_count > 0
|
||||||
|
var can_scout_claim := selected_scouts and _can_claim_tile(q, r)
|
||||||
|
var is_selected_unit_tile := (selected_archers and q == selected_archer_q and r == selected_archer_r) or (selected_scouts and q == selected_scout_q and r == selected_scout_r) or (selected_peasants and q == selected_peasant_q and r == selected_peasant_r)
|
||||||
|
if not selected_archers and not selected_scouts and not selected_peasants and not has_worker:
|
||||||
if unit_action_popup:
|
if unit_action_popup:
|
||||||
unit_action_popup.hide()
|
unit_action_popup.hide()
|
||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
coordinate_label.text = "Select a unit or right-click a staffed building."
|
coordinate_label.text = "Select a unit or right-click a staffed building."
|
||||||
return
|
return
|
||||||
if selected_archer_count > 0 and q == selected_archer_q and r == selected_archer_r:
|
if selected_peasants and not is_selected_unit_tile and not grid_view.is_owned_tile(q, r):
|
||||||
if coordinate_label:
|
|
||||||
coordinate_label.text = "Archer squad is already on that tile."
|
|
||||||
return
|
|
||||||
if selected_scouts and q == selected_scout_q and r == selected_scout_r:
|
|
||||||
if coordinate_label:
|
|
||||||
coordinate_label.text = "Scout squad is already on that tile."
|
|
||||||
return
|
|
||||||
if selected_peasants and not grid_view.is_owned_tile(q, r):
|
|
||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
coordinate_label.text = "Peasants can only move within controlled territory."
|
coordinate_label.text = "Peasants can only move within controlled territory."
|
||||||
return
|
return
|
||||||
if not grid_view.is_visible_tile(q, r):
|
if not is_selected_unit_tile and not grid_view.is_visible_tile(q, r):
|
||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
coordinate_label.text = "Archers can only move to visible tiles."
|
coordinate_label.text = "Units can only move to visible tiles."
|
||||||
return
|
return
|
||||||
|
|
||||||
pending_move_q = q
|
pending_move_q = q
|
||||||
@@ -605,10 +638,19 @@ func _on_tile_action_requested(q: int, r: int, _terrain: String, screen_position
|
|||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
coordinate_label.text = "Orders for q:%d r:%d." % [q, r]
|
coordinate_label.text = "Orders for q:%d r:%d." % [q, r]
|
||||||
if unit_action_popup:
|
if unit_action_popup:
|
||||||
unit_action_move_button.visible = selected_archer_count > 0 or selected_scouts or selected_peasants
|
unit_action_move_button.visible = not is_selected_unit_tile and (selected_archers or selected_scouts or selected_peasants)
|
||||||
|
unit_action_claim_button.visible = can_scout_claim
|
||||||
unit_action_assign_button.visible = not selected_peasant_id.is_empty() and has_building and not has_worker
|
unit_action_assign_button.visible = not selected_peasant_id.is_empty() and has_building and not has_worker
|
||||||
unit_action_deassign_button.visible = has_worker
|
unit_action_deassign_button.visible = has_worker
|
||||||
var popup_size := Vector2i(116, 132)
|
unit_action_dismiss_button.visible = is_selected_unit_tile and (selected_archers or selected_scouts or selected_peasants)
|
||||||
|
var visible_action_count := 0
|
||||||
|
for button in [unit_action_move_button, unit_action_claim_button, unit_action_assign_button, unit_action_deassign_button, unit_action_dismiss_button]:
|
||||||
|
if button.visible:
|
||||||
|
visible_action_count += 1
|
||||||
|
if visible_action_count == 0:
|
||||||
|
unit_action_popup.hide()
|
||||||
|
return
|
||||||
|
var popup_size := Vector2i(116, 16 + visible_action_count * 40)
|
||||||
var visible_rect := get_viewport().get_visible_rect()
|
var visible_rect := get_viewport().get_visible_rect()
|
||||||
var max_x := int(max(0.0, visible_rect.size.x - popup_size.x - 8.0))
|
var max_x := int(max(0.0, visible_rect.size.x - popup_size.x - 8.0))
|
||||||
var max_y := int(max(0.0, visible_rect.size.y - popup_size.y - 8.0))
|
var max_y := int(max(0.0, visible_rect.size.y - popup_size.y - 8.0))
|
||||||
@@ -629,6 +671,20 @@ func _confirm_unit_move() -> void:
|
|||||||
_move_selected_peasants(pending_move_q, pending_move_r)
|
_move_selected_peasants(pending_move_q, pending_move_r)
|
||||||
|
|
||||||
|
|
||||||
|
func _confirm_scout_claim() -> void:
|
||||||
|
if unit_action_popup:
|
||||||
|
unit_action_popup.hide()
|
||||||
|
if pending_move_q == 9999:
|
||||||
|
return
|
||||||
|
if not _can_claim_tile(pending_move_q, pending_move_r):
|
||||||
|
coordinate_label.text = "Scouts can only claim visible unclaimed territory adjacent to controlled territory."
|
||||||
|
return
|
||||||
|
selected_q = pending_move_q
|
||||||
|
selected_r = pending_move_r
|
||||||
|
selected_can_claim = true
|
||||||
|
_claim_selected_tile()
|
||||||
|
|
||||||
|
|
||||||
func _confirm_worker_assign() -> void:
|
func _confirm_worker_assign() -> void:
|
||||||
if unit_action_popup:
|
if unit_action_popup:
|
||||||
unit_action_popup.hide()
|
unit_action_popup.hide()
|
||||||
@@ -645,6 +701,17 @@ func _confirm_worker_deassign() -> void:
|
|||||||
_request_worker_action("deassign-worker", { "q": pending_move_q, "r": pending_move_r })
|
_request_worker_action("deassign-worker", { "q": pending_move_q, "r": pending_move_r })
|
||||||
|
|
||||||
|
|
||||||
|
func _confirm_unit_dismiss() -> void:
|
||||||
|
if unit_action_popup:
|
||||||
|
unit_action_popup.hide()
|
||||||
|
if selected_archer_count > 0:
|
||||||
|
_request_unit_dismiss("Archer", selected_archer_q, selected_archer_r)
|
||||||
|
elif selected_scout_count > 0:
|
||||||
|
_request_unit_dismiss("Scout", selected_scout_q, selected_scout_r)
|
||||||
|
elif selected_peasant_count > 0:
|
||||||
|
_request_unit_dismiss("Peasant", selected_peasant_q, selected_peasant_r)
|
||||||
|
|
||||||
|
|
||||||
func _location_has_completed_building(location: Dictionary) -> bool:
|
func _location_has_completed_building(location: Dictionary) -> bool:
|
||||||
if location.is_empty():
|
if location.is_empty():
|
||||||
return false
|
return false
|
||||||
@@ -675,6 +742,36 @@ func _can_build_barracks_on_selected_tile() -> bool:
|
|||||||
return kind != "Home"
|
return kind != "Home"
|
||||||
|
|
||||||
|
|
||||||
|
func _can_destroy_building_on_selected_tile() -> bool:
|
||||||
|
if not grid_view:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var location: Dictionary = grid_view.get_visible_location(selected_q, selected_r)
|
||||||
|
if location.is_empty():
|
||||||
|
return false
|
||||||
|
|
||||||
|
var username: String = str(session.get("username", ""))
|
||||||
|
var owner_username: String = str(location.get("ownerUsername", ""))
|
||||||
|
var kind: String = str(location.get("kind", ""))
|
||||||
|
var building_type: String = _nullable_string(location.get("buildingType", ""))
|
||||||
|
return owner_username == username and kind != "Home" and not building_type.is_empty()
|
||||||
|
|
||||||
|
|
||||||
|
func _can_abandon_selected_tile() -> bool:
|
||||||
|
if not grid_view:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var location: Dictionary = grid_view.get_visible_location(selected_q, selected_r)
|
||||||
|
if location.is_empty():
|
||||||
|
return false
|
||||||
|
|
||||||
|
var username: String = str(session.get("username", ""))
|
||||||
|
var owner_username: String = str(location.get("ownerUsername", ""))
|
||||||
|
var kind: String = str(location.get("kind", ""))
|
||||||
|
var building_type: String = _nullable_string(location.get("buildingType", ""))
|
||||||
|
return owner_username == username and kind != "Home" and building_type.is_empty()
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
@@ -695,6 +792,22 @@ func _can_claim_selected_tile() -> bool:
|
|||||||
return selected_can_claim and completed_scout_count > 0 and grid_view and grid_view.has_owned_neighbor(selected_q, selected_r)
|
return selected_can_claim and completed_scout_count > 0 and grid_view and grid_view.has_owned_neighbor(selected_q, selected_r)
|
||||||
|
|
||||||
|
|
||||||
|
func _can_claim_tile(q: int, r: int) -> bool:
|
||||||
|
if not grid_view:
|
||||||
|
return false
|
||||||
|
if completed_scout_count <= 0:
|
||||||
|
return false
|
||||||
|
if not grid_view.is_visible_tile(q, r):
|
||||||
|
return false
|
||||||
|
|
||||||
|
var location: Dictionary = grid_view.get_visible_location(q, r)
|
||||||
|
if location.is_empty():
|
||||||
|
return false
|
||||||
|
|
||||||
|
var owner_username: String = _nullable_string(location.get("ownerUsername", ""))
|
||||||
|
return owner_username.is_empty() and grid_view.has_owned_neighbor(q, r)
|
||||||
|
|
||||||
|
|
||||||
func _update_action_buttons() -> void:
|
func _update_action_buttons() -> void:
|
||||||
if train_button:
|
if train_button:
|
||||||
train_button.visible = selected_is_home
|
train_button.visible = selected_is_home
|
||||||
@@ -720,6 +833,12 @@ func _update_action_buttons() -> void:
|
|||||||
if build_barracks_button:
|
if build_barracks_button:
|
||||||
build_barracks_button.visible = selected_can_build_barracks
|
build_barracks_button.visible = selected_can_build_barracks
|
||||||
build_barracks_button.disabled = false
|
build_barracks_button.disabled = false
|
||||||
|
if destroy_building_button:
|
||||||
|
destroy_building_button.visible = selected_can_destroy_building
|
||||||
|
destroy_building_button.disabled = false
|
||||||
|
if abandon_territory_button:
|
||||||
|
abandon_territory_button.visible = selected_can_abandon_territory
|
||||||
|
abandon_territory_button.disabled = false
|
||||||
|
|
||||||
|
|
||||||
func _clear_selected_archers() -> void:
|
func _clear_selected_archers() -> void:
|
||||||
@@ -1356,6 +1475,47 @@ func _on_move_units_request_completed(result: int, response_code: int, _headers:
|
|||||||
_load_units()
|
_load_units()
|
||||||
|
|
||||||
|
|
||||||
|
func _request_unit_dismiss(unit_type: String, q: int, r: int) -> void:
|
||||||
|
var access_token: String = str(session.get("accessToken", ""))
|
||||||
|
if access_token.is_empty():
|
||||||
|
coordinate_label.text = "No auth token available. Unit squad was not dismissed."
|
||||||
|
return
|
||||||
|
|
||||||
|
if dismiss_units_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
||||||
|
return
|
||||||
|
|
||||||
|
coordinate_label.text = "Dismissing %s squad at q:%d r:%d..." % [unit_type, q, r]
|
||||||
|
var error: int = dismiss_units_request.request(
|
||||||
|
LOCATIONS_BASE_URL + "/api/units/dismiss",
|
||||||
|
PackedStringArray([
|
||||||
|
"Content-Type: application/json",
|
||||||
|
"Authorization: Bearer %s" % access_token
|
||||||
|
]),
|
||||||
|
HTTPClient.METHOD_POST,
|
||||||
|
JSON.stringify({ "unitType": unit_type, "q": q, "r": r })
|
||||||
|
)
|
||||||
|
if error != OK:
|
||||||
|
coordinate_label.text = "Could not contact the unit dismiss service."
|
||||||
|
|
||||||
|
|
||||||
|
func _on_dismiss_units_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||||
|
var text: String = body.get_string_from_utf8()
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
|
coordinate_label.text = "Unit dismiss request failed."
|
||||||
|
return
|
||||||
|
|
||||||
|
if response_code < 200 or response_code >= 300:
|
||||||
|
coordinate_label.text = "Unit dismiss failed: %s" % text
|
||||||
|
_refresh_player_state()
|
||||||
|
return
|
||||||
|
|
||||||
|
_clear_selected_archers()
|
||||||
|
coordinate_label.text = "Unit squad dismissed."
|
||||||
|
_load_visible_locations()
|
||||||
|
_load_units()
|
||||||
|
_refresh_player_state()
|
||||||
|
|
||||||
|
|
||||||
func _request_worker_action(endpoint: String, body: Dictionary) -> void:
|
func _request_worker_action(endpoint: String, body: Dictionary) -> 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():
|
||||||
@@ -1480,6 +1640,70 @@ func _build_barracks() -> void:
|
|||||||
_request_building("Barracks")
|
_request_building("Barracks")
|
||||||
|
|
||||||
|
|
||||||
|
func _destroy_selected_building() -> void:
|
||||||
|
if not selected_can_destroy_building:
|
||||||
|
coordinate_label.text = "Select controlled territory with a non-town-hall building."
|
||||||
|
return
|
||||||
|
|
||||||
|
_request_territory_action("destroy-building", "Destroying building")
|
||||||
|
|
||||||
|
|
||||||
|
func _abandon_selected_territory() -> void:
|
||||||
|
if not selected_can_abandon_territory:
|
||||||
|
coordinate_label.text = "Select controlled territory without a building."
|
||||||
|
return
|
||||||
|
|
||||||
|
_request_territory_action("abandon", "Giving up territory")
|
||||||
|
|
||||||
|
|
||||||
|
func _request_territory_action(endpoint: String, message: String) -> void:
|
||||||
|
var access_token: String = str(session.get("accessToken", ""))
|
||||||
|
if access_token.is_empty():
|
||||||
|
coordinate_label.text = "No auth token available. Territory action failed."
|
||||||
|
return
|
||||||
|
|
||||||
|
if territory_action_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
||||||
|
return
|
||||||
|
|
||||||
|
destroy_building_button.disabled = true
|
||||||
|
abandon_territory_button.disabled = true
|
||||||
|
coordinate_label.text = "%s at q:%d r:%d..." % [message, selected_q, selected_r]
|
||||||
|
var error: int = territory_action_request.request(
|
||||||
|
LOCATIONS_BASE_URL + "/api/locations/" + endpoint,
|
||||||
|
PackedStringArray([
|
||||||
|
"Content-Type: application/json",
|
||||||
|
"Authorization: Bearer %s" % access_token
|
||||||
|
]),
|
||||||
|
HTTPClient.METHOD_POST,
|
||||||
|
JSON.stringify({ "q": selected_q, "r": selected_r })
|
||||||
|
)
|
||||||
|
if error != OK:
|
||||||
|
_update_action_buttons()
|
||||||
|
coordinate_label.text = "Could not contact the locations service."
|
||||||
|
|
||||||
|
|
||||||
|
func _on_territory_action_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||||
|
var text: String = body.get_string_from_utf8()
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
|
coordinate_label.text = "Territory action request failed."
|
||||||
|
_update_action_buttons()
|
||||||
|
return
|
||||||
|
|
||||||
|
if response_code < 200 or response_code >= 300:
|
||||||
|
coordinate_label.text = "Territory action failed: %s" % text
|
||||||
|
_update_action_buttons()
|
||||||
|
_refresh_player_state()
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_can_destroy_building = false
|
||||||
|
selected_can_abandon_territory = false
|
||||||
|
_update_action_buttons()
|
||||||
|
coordinate_label.text = "Territory updated."
|
||||||
|
_load_visible_locations()
|
||||||
|
_load_units()
|
||||||
|
_refresh_player_state()
|
||||||
|
|
||||||
|
|
||||||
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():
|
||||||
@@ -1529,7 +1753,10 @@ func _on_build_request_completed(result: int, response_code: int, _headers: Pack
|
|||||||
|
|
||||||
selected_can_build_log_camp = false
|
selected_can_build_log_camp = false
|
||||||
selected_can_build_mine = false
|
selected_can_build_mine = false
|
||||||
|
selected_can_build_farm = false
|
||||||
selected_can_build_barracks = false
|
selected_can_build_barracks = false
|
||||||
|
selected_can_destroy_building = true
|
||||||
|
selected_can_abandon_territory = false
|
||||||
_update_action_buttons()
|
_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:
|
||||||
|
|||||||
@@ -180,6 +180,52 @@ public class LocationsController : ControllerBase
|
|||||||
: Ok(LocationResponse.FromLocation(location));
|
: Ok(LocationResponse.FromLocation(location));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Roles = "USER,SUPER")]
|
||||||
|
[HttpPost("destroy-building")]
|
||||||
|
public async Task<ActionResult<LocationResponse>> DestroyBuilding([FromBody] TerritoryLocationRequest request)
|
||||||
|
{
|
||||||
|
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||||
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var location = await _locations.DestroyBuildingAsync(username, request.Q, request.R);
|
||||||
|
return location is null
|
||||||
|
? BadRequest("Select controlled territory with a non-town-hall building.")
|
||||||
|
: Ok(LocationResponse.FromLocation(location));
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Roles = "USER,SUPER")]
|
||||||
|
[HttpPost("abandon")]
|
||||||
|
public async Task<ActionResult<LocationResponse>> Abandon([FromBody] TerritoryLocationRequest request)
|
||||||
|
{
|
||||||
|
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||||
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var location = await _locations.AbandonAsync(username, request.Q, request.R);
|
||||||
|
return location is null
|
||||||
|
? BadRequest("Select controlled non-home territory to give up.")
|
||||||
|
: Ok(LocationResponse.FromLocation(location));
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Authorize(Roles = "USER,SUPER")]
|
[Authorize(Roles = "USER,SUPER")]
|
||||||
[HttpGet("home")]
|
[HttpGet("home")]
|
||||||
public async Task<ActionResult<LocationResponse>> GetHome()
|
public async Task<ActionResult<LocationResponse>> GetHome()
|
||||||
|
|||||||
@@ -89,4 +89,34 @@ public class UnitsController : ControllerBase
|
|||||||
return BadRequest(new { error = ex.Message });
|
return BadRequest(new { error = ex.Message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Roles = "USER,SUPER")]
|
||||||
|
[HttpPost("dismiss")]
|
||||||
|
public async Task<ActionResult<DismissUnitSquadResponse>> Dismiss([FromBody] DismissUnitSquadRequest request)
|
||||||
|
{
|
||||||
|
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||||
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dismissedCount = await _units.DismissSquadAsync(username, request);
|
||||||
|
if (dismissedCount == 0)
|
||||||
|
{
|
||||||
|
return BadRequest(new { error = "No matching idle units are available to dismiss." });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new DismissUnitSquadResponse
|
||||||
|
{
|
||||||
|
UnitType = request.UnitType,
|
||||||
|
DismissedCount = dismissedCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new { error = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ public class DeassignWorkerRequest
|
|||||||
public int R { get; set; }
|
public int R { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TerritoryLocationRequest
|
||||||
|
{
|
||||||
|
public int Q { get; set; }
|
||||||
|
public int R { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class LocationResponse
|
public class LocationResponse
|
||||||
{
|
{
|
||||||
public string Id { get; set; } = "";
|
public string Id { get; set; } = "";
|
||||||
|
|||||||
@@ -17,6 +17,19 @@ public class MoveUnitSquadRequest
|
|||||||
public int ToR { get; set; }
|
public int ToR { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class DismissUnitSquadRequest
|
||||||
|
{
|
||||||
|
public string UnitType { get; set; } = "Archer";
|
||||||
|
public int Q { get; set; }
|
||||||
|
public int R { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DismissUnitSquadResponse
|
||||||
|
{
|
||||||
|
public string UnitType { get; set; } = "";
|
||||||
|
public int DismissedCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class UnitTrainingResponse
|
public class UnitTrainingResponse
|
||||||
{
|
{
|
||||||
public string Id { get; set; } = "";
|
public string Id { get; set; } = "";
|
||||||
|
|||||||
@@ -355,6 +355,93 @@ public class LocationService
|
|||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Location?> DestroyBuildingAsync(string username, int q, int r)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var location = await GetAsync(q, r);
|
||||||
|
if (location is null ||
|
||||||
|
!string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.IsNullOrWhiteSpace(location.BuildingType))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(location.Kind, "Home", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The town hall cannot be destroyed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(location.WorkerUnitId))
|
||||||
|
{
|
||||||
|
await _units.UpdateOneAsync(
|
||||||
|
x => x.Id == location.WorkerUnitId && x.Username == username,
|
||||||
|
Builders<UnitInstance>.Update
|
||||||
|
.Set(x => x.Q, q)
|
||||||
|
.Set(x => x.R, r)
|
||||||
|
.Set(x => x.Status, "Idle")
|
||||||
|
.Set(x => x.MovementRemaining, 1)
|
||||||
|
.Set(x => x.UpdatedAtUtc, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
var updated = await _locations.FindOneAndUpdateAsync(
|
||||||
|
x => x.Id == location.Id && x.OwnerUsername == username && x.BuildingType != null && x.BuildingType != "",
|
||||||
|
Builders<Location>.Update
|
||||||
|
.Unset(x => x.BuildingType)
|
||||||
|
.Unset(x => x.BuildingStatus)
|
||||||
|
.Unset(x => x.BuildingStartedAtUtc)
|
||||||
|
.Unset(x => x.BuildingCompletesAtUtc)
|
||||||
|
.Unset(x => x.BuildingBuiltAtUtc)
|
||||||
|
.Unset(x => x.WorkerUnitId)
|
||||||
|
.Unset(x => x.WorkerUnitType)
|
||||||
|
.Set(x => x.StoredResourceAmount, 0)
|
||||||
|
.Set(x => x.StoredResourceCapacity, 10)
|
||||||
|
.Unset(x => x.LastResourceGeneratedAtUtc)
|
||||||
|
.Set(x => x.UpdatedAtUtc, now),
|
||||||
|
new FindOneAndUpdateOptions<Location> { ReturnDocument = ReturnDocument.After });
|
||||||
|
|
||||||
|
await RecalculateWorkerGenerationAsync(username);
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Location?> AbandonAsync(string username, int q, int r)
|
||||||
|
{
|
||||||
|
var location = await GetAsync(q, r);
|
||||||
|
if (location is null ||
|
||||||
|
!string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(location.Kind, "Home", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The town hall cannot be given up.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(location.BuildingType) || !string.IsNullOrWhiteSpace(location.WorkerUnitId))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Destroy the building before giving up this territory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasUnits = await _units.Find(x =>
|
||||||
|
x.Username == username &&
|
||||||
|
x.Q == q &&
|
||||||
|
x.R == r)
|
||||||
|
.AnyAsync();
|
||||||
|
if (hasUnits)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Move or dismiss units on this tile before giving up the territory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _locations.FindOneAndUpdateAsync(
|
||||||
|
x => x.Id == location.Id && x.OwnerUsername == username,
|
||||||
|
Builders<Location>.Update
|
||||||
|
.Set(x => x.Kind, "Open")
|
||||||
|
.Unset(x => x.OwnerUsername)
|
||||||
|
.Unset(x => x.Label)
|
||||||
|
.Set(x => x.UpdatedAtUtc, DateTime.UtcNow),
|
||||||
|
new FindOneAndUpdateOptions<Location> { ReturnDocument = ReturnDocument.After });
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RecalculateWorkerGenerationAsync(string username)
|
private async Task RecalculateWorkerGenerationAsync(string username)
|
||||||
{
|
{
|
||||||
var built = await _locations.Find(x =>
|
var built = await _locations.Find(x =>
|
||||||
|
|||||||
@@ -197,9 +197,9 @@ public class UnitTrainingService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var movementDistance = HexDistance(request.FromQ, request.FromR, request.ToQ, request.ToR);
|
var movementDistance = HexDistance(request.FromQ, request.FromR, request.ToQ, request.ToR);
|
||||||
if (normalizedUnitType == ArcherUnitType && !await CanMoveToAsync(username, request.ToQ, request.ToR))
|
if ((normalizedUnitType == ArcherUnitType || normalizedUnitType == ScoutUnitType) && !await CanMoveToAsync(username, request.ToQ, request.ToR))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Archers can only move to visible tiles.");
|
throw new InvalidOperationException($"{normalizedUnitType}s can only move to visible tiles.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedUnitType == PeasantUnitType && !await CanPeasantMoveToAsync(username, request.ToQ, request.ToR))
|
if (normalizedUnitType == PeasantUnitType && !await CanPeasantMoveToAsync(username, request.ToQ, request.ToR))
|
||||||
@@ -257,6 +257,26 @@ public class UnitTrainingService
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<int> DismissSquadAsync(string username, DismissUnitSquadRequest request)
|
||||||
|
{
|
||||||
|
var normalizedUnitType = NormalizeUnitType(request.UnitType);
|
||||||
|
if (normalizedUnitType != ScoutUnitType && normalizedUnitType != ArcherUnitType && normalizedUnitType != PeasantUnitType)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Only Scouts, Archers, and Peasants can be dismissed right now.", nameof(request.UnitType));
|
||||||
|
}
|
||||||
|
|
||||||
|
await CompletePendingMovementAsync(username);
|
||||||
|
|
||||||
|
var result = await _units.DeleteManyAsync(x =>
|
||||||
|
x.Username == username &&
|
||||||
|
x.UnitType == normalizedUnitType &&
|
||||||
|
x.Status == "Idle" &&
|
||||||
|
x.Q == request.Q &&
|
||||||
|
x.R == request.R);
|
||||||
|
|
||||||
|
return (int)result.DeletedCount;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Location?> GetBuiltBarracksAsync(string username, int q, int r)
|
private async Task<Location?> GetBuiltBarracksAsync(string username, int q, int r)
|
||||||
{
|
{
|
||||||
await CompleteFinishedBuildingsAsync(username);
|
await CompleteFinishedBuildingsAsync(username);
|
||||||
|
|||||||
Reference in New Issue
Block a user