875 lines
35 KiB
TypeScript
875 lines
35 KiB
TypeScript
import { inject, injectable } from "tsyringe";
|
|
|
|
import { ScavCaseRewardGenerator } from "../generators/ScavCaseRewardGenerator";
|
|
import { HideoutHelper } from "../helpers/HideoutHelper";
|
|
import { InventoryHelper } from "../helpers/InventoryHelper";
|
|
import { PaymentHelper } from "../helpers/PaymentHelper";
|
|
import { PresetHelper } from "../helpers/PresetHelper";
|
|
import { ProfileHelper } from "../helpers/ProfileHelper";
|
|
import { IPmcData } from "../models/eft/common/IPmcData";
|
|
import { HideoutArea, Product, Production, ScavCase } from "../models/eft/common/tables/IBotBase";
|
|
import { Upd } from "../models/eft/common/tables/IItem";
|
|
import {
|
|
HideoutUpgradeCompleteRequestData
|
|
} from "../models/eft/hideout/HideoutUpgradeCompleteRequestData";
|
|
import { IHandleQTEEventRequestData } from "../models/eft/hideout/IHandleQTEEventRequestData";
|
|
import {
|
|
IHideoutContinuousProductionStartRequestData
|
|
} from "../models/eft/hideout/IHideoutContinuousProductionStartRequestData";
|
|
import {
|
|
IHideoutImproveAreaRequestData
|
|
} from "../models/eft/hideout/IHideoutImproveAreaRequestData";
|
|
import { IHideoutProduction } from "../models/eft/hideout/IHideoutProduction";
|
|
import { IHideoutPutItemInRequestData } from "../models/eft/hideout/IHideoutPutItemInRequestData";
|
|
import {
|
|
IHideoutScavCaseStartRequestData
|
|
} from "../models/eft/hideout/IHideoutScavCaseStartRequestData";
|
|
import {
|
|
IHideoutSingleProductionStartRequestData
|
|
} from "../models/eft/hideout/IHideoutSingleProductionStartRequestData";
|
|
import {
|
|
IHideoutTakeItemOutRequestData
|
|
} from "../models/eft/hideout/IHideoutTakeItemOutRequestData";
|
|
import {
|
|
IHideoutTakeProductionRequestData
|
|
} from "../models/eft/hideout/IHideoutTakeProductionRequestData";
|
|
import { IHideoutToggleAreaRequestData } from "../models/eft/hideout/IHideoutToggleAreaRequestData";
|
|
import { IHideoutUpgradeRequestData } from "../models/eft/hideout/IHideoutUpgradeRequestData";
|
|
import { IQteData } from "../models/eft/hideout/IQteData";
|
|
import { IRecordShootingRangePoints } from "../models/eft/hideout/IRecordShootingRangePoints";
|
|
import { IItemEventRouterResponse } from "../models/eft/itemEvent/IItemEventRouterResponse";
|
|
import { ConfigTypes } from "../models/enums/ConfigTypes";
|
|
import { HideoutAreas } from "../models/enums/HideoutAreas";
|
|
import { SkillTypes } from "../models/enums/SkillTypes";
|
|
import { IHideoutConfig } from "../models/spt/config/IHideoutConfig";
|
|
import { ILogger } from "../models/spt/utils/ILogger";
|
|
import { EventOutputHolder } from "../routers/EventOutputHolder";
|
|
import { ConfigServer } from "../servers/ConfigServer";
|
|
import { DatabaseServer } from "../servers/DatabaseServer";
|
|
import { SaveServer } from "../servers/SaveServer";
|
|
import { FenceService } from "../services/FenceService";
|
|
import { LocalisationService } from "../services/LocalisationService";
|
|
import { PlayerService } from "../services/PlayerService";
|
|
import { HashUtil } from "../utils/HashUtil";
|
|
import { HttpResponseUtil } from "../utils/HttpResponseUtil";
|
|
import { JsonUtil } from "../utils/JsonUtil";
|
|
import { RandomUtil } from "../utils/RandomUtil";
|
|
import { TimeUtil } from "../utils/TimeUtil";
|
|
|
|
@injectable()
|
|
export class HideoutController
|
|
{
|
|
protected static nameBackendCountersCrafting = "CounterHoursCrafting";
|
|
protected hideoutConfig: IHideoutConfig;
|
|
|
|
constructor(
|
|
@inject("WinstonLogger") protected logger: ILogger,
|
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
|
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
|
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
|
|
@inject("SaveServer") protected saveServer: SaveServer,
|
|
@inject("PlayerService") protected playerService: PlayerService,
|
|
@inject("PresetHelper") protected presetHelper: PresetHelper,
|
|
@inject("PaymentHelper") protected paymentHelper: PaymentHelper,
|
|
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
|
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
|
@inject("HideoutHelper") protected hideoutHelper: HideoutHelper,
|
|
@inject("ScavCaseRewardGenerator") protected scavCaseRewardGenerator: ScavCaseRewardGenerator,
|
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
|
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
|
@inject("FenceService") protected fenceService: FenceService
|
|
)
|
|
{
|
|
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
|
}
|
|
|
|
/**
|
|
* Start a hideout area upgrade
|
|
* @param pmcData Player profile
|
|
* @param request upgrade start request
|
|
* @param sessionID Session id
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
public startUpgrade(pmcData: IPmcData, request: IHideoutUpgradeRequestData, sessionID: string): IItemEventRouterResponse
|
|
{
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
const items = request.items.map(reqItem =>
|
|
{
|
|
const item = pmcData.Inventory.items.find(invItem => invItem._id === reqItem.id);
|
|
return {
|
|
inventoryItem: item,
|
|
requestedItem: reqItem
|
|
};
|
|
});
|
|
|
|
// If it's not money, its construction / barter items
|
|
for (const item of items)
|
|
{
|
|
if (!item.inventoryItem)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_item_in_inventory", item.requestedItem.id));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
if (this.paymentHelper.isMoneyTpl(item.inventoryItem._tpl)
|
|
&& item.inventoryItem.upd
|
|
&& item.inventoryItem.upd.StackObjectsCount
|
|
&& item.inventoryItem.upd.StackObjectsCount > item.requestedItem.count)
|
|
{
|
|
item.inventoryItem.upd.StackObjectsCount -= item.requestedItem.count;
|
|
}
|
|
else
|
|
{
|
|
this.inventoryHelper.removeItem(pmcData, item.inventoryItem._id, sessionID, output);
|
|
}
|
|
}
|
|
|
|
// Construction time management
|
|
const hideoutArea = pmcData.Hideout.Areas.find(area => area.type === request.areaType);
|
|
if (!hideoutArea)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area", request.areaType));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
const hideoutData = this.databaseServer.getTables().hideout.areas.find(area => area.type === request.areaType);
|
|
|
|
if (!hideoutData)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area_in_database", request.areaType));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
const ctime = hideoutData.stages[hideoutArea.level + 1].constructionTime;
|
|
|
|
if (ctime > 0)
|
|
{
|
|
const timestamp = this.timeUtil.getTimestamp();
|
|
|
|
hideoutArea.completeTime = timestamp + ctime;
|
|
hideoutArea.constructing = true;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Complete a hideout area upgrade
|
|
* @param pmcData Player profile
|
|
* @param request Completed upgrade request
|
|
* @param sessionID Session id
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
public upgradeComplete(pmcData: IPmcData, request: HideoutUpgradeCompleteRequestData, sessionID: string): IItemEventRouterResponse
|
|
{
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
const hideoutArea = pmcData.Hideout.Areas.find(area => area.type === request.areaType);
|
|
if (!hideoutArea)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area", request.areaType));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
// Upgrade area
|
|
hideoutArea.level++;
|
|
hideoutArea.completeTime = 0;
|
|
hideoutArea.constructing = false;
|
|
|
|
const hideoutData = this.databaseServer.getTables().hideout.areas.find(area => area.type === hideoutArea.type);
|
|
if (!hideoutData)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area_in_database", request.areaType));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
// Apply bonuses
|
|
const bonuses = hideoutData.stages[hideoutArea.level].bonuses;
|
|
if (bonuses?.length > 0)
|
|
{
|
|
for (const bonus of bonuses)
|
|
{
|
|
this.hideoutHelper.applyPlayerUpgradesBonuses(pmcData, bonus);
|
|
}
|
|
}
|
|
|
|
// Add Skill Points Per Area Upgrade
|
|
this.playerService.incrementSkillLevel(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.SkillPointsPerAreaUpgrade);
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Handle HideoutPutItemsInAreaSlots
|
|
* Create item in hideout slot item array, remove item from player inventory
|
|
* @param pmcData Profile data
|
|
* @param addItemToHideoutRequest reqeust from client to place item in area slot
|
|
* @param sessionID Session id
|
|
* @returns IItemEventRouterResponse object
|
|
*/
|
|
public putItemsInAreaSlots(pmcData: IPmcData, addItemToHideoutRequest: IHideoutPutItemInRequestData, sessionID: string): IItemEventRouterResponse
|
|
{
|
|
let output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
const itemsToAdd = Object.entries(addItemToHideoutRequest.items).map(kvp =>
|
|
{
|
|
const item = pmcData.Inventory.items.find(invItem => invItem._id === kvp[1]["id"]);
|
|
return {
|
|
inventoryItem: item,
|
|
requestedItem: kvp[1],
|
|
slot: kvp[0]
|
|
};
|
|
});
|
|
|
|
const hideoutArea = pmcData.Hideout.Areas.find(area => area.type === addItemToHideoutRequest.areaType);
|
|
if (!hideoutArea)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area_in_database", addItemToHideoutRequest.areaType));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
for (const item of itemsToAdd)
|
|
{
|
|
if (!item.inventoryItem)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_item_in_inventory", {itemId: item.requestedItem["id"], area: hideoutArea.type}));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
// Add item to area.slots
|
|
const destinationLocationIndex = Number(item.slot);
|
|
const hideoutSlotIndex = hideoutArea.slots.findIndex(x => x.locationIndex === destinationLocationIndex);
|
|
hideoutArea.slots[hideoutSlotIndex].item = [{
|
|
_id: item.inventoryItem._id,
|
|
_tpl: item.inventoryItem._tpl,
|
|
upd: item.inventoryItem.upd
|
|
}];
|
|
|
|
output = this.inventoryHelper.removeItem(pmcData, item.inventoryItem._id, sessionID, output);
|
|
}
|
|
|
|
// Trigger a forced update
|
|
this.hideoutHelper.updatePlayerHideout(sessionID);
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Remove item from hideout area and place into player inventory
|
|
* @param pmcData Player profile
|
|
* @param request Take item out of area request
|
|
* @param sessionID Session id
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
public takeItemsFromAreaSlots(pmcData: IPmcData, request: IHideoutTakeItemOutRequestData, sessionID: string): IItemEventRouterResponse
|
|
{
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
const hideoutArea = pmcData.Hideout.Areas.find(area => area.type === request.areaType);
|
|
if (!hideoutArea)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area", request.areaType));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
if (!hideoutArea.slots || hideoutArea.slots.length === 0)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_item_to_remove_from_area", hideoutArea.type));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
// Handle areas that have resources that can be placed in/taken out of slots from the area
|
|
if ([HideoutAreas.AIR_FILTERING, HideoutAreas.WATER_COLLECTOR, HideoutAreas.GENERATOR, HideoutAreas.BITCOIN_FARM].includes(hideoutArea.type))
|
|
{
|
|
const response = this.removeResourceFromArea(sessionID, pmcData, request, output, hideoutArea);
|
|
this.update();
|
|
return response;
|
|
}
|
|
|
|
throw new Error(this.localisationService.getText("hideout-unhandled_remove_item_from_area_request", hideoutArea.type));
|
|
}
|
|
|
|
/**
|
|
* Find resource item in hideout area, add copy to player inventory, remove Item from hideout slot
|
|
* @param sessionID Session id
|
|
* @param pmcData Profile to update
|
|
* @param removeResourceRequest client request
|
|
* @param output response to send to client
|
|
* @param hideoutArea Area fuel is being removed from
|
|
* @returns IItemEventRouterResponse response
|
|
*/
|
|
protected removeResourceFromArea(sessionID: string, pmcData: IPmcData, removeResourceRequest: IHideoutTakeItemOutRequestData, output: IItemEventRouterResponse, hideoutArea: HideoutArea): IItemEventRouterResponse
|
|
{
|
|
const slotIndexToRemove = removeResourceRequest.slots[0];
|
|
|
|
const itemToReturn = hideoutArea.slots.find(x => x.locationIndex === slotIndexToRemove).item[0];
|
|
|
|
const newReq = {
|
|
items: [{
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
item_id: itemToReturn._tpl,
|
|
count: 1
|
|
}],
|
|
tid: "ragfair"
|
|
};
|
|
|
|
output = this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, null, !!itemToReturn.upd.SpawnedInSession, itemToReturn.upd);
|
|
|
|
// If addItem returned with errors, drop out
|
|
if (output.warnings && output.warnings.length > 0)
|
|
{
|
|
return output;
|
|
}
|
|
|
|
// Remove items from slot, locationIndex remains
|
|
const hideoutSlotIndex = hideoutArea.slots.findIndex(x => x.locationIndex === slotIndexToRemove);
|
|
hideoutArea.slots[hideoutSlotIndex].item = undefined;
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Toggle area on/off
|
|
* @param pmcData Player profile
|
|
* @param request Toggle area request
|
|
* @param sessionID Session id
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
public toggleArea(pmcData: IPmcData, request: IHideoutToggleAreaRequestData, sessionID: string): IItemEventRouterResponse
|
|
{
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
const hideoutArea = pmcData.Hideout.Areas.find(area => area.type === request.areaType);
|
|
if (!hideoutArea)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area", request.areaType));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
hideoutArea.active = request.enabled;
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Start production for an item from hideout area
|
|
* @param pmcData Player profile
|
|
* @param body Start prodution of single item request
|
|
* @param sessionID Session id
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
public singleProductionStart(pmcData: IPmcData, body: IHideoutSingleProductionStartRequestData, sessionID: string): IItemEventRouterResponse
|
|
{
|
|
// Start production
|
|
this.registerProduction(pmcData, body, sessionID);
|
|
|
|
// Find the recipe of the production
|
|
const recipe = this.databaseServer.getTables().hideout.production.find(p => p._id === body.recipeId);
|
|
// Find the actual amount of items we need to remove because body can send weird data
|
|
const requirements = this.jsonUtil.clone(recipe.requirements.filter(i => i.type === "Item"));
|
|
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
for (const itemToDelete of body.items)
|
|
{
|
|
const itemToCheck = pmcData.Inventory.items.find(i => i._id === itemToDelete.id);
|
|
const requirement = requirements.find(requirement => requirement.templateId === itemToCheck._tpl);
|
|
if (requirement.count <= 0)
|
|
continue;
|
|
this.inventoryHelper.removeItemByCount(pmcData, itemToDelete.id, requirement.count, sessionID, output);
|
|
requirement.count -= itemToDelete.count;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Handles event after clicking 'start' on the scav case hideout page
|
|
* @param pmcData player profile
|
|
* @param body client request object
|
|
* @param sessionID session id
|
|
* @returns item event router response
|
|
*/
|
|
public scavCaseProductionStart(pmcData: IPmcData, body: IHideoutScavCaseStartRequestData, sessionID: string): IItemEventRouterResponse
|
|
{
|
|
let output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
for (const requestedItem of body.items)
|
|
{
|
|
const inventoryItem = pmcData.Inventory.items.find(item => item._id === requestedItem.id);
|
|
if (!inventoryItem)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_scavcase_requested_item_in_profile_inventory", requestedItem.id));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
if (inventoryItem.upd?.StackObjectsCount
|
|
&& inventoryItem.upd.StackObjectsCount > requestedItem.count)
|
|
{
|
|
inventoryItem.upd.StackObjectsCount -= requestedItem.count;
|
|
}
|
|
else
|
|
{
|
|
output = this.inventoryHelper.removeItem(pmcData, requestedItem.id, sessionID, output);
|
|
}
|
|
}
|
|
|
|
const recipe = this.databaseServer.getTables().hideout.scavcase.find(r => r._id === body.recipeId);
|
|
if (!recipe)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_scav_case_recipie_in_database", body.recipeId));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
// @Important: Here we need to be very exact:
|
|
// - normal recipe: Production time value is stored in attribute "productionType" with small "p"
|
|
// - scav case recipe: Production time value is stored in attribute "ProductionType" with capital "P"
|
|
const modifiedScavCaseTime = this.getScavCaseTime(pmcData, recipe.ProductionTime);
|
|
|
|
pmcData.Hideout.Production[body.recipeId] = this.hideoutHelper.initProduction(body.recipeId, modifiedScavCaseTime);
|
|
pmcData.Hideout.Production[body.recipeId].sptIsScavCase = true;
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Adjust scav case time based on fence standing
|
|
*
|
|
* @param pmcData Player profile
|
|
* @param productionTime Time to complete scav case in seconds
|
|
* @returns Adjusted scav case time in seconds
|
|
*/
|
|
protected getScavCaseTime(pmcData: IPmcData, productionTime: number): number
|
|
{
|
|
const fenceLevel = this.fenceService.getFenceInfo(pmcData);
|
|
if (!fenceLevel)
|
|
{
|
|
return productionTime;
|
|
}
|
|
|
|
return productionTime * fenceLevel.ScavCaseTimeModifier;
|
|
}
|
|
|
|
/**
|
|
* Add generated scav case rewards to player profile
|
|
* @param pmcData player profile to add rewards to
|
|
* @param rewards reward items to add to profile
|
|
* @param recipeId recipe id to save into Production dict
|
|
*/
|
|
protected addScavCaseRewardsToProfile(pmcData: IPmcData, rewards: Product[], recipeId: string): void
|
|
{
|
|
pmcData.Hideout.Production[`ScavCase${recipeId}`] = {
|
|
Products: rewards
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Start production of continuously created item
|
|
* @param pmcData Player profile
|
|
* @param request Continious production request
|
|
* @param sessionID Session id
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
public continuousProductionStart(pmcData: IPmcData, request: IHideoutContinuousProductionStartRequestData, sessionID: string): IItemEventRouterResponse
|
|
{
|
|
this.registerProduction(pmcData, request, sessionID);
|
|
|
|
return this.eventOutputHolder.getOutput(sessionID);
|
|
}
|
|
|
|
/**
|
|
* Take completed item out of hideout area and place into player inventory
|
|
* @param pmcData Player profile
|
|
* @param request Remove production from area request
|
|
* @param sessionID Session id
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
public takeProduction(pmcData: IPmcData, request: IHideoutTakeProductionRequestData, sessionID: string): IItemEventRouterResponse
|
|
{
|
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
|
|
|
if (request.recipeId === HideoutHelper.bitcoinFarm)
|
|
{
|
|
return this.hideoutHelper.getBTC(pmcData, request, sessionID);
|
|
}
|
|
|
|
const recipe = this.databaseServer.getTables().hideout.production.find(r => r._id === request.recipeId);
|
|
if (recipe)
|
|
{
|
|
return this.handleRecipe(sessionID, recipe, pmcData, request, output);
|
|
}
|
|
|
|
const scavCase = this.databaseServer.getTables().hideout.scavcase.find(r => r._id === request.recipeId);
|
|
if (scavCase)
|
|
{
|
|
return this.handleScavCase(sessionID, pmcData, request, output);
|
|
}
|
|
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_production_in_profile_by_recipie_id", request.recipeId));
|
|
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
/**
|
|
* Take recipe-type production out of hideout area and place into player inventory
|
|
* @param sessionID Session id
|
|
* @param recipe Completed recipe of item
|
|
* @param pmcData Player profile
|
|
* @param request Remove production from area request
|
|
* @param output Output object to update
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
protected handleRecipe(sessionID: string, recipe: IHideoutProduction, pmcData: IPmcData, request: IHideoutTakeProductionRequestData, output: IItemEventRouterResponse): IItemEventRouterResponse
|
|
{
|
|
// Variables for managemnet of skill
|
|
let craftingExpAmount = 0;
|
|
|
|
// ? move the logic of BackendCounters in new method?
|
|
let counterHoursCrafting = pmcData.BackendCounters[HideoutController.nameBackendCountersCrafting];
|
|
if (!counterHoursCrafting)
|
|
{
|
|
pmcData.BackendCounters[HideoutController.nameBackendCountersCrafting] = { "id": HideoutController.nameBackendCountersCrafting, "value": 0 };
|
|
counterHoursCrafting = pmcData.BackendCounters[HideoutController.nameBackendCountersCrafting];
|
|
}
|
|
let hoursCrafting = counterHoursCrafting.value;
|
|
|
|
|
|
// create item and throw it into profile
|
|
let id = recipe.endProduct;
|
|
|
|
// replace the base item with its main preset
|
|
if (this.presetHelper.hasPreset(id))
|
|
{
|
|
id = this.presetHelper.getDefaultPreset(id)._id;
|
|
}
|
|
|
|
const newReq = {
|
|
items: [{
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
item_id: id,
|
|
count: recipe.count
|
|
}],
|
|
tid: "ragfair"
|
|
};
|
|
|
|
const entries = Object.entries(pmcData.Hideout.Production);
|
|
let prodId: string;
|
|
for (const x of entries)
|
|
{
|
|
if (this.hideoutHelper.isProductionType(x[1])) // Production or ScavCase
|
|
{
|
|
if ((x[1] as Production).RecipeId === request.recipeId)
|
|
{
|
|
prodId = x[0]; // set to objects key
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (prodId === undefined)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_production_in_profile_by_recipie_id", request.recipeId));
|
|
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
// check if the recipe is the same as the last one
|
|
const area = pmcData.Hideout.Areas[recipe.areaType];
|
|
|
|
if (area && request.recipeId !== area.lastRecipe)
|
|
{
|
|
// 1 point per craft upon the end of production for alternating between 2 different crafting recipes in the same module
|
|
craftingExpAmount += 10;
|
|
}
|
|
|
|
// 1 point per 8 hours of crafting
|
|
hoursCrafting += recipe.productionTime;
|
|
if ((hoursCrafting / this.hideoutConfig.hoursForSkillCrafting) >= 1)
|
|
{
|
|
const multiplierCrafting = Math.floor((hoursCrafting / this.hideoutConfig.hoursForSkillCrafting));
|
|
craftingExpAmount += (1 * multiplierCrafting);
|
|
hoursCrafting -= (this.hideoutConfig.hoursForSkillCrafting * multiplierCrafting);
|
|
}
|
|
|
|
// increment
|
|
// if addItem passes validation:
|
|
// - increment skill point for crafting
|
|
// - delete the production in profile Hideout.Production
|
|
const callback = () =>
|
|
{
|
|
// manager Hideout skill
|
|
// ? use a configuration variable for the value?
|
|
this.playerService.incrementSkillLevel(pmcData, SkillTypes.HIDEOUT_MANAGEMENT, 4);
|
|
//manager Crafting skill
|
|
if (craftingExpAmount > 0)
|
|
{
|
|
this.playerService.incrementSkillLevel(pmcData, SkillTypes.CRAFTING, craftingExpAmount);
|
|
}
|
|
area.lastRecipe = request.recipeId;
|
|
counterHoursCrafting.value = hoursCrafting;
|
|
|
|
//delete production
|
|
delete pmcData.Hideout.Production[prodId];
|
|
};
|
|
|
|
// Remove the old production from output object before its sent to client
|
|
delete output.profileChanges[sessionID].production[request.recipeId];
|
|
|
|
// Handle the isEncoded flag from recipe
|
|
if (recipe.isEncoded)
|
|
{
|
|
const upd: Upd = {
|
|
RecodableComponent: { IsEncoded: true}
|
|
};
|
|
|
|
return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, true, upd);
|
|
}
|
|
|
|
return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, true);
|
|
}
|
|
|
|
/**
|
|
* Handles giving rewards stored in player profile to player after clicking 'get rewards'
|
|
* @param sessionID Session id
|
|
* @param pmcData Player profile
|
|
* @param request Get rewards from scavcase craft request
|
|
* @param output Output object to update
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
protected handleScavCase(sessionID: string, pmcData: IPmcData, request: IHideoutTakeProductionRequestData, output: IItemEventRouterResponse): IItemEventRouterResponse
|
|
{
|
|
const ongoingProductions = Object.entries(pmcData.Hideout.Production);
|
|
let prodId: string;
|
|
for (const x of ongoingProductions)
|
|
{
|
|
if (this.hideoutHelper.isProductionType(x[1])) // Production or ScavCase
|
|
{
|
|
if ((x[1] as ScavCase).RecipeId === request.recipeId)
|
|
{
|
|
prodId = x[0]; // set to objects key
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (prodId === undefined)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_production_in_profile_by_recipie_id", request.recipeId));
|
|
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
// Create rewards for scav case
|
|
const scavCaseRewards = this.scavCaseRewardGenerator.generate(request.recipeId);
|
|
|
|
pmcData.Hideout.Production[prodId].Products = scavCaseRewards;
|
|
|
|
// Remove the old production from output object before its sent to client
|
|
delete output.profileChanges[sessionID].production[request.recipeId];
|
|
|
|
const itemsToAdd = pmcData.Hideout.Production[prodId].Products.map((x: { _tpl: string; upd?: { StackObjectsCount?: number; }; }) =>
|
|
{
|
|
let id = x._tpl;
|
|
if (this.presetHelper.hasPreset(id))
|
|
{
|
|
id = this.presetHelper.getDefaultPreset(id)._id;
|
|
}
|
|
const numOfItems = !x.upd?.StackObjectsCount
|
|
? 1
|
|
: x.upd.StackObjectsCount;
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
return { item_id: id, count: numOfItems };
|
|
});
|
|
|
|
const newReq = {
|
|
items: itemsToAdd,
|
|
tid: "ragfair"
|
|
};
|
|
|
|
const callback = () =>
|
|
{
|
|
delete pmcData.Hideout.Production[prodId];
|
|
};
|
|
|
|
return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, true);
|
|
}
|
|
|
|
/**
|
|
* Start area production for item
|
|
* @param pmcData Player profile
|
|
* @param request Start production request
|
|
* @param sessionID Session id
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
public registerProduction(pmcData: IPmcData, request: IHideoutSingleProductionStartRequestData | IHideoutContinuousProductionStartRequestData, sessionID: string): IItemEventRouterResponse
|
|
{
|
|
return this.hideoutHelper.registerProduction(pmcData, request, sessionID);
|
|
}
|
|
|
|
|
|
/**
|
|
* Get quick time event list for hideout
|
|
* // TODO - implement this
|
|
* @param sessionId Session id
|
|
* @returns IQteData array
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
public getQteList(sessionId: string): IQteData[]
|
|
{
|
|
return this.databaseServer.getTables().hideout.qte;
|
|
}
|
|
|
|
/**
|
|
* Handle HideoutQuickTimeEvent on client/game/profile/items/moving
|
|
* Called after completing workout at gym
|
|
* @param sessionId Session id
|
|
* @param pmcData Profile to adjust
|
|
* @param request QTE result object
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
public handleQTEEventOutcome(sessionId: string, pmcData: IPmcData, request: IHandleQTEEventRequestData): IItemEventRouterResponse
|
|
{
|
|
// {
|
|
// "Action": "HideoutQuickTimeEvent",
|
|
// "results": [true, false, true, true, true, true, true, true, true, false, false, false, false, false, false],
|
|
// "id": "63b16feb5d012c402c01f6ef",
|
|
// "timestamp": 1672585349
|
|
// }
|
|
|
|
// Skill changes are done in
|
|
// /client/hideout/workout (applyWorkoutChanges).
|
|
|
|
pmcData.Health.Energy.Current -= 50;
|
|
if (pmcData.Health.Energy.Current < 1)
|
|
{
|
|
pmcData.Health.Energy.Current = 1;
|
|
}
|
|
|
|
pmcData.Health.Hydration.Current -= 50;
|
|
if (pmcData.Health.Hydration.Current < 1)
|
|
{
|
|
pmcData.Health.Hydration.Current = 1;
|
|
}
|
|
|
|
return this.eventOutputHolder.getOutput(sessionId);
|
|
}
|
|
|
|
/**
|
|
* Record a high score from the shooting range into a player profiles overallcounters
|
|
* @param sessionId Session id
|
|
* @param pmcData Profile to update
|
|
* @param request shooting range score request
|
|
* @returns IItemEventRouterResponse
|
|
*/
|
|
public recordShootingRangePoints(sessionId: string, pmcData: IPmcData, request: IRecordShootingRangePoints): IItemEventRouterResponse
|
|
{
|
|
// Check if counter exists, add placeholder if it doesnt
|
|
if (!pmcData.Stats.OverallCounters.Items.find(x => x.Key.includes("ShootingRangePoints")))
|
|
{
|
|
pmcData.Stats.OverallCounters.Items.push({
|
|
Key: ["ShootingRangePoints"],
|
|
Value: 0
|
|
});
|
|
}
|
|
|
|
// Find counter by key and update value
|
|
const shootingRangeHighScore = pmcData.Stats.OverallCounters.Items.find(x => x.Key.includes("ShootingRangePoints"));
|
|
shootingRangeHighScore.Value = request.points;
|
|
|
|
// Check against live, maybe a response isnt necessary
|
|
return this.eventOutputHolder.getOutput(sessionId);
|
|
}
|
|
|
|
/**
|
|
* Handle client/game/profile/items/moving - HideoutImproveArea
|
|
* @param sessionId Session id
|
|
* @param pmcData profile to improve area in
|
|
* @param request improve area request data
|
|
*/
|
|
public improveArea(sessionId: string, pmcData: IPmcData, request: IHideoutImproveAreaRequestData): IItemEventRouterResponse
|
|
{
|
|
const output = this.eventOutputHolder.getOutput(sessionId);
|
|
|
|
// Create mapping of required item with corrisponding item from player inventory
|
|
const items = request.items.map(reqItem =>
|
|
{
|
|
const item = pmcData.Inventory.items.find(invItem => invItem._id === reqItem.id);
|
|
return {
|
|
inventoryItem: item,
|
|
requestedItem: reqItem
|
|
};
|
|
});
|
|
|
|
// If it's not money, its construction / barter items
|
|
for (const item of items)
|
|
{
|
|
if (!item.inventoryItem)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_item_in_inventory", item.requestedItem.id));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
if (this.paymentHelper.isMoneyTpl(item.inventoryItem._tpl)
|
|
&& item.inventoryItem.upd
|
|
&& item.inventoryItem.upd.StackObjectsCount
|
|
&& item.inventoryItem.upd.StackObjectsCount > item.requestedItem.count)
|
|
{
|
|
item.inventoryItem.upd.StackObjectsCount -= item.requestedItem.count;
|
|
}
|
|
else
|
|
{
|
|
this.inventoryHelper.removeItem(pmcData, item.inventoryItem._id, sessionId, output);
|
|
}
|
|
}
|
|
|
|
const profileHideoutArea = pmcData.Hideout.Areas.find(x => x.type === request.areaType);
|
|
if (!profileHideoutArea)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area", request.areaType));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
const hideoutDbData = this.databaseServer.getTables().hideout.areas.find(x => x.type === request.areaType);
|
|
if (!hideoutDbData)
|
|
{
|
|
this.logger.error(this.localisationService.getText("hideout-unable_to_find_area_in_database", request.areaType));
|
|
return this.httpResponse.appendErrorToOutput(output);
|
|
}
|
|
|
|
// Add all improvemets to output object
|
|
const improvements = hideoutDbData.stages[profileHideoutArea.level].improvements;
|
|
const timestamp = this.timeUtil.getTimestamp();
|
|
for (const improvement of improvements)
|
|
{
|
|
if (!output.profileChanges[sessionId].improvements)
|
|
{
|
|
output.profileChanges[sessionId].improvements = {};
|
|
}
|
|
|
|
const improvementDetails = {completed: false, improveCompleteTimestamp: timestamp + improvement.improvementTime};
|
|
output.profileChanges[sessionId].improvements[improvement.id] = improvementDetails;
|
|
pmcData.Hideout.Improvements[improvement.id] = improvementDetails;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Function called every x seconds as part of onUpdate event
|
|
*/
|
|
public update(): void
|
|
{
|
|
for (const sessionID in this.saveServer.getProfiles())
|
|
{
|
|
if ("Hideout" in this.saveServer.getProfile(sessionID).characters.pmc)
|
|
{
|
|
this.hideoutHelper.updatePlayerHideout(sessionID);
|
|
}
|
|
}
|
|
}
|
|
}
|