|
g_DefaultMinDebris = 5 |
|
g_DefaultMaxDebris = 20 |
|
|
|
function dbgTestSP() |
|
Setpieces["FlagHillExplosion"]:Test() |
|
end |
|
|
|
DefineClass.Debris = { |
|
__parents = {"Object"}, |
|
flags = { efCollision = false, efApplyToGrids = false }, |
|
|
|
thread = false, |
|
time_fade_away_start = 0, |
|
|
|
|
|
opacity = 100, |
|
time_disappear = 0, |
|
time_fade_away = 0, |
|
spawning_obj = false, |
|
} |
|
|
|
function Debris:Done() |
|
self:DestroyThread() |
|
end |
|
|
|
function Debris:DestroyThread() |
|
if self.thread ~= CurrentThread() then |
|
DeleteThread(self.thread) |
|
end |
|
end |
|
|
|
function Debris:StartPhase(phase, ...) |
|
self:DestroyThread() |
|
self.thread = CreateGameTimeThread(function(...) |
|
self[phase](...) |
|
end, self, ...) |
|
end |
|
|
|
function Debris.enum_obj(obj, x, y, z, nx, ny, nz) |
|
if IsKindOfClasses(obj, "Object") then |
|
return true |
|
end |
|
end |
|
|
|
local saneBox = box(-const.SanePosMaxXY, -const.SanePosMaxXY, const.SanePosMaxXY - 1, const.SanePosMaxXY - 1) |
|
local saneZ = const.SanePosMaxZ |
|
|
|
function MakeDebrisPosSane(pos) |
|
return ClampPoint(pos, saneBox):SetZ(Clamp(pos:z(), -saneZ + 1, saneZ - 1)) |
|
end |
|
|
|
local hundredSeventy = 170*60 |
|
|
|
function Debris:RotatingTravel(pos, time, rpm) |
|
if not rpm then |
|
local rpm_min, rpm_max = 20, 80 |
|
rpm = rpm_min * 360 + self:Random(rpm_max * 360) |
|
end |
|
local angle = MulDivTrunc(rpm, time, 1000) |
|
local clamped_pos = MakeDebrisPosSane(pos) |
|
self:SetPos(clamped_pos, time) |
|
|
|
local ticks = 1 + angle / hundredSeventy |
|
local dt = time / ticks |
|
local tt = 0 |
|
while tt < time do |
|
local dtt = Min(dt, time - tt) |
|
self:SetAngle(self:GetAngle() + angle * dtt / time, dtt) |
|
tt = tt + dt |
|
Sleep(dtt) |
|
end |
|
|
|
return rpm |
|
end |
|
|
|
local s_ExplodeDeviationSin = sin(const.DebrisExplodeDeviationAngle / 2) |
|
local s_ExplodeDeviationCos = cos(const.DebrisExplodeDeviationAngle / 2) |
|
local s_AxisX, s_AxisY, s_AxisZ = point(4096, 0, 0), point(0, 4096, 0), point(0, 0, 4096) |
|
local s_SlabSizeZ = const.SlabSizeZ |
|
local s_MinRadius = 5 * guic |
|
|
|
function Debris:GetRandomDirInCone(dir, radius) |
|
local cone_height = dir:Len() |
|
local cone_radius = MulDivTrunc(cone_height, s_ExplodeDeviationSin, s_ExplodeDeviationCos) |
|
local cone_base_len = Max(self:Random(cone_radius), s_MinRadius) |
|
local cone_base_pt = Rotate(point(cone_base_len, 0), self:Random(360 * 60)):SetZ(0) |
|
local dx, dy, dz = dir:xyz() |
|
if dx == 0 and dx == 0 and dz ~= 0 then |
|
cone_base_pt = (dz < 0) and -cone_base_pt or cone_base_pt |
|
else |
|
local axis = Cross(dir, s_AxisX) |
|
local angle = CalcAngleBetween(dir, s_AxisX) |
|
cone_base_pt = RotateAxis(cone_base_pt, axis, angle) |
|
end |
|
|
|
return SetLen(dir + cone_base_pt, Max(self:Random(radius), s_MinRadius)) |
|
end |
|
|
|
local min_vec_len = 500 |
|
function Debris:GetRandomInSphere(radius) |
|
local axis_z_rotated = RotateAxis(point(4096, 0, 0), s_AxisZ, self:Random(360 * 60)) |
|
local axis_y_rotated = RotateAxis(axis_z_rotated, s_AxisY, self:Random(360 * 60)) |
|
local axis_x_rotated = RotateAxis(axis_y_rotated, s_AxisY, self:Random(360 * 60)) |
|
local min = Min(min_vec_len, radius) |
|
return SetLen(axis_x_rotated, self:Random(radius - min) + min) |
|
end |
|
|
|
local explode_z_offset = const.SlabSizeZ / 2 |
|
local flags_enum = const.efVisible |
|
local flags_game_ignore = const.gofSolidShadow |
|
local slab_delay = const.DebrisExplodeSlabDelay |
|
|
|
function Debris:Explode(slab_pos, radius, origin_of_destruction) |
|
origin_of_destruction = origin_of_destruction or slab_pos |
|
|
|
local z_offset = -explode_z_offset + self:Random(2 * explode_z_offset) |
|
local has_origin = origin_of_destruction and slab_pos ~= origin_of_destruction |
|
local explode_dir = has_origin and SetLen(slab_pos - origin_of_destruction, 4096) or axis_z |
|
local cone_dir = has_origin and self:GetRandomDirInCone(explode_dir, radius) or self:GetRandomInSphere(radius) |
|
local fly_dir = origin_of_destruction and cone_dir or SetLen(cone_dir, Max(cone_dir:Len() + z_offset, s_MinRadius)) |
|
|
|
local slabs_dist = has_origin and origin_of_destruction:Dist(slab_pos) / s_SlabSizeZ or 0 |
|
if slabs_dist > 0 then |
|
local delay = (slabs_dist - 1) * slab_delay + self:Random(slab_delay) |
|
Sleep(delay) |
|
end |
|
|
|
local pos = slab_pos + fly_dir |
|
local time_explode = 20 + self:Random(200) + 100 * fly_dir:Len() / guim |
|
self:SetPos(slab_pos) |
|
local rpm = self:RotatingTravel(pos, time_explode) |
|
self:StartPhase("FallDown", rpm) |
|
end |
|
|
|
function Debris:FallDown(rpm) |
|
local src = self:GetPos() |
|
local dest = src:SetZ(terrain.GetHeight(src)) - axis_z |
|
local obj, pos, norm = GetClosestRayObj(src, dest, flags_enum, flags_game_ignore, Debris.enum_obj) |
|
if not pos then |
|
DoneObject(self) |
|
return |
|
end |
|
|
|
self:SetGravity() |
|
|
|
while true do |
|
local fall_time = self:GetGravityFallTime(pos) |
|
if CalcAngleBetween(norm, axis_z) > 30 * 60 and -norm ~= self:GetAxis() then |
|
|
|
self:SetAxis(norm, fall_time) |
|
end |
|
self:RotatingTravel(pos, fall_time, rpm) |
|
|
|
local new_pos |
|
obj, new_pos, norm = GetClosestRayObj(pos, dest, flags_enum, flags_game_ignore, Debris.enum_obj) |
|
if not new_pos then |
|
DoneObject(self) |
|
return |
|
end |
|
if new_pos:Dist(pos) < s_SlabSizeZ / 2 then |
|
break |
|
end |
|
|
|
pos = new_pos |
|
end |
|
|
|
self:SetGravity(0) |
|
PlayFX("Debris", "hit", self) |
|
|
|
local disappear_time = const.DebrisDisappearTime |
|
local fade_away_time = const.DebrisFadeAwayTime- disappear_time + self:Random(2 * disappear_time) |
|
self:StartPhase("FadeAway", fade_away_time, disappear_time) |
|
end |
|
|
|
function Debris:ShouldBeVisibileWhileFading() |
|
return true |
|
end |
|
|
|
function Debris:FadeAway(time_fade_away, time_disappear) |
|
if not self:ShouldBeVisibileWhileFading() then |
|
DoneObject(self) |
|
return |
|
end |
|
|
|
self.time_fade_away = time_fade_away |
|
self.time_disappear = time_disappear |
|
self.time_fade_away_start = GameTime() |
|
self:SetOpacity(self.opacity) |
|
Sleep(self.time_fade_away) |
|
|
|
self:SetOpacity(0, self.time_disappear) |
|
Sleep(self.time_disappear) |
|
DoneObject(self) |
|
end |
|
|
|
function Debris:IsFadingAway() |
|
return self.time_fade_away ~= 0 or self.time_disappear ~= 0 |
|
end |
|
|
|
DefineClass.DebrisWeight = { |
|
__parents = {"PropertyObject"}, |
|
|
|
properties = { |
|
{id = "DebrisClass", name = "Debris Class", editor = "choice", items = ClassDescendantsCombo("Debris"), default = "" }, |
|
{id = "Weight", name = "Weight", editor = "number", default = 10 }, |
|
}, |
|
|
|
EditorView = Untranslated("<DebrisClass> (Weight: <Weight>)"), |
|
} |
|
|
|
local s_DebrisInfoCache = {} |
|
|
|
function GetDebrisInfo(entity) |
|
local cached = s_DebrisInfoCache[entity] |
|
if cached then |
|
return cached.classes, cached.debris_min, cached.debris_max |
|
end |
|
|
|
local entity_data = EntityData[entity] |
|
if not entity_data then |
|
return |
|
end |
|
|
|
local entity_data = entity_data.entity |
|
local classes = entity_data.debris_classes |
|
if classes then |
|
local new_classes, total_weight = {}, 0 |
|
for idx, entry in ipairs(classes) do |
|
total_weight = total_weight + entry.Weight |
|
new_classes[idx] = {class = entry.DebrisClass, weight = total_weight} |
|
end |
|
classes = new_classes |
|
classes.total_weight = total_weight |
|
end |
|
local min, max = entity_data.debris_min, entity_data.debris_max |
|
min = entity_data.debris_min or g_DefaultMinDebris |
|
max = entity_data.debris_max or g_DefaultMaxDebris |
|
|
|
|
|
s_DebrisInfoCache[entity] = {classes = classes, debris_min = min, debris_max = max} |
|
|
|
return classes, min, max |
|
end |
|
|
|
if Platform.developer and Platform.pc and config.RunUnpacked and not Platform.ged then |
|
|
|
AppendClass.EntitySpecProperties = { |
|
properties = { |
|
|
|
{ category = "Debris", id = "debris_min", name = "Debris min" , editor = "number", default = g_DefaultMinDebris, min = 0, max = 20, |
|
no_edit = function(self) return not self.debris_classes or #self.debris_classes == 0 end, entitydata = true, |
|
}, |
|
{ category = "Debris", id = "debris_max", name = "Debris max" , editor = "number", default = g_DefaultMaxDebris, min = 0, max = 50, |
|
no_edit = function(self) return not self.debris_classes or #self.debris_classes == 0 end, entitydata = true, |
|
}, |
|
{ category = "Debris", id = "debris_list", name = "Debris list" , editor = "dropdownlist", default = "", |
|
items = PresetGroupCombo("DebrisList", "Default"), |
|
}, |
|
{ category = "Debris", id = "debris_classes", name = "Debris classes", editor = "nested_list", default = false, |
|
base_class = "DebrisWeight", |
|
entitydata = function(prop_meta, self) |
|
return table.map(self.debris_classes, function(entry) |
|
return { DebrisClass = entry.DebrisClass, Weight = entry.Weight } |
|
end) |
|
end, |
|
}, |
|
}, |
|
} |
|
|
|
end |
|
|