2023-03-03 16:23:46 +01:00
import { inject , injectable } from "tsyringe" ;
import { DialogueHelper } from "../helpers/DialogueHelper" ;
import { ItemHelper } from "../helpers/ItemHelper" ;
import { ProfileHelper } from "../helpers/ProfileHelper" ;
import { QuestConditionHelper } from "../helpers/QuestConditionHelper" ;
import { QuestHelper } from "../helpers/QuestHelper" ;
import { IPmcData } from "../models/eft/common/IPmcData" ;
import { Quest } from "../models/eft/common/tables/IBotBase" ;
2023-03-13 15:02:39 +01:00
import { Item } from "../models/eft/common/tables/IItem" ;
2023-03-03 16:23:46 +01:00
import { AvailableForConditions , IQuest , Reward } from "../models/eft/common/tables/IQuest" ;
import { IRepeatableQuest } from "../models/eft/common/tables/IRepeatableQuests" ;
import { IItemEventRouterResponse } from "../models/eft/itemEvent/IItemEventRouterResponse" ;
import { IAcceptQuestRequestData } from "../models/eft/quests/IAcceptQuestRequestData" ;
import { ICompleteQuestRequestData } from "../models/eft/quests/ICompleteQuestRequestData" ;
import { IFailQuestRequestData } from "../models/eft/quests/IFailQuestRequestData" ;
import { IHandoverQuestRequestData } from "../models/eft/quests/IHandoverQuestRequestData" ;
import { ConfigTypes } from "../models/enums/ConfigTypes" ;
import { MessageType } from "../models/enums/MessageType" ;
import { QuestStatus } from "../models/enums/QuestStatus" ;
2023-07-09 17:31:42 +02:00
import { SeasonalEventType } from "../models/enums/SeasonalEventType" ;
2023-03-03 16:23:46 +01:00
import { IQuestConfig } from "../models/spt/config/IQuestConfig" ;
import { ILogger } from "../models/spt/utils/ILogger" ;
import { EventOutputHolder } from "../routers/EventOutputHolder" ;
import { ConfigServer } from "../servers/ConfigServer" ;
import { DatabaseServer } from "../servers/DatabaseServer" ;
import { LocaleService } from "../services/LocaleService" ;
import { LocalisationService } from "../services/LocalisationService" ;
import { PlayerService } from "../services/PlayerService" ;
2023-07-09 15:45:06 +02:00
import { SeasonalEventService } from "../services/SeasonalEventService" ;
2023-03-03 16:23:46 +01:00
import { HttpResponseUtil } from "../utils/HttpResponseUtil" ;
import { TimeUtil } from "../utils/TimeUtil" ;
@injectable ( )
export class QuestController
{
protected questConfig : IQuestConfig ;
constructor (
@inject ( "WinstonLogger" ) protected logger : ILogger ,
@inject ( "TimeUtil" ) protected timeUtil : TimeUtil ,
@inject ( "HttpResponseUtil" ) protected httpResponseUtil : HttpResponseUtil ,
@inject ( "EventOutputHolder" ) protected eventOutputHolder : EventOutputHolder ,
@inject ( "DatabaseServer" ) protected databaseServer : DatabaseServer ,
@inject ( "ItemHelper" ) protected itemHelper : ItemHelper ,
@inject ( "DialogueHelper" ) protected dialogueHelper : DialogueHelper ,
@inject ( "ProfileHelper" ) protected profileHelper : ProfileHelper ,
@inject ( "QuestHelper" ) protected questHelper : QuestHelper ,
@inject ( "QuestConditionHelper" ) protected questConditionHelper : QuestConditionHelper ,
@inject ( "PlayerService" ) protected playerService : PlayerService ,
@inject ( "LocaleService" ) protected localeService : LocaleService ,
2023-07-09 15:45:06 +02:00
@inject ( "SeasonalEventService" ) protected seasonalEventService : SeasonalEventService ,
2023-03-03 16:23:46 +01:00
@inject ( "LocalisationService" ) protected localisationService : LocalisationService ,
@inject ( "ConfigServer" ) protected configServer : ConfigServer
)
{
this . questConfig = this . configServer . getConfig ( ConfigTypes . QUEST ) ;
}
/ * *
2023-07-15 12:00:35 +02:00
* Handle client / quest / list
2023-03-03 16:23:46 +01:00
* Get all quests visible to player
* Exclude quests with incomplete preconditions ( level / loyalty )
* @param sessionID session id
* @returns array of IQuest
* /
public getClientQuests ( sessionID : string ) : IQuest [ ]
{
2023-07-10 16:48:49 +02:00
const questsToShowPlayer : IQuest [ ] = [ ] ;
2023-03-03 16:23:46 +01:00
const allQuests = this . questHelper . getQuestsFromDb ( ) ;
const profile : IPmcData = this . profileHelper . getPmcProfile ( sessionID ) ;
for ( const quest of allQuests )
{
2023-07-09 15:45:06 +02:00
// Player already accepted the quest, show it regardless of status
2023-03-03 16:23:46 +01:00
if ( profile . Quests . some ( x = > x . qid === quest . _id ) )
{
2023-07-10 16:48:49 +02:00
questsToShowPlayer . push ( quest ) ;
2023-03-03 16:23:46 +01:00
continue ;
}
2023-07-09 15:45:06 +02:00
// Filter out bear quests for usec and vice versa
2023-03-03 16:23:46 +01:00
if ( this . questIsForOtherSide ( profile . Info . Side , quest . _id ) )
{
continue ;
}
2023-07-09 17:31:42 +02:00
if ( ! this . showEventQuestToPlayer ( quest . _id ) )
2023-07-09 15:45:06 +02:00
{
continue ;
}
2023-03-03 16:23:46 +01:00
// Don't add quests that have a level higher than the user's
2023-07-10 16:48:49 +02:00
if ( ! this . playerLevelFulfillsQuestRequrement ( quest , profile . Info . Level ) )
2023-03-03 16:23:46 +01:00
{
2023-07-10 16:48:49 +02:00
continue ;
2023-03-03 16:23:46 +01:00
}
const questRequirements = this . questConditionHelper . getQuestConditions ( quest . conditions . AvailableForStart ) ;
const loyaltyRequirements = this . questConditionHelper . getLoyaltyConditions ( quest . conditions . AvailableForStart ) ;
2023-07-10 16:48:49 +02:00
// Quest has no conditions or loyalty conditions, add to visible quest list
2023-03-03 16:23:46 +01:00
if ( questRequirements . length === 0 && loyaltyRequirements . length === 0 )
{
2023-07-10 16:48:49 +02:00
questsToShowPlayer . push ( quest ) ;
2023-03-03 16:23:46 +01:00
continue ;
}
// Check the status of each quest condition, if any are not completed
// then this quest should not be visible
let haveCompletedPreviousQuest = true ;
for ( const condition of questRequirements )
{
// If the previous quest isn't in the user profile, it hasn't been completed or started
2023-07-10 16:48:49 +02:00
const previousQuest = profile . Quests . find ( pq = > pq . qid === condition . _props . target ) ;
2023-03-03 16:23:46 +01:00
if ( ! previousQuest )
{
haveCompletedPreviousQuest = false ;
break ;
}
// If previous is in user profile, check condition requirement and current status
if ( condition . _props . status . includes ( previousQuest . status ) )
{
continue ;
}
// Chemical fix: "Started" Status is catered for above. This will include it just if it's started.
// but maybe this is better:
// if ((condition._props.status[0] === QuestStatus.Started)
// && (previousQuest.status === "AvailableForFinish" || previousQuest.status === "Success")
if ( ( condition . _props . status [ 0 ] === QuestStatus . Started ) )
{
const statusName = Object . keys ( QuestStatus ) [ condition . _props . status [ 0 ] ] ;
this . logger . debug ( ` [QUESTS]: fix for polikhim bug: ${ quest . _id } ( ${ this . questHelper . getQuestNameFromLocale ( quest . _id ) } ) ${ condition . _props . status [ 0 ] } , ${ statusName } != ${ previousQuest . status } ` ) ;
continue ;
}
haveCompletedPreviousQuest = false ;
break ;
}
let passesLoyaltyRequirements = true ;
for ( const condition of loyaltyRequirements )
{
if ( ! this . questHelper . traderStandingRequirementCheck ( condition . _props , profile ) )
{
passesLoyaltyRequirements = false ;
break ;
}
}
if ( haveCompletedPreviousQuest && passesLoyaltyRequirements )
{
2023-07-10 16:48:49 +02:00
questsToShowPlayer . push ( quest ) ;
}
}
return questsToShowPlayer ;
}
/ * *
* Does a provided quest have a level requirement equal to or below defined level
* @param quest Quest to check
* @param playerLevel level of player to test against quest
* @returns true if quest can be seen / accepted by player of defined level
* /
protected playerLevelFulfillsQuestRequrement ( quest : IQuest , playerLevel : number ) : boolean
{
const levelConditions = this . questConditionHelper . getLevelConditions ( quest . conditions . AvailableForStart ) ;
if ( levelConditions . length )
{
for ( const levelCondition of levelConditions )
{
if ( ! this . questHelper . doesPlayerLevelFulfilCondition ( playerLevel , levelCondition ) )
{
// Not valid, exit out
return false ;
}
2023-03-03 16:23:46 +01:00
}
}
2023-07-10 16:48:49 +02:00
// All conditions passed / has no level requirement, valid
return true ;
2023-03-03 16:23:46 +01:00
}
2023-07-09 17:31:42 +02:00
/ * *
* Should a quest be shown to the player in trader quest screen
* @param questId Quest to check
* @returns true = show to player
* /
protected showEventQuestToPlayer ( questId : string ) : boolean
{
const isChristmasEventActive = this . seasonalEventService . christmasEventEnabled ( ) ;
const isHalloweenEventActive = this . seasonalEventService . halloweenEventEnabled ( ) ;
// Not christmas + quest is for christmas
if ( ! isChristmasEventActive && this . seasonalEventService . isQuestRelatedToEvent ( questId , SeasonalEventType . CHRISTMAS ) )
{
return false ;
}
// Not halloween + quest is for halloween
if ( ! isHalloweenEventActive && this . seasonalEventService . isQuestRelatedToEvent ( questId , SeasonalEventType . HALLOWEEN ) )
{
return false ;
}
// Should non-season event quests be shown to player
if ( ! this . questConfig . showNonSeasonalEventQuests && this . seasonalEventService . isQuestRelatedToEvent ( questId , SeasonalEventType . NONE ) )
{
return false ;
}
return true ;
}
2023-03-03 16:23:46 +01:00
/ * *
* Is the quest for the opposite side the player is on
2023-03-21 15:19:49 +01:00
* @param playerSide Player side ( usec / bear )
* @param questId QuestId to check
2023-03-03 16:23:46 +01:00
* /
2023-03-21 15:19:49 +01:00
protected questIsForOtherSide ( playerSide : string , questId : string ) : boolean
2023-03-03 16:23:46 +01:00
{
2023-03-21 15:19:49 +01:00
const isUsec = playerSide . toLowerCase ( ) === "usec" ;
2023-03-03 16:23:46 +01:00
if ( isUsec && this . questConfig . bearOnlyQuests . includes ( questId ) )
{
// player is usec and quest is bear only, skip
return true ;
}
if ( ! isUsec && this . questConfig . usecOnlyQuests . includes ( questId ) )
{
// player is bear and quest is usec only, skip
return true ;
}
return false ;
}
/ * *
2023-07-15 12:00:35 +02:00
* Handle QuestAccept event
2023-03-03 16:23:46 +01:00
* Handle the client accepting a quest and starting it
* Send starting rewards if any to player and
* Send start notification if any to player
* @param pmcData Profile to update
* @param acceptedQuest Quest accepted
* @param sessionID Session id
* @returns client response
* /
public acceptQuest ( pmcData : IPmcData , acceptedQuest : IAcceptQuestRequestData , sessionID : string ) : IItemEventRouterResponse
{
const acceptQuestResponse = this . eventOutputHolder . getOutput ( sessionID ) ;
const startedState = QuestStatus . Started ;
const newQuest = this . questHelper . getQuestReadyForProfile ( pmcData , startedState , acceptedQuest ) ;
// Does quest exist in profile
if ( pmcData . Quests . find ( x = > x . qid === acceptedQuest . qid ) )
{
// Update existing
this . questHelper . updateQuestState ( pmcData , QuestStatus . Started , acceptedQuest . qid ) ;
}
else
{
// Add new quest to server profile
pmcData . Quests . push ( newQuest ) ;
}
// Create a dialog message for starting the quest.
// Note that for starting quests, the correct locale field is "description", not "startedMessageText".
const questFromDb = this . questHelper . getQuestFromDb ( acceptedQuest . qid , pmcData ) ;
// Get messageId of text to send to player as text message in game
const messageId = this . questHelper . getMessageIdForQuestStart ( questFromDb . startedMessageText , questFromDb . description ) ;
const messageContent = this . dialogueHelper . createMessageContext ( messageId , MessageType . QUEST_START , this . questConfig . redeemTime ) ;
const startedQuestRewards = this . questHelper . applyQuestReward ( pmcData , acceptedQuest . qid , QuestStatus . Started , sessionID , acceptQuestResponse ) ;
this . dialogueHelper . addDialogueMessage ( questFromDb . traderId , messageContent , sessionID , startedQuestRewards ) ;
acceptQuestResponse . profileChanges [ sessionID ] . quests = this . questHelper . acceptedUnlocked ( acceptedQuest . qid , sessionID ) ;
return acceptQuestResponse ;
}
/ * *
* Handle the client accepting a repeatable quest and starting it
* Send starting rewards if any to player and
* Send start notification if any to player
* @param pmcData Profile to update with new quest
* @param acceptedQuest Quest being accepted
* @param sessionID Session id
* @returns IItemEventRouterResponse
* /
public acceptRepeatableQuest ( pmcData : IPmcData , acceptedQuest : IAcceptQuestRequestData , sessionID : string ) : IItemEventRouterResponse
{
const acceptQuestResponse = this . eventOutputHolder . getOutput ( sessionID ) ;
const state = QuestStatus . Started ;
const newQuest = this . questHelper . getQuestReadyForProfile ( pmcData , state , acceptedQuest ) ;
pmcData . Quests . push ( newQuest ) ;
const repeatableQuestProfile = this . getRepeatableQuestFromProfile ( pmcData , acceptedQuest ) ;
if ( ! repeatableQuestProfile )
{
this . logger . error ( this . localisationService . getText ( "repeatable-accepted_repeatable_quest_not_found_in_active_quests" , acceptedQuest . qid ) ) ;
throw new Error ( this . localisationService . getText ( "repeatable-unable_to_accept_quest_see_log" ) ) ;
}
const locale = this . localeService . getLocaleDb ( ) ;
const questStartedMessageKey = this . questHelper . getMessageIdForQuestStart ( repeatableQuestProfile . startedMessageText , repeatableQuestProfile . description ) ;
// Can be started text or description text based on above function result
let questStartedMessageText = locale [ questStartedMessageKey ] ;
// TODO: remove this whole if statement, possibly not required?
if ( ! questStartedMessageText )
{
this . logger . debug ( ` Unable to accept quest ${ acceptedQuest . qid } , cannot find the quest started message text with id ${ questStartedMessageKey } . attempting to find it in en locale instead ` ) ;
// For some reason non-en locales dont have repeatable quest ids, fall back to en and grab it if possible
const enLocale = this . databaseServer . getTables ( ) . locales . global [ "en" ] ;
questStartedMessageText = enLocale [ repeatableQuestProfile . startedMessageText ] ;
if ( ! questStartedMessageText )
{
this . logger . error ( this . localisationService . getText ( "repeatable-unable_to_accept_quest_starting_message_not_found" , { questId : acceptedQuest.qid , messageId : questStartedMessageKey } ) ) ;
return this . httpResponseUtil . appendErrorToOutput ( acceptQuestResponse , this . localisationService . getText ( "repeatable-unable_to_accept_quest_see_log" ) ) ;
}
}
const questRewards = this . questHelper . getQuestRewardItems ( < IQuest > < unknown > repeatableQuestProfile , state ) ;
const messageContent = this . dialogueHelper . createMessageContext ( questStartedMessageKey , MessageType . QUEST_START , this . questConfig . redeemTime ) ;
this . dialogueHelper . addDialogueMessage ( repeatableQuestProfile . traderId , messageContent , sessionID , questRewards ) ;
acceptQuestResponse . profileChanges [ sessionID ] . quests = this . questHelper . acceptedUnlocked ( acceptedQuest . qid , sessionID ) ;
return acceptQuestResponse ;
}
/ * *
* Look for an accepted quest inside player profile , return matching
* @param pmcData Profile to search through
* @param acceptedQuest Quest to search for
* @returns IRepeatableQuest
* /
protected getRepeatableQuestFromProfile ( pmcData : IPmcData , acceptedQuest : IAcceptQuestRequestData ) : IRepeatableQuest
{
2023-03-21 15:19:49 +01:00
for ( const repeatableQuest of pmcData . RepeatableQuests )
2023-03-03 16:23:46 +01:00
{
2023-03-21 15:22:45 +01:00
const matchingQuest = repeatableQuest . activeQuests . find ( x = > x . _id === acceptedQuest . qid ) ;
if ( matchingQuest )
2023-03-03 16:23:46 +01:00
{
2023-03-21 15:19:49 +01:00
this . logger . debug ( ` Accepted repeatable quest ${ acceptedQuest . qid } from ${ repeatableQuest . name } ` ) ;
2023-03-21 15:22:45 +01:00
return matchingQuest ;
2023-03-03 16:23:46 +01:00
}
}
2023-03-21 15:19:49 +01:00
return undefined ;
2023-03-03 16:23:46 +01:00
}
/ * *
2023-07-15 12:00:35 +02:00
* Handle QuestComplete event
2023-03-03 16:23:46 +01:00
* Update completed quest in profile
* Add newly unlocked quests to profile
2023-07-15 12:00:35 +02:00
* Also recalculate their level due to exp rewards
2023-03-03 16:23:46 +01:00
* @param pmcData Player profile
* @param body Completed quest request
* @param sessionID Session id
* @returns ItemEvent client response
* /
public completeQuest ( pmcData : IPmcData , body : ICompleteQuestRequestData , sessionID : string ) : IItemEventRouterResponse
{
const completeQuestResponse = this . eventOutputHolder . getOutput ( sessionID ) ;
const completedQuestId = body . qid ;
const beforeQuests = this . getClientQuests ( sessionID ) ; // Must be gathered prior to applyQuestReward() & failQuests()
const newQuestState = QuestStatus . Success ;
this . questHelper . updateQuestState ( pmcData , newQuestState , completedQuestId ) ;
const questRewards = this . questHelper . applyQuestReward ( pmcData , body . qid , newQuestState , sessionID , completeQuestResponse ) ;
// Check if any of linked quest is failed, and that is unrestartable.
const questsToFail = this . getQuestsFailedByCompletingQuest ( completedQuestId ) ;
if ( questsToFail && questsToFail . length > 0 )
{
this . failQuests ( sessionID , pmcData , questsToFail ) ;
}
// Show modal on player screen
this . sendSuccessDialogMessageOnQuestComplete ( sessionID , pmcData , completedQuestId , questRewards ) ;
// Add diff of quests before completion vs after to client response
const questDelta = this . questHelper . getDeltaQuests ( beforeQuests , this . getClientQuests ( sessionID ) ) ;
completeQuestResponse . profileChanges [ sessionID ] . quests = questDelta ;
this . addTimeLockedQuestsToProfile ( pmcData , questDelta , body . qid ) ;
// Update trader info data on response
Object . assign ( completeQuestResponse . profileChanges [ sessionID ] . traderRelations , pmcData . TradersInfo ) ;
// Check if it's a repeatable quest. If so remove from Quests and repeatable.activeQuests list to repeatable.inactiveQuests
for ( const currentRepeatable of pmcData . RepeatableQuests )
{
const repeatableQuest = currentRepeatable . activeQuests . find ( x = > x . _id === completedQuestId ) ;
if ( repeatableQuest )
{
currentRepeatable . activeQuests = currentRepeatable . activeQuests . filter ( x = > x . _id !== completedQuestId ) ;
currentRepeatable . inactiveQuests . push ( repeatableQuest ) ;
}
}
// Recalculate level in event player leveled up
pmcData . Info . Level = this . playerService . calculateLevel ( pmcData ) ;
return completeQuestResponse ;
}
/ * *
* Send a popup to player on successful completion of a quest
* @param sessionID session id
* @param pmcData Player profile
* @param completedQuestId Completed quest id
* @param questRewards Rewards given to player
* /
protected sendSuccessDialogMessageOnQuestComplete ( sessionID : string , pmcData : IPmcData , completedQuestId : string , questRewards : Reward [ ] ) : void
{
const quest = this . questHelper . getQuestFromDb ( completedQuestId , pmcData ) ;
const messageContent = this . dialogueHelper . createMessageContext ( quest . successMessageText , MessageType . QUEST_SUCCESS , this . questConfig . redeemTime ) ;
this . dialogueHelper . addDialogueMessage ( quest . traderId , messageContent , sessionID , questRewards ) ;
}
/ * *
* Look for newly available quests after completing a quest with a requirement to wait x minutes ( time - locked ) before being available and add data to profile
* @param pmcData Player profile to update
* @param quests Quests to look for wait conditions in
* @param completedQuestId Quest just completed
* /
protected addTimeLockedQuestsToProfile ( pmcData : IPmcData , quests : IQuest [ ] , completedQuestId : string ) : void
{
// Iterate over quests, look for quests with right criteria
for ( const quest of quests )
{
// If newly available quest has prereq of completed quest + availableAfter value > 0 (quest has wait time)
const nextQuestWaitCondition = quest . conditions . AvailableForStart . find ( x = > x . _props . target === completedQuestId && x . _props . availableAfter > 0 ) ;
if ( nextQuestWaitCondition )
{
const availableAfterTimestamp = this . timeUtil . getTimestamp ( ) + nextQuestWaitCondition . _props . availableAfter ;
// Add/update quest to profile with status of AvailableAfter
const existingQuestInProfile = pmcData . Quests . find ( x = > x . qid === quest . _id ) ;
if ( existingQuestInProfile )
{
existingQuestInProfile . availableAfter = availableAfterTimestamp ;
existingQuestInProfile . status = QuestStatus . Locked ;
existingQuestInProfile . startTime = 0 ;
existingQuestInProfile . statusTimers = { } ;
continue ;
}
pmcData . Quests . push ( {
qid : quest._id ,
startTime : 0 ,
status : QuestStatus.Locked ,
statusTimers : { } ,
availableAfter : availableAfterTimestamp
} ) ;
}
}
}
/ * *
* Returns a list of quests that should be failed when a quest is completed
* @param completedQuestId quest completed id
* @returns array of quests
* /
protected getQuestsFailedByCompletingQuest ( completedQuestId : string ) : IQuest [ ]
{
return this . questHelper . getQuestsFromDb ( ) . filter ( ( x ) = >
{
// No fail conditions, exit early
if ( ! x . conditions . Fail || x . conditions . Fail . length === 0 )
{
return false ;
}
for ( const failCondition of x . conditions . Fail )
{
if ( failCondition . _props . target === completedQuestId )
{
return true ;
}
}
return false ;
} ) ;
}
/ * *
* Fail the quests provided
* Update quest in profile , otherwise add fresh quest object with failed status
* @param sessionID session id
* @param pmcData player profile
* @param questsToFail quests to fail
* /
protected failQuests ( sessionID : string , pmcData : IPmcData , questsToFail : IQuest [ ] ) : void
{
for ( const questToFail of questsToFail )
{
if ( questToFail . conditions . Fail [ 0 ] . _props . status [ 0 ] !== QuestStatus . Success )
{
continue ;
}
const isActiveQuestInPlayerProfile = pmcData . Quests . find ( y = > y . qid === questToFail . _id ) ;
if ( isActiveQuestInPlayerProfile )
{
const failBody : IFailQuestRequestData = {
Action : "QuestComplete" ,
qid : questToFail._id ,
removeExcessItems : true
} ;
this . questHelper . failQuest ( pmcData , failBody , sessionID ) ;
}
else
{
const questData : Quest = {
qid : questToFail._id ,
startTime : this.timeUtil.getTimestamp ( ) ,
status : QuestStatus.Fail
} ;
pmcData . Quests . push ( questData ) ;
}
}
}
/ * *
2023-07-15 12:00:35 +02:00
* Handle QuestHandover event
2023-03-03 16:23:46 +01:00
* @param pmcData Player profile
* @param handoverQuestRequest handover item request
* @param sessionID Session id
* @returns IItemEventRouterResponse
* /
public handoverQuest ( pmcData : IPmcData , handoverQuestRequest : IHandoverQuestRequestData , sessionID : string ) : IItemEventRouterResponse
{
const quest = this . questHelper . getQuestFromDb ( handoverQuestRequest . qid , pmcData ) ;
const handoverQuestTypes = [ "HandoverItem" , "WeaponAssembly" ] ;
const output = this . eventOutputHolder . getOutput ( sessionID ) ;
2023-03-13 10:34:04 +01:00
let isItemHandoverQuest = true ;
2023-03-03 16:23:46 +01:00
let handedInCount = 0 ;
// Decrement number of items handed in
let handoverRequirements : AvailableForConditions ;
for ( const condition of quest . conditions . AvailableForFinish )
{
if ( condition . _props . id === handoverQuestRequest . conditionId && handoverQuestTypes . includes ( condition . _parent ) )
{
handedInCount = Number . parseInt ( < string > condition . _props . value ) ;
2023-03-13 10:34:04 +01:00
isItemHandoverQuest = condition . _parent === handoverQuestTypes [ 0 ] ;
2023-03-03 16:23:46 +01:00
handoverRequirements = condition ;
const profileCounter = ( handoverQuestRequest . conditionId in pmcData . BackendCounters )
? pmcData . BackendCounters [ handoverQuestRequest . conditionId ] . value
: 0 ;
handedInCount -= profileCounter ;
if ( handedInCount <= 0 )
{
this . logger . error ( this . localisationService . getText ( "repeatable-quest_handover_failed_condition_already_satisfied" , { questId : handoverQuestRequest.qid , conditionId : handoverQuestRequest.conditionId , profileCounter : profileCounter , value : handedInCount } ) ) ;
return output ;
}
break ;
}
}
2023-03-13 10:34:04 +01:00
if ( isItemHandoverQuest && handedInCount === 0 )
2023-03-03 16:23:46 +01:00
{
2023-03-13 10:34:04 +01:00
return this . showRepeatableQuestInvalidConditionError ( handoverQuestRequest , output ) ;
2023-03-03 16:23:46 +01:00
}
let totalItemCountToRemove = 0 ;
for ( const itemHandover of handoverQuestRequest . items )
{
2023-03-13 10:34:04 +01:00
const matchingItemInProfile = pmcData . Inventory . items . find ( x = > x . _id === itemHandover . id ) ;
if ( ! handoverRequirements . _props . target . includes ( matchingItemInProfile . _tpl ) )
2023-03-03 16:23:46 +01:00
{
// Item handed in by player doesnt match what was requested
2023-03-13 10:34:04 +01:00
return this . showQuestItemHandoverMatchError ( handoverQuestRequest , matchingItemInProfile , handoverRequirements , output ) ;
2023-03-03 16:23:46 +01:00
}
// Remove the right quantity of given items
const itemCountToRemove = Math . min ( itemHandover . count , handedInCount - totalItemCountToRemove ) ;
totalItemCountToRemove += itemCountToRemove ;
if ( itemHandover . count - itemCountToRemove > 0 )
{
// Remove single item with no children
this . questHelper . changeItemStack ( pmcData , itemHandover . id , itemHandover . count - itemCountToRemove , sessionID , output ) ;
if ( totalItemCountToRemove === handedInCount )
{
break ;
}
}
else
{
// Remove item with children
const toRemove = this . itemHelper . findAndReturnChildrenByItems ( pmcData . Inventory . items , itemHandover . id ) ;
let index = pmcData . Inventory . items . length ;
// Important: don't tell the client to remove the attachments, it will handle it
output . profileChanges [ sessionID ] . items . del . push ( { "_id" : itemHandover . id } ) ;
// Important: loop backward when removing items from the array we're looping on
while ( index -- > 0 )
{
if ( toRemove . includes ( pmcData . Inventory . items [ index ] . _id ) )
{
pmcData . Inventory . items . splice ( index , 1 ) ;
}
}
}
}
this . updateProfileBackendCounterValue ( pmcData , handoverQuestRequest . conditionId , handoverQuestRequest . qid , totalItemCountToRemove ) ;
return output ;
}
2023-03-13 10:34:04 +01:00
/ * *
* Show warning to user and write to log that repeatable quest failed a condition check
* @param handoverQuestRequest Quest request
* @param output Response to send to user
* @returns IItemEventRouterResponse
* /
protected showRepeatableQuestInvalidConditionError ( handoverQuestRequest : IHandoverQuestRequestData , output : IItemEventRouterResponse ) : IItemEventRouterResponse
{
const errorMessage = this . localisationService . getText ( "repeatable-quest_handover_failed_condition_invalid" , { questId : handoverQuestRequest.qid , conditionId : handoverQuestRequest.conditionId } ) ;
this . logger . error ( errorMessage ) ;
return this . httpResponseUtil . appendErrorToOutput ( output , errorMessage ) ;
}
/ * *
* Show warning to user and write to log quest item handed over did not match what is required
* @param handoverQuestRequest Quest request
* @param itemHandedOver Non - matching item found
* @param handoverRequirements Quest handover requirements
* @param output Response to send to user
* @returns IItemEventRouterResponse
* /
protected showQuestItemHandoverMatchError ( handoverQuestRequest : IHandoverQuestRequestData , itemHandedOver : Item , handoverRequirements : AvailableForConditions , output : IItemEventRouterResponse ) : IItemEventRouterResponse
{
const errorMessage = this . localisationService . getText ( "quest-handover_wrong_item" , { questId : handoverQuestRequest.qid , handedInTpl : itemHandedOver._tpl , requiredTpl : handoverRequirements._props.target [ 0 ] } ) ;
this . logger . error ( errorMessage ) ;
return this . httpResponseUtil . appendErrorToOutput ( output , errorMessage ) ;
}
2023-03-03 16:23:46 +01:00
/ * *
* Increment a backend counter stored value by an amount ,
* Create counter if it does not exist
* @param pmcData Profile to find backend counter in
* @param conditionId backend counter id to update
* @param questId quest id counter is associated with
* @param counterValue value to increment the backend counter with
* /
protected updateProfileBackendCounterValue ( pmcData : IPmcData , conditionId : string , questId : string , counterValue : number ) : void
{
if ( pmcData . BackendCounters [ conditionId ] !== undefined )
{
pmcData . BackendCounters [ conditionId ] . value += counterValue ;
return ;
}
pmcData . BackendCounters [ conditionId ] = {
"id" : conditionId ,
"qid" : questId ,
"value" : counterValue } ;
}
}