Created new CircleOfCultist service and transplanted all code from Hideout controller into it
This commit is contained in:
parent
6803ee6241
commit
63cf9ae132
@ -10,13 +10,13 @@ import { HideoutArea, ITaskConditionCounter, Product, ScavCase } from "@spt/mode
|
|||||||
import { Item } from "@spt/models/eft/common/tables/IItem";
|
import { Item } from "@spt/models/eft/common/tables/IItem";
|
||||||
import { HideoutUpgradeCompleteRequestData } from "@spt/models/eft/hideout/HideoutUpgradeCompleteRequestData";
|
import { HideoutUpgradeCompleteRequestData } from "@spt/models/eft/hideout/HideoutUpgradeCompleteRequestData";
|
||||||
import { IHandleQTEEventRequestData } from "@spt/models/eft/hideout/IHandleQTEEventRequestData";
|
import { IHandleQTEEventRequestData } from "@spt/models/eft/hideout/IHandleQTEEventRequestData";
|
||||||
import { IHideoutArea, IStageRequirement, Stage } from "@spt/models/eft/hideout/IHideoutArea";
|
import { IHideoutArea, Stage } from "@spt/models/eft/hideout/IHideoutArea";
|
||||||
import { IHideoutCancelProductionRequestData } from "@spt/models/eft/hideout/IHideoutCancelProductionRequestData";
|
import { IHideoutCancelProductionRequestData } from "@spt/models/eft/hideout/IHideoutCancelProductionRequestData";
|
||||||
import { IHideoutCircleOfCultistProductionStartRequestData } from "@spt/models/eft/hideout/IHideoutCircleOfCultistProductionStartRequestData";
|
import { IHideoutCircleOfCultistProductionStartRequestData } from "@spt/models/eft/hideout/IHideoutCircleOfCultistProductionStartRequestData";
|
||||||
import { IHideoutContinuousProductionStartRequestData } from "@spt/models/eft/hideout/IHideoutContinuousProductionStartRequestData";
|
import { IHideoutContinuousProductionStartRequestData } from "@spt/models/eft/hideout/IHideoutContinuousProductionStartRequestData";
|
||||||
import { IHideoutDeleteProductionRequestData } from "@spt/models/eft/hideout/IHideoutDeleteProductionRequestData";
|
import { IHideoutDeleteProductionRequestData } from "@spt/models/eft/hideout/IHideoutDeleteProductionRequestData";
|
||||||
import { IHideoutImproveAreaRequestData } from "@spt/models/eft/hideout/IHideoutImproveAreaRequestData";
|
import { IHideoutImproveAreaRequestData } from "@spt/models/eft/hideout/IHideoutImproveAreaRequestData";
|
||||||
import { IHideoutProduction, Requirement } from "@spt/models/eft/hideout/IHideoutProduction";
|
import { IHideoutProduction } from "@spt/models/eft/hideout/IHideoutProduction";
|
||||||
import { IHideoutPutItemInRequestData } from "@spt/models/eft/hideout/IHideoutPutItemInRequestData";
|
import { IHideoutPutItemInRequestData } from "@spt/models/eft/hideout/IHideoutPutItemInRequestData";
|
||||||
import { IHideoutScavCaseStartRequestData } from "@spt/models/eft/hideout/IHideoutScavCaseStartRequestData";
|
import { IHideoutScavCaseStartRequestData } from "@spt/models/eft/hideout/IHideoutScavCaseStartRequestData";
|
||||||
import { IHideoutSingleProductionStartRequestData } from "@spt/models/eft/hideout/IHideoutSingleProductionStartRequestData";
|
import { IHideoutSingleProductionStartRequestData } from "@spt/models/eft/hideout/IHideoutSingleProductionStartRequestData";
|
||||||
@ -30,7 +30,6 @@ import { IAddItemDirectRequest } from "@spt/models/eft/inventory/IAddItemDirectR
|
|||||||
import { IAddItemsDirectRequest } from "@spt/models/eft/inventory/IAddItemsDirectRequest";
|
import { IAddItemsDirectRequest } from "@spt/models/eft/inventory/IAddItemsDirectRequest";
|
||||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||||
import { BackendErrorCodes } from "@spt/models/enums/BackendErrorCodes";
|
import { BackendErrorCodes } from "@spt/models/enums/BackendErrorCodes";
|
||||||
import { BaseClasses } from "@spt/models/enums/BaseClasses";
|
|
||||||
import { BonusType } from "@spt/models/enums/BonusType";
|
import { BonusType } from "@spt/models/enums/BonusType";
|
||||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
import { HideoutAreas } from "@spt/models/enums/HideoutAreas";
|
import { HideoutAreas } from "@spt/models/enums/HideoutAreas";
|
||||||
@ -41,6 +40,7 @@ import { ILogger } from "@spt/models/spt/utils/ILogger";
|
|||||||
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
import { SaveServer } from "@spt/servers/SaveServer";
|
import { SaveServer } from "@spt/servers/SaveServer";
|
||||||
|
import { CircleOfCultistService } from "@spt/services/CircleOfCultistService";
|
||||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
import { FenceService } from "@spt/services/FenceService";
|
import { FenceService } from "@spt/services/FenceService";
|
||||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||||
@ -57,7 +57,6 @@ import { inject, injectable } from "tsyringe";
|
|||||||
export class HideoutController {
|
export class HideoutController {
|
||||||
/** Key used in TaskConditionCounters array */
|
/** Key used in TaskConditionCounters array */
|
||||||
protected static nameTaskConditionCountersCrafting = "CounterHoursCrafting";
|
protected static nameTaskConditionCountersCrafting = "CounterHoursCrafting";
|
||||||
protected static circleOfCultistSlotId = "CircleOfCultistsGrid1";
|
|
||||||
protected hideoutConfig: IHideoutConfig;
|
protected hideoutConfig: IHideoutConfig;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -81,6 +80,7 @@ export class HideoutController {
|
|||||||
@inject("ProfileActivityService") protected profileActivityService: ProfileActivityService,
|
@inject("ProfileActivityService") protected profileActivityService: ProfileActivityService,
|
||||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||||
@inject("FenceService") protected fenceService: FenceService,
|
@inject("FenceService") protected fenceService: FenceService,
|
||||||
|
@inject("CircleOfCultistService") protected circleOfCultistService: CircleOfCultistService,
|
||||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
) {
|
) {
|
||||||
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
||||||
@ -1303,221 +1303,7 @@ export class HideoutController {
|
|||||||
pmcData: IPmcData,
|
pmcData: IPmcData,
|
||||||
request: IHideoutCircleOfCultistProductionStartRequestData,
|
request: IHideoutCircleOfCultistProductionStartRequestData,
|
||||||
): IItemEventRouterResponse {
|
): IItemEventRouterResponse {
|
||||||
const cultistCircleStashId = pmcData.Inventory.hideoutAreaStashes[HideoutAreas.CIRCLE_OF_CULTISTS];
|
return this.circleOfCultistService.startSacrifice(sessionId, pmcData, request);
|
||||||
|
|
||||||
// Sparse, just has id
|
|
||||||
const cultistCraftData = this.databaseService.getHideout().production.cultistRecipes[0];
|
|
||||||
const sacrificedItems: Item[] = this.getSacrificedItems(pmcData);
|
|
||||||
const sacrificedItemCostRoubles = sacrificedItems.reduce(
|
|
||||||
(sum, curr) => sum + (this.itemHelper.getItemPrice(curr._tpl) ?? 0),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO - include hideout management skill to bonus
|
|
||||||
const rewardAmountmultiplier = this.randomUtil.getFloat(0.7, 1.4);
|
|
||||||
const rewardAmountRoubles = sacrificedItemCostRoubles * rewardAmountmultiplier;
|
|
||||||
|
|
||||||
// Create production in pmc profile
|
|
||||||
this.hideoutHelper.registerCircleOfCultistProduction(sessionId, pmcData, cultistCraftData._id, sacrificedItems);
|
|
||||||
|
|
||||||
const output = this.eventOutputHolder.getOutput(sessionId);
|
|
||||||
|
|
||||||
// Remove sacrified items
|
|
||||||
for (const item of sacrificedItems) {
|
|
||||||
if (item.slotId === HideoutController.circleOfCultistSlotId) {
|
|
||||||
this.inventoryHelper.removeItem(pmcData, item._id, sessionId, output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rewardItemPool = this.getCultistCircleRewardPool(sessionId, pmcData);
|
|
||||||
this.logger.warning(`Reward pool item count: ${rewardItemPool.length}`);
|
|
||||||
|
|
||||||
const rewards = this.getRewardsWithinBudget(rewardItemPool, rewardAmountRoubles, cultistCircleStashId);
|
|
||||||
|
|
||||||
// Get the container grid for cultist stash area
|
|
||||||
const cultistStashDbItem = this.itemHelper.getItem(ItemTpl.HIDEOUTAREACONTAINER_CIRCLEOFCULTISTS_STASH_1);
|
|
||||||
|
|
||||||
// Ensure items fit into container
|
|
||||||
const containerGrid = this.inventoryHelper.getContainerSlotMap(cultistStashDbItem[1]._id);
|
|
||||||
const canAddToContainer = this.inventoryHelper.canPlaceItemsInContainer(
|
|
||||||
this.cloner.clone(containerGrid), // MUST clone grid before passing in as function modifies grid
|
|
||||||
rewards,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (canAddToContainer) {
|
|
||||||
for (const itemToAdd of rewards) {
|
|
||||||
this.logger.warning(`Placing reward: ${itemToAdd[0]._tpl} in circle grid`);
|
|
||||||
this.inventoryHelper.placeItemInContainer(
|
|
||||||
containerGrid,
|
|
||||||
itemToAdd,
|
|
||||||
cultistCircleStashId,
|
|
||||||
HideoutController.circleOfCultistSlotId,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add item + mods to output and profile inventory
|
|
||||||
output.profileChanges[sessionId].items.new.push(...itemToAdd);
|
|
||||||
pmcData.Inventory.items.push(...itemToAdd);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.logger.error(
|
|
||||||
`Unable to fit all: ${rewards.length} reward items into sacrifice grid, nothing will be returned`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a pool of items + rouble budget, pick items until the budget is reached
|
|
||||||
* @param rewardItemTplPool Items that can be picekd
|
|
||||||
* @param rewardBudget Rouble budget to reach
|
|
||||||
* @param cultistCircleStashId Id of stash item
|
|
||||||
* @returns Array of items
|
|
||||||
*/
|
|
||||||
protected getRewardsWithinBudget(
|
|
||||||
rewardItemTplPool: string[],
|
|
||||||
rewardBudget: number,
|
|
||||||
cultistCircleStashId: string,
|
|
||||||
): Item[][] {
|
|
||||||
// Prep rewards array (reward can be item with children, hence array of arrays)
|
|
||||||
const rewards: Item[][] = [];
|
|
||||||
|
|
||||||
// Pick random rewards until we have exhausted the sacrificed items budget
|
|
||||||
let totalCost = 0;
|
|
||||||
let itemsRewardedCount = 0;
|
|
||||||
let failedAttempts = 0;
|
|
||||||
while (totalCost < rewardBudget && rewardItemTplPool.length > 0 && itemsRewardedCount < 5) {
|
|
||||||
if (failedAttempts > 5) {
|
|
||||||
this.logger.warning(`Exiting reward generation after ${failedAttempts} failed attempts`);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose a random tpl from pool
|
|
||||||
const randomItemTplFromPool = this.randomUtil.getArrayValue(rewardItemTplPool);
|
|
||||||
|
|
||||||
// Is weapon/armor, handle differently
|
|
||||||
if (
|
|
||||||
this.itemHelper.armorItemHasRemovableOrSoftInsertSlots(randomItemTplFromPool) ||
|
|
||||||
this.itemHelper.isOfBaseclass(randomItemTplFromPool, BaseClasses.WEAPON)
|
|
||||||
) {
|
|
||||||
const defaultPreset = this.presetHelper.getDefaultPreset(randomItemTplFromPool);
|
|
||||||
if (!defaultPreset) {
|
|
||||||
this.logger.warning(`Reward tpl: ${randomItemTplFromPool} lacks a default preset, skipping reward`);
|
|
||||||
failedAttempts++;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure preset has unique ids and is cloned so we don't alter the preset data stored in memory
|
|
||||||
const presetAndMods: Item[] = this.itemHelper.replaceIDs(defaultPreset._items);
|
|
||||||
|
|
||||||
this.itemHelper.remapRootItemId(presetAndMods);
|
|
||||||
|
|
||||||
rewards.push(presetAndMods);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some items can have variable stack size, e.g. ammo
|
|
||||||
const stackSize = this.getRewardStackSize(randomItemTplFromPool);
|
|
||||||
|
|
||||||
// Not a weapon/armor, standard single item
|
|
||||||
const rewardItem: Item = {
|
|
||||||
_id: this.hashUtil.generate(),
|
|
||||||
_tpl: randomItemTplFromPool,
|
|
||||||
parentId: cultistCircleStashId,
|
|
||||||
slotId: HideoutController.circleOfCultistSlotId,
|
|
||||||
upd: {
|
|
||||||
StackObjectsCount: stackSize,
|
|
||||||
SpawnedInSession: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Increment price of rewards to give to player and add to reward array
|
|
||||||
itemsRewardedCount++;
|
|
||||||
totalCost += this.itemHelper.getItemPrice(randomItemTplFromPool);
|
|
||||||
rewards.push([rewardItem]);
|
|
||||||
}
|
|
||||||
this.logger.warning(`Circle will reward ${itemsRewardedCount} items costing a total of ${totalCost} roubles`);
|
|
||||||
|
|
||||||
return rewards;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getRewardStackSize(randomItemTplFromPool: string) {
|
|
||||||
if (this.itemHelper.isOfBaseclass(randomItemTplFromPool, BaseClasses.AMMO)) {
|
|
||||||
const ammoTemplate = this.itemHelper.getItem(randomItemTplFromPool)[1];
|
|
||||||
return this.itemHelper.getRandomisedAmmoStackSize(ammoTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a pool of tpl IDs of items the player needs to complete hideout crafts/upgrade areas
|
|
||||||
* @param sessionId Session id
|
|
||||||
* @param pmcData Player profile
|
|
||||||
* @returns Array of tpls
|
|
||||||
*/
|
|
||||||
protected getCultistCircleRewardPool(sessionId: string, pmcData: IPmcData): string[] {
|
|
||||||
const rewardPool = new Set<string>();
|
|
||||||
|
|
||||||
// What does player need to upgrade hideout areas
|
|
||||||
const dbAreas = this.databaseService.getHideout().areas;
|
|
||||||
for (const area of pmcData.Hideout.Areas) {
|
|
||||||
const currentStageLevel = area.level;
|
|
||||||
const areaType = area.type;
|
|
||||||
|
|
||||||
// Get next stage of area
|
|
||||||
const dbArea = dbAreas.find((area) => area.type === areaType);
|
|
||||||
const nextStageDbData = dbArea.stages[currentStageLevel + 1];
|
|
||||||
if (nextStageDbData) {
|
|
||||||
// Next stage exists, gather up requirements and add to pool
|
|
||||||
const itemRequirements = this.getNonFunctionalItemRequirements(nextStageDbData.requirements);
|
|
||||||
for (const rewardToAdd of itemRequirements) {
|
|
||||||
rewardPool.add(rewardToAdd.templateId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// What does player need to craft items with
|
|
||||||
const playerUnlockedRecipes = pmcData.UnlockedInfo.unlockedProductionRecipe;
|
|
||||||
const allRecipes = this.databaseService.getHideout().production;
|
|
||||||
|
|
||||||
// Get default unlocked recipes + locked recipes they've unlocked
|
|
||||||
const playerAccessibleRecipes = allRecipes.recipes.filter(
|
|
||||||
(recipe) => !recipe.locked || playerUnlockedRecipes.includes(recipe._id),
|
|
||||||
);
|
|
||||||
for (const recipe of playerAccessibleRecipes) {
|
|
||||||
const itemRequirements = this.getNonFunctionalItemRequirements(recipe.requirements);
|
|
||||||
for (const requirement of itemRequirements) {
|
|
||||||
rewardPool.add(requirement.templateId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for scav case unlock in profile
|
|
||||||
const hasScavCaseAreaUnlocked = pmcData.Hideout.Areas[HideoutAreas.SCAV_CASE]?.level > 0;
|
|
||||||
if (hasScavCaseAreaUnlocked) {
|
|
||||||
// Gather up items used to start scav case crafts
|
|
||||||
const scavCaseCrafts = this.databaseService.getHideout().scavcase;
|
|
||||||
for (const craft of scavCaseCrafts) {
|
|
||||||
// Find the item requirements from each craft
|
|
||||||
const itemRequirements = this.getNonFunctionalItemRequirements(craft.Requirements);
|
|
||||||
for (const requirement of itemRequirements) {
|
|
||||||
// Add tpl to reward pool
|
|
||||||
rewardPool.add(requirement.templateId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(rewardPool);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterate over passed in hideout requirements and return the Item + nonfunctional requirements
|
|
||||||
* @param requirements Requirements to iterate over
|
|
||||||
* @returns Array of item requirements
|
|
||||||
*/
|
|
||||||
protected getNonFunctionalItemRequirements(requirements: IStageRequirement[] | Requirement[]) {
|
|
||||||
return requirements.filter((requirement) => requirement.type === "Item" && !requirement.isFunctional);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public hideoutDeleteProductionCommand(
|
public hideoutDeleteProductionCommand(
|
||||||
@ -1532,26 +1318,8 @@ export class HideoutController {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getSacrificedItems(pmcData: IPmcData): Item[] {
|
|
||||||
// Get root items that are in the cultist sacrifice window
|
|
||||||
const inventoryRootItemsInCultistGrid = pmcData.Inventory.items.filter(
|
|
||||||
(item) => item.slotId === "CircleOfCultistsGrid1",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get rootitem + its children
|
|
||||||
const sacrificedItems: Item[] = [];
|
|
||||||
for (const rootItem of inventoryRootItemsInCultistGrid) {
|
|
||||||
const rootItemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(
|
|
||||||
pmcData.Inventory.items,
|
|
||||||
rootItem._id,
|
|
||||||
);
|
|
||||||
sacrificedItems.push(...rootItemWithChildren);
|
|
||||||
}
|
|
||||||
return sacrificedItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function called every x seconds as part of onUpdate event
|
* Function called every `hideoutConfig.runIntervalSeconds` seconds as part of onUpdate event
|
||||||
*/
|
*/
|
||||||
public update(): void {
|
public update(): void {
|
||||||
for (const sessionID in this.saveServer.getProfiles()) {
|
for (const sessionID in this.saveServer.getProfiles()) {
|
||||||
|
@ -202,6 +202,7 @@ import { BotEquipmentModPoolService } from "@spt/services/BotEquipmentModPoolSer
|
|||||||
import { BotGenerationCacheService } from "@spt/services/BotGenerationCacheService";
|
import { BotGenerationCacheService } from "@spt/services/BotGenerationCacheService";
|
||||||
import { BotLootCacheService } from "@spt/services/BotLootCacheService";
|
import { BotLootCacheService } from "@spt/services/BotLootCacheService";
|
||||||
import { BotWeaponModLimitService } from "@spt/services/BotWeaponModLimitService";
|
import { BotWeaponModLimitService } from "@spt/services/BotWeaponModLimitService";
|
||||||
|
import { CircleOfCultistService } from "@spt/services/CircleOfCultistService";
|
||||||
import { CustomLocationWaveService } from "@spt/services/CustomLocationWaveService";
|
import { CustomLocationWaveService } from "@spt/services/CustomLocationWaveService";
|
||||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
import { FenceService } from "@spt/services/FenceService";
|
import { FenceService } from "@spt/services/FenceService";
|
||||||
@ -793,6 +794,9 @@ export class Container {
|
|||||||
depContainer.register<LocationLifecycleService>("LocationLifecycleService", LocationLifecycleService, {
|
depContainer.register<LocationLifecycleService>("LocationLifecycleService", LocationLifecycleService, {
|
||||||
lifecycle: Lifecycle.Singleton,
|
lifecycle: Lifecycle.Singleton,
|
||||||
});
|
});
|
||||||
|
depContainer.register<CircleOfCultistService>("CircleOfCultistService", CircleOfCultistService, {
|
||||||
|
lifecycle: Lifecycle.Singleton,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static registerServers(depContainer: DependencyContainer): void {
|
private static registerServers(depContainer: DependencyContainer): void {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user