-----------------------------------------------------------------------------
--  File          : /lua/4D_ProjectileSuperClass.lua 
-- 
--  Author        : Resin_Smoker 
-- 
--  Summary       : 4D_ProjectileSuperClass: Allows special abilities to be 
--                  easily added to custom projectiles by adding a few simple
--                  lines of script to that projectiles lua. 
-- 
--  Copyright © 2011 4DC_V0.8  All rights reserved.
-----------------------------------------------------------------------------

local utilities = import('/lua/utilities.lua')


-----------------------------------------------------------------------------
-- *** Bouncing Projectiles Ability ***
--
-- Summary: Allows projectiles to bounce on impact with terrain
--
-- The following is required in the projectiles script
-- 
-- local MyProjectile = import('/lua/aeonprojectiles.lua').WhateverMuzzleEffectsYouWantGoesHereProjectile
-- Disruptor_Projectile = import('/mods/4DC_V0.8/lua/CustomAbilities/4D_ProjectileSuperClass/4D_ProjectileSuperClass.lua').BounceProj( MyProjectile )
--  
----------------------------------------------------------------------------- 
-- Start of Bouncing Projectiles(SuperClass) 
function BounceProj(SuperClass) 
    return Class(SuperClass) {
    
    OnCreate = function(self, inWater)
        SuperClass.OnCreate(self, inWater)
        -- Get max number of bounces from proj blueprint
        self.MaxBounce = self:GetBlueprint().Physics.MaxBounce or 1
        -- Initialize the counter        
        self.ProjBounceCounter = self.MaxBounce
    end,
       
    OnImpact = function(self, targetType, targetEntity)
        if not self:BeenDestroyed() then           
			-- Reset the projectiles life to keep it from doing things it shouldn't             
			self:SetLifetime(10)						            
            if targetType == 'Terrain' and self.MaxBounce >= 1 and self.ProjBounceCounter >= 1 then
	            if not self.ProjVelocity then
	                -- Remember the proj velocity of the first impact
	                local vx, vy, vz = self:GetVelocity()
	                -- Initialize table             
	                self.ProjVelocity = {}
	                -- Fill tabel with velocity keys
	                table.insert(self.ProjVelocity, {X = vx, Y = -vy, Z = vz})
	            end 
	            local ran = utilities.GetRandomFloat(1.0,1.1) 
	            local mod_vx = ( self.ProjVelocity[1].X * self.ProjBounceCounter * ran )                                      
	            local mod_vy = ( self.ProjVelocity[1].Y * self.ProjBounceCounter * ran )
	            local mod_vz = ( self.ProjVelocity[1].Z * self.ProjBounceCounter * ran )              
	            self:SetVelocity(mod_vx, mod_vy, mod_vz)
	            self.ProjBounceCounter = self.ProjBounceCounter - 1	            
	        else
		        SuperClass.OnImpact(self, targetType, targetEntity)
		    end 
        end
    end, 
           
}
end 
-- End of Bouncing Projectiles (SuperClass)

-----------------------------------------------------------------------------
-- *** Shield Disruptor Ability ***
--
-- Summary: Allows projectiles to disrupt a shields integrity by 33% per impact
--
-- The following is required in the projectiles script
-- 
-- local MyProjectile = import('/lua/aeonprojectiles.lua').WhateverMuzzleEffectsYouWantGoesHereProjectile
-- Disruptor_Projectile = import('/mods/4DC_V0.8/lua/CustomAbilities/4D_ProjectileSuperClass/4D_ProjectileSuperClass.lua').ShieldDisruptor( MyProjectile )
--  
----------------------------------------------------------------------------- 
-- Start of Shield Disruptor(SuperClass) 
function ShieldDisruptor(SuperClass) 
    return Class(SuperClass) {
    
    OnImpact = function(self, targetType, targetEntity)
        SuperClass.OnImpact(self, targetType, targetEntity)
        if targetEntity and targetType == 'Shield' and targetEntity:GetMaxHealth() > 0 then
            -- Disruption amount is 33% of shields current health
            local dmgAmt = targetEntity:GetHealth() * 0.33
            if dmgAmt > 0 and targetEntity:GetHealth() > 0 then
                if dmgAmt < targetEntity:GetHealth() then
                    -- LOG('Shield Disruption DMG: ', dmgAmt)
                    Damage(self, {0,0,0}, targetEntity, dmgAmt, 'Normal')
                else
                    -- LOG('Shield Kill')               
                    Damage(self, {0,0,0}, targetEntity, TargetEntity:GetHealth(), 'Normal')               
                end
                -- LOG('Wrong unit type or No Dmg Data passed or the Target shield has no health')
            end	
            -- LOG('Target health after impact: ', TargetEntity:GetHealth() )        			
        end
    end,
           
}
end 
-- End of Shield Disruptor(SuperClass)


