mirror of
https://github.com/thepeacockproject/Peacock
synced 2025-04-22 08:39:50 +02: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 { log, LogLevel } from "../loggingInterop"
|
||||||
import { randomUUID } from "crypto"
|
import { randomUUID } from "crypto"
|
||||||
import {
|
import {
|
||||||
|
createObjectivesForTarget,
|
||||||
createTimeLimit,
|
createTimeLimit,
|
||||||
TargetCreator,
|
|
||||||
} from "../statemachines/contractCreation"
|
} from "../statemachines/contractCreation"
|
||||||
import { createSniperLoadouts, SniperCharacter } from "../menus/sniper"
|
import { createSniperLoadouts, SniperCharacter } from "../menus/sniper"
|
||||||
import { GetForPlay2Body } from "../types/gameSchemas"
|
import { GetForPlay2Body } from "../types/gameSchemas"
|
||||||
@ -277,13 +277,13 @@ contractRoutingRouter.post(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.body.creationData.Targets.forEach((target) => {
|
for (const target of req.body.creationData.Targets) {
|
||||||
if (!target.Selected) {
|
if (!target.Selected) {
|
||||||
return
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
objectives.push(...new TargetCreator(target).build())
|
objectives.push(...createObjectivesForTarget(target))
|
||||||
})
|
}
|
||||||
|
|
||||||
req.body.creationData.ContractConditionIds.forEach(
|
req.body.creationData.ContractConditionIds.forEach(
|
||||||
(contractConditionId) => {
|
(contractConditionId) => {
|
||||||
|
@ -833,85 +833,3 @@ export function complications(timeString: string) {
|
|||||||
targetsOnly,
|
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/>.
|
* 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 { randomUUID } from "crypto"
|
||||||
|
import assert from "assert"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The payload provided to the game for each target in the create menu route.
|
* The payload provided to the game for each target in the create menu route.
|
||||||
*/
|
*/
|
||||||
export interface IContractCreationPayload {
|
export type ContractCreationNpcTargetPayload = {
|
||||||
RepositoryId: string
|
RepositoryId: string
|
||||||
Selected: boolean
|
Selected: boolean
|
||||||
Weapon: {
|
Weapon: {
|
||||||
RepositoryId: string
|
RepositoryId: string
|
||||||
KillMethodBroad: string
|
KillMethodBroad: string
|
||||||
KillMethodStrict: string
|
KillMethodStrict: string
|
||||||
|
RequiredKillMethod: string
|
||||||
RequiredKillMethodType: number
|
RequiredKillMethodType: number
|
||||||
}
|
}
|
||||||
Outfit: {
|
Outfit: {
|
||||||
@ -39,43 +41,24 @@ export interface IContractCreationPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The target creator API.
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class TargetCreator {
|
export const createTargetKillObjective = (
|
||||||
// @ts-expect-error TODO: type this
|
params: ContractCreationNpcTargetPayload,
|
||||||
private _targetSm
|
): MissionManifestObjective => ({
|
||||||
// @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",
|
Type: "statemachine",
|
||||||
Id: randomUUID(),
|
Id: randomUUID(),
|
||||||
BriefingText: {
|
BriefingText: {
|
||||||
$loc: {
|
$loc: {
|
||||||
key: "UI_CONTRACT_GENERAL_OBJ_KILL",
|
key: "UI_CONTRACT_GENERAL_OBJ_KILL",
|
||||||
data: `$($repository ${this._params.RepositoryId}).Name`,
|
data: `$($repository ${params.RepositoryId}).Name`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HUDTemplate: {
|
HUDTemplate: {
|
||||||
display: {
|
display: {
|
||||||
$loc: {
|
$loc: {
|
||||||
key: "UI_CONTRACT_GENERAL_OBJ_KILL",
|
key: "UI_CONTRACT_GENERAL_OBJ_KILL",
|
||||||
data: `$($repository ${this._params.RepositoryId}).Name`,
|
data: `$($repository ${params.RepositoryId}).Name`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -83,39 +66,34 @@ export class TargetCreator {
|
|||||||
Definition: {
|
Definition: {
|
||||||
Scope: "Hit",
|
Scope: "Hit",
|
||||||
Context: {
|
Context: {
|
||||||
Targets: [this._params.RepositoryId],
|
Targets: [params.RepositoryId],
|
||||||
},
|
},
|
||||||
States: {
|
States: {
|
||||||
Start: {
|
Start: {
|
||||||
Kill: [
|
Kill: {
|
||||||
// this is the base state machine, we don't have fail states
|
|
||||||
{
|
|
||||||
Condition: {
|
Condition: {
|
||||||
$eq: [
|
$eq: ["$Value.RepositoryId", params.RepositoryId],
|
||||||
"$Value.RepositoryId",
|
|
||||||
this._params.RepositoryId,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
Transition: "Success",
|
Transition: "Success",
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
private _createOutfitSm(hmSuit: boolean): void {
|
/**
|
||||||
this._requireTargetConds()
|
* @internal
|
||||||
|
*/
|
||||||
this._outfitSm = {
|
export const createRequiredOutfitObjective = (
|
||||||
|
params: ContractCreationNpcTargetPayload,
|
||||||
|
): MissionManifestObjective => ({
|
||||||
Type: "statemachine",
|
Type: "statemachine",
|
||||||
Id: randomUUID(),
|
Id: randomUUID(),
|
||||||
Category: "secondary",
|
Category: "secondary",
|
||||||
Definition: {
|
Definition: {
|
||||||
Scope: "Hit",
|
Scope: "Hit",
|
||||||
Context: {
|
Context: {
|
||||||
Targets: [this._params.RepositoryId],
|
Targets: [params.RepositoryId],
|
||||||
},
|
},
|
||||||
States: {
|
States: {
|
||||||
Start: {
|
Start: {
|
||||||
@ -126,10 +104,10 @@ export class TargetCreator {
|
|||||||
{
|
{
|
||||||
$eq: [
|
$eq: [
|
||||||
"$Value.RepositoryId",
|
"$Value.RepositoryId",
|
||||||
this._params.RepositoryId,
|
params.RepositoryId,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
hmSuit
|
params.Outfit.IsHitmanSuit
|
||||||
? {
|
? {
|
||||||
$eq: [
|
$eq: [
|
||||||
"$Value.OutfitIsHitmanSuit",
|
"$Value.OutfitIsHitmanSuit",
|
||||||
@ -139,21 +117,129 @@ export class TargetCreator {
|
|||||||
: {
|
: {
|
||||||
$eq: [
|
$eq: [
|
||||||
"$Value.OutfitRepositoryId",
|
"$Value.OutfitRepositoryId",
|
||||||
this._params.Outfit
|
params.Outfit.RepositoryId,
|
||||||
.RepositoryId,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Transition: "Success",
|
Transition: "Success",
|
||||||
},
|
},
|
||||||
|
// state machines fall through to the next state if the condition is not met
|
||||||
{
|
{
|
||||||
Condition: {
|
Condition: {
|
||||||
$eq: [
|
$eq: ["$Value.RepositoryId", params.RepositoryId],
|
||||||
"$Value.RepositoryId",
|
},
|
||||||
this._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
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectives: MissionManifestObjective[] = [targetSm]
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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: outfitSm.Id,
|
||||||
|
// "Amazing!" - Athena Savalas
|
||||||
|
KillMethod: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
objectives.push(outfitSm)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.Weapon.RequiredKillMethodType !== 0) {
|
||||||
|
const weaponSm = createWeaponObjective(
|
||||||
|
params.Weapon,
|
||||||
|
params.RepositoryId,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (customIds?.kill) {
|
||||||
|
weaponSm.Id = customIds.kill
|
||||||
|
}
|
||||||
|
|
||||||
|
targetSm.TargetConditions ??= []
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
|
||||||
|
objectives.push(weaponSm)
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
Transition: "Failure",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -161,47 +247,10 @@ export class TargetCreator {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this._targetConds?.push({
|
|
||||||
Type: hmSuit ? "hitmansuit" : "disguise",
|
|
||||||
RepositoryId: this._params.Outfit.RepositoryId,
|
|
||||||
HardCondition: false,
|
|
||||||
ObjectiveId: this._outfitSm.Id,
|
|
||||||
// ioi moment
|
|
||||||
KillMethod: "",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the array of finalized state machines.
|
|
||||||
*
|
|
||||||
* @returns The state machines.
|
|
||||||
*/
|
|
||||||
public build(): MissionManifestObjective[] {
|
|
||||||
this._bootstrapEntrySm()
|
|
||||||
|
|
||||||
if (this._params.Outfit.Required) {
|
|
||||||
// not any disguise
|
|
||||||
this._createOutfitSm(this._params.Outfit.IsHitmanSuit)
|
|
||||||
}
|
|
||||||
|
|
||||||
const values = [this._targetSm]
|
|
||||||
|
|
||||||
if (this._outfitSm) {
|
|
||||||
values.push(this._outfitSm)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._targetConds && Array.isArray(this._targetConds)) {
|
|
||||||
this._targetSm.TargetConditions = this._targetConds
|
|
||||||
}
|
|
||||||
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The time limit creator API.
|
* Create a time limit objective.
|
||||||
*
|
|
||||||
* @param time The amount of time to use in seconds.
|
* @param time The amount of time to use in seconds.
|
||||||
* @param optional If the objective should be optional or not.
|
* @param optional If the objective should be optional or not.
|
||||||
* @returns The generated state machine.
|
* @returns The generated state machine.
|
||||||
@ -258,18 +307,14 @@ export function createTimeLimit(
|
|||||||
Scope: "session",
|
Scope: "session",
|
||||||
States: {
|
States: {
|
||||||
Start: {
|
Start: {
|
||||||
IntroCutEnd: [
|
IntroCutEnd: {
|
||||||
{
|
|
||||||
Transition: "TimerRunning",
|
Transition: "TimerRunning",
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
},
|
||||||
TimerRunning: {
|
TimerRunning: {
|
||||||
exit_gate: [
|
exit_gate: {
|
||||||
{
|
|
||||||
Transition: "Success",
|
Transition: "Success",
|
||||||
},
|
},
|
||||||
],
|
|
||||||
$timer: [
|
$timer: [
|
||||||
{
|
{
|
||||||
Condition: {
|
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 * as core from "express-serve-static-core"
|
||||||
|
|
||||||
import type { IContractCreationPayload } from "../statemachines/contractCreation"
|
import type { ContractCreationNpcTargetPayload } from "../statemachines/contractCreation"
|
||||||
import { Request } from "express"
|
import { Request } from "express"
|
||||||
import {
|
import {
|
||||||
ChallengeContext,
|
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.
|
* The body sent with the CreateFromParams request from the game during the final phase of contract creation.
|
||||||
*
|
*
|
||||||
* @see IContractCreationPayload
|
* @see ContractCreationNpcTargetPayload
|
||||||
*/
|
*/
|
||||||
export interface CreateFromParamsBody {
|
export interface CreateFromParamsBody {
|
||||||
creationData: {
|
creationData: {
|
||||||
@ -1152,7 +1152,7 @@ export interface CreateFromParamsBody {
|
|||||||
Description: string
|
Description: string
|
||||||
ContractId: string
|
ContractId: string
|
||||||
ContractPublicId: string
|
ContractPublicId: string
|
||||||
Targets: IContractCreationPayload[]
|
Targets: ContractCreationNpcTargetPayload[]
|
||||||
ContractConditionIds: string[]
|
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