417 lines
11 KiB
Plaintext
417 lines
11 KiB
Plaintext
-- MCM
|
|
function load_defaults()
|
|
local t = {}
|
|
local op = blood_pool_mcm.op
|
|
for i, v in ipairs(op.gr) do
|
|
if v.def ~= nil then
|
|
t[v.id] = v.def
|
|
end
|
|
end
|
|
return t
|
|
end
|
|
|
|
settings = load_defaults()
|
|
|
|
function load_settings()
|
|
settings = load_defaults()
|
|
if ui_mcm then
|
|
for k, v in pairs(settings) do
|
|
settings[k] = ui_mcm.get("blood_pool/" .. k)
|
|
end
|
|
end
|
|
return settings
|
|
end
|
|
|
|
-- UTILS
|
|
local function vecSimilar(v1, v2, eps)
|
|
eps = eps or 0.4
|
|
local abs = math.abs
|
|
return abs(v1.x - v2.x) < eps and abs(v1.y - v2.y) < eps and abs(v1.z - v2.z) < eps
|
|
end
|
|
|
|
local enable_debug = false
|
|
local function printf(...)
|
|
if enable_debug then
|
|
_G.printf(...)
|
|
end
|
|
end
|
|
|
|
pool_starts_in = 5
|
|
blood_textures_amount = 24
|
|
pool_size_mult = {
|
|
-- clsid
|
|
-- [clsid.boar_s] = boar,
|
|
-- [clsid.flesh_s] = flesh,
|
|
-- [clsid.burer_s] = burer,
|
|
-- [clsid.chimera_s] = chimera,
|
|
-- [clsid.script_stalker] = human,
|
|
-- [clsid.bloodsucker_s] = sucker,
|
|
-- [clsid.dog_s] = dog,
|
|
-- [clsid.pseudodog_s] = dog,
|
|
-- [clsid.psy_dog_s] = dog,
|
|
-- [clsid.psy_dog_phantom_s] = dog,
|
|
-- [clsid.cat_s] = cat,
|
|
-- [clsid.rat] = rat,
|
|
-- [clsid.rat_s] = rat,
|
|
-- [clsid.controller_s] = controller,
|
|
-- [clsid.fracture_s] = fracture,
|
|
-- [clsid.poltergeist_s] = poltergeist,
|
|
-- [clsid.gigant_s] = gigant,
|
|
-- [clsid.zombie_s] = zombie,
|
|
-- [clsid.snork_s] = snork,
|
|
-- [clsid.tushkano_s] = tushkano,
|
|
-- [136] = karlik,
|
|
-- [120] = sucker, -- psysucker
|
|
-- [124] = chimera, -- lurker
|
|
|
|
-- string find these patterns
|
|
bloodsucker = 1.2,
|
|
psysucker = 1.2,
|
|
boar = 1.5,
|
|
dog = 1.0,
|
|
flesh = 1.25,
|
|
burer = 1.25,
|
|
cat = 0.8,
|
|
rat = 0.3,
|
|
chimera = 2.0,
|
|
bibliotekar = 2.5,
|
|
lurker = 1.25,
|
|
karlik = 0.8,
|
|
controller = 1.0,
|
|
fracture = 1.1,
|
|
gigant = 2.0,
|
|
giant = 2.0,
|
|
zombi = 1.0,
|
|
snork = 1.0,
|
|
tushkan = 0.5,
|
|
}
|
|
|
|
boneList = {
|
|
"bip01_spine",
|
|
"bip01_spine1",
|
|
"spine",
|
|
"spine1",
|
|
}
|
|
|
|
function getBloodPoolPos(obj)
|
|
for i, v in ipairs(boneList) do
|
|
if obj:get_bone_id(v) ~= 65535 then
|
|
-- printf("found bone %s for obj %s", v, obj:section())
|
|
return utils_obj.safe_bone_pos(obj, v)
|
|
end
|
|
end
|
|
return obj:position():add(vector():set(0, 0.5, 0))
|
|
end
|
|
|
|
-- saved_pool stores information about npc which can leak blood pool
|
|
-- alreadyLeakedPools stores information about leaked pools grouped by npc
|
|
saved_pool = {}
|
|
alreadyLeakedPools = {}
|
|
|
|
-- on npc death -> time event and after 3 sec save npc bone position
|
|
function start_blood_pool(id)
|
|
local obj = level.object_by_id(id)
|
|
if not obj then return end
|
|
|
|
local pos = getBloodPoolPos(obj)
|
|
saved_pool[id] = {}
|
|
saved_pool[id].pos = pos
|
|
saved_pool[id].texture = "blood_pool_texture_" .. math.random(1, blood_textures_amount)
|
|
saved_pool[id].moved = false
|
|
saved_pool[id].size_mult = 1
|
|
if IsMonster(obj) then
|
|
for k, v in pairs(pool_size_mult) do
|
|
if obj:section():find(k) then
|
|
saved_pool[id].size_mult = v
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local ttl = settings.pool_lifetime
|
|
local function draw_pool(size, npc_id, i, size_i)
|
|
|
|
-- if npc with this id doesnt exist delete key
|
|
-- or size reached max - delete key before drawing the last decal
|
|
local npc = level.object_by_id(npc_id)
|
|
|
|
if
|
|
not npc
|
|
or i >= settings.num_of_updates
|
|
then
|
|
saved_pool[npc_id] = nil
|
|
CreateTimeEvent("remove_blood_pool", npc_id, ttl, function()
|
|
alreadyLeakedPools[npc_id] = nil
|
|
return true
|
|
end)
|
|
return true
|
|
end
|
|
|
|
-- if key is nil then dont do anything
|
|
if not saved_pool[npc_id] then
|
|
return true
|
|
end
|
|
|
|
local function genNextEvent()
|
|
-- reduce lifetime of current decal for the entire pool to disappear together
|
|
ttl = ttl - settings.update_freq
|
|
|
|
-- Generate next time event
|
|
size_i = size_i + 1
|
|
i = i + 1
|
|
CreateTimeEvent("xcv_pool_e", "xcv_pool_" .. i .. "_" .. id .. "_a", settings.update_freq, draw_pool, size, id, i, size_i)
|
|
end
|
|
|
|
local function delayRemoveCurrentPool()
|
|
CreateTimeEvent("remove_blood_pool_from_pos", npc_id .. vec_to_str(saved_pool[npc_id].pos), ttl, function(p)
|
|
printf("removing pool %s, %s", npc_id, p)
|
|
if alreadyLeakedPools[npc_id] then
|
|
if alreadyLeakedPools[npc_id][p] then
|
|
printf("found vec to remove %s, %s", npc_id, p)
|
|
end
|
|
alreadyLeakedPools[npc_id][p] = nil
|
|
end
|
|
return true
|
|
end, saved_pool[npc_id].pos)
|
|
end
|
|
|
|
-- if pos changed then refresh size
|
|
local pos = getBloodPoolPos(npc)
|
|
if saved_pool[npc_id].moved then
|
|
saved_pool[npc_id].texture = "blood_pool_texture_" .. math.random(1, blood_textures_amount)
|
|
if vecSimilar(saved_pool[npc_id].pos, pos, 0.01) then
|
|
printf("body %s settled", npc_id)
|
|
saved_pool[npc_id].moved = false
|
|
saved_pool[npc_id].pos = pos
|
|
size_i = 1
|
|
else
|
|
printf("body %s still moving", npc_id)
|
|
size_i = settings.num_of_updates * 0.08
|
|
saved_pool[npc_id].pos = pos
|
|
delayRemoveCurrentPool()
|
|
|
|
-- Uncomment to prevent blood trails from dragging
|
|
-- saved_pool[npc_id].pos = pos
|
|
-- saved_pool[npc_id].texture = "blood_pool_texture_" .. math.random(1, blood_textures_amount)
|
|
-- genNextEvent()
|
|
-- return true
|
|
end
|
|
else
|
|
if not vecSimilar(saved_pool[npc_id].pos, pos) then
|
|
printf("body %s is moved", npc_id)
|
|
saved_pool[npc_id].moved = true
|
|
|
|
delayRemoveCurrentPool()
|
|
|
|
saved_pool[npc_id].pos = pos
|
|
genNextEvent()
|
|
return true
|
|
end
|
|
end
|
|
|
|
-- draw decal
|
|
local vec_pos = vector():set(
|
|
saved_pool[npc_id].pos.x,
|
|
saved_pool[npc_id].pos.y + 1,
|
|
saved_pool[npc_id].pos.z
|
|
)
|
|
local vec_dir = vector():set(0, -0.5, 0)
|
|
size = (size_i / settings.num_of_updates) * saved_pool[npc_id].size_mult * settings.max_pool_size
|
|
wallmarks_manager():place(vec_dir, vec_pos, 8, size, saved_pool[npc_id].texture, npc, ttl, false)
|
|
|
|
-- Save leaked pool
|
|
alreadyLeakedPools[npc_id] = alreadyLeakedPools[npc_id] or {}
|
|
alreadyLeakedPools[npc_id][saved_pool[npc_id].pos] = size
|
|
|
|
genNextEvent()
|
|
return true
|
|
end
|
|
|
|
-- i is overall loop counter to draw a pool, size_i is used to determine pool size and can be resetted to 1 if body is moved
|
|
local i = 1
|
|
local size_i = 1
|
|
local size = (size_i / settings.num_of_updates) * settings.max_pool_size
|
|
CreateTimeEvent("xcv_pool_e", "xcv_pool_" .. i .. "_" .. id .. "_a", pool_starts_in, draw_pool, size, id, i, size_i)
|
|
return true
|
|
end
|
|
|
|
function on_death_callback(npc)
|
|
-- give body 3 sec to drop and save pos after
|
|
CreateTimeEvent("xcv_body_drop_e", "xcv_body_drop_" .. npc:id() .. "_a", 3, start_blood_pool, npc:id())
|
|
end
|
|
|
|
function actor_on_before_death()
|
|
CreateTimeEvent("blood_pool_check_actor", 0, 1, function()
|
|
if db.actor and not db.actor:alive() then
|
|
printf("actor died, spawn pool")
|
|
on_death_callback(db.actor)
|
|
end
|
|
return true
|
|
end)
|
|
end
|
|
|
|
-- Blood steps
|
|
local stepCount = {}
|
|
local flipFlops = {}
|
|
|
|
stepMaxCount = 10
|
|
size = 0.17
|
|
triggerDistance = 0.7^2
|
|
texture = "blood_step_"
|
|
|
|
function checkNearBloodPool(obj)
|
|
local pos = obj:position()
|
|
for npc_id, t in pairs(alreadyLeakedPools) do
|
|
for poolPos, size in pairs(t) do
|
|
if poolPos:distance_to_sqr(pos) < triggerDistance then
|
|
stepCount[obj:id()] = stepMaxCount
|
|
printf("%s near blood pool", obj:name())
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function npc_on_update(npc)
|
|
checkNearBloodPool(npc)
|
|
end
|
|
|
|
function actor_on_update()
|
|
checkNearBloodPool(db.actor)
|
|
end
|
|
|
|
function getHeading(dir)
|
|
local heading = dir:getH() * 57.2958
|
|
|
|
-- Fix East -90deg to 270deg
|
|
if heading < 0 then
|
|
heading = 360 + heading
|
|
end
|
|
|
|
-- Clamp
|
|
heading = clamp(math.floor(heading), 0, 359)
|
|
|
|
-- Count only certain headings: 0, 2, 4, 6...
|
|
heading = math.floor(heading / 2) * 2
|
|
|
|
printf("heading %s", heading)
|
|
|
|
return heading
|
|
end
|
|
|
|
function placeBloodStep(id)
|
|
local obj = level.object_by_id(id)
|
|
if not obj then return end
|
|
|
|
if not (stepCount[id] and stepCount[id] > 0) then return end
|
|
|
|
flipFlops[id] = not flipFlops[id]
|
|
local pos = id == AC_ID and device().cam_pos or utils_obj.safe_bone_pos(obj, flipFlops[id] and "bip01_r_foot" or "bip01_l_foot")
|
|
local dir = id == AC_ID and device().cam_dir or obj:direction()
|
|
|
|
local heading = getHeading(dir)
|
|
|
|
if id == AC_ID then
|
|
local v = vector():set(0, 0, 1)
|
|
dir = vector_rotate_y(v, heading)
|
|
local distanceBetweenSteps = 0.13
|
|
pos = pos:mad(vector_rotate_y(dir, flipFlops[id] and -90 or 90), distanceBetweenSteps)
|
|
end
|
|
|
|
-- Rotate 90 degrees left to align with textures
|
|
heading = (heading + 90) % 360
|
|
|
|
stepCount[id] = stepCount[id] - 1
|
|
printf("placing bloodstep for %s", obj:name())
|
|
wallmarks_manager():place(vector():set(0, -0.7, 0), pos, 100, size, texture .. (flipFlops[id] and "r_" or "l_") .. heading, obj, settings.pool_lifetime, false)
|
|
end
|
|
|
|
function on_key_press(key)
|
|
if enable_debug then
|
|
if key == DIK_keys.DIK_LSHIFT then
|
|
stepCount[AC_ID] = 10
|
|
placeBloodStep(AC_ID)
|
|
end
|
|
end
|
|
end
|
|
|
|
function actor_on_footstep()
|
|
placeBloodStep(AC_ID)
|
|
end
|
|
|
|
function npc_on_foot_step(obj,power,b_play,b_on_ground,b_hud_view)
|
|
placeBloodStep(obj:id())
|
|
end
|
|
|
|
if z_npc_footsteps then
|
|
-- 1.5.2 check
|
|
if AddScriptCallback then
|
|
RegisterScriptCallback("npc_on_foot_step", npc_on_foot_step)
|
|
end
|
|
og_npc_on_foot_step = z_npc_footsteps.npc_on_foot_step
|
|
z_npc_footsteps.npc_on_foot_step = function(binder,obj,power,b_play,b_on_ground,b_hud_view)
|
|
if og_npc_on_foot_step then og_npc_on_foot_step(binder,obj,power,b_play,b_on_ground,b_hud_view) end
|
|
npc_on_foot_step(obj,power,b_play,b_on_ground,b_hud_view)
|
|
end
|
|
else
|
|
net_spawn = xr_motivator.motivator_binder.net_spawn
|
|
|
|
xr_motivator.motivator_binder.net_spawn = function(self, se_abstract)
|
|
if not object_binder.net_spawn(self, se_abstract) then
|
|
return false
|
|
end
|
|
|
|
local se_obj = alife_object(self.object:id())
|
|
|
|
if not (se_obj) then
|
|
return false
|
|
end
|
|
|
|
local npc = self.object
|
|
|
|
if npc:alive() then
|
|
npc:set_callback(callback.on_foot_step, self.npc_on_foot_step, self)
|
|
end
|
|
|
|
return net_spawn(self, se_abstract)
|
|
end
|
|
|
|
net_destroy = xr_motivator.motivator_binder.net_destroy
|
|
|
|
xr_motivator.motivator_binder.net_destroy = function(self)
|
|
self.object:set_callback(callback.on_foot_step,nil)
|
|
|
|
net_destroy(self)
|
|
end
|
|
|
|
-- 1.5.2 check
|
|
if AddScriptCallback then
|
|
AddScriptCallback("npc_on_foot_step")
|
|
RegisterScriptCallback("npc_on_foot_step", npc_on_foot_step)
|
|
end
|
|
|
|
function xr_motivator.motivator_binder:npc_on_foot_step(obj,power,b_play,b_on_ground,b_hud_view)
|
|
-- 1.5.2 check
|
|
if AddScriptCallback then
|
|
SendScriptCallback("npc_on_foot_step", obj,power,b_play,b_on_ground,b_hud_view)
|
|
else
|
|
npc_on_foot_step(obj,power,b_play,b_on_ground,b_hud_view)
|
|
end
|
|
end
|
|
end
|
|
|
|
function on_game_start()
|
|
RegisterScriptCallback("actor_on_first_update", load_settings)
|
|
RegisterScriptCallback("on_option_change", load_settings)
|
|
|
|
RegisterScriptCallback("actor_on_before_death", actor_on_before_death)
|
|
RegisterScriptCallback("npc_on_death_callback", on_death_callback)
|
|
RegisterScriptCallback("monster_on_death_callback", on_death_callback)
|
|
|
|
RegisterScriptCallback("actor_on_update", actor_on_update)
|
|
RegisterScriptCallback("actor_on_footstep", actor_on_footstep)
|
|
RegisterScriptCallback("npc_on_update", npc_on_update)
|
|
|
|
RegisterScriptCallback("on_key_press", on_key_press)
|
|
end
|