-- COMBATCORE Common Module -- anim0 : Idle -- anim1 : Move -- anim2 : Punch/Kick/Bite -- anim3 : Hurt -- anim4 : Reload -- anim5 : Punch/Kick/Bite Damage Frames -- anim6 : Dying -- anim7 : Stood to Duck -- anim8 : Duck Idle -- anim9 : Duck to Stood -- anim10 : Run/Dash -- anim11 : Roll to Duck -- anim12 : Fast Crouch Dash -- anim13 : idle sentry -- anim14 : relaxed move -- anim15 : strafe left -- anim16 : strafe right -- sound0 : Start moving A -- sound1 : Start strike -- sound2 : Start moving B -- sound3 : Get hurt A -- sound4 : Get hurt B local abs = math.abs local rad = math.rad local sin = math.sin local cos = math.cos local atan = math.atan2 local sqrt = math.sqrt local random = math.random local M = require "scriptbank\\ai\\module_core" local MAG = require "scriptbank\\ai\\module_agro" local MCO = require "scriptbank\\ai\\module_cameraoverride" local MCE = require "scriptbank\\ai\\module_combateffects" local P = require "scriptbank\\physlib" local U = require "scriptbank\\utillib" local last_fired = {} local ai_cover_slot = {} local ai_bot_newroty = {} local avoidList = {} g_aiList = g_aiList or {} local MCC = {} function MCC.init( e, startstate, coverstate ) coverstate = coverstate or -1 Include( "physlib.lua" ) Include( "utillib.lua" ) local Ent = g_Entity[ e ] CharacterControlManual( e ) AISetEntityControl( Ent.obj, AI_MANUAL ) g_aiList[ e ] = { oldhealth = Ent.health, coverindex = coverstate, state = startstate, substate = 0, pointtime = -1, patroltime = 0, targetx = Ent.x, targety = Ent.y, targetz = Ent.z, closeenoughx = 0, closeenoughy = 0, closeenoughz = 0, gofast = 0, sighting = 0, roty = 0 } -- populate animation data if entity file does not provide it (except 5 and 6 which are melee) if GetEntityAnimationFound( e, 0 ) == 0 then SetEntityAnimation( e, 0, 100, 205 ) end if GetEntityAnimationFound( e, 1 ) == 0 then SetEntityAnimation( e, 1, 685, 707 ) end if GetEntityAnimationFound( e, 2 ) == 0 then SetEntityAnimation( e, 2, 5511, 5553 ) end if GetEntityAnimationFound( e, 3 ) == 0 then SetEntityAnimation( e, 3, 4812, 4850 ) end if GetEntityAnimationFound( e, 4 ) == 0 then SetEntityAnimation( e, 4, 515, 605 ) end if GetEntityAnimationFound( e, 7 ) == 0 then SetEntityAnimation( e, 7, 1630, 1646 ) end if GetEntityAnimationFound( e, 8 ) == 0 then SetEntityAnimation( e, 8, 1670, 1819 ) end if GetEntityAnimationFound( e, 9 ) == 0 then SetEntityAnimation( e, 9, 1646, 1663 ) end if GetEntityAnimationFound( e, 10 ) == 0 then SetEntityAnimation( e, 10, 795, 811 ) end if GetEntityAnimationFound( e, 11 ) == 0 then SetEntityAnimation( e, 11, 2160, 2218 ) end if GetEntityAnimationFound( e, 12 ) == 0 then SetEntityAnimation( e, 12, 2135, 2153 ) end if GetEntityAnimationFound( e, 13 ) == 0 then SetEntityAnimation( e, 13, 900, 999 ) end if GetEntityAnimationFound( e, 14 ) == 0 then SetEntityAnimation( e, 14, 1290, 1320 ) end if GetEntityAnimationFound( e, 15 ) == 0 then SetEntityAnimation( e, 15, 610, 640 ) end if GetEntityAnimationFound( e, 16 ) == 0 then SetEntityAnimation( e, 16, 645, 676 ) end end function MCC.findcover( e, x, y, z ) local ppx, ppz = g_PlayerPosX, g_PlayerPosZ local CoverIndex = -1 local tdx, tdz = x - ppx, z - ppz local SqDistToPlayer = tdx * tdx + tdz * tdz for prefer = 1, 2 do local SqClosest = math.huge for ca = 1, AIGetTotalCover(), 1 do local cpX, cpZ = AICoverGetPointX( ca ), AICoverGetPointZ( ca ) local pDX, pDz = x - cpX, z - cpZ local SqDistToCover = pDX * pDX + pDZ * pDZ if SqDistToCover < SqClosest and SqDistToCover < SqDistToPlayer and SqDistToCover < 3000 * 3000 then -- also reject cover that is 'further away' from player than the enemy tdx, tdz = cpX - ppx, cpZ - ppz SqPlayerCoverDist = tdx * tdx + tdz * tdz if SqPlayerCoverDist <= SqDistToPlayer then -- reject cover if facing away from player direction tdx, tdz = ppx - cpX, ppz - cpZ local tPlayerCoverAngle = ( atan( tdx, tdz ) / 6.28 ) * 360 local tanglediff = tPlayerCoverAngle - AICoverGetAngle( ca ) if tanglediff < -180 then tanglediff = tanglediff + 360 end if tanglediff > 180 then tanglediff = tanglediff - 360 end if abs( tanglediff ) < 90 then local tchoosethis = false if prefer == 1 and ai_cover_slot[ ca ] == nil then tchoosethis = true end if prefer == 2 and ai_cover_slot[ ca ] ~= nil then tchoosethis = true end if tchoosethis then SqClosest = SqDistToCover CoverIndex = ca end end end end end if prefer == 1 and CoverIndex ~= -1 then return CoverIndex end end return CoverIndex end function MCC.atcover( e, ai_bot ) local Ent = g_Entity[ e ] local CoverIndex = ai_bot.coverindex[ e ] if CoverIndex ~= -1 then tdx = AICoverGetPointX( CoverIndex ) - Ent.x tdz = AICoverGetPointZ( CoverIndex ) - Ent.z if tdx * tdx + tdz * tdz < 125 * 125 then return 1 end end return 0 end function MCC.claimcover( e, ai_bot, slot ) ai_cover_slot[ slot ] = e ai_bot.coverindex[ e ] = slot end function MCC.releasecover( e, ai_bot ) ai_bot = ai_bot or g_aiList[ e ] local slot = ai_bot.coverindex if slot > 0 then ai_cover_slot[ slot ] = nil end ai_bot.coverindex = -2 end function MCC.findandassigncover( e, ai_bot, AIObjNo, PlayerDist ) local Ent = g_Entity[ e ] local tcoverassigned = 0 if ai_bot.coverindex == -1 then local coverslot = MCC.findcover( e, Ent.x, Ent.y, Ent.z ) if coverslot > 0 then MCC.claimcover( e, ai_bot, coverslot ) ai_bot.targetx = AICoverGetPointX( ai_bot.coverindex ) ai_bot.targety = g_PlayerPosY ai_bot.targetz = AICoverGetPointZ( ai_bot.coverindex ) tcoverassigned = 1 end end if ai_bot.coverindex < 0 then MCC.releasecover( e, ai_bot ) end if ai_bot.coverindex == -2 and PlayerDist < AIGetEntityViewRange( AIObjNo ) then ai_bot.targetx = g_PlayerPosX ai_bot.targety = g_PlayerPosY ai_bot.targetz = g_PlayerPosZ end return tcoverassigned end function MCC.detectplayer( e, ai_bot, AIObjNo, PlayerDist, CanFire, detectstate ) local Ent = g_Entity[ e ] if ( PlayerDist < AIGetEntityViewRange( AIObjNo ) and ( Ent.plrvisible == 1 or ai_bot.coverindex == -2 ) ) or Ent.activated == 2 then if Ent.activated == 2 then ai_bot.targetx = g_PlayerPosX ai_bot.targety = g_PlayerPosY ai_bot.targetz = g_PlayerPosZ ai_bot.substate = 0 SetActivated( e, 0 ) elseif MCC.findandassigncover( e, ai_bot, AIObjNo, PlayerDist ) == 1 then AIEntityGoToPosition( AIObjNo, ai_bot.targetx, ai_bot.targety, ai_bot.targetz ) ai_bot.state = "startmove" ai_bot.substate = 0 end if ai_bot.targetx ~= nil then if ai_bot.substate == 0 then if MCC.closeenough( e, ai_bot, 25 ) then RotateToPlayerSlowly( e, 1.0 ) else Prompt( "here" ) AIEntityGoToPosition( AIObjNo, ai_bot.targetx, ai_bot.targety, ai_bot.targetz ) if AIGetEntityIsMoving( AIObjNo ) == 1 then ai_bot.state = detectstate PlaySound( e, 0 + ( random( 0, 1 ) * 2 ) ) elseif Ent.plrvisible == 1 then RotateToPlayer( e ) if CanFire == 1 then MCC.fireweapon( e, ai_bot ) end ai_bot.targetx = g_PlayerPosX ai_bot.targety = g_PlayerPosY ai_bot.targetz = g_PlayerPosZ AIEntityGoToPosition( AIObjNo, ai_bot.targetx, ai_bot.targety, ai_bot.targetz ) end end end end end end function MCC.closeenough( e, ai_bot, dist ) local dx = ai_bot.closeenoughx - g_PlayerPosX local dy = ai_bot.closeenoughy - g_PlayerPosY local dz = ai_bot.closeenoughz - g_PlayerPosZ return ( dx * dx + dy * dy + dz * dz ) < ( dist * dist ) end function MCC.idle( e, ai_bot, AIObjNo, PlayerDist, CanFire, detectstate, combattype ) CharacterControlManual( e ) AISetEntityControl( AIObjNo, AI_MANUAL ) if ai_bot.state == "startidle" then ai_bot.state = "idle" if combattype == ai_combattype_regular then SetAnimation( 13 ) else SetAnimation( 0 ) end LoopAnimation( e ) SetAnimationSpeedModulation( e, 1.0 ) elseif ai_bot.state == "idle" then MCC.detectplayer( e, ai_bot, AIObjNo, PlayerDist, CanFire, detectstate ) MCC.donotmove( e, ai_bot ) elseif g_Entity[ e ].plrvisible == 0 and PlayerDist < 200 then ai_bot.state = "idle" SetAnimation( 0 ) CanFire = false MCC.donotmove( e, ai_bot ) end end function MCC.patrol( e, ai_bot, AIObjNo, PlayerDist, MoveType, CanFire, detectstate, stopstate, combattype ) local Ent = g_Entity[ e ] if ai_bot.pointtime == -1 then ai_bot.pointtime = 0 StartTimer( e ) end if ai_bot.state == "findpatrolpath" and ai_bot.pointtime == 0 then local PathIndex = -1 local PointIndex = 2 local SqClosest = math.huge for pa = 1, AIGetTotalPaths() do for po = 1, AIGetPathCountPoints( pa ) do local pDX = Ent.x - AIPathGetPointX( pa, po ) local pDY = Ent.y - AIPathGetPointY( pa, po ) local pDZ = Ent.z - AIPathGetPointZ( pa, po ) SqDist = pDX * pDX + pDY * pDY + pDZ * pDZ if SqDist < SqClosest and SqDist < 200 * 200 then SqClosest = SqDist PathIndex = pa PointIndex = po end end -- po end -- pa ai_bot.pathindex = PathIndex if PathIndex > -1 then ai_bot.state = "startpatrol" ai_bot.pointdirection = 1 ai_bot.pointindex = PointIndex ai_bot.pointmax = AIGetPathCountPoints( PathIndex ) else ai_bot.state = "startidle" end elseif ai_bot.state == "startpatrol" then if ai_bot.pathindex ~= -1 then ai_bot.state = "patrol" ai_bot.pointtime = g_Time + 100 AISetEntityMoveBoostPriority( AIObjNo ) SetAnimationSpeedModulation( e, 0.0 ) if combattype == ai_combattype_regular then SetAnimation( 14 ) else SetAnimation( 1 ) end LoopAnimation( e ) StartTimer( e ) else ai_bot.state = "idle" if combattype == ai_combattype_regular then SetAnimation( 13 ) else SetAnimation( 0 ) end LoopAnimation( e ) SetAnimationSpeedModulation( e, 1.0 ) end elseif ai_bot.state == "patrol" then local avoid = avoidList[ e ] if avoid ~= nil and avoid.active then MCC.moveandavoid( e, ai_bot, AIObjNo, PlayerDist, MoveType, ai_bot.targetx, ai_bot.targety, ai_bot.targetz, stopstate ) else local path, point = ai_bot.pathindex, ai_bot.pointindex local px = AIPathGetPointX( path, point ) local py = AIPathGetPointY( path, point ) local pz = AIPathGetPointZ( path, point ) MCC.moveandavoid( e, ai_bot, AIObjNo, PlayerDist, MoveType, px, py, pz, stopstate ) if g_Time > ai_bot.pointtime and abs( Ent.y - py ) < 95 then local tDX, tDZ = Ent.x - px, Ent.z - pz local SqDistFromPath = tDX * tDX + tDZ * tDZ if SqDistFromPath < 25 * 25 then ai_bot.pointtime = g_Time + 100 AISetEntityMoveBoostPriority( AIObjNo ) StartTimer( e ) if ai_bot.pointdirection == 1 then if point < ai_bot.pointmax then ai_bot.pointindex = point + 1 else ai_bot.pointindex = ai_bot.pointmax - 1 ai_bot.pointdirection = 0 end else if point > 1 then ai_bot.pointindex = point - 1 else ai_bot.pointindex = 2 ai_bot.pointdirection = 1 end end end end end if AIGetEntityIsMoving( AIObjNo ) == 1 then if GetAnimationSpeedModulation( e ) == 0.0 then SetAnimationSpeedModulation( e, 0.1 ) else local tWalkDelta = GetMovementDelta( e ) * 2.0 if tWalkDelta > 0.9 then tWalkDelta = 0.9 end SetAnimationSpeedModulation( e, 0.1 + tWalkDelta ) end end MCC.detectplayer( e, ai_bot, AIObjNo, PlayerDist, CanFire, detectstate ) end end function MCC.hunt( e, ai_bot, AIObjNo, PlayerDist, MoveType, CanFire, stopstate ) local Ent = g_Entity[ e ] if ai_bot.state == "startmove" then ai_bot.state = "move" local Sqdist = 0 if ai_bot.targetx ~= nil then tdX = Ent.x - ai_bot.targetx tdZ = Ent.z - ai_bot.targetz Sqdist = tdX * tdX + tdZ * tdZ end if Sqdist > 200 * 200 then ai_bot.gofast = Timer() else ai_bot.gofast = 0 end if ai_bot.gofast > 0 then ModulateSpeed( e, 1.5 ) SetAnimation( 10 ) else ModulateSpeed( e, 1.0 ) SetAnimation( 1 ) end LoopAnimation( e ) SetAnimationSpeedModulation( e, 1.0 ) StartTimer( e ) elseif ai_bot.state == "move" then if GetTimer( e ) > 500 then if ai_bot.coverindex > 0 then if MCC.atcover( e, ai_bot ) == 1 then if Ent.plrdist > 400 then local rays = MCC.checkentityrays( e, 130, 0, Ent.obj, 57 ) if rays == 0 then if ai_bot.state ~= "duck" then ai_bot.state = "duckstart" end else --can't see over cover so try roll sideways? if ai_bot.state ~= "rollstart" and ai_bot.state ~= "roll" then ai_bot.state = "rollstart" MCC.handleducking( e, ai_bot, AIObjNo, PlayerDist ) return end end else -- break from cover, player too close MCC.releasecover( e, ai_bot ) end end end if ai_bot.coverindex == -2 then if Ent.plrvisible == 1 and CanFire then ai_bot.state = "startfireonspot" end end if ai_bot.substate == 0 then StartTimer( e ) end end if ai_bot.state ~= "startfireonspot" then if ai_bot.coverindex == -2 then if Ent.plrvisible == 1 then ai_bot.targetx = g_PlayerPosX ai_bot.targety = g_PlayerPosY ai_bot.targetz = g_PlayerPosZ end elseif ai_bot.coverindex > 0 then ai_bot.targetx = AICoverGetPointX( ai_bot.coverindex ) ai_bot.targety = g_PlayerPosY ai_bot.targetz = AICoverGetPointZ( ai_bot.coverindex ) end if ai_bot.targetx ~= nil then MCC.moveandavoid( e, ai_bot, AIObjNo, PlayerDist, MoveType, ai_bot.targetx, ai_bot.targety, ai_bot.targetz, stopstate ) if CanFire then MCC.fireweapon( e, ai_bot ) end end if ai_bot.gofast > 0 and Timer() > ai_bot.gofast + 500 then if GetMovementDeltaManually( e, Ent.x, Ent.y, Ent.z ) < 1 then StopAnimation( e ) SetAnimationFrame( e, GetEntityAnimationStart( e, 1 ) ) SetAnimation( 1 ) LoopAnimation( e ) ModulateSpeed( e, 1.0 ) ai_bot.gofast = 0 end end end end if Ent.plrvisible == 1 then ai_bot.hunttime = Timer() else if ai_bot.hunttime ~= nil then if ai_bot.hunttime > 0 and Timer() > ai_bot.hunttime + 5000 then if ai_bot.state ~= "duck" then ai_bot.state = "startmove" ai_bot.hunttime = 0 SetActivated( e, 0 ) end end end if Ent.plrvisible == 0 and PlayerDist < 200 then ai_bot.state = "idle" SetAnimation( 0 ) CanFire = false MCC.donotmove( e, ai_bot ) end end end function MCC.handleducking( e, ai_bot, AIObjNo, PlayerDist ) local Ent = g_Entity[ e ] if ai_bot.state == "crouchdashstart" then ai_bot.state = "crouchdash" ai_bot.substate = 0 StopAnimation( e ) SetAnimationFrame( e, GetEntityAnimationStart( e, 12 ) ) SetAnimation( 12 ) LoopAnimation( e ) ModulateSpeed( e, 1.25 ) SetAnimationSpeedModulation( e, 1.25 ) local randomevade = random( 45, 70 ) if random() > 0.5 then randomevade = -randomevade end SetRotation( e, 0, AIGetEntityAngleY( AIObjNo ) + randomevade, 0 ) StartTimer( e ) elseif ai_bot.state == "crouchdash" then local tWalkDelta = GetMovementDeltaManually( e, Ent.x, Ent.y, Ent.z ) local tm = GetTimer( e ) if tm < 250 or ( tm < 2000 and PlayerDist > 200 and tWalkDelta > 1.0 ) then if tm < 1750 then MoveForward( e, AIGetEntitySpeed( AIObjNo ) ) AISetEntityPosition( AIObjNo, GetEntityPositionX( e ), GetEntityPositionY( e ), GetEntityPositionZ( e ) ) end if tm > 1250 then RotateToPlayerSlowly( e, 10.0 ) ModulateSpeed( e, 1.0 ) SetAnimationSpeedModulation( e, 1.0 ) end else ai_bot.state = "checkforcover" end end MCC.evasiveactions( e, ai_bot, AIObjNo, PlayerDist ) MCC.strafeleft( e, ai_bot, 0.5 ) MCC.straferight( e, ai_bot, 0.5 ) MCC.fireweapon( e, ai_bot ) if ai_bot.state == "rollstart" then local randomevade = random( 65, 90 ) if random() > 0.5 then randomevade = -randomevade end local roty = AIGetEntityAngleY( AIObjNo ) + randomevade local dist = 130 local rays = MCC.checkentityrays( e, dist, roty, Ent.obj, 20 ) if rays == 0 then SetRotation( e, 0, roty, 0 ) ai_bot.state = "roll" ai_bot.substate = 0 SetAnimation( 11 ) PlayAnimation( e ) ModulateSpeed( e, 1.25 ) SetAnimationSpeedModulation( e, 1.25 ) else ai_bot.state = "checkforcover" end elseif ai_bot.state == "roll" then local tFrame = GetAnimationFrame( e ) local tStart = GetEntityAnimationStart( e, 11 ) local tFinish = GetEntityAnimationFinish( e, 11 ) if tFrame > tStart + 1 and tFrame < tFinish - 20 then MoveForward( e, AIGetEntitySpeed( AIObjNo ) ) AISetEntityPosition( AIObjNo, GetEntityPositionX( e ), GetEntityPositionY( e ), GetEntityPositionZ( e ) ) else if tFrame >= tFinish - 20 then RotateToPlayerSlowly( e, 30.0 ) end MoveForward( e, 0.0 ) end if tFrame >= tFinish then ai_bot.state = "checkforcover" end elseif ai_bot.state == "checkforcover" then SetAnimationSpeedModulation( e, 1.0 ) ModulateSpeed( e, 1.0 ) StopAnimation( e ) if MCC.findandassigncover( e, ai_bot, AIObjNo, PlayerDist ) == 1 then AIEntityGoToPosition( AIObjNo, ai_bot.targetx, ai_bot.targety, ai_bot.targetz ) ai_bot.state = "startmove" ai_bot.substate = 0 else ai_bot.state = "duckstart" end elseif ai_bot.state == "duckstart" then ai_bot.state = "duck" ai_bot.substate = 0 RotateToPlayer( e ) SetAnimation( 7 ) PlayAnimation( e ) SetAnimationSpeedModulation( e, 1.0 ) elseif ai_bot.state == "duck" then if ai_bot.substate == 0 then local tFrame = GetAnimationFrame( e ) local tFinish = GetEntityAnimationFinish( e, 7 ) if tFrame >= tFinish then ai_bot.substate = 1 SetAnimation( 8 ) PlayAnimation( e ) end end -- while ducking if ai_bot.substate == 1 then -- stay put MoveForward( e, 0.0 ) -- if see player, rotate slowly to shoot if Ent.plrvisible == 1 then RotateToPlayerSlowly( e, 30.0 ) MCC.fireweapon( e, ai_bot ) end -- if player gets too close, end duck and cover if Ent.plrdist < 400 then ai_bot.state = "unduckstart" end -- if player at distance and in cover, pop head up and shoot at random if ai_bot.coverindex > 0 and random( 1, 30 ) == 1 then ai_bot.state = "unduckstart" end elseif ai_bot.substate == 2 then -- while ducking-popupshoot StopAnimation( e ) SetAnimationFrame( e, GetEntityAnimationStart( e, 9 ) ) SetAnimation( 9 ) PlayAnimation( e ) ai_bot.substate = 3 elseif ai_bot.substate == 3 then RotateToPlayerSlowly( e, 20.0 ) MCC.fireweapon( e, ai_bot ) local tFrame = GetAnimationFrame( e ) local tFinish = GetEntityAnimationFinish( e, 9 ) if tFrame >= tFinish then StopAnimation( e ) SetAnimationFrame( e, GetEntityAnimationStart( e, 7 ) ) SetAnimation( 7 ) PlayAnimation( e ) ai_bot.substate = 4 end elseif ai_bot.substate == 4 then RotateToPlayerSlowly( e, 20.0 ) MCC.fireweapon( e, ai_bot ) local tFrame = GetAnimationFrame( e ) local tFinish = GetEntityAnimationFinish( e, 7 ) if tFrame >= tFinish then ai_bot.substate = 1 StopAnimation( e ) SetAnimationFrame( e, GetEntityAnimationStart( e, 8 ) ) SetAnimation( 8 ) PlayAnimation( e ) end end elseif ai_bot.state == "unduckstart" then ai_bot.state = "unduck" SetAnimation( 9 ) PlayAnimation( e ) elseif ai_bot.state == "unduck" then local tFrame = GetAnimationFrame( e ) local tFinish = GetEntityAnimationFinish( e, 9 ) if tFrame >= tFinish-1 and tFrame <= tFinish then ai_bot.state = "startidle" MCC.releasecover( e, ai_bot ) ai_bot.substate = 0 end end end local function limitAngle ( Angle ) while Angle < 0 do Angle = 360 + Angle end while Angle > 360 do Angle = Angle - 360 end return Angle end function MCC.homein( e, ai_bot, AIObjNo, PlayerDist, MoveType, CanFire, stopstate ) if ai_bot.state == "startmove" then if MCC.closeenough( e, ai_bot, 25 ) then -- no need to go to target, happy with close location determined by player position ai_bot.state = stopstate else ai_bot.state = "move" SetAnimation( 1 ) LoopAnimation( e ) SetAnimationSpeedModulation( e, 1.0 ) end elseif ai_bot.state == "move" then -- scan if another bot is crowding player local tbeingcrowded = false local ppx, ppy, ppz = g_PlayerPosX, g_PlayerPosY, g_PlayerPosZ for k, v in pairs( g_Entity ) do if e ~= k then local pDX, pDY, pDZ = v.x - ppx, v.y - ppy, v.z - ppz if pDX*pDX + pDY*pDY + pDZ*pDZ < 100 * 100 then tbeingcrowded = true break end end end if not tbeingcrowded then ai_bot.targetx = ppx ai_bot.targety = ppy ai_bot.targetz = ppz else local ang = rad( limitAngle( e ) ) ai_bot.targetx = ppx + sin( ang ) * 50.0 ai_bot.targety = ppy ai_bot.targetz = ppz + cos( ang ) * 50.0 end MCC.moveandavoid( e, ai_bot, AIObjNo, PlayerDist, MoveType, ai_bot.targetx, ai_bot.targety, ai_bot.targetz, stopstate ) end end function MCC.moveandavoid( e, ai_bot, AIObjNo, PlayerDist, MoveType, x, y, z, stopstate ) local Ent = g_Entity[ e ] local avoid = avoidList[ e ] local movementfrozen = MCE.ismovementfrozen( e, ai_bot ) --work out this entity's velocity so we know which way it is heading (used to try move around behind it when avoiding) if avoid == nil then avoidList[ e ] = { px = Ent.x, pz = Ent.z, vx = 0, vz = 0, timer = 0, active = false } return end avoid.vx = Ent.x - avoid.px avoid.vz = Ent.z - avoid.pz avoid.px = Ent.x avoid.pz = Ent.z if ai_bot.substate == 0 then if PlayerDist < 100 then if M.countaiaroundplayer() > 1 then local tdX, tdZ = x - Ent.x, z - Ent.z local tDA = atan( tdX, tdZ ) x = x + ( sin( tDA ) * 50 ) z = z + ( cos( tDA ) * 50 ) end end AIEntityGoToPosition( AIObjNo, x, y, z ) SetRotationYSlowly( e, AIGetEntityAngleY( AIObjNo ), 10.0 ) if movementfrozen == 0 then local stopifclosetofinaldest = 0 if ai_bot.state ~= "patrol" then local ppx, ppy, ppz = g_PlayerPosX, g_PlayerPosY, g_PlayerPosZ stopifclosetofinaldest = ppy - ( Ent.y + 50 ) if stopifclosetofinaldest < 75 then local dx, dz = ppx - Ent.x, ppz - Ent.z stopifclosetofinaldest = dx * dx + dz * dz if stopifclosetofinaldest < 35 * 35 then ai_bot.closeenoughx = ppx ai_bot.closeenoughy = ppy ai_bot.closeenoughz = ppz AIEntityGoToPosition( AIObjNo, ai_bot.closeenoughx, ai_bot.closeenoughy, ai_bot.closeenoughz ) RotateToPlayer( e ) stopifclosetofinaldest = 1 else stopifclosetofinaldest = 0 end else stopifclosetofinaldest = 0 end end if AIGetEntityIsMoving( AIObjNo ) == 1 and stopifclosetofinaldest == 0 then ai_bot.closeenoughx = 0 ai_bot.closeenoughy = 0 ai_bot.closeenoughz = 0 if MoveType == ai_movetype_useanim then MoveWithAnimation( e, 1 ) else local speedmod = 1 if ai_bot.state == "patrol" then speedmod = 0.5 end MoveForward( e, AIGetEntitySpeed( AIObjNo ) * speedmod ) end else MoveForward( e, 0.0 ) if GetTimer( e ) > 250 then ai_bot.state = stopstate end end AISetEntityPosition( AIObjNo, GetEntityPositionX( e ), GetEntityPositionY( e ), GetEntityPositionZ( e ) ) end if Ent.avoid == 2 or ( Ent.avoid == 1 and stopstate ~= "startpatrol" ) then ai_bot.substate = random( 1, 2 ) local tAvoidAngle = 0 if ai_bot.substate == 1 then tAvoidAngle = AIGetEntityAngleY( AIObjNo ) - 95 else tAvoidAngle = AIGetEntityAngleY( AIObjNo ) + 95 end tAvoidAngle = ( tAvoidAngle / 360.0 ) * 6.28 local tAvoidX = GetEntityPositionX( e ) + sin( tAvoidAngle ) * 30 local tAvoidZ = GetEntityPositionZ( e ) + cos( tAvoidAngle ) * 30 AIEntityGoToPosition( AIObjNo, tAvoidX, GetEntityPositionY( e ), tAvoidZ ) StartTimer( e ) else if g_Time > avoid.timer then local avoidance_time = 100 local raydist = 120 local yoffset = 15 local obsray = MCC.checkentityrays( e, raydist, 0, AIObjNo, yoffset ) if obsray ~= nil and obsray > 0 then local obsE = P.ObjectToEntity( obsray ) if obsE ~= nil and obsE > 0 then local otherAvoid = avoidList[ obsE ] if otherAvoid ~= nil and not otherAvoid.active then local nex = GetEntityPositionX( obsE ) local nez = GetEntityPositionZ( obsE ) PromptLocal( e, "Avoiding entity " .. ( obsE or 'nil' ) ) local oangy = MCC.getangletopoint( obsE, nex + otherAvoid.vx * 1000, nez + otherAvoid.vz * 1000 ) + 180 if oangy ~= 0 then oangy = ( oangy / 360.0 ) * 6.28 ai_bot.targetx = GetEntityPositionX( e ) + sin( oangy ) * 30 ai_bot.targety = y ai_bot.targetz = GetEntityPositionZ( e ) + cos( oangy ) * 30 avoid.active = true AIEntityGoToPosition( AIObjNo, ai_bot.targetx, ai_bot.targety, ai_bot.targetz) end end end else avoid.active = false end avoid.timer = g_Time + avoidance_time end end end if ai_bot.substate > 0 and ai_bot.state ~= "duck" then SetRotation( e, 0, AIGetEntityAngleY( AIObjNo ), 0 ) if movementfrozen == 0 then if AIGetEntityIsMoving( AIObjNo ) == 1 then if MoveType == ai_movetype_useanim then MoveWithAnimation( e, 1 ) else MoveForward( e, AIGetEntitySpeed( AIObjNo ) ) end else MoveForward( e, 0.0 ) end AISetEntityPosition( AIObjNo, GetEntityPositionX( e ), GetEntityPositionY( e ), GetEntityPositionZ( e ) ) end if GetTimer( e ) > 1000 then ai_bot.substate = 0 end end end function MCC.donotmove( e, ai_bot ) MoveForward( e, 0 ) ai_bot.substate = 0 end function MCC.sensepunch( e, ai_bot, AIObjNo, PlayerDist, combattype ) local Ent = g_Entity[ e ] if PlayerDist < 150 and combattype == ai_combattype_freezermelee and ai_bot.state ~= "move" then RotateToPlayer( e ) end if ai_bot.state == "move" or ai_bot.state == "fireonspot" then if PlayerDist < 75 and g_PlayerPosY > Ent.y and g_PlayerPosY < Ent.y + 70 then -- must also check for line of sight (i.e not through walls) (first and third person) local thaslineofsight = false local tthitvalue = IntersectAll( Ent.x, Ent.y + 50, Ent.z, g_PlayerPosX, g_PlayerPosY + 50, g_PlayerPosZ, AIObjNo ) local ttpersonobj = 0 if GetGamePlayerControlThirdpersonEnabled() == 1 then local ttpersone = GetGamePlayerControlThirdpersonCharactere() if ttpersone > 0 then ttpersonobj = GetEntityElementObj( ttpersone ) if GetObjectExist( ttpersonobj ) == 0 then ttpersonobj = 0 end end end if tthitvalue == 0 or tthitvalue == playerobj then thaslineofsight = true end if thaslineofsight then ai_bot.state = "punch" SetAnimation( 2 ) PlayAnimation( e ) SetAnimationSpeedModulation( e, 1.0 ) PlaySound( e, 1 ) RotateToPlayer(e) if combattype == ai_combattype_freezermelee then MCO.beingattackedby( e, 60.0 ) end end end end end function MCC.hurt( e, ai_bot, PlayerDist, responsestate ) local Ent = g_Entity[ e ] if ai_bot.state == "idle" or ai_bot.state == "patrol" or ai_bot.state == "move" or ai_bot.state == "fireonspot" or ai_bot.state == "recover" or ai_bot.state == "punch" or ai_bot.state == "duck" then if Ent.health < ai_bot.oldhealth then if ai_bot.state == "duck" then ai_bot.state = "unduckstart" elseif ( ai_bot.state == "move" or ai_bot.state == "idle" or ai_bot.state == "fireonspot" ) and PlayerDist > 300 then if random( 1, 3 ) == 1 then ai_bot.state = "rollstart" else ai_bot.state = "crouchdashstart" end elseif ai_bot.state == "roll" then -- AI is immune to damage when rolling SetEntityHealth( e, ai_bot.oldhealth ) else local flinch = math.random( 1, 3 ) if flinch == 1 then ai_bot.state = "hurt" SetAnimationSpeed( e, 3.0 ) SetAnimation( 3 ) PlayAnimation( e ) SetAnimationSpeedModulation( e, 1.0 ) end end ai_bot.oldhealth = Ent.health ai_bot.angryhurt = 1 PlaySound( e, math.random( 3, 4 ) ) MAG.alertallwithinradius( Ent.x, Ent.y, Ent.z, 250.0 ) end end if ai_bot.state == "hurt" then MoveForward( e, 0.0 ) RotateToPlayerSlowly( e, 10.0 ) local tFrame = GetAnimationFrame( e ) local tStart = GetEntityAnimationFinish( e, 3 ) if tFrame >= tStart then SetAnimationSpeed( e, 1.0 ) ai_bot.state = responsestate end end end function MCC.headshot( e ) if string.find( string.lower( g_Entity[ e ].limbhit ), "head" ) ~= nil then SetEntityHealth( e, -1 ) ResetLimbHit( e ) end end function MCC.soundawareness( e, ai_bot, AIObjNo ) if AIGetEntityHeardSound( AIObjNo ) == 1 then if ai_bot.state == "idle" or ai_bot.state == "hurt" then ai_bot.sighting = Timer() + 500 elseif ai_bot.state == "patrol" then ai_bot.state = "startidle" ai_bot.sighting = Timer() + 500 else ai_bot.sighting = 0 end end if ai_bot.sighting > 0 and Timer() < ai_bot.sighting then RotateToPlayerSlowly( e, 10.0 ) end end function MCC.punch( e, ai_bot, AIObjNo, PlayerDist, combattype, afterstate ) if ai_bot.state == "punch" then local wemoved = 0 if combattype == ai_combattype_freezermelee then if MCO.manageattackcycle( e ) == 1 then MoveWithAnimation( e, 1 ) wemoved = 1 end end local tFrame = GetAnimationFrame( e ) local tStart = GetEntityAnimationStart( e, 2 ) local tFinish = GetEntityAnimationFinish( e, 2 ) local tDamageFramesStart = GetEntityAnimationStart( e, 5 ) local tDamageFramesFinish = GetEntityAnimationFinish( e, 5 ) if tDamageFramesStart > 0 and tDamageFramesFinish > 0 then -- override defaults if anim5 specifies specific damage frame range tStart = tDamageFramesStart tFinish = tDamageFramesFinish if combattype == ai_combattype_freezermelee then tStart = tStart - 10 tFinish = tFinish + 30 elseif combattype == ai_combattype_bashmelee then tStart = tStart - 25 else tStart = tStart - 10 end end if PlayerDist < 90 then if combattype == ai_combattype_freezermelee then if tFrame >= tStart + 10 and tFrame <= tFinish - 30 then if random( 1, 7 ) == 1 then HurtPlayer( e, random( 1, 2 ) ) end end elseif combattype == ai_combattype_bashmelee then if tFrame >= tStart + 25 and tFrame <= tStart + 35 then HurtPlayer( e, math.random( 2, 3 ) ) ForcePlayer( g_Entity[ e ].angley, 2.0 ) end elseif tFrame >= tStart + 10 and tFrame <= tStart + 15 then HurtPlayer( e, math.random( 2, 3 ) ) ForcePlayer ( g_Entity[ e ].angley, 3.0 ) end end if combattype == ai_combattype_freezermelee then if tFrame >= tFinish - 30 then if MCO.finishbeingattacked( e ) == 1 then ForcePlayer ( g_Entity[ e ].angley, 3.0 ) end else if MCO.hasowner() == 0 then ai_bot.state = "recoverstart" end end end if tFrame >= tFinish then if combattype == ai_combattype_freezermelee or combattype == ai_combattype_bashmelee then ai_bot.state = "recoverstart" else ai_bot.state = afterstate end end if wemoved == 0 then MCC.donotmove( e, ai_bot ) end end end function MCC.recover (e, ai_bot, resumestate ) if ai_bot.state == "recoverstart" then ai_bot.state = "recover" StopAnimation( e ) SetAnimation( 0 ) SetAnimationFrame( e, GetEntityAnimationStart( e, 0 ) ) PlayAnimation( e ) SetAnimationSpeedModulation( e, 1.0 ) StartTimer( e ) end if ai_bot.state == "recover" then MCC.donotmove( e, ai_bot ) if GetTimer( e ) > 100 then ai_bot.state = resumestate end end end function MCC.fireonspot( e, ai_bot, AIObjNo ) if ai_bot.state == "startfireonspot" then ai_bot.state = "fireonspot" StopAnimation( e ) SetAnimation( 0 ) LoopAnimation( e ) SetAnimationSpeedModulation( e, 1.0 ) StartTimer( e ) end if ai_bot.state == "fireonspot" then if g_Entity[ e ].plrvisible == 0 and GetTimer( e ) > 500 then if ai_bot.state ~= "startidle" then ai_bot.state = "startidle" end else RotateToPlayer( e ) MCC.fireweapon( e, ai_bot ) ai_bot.targetx = g_PlayerPosX ai_bot.targety = g_PlayerPosY ai_bot.targetz = g_PlayerPosZ AIEntityGoToPosition( AIObjNo, ai_bot.targetx, ai_bot.targety, ai_bot.targetz ) end MCC.donotmove( e, ai_bot ) end end function MCC.fireweapon( e, ai_bot ) if ai_bot.state ~= "startreload" and ai_bot.state ~= "reload" and ai_bot.state ~= "reloadsettle" then if GetAmmoClip( e ) > 0 then FireWeapon( e ) else local tReloadFinishFrame = GetEntityAnimationFinish( e, 4 ) if tReloadFinishFrame > 0 then ai_bot.state = "startreload" end end end end function MCC.reloadweapon( e, ai_bot ) if ai_bot.state == "startreload" then ai_bot.state = "reload" SetAnimation( 4 ) PlayAnimation( e ) SetAnimationSpeedModulation( e, 1.0 ) local tStart = GetEntityAnimationStart( e, 4 ) SetAnimationFrame( e, tStart ) StartTimer( e ) elseif ai_bot.state == "reload" then if GetTimer( e ) > 500 then local tFrame = GetAnimationFrame( e ) local tStart = GetEntityAnimationStart( e, 4 ) local tFinish = GetEntityAnimationFinish( e, 4 ) local tDiffHalf = ( tFinish - tStart ) / 3 if ( tFrame >= tFinish - tDiffHalf and tFrame <= tFinish ) or tFinish == 0 then ai_bot.state = "reloadsettle" StartTimer( e ) end end MCC.donotmove( e, ai_bot ) elseif ai_bot.state == "reloadsettle" then if GetTimer( e ) > 750 then SetAmmoClip( e, GetAmmoClipMax( e ) ) ai_bot.state = "startidle" end MCC.donotmove( e, ai_bot ) end end function MCC.preexit( e, ai_bot, MoveType ) local tFrame = GetAnimationFrame( e ) local tDyingAnimStart = GetEntityAnimationStart( e, 6 ) local tDyingAnimFinish = GetEntityAnimationFinish( e, 6 ) if tDyingAnimStart > 0 and tDyingAnimFinish > 0 then if ai_bot.state ~= "preexit" then ai_bot.state = "preexit" CharacterControlLimbo( e ) StopAnimation( e ) SetAnimation( 6 ) PlayAnimation( e ) SetAnimationFrame( e, tDyingAnimStart ) SetAnimationSpeedModulation( e, 1.0 ) end else return 1 end if tFrame >= tDyingAnimFinish - 2 then return 1 else return 0 end end function MCC.exit( e ) StopSound( e, 0 ) StopSound( e, 1 ) CollisionOff( e ) end function MCC.evasiveactions( e, ai_bot, AIObjNo, PlayerDist ) if ai_bot.state ~= "idle" and ai_bot.state ~= "move" and ai_bot.state ~= "startmove" and ai_bot.state ~= "roll" and ai_bot.state ~= "strafeleft" and ai_bot.state ~= "strafeleftstart" and ai_bot.state ~= "straferightstart" and ai_bot.state ~= "straferight" and ai_bot.state ~= "duckstart" and ai_bot.state ~= "duck" and ai_bot.state ~= "unduckstart" then if random() > 0.5 then if PlayerDist > 200 then if MCC.playerlooking( e, PlayerDist, 10 ) == 1 then local rays = MCC.checkplayertoentityrays( PlayerDist + 5, 0, 0, 60 ) if rays == AIObjNo or rays == 0 then local temp = math.random( 1, 6 ) --set max to 7 or higher to add a chance to roll if temp < 7 then if MCC.entitylookingatplayer( e, PlayerDist, 10 ) ~= 1 then temp = 99 end end if ai_bot.last_sidestep == nil then ai_bot.last_sidestep = 0 end if temp < 4 then if ai_bot.last_sidestep == 0 then ai_bot.last_sidestep = -15 ai_bot.state = "strafeleftstart" else if ai_bot.last_sidestep < 0 then ai_bot.last_sidestep = ai_bot.last_sidestep + 1 else ai_bot.last_sidestep = ai_bot.last_sidestep - 1 end end elseif temp < 7 then if ai_bot.last_sidestep == 0 then ai_bot.last_sidestep = 15 ai_bot.state = "straferightstart" else if ai_bot.last_sidestep < 0 then ai_bot.last_sidestep = ai_bot.last_sidestep + 1 else ai_bot.last_sidestep = ai_bot.last_sidestep - 1 end end elseif temp < 8 then ai_bot.state = "rollstart" end end end end end end end function MCC.strafeleft( e, ai_bot, speedmod ) local Ent = g_Entity[ e ] if ai_bot.state == "strafeleftstart" then ai_bot.state = "checkforcover" local randomevade = random( 45, 90 ) * -1 local dist = 120 local rays = MCC.checkentityrays( e, dist, randomevade, Ent.obj, 20 ) if rays == 0 then ai_bot.state = "strafeleft" ai_bot.substate = 0 SetAnimationFrame( e, GetEntityAnimationStart( e, 15 ) ) SetAnimation( 15 ) PlayAnimation( e ) Ent.animating = 15 ModulateSpeed( e, 1 ) SetAnimationSpeedModulation( e, 1.5 ) ai_bot.roty = AIGetEntityAngleY( Ent.obj ) local temprot = ai_bot.roty + randomevade SetRotation( e, 0, temprot, 0 ) ai_bot_newroty[ e ] = temprot end elseif ai_bot.state == "strafeleft" then local tFrame = GetAnimationFrame( e ) local tStart = GetEntityAnimationStart( e, 15 ) local tFinish = GetEntityAnimationFinish( e, 15 ) if tFrame >= tStart + 5 and tFrame < tFinish - 8 and Ent.animating == 15 then SetRotation( e, 0, ai_bot_newroty[ e ], 0 ) MoveForward( e, AIGetEntitySpeed( Ent.obj ) * speedmod ) end SetRotation( e, 0, ai_bot.roty, 0 ) AISetEntityPosition( Ent.obj, GetEntityPositionX( e ), GetEntityPositionY( e ), GetEntityPositionZ( e ) ) if tFrame >= tFinish - 2 and tFrame <= tFinish then ai_bot.state = "startfireonspot" SetAnimationSpeedModulation( e, 1 ) end end end function MCC.straferight( e, ai_bot, speedmod ) local Ent = g_Entity[ e ] if ai_bot.state == "straferightstart" then ai_bot.state = "checkforcover" local randomevade = random( 45, 90 ) local dist = 120 local rays = MCC.checkentityrays( e, dist, randomevade, Ent.obj, 20 ) if rays == 0 then ai_bot.state = "straferight" ai_bot.substate = 0 SetAnimationFrame( e, GetEntityAnimationStart( e, 16 ) ) SetAnimation( 16 ) PlayAnimation( e ) Ent.animating = 16 ModulateSpeed( e, 1 ) SetAnimationSpeedModulation( e, 1.5 ) ai_bot.roty = AIGetEntityAngleY( Ent.obj ) local temprot = ai_bot.roty + randomevade SetRotation( e, 0, temprot, 0 ) ai_bot_newroty[ e ] = temprot end elseif ai_bot.state == "straferight" then local tFrame = GetAnimationFrame( e ) local tStart = GetEntityAnimationStart( e, 16 ) local tFinish = GetEntityAnimationFinish( e, 16 ) if tFrame >= tStart + 5 and tFrame < tFinish - 8 then SetRotation( e, 0, ai_bot_newroty[ e ], 0 ) MoveForward( e, AIGetEntitySpeed( Ent.obj )* speedmod ) end SetRotation( e, 0, ai_bot.roty, 0 ) AISetEntityPosition( Ent.obj, GetEntityPositionX( e ), GetEntityPositionY( e ), GetEntityPositionZ( e ) ) if tFrame >= tFinish - 2 and tFrame <= tFinish and Ent.animating == 16 then ai_bot.state = "startfireonspot" SetAnimationSpeedModulation( e, 1 ) end end end function MCC.getangletopoint( e, x, z ) local Ent = g_Entity[ e ] if Ent ~= nil and x > 0 and z > 0 then local destx = x - Ent.x local destz = z - Ent.z local angle = atan( destx, destz ) angle = angle * ( 180.0 / math.pi ) if angle < 0 then angle = 360 + angle elseif angle > 360 then angle = angle - 360 end return angle end return 0 end function MCC.checkfiredrecently( e, window ) if last_fired[ e ] == nil then last_fired[ e ] = g_Time end if g_Time > last_fired[ e ] + window then return 0 else return 1 end end function MCC.checkentityrays( e, dist, ang, obj, yoff ) local new_y = rad( GetEntityAngleY( e ) + ang ) local x1 = GetEntityPositionX( e ) local y1 = GetEntityPositionY( e ) + yoff local z1 = GetEntityPositionZ( e ) local x2 = x1 + sin( new_y ) * dist local z2 = z1 + cos( new_y ) * dist local hit = IntersectAll( x1, y1, z1, x2, y1, z2, obj ) if hit and hit ~= 0 then return hit end hit = RayTerrain( x1, y1, z1, x2, y1, z2 ) if hit and hit ~= 0 then return 1 end return 0 end function MCC.checkplayertoentityrays( dist, ang, obj, yoff ) local new_y = rad( g_PlayerAngY ) + ang local x1, y1, z1 = g_PlayerPosX, g_PlayerPosY + yoff, g_PlayerPosZ local x2 = x1 + sin( new_y ) * dist local z2 = z1 + cos( new_y ) * dist local hit = IntersectAll( x1, y1, z1, x2, y1, z2, obj ) if hit ~= nil and hit ~= 0 then return 1 end hit = RayTerrain( x1, y1, z1, x2, y1, z2 ) if hit ~= nil and hit ~= 0 then return 1 end return 0 end function MCC.playerlooking( e, dis, v ) dis = dis or 3000 v = v or 0.5 if U.PlayerLookingNear( e, dis, v ) then return 1 end return 0 end local function limitAngle ( Angle ) while Angle < 0 do Angle = 360 + Angle end while Angle > 360 do Angle = Angle - 360 end return Angle end function MCC.entitylookingatplayer( e, dis, v ) dis = dis or 3000 v = v or 0.5 local Ent = g_Entity[ e ] if Ent ~= nil then local distx = g_PlayerPosX - Ent.x local disty = g_PlayerPosY - Ent.y local distz = g_PlayerPosZ - Ent.z local Sqdist = distx * distx + disty * disty + distz * distz if Sqdist > dis * dis then return 0 end local angle = limitAngle( atan( distx, distz ) * ( 180.0 / math.pi ) ) local pAng = limitAngle( Ent.angley ) local L = limitAngle( angle - v ) local R = limitAngle( angle + v ) if ( L < R and ( pAng > L and pAng < R ) ) or ( L > R and ( pAng > L or pAng < R ) ) then return 1 end end return 0 end return MCC