mirror of
https://github.com/thepeacockproject/Peacock
synced 2025-03-27 11:12:44 +01:00
feat(contracts): Implement contract creation kill conditions (#414)
This commit is contained in:
parent
e3912f099a
commit
e77d83d6f2
components
tests
src
testData
@ -49,8 +49,8 @@ import {
|
||||
import { log, LogLevel } from "../loggingInterop"
|
||||
import { randomUUID } from "crypto"
|
||||
import {
|
||||
createObjectivesForTarget,
|
||||
createTimeLimit,
|
||||
TargetCreator,
|
||||
} from "../statemachines/contractCreation"
|
||||
import { createSniperLoadouts, SniperCharacter } from "../menus/sniper"
|
||||
import { GetForPlay2Body } from "../types/gameSchemas"
|
||||
@ -277,13 +277,13 @@ contractRoutingRouter.post(
|
||||
return
|
||||
}
|
||||
|
||||
req.body.creationData.Targets.forEach((target) => {
|
||||
for (const target of req.body.creationData.Targets) {
|
||||
if (!target.Selected) {
|
||||
return
|
||||
continue
|
||||
}
|
||||
|
||||
objectives.push(...new TargetCreator(target).build())
|
||||
})
|
||||
objectives.push(...createObjectivesForTarget(target))
|
||||
}
|
||||
|
||||
req.body.creationData.ContractConditionIds.forEach(
|
||||
(contractConditionId) => {
|
||||
|
@ -833,85 +833,3 @@ export function complications(timeString: string) {
|
||||
targetsOnly,
|
||||
]
|
||||
}
|
||||
|
||||
/*
|
||||
const PISTOL_ELIM = {
|
||||
$any: {
|
||||
"?": {
|
||||
$or: [
|
||||
{
|
||||
$eq: ["$.#", "pistol"],
|
||||
},
|
||||
{
|
||||
$eq: ["$.#", "close_combat_pistol_elimination"],
|
||||
},
|
||||
],
|
||||
},
|
||||
in: ["$Value.KillMethodBroad", "$Value.KillMethodStrict"],
|
||||
},
|
||||
}
|
||||
|
||||
export class StateMachineGenerationContext {
|
||||
public createKillMethodStateMachine(): void {
|
||||
this.weaponTargetStateMachine = {
|
||||
_comment: `Eliminate ${this.targetId} using weapon`,
|
||||
Type: "statemachine",
|
||||
Id: this.weaponTargetStateMachineId,
|
||||
Category: "secondary",
|
||||
Definition: {
|
||||
Scope: "Hit",
|
||||
Context: {
|
||||
Targets: [this.targetId],
|
||||
},
|
||||
States: {
|
||||
Start: {
|
||||
Kill: [
|
||||
{
|
||||
Condition: {
|
||||
$and: [
|
||||
{
|
||||
$eq: [
|
||||
"$Value.RepositoryId",
|
||||
this.targetId,
|
||||
],
|
||||
},
|
||||
{
|
||||
$eq: [
|
||||
"$Value.KillMethodStrict",
|
||||
this.genContextParams
|
||||
.KillMethodStrict,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
Transition: "Success",
|
||||
},
|
||||
{
|
||||
Condition: {
|
||||
$eq: ["$Value.RepositoryId", this.targetId],
|
||||
},
|
||||
Transition: "Failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
private kmConditions(killMethodBroad) {
|
||||
switch (killMethodBroad) {
|
||||
case "pistol":
|
||||
return PISTOL_ELIM
|
||||
default:
|
||||
// weapon
|
||||
return {
|
||||
$eq: [
|
||||
"$Value.KillMethodStrict",
|
||||
this.genContextParams.KillMethodStrict,
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -16,19 +16,21 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { MissionManifestObjective } from "../types/types"
|
||||
import type { MissionManifestObjective, RepositoryId } from "../types/types"
|
||||
import { randomUUID } from "crypto"
|
||||
import assert from "assert"
|
||||
|
||||
/**
|
||||
* The payload provided to the game for each target in the create menu route.
|
||||
*/
|
||||
export interface IContractCreationPayload {
|
||||
export type ContractCreationNpcTargetPayload = {
|
||||
RepositoryId: string
|
||||
Selected: boolean
|
||||
Weapon: {
|
||||
RepositoryId: string
|
||||
KillMethodBroad: string
|
||||
KillMethodStrict: string
|
||||
RequiredKillMethod: string
|
||||
RequiredKillMethodType: number
|
||||
}
|
||||
Outfit: {
|
||||
@ -39,169 +41,216 @@ export interface IContractCreationPayload {
|
||||
}
|
||||
|
||||
/**
|
||||
* The target creator API.
|
||||
* @internal
|
||||
*/
|
||||
export class TargetCreator {
|
||||
// @ts-expect-error TODO: type this
|
||||
private _targetSm
|
||||
// @ts-expect-error TODO: type this
|
||||
private _outfitSm
|
||||
private _targetConds: undefined | unknown[] = undefined
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param _params The contract creation payload.
|
||||
*/
|
||||
public constructor(private readonly _params: IContractCreationPayload) {}
|
||||
|
||||
private _requireTargetConds(): void {
|
||||
if (!this._targetConds || !Array.isArray(this._targetConds)) {
|
||||
this._targetConds = []
|
||||
}
|
||||
}
|
||||
|
||||
private _bootstrapEntrySm(): void {
|
||||
this._targetSm = {
|
||||
Type: "statemachine",
|
||||
Id: randomUUID(),
|
||||
BriefingText: {
|
||||
$loc: {
|
||||
key: "UI_CONTRACT_GENERAL_OBJ_KILL",
|
||||
data: `$($repository ${this._params.RepositoryId}).Name`,
|
||||
},
|
||||
export const createTargetKillObjective = (
|
||||
params: ContractCreationNpcTargetPayload,
|
||||
): MissionManifestObjective => ({
|
||||
Type: "statemachine",
|
||||
Id: randomUUID(),
|
||||
BriefingText: {
|
||||
$loc: {
|
||||
key: "UI_CONTRACT_GENERAL_OBJ_KILL",
|
||||
data: `$($repository ${params.RepositoryId}).Name`,
|
||||
},
|
||||
},
|
||||
HUDTemplate: {
|
||||
display: {
|
||||
$loc: {
|
||||
key: "UI_CONTRACT_GENERAL_OBJ_KILL",
|
||||
data: `$($repository ${params.RepositoryId}).Name`,
|
||||
},
|
||||
HUDTemplate: {
|
||||
display: {
|
||||
$loc: {
|
||||
key: "UI_CONTRACT_GENERAL_OBJ_KILL",
|
||||
data: `$($repository ${this._params.RepositoryId}).Name`,
|
||||
},
|
||||
},
|
||||
Category: "primary",
|
||||
Definition: {
|
||||
Scope: "Hit",
|
||||
Context: {
|
||||
Targets: [params.RepositoryId],
|
||||
},
|
||||
States: {
|
||||
Start: {
|
||||
Kill: {
|
||||
Condition: {
|
||||
$eq: ["$Value.RepositoryId", params.RepositoryId],
|
||||
},
|
||||
Transition: "Success",
|
||||
},
|
||||
},
|
||||
Category: "primary",
|
||||
Definition: {
|
||||
Scope: "Hit",
|
||||
Context: {
|
||||
Targets: [this._params.RepositoryId],
|
||||
},
|
||||
States: {
|
||||
Start: {
|
||||
Kill: [
|
||||
// this is the base state machine, we don't have fail states
|
||||
{
|
||||
Condition: {
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const createRequiredOutfitObjective = (
|
||||
params: ContractCreationNpcTargetPayload,
|
||||
): MissionManifestObjective => ({
|
||||
Type: "statemachine",
|
||||
Id: randomUUID(),
|
||||
Category: "secondary",
|
||||
Definition: {
|
||||
Scope: "Hit",
|
||||
Context: {
|
||||
Targets: [params.RepositoryId],
|
||||
},
|
||||
States: {
|
||||
Start: {
|
||||
Kill: [
|
||||
{
|
||||
Condition: {
|
||||
$and: [
|
||||
{
|
||||
$eq: [
|
||||
"$Value.RepositoryId",
|
||||
this._params.RepositoryId,
|
||||
params.RepositoryId,
|
||||
],
|
||||
},
|
||||
Transition: "Success",
|
||||
},
|
||||
],
|
||||
params.Outfit.IsHitmanSuit
|
||||
? {
|
||||
$eq: [
|
||||
"$Value.OutfitIsHitmanSuit",
|
||||
true,
|
||||
],
|
||||
}
|
||||
: {
|
||||
$eq: [
|
||||
"$Value.OutfitRepositoryId",
|
||||
params.Outfit.RepositoryId,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
Transition: "Success",
|
||||
},
|
||||
},
|
||||
// state machines fall through to the next state if the condition is not met
|
||||
{
|
||||
Condition: {
|
||||
$eq: ["$Value.RepositoryId", params.RepositoryId],
|
||||
},
|
||||
Transition: "Failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
TargetConditions: [],
|
||||
})
|
||||
|
||||
/**
|
||||
* Create the target, weapon, and kill conditions for a contracts target.
|
||||
* @param params The parameters from the request.
|
||||
* @param customIds Custom objective IDs for testing purposes.
|
||||
*/
|
||||
export function createObjectivesForTarget(
|
||||
params: ContractCreationNpcTargetPayload,
|
||||
customIds?: { base: string; kill: string; outfit: string },
|
||||
): MissionManifestObjective[] {
|
||||
const targetSm = createTargetKillObjective(params)
|
||||
|
||||
if (customIds?.base) {
|
||||
targetSm.Id = customIds.base
|
||||
}
|
||||
|
||||
private _createOutfitSm(hmSuit: boolean): void {
|
||||
this._requireTargetConds()
|
||||
const objectives: MissionManifestObjective[] = [targetSm]
|
||||
|
||||
this._outfitSm = {
|
||||
Type: "statemachine",
|
||||
Id: randomUUID(),
|
||||
Category: "secondary",
|
||||
Definition: {
|
||||
Scope: "Hit",
|
||||
Context: {
|
||||
Targets: [this._params.RepositoryId],
|
||||
},
|
||||
States: {
|
||||
Start: {
|
||||
Kill: [
|
||||
{
|
||||
Condition: {
|
||||
$and: [
|
||||
{
|
||||
$eq: [
|
||||
"$Value.RepositoryId",
|
||||
this._params.RepositoryId,
|
||||
],
|
||||
},
|
||||
hmSuit
|
||||
? {
|
||||
$eq: [
|
||||
"$Value.OutfitIsHitmanSuit",
|
||||
true,
|
||||
],
|
||||
}
|
||||
: {
|
||||
$eq: [
|
||||
"$Value.OutfitRepositoryId",
|
||||
this._params.Outfit
|
||||
.RepositoryId,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
Transition: "Success",
|
||||
},
|
||||
{
|
||||
Condition: {
|
||||
$eq: [
|
||||
"$Value.RepositoryId",
|
||||
this._params.RepositoryId,
|
||||
],
|
||||
},
|
||||
Transition: "Failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
// if the required field is true, that means the user requested something OTHER than any disguise
|
||||
if (params.Outfit.Required) {
|
||||
const outfitSm = createRequiredOutfitObjective(params)
|
||||
|
||||
if (customIds?.outfit) {
|
||||
outfitSm.Id = customIds.outfit
|
||||
}
|
||||
|
||||
this._targetConds?.push({
|
||||
Type: hmSuit ? "hitmansuit" : "disguise",
|
||||
RepositoryId: this._params.Outfit.RepositoryId,
|
||||
targetSm.TargetConditions ??= []
|
||||
|
||||
targetSm.TargetConditions.push({
|
||||
Type: params.Outfit.IsHitmanSuit ? "hitmansuit" : "disguise",
|
||||
RepositoryId: params.Outfit.RepositoryId,
|
||||
// for contract creation it's always optional, only escalations set hard fail conditions
|
||||
HardCondition: false,
|
||||
ObjectiveId: this._outfitSm.Id,
|
||||
// ioi moment
|
||||
ObjectiveId: outfitSm.Id,
|
||||
// "Amazing!" - Athena Savalas
|
||||
KillMethod: "",
|
||||
})
|
||||
|
||||
objectives.push(outfitSm)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of finalized state machines.
|
||||
*
|
||||
* @returns The state machines.
|
||||
*/
|
||||
public build(): MissionManifestObjective[] {
|
||||
this._bootstrapEntrySm()
|
||||
if (params.Weapon.RequiredKillMethodType !== 0) {
|
||||
const weaponSm = createWeaponObjective(
|
||||
params.Weapon,
|
||||
params.RepositoryId,
|
||||
)
|
||||
|
||||
if (this._params.Outfit.Required) {
|
||||
// not any disguise
|
||||
this._createOutfitSm(this._params.Outfit.IsHitmanSuit)
|
||||
if (customIds?.kill) {
|
||||
weaponSm.Id = customIds.kill
|
||||
}
|
||||
|
||||
const values = [this._targetSm]
|
||||
targetSm.TargetConditions ??= []
|
||||
|
||||
if (this._outfitSm) {
|
||||
values.push(this._outfitSm)
|
||||
}
|
||||
targetSm.TargetConditions.push({
|
||||
Type: "killmethod",
|
||||
RepositoryId: params.Weapon.RepositoryId,
|
||||
// for contract creation it's always optional, only escalations set hard fail conditions
|
||||
HardCondition: false,
|
||||
ObjectiveId: weaponSm.Id,
|
||||
KillMethod: params.Weapon.RequiredKillMethod,
|
||||
})
|
||||
|
||||
if (this._targetConds && Array.isArray(this._targetConds)) {
|
||||
this._targetSm.TargetConditions = this._targetConds
|
||||
}
|
||||
objectives.push(weaponSm)
|
||||
}
|
||||
|
||||
return values
|
||||
return objectives
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an objective for killing a target with a specific weapon.
|
||||
* @param weapon The weapon details from the request.
|
||||
* @param npcId The target NPC's repository ID.
|
||||
*/
|
||||
function createWeaponObjective(
|
||||
weapon: Weapon,
|
||||
npcId: RepositoryId,
|
||||
): MissionManifestObjective {
|
||||
return {
|
||||
Type: "statemachine",
|
||||
Id: randomUUID(),
|
||||
Category: "secondary",
|
||||
Definition: {
|
||||
Scope: "Hit",
|
||||
Context: {
|
||||
Targets: [npcId],
|
||||
},
|
||||
States: {
|
||||
Start: {
|
||||
Kill: [
|
||||
{
|
||||
Condition: {
|
||||
$and: [
|
||||
{
|
||||
$eq: ["$Value.RepositoryId", npcId],
|
||||
},
|
||||
genStateMachineKillSuccessCondition(weapon),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
Condition: {
|
||||
$eq: ["$Value.RepositoryId", npcId],
|
||||
},
|
||||
Transition: "Failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The time limit creator API.
|
||||
*
|
||||
* Create a time limit objective.
|
||||
* @param time The amount of time to use in seconds.
|
||||
* @param optional If the objective should be optional or not.
|
||||
* @returns The generated state machine.
|
||||
@ -258,18 +307,14 @@ export function createTimeLimit(
|
||||
Scope: "session",
|
||||
States: {
|
||||
Start: {
|
||||
IntroCutEnd: [
|
||||
{
|
||||
Transition: "TimerRunning",
|
||||
},
|
||||
],
|
||||
IntroCutEnd: {
|
||||
Transition: "TimerRunning",
|
||||
},
|
||||
},
|
||||
TimerRunning: {
|
||||
exit_gate: [
|
||||
{
|
||||
Transition: "Success",
|
||||
},
|
||||
],
|
||||
exit_gate: {
|
||||
Transition: "Success",
|
||||
},
|
||||
$timer: [
|
||||
{
|
||||
Condition: {
|
||||
@ -283,3 +328,150 @@ export function createTimeLimit(
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These are all the possible ways to get a kill in contracts mode.
|
||||
*/
|
||||
export const enum ContractKillMethod {
|
||||
Any,
|
||||
AnyMelee,
|
||||
ObjectMelee,
|
||||
AnyThrown,
|
||||
ObjectThrown,
|
||||
Pistol,
|
||||
PistolElimination,
|
||||
Smg,
|
||||
AssaultRifle,
|
||||
Shotgun,
|
||||
SniperRifle,
|
||||
AnyPoison,
|
||||
ConsumedPoison,
|
||||
InjectedPoison,
|
||||
AnyAccident,
|
||||
ExplosiveDevice,
|
||||
FiberWire,
|
||||
UnarmedNeckSnap,
|
||||
}
|
||||
|
||||
type Weapon = ContractCreationNpcTargetPayload["Weapon"]
|
||||
type KillSuccessStateCondition = unknown
|
||||
|
||||
export function genStateMachineKillSuccessCondition(
|
||||
weapon: Weapon,
|
||||
): KillSuccessStateCondition {
|
||||
const km = weaponToKillMethod(weapon)
|
||||
|
||||
if (km === ContractKillMethod.PistolElimination) {
|
||||
return {
|
||||
$any: {
|
||||
"?": {
|
||||
$or: [
|
||||
{
|
||||
$eq: ["$.#", "pistol"],
|
||||
},
|
||||
{
|
||||
$eq: ["$.#", "close_combat_pistol_elimination"],
|
||||
},
|
||||
],
|
||||
},
|
||||
in: ["$Value.KillMethodBroad", "$Value.KillMethodStrict"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (km === ContractKillMethod.Pistol) {
|
||||
return {
|
||||
$any: {
|
||||
"?": {
|
||||
$or: [
|
||||
{
|
||||
$eq: ["$.#", "pistol"],
|
||||
},
|
||||
{
|
||||
$eq: ["$.#", "close_combat_pistol_elimination"],
|
||||
},
|
||||
],
|
||||
},
|
||||
in: ["$Value.KillMethodBroad", "$Value.KillMethodStrict"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
$any: {
|
||||
"?": {
|
||||
$eq: ["$.#", weapon.RequiredKillMethod],
|
||||
},
|
||||
in: ["$Value.KillMethodBroad", "$Value.KillMethodStrict"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the equivalent kill method from a weapon object.
|
||||
* @param weapon The weapon's details.
|
||||
*/
|
||||
export function weaponToKillMethod(weapon: Weapon): ContractKillMethod {
|
||||
const type = weapon.RequiredKillMethodType
|
||||
|
||||
if (type === 0) {
|
||||
return ContractKillMethod.Any
|
||||
}
|
||||
|
||||
switch (weapon.KillMethodBroad) {
|
||||
case "pistol":
|
||||
return ContractKillMethod.Pistol
|
||||
case "smg":
|
||||
return ContractKillMethod.Smg
|
||||
case "sniperrifle":
|
||||
return ContractKillMethod.SniperRifle
|
||||
case "assaultrifle":
|
||||
return ContractKillMethod.AssaultRifle
|
||||
case "shotgun":
|
||||
return ContractKillMethod.Shotgun
|
||||
case "close_combat_pistol_elimination":
|
||||
return ContractKillMethod.PistolElimination
|
||||
case "fiberwire":
|
||||
return ContractKillMethod.FiberWire
|
||||
case "throw": {
|
||||
return type === 1
|
||||
? ContractKillMethod.AnyThrown
|
||||
: ContractKillMethod.ObjectThrown
|
||||
}
|
||||
case "melee_lethal": {
|
||||
return type === 1
|
||||
? ContractKillMethod.AnyMelee
|
||||
: ContractKillMethod.ObjectMelee
|
||||
}
|
||||
case "poison": {
|
||||
if (weapon.KillMethodStrict === "consumed_poison" && type === 2) {
|
||||
return ContractKillMethod.ConsumedPoison
|
||||
}
|
||||
|
||||
if (
|
||||
weapon.KillMethodStrict === "injected_poison" &&
|
||||
weapon.RequiredKillMethodType === 2
|
||||
) {
|
||||
return ContractKillMethod.InjectedPoison
|
||||
}
|
||||
|
||||
assert(
|
||||
type === 1,
|
||||
`Unhandled poison: ${weapon.KillMethodStrict} ${type}`,
|
||||
)
|
||||
|
||||
return ContractKillMethod.AnyPoison
|
||||
}
|
||||
case "unarmed":
|
||||
return ContractKillMethod.UnarmedNeckSnap
|
||||
case "accident":
|
||||
return ContractKillMethod.AnyAccident
|
||||
case "explosive":
|
||||
return ContractKillMethod.ExplosiveDevice
|
||||
default: {
|
||||
assert.fail(
|
||||
`Unhandled condition: ${weapon.KillMethodBroad} ${type}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
import type * as core from "express-serve-static-core"
|
||||
|
||||
import type { IContractCreationPayload } from "../statemachines/contractCreation"
|
||||
import type { ContractCreationNpcTargetPayload } from "../statemachines/contractCreation"
|
||||
import { Request } from "express"
|
||||
import {
|
||||
ChallengeContext,
|
||||
@ -1144,7 +1144,7 @@ export interface GameLocationsData {
|
||||
/**
|
||||
* The body sent with the CreateFromParams request from the game during the final phase of contract creation.
|
||||
*
|
||||
* @see IContractCreationPayload
|
||||
* @see ContractCreationNpcTargetPayload
|
||||
*/
|
||||
export interface CreateFromParamsBody {
|
||||
creationData: {
|
||||
@ -1152,7 +1152,7 @@ export interface CreateFromParamsBody {
|
||||
Description: string
|
||||
ContractId: string
|
||||
ContractPublicId: string
|
||||
Targets: IContractCreationPayload[]
|
||||
Targets: ContractCreationNpcTargetPayload[]
|
||||
ContractConditionIds: string[]
|
||||
}
|
||||
}
|
||||
|
3544
tests/src/__snapshots__/contractCreation.test.ts.snap
Normal file
3544
tests/src/__snapshots__/contractCreation.test.ts.snap
Normal file
File diff suppressed because it is too large
Load Diff
113
tests/src/contractCreation.test.ts
Normal file
113
tests/src/contractCreation.test.ts
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* The Peacock Project - a HITMAN server replacement.
|
||||
* Copyright (C) 2021-2024 The Peacock Project Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
import * as cc from "../../components/statemachines/contractCreation"
|
||||
import { ContractCreationNpcTargetPayload } from "../../components/statemachines/contractCreation"
|
||||
import { nilUuid } from "../../components/utils"
|
||||
// @ts-expect-error No JSON imports allowed - TS language server will choke on trying to resolve JSON
|
||||
// imports from the config manager, some of which are megabytes large, a nightmare for performance.
|
||||
import killCases from "../testData/contractCreationCases.json"
|
||||
|
||||
/** dummy data for basic operations that require no special cases, or as a base to build data with */
|
||||
const partialObjData: ContractCreationNpcTargetPayload = {
|
||||
RepositoryId: "b69187a0-0860-403f-9616-981127fef877",
|
||||
Selected: true,
|
||||
Weapon: {
|
||||
RepositoryId: nilUuid,
|
||||
RequiredKillMethod: "",
|
||||
RequiredKillMethodType: 0,
|
||||
KillMethodStrict: "",
|
||||
KillMethodBroad: "",
|
||||
},
|
||||
Outfit: {
|
||||
Required: false,
|
||||
RepositoryId: "48a7f8a4-37ae-4522-a8b0-db746d970a7f",
|
||||
IsHitmanSuit: true,
|
||||
},
|
||||
}
|
||||
|
||||
describe("state machines", () => {
|
||||
test("can use createTargetKillObjective", () => {
|
||||
const objective = cc.createTargetKillObjective(partialObjData)
|
||||
|
||||
objective.Id = nilUuid
|
||||
|
||||
expect(objective).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe("outfits", () => {
|
||||
test.each([
|
||||
["e4d6dcb4-6c2c-44f5-98a6-b41ec1d92dee", +true, +true],
|
||||
["a2deb86a-f38c-47d3-af98-848ea22500d6", +true, +false],
|
||||
["b47eff05-6e65-4b27-860d-b114077e084c", +false, +true],
|
||||
["f1f7850d-9292-4cfc-b66e-e5661ef4a142", +false, +false],
|
||||
])(
|
||||
"can create an outfit state machine - id: %s, is suit: %i, is required: %i",
|
||||
(id, isSuit, isRequired) => {
|
||||
const objective = cc.createRequiredOutfitObjective({
|
||||
...partialObjData,
|
||||
Outfit: {
|
||||
RepositoryId: id,
|
||||
IsHitmanSuit: Boolean(isSuit),
|
||||
Required: Boolean(isRequired),
|
||||
},
|
||||
})
|
||||
|
||||
objective.Id = nilUuid
|
||||
expect(objective).toMatchSnapshot()
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("real world input data with createObjectivesForTarget", () => {
|
||||
test.each(killCases)(
|
||||
"kill conditions: $name",
|
||||
// @ts-expect-error TS doesn't wanna narrow this
|
||||
(options: {
|
||||
name: string
|
||||
params: ContractCreationNpcTargetPayload
|
||||
}) => {
|
||||
const objectives = cc.createObjectivesForTarget(options.params, {
|
||||
// because we are comparing against snapshots, we intentionally use custom IDs for each of the generated objectives
|
||||
base: "094a2f76-e0b4-4ab6-bf54-b5bdfdd8a0c6",
|
||||
kill: "3df3bec8-17e6-4007-b1ac-5bd14485209a",
|
||||
outfit: "59bfbf74-c38f-4cd4-bcd7-d0c44cd0940c",
|
||||
})
|
||||
|
||||
expect(objectives).toMatchSnapshot()
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
describe("time limits", () => {
|
||||
test.each([
|
||||
[91, +true],
|
||||
[91, +false],
|
||||
[346, +false],
|
||||
[346, +true],
|
||||
])(
|
||||
"can create a time limit - seconds: %i, optional: %i",
|
||||
(time, optional) => {
|
||||
expect(
|
||||
cc.createTimeLimit(time, Boolean(optional)),
|
||||
).toMatchSnapshot()
|
||||
},
|
||||
)
|
||||
})
|
363
tests/testData/contractCreationCases.json
Normal file
363
tests/testData/contractCreationCases.json
Normal file
@ -0,0 +1,363 @@
|
||||
[
|
||||
{
|
||||
"name": "pistol explicit",
|
||||
"params": {
|
||||
"RepositoryId": "6d5bf7f7-56bb-4329-acbd-2c6f341f0d79",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "73875794-5a86-410e-84a4-1b5b2f7e5a54",
|
||||
"KillMethodBroad": "pistol",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 1.0,
|
||||
"RequiredKillMethod": "pistol"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "054f443b-824f-4913-8b29-64dfcd82b089",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pistol elimination specific",
|
||||
"params": {
|
||||
"RepositoryId": "6d5bf7f7-56bb-4329-acbd-2c6f341f0d79",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "73875794-5a86-410e-84a4-1b5b2f7e5a54",
|
||||
"KillMethodBroad": "close_combat_pistol_elimination",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 1.0,
|
||||
"RequiredKillMethod": "close_combat_pistol_elimination"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "054f443b-824f-4913-8b29-64dfcd82b089",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fiber wire explicit",
|
||||
"params": {
|
||||
"RepositoryId": "6d5bf7f7-56bb-4329-acbd-2c6f341f0d79",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "1a11a060-358c-4054-98ec-d3491af1d7c6",
|
||||
"KillMethodBroad": "fiberwire",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 1.0,
|
||||
"RequiredKillMethod": "fiberwire"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "054f443b-824f-4913-8b29-64dfcd82b089",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "screwdriver thrown weapon",
|
||||
"params": {
|
||||
"RepositoryId": "2fa9698f-eb9b-4991-8aaf-3bddcf23e247",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "12cb6b51-a6dd-4bf5-9653-0ab727820cac",
|
||||
"KillMethodBroad": "throw",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 3.0,
|
||||
"RequiredKillMethod": "throw"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "2c649c52-f85a-4b29-838a-31c2525cc862",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "any thrown weapon",
|
||||
"params": {
|
||||
"RepositoryId": "2fa9698f-eb9b-4991-8aaf-3bddcf23e247",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "12cb6b51-a6dd-4bf5-9653-0ab727820cac",
|
||||
"KillMethodBroad": "throw",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 1.0,
|
||||
"RequiredKillMethod": "throw"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "2c649c52-f85a-4b29-838a-31c2525cc862",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "screwdriver melee",
|
||||
"params": {
|
||||
"RepositoryId": "2fa9698f-eb9b-4991-8aaf-3bddcf23e247",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "12cb6b51-a6dd-4bf5-9653-0ab727820cac",
|
||||
"KillMethodBroad": "melee_lethal",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 3.0,
|
||||
"RequiredKillMethod": "melee_lethal"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "2c649c52-f85a-4b29-838a-31c2525cc862",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "any melee weapon",
|
||||
"params": {
|
||||
"RepositoryId": "2fa9698f-eb9b-4991-8aaf-3bddcf23e247",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "12cb6b51-a6dd-4bf5-9653-0ab727820cac",
|
||||
"KillMethodBroad": "melee_lethal",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 1.0,
|
||||
"RequiredKillMethod": "melee_lethal"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "2c649c52-f85a-4b29-838a-31c2525cc862",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "consumed poison explicit",
|
||||
"params": {
|
||||
"RepositoryId": "f25ced88-e077-47f1-a84f-700e21509494",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "7d668011-77f9-4cae-97f1-e3eda5e0c8b2",
|
||||
"KillMethodBroad": "poison",
|
||||
"KillMethodStrict": "consumed_poison",
|
||||
"RequiredKillMethodType": 2.0,
|
||||
"RequiredKillMethod": "consumed_poison"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "eb12cc2b-6dcf-4831-ba4e-ef8e53180e2f",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "any poison",
|
||||
"params": {
|
||||
"RepositoryId": "f25ced88-e077-47f1-a84f-700e21509494",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "7d668011-77f9-4cae-97f1-e3eda5e0c8b2",
|
||||
"KillMethodBroad": "poison",
|
||||
"KillMethodStrict": "consumed_poison",
|
||||
"RequiredKillMethodType": 1.0,
|
||||
"RequiredKillMethod": "poison"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "eb12cc2b-6dcf-4831-ba4e-ef8e53180e2f",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "injected poison explicit",
|
||||
"params": {
|
||||
"RepositoryId": "f25ced88-e077-47f1-a84f-700e21509494",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "cdc20ebf-cd83-4707-8732-913c0f970cb5",
|
||||
"KillMethodBroad": "poison",
|
||||
"KillMethodStrict": "injected_poison",
|
||||
"RequiredKillMethodType": 2.0,
|
||||
"RequiredKillMethod": "injected_poison"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "eb12cc2b-6dcf-4831-ba4e-ef8e53180e2f",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "falling object accident",
|
||||
"params": {
|
||||
"RepositoryId": "5b54d9fb-fa85-4302-a8d5-c5c5e97344c4",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "00000000-0000-0000-0000-000000000000",
|
||||
"KillMethodBroad": "accident",
|
||||
"KillMethodStrict": "accident_suspended_object",
|
||||
"RequiredKillMethodType": 2.0,
|
||||
"RequiredKillMethod": "accident_suspended_object"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "aab7f28d-84d9-47d1-be52-d142f5970086",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "explosive device explicit",
|
||||
"params": {
|
||||
"RepositoryId": "5b54d9fb-fa85-4302-a8d5-c5c5e97344c4",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "e68927af-746d-41fa-83e2-402da0163262",
|
||||
"KillMethodBroad": "explosive",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 1.0,
|
||||
"RequiredKillMethod": "explosive"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "aab7f28d-84d9-47d1-be52-d142f5970086",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accident explosion",
|
||||
"params": {
|
||||
"RepositoryId": "90e997f4-67c6-486d-afb9-63c4424b1fd1",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "a8a0c154-c36f-413e-8f29-b83a1b7a22f0",
|
||||
"KillMethodBroad": "accident",
|
||||
"KillMethodStrict": "accident_explosion",
|
||||
"RequiredKillMethodType": 2.0,
|
||||
"RequiredKillMethod": "accident_explosion"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "59c4f7db-f065-4fac-bc6c-5c7ac3758eed",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fire accident",
|
||||
"params": {
|
||||
"RepositoryId": "5b54d9fb-fa85-4302-a8d5-c5c5e97344c4",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "00000000-0000-0000-0000-000000000000",
|
||||
"KillMethodBroad": "accident",
|
||||
"KillMethodStrict": "accident_burn",
|
||||
"RequiredKillMethodType": 2.0,
|
||||
"RequiredKillMethod": "accident_burn"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "59c4f7db-f065-4fac-bc6c-5c7ac3758eed",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fall (push) accident",
|
||||
"params": {
|
||||
"RepositoryId": "5b54d9fb-fa85-4302-a8d5-c5c5e97344c4",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "00000000-0000-0000-0000-000000000000",
|
||||
"KillMethodBroad": "accident",
|
||||
"KillMethodStrict": "accident_push",
|
||||
"RequiredKillMethodType": 2.0,
|
||||
"RequiredKillMethod": "accident_push"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "59c4f7db-f065-4fac-bc6c-5c7ac3758eed",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "drowning accident",
|
||||
"params": {
|
||||
"RepositoryId": "963c2774-cb9a-4b0c-ab69-210b2405383b",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "00000000-0000-0000-0000-000000000000",
|
||||
"KillMethodBroad": "accident",
|
||||
"KillMethodStrict": "accident_drown",
|
||||
"RequiredKillMethodType": 2.0,
|
||||
"RequiredKillMethod": "accident_drown"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "aab7f28d-84d9-47d1-be52-d142f5970086",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "any method",
|
||||
"params": {
|
||||
"RepositoryId": "390807a3-7f83-4bf9-a454-7e55e0559090",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "73875794-5a86-410e-84a4-1b5b2f7e5a54",
|
||||
"KillMethodBroad": "pistol",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 0.0,
|
||||
"RequiredKillMethod": ""
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "054f443b-824f-4913-8b29-64dfcd82b089",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "neck snap (unarmed)",
|
||||
"params": {
|
||||
"RepositoryId": "390807a3-7f83-4bf9-a454-7e55e0559090",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "00000000-0000-0000-0000-000000000000",
|
||||
"KillMethodBroad": "unarmed",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 3,
|
||||
"RequiredKillMethod": "unarmed"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "054f443b-824f-4913-8b29-64dfcd82b089",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "shotgun",
|
||||
"params": {
|
||||
"RepositoryId": "390807a3-7f83-4bf9-a454-7e55e0559090",
|
||||
"Selected": true,
|
||||
"Weapon": {
|
||||
"RepositoryId": "545ff36e-b43c-4a35-9ab3-680b23b9e354",
|
||||
"KillMethodBroad": "shotgun",
|
||||
"KillMethodStrict": "",
|
||||
"RequiredKillMethodType": 1.0,
|
||||
"RequiredKillMethod": "shotgun"
|
||||
},
|
||||
"Outfit": {
|
||||
"RepositoryId": "054f443b-824f-4913-8b29-64dfcd82b089",
|
||||
"Required": true,
|
||||
"IsHitmanSuit": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user