264 lines
11 KiB
C#
264 lines
11 KiB
C#
using Core.Context;
|
|
using Core.Helpers;
|
|
using SptCommon.Annotations;
|
|
using Core.Models.Eft.Common;
|
|
using Core.Models.Eft.Game;
|
|
using Core.Models.Spt.Config;
|
|
using Core.Models.Spt.Location;
|
|
using Core.Models.Utils;
|
|
using Core.Servers;
|
|
using Core.Utils;
|
|
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
|
|
|
namespace Core.Services;
|
|
|
|
[Injectable(InjectionType.Singleton)]
|
|
public class RaidTimeAdjustmentService(
|
|
ISptLogger<RaidTimeAdjustmentService> _logger,
|
|
DatabaseService _databaseService,
|
|
RandomUtil _randomUtil,
|
|
WeightedRandomHelper _weightedRandomHelper,
|
|
ApplicationContext _applicationContext,
|
|
ConfigServer _configServer
|
|
)
|
|
{
|
|
protected LocationConfig _locationConfig = _configServer.GetConfig<LocationConfig>();
|
|
|
|
/// <summary>
|
|
/// Make alterations to the base map data passed in
|
|
/// Loot multipliers/waves/wave start times
|
|
/// </summary>
|
|
/// <param name="raidAdjustments">Changes to process on map</param>
|
|
/// <param name="mapBase">Map to adjust</param>
|
|
public void MakeAdjustmentsToMap(RaidChanges raidAdjustments, LocationBase mapBase)
|
|
{
|
|
_logger.Debug(
|
|
$"Adjusting dynamic loot multipliers to {raidAdjustments.DynamicLootPercent}% and static loot multipliers to {raidAdjustments.StaticLootPercent}% of original"
|
|
);
|
|
|
|
// Change loot multiplier values before they're used below
|
|
AdjustLootMultipliers(_locationConfig.LooseLootMultiplier, raidAdjustments.DynamicLootPercent);
|
|
AdjustLootMultipliers(_locationConfig.StaticLootMultiplier, raidAdjustments.StaticLootPercent);
|
|
|
|
var mapSettings = GetMapSettings(mapBase.Id);
|
|
if (mapSettings.AdjustWaves)
|
|
{
|
|
// Make alterations to bot spawn waves now player is simulated spawning later
|
|
AdjustWaves(mapBase, raidAdjustments);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjust the loot multiplier values passed in to be a % of their original value
|
|
/// </summary>
|
|
/// <param name="mapLootMultiplers">Multipliers to adjust</param>
|
|
/// <param name="loosePercent">Percent to change values to</param>
|
|
protected void AdjustLootMultipliers(Dictionary<string, double> mapLootMultiplers, double? loosePercent)
|
|
{
|
|
foreach (var location in mapLootMultiplers)
|
|
{
|
|
mapLootMultiplers[location.Key] = _randomUtil.GetPercentOfValue(mapLootMultiplers[location.Key], loosePercent ?? 1);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjust bot waves to act as if player spawned later
|
|
/// </summary>
|
|
/// <param name="mapBase">Map to adjust</param>
|
|
/// <param name="raidAdjustments">Map adjustments</param>
|
|
protected void AdjustWaves(LocationBase mapBase, RaidChanges raidAdjustments)
|
|
{
|
|
// Remove waves that spawned before the player joined
|
|
var originalWaveCount = mapBase.Waves.Count;
|
|
mapBase.Waves = mapBase.Waves.Where(x => x.TimeMax > raidAdjustments.SimulatedRaidStartSeconds).ToList();
|
|
|
|
// Adjust wave min/max times to match new simulated start
|
|
foreach (var wave in mapBase.Waves)
|
|
{
|
|
// Dont let time fall below 0
|
|
wave.TimeMax -= Math.Max(raidAdjustments.SimulatedRaidStartSeconds ?? 1, 0);
|
|
wave.TimeMax -= Math.Max(raidAdjustments.SimulatedRaidStartSeconds ?? 1, 0);
|
|
}
|
|
|
|
_logger.Debug(
|
|
$"Removed {originalWaveCount - mapBase.Waves.Count} wave from map due to simulated raid start time of {raidAdjustments.SimulatedRaidStartSeconds / 60} minutes"
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a randomised adjustment to the raid based on map data in location.json
|
|
/// </summary>
|
|
/// <param name="sessionId">Session id</param>
|
|
/// <param name="request">Raid adjustment request</param>
|
|
/// <returns>Response to send to client</returns>
|
|
public GetRaidTimeResponse GetRaidAdjustments(string sessionId, GetRaidTimeRequest request)
|
|
{
|
|
var globals = _databaseService.GetGlobals();
|
|
LocationBase mapBase = _databaseService.GetLocation(request.Location.ToLower()).Base;
|
|
var baseEscapeTimeMinutes = mapBase.EscapeTimeLimit;
|
|
|
|
// Prep result object to return
|
|
GetRaidTimeResponse result = new GetRaidTimeResponse
|
|
{
|
|
RaidTimeMinutes = baseEscapeTimeMinutes,
|
|
ExitChanges = [],
|
|
NewSurviveTimeSeconds = null,
|
|
OriginalSurvivalTimeSeconds = globals.Configuration.Exp.MatchEnd.SurvivedSecondsRequirement,
|
|
};
|
|
|
|
// Pmc raid, send default
|
|
if (request.Side.ToLower() == "pmc")
|
|
{
|
|
return result;
|
|
}
|
|
|
|
// We're scav adjust values
|
|
var mapSettings = GetMapSettings(request.Location);
|
|
|
|
// Chance of reducing raid time for scav, not guaranteed
|
|
if (!_randomUtil.GetChance100(mapSettings.ReducedChancePercent))
|
|
{
|
|
// Send default
|
|
return result;
|
|
}
|
|
|
|
// Get the weighted percent to reduce the raid time by
|
|
var chosenRaidReductionPercent = int.Parse(
|
|
_weightedRandomHelper.GetWeightedValue(mapSettings.ReductionPercentWeights)
|
|
);
|
|
var raidTimeRemainingPercent = 100 - chosenRaidReductionPercent;
|
|
|
|
// How many minutes raid will last
|
|
var newRaidTimeMinutes = Math.Floor(
|
|
_randomUtil.ReduceValueByPercent(baseEscapeTimeMinutes ?? 1, chosenRaidReductionPercent)
|
|
);
|
|
|
|
// Time player spawns into the raid if it was online
|
|
var simulatedRaidStartTimeMinutes = baseEscapeTimeMinutes - newRaidTimeMinutes;
|
|
|
|
if (mapSettings.ReduceLootByPercent)
|
|
{
|
|
// Store time reduction percent in app context so loot gen can pick it up later
|
|
_applicationContext.AddValue(
|
|
ContextVariableType.RAID_ADJUSTMENTS,
|
|
new
|
|
{
|
|
dynamicLootPercent = Math.Max(raidTimeRemainingPercent, mapSettings.MinDynamicLootPercent),
|
|
staticLootPercent = Math.Max(raidTimeRemainingPercent, mapSettings.MinStaticLootPercent),
|
|
simulatedRaidStartSeconds = simulatedRaidStartTimeMinutes * 60,
|
|
}
|
|
);
|
|
}
|
|
|
|
// Update result object with new time
|
|
result.RaidTimeMinutes = newRaidTimeMinutes;
|
|
|
|
_logger.Debug($"Reduced: {request.Location} raid time by: {chosenRaidReductionPercent}% to {newRaidTimeMinutes} minutes");
|
|
|
|
// Calculate how long player needs to be in raid to get a `survived` extract status
|
|
result.NewSurviveTimeSeconds = Math.Max(
|
|
(result.OriginalSurvivalTimeSeconds - (baseEscapeTimeMinutes - newRaidTimeMinutes) * 60) ?? 0,
|
|
0D
|
|
);
|
|
|
|
var exitAdjustments = GetExitAdjustments(mapBase, newRaidTimeMinutes);
|
|
if (exitAdjustments is not null)
|
|
{
|
|
result.ExitChanges.AddRange(exitAdjustments);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get raid start time settings for specific map
|
|
/// </summary>
|
|
/// <param name="location">Map Location e.g. bigmap</param>
|
|
/// <returns>ScavRaidTimeLocationSettings</returns>
|
|
protected ScavRaidTimeLocationSettings GetMapSettings(string location)
|
|
{
|
|
var mapSettings = _locationConfig.ScavRaidTimeSettings.Maps?[location.ToLower()];
|
|
if (mapSettings is null)
|
|
{
|
|
_logger.Warning($"Unable to find scav raid time settings for map: {location}, using defaults");
|
|
return new ScavRaidTimeLocationSettings();
|
|
}
|
|
|
|
return mapSettings;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjust exit times to handle scavs entering raids part-way through
|
|
/// </summary>
|
|
/// <param name="mapBase">Map base file player is on</param>
|
|
/// <param name="newRaidTimeMinutes">How long raid is in minutes</param>
|
|
/// <returns>List of exit changes to send to client</returns>
|
|
protected List<ExtractChange> GetExitAdjustments(LocationBase mapBase, double newRaidTimeMinutes)
|
|
{
|
|
List<ExtractChange> result = [];
|
|
// Adjust train exits only
|
|
foreach (var exit in mapBase.Exits) {
|
|
if (exit.PassageRequirement != "Train") {
|
|
continue;
|
|
}
|
|
|
|
// Prepare train adjustment object
|
|
ExtractChange exitChange = new ExtractChange {
|
|
Name = exit.Name,
|
|
MinTime = null,
|
|
MaxTime = null,
|
|
Chance = null,
|
|
};
|
|
|
|
// At what minute we simulate the player joining the raid
|
|
var simulatedRaidEntryTimeMinutes = mapBase.EscapeTimeLimit - newRaidTimeMinutes;
|
|
|
|
// How many seconds have elapsed in the raid when the player joins
|
|
var reductionSeconds = simulatedRaidEntryTimeMinutes * 60;
|
|
|
|
// Delay between the train extract activating and it becoming available to board
|
|
//
|
|
// Test method for determining this value:
|
|
// 1) Set MinTime, MaxTime, and Count for the train extract all to 120
|
|
// 2) Load into Reserve or Lighthouse as a PMC (both have the same result)
|
|
// 3) Board the train when it arrives
|
|
// 4) Check the raid time on the Raid Ended Screen (it should always be the same)
|
|
//
|
|
// trainArrivalDelaySeconds = [raid time on raid-ended screen] - MaxTime - Count - ExfiltrationTime
|
|
// Example: Raid Time = 5:33 = 333 seconds
|
|
// trainArrivalDelaySeconds = 333 - 120 - 120 - 5 = 88
|
|
//
|
|
// I added 2 seconds just to be safe...
|
|
//
|
|
var trainArrivalDelaySeconds =
|
|
_locationConfig.ScavRaidTimeSettings.Settings.TrainArrivalDelayObservedSeconds;
|
|
|
|
// Determine the earliest possible time in the raid when the train would leave
|
|
var earliestPossibleDepartureMinutes =
|
|
(exit.MinTime + exit.Count + exit.ExfiltrationTime + trainArrivalDelaySeconds) / 60;
|
|
|
|
// If raid is after last moment train can leave, assume train has already left, disable extract
|
|
var mostPossibleTimeRemainingAfterDeparture = mapBase.EscapeTimeLimit - earliestPossibleDepartureMinutes;
|
|
if (newRaidTimeMinutes < mostPossibleTimeRemainingAfterDeparture) {
|
|
exitChange.Chance = 0;
|
|
|
|
_logger.Debug($"Train Exit: {exit.Name} disabled as new raid time {newRaidTimeMinutes} minutes is below {mostPossibleTimeRemainingAfterDeparture} minutes");
|
|
|
|
result.Add(exitChange);
|
|
|
|
continue;
|
|
}
|
|
|
|
// Reduce extract arrival times. Negative values seem to make extract turn red in game.
|
|
exitChange.MinTime = Math.Max((exit.MinTime - reductionSeconds) ?? 0, 0);
|
|
exitChange.MaxTime = Math.Max((exit.MaxTime - reductionSeconds) ?? 0, 0);
|
|
|
|
_logger.Debug($"Train appears between: {exitChange.MinTime} and {exitChange.MaxTime} seconds raid time");
|
|
|
|
result.Add(exitChange);
|
|
}
|
|
|
|
return result.Count > 0 ? result : null;
|
|
}
|
|
}
|