Files
SPT-Server-Build/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs
T
acidphantasm 432dd2e733 Furtherfix ragfair modlimits (#724)
* Further fix to mod limit debug log

* Small improvements on FlagOfferForRemoval to correctly fix logging instead of FromSeconds using the unixtimestamp

---------

Co-authored-by: acidphantasm <acidphantasm@acidphantasm.com>
2026-01-06 11:52:48 +00:00

173 lines
6.9 KiB
C#

using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Spt.Bots;
using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
namespace SPTarkov.Server.Core.Services;
[Injectable(InjectionType.Singleton)]
public class BotWeaponModLimitService(ISptLogger<BotWeaponModLimitService> logger, ConfigServer configServer, ItemHelper itemHelper)
{
protected readonly BotConfig BotConfig = configServer.GetConfig<BotConfig>();
/// <summary>
/// Initalise mod limits to be used when generating a weapon
/// </summary>
/// <param name="botRole">"assault", "bossTagilla" or "pmc"</param>
/// <returns>BotModLimits object</returns>
public BotModLimits GetWeaponModLimits(string botRole)
{
return new BotModLimits
{
Scope = new ItemCount { Count = 0 },
ScopeMax = BotConfig.Equipment[botRole]?.WeaponModLimits?.ScopeLimit,
ScopeBaseTypes =
[
BaseClasses.OPTIC_SCOPE,
BaseClasses.ASSAULT_SCOPE,
BaseClasses.COLLIMATOR,
BaseClasses.COMPACT_COLLIMATOR,
BaseClasses.SPECIAL_SCOPE,
],
FlashlightLaser = new ItemCount { Count = 0 },
FlashlightLaserMax = BotConfig.Equipment[botRole]?.WeaponModLimits?.LightLaserLimit,
FlashlightLaserBaseTypes = [BaseClasses.TACTICAL_COMBO, BaseClasses.FLASHLIGHT, BaseClasses.PORTABLE_RANGE_FINDER],
};
}
/// <summary>
/// Check if weapon mod item is on limited list + has surpassed the limit set for it <br />
/// Exception: Always allow ncstar backup mount <br />
/// Exception: Always allow scopes with a scope for a parent <br />
/// Exception: Always disallow mounts that hold only scopes once scope limit reached <br />
/// Exception: Always disallow mounts that hold only flashlights once flashlight limit reached
/// </summary>
/// <param name="botRole">role the bot has e.g. assault</param>
/// <param name="modTemplate">mods template data</param>
/// <param name="modLimits">limits set for weapon being generated for this bot</param>
/// <param name="modsParent">The parent of the mod to be checked</param>
/// <param name="weapon">Array of IItem</param>
/// <returns>true if over item limit</returns>
public bool WeaponModHasReachedLimit(
string botRole,
TemplateItem modTemplate,
BotModLimits modLimits,
TemplateItem modsParent,
IEnumerable<Item> weapon
)
{
// If mod or mods parent is the NcSTAR MPR45 Backup mount, allow it as it looks cool
if (modsParent.Id == ItemTpl.MOUNT_NCSTAR_MPR45_BACKUP || modTemplate.Id == ItemTpl.MOUNT_NCSTAR_MPR45_BACKUP)
{
// If weapon already has a longer ranged scope on it, allow ncstar to be spawned
if (
weapon.Any(item =>
itemHelper.IsOfBaseclasses(
item.Template,
[BaseClasses.ASSAULT_SCOPE, BaseClasses.OPTIC_SCOPE, BaseClasses.SPECIAL_SCOPE]
)
)
)
{
return false;
}
return true;
}
// Mods parent is scope and mod is scope, allow it (adds those mini-sights to the tops of sights)
var modIsScope = itemHelper.IsOfBaseclasses(modTemplate.Id, modLimits.ScopeBaseTypes);
if (itemHelper.IsOfBaseclasses(modsParent.Id, modLimits.ScopeBaseTypes) && modIsScope)
{
return false;
}
// If mod is a scope , Exit early
if (modIsScope)
{
return WeaponModLimitReached(modTemplate.Id, modLimits.Scope, modLimits.ScopeMax ?? 0, botRole);
}
// Don't allow multiple mounts on a weapon (except when mount is on another mount)
// Fail when:
// Over or at scope limit on weapon
// Item being added is a mount but the parent item is NOT a mount (Allows red dot sub-mounts on mounts)
// Mount has one slot and it is for a mod_scope
if (
modLimits.Scope.Count >= modLimits.ScopeMax
&& modTemplate.Properties?.Slots?.Count() == 1
&& itemHelper.IsOfBaseclass(modTemplate.Id, BaseClasses.MOUNT)
&& !itemHelper.IsOfBaseclass(modsParent.Id, BaseClasses.MOUNT)
&& modTemplate.Properties.Slots.Any(slot => slot.Name == "mod_scope")
)
{
return true;
}
// If mod is a light/laser, return if limit reached
var modIsLightOrLaser = itemHelper.IsOfBaseclasses(modTemplate.Id, modLimits.FlashlightLaserBaseTypes);
if (modIsLightOrLaser)
{
return WeaponModLimitReached(
modTemplate.Id,
modLimits.FlashlightLaser,
modLimits.FlashlightLaserMax ?? 0,
botRole,
"light/laser"
);
}
// Mod is a mount that can hold only flashlights ad limit is reached (don't want to add empty mounts if limit is reached)
if (
modLimits.Scope.Count >= modLimits.ScopeMax
&& modTemplate.Properties?.Slots?.Count() == 1
&& itemHelper.IsOfBaseclass(modTemplate.Id, BaseClasses.MOUNT)
&& modTemplate.Properties.Slots.Any(slot => slot.Name == "mod_flashlight")
)
{
return true;
}
return false;
}
/// <summary>
/// Check if the specific item type on the weapon has reached the set limit
/// </summary>
/// <param name="modTpl">log mod tpl if over type limit</param>
/// <param name="currentCount">current number of this item on gun</param>
/// <param name="maxLimit">mod limit allowed</param>
/// <param name="botRole">role of bot we're checking weapon of</param>
/// <param name="modType">OPTIONAL: Type of mod, scope or lightlaser</param>
/// <returns>true if limit reached</returns>
protected bool WeaponModLimitReached(MongoId modTpl, ItemCount currentCount, int? maxLimit, string botRole, string modType = "scope")
{
// No limit, ignore
if (maxLimit is null or 0)
{
return false;
}
// Has mod limit for bot type been reached
if (currentCount.Count >= maxLimit)
{
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"[{botRole}] {modType} limit reached! tried to add: {modTpl} but {modType} count is: {currentCount.Count}");
}
return true;
}
// Increment mod count limit
currentCount.Count++;
return false;
}
}