myspace / Lua /Tactical /UnitStain.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
11 kB
DefineClass.UnitStain = {
__parents = { "InitDone", "SkinDecalData" },
properties = {
{ id = "SpotIdx", editor = "number", default = -1 },
{ id = "Rotation", editor = "number", default = -1 },
{ id = "initialized", editor = "bool", default = false },
},
decal = false,
}
function UnitStain:Done()
if self.decal then
DoneObject(self.decal)
self.decal = nil
end
end
function UnitStainPresetName(base_entity, stain_type, spot)
return string.format("%s-%s-%s", spot, stain_type, base_entity)
end
function CopyUnitStainProperties(from, to)
local props = from:GetProperties()
for _, prop in ipairs(props) do
if to:GetPropertyMetadata(prop.id) then
to:SetProperty(prop.id, from:GetProperty(prop.id))
end
end
end
function UnitStain:InitParams(unit, params)
if self.Spot == "" or self.DecType == "" then
return
end
local base_entity = GetAnimEntity(unit:GetEntity(), unit:GetState())
-- check for existing preset
local id = UnitStainPresetName(base_entity, self.DecType, self.Spot)
local preset = Presets.SkinDecalMetadata.Default and Presets.SkinDecalMetadata.Default[id]
local base = Presets.SkinDecalType.Default[self.DecType]
if preset then
CopyUnitStainProperties(preset, self)
elseif base then
self.DecEntity = base.DefaultEntity
self.DecScale = base.DefaultScale
else
print("unknown stain type: ", self.DecType)
end
local min, max = unit:GetSpotRange(self.Spot)
if min < 0 or max < 0 then
return
end
self.SpotIdx = min + AsyncRand(max - min)
self.Rotation = self.DecAttachAngleRange.from*60 + AsyncRand(self.DecAttachAngleRange.to*60 - self.DecAttachAngleRange.from*60)
for param, value in pairs(params) do
if self:GetPropertyMetadata(param) then
self:SetProperty(param, value)
end
end
self.initialized = true
return true
end
function UnitStain:Apply(unit, params)
if self.Spot == "" or self.DecType == "" then
return
end
if self.decal then
DoneObject(self.decal)
self.decal = nil
end
if self.initialized then
local min, max = unit:GetSpotRange(self.Spot)
if self.SpotIdx < min or self.SpotIdx > max then
self.initialized = false
end
end
if not self.initialized and not self:InitParams(unit, params) then
return
end
local dec = PlaceObject("SkinDecal")
dec:ChangeEntity(self.DecEntity)
unit:Attach(dec, self.SpotIdx, true)
local axis, angle = ComposeRotation(axis_y, self.InvertFacing and -90*60 or 90*60, SkinDecalAttachAxis[self.DecAttachAxis], self.Rotation)
dec:SetAttachAxis(axis)
dec:SetAttachAngle(angle)
dec:SetScale(self.DecScale)
dec:SetAttachOffset(point(self.DecOffsetX * (self.InvertFacing and -1 or 1), self.DecOffsetY, self.DecOffsetZ))
dec:SetColorModifier(self.ClrMod)
self.decal = dec
return dec
end
function Unit:AddStain(stain_type, spot, params)
local stain = UnitStain:new()
stain.DecType = stain_type
stain.Spot = spot
self.stains = self.stains or {}
table.insert(self.stains, stain)
stain:Apply(self, params)
return stain
end
function Unit:ClearStains(stain_type, ...) -- spots to clear
if not self.stains then return end
local nspots = select("#", ...)
for i = #self.stains, 1, -1 do
local stain = self.stains[i]
if stain.DecType == stain_type then
local match
for j = 1, nspots do
local spot = select(j, ...)
if spot == stain.Spot then
match = true
break
end
end
if nspots == 0 or match then
DoneObject(stain)
table.remove(self.stains, i)
end
end
end
end
function Unit:ClearStainsFromSpots(...) -- spots to clear
if not self.stains then return end
local nspots = select("#", ...)
for i = #self.stains, 1, -1 do
local stain = self.stains[i]
local match
for j = 1, nspots do
local spot = select(j, ...)
if spot == stain.Spot then
match = true
break
end
end
if nspots == 0 or match then
DoneObject(stain)
table.remove(self.stains, i)
end
end
end
function Unit:WashStainsFromSpot(spot) -- cleared by water, checks ClearedByWater flag from stain type
if not self.stains then return end
for i = #self.stains, 1, -1 do
local stain = self.stains[i]
if (not spot or stain.Spot == spot) and SkinDecalTypes[stain.DecType] and SkinDecalTypes[stain.DecType].ClearedByWater then
DoneObject(stain)
table.remove(self.stains, i)
end
end
end
function Unit:CanStain(stain_type, spot)
local target_prio = SkinDecalTypes[stain_type] and SkinDecalTypes[stain_type].SortKey or 0
for _, stain in ipairs(self.stains) do
if stain.Spot == spot then
local curr_prio = SkinDecalTypes[stain.DecType] and SkinDecalTypes[stain.DecType].SortKey or 0
if curr_prio >= target_prio then
return false
end
end
end
return true
end
function Unit:HasStainType(stain_type)
for _, stain in ipairs(self.stains) do
if stain.DecType == stain_type then
return true
end
end
end
local StainSpotGroups = {
Head = { "Head", "Neck" },
Torso = { "Ribslowerl", "Ribslowerr", "Ribsupperl", "Ribsupperr", "Torso", "Shoulderl", "Shoulderr" },
Groin = { "Groin", "Pelvisl", "Pelvisr" },
Arms = { "Shoulderl", "Shoulderr", "Elbowl", "Elbowr", "Wristl", "Wristr" },
Legs = { "Kneel", "Kneer", "Anklel", "Ankler" },
[false] = { "Ribslowerl", "Ribslowerr", "Ribsupperl", "Ribsupperr", "Torso", "Shoulderl", "Shoulderr", "Groin", "Pelvisl", "Pelvisr" },
}
function CheckStainSpotGroups()
if not SelectedObj then return end
for group, list in pairs(StainSpotGroups) do
for _, spot in ipairs(list) do
if not SelectedObj:HasSpot(spot) then
printf("Invalid spot %s in group %s", spot, group)
end
end
end
end
function GetRandomStainSpot(spot_group)
local spot_group = StainSpotGroups[spot_group or false] or StainSpotGroups[false]
local spot = table.rand(spot_group) -- cut off the returned index
return spot
end
function CalcStainParamsFromShot(target, attacker, hit)
-- find the nearest spot to the hit position, calculate the offset/orientation/facing to match it from that spot
local spots_data = GetEntitySpots(target:GetEntity())
local nearest_spot, nearest_dist, nearest_idx
local hit_pos = hit.pos or target:GetSpotLocPos(target:GetSpotBeginIndex("Torso"))
local attack_dir = SetLen(hit.shot_dir or (hit_pos - attacker:GetPos()), guim)
for spot, spot_indices in pairs(spots_data) do
for _, spot_idx in ipairs(spot_indices) do
local pos, angle, axis, scale = target:GetSpotLoc(spot_idx)
local dist = pos:Dist(hit_pos)
if not nearest_dist or dist < nearest_dist then
nearest_spot, nearest_dist, nearest_idx = spot, dist, spot_idx
end
end
end
--printf("nearest spot: %s (%d)", tostring(nearest_spot), nearest_idx or -1)
if nearest_idx then
local pos, angle, axis, scale = target:GetSpotLoc(nearest_idx)
local spot_x = RotateAxis(point(guim, 0, 0), axis, angle)
local spot_y = RotateAxis(point(0, guim, 0), axis, angle)
local spot_z = RotateAxis(point(0, 0, guim), axis, angle)
local invert_facing = false
if Dot2D(spot_x, attack_dir) > 0 then
invert_facing = true
end
local v = hit_pos - pos
local ox = Dot(spot_x, v) / guim
local oy = Dot(spot_y, v) / guim
local oz = Dot(spot_z, v) / guim
return nearest_spot, {
InvertFacing = invert_facing,
DecOffsetX = ox,
DecOffsetY = oy,
DecOffsetZ = oz,
DecScale = (hit.impact_force or 0) > 0 and 100 or 60,
}
end
end
local StainApplyInterval = 3000 -- minimum time before rechecking if we should apply a stain on the same spot
local StainChanceStanding = 10
local StainChanceCrouch = 80
local StainChanceProne = 100
local StainClearChance = 90 -- when moving in water/shallowwater
local function check_stain_update_timer(stain_update_times, spot, time)
local update_time = stain_update_times[spot] or time
if time >= update_time then
stain_update_times[spot] = time + StainApplyInterval -- timestamp the update
return true
end
end
function Unit:WalkUpdateStains(foot)
-- ATTN: FX code, keep it async
local surf_fx_type = GetObjMaterial(self:GetVisualPos())
local time = GameTime()
local stain_update_times = self.stain_update_times
if surf_fx_type == "Surface:Water" or surf_fx_type == "Surface:ShallowWater" then -- clearing stains instead of adding
-- feet spots (any stance)
local spot = (foot == "left") and "Leftfoot" or "Rightfoot"
if check_stain_update_timer(stain_update_times, spot, time) and AsyncRand(100) < StainClearChance then
self:WashStainsFromSpot(spot)
end
-- knee spots (Prone/Crouch)
if self.stance ~= "Standing" then
local spot = (foot == "left") and "Kneel" or "Kneer"
if check_stain_update_timer(stain_update_times, spot, time) and AsyncRand(100) < StainClearChance then
self:WashStainsFromSpot(spot)
end
end
-- shoulder spots (Prone only)
if self.stance == "Prone" then
local spot = (foot == "left") and "Shoulderl" or "Shoulderr" -- maybe reverse the shoulders?
if check_stain_update_timer(stain_update_times, spot, time) and AsyncRand(100) < StainClearChance then
self:WashStainsFromSpot(spot)
end
end
return
end
-- adding mud/dirt
local stain_type
if surf_fx_type == "Surface:Mud" then
stain_type = "Mud"
elseif surf_fx_type == "Surface:Dirt" or surf_fx_type == "Surface:Sand" then
stain_type = "Dirt"
end
if not stain_type then return end
local stain_chance = StainChanceStanding
if self.stance == "Crouch" then
stain_chance = StainChanceCrouch
elseif self.stance == "Prone" then
stain_chance = StainChanceProne
end
-- feet spots (any stance)
local spot = (foot == "left") and "Leftfoot" or "Rightfoot"
if check_stain_update_timer(stain_update_times, spot, time) and AsyncRand(100) < stain_chance and self:CanStain(stain_type, spot) then
self:ClearStainsFromSpots(spot)
self:AddStain(stain_type, spot)
end
-- knee spots (Prone/Crouch)
if self.stance ~= "Standing" then
local spot = (foot == "left") and "Kneel" or "Kneer"
if check_stain_update_timer(stain_update_times, spot, time) and AsyncRand(100) < stain_chance and self:CanStain(stain_type, spot) then
self:ClearStainsFromSpots(spot)
self:AddStain(stain_type, spot)
end
end
-- shoulder spots (Prone only)
if self.stance == "Prone" then
local spot = (foot == "left") and "Shoulderl" or "Shoulderr" -- maybe reverse the shoulders?
if check_stain_update_timer(stain_update_times, spot, time) and AsyncRand(100) < stain_chance and self:CanStain(stain_type, spot) then
self:ClearStainsFromSpots(spot)
self:AddStain(stain_type, spot)
end
end
end
function Unit:OnMomentFootLeft()
if self.team and self.team.side ~= "neutral" then
self:WalkUpdateStains("left")
end
return StepObject.OnMomentFootLeft(self)
end
function Unit:OnMomentFootRight()
if self.team and self.team.side ~= "neutral" then
self:WalkUpdateStains("right")
end
return StepObject.OnMomentFootRight(self)
end
function OnMsg.EnterSector()
for _, unit in ipairs(g_Units) do
if not unit:IsDead() and not unit:HasStatusEffect("Wounded") then
unit:ClearStains("Blood")
end
end
end