Advertisement
Guest User

Untitled

a guest
Nov 1st, 2015
116
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 18.09 KB | None | 0 0
  1. -- v0.9
  2.  
  3. controllingMainframe = 0  --the mainframe with the target prioritization card
  4. restrictSlot = 1          --ai will only control these weapons, and will orient the craft along those
  5. attackFromDistance = 750  -- distance at which do the attack run
  6. abortAttackDistance = 75 -- distance at which we disengage
  7. abortRunDistance = 300    -- distance at which we reengage
  8.  
  9. Kp = 10   --amount of control proportional to deviation
  10. Ki = 0.01  --amount of accumulation to contrast deviation (keep small!)
  11. Kd = 1   --amount of dampening proportional to oscillation
  12.  
  13. minAlt = 75
  14. maxAlt = 300
  15.  
  16.  
  17. useOptimizer = 0
  18.  
  19. function SimplexOptimizerFactory()
  20.     local this = {}
  21.     function this.CreateSimplexOptimizer(this,dims)
  22.         local simple = {}
  23.         simple.Dim=dims
  24.         simple.Simplex = {}
  25.        
  26.         for a,b in pairs(this) do
  27.             simple[a] = b
  28.         end
  29.         simple.CreateSimplexOptimizer = nil
  30.         simple.Update = simple.Initial
  31.        
  32.         return simple
  33.     end
  34.     function this.SortFirst(a,b)
  35.         if a[1]<b[1] then
  36.             return true
  37.         else
  38.             return false
  39.         end
  40.     end
  41.     function this.AddHint(this,hint)
  42.         if not this.Hint then
  43.             this.Hint = {}
  44.         end
  45.         table.insert(this.Hint,hint)
  46.     end
  47.     function this.Initial(this,result,sample) -- Initial phase, populate samples
  48.         if result then
  49.             local merge = {result,sample}
  50.             table.insert(this.Simplex,merge)
  51.         end
  52.         if this.Hint then
  53.             local thishint = table.remove(this.Hint)
  54.             if #this.Hint == 0 then
  55.                 this.Hint = nil
  56.             end
  57.             return thishint
  58.         end
  59.         if #this.Simplex > this.Dim then
  60.             table.sort(this.Simplex,this.SortFirst)
  61.             while #this.Simplex > this.Dim+1 do -- Drop worst samples to create a simplex
  62.                 table.remove(this.Simplex)
  63.             end
  64.             this.Update=this.SReflection
  65.             return this.Point(this,1) -- 1 = reflection
  66.         end
  67.         local ret = {}
  68.         for i=1,this.Dim do
  69.             table.insert(ret,0.5-math.random())
  70.         end
  71.         return ret
  72.     end
  73.     function this.Centroid(this)
  74.         local centroid = {}
  75.         local ndim = this.Dim
  76.         for i=1,ndim do
  77.             table.insert(centroid,0)
  78.         end
  79.         for i=1,ndim do
  80.             for a,b in pairs(this.Simplex[i][2]) do
  81.                 centroid[a] = centroid[a] + b
  82.             end
  83.         end
  84.         for a,b in pairs(centroid) do
  85.             centroid[a] = (b / ndim)
  86.         end
  87.         return centroid
  88.     end
  89.     function this.Point(this,pow)
  90.         local point = this.Centroid(this)
  91.         local ndim = this.Dim
  92.         for a,b in pairs(point) do
  93.             point[a] = b + pow*(b - this.Simplex[ndim+1][2][a])
  94.         end
  95.         return point
  96.     end
  97.     function this.SReflection(this,result,sample) -- Reflection stage
  98.         if result > this.Simplex[1][1] then -- If it is the best one go to expansion
  99.             this.Update = this.SExpansion
  100.             this.Reflected={result,sample}
  101.             return this.Point(this,2)
  102.         end
  103.         if result > this.Simplex[this.Dim][1] then -- if better than second worse
  104.             local ns = {result,sample}
  105.             table.insert(this.Simplex)
  106.             table.sort(this.Simplex,this.SortFirst)
  107.             table.remove(this.Simplex)
  108.             return this.Reflection(this)
  109.         end
  110.         --- here we do contraction
  111.         this.Update = this.SContraction
  112.         return this.Point(this,-0.5)
  113.     end
  114.     function this.SExpansion(this,result,sample)        
  115.         if result > this.Reflected[1] then -- if the epanded point is better than the reflected point
  116.             table.insert(this.Simplex,{result,sample})
  117.         else
  118.             table.insert(this.Simplex,this.Reflected)
  119.         end
  120.         table.sort(this.Simplex,this.SortFirst)
  121.         table.remove(this.Simplex)
  122.         this.Update = this.SReflection
  123.         return this.Point(this,1)
  124.     end
  125.     function this.SContraction(this,result,sample)
  126.         if result > this.Simplex[this.Dim+1][1] then -- If the contracted point is better than the worst point
  127.             table.insert(this.Simplex,{result,sample})
  128.             table.sort(this.Simplex,this.SortFirst)
  129.             table.remove(this.Simplex)
  130.         else -- do reduction
  131.             for i=2,this.Dim+1 do
  132.                 local arr = this.Simplex[i][2]
  133.                 for a,b in pairs(arr) do
  134.                     local x1 = this.Simplex[1][2][a]
  135.                     arr[a] = x1 + 0.5*(b-x1)
  136.                 end
  137.                 this.AddHint(this,arr)
  138.             end
  139.             while #this.Simplex > 1 do
  140.                 table.remove(this.Simplex)
  141.             end
  142.             this.Update=this.Initial
  143.             return this.Initial(this)
  144.         end
  145.     end
  146.     return this
  147. end
  148.  
  149. local function PIDFactory()
  150.     local this = {}
  151.     function this.pidupd(this,err)
  152.         local P = this.P
  153.         local I = this.I
  154.         local D = this.D
  155.         local Integrator = this.Integrator + I*err
  156.         local max = this.SatMax
  157.         local min = this.SatMin
  158.         local last = this.Last
  159.         this.Last = err
  160.         local delta = err-last
  161.         local out = P*err+D*delta+Integrator
  162.         if out > max then -- Integrator windup prevention
  163.             Integrator = this.Integrator
  164.             out = max
  165.         end
  166.         if out < min then -- Integrator windup prevention
  167.             Integrator = this.Integrator
  168.             out = min
  169.         end
  170.         this.Integrator = Integrator
  171.         return out
  172.     end
  173.     function this.pidconfig(this,p,i,d)
  174.         this.P = p
  175.         this.I = i
  176.         this.D = d
  177.     end
  178.     function this.pidlimits(this,max,min)
  179.         this.SatMax = max
  180.         this.SatMin = min
  181.     end
  182.     function this.CreatePID(this)
  183.         local pid = {}
  184.         pid.Update = this.pidupd
  185.         pid.Configure = this.pidconfig
  186.         pid.SetLimits = this.pidlimits
  187.         pid.P = 1 -- Just make it a simple passthrough for now
  188.         pid.I = 0
  189.         pid.D = 0
  190.         pid.Integrator=0
  191.         pid.Last=0
  192.         pid.SatMax = 1
  193.         pid.SatMin = -1
  194.         return pid
  195.     end
  196.     return this
  197. end
  198.  
  199. function killThatThing(I, weaponInfo, targetInfo, weaponIndex, turretSpinnerIndex)
  200.  
  201.     P = targetInfo.AimPointPosition
  202.     V = targetInfo.Velocity
  203.     WS = weaponInfo.Speed
  204.     G = I:GetGravityForAltitude(P.y).magnitude
  205.  
  206.  
  207.     T = (P - weaponInfo.GlobalPosition).magnitude / (WS*0.85) -- FIRST ESTIMATE OF FUTURE TARGET POSITION
  208.  
  209.  
  210.     FP = P + V*T  - I:GetVelocityVector()*T
  211.  -- position the target will be
  212.  
  213.     AD = FP - weaponInfo.GlobalPosition
  214.   -- direction direct to the target
  215.  
  216.  
  217.  
  218.     PJ = Mathf.Sqrt(AD.x*AD.x + AD.z*AD.z)
  219.  -- horizontal distance to the target
  220.  
  221.    
  222.     S2 = WS*WS
  223.   -- speed^2
  224.     S4 = S2*WS*WS
  225.   -- speed^4 (need these many times)
  226.     DELTA = S4 - G*(G*PJ*PJ + 2*AD.y * S2)
  227.  -- is there a solution to the parable?
  228.      
  229.  
  230.     if ( DELTA > 0 ) then
  231.         --ok we can reach it and now we have a better estimate
  232.        
  233.         AG = Mathf.Atan2(S2 - Mathf.Sqrt(DELTA),G*PJ)
  234.  --calculate angle
  235.        
  236.         --now we can calculate a better time to target using the horizontal speed
  237.         --as obtained from the firing angle
  238.        
  239. T = (P - weaponInfo.GlobalPosition).magnitude / (WS * Mathf.Cos(AG))
  240.        
  241.  
  242.         FP = P + V*T - I:GetVelocityVector()*T
  243.   --position target will be
  244.  
  245.         AD = FP - weaponInfo.GlobalPosition
  246.  -- line direct to the target position
  247.  
  248.  
  249.  
  250.         PJ = Mathf.Sqrt(AD.x*AD.x + AD.z*AD.z)
  251.   -- horizontal distance to the target
  252.  
  253.    
  254.      
  255.         DELTA = S4 - G*(G*PJ*PJ + 2*AD.y * S2)
  256.  -- check the parable solution
  257.  
  258.  
  259.         if ( DELTA > 0 ) then
  260.         --ok we can reach it and now we have a better estimate
  261.         PY = (S2 - Mathf.Sqrt(DELTA))/(G)
  262.  -- no need to calculate angle or tangent, just the elev
  263.  
  264.  
  265.  
  266.         AD.y = PY --assign new elevation to the firing direction
  267.  
  268.  
  269.         if(turretSpinnerIndex < 0) then
  270.            I:AimWeaponInDirection(weaponIndex, AD.x, AD.y, AD.z, weaponInfo.WeaponSlot)
  271.            I:FireWeapon(weaponIndex, weaponInfo.WeaponSlot)
  272.         else
  273.            I:AimWeaponInDirectionOnTurretOrSpinner(
  274.                  turretSpinnerIndex,weaponIndex, AD.x, AD.y, AD.z, weaponInfo.WeaponSlot)
  275.  
  276.          
  277.            I:FireWeaponOnTurretOrSpinner(
  278.                  turretSpinnerIndex,weaponIndex,weaponInfo.WeaponSlot)
  279.  
  280.         end
  281.  
  282.       end
  283.          
  284.     end
  285.    return AD
  286. end
  287.  
  288. function idleAround(I)
  289.     idlePosition = I:GetConstructCenterOfMass()
  290.     com = I:GetConstructCenterOfMass()
  291.     fcom = I:GetConstructCenterOfMass() + I:GetVelocityVector()
  292.     if (I:GetFriendlyCount() > 0) then
  293.         idlePosition = idlePosition / I:GetFriendlyCount()
  294.         for friendlyIndex=0, I:GetFriendlyCount() do
  295.             friendlyInfo = I:GetFriendlyInfo(friendlyIndex)
  296.             idlePosition = friendlyInfo.ReferencePosition / I:GetFriendlyCount()
  297.         end
  298.     end
  299.    
  300.     flyDirection = I:GetConstructCenterOfMass()-I:GetVelocityVector()-idlePosition
  301.         if (com.y < I:GetTerrainAltitudeForLocalPosition(com.x,0,com.z) + minAlt or
  302.          fcom.y < I:GetTerrainAltitudeForLocalPosition(fcom.x,0,fcom.z) + minAlt or
  303.          com.y < minAlt) then
  304.       flyDirection = Vector3(0,1,0)
  305.         I:LogToHud("EC")
  306.     I:Component_SetBoolLogicAll(0, true)
  307.     else
  308.     I:Component_SetBoolLogicAll(0, false)
  309.     end
  310.     flyAlong(I, flyDirection, -I:GetGravityForAltitude(I:GetConstructCenterOfMass().y) )
  311. end
  312.  
  313. factory = PIDFactory()
  314. yawPID = factory.CreatePID(factory)
  315. pitchPID = factory.CreatePID(factory)
  316. rollPID = factory.CreatePID(factory)
  317.  
  318. yawPID.SetLimits(yawPID,1,-1)
  319. pitchPID.SetLimits(pitchPID,1,-1)
  320. rollPID.SetLimits(rollPID,1,-1)
  321.  
  322. if (useOptimizer == 1) then
  323. SimpFact = SimplexOptimizerFactory()
  324.  
  325. yawOpti = SimpFact.CreateSimplexOptimizer(SimpFact,3)
  326. yawOpti.AddHint(yawOpti,{Kp,Ki,Kd})
  327. yawSample = yawOpti.Update(yawOpti,nil,nil)
  328. yawPID.Configure(yawPID,unpack(yawSample))
  329.  
  330. rollOpti = SimpFact.CreateSimplexOptimizer(SimpFact,3)
  331. rollOpti.AddHint(rollOpti,{Kp,Ki,Kd})
  332. rollSample = rollOpti.Update(rollOpti,nil,nil)
  333. rollPID.Configure(rollPID,unpack(rollSample))
  334.  
  335. pitchOpti = SimpFact.CreateSimplexOptimizer(SimpFact,3)
  336. pitchOpti.AddHint(pitchOpti,{Kp,Ki,Kd})
  337. pitchSample = pitchOpti.Update(pitchOpti,nil,nil)
  338. pitchPID.Configure(pitchPID,unpack(pitchSample))
  339.  
  340. else
  341. yawPID.Configure(yawPID,Kp,Ki,Kd)
  342. pitchPID.Configure(pitchPID,Kp,Ki,Kd)
  343. rollPID.Configure(rollPID,Kp,Ki,Kd)
  344. end
  345.  
  346.  
  347. yawScore = 0
  348. pitchScore = 0
  349. rollScore = 0
  350. accumulator = 0
  351.  
  352.  
  353. function flyAlong(I, direction, normal)
  354.     I:RequestThrustControl(0, 1)
  355.     logstr = ""
  356.      dt = 1
  357.     lastYaw = eYaw
  358.     lastPitch = ePitch
  359.     lastRoll = eRoll
  360.     --I:RequestControl(mode,type,drive)
  361.     eYaw =  Vector3.Dot(
  362.             Vector3.ProjectOnPlane(direction, I:GetConstructUpVector() ),
  363.             I:GetConstructRightVector()
  364.             )
  365.     ePitch = Vector3.Dot(
  366.             Vector3.ProjectOnPlane(direction, I:GetConstructRightVector() ),
  367.             I:GetConstructUpVector()
  368.             )
  369.     eRoll = Vector3.Dot(
  370.             Vector3.ProjectOnPlane(normal, I:GetConstructForwardVector() ),
  371.             I:GetConstructRightVector()
  372.             )
  373.      
  374.     accumulator = accumulator + 1
  375.     yawScore = yawScore + math.abs(eYaw)
  376.     pitchScore = pitchScore + math.abs(ePitch)
  377.     rollScore = rollScore + math.abs(eRoll)
  378.    
  379.     if accumulator > 100 then
  380.         if(useOptimizer == 1) then
  381.             if (yawScore > 100) then
  382.               yawSample = yawOpti.Update(yawOpti,yawScore/accumulator,yawSample)
  383.               if(not yawSample == nil ) then yawPID.Configure(yawPID,unpack(yawSample)) end
  384.             end
  385.             if(pitchScore>100) then
  386.               pitchSample = pitchOpti.Update(pitchOpti,pitchScore/accumulator,pitchSample)
  387.               if(not pithSample == nil ) then pitchPID.Configure(pitchPID,unpack(pitchSample)) end
  388.             end
  389.             if(rollScore>100) then
  390.                rollSample = rollOpti.Update(rollOpti,rollScore/accumulator,rollSample)
  391.               if(not rollSample == nil ) then rollPID.Configure(rollPID,unpack(rollSample)) end
  392.             end
  393.         else
  394.             yawPID.Configure(yawPID,Kp,Ki,Kd)
  395.             pitchPID.Configure(pitchPID,Kp,Ki,Kd)
  396.             rollPID.Configure(rollPID,Kp,Ki,Kd)
  397.         end
  398.        
  399.         pitchScore=0
  400.         rollScore=0
  401.         yawScore = 0
  402.         accumulator = 0
  403.     end
  404.    
  405.    
  406.     CYaw =  yawPID.Update(yawPID,eYaw)
  407.     CPitch =  pitchPID.Update(pitchPID,ePitch)
  408.     CRoll =  rollPID.Update(rollPID,eRoll)
  409.    
  410.     --CYaw = 0.63661977*Mathf.Atan(CYaw*10)
  411.     --CPitch = 0.63661977*Mathf.Atan(CPitch*10)
  412.     --CRoll = 0.63661977*Mathf.Atan(CRoll*10)
  413.    
  414.     if ( CYaw < 0 ) then
  415.             --turn left
  416.             I:RequestControl(2,0,-CYaw)
  417.             I:RequestControl(2,1,0)
  418.             logstr = logstr .. " left " .. -CYaw
  419.     else
  420.             --turn right
  421.             I:RequestControl(2,1,CYaw)
  422.             I:RequestControl(2,0,0)
  423.             logstr = logstr .. " right " ..CYaw
  424.     end
  425.     if ( CPitch < 0 ) then
  426.             --pitch down
  427.             I:RequestControl(2,5,-CPitch)
  428.             I:RequestControl(2,4,0)
  429.             logstr = logstr .. " down " .. -CPitch
  430.     else
  431.             --pitch up
  432.             I:RequestControl(2,4,CPitch)
  433.             I:RequestControl(2,5,0)
  434.             logstr = logstr .. " up " .. CPitch
  435.     end    
  436.            
  437.     if ( CRoll < 0 ) then
  438.             -- roll left
  439.             logstr = logstr .. " rccw " .. -CRoll
  440.             I:RequestControl(2,2,-CRoll)
  441.             I:RequestControl(2,3,0)
  442.     else
  443.             -- roll right  
  444.             I:RequestControl(2,3,CRoll)
  445.             I:RequestControl(2,2,0)
  446.             logstr = logstr .. " rcw " .. CRoll
  447.  
  448.     end
  449.     --I:LogToHud(logstr)
  450. end
  451.  
  452.  
  453. GAIN = 0 -- close in to target at max altitude
  454. SPLIT = 1 -- match target direction rolling down/up according to start alt
  455. ROLL = 2 -- if enemy vector are parallel, roll into tail
  456. TARGET = 3 -- aim to a shoot solution
  457. RUN = 4 -- too close and enemy not fast enough
  458. LOOP = 5 --enemy is faster loop on it's tail
  459.  
  460. attackState = GAIN
  461.  
  462. function performManeuver(I,AD,targetInfo,attackState)
  463.     P = targetInfo.AimPointPosition
  464.     V = targetInfo.Velocity
  465.     FP = P
  466.     TD = FP - I:GetConstructCenterOfMass()
  467.     com = I:GetConstructCenterOfMass()
  468.     fcom = I:GetConstructCenterOfMass() + I:GetVelocityVector()
  469.     normal =  -I:GetGravityForAltitude(I:GetConstructCenterOfMass().y)
  470.     if(attackState == GAIN) then
  471.         I:LogToHud("GAINING")
  472.         y = I:GetConstructCenterOfMass().y
  473.         if ( y < maxAlt - 50 ) then
  474.              AD = AD.normalized
  475.              AD.y = 1
  476.         else
  477.             AD.y = 0
  478.         end
  479.     end
  480.     if(attackState == RUN) then
  481.         V = targetInfo.Velocity
  482.        
  483.         I:LogToHud("RUNNING")
  484.         if (V.magnitude > I:GetVelocityMagnitude()*0.80) then
  485.             AD = V * -1
  486.         else
  487.             AD = TD * -1
  488.         end
  489.         y = I:GetConstructCenterOfMass().y
  490.         if ( y < maxAlt - 50 ) then
  491.              AD = AD.normalized
  492.              AD.y = 4
  493.         else
  494.             if ( y > (maxAlt - minAlt / 2) ) then
  495.                 AD = AD.normalized
  496.                 AD.y = -4
  497.              else
  498.                 AD.y=0
  499.              end
  500.         end
  501.     end
  502.     if(attackState == TARGET) then
  503.         I:LogToHud("TARGETING")
  504.         normal = TD.normalized
  505.         normal.y = 0.1
  506.     end
  507.     if(attackState == LOOP) then
  508.         I:LogToHud("LOOP")
  509.         AD = V * -1
  510.         normal = AD
  511.     end
  512.     -- emergency climb
  513.  
  514.     if (com.y < I:GetTerrainAltitudeForLocalPosition(com.x,0,com.z) + minAlt or
  515.          fcom.y < I:GetTerrainAltitudeForLocalPosition(fcom.x,0,fcom.z) + minAlt or
  516.          com.y < minAlt) then
  517.       AD = Vector3(0,1,0)
  518.         I:LogToHud("EC")
  519.       normal =  -I:GetGravityForAltitude(I:GetConstructCenterOfMass().y)
  520. I:Component_SetBoolLogicAll(0, true)
  521.     else
  522.     I:Component_SetBoolLogicAll(0, false)
  523.     end
  524.     flyAlong(I, AD, normal)
  525. end
  526.  
  527.  
  528.  
  529. function attackEnemy(I, AD, targetInfo)
  530.     P = targetInfo.AimPointPosition
  531.     V = targetInfo.Velocity
  532.     FP = P
  533.     TD = FP - I:GetConstructCenterOfMass()
  534.     distance = TD.magnitude
  535.  
  536.     if (distance > attackFromDistance) then
  537.         attackState = GAIN
  538.     else
  539.       if (distance < abortAttackDistance) then
  540.           attackState = RUN
  541.       else
  542.           if (attackState == GAIN or distance > abortRunDistance) then
  543.               attackState = TARGET
  544.           end
  545.           if (attackState == RUN and distance < abortRunDistance and Vector3.Dot(V.normalized,I:GetConstructForwardVector() )> 0.7) then
  546.              attackState = LOOP
  547.           end
  548.           if (attackState == LOOP and distance > abortAttackDistance and Vector3.Dot(V,I:GetConstructForwardVector() ) < 0) then
  549.              attackState = TARGET
  550.           end
  551.           if (attackState == LOOP and distance < abortAttackDistance and Vector3.Dot(V,I:GetConstructForwardVector() ) < 0) then
  552.              attackState = RUN
  553.           end
  554.            if (attackState == TARGET and  math.abs(Vector3.Dot(TD.normalized,I:GetConstructForwardVector() )) < 0.15 and distance < abortRunDistance) then
  555.              attackState = LOOP
  556.           end
  557.       end
  558.     end
  559.  
  560.     performManeuver(I,AD,targetInfo,attackState)
  561.  
  562. end
  563.  
  564.  
  565. function Update(I)
  566.    
  567.     targetInfo = I:GetTargetInfo(controllingMainframe, 0)
  568.     AttackDirection = nil
  569.    
  570.    
  571.    
  572.     if(targetInfo.Valid) then
  573.         for weaponIndex=0,I:GetWeaponCount() do
  574.           weaponInfo = I:GetWeaponInfo(weaponIndex)
  575.      
  576.           if ((weaponInfo.WeaponType == 4 or weaponInfo.WeaponType == 0 )
  577.                and weaponInfo.Valid and weaponInfo.PlayerCurrentlyControllingIt == false
  578.                and weaponInfo.WeaponSlot == restrictSlot) then
  579.                AttackDirection = killThatThing(I, weaponInfo, targetInfo, weaponIndex, -1)
  580.           end
  581.         end
  582.        
  583.         for turretSpinnerIndex=0,I:GetTurretSpinnerCount() do
  584.             for weaponIndex=0,I:GetWeaponCountOnTurretOrSpinner(turretSpinnerIndex) do
  585.               weaponInfo = I:GetWeaponInfoOnTurretOrSpinner(turretSpinnerIndex, weaponIndex)
  586.                 if ((weaponInfo.WeaponType == 4 or weaponInfo.WeaponType == 0 )
  587.                 and weaponInfo.Valid and weaponInfo.PlayerCurrentlyControllingIt == false
  588.                 and weaponInfo.WeaponSlot == restrictSlot ) then
  589.                 AttackDirection = killThatThing(I, weaponInfo, targetInfo, weaponIndex, turretSpinnerIndex)
  590.                 end
  591.             end
  592.         end
  593.         if (AttackDirection == nil) then
  594.             P = targetInfo.AimPointPosition
  595.             V = targetInfo.Velocity
  596.        
  597.             FP = P + V
  598.        
  599.          
  600.               I:LogToHud("No weapon configured!!")
  601.             AttackDirection = FP - I:GetConstructCenterOfMass()
  602.         end
  603.         attackEnemy(I, AttackDirection, targetInfo)
  604.     else
  605.    
  606.         idleAround(I)
  607.        I:LogToHud("IDLING")
  608.    
  609.     end
  610.        
  611. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement