-- Ai Script - Created with xImporter - Credits to smallg for this fantastic script 31/08/2021 10:04:12 local attackdamage = {} --how far away the character can attack from local attackrange = 100 --how much damage the attacks do attackdamage[1] = 10 attackdamage[2] = 10 attackdamage[3] = 10 --how often to flinch when hurt (in percentage of health loss) local hurthealthperc = 30 --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 --attack[1][3] is the frame to apply damage after --[[ idle;1;151; idle2;152;351; roar;352;514; walking;515;558; running;559;591; attack1;592;672; attack2;673;752; attack3;753;832; hit1;833;876; hit2;877;942; death1;943;1032; death2;1033;1133; --]] local idle = {} idle[1] = {} idle[1][1] = 1 idle[1][2] = 151 idle[2] = {} idle[2][1] = 152 idle[2][2] = 351 local walk = {} walk[1] = {} walk[1][1] = 516 walk[1][2] = 557 local run = {} run[1] = {} run[1][1] = 560 run[1][2] = 590 local attack = {} attack[1] = {} attack[1][1] = 593 attack[1][2] = 671 attack[2] = {} attack[2][1] = 674 attack[2][2] = 751 attack[3] = {} attack[3][1] = 752 attack[3][2] = 831 --this frame should be the @hit impact on the player for damage attack[1][3] = 630 attack[2][3] = 710 attack[3][3] = 795 local hurt = {} hurt[1] = {} hurt[1][1] = 834 hurt[1][2] = 875 hurt[2] = {} hurt[2][1] = 878 hurt[2][2] = 941 hurt[3] = {} hurt[3][1] = 353 hurt[3][2] = 513 local death = {} death[1] = {} death[1][1] = 944 death[1][2] = 1031 death[2] = {} death[2][1] = 1034 death[2][2] = 1132 ai_bot_state = {} local pathindex = {} local pathpoint = {} local pathdirection = {} local destx = {} local desty = {} local destz = {} local anim = {} local hitplayer = {} local movemod = {} local maxhealth = {} local flinchat = {} local freeroam = {} U = U or require "scriptbank\\utillib" hurthealthperc = hurthealthperc / 100 hurthealthperc = 1 - hurthealthperc function smallg_nuclear_liquidator_ai_init(e) SetPreExitValue(e,0) CharacterControlManual(e) local AIObjNo = g_Entity[e]['obj'] AISetEntityControl(AIObjNo,AI_MANUAL) LoadGlobalSound("audiobank\\character\\ghostface\\onAlert\\0.WAV",1) if GetGlobalSoundExist(1) == 1 then SetCharacterSoundSet(e) end ai_bot_oldhealth[e] = g_Entity[e]['health'] maxhealth[e] = ai_bot_oldhealth[e] flinchat[e] = ai_bot_oldhealth[e] * hurthealthperc pathindex[e] = -1 ai_bot_state[e] = "idle" --set to true if you don't want to use waypoints and just let the character walk around randomly freeroam[e] = false end function smallg_nuclear_liquidator_ai_main(e) local ent = g_Entity[e] local state = ai_bot_state[e] local AIObjNo = ent.obj if state == "idle" then PlayCharacterSound(e,"onIdle") if walk[1][1] ~= -1 then if freeroam[e] == true then ai_bot_state[e] = "free roam" elseif pathindex[e] == -1 then pathindex[e], pathpoint[e], pathdirection[e] = FindPath(e) end end if pathindex[e] > -1 then ai_bot_state[e] = "patrol" elseif freeroam[e] == false then ai_bot_state[e] = "idling" end local ay = math.rad(ent.angley) destx[e] = ent.x + math.sin(ay) * 100 desty[e] = ent.y destz[e] = ent.z + math.cos(ay) * 100 end state = ai_bot_state[e] if state == "idling" then PlayCharacterSound(e,"onIdle") 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 movemod[e] = 0 end if DetectPlayer(e) == 1 then ai_bot_state[e] = "alerted" 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 movemod[e] = 1 end if DetectPlayer(e) == 1 then ai_bot_state[e] = "alerted" end elseif state == "free roam" then local x1,z1 = g_Entity[e]['x'],g_Entity[e]['z'] local x2,z2 = destx[e],destz[e] if CloseTo(x1,z1,x2,z2) < 80*80 then local ay = math.rad(ent.angley * math.random(-90,90)) destx[e] = ent.x + math.sin(ay) * 500 desty[e] = ent.y destz[e] = ent.z + math.cos(ay) * 500 else 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 movemod[e] = 1 end end if DetectPlayer(e) == 1 then ai_bot_state[e] = "alerted" end end state = ai_bot_state[e] if state == "alerted" then PlayCharacterSound(e,"onAlert") if attack[1][1] ~= -1 then ai_bot_state[e] = "move to attack" else ai_bot_state[e] = "run away" end end state = ai_bot_state[e] if state == "move to attack" then destx[e] = g_PlayerPosX desty[e] = g_PlayerPosY destz[e] = g_PlayerPosZ 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 movemod[e] = 1 end if GetPlayerDistance(e) < attackrange and ent.plrvisible == 1 then ai_bot_state[e] = "attack" end elseif state == "run away" then local ang = AngleToPoint(e, g_PlayerPosX, g_PlayerPosZ) local ay = math.rad(ang+180) destx[e] = ent.x + math.sin(ay) * 500 desty[e] = ent.y destz[e] = ent.z + math.cos(ay) * 500 local pDist = GetPlayerDistance(e) if pDist > 2000 then ai_bot_state[e] = "idle" elseif pDist > 1200 and walk[1][1] ~= -1 then if ent.animating ~= 2 then ModulateSpeed(e,1.4) anim[e] = math.random(1,#walk) SetAnimationFrames(walk[anim[e]][1],walk[anim[e]][2]) LoopAnimation(e) g_Entity[e]['animating'] = 2 movemod[e] = 1 end else 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 movemod[e] = 1 end end end state = ai_bot_state[e] if state == "attack" then local ay = math.rad(ent.angley) destx[e] = ent.x + math.sin(ay) * 100 desty[e] = ent.y destz[e] = ent.z + math.cos(ay) * 100 if DetectPlayer(e) == 0 or GetPlayerDistance(e) > attackrange * 1.1 then ai_bot_state[e] = "idle" return end 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 hitplayer[e] = false movemod[e] = 0 ai_bot_state[e] = "attacking" end end state = ai_bot_state[e] if state == "attacking" then PlayCharacterSound(e,"onHurtPlayer") if ent.animating == 0 then ai_bot_state[e] = "attack" return elseif not hitplayer[e] then local tframe = GetAnimationFrame(e) if tframe >= attack[anim[e]][3] - 2 and tframe <= attack[anim[e]][3] + 2 then HurtPlayer(e, attackdamage[anim[e]]) hitplayer[e] = true end end end state = ai_bot_state[e] if state == "flinch" then local ay = math.rad(ent.angley) destx[e] = ent.x + math.sin(ay) * 100 desty[e] = ent.y destz[e] = ent.z + math.cos(ay) * 100 if ent.animating ~= 5 then PlayCharacterSound(e,"onAlert") ModulateSpeed(e,1) anim[e] = math.random(1,#hurt) SetAnimationFrames(hurt[anim[e]][1],hurt[anim[e]][2]) PlayAnimation(e) g_Entity[e]['animating'] = 5 movemod[e] = 0 ai_bot_state[e] = "flinching" end end state = ai_bot_state[e] if state == "flinching" then PlayCharacterSound(e,"onHurt") if ent.animating == 0 then if attack[1][1] ~= -1 then ai_bot_state[e] = "move to attack" else ai_bot_state[e] = "run away" end end end --triple damage for a headshot if string.find(string.lower(ent.limbhit), "head") ~= nil then SetEntityHealth(e,ent.health-(ai_bot_oldhealth[e]-ent.health)*2) ResetLimbHit(e) end if movemod[e] > 0 then AIEntityGoToPosition(AIObjNo, destx[e], desty[e], destz[e]) end local movespeed = AIGetEntitySpeed(AIObjNo) state = ai_bot_state[e] if state == "idling" then elseif state ~= "idle" and state ~= "attacking" and state ~= "flinching" then RotateToPointSlowly(e, destx[e], destz[e], movespeed / 20) SetRotation(e,0,AIGetEntityAngleY(AIObjNo),0) if movemod[e] > 0 then MoveForward(e,movespeed * movemod[e]) end elseif state ~= "flinching" then RotateToPlayer(e) end AISetEntityPosition(AIObjNo,GetEntityPositionX(e),GetEntityPositionY(e),GetEntityPositionZ(e)) if ent.health < ai_bot_oldhealth[e] then ai_bot_oldhealth[e] = ent.health if ai_bot_oldhealth[e] <= flinchat[e] then flinchat[e] = ai_bot_oldhealth[e] * hurthealthperc ai_bot_state[e] = "flinch" end end --PromptLocal(e,ai_bot_state[e]) end function smallg_nuclear_liquidator_ai_preexit(e) SetPreExitValue(e,1) if ai_bot_state[e] ~= "dead" then PlayCharacterSound(e,"onDeath") anim[e] = math.random(1,#death) SetAnimationFrames(death[anim[e]][1],death[anim[e]][2]) PlayAnimation(e) g_Entity[e]['animating'] = 5 ai_bot_state[e] = "dead" elseif g_Entity[e]['animating'] == 0 then SetPreExitValue(e,2) end end function smallg_nuclear_liquidator_ai_exit(e) CollisionOff(e) end function DetectPlayer(e) local playerdist = GetPlayerDistance(e) local AIObjNo = g_Entity[e]['obj'] if (playerdist < AIGetEntityViewRange(AIObjNo) and g_Entity[e]['plrvisible'] == 1) or AIGetEntityHeardSound(AIObjNo) == 1 or playerdist < AIGetEntityViewRange(AIObjNo) * 0.25 then return 1 else return 0 end end function CloseTo(x1,z1,x2,z2) local DX = x1 - x2 local DZ = z1 - z2 local Dist = DX*DX+DZ*DZ return Dist 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 AngleToPoint(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