Predator Camouflage
-
Did a short little script to allow my bot to look like any prop its standing next to.
Video (or it didn't happen): https://youtu.be/frnP8V9ZPW8
Script...
----------------------------------------------------------------------------- -- File : /units/ual0204/ual0204_script.lua -- -- Author(s): EbolaSoup, Resin Smoker, Optimus Prime, Vissroid -- -- Summary : Aeon T2 Sniper Bot -- -- Copyright © 2024 4DFAF, All rights reserved. ----------------------------------------------------------------------------- -- Misc Lua called local AWalkingLandUnit = import('/lua/aeonunits.lua').AWalkingLandUnit local EffectUtils = import('/lua/EffectUtilities.lua') local EffectTemplate = import('/lua/effecttemplates.lua') local Custom_4D_EffectTemplate = import('/mods/4DFAF/lua/4D_EffectTemplates.lua') local utilities = import('/lua/utilities.lua') local myDebug = false -- Weapon Local lua called local SniperWeapon = import('/lua/aeonweapons.lua').ADFDisruptorCannonWeapon -- Bones for weapon recoil effects local weaponBones = { 'sniper_rifle', 'sniper_barrel' } ual0204 = Class(AWalkingLandUnit) { Weapons = { -- Shield Piercing Rifle Sniper_Piercing_Rifle = Class(SniperWeapon) { PlayFxMuzzleSequence = function(self, muzzle) for k, v in weaponBones do local steamEffects = EffectUtils.CreateBoneEffects( self.unit, v, self.unit:GetArmy(), EffectTemplate.WeaponSteam01 ) end local groundEffects = EffectUtils.CreateBoneEffects( self.unit, 'ual0204', self.unit:GetArmy(), Custom_4D_EffectTemplate.ConcussionRing ) if self.unit.CamoEntity then self.unit.CamoEntity:Destroy() self.unit.CamoEntity = nil self.unit:DisableIntel('Cloak') self.unit:SetVizToFocusPlayer('Always') self.unit:SetVizToAllies('Always') end SniperWeapon.PlayFxMuzzleSequence(self) end, }, }, OnCreate = function(self,builder,layer) AWalkingLandUnit.OnCreate(self) self:DisableIntel('Cloak') self.CamoEntity = nil end, OnMotionHorzEventChange = function(self, new, old) if myDebug then WARN('OnMotionHorzEventChange') end if self and not self:IsDead() then if new == 'Stopped' then if myDebug then WARN(' Stopped') end local propTbl = self:GetPropsInRadius(2) local propBp = propTbl[1].prop.Blueprint if myDebug then WARN(' prop blueprint '..repr(propBp) ) end local mesh = propBp.Display.MeshBlueprint or nil local scale = propBp.Display.UniformScale if myDebug then WARN(' Mesh / Scale: ', mesh, scale) end if mesh then self:EnableIntel('Cloak') self:SetVizToFocusPlayer('Never') self:SetVizToAllies('Never') self.CreateCamoEntity(self, mesh, scale) end else if myDebug then WARN(' moving') end if self.CamoEntity then self.CamoEntity:Destroy() self.CamoEntity = nil self:DisableIntel('Cloak') self:SetVizToFocusPlayer('Always') self:SetVizToAllies('Always') end end end AWalkingLandUnit.OnMotionHorzEventChange(self, new, old) end, CreateCamoEntity = function(self, mesh, scale) if myDebug then WARN('CreateCloakEntity') end ent = import('/lua/sim/Entity.lua').Entity({Owner = self,}) ent:AttachBoneTo( -1, self, 'ual0204' ) ent:SetMesh(mesh) ent:SetDrawScale(scale or 1) ent:SetVizToFocusPlayer('Always') ent:SetVizToAllies('Always') ent:SetVizToNeutrals('Intel') ent:SetVizToEnemies('Intel') self.CamoEntity = ent self.Trash:Add(ent) end, GetPropsInRadius = function(self, radius) if myDebug then WARN('GetPropsInRadius') end radius = radius or 1 local pos = self:GetPosition() local props = GetReclaimablesInRect(Rect(pos[1] - radius, pos[3] - radius, pos[1] + radius, pos[3] + radius)) if table.getn(props) then local propsByDist = {} for a, b in props do if not b:BeenDestroyed() then if not IsUnit(b) and not b.IsWreckage then table.insert(propsByDist, {dist = utilities.XZDistanceTwoVectors(pos, b:GetPosition()), prop = b}) end end end if myDebug then WARN(' num props after filter: ', table.getn(propsByDist)) end table.sort(propsByDist, sort_by('dist')) return propsByDist else if myDebug then WARN(' no props in radius') end return false end end, } TypeClass = ual0204
Still a lot of work to do on this concept, but at least the hard part is done.
Resin
-
I absolutely love it
-
@jip Thanks! Will tie it into to units movement and its attack. So it could remain unseen until it moves are fires. Which will force it to decloak. Figured this would be way cooler than just another phase mesh swapout.
-
Woot got everything working! I love having the terrain specific stealth.
-
Updated the script above. Only need to have the weapon charge timer (RenderFireClock) display correctly. Currently it works off of the weapons rate of fire but as this unit must charge to fire, I need to make something custom for it to display correctly.
-
OK here is a proper fire clock that uses the weapons energy drain as its basis.
OnCreate = function(self,builder,layer) AWalkingLandUnit.OnCreate(self) self:DisableIntel('Cloak') self.CamoEntity = nil self:SetWorkProgress(100) end, RenderChargeClockThread = function(self, eRequired, eDrain) local clockTime = math.round(10 * (eRequired / eDrain)) local totalTime = clockTime while clockTime >= 0 and not self:BeenDestroyed() and not self:IsDead() do self:SetWorkProgress(1 - clockTime / totalTime) clockTime = clockTime - 1 WaitSeconds(0.1) end end,
Its called from the weapons PlayFxMuzzleSequence via...
-- Reset the units reload bar local eDrain = self.EnergyDrainPerSecond local eRequired = self.EnergyRequired self.unit:ForkThread(self.unit.RenderChargeClockThread, eRequired, eDrain)
While it does work, I've noticed that there is a short delay between when the clock finished and when the weapon fires. I'm thinking that the weapons internal reload may have some delays involved that don't show properly in the units UI. Currently the unit shows as 10 seconds but with that delay I'd say its more like 11 seconds.
-
Not making this part of the Cybran faction is a criminal oversight, however.
-
@indexlibrorum IKR... Anyways, I've been pondering making the script into a superclass, so it can later be applied to other units quickly. Including the nasty Cybrans.
-
Ok made the script into a SuperClass so it can be easily added to other units, including your Nasty Cybrans!
---------------------------------------------------------------------------- -- File : /mods/4DFAF/lua/4D_TerrainCamo/4D_TerrainCamo.lua -- -- Author : Resin_Smoker -- -- Summary : Allows a unit to take the appearance of nearby props. -- -- Copyright © 2024 4DFAF, All rights reserved. ---------------------------------------------------------------------------- -- -- Add the following within a units PlayFxMuzzleSequence or other OnFire type event to remove Terrain Camo -- -- -- Remove the Camo entity and cloaking during the units weapon firing event -- if self.unit.TerrainCamoEntity then -- self.unit.TerrainCamoEntity:Destroy() -- self.unit.TerrainCamoEntity = nil -- if self.TerrainCamo_Unit_BP.Intel.Cloak then self.unit:DisableIntel('Cloak') end -- if self.TerrainCamo_Unit_BP.Intel.RadarStealth then self.unit:DisableIntel('RadarStealth') end -- self.unit:SetVizToFocusPlayer('Always') -- self.unit:SetVizToAllies('Always') -- end -- -- Add these two line within the units lua script, adjusting the local unit info called as nessisary -- -- local CLandUnit = import('/lua/cybranunits.lua').CLandUnit -- ClandUnit = import('/mods/4DFAF/lua/CustomAbilities/4D_TerrainCamo/4D_TerrainCamo.lua').TerrainCamo( CLandUnit ) -- ---------------------------------------------------------------------------- -- Misc Lua called local utilities = import('/lua/utilities.lua') -- Set flag "true" to see script progress within the error log local myDebug = false ### Start of TerrainCamo(SuperClass) ### function TerrainCamo(SuperClass) return Class(SuperClass) { OnCreate = function(self,builder,layer) SuperClass.OnCreate(self) self.TerrainCamo_Unit_BP = self:GetBlueprint() -- Disable unit cloak / stealth until TerrainCamo goes active if self.TerrainCamo_Unit_BP.Intel.Cloak then self:DisableIntel('Cloak') end if self.TerrainCamo_Unit_BP.Intel.RadarStealth then self:DisableIntel('RadarStealth') end -- Global for our camo entity self.TerrainCamoEntity = nil end, OnMotionHorzEventChange = function(self, new, old) if myDebug then WARN('OnMotionHorzEventChange') end if self and not self:IsDead() then -- When the units stops, engage cloak / camo if new == 'Stopped' then if myDebug then WARN(' Stopped') end local propTbl = self:GetPropsInRadius(2) local propBp = propTbl[1].prop.Blueprint if myDebug then WARN(' prop blueprint '..repr(propBp) ) end local mesh = propBp.Display.MeshBlueprint or nil local scale = propBp.Display.UniformScale if myDebug then WARN(' Mesh / Scale: ', mesh, scale) end if mesh and scale then if self.TerrainCamo_Unit_BP.Intel.Cloak then self:EnableIntel('Cloak') end if self.TerrainCamo_Unit_BP.Intel.RadarStealth then self:EnableIntel('RadarStealth') end self:SetVizToFocusPlayer('Never') self:SetVizToAllies('Never') self:ForkThread(self.CreateFlashFX) self.CreateTerrainCamoEntity(self, mesh, scale) end else -- When unit moves, remove cloak / camo if myDebug then WARN(' moving') end if self.TerrainCamoEntity then self.TerrainCamoEntity:Destroy() self.TerrainCamoEntity = nil if self.TerrainCamo_Unit_BP.Intel.Cloak then self:DisableIntel('Cloak') end if self.TerrainCamo_Unit_BP.Intel.RadarStealth then self:DisableIntel('RadarStealth') end self:SetVizToFocusPlayer('Always') self:SetVizToAllies('Always') end end end SuperClass.OnMotionHorzEventChange(self, new, old) end, CreateFlashFX = function(self) -- Simple FX to make the transition nicer looking self:PlayUnitSound('EnhanceStart') local fx = CreateAttachedEmitter(self, -1, self.Army, '/effects/emitters/aeon_sacrifice_02_emit.bp'):ScaleEmitter(1) WaitTicks(5) fx:Destroy() end, CreateTerrainCamoEntity = function(self, mesh, scale) if myDebug then WARN('CreateCloakEntity') end ent = import('/lua/sim/Entity.lua').Entity({Owner = self,}) ent:AttachBoneTo( -1, self, 0 ) ent:SetMesh(mesh) ent:SetDrawScale(scale or 1) ent:SetVizToFocusPlayer('Always') ent:SetVizToAllies('Always') ent:SetVizToNeutrals('Intel') ent:SetVizToEnemies('Intel') self.TerrainCamoEntity = ent self.Trash:Add(ent) end, GetPropsInRadius = function(self, radius) if myDebug then WARN('GetPropsInRadius') end radius = radius or 1 local pos = self:GetPosition() local props = GetReclaimablesInRect(Rect(pos[1] - radius, pos[3] - radius, pos[1] + radius, pos[3] + radius)) if table.getn(props) then local propsByDist = {} for a, b in props do if not b:BeenDestroyed() then if not IsUnit(b) and not b.IsWreckage then table.insert(propsByDist, {dist = utilities.XZDistanceTwoVectors(pos, b:GetPosition()), prop = b}) end end end if myDebug then WARN(' num props after filter: ', table.getn(propsByDist)) end table.sort(propsByDist, sort_by('dist')) return propsByDist else if myDebug then WARN(' no props in radius') end return false end end, } end ### End of TerrainCamo(SuperClass) ###
-
I've added this ability to the 4DFAF URL0216 Insurgent and will remove it from the Predator. It will instead have a custom unit mesh for cloaking. (need to make it)
-
Made some improvements to the Terrain Camo. It now has a phase out effect before the units takes on the mesh of the nearby trees.