-----------------------------------------------------------------------------
-- *** Gauss Rifle Ability #1 ***
--
-- Summary: Allows projectiles to pass thru up to 3 targets
--
-- The following is required in the projectiles script
-- 
-- local MyProjectile = import('/lua/aeonprojectiles.lua').WhateverMuzzleEffectsYouWantGoesHereProjectile
-- Gauss1_Projectile = import('/mods/4DC_V0.8/lua/CustomAbilities/4D_ProjectileSuperClass/4D_ProjectileSuperClass.lua').GaussRifle1( MyProjectile )
--  
--
-- The following is required within the Unit.lua
--
--  OnCollisionCheck = function(self, other, firingWeapon)
--      if other.LastImpact and other.LastImpact == self:GetEntityId() then
--          --LOG('Hit the same unit twice')
--          return false 
--      end 
--      return oldUnit.OnCollisionCheck(self, other, firingWeapon) 
--  end,    
----------------------------------------------------------------------------- 
-- Start of Gauss Rifle(SuperClass) 
function GaussRifle1(SuperClass) 
    return Class(SuperClass) {
    
    OnCreate = function(self, inWater)
        SuperClass.OnCreate(self, inWater)  
        -- Initialize the counter
        self.ImpactCounter = 0
    end,
       
    OnImpact = function(self, targetType, targetEntity)  
        if targetEntity and targetType == 'Unit' then       
            -- LOG('OnImpact, hit unit')
            -- Check to see if we hit the same target                     
            if not self.LastImpact or self.LastImpact != targetEntity:GetEntityId() then
                -- Remember what was hit last
                -- LOG('OnImpact, saving last target hit') 
                self.LastImpact = targetEntity:GetEntityId()                
                -- Perform DMG and effects
                SuperClass.OnImpact(self, targetType, targetEntity)                
                if self.ImpactCounter then
                    -- LOG('OnImpact, counter: ', self.ImpactCounter) 
                    if self.ImpactCounter < 2 then
                        -- LOG('OnImpact, incrementing Counter') 
                        self.ImpactCounter = self.ImpactCounter + 1 
                    end 
                else
                   -- LOG('OnImpact, starting counter') 
                   -- Advance the counter
                   self.ImpactCounter = 1 
                end                          
            else
               -- If the same target is hit do nothing
               -- LOG('OnImpact, hit the same unit')
            end
        else
            -- Upon hitting a non-unit, perform DMG and effects
            -- LOG('OnImpact, passing dmg effects to non-unit target')
            SuperClass.OnImpact(self, targetType, targetEntity)            
        end
    end,      
          
    OnImpactDestroy = function( self, targetType, targetEntity ) 
        if targetEntity and targetType == 'Unit' then 
            -- LOG('OnImpactDestroy, Hit unit')
            -- LOG('OnImpactDestroy, Current count: ', self.ImpactCounter)
            if self.ImpactCounter < 2 then
                -- LOG('OnImpactDestroy, Counter < 3')
                return            
            else
                -- Destroy projectile if impact count = 3
                -- LOG('OnImpactDestroy, hit 3 or more units, destroying projectile') 
                SuperClass.OnImpactDestroy(self, targetType, targetEntity)      
            end
        else
            -- Destroy projectile if non-unit impacted
            -- LOG('OnImpactDestroy, hit non-unit')           
            SuperClass.OnImpactDestroy(self, targetType, targetEntity)
        end     
    end,     
           
}
end 
-- End of Gauss Rifle(SuperClass)


-----------------------------------------------------------------------------
-- *** Singularity Ability ***
--
-- Summary: Allows projectiles to create gravitational singularities
--
-- The following is required in the projectiles script
-- 
-- local MyProjectile = import('/lua/aeonprojectiles.lua').WhateverMuzzleEffectsYouWantGoesHereProjectile
-- Singularity_Projectile = import('/mods/4DC_V0.8/lua/CustomAbilities/4D_ProjectileSuperClass/4D_ProjectileSuperClass.lua').Singularity( MyProjectile )
--  
----------------------------------------------------------------------------- 
-- Start of Singularity(SuperClass) 
function Singularity(SuperClass)
    return Class(SuperClass) {
    
    OnImpact = function(self, targetType, targetEntity)
        if targetType != 'Shield' then
            local pos = self:GetPosition()  
            local gPos = GetSurfaceHeight( pos[1], pos[3] ) + 0.5
            local deathOrb = CreateUnit('Singularity', self:GetArmy(), pos[1], (gPos), pos[3], 0, 0, 0, 1, 'Air' )
            if self:GetLauncher() then
                self.DamageData.DamageRadius = self.DamageData.DamageRadius 
                deathOrb:PassInfoToSingularity(self.DamageData, self:GetLauncher())    
            else
                deathOrb:PassInfoToSingularity(self.DamageData, nil)
            end                       
            self:Destroy()
        else
            SuperClass.OnImpact( self, targetType, targetEntity )
        end                                   
    end,                        
}    
end
-- End of Singularity(SuperClass)


