REC_HeistManager
Stracture
Installation, configuration, and usage for REC_HeistManager.
- Added
- Updated
The following configuration files and manifest are taken from the current source.
config/sh_config.lua
---@type REC_Utils.Shared.Enum
local shEnum = require "@REC_Utils.shared.sh_enum"
local placeEntityTypes = shEnum.placeentityTypes
---@class REC_HeistManager.Shared.Config
local config = {}
---[[
--- Debug Mode
---]]
---@type boolean
config.debugMode = true
config.dev = {}
---[[
---
---]]
config.dev.entityPlacer = {
default = {
---@type string
model = "sf_prop_sf_art_basketball_01a",
---@type string[]
defaultHelpText = {
"~INPUT_CELLPHONE_CANCEL~ でキャンセル ~INPUT_ATTACK~ で設置 ",
"~INPUT_JUMP~ でモデル変更 ~INPUT_INTERACTION_MENU~ で設置モード切替",
"~INPUT_SPRINT~ + ~INPUT_FRONTEND_RDOWN~ で出力",
},
},
}
---[[
--- Patrol guards
---
--- Every value here is the default a guard falls back to, so a heist only writes
--- what it wants different. A guard becomes a watcher by declaring patrol or vision,
--- guards that declare neither behave exactly as they did before.
---]]
config.guard = {
---[[
--- Walking the route
---]]
patrol = {
---@type "idle" | "loop" | "pingpong" | "random"
mode = "loop",
---@type "walk" | "run"
speed = "walk",
---@type { min: integer, max: integer, } how long it stands at a waypoint, ms
waitTime = { min = 3000, max = 8000, },
---@type number the waypoint counts as reached from here
arriveDistance = 1.5,
---@type integer ms before a guard that cannot reach a waypoint is sent to the next one
moveTimeout = 25000,
},
---[[
--- What a guard notices
---]]
vision = {
---@type number metres it can see
distance = 22.0,
---@type number full cone in degrees
fov = 100.0,
---@type number seconds inside the cone before the player is spotted
detectSeconds = 1.8,
---@type number seconds it takes to forget somebody it lost sight of
forgetSeconds = 6.0,
---@type number seen at once inside this radius, no matter where it looks
proximity = 2.5,
---@type number sprinting and shooting inside this radius is heard
hearing = 12.0,
---@type boolean ignore the player until the meter is full, which is what makes sneaking possible
blockEvents = true,
},
---[[
--- Day and night
---
--- Every multiplier is applied to a guard's vision as the game clock moves into
--- the night, and fades out again at dawn. A guard can bring its own set in
--- vision.timeOfDay, or opt out of it with vision.timeOfDay = false.
---]]
timeOfDay = {
---@type boolean
enabled = true,
---@type { from: integer, to: integer, } clock hours of the night, may wrap past midnight
night = { from = 20, to = 6, },
---@type integer game minutes the fade takes after dusk and before dawn
transitionMinutes = 60,
---[[
--- What the night does, 1.0 changes nothing
---]]
multipliers = {
---@type number it sees shorter
distance = 0.6,
---@type number and narrower
fov = 0.85,
---@type number
proximity = 1.0,
---@type number and takes longer to be sure
detectSeconds = 1.3,
---@type number but hears further
hearing = 1.6,
},
},
---[[
--- What happens once somebody is spotted
---]]
alert = {
---@type "investigate" | "combat"
escalate = "combat",
---@type number other guards of the same location that join in
shareRadius = 60.0,
---@type boolean a guard going down raises the alarm on its own
raiseOnDeath = true,
---@type integer quiet seconds before one level is dropped
holdSeconds = 200,
---@type integer how often the server checks for that quiet time, ms
tickInterval = 1000,
---[[
--- Losing the crew after an alarm. Once alerted drops to suspicious the
--- guards close in on a guess of where the crew went and look around from
--- a short distance, until the location calms down.
---]]
search = {
---@type boolean
enabled = true,
---@type number metres the guess is off by, so the crew is not walked straight into
scatter = 4.0,
---@type number metres short of the guess the guard stops at
stopDistance = 6.0,
---@type "walk" | "run"
speed = "walk",
---@type number degrees it sweeps to either side while it looks around
sweep = 60.0,
---@type integer ms between two glances
glanceInterval = 2500,
},
---[[
--- What knowing about an alarm does to a guard's senses.
--- Applied on top of the night, so a guard that was told something sees
--- further and needs less time to be sure of what it saw.
---]]
senses = {
---@type boolean
enabled = true,
---@type number
distance = 1.2,
---@type number
fov = 1.2,
---@type number
proximity = 1.3,
---@type number lower is quicker
detectSeconds = 0.65,
---@type number
hearing = 1.5,
},
---[[
--- How a guard carries itself once it knows something is wrong
---]]
stance = {
---@type boolean
enabled = true,
---@type "action" | "clipset" the game's own ready walk, or a movement clipset of your own
mode = "action",
---@type string|false movement clipset while it is not calm, read on the clipset mode
clipset = "move_ped_crouched",
---@type string|false the strafe that goes with it, false leaves the game's own
strafeClipset = "move_ped_crouched_strafing",
---@type boolean drop the posture once it is a fight, the game carries a fight on its own
uprightWhenAlerted = true,
---@type integer 0 to 3, how twitchy the game's own AI is about it
alertness = 3,
},
---[[
--- Walking into the body of another guard.
--- A quiet kill only stays quiet while nobody comes past.
---]]
body = {
---@type boolean
enabled = true,
---@type "suspicious" | "alerted" what a body does to the location
level = "alerted",
---@type number metres a body is noticed from, never further than the guard sees
distance = 14.0,
---@type boolean the body has to be in the cone, not only in range
requireFov = false,
---@type integer ms between two rounds of looking, per guard
interval = 1500,
---@type integer ms before the same body is worth reporting again
reportInterval = 30000,
},
---[[
--- How the alarm travels. The guard that noticed something tells the ones
--- around it, those tell the ones around them, and a guard that was told
--- nothing keeps walking its route.
---]]
spread = {
---@type boolean false puts every guard of the location on alert at once
enabled = true,
---@type integer ms before the guards that know pass it on one step further
stepInterval = 1500,
},
},
---[[
--- How fast the meter fills. Every multiplier is applied to the fill rate.
---]]
detection = {
---@type boolean only the crew is worth reacting to
membersOnly = true,
---[[
--- A player the game is not drawing is not there as far as the guards are
--- concerned. Admins watching, cutscenes and anything else that hides the
--- ped stop filling the meter and stop making noise.
---]]
---@type boolean
ignoreInvisible = true,
---@type number the meter counts as suspicious from here, 1.0 is spotted
suspicionAt = 0.45,
---@type number
sprintMultiplier = 1.6,
---@type number
crouchMultiplier = 0.45,
---@type number
aimMultiplier = 1.35,
---@type number
vehicleMultiplier = 1.4,
---@type integer ms between two reports of the same client
reportInterval = 1500,
},
---[[
--- Noise the crew makes.
---
--- A noise never says where the crew is, only where the sound came from, so
--- the guards that heard it go and look there instead of walking into it.
---]]
noise = {
---@type boolean
enabled = true,
---@type "suspicious" | "alerted" what a noise does to the location
level = "suspicious",
---@type integer ms between two noises of the same kind one client may report
reportInterval = 3000,
---[[
--- Noise that keeps coming. One shot is worth a look, shot after shot is
--- worth the alarm.
---]]
escalate = {
---@type boolean
enabled = true,
---@type integer noises heard inside the window before the level goes up
count = 3,
---@type integer ms the count is kept for
window = 20000,
---@type "suspicious" | "alerted"
level = "alerted",
},
gunshot = {
---@type boolean
enabled = true,
---@type number metres the shot carries
radius = 70.0,
---@type number applied to the radius while the weapon is suppressed
silencedMultiplier = 0.3,
---@type boolean the guards walk over to look, false only turns them that way
investigate = true,
},
horn = {
---@type boolean
enabled = true,
---@type number metres a horn carries
radius = 60.0,
---@type boolean
investigate = true,
},
vehicle = {
---@type boolean
enabled = true,
---@type number metres an engine carries
radius = 32.0,
---@type number m/s from which a vehicle is loud at all
minSpeed = 5.0,
---[[
--- An engine keeps making noise, so the guards only look that way.
--- Turning this on has them chase a car that is still driving.
---]]
---@type boolean
investigate = false,
},
---[[
--- A helicopter or a plane with its engine running, hovering or not.
--- Rotors carry far and straight down, so the radius has to cover the height too.
---]]
aircraft = {
---@type boolean
enabled = true,
---@type number metres the rotors carry
radius = 150.0,
---@type boolean the guards only look up, they cannot walk over to it
investigate = false,
},
---[[
--- Anything going off nearby, whoever set it off
---]]
explosion = {
---@type boolean
enabled = true,
---@type number metres the blast is heard
radius = 160.0,
---@type number metres around the player a blast is picked up at all
detectRadius = 45.0,
---@type "suspicious" | "alerted" a blast is never just a noise
level = "alerted",
---@type boolean
investigate = true,
},
---[[
--- Swimming. On the surface it splashes, under it almost nothing gets out.
---]]
swim = {
---@type boolean
enabled = true,
---@type number metres the splashing carries
radius = 20.0,
---@type boolean
investigate = true,
},
dive = {
---@type boolean
enabled = true,
---@type number metres it carries, the surface swallows the rest
radius = 7.0,
---@type boolean
investigate = false,
},
},
---[[
--- Fire. A guard walks around what is burning instead of into it.
---]]
fire = {
---@type boolean
enabled = true,
---@type number metres of fire around a waypoint that has it skipped
waypointRadius = 6.0,
---@type number metres of fire around the guard itself before it backs off
fleeRadius = 7.0,
---@type number metres it backs off to
fleeDistance = 14.0,
---@type integer ms between two looks, per guard
checkInterval = 1000,
},
---[[
--- What becomes of a guard once it is down.
--- A networked body keeps its network id and keeps changing owner for as long
--- as it lies there, so the server lets it go once nobody is close enough to
--- see the swap, and every client lays a local body in its place.
---]]
corpse = {
---@type boolean false keeps the networked body until the phase ends
replaceLocal = true,
---@type integer ms the networked body stays after the death, so the fall itself is seen
delay = 8000,
---@type number metres, no player may stand closer to the body when it is swapped
clearRadius = 40.0,
---@type integer ms between two rounds of looking at the bodies, per location
checkInterval = 2000,
},
---[[
--- Thread intervals. sense is what the meter is timed against.
---]]
tick = {
---@type integer ms between two rounds of guard tasking
drive = 500,
---@type integer ms between two vision checks
sense = 150,
},
---[[
--- The in-world route recorder (/heistroute)
--- Control ids, so they can be moved to keys that suit the server
---]]
route = {
controls = {
---@type integer INPUT_CONTEXT (E)
add = 38,
---@type integer INPUT_CELLPHONE_CANCEL (BACKSPACE)
undo = 177,
---@type integer INPUT_FRONTEND_RDOWN (ENTER)
finish = 191,
---@type integer INPUT_FRONTEND_PAUSE_ALTERNATE (ESC)
cancel = 200,
},
---@type "loop" | "pingpong" | "random" written into the snippet, change it there when it is not what you want
mode = "loop",
---@type "walk" | "run"
speed = "walk",
---@type integer decimals of the recorded coordinates
precision = 2,
},
---[[
--- What the crew is told when the location changes its mind
---]]
feedback = {
---@type boolean a notification to every member on each level change, also when a body is found or a noise is heard
notify = true,
---[[
--- Frontend sound played on this client when the level changes, false for silence
---]]
sounds = {
---@type { name: string, set: string }|false
suspicious = { name = "TIMER_STOP", set = "HUD_MINI_GAME_SOUNDSET", },
---@type { name: string, set: string }|false
alerted = { name = "Beep_Red", set = "DLC_HEIST_HACKING_SNAKE_SOUNDS", },
---@type { name: string, set: string }|false
calm = false,
},
},
---[[
--- The detection meter, a REC_Library gauge drawn while a guard is filling it
---]]
hud = {
---@type boolean false draws no meter at all, the guards still sense as usual
enabled = true,
---[[
--- A marker over the guard that is filling the meter
---]]
---@type boolean
marker = true,
---@type REC_Library.Shared.Enums.HelpTextPosition
position = "bottom-center",
---@type "bar" | "ring"
shape = "bar",
---@type "card" | "plain" plain is the bare bar and wording the native meter had
variant = "plain",
---@type string|false CSS width of the bar, false for the REC_Library default
width = "10vw",
---@type string|false font awesome icon, plain never draws it
icon = false,
---@type boolean draw the percentage next to the wording
showValue = false,
---[[
--- Fill colour for each alert level, suspicious also once the meter passes detection.suspicionAt
---]]
colors = {
---@type string
calm = "#ebebeb",
---@type string
suspicious = "#e19822",
---@type string
alerted = "#d63031",
},
},
}
---[[
--- Relationship groups
---
--- A heist that names nothing in registerClient gets these. member, whitelist and
--- guard are filled by the runtime, player is the group every other player already
--- stands in and cannot be renamed. relations read as [from][to], and a pair that
--- does not declare its reverse is applied both ways.
---]]
config.relationship = {
---@type table<string, string> appended to the heist key when it names no group of its own
groupSuffixes = {
member = "_PLAYER_MEMBER",
whitelist = "_PLAYER_POLICE",
guard = "_NPC_ENEMY",
},
---@type table<string, table<string, REC_HeistManager.Shared.Enum.RelationshipTypes>>
relations = {
guard = {
guard = "companion",
whitelist = "companion",
member = "hate",
player = "neutral",
},
},
---[[
--- A member that is down is not a target. Every client publishes its own
--- downed state under this key, the clients driving the guards read it.
---]]
---@type string
downedStateBagKey = "RECHM_isDowned",
}
---[[
--- Dropoff
---]]
config.dropoff = {
---[[
--- Played when a member hands the loot over.
--- A heist can override it per registration.
---]]
---@type string
cutsceneName = "hs3f_all_drp3",
}
---[[
--- Cross-heist cooldowns
---
--- Both are shared by every heistKey, unlike timing.heistCooldown which stays
--- local to the location that registered it. 0 disables a check, which is the
--- default, so a server that never touches this file behaves exactly as before.
---]]
config.cooldown = {
---[[
--- One lockout the whole server shares. Started when any heist of any kind
--- completes, so nobody may order anything until it lifts.
---]]
global = {
---@type integer seconds, 0 disables it
seconds = 0,
},
---[[
--- One lockout per citizenId, shared by every heistKey. Started for every
--- member of a crew, owner included, the moment its order is accepted.
---]]
player = {
---@type integer seconds, 0 disables it
seconds = 0,
---@type "memory" | "database" memory resets on a restart, database survives it
persistence = "memory",
},
}
return config
