Files
SPT-Server-Build/Libraries/Core/Generators/RagfairOfferGenerator.cs
T
2025-02-21 15:05:00 +00:00

1049 lines
37 KiB
C#

using System.Diagnostics;
using Core.Helpers;
using Core.Models.Eft.Common.Tables;
using Core.Models.Eft.Ragfair;
using Core.Models.Enums;
using Core.Models.Spt.Config;
using Core.Models.Spt.Ragfair;
using Core.Models.Utils;
using Core.Servers;
using Core.Services;
using Core.Utils;
using Core.Utils.Cloners;
using SptCommon.Annotations;
using SptCommon.Extensions;
using LogLevel = Core.Models.Spt.Logging.LogLevel;
namespace Core.Generators;
[Injectable]
public class RagfairOfferGenerator(
ISptLogger<RagfairOfferGenerator> logger,
HashUtil hashUtil,
RandomUtil randomUtil,
TimeUtil timeUtil,
DatabaseService databaseService,
RagfairServerHelper ragfairServerHelper,
ProfileHelper profileHelper,
HandbookHelper handbookHelper,
BotHelper botHelper,
SaveServer saveServer,
PresetHelper presetHelper,
RagfairAssortGenerator ragfairAssortGenerator,
RagfairOfferService ragfairOfferService,
RagfairPriceService ragfairPriceService,
LocalisationService localisationService,
PaymentHelper paymentHelper,
ItemHelper itemHelper,
ConfigServer configServer,
ICloner cloner
)
{
protected List<TplWithFleaPrice>? allowedFleaPriceItemsForBarter;
protected BotConfig botConfig = configServer.GetConfig<BotConfig>();
/**
* Internal counter to ensure each offer created has a unique value for its intId property
*/
protected int offerCounter;
protected RagfairConfig ragfairConfig = configServer.GetConfig<RagfairConfig>();
/// <summary>
/// Create a flea offer and store it in the Ragfair server offers array
/// </summary>
/// <param name="userId">Owner of the offer</param>
/// <param name="time">Time offer is listed at</param>
/// <param name="items">Items in the offer</param>
/// <param name="barterScheme">Cost of item (currency or barter)</param>
/// <param name="loyalLevel">Loyalty level needed to buy item</param>
/// <param name="sellInOnePiece">Flags sellInOnePiece to be true</param>
/// <returns>RagfairOffer</returns>
public RagfairOffer CreateAndAddFleaOffer(
string userId,
long time,
List<Item> items,
List<BarterScheme> barterScheme,
int loyalLevel,
bool sellInOnePiece = false
)
{
var offer = CreateOffer(userId, time, items, barterScheme, loyalLevel, sellInOnePiece);
ragfairOfferService.AddOffer(offer);
return offer;
}
/// <summary>
/// Create an offer object ready to send to ragfairOfferService.addOffer()
/// </summary>
/// <param name="userId">Owner of the offer</param>
/// <param name="time">Timestamp offer is listed at</param>
/// <param name="items">Items in the offer</param>
/// <param name="barterScheme">Cost of item (currency or barter)</param>
/// <param name="loyalLevel">Loyalty level needed to buy item</param>
/// <param name="isPackOffer">Is offer being created flagged as a pack</param>
/// <returns>RagfairOffer</returns>
protected RagfairOffer CreateOffer(
string userId,
long time,
List<Item> items,
List<BarterScheme> barterScheme,
int loyalLevel,
bool isPackOffer = false
)
{
var offerRequirements = barterScheme.Select(
barter =>
{
var offerRequirement = new OfferRequirement
{
Template = barter.Template,
Count = Math.Round(barter.Count.Value, 2),
OnlyFunctional = barter.OnlyFunctional ?? false
};
// Dogtags define level and side
if (barter.Level != null)
{
offerRequirement.Level = barter.Level;
offerRequirement.Side = barter.Side;
}
return offerRequirement;
}
)
.ToList();
// Clone to avoid modifying original array
var itemsClone = cloner.Clone(items);
var rootItem = itemsClone.FirstOrDefault();
var itemStackCount = itemsClone[0].Upd?.StackObjectsCount ?? 1;
// Hydrate ammo boxes with cartridges + ensure only 1 item is present (ammo box)
// On offer refresh don't re-add cartridges to ammo box that already has cartridges
if (itemHelper.IsOfBaseclass(itemsClone[0].Template, BaseClasses.AMMO_BOX) && itemsClone.Count == 1)
{
itemHelper.AddCartridgesToAmmoBox(itemsClone, itemHelper.GetItem(rootItem.Template).Value);
}
var roubleListingPrice = Math.Round(ConvertOfferRequirementsIntoRoubles(offerRequirements));
var singleItemListingPrice = isPackOffer ? roubleListingPrice / itemStackCount : roubleListingPrice;
var offer = new RagfairOffer
{
Id = hashUtil.Generate(),
InternalId = offerCounter,
User = CreateUserDataForFleaOffer(userId, ragfairServerHelper.IsTrader(userId)),
Root = rootItem.Id,
Items = itemsClone,
ItemsCost = Math.Round(handbookHelper.GetTemplatePrice(rootItem.Template)), // Handbook price
Requirements = offerRequirements,
RequirementsCost = Math.Round(singleItemListingPrice),
SummaryCost = roubleListingPrice,
StartTime = time,
EndTime = GetOfferEndTime(userId, time),
LoyaltyLevel = loyalLevel,
SellInOnePiece = isPackOffer,
Locked = false,
Quantity = (int)(rootItem.Upd?.StackObjectsCount ?? 1)
};
offerCounter++;
return offer;
}
/// <summary>
/// Create the user object stored inside each flea offer object
/// </summary>
/// <param name="userId">User creating the offer</param>
/// <param name="isTrader">Is the user creating the offer a trader</param>
/// <returns>RagfairOfferUser</returns>
protected RagfairOfferUser CreateUserDataForFleaOffer(string userId, bool isTrader)
{
// Trader offer
if (isTrader)
{
return new RagfairOfferUser
{
Id = userId,
MemberType = MemberCategory.Trader
};
}
var isPlayerOffer = profileHelper.IsPlayer(userId);
if (isPlayerOffer)
{
var playerProfile = profileHelper.GetPmcProfile(userId);
return new RagfairOfferUser
{
Id = playerProfile.Id,
MemberType = playerProfile.Info.MemberCategory,
SelectedMemberCategory = playerProfile.Info.SelectedMemberCategory,
Nickname = playerProfile.Info.Nickname,
Rating = playerProfile.RagfairInfo.Rating ?? 0,
IsRatingGrowing = playerProfile.RagfairInfo.IsRatingGrowing,
Avatar = null,
Aid = playerProfile.Aid
};
}
// 'Fake' pmc offer
return new RagfairOfferUser
{
Id = userId,
MemberType = MemberCategory.Default,
Nickname = botHelper.GetPmcNicknameOfMaxLength(botConfig.BotNameLengthLimit),
Rating = randomUtil.GetDouble(
ragfairConfig.Dynamic.Rating.Min,
ragfairConfig.Dynamic.Rating.Max
),
IsRatingGrowing = randomUtil.GetBool(),
Avatar = null,
Aid = hashUtil.GenerateAccountId()
};
}
/**
* Calculate the offer price that's listed on the flea listing
* @param offerRequirements barter requirements for offer
* @returns rouble cost of offer
*/
protected double ConvertOfferRequirementsIntoRoubles(IEnumerable<OfferRequirement> offerRequirements)
{
var roublePrice = 0d;
foreach (var requirement in offerRequirements)
{
roublePrice += (paymentHelper.IsMoneyTpl(requirement.Template)
? Math.Round(CalculateRoublePrice(requirement.Count.Value, requirement.Template))
: ragfairPriceService.GetFleaPriceForItem(requirement.Template) * requirement.Count.Value); // Get flea price for barter offer items
}
return roublePrice;
}
/**
* Get avatar url from trader table in db
* @param isTrader Is user we're getting avatar for a trader
* @param userId persons id to get avatar of
* @returns url of avatar
*/
protected string GetAvatarUrl(bool isTrader, string userId)
{
if (isTrader)
{
return databaseService.GetTrader(userId).Base.Avatar;
}
return "/files/trader/avatar/unknown.jpg";
}
/**
* Convert a count of currency into roubles
* @param currencyCount amount of currency to convert into roubles
* @param currencyType Type of currency (euro/dollar/rouble)
* @returns count of roubles
*/
protected double CalculateRoublePrice(double currencyCount, string currencyType)
{
if (currencyType == Money.ROUBLES)
{
return currencyCount;
}
return handbookHelper.InRUB(currencyCount, currencyType);
}
/**
* Check userId, if its a player, return their pmc _id, otherwise return userId parameter
* @param userId Users Id to check
* @returns Users Id
*/
protected string GetTraderId(string userId)
{
if (profileHelper.IsPlayer(userId))
{
return saveServer.GetProfile(userId).CharacterData.PmcData.Id;
}
return userId;
}
/**
* Get a flea trading rating for the passed in user
* @param userId User to get flea rating of
* @returns Flea rating value
*/
protected double? GetRating(string userId)
{
if (profileHelper.IsPlayer(userId))
// Player offer
{
return saveServer.GetProfile(userId).CharacterData?.PmcData?.RagfairInfo?.Rating;
}
if (ragfairServerHelper.IsTrader(userId))
// Trader offer
{
return 1;
}
// Generated pmc offer
return randomUtil.GetDouble((double) ragfairConfig.Dynamic.Rating.Min, (double) ragfairConfig.Dynamic.Rating.Max);
}
/**
* Is the offers user rating growing
* @param userID user to check rating of
* @returns true if its growing
*/
protected bool GetRatingGrowing(string userID)
{
if (profileHelper.IsPlayer(userID))
// player offer
{
return saveServer.GetProfile(userID).CharacterData?.PmcData?.RagfairInfo?.IsRatingGrowing ?? false;
}
if (ragfairServerHelper.IsTrader(userID))
// trader offer
{
return true;
}
// generated offer
// 50/50 growing/falling
return randomUtil.GetBool();
}
/**
* Get number of section until offer should expire
* @param userID Id of the offer owner
* @param time Time the offer is posted
* @returns number of seconds until offer expires
*/
protected long GetOfferEndTime(string userID, long time)
{
if (profileHelper.IsPlayer(userID))
{
// Player offer = current time + offerDurationTimeInHour;
var offerDurationTimeHours = databaseService.GetGlobals().Configuration.RagFair.OfferDurationTimeInHour;
return (long) (timeUtil.GetTimeStamp() + Math.Round((double) offerDurationTimeHours * TimeUtil.OneHourAsSeconds));
}
if (ragfairServerHelper.IsTrader(userID))
// Trader offer
{
return (long) databaseService.GetTrader(userID).Base.NextResupply;
}
// Generated fake-player offer
return (long) Math.Round(
time + randomUtil.GetDouble(ragfairConfig.Dynamic.EndTimeSeconds.Min, ragfairConfig.Dynamic.EndTimeSeconds.Max)
);
}
/**
* Create multiple offers for items by using a unique list of items we've generated previously
* @param expiredOffers optional, expired offers to regenerate
*/
public void GenerateDynamicOffers(List<List<Item>>? expiredOffers = null)
{
var replacingExpiredOffers = (expiredOffers?.Count ?? 0) > 0;
var stopwatch = Stopwatch.StartNew();
// get assort items from param if they exist, otherwise grab freshly generated assorts
var assortItemsToProcess = replacingExpiredOffers
? expiredOffers ?? []
: ragfairAssortGenerator.GetAssortItems();
stopwatch.Stop();
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GetRagfairAssorts");
}
stopwatch.Restart();
var tasks = new List<Task>();
foreach (var assortItem in assortItemsToProcess)
{
tasks.Add(
Task.Factory.StartNew(
() =>
{
CreateOffersFromAssort(assortItem, replacingExpiredOffers, ragfairConfig.Dynamic);
}
)
);
}
Task.WaitAll(tasks.ToArray());
stopwatch.Stop();
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to CreateOffersFromAssort");
}
}
/**
* @param assortItemWithChildren Item with its children to process into offers
* @param isExpiredOffer is an expired offer
* @param config Ragfair dynamic config
*/
protected void CreateOffersFromAssort(
List<Item> assortItemWithChildren,
bool isExpiredOffer,
Dynamic config
)
{
var itemToSellDetails = itemHelper.GetItem(assortItemWithChildren[0].Template);
var isPreset = presetHelper.IsPreset(assortItemWithChildren[0].Upd.SptPresetId);
// Only perform checks on newly generated items, skip expired items being refreshed
if (!(isExpiredOffer || ragfairServerHelper.IsItemValidRagfairItem(itemToSellDetails)))
{
return;
}
// Armor presets can hold plates above the allowed flea level, remove if necessary
if (isPreset && ragfairConfig.Dynamic.Blacklist.EnableBsgList)
{
RemoveBannedPlatesFromPreset(assortItemWithChildren, ragfairConfig.Dynamic.Blacklist.ArmorPlate);
}
// Get number of offers to create
// Limit to 1 offer when processing expired - like-for-like replacement
var offerCount = isExpiredOffer
? 1
: randomUtil.GetDouble(config.OfferItemCount.Min, config.OfferItemCount.Max);
/* // TODO: ???????
if (ProgramStatics.DEBUG && !ProgramStatics.COMPILED) {
offerCount = 2;
}
*/
for (var index = 0; index < offerCount; index++)
{
// Clone the item so we don't have shared references and generate new item IDs
var clonedAssort = cloner.Clone(assortItemWithChildren);
itemHelper.ReparentItemAndChildren(clonedAssort[0], clonedAssort);
// Clear unnecessary properties
clonedAssort[0].ParentId = null;
clonedAssort[0].SlotId = null;
CreateSingleOfferForItem(hashUtil.Generate(), clonedAssort, isPreset, itemToSellDetails.Value);
}
}
/**
* iterate over an items chidren and look for plates above desired level and remove them
* @param presetWithChildren preset to check for plates
* @param plateSettings Settings
* @returns True if plate removed
*/
protected bool RemoveBannedPlatesFromPreset(
List<Item> presetWithChildren,
ArmorPlateBlacklistSettings plateSettings
)
{
if (!itemHelper.ArmorItemCanHoldMods(presetWithChildren[0].Template))
// Cant hold armor inserts, skip
{
return false;
}
var plateSlots = presetWithChildren.Where(item => itemHelper.GetRemovablePlateSlotIds().Contains(item.SlotId?.ToLower())).ToList();
if (plateSlots.Count == 0)
// Has no plate slots e.g. "front_plate", exit
{
return false;
}
var removedPlate = false;
foreach (var plateSlot in plateSlots)
{
var plateDetails = itemHelper.GetItem(plateSlot.Template).Value;
if (plateSettings.IgnoreSlots.Contains(plateSlot.SlotId.ToLower()))
{
continue;
}
var plateArmorLevel = plateDetails.Properties.ArmorClass ?? 0;
if (plateArmorLevel > plateSettings.MaxProtectionLevel)
{
presetWithChildren.Splice(presetWithChildren.IndexOf(plateSlot), 1);
removedPlate = true;
}
}
return removedPlate;
}
/**
* Create one flea offer for a specific item
* @param sellerId Id of seller
* @param itemWithChildren Item to create offer for
* @param isPreset Is item a weapon preset
* @param itemToSellDetails Raw db item details
* @returns Item array
*/
protected void CreateSingleOfferForItem(
string sellerId,
List<Item> itemWithChildren,
bool isPreset,
TemplateItem itemToSellDetails
)
{
// Set stack size to random value
itemWithChildren[0].Upd.StackObjectsCount = ragfairServerHelper.CalculateDynamicStackCount(
itemWithChildren[0].Template,
isPreset
);
var isBarterOffer = randomUtil.GetChance100(ragfairConfig.Dynamic.Barter.ChancePercent);
var isPackOffer =
randomUtil.GetChance100(ragfairConfig.Dynamic.Pack.ChancePercent) &&
!isBarterOffer &&
itemWithChildren.Count == 1 &&
itemHelper.IsOfBaseclasses(
itemWithChildren[0].Template,
ragfairConfig.Dynamic.Pack.ItemTypeWhitelist
);
// Remove removable plates if % check passes
if (itemHelper.ArmorItemCanHoldMods(itemWithChildren[0].Template))
{
var armorConfig = ragfairConfig.Dynamic.Armor;
var shouldRemovePlates = randomUtil.GetChance100(armorConfig.RemoveRemovablePlateChance);
if (shouldRemovePlates && itemHelper.ArmorItemHasRemovablePlateSlots(itemWithChildren[0].Template))
{
var offerItemPlatesToRemove = itemWithChildren.Where(
item =>
armorConfig.PlateSlotIdToRemovePool.Contains(item.SlotId?.ToLower())
);
// Latest first, to ensure we don't move later items off by 1 each time we remove an item below it
var indexesToRemove = offerItemPlatesToRemove.Select(plateItem => itemWithChildren.IndexOf(plateItem))
.ToHashSet();
foreach (var index in indexesToRemove.OrderByDescending(x => x))
{
itemWithChildren.RemoveAt(index);
}
}
}
List<BarterScheme> barterScheme;
if (isPackOffer)
{
// Set pack size
var stackSize = randomUtil.GetInt(
ragfairConfig.Dynamic.Pack.ItemCountMin,
ragfairConfig.Dynamic.Pack.ItemCountMax
);
itemWithChildren[0].Upd.StackObjectsCount = stackSize;
// Don't randomise pack items
barterScheme = CreateCurrencyBarterScheme(itemWithChildren, isPackOffer, stackSize);
}
else if (isBarterOffer)
{
// Apply randomised properties
RandomiseOfferItemUpdProperties(sellerId, itemWithChildren, itemToSellDetails);
barterScheme = CreateBarterBarterScheme(itemWithChildren, ragfairConfig.Dynamic.Barter);
if (ragfairConfig.Dynamic.Barter.MakeSingleStackOnly)
{
itemWithChildren[0].Upd.StackObjectsCount = 1;
}
}
else
{
// Apply randomised properties
RandomiseOfferItemUpdProperties(sellerId, itemWithChildren, itemToSellDetails);
barterScheme = CreateCurrencyBarterScheme(itemWithChildren, isPackOffer);
}
var offer = CreateAndAddFleaOffer(
sellerId,
timeUtil.GetTimeStamp(),
itemWithChildren,
barterScheme,
1,
isPackOffer // sellAsOnePiece - pack offer
);
}
/**
* Generate trader offers on flea using the traders assort data
* @param traderID Trader to generate offers for
*/
public void GenerateFleaOffersForTrader(string traderID)
{
// Purge
ragfairOfferService.RemoveAllOffersByTrader(traderID);
var time = timeUtil.GetTimeStamp();
var trader = databaseService.GetTrader(traderID);
var assortsClone = cloner.Clone(trader.Assort);
// Trader assorts / assort items are missing
if (assortsClone?.Items?.Count is null or 0)
{
logger.Error(
localisationService.GetText(
"ragfair-no_trader_assorts_cant_generate_flea_offers",
trader.Base.Nickname
)
);
return;
}
var blacklist = ragfairConfig.Dynamic.Blacklist;
foreach (var item in assortsClone.Items)
{
// We only want to process 'base/root' items, no children
if (item.SlotId != "hideout")
// skip mod items
{
continue;
}
// Run blacklist check on trader offers
if (blacklist.TraderItems)
{
var itemDetails = itemHelper.GetItem(item.Template);
if (!itemDetails.Key)
{
logger.Warning(localisationService.GetText("ragfair-tpl_not_a_valid_item", item.Template));
continue;
}
// Don't include items that BSG has blacklisted from flea
if (blacklist.EnableBsgList && !(itemDetails.Value?.Properties?.CanSellOnRagfair ?? false))
{
continue;
}
}
var isPreset = presetHelper.IsPreset(item.Id);
var items = isPreset
? ragfairServerHelper.GetPresetItems(item)
: [item, ..itemHelper.FindAndReturnChildrenByAssort(item.Id, assortsClone.Items)];
if (!assortsClone.BarterScheme.TryGetValue(item.Id, out var barterScheme))
{
logger.Warning(
localisationService.GetText(
"ragfair-missing_barter_scheme",
new
{
itemId = item.Id,
tpl = item.Template,
name = trader.Base.Nickname
}
)
);
continue;
}
var barterSchemeItems = assortsClone.BarterScheme[item.Id][0];
var loyalLevel = assortsClone.LoyalLevelItems[item.Id];
var offer = CreateAndAddFleaOffer(traderID, time, items, barterSchemeItems, loyalLevel);
// Refresh complete, reset flag to false
trader.Base.RefreshTraderRagfairOffers = false;
}
}
/**
* Get array of an item with its mods + condition properties (e.g durability)
* Apply randomisation adjustments to condition if item base is found in ragfair.json/dynamic/condition
* @param userID id of owner of item
* @param itemWithMods Item and mods, get condition of first item (only first array item is modified)
* @param itemDetails db details of first item
*/
protected void RandomiseOfferItemUpdProperties(string userID, List<Item> itemWithMods, TemplateItem itemDetails)
{
// Add any missing properties to first item in array
AddMissingConditions(itemWithMods[0]);
if (!(profileHelper.IsPlayer(userID) || ragfairServerHelper.IsTrader(userID)))
{
var parentId = GetDynamicConditionIdForTpl(itemDetails.Id);
if (string.IsNullOrEmpty(parentId))
// No condition details found, don't proceed with modifying item conditions
{
return;
}
// Roll random chance to randomise item condition
if (randomUtil.GetChance100(ragfairConfig.Dynamic.Condition[parentId].ConditionChance * 100))
{
RandomiseItemCondition(parentId, itemWithMods, itemDetails);
}
}
}
/**
* Get the relevant condition id if item tpl matches in ragfair.json/condition
* @param tpl Item to look for matching condition object
* @returns condition id
*/
protected string? GetDynamicConditionIdForTpl(string tpl)
{
// Get keys from condition config dictionary
var configConditions = ragfairConfig.Dynamic.Condition.Keys;
foreach (var baseClass in configConditions)
{
if (itemHelper.IsOfBaseclass(tpl, baseClass))
{
return baseClass;
}
}
return null;
}
/**
* Alter an items condition based on its item base type
* @param conditionSettingsId also the parentId of item being altered
* @param itemWithMods Item to adjust condition details of
* @param itemDetails db item details of first item in array
*/
protected void RandomiseItemCondition(
string conditionSettingsId,
List<Item> itemWithMods,
TemplateItem itemDetails
)
{
var rootItem = itemWithMods[0];
var itemConditionValues = ragfairConfig.Dynamic.Condition[conditionSettingsId];
var maxMultiplier = randomUtil.GetDouble((double) itemConditionValues.Max.Min, (double) itemConditionValues.Max.Min);
var currentMultiplier = randomUtil.GetDouble(
(double) itemConditionValues.Current.Min,
(double) itemConditionValues.Current.Max
);
// Randomise armor + plates + armor related things
if (itemHelper.ArmorItemCanHoldMods(rootItem.Template) ||
itemHelper.IsOfBaseclasses(rootItem.Template, [BaseClasses.ARMOR_PLATE, BaseClasses.ARMORED_EQUIPMENT])
)
{
RandomiseArmorDurabilityValues(itemWithMods, currentMultiplier, maxMultiplier);
// Add hits to visor
var visorMod = itemWithMods.FirstOrDefault(item => item.ParentId == BaseClasses.ARMORED_EQUIPMENT && item.SlotId == "mod_equipment_000");
if (randomUtil.GetChance100(25) && visorMod != null)
{
itemHelper.AddUpdObjectToItem(visorMod);
visorMod.Upd.FaceShield = new UpdFaceShield
{
Hits = randomUtil.GetInt(1, 3)
};
}
return;
}
// Randomise Weapons
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.WEAPON))
{
RandomiseWeaponDurability(itemWithMods[0], itemDetails, maxMultiplier, currentMultiplier);
return;
}
if (rootItem.Upd?.MedKit != null)
{
// Randomize health
var hpResource = Math.Round((double) rootItem.Upd.MedKit.HpResource * maxMultiplier);
rootItem.Upd.MedKit.HpResource = hpResource == 0D ? 1D : hpResource;
return;
}
if (rootItem.Upd?.Key != null && itemDetails.Properties.MaximumNumberOfUsage > 1)
{
// Randomize key uses
rootItem.Upd.Key.NumberOfUsages = (int?) Math.Round(itemDetails.Properties.MaximumNumberOfUsage.Value * (1 - maxMultiplier));
return;
}
if (rootItem.Upd?.FoodDrink != null)
{
// randomize food/drink value
var hpPercent = Math.Round((double) itemDetails.Properties.MaxResource * maxMultiplier);
rootItem.Upd.FoodDrink.HpPercent = hpPercent == 0D ? 1D : hpPercent;
return;
}
if (rootItem.Upd?.RepairKit != null)
{
// randomize repair kit (armor/weapon) uses
var resource = Math.Round((double) itemDetails.Properties.MaxRepairResource * maxMultiplier);
rootItem.Upd.RepairKit.Resource = resource == 0D ? 1D : resource;
return;
}
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.FUEL))
{
var totalCapacity = itemDetails.Properties.MaxResource;
var remainingFuel = Math.Round((double) totalCapacity * maxMultiplier);
rootItem.Upd.Resource = new UpdResource
{
UnitsConsumed = totalCapacity - remainingFuel,
Value = remainingFuel
};
}
}
/**
* Adjust an items durability/maxDurability value
* @param item item (weapon/armor) to Adjust
* @param itemDbDetails Weapon details from db
* @param maxMultiplier Value to multiply max durability by
* @param currentMultiplier Value to multiply current durability by
*/
protected void RandomiseWeaponDurability(
Item item,
TemplateItem itemDbDetails,
double maxMultiplier,
double currentMultiplier
)
{
// Max
var baseMaxDurability = itemDbDetails.Properties.MaxDurability;
var lowestMaxDurability = randomUtil.GetDouble(maxMultiplier, 1) * baseMaxDurability;
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double) lowestMaxDurability, (double) baseMaxDurability));
// Current
var lowestCurrentDurability = randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability;
var chosenCurrentDurability = Math.Round(randomUtil.GetDouble(lowestCurrentDurability, chosenMaxDurability));
item.Upd.Repairable.Durability = chosenCurrentDurability == 0 ? 1D : chosenCurrentDurability; // Never var value become 0
item.Upd.Repairable.MaxDurability = chosenMaxDurability;
}
/**
* Randomise the durabiltiy values for an armors plates and soft inserts
* @param armorWithMods Armor item with its child mods
* @param currentMultiplier Chosen multipler to use for current durability value
* @param maxMultiplier Chosen multipler to use for max durability value
*/
protected void RandomiseArmorDurabilityValues(
List<Item> armorWithMods,
double currentMultiplier,
double maxMultiplier
)
{
foreach (var armorItem in armorWithMods)
{
var itemDbDetails = itemHelper.GetItem(armorItem.Template).Value;
if (itemDbDetails.Properties.ArmorClass > 1)
{
itemHelper.AddUpdObjectToItem(armorItem);
var baseMaxDurability = itemDbDetails.Properties.MaxDurability;
var lowestMaxDurability = randomUtil.GetDouble(maxMultiplier, 1) * baseMaxDurability;
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double) lowestMaxDurability, (double) baseMaxDurability));
var lowestCurrentDurability = randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability;
var chosenCurrentDurability = Math.Round(randomUtil.GetDouble(lowestCurrentDurability, chosenMaxDurability));
armorItem.Upd.Repairable = new UpdRepairable
{
Durability = chosenCurrentDurability == 0D ? 1D : chosenCurrentDurability, // Never var value become 0
MaxDurability = chosenMaxDurability
};
}
}
}
/**
* Add missing conditions to an item if needed
* Durabiltiy for repairable items
* HpResource for medical items
* @param item item to add conditions to
*/
protected void AddMissingConditions(Item item)
{
var props = itemHelper.GetItem(item.Template).Value.Properties;
var isRepairable = props.Durability != null;
var isMedkit = props.MaxHpResource != null;
var isKey = props.MaximumNumberOfUsage != null;
var isConsumable = props.MaxResource > 1 && props.FoodUseTime != null;
var isRepairKit = props.MaxRepairResource != null;
if (isRepairable && props.Durability > 0)
{
item.Upd.Repairable = new UpdRepairable
{
Durability = props.Durability,
MaxDurability = props.Durability
};
return;
}
if (isMedkit && props.MaxHpResource > 0)
{
item.Upd.MedKit = new UpdMedKit
{
HpResource = props.MaxHpResource
};
return;
}
if (isKey)
{
item.Upd.Key = new UpdKey
{
NumberOfUsages = 0
};
return;
}
// Food/drink
if (isConsumable)
{
item.Upd.FoodDrink = new UpdFoodDrink
{
HpPercent = props.MaxResource
};
return;
}
if (isRepairKit)
{
item.Upd.RepairKit = new UpdRepairKit
{
Resource = props.MaxRepairResource
};
}
}
/**
* Create a barter-based barter scheme, if not possible, fall back to making barter scheme currency based
* @param offerItems Items for sale in offer
* @param barterConfig Barter config from ragfairConfig.Dynamic.barter
* @returns Barter scheme
*/
protected List<BarterScheme> CreateBarterBarterScheme(List<Item> offerItems, BarterDetails barterConfig)
{
// Get flea price of item being sold
var priceOfOfferItem = ragfairPriceService.GetDynamicOfferPriceForOffer(
offerItems,
Money.ROUBLES,
false
);
// Dont make items under a designated rouble value into barter offers
if (priceOfOfferItem < barterConfig.MinRoubleCostToBecomeBarter)
{
return CreateCurrencyBarterScheme(offerItems, false);
}
// Get a randomised number of barter items to list offer for
var barterItemCount = randomUtil.GetInt(barterConfig.ItemCountMin, barterConfig.ItemCountMax);
// Get desired cost of individual item offer will be listed for e.g. offer = 15k, item count = 3, desired item cost = 5k
var desiredItemCostRouble = Math.Round(priceOfOfferItem / barterItemCount);
// Rouble amount to go above/below when looking for an item (Wiggle cost of item a little)
var offerCostVarianceRoubles = desiredItemCostRouble * barterConfig.PriceRangeVariancePercent / 100;
// Dict of items and their flea price (cached on first use)
var itemFleaPrices = GetFleaPricesAsArray();
// Filter possible barters to items that match the price range + not itself
var itemsInsidePriceBounds = itemFleaPrices.Where(
itemAndPrice =>
itemAndPrice.Price >= desiredItemCostRouble - offerCostVarianceRoubles &&
itemAndPrice.Price <= desiredItemCostRouble + offerCostVarianceRoubles &&
itemAndPrice.Tpl != offerItems[0].Template // Don't allow the item being sold to be chosen
)
.ToList();
// No items on flea have a matching price, fall back to currency
if (itemsInsidePriceBounds.Count == 0)
{
return CreateCurrencyBarterScheme(offerItems, false);
}
// Choose random item from price-filtered flea items
var randomItem = randomUtil.GetArrayValue(itemsInsidePriceBounds);
return
[
new BarterScheme
{
Count = barterItemCount,
Template = randomItem.Tpl
}
];
}
/**
* Get an array of flea prices + item tpl, cached in generator class inside `allowedFleaPriceItemsForBarter`
* @returns array with tpl/price values
*/
protected List<TplWithFleaPrice> GetFleaPricesAsArray()
{
// Generate if needed
if (allowedFleaPriceItemsForBarter == null)
{
var fleaPrices = databaseService.GetPrices();
// Only get prices for items that also exist in items.json
var filteredFleaItems = fleaPrices
.Select(
kvTpl => new TplWithFleaPrice
{
Tpl = kvTpl.Key,
Price = kvTpl.Value
}
)
.Where(item => itemHelper.GetItem(item.Tpl).Key);
var itemTypeBlacklist = ragfairConfig.Dynamic.Barter.ItemTypeBlacklist;
allowedFleaPriceItemsForBarter = filteredFleaItems.Where(item => !itemHelper.IsOfBaseclasses(item.Tpl, itemTypeBlacklist)).ToList();
}
return allowedFleaPriceItemsForBarter;
}
/**
* Create a random currency-based barter scheme for an array of items
* @param offerWithChildren Items on offer
* @param isPackOffer Is the barter scheme being created for a pack offer
* @param multipler What to multiply the resulting price by
* @returns Barter scheme for offer
*/
protected List<BarterScheme> CreateCurrencyBarterScheme(
List<Item> offerWithChildren,
bool isPackOffer,
double multipler = 1
)
{
var currency = ragfairServerHelper.GetDynamicOfferCurrency();
var price = ragfairPriceService.GetDynamicOfferPriceForOffer(offerWithChildren, currency, isPackOffer) * multipler;
return
[
new BarterScheme
{
Count = price,
Template = currency
}
];
}
}