tracking units
This commit is contained in:
@@ -432,7 +432,12 @@ func _can_build_mine_on_selected_tile() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
func _can_build_barracks_on_selected_tile() -> bool:
|
func _can_build_barracks_on_selected_tile() -> bool:
|
||||||
return _can_build_on_selected_tile("")
|
if not _can_build_on_selected_tile(""):
|
||||||
|
return false
|
||||||
|
|
||||||
|
var location: Dictionary = grid_view.get_visible_location(selected_q, selected_r)
|
||||||
|
var kind: String = str(location.get("kind", ""))
|
||||||
|
return kind != "Home"
|
||||||
|
|
||||||
|
|
||||||
func _can_build_on_selected_tile(required_terrain: String) -> bool:
|
func _can_build_on_selected_tile(required_terrain: String) -> bool:
|
||||||
@@ -813,6 +818,13 @@ 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))
|
||||||
|
if grid_view:
|
||||||
|
grid_view.set_completed_archers(completed_archer_count)
|
||||||
|
var unit_instances: Array = []
|
||||||
|
var raw_units = units.get("units", [])
|
||||||
|
if typeof(raw_units) == TYPE_ARRAY:
|
||||||
|
unit_instances = raw_units
|
||||||
|
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 | Archers: %d | Queue: %d" % [completed_scout_count, completed_archer_count, training.size()]
|
||||||
_update_action_buttons()
|
_update_action_buttons()
|
||||||
|
|
||||||
@@ -861,6 +873,10 @@ func _request_unit_training(unit_type: String) -> void:
|
|||||||
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
|
||||||
|
var request_body := { "unitType": unit_type }
|
||||||
|
if unit_type == "Archer":
|
||||||
|
request_body["q"] = selected_q
|
||||||
|
request_body["r"] = selected_r
|
||||||
var error: int = train_unit_request.request(
|
var error: int = train_unit_request.request(
|
||||||
LOCATIONS_BASE_URL + "/api/units/train",
|
LOCATIONS_BASE_URL + "/api/units/train",
|
||||||
PackedStringArray([
|
PackedStringArray([
|
||||||
@@ -868,7 +884,7 @@ func _request_unit_training(unit_type: String) -> void:
|
|||||||
"Authorization: Bearer %s" % access_token
|
"Authorization: Bearer %s" % access_token
|
||||||
]),
|
]),
|
||||||
HTTPClient.METHOD_POST,
|
HTTPClient.METHOD_POST,
|
||||||
JSON.stringify({ "unitType": unit_type })
|
JSON.stringify(request_body)
|
||||||
)
|
)
|
||||||
if error != OK:
|
if error != OK:
|
||||||
_update_action_buttons()
|
_update_action_buttons()
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ var tiles: Array[Dictionary] = []
|
|||||||
var visible_tiles: Dictionary = {}
|
var visible_tiles: Dictionary = {}
|
||||||
var owned_tiles: Dictionary = {}
|
var owned_tiles: Dictionary = {}
|
||||||
var rival_tiles: Dictionary = {}
|
var rival_tiles: Dictionary = {}
|
||||||
|
var completed_archer_count := 0
|
||||||
|
var unit_instances: Array[Dictionary] = []
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -174,6 +176,10 @@ func _draw_tile(tile: Dictionary) -> void:
|
|||||||
elif not building_type.is_empty():
|
elif not building_type.is_empty():
|
||||||
_draw_building_marker(center, height)
|
_draw_building_marker(center, height)
|
||||||
|
|
||||||
|
var archer_count := _archer_count_at(q, r)
|
||||||
|
if archer_count > 0:
|
||||||
|
_draw_archer_unit_marker(center, height, archer_count)
|
||||||
|
|
||||||
|
|
||||||
func _draw_control_regions(sorted_tiles: Array) -> void:
|
func _draw_control_regions(sorted_tiles: Array) -> void:
|
||||||
for control in ["owned", "rival"]:
|
for control in ["owned", "rival"]:
|
||||||
@@ -417,6 +423,44 @@ func _draw_building_marker(center: Vector2, height: float) -> void:
|
|||||||
draw_line(left, right.lerp(top_right, 0.2), post_color.darkened(0.12), 1.5 * zoom)
|
draw_line(left, right.lerp(top_right, 0.2), post_color.darkened(0.12), 1.5 * zoom)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_archer_unit_marker(center: Vector2, height: float, count: int) -> void:
|
||||||
|
var marker_center: Vector2 = center + Vector2(18.0 * zoom, -height * zoom - 14.0 * zoom)
|
||||||
|
var marker_scale: float = zoom
|
||||||
|
var cloak_color := Color(0.20, 0.38, 0.26)
|
||||||
|
var hood_color := Color(0.12, 0.23, 0.16)
|
||||||
|
var bow_color := Color(0.72, 0.50, 0.26)
|
||||||
|
var string_color := Color(0.92, 0.86, 0.66)
|
||||||
|
var arrow_color := Color(0.86, 0.78, 0.56)
|
||||||
|
var outline_color := Color(0.04, 0.06, 0.05)
|
||||||
|
var badge_color := Color(0.08, 0.12, 0.11)
|
||||||
|
var badge_border := Color(0.76, 0.62, 0.28)
|
||||||
|
|
||||||
|
draw_circle(marker_center + Vector2(1, 2) * marker_scale, 10.0 * marker_scale, Color(0.01, 0.02, 0.02, 0.55))
|
||||||
|
|
||||||
|
var body := PackedVector2Array([
|
||||||
|
marker_center + Vector2(-7.0, 8.0) * marker_scale,
|
||||||
|
marker_center + Vector2(0.0, -9.0) * marker_scale,
|
||||||
|
marker_center + Vector2(8.0, 8.0) * marker_scale
|
||||||
|
])
|
||||||
|
draw_colored_polygon(body, cloak_color)
|
||||||
|
draw_polyline(_closed_points(body), outline_color, 1.2 * marker_scale)
|
||||||
|
draw_circle(marker_center + Vector2(0.0, -6.0) * marker_scale, 4.5 * marker_scale, hood_color)
|
||||||
|
|
||||||
|
var bow_center: Vector2 = marker_center + Vector2(8.0, -1.0) * marker_scale
|
||||||
|
draw_arc(bow_center, 10.0 * marker_scale, deg_to_rad(-82), deg_to_rad(82), 16, bow_color, 2.0 * marker_scale)
|
||||||
|
draw_line(bow_center + Vector2(1.8, -9.8) * marker_scale, bow_center + Vector2(1.8, 9.8) * marker_scale, string_color, 1.0 * marker_scale)
|
||||||
|
draw_line(marker_center + Vector2(1.0, -1.0) * marker_scale, marker_center + Vector2(18.0, -1.0) * marker_scale, arrow_color, 1.6 * marker_scale)
|
||||||
|
draw_line(marker_center + Vector2(18.0, -1.0) * marker_scale, marker_center + Vector2(14.0, -4.0) * marker_scale, arrow_color, 1.2 * marker_scale)
|
||||||
|
draw_line(marker_center + Vector2(18.0, -1.0) * marker_scale, marker_center + Vector2(14.0, 2.0) * marker_scale, arrow_color, 1.2 * marker_scale)
|
||||||
|
|
||||||
|
if count > 1:
|
||||||
|
var badge_center: Vector2 = marker_center + Vector2(10.0, 9.0) * marker_scale
|
||||||
|
draw_circle(badge_center, 6.0 * marker_scale, badge_color)
|
||||||
|
draw_arc(badge_center, 6.0 * marker_scale, 0.0, TAU, 18, badge_border, 1.4 * marker_scale)
|
||||||
|
var font := ThemeDB.fallback_font
|
||||||
|
draw_string(font, badge_center + Vector2(-3.5, 3.5) * marker_scale, str(min(count, 9)), HORIZONTAL_ALIGNMENT_LEFT, -1, 9 * marker_scale, Color(0.93, 0.86, 0.58))
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
@@ -474,6 +518,19 @@ func has_owned_neighbor(q: int, r: int) -> bool:
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func set_completed_archers(count: int) -> void:
|
||||||
|
completed_archer_count = max(0, count)
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
func set_unit_instances(units: Array) -> void:
|
||||||
|
unit_instances.clear()
|
||||||
|
for item in units:
|
||||||
|
if typeof(item) == TYPE_DICTIONARY:
|
||||||
|
unit_instances.append(item)
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
func _tile_key(q: int, r: int) -> String:
|
func _tile_key(q: int, r: int) -> String:
|
||||||
return "%d,%d" % [q, r]
|
return "%d,%d" % [q, r]
|
||||||
|
|
||||||
@@ -530,6 +587,9 @@ func _tile_display_label(key: String, data: Dictionary) -> String:
|
|||||||
building_label = " | Mine" if building_status == "Built" or building_status.is_empty() else " | Mine building"
|
building_label = " | Mine" if building_status == "Built" or building_status.is_empty() else " | Mine building"
|
||||||
elif building_type == "Barracks":
|
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"
|
||||||
|
var archer_count := _archer_count_at(int(data.get("q", 0)), int(data.get("r", 0)))
|
||||||
|
if archer_count > 0:
|
||||||
|
building_label += " | Archers: %d" % archer_count
|
||||||
|
|
||||||
if home_tile == Vector2i(int(data.get("q", 0)), int(data.get("r", 0))):
|
if home_tile == Vector2i(int(data.get("q", 0)), int(data.get("r", 0))):
|
||||||
return "%s | Your home%s" % [terrain, building_label]
|
return "%s | Your home%s" % [terrain, building_label]
|
||||||
@@ -544,6 +604,38 @@ func _tile_display_label(key: String, data: Dictionary) -> String:
|
|||||||
return "%s | Unclaimed%s" % [terrain, building_label]
|
return "%s | Unclaimed%s" % [terrain, building_label]
|
||||||
|
|
||||||
|
|
||||||
|
func _archer_station_tile() -> Vector2i:
|
||||||
|
var station := Vector2i(9999, 9999)
|
||||||
|
for key in owned_tiles.keys():
|
||||||
|
var location: Dictionary = owned_tiles[key]
|
||||||
|
var building_type: String = _nullable_string(location.get("buildingType", ""))
|
||||||
|
var building_status: String = _nullable_string(location.get("buildingStatus", ""))
|
||||||
|
if building_type != "Barracks":
|
||||||
|
continue
|
||||||
|
if not (building_status == "Built" or building_status.is_empty()):
|
||||||
|
continue
|
||||||
|
|
||||||
|
var candidate := Vector2i(int(location.get("q", 0)), int(location.get("r", 0)))
|
||||||
|
if station.x == 9999 or candidate.y < station.y or (candidate.y == station.y and candidate.x < station.x):
|
||||||
|
station = candidate
|
||||||
|
|
||||||
|
return home_tile if station.x == 9999 else station
|
||||||
|
|
||||||
|
|
||||||
|
func _archer_count_at(q: int, r: int) -> int:
|
||||||
|
var count := 0
|
||||||
|
for unit in unit_instances:
|
||||||
|
if str(unit.get("unitType", "")) != "Archer":
|
||||||
|
continue
|
||||||
|
if int(unit.get("q", 0)) == q and int(unit.get("r", 0)) == r:
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if count == 0 and completed_archer_count > 0 and Vector2i(q, r) == _archer_station_tile():
|
||||||
|
count = completed_archer_count
|
||||||
|
|
||||||
|
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:
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class UnitsController : ControllerBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var training = await _units.TrainAsync(username, request.UnitType);
|
var training = await _units.TrainAsync(username, request);
|
||||||
if (training is null)
|
if (training is null)
|
||||||
{
|
{
|
||||||
return BadRequest(new { error = "Not enough resources." });
|
return BadRequest(new { error = "Not enough resources." });
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ namespace LocationsApi.Models;
|
|||||||
public class TrainUnitRequest
|
public class TrainUnitRequest
|
||||||
{
|
{
|
||||||
public string UnitType { get; set; } = "Scout";
|
public string UnitType { get; set; } = "Scout";
|
||||||
|
public int? Q { get; set; }
|
||||||
|
public int? R { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UnitTrainingResponse
|
public class UnitTrainingResponse
|
||||||
@@ -13,6 +15,8 @@ public class UnitTrainingResponse
|
|||||||
public int GoldCost { get; set; }
|
public int GoldCost { get; set; }
|
||||||
public int WoodCost { get; set; }
|
public int WoodCost { get; set; }
|
||||||
public int OreCost { get; set; }
|
public int OreCost { get; set; }
|
||||||
|
public int? OriginQ { get; set; }
|
||||||
|
public int? OriginR { get; set; }
|
||||||
public string Status { get; set; } = "";
|
public string Status { get; set; } = "";
|
||||||
public DateTime StartedAtUtc { get; set; }
|
public DateTime StartedAtUtc { get; set; }
|
||||||
public DateTime CompletesAtUtc { get; set; }
|
public DateTime CompletesAtUtc { get; set; }
|
||||||
@@ -26,6 +30,8 @@ public class UnitTrainingResponse
|
|||||||
GoldCost = training.GoldCost,
|
GoldCost = training.GoldCost,
|
||||||
WoodCost = training.WoodCost,
|
WoodCost = training.WoodCost,
|
||||||
OreCost = training.OreCost,
|
OreCost = training.OreCost,
|
||||||
|
OriginQ = training.OriginQ,
|
||||||
|
OriginR = training.OriginR,
|
||||||
Status = training.Status,
|
Status = training.Status,
|
||||||
StartedAtUtc = training.StartedAtUtc,
|
StartedAtUtc = training.StartedAtUtc,
|
||||||
CompletesAtUtc = training.CompletesAtUtc,
|
CompletesAtUtc = training.CompletesAtUtc,
|
||||||
@@ -33,9 +39,34 @@ public class UnitTrainingResponse
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class UnitInstanceResponse
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = "";
|
||||||
|
public string Username { get; set; } = "";
|
||||||
|
public string UnitType { get; set; } = "";
|
||||||
|
public int Q { get; set; }
|
||||||
|
public int R { get; set; }
|
||||||
|
public string Status { get; set; } = "";
|
||||||
|
public DateTime CreatedAtUtc { get; set; }
|
||||||
|
public DateTime UpdatedAtUtc { get; set; }
|
||||||
|
|
||||||
|
public static UnitInstanceResponse FromUnit(UnitInstance unit) => new()
|
||||||
|
{
|
||||||
|
Id = unit.Id,
|
||||||
|
Username = unit.Username,
|
||||||
|
UnitType = unit.UnitType,
|
||||||
|
Q = unit.Q,
|
||||||
|
R = unit.R,
|
||||||
|
Status = unit.Status,
|
||||||
|
CreatedAtUtc = unit.CreatedAtUtc,
|
||||||
|
UpdatedAtUtc = unit.UpdatedAtUtc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public class PlayerUnitsResponse
|
public class PlayerUnitsResponse
|
||||||
{
|
{
|
||||||
public int CompletedScouts { get; set; }
|
public int CompletedScouts { get; set; }
|
||||||
public int CompletedArchers { get; set; }
|
public int CompletedArchers { get; set; }
|
||||||
public List<UnitTrainingResponse> Training { get; set; } = [];
|
public List<UnitTrainingResponse> Training { get; set; } = [];
|
||||||
|
public List<UnitInstanceResponse> Units { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using MongoDB.Bson;
|
||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
|
||||||
|
namespace LocationsApi.Models;
|
||||||
|
|
||||||
|
public class UnitInstance
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
[BsonRepresentation(BsonType.ObjectId)]
|
||||||
|
public string Id { get; set; } = "";
|
||||||
|
|
||||||
|
public string Username { get; set; } = "";
|
||||||
|
public string UnitType { get; set; } = "Archer";
|
||||||
|
public int Q { get; set; }
|
||||||
|
public int R { get; set; }
|
||||||
|
public string Status { get; set; } = "Idle";
|
||||||
|
public string? SourceTrainingId { get; set; }
|
||||||
|
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime UpdatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ public class UnitTraining
|
|||||||
public int GoldCost { get; set; } = 50;
|
public int GoldCost { get; set; } = 50;
|
||||||
public int WoodCost { get; set; }
|
public int WoodCost { get; set; }
|
||||||
public int OreCost { get; set; }
|
public int OreCost { get; set; }
|
||||||
|
public int? OriginQ { get; set; }
|
||||||
|
public int? OriginR { get; set; }
|
||||||
public string Status { get; set; } = "Training";
|
public string Status { get; set; } = "Training";
|
||||||
public DateTime StartedAtUtc { get; set; } = DateTime.UtcNow;
|
public DateTime StartedAtUtc { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime CompletesAtUtc { get; set; } = DateTime.UtcNow.AddMinutes(1);
|
public DateTime CompletesAtUtc { get; set; } = DateTime.UtcNow.AddMinutes(1);
|
||||||
|
|||||||
@@ -165,6 +165,11 @@ public class LocationService
|
|||||||
throw new InvalidOperationException("This location already has a building or construction in progress.");
|
throw new InvalidOperationException("This location already has a building or construction in progress.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buildingType == "Barracks" && string.Equals(location.Kind, "Home", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Barracks cannot be built on the town hall.");
|
||||||
|
}
|
||||||
|
|
||||||
var requiredTerrain = RequiredTerrainForBuilding(buildingType);
|
var requiredTerrain = RequiredTerrainForBuilding(buildingType);
|
||||||
if (requiredTerrain is not null && !string.Equals(location.Terrain, requiredTerrain, StringComparison.OrdinalIgnoreCase))
|
if (requiredTerrain is not null && !string.Equals(location.Terrain, requiredTerrain, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public class UnitTrainingService
|
|||||||
private const string ArcherUnitType = "Archer";
|
private const string ArcherUnitType = "Archer";
|
||||||
|
|
||||||
private readonly IMongoCollection<UnitTraining> _training;
|
private readonly IMongoCollection<UnitTraining> _training;
|
||||||
|
private readonly IMongoCollection<UnitInstance> _units;
|
||||||
private readonly IMongoCollection<Location> _locations;
|
private readonly IMongoCollection<Location> _locations;
|
||||||
private readonly ResourceService _resources;
|
private readonly ResourceService _resources;
|
||||||
private readonly GameSettingsService _settings;
|
private readonly GameSettingsService _settings;
|
||||||
@@ -25,6 +26,7 @@ public class UnitTrainingService
|
|||||||
var client = new MongoClient(connectionString);
|
var client = new MongoClient(connectionString);
|
||||||
var database = client.GetDatabase(databaseName);
|
var database = client.GetDatabase(databaseName);
|
||||||
_training = database.GetCollection<UnitTraining>("UnitTraining");
|
_training = database.GetCollection<UnitTraining>("UnitTraining");
|
||||||
|
_units = database.GetCollection<UnitInstance>("Units");
|
||||||
_locations = database.GetCollection<Location>("Locations");
|
_locations = database.GetCollection<Location>("Locations");
|
||||||
_resources = resources;
|
_resources = resources;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
@@ -40,6 +42,15 @@ public class UnitTrainingService
|
|||||||
|
|
||||||
_training.Indexes.CreateOne(new CreateIndexModel<UnitTraining>(
|
_training.Indexes.CreateOne(new CreateIndexModel<UnitTraining>(
|
||||||
Builders<UnitTraining>.IndexKeys.Ascending(x => x.Username).Ascending(x => x.Status)));
|
Builders<UnitTraining>.IndexKeys.Ascending(x => x.Username).Ascending(x => x.Status)));
|
||||||
|
_units.Indexes.CreateOne(new CreateIndexModel<UnitInstance>(
|
||||||
|
Builders<UnitInstance>.IndexKeys.Ascending(x => x.Username).Ascending(x => x.UnitType)));
|
||||||
|
_units.Indexes.CreateOne(new CreateIndexModel<UnitInstance>(
|
||||||
|
Builders<UnitInstance>.IndexKeys.Ascending(x => x.SourceTrainingId),
|
||||||
|
new CreateIndexOptions<UnitInstance>
|
||||||
|
{
|
||||||
|
Unique = true,
|
||||||
|
Sparse = true
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PlayerUnitsResponse> GetMineAsync(string username)
|
public async Task<PlayerUnitsResponse> GetMineAsync(string username)
|
||||||
@@ -47,10 +58,11 @@ public class UnitTrainingService
|
|||||||
await CompleteFinishedTrainingAsync(username);
|
await CompleteFinishedTrainingAsync(username);
|
||||||
await _training.DeleteManyAsync(x => x.Username == username && x.Status == "Consumed");
|
await _training.DeleteManyAsync(x => x.Username == username && x.Status == "Consumed");
|
||||||
|
|
||||||
var all = await _training.Find(x => x.Username == username).ToListAsync();
|
var allTraining = await _training.Find(x => x.Username == username).ToListAsync();
|
||||||
var completedScouts = all.Count(x => x.UnitType == ScoutUnitType && x.Status == "Completed");
|
var allUnits = await _units.Find(x => x.Username == username).ToListAsync();
|
||||||
var completedArchers = all.Count(x => x.UnitType == ArcherUnitType && x.Status == "Completed");
|
var completedScouts = allTraining.Count(x => x.UnitType == ScoutUnitType && x.Status == "Completed");
|
||||||
var activeTraining = all
|
var completedArchers = allUnits.Count(x => x.UnitType == ArcherUnitType);
|
||||||
|
var activeTraining = allTraining
|
||||||
.Where(x => x.Status == "Training")
|
.Where(x => x.Status == "Training")
|
||||||
.OrderBy(x => x.CompletesAtUtc)
|
.OrderBy(x => x.CompletesAtUtc)
|
||||||
.Select(UnitTrainingResponse.FromTraining)
|
.Select(UnitTrainingResponse.FromTraining)
|
||||||
@@ -60,22 +72,38 @@ public class UnitTrainingService
|
|||||||
{
|
{
|
||||||
CompletedScouts = completedScouts,
|
CompletedScouts = completedScouts,
|
||||||
CompletedArchers = completedArchers,
|
CompletedArchers = completedArchers,
|
||||||
Training = activeTraining
|
Training = activeTraining,
|
||||||
|
Units = allUnits
|
||||||
|
.OrderBy(x => x.Q)
|
||||||
|
.ThenBy(x => x.R)
|
||||||
|
.ThenBy(x => x.CreatedAtUtc)
|
||||||
|
.Select(UnitInstanceResponse.FromUnit)
|
||||||
|
.ToList()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UnitTraining?> TrainAsync(string username, string unitType)
|
public async Task<UnitTraining?> TrainAsync(string username, TrainUnitRequest request)
|
||||||
{
|
{
|
||||||
var normalizedUnitType = NormalizeUnitType(unitType);
|
var normalizedUnitType = NormalizeUnitType(request.UnitType);
|
||||||
if (normalizedUnitType is null)
|
if (normalizedUnitType is null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Unsupported unit type.", nameof(unitType));
|
throw new ArgumentException("Unsupported unit type.", nameof(request.UnitType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedUnitType == ArcherUnitType && !await HasBuiltBarracksAsync(username))
|
Location? origin = null;
|
||||||
|
if (normalizedUnitType == ArcherUnitType)
|
||||||
|
{
|
||||||
|
if (request.Q is null || request.R is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Select a completed Barracks to train Archers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
origin = await GetBuiltBarracksAsync(username, request.Q.Value, request.R.Value);
|
||||||
|
if (origin is null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("A completed Barracks is required to train Archers.");
|
throw new InvalidOperationException("A completed Barracks is required to train Archers.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var goldCost = normalizedUnitType == ArcherUnitType ? _archerGoldCost : _scoutGoldCost;
|
var goldCost = normalizedUnitType == ArcherUnitType ? _archerGoldCost : _scoutGoldCost;
|
||||||
var woodCost = normalizedUnitType == ArcherUnitType ? _archerWoodCost : 0;
|
var woodCost = normalizedUnitType == ArcherUnitType ? _archerWoodCost : 0;
|
||||||
@@ -97,6 +125,8 @@ public class UnitTrainingService
|
|||||||
UnitType = normalizedUnitType,
|
UnitType = normalizedUnitType,
|
||||||
GoldCost = goldCost,
|
GoldCost = goldCost,
|
||||||
WoodCost = woodCost,
|
WoodCost = woodCost,
|
||||||
|
OriginQ = origin?.Q,
|
||||||
|
OriginR = origin?.R,
|
||||||
Status = "Training",
|
Status = "Training",
|
||||||
StartedAtUtc = now,
|
StartedAtUtc = now,
|
||||||
CompletesAtUtc = now.AddSeconds(trainingSeconds),
|
CompletesAtUtc = now.AddSeconds(trainingSeconds),
|
||||||
@@ -108,14 +138,16 @@ public class UnitTrainingService
|
|||||||
return training;
|
return training;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> HasBuiltBarracksAsync(string username)
|
private async Task<Location?> GetBuiltBarracksAsync(string username, int q, int r)
|
||||||
{
|
{
|
||||||
await CompleteFinishedBuildingsAsync(username);
|
await CompleteFinishedBuildingsAsync(username);
|
||||||
return await _locations.Find(x =>
|
return await _locations.Find(x =>
|
||||||
x.OwnerUsername == username &&
|
x.OwnerUsername == username &&
|
||||||
|
x.Q == q &&
|
||||||
|
x.R == r &&
|
||||||
x.BuildingType == "Barracks" &&
|
x.BuildingType == "Barracks" &&
|
||||||
(x.BuildingStatus == "Built" || x.BuildingStatus == null || x.BuildingStatus == ""))
|
(x.BuildingStatus == "Built" || x.BuildingStatus == null || x.BuildingStatus == ""))
|
||||||
.AnyAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CompleteFinishedBuildingsAsync(string username)
|
private async Task CompleteFinishedBuildingsAsync(string username)
|
||||||
@@ -157,14 +189,52 @@ public class UnitTrainingService
|
|||||||
private async Task CompleteFinishedTrainingAsync(string username)
|
private async Task CompleteFinishedTrainingAsync(string username)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
var readyTraining = await _training.Find(x =>
|
||||||
|
x.Username == username &&
|
||||||
|
x.Status == "Training" &&
|
||||||
|
x.CompletesAtUtc <= now)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
foreach (var training in readyTraining)
|
||||||
|
{
|
||||||
var update = Builders<UnitTraining>.Update
|
var update = Builders<UnitTraining>.Update
|
||||||
.Set(x => x.Status, "Completed")
|
.Set(x => x.Status, "Completed")
|
||||||
.Set(x => x.CompletedAtUtc, now)
|
.Set(x => x.CompletedAtUtc, now)
|
||||||
.Set(x => x.UpdatedAtUtc, now);
|
.Set(x => x.UpdatedAtUtc, now);
|
||||||
|
|
||||||
await _training.UpdateManyAsync(
|
var completed = await _training.FindOneAndUpdateAsync(
|
||||||
x => x.Username == username && x.Status == "Training" && x.CompletesAtUtc <= now,
|
x => x.Id == training.Id && x.Status == "Training",
|
||||||
update);
|
update,
|
||||||
|
new FindOneAndUpdateOptions<UnitTraining>
|
||||||
|
{
|
||||||
|
ReturnDocument = ReturnDocument.After
|
||||||
|
});
|
||||||
|
|
||||||
|
if (completed is null || completed.UnitType != ArcherUnitType)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var unit = new UnitInstance
|
||||||
|
{
|
||||||
|
Username = completed.Username,
|
||||||
|
UnitType = ArcherUnitType,
|
||||||
|
Q = completed.OriginQ ?? 0,
|
||||||
|
R = completed.OriginR ?? 0,
|
||||||
|
Status = "Idle",
|
||||||
|
SourceTrainingId = completed.Id,
|
||||||
|
CreatedAtUtc = now,
|
||||||
|
UpdatedAtUtc = now
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _units.InsertOneAsync(unit);
|
||||||
|
}
|
||||||
|
catch (MongoWriteException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? NormalizeUnitType(string unitType)
|
private static string? NormalizeUnitType(string unitType)
|
||||||
|
|||||||
Reference in New Issue
Block a user