Updating peasants
This commit is contained in:
@@ -16,6 +16,7 @@ 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 worker_request: HTTPRequest
|
||||||
var claim_request: HTTPRequest
|
var claim_request: HTTPRequest
|
||||||
var build_request: HTTPRequest
|
var build_request: HTTPRequest
|
||||||
var refresh_request: HTTPRequest
|
var refresh_request: HTTPRequest
|
||||||
@@ -32,6 +33,7 @@ var resource_text := "Gold: --\nWood: --\nOre: --\nFood: --"
|
|||||||
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 train_peasant_button: Button
|
||||||
var train_archer_button: Button
|
var train_archer_button: Button
|
||||||
var unit_label: Label
|
var unit_label: Label
|
||||||
var training_box: VBoxContainer
|
var training_box: VBoxContainer
|
||||||
@@ -57,6 +59,8 @@ var build_farm_button: Button
|
|||||||
var build_barracks_button: Button
|
var build_barracks_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_assign_button: Button
|
||||||
|
var unit_action_deassign_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
|
||||||
@@ -68,6 +72,10 @@ 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
|
||||||
var completed_archer_count := 0
|
var completed_archer_count := 0
|
||||||
|
var completed_peasant_count := 0
|
||||||
|
var selected_peasant_id := ""
|
||||||
|
var selected_peasant_q := 9999
|
||||||
|
var selected_peasant_r := 9999
|
||||||
var selected_archer_q := 9999
|
var selected_archer_q := 9999
|
||||||
var selected_archer_r := 9999
|
var selected_archer_r := 9999
|
||||||
var selected_archer_count := 0
|
var selected_archer_count := 0
|
||||||
@@ -218,6 +226,11 @@ func _build_ui() -> void:
|
|||||||
train_button.pressed.connect(_train_scout)
|
train_button.pressed.connect(_train_scout)
|
||||||
action_buttons.add_child(train_button)
|
action_buttons.add_child(train_button)
|
||||||
|
|
||||||
|
train_peasant_button = _small_button("Peasant 25g")
|
||||||
|
train_peasant_button.visible = false
|
||||||
|
train_peasant_button.pressed.connect(_train_peasant)
|
||||||
|
action_buttons.add_child(train_peasant_button)
|
||||||
|
|
||||||
train_archer_button = _small_button("Archer 100g 100w")
|
train_archer_button = _small_button("Archer 100g 100w")
|
||||||
train_archer_button.visible = false
|
train_archer_button.visible = false
|
||||||
train_archer_button.pressed.connect(_train_archer)
|
train_archer_button.pressed.connect(_train_archer)
|
||||||
@@ -279,6 +292,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)
|
||||||
|
|
||||||
|
worker_request = HTTPRequest.new()
|
||||||
|
worker_request.request_completed.connect(_on_worker_request_completed)
|
||||||
|
add_child(worker_request)
|
||||||
|
|
||||||
claim_request = HTTPRequest.new()
|
claim_request = HTTPRequest.new()
|
||||||
claim_request.request_completed.connect(_on_claim_request_completed)
|
claim_request.request_completed.connect(_on_claim_request_completed)
|
||||||
add_child(claim_request)
|
add_child(claim_request)
|
||||||
@@ -441,10 +458,24 @@ func _build_unit_action_menu() -> void:
|
|||||||
margin.add_theme_constant_override("margin_bottom", 8)
|
margin.add_theme_constant_override("margin_bottom", 8)
|
||||||
panel.add_child(margin)
|
panel.add_child(margin)
|
||||||
|
|
||||||
|
var actions := VBoxContainer.new()
|
||||||
|
actions.add_theme_constant_override("separation", 6)
|
||||||
|
margin.add_child(actions)
|
||||||
|
|
||||||
unit_action_move_button = _small_button("Move")
|
unit_action_move_button = _small_button("Move")
|
||||||
unit_action_move_button.custom_minimum_size = Vector2(92, 34)
|
unit_action_move_button.custom_minimum_size = Vector2(92, 34)
|
||||||
unit_action_move_button.pressed.connect(_confirm_unit_move)
|
unit_action_move_button.pressed.connect(_confirm_unit_move)
|
||||||
margin.add_child(unit_action_move_button)
|
actions.add_child(unit_action_move_button)
|
||||||
|
|
||||||
|
unit_action_assign_button = _small_button("Assign")
|
||||||
|
unit_action_assign_button.custom_minimum_size = Vector2(92, 34)
|
||||||
|
unit_action_assign_button.pressed.connect(_confirm_worker_assign)
|
||||||
|
actions.add_child(unit_action_assign_button)
|
||||||
|
|
||||||
|
unit_action_deassign_button = _small_button("Deassign")
|
||||||
|
unit_action_deassign_button.custom_minimum_size = Vector2(92, 34)
|
||||||
|
unit_action_deassign_button.pressed.connect(_confirm_worker_deassign)
|
||||||
|
actions.add_child(unit_action_deassign_button)
|
||||||
|
|
||||||
|
|
||||||
func _on_tile_hovered(q: int, r: int, terrain: String) -> void:
|
func _on_tile_hovered(q: int, r: int, terrain: String) -> void:
|
||||||
@@ -475,12 +506,26 @@ func _handle_unit_selection_or_move(q: int, r: int) -> bool:
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
var archer_count: int = grid_view.get_archer_count_at(q, r)
|
var archer_count: int = grid_view.get_archer_count_at(q, r)
|
||||||
|
var peasant_id: String = grid_view.get_peasant_id_at(q, r)
|
||||||
|
if not peasant_id.is_empty():
|
||||||
|
if unit_action_popup:
|
||||||
|
unit_action_popup.hide()
|
||||||
|
selected_peasant_id = peasant_id
|
||||||
|
selected_peasant_q = q
|
||||||
|
selected_peasant_r = r
|
||||||
|
selected_archer_count = 0
|
||||||
|
grid_view.set_selected_unit_tile(q, r)
|
||||||
|
if coordinate_label:
|
||||||
|
coordinate_label.text = "Selected Peasant at q:%d r:%d. Right-click a building to assign work." % [q, r]
|
||||||
|
return true
|
||||||
|
|
||||||
if archer_count > 0:
|
if archer_count > 0:
|
||||||
if unit_action_popup:
|
if unit_action_popup:
|
||||||
unit_action_popup.hide()
|
unit_action_popup.hide()
|
||||||
selected_archer_q = q
|
selected_archer_q = q
|
||||||
selected_archer_r = r
|
selected_archer_r = r
|
||||||
selected_archer_count = archer_count
|
selected_archer_count = archer_count
|
||||||
|
selected_peasant_id = ""
|
||||||
grid_view.set_selected_unit_tile(q, r)
|
grid_view.set_selected_unit_tile(q, r)
|
||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
coordinate_label.text = "Selected Archer squad: %d at q:%d r:%d. Right-click an adjacent visible tile for orders." % [archer_count, q, r]
|
coordinate_label.text = "Selected Archer squad: %d at q:%d r:%d. Right-click an adjacent visible tile for orders." % [archer_count, q, r]
|
||||||
@@ -492,17 +537,20 @@ func _handle_unit_selection_or_move(q: int, r: int) -> bool:
|
|||||||
func _on_tile_action_requested(q: int, r: int, _terrain: String, screen_position: Vector2) -> void:
|
func _on_tile_action_requested(q: int, r: int, _terrain: String, screen_position: Vector2) -> void:
|
||||||
if not grid_view:
|
if not grid_view:
|
||||||
return
|
return
|
||||||
if selected_archer_count <= 0:
|
var location: Dictionary = grid_view.get_visible_location(q, r)
|
||||||
|
var has_building := _location_has_completed_building(location)
|
||||||
|
var has_worker := not _nullable_string(location.get("workerUnitId", "")).is_empty()
|
||||||
|
if selected_archer_count <= 0 and selected_peasant_id.is_empty() 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 an Archer squad before giving orders."
|
coordinate_label.text = "Select a unit or right-click a staffed building."
|
||||||
return
|
return
|
||||||
if q == selected_archer_q and r == selected_archer_r:
|
if selected_archer_count > 0 and q == selected_archer_q and r == selected_archer_r:
|
||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
coordinate_label.text = "Archer squad is already on that tile."
|
coordinate_label.text = "Archer squad is already on that tile."
|
||||||
return
|
return
|
||||||
if not grid_view.is_neighbor(selected_archer_q, selected_archer_r, q, r):
|
if selected_archer_count > 0 and not grid_view.is_neighbor(selected_archer_q, selected_archer_r, q, r):
|
||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
coordinate_label.text = "Archers can only move to an adjacent tile."
|
coordinate_label.text = "Archers can only move to an adjacent tile."
|
||||||
return
|
return
|
||||||
@@ -514,9 +562,12 @@ func _on_tile_action_requested(q: int, r: int, _terrain: String, screen_position
|
|||||||
pending_move_q = q
|
pending_move_q = q
|
||||||
pending_move_r = r
|
pending_move_r = r
|
||||||
if coordinate_label:
|
if coordinate_label:
|
||||||
coordinate_label.text = "Orders for Archer squad: 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:
|
||||||
var popup_size := Vector2i(116, 52)
|
unit_action_move_button.visible = selected_archer_count > 0
|
||||||
|
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
|
||||||
|
var popup_size := Vector2i(116, 132)
|
||||||
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))
|
||||||
@@ -532,6 +583,30 @@ func _confirm_unit_move() -> void:
|
|||||||
_move_selected_archers(pending_move_q, pending_move_r)
|
_move_selected_archers(pending_move_q, pending_move_r)
|
||||||
|
|
||||||
|
|
||||||
|
func _confirm_worker_assign() -> void:
|
||||||
|
if unit_action_popup:
|
||||||
|
unit_action_popup.hide()
|
||||||
|
if selected_peasant_id.is_empty() or pending_move_q == 9999:
|
||||||
|
return
|
||||||
|
_request_worker_action("assign-worker", { "q": pending_move_q, "r": pending_move_r, "unitId": selected_peasant_id })
|
||||||
|
|
||||||
|
|
||||||
|
func _confirm_worker_deassign() -> void:
|
||||||
|
if unit_action_popup:
|
||||||
|
unit_action_popup.hide()
|
||||||
|
if pending_move_q == 9999:
|
||||||
|
return
|
||||||
|
_request_worker_action("deassign-worker", { "q": pending_move_q, "r": pending_move_r })
|
||||||
|
|
||||||
|
|
||||||
|
func _location_has_completed_building(location: Dictionary) -> bool:
|
||||||
|
if location.is_empty():
|
||||||
|
return false
|
||||||
|
var building_type: String = _nullable_string(location.get("buildingType", ""))
|
||||||
|
var building_status: String = _nullable_string(location.get("buildingStatus", ""))
|
||||||
|
return not building_type.is_empty() and (building_status == "Built" or building_status.is_empty())
|
||||||
|
|
||||||
|
|
||||||
func _can_build_log_camp_on_selected_tile() -> bool:
|
func _can_build_log_camp_on_selected_tile() -> bool:
|
||||||
return _can_build_on_selected_tile("Forest")
|
return _can_build_on_selected_tile("Forest")
|
||||||
|
|
||||||
@@ -577,6 +652,9 @@ func _update_action_buttons() -> void:
|
|||||||
if train_button:
|
if train_button:
|
||||||
train_button.visible = selected_is_home
|
train_button.visible = selected_is_home
|
||||||
train_button.disabled = false
|
train_button.disabled = false
|
||||||
|
if train_peasant_button:
|
||||||
|
train_peasant_button.visible = selected_is_home
|
||||||
|
train_peasant_button.disabled = false
|
||||||
if train_archer_button:
|
if train_archer_button:
|
||||||
train_archer_button.visible = selected_can_train_archer
|
train_archer_button.visible = selected_can_train_archer
|
||||||
train_archer_button.disabled = false
|
train_archer_button.disabled = false
|
||||||
@@ -601,6 +679,9 @@ func _clear_selected_archers() -> void:
|
|||||||
selected_archer_q = 9999
|
selected_archer_q = 9999
|
||||||
selected_archer_r = 9999
|
selected_archer_r = 9999
|
||||||
selected_archer_count = 0
|
selected_archer_count = 0
|
||||||
|
selected_peasant_id = ""
|
||||||
|
selected_peasant_q = 9999
|
||||||
|
selected_peasant_r = 9999
|
||||||
pending_move_q = 9999
|
pending_move_q = 9999
|
||||||
pending_move_r = 9999
|
pending_move_r = 9999
|
||||||
if unit_action_popup:
|
if unit_action_popup:
|
||||||
@@ -952,6 +1033,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))
|
||||||
completed_archer_count = int(units.get("completedArchers", 0))
|
completed_archer_count = int(units.get("completedArchers", 0))
|
||||||
|
completed_peasant_count = int(units.get("completedPeasants", 0))
|
||||||
if grid_view:
|
if grid_view:
|
||||||
grid_view.set_completed_archers(completed_archer_count)
|
grid_view.set_completed_archers(completed_archer_count)
|
||||||
var unit_instances: Array = []
|
var unit_instances: Array = []
|
||||||
@@ -959,7 +1041,7 @@ func _on_units_request_completed(result: int, response_code: int, _headers: Pack
|
|||||||
if typeof(raw_units) == TYPE_ARRAY:
|
if typeof(raw_units) == TYPE_ARRAY:
|
||||||
unit_instances = raw_units
|
unit_instances = raw_units
|
||||||
grid_view.set_unit_instances(unit_instances)
|
grid_view.set_unit_instances(unit_instances)
|
||||||
unit_label.text = "Scouts: %d | Archers: %d | Queue: %d" % [completed_scout_count, completed_archer_count, training.size()]
|
unit_label.text = "Scouts: %d | Peasants: %d | Archers: %d | Queue: %d" % [completed_scout_count, completed_peasant_count, completed_archer_count, training.size()]
|
||||||
_update_action_buttons()
|
_update_action_buttons()
|
||||||
|
|
||||||
if training.is_empty():
|
if training.is_empty():
|
||||||
@@ -985,6 +1067,14 @@ func _train_scout() -> void:
|
|||||||
_request_unit_training("Scout")
|
_request_unit_training("Scout")
|
||||||
|
|
||||||
|
|
||||||
|
func _train_peasant() -> void:
|
||||||
|
if not selected_is_home:
|
||||||
|
coordinate_label.text = "Select your town hall to train Peasants."
|
||||||
|
return
|
||||||
|
|
||||||
|
_request_unit_training("Peasant")
|
||||||
|
|
||||||
|
|
||||||
func _train_archer() -> void:
|
func _train_archer() -> void:
|
||||||
if not selected_can_train_archer:
|
if not selected_can_train_archer:
|
||||||
coordinate_label.text = "Select a completed Barracks to train Archers."
|
coordinate_label.text = "Select a completed Barracks to train Archers."
|
||||||
@@ -1004,6 +1094,8 @@ func _request_unit_training(unit_type: String) -> void:
|
|||||||
|
|
||||||
if unit_type == "Archer":
|
if unit_type == "Archer":
|
||||||
train_archer_button.disabled = true
|
train_archer_button.disabled = true
|
||||||
|
elif unit_type == "Peasant":
|
||||||
|
train_peasant_button.disabled = true
|
||||||
else:
|
else:
|
||||||
train_button.disabled = true
|
train_button.disabled = true
|
||||||
coordinate_label.text = "Starting %s training..." % unit_type
|
coordinate_label.text = "Starting %s training..." % unit_type
|
||||||
@@ -1106,6 +1198,42 @@ func _on_move_units_request_completed(result: int, response_code: int, _headers:
|
|||||||
_load_visible_locations()
|
_load_visible_locations()
|
||||||
|
|
||||||
|
|
||||||
|
func _request_worker_action(endpoint: String, body: Dictionary) -> void:
|
||||||
|
var access_token: String = str(session.get("accessToken", ""))
|
||||||
|
if access_token.is_empty():
|
||||||
|
coordinate_label.text = "No auth token available. Worker assignment failed."
|
||||||
|
return
|
||||||
|
if worker_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
||||||
|
return
|
||||||
|
|
||||||
|
var error: int = worker_request.request(
|
||||||
|
LOCATIONS_BASE_URL + "/api/locations/" + endpoint,
|
||||||
|
PackedStringArray([
|
||||||
|
"Content-Type: application/json",
|
||||||
|
"Authorization: Bearer %s" % access_token
|
||||||
|
]),
|
||||||
|
HTTPClient.METHOD_POST,
|
||||||
|
JSON.stringify(body)
|
||||||
|
)
|
||||||
|
if error != OK:
|
||||||
|
coordinate_label.text = "Could not contact the worker assignment service."
|
||||||
|
|
||||||
|
|
||||||
|
func _on_worker_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 = "Worker request failed."
|
||||||
|
return
|
||||||
|
if response_code < 200 or response_code >= 300:
|
||||||
|
coordinate_label.text = "Worker assignment failed: %s" % text
|
||||||
|
_refresh_player_state()
|
||||||
|
return
|
||||||
|
|
||||||
|
_clear_selected_archers()
|
||||||
|
coordinate_label.text = "Worker assignment updated."
|
||||||
|
_load_visible_locations()
|
||||||
|
|
||||||
|
|
||||||
func _claim_selected_tile() -> void:
|
func _claim_selected_tile() -> void:
|
||||||
if not selected_can_claim:
|
if not selected_can_claim:
|
||||||
coordinate_label.text = "Select a visible unclaimed tile to send a scout."
|
coordinate_label.text = "Select a visible unclaimed tile to send a scout."
|
||||||
@@ -1393,6 +1521,8 @@ func _unit_display_name(unit_type: String) -> String:
|
|||||||
return "Scout"
|
return "Scout"
|
||||||
"Archer":
|
"Archer":
|
||||||
return "Archer"
|
return "Archer"
|
||||||
|
"Peasant":
|
||||||
|
return "Peasant"
|
||||||
_:
|
_:
|
||||||
return "Training"
|
return "Training"
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func _gui_input(event: InputEvent) -> void:
|
|||||||
if event.pressed:
|
if event.pressed:
|
||||||
drag_distance = 0.0
|
drag_distance = 0.0
|
||||||
if not event.pressed and drag_distance < 6.0 and hovered_tile.x != 9999:
|
if not event.pressed and drag_distance < 6.0 and hovered_tile.x != 9999:
|
||||||
var clicked_unit_tile := _archer_tile_at(event.position)
|
var clicked_unit_tile := _unit_tile_at(event.position)
|
||||||
selected_tile = clicked_unit_tile if clicked_unit_tile.x != 9999 else hovered_tile
|
selected_tile = clicked_unit_tile if clicked_unit_tile.x != 9999 else hovered_tile
|
||||||
var data: Dictionary = _tile_data(selected_tile.x, selected_tile.y)
|
var data: Dictionary = _tile_data(selected_tile.x, selected_tile.y)
|
||||||
var key: String = _tile_key(selected_tile.x, selected_tile.y)
|
var key: String = _tile_key(selected_tile.x, selected_tile.y)
|
||||||
@@ -194,6 +194,9 @@ func _draw_tile(tile: Dictionary) -> void:
|
|||||||
var archer_count := _archer_count_at(q, r)
|
var archer_count := _archer_count_at(q, r)
|
||||||
if archer_count > 0:
|
if archer_count > 0:
|
||||||
_draw_archer_unit_marker(center, height, archer_count)
|
_draw_archer_unit_marker(center, height, archer_count)
|
||||||
|
var peasant_count := _peasant_count_at(q, r)
|
||||||
|
if peasant_count > 0:
|
||||||
|
_draw_peasant_unit_marker(center, height, peasant_count)
|
||||||
|
|
||||||
|
|
||||||
func _draw_control_regions(sorted_tiles: Array) -> void:
|
func _draw_control_regions(sorted_tiles: Array) -> void:
|
||||||
@@ -542,6 +545,56 @@ func _archer_tile_at(mouse_position: Vector2) -> Vector2i:
|
|||||||
return closest
|
return closest
|
||||||
|
|
||||||
|
|
||||||
|
func _unit_tile_at(mouse_position: Vector2) -> Vector2i:
|
||||||
|
var peasant_tile := _peasant_tile_at(mouse_position)
|
||||||
|
if peasant_tile.x != 9999:
|
||||||
|
return peasant_tile
|
||||||
|
return _archer_tile_at(mouse_position)
|
||||||
|
|
||||||
|
|
||||||
|
func _peasant_marker_center(center: Vector2, height: float) -> Vector2:
|
||||||
|
return center + Vector2(-16.0 * zoom, -height * zoom - 12.0 * zoom)
|
||||||
|
|
||||||
|
|
||||||
|
func _peasant_tile_at(mouse_position: Vector2) -> Vector2i:
|
||||||
|
var closest := Vector2i(9999, 9999)
|
||||||
|
var closest_distance := 999999.0
|
||||||
|
for tile in tiles:
|
||||||
|
var q: int = int(tile["q"])
|
||||||
|
var r: int = int(tile["r"])
|
||||||
|
if _peasant_count_at(q, r) <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
var center: Vector2 = _tile_center(q, r)
|
||||||
|
var height: float = float(tile["height"])
|
||||||
|
var distance: float = _peasant_marker_center(center, height).distance_to(mouse_position)
|
||||||
|
if distance < closest_distance:
|
||||||
|
closest_distance = distance
|
||||||
|
closest = Vector2i(q, r)
|
||||||
|
|
||||||
|
if closest_distance > 24.0 * zoom:
|
||||||
|
return Vector2i(9999, 9999)
|
||||||
|
return closest
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_peasant_unit_marker(center: Vector2, height: float, count: int) -> void:
|
||||||
|
var marker_center: Vector2 = _peasant_marker_center(center, height)
|
||||||
|
var marker_scale: float = zoom
|
||||||
|
var tunic_color := Color(0.54, 0.42, 0.24)
|
||||||
|
var skin_color := Color(0.78, 0.58, 0.38)
|
||||||
|
var tool_color := Color(0.55, 0.42, 0.22)
|
||||||
|
var outline_color := Color(0.05, 0.04, 0.03)
|
||||||
|
|
||||||
|
draw_circle(marker_center + Vector2(1, 2) * marker_scale, 9.0 * marker_scale, Color(0.01, 0.02, 0.02, 0.45))
|
||||||
|
draw_rect(Rect2(marker_center + Vector2(-5, -1) * marker_scale, Vector2(10, 12) * marker_scale), tunic_color, true)
|
||||||
|
draw_circle(marker_center + Vector2(0, -7) * marker_scale, 4.5 * marker_scale, skin_color)
|
||||||
|
draw_line(marker_center + Vector2(7, -8) * marker_scale, marker_center + Vector2(13, 8) * marker_scale, tool_color, 2.0 * marker_scale)
|
||||||
|
draw_line(marker_center + Vector2(10, -2) * marker_scale, marker_center + Vector2(16, -4) * marker_scale, tool_color, 1.5 * marker_scale)
|
||||||
|
draw_rect(Rect2(marker_center + Vector2(-5, -1) * marker_scale, Vector2(10, 12) * marker_scale), outline_color, false, 1.0 * marker_scale)
|
||||||
|
if count > 1:
|
||||||
|
draw_string(ThemeDB.fallback_font, marker_center + Vector2(7, 10) * marker_scale, str(count), HORIZONTAL_ALIGNMENT_LEFT, -1, int(9 * marker_scale), Color(0.94, 0.86, 0.6))
|
||||||
|
|
||||||
|
|
||||||
func _tile_data(q: int, r: int) -> Dictionary:
|
func _tile_data(q: int, r: int) -> Dictionary:
|
||||||
for tile in tiles:
|
for tile in tiles:
|
||||||
if int(tile["q"]) == q and int(tile["r"]) == r:
|
if int(tile["q"]) == q and int(tile["r"]) == r:
|
||||||
@@ -633,6 +686,15 @@ func get_archer_count_at(q: int, r: int) -> int:
|
|||||||
return _archer_count_at(q, r)
|
return _archer_count_at(q, r)
|
||||||
|
|
||||||
|
|
||||||
|
func get_peasant_id_at(q: int, r: int) -> String:
|
||||||
|
for unit in unit_instances:
|
||||||
|
if str(unit.get("unitType", "")) != "Peasant" or str(unit.get("status", "")) != "Idle":
|
||||||
|
continue
|
||||||
|
if int(unit.get("q", 0)) == q and int(unit.get("r", 0)) == r:
|
||||||
|
return str(unit.get("id", ""))
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
func is_visible_non_rival_tile(q: int, r: int) -> bool:
|
func is_visible_non_rival_tile(q: int, r: int) -> bool:
|
||||||
var key := _tile_key(q, r)
|
var key := _tile_key(q, r)
|
||||||
return visible_tiles.has(key) and not rival_tiles.has(key)
|
return visible_tiles.has(key) and not rival_tiles.has(key)
|
||||||
@@ -691,6 +753,9 @@ 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 = _nullable_string(data.get("buildingType", ""))
|
var building_type: String = _nullable_string(data.get("buildingType", ""))
|
||||||
var building_status: String = _nullable_string(data.get("buildingStatus", ""))
|
var building_status: String = _nullable_string(data.get("buildingStatus", ""))
|
||||||
|
var worker_unit_id: String = _nullable_string(data.get("workerUnitId", ""))
|
||||||
|
var stored_resource: int = int(data.get("storedResourceAmount", 0))
|
||||||
|
var stored_capacity: int = int(data.get("storedResourceCapacity", 0))
|
||||||
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"
|
||||||
@@ -700,6 +765,10 @@ func _tile_display_label(key: String, data: Dictionary) -> String:
|
|||||||
building_label = " | Farm" if building_status == "Built" or building_status.is_empty() else " | Farm building"
|
building_label = " | Farm" if building_status == "Built" or building_status.is_empty() else " | Farm building"
|
||||||
elif building_type == "Barracks":
|
elif building_type == "Barracks":
|
||||||
building_label = " | Barracks" if building_status == "Built" or building_status.is_empty() else " | Barracks building"
|
building_label = " | Barracks" if building_status == "Built" or building_status.is_empty() else " | Barracks building"
|
||||||
|
if not building_label.is_empty() and not worker_unit_id.is_empty():
|
||||||
|
building_label += " staffed"
|
||||||
|
if not building_label.is_empty() and stored_capacity > 0:
|
||||||
|
building_label += " %d/%d" % [stored_resource, stored_capacity]
|
||||||
var archer_count := _archer_count_at(int(data.get("q", 0)), int(data.get("r", 0)))
|
var archer_count := _archer_count_at(int(data.get("q", 0)), int(data.get("r", 0)))
|
||||||
if archer_count > 0:
|
if archer_count > 0:
|
||||||
building_label += " | Archers: %d" % archer_count
|
building_label += " | Archers: %d" % archer_count
|
||||||
@@ -749,6 +818,16 @@ func _archer_count_at(q: int, r: int) -> int:
|
|||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
func _peasant_count_at(q: int, r: int) -> int:
|
||||||
|
var count := 0
|
||||||
|
for unit in unit_instances:
|
||||||
|
if str(unit.get("unitType", "")) != "Peasant" or str(unit.get("status", "")) != "Idle":
|
||||||
|
continue
|
||||||
|
if int(unit.get("q", 0)) == q and int(unit.get("r", 0)) == r:
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
func _owner_username(location: Dictionary) -> String:
|
func _owner_username(location: Dictionary) -> String:
|
||||||
var owner_value: Variant = location.get("ownerUsername", "")
|
var owner_value: Variant = location.get("ownerUsername", "")
|
||||||
if owner_value == null:
|
if owner_value == null:
|
||||||
|
|||||||
@@ -148,6 +148,38 @@ public class LocationsController : ControllerBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Roles = "USER,SUPER")]
|
||||||
|
[HttpPost("assign-worker")]
|
||||||
|
public async Task<ActionResult<LocationResponse>> AssignWorker([FromBody] AssignWorkerRequest request)
|
||||||
|
{
|
||||||
|
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||||
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
var location = await _locations.AssignWorkerAsync(username, request);
|
||||||
|
return location is null
|
||||||
|
? BadRequest("Select an idle Peasant and a controlled completed building without a worker.")
|
||||||
|
: Ok(LocationResponse.FromLocation(location));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Roles = "USER,SUPER")]
|
||||||
|
[HttpPost("deassign-worker")]
|
||||||
|
public async Task<ActionResult<LocationResponse>> DeassignWorker([FromBody] DeassignWorkerRequest request)
|
||||||
|
{
|
||||||
|
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||||
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
var location = await _locations.DeassignWorkerAsync(username, request);
|
||||||
|
return location is null
|
||||||
|
? BadRequest("Select a controlled building with an assigned worker.")
|
||||||
|
: Ok(LocationResponse.FromLocation(location));
|
||||||
|
}
|
||||||
|
|
||||||
[Authorize(Roles = "USER,SUPER")]
|
[Authorize(Roles = "USER,SUPER")]
|
||||||
[HttpGet("home")]
|
[HttpGet("home")]
|
||||||
public async Task<ActionResult<LocationResponse>> GetHome()
|
public async Task<ActionResult<LocationResponse>> GetHome()
|
||||||
|
|||||||
@@ -22,6 +22,19 @@ public class BuildLocationRequest
|
|||||||
public string BuildingType { get; set; } = "LogCamp";
|
public string BuildingType { get; set; } = "LogCamp";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AssignWorkerRequest
|
||||||
|
{
|
||||||
|
public int Q { get; set; }
|
||||||
|
public int R { get; set; }
|
||||||
|
public string UnitId { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DeassignWorkerRequest
|
||||||
|
{
|
||||||
|
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; } = "";
|
||||||
@@ -37,6 +50,11 @@ public class LocationResponse
|
|||||||
public DateTime? BuildingStartedAtUtc { get; set; }
|
public DateTime? BuildingStartedAtUtc { get; set; }
|
||||||
public DateTime? BuildingCompletesAtUtc { get; set; }
|
public DateTime? BuildingCompletesAtUtc { get; set; }
|
||||||
public DateTime? BuildingBuiltAtUtc { get; set; }
|
public DateTime? BuildingBuiltAtUtc { get; set; }
|
||||||
|
public string? WorkerUnitId { get; set; }
|
||||||
|
public string? WorkerUnitType { get; set; }
|
||||||
|
public int StoredResourceAmount { get; set; }
|
||||||
|
public int StoredResourceCapacity { get; set; }
|
||||||
|
public DateTime? LastResourceGeneratedAtUtc { get; set; }
|
||||||
public DateTime? HomeAssignedAtUtc { get; set; }
|
public DateTime? HomeAssignedAtUtc { get; set; }
|
||||||
public DateTime UpdatedAtUtc { get; set; }
|
public DateTime UpdatedAtUtc { get; set; }
|
||||||
public bool IsVisible { get; set; } = true;
|
public bool IsVisible { get; set; } = true;
|
||||||
@@ -58,6 +76,11 @@ public class LocationResponse
|
|||||||
BuildingStartedAtUtc = revealDetails ? location.BuildingStartedAtUtc : null,
|
BuildingStartedAtUtc = revealDetails ? location.BuildingStartedAtUtc : null,
|
||||||
BuildingCompletesAtUtc = revealDetails ? location.BuildingCompletesAtUtc : null,
|
BuildingCompletesAtUtc = revealDetails ? location.BuildingCompletesAtUtc : null,
|
||||||
BuildingBuiltAtUtc = revealDetails ? location.BuildingBuiltAtUtc : null,
|
BuildingBuiltAtUtc = revealDetails ? location.BuildingBuiltAtUtc : null,
|
||||||
|
WorkerUnitId = revealDetails ? location.WorkerUnitId : null,
|
||||||
|
WorkerUnitType = revealDetails ? location.WorkerUnitType : null,
|
||||||
|
StoredResourceAmount = revealDetails ? location.StoredResourceAmount : 0,
|
||||||
|
StoredResourceCapacity = revealDetails ? location.StoredResourceCapacity : 0,
|
||||||
|
LastResourceGeneratedAtUtc = revealDetails ? location.LastResourceGeneratedAtUtc : null,
|
||||||
HomeAssignedAtUtc = location.HomeAssignedAtUtc,
|
HomeAssignedAtUtc = location.HomeAssignedAtUtc,
|
||||||
UpdatedAtUtc = location.UpdatedAtUtc,
|
UpdatedAtUtc = location.UpdatedAtUtc,
|
||||||
IsVisible = true
|
IsVisible = true
|
||||||
@@ -80,6 +103,11 @@ public class LocationResponse
|
|||||||
BuildingStartedAtUtc = intel.BuildingStartedAtUtc,
|
BuildingStartedAtUtc = intel.BuildingStartedAtUtc,
|
||||||
BuildingCompletesAtUtc = intel.BuildingCompletesAtUtc,
|
BuildingCompletesAtUtc = intel.BuildingCompletesAtUtc,
|
||||||
BuildingBuiltAtUtc = intel.BuildingBuiltAtUtc,
|
BuildingBuiltAtUtc = intel.BuildingBuiltAtUtc,
|
||||||
|
WorkerUnitId = intel.WorkerUnitId,
|
||||||
|
WorkerUnitType = intel.WorkerUnitType,
|
||||||
|
StoredResourceAmount = intel.StoredResourceAmount,
|
||||||
|
StoredResourceCapacity = intel.StoredResourceCapacity,
|
||||||
|
LastResourceGeneratedAtUtc = intel.LastResourceGeneratedAtUtc,
|
||||||
HomeAssignedAtUtc = intel.HomeAssignedAtUtc,
|
HomeAssignedAtUtc = intel.HomeAssignedAtUtc,
|
||||||
UpdatedAtUtc = intel.ObservedAtUtc,
|
UpdatedAtUtc = intel.ObservedAtUtc,
|
||||||
IsVisible = true
|
IsVisible = true
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ public class Location
|
|||||||
public DateTime? BuildingStartedAtUtc { get; set; }
|
public DateTime? BuildingStartedAtUtc { get; set; }
|
||||||
public DateTime? BuildingCompletesAtUtc { get; set; }
|
public DateTime? BuildingCompletesAtUtc { get; set; }
|
||||||
public DateTime? BuildingBuiltAtUtc { get; set; }
|
public DateTime? BuildingBuiltAtUtc { get; set; }
|
||||||
|
public string? WorkerUnitId { get; set; }
|
||||||
|
public string? WorkerUnitType { get; set; }
|
||||||
|
public int StoredResourceAmount { get; set; }
|
||||||
|
public int StoredResourceCapacity { get; set; } = 10;
|
||||||
|
public DateTime? LastResourceGeneratedAtUtc { get; set; }
|
||||||
public DateTime? HomeAssignedAtUtc { get; set; }
|
public DateTime? HomeAssignedAtUtc { get; set; }
|
||||||
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime UpdatedAtUtc { get; set; } = DateTime.UtcNow;
|
public DateTime UpdatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ public class LocationIntel
|
|||||||
public DateTime? BuildingStartedAtUtc { get; set; }
|
public DateTime? BuildingStartedAtUtc { get; set; }
|
||||||
public DateTime? BuildingCompletesAtUtc { get; set; }
|
public DateTime? BuildingCompletesAtUtc { get; set; }
|
||||||
public DateTime? BuildingBuiltAtUtc { get; set; }
|
public DateTime? BuildingBuiltAtUtc { get; set; }
|
||||||
|
public string? WorkerUnitId { get; set; }
|
||||||
|
public string? WorkerUnitType { get; set; }
|
||||||
|
public int StoredResourceAmount { get; set; }
|
||||||
|
public int StoredResourceCapacity { get; set; } = 10;
|
||||||
|
public DateTime? LastResourceGeneratedAtUtc { get; set; }
|
||||||
public DateTime? HomeAssignedAtUtc { get; set; }
|
public DateTime? HomeAssignedAtUtc { get; set; }
|
||||||
public DateTime ObservedAtUtc { get; set; } = DateTime.UtcNow;
|
public DateTime ObservedAtUtc { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ public class PlayerUnitsResponse
|
|||||||
{
|
{
|
||||||
public int CompletedScouts { get; set; }
|
public int CompletedScouts { get; set; }
|
||||||
public int CompletedArchers { get; set; }
|
public int CompletedArchers { get; set; }
|
||||||
|
public int CompletedPeasants { get; set; }
|
||||||
public List<UnitTrainingResponse> Training { get; set; } = [];
|
public List<UnitTrainingResponse> Training { get; set; } = [];
|
||||||
public List<UnitInstanceResponse> Units { get; set; } = [];
|
public List<UnitInstanceResponse> Units { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ public class GameSettingsService
|
|||||||
{
|
{
|
||||||
public const string ScoutTrainingSeconds = "ScoutTrainingSeconds";
|
public const string ScoutTrainingSeconds = "ScoutTrainingSeconds";
|
||||||
public const string ArcherTrainingSeconds = "ArcherTrainingSeconds";
|
public const string ArcherTrainingSeconds = "ArcherTrainingSeconds";
|
||||||
|
public const string PeasantTrainingSeconds = "PeasantTrainingSeconds";
|
||||||
public const string ScoutGoldCost = "ScoutGoldCost";
|
public const string ScoutGoldCost = "ScoutGoldCost";
|
||||||
public const string ArcherGoldCost = "ArcherGoldCost";
|
public const string ArcherGoldCost = "ArcherGoldCost";
|
||||||
public const string ArcherWoodCost = "ArcherWoodCost";
|
public const string ArcherWoodCost = "ArcherWoodCost";
|
||||||
|
public const string PeasantGoldCost = "PeasantGoldCost";
|
||||||
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";
|
public const string BarracksBuildSeconds = "BarracksBuildSeconds";
|
||||||
@@ -20,6 +22,7 @@ public class GameSettingsService
|
|||||||
public const string LogCampWoodPerMinute = "LogCampWoodPerMinute";
|
public const string LogCampWoodPerMinute = "LogCampWoodPerMinute";
|
||||||
public const string MineOrePerMinute = "MineOrePerMinute";
|
public const string MineOrePerMinute = "MineOrePerMinute";
|
||||||
public const string FarmFoodPerMinute = "FarmFoodPerMinute";
|
public const string FarmFoodPerMinute = "FarmFoodPerMinute";
|
||||||
|
public const string ProductionBuildingResourceCapacity = "ProductionBuildingResourceCapacity";
|
||||||
public const string StartingGold = "StartingGold";
|
public const string StartingGold = "StartingGold";
|
||||||
public const string HeadquartersGoldPerMinute = "HeadquartersGoldPerMinute";
|
public const string HeadquartersGoldPerMinute = "HeadquartersGoldPerMinute";
|
||||||
public const string TownHallGoldCapacity = "TownHallGoldCapacity";
|
public const string TownHallGoldCapacity = "TownHallGoldCapacity";
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ public class LocationService
|
|||||||
var update = Builders<Location>.Update
|
var update = Builders<Location>.Update
|
||||||
.Set(x => x.BuildingStatus, "Built")
|
.Set(x => x.BuildingStatus, "Built")
|
||||||
.Set(x => x.BuildingBuiltAtUtc, now)
|
.Set(x => x.BuildingBuiltAtUtc, now)
|
||||||
|
.Set(x => x.StoredResourceCapacity, 10)
|
||||||
.Set(x => x.UpdatedAtUtc, now);
|
.Set(x => x.UpdatedAtUtc, now);
|
||||||
|
|
||||||
var updated = await _locations.FindOneAndUpdateAsync(
|
var updated = await _locations.FindOneAndUpdateAsync(
|
||||||
@@ -275,25 +276,120 @@ public class LocationService
|
|||||||
|
|
||||||
if (updated is not null)
|
if (updated is not null)
|
||||||
{
|
{
|
||||||
if (updated.BuildingType == "LogCamp")
|
await RecalculateWorkerGenerationAsync(username);
|
||||||
{
|
|
||||||
var woodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.LogCampWoodPerMinute, _logCampWoodPerMinute);
|
|
||||||
await _resources.AddWoodGenerationAsync(username, woodPerMinute);
|
|
||||||
}
|
|
||||||
else if (updated.BuildingType == "Mine")
|
|
||||||
{
|
|
||||||
var orePerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.MineOrePerMinute, _mineOrePerMinute);
|
|
||||||
await _resources.AddOreGenerationAsync(username, orePerMinute);
|
|
||||||
}
|
|
||||||
else if (updated.BuildingType == "Farm")
|
|
||||||
{
|
|
||||||
var foodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.FarmFoodPerMinute, _farmFoodPerMinute);
|
|
||||||
await _resources.AddFoodGenerationAsync(username, foodPerMinute);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Location?> AssignWorkerAsync(string username, AssignWorkerRequest request)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var location = await GetAsync(request.Q, request.R);
|
||||||
|
if (location is null ||
|
||||||
|
!string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.IsNullOrWhiteSpace(location.BuildingType) ||
|
||||||
|
location.BuildingStatus == "Building" ||
|
||||||
|
!string.IsNullOrWhiteSpace(location.WorkerUnitId))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var unit = await _units.Find(x =>
|
||||||
|
x.Id == request.UnitId &&
|
||||||
|
x.Username == username &&
|
||||||
|
x.UnitType == "Peasant" &&
|
||||||
|
x.Status == "Idle")
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (unit is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var claimed = await _units.UpdateOneAsync(
|
||||||
|
x => x.Id == unit.Id && x.Status == "Idle",
|
||||||
|
Builders<UnitInstance>.Update
|
||||||
|
.Set(x => x.Q, request.Q)
|
||||||
|
.Set(x => x.R, request.R)
|
||||||
|
.Set(x => x.Status, "Working")
|
||||||
|
.Set(x => x.UpdatedAtUtc, now));
|
||||||
|
if (claimed.ModifiedCount == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var updated = await _locations.FindOneAndUpdateAsync(
|
||||||
|
x => x.Id == location.Id && (x.WorkerUnitId == null || x.WorkerUnitId == ""),
|
||||||
|
Builders<Location>.Update
|
||||||
|
.Set(x => x.WorkerUnitId, unit.Id)
|
||||||
|
.Set(x => x.WorkerUnitType, "Peasant")
|
||||||
|
.Set(x => x.LastResourceGeneratedAtUtc, now)
|
||||||
|
.Set(x => x.UpdatedAtUtc, now),
|
||||||
|
new FindOneAndUpdateOptions<Location> { ReturnDocument = ReturnDocument.After });
|
||||||
|
|
||||||
|
if (updated is null)
|
||||||
|
{
|
||||||
|
await _units.UpdateOneAsync(
|
||||||
|
x => x.Id == unit.Id,
|
||||||
|
Builders<UnitInstance>.Update
|
||||||
|
.Set(x => x.Status, "Idle")
|
||||||
|
.Set(x => x.UpdatedAtUtc, DateTime.UtcNow));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await RecalculateWorkerGenerationAsync(username);
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Location?> DeassignWorkerAsync(string username, DeassignWorkerRequest request)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var location = await GetAsync(request.Q, request.R);
|
||||||
|
if (location is null ||
|
||||||
|
!string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.IsNullOrWhiteSpace(location.WorkerUnitId))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _units.UpdateOneAsync(
|
||||||
|
x => x.Id == location.WorkerUnitId && x.Username == username && x.Status == "Working",
|
||||||
|
Builders<UnitInstance>.Update
|
||||||
|
.Set(x => x.Q, request.Q)
|
||||||
|
.Set(x => x.R, request.R)
|
||||||
|
.Set(x => x.Status, "Idle")
|
||||||
|
.Set(x => x.UpdatedAtUtc, now));
|
||||||
|
|
||||||
|
var updated = await _locations.FindOneAndUpdateAsync(
|
||||||
|
x => x.Id == location.Id,
|
||||||
|
Builders<Location>.Update
|
||||||
|
.Unset(x => x.WorkerUnitId)
|
||||||
|
.Unset(x => x.WorkerUnitType)
|
||||||
|
.Set(x => x.UpdatedAtUtc, now),
|
||||||
|
new FindOneAndUpdateOptions<Location> { ReturnDocument = ReturnDocument.After });
|
||||||
|
|
||||||
|
await RecalculateWorkerGenerationAsync(username);
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RecalculateWorkerGenerationAsync(string username)
|
||||||
|
{
|
||||||
|
var built = await _locations.Find(x =>
|
||||||
|
x.OwnerUsername == username &&
|
||||||
|
!string.IsNullOrWhiteSpace(x.WorkerUnitId) &&
|
||||||
|
(x.BuildingStatus == "Built" || x.BuildingStatus == null || x.BuildingStatus == "") &&
|
||||||
|
(x.BuildingType == "LogCamp" || x.BuildingType == "Mine" || x.BuildingType == "Farm"))
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var woodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.LogCampWoodPerMinute, _logCampWoodPerMinute);
|
||||||
|
var orePerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.MineOrePerMinute, _mineOrePerMinute);
|
||||||
|
var foodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.FarmFoodPerMinute, _farmFoodPerMinute);
|
||||||
|
await _resources.SetBuildingGenerationAsync(
|
||||||
|
username,
|
||||||
|
built.Count(x => x.BuildingType == "LogCamp") * woodPerMinute,
|
||||||
|
built.Count(x => x.BuildingType == "Mine") * orePerMinute,
|
||||||
|
built.Count(x => x.BuildingType == "Farm") * foodPerMinute);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Location?> GetHomeAsync(string username) =>
|
public async Task<Location?> GetHomeAsync(string username) =>
|
||||||
await _locations.Find(x => x.OwnerUsername == username && x.Kind == "Home").FirstOrDefaultAsync();
|
await _locations.Find(x => x.OwnerUsername == username && x.Kind == "Home").FirstOrDefaultAsync();
|
||||||
|
|
||||||
@@ -337,6 +433,7 @@ public class LocationService
|
|||||||
public async Task<List<LocationResponse>> GetVisibleAsync(string username, int? visibilityDepth = null)
|
public async Task<List<LocationResponse>> GetVisibleAsync(string username, int? visibilityDepth = null)
|
||||||
{
|
{
|
||||||
await CompleteFinishedBuildingsAsync(username);
|
await CompleteFinishedBuildingsAsync(username);
|
||||||
|
await RecalculateWorkerGenerationAsync(username);
|
||||||
|
|
||||||
var ownedLocations = await _locations.Find(x => x.OwnerUsername == username).ToListAsync();
|
var ownedLocations = await _locations.Find(x => x.OwnerUsername == username).ToListAsync();
|
||||||
if (ownedLocations.Count == 0)
|
if (ownedLocations.Count == 0)
|
||||||
@@ -551,6 +648,11 @@ public class LocationService
|
|||||||
.Set(x => x.BuildingStartedAtUtc, location.BuildingStartedAtUtc)
|
.Set(x => x.BuildingStartedAtUtc, location.BuildingStartedAtUtc)
|
||||||
.Set(x => x.BuildingCompletesAtUtc, location.BuildingCompletesAtUtc)
|
.Set(x => x.BuildingCompletesAtUtc, location.BuildingCompletesAtUtc)
|
||||||
.Set(x => x.BuildingBuiltAtUtc, location.BuildingBuiltAtUtc)
|
.Set(x => x.BuildingBuiltAtUtc, location.BuildingBuiltAtUtc)
|
||||||
|
.Set(x => x.WorkerUnitId, location.WorkerUnitId)
|
||||||
|
.Set(x => x.WorkerUnitType, location.WorkerUnitType)
|
||||||
|
.Set(x => x.StoredResourceAmount, location.StoredResourceAmount)
|
||||||
|
.Set(x => x.StoredResourceCapacity, location.StoredResourceCapacity)
|
||||||
|
.Set(x => x.LastResourceGeneratedAtUtc, location.LastResourceGeneratedAtUtc)
|
||||||
.Set(x => x.HomeAssignedAtUtc, location.HomeAssignedAtUtc)
|
.Set(x => x.HomeAssignedAtUtc, location.HomeAssignedAtUtc)
|
||||||
.Set(x => x.ObservedAtUtc, now)
|
.Set(x => x.ObservedAtUtc, now)
|
||||||
.Set(x => x.UpdatedAtUtc, now);
|
.Set(x => x.UpdatedAtUtc, now);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace LocationsApi.Services;
|
|||||||
public class ResourceService
|
public class ResourceService
|
||||||
{
|
{
|
||||||
private readonly IMongoCollection<PlayerResources> _resources;
|
private readonly IMongoCollection<PlayerResources> _resources;
|
||||||
|
private readonly IMongoCollection<Location> _locations;
|
||||||
private readonly GameSettingsService _settings;
|
private readonly GameSettingsService _settings;
|
||||||
private readonly int _startingGold;
|
private readonly int _startingGold;
|
||||||
private readonly int _headquartersGoldPerMinute;
|
private readonly int _headquartersGoldPerMinute;
|
||||||
@@ -13,6 +14,7 @@ public class ResourceService
|
|||||||
private readonly int _townHallWoodCapacity;
|
private readonly int _townHallWoodCapacity;
|
||||||
private readonly int _townHallOreCapacity;
|
private readonly int _townHallOreCapacity;
|
||||||
private readonly int _townHallFoodCapacity;
|
private readonly int _townHallFoodCapacity;
|
||||||
|
private readonly int _productionBuildingResourceCapacity;
|
||||||
|
|
||||||
public ResourceService(IConfiguration cfg, GameSettingsService settings)
|
public ResourceService(IConfiguration cfg, GameSettingsService settings)
|
||||||
{
|
{
|
||||||
@@ -21,6 +23,7 @@ public class ResourceService
|
|||||||
var client = new MongoClient(connectionString);
|
var client = new MongoClient(connectionString);
|
||||||
var database = client.GetDatabase(databaseName);
|
var database = client.GetDatabase(databaseName);
|
||||||
_resources = database.GetCollection<PlayerResources>("PlayerResources");
|
_resources = database.GetCollection<PlayerResources>("PlayerResources");
|
||||||
|
_locations = database.GetCollection<Location>("Locations");
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
_startingGold = int.TryParse(cfg["Resources:StartingGold"], out var startingGold) ? Math.Max(0, startingGold) : 100;
|
_startingGold = int.TryParse(cfg["Resources:StartingGold"], out var startingGold) ? Math.Max(0, startingGold) : 100;
|
||||||
_headquartersGoldPerMinute = int.TryParse(cfg["Resources:HeadquartersGoldPerMinute"], out var goldPerMinute)
|
_headquartersGoldPerMinute = int.TryParse(cfg["Resources:HeadquartersGoldPerMinute"], out var goldPerMinute)
|
||||||
@@ -38,6 +41,9 @@ public class ResourceService
|
|||||||
_townHallFoodCapacity = int.TryParse(cfg["Resources:TownHallFoodCapacity"], out var foodCapacity)
|
_townHallFoodCapacity = int.TryParse(cfg["Resources:TownHallFoodCapacity"], out var foodCapacity)
|
||||||
? Math.Max(0, foodCapacity)
|
? Math.Max(0, foodCapacity)
|
||||||
: 100;
|
: 100;
|
||||||
|
_productionBuildingResourceCapacity = int.TryParse(cfg["Buildings:ProductionBuildingResourceCapacity"], out var productionBuildingResourceCapacity)
|
||||||
|
? Math.Max(0, productionBuildingResourceCapacity)
|
||||||
|
: 10;
|
||||||
|
|
||||||
_resources.Indexes.CreateOne(new CreateIndexModel<PlayerResources>(
|
_resources.Indexes.CreateOne(new CreateIndexModel<PlayerResources>(
|
||||||
Builders<PlayerResources>.IndexKeys.Ascending(x => x.Username),
|
Builders<PlayerResources>.IndexKeys.Ascending(x => x.Username),
|
||||||
@@ -85,7 +91,8 @@ public class ResourceService
|
|||||||
|
|
||||||
public async Task<PlayerResources> GetAsync(string username)
|
public async Task<PlayerResources> GetAsync(string username)
|
||||||
{
|
{
|
||||||
return await EnsureAsync(username);
|
var resources = await EnsureAsync(username);
|
||||||
|
return await ApplyProductionBuildingDeliveryAsync(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> TrySpendGoldAsync(string username, int amount)
|
public async Task<bool> TrySpendGoldAsync(string username, int amount)
|
||||||
@@ -207,6 +214,33 @@ public class ResourceService
|
|||||||
await _resources.UpdateOneAsync(x => x.Username == username, update);
|
await _resources.UpdateOneAsync(x => x.Username == username, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SetBuildingGenerationAsync(string username, int woodPerMinute, int orePerMinute, int foodPerMinute)
|
||||||
|
{
|
||||||
|
await EnsureAsync(username);
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var update = Builders<PlayerResources>.Update
|
||||||
|
.Set(x => x.WoodPerMinute, Math.Max(0, woodPerMinute))
|
||||||
|
.Set(x => x.OrePerMinute, Math.Max(0, orePerMinute))
|
||||||
|
.Set(x => x.FoodPerMinute, Math.Max(0, foodPerMinute))
|
||||||
|
.Set(x => x.UpdatedAtUtc, now);
|
||||||
|
|
||||||
|
if (woodPerMinute > 0)
|
||||||
|
{
|
||||||
|
update = update.Set(x => x.LastWoodGeneratedAtUtc, now);
|
||||||
|
}
|
||||||
|
if (orePerMinute > 0)
|
||||||
|
{
|
||||||
|
update = update.Set(x => x.LastOreGeneratedAtUtc, now);
|
||||||
|
}
|
||||||
|
if (foodPerMinute > 0)
|
||||||
|
{
|
||||||
|
update = update.Set(x => x.LastFoodGeneratedAtUtc, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _resources.UpdateOneAsync(x => x.Username == username, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task<PlayerResources> ApplyResourceGenerationAsync(PlayerResources resources)
|
private async Task<PlayerResources> ApplyResourceGenerationAsync(PlayerResources resources)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
@@ -218,22 +252,13 @@ public class ResourceService
|
|||||||
var goldElapsedMinutes = goldPerMinute <= 0
|
var goldElapsedMinutes = goldPerMinute <= 0
|
||||||
? 0
|
? 0
|
||||||
: (int)Math.Floor((now - resources.LastGoldGeneratedAtUtc).TotalMinutes);
|
: (int)Math.Floor((now - resources.LastGoldGeneratedAtUtc).TotalMinutes);
|
||||||
var woodElapsedMinutes = resources.WoodPerMinute <= 0 || resources.LastWoodGeneratedAtUtc.Year < 2000
|
|
||||||
? 0
|
|
||||||
: (int)Math.Floor((now - resources.LastWoodGeneratedAtUtc).TotalMinutes);
|
|
||||||
var oreElapsedMinutes = resources.OrePerMinute <= 0 || resources.LastOreGeneratedAtUtc.Year < 2000
|
|
||||||
? 0
|
|
||||||
: (int)Math.Floor((now - resources.LastOreGeneratedAtUtc).TotalMinutes);
|
|
||||||
var foodElapsedMinutes = resources.FoodPerMinute <= 0 || resources.LastFoodGeneratedAtUtc.Year < 2000
|
|
||||||
? 0
|
|
||||||
: (int)Math.Floor((now - resources.LastFoodGeneratedAtUtc).TotalMinutes);
|
|
||||||
var cappedGold = Math.Min(resources.Gold, goldCapacity);
|
var cappedGold = Math.Min(resources.Gold, goldCapacity);
|
||||||
var cappedWood = Math.Min(resources.Wood, woodCapacity);
|
var cappedWood = Math.Min(resources.Wood, woodCapacity);
|
||||||
var cappedOre = Math.Min(resources.Ore, oreCapacity);
|
var cappedOre = Math.Min(resources.Ore, oreCapacity);
|
||||||
var cappedFood = Math.Min(resources.Food, foodCapacity);
|
var cappedFood = Math.Min(resources.Food, foodCapacity);
|
||||||
var isOverCapacity = resources.Gold != cappedGold || resources.Wood != cappedWood || resources.Ore != cappedOre || resources.Food != cappedFood;
|
var isOverCapacity = resources.Gold != cappedGold || resources.Wood != cappedWood || resources.Ore != cappedOre || resources.Food != cappedFood;
|
||||||
|
|
||||||
if (goldElapsedMinutes <= 0 && woodElapsedMinutes <= 0 && oreElapsedMinutes <= 0 && foodElapsedMinutes <= 0 && !isOverCapacity)
|
if (goldElapsedMinutes <= 0 && !isOverCapacity)
|
||||||
{
|
{
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
@@ -263,30 +288,6 @@ public class ResourceService
|
|||||||
.Set(x => x.LastGoldGeneratedAtUtc, resources.LastGoldGeneratedAtUtc.AddMinutes(goldElapsedMinutes));
|
.Set(x => x.LastGoldGeneratedAtUtc, resources.LastGoldGeneratedAtUtc.AddMinutes(goldElapsedMinutes));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (woodElapsedMinutes > 0)
|
|
||||||
{
|
|
||||||
var generatedWood = Math.Min(woodCapacity, cappedWood + (woodElapsedMinutes * resources.WoodPerMinute));
|
|
||||||
update = update
|
|
||||||
.Set(x => x.Wood, generatedWood)
|
|
||||||
.Set(x => x.LastWoodGeneratedAtUtc, resources.LastWoodGeneratedAtUtc.AddMinutes(woodElapsedMinutes));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oreElapsedMinutes > 0)
|
|
||||||
{
|
|
||||||
var generatedOre = Math.Min(oreCapacity, cappedOre + (oreElapsedMinutes * resources.OrePerMinute));
|
|
||||||
update = update
|
|
||||||
.Set(x => x.Ore, generatedOre)
|
|
||||||
.Set(x => x.LastOreGeneratedAtUtc, resources.LastOreGeneratedAtUtc.AddMinutes(oreElapsedMinutes));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foodElapsedMinutes > 0)
|
|
||||||
{
|
|
||||||
var generatedFood = Math.Min(foodCapacity, cappedFood + (foodElapsedMinutes * resources.FoodPerMinute));
|
|
||||||
update = update
|
|
||||||
.Set(x => x.Food, generatedFood)
|
|
||||||
.Set(x => x.LastFoodGeneratedAtUtc, resources.LastFoodGeneratedAtUtc.AddMinutes(foodElapsedMinutes));
|
|
||||||
}
|
|
||||||
|
|
||||||
return await _resources.FindOneAndUpdateAsync(
|
return await _resources.FindOneAndUpdateAsync(
|
||||||
x => x.Username == resources.Username,
|
x => x.Username == resources.Username,
|
||||||
update,
|
update,
|
||||||
@@ -295,4 +296,85 @@ public class ResourceService
|
|||||||
ReturnDocument = ReturnDocument.After
|
ReturnDocument = ReturnDocument.After
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<PlayerResources> ApplyProductionBuildingDeliveryAsync(PlayerResources resources)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var woodCapacity = await _settings.GetNonNegativeIntAsync(GameSettingsService.TownHallWoodCapacity, _townHallWoodCapacity);
|
||||||
|
var oreCapacity = await _settings.GetNonNegativeIntAsync(GameSettingsService.TownHallOreCapacity, _townHallOreCapacity);
|
||||||
|
var foodCapacity = await _settings.GetNonNegativeIntAsync(GameSettingsService.TownHallFoodCapacity, _townHallFoodCapacity);
|
||||||
|
var buildingCapacity = await _settings.GetNonNegativeIntAsync(GameSettingsService.ProductionBuildingResourceCapacity, _productionBuildingResourceCapacity);
|
||||||
|
var logCampWoodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.LogCampWoodPerMinute, 5);
|
||||||
|
var mineOrePerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.MineOrePerMinute, 5);
|
||||||
|
var farmFoodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.FarmFoodPerMinute, 5);
|
||||||
|
|
||||||
|
var buildings = await _locations.Find(x =>
|
||||||
|
x.OwnerUsername == resources.Username &&
|
||||||
|
!string.IsNullOrWhiteSpace(x.WorkerUnitId) &&
|
||||||
|
(x.BuildingStatus == "Built" || x.BuildingStatus == null || x.BuildingStatus == "") &&
|
||||||
|
(x.BuildingType == "LogCamp" || x.BuildingType == "Mine" || x.BuildingType == "Farm"))
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var wood = Math.Min(resources.Wood, woodCapacity);
|
||||||
|
var ore = Math.Min(resources.Ore, oreCapacity);
|
||||||
|
var food = Math.Min(resources.Food, foodCapacity);
|
||||||
|
|
||||||
|
foreach (var building in buildings)
|
||||||
|
{
|
||||||
|
var rate = building.BuildingType switch
|
||||||
|
{
|
||||||
|
"LogCamp" => logCampWoodPerMinute,
|
||||||
|
"Mine" => mineOrePerMinute,
|
||||||
|
"Farm" => farmFoodPerMinute,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
var lastGenerated = building.LastResourceGeneratedAtUtc ?? building.BuildingBuiltAtUtc ?? now;
|
||||||
|
var elapsedMinutes = rate <= 0 ? 0 : (int)Math.Floor((now - lastGenerated).TotalMinutes);
|
||||||
|
var stored = Math.Min(buildingCapacity, Math.Max(0, building.StoredResourceAmount) + (elapsedMinutes * rate));
|
||||||
|
|
||||||
|
var delivered = 0;
|
||||||
|
if (building.BuildingType == "LogCamp")
|
||||||
|
{
|
||||||
|
delivered = Math.Min(stored, Math.Max(0, woodCapacity - wood));
|
||||||
|
wood += delivered;
|
||||||
|
}
|
||||||
|
else if (building.BuildingType == "Mine")
|
||||||
|
{
|
||||||
|
delivered = Math.Min(stored, Math.Max(0, oreCapacity - ore));
|
||||||
|
ore += delivered;
|
||||||
|
}
|
||||||
|
else if (building.BuildingType == "Farm")
|
||||||
|
{
|
||||||
|
delivered = Math.Min(stored, Math.Max(0, foodCapacity - food));
|
||||||
|
food += delivered;
|
||||||
|
}
|
||||||
|
|
||||||
|
var update = Builders<Location>.Update
|
||||||
|
.Set(x => x.StoredResourceAmount, stored - delivered)
|
||||||
|
.Set(x => x.StoredResourceCapacity, buildingCapacity)
|
||||||
|
.Set(x => x.UpdatedAtUtc, now);
|
||||||
|
|
||||||
|
if (elapsedMinutes > 0 || building.LastResourceGeneratedAtUtc is null)
|
||||||
|
{
|
||||||
|
update = update.Set(x => x.LastResourceGeneratedAtUtc, lastGenerated.AddMinutes(elapsedMinutes));
|
||||||
|
}
|
||||||
|
|
||||||
|
await _locations.UpdateOneAsync(x => x.Id == building.Id, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _resources.FindOneAndUpdateAsync(
|
||||||
|
x => x.Username == resources.Username,
|
||||||
|
Builders<PlayerResources>.Update
|
||||||
|
.Set(x => x.Wood, wood)
|
||||||
|
.Set(x => x.Ore, ore)
|
||||||
|
.Set(x => x.Food, food)
|
||||||
|
.Set(x => x.WoodCapacity, woodCapacity)
|
||||||
|
.Set(x => x.OreCapacity, oreCapacity)
|
||||||
|
.Set(x => x.FoodCapacity, foodCapacity)
|
||||||
|
.Set(x => x.UpdatedAtUtc, now),
|
||||||
|
new FindOneAndUpdateOptions<PlayerResources>
|
||||||
|
{
|
||||||
|
ReturnDocument = ReturnDocument.After
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public class UnitTrainingService
|
|||||||
{
|
{
|
||||||
private const string ScoutUnitType = "Scout";
|
private const string ScoutUnitType = "Scout";
|
||||||
private const string ArcherUnitType = "Archer";
|
private const string ArcherUnitType = "Archer";
|
||||||
|
private const string PeasantUnitType = "Peasant";
|
||||||
|
|
||||||
private readonly IMongoCollection<UnitTraining> _training;
|
private readonly IMongoCollection<UnitTraining> _training;
|
||||||
private readonly IMongoCollection<UnitInstance> _units;
|
private readonly IMongoCollection<UnitInstance> _units;
|
||||||
@@ -16,8 +17,10 @@ public class UnitTrainingService
|
|||||||
private readonly int _scoutGoldCost;
|
private readonly int _scoutGoldCost;
|
||||||
private readonly int _archerGoldCost;
|
private readonly int _archerGoldCost;
|
||||||
private readonly int _archerWoodCost;
|
private readonly int _archerWoodCost;
|
||||||
|
private readonly int _peasantGoldCost;
|
||||||
private readonly int _defaultScoutTrainingSeconds;
|
private readonly int _defaultScoutTrainingSeconds;
|
||||||
private readonly int _defaultArcherTrainingSeconds;
|
private readonly int _defaultArcherTrainingSeconds;
|
||||||
|
private readonly int _defaultPeasantTrainingSeconds;
|
||||||
private readonly int _visibilityDepth;
|
private readonly int _visibilityDepth;
|
||||||
|
|
||||||
public UnitTrainingService(IConfiguration cfg, ResourceService resources, GameSettingsService settings)
|
public UnitTrainingService(IConfiguration cfg, ResourceService resources, GameSettingsService settings)
|
||||||
@@ -34,12 +37,16 @@ public class UnitTrainingService
|
|||||||
_scoutGoldCost = int.TryParse(cfg["Units:ScoutGoldCost"], out var scoutGoldCost) ? Math.Max(0, scoutGoldCost) : 50;
|
_scoutGoldCost = int.TryParse(cfg["Units:ScoutGoldCost"], out var scoutGoldCost) ? Math.Max(0, scoutGoldCost) : 50;
|
||||||
_archerGoldCost = int.TryParse(cfg["Units:ArcherGoldCost"], out var archerGoldCost) ? Math.Max(0, archerGoldCost) : 100;
|
_archerGoldCost = int.TryParse(cfg["Units:ArcherGoldCost"], out var archerGoldCost) ? Math.Max(0, archerGoldCost) : 100;
|
||||||
_archerWoodCost = int.TryParse(cfg["Units:ArcherWoodCost"], out var archerWoodCost) ? Math.Max(0, archerWoodCost) : 100;
|
_archerWoodCost = int.TryParse(cfg["Units:ArcherWoodCost"], out var archerWoodCost) ? Math.Max(0, archerWoodCost) : 100;
|
||||||
|
_peasantGoldCost = int.TryParse(cfg["Units:PeasantGoldCost"], out var peasantGoldCost) ? Math.Max(0, peasantGoldCost) : 25;
|
||||||
_defaultScoutTrainingSeconds = int.TryParse(cfg["Units:ScoutTrainingSeconds"], out var scoutTrainingSeconds)
|
_defaultScoutTrainingSeconds = int.TryParse(cfg["Units:ScoutTrainingSeconds"], out var scoutTrainingSeconds)
|
||||||
? Math.Max(1, scoutTrainingSeconds)
|
? Math.Max(1, scoutTrainingSeconds)
|
||||||
: 60;
|
: 60;
|
||||||
_defaultArcherTrainingSeconds = int.TryParse(cfg["Units:ArcherTrainingSeconds"], out var archerTrainingSeconds)
|
_defaultArcherTrainingSeconds = int.TryParse(cfg["Units:ArcherTrainingSeconds"], out var archerTrainingSeconds)
|
||||||
? Math.Max(1, archerTrainingSeconds)
|
? Math.Max(1, archerTrainingSeconds)
|
||||||
: 60;
|
: 60;
|
||||||
|
_defaultPeasantTrainingSeconds = int.TryParse(cfg["Units:PeasantTrainingSeconds"], out var peasantTrainingSeconds)
|
||||||
|
? Math.Max(1, peasantTrainingSeconds)
|
||||||
|
: 60;
|
||||||
_visibilityDepth = int.TryParse(cfg["World:VisibilityDepth"], out var visibilityDepth) ? Math.Max(0, visibilityDepth) : 3;
|
_visibilityDepth = int.TryParse(cfg["World:VisibilityDepth"], out var visibilityDepth) ? Math.Max(0, visibilityDepth) : 3;
|
||||||
|
|
||||||
_training.Indexes.CreateOne(new CreateIndexModel<UnitTraining>(
|
_training.Indexes.CreateOne(new CreateIndexModel<UnitTraining>(
|
||||||
@@ -64,6 +71,7 @@ public class UnitTrainingService
|
|||||||
var allUnits = await _units.Find(x => x.Username == username).ToListAsync();
|
var allUnits = await _units.Find(x => x.Username == username).ToListAsync();
|
||||||
var completedScouts = allTraining.Count(x => x.UnitType == ScoutUnitType && x.Status == "Completed");
|
var completedScouts = allTraining.Count(x => x.UnitType == ScoutUnitType && x.Status == "Completed");
|
||||||
var completedArchers = allUnits.Count(x => x.UnitType == ArcherUnitType);
|
var completedArchers = allUnits.Count(x => x.UnitType == ArcherUnitType);
|
||||||
|
var completedPeasants = allUnits.Count(x => x.UnitType == PeasantUnitType && x.Status == "Idle");
|
||||||
var activeTraining = allTraining
|
var activeTraining = allTraining
|
||||||
.Where(x => x.Status == "Training")
|
.Where(x => x.Status == "Training")
|
||||||
.OrderBy(x => x.CompletesAtUtc)
|
.OrderBy(x => x.CompletesAtUtc)
|
||||||
@@ -74,6 +82,7 @@ public class UnitTrainingService
|
|||||||
{
|
{
|
||||||
CompletedScouts = completedScouts,
|
CompletedScouts = completedScouts,
|
||||||
CompletedArchers = completedArchers,
|
CompletedArchers = completedArchers,
|
||||||
|
CompletedPeasants = completedPeasants,
|
||||||
Training = activeTraining,
|
Training = activeTraining,
|
||||||
Units = allUnits
|
Units = allUnits
|
||||||
.OrderBy(x => x.Q)
|
.OrderBy(x => x.Q)
|
||||||
@@ -106,10 +115,20 @@ public class UnitTrainingService
|
|||||||
throw new InvalidOperationException("A completed Barracks is required to train Archers.");
|
throw new InvalidOperationException("A completed Barracks is required to train Archers.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (normalizedUnitType == PeasantUnitType)
|
||||||
|
{
|
||||||
|
origin = await _locations.Find(x => x.OwnerUsername == username && x.Kind == "Home").FirstOrDefaultAsync();
|
||||||
|
if (origin is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("A town hall is required to train Peasants.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var goldCost = normalizedUnitType == ArcherUnitType
|
var goldCost = normalizedUnitType == ArcherUnitType
|
||||||
? await _settings.GetNonNegativeIntAsync(GameSettingsService.ArcherGoldCost, _archerGoldCost)
|
? await _settings.GetNonNegativeIntAsync(GameSettingsService.ArcherGoldCost, _archerGoldCost)
|
||||||
: await _settings.GetNonNegativeIntAsync(GameSettingsService.ScoutGoldCost, _scoutGoldCost);
|
: normalizedUnitType == PeasantUnitType
|
||||||
|
? await _settings.GetNonNegativeIntAsync(GameSettingsService.PeasantGoldCost, _peasantGoldCost)
|
||||||
|
: await _settings.GetNonNegativeIntAsync(GameSettingsService.ScoutGoldCost, _scoutGoldCost);
|
||||||
var woodCost = normalizedUnitType == ArcherUnitType
|
var woodCost = normalizedUnitType == ArcherUnitType
|
||||||
? await _settings.GetNonNegativeIntAsync(GameSettingsService.ArcherWoodCost, _archerWoodCost)
|
? await _settings.GetNonNegativeIntAsync(GameSettingsService.ArcherWoodCost, _archerWoodCost)
|
||||||
: 0;
|
: 0;
|
||||||
@@ -124,6 +143,8 @@ public class UnitTrainingService
|
|||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var trainingSeconds = normalizedUnitType == ArcherUnitType
|
var trainingSeconds = normalizedUnitType == ArcherUnitType
|
||||||
? await _settings.GetIntAsync(GameSettingsService.ArcherTrainingSeconds, _defaultArcherTrainingSeconds)
|
? await _settings.GetIntAsync(GameSettingsService.ArcherTrainingSeconds, _defaultArcherTrainingSeconds)
|
||||||
|
: normalizedUnitType == PeasantUnitType
|
||||||
|
? await _settings.GetIntAsync(GameSettingsService.PeasantTrainingSeconds, _defaultPeasantTrainingSeconds)
|
||||||
: await _settings.GetIntAsync(GameSettingsService.ScoutTrainingSeconds, _defaultScoutTrainingSeconds);
|
: await _settings.GetIntAsync(GameSettingsService.ScoutTrainingSeconds, _defaultScoutTrainingSeconds);
|
||||||
var training = new UnitTraining
|
var training = new UnitTraining
|
||||||
{
|
{
|
||||||
@@ -350,7 +371,7 @@ public class UnitTrainingService
|
|||||||
ReturnDocument = ReturnDocument.After
|
ReturnDocument = ReturnDocument.After
|
||||||
});
|
});
|
||||||
|
|
||||||
if (completed is null || completed.UnitType != ArcherUnitType)
|
if (completed is null || (completed.UnitType != ArcherUnitType && completed.UnitType != PeasantUnitType))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -358,7 +379,7 @@ public class UnitTrainingService
|
|||||||
var unit = new UnitInstance
|
var unit = new UnitInstance
|
||||||
{
|
{
|
||||||
Username = completed.Username,
|
Username = completed.Username,
|
||||||
UnitType = ArcherUnitType,
|
UnitType = completed.UnitType,
|
||||||
Q = completed.OriginQ ?? 0,
|
Q = completed.OriginQ ?? 0,
|
||||||
R = completed.OriginR ?? 0,
|
R = completed.OriginR ?? 0,
|
||||||
Status = "Idle",
|
Status = "Idle",
|
||||||
@@ -384,6 +405,7 @@ public class UnitTrainingService
|
|||||||
{
|
{
|
||||||
"scout" => ScoutUnitType,
|
"scout" => ScoutUnitType,
|
||||||
"archer" => ArcherUnitType,
|
"archer" => ArcherUnitType,
|
||||||
|
"peasant" => PeasantUnitType,
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
"Units": {
|
"Units": {
|
||||||
"ScoutGoldCost": 50,
|
"ScoutGoldCost": 50,
|
||||||
"ScoutTrainingSeconds": 60,
|
"ScoutTrainingSeconds": 60,
|
||||||
|
"PeasantGoldCost": 25,
|
||||||
|
"PeasantTrainingSeconds": 60,
|
||||||
"ArcherGoldCost": 100,
|
"ArcherGoldCost": 100,
|
||||||
"ArcherWoodCost": 100,
|
"ArcherWoodCost": 100,
|
||||||
"ArcherTrainingSeconds": 60
|
"ArcherTrainingSeconds": 60
|
||||||
@@ -40,6 +42,7 @@
|
|||||||
"MineOrePerMinute": 5,
|
"MineOrePerMinute": 5,
|
||||||
"MineBuildSeconds": 60,
|
"MineBuildSeconds": 60,
|
||||||
"FarmFoodPerMinute": 5,
|
"FarmFoodPerMinute": 5,
|
||||||
|
"ProductionBuildingResourceCapacity": 10,
|
||||||
"BarracksBuildSeconds": 60,
|
"BarracksBuildSeconds": 60,
|
||||||
"FarmBuildSeconds": 60,
|
"FarmBuildSeconds": 60,
|
||||||
"BarracksGoldCost": 500,
|
"BarracksGoldCost": 500,
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ data:
|
|||||||
Resources__TownHallFoodCapacity: "100"
|
Resources__TownHallFoodCapacity: "100"
|
||||||
Units__ScoutGoldCost: "50"
|
Units__ScoutGoldCost: "50"
|
||||||
Units__ScoutTrainingSeconds: "60"
|
Units__ScoutTrainingSeconds: "60"
|
||||||
|
Units__PeasantGoldCost: "25"
|
||||||
|
Units__PeasantTrainingSeconds: "60"
|
||||||
Units__ArcherGoldCost: "100"
|
Units__ArcherGoldCost: "100"
|
||||||
Units__ArcherWoodCost: "100"
|
Units__ArcherWoodCost: "100"
|
||||||
Units__ArcherTrainingSeconds: "60"
|
Units__ArcherTrainingSeconds: "60"
|
||||||
@@ -27,6 +29,7 @@ data:
|
|||||||
Buildings__MineOrePerMinute: "5"
|
Buildings__MineOrePerMinute: "5"
|
||||||
Buildings__MineBuildSeconds: "60"
|
Buildings__MineBuildSeconds: "60"
|
||||||
Buildings__FarmFoodPerMinute: "5"
|
Buildings__FarmFoodPerMinute: "5"
|
||||||
|
Buildings__ProductionBuildingResourceCapacity: "10"
|
||||||
Buildings__BarracksBuildSeconds: "60"
|
Buildings__BarracksBuildSeconds: "60"
|
||||||
Buildings__FarmBuildSeconds: "60"
|
Buildings__FarmBuildSeconds: "60"
|
||||||
Buildings__BarracksGoldCost: "500"
|
Buildings__BarracksGoldCost: "500"
|
||||||
|
|||||||
Reference in New Issue
Block a user