diff --git a/ext/Server/Bot.lua b/ext/Server/Bot.lua deleted file mode 100644 index 4891c697..00000000 --- a/ext/Server/Bot.lua +++ /dev/null @@ -1,1541 +0,0 @@ ----@class Bot ----@overload fun(p_Player: Player):Bot -Bot = class('Bot') - -require('__shared/Config') -require('PidController') - ----@type NodeCollection -local m_NodeCollection = require('NodeCollection') ----@type Vehicles -local m_Vehicles = require('Vehicles') ----@type Logger -local m_Logger = Logger('Bot', Debug.Server.BOT) - --- Create a new bot. ----@param p_Player Player -function Bot:__init(p_Player) - -- Player Object. - ---@type Player - self.m_Player = p_Player - -- The player name. - ---@type string - self.m_Name = p_Player.name - -- The ID of the player. - ---@type integer - self.m_Id = p_Player.id - - -- statemachine-part - -- The active state object. - -- TODO: think about making use of subclasses which work like `class ("StateAttacking", BaseSoldierState)` & `class ("StateInVehicleAttacking", BaseInVehicleState)` & `class ("BaseInVehicleState", BaseVehicleState)`. - ---@type StateAttacking|StateIdle|StateInVehicleAttacking|StateInVehicleChopperControl|StateInVehicleIdle|StateInVehicleJetControl|StateInVehicleMoving|StateInVehicleStationaryAAControl|StateMoving|StateOnVehicleAttacking|StateOnVehicleIdle - self.m_ActiveState = g_BotStates.States.Idle - -- TODO: only used in StateInVehicleAttacking. Might make sense to move it to that class. Or move it to a BaseState class and inherit that in every other state as a subclass. - -- The timer of the current state. - self.m_StateTimer = 0.0 - - --[[ - TODO: move this to an inner class. Like `Bot.Persona = class('Bot.Persona')` or `Bot.Attributes = class('Bot.Attributes')` and have there all the attributes. - If it is going to be persona you can add even more stuff to it like name, clan tag, dog tags etc. - Percentage kit choice, percentage weapon choice, behaviors like "TriesToRevengeAfterXDeaths" by pulling out the noobtube or "PlayForKills", "PlayForObjective" etc etc. - This can get a huge thing in the future if it gets fed with data. - ]] - -- create some character proporties - ---@type BotBehavior - self.m_Behavior = nil - self.m_Reaction = 0.0 - self.m_Accuracy = 0.0 - self.m_Skill = 0.0 - self.m_PrefWeapon = "" - self.m_PrefVehicle = "" - - self._AttackPosition = Vec3.zero - - -- Common settings. - ---@type BotSpawnModes - self._SpawnMode = BotSpawnModes.NoRespawn - ---@type BotMoveModes - self._MoveMode = BotMoveModes.Standstill - self._ForcedMovement = false - - -- TODO: this whole block could be moved to an inner class `Bot.Loadout = class('Bot.Loadout')`. - ---@type BotKits|integer - self.m_Kit = nil - -- Only used in BotSpawner. - -- The bot color is the soldier camo (color). - ---@type BotColors|integer - self.m_Color = nil - ---@type Weapon|nil - self.m_ActiveWeapon = nil - self.m_ActiveVehicle = nil - self.m_ActiveGmWeaponName = nil - ---@type Weapon|nil - self.m_Primary = nil - ---@type Weapon|nil - self.m_Pistol = nil - ---@type Weapon|nil - self.m_PrimaryGadget = nil - ---@type Weapon|nil - self.m_SecondaryGadget = nil - ---@type Weapon|nil - self.m_Grenade = nil - ---@type Weapon|nil - self.m_Knife = nil - - self._Respawning = false - self.m_HasBeacon = false - self.m_DontRevive = false - self.m_AttackPriority = 1 - - -- Timers. - self._SpawnDelayTimer = 0.0 - self._WayWaitTimer = 0.0 - self._VehicleWaitTimer = 0.0 - self._VehicleSeatTimer = 0.0 - self._VehicleTakeoffTimer = 0.0 - self._WayWaitYawTimer = 0.0 - self._ObstacleSequenceTimer = 0.0 - self._StuckTimer = 0.0 - self._ShotTimer = 0.0 - self._SoundTimer = 30.0 - self._VehicleSecondaryWeaponTimer = 0.0 - self._ShootModeTimer = 0.0 - self._ReloadTimer = 0.0 - self._DeployTimer = 0.0 - self._AttackModeMoveTimer = 0.0 - self._MeleeCooldownTimer = 0.0 - self._ShootTraceTimer = 0.0 - self._ActionTimer = 0.0 - self._BrakeTimer = 0.0 - self._SpawnProtectionTimer = 0.0 - self._DefendTimer = 0.0 - self._SidewardsTimer = 0.0 - self._KillYourselfTimer = 0.0 - self._RocketCooldownTimer = 0.0 - - -- Shared movement vars. - ---@type BotMoveModes - self.m_ActiveMoveMode = BotMoveModes.Standstill - ---@type BotMoveSpeeds - self.m_ActiveSpeedValue = BotMoveSpeeds.NoMovement - self.m_KnifeMode = false - - ---@class ActiveInput - ---@field value number - ---@field reset boolean - - ---@type table - self.m_ActiveInputs = {} - self.m_DelayedInputs = {} - - -- Sidewards movement. - self.m_YawOffset = 0.0 - self.m_StrafeValue = 0.0 - - -- Advanced movement. - ---@type BotAttackModes - self._AttackMode = BotAttackModes.RandomNotSet - ---@type BotActionFlags - self._ActiveAction = BotActionFlags.NoActionActive - ---@type Waypoint|nil - self._CurrentWayPoint = nil - self._TargetYaw = 0.0 - self._TargetYawMovementVehicle = 0.0 - self._TargetPitch = 0.0 - ---@type Waypoint|nil - self._TargetPoint = nil - ---@type Waypoint|nil - self._NextTargetPoint = nil - self._PathIndex = 0 - self._LastWayDistance = 0.0 - self._InvertPathDirection = false - self._ExitVehicleActive = false - self._ObstacleRetryCounter = 0 - self._Objective = '' - self._ObjectiveMode = BotObjectiveModes.Default - self._OnSwitch = false - self._ActiveDelay = 0.0 - self._VehicleMoveWhileShooting = false - self._FireCycleModifier = 1.0 - - -- Vehicle stuff. - ---@type integer|nil - self._VehicleMovableId = -1 - self._LastVehicleYaw = 0.0 - self._VehicleReadyToShoot = false - self._FullVehicleSteering = false - self._VehicleDirBackPositive = false - self._JetAbortAttackActive = false - self._JetTakeoffActive = false - self._ExitVehicleHealth = 0.0 - self._LastVehicleHealth = 0.0 - self._VehicleWeaponSlotToUse = 1 - self._ActiveVehicleWeaponSlot = 0 - ---@type ControllableEntity|nil - self._RepairVehicleEntity = nil - -- PID Controllers. - -- Normal driving. - ---@type PidController - self._Pid_Drv_Yaw = PidController(5, 0.05, 0.2, 1.0) - -- Chopper / Plane - ---@type PidController - self._Pid_Drv_Throttle = PidController(3, 0.05, 0.2, 1.0) - ---@type PidController - self._Pid_Drv_Tilt = PidController(6, 0.1, 0.2, 1.0) - ---@type PidController - self._Pid_Drv_Roll = PidController(6, 0.1, 0.2, 1.0) - -- Guns. - ---@type PidController - self._Pid_Att_Yaw = PidController(10, 2.0, 2.0, 1.0) - ---@type PidController - self._Pid_Att_Pitch = PidController(10, 2.0, 2.0, 1.0) - - -- Shooting. - self._Shoot = false - ---@type Player|nil - self._ShootPlayer = nil - self._ActiveShootDuration = 0.0 - self._DoneShootDuration = 0.0 - self._DontAttackPlayers = false - ---@type VehicleTypes - self._ShootPlayerVehicleType = VehicleTypes.NoVehicle - self._ShootPlayerName = '' - self._DistanceToPlayer = 0.0 - ---@type BotWeapons - self._WeaponToUse = BotWeapons.Primary - -- To-do: add emmylua type. - self._ShootWayPoints = {} - ---@type Vec3[] - self._KnifeWayPositions = {} - self._Accuracy = 0.0 - self._AccuracySniper = 0.0 - self._SkillFound = false - - ---@type Player|nil - self._TargetPlayer = nil -end - ----@param p_State any -function Bot:SetState(p_State) - self.m_ActiveState = p_State - self.m_StateTimer = 0.0 -end - --- ============================================= --- Events --- ============================================= - --- ============================================= --- Functions --- ============================================= - --- ============================================= --- Public Functions --- ============================================= - --- Initialize a revive on a teammate. ----@param p_Player Player the player to revive. -function Bot:Revive(p_Player) - -- Config for revives disabled. - if not Config.BotsRevive then return end - - -- TODO: if some mod allows defib in other kits then this isn't accurate. - -- Only Assaults can revive. - if self.m_Kit ~= BotKits.Assault then return end - - -- TODO: if some mod allows defib in one of these modes then this isn't accurate. - -- These gamemodes don't allow revives. - if Globals.IsGm or Globals.IsScavenger then return end - - -- The player to revive is not revivable. Could be already alive or dead. - if not p_Player.corpse or p_Player.corpse.isDead then return end - - -- The bot is not alive. - if not self.m_Player.soldier then return end - - --[[ - TODO: if some mod allows defib in other weapon slots then this isn't accurate. - Might make sense to create a m_HasDefibrillator bool inside the non-existing Bot.Loadout class. - And then get rid of all other checks that are not needed. - ]] - -- Make sure the bot has a defibrillator. - if self.m_Player.soldier.weaponsComponent.weapons[6] and - string.find(self.m_Player.soldier.weaponsComponent.weapons[6].name, "Defibrillator") then - self._ActiveAction = BotActionFlags.ReviveActive - self._ShootPlayerName = p_Player.name - self._ShootPlayer = PlayerManager:GetPlayerByName(self._ShootPlayerName) - self._ShootPlayerVehicleType = VehicleTypes.NoVehicle -- does not matter here - end -end - --- For vehicles to block missiles this fires a flare or smoke. ----@param p_TimeDelay number -function Bot:FireFlareSmoke(p_TimeDelay) - self:_SetDelayedInput(EntryInputActionEnum.EIAFireCountermeasure, 1, p_TimeDelay) -end - ----@param p_Player Player -function Bot:Repair(p_Player) - if self.m_Kit == BotKits.Engineer and p_Player.soldier ~= nil and p_Player.controlledControllable ~= nil then - self._ActiveAction = BotActionFlags.RepairActive - self._RepairVehicleEntity = p_Player.controlledControllable - self._LastVehicleHealth = 0.0 - self._ShootPlayerName = p_Player.name - self._ShootPlayer = PlayerManager:GetPlayerByName(self._ShootPlayerName) - self._ShootPlayerVehicleType = VehicleTypes.NoVehicle -- does not matter here - self._ShootModeTimer = Registry.BOT.MAX_TIME_TRY_REPAIR - end -end - -function Bot:ResetVehicleTimer() - self._VehicleWaitTimer = 0.0 -end - ----@param p_Player Player -function Bot:EnterVehicleOfPlayer(p_Player) - self._ActiveAction = BotActionFlags.EnterVehicleActive - self._ShootPlayerName = p_Player.name - self._ShootPlayer = PlayerManager:GetPlayerByName(self._ShootPlayerName) - self._ShootPlayerVehicleType = VehicleTypes.NoVehicle -- does not matter here - self._ShootModeTimer = 12.0 -end - -function Bot:UpdateObjective(p_Objective, p_ObjectiveMode) - local s_AllObjectives = m_NodeCollection:GetKnownObjectives() - - for l_Objective, _ in pairs(s_AllObjectives) do - if l_Objective == p_Objective then - self:SetObjective(p_Objective, p_ObjectiveMode) - break - end - end -end - -function Bot:DeployIfPossible() - -- Deploy from time to time. - if self.m_PrimaryGadget ~= nil and (self.m_Kit == BotKits.Support or self.m_Kit == BotKits.Assault) and not Globals.IsGm then - if self.m_PrimaryGadget.type == WeaponTypes.Ammobag or self.m_PrimaryGadget.type == WeaponTypes.Medkit then - self:AbortAttack() - self._WeaponToUse = BotWeapons.Gadget1 - self._DeployTimer = 0.0 - end - end -end - ----@return boolean -function Bot:_DoExitVehicle() - if self._ExitVehicleActive then - self:AbortAttack() - self.m_Player:ExitVehicle(true, false) - self.m_ActiveVehicle = nil - self:SetState(g_BotStates.States.Moving) - local s_Node = g_GameDirector:FindClosestPath(self.m_Player.soldier.worldTransform.trans, false, true, nil) - - if s_Node ~= nil then - -- Switch to foot. - self._InvertPathDirection = false - self._PathIndex = s_Node.PathIndex - self._CurrentWayPoint = s_Node.PointIndex - self._LastWayDistance = 1000.0 - end - self._KillYourselfTimer = 0.0 - self._ExitVehicleActive = false - self._DontAttackPlayers = false - return true - end - - return false -end - -function Bot:ExitVehicle() - self._ExitVehicleActive = true -end - ----@param p_ShootBackAfterHit boolean ----@param p_Player Player | nil ----@param p_CheckShootTimer boolean ----@param p_IsNewTarget boolean ----@return boolean -function Bot:IsReadyToAttack(p_ShootBackAfterHit, p_Player, p_CheckShootTimer, p_IsNewTarget) - -- update timers first - if self._ShootPlayerName == '' then - self._DoneShootDuration = 0.0 - elseif p_Player and not p_IsNewTarget then - self._DoneShootDuration = self._DoneShootDuration + (self._ActiveShootDuration - self._ShootModeTimer) - end - - if self._ActiveAction == BotActionFlags.OtherActionActive or - (self._ActiveAction == BotActionFlags.ReviveActive and not p_ShootBackAfterHit) or - self._ActiveAction == BotActionFlags.RepairActive or - self._ActiveAction == BotActionFlags.EnterVehicleActive or - self._ActiveAction == BotActionFlags.GrenadeActive or - self._DontAttackPlayers then - return false - end - - if not p_CheckShootTimer then - return true - end - - local s_InVehicle = g_BotStates:IsInVehicleState(self.m_ActiveState) - if self._ShootPlayerName == '' or - (p_Player and p_IsNewTarget) or -- if still the same enemy, you can trigger directly again - (s_InVehicle and (self._DoneShootDuration > Config.BotVehicleMinTimeShootAtPlayer)) or - (not s_InVehicle and (self._DoneShootDuration > Config.BotMinTimeShootAtPlayer)) or - (self.m_KnifeMode and self._ShootModeTimer > ((Config.BotMinTimeShootAtPlayer * 0.5))) then - return true - else - return false - end -end - ----@param p_EnemyVehicleType VehicleTypes ----@return integer -function Bot:GetAttackPriority(p_EnemyVehicleType) - local s_BotVehicleType = m_Vehicles:VehicleType(self.m_ActiveVehicle) - - -- attack as soldier - if s_BotVehicleType == VehicleTypes.NoVehicle and self.m_PrimaryGadget ~= nil then - if self.m_PrimaryGadget.type == WeaponTypes.MissileAir - and m_Vehicles:IsAirVehicleType(p_EnemyVehicleType) - then - return 2 - elseif self.m_PrimaryGadget.type == WeaponTypes.MissileLand - and m_Vehicles:IsArmoredVehicleType(p_EnemyVehicleType) - then - return 2 - end - end - - -- attack as air-vehicle - if m_Vehicles:IsAirVehicleType(s_BotVehicleType) then - if m_Vehicles:IsAirVehicleType(p_EnemyVehicleType) then - return 3 - elseif m_Vehicles:IsArmoredVehicleType(p_EnemyVehicleType) then - return 2 - end - end - - -- attack as ground-vehicle - if m_Vehicles:IsArmoredVehicleType(s_BotVehicleType) - and m_Vehicles:IsArmoredVehicleType(p_EnemyVehicleType) - then - return 2 - end - - return 1 -end - ----@return number -function Bot:GetAttackDistance(p_ShootBackAfterHit, p_VehicleAttackMode) - local s_AttackDistance = 0.0 - - if not g_BotStates:IsInVehicleState(self.m_ActiveState) then - if p_VehicleAttackMode and (p_VehicleAttackMode == VehicleAttackModes.AttackWithMissileAir) then - s_AttackDistance = Config.MaxShootDistanceMissileAir - elseif self.m_ActiveWeapon and self.m_ActiveWeapon.type == WeaponTypes.Sniper then - if p_ShootBackAfterHit then - s_AttackDistance = Config.MaxDistanceShootBackSniper - else - s_AttackDistance = Config.MaxShootDistanceSniper - end - else - if p_ShootBackAfterHit then - s_AttackDistance = Config.MaxDistanceShootBack - else - s_AttackDistance = Config.MaxShootDistance - end - end - else - if m_Vehicles:IsGunship(self.m_ActiveVehicle) then - s_AttackDistance = Config.MaxShootDistanceGunship - elseif not m_Vehicles:IsAirVehicle(self.m_ActiveVehicle) - and m_Vehicles:IsNotVehicleType(self.m_ActiveVehicle, VehicleTypes.MobileArtillery) - and not m_Vehicles:IsAAVehicle(self.m_ActiveVehicle) then - if p_ShootBackAfterHit then - s_AttackDistance = Config.MaxShootDistanceNoAntiAir * 2 - else - s_AttackDistance = Config.MaxShootDistanceNoAntiAir - end - else - if p_ShootBackAfterHit then - s_AttackDistance = Config.MaxShootDistanceVehicles * 2 - else - s_AttackDistance = Config.MaxShootDistanceVehicles - end - end - end - - return s_AttackDistance -end - -function Bot:UpdateDontAttackFlag() - -- Don't attack as driver in some vehicles. - if g_BotStates:IsInVehicleState(self.m_ActiveState) and self.m_Player.controlledEntryId == 0 then - if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.Chopper) then -- do not include ScoutChopper here (they can attack) - if self._VehicleMovableId == -1 then - self._DontAttackPlayers = true -- Tranotort-choppers don't attack as driver. - return - elseif self.m_Player.controlledControllable:GetPlayerInEntry(1) ~= nil and not Config.ChopperDriversAttack then -- Don't attack if gunner available and config is false. - self._DontAttackPlayers = true - return - end - end - - -- If jet targets get assigned in another way. - if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.Plane) then - self._DontAttackPlayers = true - return - end - - if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.NoArmorVehicle) then - self._DontAttackPlayers = true - return - end - - if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.LightVehicle) then - self._DontAttackPlayers = true - return - end - - -- If stationary AA targets get assigned in another way. - if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.StationaryAA) then - self._DontAttackPlayers = true - return - end - end - self._DontAttackPlayers = false -end - ----@param p_Player Player ----@param p_IgnoreYaw boolean ----@return boolean -function Bot:ShootAt(p_Player, p_IgnoreYaw) - if p_IgnoreYaw and self._DefendTimer == 0.0 then -- was hit, not in defend-mode, check for special behavior - if self.m_Behavior == BotBehavior.DontShootBackHide then - self._ActionTimer = 7.0 - self._ActiveAction = BotActionFlags.HideOnAttack - return false - elseif self.m_Behavior == BotBehavior.DontShootBackBail then - self._ActionTimer = 5.0 - self._ActiveAction = BotActionFlags.RunAway - return false - end - end - - -- Don't shoot at teammates. - if self.m_Player.teamId == p_Player.teamId then - return false - end - - if p_Player.soldier == nil or self.m_Player.soldier == nil then - return false - end - - -- Check for vehicles. - local s_Type = m_Vehicles:FindOutVehicleType(p_Player) - - -- Don't shoot at stationary AA. - if s_Type == VehicleTypes.StationaryAA then - return false - end - - local s_NewAttackPriority = self:GetAttackPriority(s_Type) - local s_NewTarget = self._ShootPlayerName ~= p_Player.name - - local s_Ready = (s_NewAttackPriority > self.m_AttackPriority) or self:IsReadyToAttack(p_IgnoreYaw, p_Player, true, s_NewTarget) - if not s_Ready or self._Shoot == false or self._DontAttackPlayers then - return false - end - - -- Don't shoot if too far away. - self._DistanceToPlayer = 0.0 - local s_PlayerPos = nil - local s_TargetPos = nil - if s_Type == VehicleTypes.MavBot or s_Type == VehicleTypes.MobileArtillery then - s_TargetPos = p_Player.controlledControllable.transform.trans:Clone() - else - s_TargetPos = p_Player.soldier.worldTransform.trans:Clone() - end - - if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.MobileArtillery) then - s_PlayerPos = self.m_Player.controlledControllable.transform.trans:Clone() - else - s_PlayerPos = self.m_Player.soldier.worldTransform.trans:Clone() - end - - self._DistanceToPlayer = s_TargetPos:Distance(s_PlayerPos) - - local s_VehicleAttackMode = nil - local s_InVehicle = g_BotStates:IsInVehicleState(self.m_ActiveState) - if s_Type ~= VehicleTypes.NoVehicle then - s_VehicleAttackMode = m_Vehicles:CheckForVehicleAttack(s_Type, self) - if s_VehicleAttackMode == VehicleAttackModes.NoAttack then - return false - end - end - - -- in lightAA: only attack air-vehicles - if s_InVehicle and m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.LightAA) and not m_Vehicles:IsAirVehicleType(s_Type) then - return false - end - - local s_AttackDistance = self:GetAttackDistance(p_IgnoreYaw, s_VehicleAttackMode) - - -- Don't attack if too far away. - if self._DistanceToPlayer > s_AttackDistance then - return false - end - - self._ShootPlayerVehicleType = s_Type - - local s_DifferenceYaw = 0 - local s_Pitch = 0 - local s_FovHalf = 0 - local s_PitchHalf = 0 - - -- If target is air-vehicle and bot is in AA → ignore yaw. - if m_Vehicles:IsAirVehicleType(s_Type) then - if (s_InVehicle and m_Vehicles:IsAAVehicle(self.m_ActiveVehicle)) or - (s_VehicleAttackMode == VehicleAttackModes.AttackWithMissileAir) then - p_IgnoreYaw = true - end - end - - local s_Bot = g_BotManager:GetBotByName(p_Player.name) - - local s_SoundDistance = 100 - if s_Type == VehicleTypes.NoVehicle then - s_SoundDistance = 50 - end - - if s_Bot ~= nil - and s_Bot._SoundTimer <= 5.0 - and self._DistanceToPlayer < s_SoundDistance - then - p_IgnoreYaw = true - end - - if not p_IgnoreYaw then - local s_OldYaw = self.m_Player.input.authoritativeAimingYaw - - local s_DifferenceY = s_TargetPos.z - s_PlayerPos.z - local s_DifferenceX = s_TargetPos.x - s_PlayerPos.x - local s_DifferenceZ = s_TargetPos.y - s_PlayerPos.y - - local s_AtanYaw = math.atan(s_DifferenceY, s_DifferenceX) - local s_Yaw = (s_AtanYaw > math.pi / 2) and (s_AtanYaw - math.pi / 2) or (s_AtanYaw + 3 * math.pi / 2) - - local s_DistanceHoizontal = math.sqrt(s_DifferenceX ^ 2 + s_DifferenceY ^ 2) - s_Pitch = math.abs(math.atan(s_DifferenceZ, s_DistanceHoizontal)) - - s_DifferenceYaw = math.abs(s_OldYaw - s_Yaw) - - if s_DifferenceYaw > math.pi then - s_DifferenceYaw = math.pi * 2 - s_DifferenceYaw - end - - if s_InVehicle then - if m_Vehicles:IsAAVehicle(self.m_ActiveVehicle) then - s_FovHalf = Config.FovVehicleAAForShooting / 360 * math.pi - s_PitchHalf = Config.FovVerticleVehicleAAForShooting / 360 * math.pi - elseif m_Vehicles:IsAirVehicle(self.m_ActiveVehicle) and - self.m_Player.controlledEntryId == 0 then - s_FovHalf = Config.FovVehicleForShooting / 360 * math.pi - s_PitchHalf = Config.FovVerticleChopperForShooting / 360 * math.pi - else - s_FovHalf = Config.FovVehicleForShooting / 360 * math.pi - s_PitchHalf = Config.FovVerticleVehicleForShooting / 360 * math.pi - end - else - s_FovHalf = Config.FovForShooting / 360 * math.pi - s_PitchHalf = Config.FovVerticleForShooting / 360 * math.pi - end - end - - if p_IgnoreYaw or (s_DifferenceYaw < s_FovHalf and s_Pitch < s_PitchHalf) then - if self._Shoot then - -- only reset ShotTimer, if not already attacking - if self._ShootModeTimer <= 0 then - self._ShotTimer = -self:GetFirstShotDelay(self._DistanceToPlayer, false) - end - if s_InVehicle then - self._ShootModeTimer = Config.BotVehicleFireModeDuration - else - self._ShootModeTimer = Config.BotFireModeDuration - if self.m_Behavior == BotBehavior.LongerAttacking then - self._ShootModeTimer = Config.BotFireModeDuration * 1.7 - elseif self.m_Behavior == BotBehavior.AbortAttackFast then - self._ShootModeTimer = Config.BotFireModeDuration * 0.5 - end - end - self._ActiveShootDuration = self._ShootModeTimer - if s_NewTarget then - self._DoneShootDuration = 0.0 - self._ShootPlayerName = p_Player.name - self._ShootPlayer = PlayerManager:GetPlayerByName(self._ShootPlayerName) - local s_PlayerData = g_PlayerData:GetData(self._ShootPlayerName) - if s_PlayerData then - self._ShootPlayerVehicleType = s_PlayerData.Vehicle - else - self._ShootPlayerVehicleType = VehicleTypes.NoVehicle - end - end - self._KnifeWayPositions = {} - self._VehicleReadyToShoot = false - - if self.m_KnifeMode then - self._KnifeWayPositions[#self._KnifeWayPositions + 1] = p_Player.soldier.worldTransform.trans:Clone() - end - - if Globals.IsGm then - -- check for changed weapon - BotSpawner:UpdateGmWeapon(self) - end - self._KillYourselfTimer = 0.0 - self.m_AttackPriority = s_NewAttackPriority - return true - else - self._ShootPlayerName = '' - self._ShootPlayer = nil - self._ShootModeTimer = 0.0 - return false - end - end - - return false -end - ----@param p_DeltaTime number -function Bot:_CheckForVehicleActions(p_DeltaTime, p_AttackActive) - local s_InVehicle = g_BotStates:IsInVehicleState(self.m_ActiveState) - local s_OnVehicle = g_BotStates:IsOnVehicleState(self.m_ActiveState) - - local s_VehicleEntity = self.m_Player.controlledControllable - if s_VehicleEntity and s_VehicleEntity.typeInfo.name == "ServerSoldierEntity" then - s_VehicleEntity = self.m_Player.attachedControllable - end - -- No vehicle found. - if not s_VehicleEntity then - return - end - - -- Check if exit of vehicle is needed (because of low health). - if not self._ExitVehicleActive then - local s_CurrentVehicleHealth = 0 - if s_VehicleEntity then - s_CurrentVehicleHealth = PhysicsEntity(s_VehicleEntity).internalHealth - end - - if s_CurrentVehicleHealth <= self._ExitVehicleHealth then - if math.random(0, 100) <= Registry.VEHICLES.VEHICLE_PROPABILITY_EXIT_LOW_HEALTH then - self:AbortAttack() - self:ExitVehicle() - end - end - end - - self:_CheckShouldExitVehicleIfPassenger(s_VehicleEntity, s_OnVehicle) - - if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.MobileArtillery) - or m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.LightAA) - then - -- Change seat, for attack. - local s_DesiredSeat = 0 - - -- Switch to gunner seat - if p_AttackActive then - s_DesiredSeat = 1 - end - - if s_DesiredSeat ~= self.m_Player.controlledEntryId - and s_VehicleEntity:GetPlayerInEntry(s_DesiredSeat) == nil - then - self.m_Player:EnterVehicle(s_VehicleEntity, s_DesiredSeat) - self:UpdateVehicleMovableId() - end - else - -- Check if better seat is available. - self._VehicleSeatTimer = self._VehicleSeatTimer + p_DeltaTime - if self._VehicleSeatTimer >= Registry.VEHICLES.VEHICLE_SEAT_CHECK_CYCLE_TIME then - self._VehicleSeatTimer = 0 - - if s_InVehicle and self.m_ActiveVehicle.Type ~= VehicleTypes.Gunship then -- In vehicle. - for l_SeatIndex = 0, self.m_Player.controlledEntryId do - if s_VehicleEntity:GetPlayerInEntry(l_SeatIndex) == nil then - -- Better seat available → switch seats. - m_Logger:Write('switch to better seat') - self:AbortAttack() - self.m_Player:EnterVehicle(s_VehicleEntity, l_SeatIndex) - self:UpdateVehicleMovableId() - break - end - end - elseif s_OnVehicle then -- Only passenger. - local s_LowestSeatIndex = -1 - for l_SeatIndex = 0, s_VehicleEntity.entryCount - 1 do - if s_VehicleEntity:GetPlayerInEntry(l_SeatIndex) == nil then - -- Maybe better seat available. - s_LowestSeatIndex = l_SeatIndex - else -- Check if there is a gap. - if s_LowestSeatIndex >= 0 then -- There is a better place. - m_Logger:Write('switch to better seat') - self:AbortAttack() - self.m_Player:EnterVehicle(s_VehicleEntity, s_LowestSeatIndex) - self:UpdateVehicleMovableId() - break - end - end - end - end - end - end -end - -function Bot:_CheckShouldExitVehicleIfPassenger(p_VehicleEntity, p_OnVehicle) - if self._ExitVehicleActive then - return - end - - if not p_OnVehicle - and not m_Vehicles:IsPassengerSeat(p_VehicleEntity, self.m_Player.controlledEntryId) - then - return - end - - local s_ShouldExit = false - local s_ExitDistance = Registry.BOT.PASSENGER_EXIT_DISTANCE - local s_AllCapturePoints = g_GameDirector:GetAllCapturePoints() - local s_ActiveMcoms = g_GameDirector:GetActiveMcomPositions() - local s_CurrentPosition = self.m_Player.soldier.worldTransform.trans:Clone() - s_CurrentPosition.y = 0 - - s_Coordinates = {} - for l_Index = 1, #s_AllCapturePoints do - s_Coordinates[#s_Coordinates + 1] = s_AllCapturePoints[l_Index].transform.trans - end - for l_Index = 1, #s_ActiveMcoms do - s_Coordinates[#s_Coordinates + 1] = s_ActiveMcoms[l_Index] - end - - for l_Index = 1, #s_Coordinates do - local l_Coord = s_Coordinates[l_Index] - local s_Position = l_Coord:Clone() - s_Position.y = 0 - - if s_Position:Distance(s_CurrentPosition) < s_ExitDistance then - s_ShouldExit = true - break - end - end - - if s_ShouldExit then - self:AbortAttack() - self:ExitVehicle() - end -end - -function Bot:ResetVars() - self._SpawnMode = BotSpawnModes.NoRespawn - self._ForcedMovement = false - self._ActiveAction = BotActionFlags.NoActionActive - self._PathIndex = 0 - self._Respawning = false - self._Shoot = false - self._TargetPlayer = nil - self._ShootPlayer = nil - self._ShootPlayerName = '' - self._InvertPathDirection = false - self._ExitVehicleActive = false - self._ShotTimer = 0.0 - self._SoundTimer = 30.0 - self._ActiveDelay = 0.0 - self._TargetPoint = nil - self._NextTargetPoint = nil - self._KnifeWayPositions = {} - self._ShootWayPoints = {} - self._SpawnDelayTimer = 0.0 - self._KillYourselfTimer = 0.0 - self._RocketCooldownTimer = 0.0 - self._SpawnProtectionTimer = 0.0 - self._Objective = '' - self._WeaponToUse = BotWeapons.Primary -end - ----@param p_DistanceToTarget number ----@param p_ReducedTiming boolean ----@return number -function Bot:GetFirstShotDelay(p_DistanceToTarget, p_ReducedTiming) - local s_Delay = (Config.BotFirstShotDelay + (Config.ReactionTime * MathUtils:GetRandom(0.8, 1.2) * self.m_Reaction)) - - if p_ReducedTiming then - s_Delay = s_Delay * 0.6 - end - - -- Slower reaction on greater distances. 100 m = 0.5 extra seconda. - s_Delay = s_Delay + (p_DistanceToTarget * 0.005 * (1.0 + ((self.m_Reaction - 0.5) * 0.4))) -- +-20% depending on reaction-characteristic of bot - return s_Delay -end - ----@param p_Player Player -function Bot:SetVarsStatic(p_Player) - self._SpawnMode = BotSpawnModes.NoRespawn - self._ForcedMovement = true - self._MoveMode = BotMoveModes.Standstill - self._PathIndex = 0 - self._Respawning = false - self._Shoot = false - self._TargetPlayer = p_Player -end - ----@param p_Player Player|nil ----@param p_UseRandomWay boolean ----@param p_PathIndex integer ----@param p_CurrentWayPoint any To-do: add emmylua type ----@param p_InverseDirection boolean -function Bot:SetVarsWay(p_Player, p_UseRandomWay, p_PathIndex, p_CurrentWayPoint, p_InverseDirection) - if p_UseRandomWay then - self._SpawnMode = BotSpawnModes.RespawnRandomPath - self._TargetPlayer = nil - self._Shoot = Globals.AttackWayBots - self._Respawning = Globals.RespawnWayBots - else - self._SpawnMode = BotSpawnModes.RespawnFixedPath - self._TargetPlayer = p_Player - self._Shoot = false - self._Respawning = false - end - - self.m_ActiveMoveMode = BotMoveModes.Paths - self._PathIndex = p_PathIndex - self._CurrentWayPoint = p_CurrentWayPoint - self._InvertPathDirection = p_InverseDirection -end - ----@return boolean -function Bot:IsStaticMovement() - if self._ForcedMovement and (self._MoveMode == BotMoveModes.Standstill or - self._MoveMode == BotMoveModes.Mirror or - self._MoveMode == BotMoveModes.Mimic) then - return true - else - return false - end -end - ----@param p_MoveMode BotMoveModes|integer -function Bot:SetMoveMode(p_MoveMode) - self._ForcedMovement = true - self._MoveMode = p_MoveMode -end - ----@param p_Respawn boolean -function Bot:SetRespawn(p_Respawn) - self._Respawning = p_Respawn -end - ----@param p_Shoot boolean -function Bot:SetShoot(p_Shoot) - self._Shoot = p_Shoot -end - -function Bot:SetObjectiveIfPossible(p_Objective, p_ObjectiveMode) - if self._Objective ~= p_Objective and p_Objective ~= '' then - local s_Point = m_NodeCollection:Get(self._CurrentWayPoint, self._PathIndex) - - if s_Point ~= nil then - local s_Direction, s_BestWaypoint = m_NodeCollection:ObjectiveDirection(s_Point, p_Objective, g_BotStates:IsInVehicleState(self.m_ActiveState)) - if s_BestWaypoint then - self._Objective = p_Objective - self._ObjectiveMode = p_ObjectiveMode - if s_Direction then - self._InvertPathDirection = (s_Direction == 'Previous') - end - return true - end - end - end - return false -end - -function Bot:SetObjective(p_Objective, p_ObjectiveMode) - if self._Objective ~= p_Objective or p_ObjectiveMode ~= self._ObjectiveMode then - self._Objective = p_Objective or '' - self._ObjectiveMode = p_ObjectiveMode or BotObjectiveModes.Default - local s_Point = m_NodeCollection:Get(self._CurrentWayPoint, self._PathIndex) - - if s_Point ~= nil then - local s_Direction = m_NodeCollection:ObjectiveDirection(s_Point, self._Objective, g_BotStates:IsInVehicleState(self.m_ActiveState)) - if s_Direction then - self._InvertPathDirection = (s_Direction == 'Previous') - end - end - end -end - -function Bot:ResetSkill() - self._SkillFound = false -end - ----@return string -function Bot:GetObjective() - return self._Objective -end - ----@return integer|BotObjectiveModes -function Bot:GetObjectiveMode() - return self._ObjectiveMode -end - ----@return integer|BotSpawnModes -function Bot:GetSpawnMode() - return self._SpawnMode -end - ----@return integer -function Bot:GetWayIndex() - return self._PathIndex -end - ----@return integer -function Bot:GetPointIndex() - return self._CurrentWayPoint -end - ----@return Player|nil -function Bot:GetTargetPlayer() - return self._TargetPlayer -end - ----@return boolean -function Bot:IsInactive() - if self.m_Player.soldier ~= nil or self._SpawnMode ~= BotSpawnModes.NoRespawn then - return false - else - return true - end -end - ----@return boolean -function Bot:IsStuck() - if self._ObstacleSequenceTimer ~= 0 then - return true - else - return false - end -end - -function Bot:ResetSpawnVars() - -- Timers - self._SpawnDelayTimer = 0.0 - self._WayWaitTimer = 0.0 - self._VehicleWaitTimer = 0.0 - self._VehicleSeatTimer = 0.0 - self._VehicleTakeoffTimer = 0.0 - self._WayWaitYawTimer = 0.0 - self._ObstacleSequenceTimer = 0.0 - self._StuckTimer = 0.0 - self._SoundTimer = 30.0 - self._ShotTimer = 0.0 - self._VehicleSecondaryWeaponTimer = 0.0 - self._ShootModeTimer = 0.0 - self._ReloadTimer = 0.0 - self._AttackModeMoveTimer = 0.0 - self._MeleeCooldownTimer = 0.0 - self._ShootTraceTimer = 0.0 - self._ActionTimer = 0.0 - self._BrakeTimer = 0.0 - self._DefendTimer = 0.0 - self._SidewardsTimer = 0.0 - self._KillYourselfTimer = 0.0 - self._RocketCooldownTimer = 0.0 - self._SpawnProtectionTimer = 2.0 - self._DeployTimer = MathUtils:GetRandomInt(1, Config.DeployCycle) - - self._ObstacleRetryCounter = 0 - self._LastWayDistance = 1000.0 - self._ShootPlayer = nil - self._DontAttackPlayers = false - self._ShootPlayerName = '' - self._AttackMode = BotAttackModes.RandomNotSet - self._ShootWayPoints = {} - - -- Skill. - if not self._SkillFound then - self._Accuracy = Config.BotWorseningSkill + Config.BotWorseningSkill * (self.m_Accuracy - 0.5) -- up to 0.5 better or worse - self._AccuracySniper = Config.BotSniperWorseningSkill + Config.BotSniperWorseningSkill * (self.m_Accuracy - 0.5) -- up to 0.5 better or worse - self._SkillFound = true - end - - self._TargetPoint = nil - self._NextTargetPoint = nil - self._ActiveAction = BotActionFlags.NoActionActive - self._KnifeWayPositions = {} - self._ActiveDelay = 0.0 - self._TargetPitch = 0.0 - self._Objective = '' -- Reset objective on spawn, as another spawn-point might have chosen... - self._WeaponToUse = BotWeapons.Primary - - self.m_HasBeacon = false - self.m_DontRevive = false - self._JetAbortAttackActive = false - self._JetTakeoffActive = false - self._VehicleReadyToShoot = false - self._FullVehicleSteering = false - self._VehicleDirBackPositive = false - self._ExitVehicleActive = false - self._OnSwitch = false - self._VehicleMoveWhileShooting = false - self._FireCycleModifier = 1.0 -- don't init it at all on spawn? - - self.m_AttackPriority = 1 - self.m_DelayedInputs = {} - - -- Reset all input-vars. - ---@type EntryInputActionEnum - for l_EIA = 0, 36 do - self.m_ActiveInputs[l_EIA] = { - value = 0, - reset = false, - } - self.m_Player.input:SetLevel(l_EIA, 0.0) - end -end - ----@param p_Player Player -function Bot:ClearPlayer(p_Player) - if self._ShootPlayer == p_Player then - self._ShootPlayer = nil - end - - if self._TargetPlayer == p_Player then - self._TargetPlayer = nil - end - - local s_CurrentShootPlayer = PlayerManager:GetPlayerByName(self._ShootPlayerName) - - if s_CurrentShootPlayer == p_Player then - self._ShootPlayerName = '' - self._ShootPlayer = nil - end -end - -function Bot:Kill() - self:ResetVars() - - if self.m_Player.soldier ~= nil then - if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.StationaryAA) then - g_GameDirector:ReturnStationaryAaEntity(self.m_Player.controlledControllable, self.m_Player.teamId) - end - self.m_Player.soldier:Kill() - end -end - -function Bot:Destroy() - self:ResetVars() - self.m_Player.input = nil - - if self.m_Player.soldier ~= nil then - self.m_Player.soldier:Destroy() - end - - PlayerManager:DeletePlayer(self.m_Player) - self.m_Player = nil -end - --- ============================================= --- Private Functions --- ============================================= - ----@param p_DeltaTime number -function Bot:_UpdateLookAroundPassenger(p_DeltaTime) - -- Move around a little. - -- TODO: why is this check needed sometimes? - if self.m_Player.attachedControllable == nil then - return - end - - local s_Pos = self.m_Player.attachedControllable.transform.forward - local s_AtanDzDx = math.atan(s_Pos.z, s_Pos.x) - self._TargetYaw = (s_AtanDzDx > math.pi / 2) and (s_AtanDzDx - math.pi / 2) or (s_AtanDzDx + 3 * math.pi / 2) - self._TargetPitch = 0.0 - - self._VehicleWaitTimer = self._VehicleWaitTimer + p_DeltaTime - - if self._VehicleWaitTimer > 9.0 then - self._VehicleWaitTimer = 0.0 - elseif self._VehicleWaitTimer >= 6.0 then - elseif self._VehicleWaitTimer >= 3.0 then - self._TargetYaw = self._TargetYaw - 1.0 -- 60° rotation left. - self._TargetPitch = 0.2 - - if self._TargetYaw < 0.0 then - self._TargetYaw = self._TargetYaw + (2 * math.pi) - end - elseif self._VehicleWaitTimer >= 0.0 then - self._TargetYaw = self._TargetYaw + 1.0 -- 60° rotation right. - self._TargetPitch = -0.2 - - if self._TargetYaw > (math.pi * 2) then - self._TargetYaw = self._TargetYaw - (2 * math.pi) - end - end -end - ----@param p_Input EntryInputActionEnum|integer ----@param p_Value number -function Bot:_SetInput(p_Input, p_Value) - self.m_ActiveInputs[p_Input] = { - value = p_Value, - reset = p_Value == 0, - } -end - ----@param p_Input EntryInputActionEnum|integer ----@param p_Value number ----@param p_Delay number -function Bot:_SetDelayedInput(p_Input, p_Value, p_Delay) - table.insert(self.m_DelayedInputs, { - input = p_Input, - delay = p_Delay, - value = p_Value, - }) -end - ----@param p_DeltaTime number -function Bot:_UpdateInputs(p_DeltaTime) - ---@type EntryInputActionEnum - for i = 0, 36 do - if self.m_ActiveInputs[i].reset then - self.m_Player.input:SetLevel(i, 0) - self.m_ActiveInputs[i].value = 0 - self.m_ActiveInputs[i].reset = false - elseif self.m_ActiveInputs[i].value ~= 0 then - self.m_Player.input:SetLevel(i, self.m_ActiveInputs[i].value) - self.m_ActiveInputs[i].reset = true - end - end - - for l_Index = 1, #self.m_DelayedInputs do - local l_DelayedInput = self.m_DelayedInputs[l_Index] - l_DelayedInput.delay = l_DelayedInput.delay - p_DeltaTime - if l_DelayedInput.delay <= 0 then - self.m_ActiveInputs[l_DelayedInput.input] = { - value = l_DelayedInput.value, - reset = l_DelayedInput.value == 0, - } - table.remove(self.m_DelayedInputs, l_Index) - break - end - end -end - ----@param p_DeltaTime number -function Bot:_UpdateRespawn(p_DeltaTime) - if not self._Respawning or self._SpawnMode == BotSpawnModes.NoRespawn then - return - end - - -- Wait for respawn-delay gone. - if self._SpawnDelayTimer < (Globals.RespawnDelay + Config.AdditionalBotSpawnDelay) then - self._SpawnDelayTimer = self._SpawnDelayTimer + p_DeltaTime - else - self._SpawnDelayTimer = 0.0 -- Prevent triggering again. - g_BotSpawner:TriggerRespawnBot(self) - end -end - ----@param p_Position Vec3 -function Bot:FindVehiclePath(p_Position) - local s_Node = g_GameDirector:FindClosestPath(p_Position, true, true, self.m_ActiveVehicle.Terrain) - - if s_Node ~= nil then - -- Switch to vehicle. - self._InvertPathDirection = false - self._PathIndex = s_Node.PathIndex - self._CurrentWayPoint = s_Node.PointIndex - self._LastWayDistance = 1000.0 - -- Set path. - self._TargetPoint = s_Node - self._NextTargetPoint = s_Node - end -end - -function Bot:UpdateVehicleMovableId() - local s_InVehicle = false - local s_OnVehicle = false - self:ResetSpawnVars() -- TODO: this might be too hard? Better solution? Only Inputs relevant? - if self.m_Player.controlledControllable ~= nil and not self.m_Player.controlledControllable:Is('ServerSoldierEntity') then - s_InVehicle = true - s_OnVehicle = false - - -- transition to vehicle state - if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.Plane) then - self:SetState(g_BotStates.States.InVehicleJetControl) - elseif m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.StationaryAA) then - self:SetState(g_BotStates.States.InVehicleStationaryAaControl) - elseif m_Vehicles:IsChopper(self.m_ActiveVehicle) and self.m_Player.controlledEntryId == 0 then - self:SetState(g_BotStates.States.InVehicleChopperControl) - else - self:SetState(g_BotStates.States.InVehicleMoving) - end - elseif self.m_Player.attachedControllable ~= nil then - s_InVehicle = false - s_OnVehicle = true - self:SetState(g_BotStates.States.OnVehicleIdle) - end - - if s_OnVehicle then - self._VehicleMovableId = -1 - elseif s_InVehicle then - self._ActiveVehicleWeaponSlot = 0 - self._VehicleMovableId = m_Vehicles:GetPartIdForSeat(self.m_ActiveVehicle, self.m_Player.controlledEntryId, - self._ActiveVehicleWeaponSlot) - - if self.m_Player.controlledEntryId == 0 then - self:FindVehiclePath(self.m_Player.soldier.worldTransform.trans) - end - end - self:UpdateDontAttackFlag() -end - ----@param p_Entity ControllableEntity|nil ----@param p_PlayerIsDriver boolean ----@return integer ----@return Vec3|nil -function Bot:_EnterVehicleEntity(p_Entity, p_PlayerIsDriver) - if not p_Entity then - return -2 - end - - local s_Position = p_Entity.transform.trans - local s_VehicleData = m_Vehicles:GetVehicleByEntity(p_Entity) - - if not s_VehicleData then - return -2 - end - - if not Config.UseAirVehicles and m_Vehicles:IsAirVehicle(s_VehicleData) then - return -3 -- Not allowed to use. - end - - if not Config.UseJets and m_Vehicles:IsVehicleType(s_VehicleData, VehicleTypes.Plane) then - return -3 -- Not allowed to use. - end - - -- Keep one seat free, if enough available. - local s_MaxEntries = p_Entity.entryCount - if s_VehicleData.Type == VehicleTypes.Gunship then - s_MaxEntries = 2 - end - if s_VehicleData.Type == VehicleTypes.MobileArtillery then - s_MaxEntries = 1 - end - -- The idea is to avoid the bots from seating in the 3rd slot of the tanks to be more useful somwhere else. - if s_VehicleData.Type == VehicleTypes.UnarmedGunship then - s_MaxEntries = 0 - end - - --Now the bots may fully occupy a vehicle ( attack choppers, scout choppers, and some other transport vehicles.) - if not p_PlayerIsDriver then - -- Leave a place for a player if more than two seats are available. - if s_MaxEntries > 2 and Config.KeepVehicleSeatForPlayer then - s_MaxEntries = s_MaxEntries - 1 - end - -- Limit the bots per vehicle, if no player is the driver. - if s_MaxEntries > Config.MaxBotsPerVehicle then - s_MaxEntries = Config.MaxBotsPerVehicle - end - else - -- Allow one more bot, if driver is player. - if s_MaxEntries > (Config.MaxBotsPerVehicle + 1) then - s_MaxEntries = Config.MaxBotsPerVehicle + 1 - end - end - - for seatIndex = 0, s_MaxEntries - 1 do - if s_VehicleData.Type == VehicleTypes.Gunship then - seatIndex = seatIndex + 1 - end - if p_Entity:GetPlayerInEntry(seatIndex) == nil or Globals.IsAirSuperiority or (Globals.MapHasDynamiJetSpawns and m_Vehicles:IsVehicleType(s_VehicleData, VehicleTypes.Plane)) then -- already in this seat in air superiority - self.m_Player:EnterVehicle(p_Entity, seatIndex) - self._ExitVehicleHealth = PhysicsEntity(p_Entity).internalHealth * (Registry.VEHICLES.VEHICLE_EXIT_HEALTH / 100.0) - -- Get ID. - self.m_ActiveVehicle = s_VehicleData - self._ActiveVehicleWeaponSlot = 0 - self:UpdateVehicleMovableId() - if seatIndex == 0 then - if seatIndex == s_MaxEntries - 1 then - self._VehicleWaitTimer = 0.5 -- Always wait a short time to check for free start. - if Globals.IsAirSuperiority then - self._VehicleTakeoffTimer = 0.0 - self._JetTakeoffActive = false - else - self._VehicleTakeoffTimer = Registry.VEHICLES.JET_TAKEOFF_TIME - self._JetTakeoffActive = true - end - g_GameDirector:_SetVehicleObjectiveState(p_Entity.transform.trans, false) - else - self._VehicleWaitTimer = Config.VehicleWaitForPassengersTime - self._BrakeTimer = 0.0 - end - else - self._VehicleWaitTimer = 0.0 - - if seatIndex == s_MaxEntries - 1 then - -- Last seat taken: Disable vehicle and abort, wait for passengers. - local s_Driver = p_Entity:GetPlayerInEntry(0) - - if s_Driver ~= nil then - Events:Dispatch('Bot:AbortWait', s_Driver.id) - g_GameDirector:_SetVehicleObjectiveState(p_Entity.transform.trans, false) - end - end - end - - return 0, s_Position -- Everything fine. - end - end - - -- No place left. - return -2 -end - ----@param p_PlayerIsDriver boolean ----@param p_Distance integer|nil ----@return integer ----@return Vec3|nil -function Bot:_EnterVehicle(p_PlayerIsDriver, p_Distance) - local s_Iterator = EntityManager:GetIterator('ServerVehicleEntity') - local s_Entity = s_Iterator:Next() - - local s_ClosestEntity = nil - local s_ClosestDistance = p_Distance or Registry.VEHICLES.MIN_DISTANCE_VEHICLE_ENTER - - while s_Entity ~= nil do - s_Entity = ControllableEntity(s_Entity) - local s_Position = s_Entity.transform.trans - local s_Distance = s_Position:Distance(self.m_Player.soldier.worldTransform.trans) - - if s_Distance < s_ClosestDistance then - s_ClosestEntity = s_Entity - s_ClosestDistance = s_Distance - end - - s_Entity = s_Iterator:Next() - end - - if s_ClosestEntity ~= nil then - return self:_EnterVehicleEntity(s_ClosestEntity, p_PlayerIsDriver) - end - - return -3 -- No vehicle found. -end - ----@param p_CurrentWayPoint integer|nil ----@return integer -function Bot:_GetWayIndex(p_CurrentWayPoint) - local s_ActivePointIndex = 1 - - if p_CurrentWayPoint == nil then - p_CurrentWayPoint = s_ActivePointIndex - else - s_ActivePointIndex = p_CurrentWayPoint - - -- Direction handling. - local s_CountOfPoints = #m_NodeCollection:Get(nil, self._PathIndex) - local s_FirstPoint = m_NodeCollection:GetFirst(self._PathIndex) - - if s_ActivePointIndex > s_CountOfPoints then - if s_FirstPoint.OptValue == 0xFF then -- Inversion needed. - s_ActivePointIndex = s_CountOfPoints - self._InvertPathDirection = true - else - s_ActivePointIndex = 1 - end - elseif s_ActivePointIndex < 1 then - if s_FirstPoint.OptValue == 0xFF then -- Inversion needed. - s_ActivePointIndex = 1 - self._InvertPathDirection = false - else - s_ActivePointIndex = s_CountOfPoints - end - end - end - - return s_ActivePointIndex -end - -function Bot:AtObjectivePath() - local s_FirstPoint = m_NodeCollection:GetFirst(self._PathIndex) - - if #s_FirstPoint.Data.Objectives == 1 and s_FirstPoint.Data.Objectives[1] == self._Objective then - return true - end - - return false -end - -function Bot:AbortAttack() - if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.Plane) and - self._ShootPlayerName ~= '' then - if self._ShootPlayerVehicleType ~= VehicleTypes.Plane then - self._VehicleTakeoffTimer = Registry.VEHICLES.JET_ABORT_ATTACK_TIME - else - self._VehicleTakeoffTimer = Registry.VEHICLES.JET_ABORT_JET_ATTACK_TIME - end - - self._JetAbortAttackActive = true - self._Pid_Drv_Yaw:Reset() - self._Pid_Drv_Tilt:Reset() - self._Pid_Drv_Roll:Reset() - end - - self.m_Player.input.zoomLevel = 0 - self._ShootPlayerName = '' - self._ShootPlayer = nil - self._ShootModeTimer = 0.0 - self._AttackMode = BotAttackModes.RandomNotSet - self.m_AttackPriority = 1 -end - ----@param p_FlagValue integer|BotActionFlags|nil -function Bot:_ResetActionFlag(p_FlagValue) - if p_FlagValue == nil then - self._ActiveAction = BotActionFlags.NoActionActive - else - if self._ActiveAction == p_FlagValue then - self._ActiveAction = BotActionFlags.NoActionActive - end - end -end - -function Bot:_SetActiveVarsSlow() - if self._ShootPlayer ~= nil then - -- only needed on change - local s_PlayerData = g_PlayerData:GetData(self._ShootPlayerName) - if s_PlayerData then - self._ShootPlayerVehicleType = s_PlayerData.Vehicle - else - self._ShootPlayerVehicleType = VehicleTypes.NoVehicle - end - end -end - -function Bot:_SetActiveVars() - if self._ForcedMovement then - self.m_ActiveMoveMode = self._MoveMode - end - - if Config.BotWeapon == BotWeapons.Knife then - self.m_KnifeMode = true - else - self.m_KnifeMode = false - end -end - -function Bot:SetActiveDelay(p_Time) - self._ActiveDelay = p_Time -end - -return Bot diff --git a/ext/Server/Bot/Bot.lua b/ext/Server/Bot/Bot.lua new file mode 100644 index 00000000..a22796ce --- /dev/null +++ b/ext/Server/Bot/Bot.lua @@ -0,0 +1,640 @@ +---@class Bot +---@overload fun(p_Player: Player):Bot +Bot = class('Bot') + +require('Bot/BotAiming') +require('Bot/BotAttacking') +require('Bot/BotMovement') +require('Bot/BotWeaponHandling') + +require('Bot/BotActions') +require('Bot/VehicleActions') + +require('Bot/BotGetters') +require('Bot/BotSetters') + +require('__shared/Config') +require('PidController') + +---@type NodeCollection +local m_NodeCollection = require('NodeCollection') +---@type Logger +local m_Logger = Logger('Bot', Debug.Server.BOT) +---@type Vehicles +local m_Vehicles = require('Vehicles') + +-- Create a new bot. +---@param p_Player Player +function Bot:__init(p_Player) + -- Player Object. + ---@type Player + self.m_Player = p_Player + -- The player name. + ---@type string + self.m_Name = p_Player.name + -- The ID of the player. + ---@type integer + self.m_Id = p_Player.id + + -- statemachine-part + -- The active state object. + -- TODO: think about making use of subclasses which work like `class ("StateAttacking", BaseSoldierState)` & `class ("StateInVehicleAttacking", BaseInVehicleState)` & `class ("BaseInVehicleState", BaseVehicleState)`. + ---@type StateAttacking|StateIdle|StateInVehicleAttacking|StateInVehicleChopperControl|StateInVehicleIdle|StateInVehicleJetControl|StateInVehicleMoving|StateInVehicleStationaryAAControl|StateMoving|StateOnVehicleAttacking|StateOnVehicleIdle + self.m_ActiveState = g_BotStates.States.Idle + -- TODO: only used in StateInVehicleAttacking. Might make sense to move it to that class. Or move it to a BaseState class and inherit that in every other state as a subclass. + -- The timer of the current state. + self.m_StateTimer = 0.0 + + --[[ + TODO: move this to an inner class. Like `Bot.Persona = class('Bot.Persona')` or `Bot.Attributes = class('Bot.Attributes')` and have there all the attributes. + If it is going to be persona you can add even more stuff to it like name, clan tag, dog tags etc. + Percentage kit choice, percentage weapon choice, behaviors like "TriesToRevengeAfterXDeaths" by pulling out the noobtube or "PlayForKills", "PlayForObjective" etc etc. + This can get a huge thing in the future if it gets fed with data. + ]] + -- create some character proporties + ---@type BotBehavior + self.m_Behavior = nil + self.m_Reaction = 0.0 + self.m_Accuracy = 0.0 + self.m_Skill = 0.0 + self.m_PrefWeapon = "" + self.m_PrefVehicle = "" + + self._AttackPosition = Vec3.zero + + -- Common settings. + ---@type BotSpawnModes + self._SpawnMode = BotSpawnModes.NoRespawn + ---@type BotMoveModes + self._MoveMode = BotMoveModes.Standstill + self._ForcedMovement = false + + -- TODO: this whole block could be moved to an inner class `Bot.Loadout = class('Bot.Loadout')`. + ---@type BotKits|integer + self.m_Kit = nil + -- Only used in BotSpawner. + -- The bot color is the soldier camo (color). + ---@type BotColors|integer + self.m_Color = nil + ---@type Weapon|nil + self.m_ActiveWeapon = nil + self.m_ActiveVehicle = nil + self.m_ActiveGmWeaponName = nil + ---@type Weapon|nil + self.m_Primary = nil + ---@type Weapon|nil + self.m_Pistol = nil + ---@type Weapon|nil + self.m_PrimaryGadget = nil + ---@type Weapon|nil + self.m_SecondaryGadget = nil + ---@type Weapon|nil + self.m_Grenade = nil + ---@type Weapon|nil + self.m_Knife = nil + + self._Respawning = false + self.m_HasBeacon = false + self.m_DontRevive = false + self.m_AttackPriority = 1 + + -- Timers. + self._SpawnDelayTimer = 0.0 + self._WayWaitTimer = 0.0 + self._VehicleWaitTimer = 0.0 + self._VehicleSeatTimer = 0.0 + self._VehicleTakeoffTimer = 0.0 + self._WayWaitYawTimer = 0.0 + self._ObstacleSequenceTimer = 0.0 + self._StuckTimer = 0.0 + self._ShotTimer = 0.0 + self._SoundTimer = 30.0 + self._VehicleSecondaryWeaponTimer = 0.0 + self._ShootModeTimer = 0.0 + self._ReloadTimer = 0.0 + self._DeployTimer = 0.0 + self._AttackModeMoveTimer = 0.0 + self._MeleeCooldownTimer = 0.0 + self._ShootTraceTimer = 0.0 + self._ActionTimer = 0.0 + self._BrakeTimer = 0.0 + self._SpawnProtectionTimer = 0.0 + self._DefendTimer = 0.0 + self._SidewardsTimer = 0.0 + self._KillYourselfTimer = 0.0 + self._RocketCooldownTimer = 0.0 + + -- Shared movement vars. + ---@type BotMoveModes + self.m_ActiveMoveMode = BotMoveModes.Standstill + ---@type BotMoveSpeeds + self.m_ActiveSpeedValue = BotMoveSpeeds.NoMovement + self.m_KnifeMode = false + + ---@class ActiveInput + ---@field value number + ---@field reset boolean + + ---@type table + self.m_ActiveInputs = {} + self.m_DelayedInputs = {} + + -- Sidewards movement. + self.m_YawOffset = 0.0 + self.m_StrafeValue = 0.0 + + -- Advanced movement. + ---@type BotAttackModes + self._AttackMode = BotAttackModes.RandomNotSet + ---@type BotActionFlags + self._ActiveAction = BotActionFlags.NoActionActive + ---@type Waypoint|nil + self._CurrentWayPoint = nil + self._TargetYaw = 0.0 + self._TargetYawMovementVehicle = 0.0 + self._TargetPitch = 0.0 + ---@type Waypoint|nil + self._TargetPoint = nil + ---@type Waypoint|nil + self._NextTargetPoint = nil + self._PathIndex = 0 + self._LastWayDistance = 0.0 + self._InvertPathDirection = false + self._ExitVehicleActive = false + self._ObstacleRetryCounter = 0 + self._Objective = '' + self._ObjectiveMode = BotObjectiveModes.Default + self._OnSwitch = false + self._ActiveDelay = 0.0 + self._VehicleMoveWhileShooting = false + self._FireCycleModifier = 1.0 + + -- Vehicle stuff. + ---@type integer|nil + self._VehicleMovableId = -1 + self._LastVehicleYaw = 0.0 + self._VehicleReadyToShoot = false + self._FullVehicleSteering = false + self._VehicleDirBackPositive = false + self._JetAbortAttackActive = false + self._JetTakeoffActive = false + self._ExitVehicleHealth = 0.0 + self._LastVehicleHealth = 0.0 + self._VehicleWeaponSlotToUse = 1 + self._ActiveVehicleWeaponSlot = 0 + ---@type ControllableEntity|nil + self._RepairVehicleEntity = nil + -- PID Controllers. + -- Normal driving. + ---@type PidController + self._Pid_Drv_Yaw = PidController(5, 0.05, 0.2, 1.0) + -- Chopper / Plane + ---@type PidController + self._Pid_Drv_Throttle = PidController(3, 0.05, 0.2, 1.0) + ---@type PidController + self._Pid_Drv_Tilt = PidController(6, 0.1, 0.2, 1.0) + ---@type PidController + self._Pid_Drv_Roll = PidController(6, 0.1, 0.2, 1.0) + -- Guns. + ---@type PidController + self._Pid_Att_Yaw = PidController(10, 2.0, 2.0, 1.0) + ---@type PidController + self._Pid_Att_Pitch = PidController(10, 2.0, 2.0, 1.0) + + -- Shooting. + self._Shoot = false + ---@type Player|nil + self._ShootPlayer = nil + self._ActiveShootDuration = 0.0 + self._DoneShootDuration = 0.0 + self._DontAttackPlayers = false + ---@type VehicleTypes + self._ShootPlayerVehicleType = VehicleTypes.NoVehicle + self._ShootPlayerName = '' + self._DistanceToPlayer = 0.0 + ---@type BotWeapons + self._WeaponToUse = BotWeapons.Primary + -- To-do: add emmylua type. + self._ShootWayPoints = {} + ---@type Vec3[] + self._KnifeWayPositions = {} + self._Accuracy = 0.0 + self._AccuracySniper = 0.0 + self._SkillFound = false + + ---@type Player|nil + self._TargetPlayer = nil +end + +-- ============================================= +-- Events +-- ============================================= + +-- ============================================= +-- Functions +-- ============================================= + +-- ============================================= +-- Public Functions +-- ============================================= + +function Bot:UpdateObjective(p_Objective, p_ObjectiveMode) + local s_AllObjectives = m_NodeCollection:GetKnownObjectives() + + for l_Objective, _ in pairs(s_AllObjectives) do + if l_Objective == p_Objective then + self:SetObjective(p_Objective, p_ObjectiveMode) + break + end + end +end + +function Bot:DeployIfPossible() + -- Deploy from time to time. + if self.m_PrimaryGadget ~= nil and (self.m_Kit == BotKits.Support or self.m_Kit == BotKits.Assault) and not Globals.IsGm then + if self.m_PrimaryGadget.type == WeaponTypes.Ammobag or self.m_PrimaryGadget.type == WeaponTypes.Medkit then + self:AbortAttack() + self._WeaponToUse = BotWeapons.Gadget1 + self._DeployTimer = 0.0 + end + end +end + +function Bot:UpdateDontAttackFlag() + -- Don't attack as driver in some vehicles. + if g_BotStates:IsInVehicleState(self.m_ActiveState) and self.m_Player.controlledEntryId == 0 then + if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.Chopper) then -- do not include ScoutChopper here (they can attack) + if self._VehicleMovableId == -1 then + self._DontAttackPlayers = true -- Tranotort-choppers don't attack as driver. + return + elseif self.m_Player.controlledControllable:GetPlayerInEntry(1) ~= nil and not Config.ChopperDriversAttack then -- Don't attack if gunner available and config is false. + self._DontAttackPlayers = true + return + end + end + + -- If jet targets get assigned in another way. + if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.Plane) then + self._DontAttackPlayers = true + return + end + + if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.NoArmorVehicle) then + self._DontAttackPlayers = true + return + end + + if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.LightVehicle) then + self._DontAttackPlayers = true + return + end + + -- If stationary AA targets get assigned in another way. + if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.StationaryAA) then + self._DontAttackPlayers = true + return + end + end + self._DontAttackPlayers = false +end + +---@param p_DeltaTime number +function Bot:_CheckForVehicleActions(p_DeltaTime, p_AttackActive) + local s_InVehicle = g_BotStates:IsInVehicleState(self.m_ActiveState) + local s_OnVehicle = g_BotStates:IsOnVehicleState(self.m_ActiveState) + + local s_VehicleEntity = self.m_Player.controlledControllable + if s_VehicleEntity and s_VehicleEntity.typeInfo.name == "ServerSoldierEntity" then + s_VehicleEntity = self.m_Player.attachedControllable + end + -- No vehicle found. + if not s_VehicleEntity then + return + end + + -- Check if exit of vehicle is needed (because of low health). + if not self._ExitVehicleActive then + local s_CurrentVehicleHealth = 0 + if s_VehicleEntity then + s_CurrentVehicleHealth = PhysicsEntity(s_VehicleEntity).internalHealth + end + + if s_CurrentVehicleHealth <= self._ExitVehicleHealth then + if math.random(0, 100) <= Registry.VEHICLES.VEHICLE_PROPABILITY_EXIT_LOW_HEALTH then + self:AbortAttack() + self:ExitVehicle() + end + end + end + + self:_CheckShouldExitVehicleIfPassenger(s_VehicleEntity, s_OnVehicle) + + if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.MobileArtillery) + or m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.LightAA) + then + -- Change seat, for attack. + local s_DesiredSeat = 0 + + -- Switch to gunner seat + if p_AttackActive then + s_DesiredSeat = 1 + end + + if s_DesiredSeat ~= self.m_Player.controlledEntryId + and s_VehicleEntity:GetPlayerInEntry(s_DesiredSeat) == nil + then + self.m_Player:EnterVehicle(s_VehicleEntity, s_DesiredSeat) + self:UpdateVehicleMovableId() + end + else + -- Check if better seat is available. + self._VehicleSeatTimer = self._VehicleSeatTimer + p_DeltaTime + if self._VehicleSeatTimer >= Registry.VEHICLES.VEHICLE_SEAT_CHECK_CYCLE_TIME then + self._VehicleSeatTimer = 0 + + if s_InVehicle and self.m_ActiveVehicle.Type ~= VehicleTypes.Gunship then -- In vehicle. + for l_SeatIndex = 0, self.m_Player.controlledEntryId do + if s_VehicleEntity:GetPlayerInEntry(l_SeatIndex) == nil then + -- Better seat available → switch seats. + m_Logger:Write('switch to better seat') + self:AbortAttack() + self.m_Player:EnterVehicle(s_VehicleEntity, l_SeatIndex) + self:UpdateVehicleMovableId() + break + end + end + elseif s_OnVehicle then -- Only passenger. + local s_LowestSeatIndex = -1 + for l_SeatIndex = 0, s_VehicleEntity.entryCount - 1 do + if s_VehicleEntity:GetPlayerInEntry(l_SeatIndex) == nil then + -- Maybe better seat available. + s_LowestSeatIndex = l_SeatIndex + else -- Check if there is a gap. + if s_LowestSeatIndex >= 0 then -- There is a better place. + m_Logger:Write('switch to better seat') + self:AbortAttack() + self.m_Player:EnterVehicle(s_VehicleEntity, s_LowestSeatIndex) + self:UpdateVehicleMovableId() + break + end + end + end + end + end + end +end + +function Bot:_CheckShouldExitVehicleIfPassenger(p_VehicleEntity, p_OnVehicle) + if self._ExitVehicleActive then + return + end + + if not p_OnVehicle + and not m_Vehicles:IsPassengerSeat(p_VehicleEntity, self.m_Player.controlledEntryId) + then + return + end + + local s_ShouldExit = false + local s_ExitDistance = Registry.BOT.PASSENGER_EXIT_DISTANCE + local s_AllCapturePoints = g_GameDirector:GetAllCapturePoints() + local s_ActiveMcoms = g_GameDirector:GetActiveMcomPositions() + local s_CurrentPosition = self.m_Player.soldier.worldTransform.trans:Clone() + s_CurrentPosition.y = 0 + + s_Coordinates = {} + for l_Index = 1, #s_AllCapturePoints do + s_Coordinates[#s_Coordinates + 1] = s_AllCapturePoints[l_Index].transform.trans + end + for l_Index = 1, #s_ActiveMcoms do + s_Coordinates[#s_Coordinates + 1] = s_ActiveMcoms[l_Index] + end + + for l_Index = 1, #s_Coordinates do + local l_Coord = s_Coordinates[l_Index] + local s_Position = l_Coord:Clone() + s_Position.y = 0 + + if s_Position:Distance(s_CurrentPosition) < s_ExitDistance then + s_ShouldExit = true + break + end + end + + if s_ShouldExit then + self:AbortAttack() + self:ExitVehicle() + end +end + +---@param p_Player Player +function Bot:ClearPlayer(p_Player) + if self._ShootPlayer == p_Player then + self._ShootPlayer = nil + end + + if self._TargetPlayer == p_Player then + self._TargetPlayer = nil + end + + local s_CurrentShootPlayer = PlayerManager:GetPlayerByName(self._ShootPlayerName) + + if s_CurrentShootPlayer == p_Player then + self._ShootPlayerName = '' + self._ShootPlayer = nil + end +end + +function Bot:Kill() + self:ResetVars() + + if self.m_Player.soldier ~= nil then + if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.StationaryAA) then + g_GameDirector:ReturnStationaryAaEntity(self.m_Player.controlledControllable, self.m_Player.teamId) + end + self.m_Player.soldier:Kill() + end +end + +function Bot:Destroy() + self:ResetVars() + self.m_Player.input = nil + + if self.m_Player.soldier ~= nil then + self.m_Player.soldier:Destroy() + end + + PlayerManager:DeletePlayer(self.m_Player) + self.m_Player = nil +end + +-- ============================================= +-- Private Functions +-- ============================================= + +---@param p_DeltaTime number +function Bot:_UpdateLookAroundPassenger(p_DeltaTime) + -- Move around a little. + -- TODO: why is this check needed sometimes? + if self.m_Player.attachedControllable == nil then + return + end + + local s_Pos = self.m_Player.attachedControllable.transform.forward + local s_AtanDzDx = math.atan(s_Pos.z, s_Pos.x) + self._TargetYaw = (s_AtanDzDx > math.pi / 2) and (s_AtanDzDx - math.pi / 2) or (s_AtanDzDx + 3 * math.pi / 2) + self._TargetPitch = 0.0 + + self._VehicleWaitTimer = self._VehicleWaitTimer + p_DeltaTime + + if self._VehicleWaitTimer > 9.0 then + self._VehicleWaitTimer = 0.0 + elseif self._VehicleWaitTimer >= 6.0 then + elseif self._VehicleWaitTimer >= 3.0 then + self._TargetYaw = self._TargetYaw - 1.0 -- 60° rotation left. + self._TargetPitch = 0.2 + + if self._TargetYaw < 0.0 then + self._TargetYaw = self._TargetYaw + (2 * math.pi) + end + elseif self._VehicleWaitTimer >= 0.0 then + self._TargetYaw = self._TargetYaw + 1.0 -- 60° rotation right. + self._TargetPitch = -0.2 + + if self._TargetYaw > (math.pi * 2) then + self._TargetYaw = self._TargetYaw - (2 * math.pi) + end + end +end + +---@param p_DeltaTime number +function Bot:_UpdateInputs(p_DeltaTime) + ---@type EntryInputActionEnum + for i = 0, 36 do + if self.m_ActiveInputs[i].reset then + self.m_Player.input:SetLevel(i, 0) + self.m_ActiveInputs[i].value = 0 + self.m_ActiveInputs[i].reset = false + elseif self.m_ActiveInputs[i].value ~= 0 then + self.m_Player.input:SetLevel(i, self.m_ActiveInputs[i].value) + self.m_ActiveInputs[i].reset = true + end + end + + for l_Index = 1, #self.m_DelayedInputs do + local l_DelayedInput = self.m_DelayedInputs[l_Index] + l_DelayedInput.delay = l_DelayedInput.delay - p_DeltaTime + if l_DelayedInput.delay <= 0 then + self.m_ActiveInputs[l_DelayedInput.input] = { + value = l_DelayedInput.value, + reset = l_DelayedInput.value == 0, + } + table.remove(self.m_DelayedInputs, l_Index) + break + end + end +end + +---@param p_DeltaTime number +function Bot:_UpdateRespawn(p_DeltaTime) + if not self._Respawning or self._SpawnMode == BotSpawnModes.NoRespawn then + return + end + + -- Wait for respawn-delay gone. + if self._SpawnDelayTimer < (Globals.RespawnDelay + Config.AdditionalBotSpawnDelay) then + self._SpawnDelayTimer = self._SpawnDelayTimer + p_DeltaTime + else + self._SpawnDelayTimer = 0.0 -- Prevent triggering again. + g_BotSpawner:TriggerRespawnBot(self) + end +end + +---@param p_Position Vec3 +function Bot:FindVehiclePath(p_Position) + local s_Node = g_GameDirector:FindClosestPath(p_Position, true, true, self.m_ActiveVehicle.Terrain) + + if s_Node ~= nil then + -- Switch to vehicle. + self._InvertPathDirection = false + self._PathIndex = s_Node.PathIndex + self._CurrentWayPoint = s_Node.PointIndex + self._LastWayDistance = 1000.0 + -- Set path. + self._TargetPoint = s_Node + self._NextTargetPoint = s_Node + end +end + +function Bot:UpdateVehicleMovableId() + local s_InVehicle = false + local s_OnVehicle = false + self:ResetSpawnVars() -- TODO: this might be too hard? Better solution? Only Inputs relevant? + if self.m_Player.controlledControllable ~= nil and not self.m_Player.controlledControllable:Is('ServerSoldierEntity') then + s_InVehicle = true + s_OnVehicle = false + + -- transition to vehicle state + if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.Plane) then + self:SetState(g_BotStates.States.InVehicleJetControl) + elseif m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.StationaryAA) then + self:SetState(g_BotStates.States.InVehicleStationaryAaControl) + elseif m_Vehicles:IsChopper(self.m_ActiveVehicle) and self.m_Player.controlledEntryId == 0 then + self:SetState(g_BotStates.States.InVehicleChopperControl) + else + self:SetState(g_BotStates.States.InVehicleMoving) + end + elseif self.m_Player.attachedControllable ~= nil then + s_InVehicle = false + s_OnVehicle = true + self:SetState(g_BotStates.States.OnVehicleIdle) + end + + if s_OnVehicle then + self._VehicleMovableId = -1 + elseif s_InVehicle then + self._ActiveVehicleWeaponSlot = 0 + self._VehicleMovableId = m_Vehicles:GetPartIdForSeat(self.m_ActiveVehicle, self.m_Player.controlledEntryId, + self._ActiveVehicleWeaponSlot) + + if self.m_Player.controlledEntryId == 0 then + self:FindVehiclePath(self.m_Player.soldier.worldTransform.trans) + end + end + self:UpdateDontAttackFlag() +end + +function Bot:AtObjectivePath() + local s_FirstPoint = m_NodeCollection:GetFirst(self._PathIndex) + + if #s_FirstPoint.Data.Objectives == 1 and s_FirstPoint.Data.Objectives[1] == self._Objective then + return true + end + + return false +end + +function Bot:AbortAttack() + if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.Plane) and + self._ShootPlayerName ~= '' then + if self._ShootPlayerVehicleType ~= VehicleTypes.Plane then + self._VehicleTakeoffTimer = Registry.VEHICLES.JET_ABORT_ATTACK_TIME + else + self._VehicleTakeoffTimer = Registry.VEHICLES.JET_ABORT_JET_ATTACK_TIME + end + + self._JetAbortAttackActive = true + self._Pid_Drv_Yaw:Reset() + self._Pid_Drv_Tilt:Reset() + self._Pid_Drv_Roll:Reset() + end + + self.m_Player.input.zoomLevel = 0 + self._ShootPlayerName = '' + self._ShootPlayer = nil + self._ShootModeTimer = 0.0 + self._AttackMode = BotAttackModes.RandomNotSet + self.m_AttackPriority = 1 +end + +return Bot diff --git a/ext/Server/Bot/BotActions.lua b/ext/Server/Bot/BotActions.lua new file mode 100644 index 00000000..13152050 --- /dev/null +++ b/ext/Server/Bot/BotActions.lua @@ -0,0 +1,391 @@ +---@type Vehicles +local m_Vehicles = require('Vehicles') + +-- Initialize a revive on a teammate. +---@param p_Player Player the player to revive. +function Bot:Revive(p_Player) + -- Config for revives disabled. + if not Config.BotsRevive then return end + + -- TODO: if some mod allows defib in other kits then this isn't accurate. + -- Only Assaults can revive. + if self.m_Kit ~= BotKits.Assault then return end + + -- TODO: if some mod allows defib in one of these modes then this isn't accurate. + -- These gamemodes don't allow revives. + if Globals.IsGm or Globals.IsScavenger then return end + + -- The player to revive is not revivable. Could be already alive or dead. + if not p_Player.corpse or p_Player.corpse.isDead then return end + + -- The bot is not alive. + if not self.m_Player.soldier then return end + + --[[ + TODO: if some mod allows defib in other weapon slots then this isn't accurate. + Might make sense to create a m_HasDefibrillator bool inside the non-existing Bot.Loadout class. + And then get rid of all other checks that are not needed. + ]] + -- Make sure the bot has a defibrillator. + if self.m_Player.soldier.weaponsComponent.weapons[6] and + string.find(self.m_Player.soldier.weaponsComponent.weapons[6].name, "Defibrillator") then + self._ActiveAction = BotActionFlags.ReviveActive + self._ShootPlayerName = p_Player.name + self._ShootPlayer = PlayerManager:GetPlayerByName(self._ShootPlayerName) + self._ShootPlayerVehicleType = VehicleTypes.NoVehicle -- does not matter here + end +end + +---@param p_Player Player +function Bot:Repair(p_Player) + if self.m_Kit == BotKits.Engineer and p_Player.soldier ~= nil and p_Player.controlledControllable ~= nil then + self._ActiveAction = BotActionFlags.RepairActive + self._RepairVehicleEntity = p_Player.controlledControllable + self._LastVehicleHealth = 0.0 + self._ShootPlayerName = p_Player.name + self._ShootPlayer = PlayerManager:GetPlayerByName(self._ShootPlayerName) + self._ShootPlayerVehicleType = VehicleTypes.NoVehicle -- does not matter here + self._ShootModeTimer = Registry.BOT.MAX_TIME_TRY_REPAIR + end +end + +---@param p_Player Player +function Bot:EnterVehicleOfPlayer(p_Player) + self._ActiveAction = BotActionFlags.EnterVehicleActive + self._ShootPlayerName = p_Player.name + self._ShootPlayer = PlayerManager:GetPlayerByName(self._ShootPlayerName) + self._ShootPlayerVehicleType = VehicleTypes.NoVehicle -- does not matter here + self._ShootModeTimer = 12.0 +end + +---@param p_Player Player +---@param p_IgnoreYaw boolean +---@return boolean +function Bot:ShootAt(p_Player, p_IgnoreYaw) + if p_IgnoreYaw and self._DefendTimer == 0.0 then -- was hit, not in defend-mode, check for special behavior + if self.m_Behavior == BotBehavior.DontShootBackHide then + self._ActionTimer = 7.0 + self._ActiveAction = BotActionFlags.HideOnAttack + return false + elseif self.m_Behavior == BotBehavior.DontShootBackBail then + self._ActionTimer = 5.0 + self._ActiveAction = BotActionFlags.RunAway + return false + end + end + + -- Don't shoot at teammates. + if self.m_Player.teamId == p_Player.teamId then + return false + end + + if p_Player.soldier == nil or self.m_Player.soldier == nil then + return false + end + + -- Check for vehicles. + local s_Type = m_Vehicles:FindOutVehicleType(p_Player) + + -- Don't shoot at stationary AA. + if s_Type == VehicleTypes.StationaryAA then + return false + end + + local s_NewAttackPriority = self:GetAttackPriority(s_Type) + local s_NewTarget = self._ShootPlayerName ~= p_Player.name + + local s_Ready = (s_NewAttackPriority > self.m_AttackPriority) or self:IsReadyToAttack(p_IgnoreYaw, p_Player, true, s_NewTarget) + if not s_Ready or self._Shoot == false or self._DontAttackPlayers then + return false + end + + -- Don't shoot if too far away. + self._DistanceToPlayer = 0.0 + local s_PlayerPos = nil + local s_TargetPos = nil + if s_Type == VehicleTypes.MavBot or s_Type == VehicleTypes.MobileArtillery then + s_TargetPos = p_Player.controlledControllable.transform.trans:Clone() + else + s_TargetPos = p_Player.soldier.worldTransform.trans:Clone() + end + + if m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.MobileArtillery) then + s_PlayerPos = self.m_Player.controlledControllable.transform.trans:Clone() + else + s_PlayerPos = self.m_Player.soldier.worldTransform.trans:Clone() + end + + self._DistanceToPlayer = s_TargetPos:Distance(s_PlayerPos) + + local s_VehicleAttackMode = nil + local s_InVehicle = g_BotStates:IsInVehicleState(self.m_ActiveState) + if s_Type ~= VehicleTypes.NoVehicle then + s_VehicleAttackMode = m_Vehicles:CheckForVehicleAttack(s_Type, self) + if s_VehicleAttackMode == VehicleAttackModes.NoAttack then + return false + end + end + + -- in lightAA: only attack air-vehicles + if s_InVehicle and m_Vehicles:IsVehicleType(self.m_ActiveVehicle, VehicleTypes.LightAA) and not m_Vehicles:IsAirVehicleType(s_Type) then + return false + end + + local s_AttackDistance = self:GetAttackDistance(p_IgnoreYaw, s_VehicleAttackMode) + + -- Don't attack if too far away. + if self._DistanceToPlayer > s_AttackDistance then + return false + end + + self._ShootPlayerVehicleType = s_Type + + local s_DifferenceYaw = 0 + local s_Pitch = 0 + local s_FovHalf = 0 + local s_PitchHalf = 0 + + -- If target is air-vehicle and bot is in AA → ignore yaw. + if m_Vehicles:IsAirVehicleType(s_Type) then + if (s_InVehicle and m_Vehicles:IsAAVehicle(self.m_ActiveVehicle)) or + (s_VehicleAttackMode == VehicleAttackModes.AttackWithMissileAir) then + p_IgnoreYaw = true + end + end + + local s_Bot = g_BotManager:GetBotByName(p_Player.name) + + local s_SoundDistance = 100 + if s_Type == VehicleTypes.NoVehicle then + s_SoundDistance = 50 + end + + if s_Bot ~= nil + and s_Bot._SoundTimer <= 5.0 + and self._DistanceToPlayer < s_SoundDistance + then + p_IgnoreYaw = true + end + + if not p_IgnoreYaw then + local s_OldYaw = self.m_Player.input.authoritativeAimingYaw + + local s_DifferenceY = s_TargetPos.z - s_PlayerPos.z + local s_DifferenceX = s_TargetPos.x - s_PlayerPos.x + local s_DifferenceZ = s_TargetPos.y - s_PlayerPos.y + + local s_AtanYaw = math.atan(s_DifferenceY, s_DifferenceX) + local s_Yaw = (s_AtanYaw > math.pi / 2) and (s_AtanYaw - math.pi / 2) or (s_AtanYaw + 3 * math.pi / 2) + + local s_DistanceHoizontal = math.sqrt(s_DifferenceX ^ 2 + s_DifferenceY ^ 2) + s_Pitch = math.abs(math.atan(s_DifferenceZ, s_DistanceHoizontal)) + + s_DifferenceYaw = math.abs(s_OldYaw - s_Yaw) + + if s_DifferenceYaw > math.pi then + s_DifferenceYaw = math.pi * 2 - s_DifferenceYaw + end + + if s_InVehicle then + if m_Vehicles:IsAAVehicle(self.m_ActiveVehicle) then + s_FovHalf = Config.FovVehicleAAForShooting / 360 * math.pi + s_PitchHalf = Config.FovVerticleVehicleAAForShooting / 360 * math.pi + elseif m_Vehicles:IsAirVehicle(self.m_ActiveVehicle) and + self.m_Player.controlledEntryId == 0 then + s_FovHalf = Config.FovVehicleForShooting / 360 * math.pi + s_PitchHalf = Config.FovVerticleChopperForShooting / 360 * math.pi + else + s_FovHalf = Config.FovVehicleForShooting / 360 * math.pi + s_PitchHalf = Config.FovVerticleVehicleForShooting / 360 * math.pi + end + else + s_FovHalf = Config.FovForShooting / 360 * math.pi + s_PitchHalf = Config.FovVerticleForShooting / 360 * math.pi + end + end + + if p_IgnoreYaw or (s_DifferenceYaw < s_FovHalf and s_Pitch < s_PitchHalf) then + if self._Shoot then + -- only reset ShotTimer, if not already attacking + if self._ShootModeTimer <= 0 then + self._ShotTimer = -self:GetFirstShotDelay(self._DistanceToPlayer, false) + end + if s_InVehicle then + self._ShootModeTimer = Config.BotVehicleFireModeDuration + else + self._ShootModeTimer = Config.BotFireModeDuration + if self.m_Behavior == BotBehavior.LongerAttacking then + self._ShootModeTimer = Config.BotFireModeDuration * 1.7 + elseif self.m_Behavior == BotBehavior.AbortAttackFast then + self._ShootModeTimer = Config.BotFireModeDuration * 0.5 + end + end + self._ActiveShootDuration = self._ShootModeTimer + if s_NewTarget then + self._DoneShootDuration = 0.0 + self._ShootPlayerName = p_Player.name + self._ShootPlayer = PlayerManager:GetPlayerByName(self._ShootPlayerName) + local s_PlayerData = g_PlayerData:GetData(self._ShootPlayerName) + if s_PlayerData then + self._ShootPlayerVehicleType = s_PlayerData.Vehicle + else + self._ShootPlayerVehicleType = VehicleTypes.NoVehicle + end + end + self._KnifeWayPositions = {} + self._VehicleReadyToShoot = false + + if self.m_KnifeMode then + self._KnifeWayPositions[#self._KnifeWayPositions + 1] = p_Player.soldier.worldTransform.trans:Clone() + end + + if Globals.IsGm then + -- check for changed weapon + BotSpawner:UpdateGmWeapon(self) + end + self._KillYourselfTimer = 0.0 + self.m_AttackPriority = s_NewAttackPriority + return true + else + self._ShootPlayerName = '' + self._ShootPlayer = nil + self._ShootModeTimer = 0.0 + return false + end + end + + return false +end + +---@param p_Entity ControllableEntity|nil +---@param p_PlayerIsDriver boolean +---@return integer +---@return Vec3|nil +function Bot:_EnterVehicleEntity(p_Entity, p_PlayerIsDriver) + if not p_Entity then + return -2 + end + + local s_Position = p_Entity.transform.trans + local s_VehicleData = m_Vehicles:GetVehicleByEntity(p_Entity) + + if not s_VehicleData then + return -2 + end + + if not Config.UseAirVehicles and m_Vehicles:IsAirVehicle(s_VehicleData) then + return -3 -- Not allowed to use. + end + + if not Config.UseJets and m_Vehicles:IsVehicleType(s_VehicleData, VehicleTypes.Plane) then + return -3 -- Not allowed to use. + end + + -- Keep one seat free, if enough available. + local s_MaxEntries = p_Entity.entryCount + if s_VehicleData.Type == VehicleTypes.Gunship then + s_MaxEntries = 2 + end + if s_VehicleData.Type == VehicleTypes.MobileArtillery then + s_MaxEntries = 1 + end + -- The idea is to avoid the bots from seating in the 3rd slot of the tanks to be more useful somwhere else. + if s_VehicleData.Type == VehicleTypes.UnarmedGunship then + s_MaxEntries = 0 + end + + --Now the bots may fully occupy a vehicle ( attack choppers, scout choppers, and some other transport vehicles.) + if not p_PlayerIsDriver then + -- Leave a place for a player if more than two seats are available. + if s_MaxEntries > 2 and Config.KeepVehicleSeatForPlayer then + s_MaxEntries = s_MaxEntries - 1 + end + -- Limit the bots per vehicle, if no player is the driver. + if s_MaxEntries > Config.MaxBotsPerVehicle then + s_MaxEntries = Config.MaxBotsPerVehicle + end + else + -- Allow one more bot, if driver is player. + if s_MaxEntries > (Config.MaxBotsPerVehicle + 1) then + s_MaxEntries = Config.MaxBotsPerVehicle + 1 + end + end + + for seatIndex = 0, s_MaxEntries - 1 do + if s_VehicleData.Type == VehicleTypes.Gunship then + seatIndex = seatIndex + 1 + end + if p_Entity:GetPlayerInEntry(seatIndex) == nil or Globals.IsAirSuperiority or (Globals.MapHasDynamiJetSpawns and m_Vehicles:IsVehicleType(s_VehicleData, VehicleTypes.Plane)) then -- already in this seat in air superiority + self.m_Player:EnterVehicle(p_Entity, seatIndex) + self._ExitVehicleHealth = PhysicsEntity(p_Entity).internalHealth * (Registry.VEHICLES.VEHICLE_EXIT_HEALTH / 100.0) + -- Get ID. + self.m_ActiveVehicle = s_VehicleData + self._ActiveVehicleWeaponSlot = 0 + self:UpdateVehicleMovableId() + if seatIndex == 0 then + if seatIndex == s_MaxEntries - 1 then + self._VehicleWaitTimer = 0.5 -- Always wait a short time to check for free start. + if Globals.IsAirSuperiority then + self._VehicleTakeoffTimer = 0.0 + self._JetTakeoffActive = false + else + self._VehicleTakeoffTimer = Registry.VEHICLES.JET_TAKEOFF_TIME + self._JetTakeoffActive = true + end + g_GameDirector:_SetVehicleObjectiveState(p_Entity.transform.trans, false) + else + self._VehicleWaitTimer = Config.VehicleWaitForPassengersTime + self._BrakeTimer = 0.0 + end + else + self._VehicleWaitTimer = 0.0 + + if seatIndex == s_MaxEntries - 1 then + -- Last seat taken: Disable vehicle and abort, wait for passengers. + local s_Driver = p_Entity:GetPlayerInEntry(0) + + if s_Driver ~= nil then + Events:Dispatch('Bot:AbortWait', s_Driver.id) + g_GameDirector:_SetVehicleObjectiveState(p_Entity.transform.trans, false) + end + end + end + + return 0, s_Position -- Everything fine. + end + end + + -- No place left. + return -2 +end + +---@param p_PlayerIsDriver boolean +---@param p_Distance integer|nil +---@return integer +---@return Vec3|nil +function Bot:_EnterVehicle(p_PlayerIsDriver, p_Distance) + local s_Iterator = EntityManager:GetIterator('ServerVehicleEntity') + local s_Entity = s_Iterator:Next() + + local s_ClosestEntity = nil + local s_ClosestDistance = p_Distance or Registry.VEHICLES.MIN_DISTANCE_VEHICLE_ENTER + + while s_Entity ~= nil do + s_Entity = ControllableEntity(s_Entity) + local s_Position = s_Entity.transform.trans + local s_Distance = s_Position:Distance(self.m_Player.soldier.worldTransform.trans) + + if s_Distance < s_ClosestDistance then + s_ClosestEntity = s_Entity + s_ClosestDistance = s_Distance + end + + s_Entity = s_Iterator:Next() + end + + if s_ClosestEntity ~= nil then + return self:_EnterVehicleEntity(s_ClosestEntity, p_PlayerIsDriver) + end + + return -3 -- No vehicle found. +end diff --git a/ext/Server/Bot/BotAiming.lua b/ext/Server/Bot/BotAiming.lua index c4a9cff1..79eb9844 100644 --- a/ext/Server/Bot/BotAiming.lua +++ b/ext/Server/Bot/BotAiming.lua @@ -1,14 +1,6 @@ ----@class BotAiming ----@overload fun():BotAiming -BotAiming = class('BotAiming') - ---@type Utilities local m_Utilities = require('__shared/Utilities') -function BotAiming:__init() - -- Nothing to do. -end - ---@param p_Distance number ---@return number local function _GetGrenadePitch(p_Distance) @@ -364,24 +356,16 @@ local function _RepairAimingAction(p_Bot) p_Bot._TargetYaw = s_Yaw end ----@param p_Bot Bot -function BotAiming:UpdateAiming(p_Bot) - if p_Bot._ShootPlayer == nil or p_Bot.m_Player.soldier == nil then +function Bot:UpdateAiming() + if self._ShootPlayer == nil or self.m_Player.soldier == nil then return end - if p_Bot._ActiveAction == BotActionFlags.ReviveActive then - _ReviveAimingAction(p_Bot) - elseif p_Bot._ActiveAction == BotActionFlags.RepairActive then - _RepairAimingAction(p_Bot) + if self._ActiveAction == BotActionFlags.ReviveActive then + _ReviveAimingAction(self) + elseif self._ActiveAction == BotActionFlags.RepairActive then + _RepairAimingAction(self) else - _DefaultAimingAction(p_Bot) + _DefaultAimingAction(self) end end - -if g_BotAiming == nil then - ---@type BotAiming - g_BotAiming = BotAiming() -end - -return g_BotAiming diff --git a/ext/Server/Bot/BotAttacking.lua b/ext/Server/Bot/BotAttacking.lua index 7c9ee19a..b75a8476 100644 --- a/ext/Server/Bot/BotAttacking.lua +++ b/ext/Server/Bot/BotAttacking.lua @@ -1,16 +1,8 @@ ----@class BotAttacking ----@overload fun():BotAttacking -BotAttacking = class('BotAttacking') - ---@type Utilities local m_Utilities = require('__shared/Utilities') ---@type Vehicles local m_Vehicles = require("Vehicles") -function BotAttacking:__init() - -- Nothing to do. -end - local function _Fire(p_Bot) p_Bot._SoundTimer = 0.0 p_Bot:_SetInput(EntryInputActionEnum.EIAFire, 1) @@ -361,28 +353,20 @@ local function _DefaultAttackingAction(p_DeltaTime, p_Bot) end ---@param p_DeltaTime number ----@param p_Bot Bot -function BotAttacking:UpdateAttacking(p_DeltaTime, p_Bot) +function Bot:UpdateAttacking(p_DeltaTime) -- Reset if enemy is dead or attack is disabled. - if not p_Bot._ShootPlayer then - p_Bot:AbortAttack() + if not self._ShootPlayer then + self:AbortAttack() return end - if p_Bot._ActiveAction == BotActionFlags.ReviveActive then - _ReviveAttackingAction(p_DeltaTime, p_Bot) - elseif p_Bot._ActiveAction == BotActionFlags.EnterVehicleActive then - _EnterVehicleAttackingAction(p_DeltaTime, p_Bot) - elseif p_Bot._ActiveAction == BotActionFlags.RepairActive then - _RepairAttackingAction(p_DeltaTime, p_Bot) + if self._ActiveAction == BotActionFlags.ReviveActive then + _ReviveAttackingAction(p_DeltaTime, self) + elseif self._ActiveAction == BotActionFlags.EnterVehicleActive then + _EnterVehicleAttackingAction(p_DeltaTime, self) + elseif self._ActiveAction == BotActionFlags.RepairActive then + _RepairAttackingAction(p_DeltaTime, self) else - _DefaultAttackingAction(p_DeltaTime, p_Bot) + _DefaultAttackingAction(p_DeltaTime, self) end end - -if g_BotAttacking == nil then - ---@type BotAttacking - g_BotAttacking = BotAttacking() -end - -return g_BotAttacking diff --git a/ext/Server/Bot/BotGetters.lua b/ext/Server/Bot/BotGetters.lua new file mode 100644 index 00000000..ebe5a5cd --- /dev/null +++ b/ext/Server/Bot/BotGetters.lua @@ -0,0 +1,230 @@ +---@type Vehicles +local m_Vehicles = require('Vehicles') +---@type NodeCollection +local m_NodeCollection = require('NodeCollection') + +---@param p_ShootBackAfterHit boolean +---@param p_Player Player | nil +---@param p_CheckShootTimer boolean +---@param p_IsNewTarget boolean +---@return boolean +function Bot:IsReadyToAttack(p_ShootBackAfterHit, p_Player, p_CheckShootTimer, p_IsNewTarget) + -- update timers first + if self._ShootPlayerName == '' then + self._DoneShootDuration = 0.0 + elseif p_Player and not p_IsNewTarget then + self._DoneShootDuration = self._DoneShootDuration + (self._ActiveShootDuration - self._ShootModeTimer) + end + + if self._ActiveAction == BotActionFlags.OtherActionActive or + (self._ActiveAction == BotActionFlags.ReviveActive and not p_ShootBackAfterHit) or + self._ActiveAction == BotActionFlags.RepairActive or + self._ActiveAction == BotActionFlags.EnterVehicleActive or + self._ActiveAction == BotActionFlags.GrenadeActive or + self._DontAttackPlayers then + return false + end + + if not p_CheckShootTimer then + return true + end + + local s_InVehicle = g_BotStates:IsInVehicleState(self.m_ActiveState) + if self._ShootPlayerName == '' or + (p_Player and p_IsNewTarget) or -- if still the same enemy, you can trigger directly again + (s_InVehicle and (self._DoneShootDuration > Config.BotVehicleMinTimeShootAtPlayer)) or + (not s_InVehicle and (self._DoneShootDuration > Config.BotMinTimeShootAtPlayer)) or + (self.m_KnifeMode and self._ShootModeTimer > ((Config.BotMinTimeShootAtPlayer * 0.5))) then + return true + else + return false + end +end + +---@param p_EnemyVehicleType VehicleTypes +---@return integer +function Bot:GetAttackPriority(p_EnemyVehicleType) + local s_BotVehicleType = m_Vehicles:VehicleType(self.m_ActiveVehicle) + + -- attack as soldier + if s_BotVehicleType == VehicleTypes.NoVehicle and self.m_PrimaryGadget ~= nil then + if self.m_PrimaryGadget.type == WeaponTypes.MissileAir + and m_Vehicles:IsAirVehicleType(p_EnemyVehicleType) + then + return 2 + elseif self.m_PrimaryGadget.type == WeaponTypes.MissileLand + and m_Vehicles:IsArmoredVehicleType(p_EnemyVehicleType) + then + return 2 + end + end + + -- attack as air-vehicle + if m_Vehicles:IsAirVehicleType(s_BotVehicleType) then + if m_Vehicles:IsAirVehicleType(p_EnemyVehicleType) then + return 3 + elseif m_Vehicles:IsArmoredVehicleType(p_EnemyVehicleType) then + return 2 + end + end + + -- attack as ground-vehicle + if m_Vehicles:IsArmoredVehicleType(s_BotVehicleType) + and m_Vehicles:IsArmoredVehicleType(p_EnemyVehicleType) + then + return 2 + end + + return 1 +end + +---@return number +function Bot:GetAttackDistance(p_ShootBackAfterHit, p_VehicleAttackMode) + local s_AttackDistance = 0.0 + + if not g_BotStates:IsInVehicleState(self.m_ActiveState) then + if p_VehicleAttackMode and (p_VehicleAttackMode == VehicleAttackModes.AttackWithMissileAir) then + s_AttackDistance = Config.MaxShootDistanceMissileAir + elseif self.m_ActiveWeapon and self.m_ActiveWeapon.type == WeaponTypes.Sniper then + if p_ShootBackAfterHit then + s_AttackDistance = Config.MaxDistanceShootBackSniper + else + s_AttackDistance = Config.MaxShootDistanceSniper + end + else + if p_ShootBackAfterHit then + s_AttackDistance = Config.MaxDistanceShootBack + else + s_AttackDistance = Config.MaxShootDistance + end + end + else + if m_Vehicles:IsGunship(self.m_ActiveVehicle) then + s_AttackDistance = Config.MaxShootDistanceGunship + elseif not m_Vehicles:IsAirVehicle(self.m_ActiveVehicle) + and m_Vehicles:IsNotVehicleType(self.m_ActiveVehicle, VehicleTypes.MobileArtillery) + and not m_Vehicles:IsAAVehicle(self.m_ActiveVehicle) then + if p_ShootBackAfterHit then + s_AttackDistance = Config.MaxShootDistanceNoAntiAir * 2 + else + s_AttackDistance = Config.MaxShootDistanceNoAntiAir + end + else + if p_ShootBackAfterHit then + s_AttackDistance = Config.MaxShootDistanceVehicles * 2 + else + s_AttackDistance = Config.MaxShootDistanceVehicles + end + end + end + + return s_AttackDistance +end + +---@param p_DistanceToTarget number +---@param p_ReducedTiming boolean +---@return number +function Bot:GetFirstShotDelay(p_DistanceToTarget, p_ReducedTiming) + local s_Delay = (Config.BotFirstShotDelay + (Config.ReactionTime * MathUtils:GetRandom(0.8, 1.2) * self.m_Reaction)) + + if p_ReducedTiming then + s_Delay = s_Delay * 0.6 + end + + -- Slower reaction on greater distances. 100 m = 0.5 extra seconda. + s_Delay = s_Delay + (p_DistanceToTarget * 0.005 * (1.0 + ((self.m_Reaction - 0.5) * 0.4))) -- +-20% depending on reaction-characteristic of bot + return s_Delay +end + +---@return boolean +function Bot:IsStaticMovement() + if self._ForcedMovement and (self._MoveMode == BotMoveModes.Standstill or + self._MoveMode == BotMoveModes.Mirror or + self._MoveMode == BotMoveModes.Mimic) then + return true + else + return false + end +end + +---@return string +function Bot:GetObjective() + return self._Objective +end + +---@return integer|BotObjectiveModes +function Bot:GetObjectiveMode() + return self._ObjectiveMode +end + +---@return integer|BotSpawnModes +function Bot:GetSpawnMode() + return self._SpawnMode +end + +---@return integer +function Bot:GetWayIndex() + return self._PathIndex +end + +---@return integer +function Bot:GetPointIndex() + return self._CurrentWayPoint +end + +---@return Player|nil +function Bot:GetTargetPlayer() + return self._TargetPlayer +end + +---@return boolean +function Bot:IsInactive() + if self.m_Player.soldier ~= nil or self._SpawnMode ~= BotSpawnModes.NoRespawn then + return false + else + return true + end +end + +---@return boolean +function Bot:IsStuck() + if self._ObstacleSequenceTimer ~= 0 then + return true + else + return false + end +end + +---@param p_CurrentWayPoint integer|nil +---@return integer +function Bot:_GetWayIndex(p_CurrentWayPoint) + local s_ActivePointIndex = 1 + + if p_CurrentWayPoint == nil then + p_CurrentWayPoint = s_ActivePointIndex + else + s_ActivePointIndex = p_CurrentWayPoint + + -- Direction handling. + local s_CountOfPoints = #m_NodeCollection:Get(nil, self._PathIndex) + local s_FirstPoint = m_NodeCollection:GetFirst(self._PathIndex) + + if s_ActivePointIndex > s_CountOfPoints then + if s_FirstPoint.OptValue == 0xFF then -- Inversion needed. + s_ActivePointIndex = s_CountOfPoints + self._InvertPathDirection = true + else + s_ActivePointIndex = 1 + end + elseif s_ActivePointIndex < 1 then + if s_FirstPoint.OptValue == 0xFF then -- Inversion needed. + s_ActivePointIndex = 1 + self._InvertPathDirection = false + else + s_ActivePointIndex = s_CountOfPoints + end + end + end + + return s_ActivePointIndex +end diff --git a/ext/Server/Bot/BotMovement.lua b/ext/Server/Bot/BotMovement.lua index eda2cd25..ac37d5d4 100644 --- a/ext/Server/Bot/BotMovement.lua +++ b/ext/Server/Bot/BotMovement.lua @@ -1,7 +1,3 @@ ----@class BotMovement ----@overload fun():BotMovement -BotMovement = class('BotMovement') - ---@type Logger local m_Logger = Logger('Bot', Debug.Server.BOT) ---@type Utilities @@ -11,52 +7,47 @@ local m_PathSwitcher = require('PathSwitcher') ---@type NodeCollection local m_NodeCollection = require('NodeCollection') -function BotMovement:__init() - -- Nothing to do. -end - ----comment ---@param p_DeltaTime number ----@param p_Bot Bot -function BotMovement:UpdateNormalMovement(p_DeltaTime, p_Bot) +function Bot:UpdateNormalMovement(p_DeltaTime) -- Move along points. - p_Bot._AttackModeMoveTimer = 0.0 + self._AttackModeMoveTimer = 0.0 - if m_NodeCollection:Get(1, p_Bot._PathIndex) ~= nil then -- Check for valid point. + if m_NodeCollection:Get(1, self._PathIndex) ~= nil then -- Check for valid point. -- Get next point. - local s_ActivePointIndex = p_Bot:_GetWayIndex(p_Bot._CurrentWayPoint) + local s_ActivePointIndex = self:_GetWayIndex(self._CurrentWayPoint) local s_Point = nil local s_NextPoint = nil local s_PointIncrement = 1 local s_NoStuckReset = false - if #p_Bot._ShootWayPoints > 0 then -- We need to go back to the path first. - s_Point = m_NodeCollection:Get(s_ActivePointIndex, p_Bot._PathIndex) - local s_ClosestDistance = p_Bot.m_Player.soldier.worldTransform.trans:Distance(s_Point.Position) + if #self._ShootWayPoints > 0 then -- We need to go back to the path first. + s_Point = m_NodeCollection:Get(s_ActivePointIndex, self._PathIndex) + ---@cast s_Point -nil + local s_ClosestDistance = self.m_Player.soldier.worldTransform.trans:Distance(s_Point.Position) local s_ClosestNode = s_ActivePointIndex for i = 1, Registry.BOT.NUMBER_NODES_TO_SCAN_AFTER_ATTACK, 2 do - s_Point = m_NodeCollection:Get(s_ActivePointIndex - i, p_Bot._PathIndex) - if s_Point and p_Bot.m_Player.soldier.worldTransform.trans:Distance(s_Point.Position) < s_ClosestDistance then + s_Point = m_NodeCollection:Get(s_ActivePointIndex - i, self._PathIndex) + if s_Point and self.m_Player.soldier.worldTransform.trans:Distance(s_Point.Position) < s_ClosestDistance then s_ClosestNode = s_ActivePointIndex - i end - s_Point = m_NodeCollection:Get(s_ActivePointIndex + i, p_Bot._PathIndex) - if s_Point and p_Bot.m_Player.soldier.worldTransform.trans:Distance(s_Point.Position) < s_ClosestDistance then + s_Point = m_NodeCollection:Get(s_ActivePointIndex + i, self._PathIndex) + if s_Point and self.m_Player.soldier.worldTransform.trans:Distance(s_Point.Position) < s_ClosestDistance then s_ClosestNode = s_ActivePointIndex + i end end if s_ClosestDistance < 5.0 then - p_Bot._CurrentWayPoint = s_ClosestNode + self._CurrentWayPoint = s_ClosestNode s_ActivePointIndex = s_ClosestNode end - p_Bot._ShootWayPoints = {} + self._ShootWayPoints = {} end - s_Point = m_NodeCollection:Get(s_ActivePointIndex, p_Bot._PathIndex) + s_Point = m_NodeCollection:Get(s_ActivePointIndex, self._PathIndex) - if not p_Bot._InvertPathDirection then - s_NextPoint = m_NodeCollection:Get(p_Bot:_GetWayIndex(p_Bot._CurrentWayPoint + 1), p_Bot._PathIndex) + if not self._InvertPathDirection then + s_NextPoint = m_NodeCollection:Get(self:_GetWayIndex(self._CurrentWayPoint + 1), self._PathIndex) else - s_NextPoint = m_NodeCollection:Get(p_Bot:_GetWayIndex(p_Bot._CurrentWayPoint - 1), p_Bot._PathIndex) + s_NextPoint = m_NodeCollection:Get(self:_GetWayIndex(self._CurrentWayPoint - 1), self._PathIndex) end if s_Point == nil then @@ -65,103 +56,104 @@ function BotMovement:UpdateNormalMovement(p_DeltaTime, p_Bot) -- Do defense, if needed - if p_Bot._ObjectiveMode == BotObjectiveModes.Defend and g_GameDirector:IsAtTargetObjective(p_Bot._PathIndex, p_Bot._Objective) then - p_Bot._DefendTimer = p_Bot._DefendTimer + p_DeltaTime + if self._ObjectiveMode == BotObjectiveModes.Defend and g_GameDirector:IsAtTargetObjective(self._PathIndex, self._Objective) then + self._DefendTimer = self._DefendTimer + p_DeltaTime - local s_TargetTime = p_Bot.m_Id % 5 + 4 -- min 2 sec on path, then 2 sec movement to side - if p_Bot._DefendTimer >= s_TargetTime then + local s_TargetTime = self.m_Id % 5 + 4 -- min 2 sec on path, then 2 sec movement to side + if self._DefendTimer >= s_TargetTime then -- look around - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.NoMovement + self.m_ActiveSpeedValue = BotMoveSpeeds.NoMovement - local s_DefendMode = p_Bot.m_Id % 3 + local s_DefendMode = self.m_Id % 3 if s_DefendMode == 0 then - if p_Bot.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Crouch then - p_Bot.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Crouch, true, true) + if self.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Crouch then + self.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Crouch, true, true) end elseif s_DefendMode == 1 then - if p_Bot.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Stand then - p_Bot.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Stand, true, true) + if self.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Stand then + self.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Stand, true, true) end else - if p_Bot.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Prone then - p_Bot.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Prone, true, true) + if self.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Prone then + self.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Prone, true, true) end end - self:LookAround(p_Bot, p_DeltaTime) + self:LookAround(p_DeltaTime) -- TODO: look at target -- don't do anything else return - elseif p_Bot._DefendTimer >= (s_TargetTime - 2) then - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.Backwards + elseif self._DefendTimer >= (s_TargetTime - 2) then + self.m_ActiveSpeedValue = BotMoveSpeeds.Backwards local s_StrafeValue = 1.0 - if p_Bot.m_Id % 2 then + if self.m_Id % 2 then s_StrafeValue = -1.0 end - p_Bot:_SetInput(EntryInputActionEnum.EIAStrafe, s_StrafeValue) + self:_SetInput(EntryInputActionEnum.EIAStrafe, s_StrafeValue) return end else - p_Bot._DefendTimer = 0.0 + self._DefendTimer = 0.0 end -- Execute Action if needed. - if p_Bot._ActiveAction == BotActionFlags.OtherActionActive then + if self._ActiveAction == BotActionFlags.OtherActionActive then if s_Point.Data ~= nil and s_Point.Data.Action ~= nil then if s_Point.Data.Action.type == 'vehicle' then if Config.UseVehicles then - local s_RetCode, s_Position = p_Bot:_EnterVehicle(false) + local s_RetCode, s_Position = self:_EnterVehicle(false) if s_RetCode == 0 then - p_Bot:_ResetActionFlag(BotActionFlags.OtherActionActive) - local s_Node = g_GameDirector:FindClosestPath(s_Position, true, false, p_Bot.m_ActiveVehicle.Terrain) + ---@cast s_Position -nil + self:_ResetActionFlag(BotActionFlags.OtherActionActive) + local s_Node = g_GameDirector:FindClosestPath(s_Position, true, false, self.m_ActiveVehicle.Terrain) if s_Node ~= nil then -- Switch to vehicle. s_Point = s_Node - p_Bot._InvertPathDirection = false - p_Bot._PathIndex = s_Node.PathIndex - p_Bot._CurrentWayPoint = s_Node.PointIndex - s_NextPoint = m_NodeCollection:Get(p_Bot:_GetWayIndex(p_Bot._CurrentWayPoint + 1), p_Bot._PathIndex) - p_Bot._LastWayDistance = 1000.0 + self._InvertPathDirection = false + self._PathIndex = s_Node.PathIndex + self._CurrentWayPoint = s_Node.PointIndex + s_NextPoint = m_NodeCollection:Get(self:_GetWayIndex(self._CurrentWayPoint + 1), self._PathIndex) + self._LastWayDistance = 1000.0 end end end - p_Bot:_ResetActionFlag(BotActionFlags.OtherActionActive) + self:_ResetActionFlag(BotActionFlags.OtherActionActive) elseif s_Point.Data.Action.type == "beacon" - and p_Bot.m_SecondaryGadget.type == WeaponTypes.Beacon - and not p_Bot.m_HasBeacon + and self.m_SecondaryGadget.type == WeaponTypes.Beacon + and not self.m_HasBeacon then - p_Bot._WeaponToUse = BotWeapons.Gadget2 + self._WeaponToUse = BotWeapons.Gadget2 - if p_Bot.m_Player.soldier.weaponsComponent.currentWeaponSlot == WeaponSlot.WeaponSlot_5 then - if p_Bot.m_Player.soldier.weaponsComponent.weapons[6] and p_Bot.m_Player.soldier.weaponsComponent.weapons[6].primaryAmmo > 0 then - p_Bot:_SetInput(EntryInputActionEnum.EIAFire, 1) + if self.m_Player.soldier.weaponsComponent.currentWeaponSlot == WeaponSlot.WeaponSlot_5 then + if self.m_Player.soldier.weaponsComponent.weapons[6] and self.m_Player.soldier.weaponsComponent.weapons[6].primaryAmmo > 0 then + self:_SetInput(EntryInputActionEnum.EIAFire, 1) else - p_Bot:_SetInput(EntryInputActionEnum.EIAFire, 0) - p_Bot:_ResetActionFlag(BotActionFlags.OtherActionActive) + self:_SetInput(EntryInputActionEnum.EIAFire, 0) + self:_ResetActionFlag(BotActionFlags.OtherActionActive) end end elseif s_Point.Data.Action.type == "beacon" then - p_Bot:_ResetActionFlag(BotActionFlags.OtherActionActive) - elseif p_Bot._ActionTimer <= s_Point.Data.Action.time then + self:_ResetActionFlag(BotActionFlags.OtherActionActive) + elseif self._ActionTimer <= s_Point.Data.Action.time then for l_Index = 1, #s_Point.Data.Action.inputs do local l_Input = s_Point.Data.Action.inputs[l_Index] - p_Bot:_SetInput(l_Input, 1) + self:_SetInput(l_Input, 1) end end else - p_Bot:_ResetActionFlag(BotActionFlags.OtherActionActive) + self:_ResetActionFlag(BotActionFlags.OtherActionActive) end - p_Bot._ActionTimer = p_Bot._ActionTimer - p_DeltaTime + self._ActionTimer = self._ActionTimer - p_DeltaTime - if p_Bot._ActionTimer <= 0.0 then - p_Bot:_ResetActionFlag(BotActionFlags.OtherActionActive) + if self._ActionTimer <= 0.0 then + self:_ResetActionFlag(BotActionFlags.OtherActionActive) end - if p_Bot._ActiveAction == BotActionFlags.OtherActionActive then + if self._ActiveAction == BotActionFlags.OtherActionActive then return -- DON'T EXECUTE ANYTHING ELSE. else if s_NextPoint then @@ -171,134 +163,134 @@ function BotMovement:UpdateNormalMovement(p_DeltaTime, p_Bot) end if s_Point.SpeedMode ~= BotMoveSpeeds.NoMovement then -- Movement. - p_Bot._WayWaitTimer = 0.0 - p_Bot._WayWaitYawTimer = 0.0 - p_Bot.m_ActiveSpeedValue = s_Point.SpeedMode -- Speed. + self._WayWaitTimer = 0.0 + self._WayWaitYawTimer = 0.0 + self.m_ActiveSpeedValue = s_Point.SpeedMode -- Speed. - if p_Bot._ActiveAction == BotActionFlags.RunAway and p_Bot._ActionTimer > 0.0 then - p_Bot._ActionTimer = p_Bot._ActionTimer - p_DeltaTime + if self._ActiveAction == BotActionFlags.RunAway and self._ActionTimer > 0.0 then + self._ActionTimer = self._ActionTimer - p_DeltaTime - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.Sprint - if p_Bot._ActionTimer <= 0.0 then - p_Bot:_ResetActionFlag(BotActionFlags.RunAway) + self.m_ActiveSpeedValue = BotMoveSpeeds.Sprint + if self._ActionTimer <= 0.0 then + self:_ResetActionFlag(BotActionFlags.RunAway) end end - if p_Bot._ActiveAction == BotActionFlags.HideOnAttack and p_Bot._ActionTimer > 0.0 then - p_Bot._ActionTimer = p_Bot._ActionTimer - p_DeltaTime + if self._ActiveAction == BotActionFlags.HideOnAttack and self._ActionTimer > 0.0 then + self._ActionTimer = self._ActionTimer - p_DeltaTime - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.VerySlowProne - if p_Bot._ActionTimer <= 0.0 then - p_Bot:_ResetActionFlag(BotActionFlags.HideOnAttack) + self.m_ActiveSpeedValue = BotMoveSpeeds.VerySlowProne + if self._ActionTimer <= 0.0 then + self:_ResetActionFlag(BotActionFlags.HideOnAttack) end end if Config.OverWriteBotSpeedMode ~= BotMoveSpeeds.NoMovement then - p_Bot.m_ActiveSpeedValue = Config.OverWriteBotSpeedMode + self.m_ActiveSpeedValue = Config.OverWriteBotSpeedMode end -- Sidewards movement. if Config.MoveSidewards then - if p_Bot._SidewardsTimer <= 0.0 then - if p_Bot.m_StrafeValue ~= 0 then - p_Bot._SidewardsTimer = MathUtils:GetRandom(Config.MinMoveCycle, Config.MaxStraigtCycle) - p_Bot.m_StrafeValue = 0.0 - p_Bot.m_YawOffset = 0.0 + if self._SidewardsTimer <= 0.0 then + if self.m_StrafeValue ~= 0 then + self._SidewardsTimer = MathUtils:GetRandom(Config.MinMoveCycle, Config.MaxStraigtCycle) + self.m_StrafeValue = 0.0 + self.m_YawOffset = 0.0 else - p_Bot._SidewardsTimer = MathUtils:GetRandom(Config.MinMoveCycle, Config.MaxSideCycle) + self._SidewardsTimer = MathUtils:GetRandom(Config.MinMoveCycle, Config.MaxSideCycle) if MathUtils:GetRandomInt(0, 1) > 0 then -- Random direction. - p_Bot.m_StrafeValue = 1.0 + self.m_StrafeValue = 1.0 else - p_Bot.m_StrafeValue = -1.0 + self.m_StrafeValue = -1.0 end - if p_Bot.m_ActiveSpeedValue == BotMoveSpeeds.Sprint then - p_Bot.m_YawOffset = 0.3927 * -p_Bot.m_StrafeValue + if self.m_ActiveSpeedValue == BotMoveSpeeds.Sprint then + self.m_YawOffset = 0.3927 * -self.m_StrafeValue else - p_Bot.m_YawOffset = 0.7854 * -p_Bot.m_StrafeValue + self.m_YawOffset = 0.7854 * -self.m_StrafeValue end end end - p_Bot:_SetInput(EntryInputActionEnum.EIAStrafe, p_Bot.m_StrafeValue) - p_Bot._SidewardsTimer = p_Bot._SidewardsTimer - p_DeltaTime + self:_SetInput(EntryInputActionEnum.EIAStrafe, self.m_StrafeValue) + self._SidewardsTimer = self._SidewardsTimer - p_DeltaTime end -- Use parachute if needed. - local s_VelocityFalling = PhysicsEntity(p_Bot.m_Player.soldier).velocity.y + local s_VelocityFalling = PhysicsEntity(self.m_Player.soldier).velocity.y if s_VelocityFalling < -25.0 then - p_Bot:_SetInput(EntryInputActionEnum.EIAToggleParachute, 1) + self:_SetInput(EntryInputActionEnum.EIAToggleParachute, 1) end - local s_DifferenceY = s_Point.Position.z - p_Bot.m_Player.soldier.worldTransform.trans.z - local s_DifferenceX = s_Point.Position.x - p_Bot.m_Player.soldier.worldTransform.trans.x + local s_DifferenceY = s_Point.Position.z - self.m_Player.soldier.worldTransform.trans.z + local s_DifferenceX = s_Point.Position.x - self.m_Player.soldier.worldTransform.trans.x local s_DistanceFromTarget = math.sqrt(s_DifferenceX ^ 2 + s_DifferenceY ^ 2) - local s_HeightDistance = math.abs(s_Point.Position.y - p_Bot.m_Player.soldier.worldTransform.trans.y) + local s_HeightDistance = math.abs(s_Point.Position.y - self.m_Player.soldier.worldTransform.trans.y) -- Detect obstacle and move over or around. To-do: Move before normal jump. - local s_CurrentWayPointDistance = p_Bot.m_Player.soldier.worldTransform.trans:Distance(s_Point.Position) + local s_CurrentWayPointDistance = self.m_Player.soldier.worldTransform.trans:Distance(s_Point.Position) - if s_CurrentWayPointDistance > p_Bot._LastWayDistance + 0.02 and p_Bot._ObstacleSequenceTimer == 0 then + if s_CurrentWayPointDistance > self._LastWayDistance + 0.02 and self._ObstacleSequenceTimer == 0 then -- Skip one point. s_DistanceFromTarget = 0 s_HeightDistance = 0 end - p_Bot._TargetPoint = s_Point - p_Bot._NextTargetPoint = s_NextPoint + self._TargetPoint = s_Point + self._NextTargetPoint = s_NextPoint - if math.abs(s_CurrentWayPointDistance - p_Bot._LastWayDistance) < 0.02 or p_Bot._ObstacleSequenceTimer ~= 0 then + if math.abs(s_CurrentWayPointDistance - self._LastWayDistance) < 0.02 or self._ObstacleSequenceTimer ~= 0 then -- Try to get around obstacle. - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.Sprint -- Always try to stand. - - if p_Bot._ObstacleSequenceTimer == 0 then -- Step 0 - elseif p_Bot._ObstacleSequenceTimer > 2.4 then -- Step 4 - repeat afterwards. - p_Bot._ObstacleSequenceTimer = 0 - p_Bot:_ResetActionFlag(BotActionFlags.MeleeActive) - p_Bot._ObstacleRetryCounter = p_Bot._ObstacleRetryCounter + 1 - elseif p_Bot._ObstacleSequenceTimer > 1.0 then -- Step 3 - if p_Bot._ObstacleRetryCounter == 0 then - if p_Bot._ActiveAction ~= BotActionFlags.MeleeActive then - p_Bot._ActiveAction = BotActionFlags.MeleeActive - p_Bot:_SetInput(EntryInputActionEnum.EIASelectWeapon7, 1) - p_Bot:_SetInput(EntryInputActionEnum.EIAQuicktimeFastMelee, 1) - p_Bot:_SetInput(EntryInputActionEnum.EIAMeleeAttack, 1) - p_Bot.m_ActiveWeapon = p_Bot.m_Knife - p_Bot._MeleeCooldownTimer = Config.MeleeAttackCoolDown -- Set time to ensure bot exit knife-mode when attack starts. + self.m_ActiveSpeedValue = BotMoveSpeeds.Sprint -- Always try to stand. + + if self._ObstacleSequenceTimer == 0 then -- Step 0 + elseif self._ObstacleSequenceTimer > 2.4 then -- Step 4 - repeat afterwards. + self._ObstacleSequenceTimer = 0 + self:_ResetActionFlag(BotActionFlags.MeleeActive) + self._ObstacleRetryCounter = self._ObstacleRetryCounter + 1 + elseif self._ObstacleSequenceTimer > 1.0 then -- Step 3 + if self._ObstacleRetryCounter == 0 then + if self._ActiveAction ~= BotActionFlags.MeleeActive then + self._ActiveAction = BotActionFlags.MeleeActive + self:_SetInput(EntryInputActionEnum.EIASelectWeapon7, 1) + self:_SetInput(EntryInputActionEnum.EIAQuicktimeFastMelee, 1) + self:_SetInput(EntryInputActionEnum.EIAMeleeAttack, 1) + self.m_ActiveWeapon = self.m_Knife + self._MeleeCooldownTimer = Config.MeleeAttackCoolDown -- Set time to ensure bot exit knife-mode when attack starts. else - p_Bot:_SetInput(EntryInputActionEnum.EIAFire, 1) + self:_SetInput(EntryInputActionEnum.EIAFire, 1) end else - p_Bot:_SetInput(EntryInputActionEnum.EIAFire, 1) + self:_SetInput(EntryInputActionEnum.EIAFire, 1) end - elseif p_Bot._ObstacleSequenceTimer > 0.4 then -- Step 2 - p_Bot._TargetPitch = 0.0 + elseif self._ObstacleSequenceTimer > 0.4 then -- Step 2 + self._TargetPitch = 0.0 if (MathUtils:GetRandomInt(0, 1) == 1) then - p_Bot:_SetInput(EntryInputActionEnum.EIAStrafe, 1.0 * Config.SpeedFactor) + self:_SetInput(EntryInputActionEnum.EIAStrafe, 1.0 * Config.SpeedFactor) else - p_Bot:_SetInput(EntryInputActionEnum.EIAStrafe, -1.0 * Config.SpeedFactor) + self:_SetInput(EntryInputActionEnum.EIAStrafe, -1.0 * Config.SpeedFactor) end - elseif p_Bot._ObstacleSequenceTimer > 0.0 then -- Step 1 - p_Bot:_SetInput(EntryInputActionEnum.EIAQuicktimeJumpClimb, 1) - p_Bot:_SetInput(EntryInputActionEnum.EIAJump, 1) + elseif self._ObstacleSequenceTimer > 0.0 then -- Step 1 + self:_SetInput(EntryInputActionEnum.EIAQuicktimeJumpClimb, 1) + self:_SetInput(EntryInputActionEnum.EIAJump, 1) end - p_Bot._ObstacleSequenceTimer = p_Bot._ObstacleSequenceTimer + p_DeltaTime - p_Bot._StuckTimer = p_Bot._StuckTimer + p_DeltaTime + self._ObstacleSequenceTimer = self._ObstacleSequenceTimer + p_DeltaTime + self._StuckTimer = self._StuckTimer + p_DeltaTime - if p_Bot._ObstacleRetryCounter >= 2 then -- Try next waypoint. - p_Bot._ObstacleRetryCounter = 0 - p_Bot:_ResetActionFlag(BotActionFlags.MeleeActive) + if self._ObstacleRetryCounter >= 2 then -- Try next waypoint. + self._ObstacleRetryCounter = 0 + self:_ResetActionFlag(BotActionFlags.MeleeActive) s_DistanceFromTarget = 0 s_HeightDistance = 0 -- Teleport to target. s_NoStuckReset = true if Config.TeleportIfStuck and m_Utilities:CheckProbablity(Registry.BOT.PROBABILITY_TELEPORT_IF_STUCK) then - local s_Transform = p_Bot.m_Player.soldier.worldTransform:Clone() - s_Transform.trans = p_Bot._TargetPoint.Position - s_Transform:LookAtTransform(p_Bot._TargetPoint.Position, p_Bot._NextTargetPoint.Position) - p_Bot.m_Player.soldier:SetTransform(s_Transform) - m_Logger:Write('teleported ' .. p_Bot.m_Player.name) + local s_Transform = self.m_Player.soldier.worldTransform:Clone() + s_Transform.trans = self._TargetPoint.Position + s_Transform:LookAtTransform(self._TargetPoint.Position, self._NextTargetPoint.Position) + self.m_Player.soldier:SetTransform(s_Transform) + m_Logger:Write('teleported ' .. self.m_Player.name) else s_PointIncrement = MathUtils:GetRandomInt(-5, 5) -- Go 5 points further. -- Experimental. @@ -307,36 +299,36 @@ function BotMovement:UpdateNormalMovement(p_DeltaTime, p_Bot) end if (Globals.IsConquest or Globals.IsRush) then - if g_GameDirector:IsOnObjectivePath(p_Bot._PathIndex) then - p_Bot._InvertPathDirection = m_Utilities:CheckProbablity(Registry.BOT.PROBABILITY_CHANGE_DIRECTION_IF_STUCK) + if g_GameDirector:IsOnObjectivePath(self._PathIndex) then + self._InvertPathDirection = m_Utilities:CheckProbablity(Registry.BOT.PROBABILITY_CHANGE_DIRECTION_IF_STUCK) end end end end - if p_Bot._StuckTimer > 15.0 then - p_Bot.m_Player.soldier:Kill() + if self._StuckTimer > 15.0 then + self.m_Player.soldier:Kill() - m_Logger:Write(p_Bot.m_Player.name .. ' got stuck. Kill') + m_Logger:Write(self.m_Player.name .. ' got stuck. Kill') return end else - p_Bot:_ResetActionFlag(BotActionFlags.MeleeActive) + self:_ResetActionFlag(BotActionFlags.MeleeActive) end - p_Bot._LastWayDistance = s_CurrentWayPointDistance + self._LastWayDistance = s_CurrentWayPointDistance -- Jump detection. Much more simple now, but works fine -) - if p_Bot._ObstacleSequenceTimer == 0 then - if (s_Point.Position.y - p_Bot.m_Player.soldier.worldTransform.trans.y) > 0.3 and Config.JumpWhileMoving then + if self._ObstacleSequenceTimer == 0 then + if (s_Point.Position.y - self.m_Player.soldier.worldTransform.trans.y) > 0.3 and Config.JumpWhileMoving then -- Detect, if a jump was recorded or not. local s_TimeForwardBackwardJumpDetection = 1.1 -- 1.5 s ahead and back. local s_JumpValid = false for i = 1, math.floor(s_TimeForwardBackwardJumpDetection / Config.TraceDelta) do - local s_PointBefore = m_NodeCollection:Get(s_ActivePointIndex - i, p_Bot._PathIndex) - local s_PointAfter = m_NodeCollection:Get(s_ActivePointIndex + i, p_Bot._PathIndex) + local s_PointBefore = m_NodeCollection:Get(s_ActivePointIndex - i, self._PathIndex) + local s_PointAfter = m_NodeCollection:Get(s_ActivePointIndex + i, self._PathIndex) if (s_PointBefore ~= nil and s_PointBefore.ExtraMode == 1) or (s_PointAfter ~= nil and s_PointAfter.ExtraMode == 1) then @@ -346,26 +338,26 @@ function BotMovement:UpdateNormalMovement(p_DeltaTime, p_Bot) end if s_JumpValid then - p_Bot:_SetInput(EntryInputActionEnum.EIAJump, 1) - p_Bot:_SetInput(EntryInputActionEnum.EIAQuicktimeJumpClimb, 1) + self:_SetInput(EntryInputActionEnum.EIAJump, 1) + self:_SetInput(EntryInputActionEnum.EIAQuicktimeJumpClimb, 1) end end end local s_TargetDistanceSpeed = Config.TargetDistanceWayPoint - if p_Bot.m_ActiveSpeedValue == BotMoveSpeeds.Sprint then + if self.m_ActiveSpeedValue == BotMoveSpeeds.Sprint then s_TargetDistanceSpeed = s_TargetDistanceSpeed * 1.5 - elseif p_Bot.m_ActiveSpeedValue == BotMoveSpeeds.SlowCrouch then + elseif self.m_ActiveSpeedValue == BotMoveSpeeds.SlowCrouch then s_TargetDistanceSpeed = s_TargetDistanceSpeed * 0.7 - elseif p_Bot.m_ActiveSpeedValue == BotMoveSpeeds.VerySlowProne then + elseif self.m_ActiveSpeedValue == BotMoveSpeeds.VerySlowProne then s_TargetDistanceSpeed = s_TargetDistanceSpeed * 0.5 end -- Check for reached target. if s_DistanceFromTarget <= s_TargetDistanceSpeed and s_HeightDistance <= Registry.BOT.TARGET_HEIGHT_DISTANCE_WAYPOINT then if not s_NoStuckReset then - p_Bot._StuckTimer = 0.0 + self._StuckTimer = 0.0 end @@ -373,21 +365,21 @@ function BotMovement:UpdateNormalMovement(p_DeltaTime, p_Bot) if s_Point.Data.Action ~= nil then local s_Action = s_Point.Data.Action - if g_GameDirector:CheckForExecution(s_Point, p_Bot.m_Player.teamId, false) then - p_Bot._ActiveAction = BotActionFlags.OtherActionActive + if g_GameDirector:CheckForExecution(s_Point, self.m_Player.teamId, false) then + self._ActiveAction = BotActionFlags.OtherActionActive if s_Action.time ~= nil then - p_Bot._ActionTimer = s_Action.time + self._ActionTimer = s_Action.time else - p_Bot._ActionTimer = 0.0 + self._ActionTimer = 0.0 end if s_Action.yaw ~= nil then - p_Bot._TargetYaw = s_Action.yaw + self._TargetYaw = s_Action.yaw end if s_Action.pitch ~= nil then - p_Bot._TargetPitch = s_Action.pitch + self._TargetPitch = s_Action.pitch end return -- DON'T DO ANYTHING ELSE ANY MORE. @@ -397,54 +389,54 @@ function BotMovement:UpdateNormalMovement(p_DeltaTime, p_Bot) -- CHECK FOR PATH-SWITCHES. local s_NewWaypoint = nil local s_SwitchPath = false - s_SwitchPath, s_NewWaypoint = m_PathSwitcher:GetNewPath(p_Bot, p_Bot.m_Id, s_Point, p_Bot._Objective, false, - p_Bot.m_Player.teamId, nil) + s_SwitchPath, s_NewWaypoint = m_PathSwitcher:GetNewPath(self, self.m_Id, s_Point, self._Objective, false, + self.m_Player.teamId, nil) - if p_Bot.m_Player.soldier == nil then + if self.m_Player.soldier == nil then return end - if s_SwitchPath == true and not p_Bot._OnSwitch and s_NewWaypoint then - if p_Bot._Objective ~= '' then + if s_SwitchPath == true and not self._OnSwitch and s_NewWaypoint then + if self._Objective ~= '' then -- 'Best' direction for objective on switch. - local s_Direction = m_NodeCollection:ObjectiveDirection(s_NewWaypoint, p_Bot._Objective, false) + local s_Direction = m_NodeCollection:ObjectiveDirection(s_NewWaypoint, self._Objective, false) if s_Direction then - p_Bot._InvertPathDirection = (s_Direction == 'Previous') + self._InvertPathDirection = (s_Direction == 'Previous') end else -- Random path direction on switch. - p_Bot._InvertPathDirection = MathUtils:GetRandomInt(1, 2) == 1 + self._InvertPathDirection = MathUtils:GetRandomInt(1, 2) == 1 end - p_Bot._PathIndex = s_NewWaypoint.PathIndex - p_Bot._CurrentWayPoint = s_NewWaypoint.PointIndex - p_Bot._OnSwitch = true + self._PathIndex = s_NewWaypoint.PathIndex + self._CurrentWayPoint = s_NewWaypoint.PointIndex + self._OnSwitch = true else - p_Bot._OnSwitch = false + self._OnSwitch = false - if p_Bot._InvertPathDirection then - p_Bot._CurrentWayPoint = s_ActivePointIndex - s_PointIncrement + if self._InvertPathDirection then + self._CurrentWayPoint = s_ActivePointIndex - s_PointIncrement else - p_Bot._CurrentWayPoint = s_ActivePointIndex + s_PointIncrement + self._CurrentWayPoint = s_ActivePointIndex + s_PointIncrement end end - p_Bot._ObstacleSequenceTimer = 0 - p_Bot:_ResetActionFlag(BotActionFlags.MeleeActive) - p_Bot._LastWayDistance = 1000.0 + self._ObstacleSequenceTimer = 0 + self:_ResetActionFlag(BotActionFlags.MeleeActive) + self._LastWayDistance = 1000.0 end else -- Wait mode. - p_Bot._WayWaitTimer = p_Bot._WayWaitTimer + p_DeltaTime + self._WayWaitTimer = self._WayWaitTimer + p_DeltaTime - self:LookAround(p_Bot, p_DeltaTime) + self:LookAround(p_DeltaTime) - if p_Bot._WayWaitTimer > s_Point.OptValue then - p_Bot._WayWaitTimer = 0.0 + if self._WayWaitTimer > s_Point.OptValue then + self._WayWaitTimer = 0.0 - if p_Bot._InvertPathDirection then - p_Bot._CurrentWayPoint = s_ActivePointIndex - 1 + if self._InvertPathDirection then + self._CurrentWayPoint = s_ActivePointIndex - 1 else - p_Bot._CurrentWayPoint = s_ActivePointIndex + 1 + self._CurrentWayPoint = s_ActivePointIndex + 1 end end end @@ -453,223 +445,217 @@ function BotMovement:UpdateNormalMovement(p_DeltaTime, p_Bot) end ---@param p_DeltaTime number ----@param p_Bot Bot -function BotMovement:UpdateMovementSprintToTarget(p_DeltaTime, p_Bot) - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.Sprint -- Run to target. +function Bot:UpdateMovementSprintToTarget(p_DeltaTime) + self.m_ActiveSpeedValue = BotMoveSpeeds.Sprint -- Run to target. - if p_Bot.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Stand then - p_Bot.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Stand, true, true) + if self.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Stand then + self.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Stand, true, true) end local s_Jump = true - if p_Bot._ShootPlayer ~= nil and p_Bot._ShootPlayer.corpse ~= nil then - if p_Bot.m_Player.soldier.worldTransform.trans:Distance(p_Bot._ShootPlayer.corpse.worldTransform.trans) < 2 then - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.SlowCrouch + if self._ShootPlayer ~= nil and self._ShootPlayer.corpse ~= nil then + if self.m_Player.soldier.worldTransform.trans:Distance(self._ShootPlayer.corpse.worldTransform.trans) < 2 then + self.m_ActiveSpeedValue = BotMoveSpeeds.SlowCrouch s_Jump = false end end -- To-do: obstacle detection. if s_Jump == true then - p_Bot._AttackModeMoveTimer = p_Bot._AttackModeMoveTimer + p_DeltaTime + self._AttackModeMoveTimer = self._AttackModeMoveTimer + p_DeltaTime - if p_Bot._AttackModeMoveTimer > 3.0 then - p_Bot._AttackModeMoveTimer = 0.0 - elseif p_Bot._AttackModeMoveTimer > 2.5 then - p_Bot:_SetInput(EntryInputActionEnum.EIAJump, 1) - p_Bot:_SetInput(EntryInputActionEnum.EIAQuicktimeJumpClimb, 1) + if self._AttackModeMoveTimer > 3.0 then + self._AttackModeMoveTimer = 0.0 + elseif self._AttackModeMoveTimer > 2.5 then + self:_SetInput(EntryInputActionEnum.EIAJump, 1) + self:_SetInput(EntryInputActionEnum.EIAQuicktimeJumpClimb, 1) end end end ---@param p_DeltaTime number ----@param p_Bot Bot -function BotMovement:UpdateShootMovement(p_DeltaTime, p_Bot) - p_Bot._DefendTimer = 0.0 +function Bot:UpdateShootMovement(p_DeltaTime) + self._DefendTimer = 0.0 -- Shoot MoveMode. - if p_Bot._AttackMode == BotAttackModes.RandomNotSet then + if self._AttackMode == BotAttackModes.RandomNotSet then if Config.BotAttackMode ~= BotAttackModes.RandomNotSet then - p_Bot._AttackMode = Config.BotAttackMode + self._AttackMode = Config.BotAttackMode else -- Random. if MathUtils:GetRandomInt(0, 1) == 1 then - p_Bot._AttackMode = BotAttackModes.Stand + self._AttackMode = BotAttackModes.Stand else - p_Bot._AttackMode = BotAttackModes.Crouch + self._AttackMode = BotAttackModes.Crouch end end end - if (p_Bot.m_ActiveWeapon and (p_Bot.m_ActiveWeapon.type == WeaponTypes.Sniper or - p_Bot.m_ActiveWeapon.type == WeaponTypes.MissileAir or - p_Bot.m_ActiveWeapon.type == WeaponTypes.MissileLand) and - not p_Bot.m_KnifeMode) then -- Don't move while shooting some weapons. - if p_Bot._AttackMode == BotAttackModes.Crouch then - if p_Bot.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Crouch then - p_Bot.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Crouch, true, true) + if (self.m_ActiveWeapon and (self.m_ActiveWeapon.type == WeaponTypes.Sniper or + self.m_ActiveWeapon.type == WeaponTypes.MissileAir or + self.m_ActiveWeapon.type == WeaponTypes.MissileLand) and + not self.m_KnifeMode) then -- Don't move while shooting some weapons. + if self._AttackMode == BotAttackModes.Crouch then + if self.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Crouch then + self.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Crouch, true, true) end else - if p_Bot.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Stand then - p_Bot.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Stand, true, true) + if self.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Stand then + self.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Stand, true, true) end end - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.NoMovement + self.m_ActiveSpeedValue = BotMoveSpeeds.NoMovement else local s_TargetTime = 5.0 local s_TargetCycles = math.floor(s_TargetTime / Registry.BOT.TRACE_DELTA_SHOOTING) - if p_Bot.m_KnifeMode then -- Knife Only Mode. + if self.m_KnifeMode then -- Knife Only Mode. s_TargetCycles = 1 - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.Sprint -- Run towards player. + self.m_ActiveSpeedValue = BotMoveSpeeds.Sprint -- Run towards player. else - if p_Bot._AttackMode == BotAttackModes.Crouch then - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.SlowCrouch + if self._AttackMode == BotAttackModes.Crouch then + self.m_ActiveSpeedValue = BotMoveSpeeds.SlowCrouch else - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.Normal + self.m_ActiveSpeedValue = BotMoveSpeeds.Normal end end if Config.OverWriteBotAttackMode ~= BotMoveSpeeds.NoMovement then - p_Bot.m_ActiveSpeedValue = Config.OverWriteBotAttackMode + self.m_ActiveSpeedValue = Config.OverWriteBotAttackMode end - if #p_Bot._ShootWayPoints > s_TargetCycles and Config.JumpWhileShooting then - local s_DistanceDone = p_Bot._ShootWayPoints[#p_Bot._ShootWayPoints].Position:Distance(p_Bot._ShootWayPoints[ - #p_Bot._ShootWayPoints - s_TargetCycles].Position) - if s_DistanceDone < 0.5 and p_Bot._DistanceToPlayer > 1.0 then -- No movement was possible. Try to jump over an obstacle. - table.remove(p_Bot._ShootWayPoints) - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.Normal - p_Bot:_SetInput(EntryInputActionEnum.EIAJump, 1) - p_Bot:_SetInput(EntryInputActionEnum.EIAQuicktimeJumpClimb, 1) + if #self._ShootWayPoints > s_TargetCycles and Config.JumpWhileShooting then + local s_DistanceDone = self._ShootWayPoints[#self._ShootWayPoints].Position:Distance(self._ShootWayPoints[ + #self._ShootWayPoints - s_TargetCycles].Position) + if s_DistanceDone < 0.5 and self._DistanceToPlayer > 1.0 then -- No movement was possible. Try to jump over an obstacle. + table.remove(self._ShootWayPoints) + self.m_ActiveSpeedValue = BotMoveSpeeds.Normal + self:_SetInput(EntryInputActionEnum.EIAJump, 1) + self:_SetInput(EntryInputActionEnum.EIAQuicktimeJumpClimb, 1) end end -- Do some sidewards movement from time to time. - if p_Bot._AttackModeMoveTimer > 20.0 then - p_Bot._AttackModeMoveTimer = 0.0 - elseif p_Bot._AttackModeMoveTimer > 17.0 then - p_Bot:_SetInput(EntryInputActionEnum.EIAStrafe, -0.5 * Config.SpeedFactorAttack) - elseif p_Bot._AttackModeMoveTimer > 12.0 and p_Bot._AttackModeMoveTimer <= 13.0 then - p_Bot:_SetInput(EntryInputActionEnum.EIAStrafe, 0.5 * Config.SpeedFactorAttack) - elseif p_Bot._AttackModeMoveTimer > 7.0 and p_Bot._AttackModeMoveTimer <= 9.0 then - p_Bot:_SetInput(EntryInputActionEnum.EIAStrafe, 0.5 * Config.SpeedFactorAttack) + if self._AttackModeMoveTimer > 20.0 then + self._AttackModeMoveTimer = 0.0 + elseif self._AttackModeMoveTimer > 17.0 then + self:_SetInput(EntryInputActionEnum.EIAStrafe, -0.5 * Config.SpeedFactorAttack) + elseif self._AttackModeMoveTimer > 12.0 and self._AttackModeMoveTimer <= 13.0 then + self:_SetInput(EntryInputActionEnum.EIAStrafe, 0.5 * Config.SpeedFactorAttack) + elseif self._AttackModeMoveTimer > 7.0 and self._AttackModeMoveTimer <= 9.0 then + self:_SetInput(EntryInputActionEnum.EIAStrafe, 0.5 * Config.SpeedFactorAttack) end - p_Bot._AttackModeMoveTimer = p_Bot._AttackModeMoveTimer + p_DeltaTime + self._AttackModeMoveTimer = self._AttackModeMoveTimer + p_DeltaTime end end ----@param p_Bot Bot -function BotMovement:UpdateSpeedOfMovement(p_Bot) +function Bot:UpdateSpeedOfMovement() -- Additional movement. - if p_Bot.m_Player.soldier == nil then + if self.m_Player.soldier == nil then return end - if p_Bot._ActiveAction == BotActionFlags.OtherActionActive then + if self._ActiveAction == BotActionFlags.OtherActionActive then return end local s_SpeedVal = 0 - if p_Bot.m_ActiveMoveMode ~= BotMoveModes.Standstill then - if p_Bot.m_ActiveSpeedValue == BotMoveSpeeds.VerySlowProne then + if self.m_ActiveMoveMode ~= BotMoveModes.Standstill then + if self.m_ActiveSpeedValue == BotMoveSpeeds.VerySlowProne then s_SpeedVal = 1.0 - if p_Bot.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Prone then - p_Bot.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Prone, true, true) + if self.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Prone then + self.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Prone, true, true) end - elseif p_Bot.m_ActiveSpeedValue == BotMoveSpeeds.SlowCrouch then + elseif self.m_ActiveSpeedValue == BotMoveSpeeds.SlowCrouch then s_SpeedVal = 1.0 - if p_Bot.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Crouch then - p_Bot.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Crouch, true, true) + if self.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Crouch then + self.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Crouch, true, true) end - elseif p_Bot.m_ActiveSpeedValue >= BotMoveSpeeds.Normal then + elseif self.m_ActiveSpeedValue >= BotMoveSpeeds.Normal then s_SpeedVal = 1.0 - if p_Bot.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Stand then - p_Bot.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Stand, true, true) + if self.m_Player.soldier.pose ~= CharacterPoseType.CharacterPoseType_Stand then + self.m_Player.soldier:SetPose(CharacterPoseType.CharacterPoseType_Stand, true, true) end end end -- Do not reduce speed if sprinting. - if s_SpeedVal > 0 and p_Bot._ShootPlayer ~= nil and p_Bot._ShootPlayer.soldier ~= nil and - p_Bot.m_ActiveSpeedValue <= BotMoveSpeeds.Normal then + if s_SpeedVal > 0 and self._ShootPlayer ~= nil and self._ShootPlayer.soldier ~= nil and + self.m_ActiveSpeedValue <= BotMoveSpeeds.Normal then s_SpeedVal = s_SpeedVal * Config.SpeedFactorAttack end -- Movent speed. - if p_Bot.m_ActiveSpeedValue ~= BotMoveSpeeds.Sprint then - p_Bot:_SetInput(EntryInputActionEnum.EIAThrottle, s_SpeedVal * Config.SpeedFactor) + if self.m_ActiveSpeedValue ~= BotMoveSpeeds.Sprint then + self:_SetInput(EntryInputActionEnum.EIAThrottle, s_SpeedVal * Config.SpeedFactor) else - p_Bot:_SetInput(EntryInputActionEnum.EIAThrottle, 1) - p_Bot:_SetInput(EntryInputActionEnum.EIASprint, s_SpeedVal * Config.SpeedFactor) + self:_SetInput(EntryInputActionEnum.EIAThrottle, 1) + self:_SetInput(EntryInputActionEnum.EIASprint, s_SpeedVal * Config.SpeedFactor) end end ----@param p_Bot Bot -function BotMovement:UpdateTargetMovement(p_Bot) - if p_Bot._TargetPoint and p_Bot.m_Player.soldier then - local s_Distance = p_Bot.m_Player.soldier.worldTransform.trans:Distance(p_Bot._TargetPoint.Position) +function Bot:UpdateTargetMovement() + if self._TargetPoint and self.m_Player.soldier then + local s_Distance = self.m_Player.soldier.worldTransform.trans:Distance(self._TargetPoint.Position) if s_Distance < 0.2 then - p_Bot._TargetPoint = p_Bot._NextTargetPoint + self._TargetPoint = self._NextTargetPoint end - local s_DifferenceY = p_Bot._TargetPoint.Position.z - p_Bot.m_Player.soldier.worldTransform.trans.z - local s_DifferenceX = p_Bot._TargetPoint.Position.x - p_Bot.m_Player.soldier.worldTransform.trans.x + local s_DifferenceY = self._TargetPoint.Position.z - self.m_Player.soldier.worldTransform.trans.z + local s_DifferenceX = self._TargetPoint.Position.x - self.m_Player.soldier.worldTransform.trans.x local s_AtanDzDx = math.atan(s_DifferenceY, s_DifferenceX) local s_Yaw = (s_AtanDzDx > math.pi / 2) and (s_AtanDzDx - math.pi / 2) or (s_AtanDzDx + 3 * math.pi / 2) - p_Bot._TargetYaw = s_Yaw - p_Bot._TargetYaw = p_Bot._TargetYaw + p_Bot.m_YawOffset + self._TargetYaw = s_Yaw + self._TargetYaw = self._TargetYaw + self.m_YawOffset end end ----@param p_Bot Bot ---@param p_DeltaTime number -function BotMovement:LookAround(p_Bot, p_DeltaTime) +function Bot:LookAround(p_DeltaTime) -- Move around a little. - local s_LastYawTimer = p_Bot._WayWaitYawTimer - p_Bot._WayWaitYawTimer = p_Bot._WayWaitYawTimer + p_DeltaTime - p_Bot.m_ActiveSpeedValue = BotMoveSpeeds.NoMovement - p_Bot._TargetPoint = nil - p_Bot._TargetPitch = 0.0 - - if p_Bot._WayWaitYawTimer > 6.0 then - p_Bot._WayWaitYawTimer = 0.0 - p_Bot._TargetYaw = p_Bot._TargetYaw + 1.0 -- 60° rotation right. - - if p_Bot._TargetYaw > (math.pi * 2) then - p_Bot._TargetYaw = p_Bot._TargetYaw - (2 * math.pi) + local s_LastYawTimer = self._WayWaitYawTimer + self._WayWaitYawTimer = self._WayWaitYawTimer + p_DeltaTime + self.m_ActiveSpeedValue = BotMoveSpeeds.NoMovement + self._TargetPoint = nil + self._TargetPitch = 0.0 + + if self._WayWaitYawTimer > 6.0 then + self._WayWaitYawTimer = 0.0 + self._TargetYaw = self._TargetYaw + 1.0 -- 60° rotation right. + + if self._TargetYaw > (math.pi * 2) then + self._TargetYaw = self._TargetYaw - (2 * math.pi) end - elseif p_Bot._WayWaitYawTimer >= 4.0 and s_LastYawTimer < 4.0 then - p_Bot._TargetYaw = p_Bot._TargetYaw - 1.0 -- 60° rotation left. + elseif self._WayWaitYawTimer >= 4.0 and s_LastYawTimer < 4.0 then + self._TargetYaw = self._TargetYaw - 1.0 -- 60° rotation left. - if p_Bot._TargetYaw < 0.0 then - p_Bot._TargetYaw = p_Bot._TargetYaw + (2 * math.pi) + if self._TargetYaw < 0.0 then + self._TargetYaw = self._TargetYaw + (2 * math.pi) end - elseif p_Bot._WayWaitYawTimer >= 3.0 and s_LastYawTimer < 3.0 then - p_Bot._TargetYaw = p_Bot._TargetYaw - 1.0 -- 60° rotation left. + elseif self._WayWaitYawTimer >= 3.0 and s_LastYawTimer < 3.0 then + self._TargetYaw = self._TargetYaw - 1.0 -- 60° rotation left. - if p_Bot._TargetYaw < 0.0 then - p_Bot._TargetYaw = p_Bot._TargetYaw + (2 * math.pi) + if self._TargetYaw < 0.0 then + self._TargetYaw = self._TargetYaw + (2 * math.pi) end - elseif p_Bot._WayWaitYawTimer >= 1.0 and s_LastYawTimer < 1.0 then - p_Bot._TargetYaw = p_Bot._TargetYaw + 1.0 -- 60° rotation right. + elseif self._WayWaitYawTimer >= 1.0 and s_LastYawTimer < 1.0 then + self._TargetYaw = self._TargetYaw + 1.0 -- 60° rotation right. - if p_Bot._TargetYaw > (math.pi * 2) then - p_Bot._TargetYaw = p_Bot._TargetYaw - (2 * math.pi) + if self._TargetYaw > (math.pi * 2) then + self._TargetYaw = self._TargetYaw - (2 * math.pi) end end end ----@param p_Bot Bot -function BotMovement:UpdateYaw(p_Bot) +function Bot:UpdateYaw() local s_DeltaYaw = 0 - s_DeltaYaw = p_Bot.m_Player.input.authoritativeAimingYaw - p_Bot._TargetYaw + s_DeltaYaw = self.m_Player.input.authoritativeAimingYaw - self._TargetYaw if s_DeltaYaw > math.pi then s_DeltaYaw = s_DeltaYaw - 2 * math.pi @@ -681,8 +667,8 @@ function BotMovement:UpdateYaw(p_Bot) local s_Increment = Globals.YawPerFrame if s_AbsDeltaYaw < s_Increment then - p_Bot.m_Player.input.authoritativeAimingYaw = p_Bot._TargetYaw - p_Bot.m_Player.input.authoritativeAimingPitch = p_Bot._TargetPitch + self.m_Player.input.authoritativeAimingYaw = self._TargetYaw + self.m_Player.input.authoritativeAimingPitch = self._TargetPitch return end @@ -690,7 +676,7 @@ function BotMovement:UpdateYaw(p_Bot) s_Increment = -s_Increment end - local s_TempYaw = p_Bot.m_Player.input.authoritativeAimingYaw + s_Increment + local s_TempYaw = self.m_Player.input.authoritativeAimingYaw + s_Increment if s_TempYaw >= (math.pi * 2) then s_TempYaw = s_TempYaw - (math.pi * 2) @@ -698,42 +684,34 @@ function BotMovement:UpdateYaw(p_Bot) s_TempYaw = s_TempYaw + (math.pi * 2) end - p_Bot.m_Player.input.authoritativeAimingYaw = s_TempYaw - p_Bot.m_Player.input.authoritativeAimingPitch = p_Bot._TargetPitch + self.m_Player.input.authoritativeAimingYaw = s_TempYaw + self.m_Player.input.authoritativeAimingPitch = self._TargetPitch end ----@param p_Bot Bot -function BotMovement:UpdateStaticMovement(p_Bot) +function Bot:UpdateStaticMovement() -- Mimicking. - if p_Bot.m_ActiveMoveMode == BotMoveModes.Mimic and p_Bot._TargetPlayer ~= nil then + if self.m_ActiveMoveMode == BotMoveModes.Mimic and self._TargetPlayer ~= nil then ---@type EntryInputActionEnum|integer for i = 0, 36 do - p_Bot:_SetInput(i, p_Bot._TargetPlayer.input:GetLevel(i)) + self:_SetInput(i, self._TargetPlayer.input:GetLevel(i)) end - p_Bot._TargetYaw = p_Bot._TargetPlayer.input.authoritativeAimingYaw - p_Bot._TargetPitch = p_Bot._TargetPlayer.input.authoritativeAimingPitch + self._TargetYaw = self._TargetPlayer.input.authoritativeAimingYaw + self._TargetPitch = self._TargetPlayer.input.authoritativeAimingPitch -- Mirroring. - elseif p_Bot.m_ActiveMoveMode == BotMoveModes.Mirror and p_Bot._TargetPlayer ~= nil then + elseif self.m_ActiveMoveMode == BotMoveModes.Mirror and self._TargetPlayer ~= nil then ---@type EntryInputActionEnum|integer for i = 0, 36 do - p_Bot:_SetInput(i, p_Bot._TargetPlayer.input:GetLevel(i)) + self:_SetInput(i, self._TargetPlayer.input:GetLevel(i)) end - p_Bot._TargetYaw = p_Bot._TargetPlayer.input.authoritativeAimingYaw + + self._TargetYaw = self._TargetPlayer.input.authoritativeAimingYaw + ( - (p_Bot._TargetPlayer.input.authoritativeAimingYaw > math.pi) and + (self._TargetPlayer.input.authoritativeAimingYaw > math.pi) and -math.pi or math.pi ) - p_Bot._TargetPitch = p_Bot._TargetPlayer.input.authoritativeAimingPitch + self._TargetPitch = self._TargetPlayer.input.authoritativeAimingPitch end end - -if g_BotMovement == nil then - ---@type BotMovement - g_BotMovement = BotMovement() -end - -return g_BotMovement diff --git a/ext/Server/Bot/BotSetters.lua b/ext/Server/Bot/BotSetters.lua new file mode 100644 index 00000000..6c6f0fd7 --- /dev/null +++ b/ext/Server/Bot/BotSetters.lua @@ -0,0 +1,264 @@ +---@type NodeCollection +local m_NodeCollection = require('NodeCollection') + +---@param p_State any +function Bot:SetState(p_State) + self.m_ActiveState = p_State + self.m_StateTimer = 0.0 +end + +---@param p_Player Player +function Bot:SetVarsStatic(p_Player) + self._SpawnMode = BotSpawnModes.NoRespawn + self._ForcedMovement = true + self._MoveMode = BotMoveModes.Standstill + self._PathIndex = 0 + self._Respawning = false + self._Shoot = false + self._TargetPlayer = p_Player +end + +---@param p_Player Player|nil +---@param p_UseRandomWay boolean +---@param p_PathIndex integer +---@param p_CurrentWayPoint any To-do: add emmylua type +---@param p_InverseDirection boolean +function Bot:SetVarsWay(p_Player, p_UseRandomWay, p_PathIndex, p_CurrentWayPoint, p_InverseDirection) + if p_UseRandomWay then + self._SpawnMode = BotSpawnModes.RespawnRandomPath + self._TargetPlayer = nil + self._Shoot = Globals.AttackWayBots + self._Respawning = Globals.RespawnWayBots + else + self._SpawnMode = BotSpawnModes.RespawnFixedPath + self._TargetPlayer = p_Player + self._Shoot = false + self._Respawning = false + end + + self.m_ActiveMoveMode = BotMoveModes.Paths + self._PathIndex = p_PathIndex + self._CurrentWayPoint = p_CurrentWayPoint + self._InvertPathDirection = p_InverseDirection +end + +---@param p_MoveMode BotMoveModes|integer +function Bot:SetMoveMode(p_MoveMode) + self._ForcedMovement = true + self._MoveMode = p_MoveMode +end + +---@param p_Respawn boolean +function Bot:SetRespawn(p_Respawn) + self._Respawning = p_Respawn +end + +---@param p_Shoot boolean +function Bot:SetShoot(p_Shoot) + self._Shoot = p_Shoot +end + +function Bot:SetObjectiveIfPossible(p_Objective, p_ObjectiveMode) + if self._Objective ~= p_Objective and p_Objective ~= '' then + local s_Point = m_NodeCollection:Get(self._CurrentWayPoint, self._PathIndex) + + if s_Point ~= nil then + local s_Direction, s_BestWaypoint = m_NodeCollection:ObjectiveDirection(s_Point, p_Objective, g_BotStates:IsInVehicleState(self.m_ActiveState)) + if s_BestWaypoint then + self._Objective = p_Objective + self._ObjectiveMode = p_ObjectiveMode + if s_Direction then + self._InvertPathDirection = (s_Direction == 'Previous') + end + return true + end + end + end + return false +end + +function Bot:SetObjective(p_Objective, p_ObjectiveMode) + if self._Objective ~= p_Objective or p_ObjectiveMode ~= self._ObjectiveMode then + self._Objective = p_Objective or '' + self._ObjectiveMode = p_ObjectiveMode or BotObjectiveModes.Default + local s_Point = m_NodeCollection:Get(self._CurrentWayPoint, self._PathIndex) + + if s_Point ~= nil then + local s_Direction = m_NodeCollection:ObjectiveDirection(s_Point, self._Objective, g_BotStates:IsInVehicleState(self.m_ActiveState)) + if s_Direction then + self._InvertPathDirection = (s_Direction == 'Previous') + end + end + end +end + +---@param p_Input EntryInputActionEnum|integer +---@param p_Value number +function Bot:_SetInput(p_Input, p_Value) + self.m_ActiveInputs[p_Input] = { + value = p_Value, + reset = p_Value == 0, + } +end + +---@param p_Input EntryInputActionEnum|integer +---@param p_Value number +---@param p_Delay number +function Bot:_SetDelayedInput(p_Input, p_Value, p_Delay) + table.insert(self.m_DelayedInputs, { + input = p_Input, + delay = p_Delay, + value = p_Value, + }) +end + +function Bot:_SetActiveVarsSlow() + if self._ShootPlayer ~= nil then + -- only needed on change + local s_PlayerData = g_PlayerData:GetData(self._ShootPlayerName) + if s_PlayerData then + self._ShootPlayerVehicleType = s_PlayerData.Vehicle + else + self._ShootPlayerVehicleType = VehicleTypes.NoVehicle + end + end +end + +function Bot:_SetActiveVars() + if self._ForcedMovement then + self.m_ActiveMoveMode = self._MoveMode + end + + if Config.BotWeapon == BotWeapons.Knife then + self.m_KnifeMode = true + else + self.m_KnifeMode = false + end +end + +function Bot:SetActiveDelay(p_Time) + self._ActiveDelay = p_Time +end + +function Bot:ResetVehicleTimer() + self._VehicleWaitTimer = 0.0 +end + +function Bot:ResetVars() + self._SpawnMode = BotSpawnModes.NoRespawn + self._ForcedMovement = false + self._ActiveAction = BotActionFlags.NoActionActive + self._PathIndex = 0 + self._Respawning = false + self._Shoot = false + self._TargetPlayer = nil + self._ShootPlayer = nil + self._ShootPlayerName = '' + self._InvertPathDirection = false + self._ExitVehicleActive = false + self._ShotTimer = 0.0 + self._SoundTimer = 30.0 + self._ActiveDelay = 0.0 + self._TargetPoint = nil + self._NextTargetPoint = nil + self._KnifeWayPositions = {} + self._ShootWayPoints = {} + self._SpawnDelayTimer = 0.0 + self._KillYourselfTimer = 0.0 + self._RocketCooldownTimer = 0.0 + self._SpawnProtectionTimer = 0.0 + self._Objective = '' + self._WeaponToUse = BotWeapons.Primary +end + +function Bot:ResetSkill() + self._SkillFound = false +end + +function Bot:ResetSpawnVars() + -- Timers + self._SpawnDelayTimer = 0.0 + self._WayWaitTimer = 0.0 + self._VehicleWaitTimer = 0.0 + self._VehicleSeatTimer = 0.0 + self._VehicleTakeoffTimer = 0.0 + self._WayWaitYawTimer = 0.0 + self._ObstacleSequenceTimer = 0.0 + self._StuckTimer = 0.0 + self._SoundTimer = 30.0 + self._ShotTimer = 0.0 + self._VehicleSecondaryWeaponTimer = 0.0 + self._ShootModeTimer = 0.0 + self._ReloadTimer = 0.0 + self._AttackModeMoveTimer = 0.0 + self._MeleeCooldownTimer = 0.0 + self._ShootTraceTimer = 0.0 + self._ActionTimer = 0.0 + self._BrakeTimer = 0.0 + self._DefendTimer = 0.0 + self._SidewardsTimer = 0.0 + self._KillYourselfTimer = 0.0 + self._RocketCooldownTimer = 0.0 + self._SpawnProtectionTimer = 2.0 + self._DeployTimer = MathUtils:GetRandomInt(1, Config.DeployCycle) + + self._ObstacleRetryCounter = 0 + self._LastWayDistance = 1000.0 + self._ShootPlayer = nil + self._DontAttackPlayers = false + self._ShootPlayerName = '' + self._AttackMode = BotAttackModes.RandomNotSet + self._ShootWayPoints = {} + + -- Skill. + if not self._SkillFound then + self._Accuracy = Config.BotWorseningSkill + Config.BotWorseningSkill * (self.m_Accuracy - 0.5) -- up to 0.5 better or worse + self._AccuracySniper = Config.BotSniperWorseningSkill + Config.BotSniperWorseningSkill * (self.m_Accuracy - 0.5) -- up to 0.5 better or worse + self._SkillFound = true + end + + self._TargetPoint = nil + self._NextTargetPoint = nil + self._ActiveAction = BotActionFlags.NoActionActive + self._KnifeWayPositions = {} + self._ActiveDelay = 0.0 + self._TargetPitch = 0.0 + self._Objective = '' -- Reset objective on spawn, as another spawn-point might have chosen... + self._WeaponToUse = BotWeapons.Primary + + self.m_HasBeacon = false + self.m_DontRevive = false + self._JetAbortAttackActive = false + self._JetTakeoffActive = false + self._VehicleReadyToShoot = false + self._FullVehicleSteering = false + self._VehicleDirBackPositive = false + self._ExitVehicleActive = false + self._OnSwitch = false + self._VehicleMoveWhileShooting = false + self._FireCycleModifier = 1.0 -- don't init it at all on spawn? + + self.m_AttackPriority = 1 + self.m_DelayedInputs = {} + + -- Reset all input-vars. + ---@type EntryInputActionEnum + for l_EIA = 0, 36 do + self.m_ActiveInputs[l_EIA] = { + value = 0, + reset = false, + } + self.m_Player.input:SetLevel(l_EIA, 0.0) + end +end + +---@param p_FlagValue integer|BotActionFlags|nil +function Bot:_ResetActionFlag(p_FlagValue) + if p_FlagValue == nil then + self._ActiveAction = BotActionFlags.NoActionActive + else + if self._ActiveAction == p_FlagValue then + self._ActiveAction = BotActionFlags.NoActionActive + end + end +end diff --git a/ext/Server/Bot/BotWeaponHandling.lua b/ext/Server/Bot/BotWeaponHandling.lua index 9c2ee3d6..f509d4de 100644 --- a/ext/Server/Bot/BotWeaponHandling.lua +++ b/ext/Server/Bot/BotWeaponHandling.lua @@ -1,60 +1,48 @@ ----@class BotWeaponHandling ----@overload fun():BotWeaponHandling -BotWeaponHandling = class('BotWeaponHandling') - ----@type Utilities -local m_Utilities = require('__shared/Utilities') - -function BotWeaponHandling:__init() - -- Nothing to do. -end - ---@param p_DeltaTime number ----@param p_Bot Bot ---@param p_Deploy boolean -function BotWeaponHandling:UpdateDeployAndReload(p_DeltaTime, p_Bot, p_Deploy) - if p_Bot._ActiveAction == BotActionFlags.MeleeActive or p_Bot._ActiveAction == BotActionFlags.OtherActionActive or p_Bot.m_Player.soldier == nil then +function Bot:UpdateDeployAndReload(p_DeltaTime, p_Deploy) + if self._ActiveAction == BotActionFlags.MeleeActive or self._ActiveAction == BotActionFlags.OtherActionActive or self.m_Player.soldier == nil then return end - p_Bot._WeaponToUse = BotWeapons.Primary - p_Bot:_ResetActionFlag(BotActionFlags.C4Active) - p_Bot:_ResetActionFlag(BotActionFlags.ReviveActive) - p_Bot:_ResetActionFlag(BotActionFlags.RepairActive) - p_Bot:_ResetActionFlag(BotActionFlags.EnterVehicleActive) - p_Bot:_ResetActionFlag(BotActionFlags.GrenadeActive) - p_Bot:AbortAttack() + self._WeaponToUse = BotWeapons.Primary + self:_ResetActionFlag(BotActionFlags.C4Active) + self:_ResetActionFlag(BotActionFlags.ReviveActive) + self:_ResetActionFlag(BotActionFlags.RepairActive) + self:_ResetActionFlag(BotActionFlags.EnterVehicleActive) + self:_ResetActionFlag(BotActionFlags.GrenadeActive) + self:AbortAttack() - if p_Bot._ActiveAction ~= BotActionFlags.OtherActionActive then - p_Bot._TargetPitch = 0.0 + if self._ActiveAction ~= BotActionFlags.OtherActionActive then + self._TargetPitch = 0.0 end - p_Bot._ReloadTimer = p_Bot._ReloadTimer + p_DeltaTime + self._ReloadTimer = self._ReloadTimer + p_DeltaTime -- reload primary weapon - if p_Bot.m_ActiveWeapon ~= nil and p_Bot._ReloadTimer > 1.5 and p_Bot._ReloadTimer < 2.55 and - p_Bot.m_Player.soldier.weaponsComponent.weapons[1] and - p_Bot.m_Player.soldier.weaponsComponent.weapons[1].primaryAmmo <= p_Bot.m_ActiveWeapon.reload then - p_Bot:_SetInput(EntryInputActionEnum.EIAReload, 1) + if self.m_ActiveWeapon ~= nil and self._ReloadTimer > 1.5 and self._ReloadTimer < 2.55 and + self.m_Player.soldier.weaponsComponent.weapons[1] and + self.m_Player.soldier.weaponsComponent.weapons[1].primaryAmmo <= self.m_ActiveWeapon.reload then + self:_SetInput(EntryInputActionEnum.EIAReload, 1) end -- keep nades filled - if p_Bot.m_Player.soldier.weaponsComponent.weapons[7] and p_Bot.m_Player.soldier.weaponsComponent.weapons[7].primaryAmmo <= 0 then - p_Bot.m_Player.soldier.weaponsComponent.weapons[7].primaryAmmo = 1 - p_Bot.m_Player.soldier.weaponsComponent.weapons[7].secondaryAmmo = 0 + if self.m_Player.soldier.weaponsComponent.weapons[7] and self.m_Player.soldier.weaponsComponent.weapons[7].primaryAmmo <= 0 then + self.m_Player.soldier.weaponsComponent.weapons[7].primaryAmmo = 1 + self.m_Player.soldier.weaponsComponent.weapons[7].secondaryAmmo = 0 end -- Deploy from time to time. if Config.BotsDeploy and p_Deploy and not Globals.IsScavenger then - if p_Bot.m_PrimaryGadget ~= nil and (p_Bot.m_Kit == BotKits.Support or p_Bot.m_Kit == BotKits.Assault) then - if p_Bot.m_PrimaryGadget.type == WeaponTypes.Ammobag or p_Bot.m_PrimaryGadget.type == WeaponTypes.Medkit then - p_Bot._DeployTimer = p_Bot._DeployTimer + p_DeltaTime + if self.m_PrimaryGadget ~= nil and (self.m_Kit == BotKits.Support or self.m_Kit == BotKits.Assault) then + if self.m_PrimaryGadget.type == WeaponTypes.Ammobag or self.m_PrimaryGadget.type == WeaponTypes.Medkit then + self._DeployTimer = self._DeployTimer + p_DeltaTime - if p_Bot._DeployTimer > Config.DeployCycle then - p_Bot._DeployTimer = 0.0 + if self._DeployTimer > Config.DeployCycle then + self._DeployTimer = 0.0 end - if p_Bot._DeployTimer < 0.7 then - p_Bot._WeaponToUse = BotWeapons.Gadget1 + if self._DeployTimer < 0.7 then + self._WeaponToUse = BotWeapons.Gadget1 end end end @@ -62,82 +50,74 @@ function BotWeaponHandling:UpdateDeployAndReload(p_DeltaTime, p_Bot, p_Deploy) end ---@param p_DeltaTime number ----@param p_Bot Bot -function BotWeaponHandling:UpdateWeaponSelection(p_DeltaTime, p_Bot) +function Bot:UpdateWeaponSelection(p_DeltaTime) -- Select weapon-slot. - if p_Bot._ActiveAction ~= BotActionFlags.MeleeActive then - if p_Bot.m_Player.soldier.weaponsComponent ~= nil then - if p_Bot.m_KnifeMode then - if p_Bot.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_7 then - p_Bot:_SetInput(EntryInputActionEnum.EIASelectWeapon7, 1) - p_Bot.m_ActiveWeapon = p_Bot.m_Knife - p_Bot._ShotTimer = 0.0 + if self._ActiveAction ~= BotActionFlags.MeleeActive then + if self.m_Player.soldier.weaponsComponent ~= nil then + if self.m_KnifeMode then + if self.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_7 then + self:_SetInput(EntryInputActionEnum.EIASelectWeapon7, 1) + self.m_ActiveWeapon = self.m_Knife + self._ShotTimer = 0.0 end - elseif p_Bot._ActiveAction == BotActionFlags.ReviveActive or p_Bot._ActiveAction == BotActionFlags.RepairActive or - (p_Bot._WeaponToUse == BotWeapons.Gadget2 and Config.BotWeapon == BotWeapons.Auto) or + elseif self._ActiveAction == BotActionFlags.ReviveActive or self._ActiveAction == BotActionFlags.RepairActive or + (self._WeaponToUse == BotWeapons.Gadget2 and Config.BotWeapon == BotWeapons.Auto) or Config.BotWeapon == BotWeapons.Gadget2 then - if p_Bot.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_5 then - p_Bot:_SetInput(EntryInputActionEnum.EIASelectWeapon5, 1) - p_Bot.m_ActiveWeapon = p_Bot.m_SecondaryGadget - p_Bot._ShotTimer = -p_Bot:GetFirstShotDelay(p_Bot._DistanceToPlayer, false) + if self.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_5 then + self:_SetInput(EntryInputActionEnum.EIASelectWeapon5, 1) + self.m_ActiveWeapon = self.m_SecondaryGadget + self._ShotTimer = -self:GetFirstShotDelay(self._DistanceToPlayer, false) end - elseif (p_Bot._WeaponToUse == BotWeapons.Gadget1 and Config.BotWeapon == BotWeapons.Auto) or + elseif (self._WeaponToUse == BotWeapons.Gadget1 and Config.BotWeapon == BotWeapons.Auto) or Config.BotWeapon == BotWeapons.Gadget1 then - if p_Bot.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_2 and - p_Bot.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_4 then - p_Bot:_SetInput(EntryInputActionEnum.EIASelectWeapon4, 1) - p_Bot:_SetInput(EntryInputActionEnum.EIASelectWeapon3, 1) - p_Bot.m_ActiveWeapon = p_Bot.m_PrimaryGadget - p_Bot._ShotTimer = -p_Bot:GetFirstShotDelay(p_Bot._DistanceToPlayer, false) + if self.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_2 and + self.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_4 then + self:_SetInput(EntryInputActionEnum.EIASelectWeapon4, 1) + self:_SetInput(EntryInputActionEnum.EIASelectWeapon3, 1) + self.m_ActiveWeapon = self.m_PrimaryGadget + self._ShotTimer = -self:GetFirstShotDelay(self._DistanceToPlayer, false) end - elseif p_Bot._ActiveAction == BotActionFlags.GrenadeActive or - (p_Bot._WeaponToUse == BotWeapons.Grenade and Config.BotWeapon == BotWeapons.Auto) or + elseif self._ActiveAction == BotActionFlags.GrenadeActive or + (self._WeaponToUse == BotWeapons.Grenade and Config.BotWeapon == BotWeapons.Auto) or Config.BotWeapon == BotWeapons.Grenade then - if p_Bot.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_6 then - p_Bot:_SetInput(EntryInputActionEnum.EIASelectWeapon6, 1) - p_Bot.m_ActiveWeapon = p_Bot.m_Grenade - p_Bot._ShotTimer = -p_Bot:GetFirstShotDelay(p_Bot._DistanceToPlayer, false) + if self.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_6 then + self:_SetInput(EntryInputActionEnum.EIASelectWeapon6, 1) + self.m_ActiveWeapon = self.m_Grenade + self._ShotTimer = -self:GetFirstShotDelay(self._DistanceToPlayer, false) end - elseif (p_Bot._WeaponToUse == BotWeapons.Pistol and Config.BotWeapon == BotWeapons.Auto) or + elseif (self._WeaponToUse == BotWeapons.Pistol and Config.BotWeapon == BotWeapons.Auto) or Config.BotWeapon == BotWeapons.Pistol then - if p_Bot.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_1 then - p_Bot:_SetInput(EntryInputActionEnum.EIASelectWeapon2, 1) - p_Bot.m_ActiveWeapon = p_Bot.m_Pistol - p_Bot._ShotTimer = -p_Bot:GetFirstShotDelay(p_Bot._DistanceToPlayer, true) + if self.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_1 then + self:_SetInput(EntryInputActionEnum.EIASelectWeapon2, 1) + self.m_ActiveWeapon = self.m_Pistol + self._ShotTimer = -self:GetFirstShotDelay(self._DistanceToPlayer, true) end - if p_Bot.m_Player.soldier.weaponsComponent.weapons[2] and - p_Bot.m_Player.soldier.weaponsComponent.weapons[2].secondaryAmmo < - p_Bot.m_Player.soldier.weaponsComponent.weapons[2].primaryAmmo + 1 then + if self.m_Player.soldier.weaponsComponent.weapons[2] and + self.m_Player.soldier.weaponsComponent.weapons[2].secondaryAmmo < + self.m_Player.soldier.weaponsComponent.weapons[2].primaryAmmo + 1 then -- keep pistol filled - p_Bot.m_Player.soldier.weaponsComponent.weapons[2].secondaryAmmo = p_Bot.m_Player.soldier.weaponsComponent.weapons[ + self.m_Player.soldier.weaponsComponent.weapons[2].secondaryAmmo = self.m_Player.soldier.weaponsComponent.weapons[ 2].primaryAmmo + 3 end - elseif (p_Bot._WeaponToUse == BotWeapons.Primary and Config.BotWeapon == BotWeapons.Auto) or + elseif (self._WeaponToUse == BotWeapons.Primary and Config.BotWeapon == BotWeapons.Auto) or Config.BotWeapon == BotWeapons.Primary then - if p_Bot.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_0 then - p_Bot:_SetInput(EntryInputActionEnum.EIASelectWeapon1, 1) - p_Bot.m_ActiveWeapon = p_Bot.m_Primary - p_Bot._ShotTimer = -0.05 + if self.m_Player.soldier.weaponsComponent.currentWeaponSlot ~= WeaponSlot.WeaponSlot_0 then + self:_SetInput(EntryInputActionEnum.EIASelectWeapon1, 1) + self.m_ActiveWeapon = self.m_Primary + self._ShotTimer = -0.05 end -- keep primary filled - if p_Bot.m_Player.soldier.weaponsComponent.weapons[1] and - p_Bot.m_Player.soldier.weaponsComponent.weapons[1].secondaryAmmo < - p_Bot.m_Player.soldier.weaponsComponent.weapons[1].primaryAmmo + 1 then - p_Bot.m_Player.soldier.weaponsComponent.weapons[1].secondaryAmmo = p_Bot.m_Player.soldier.weaponsComponent.weapons[ + if self.m_Player.soldier.weaponsComponent.weapons[1] and + self.m_Player.soldier.weaponsComponent.weapons[1].secondaryAmmo < + self.m_Player.soldier.weaponsComponent.weapons[1].primaryAmmo + 1 then + self.m_Player.soldier.weaponsComponent.weapons[1].secondaryAmmo = self.m_Player.soldier.weaponsComponent.weapons[ 1].primaryAmmo + 3 end end end end - if p_Bot._RocketCooldownTimer >= 0 then - p_Bot._RocketCooldownTimer = p_Bot._RocketCooldownTimer - p_DeltaTime + if self._RocketCooldownTimer >= 0 then + self._RocketCooldownTimer = self._RocketCooldownTimer - p_DeltaTime end end - -if g_BotWeaponHandling == nil then - ---@type BotWeaponHandling - g_BotWeaponHandling = BotWeaponHandling() -end - -return g_BotWeaponHandling diff --git a/ext/Server/Bot/VehicleActions.lua b/ext/Server/Bot/VehicleActions.lua new file mode 100644 index 00000000..30d7de4e --- /dev/null +++ b/ext/Server/Bot/VehicleActions.lua @@ -0,0 +1,34 @@ +-- For vehicles to block missiles this fires a flare or smoke. +---@param p_TimeDelay number +function Bot:FireFlareSmoke(p_TimeDelay) + self:_SetDelayedInput(EntryInputActionEnum.EIAFireCountermeasure, 1, p_TimeDelay) +end + +---@return boolean +function Bot:_DoExitVehicle() + if self._ExitVehicleActive then + self:AbortAttack() + self.m_Player:ExitVehicle(true, false) + self.m_ActiveVehicle = nil + self:SetState(g_BotStates.States.Moving) + local s_Node = g_GameDirector:FindClosestPath(self.m_Player.soldier.worldTransform.trans, false, true, nil) + + if s_Node ~= nil then + -- Switch to foot. + self._InvertPathDirection = false + self._PathIndex = s_Node.PathIndex + self._CurrentWayPoint = s_Node.PointIndex + self._LastWayDistance = 1000.0 + end + self._KillYourselfTimer = 0.0 + self._ExitVehicleActive = false + self._DontAttackPlayers = false + return true + end + + return false +end + +function Bot:ExitVehicle() + self._ExitVehicleActive = true +end diff --git a/ext/Server/BotManager.lua b/ext/Server/BotManager.lua index fb1ebf72..6fbe2360 100644 --- a/ext/Server/BotManager.lua +++ b/ext/Server/BotManager.lua @@ -2,7 +2,7 @@ ---@overload fun():BotManager BotManager = class('BotManager') -require('Bot') +require('Bot/Bot') ---@type Utilities local m_Utilities = require('__shared/Utilities') @@ -35,7 +35,7 @@ function BotManager:RegisterVars() self._BotAttackBotTimer = 0.0 self._BotReviveBotTimer = 0.0 self._DestroyBotsTimer = 0.0 - ---@type integer[] + ---@type string[] ---`BotId[]` self._BotsToDestroy = {} @@ -1563,9 +1563,9 @@ function BotManager:_CheckForBotBotRevive() end local s_NrOfRaycastsDone = 0 - ---@type MaterialFlags + ---@type MaterialFlags|integer local s_MaterialFlags = 0 - ---@type RayCastFlags + ---@type RayCastFlags|integer local s_RaycastFlags = RayCastFlags.DontCheckWater | RayCastFlags.DontCheckCharacter if #s_MedicBots > 0 and #s_BotsToRevive > 0 then diff --git a/ext/Server/BotSpawner.lua b/ext/Server/BotSpawner.lua index a02213bb..9d702e3e 100644 --- a/ext/Server/BotSpawner.lua +++ b/ext/Server/BotSpawner.lua @@ -59,7 +59,7 @@ end ---@param p_Round integer function BotSpawner:OnLevelLoaded(p_Round) - local s_CurrentGameMode = SharedUtils:GetCurrentGameMode() + local s_CurrentGameMode = SharedUtils:GetCurrentGameMode() or "" if (Globals.LevelName == "XP5_002" or Globals.LevelName == "XP5_004") and s_CurrentGameMode:match("Conquest") then print('Enabling dynamic jets to spawn.') Globals.MapHasDynamiJetSpawns = true @@ -1059,7 +1059,6 @@ function BotSpawner:_FindAttackedSpawnPoint(p_TeamId) local s_BestSpawnPoint = nil local s_LowestFlagLocation = 100.0 local s_EntityIterator = EntityManager:GetIterator("ServerCapturePointEntity") - ---@type CapturePointEntity local s_Entity = s_EntityIterator:Next() while s_Entity do @@ -1105,7 +1104,6 @@ function BotSpawner:_FindClosestSpawnPoint(p_TeamId) -- Enemy and Neutralized CapturePoints. local s_TargetLocation = self:_FindTargetLocation(p_TeamId) local s_EntityIterator = EntityManager:GetIterator("ServerCapturePointEntity") - ---@type CapturePointEntity local s_Entity = s_EntityIterator:Next() while s_Entity do @@ -1154,7 +1152,6 @@ function BotSpawner:_FindTargetLocation(p_TeamId) ---@type Vec3|nil local s_TargetLocation = nil local s_EntityIterator = EntityManager:GetIterator("ServerCapturePointEntity") - ---@type CapturePointEntity local s_Entity = s_EntityIterator:Next() while s_Entity do @@ -1318,6 +1315,7 @@ function BotSpawner:_SpawnSingleWayBot(p_Player, p_UseRandomWay, p_ActiveWayInde local s_InverseDirection = nil if s_Name ~= nil or s_IsRespawn then + ---@cast s_Name -nil -- g_Profiler:Start("BotSpawner:SpawnPart2") -- about 60 ms on conquest (close to 0 on deathmatch) if Globals.UsedSpawnMethod == SpawnMethod.Spawn then local s_Bot = self:GetBot(p_ExistingBot, s_Name, s_TeamId, s_SquadId) @@ -1560,8 +1558,8 @@ end ---comment ---@param p_Bot Bot ---@param p_TeamId TeamId ----@return string ----@return unknown +---@return string? +---@return ControllableEntity? function BotSpawner:_GetSpecialSpawnEnity(p_Bot, p_TeamId) if Globals.IsAirSuperiority or Globals.MapHasDynamiJetSpawns then return "SpawnInJet", p_Bot.m_Player.controlledControllable @@ -1696,6 +1694,7 @@ function BotSpawner:_GetSpawnPoint(p_TeamId, p_SquadId) end s_TargetNode = m_NodeCollection:Get(s_IndexOnPath, s_ActiveWayIndex) + ---@cast s_TargetNode -nil local s_SpawnPoint = s_TargetNode.Position -- Check for nearby player. diff --git a/ext/Server/BotStates/StateAttacking.lua b/ext/Server/BotStates/StateAttacking.lua index 14e55d9d..a2f8f484 100644 --- a/ext/Server/BotStates/StateAttacking.lua +++ b/ext/Server/BotStates/StateAttacking.lua @@ -11,14 +11,6 @@ StateAttacking = class('StateAttacking') -- - enter vehicle -- - idle (on death) ----@type Utilities -local m_Utilities = require('__shared/Utilities') --- bot-methods -local m_BotAiming = require('Bot/BotAiming') -local m_BotAttacking = require('Bot/BotAttacking') -local m_BotMovement = require('Bot/BotMovement') -local m_BotWeaponHandling = require('Bot/BotWeaponHandling') - function StateAttacking:__init() -- Nothing to do. end @@ -41,21 +33,21 @@ function StateAttacking:Update(p_Bot, p_DeltaTime) p_Bot.m_StateTimer = p_Bot.m_StateTimer + p_DeltaTime -- default-handling - m_BotWeaponHandling:UpdateWeaponSelection(p_DeltaTime, p_Bot) -- TODO: maybe compbine with reload now? + p_Bot:UpdateWeaponSelection(p_DeltaTime) -- TODO: maybe compbine with reload now? -- TODO: split revive, repari, c4 and so on - m_BotAttacking:UpdateAttacking(p_DeltaTime, p_Bot) + p_Bot:UpdateAttacking(p_DeltaTime) if p_Bot._ActiveAction == BotActionFlags.ReviveActive or p_Bot._ActiveAction == BotActionFlags.EnterVehicleActive or p_Bot._ActiveAction == BotActionFlags.RepairActive or p_Bot._ActiveAction == BotActionFlags.C4Active then - m_BotMovement:UpdateMovementSprintToTarget(p_DeltaTime, p_Bot) + p_Bot:UpdateMovementSprintToTarget(p_DeltaTime) else - m_BotMovement:UpdateShootMovement(p_DeltaTime, p_Bot) + p_Bot:UpdateShootMovement(p_DeltaTime) end - m_BotMovement:UpdateSpeedOfMovement(p_Bot) + p_Bot:UpdateSpeedOfMovement() p_Bot:_UpdateInputs(p_DeltaTime) end @@ -63,14 +55,14 @@ end ---@param p_Bot Bot ---@param p_DeltaTime number function StateAttacking:UpdateFast(p_Bot, p_DeltaTime) - m_BotAiming:UpdateAiming(p_Bot) + p_Bot:UpdateAiming() end ---update in every frame ---@param p_Bot Bot function StateAttacking:UpdateVeryFast(p_Bot) -- Update yaw of soldier every tick. - m_BotMovement:UpdateYaw(p_Bot) + p_Bot:UpdateYaw() end ---slow update-function diff --git a/ext/Server/BotStates/StateIdle.lua b/ext/Server/BotStates/StateIdle.lua index a2445bef..3ac8988d 100644 --- a/ext/Server/BotStates/StateIdle.lua +++ b/ext/Server/BotStates/StateIdle.lua @@ -11,9 +11,6 @@ StateIdle = class('StateIdle') -- - moving -- (- vehicle-idle?) --- bot-methods -local m_BotMovement = require('Bot/BotMovement') - function StateIdle:__init() -- Nothing to do. end @@ -38,8 +35,8 @@ function StateIdle:Update(p_Bot, p_DeltaTime) p_Bot._SpawnProtectionTimer = p_Bot._SpawnProtectionTimer - p_DeltaTime end - m_BotMovement:UpdateYaw(p_Bot) - m_BotMovement:LookAround(p_Bot, p_DeltaTime) + p_Bot:UpdateYaw() + p_Bot:LookAround(p_DeltaTime) p_Bot:_UpdateInputs(p_DeltaTime) return end @@ -72,7 +69,7 @@ end ---@param p_Bot Bot function StateIdle:UpdateVeryFast(p_Bot) -- Update yaw of soldier every tick. - m_BotMovement:UpdateYaw(p_Bot) + p_Bot:UpdateYaw() end ---slow update-function diff --git a/ext/Server/BotStates/StateInVehicleIdle.lua b/ext/Server/BotStates/StateInVehicleIdle.lua index 49858c11..78eb8e80 100644 --- a/ext/Server/BotStates/StateInVehicleIdle.lua +++ b/ext/Server/BotStates/StateInVehicleIdle.lua @@ -11,13 +11,6 @@ StateInVehicleIdle = class('StateInVehicleIdle') -- - moving -- (- vehicle-idle?) ----@type Utilities -local m_Utilities = require('__shared/Utilities') --- bot-methods -local m_BotAiming = require('Bot/BotAiming') -local m_BotAttacking = require('Bot/BotAttacking') -local m_BotMovement = require('Bot/BotMovement') -local m_BotWeaponHandling = require('Bot/BotWeaponHandling') local m_VehicleAiming = require('Bot/VehicleAiming') local m_VehicleAttacking = require('Bot/VehicleAttacking') local m_VehicleMovement = require('Bot/VehicleMovement') diff --git a/ext/Server/BotStates/StateMoving.lua b/ext/Server/BotStates/StateMoving.lua index 0f8e3a65..9ecd25fb 100644 --- a/ext/Server/BotStates/StateMoving.lua +++ b/ext/Server/BotStates/StateMoving.lua @@ -11,10 +11,6 @@ StateMoving = class('StateMoving') -- - enter vehicle -- - idle (on death) --- bot-methods -local m_BotMovement = require('Bot/BotMovement') -local m_BotWeaponHandling = require('Bot/BotWeaponHandling') - function StateMoving:__init() -- Nothing to do. end @@ -38,11 +34,11 @@ function StateMoving:Update(p_Bot, p_DeltaTime) p_Bot.m_StateTimer = p_Bot.m_StateTimer + p_DeltaTime -- default-handling - m_BotWeaponHandling:UpdateWeaponSelection(p_DeltaTime, p_Bot) -- TODO: maybe compbine with reload now? + p_Bot:UpdateWeaponSelection(p_DeltaTime) -- TODO: maybe compbine with reload now? - m_BotMovement:UpdateNormalMovement(p_DeltaTime, p_Bot) + p_Bot:UpdateNormalMovement(p_DeltaTime) - m_BotMovement:UpdateSpeedOfMovement(p_Bot) + p_Bot:UpdateSpeedOfMovement() p_Bot:_UpdateInputs(p_DeltaTime) end @@ -50,21 +46,21 @@ end ---@param p_Bot Bot ---@param p_DeltaTime number function StateMoving:UpdateFast(p_Bot, p_DeltaTime) - m_BotMovement:UpdateTargetMovement(p_Bot) + p_Bot:UpdateTargetMovement() end ---update in every frame ---@param p_Bot Bot function StateMoving:UpdateVeryFast(p_Bot) -- Update yaw of soldier every tick. - m_BotMovement:UpdateYaw(p_Bot) + p_Bot:UpdateYaw() end ---slow update-function ---@param p_Bot Bot ---@param p_DeltaTime number function StateMoving:UpdateSlow(p_Bot, p_DeltaTime) - m_BotWeaponHandling:UpdateDeployAndReload(p_DeltaTime, p_Bot, true) + p_Bot:UpdateDeployAndReload(p_DeltaTime, true) end if g_StateMoving == nil then diff --git a/ext/Server/BotStates/StateOnVehicleAttacking.lua b/ext/Server/BotStates/StateOnVehicleAttacking.lua index b13a735f..125335d3 100644 --- a/ext/Server/BotStates/StateOnVehicleAttacking.lua +++ b/ext/Server/BotStates/StateOnVehicleAttacking.lua @@ -11,12 +11,6 @@ StateOnVehicleAttacking = class('StateOnVehicleAttacking') -- - enter vehicle -- - idle (on death) --- bot-methods -local m_BotAiming = require('Bot/BotAiming') -local m_BotAttacking = require('Bot/BotAttacking') -local m_BotMovement = require('Bot/BotMovement') -local m_BotWeaponHandling = require('Bot/BotWeaponHandling') - function StateOnVehicleAttacking:__init() -- Nothing to do. end @@ -39,9 +33,9 @@ function StateOnVehicleAttacking:Update(p_Bot, p_DeltaTime) p_Bot.m_StateTimer = p_Bot.m_StateTimer + p_DeltaTime -- Common part. - m_BotWeaponHandling:UpdateWeaponSelection(p_DeltaTime, p_Bot) + p_Bot:UpdateWeaponSelection(p_DeltaTime) - m_BotAttacking:UpdateAttacking(p_DeltaTime, p_Bot) + p_Bot:UpdateAttacking(p_DeltaTime) p_Bot:_UpdateInputs(p_DeltaTime) end @@ -50,14 +44,14 @@ end ---@param p_Bot Bot ---@param p_DeltaTime number function StateOnVehicleAttacking:UpdateFast(p_Bot, p_DeltaTime) - m_BotAiming:UpdateAiming(p_Bot) + p_Bot:UpdateAiming() end ---update in every frame ---@param p_Bot Bot function StateOnVehicleAttacking:UpdateVeryFast(p_Bot) -- Update yaw of soldier every tick. - m_BotMovement:UpdateYaw(p_Bot) + p_Bot:UpdateYaw() end ---slow update-function diff --git a/ext/Server/BotStates/StateOnVehicleIdle.lua b/ext/Server/BotStates/StateOnVehicleIdle.lua index c7e4eda3..b4030dd9 100644 --- a/ext/Server/BotStates/StateOnVehicleIdle.lua +++ b/ext/Server/BotStates/StateOnVehicleIdle.lua @@ -11,10 +11,6 @@ StateOnVehicleIdle = class('StateOnVehicleIdle') -- - moving -- (- vehicle-idle?) --- bot-methods -local m_BotMovement = require('Bot/BotMovement') -local m_BotWeaponHandling = require('Bot/BotWeaponHandling') - function StateOnVehicleIdle:__init() -- Nothing to do. end @@ -33,9 +29,9 @@ function StateOnVehicleIdle:Update(p_Bot, p_DeltaTime) p_Bot.m_StateTimer = p_Bot.m_StateTimer + p_DeltaTime - m_BotWeaponHandling:UpdateWeaponSelection(p_DeltaTime, p_Bot) + p_Bot:UpdateWeaponSelection(p_DeltaTime) - m_BotWeaponHandling:UpdateDeployAndReload(p_DeltaTime, p_Bot, false) + p_Bot:UpdateDeployAndReload(p_DeltaTime, false) p_Bot:_UpdateInputs(p_DeltaTime) @@ -57,7 +53,7 @@ end ---@param p_Bot Bot function StateOnVehicleIdle:UpdateVeryFast(p_Bot) -- Update yaw of soldier every tick. - m_BotMovement:UpdateYaw(p_Bot) + p_Bot:UpdateYaw() end ---slow update-function diff --git a/ext/Server/GameDirector.lua b/ext/Server/GameDirector.lua index 2017af21..cdf321a4 100644 --- a/ext/Server/GameDirector.lua +++ b/ext/Server/GameDirector.lua @@ -1877,7 +1877,6 @@ function GameDirector:_InitFlagTeams() end local s_Iterator = EntityManager:GetIterator('ServerCapturePointEntity') - ---@type CapturePointEntity local s_Entity = s_Iterator:Next() while s_Entity ~= nil do diff --git a/ext/Server/NodeCollection.lua b/ext/Server/NodeCollection.lua index 04ba279e..2856e81b 100644 --- a/ext/Server/NodeCollection.lua +++ b/ext/Server/NodeCollection.lua @@ -377,6 +377,7 @@ function NodeCollection:RecalculateIndexes(p_Waypoint) end while p_Waypoint do + ---@cast p_Waypoint Waypoint self._Waypoints[p_Waypoint.Index] = self:_processWaypointRecalc(p_Waypoint) p_Waypoint = p_Waypoint[s_Direction] s_Counter = s_Counter + 1 @@ -455,8 +456,10 @@ function NodeCollection:_processWaypointMetadata(p_Waypoint) p_Waypoint.Data = {} end - if type(p_Waypoint.Data) == 'string' then - p_Waypoint.Data = json.decode(p_Waypoint.Data) + local s_WaypointData = p_Waypoint.Data + + if type(s_WaypointData) == 'string' then + p_Waypoint.Data = json.decode(s_WaypointData) end -- ----- @@ -653,6 +656,7 @@ function NodeCollection:LinkSpawn(p_SpawnPoint, p_Node) return true end +-- TODO: Finish this, or remove this. ---@param p_SelectionId integer ---@return boolean ---@return string @@ -1256,8 +1260,9 @@ end function NodeCollection:ParseSingleSpawn(p_SpawnPoint) local s_PositionSpawn = p_SpawnPoint.Transform.trans + ---@type MaterialFlags|integer local s_MaterialFlags = 0 - ---@type RayCastFlags + ---@type RayCastFlags|integer local s_RaycastFlags = RayCastFlags.DontCheckWater | RayCastFlags.DontCheckCharacter | RayCastFlags.DontCheckRagdoll | RayCastFlags.CheckDetailMesh local s_Paths = self:GetPaths() diff --git a/ext/Server/PlayerData.lua b/ext/Server/PlayerData.lua index b2636b39..be2fb7df 100644 --- a/ext/Server/PlayerData.lua +++ b/ext/Server/PlayerData.lua @@ -5,10 +5,11 @@ PlayerData = class('PlayerData') ---@class PlayerInformation ---@field Vehicle VehicleTypes|integer ---@field ShootPlayerName string +---@field Team TeamId function PlayerData:__init() ---@type PlayerInformation[] - self._Players = { PlayerInformation } + self._Players = {} end ---comment @@ -17,7 +18,7 @@ function PlayerData:SetPlayerData(p_Player) self._Players[p_Player.name] = { Vehicle = VehicleTypes.NoVehicle, ShootPlayerName = '', - Team = p_Player.teamId ---@type TeamId + Team = p_Player.teamId } end diff --git a/ext/Server/SettingsManager.lua b/ext/Server/SettingsManager.lua index adc0f073..093ed134 100644 --- a/ext/Server/SettingsManager.lua +++ b/ext/Server/SettingsManager.lua @@ -226,8 +226,8 @@ function SettingsManager:UpdateSetting(p_Name, p_Value) if l_Item.Name == p_Name then if l_Item.Type == Type.Integer or l_Item.Type == Type.Float then s_ConvertedValue = tonumber(p_Value) - ---@type Range local s_Reference = l_Item.Reference + ---@cast s_Reference Range -- Check for Range. if s_Reference:GetMax() >= s_ConvertedValue and s_Reference:GetMin() <= s_ConvertedValue then diff --git a/ext/Server/UIServer.lua b/ext/Server/UIServer.lua index 42129706..53a95a40 100644 --- a/ext/Server/UIServer.lua +++ b/ext/Server/UIServer.lua @@ -647,8 +647,8 @@ function FunBotUIServer:_writeSettings(p_Player, p_Request) end elseif l_Item.Type == Type.Integer or l_Item.Type == Type.Float then s_Value = tonumber(p_Request[l_Item.Name]) - ---@type Range local s_Reference = l_Item.Reference + ---@cast s_Reference Range if s_Reference:IsValid(s_Value) then s_Valid = true diff --git a/ext/Server/Vehicles.lua b/ext/Server/Vehicles.lua index 0ee15f0c..c5a54e37 100644 --- a/ext/Server/Vehicles.lua +++ b/ext/Server/Vehicles.lua @@ -24,7 +24,7 @@ function Vehicles:FindOutVehicleType(p_Player) return s_VehicleType end ----@param p_Player Player +---@param p_Player Player? function Vehicles:GetVehicleName(p_Player) if p_Player and p_Player.controlledControllable and not p_Player.controlledControllable:Is("ServerSoldierEntity") then return VehicleEntityData(p_Player.controlledControllable.data).controllableType:gsub(".+/.+/", "") @@ -33,7 +33,7 @@ function Vehicles:GetVehicleName(p_Player) end end ----@param p_Player Player +---@param p_Player Player? function Vehicles:GetVehicle(p_Player) local s_VehicleName = self:GetVehicleName(p_Player) if s_VehicleName == nil then @@ -64,17 +64,17 @@ function Vehicles:GetVehicleByEntity(p_Entity) return s_VehicleData end ----@param p_Entity ControllableEntity +---@param p_Entity ControllableEntity? ---@param p_PlayerIsDriver boolean function Vehicles:GetNrOfFreeSeats(p_Entity, p_PlayerIsDriver) - local s_MaxEntries = p_Entity.entryCount - local s_NrOfFreeSeats = 0 - local s_NumBotsInVehicle = 0 - if not p_Entity then return 0 end + local s_MaxEntries = p_Entity.entryCount + local s_NrOfFreeSeats = 0 + local s_NumBotsInVehicle = 0 + local vehicleData = self:GetVehicle(p_Entity:GetPlayerInEntry(0)) if not vehicleData then return 0 @@ -95,9 +95,11 @@ function Vehicles:GetNrOfFreeSeats(p_Entity, p_PlayerIsDriver) end for i = 0, s_MaxEntries - 1 do - if p_Entity:GetPlayerInEntry(i) == nil then + local s_Player = p_Entity:GetPlayerInEntry(i) + + if s_Player == nil then s_NrOfFreeSeats = s_NrOfFreeSeats + 1 - elseif m_Utilities:isBot(p_Entity:GetPlayerInEntry(i)) then + elseif m_Utilities:isBot(s_Player) then s_NumBotsInVehicle = s_NumBotsInVehicle + 1 end diff --git a/ext/Shared/Constants/VehicleData.lua b/ext/Shared/Constants/VehicleData.lua index 12fb535f..4bdc4fc6 100644 --- a/ext/Shared/Constants/VehicleData.lua +++ b/ext/Shared/Constants/VehicleData.lua @@ -24,6 +24,7 @@ ---@field Team TeamId|integer|nil ---@field Offset Vec3[] ---@field FirstPassengerSeat integer +---@field RotationOffset Vec3[]|nil ---@class VehicleData ---@type VehicleDataInner[] diff --git a/ext/Shared/Utilities.lua b/ext/Shared/Utilities.lua index f6f4cf4d..141cce00 100644 --- a/ext/Shared/Utilities.lua +++ b/ext/Shared/Utilities.lua @@ -203,22 +203,24 @@ function string:isDigit(p_Value) return tonumber(p_Value) ~= nil end +---@param p_Str string ---@param p_Sep string ---@return table -function string:split(p_Sep) +function string.split(p_Str, p_Sep) local s_Fields = nil p_Sep, s_Fields = p_Sep or ':', {} local s_Pattern = string.format("([^%s]+)", p_Sep) - self:gsub(s_Pattern, function(c) s_Fields[#s_Fields + 1] = c end) + p_Str:gsub(s_Pattern, function(c) s_Fields[#s_Fields + 1] = c end) return s_Fields end +---@param p_Str string ---@param p_Start string ---@return boolean -function string:starts(p_Start) - return string.sub(self, 1, string.len(p_Start)) == p_Start +function string.starts(p_Str, p_Start) + return string.sub(p_Str, 1, string.len(p_Start)) == p_Start end function requireExists(p_Module)