feat(contracts): Implement contract creation kill conditions (#414)

This commit is contained in:
Reece Dunham 2024-04-13 11:51:14 -04:00 committed by GitHub
parent e3912f099a
commit e77d83d6f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 4364 additions and 234 deletions

View File

@ -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) => {

View File

@ -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,
],
}
}
}
}
*/

View File

@ -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}`,
)
}
}
}

View File

@ -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[]
}
}

File diff suppressed because it is too large Load Diff

View 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()
},
)
})

View 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
}
}
}
]