-----------------------------------------------------------------------------
--- File          :  /lua/CustomAbilities/4D_Holograms/4D_Holograms.lua 
---
--- Author (s)    :  Resin_Smoker / Domino
---
--- Co-Author(s)  :  Brute51 
---
--- Special Thanks:  BulletMagnet, Mooilo, Sorian
---
--- Summary       :  HologramEmitter script:  Creates realistic hologram units 
---                  UnitHolograms script: Unit class used to create hologram units 
---
--- Copyright © 2011 4DC_V0.8  All rights reserved.
-----------------------------------------------------------------------------
---
--- The following is required in the units script for Hologram Emitter 
--- This calls into action the hologram scripts for TConstructionUnit 
---
--- TConstructionUnit = import('/mods/4DC_V0.8/lua/CustomAbilities/4D_Holograms/4D_Holograms.lua').HologramEmitter( TConstructionUnit ) 
---
-----------------------------------------------------------------------------
---
--- The following is required in the unit blueprints for Advanced Jamming 
--- This sets the parimeters of each jamming unit. 
---
--- UNIT BP: 
---
---   Intel = { 
---       Holograms = { 
---           DisabledInTransport = true,
---           MaxDistance = 32,
---           MinDistance = 10,    
---           NumT1Engineer = 1,
---           NumT2Engineer = 0,            
---           NumT1Ground = 0,
---           NumT2Ground = 0, 
---           NumT1Sea = 0,                     
---           StartEnabled = true,
---       }, 
---   }, 
---
---   ToggleCaps = { 
---       -- abusing the intel toggle here, so we can use the onintelenabled events 
---       RULEUTC_IntelToggle = true,
---       -- provide for intel toggle so the player can toggle off the view of their own holograms
---       RULEUTC_SpecialToggle = true, 
---   }, 
---
---   OrderOverrides = { 
---       -- faking the jamming icon 
---       RULEUTC_IntelToggle = { 
---           bitmapId = 'advanced jamming', 
---           helpText = 'toggle_jamming', 
---       },
---       RULEUTC_SpecialToggle = { 
---           bitmapId = 'intel-counter',       
---           helpText = 'View Holograms',
---       },   
---   }, 
---
-----------------------------------------------------------------------------
---
---   This script snippet is required to be within the /lua/sim/Units.lua. This is borrowed from the CBFP. It 
---   adds a unit event that's fired when the unit is picked up by a transport or when it's dropped from one. 
---
---   OnTransportAttach = function(self, attachBone, unit) 
---       oldUnit.OnTransportAttach(self, attachBone, unit) 
---       unit:OnAttachedToTransport(self) 
---   end, 
---
---   OnAttachedToTransport = function(self, transport) 
---   end, 
---
---   OnTransportDetach = function(self, attachBone, unit) 
---       oldUnit.OnTransportDetach(self, attachBone, unit) 
---       unit:OnDetachedToTransport(self) 
---   end, 
---
---   OnDetachedToTransport = function(self, transport) 
---   end,    
---
-----------------------------------------------------------------------------
 
