Fix job boards

This commit is contained in:
Yury
2025-03-25 18:51:29 +02:00
parent eec90dae91
commit 4e0b6440d9
3 changed files with 116 additions and 78 deletions
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Prospect.Server.Api.Services.CloudScript.Models.Data;
namespace Prospect.Server.Api.Models.Data; namespace Prospect.Server.Api.Models.Data;
@@ -13,3 +14,12 @@ public class PlayerBalance
[JsonPropertyName("IN")] [JsonPropertyName("IN")]
public int InsuranceTokens { get; set; } public int InsuranceTokens { get; set; }
} }
public class JobBoardsData
{
[JsonPropertyName("lastBoardRefreshTimeUtc")]
public FYTimestamp LastBoardRefreshTimeUtc { get; set; }
[JsonPropertyName("boards")]
public List<FYFactionContractsData> Boards { get; set; }
}
@@ -50,6 +50,8 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly UserDataService _userDataService; private readonly UserDataService _userDataService;
private readonly TitleDataService _titleDataService; private readonly TitleDataService _titleDataService;
private readonly string[] difficulties = ["Easy", "Medium", "Hard"];
private readonly Random rnd = new Random();
public ClaimActiveContract(IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService) public ClaimActiveContract(IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService)
{ {
@@ -66,11 +68,10 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
throw new CloudScriptException("CloudScript was not called within a http request"); throw new CloudScriptException("CloudScript was not called within a http request");
} }
var userId = context.User.FindAuthUserId(); var userId = context.User.FindAuthUserId();
var titleData = _titleDataService.Find(new List<string>{"Contracts", "Blueprints"}); var titleData = _titleDataService.Find(new List<string>{ "Contracts", "Blueprints", "Jobs", "LevelData" });
var contracts = JsonSerializer.Deserialize<Dictionary<string, TitleDataContractInfo>>(titleData["Contracts"]); var contracts = JsonSerializer.Deserialize<Dictionary<string, TitleDataContractInfo>>(titleData["Contracts"]);
var contract = contracts[request.ContractID]; if (!contracts.TryGetValue(request.ContractID, out var contract)) {
if (contract == null) {
return new FYClaimCompletedActiveContractRewardsResult return new FYClaimCompletedActiveContractRewardsResult
{ {
UserID = userId, UserID = userId,
@@ -82,16 +83,17 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
var factionKey = "FactionProgression" + contract.Faction; var factionKey = "FactionProgression" + contract.Faction;
var userData = await _userDataService.FindAsync( var userData = await _userDataService.FindAsync(
userId, userId, userId, userId,
new List<string>{"ContractsActive", "ContractsOneTimeCompleted", "Balance", "Inventory", factionKey} new List<string>{"ContractsActive", "ContractsOneTimeCompleted", "Balance", "Inventory", factionKey, "JobBoardsData" }
); );
var factionProgression = JsonSerializer.Deserialize<int>(userData[factionKey].Value); var factionProgression = JsonSerializer.Deserialize<int>(userData[factionKey].Value);
var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value); var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value);
var contractsCompleted = JsonSerializer.Deserialize<FYGetCompletedContractsResult>(userData["ContractsOneTimeCompleted"].Value); var contractsCompleted = JsonSerializer.Deserialize<FYGetCompletedContractsResult>(userData["ContractsOneTimeCompleted"].Value);
var jobBoardsData = JsonSerializer.Deserialize<JobBoardsData>(userData["JobBoardsData"].Value);
var balance = JsonSerializer.Deserialize<PlayerBalance>(userData["Balance"].Value); var balance = JsonSerializer.Deserialize<PlayerBalance>(userData["Balance"].Value);
var inventory = JsonSerializer.Deserialize<List<FYCustomItemInfo>>(userData["Inventory"].Value); var inventory = JsonSerializer.Deserialize<List<FYCustomItemInfo>>(userData["Inventory"].Value);
var targetContractIdx = contractsActive.Contracts.FindIndex(item => item.ContractID == request.ContractID); var targetContractIdx = contractsActive.Contracts.FindIndex(item => item.ContractID == contract.Name);
if (targetContractIdx == -1) { if (targetContractIdx == -1) {
return new FYClaimCompletedActiveContractRewardsResult return new FYClaimCompletedActiveContractRewardsResult
{ {
@@ -106,6 +108,7 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
var targetContract = contractsActive.Contracts[targetContractIdx]; var targetContract = contractsActive.Contracts[targetContractIdx];
List<FYCustomItemInfo> itemsUpdatedOrRemoved = []; List<FYCustomItemInfo> itemsUpdatedOrRemoved = [];
HashSet<string> deletedItemsIds = []; HashSet<string> deletedItemsIds = [];
// TODO: Optimize
for (var i = 0; i < contract.Objectives.Length; i++) { for (var i = 0; i < contract.Objectives.Length; i++) {
var objective = contract.Objectives[i]; var objective = contract.Objectives[i];
int remaining = objective.MaxProgress; int remaining = objective.MaxProgress;
@@ -211,10 +214,33 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
factionProgression += contract.ReputationIncrease; factionProgression += contract.ReputationIncrease;
contractsActive.Contracts.RemoveAt(targetContractIdx); contractsActive.Contracts.RemoveAt(targetContractIdx);
// Main contracts can be completed only once. // Main contracts can be completed only once.
// Other contracts should be jobs.
var newContractIdOnBoard = "";
if (contract.IsMainContract) { if (contract.IsMainContract) {
contractsCompleted.ContractsIDs.Add(request.ContractID); contractsCompleted.ContractsIDs.Add(contract.Name);
} else {
var parts = contract.Name.Split('-');
var difficulty = parts[1];
var levelsData = JsonSerializer.Deserialize<Dictionary<string, int[]>>(titleData["LevelData"]);
var levels = levelsData[contract.Faction];
var jobs = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string[]>>>(titleData["Jobs"]);
var level = levels.Length - Array.FindIndex(levels, (int xp) => {
return factionProgression >= xp;
});
var factionBoard = jobBoardsData.Boards.Find(c => c.FactionId == contract.Faction);
var idx = Array.FindIndex(difficulties, d => d == difficulty);
var factionJobs = jobs[contract.Faction];
var jobsAvailable = Array.FindAll(factionJobs[difficulty], (string jobId) => {
return level >= contracts[jobId].UnlockData.Level;
});
// If player completes a job, it means he has at least one unlocked anyway.
newContractIdOnBoard = jobsAvailable[rnd.Next(jobsAvailable.Length)];
factionBoard.Contracts[idx].ContractId = newContractIdOnBoard;
} }
// TODO: Check for next unlock in contracts instead of relying on client
var newContracts = new List<FYActiveContractPlayerData>(); var newContracts = new List<FYActiveContractPlayerData>();
foreach (var contractIdToUnlock in request.ContractsToUnlock) { foreach (var contractIdToUnlock in request.ContractsToUnlock) {
var contractToUnlock = contracts[contractIdToUnlock]; var contractToUnlock = contracts[contractIdToUnlock];
@@ -235,6 +261,7 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
["Balance"] = JsonSerializer.Serialize(balance), ["Balance"] = JsonSerializer.Serialize(balance),
["Inventory"] = JsonSerializer.Serialize(newInventory), ["Inventory"] = JsonSerializer.Serialize(newInventory),
[factionKey] = JsonSerializer.Serialize(factionProgression), [factionKey] = JsonSerializer.Serialize(factionProgression),
["JobBoardsData"] = JsonSerializer.Serialize(jobBoardsData),
} }
); );
@@ -242,12 +269,12 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
{ {
UserID = userId, UserID = userId,
Error = "", Error = "",
ClaimedContractID = request.ContractID, ClaimedContractID = contract.Name,
ContractsActivated = newContracts, ContractsActivated = newContracts,
ItemsUpdatedOrRemoved = itemsUpdatedOrRemoved, ItemsUpdatedOrRemoved = itemsUpdatedOrRemoved,
ChangedCurrencies = changedCurrencies.ToArray(), ChangedCurrencies = changedCurrencies.ToArray(),
ItemsGranted = itemsGranted, ItemsGranted = itemsGranted,
NewContractIdOnBoard = "", // TODO: This should be a replacement contract apparently? NewContractIdOnBoard = newContractIdOnBoard,
PlayerFactionProgressData = new FYPlayerFactionProgressData { PlayerFactionProgressData = new FYPlayerFactionProgressData {
FactionID = contract.Faction, FactionID = contract.Faction,
CurrentProgression = factionProgression, CurrentProgression = factionProgression,
@@ -1,4 +1,7 @@
using System.Text.Json; using System.Collections;
using System.Diagnostics.Contracts;
using System.Text.Json;
using Prospect.Server.Api.Models.Data;
using Prospect.Server.Api.Services.Auth.Extensions; using Prospect.Server.Api.Services.Auth.Extensions;
using Prospect.Server.Api.Services.CloudScript.Models; using Prospect.Server.Api.Services.CloudScript.Models;
using Prospect.Server.Api.Services.CloudScript.Models.Data; using Prospect.Server.Api.Services.CloudScript.Models.Data;
@@ -11,11 +14,16 @@ public class RequestActiveObjectivesAndBoardsData : ICloudScriptFunction<FYQuery
{ {
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly UserDataService _userDataService; private readonly UserDataService _userDataService;
private readonly TitleDataService _titleDataService;
private readonly string[] factions = ["Korolev", "Osiris", "ICA"]; // TODO: Order is important
private readonly string[] difficulties = ["Easy", "Medium", "Hard"];
private readonly Random rnd = new Random();
public RequestActiveObjectivesAndBoardsData(IHttpContextAccessor httpContextAccessor, UserDataService userDataService) public RequestActiveObjectivesAndBoardsData(IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService)
{ {
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_userDataService = userDataService; _userDataService = userDataService;
_titleDataService = titleDataService;
} }
public async Task<FYGetPlayerContractsResult> ExecuteAsync(FYQueryFactionProgressionRequest request) public async Task<FYGetPlayerContractsResult> ExecuteAsync(FYQueryFactionProgressionRequest request)
@@ -26,10 +34,65 @@ public class RequestActiveObjectivesAndBoardsData : ICloudScriptFunction<FYQuery
return new FYGetPlayerContractsResult{}; return new FYGetPlayerContractsResult{};
} }
var userId = context.User.FindAuthUserId(); var userId = context.User.FindAuthUserId();
var userData = await _userDataService.FindAsync(userId, userId, new List<string>{"ContractsActive"});
var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value);
// TODO: Get player's contract boards and refresh var userData = await _userDataService.FindAsync(userId, userId, new List<string>{ "ContractsActive", "JobBoardsData", "FactionProgressionKorolev", "FactionProgressionICA", "FactionProgressionOsiris" });
var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value);
var jobBoardsData = JsonSerializer.Deserialize<JobBoardsData>(userData["JobBoardsData"].Value);
var now = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var lastBoardRefreshTimeUtc = jobBoardsData.LastBoardRefreshTimeUtc;
var boards = jobBoardsData.Boards;
// The boards are updated once in 3 hours
if (now >= lastBoardRefreshTimeUtc.Seconds + 60 * 60 * 3) {
var titleData = _titleDataService.Find(new List<string> { "Contracts", "Jobs", "LevelData" });
var contracts = JsonSerializer.Deserialize<Dictionary<string, TitleDataContractInfo>>(titleData["Contracts"]);
var jobs = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string[]>>>(titleData["Jobs"]);
var levelsData = JsonSerializer.Deserialize<Dictionary<string, int[]>>(titleData["LevelData"]);
var currentQuests = new Dictionary<string, Dictionary<string, bool>> {
["Korolev"] = new Dictionary<string, bool> { ["Easy"] = false, ["Medium"] = false, ["Hard"] = false },
["Osiris"] = new Dictionary<string, bool> { ["Easy"] = false, ["Medium"] = false, ["Hard"] = false },
["ICA"] = new Dictionary<string, bool> { ["Easy"] = false, ["Medium"] = false, ["Hard"] = false },
};
foreach (var contract in contractsActive.Contracts) {
var parts = contract.ContractID.Split('-');
if (parts[0] != "NEW") {
continue;
}
var difficulty = parts[1];
var faction = parts[2] == "KOR" ? "Korolev" : parts[2];
currentQuests[faction][difficulty] = true;
}
for (var i = 0; i < factions.Length; i++) {
var faction = factions[i];
var factionProgression = JsonSerializer.Deserialize<int>(userData[$"FactionProgression{faction}"].Value);
var levels = levelsData[faction];
var level = levels.Length - Array.FindIndex(levels, (int xp) => {
return factionProgression >= xp;
});
for (var j = 0; j < difficulties.Length; j++) {
var difficulty = difficulties[j];
if (currentQuests[faction][difficulty]) {
continue;
}
var factionJobs = jobs[faction];
var jobsAvailable = Array.FindAll(factionJobs[difficulty], (string jobId) => {
return level >= contracts[jobId].UnlockData.Level;
});
string jobId = jobsAvailable.Length > 0 ? jobsAvailable[rnd.Next(jobsAvailable.Length)] : factionJobs[difficulty][0];
boards[i].Contracts[j] = new FYFactionContractData { ContractId = jobId };
}
}
lastBoardRefreshTimeUtc.Seconds = now;
await _userDataService.UpdateAsync(
userId, userId,
new Dictionary<string, string> { ["JobBoardsData"] = JsonSerializer.Serialize(jobBoardsData) }
);
}
return new FYGetPlayerContractsResult return new FYGetPlayerContractsResult
{ {
@@ -38,72 +101,10 @@ public class RequestActiveObjectivesAndBoardsData : ICloudScriptFunction<FYQuery
ActiveContracts = contractsActive.Contracts, ActiveContracts = contractsActive.Contracts,
FactionsContracts = new FYFactionsContractsData FactionsContracts = new FYFactionsContractsData
{ {
Boards = new List<FYFactionContractsData> Boards = boards,
{ LastBoardRefreshTimeUtc = lastBoardRefreshTimeUtc
new FYFactionContractsData
{
FactionId = "ICA",
Contracts = new List<FYFactionContractData>
{
new FYFactionContractData
{
ContractId = "NEW-Easy-ICA-Gather-1",
},
new FYFactionContractData
{
ContractId = "NEW-Medium-ICA-Uplink-1",
},
new FYFactionContractData
{
ContractId = "NEW-Hard-ICA-Uplink-1",
}
}
},
new FYFactionContractsData
{
FactionId = "Korolev",
Contracts = new List<FYFactionContractData>
{
new FYFactionContractData
{
ContractId = "NEW-Easy-KOR-Mine-4",
},
new FYFactionContractData
{
ContractId = "NEW-Medium-KOR-Mine-1",
},
new FYFactionContractData
{
ContractId = "NEW-Hard-KOR-PvP-6",
}
}
},
new FYFactionContractsData
{
FactionId = "Osiris",
Contracts = new List<FYFactionContractData>
{
new FYFactionContractData
{
ContractId = "NEW-Easy-Osiris-Brightcaps-1",
},
new FYFactionContractData
{
ContractId = "NEW-Medium-Osiris-Gather-1",
},
new FYFactionContractData
{
ContractId = "NEW-Hard-Osiris-Gather-7",
}
}
}
},
LastBoardRefreshTimeUtc = new FYTimestamp
{
Seconds = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()
}
}, },
RefreshHours24UtcFromBackend = 12 RefreshHours24UtcFromBackend = 12 // Doesn't seem to work
}; };
} }
} }