local alert_range = {} local watch_range = {} local turn_speed = {} local safe_range = {} local call_range = {} local attack_range = {} local health = {} ai_bot_state = {} local pathindex = {} local pathpoint = {} local pathdirection = {} local destx = {} local desty = {} local destz = {} local anim = {} local hitplayer = {} local ismelee = {} local dodge = {} local dodgedrecently = {} local persuetime = {} local mypersuetime = {} local movemod = {} local turnmod = {} local checkdelay = 10 local mycheck = {} local isgangmember = {} local sqrt = math.sqrt local rad = math.rad U = U or require "scriptbank\\utillib" --store the animation frames here --allows multiple animations for the same action, it will pick 1 randomly --so idle[1] and idle[2] are different animations etc --idle[1][1] is the start frame of the first idle animation --idle[2][1] is the start frame of the second idle animation --idle[1][2] is the end frame of the first idle animation --attack[1][3] is the frame to apply damage after local idle = {} --when just standing and relaxed idle[1] = {} idle[1][1] = 1000 idle[1][2] = 1282 local walk = {} --when relaxed and following a path walk[1] = {} walk[1][1] = 1290 walk[1][2] = 1319 local watch = {} --when keeping an eye on the player watch[1] = {} watch[1][1] = 900 watch[1][2] = 999 local run = {} --when alert and chasing the player run[1] = {} run[1][1] = 795 run[1][2] = 811 local attack = {} --when attacking the player attack[1] = {} attack[1][1] = 100 attack[1][2] = 150 attack[1][3] = 0 --ignore this if not melee local stepleft = {} --when dodging the player's attacks stepleft[1] = {} stepleft[1][1] = 795 stepleft[1][2] = 811 local stepright = {} --when dodging the player's attacks stepright[1] = {} stepright[1][1] = 795 stepright[1][2] = 811 --gang members will stop and keep an eye on the player when he's nearby --and attack if he gets too close or if attacked first function gangmember_init(e) --ranges are based on % of view range in editor properties --how close player can get before AI stops and watches him watch_range[e] = 30 --if player gets too close the AI will go aggro and attack alert_range[e] = 2 --calls all other gangmembers within this range (alerts nearby gangmembers to help) call_range[e] = 200 --how far away the AI can attack from (melee attacks should be a lot lower) attack_range[e] = 100 --when player is outside this range for a while the AI will stop chasing safe_range[e] = 130 --how long the AI will keep trying to find the player for after he moves out of range or is no longer visible etc persuetime[e] = 6 --adjusts how smoothly the AI will turn while patrolling turn_speed[e] = 4 --if character has a gun set ismelee[e] = false otherwise set it to true for melee attacks (animation based) ismelee[e] = false --if you want the character to use dodge animations set dodge[e] = true otherwise set it to false dodge[e] = false ai_bot_state[e] = "" isgangmember[e] = e SetPreExitValue(e,0) CharacterControlManual(e) --CharacterControlLimbo(e) local AIObjNo = g_Entity[e]['obj'] dodgedrecently[e] = 60 AISetEntityControl(AIObjNo,AI_MANUAL) SetCharacterSoundSet(e) ai_bot_oldhealth[e] = g_Entity[e]['health'] mycheck[e] = 0 watch_range[e] = watch_range[e] / 100 alert_range[e] = alert_range[e] / 100 call_range[e] = call_range[e] / 100 attack_range[e] = attack_range[e] / 100 safe_range[e] = safe_range[e] / 100 movemod[e] = 0 turnmod[e] = 0 end function gangmember_main(e) local ent = g_Entity[e] local AIObjNo = ent.obj local state = ai_bot_state[e] if state == "" then pathindex[e], pathpoint[e], pathdirection[e] = FindPath(e) if pathindex[e] > -1 then ai_bot_state[e] = "patrol" else ai_bot_state[e] = "idling" end destx[e] = ent.x desty[e] = ent.y destz[e] = ent.z elseif state == "idling" then if ent.animating ~= 1 then ModulateSpeed(e,1) anim[e] = math.random(1,#idle) SetAnimationFrames(idle[anim[e]][1],idle[anim[e]][2]) PlayAnimation(e) g_Entity[e]['animating'] = 1 end movemod[e] = 0 turnmod[e] = 0 local detect = DectectPlayer(e) if detect == 1 or detect == 3 then ai_bot_state[e] = "alerted" elseif detect == 2 then ai_bot_state[e] = "watch" end elseif state == "patrol" then destx[e], desty[e], destz[e] = FollowPath(e) if ent.animating ~= 2 then ModulateSpeed(e,1) anim[e] = math.random(1,#walk) SetAnimationFrames(walk[anim[e]][1],walk[anim[e]][2]) LoopAnimation(e) g_Entity[e]['animating'] = 2 end movemod[e] = 1 turnmod[e] = 1 local detect = DectectPlayer(e) if detect == 1 or detect == 3 then ai_bot_state[e] = "alerted" elseif detect == 2 then ai_bot_state[e] = "watch" end elseif state == "watch" then destx[e] = g_PlayerPosX desty[e] = g_PlayerPosY destz[e] = g_PlayerPosZ if ent.animating ~= 5 then ModulateSpeed(e,1) anim[e] = math.random(1,#watch) SetAnimationFrames(watch[anim[e]][1],watch[anim[e]][2]) LoopAnimation(e) g_Entity[e]['animating'] = 5 end movemod[e] = 0 turnmod[e] = 1 RotateToPlayerSlowly(e,3) LookAtPlayer(e) local detect = DectectPlayer(e) if detect == 1 or detect == 3 then ai_bot_state[e] = "alerted" end elseif state == "alerted" then PlayCharacterSound(e,"onAlert") ai_bot_state[e] = "call nearby" elseif state == "call nearby" then local x1,y1,z1 = GetEntityPosAng(e) for a,b in pairs (isgangmember) do local ostate = ai_bot_state[a] if ostate == "idling" or ostate == "watch" or ostate == "patrol" then local x2,y2,z2 = GetEntityPosAng(a) if GetDistance(x1,y1,z1,x2,y2,z2) < AIGetEntityViewRange(AIObjNo) * call_range[e] then ai_bot_state[a] = "call nearby" end end end ai_bot_state[e] = "prep move to attack" elseif state == "prep move to attack" then mypersuetime[e] = 0 dodgedrecently[e] = 60 destx[e] = g_PlayerPosX desty[e] = g_PlayerPosY destz[e] = g_PlayerPosZ ai_bot_state[e] = "move to attack" elseif state == "move to attack" then if ent.animating ~= 3 then ModulateSpeed(e,1.4) anim[e] = math.random(1,#run) SetAnimationFrames(run[anim[e]][1],run[anim[e]][2]) LoopAnimation(e) g_Entity[e]['animating'] = 3 end movemod[e] = 1 turnmod[e] = 1 dodgedrecently[e] = dodgedrecently[e] - 1 local rnd = 0 if U.PlayerLookingAtObj(AIObjNo, 3000, 0) == true then if math.random(10,10) == 10 or ent.health < ai_bot_oldhealth[e] or dodgedrecently[e] < 1 then rnd = math.random(-2,#stepleft) if rnd > 0 then anim[e] = math.random(1,#stepleft) ModulateSpeed(e,1) SetAnimationFrames(stepleft[anim[e]][1],stepleft[anim[e]][2]) local x1,y1,z1 = GetEntityPosAng(e) local ay = g_PlayerAngY+180 local ox,oy,oz = U.Rotate3D(-AIGetEntitySpeed(AIObjNo)/2,0,AIGetEntitySpeed(AIObjNo)/4, 0,rad(ay),0) destx[e] = x1+ox desty[e] = y1+oy destz[e] = z1+oz else rnd = math.random(-1,#stepright) if rnd > 0 then ModulateSpeed(e,1) anim[e] = math.random(1,#stepright) SetAnimationFrames(stepright[anim[e]][1],stepright[anim[e]][2]) local x1,y1,z1 = GetEntityPosAng(e) local ay = g_PlayerAngY+180 local ox,oy,oz = U.Rotate3D(AIGetEntitySpeed(AIObjNo)/2,0,AIGetEntitySpeed(AIObjNo)/4, 0,rad(ay),0) destx[e] = x1+ox desty[e] = y1+oy destz[e] = z1+oz end end if rnd > 0 then movemod[e] = 1 turnmod[e] = 1 PlayAnimation(e) g_Entity[e]['animating'] = 5 ai_bot_state[e] = "dodging" end end end if ent.health < ai_bot_oldhealth[e] then ai_bot_oldhealth[e] = ent.health end GetEntityPlayerVisibility(e) if ent.plrvisible == 1 and GetPlayerDistance(e) < 1500 then destx[e] = g_PlayerPosX desty[e] = g_PlayerPosY destz[e] = g_PlayerPosZ local playerdist = GetPlayerDistance(e) if playerdist < AIGetEntityViewRange(AIObjNo) * attack_range[e] and rnd < 1 then ai_bot_state[e] = "attack" end else local x1,y1,z1 = GetEntityPosAng(e) if GetDistance(x1,y1,z1,destx[e], desty[e], destz[e]) < 70 then destx[e] = g_PlayerPosX desty[e] = g_PlayerPosY destz[e] = g_PlayerPosZ movemod[e] = 0.1 turnmod[e] = 1 end mypersuetime[e] = mypersuetime[e] + GetElapsedTime() if mypersuetime[e] > persuetime[e] or g_PlayerHealth < 1 then if pathindex[e] > -1 then ai_bot_state[e] = "patrol" else ai_bot_state[e] = "idling" end end end elseif state == "dodging" then if ent.animating == 0 then movemod[e] = 1 turnmod[e] = 1 ai_bot_state[e] = "prep move to attack" end elseif state == "attack" then movemod[e] = 0 turnmod[e] = 1 destx[e] = g_PlayerPosX desty[e] = g_PlayerPosY destz[e] = g_PlayerPosZ hitplayer[e] = false if ent.animating ~= 4 then ModulateSpeed(e,1) anim[e] = math.random(1,#attack) SetAnimationFrames(attack[anim[e]][1],attack[anim[e]][2]) PlayAnimation(e) g_Entity[e]['animating'] = 4 end ai_bot_state[e] = "attacking" elseif state == "attacking" then if ent.animating == 0 then if g_PlayerHealth > 0 then if math.random(1,10) == 10 then ai_bot_state[e] = "alerted" else ai_bot_state[e] = "prep move to attack" end else if pathindex[e] > -1 then ai_bot_state[e] = "patrol" else ai_bot_state[e] = "idling" end end else if ismelee[e] == true then if GetAnimationFrame(e) >= attack[anim[e]][3] and not hitplayer[e] then HurtPlayer(e, attackdamage[anim[e]]) hitplayer[e] = true end else FireWeapon(e) end end end AIEntityGoToPosition(AIObjNo, destx[e], desty[e], destz[e]) SetRotationYSlowly(e,AIGetEntityAngleY(AIObjNo),25.0) local movespeed = AIGetEntitySpeed(AIObjNo) MoveForward(e,movespeed * movemod[e]) AISetEntityPosition(AIObjNo,GetEntityPositionX(e),GetEntityPositionY(e),GetEntityPositionZ(e)) --SetRotation(e,0,AIGetEntityAngleY(AIObjNo),0) --PromptLocal(e,"state = "..state..", health = "..g_Entity[e].health) end function gangmember_preexit(e) SetPreExitValue(e,2) end function gangmember_exit(e) end function DectectPlayer(e) mycheck[e] = mycheck[e] + 1 if mycheck[e] < checkdelay then return 0 else mycheck[e] = 0 end local playerdist = GetPlayerDistance(e) local ent = g_Entity[e] local AIObjNo = ent.obj GetEntityPlayerVisibility(e) if (ent.plrvisible == 1 and playerdist < AIGetEntityViewRange(AIObjNo) * alert_range[e]) then return 1 elseif ent.plrvisible == 1 and playerdist < AIGetEntityViewRange(AIObjNo) * watch_range[e] then return 2 elseif AIGetEntityHeardSound(AIObjNo) == 1 or ent.health < ai_bot_oldhealth[e] then return 3 else return 0 end end function GetDistance(x1,y1,z1,x2,y2,z2) local x3 = x2-x1 local y3 = y2-y1 if y3 > 100 then y3 = y3 * 4 end local z3 = z2-z1 local dx = x3*x3 local dy = y3*y3 local dz = z3*z3 return sqrt(dx + dy + dz) end function FindPath(e) local pi = -1 local pp = 0 local pClosest = 999999 for pa = 1, AIGetTotalPaths(), 1 do for po = 1 , AIGetPathCountPoints(pa), 1 do local pDX = g_Entity[e]['x'] - AIPathGetPointX(pa,po) local pDZ = g_Entity[e]['z'] - AIPathGetPointZ(pa,po) local pDist = pDX*pDX+pDZ*pDZ if pDist < pClosest and pDist < 6000 then pClosest = pDist pi = pa pp = po end end -- po end -- pa local pd = math.random(0,1) return pi, pp, pd end function FollowPath(e) local px = AIPathGetPointX(pathindex[e],pathpoint[e]) local pz = AIPathGetPointZ(pathindex[e],pathpoint[e]) local tDistX = g_Entity[e]['x'] - px local tDistZ = g_Entity[e]['z'] - pz local DistFromPath = tDistX*tDistX+tDistZ*tDistZ if DistFromPath < 4000 then if pathdirection[e] == 1 then pathpoint[e] = pathpoint[e] + 1 local maxp = AIGetPathCountPoints(pathindex[e]) if pathpoint[e] > maxp then pathpoint[e] = maxp - 1 pathdirection[e] = 0 end else pathpoint[e] = pathpoint[e] - 1 if pathpoint[e] < 1 then pathpoint[e] = 2 pathdirection[e] = 1 end end px = AIPathGetPointX(pathindex[e],pathpoint[e]) pz = AIPathGetPointZ(pathindex[e],pathpoint[e]) end local py = GetTerrainHeight(px,pz) return px,py,pz end function RotateToPoint(e,x,z) if g_Entity[e] ~= nil and x > 0 and z > 0 then local destx = x - g_Entity[e]['x'] local destz = z - g_Entity[e]['z'] local angle = math.atan2(destx,destz) angle = angle * (180.0 / math.pi) if angle < 0 then angle = 360 + angle elseif angle > 360 then angle = angle - 360 end SetRotation(e,0,angle,0) return angle end end function RotateToPointSlowly(e,x,z,v) if g_Entity[e] ~= nil and x > 0 and z > 0 then if v == nil then v = 1 end local destx = x - g_Entity[e]['x'] local destz = z - g_Entity[e]['z'] local angleneeded = math.atan2(destx,destz) angleneeded = angleneeded * (180.0 / math.pi) if angleneeded < 0 then angleneeded = 360 + angleneeded elseif angleneeded > 360 then angleneeded = angleneeded - 360 end local current_angy = g_Entity[e]['angley'] while current_angy < 0 or current_angy > 360 do if current_angy <= 0 then current_angy = 360 + current_angy elseif current_angy > 360 then current_angy = current_angy - 360 end end local L = angleneeded - v local R = angleneeded + v if L <= 0 then L = 360 + L elseif L > 360 then L = L - 360 end if R <= 0 then R = 360 + R elseif R > 360 then R = R - 360 end --if not already facing the target direction (+/- the rotation speed) if current_angy < L or current_angy > R then --do it in 2 halfs so we can account for 0/360 wrap if current_angy > 180 then if angleneeded > current_angy - 180 and angleneeded < current_angy then current_angy = current_angy - v else current_angy = current_angy + v end else if angleneeded < current_angy + 180 and angleneeded > current_angy then current_angy = current_angy + v else current_angy = current_angy - v end end SetRotation(e,0,current_angy,0) return current_angy end end end