-- Start of HologramEmitter(SuperClass)
function HologramEmitter(SuperClass) 
    return Class(SuperClass) {
    
    OnCreate = function(self,builder,layer)
        -- Sets up table to track the holograms 
        if not self.HologramTable then
            self.HologramTable = {}
        end 
        -- Initializes global variable for the jammers unit motion state         
        if not self.SpeedState then
           self.SpeedState = nil
           self.OldState = nil 
        end                                
        SuperClass.OnCreate(self,builder,layer)
    end,

    OnStopBeingBuilt = function(self,builder,layer)               
        -- Initializes global variable for the View of Holograms
		--DOMINO-- i've turned these off intially, so they are NOT visable.
        if not self.ViewHolograms then
            self.ViewHolograms = false 
        end   

		--Domino-- Add our extra toggle caps.. 
		self:AddExtraCap('RULEETC_HologramToggle')
		self:AddExtraCap('RULEETC_HologramShowToggle')
		        
        -- Ensures the jammer comes on if specified within the units BP
		local StartOn = self:GetBlueprint().Intel.Holograms.StartEnabled
        if StartOn then
            if not self:GetJammingStatus() then
				--DOMINO-- Turn the toggle on.
				self:SetExtraBit('RULEETC_HologramToggle', false)
            end
        end   
                 
        SuperClass.OnStopBeingBuilt(self,builder,layer)     
    end,

    OnKilled = function(self, instigator, type, overkillRatio)
        if self:GetJammingStatus() then 
            self:SetJammingStatus(false)
        end
        SuperClass.OnKilled(self, instigator, type, overkillRatio)
    end,

    OnAttachedToTransport = function(self, transport) -- new event, borrowed from CBFP v3 (and up)
        SuperClass.OnAttachedToTransport(self, transport)
        self.PrevJammingStatus = self:GetJammingStatus()
        if self:GetBlueprint().Intel.Holograms.DisabledInTransport then
            self:SetJammingStatus(false)
        end
    end,

    OnDetachedToTransport = function(self, transport) -- new event, borrowed from CBFP v3 (and up)
        SuperClass.OnDetachedToTransport(self, transport)
        self:SetJammingStatus(self.PrevJammingStatus)
    end,
    
    GetJammingStatus = function(self)
        return self.JammingActive
    end,  
    
	OnExtraToggleClear = function(self, ToggleName)
		SuperClass.OnExtraToggleClear(self, ToggleName)
		
		if ToggleName == 'RULEETC_HologramToggle' then			
			if not self:GetJammingStatus() then 
				self:SetJammingStatus(true)
			end
		end	
		
		if ToggleName == 'RULEETC_HologramShowToggle' then
			self.ViewHolograms = true
            if table.getn(self.HologramTable) > 0 then
                for k, v in self.HologramTable do           
                    v:SetVizToAllies('Intel') 
                    v:SetVizToFocusPlayer('Intel') 
                    v:SetVizToNeutrals('Intel') 
                end
            end           
		end	
	end,
	
	OnExtraToggleSet = function(self, ToggleName)
		SuperClass.OnExtraToggleSet(self, ToggleName)
		
		if ToggleName == 'RULEETC_HologramToggle' then			
			if self:GetJammingStatus() then 
				self:SetJammingStatus(false)
			end
		end	
		
		if ToggleName == 'RULEETC_HologramShowToggle' then
			self.ViewHolograms = false
            if table.getn(self.HologramTable) > 0 then
                for k, v in self.HologramTable do           
                    v:SetVizToAllies('Never') 
                    v:SetVizToFocusPlayer('Never') 
                    v:SetVizToNeutrals('Never') 
                end
            end           
		end	
	end,

    SetJammingStatus = function(self, enabled)
        if enabled then
            -- Add holograms and economy on jammer enable      
            self:SetMaintenanceConsumptionActive()
			self.JammingActive = true
            self:CreateHolograms()
        else
            -- Remove holograms and economy on jammer disable      
            self:SetMaintenanceConsumptionInactive()
            if table.getn(self.HologramTable) > 0 then
                for k, v in self.HologramTable do
                    if v:GetParent() == self then
                       v:Destroy()
                    end
                end
				
            end 
            -- Reset hologram table
			self.JammingActive = false
            self.HologramTable = {}
        end
    end,
 
    -- Add hologram BPs here
    HologramBPs = {
        -- Engineer Holograms
        engineerT1 = {
             'hologram_t1_engineer',
        },
        engineerT2 = {
             'hologram_t2_engineer',
        },        
        -- Ground holograms
        groundT1 = {
            'hologram_t1_antiair','hologram_t1_artillery','hologram_t1_bot','hologram_t1_scout','hologram_t1_tank',
        },
        groundT2 = {
            'hologram_t2_flak','hologram_t2_gatbot','hologram_t2_hovertank','hologram_t2_launcher','hologram_t2_tank',
        }, 
        seaT1 = {
            'hologram_t1_frigate','hologram_t1_sub',
        },                      
    },      
    
    CreateHolograms = function(self)
        -- Local variables needed    
        local spawnHologram
        local basePos = self:GetPosition()
        local bp = self:GetBlueprint().Intel.Holograms
        local dist = bp.MaxDistance * 0.75 or 20
        local NumT1Engineer = bp.NumT1Engineer or 0
        local NumT2Engineer = bp.NumT2Engineer or 0        
        local NumT1Ground = bp.NumT1Ground or 0
        local NumT2Ground = bp.NumT2Ground or 0  
        local NumT1Sea = bp.NumT1Sea or 0                    
        local NumTotalHolograms = NumT1Engineer + NumT2Engineer +  NumT1Ground + NumT2Ground + NumT1Sea
                             
        -- This function determines what hologram should be created, creates it and then returns that unit
        local function CreateHologramUnit(HologramNum)
            local HologramBP
        -- T1 Engineer 
            if HologramNum <= NumT1Engineer then
                HologramBP = self.HologramBPs['engineerT1'][ Random( 1, table.getn( self.HologramBPs['engineerT1'] )) ]
        -- T2 Engineer
            elseif HologramNum <= (NumT1Engineer + NumT2Engineer) then
                HologramBP = self.HologramBPs['engineerT2'][ Random( 1, table.getn( self.HologramBPs['engineerT2'] )) ]  
        -- T1 ground              
            elseif HologramNum <= (NumT1Engineer + NumT2Engineer + NumT1Ground) then
                HologramBP = self.HologramBPs['groundT1'][ Random( 1, table.getn( self.HologramBPs['groundT1'] )) ]
        -- T2 ground
            elseif HologramNum <= (NumT1Engineer + NumT2Engineer + NumT1Ground + NumT2Ground) then
                HologramBP = self.HologramBPs['groundT2'][ Random( 1, table.getn( self.HologramBPs['groundT2'] )) ]
        -- T1 sea              
            elseif HologramNum <= (NumT1Engineer + NumT2Engineer + NumT1Ground + NumT2Ground + NumT1Sea) then
                HologramBP = self.HologramBPs['seaT1'][ Random( 1, table.getn( self.HologramBPs['seaT1'] )) ]                                              
            else
                return nil
            end
            return CreateUnitHPR(HologramBP, self:GetArmy(), basePos[1]+Random(-dist, dist), basePos[2], basePos[3]+ Random(-dist, dist), 0, 0, 0)
        end
        
        -- Creates the holigrams 
        for i = 1, NumTotalHolograms do 
            -- Spawns the replacement Hologram and sets the jamming unit as its parent
            spawnHologram = CreateHologramUnit(i) 
            spawnHologram:SetCreator(self) 
            spawnHologram:SetParent(self) 
            
            -- Sets the visability options for the holograms dependant on the players toggle state
            -- Avalible options: 'Never','Intel','Always' 
                                                   
            if self.ViewHolograms == true then     
                spawnHologram:SetVizToAllies('Intel') 
                spawnHologram:SetVizToFocusPlayer('Intel') 
                spawnHologram:SetVizToNeutrals('Intel') 
            else
                spawnHologram:SetVizToAllies('Never') 
                spawnHologram:SetVizToFocusPlayer('Never') 
                spawnHologram:SetVizToNeutrals('Never')                 
            end
            -- Allow all enemies to see the Holograms so long as they have L.O.S or radar
            spawnHologram:SetVizToEnemies('Intel') 
            
            -- Add the new Hologram to a table so we can manipulate it later on                                
            table.insert(self.HologramTable, spawnHologram)                      
            spawnHologram:SetUpPatrol() 
            self.Trash:Add(spawnHologram) 
        end          
    end,
    
    ReplaceHologram = function(self, hologram)
        if not self:IsDead() then
            local spawnHologram
            local bp = self:GetBlueprint().Intel.Holograms
            local basePos = self:GetPosition()
            local dist = bp.MaxDistance * 0.75 or 20
            -- Spawns the replacement Hologram and sets the jamming unit as its parent
            spawnHologram = CreateUnitHPR(hologram, self:GetArmy(), basePos[1]+Random(-dist, dist), basePos[2], basePos[3]+ Random(-dist, dist), 0, 0, 0)     
            spawnHologram:SetCreator(self)
            spawnHologram:SetParent(self) 
            
            -- Sets the visability options for the holograms dependant on the players toggle state
            -- Avalible options: 'Never','Intel','Always'            
            if self.ViewHolograms == true then          
                spawnHologram:SetVizToAllies('Intel') 
                spawnHologram:SetVizToFocusPlayer('Intel') 
                spawnHologram:SetVizToNeutrals('Intel') 
            else
                spawnHologram:SetVizToAllies('Never') 
                spawnHologram:SetVizToFocusPlayer('Never') 
                spawnHologram:SetVizToNeutrals('Never')                 
            end
            spawnHologram:SetVizToEnemies('Intel') 
            
            -- Add the new Hologram to a table so we can manipulate it later on                                  
            table.insert(self.HologramTable, spawnHologram)
            spawnHologram:SetUpPatrol()
            self.Trash:Add(spawnHologram)            
        end           
    end,
    
   OnMotionHorzEventChange = function(self,new,old)
       -- Updates the jammers movement state for later use by the holograms KeepCloseToParentThread
       if not self:IsDead() then
           self.SpeedState = new
       end
       SuperClass.OnMotionHorzEventChange(self,new,old)
   end,       
}
end 
-- End of HologramEmitter(SuperClass) --

