Merge branch 'main' of https://github.com/sp-tarkov/server-csharp
This commit is contained in:
@@ -99,8 +99,27 @@ public class CustomizationController
|
||||
ItemId = suitDetails?.Id,
|
||||
ItemName = suitDetails?.Name,
|
||||
}));
|
||||
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
|
||||
PayForClothingItems(sessionId, pmcData, buyClothingRequest.Items, output);
|
||||
|
||||
var profile = _saveServer.GetProfile(sessionId);
|
||||
|
||||
profile.Suits.Add(suitId);
|
||||
|
||||
//TODO: Merge with function _profileHelper.addHideoutCustomisationUnlock
|
||||
var rewardToStore = new CustomisationStorage()
|
||||
{
|
||||
Id = suitId,
|
||||
Source = CustomisationSource.UNLOCKED_IN_GAME,
|
||||
Type = CustomisationType.SUITE
|
||||
};
|
||||
profile.CustomisationUnlocks.Add(rewardToStore);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ public class BotGenerator
|
||||
{
|
||||
_logger.Error("NOT IMPLEMENTED BotGenerator.GenerateBot");
|
||||
|
||||
return new BotBase();
|
||||
return bot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+16
-15
@@ -685,7 +685,7 @@ public class ItemHelper
|
||||
slot?.Name == item?.SlotId &&
|
||||
(slot?.Required ?? false)
|
||||
) ?? false;
|
||||
|
||||
|
||||
return itemTemplate.Key && parentTemplate.Key && (isNotRaidModdable || isRequiredSlot);
|
||||
}
|
||||
|
||||
@@ -714,7 +714,7 @@ public class ItemHelper
|
||||
if (currentItem == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return currentItem;
|
||||
}
|
||||
|
||||
@@ -728,7 +728,6 @@ public class ItemHelper
|
||||
{
|
||||
// TODO: actually implement
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -976,22 +975,24 @@ public class ItemHelper
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Update a root items _id property value to be unique
|
||||
// Item to update root items _id property
|
||||
// Optional: new id to use
|
||||
// Returns New root id
|
||||
public string RemapRootItemId(List<Item> itemWithChildren, string newId) // TODO: string newId = this.hashUtil.Generate()
|
||||
// Update a root items _id property value to be unique
|
||||
// Item to update root items _id property
|
||||
// Optional: new id to use
|
||||
// Returns New root id
|
||||
// TODO: string newId used to default with _hashUtil.Generate(), Now pass this in
|
||||
|
||||
public string RemapRootItemId(List<Item> itemWithChildren, string newId = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Adopts orphaned items by resetting them as root "hideout" items. Helpful in situations where a parent has been
|
||||
// deleted from a group of items and there are children still referencing the missing parent. This method will
|
||||
// remove the reference from the children to the parent and set item properties to root values.
|
||||
//
|
||||
// The ID of the "root" of the container.
|
||||
// Array of Items that should be adjusted.
|
||||
// Returns Array of Items that have been adopted.
|
||||
// Adopts orphaned items by resetting them as root "hideout" items. Helpful in situations where a parent has been
|
||||
// deleted from a group of items and there are children still referencing the missing parent. This method will
|
||||
// remove the reference from the children to the parent and set item properties to root values.
|
||||
//
|
||||
// The ID of the "root" of the container.
|
||||
// Array of Items that should be adjusted.
|
||||
// Returns Array of Items that have been adopted.
|
||||
public List<Item> AdoptOrphanedItems(string rootId, List<Item> items)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
@@ -8,6 +8,7 @@ using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Helpers;
|
||||
@@ -27,6 +28,7 @@ public class QuestRewardHelper
|
||||
private readonly PresetHelper _presetHelper;
|
||||
private readonly LocalisationService _localisationService;
|
||||
private readonly QuestConfig _questConfig;
|
||||
private readonly ICloner _cloner;
|
||||
|
||||
public QuestRewardHelper(
|
||||
ILogger logger,
|
||||
@@ -40,7 +42,9 @@ public class QuestRewardHelper
|
||||
ProfileHelper profileHelper,
|
||||
PresetHelper presetHelper,
|
||||
LocalisationService localisationService,
|
||||
ConfigServer configServer)
|
||||
ConfigServer configServer,
|
||||
ICloner cloner
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_hashUtil = hashUtil;
|
||||
@@ -53,6 +57,7 @@ public class QuestRewardHelper
|
||||
_profileHelper = profileHelper;
|
||||
_presetHelper = presetHelper;
|
||||
_localisationService = localisationService;
|
||||
_cloner = cloner;
|
||||
|
||||
_questConfig = configServer.GetConfig<QuestConfig>(ConfigTypes.QUEST);
|
||||
}
|
||||
@@ -66,9 +71,95 @@ public class QuestRewardHelper
|
||||
* @param questResponse Response to send back to client
|
||||
* @returns Array of reward objects
|
||||
*/
|
||||
public IEnumerable<Item> ApplyQuestReward(PmcData profileData, string questId, QuestStatusEnum state, string sessionId, ItemEventRouterResponse questResponse)
|
||||
public IEnumerable<Item> ApplyQuestReward(PmcData profileData, string questId, QuestStatusEnum state, string sessionId,
|
||||
ItemEventRouterResponse questResponse)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
// Repeatable quest base data is always in PMCProfile, `profileData` may be scav profile
|
||||
// TODO: consider moving repeatable quest data to profile-agnostic location
|
||||
var fullProfile = _profileHelper.GetFullProfile(sessionId);
|
||||
var pmcProfile = fullProfile.CharacterData.PmcData;
|
||||
if (pmcProfile != null)
|
||||
{
|
||||
_logger.Error($"Unable to get pmc profile for: {sessionId}, no rewards given");
|
||||
return Enumerable.Empty<Item>();
|
||||
}
|
||||
|
||||
var questDetails = GetQuestFromDb(questId, pmcProfile);
|
||||
if (questDetails != null)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("quest-unable_to_find_quest_in_db_no_quest_rewards", questId));
|
||||
return Enumerable.Empty<Item>();
|
||||
}
|
||||
|
||||
var questMoneyRewardBonusMultiplier = GetQuestMoneyRewardBonusMultiplier(pmcProfile);
|
||||
if (questMoneyRewardBonusMultiplier > 0) // money = money + (money * intelCenterBonus / 100)
|
||||
questDetails = ApplyMoneyBoost(questDetails, questMoneyRewardBonusMultiplier, state);
|
||||
|
||||
// e.g. 'Success' or 'AvailableForFinish'
|
||||
var questStateAsString = state.ToString();
|
||||
var gameVersion = pmcProfile.Info.GameVersion;
|
||||
var questRewards = (List<QuestReward>?)questDetails.Rewards.GetType().GetProperties().FirstOrDefault(p =>
|
||||
p.Name == questStateAsString).GetValue(questDetails.Rewards);
|
||||
foreach (var reward in questRewards)
|
||||
{
|
||||
if (!QuestRewardIsForGameEdition(reward, gameVersion))
|
||||
continue;
|
||||
|
||||
SkillTypes skillType;
|
||||
|
||||
if (SkillTypes.TryParse(reward.Target, out skillType))
|
||||
{
|
||||
_logger.Error($"Unable to get skill points for: {reward.Target}");
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (reward.Type)
|
||||
{
|
||||
case QuestRewardType.Skill:
|
||||
_profileHelper.AddSkillPointsToPlayer(profileData, skillType, double.Parse((string)reward.Value));
|
||||
break;
|
||||
case QuestRewardType.Experience: // this must occur first as the output object needs to take the modified profile exp value
|
||||
_profileHelper.AddExperienceToPmc(sessionId, int.Parse(reward.Target));
|
||||
break;
|
||||
case QuestRewardType.TraderStanding:
|
||||
_traderHelper.AddStandingToTrader(sessionId, reward.Target, double.Parse((string)reward.Value));
|
||||
break;
|
||||
case QuestRewardType.TraderUnlock:
|
||||
_traderHelper.SetTraderUnlockedState(reward.Target, true, sessionId);
|
||||
break;
|
||||
case QuestRewardType.Item:
|
||||
// Handled by getQuestRewardItems() below
|
||||
break;
|
||||
case QuestRewardType.AssortmentUnlock:
|
||||
// Handled by getAssort(), locked assorts are stripped out by `assortHelper.stripLockedLoyaltyAssort()` before being sent to player
|
||||
break;
|
||||
case QuestRewardType.Achievement:
|
||||
_profileHelper.AddAchievementToProfile(fullProfile, reward.Target);
|
||||
break;
|
||||
case QuestRewardType.StashRows: // Add specified stash rows from quest reward - requires client restart
|
||||
_profileHelper.AddStashRowsBonusToProfile(sessionId, int.Parse((string)reward.Value));
|
||||
break;
|
||||
case QuestRewardType.ProductionScheme:
|
||||
FindAndAddHideoutProductionIdToProfile(pmcProfile, reward, questDetails, sessionId, questResponse);
|
||||
break;
|
||||
case QuestRewardType.Pockets:
|
||||
_profileHelper.ReplaceProfilePocketTpl(pmcProfile, reward.Target);
|
||||
break;
|
||||
case QuestRewardType.CustomizationDirect:
|
||||
_profileHelper.AddHideoutCustomisationUnlock(fullProfile, reward, CustomisationSource.UNLOCKED_IN_GAME);
|
||||
break;
|
||||
default:
|
||||
_logger.Error(_localisationService.GetText("quest-reward_type_not_handled", new
|
||||
{
|
||||
RewardType = reward.Type,
|
||||
QuestId = questId,
|
||||
questName = questDetails.QuestName
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return GetQuestRewardItems(questDetails, state, gameVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,9 +168,22 @@ public class QuestRewardHelper
|
||||
* @param gameVersion Version of game to check reward against
|
||||
* @returns True if it has requirement, false if it doesnt pass check
|
||||
*/
|
||||
public bool QuestRewardIsForGameEdition(RewardDetails reward, string gameVersion)
|
||||
public bool QuestRewardIsForGameEdition(QuestReward reward, string gameVersion)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
if (reward?.AvailableInGameEditions?.Count > 0 && !reward.AvailableInGameEditions.Any(ge => ge == gameVersion))
|
||||
{
|
||||
// Reward has edition whitelist and game version isnt in it
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reward?.NotAvailableInGameEditions?.Count > 0 && reward.NotAvailableInGameEditions.Any(ge => ge == gameVersion))
|
||||
{
|
||||
// Reward has edition blacklist and game version is in it
|
||||
return false;
|
||||
}
|
||||
|
||||
// No whitelist/blacklist or reward isnt blacklisted/whitelisted
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +206,7 @@ public class QuestRewardHelper
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return quest;
|
||||
}
|
||||
|
||||
@@ -113,7 +217,23 @@ public class QuestRewardHelper
|
||||
/// <returns>bonus as a percent</returns>
|
||||
protected double GetQuestMoneyRewardBonusMultiplier(PmcData pmcData)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Check player has intel center
|
||||
var moneyRewardbonuses = pmcData.Bonuses.Where(bonus => bonus.Type == BonusType.QuestMoneyReward);
|
||||
|
||||
// Get a total of the quest money reward percent bonuses
|
||||
var moneyRewardBonusPercent = moneyRewardbonuses.Aggregate(0D, (accumulate, bonus) => accumulate + bonus.Value ?? 0);
|
||||
|
||||
// Calculate hideout management bonus as a percentage (up to 51% bonus)
|
||||
var hideoutManagementSkill = _profileHelper.GetSkillFromProfile(pmcData, SkillTypes.HideoutManagement);
|
||||
|
||||
// 5100 becomes 0.51, add 1 to it, 1.51
|
||||
// We multiply the money reward bonuses by the hideout management skill multipler, giving the new result
|
||||
var hideoutManagementBonusMultiplier = (hideoutManagementSkill != null)
|
||||
? (1 + hideoutManagementSkill.Progress / 1000)
|
||||
: 1;
|
||||
|
||||
// e.g 15% * 1.4
|
||||
return moneyRewardBonusPercent * hideoutManagementBonusMultiplier ?? 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,9 +243,23 @@ public class QuestRewardHelper
|
||||
* @param questStatus Status of quest to apply money boost to rewards of
|
||||
* @returns Updated quest
|
||||
*/
|
||||
public Quest ApplyMoneyBoost(Quest quest, double bonusPercent, QuestStatus questStatus)
|
||||
public Quest ApplyMoneyBoost(Quest quest, double bonusPercent, QuestStatusEnum questStatus)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var rewards = (List<QuestReward>)quest?.Rewards.GetType().GetProperties().FirstOrDefault(p => p.Name == questStatus.ToString())
|
||||
.GetValue(quest.Rewards) ?? new();
|
||||
var currencyRewards = rewards.Where(r =>
|
||||
r.Type.ToString() == "Item" &&
|
||||
_paymentHelper.IsMoneyTpl(r.Items[0].Template));
|
||||
foreach (var reward in currencyRewards)
|
||||
{
|
||||
// Add % bonus to existing StackObjectsCount
|
||||
var rewardItem = reward.Items[0];
|
||||
var newCurrencyAmount = Math.Floor((rewardItem.Upd.StackObjectsCount ?? 0) * (1 + bonusPercent / 100));
|
||||
rewardItem.Upd.StackObjectsCount = newCurrencyAmount;
|
||||
reward.Value = newCurrencyAmount;
|
||||
}
|
||||
|
||||
return quest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,7 +274,22 @@ public class QuestRewardHelper
|
||||
protected void FindAndAddHideoutProductionIdToProfile(PmcData pmcData, QuestReward craftUnlockReward, Quest questDetails, string sessionID,
|
||||
ItemEventRouterResponse response)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var matchingProductions = GetRewardProductionMatch(craftUnlockReward, questDetails);
|
||||
if (matchingProductions.Count != 1)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("quest-unable_to_find_matching_hideout_production", new
|
||||
{
|
||||
QuestName = questDetails.QuestName,
|
||||
MatchCount = matchingProductions.Count
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Add above match to pmc profile + client response
|
||||
var matchingCraftId = matchingProductions[0]?.Id;
|
||||
pmcData?.UnlockedInfo?.UnlockedProductionRecipe?.Add(matchingCraftId);
|
||||
response.ProfileChanges[sessionID].RecipeUnlocked[matchingCraftId] = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,7 +300,25 @@ public class QuestRewardHelper
|
||||
/// <returns>Hideout craft</returns>
|
||||
public List<HideoutProduction> GetRewardProductionMatch(QuestReward craftUnlockReward, Quest questDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Get hideout crafts and find those that match by areatype/required level/end product tpl - hope for just one match
|
||||
var craftingRecipes = _databaseService.GetHideout().Production.Recipes;
|
||||
|
||||
// Area that will be used to craft unlocked item
|
||||
var desiredHideoutAreaType = int.Parse((string)craftUnlockReward.TraderId);
|
||||
|
||||
var matchingProductions = craftingRecipes.Where(p =>
|
||||
p.AreaType == desiredHideoutAreaType &&
|
||||
p.Requirements.Any(r => r.Type == "QuestComplete") &&
|
||||
p.Requirements.Any(r => r.RequiredLevel == craftUnlockReward.LoyaltyLevel) &&
|
||||
p.EndProduct == craftUnlockReward.Items[0].Template);
|
||||
|
||||
// More/less than single match, above filtering wasn't strict enough
|
||||
if (matchingProductions.Count() != 1)
|
||||
matchingProductions = matchingProductions.Where(p =>
|
||||
p.Requirements.Any(r =>
|
||||
r.QuestId == questDetails.Id));
|
||||
|
||||
return matchingProductions.ToList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,9 +327,22 @@ public class QuestRewardHelper
|
||||
* @param status Quest status that holds the items (Started, Success, Fail)
|
||||
* @returns List of items with the correct maxStack
|
||||
*/
|
||||
protected List<Item> GetQuestRewardItems(Quest quest, QuestStatus status, string gameVersion)
|
||||
protected List<Item> GetQuestRewardItems(Quest quest, QuestStatusEnum status, string gameVersion)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var rewards = (List<QuestReward>)quest?.Rewards.GetType().GetProperties().FirstOrDefault(p => p.Name == status.ToString())
|
||||
.GetValue(quest.Rewards);
|
||||
|
||||
if (rewards == null)
|
||||
return new();
|
||||
|
||||
// Iterate over all rewards with the desired status, flatten out items that have a type of Item
|
||||
var questRewards = rewards.SelectMany(r =>
|
||||
r.Type.ToString() == "Item" &&
|
||||
QuestRewardIsForGameEdition(r, gameVersion)
|
||||
? ProcessReward(r)
|
||||
: new());
|
||||
|
||||
return questRewards.ToList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,7 +352,75 @@ public class QuestRewardHelper
|
||||
*/
|
||||
protected List<Item> ProcessReward(QuestReward questReward)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// item with mods to return
|
||||
var rewardItems = new List<Item>();
|
||||
var targets = new List<Item>();
|
||||
var mods = new List<Item>();
|
||||
|
||||
// Is armor item that may need inserts / plates
|
||||
if (questReward.Items.Count == 1 && _itemHelper.ArmorItemCanHoldMods(questReward.Items[0].Template))
|
||||
{
|
||||
// Attempt to pull default preset from globals and add child items to reward (clones questReward.items)
|
||||
GenerateArmorRewardChildSlots(questReward.Items[0], questReward);
|
||||
}
|
||||
|
||||
foreach (var rewardItem in questReward.Items)
|
||||
{
|
||||
_itemHelper.AddUpdObjectToItem(rewardItem);
|
||||
|
||||
// Reward items are granted Found in Raid status
|
||||
rewardItem.Upd.SpawnedInSession = true;
|
||||
|
||||
// Is root item, fix stacks
|
||||
if (rewardItem.Id == questReward.Type.ToString())
|
||||
{
|
||||
// Is base reward item
|
||||
if (rewardItem.ParentId != null &&
|
||||
rewardItem.ParentId == "hideout" &&
|
||||
rewardItem.Upd != null &&
|
||||
rewardItem.Upd.StackObjectsCount != null &&
|
||||
rewardItem.Upd.StackObjectsCount > 0)
|
||||
{
|
||||
rewardItem.Upd.StackObjectsCount = 1;
|
||||
}
|
||||
|
||||
targets = _itemHelper.SplitStack(rewardItem);
|
||||
// splitStack created new ids for the new stacks. This would destroy the relation to possible children.
|
||||
// Instead, we reset the id to preserve relations and generate a new id in the downstream loop, where we are also reparenting if required
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
target.Id = rewardItem.Id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Is child mod
|
||||
if (questReward.Items[0].Upd.SpawnedInSession ?? false) // Propigate FiR status into child items
|
||||
rewardItem.Upd.SpawnedInSession = questReward.Items[0].Upd.SpawnedInSession;
|
||||
|
||||
mods.Add(rewardItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Add mods to the base items, fix ids
|
||||
foreach (var target in targets)
|
||||
{
|
||||
// This has all the original id relations since we reset the id to the original after the splitStack
|
||||
var itemsClone = new List<Item>();
|
||||
itemsClone.Add(_cloner.Clone(target));
|
||||
// Here we generate a new id for the root item
|
||||
target.Id = _hashUtil.Generate();
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
itemsClone.Add(_cloner.Clone(mod));
|
||||
}
|
||||
|
||||
rewardItems.AddRange(_itemHelper.ReparentItemAndChildren(target, itemsClone));
|
||||
}
|
||||
|
||||
return rewardItems;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,7 +430,34 @@ public class QuestRewardHelper
|
||||
*/
|
||||
protected void GenerateArmorRewardChildSlots(Item originalRewardRootItem, QuestReward questReward)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
// Look for a default preset from globals for armor
|
||||
var defaultPreset = _presetHelper.GetDefaultPreset(originalRewardRootItem.Template);
|
||||
if (defaultPreset != null)
|
||||
{
|
||||
// Found preset, use mods to hydrate reward item
|
||||
var presetAndMods = _itemHelper.ReplaceIDs(defaultPreset.Items);
|
||||
var newRootId = _itemHelper.RemapRootItemId(presetAndMods, _hashUtil.Generate());
|
||||
|
||||
questReward.Items = presetAndMods;
|
||||
|
||||
// Find root item and set its stack count
|
||||
var rootItem = questReward.Items.FirstOrDefault(i => i.Id == newRootId);
|
||||
|
||||
// Remap target id to the new presets root id
|
||||
questReward.Target = rootItem.Id;
|
||||
|
||||
// Copy over stack count otherwise reward shows as missing in client
|
||||
_itemHelper.AddUpdObjectToItem(rootItem);
|
||||
|
||||
rootItem.Upd.StackObjectsCount = originalRewardRootItem.Upd.StackObjectsCount;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Warning($"Unable to find default preset for armor {originalRewardRootItem.Template}, adding mods manually");
|
||||
var itemDbData = _itemHelper.GetItem(originalRewardRootItem.Template).Value;
|
||||
|
||||
// Hydrate reward with only 'required' mods - necessary for things like helmets otherwise you end up with nvgs/visors etc
|
||||
questReward.Items = _itemHelper.AddChildSlotItems(questReward.Items, itemDbData, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user