-----------------------------------------------------------------------------
-- *** Advanced Missile Retargeting Ability ***
--
-- Summary: Allows missiles to find new targets
--
-- The following is required in the projectiles script
-- 
-- local MyProjectile = import('/lua/seraphimprojectiles.lua').SLaanseTacticalMissile
-- SLaanseTacticalMissile = import('/mods/4DC_V0.8/lua/CustomAbilities/4D_ProjectileSuperClass/4D_ProjectileSuperClass.lua').Advanced_Retargeting( MyProjectile )
-- 
-- The following is required in the projectiles blueprint for the child projectiles desired, if any. 
-- Setting ProjNum = 0 prevents child projectiles from being used.
--
--     ChildProj = {
--         ProjBP = '/projectiles/SIFLaanseTacticalMissile02/SIFLaanseTacticalMissile02_proj.bp', 
--         ProjNum = 6,
--         ProjFX = {
--             '/effects/emitters/aeon_quanticcluster_split_01_emit.bp',
--             '/effects/emitters/aeon_quanticcluster_split_02_emit.bp',
--             '/effects/emitters/aeon_quanticcluster_split_03_emit.bp',
--         }  
--     },
--
-- The following is required in the launchers blueprint for proper support of OKC.
--
-- dontOKCheck = true,  --	don't do OKC check for this weapon.
----------------------------------------------------------------------------- 
-- Start of Advanced_Retargeting(SuperClass) 
function Advanced_Retargeting(SuperClass)
    return Class(SuperClass) {
     
    OnCreate = function(self)
        SuperClass.OnCreate(self)   
        self.Debug_Advanced_Retargeting = false
        self:ForkThread( self.ProjHeartBeat )  
    end,
    
    GDTT = function(self) -- Get Distance To Target (2 Vector comparison)
        local mpos = self:GetPosition()    
        local tpos = self:GetCurrentTargetPosition()
        local dist = VDist2(mpos[1], mpos[3], tpos[1], tpos[3])
        return math.floor(dist)
    end,          
    
    SRTT = function(self) -- Slant Range To Target (3 Vector comparison)       
        local dist = utilities.GetDistanceBetweenTwoEntities(self, self:GetTrackingTarget())
        return math.floor(dist)
    end,
                                    
    ProjHeartBeat = function(self)
        if not self:GetLauncher() then
            self:Destroy()
        else
            -- Store data from launcher
            if not self.Data then 
                local launcher = self:GetLauncher()                              
                for i = 1, launcher:GetWeaponCount() do
                    local wep = launcher:GetWeapon(i)
                    if wep:GetBlueprint().WeaponCategory == 'Missile' then 
                        local aiBrain = launcher:GetAIBrain() 
                        local data = {
                            Brain = aiBrain,                        
                            Weapon = wep,
                        }             
                        self:PassData(data)
                        break
                    end
                end                 
            end                
            -- Establish Globals        
            self.MyWep = self.Data.Weapon:GetBlueprint() 
            local lifeTime = self.MyWep.ProjectileLifetime or 60 
            self.RangeIncrement = self.MyWep.MaxRadius / lifeTime  
            self.LifeCounter = 1            	                               
            -- Prime Re-Targeting flag
            self.RetargetFlag = false                        
            -- Define the target filter
            self.PriList = self.MyWep.TargetPriorities                                           
            self.TargetFilter = categories.VISIBLETORECON  
            for id, cat in self.PriList do
                if cat ~= 'ALLUNITS' then 
                    self.TargetFilter = self.TargetFilter + ParseEntityCategory(cat) - categories.BENIGN - categories.CIVILIAN - categories.UNTARGETABLE
                end
            end                         
            -- Small delay to allow the missile to climb out from launcher before turning the missile towards its target       
            WaitSeconds(2)
            self:ForkThread( self.SetSmoothTurnRate )    
            while not self:BeenDestroyed() do
                if self.LifeCounter > lifeTime then
                    self:Destroy()
                elseif not self:GetTrackingTarget() then
                    if not self.RetargetFlag then
                        -- Retarget as required
                        self.RetargetFlag = true
                        self:SetMaxSpeed(self:GetBlueprint().Physics.MaxSpeed * 0.5)          
                        self:ForkThread( self.AdvRetargeting )
                    end
                elseif self:SRTT() <= 45 and self:SRTT() >= 20 then
                    self:ForkThread( self.SetSmoothTurnRate )                      
                elseif self:SRTT() < 20 and self:GetBlueprint().ChildProj.ProjNum >= 1 then
                    -- Force the projectile to splinter          
                    self:Split()
                    break                                                               
                end
                -- Update the counter
                self.LifeCounter = self.LifeCounter + 1
                WaitSeconds(1) 
            end
        end              
    end,
            
    SetSmoothTurnRate = function(self)  
        if not self:BeenDestroyed() then
            -- Degree of arc per unit of distance
            local deg = 6.36
            -- Compair distance to target alter turn rate to allow for a smooth arc  
            local rate = math.ceil((256 / math.ceil( self:GDTT() ) ) * deg)
            -- Normalize values
            if rate < 20 then
                rate = 20
            elseif rate > 120 then
                rate = 120                    
            end 
            self:SetMaxSpeed(self:GetBlueprint().Physics.MaxSpeed)                           
            self:SetTurnRate(rate)           
        end
    end, 
    
    AdvRetargeting = function(self)  
        if self.Debug_Advanced_Retargeting then
            WARN('*** Retargeting ***')            
        end                                                                 
        while not self:BeenDestroyed() and not self:GetTrackingTarget() do
            -- Calculate the radius based upon max range and lifetime left avalible            
            local radius = self.RangeIncrement * (self.MyWep.ProjectileLifetime - self.LifeCounter)
            if radius >= 30 then
                local pos = self:GetPosition()
                -- Search for all enemy units in range 
	            local targList = self.Data.Brain:GetUnitsAroundPoint( self.TargetFilter, pos, radius, 'Enemy' )
	            if targList and table.getn(targList) >= 1 then
	                for a, b in self.PriList do
	                    if self.Debug_Advanced_Retargeting then
                            WARN('#  Cat: ', b)
                        end
	                	local targDist = {}
	                    for k, v in targList do	
	                        if EntityCategoryContains( ParseEntityCategory(b), v ) then	                  
	                            table.insert(targDist, {dist = utilities.XZDistanceTwoVectors(pos, v:GetPosition()), unit = v})
	                            if self.Debug_Advanced_Retargeting then
	                                WARN('   Unit Inserted: ', v:GetUnitId())
	                            end
	                        end
	                    end
	                    if targDist and table.getn(targDist) >= 1 then
		                    -- Sort thru units by distance
		                    table.sort(targDist, sort_by('dist')) 
		                    for s, t in targDist do
		                        if t.unit:GetHealth() >=1 and (t.unit:GetCurrentLayer() == 'Land' or t.unit:GetCurrentLayer() == 'Water') then
		                            local targBp = t.unit:GetBlueprint().General    
		                            if self.Debug_Advanced_Retargeting then      
		                                WARN(' *** AdvRetargeting: New Target assigned', targBp.UnitName, t.unit:GetUnitId())                          
		                            end
		                            self:TrackTarget(false)
		                            self:SetNewTarget(t.unit)
		                            self:TrackTarget(true)
		                            if self:GetTrackingTarget() then
		                                self.RetargetFlag = false
		                                break
		                            end                               
		                        end                
		  	                end 	                                                  
		  	            end
                    end  	                                 
                end
	        else
	            self:Destroy()
	        end
	        WaitSeconds(2) 
        end                             
    end,       
    
    Split = function(self)
        -- Split effects                                          
        local FxSplitEffect = self:GetBlueprint().ChildProj.ProjFX
        for k, v in FxSplitEffect do
            CreateEmitterAtBone( self, -1, self:GetArmy(), v )
        end
        -- Do the math                                            
        local vx, vy, vz = self:GetVelocity()
        local velocity = 8
        local numProj = self:GetBlueprint().ChildProj.ProjNum 
        local angle = (2 * math.pi) / numProj 
        self.DamageData.DamageAmount = self.DamageData.DamageAmount / numProj 
        local spread = 1.0
        local tpos = self:GetCurrentTargetPosition()        
        -- Launch child projectiles 
        for i = 0, (numProj - 1) do                          
            local proj = self:CreateChildProjectile(self:GetBlueprint().ChildProj.ProjBP)
            proj:SetVelocity(vx,vy,vz)
            proj:SetVelocity(velocity)            
            proj:PassDamageData(self.DamageData)             
            -- Pass the targets ground location to the projectile and spread them around that point           
            local xTVec = tpos[1] + math.sin(i * angle) * (self.DamageData.DamageRadius * spread)
            local zTVec = tpos[3] + math.cos(i * angle) * (self.DamageData.DamageRadius * spread)
            local gTPos = GetSurfaceHeight( xTVec, zTVec )
            local modpos = {xTVec, gTPos, zTVec}  
            proj:SetNewTargetGround(modpos)
            -- Alternate the proj spread 
            if spread == 1.0 then
                spread = 1.5
            else
                spread = 1.0
            end                                                           
        end
        self:Destroy()
    end,                                           
}    
end
-- End of Advanced_Retargeting(SuperClass)