-- Start of UnitHolograms(SuperClass) -- 
local RandomFloat = import('/lua/utilities.lua').GetRandomFloat 
function UnitHolograms(SuperClass) 
    return Class(SuperClass) { 

    OnCreate = function(self,builder,layer) 
        -- Disables weapons and selectability 
        self:SetAllWeaponsEnabled(false) 
        self:SetUnSelectable(true)
        self:SetCollisionShape('Sphere', 0, 0, 0, 1.0)
        SuperClass.OnCreate(self,builder,layer) 
    end, 

    SetParent = function(self, parent) 
        self.Parent = parent 
    end, 

    GetParent = function(self) 
        return self.Parent or nil 
    end, 

    SetUpPatrol = function(self) 
        self.ParentBP = self.Parent:GetBlueprint().Intel.Holograms 
        self.PrtMaxDist = self.ParentBP.MaxDistance  
        self.PrtMinDist = self.ParentBP.MinDistance        
        -- Notes the parents speed values for later comparison          
        if EntityCategoryContains(categories.AIR, self.Parent) then 
            self.ParentSpeed = self.Parent:GetBlueprint().Air.MaxAirspeed 
        else  
            self.ParentSpeed = self.Parent:GetBlueprint().Physics.MaxSpeed 
        end                
        -- Notes the holograms speed values for later comparison 
        if EntityCategoryContains(categories.AIR, self) then 
            self.MyMaxSpeed = self:GetBlueprint().Air.MaxAirspeed 
        else  
            self.MyMaxSpeed = self:GetBlueprint().Physics.MaxSpeed 
        end                 
        -- Choose the right speed multi for the units involved            
        if self.ParentSpeed > self.MyMaxSpeed then 
            self.SpeedMulti = self.ParentSpeed / self.MyMaxSpeed
        else 
            self.SpeedMulti = 1 
        end               
        -- Multi-purpuse booleans, used to prevent issuing same orders twice                                              
        self.Patrol = false                                                                   
        -- periodically checks distance to parent    
        self:ForkThread(self.KeepCloseToParentThread) 
    end, 
 
    KeepCloseToParentThread = function(self)                                                                  
        while not self:BeenDestroyed() and not self.Parent:IsDead() do 
            -- Calculate the offset factor to ensure hologram can travel in parallel formation with jammer 
            local myX, myY, myZ = unpack(self:GetPosition())              
            local prtX, prtY, prtZ = unpack(self.Parent:GetPosition())        
            local dist = VDist2(myX, myZ, prtX, prtZ)            
            local offSet = myX - prtX    
           
            -- Dont get too close or too far away
            if dist < self.PrtMinDist or dist > self.PrtMaxDist then
                self:Destroy() 
            -- Parent is moving fast so keep up                                                                         
            elseif self.Parent.SpeedState == 'TopSpeed' and not EntityCategoryContains(categories.AIR, self) then   
                self.Patrol = false                                                                                         
                IssueClearCommands({self})                
                local parentPos = self.Parent:CalculateWorldPositionFromRelative({offSet, 0, Random(self.ParentBP.MaxDistance * 0.5, self.ParentBP.MaxDistance)}) 
                IssueMove({self}, parentPos) 
                self:SetHologramSpeed(dist)                                                 
            elseif self.Patrol == false and self.Parent.SpeedState != 'TopSpeed' and self.Parent.OldState == self.Parent.SpeedState then 
                -- when unit comes back in range issue a new patrol                
                self.Patrol = true            
                self:ForkThread(self.IssueRandomPatrol)                 
            end             
            -- Remember the past movement state
            if self.Parent.SpeedState then
                self.Parent.OldState = self.Parent.SpeedState
            end                                
            -- Time between checks in seconds             
            WaitTicks(Random(5 , 10))           
        end 
    end,
         
    IssueRandomPatrol = function(self) 
        -- Randomly determines hologram patrol within the preset limits 
        if not self:BeenDestroyed() and not self.Parent:IsDead() then
            -- Remove prior commands 
            IssueClearCommands({self})
            -- Have AIR Holograms guard the parent 
            if EntityCategoryContains(categories.AIR, self) then 
                IssueGuard({self}, self.Parent) 
            else            
                local holoPos = self:GetPosition() 
                local emtPos = self.Parent:GetPosition() 
                local span = math.floor( VDist2(holoPos[1], holoPos[3], emtPos[1], emtPos[3]) ) * 0.5
                local minRange =  math.floor( self.PrtMinDist * 1.0 )
                local maxRange =  math.ceil( self.PrtMaxDist * 0.9 )     
                local numPatrols = Random(4, 4) 
                self.PatTable = {}                              
                for k = 1,numPatrols do 
                    while not self:BeenDestroyed() and not self.Parent:IsDead() and table.getsize(self.PatTable) < k do
                        local modPos = Vector( (holoPos[1]+ RandomFloat(-span, span)), holoPos[2], (holoPos[3]+ RandomFloat(-span, span)) ) 
                        local modDist = math.floor( VDist2(modPos[1], modPos[3], emtPos[1], emtPos[3]) )                       
                        --WARN('   Mod Dist:', modDist)
                        -- Ensure the patrols are not too close together and they are within the min and max ranges
                        if table.getsize(self.PatTable) > 0 then
                            local validPat = true                        
                            for k, v in self.PatTable do
                                local patDist = math.floor( VDist2(modPos[1], modPos[3], v[1], v[3]) )
                                if not ( modDist > minRange and modDist < maxRange ) then
                                    validPat = false
                                    --WARN('   Outside Range')
                                    break
                                else
                                    --WARN('   patDist ', patDist)
                                    if patDist < 3  then
                                        validPat = false
                                        break
                                    end                                                                   
                                end
                            end
                            if validPat == true then
                                --WARN('   valid pat, performing insert')
                                table.insert(self.PatTable, modPos)                                 
                            end                            
                        elseif modDist > minRange and modDist < maxRange then
                        --WARN('   Table size ZERO, peforming first insert')
                            table.insert(self.PatTable, modPos) 
                        end
                    --    WaitTicks(1)
                    end                
                end 
                if not self:BeenDestroyed() then
                    if table.getsize(self.PatTable) > 0 then
                        --WARN(' *** Patrol Issued *** ', 'Num patrols:', numPatrols, ' Table size:', table.getsize(self.PatTable))
                        for k, v in self.PatTable do
                            IssuePatrol({self}, v )
                        end 
                    end
                end                  
            end 
            self:SetHologramSpeed() 
        end 
    end, 
    
    SetHologramSpeed = function(self, distance) 
        if not self:BeenDestroyed() and not self.Parent:IsDead() then 
            if EntityCategoryContains(categories.AIR, self) then
                self:SetSpeedMult(1.0) 
                self:SetAccMult(1.0) 
                self:SetTurnMult(1.0)
            else 
                if self.Parent.SpeedState == 'TopSpeed' and distance >= (self.PrtMaxDist * 0.75) then 
                    self:SetSpeedMult(self.SpeedMulti * 2 ) 
                    self:SetAccMult(1.5) 
                    self:SetTurnMult(1.5)  
                elseif self.Parent.SpeedState == 'TopSpeed' then 
                    self:SetSpeedMult(self.SpeedMulti * 1.5) 
                    self:SetAccMult(1.25) 
                    self:SetTurnMult(1.25)                           
                else 
                    self:SetSpeedMult(self.SpeedMulti) 
                    self:SetAccMult(1.0) 
                    self:SetTurnMult(1.0)                        
                end 
            end 
        end   
    end,
     
    OnDamage = function(self, instigator, amount, vector, damagetype) 
        self:Destroy() 
    end, 

    OnCollisionCheck = function(self, other, firingWeapon)
        self:Destroy()      
    end,          
    
    OnDestroy = function(self, instigator, type, overkillRatio)   
        -- Clears the current hologram commands if any
        IssueClearCommands(self)       
        -- Clears the offending hologram from the parents table
        if self.Parent and not self.Parent:IsDead() and self.Parent:GetJammingStatus() then
            table.removeByValue(self.Parent.HologramTable, self)
            local hologram = self:GetUnitId() 
            self.Parent:ReplaceHologram(hologram)
            self.Parent = nil
        end
        SuperClass.OnDestroy(self, instigator, type, overkillRatio)
    end,                  
                     
}
end 
-- End of UnitHolograms(SuperClass)