commit 4ee71d6bcb0ef882f67eeb0e049ba4a5a9d90b2c Author: Refosel Date: Sat Mar 28 14:49:46 2026 +0300 Initial commit - RefoselBots diff --git a/README.md b/README.md new file mode 100644 index 0000000..380d027 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# RefoselBots + +Bot system designed specifically for RefoselTeamWork gamemodes. + +## Features + +- Team-based AI bots for Team Deathmatch +- NextBot navigation system +- Quota system (auto-balance bot count) +- VJ Base weapon support + +## Console Commands + +- `refoselbots_add [amount]` - Add bots +- `refoselbots_kick [name/all]` - Kick bots +- `refoselbots_quota [number]` - Auto-balance bots + +## Installation + +1. Copy `refoselbots` folder to `garrysmod/addons` +2. Ensure navmesh is generated on your map (`nav_generate`) +3. Add bots with `refoselbots_add` or set quota + +--- + +## Credits + +### Original Code +**LeadBot** - https://github.com/LeadKiller/leadbot diff --git a/lua/autorun/client/refoselbots.lua b/lua/autorun/client/refoselbots.lua new file mode 100644 index 0000000..7af3223 --- /dev/null +++ b/lua/autorun/client/refoselbots.lua @@ -0,0 +1,15 @@ +--if game.SinglePlayer() or SERVER then return end + +-- Modules + +local _, dir = file.Find("refoselbots/modules/*", "LUA") + +for k, v in pairs(dir) do + local f = table.Add(file.Find("refoselbots/modules/" .. v .. "/cl_*.lua", "LUA"), file.Find("refoselbots/modules/" .. v .. "/sh_*.lua", "LUA")) + + for i, o in pairs(f) do + local file = "refoselbots/modules/" .. v .. "/" .. o + + include(file) + end +end \ No newline at end of file diff --git a/lua/autorun/server/refoselbots.lua b/lua/autorun/server/refoselbots.lua new file mode 100644 index 0000000..304c146 --- /dev/null +++ b/lua/autorun/server/refoselbots.lua @@ -0,0 +1,59 @@ +--if game.SinglePlayer() or CLIENT then return end + +LeadBot = {} +LeadBot.NoNavMesh = {} +LeadBot.Models = {} -- Models, leave as {} if random is desired + +--[[----- + +CONFIG START CONFIG START +CONFIG START CONFIG START +CONFIG START CONFIG START + +--]]----- + +-- Name Prefix + +LeadBot.Prefix = "" + +--[[----- + +CONFIG END CONFIG END +CONFIG END CONFIG END +CONFIG END CONFIG END + +--]]----- + +include("refoselbots/base.lua") + +-- Modules + +local _, dir = file.Find("refoselbots/modules/*", "LUA") + +for k, v in pairs(dir) do + local f = table.Add(file.Find("refoselbots/modules/" .. v .. "/sv_*.lua", "LUA"), file.Find("refoselbots/modules/" .. v .. "/sh_*.lua", "LUA")) + f = table.Add(f, file.Find("refoselbots/modules/" .. v .. "/cl_*.lua", "LUA")) + for i, o in pairs(f) do + local file = "refoselbots/modules/" .. v .. "/" .. o + + if string.StartWith(o, "cl_") then + AddCSLuaFile(file) + else + include(file) + if string.StartWith(o, "sh_") then + AddCSLuaFile(file) + end + end + end +end + +-- Configs + +local map = game.GetMap() +local gamemode = engine.ActiveGamemode() + +if file.Find("refoselbots/gamemodes/" .. map .. ".lua", "LUA")[1] then + include("refoselbots/gamemodes/" .. map .. ".lua") +elseif file.Find("refoselbots/gamemodes/" .. gamemode .. ".lua", "LUA")[1] then + include("refoselbots/gamemodes/" .. gamemode .. ".lua") +end \ No newline at end of file diff --git a/lua/entities/refoselbots_navigator.lua b/lua/entities/refoselbots_navigator.lua new file mode 100644 index 0000000..7800bb0 --- /dev/null +++ b/lua/entities/refoselbots_navigator.lua @@ -0,0 +1,125 @@ +if SERVER then AddCSLuaFile() end + +ENT.Base = "base_nextbot" +ENT.Type = "nextbot" + +function ENT:Initialize() + if CLIENT then return end + + self:SetModel("models/player.mdl") + self:SetNoDraw(!GetConVar("developer"):GetBool()) + self:SetSolid(SOLID_NONE) + + local fov_convar = GetConVar("refoselbots_fov") + + self:SetFOV((fov_convar:GetBool() and math.Clamp(fov_convar:GetInt(), 75, 100)) or 90) + self.PosGen = nil + self.NextJump = -1 + self.NextDuck = 0 + self.cur_segment = 2 + self.Target = nil + self.LastSegmented = 0 + self.ForgetTarget = 0 + self.NextCenter = 0 + self.LookAt = Angle(0, 0, 0) + self.LookAtTime = 0 + self.goalPos = Vector(0, 0, 0) + self.strafeAngle = 0 + self.nextStuckJump = 0 + + if RefoselBots.AddControllerOverride then + RefoselBots.AddControllerOverride(self) + end +end + +function ENT:ChasePos() + self.P = Path("Follow") + self.P:SetMinLookAheadDistance(10) + self.P:SetGoalTolerance(20) + self.P:Compute(self, self.PosGen) + + if !self.P:IsValid() then return end + + while self.P:IsValid() do + if self.PosGen then + self.P:Compute(self, self.PosGen) + self.cur_segment = 2 + end + + coroutine.wait(1) + coroutine.yield() + end +end + +function ENT:OnInjured() + return false +end + +function ENT:OnKilled() + return false +end + +function ENT:IsNPC() + return false +end + +function ENT:Health() + return nil +end + +-- remade this in lua so we can finally ignore the controller's bot +-- for some reason it's not really possible to overwrite IsAbleToSee +local function PointWithinViewAngle(pos, targetpos, lookdir, fov) + pos = targetpos - pos + local diff = lookdir:Dot(pos) + if diff < 0 then return false end + local len = pos:LengthSqr() + return diff * diff > len * fov * fov +end + +function ENT:InFOV(pos, fov) + local owner = self:GetOwner() + + if IsEntity(pos) then + -- we must check eyepos and worldspacecenter + -- maybe in the future add more points + + if PointWithinViewAngle(owner:EyePos(), pos:WorldSpaceCenter(), owner:GetAimVector(), fov) then + return true + end + + return PointWithinViewAngle(owner:EyePos(), pos:EyePos(), owner:GetAimVector(), fov) + else + return PointWithinViewAngle(owner:EyePos(), pos, owner:GetAimVector(), fov) + end +end + +function ENT:CanSee(ply, fov) + if ply:GetPos():DistToSqr(self:GetPos()) > self:GetMaxVisionRange() * self:GetMaxVisionRange() then + return false + end + + -- TODO: check fog farz and compare with distance + + -- half fov or something + -- probably should move this to a variable + fov = fov or true + + if fov and !self:InFOV(ply, math.cos(0.5 * (self:GetFOV() or 90) * math.pi / 180)) then + return false + end + + -- TODO: we really should check worldspacecenter too + local owner = self:GetOwner() + return util.QuickTrace(owner:EyePos(), ply:EyePos() - owner:EyePos(), {owner, self}).Entity == ply +end + +function ENT:RunBehaviour() + while (true) do + if self.PosGen then + self:ChasePos({}) + end + + coroutine.yield() + end +end \ No newline at end of file diff --git a/lua/refoselbots/base.lua b/lua/refoselbots/base.lua new file mode 100644 index 0000000..192d1e3 --- /dev/null +++ b/lua/refoselbots/base.lua @@ -0,0 +1,632 @@ +RefoselBots.RespawnAllowed = true -- allows bots to respawn automatically when dead +RefoselBots.PlayerColor = true -- disable this to get the default gmod style players +RefoselBots.NoNavMesh = false -- disable the nav mesh check +RefoselBots.TeamPlay = false -- don't hurt players on the bots team +RefoselBots.LerpAim = true -- interpolate aim (smooth aim) +RefoselBots.AFKBotOverride = false -- allows for gamemodes such as Dogfight which use IsBot() to pass real humans as bots +RefoselBots.SuicideAFK = false -- kill the player when entering/exiting afk +RefoselBots.NoFlashlight = false -- disable flashlight being enabled in dark areas +RefoselBots.Strategies = 1 -- how many strategies can the bot pick from + +--[[ COMMANDS ]]-- + +concommand.Add("refoselbots_add", function(ply, _, args) if IsValid(ply) and !ply:IsSuperAdmin() then return end local amount = 1 if tonumber(args[1]) then amount = tonumber(args[1]) end for i = 1, amount do timer.Simple(i * 0.1, function() RefoselBots.AddBot() end) end end, nil, "Adds a RefoselBots") +concommand.Add("refoselbots_kick", function(ply, _, args) if !args[1] or IsValid(ply) and !ply:IsSuperAdmin() then return end if args[1] ~= "all" then for k, v in pairs(player.GetBots()) do if string.find(v:GetName(), args[1]) then v:Kick() return end end else for k, v in pairs(player.GetBots()) do v:Kick() end end end, nil, "Kicks RefoselBotss (all is avaliable!)") +CreateConVar("refoselbots_strategy", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "Enables the strategy system for newly created bots.") +CreateConVar("refoselbots_names", "", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "Bot names, seperated by commas.") +CreateConVar("refoselbots_models", "", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "Bot models, seperated by commas.") +CreateConVar("refoselbots_name_prefix", "", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "Bot name prefix") +CreateConVar("refoselbots_fov", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY}, "RefoselBots FOV\nSet to 0 to use the preset FOV.") + +--[[ FUNCTIONS ]]-- + +local name_Default = { + alyx = "Alyx Vance", + kleiner = "Isaac Kleiner", + breen = "Dr. Wallace Breen", + gman = "The G-Man", + odessa = "Odessa Cubbage", + eli = "Eli Vance", + monk = "Father Grigori", + mossman = "Judith Mossman", + mossmanarctic = "Judith Mossman", + barney = "Barney Calhoun", + + + dod_american = "American Soldier", + dod_german = "German Soldier", + + css_swat = "GIGN", + css_leet = "Elite Crew", + css_arctic = "Artic Avengers", + css_urban = "SEAL Team Six", + css_riot = "GSG-9", + css_gasmask = "SAS", + css_phoenix = "Phoenix Connexion", + css_guerilla = "Guerilla Warfare", + + hostage01 = "Art", + hostage02 = "Sandro", + hostage03 = "Vance", + hostage04 = "Cohrt", + + police = "Civil Protection", + policefem = "Civil Protection", + + chell = "Chell", + + combine = "Combine Soldier", + combineprison = "Combine Prison Guard", + combineelite = "Elite Combine Soldier", + stripped = "Stripped Combine Soldier", + + zombie = "Zombie", + zombiefast = "Fast Zombie", + zombine = "Zombine", + corpse = "Corpse", + charple = "Charple", + skeleton = "Skeleton", + + male01 = "Van", + male02 = "Ted", + male03 = "Joe", + male04 = "Eric", + male05 = "Art", + male06 = "Sandro", + male07 = "Mike", + male08 = "Vance", + male09 = "Erdin", + male10 = "Van", + male11 = "Ted", + male12 = "Joe", + male13 = "Eric", + male14 = "Art", + male15 = "Sandro", + male16 = "Mike", + male17 = "Vance", + male18 = "Erdin", + female01 = "Joey", + female02 = "Kanisha", + female03 = "Kim", + female04 = "Chau", + female05 = "Naomi", + female06 = "Lakeetra", + female07 = "Joey", + female08 = "Kanisha", + female09 = "Kim", + female10 = "Chau", + female11 = "Naomi", + female12 = "Lakeetra", + + medic01 = "Van", + medic02 = "Ted", + medic03 = "Joe", + medic04 = "Eric", + medic05 = "Art", + medic06 = "Sandro", + medic07 = "Mike", + medic08 = "Vance", + medic09 = "Erdin", + medic10 = "Joey", + medic11 = "Kanisha", + medic12 = "Kim", + medic13 = "Chau", + medic14 = "Naomi", + medic15 = "Lakeetra", + + refugee01 = "Ted", + refugee02 = "Eric", + refugee03 = "Sandro", + refugee04 = "Vance", +} + +function RefoselBots.AddBot() + if !FindMetaTable("NextBot").GetFOV then + ErrorNoHalt("You must be using the dev version of Garry's mod!\nhttps://wiki.facepunch.com/gmod/Dev_Branch\n") + return + end + + if !navmesh.IsLoaded() and !RefoselBots.NoNavMesh then + ErrorNoHalt("There is no navmesh! Generate one using \"nav_generate\"!\n") + return + end + + if player.GetCount() == game.MaxPlayers() then + MsgN("[RefoselBots] Player limit reached!") + return + end + + local original_name + local generated = "Leadbot #" .. #player.GetBots() + 1 + local model = "" + local color = Vector(-1, -1, -1) + local weaponcolor = Vector(0.30, 1.80, 2.10) + local strategy = 0 + + if GetConVar("refoselbots_names"):GetString() ~= "" then + generated = table.Random(string.Split(GetConVar("refoselbots_names"):GetString(), ",")) + elseif GetConVar("refoselbots_models"):GetString() == "" then + local name, _ = table.Random(player_manager.AllValidModels()) + local translate = player_manager.TranslateToPlayerModelName(name) + name = translate + + for _, ply in pairs(player.GetBots()) do + if ply.OriginalName == name or string.lower(ply:Nick()) == name or name_Default[name] and ply:Nick() == name_Default[name] then + name = "" + end + end + + if name == "" then + local i = 0 + while name == "" do + i = i + 1 + local str = player_manager.TranslateToPlayerModelName(table.Random(player_manager.AllValidModels())) + for _, ply in pairs(player.GetBots()) do + if ply.OriginalName == str or string.lower(ply:Nick()) == str or name_Default[str] and ply:Nick() == name_Default[str] then + str = "" + end + end + + if str == "" and i < #player_manager.AllValidModels() then continue end + name = str + end + end + + original_name = name + model = name + name = string.lower(name) + name = name_Default[name] or name + + local name_Generated = string.Split(name, "/") + name_Generated = name_Generated[#name_Generated] + name_Generated = string.Split(name_Generated, " ") + + for i, namestr in pairs(name_Generated) do + name_Generated[i] = string.upper(string.sub(namestr, 1, 1)) .. string.sub(namestr, 2) + end + + name_Generated = table.concat(name_Generated, " ") + generated = name_Generated + end + + if RefoselBots.PlayerColor == "default" then + generated = "Kleiner" + end + + generated = GetConVar("refoselbots_name_prefix"):GetString() .. generated + + local name = RefoselBots.Prefix .. generated + local bot = player.CreateNextBot(name) + + if !IsValid(bot) then + MsgN("[RefoselBots] Unable to create bot!") + return + end + + if GetConVar("refoselbots_strategy"):GetBool() then + strategy = math.random(0, RefoselBots.Strategies) + end + + if RefoselBots.PlayerColor ~= "default" then + if model == "" then + if GetConVar("refoselbots_models"):GetString() ~= "" then + model = table.Random(string.Split(GetConVar("refoselbots_models"):GetString(), ",")) + else + model = player_manager.TranslateToPlayerModelName(table.Random(player_manager.AllValidModels())) + end + end + + if color == Vector(-1, -1, -1) then + local botcolor = ColorRand() + local botweaponcolor = ColorRand() + color = Vector(botcolor.r / 255, botcolor.g / 255, botcolor.b / 255) + weaponcolor = Vector(botweaponcolor.r / 255, botweaponcolor.g / 255, botweaponcolor.b / 255) + end + else + model = "kleiner" + color = Vector(0.24, 0.34, 0.41) + end + + bot.RefoselBots_Config = {model, color, weaponcolor, strategy} + + -- for legacy purposes, will be removed soon when gamemodes are updated + bot.BotStrategy = strategy + bot.OriginalName = original_name + bot.ControllerBot = ents.Create("refoselbots_navigator") + bot.ControllerBot:Spawn() + bot.ControllerBot:SetOwner(bot) + bot.RefoselBots = true + RefoselBots.AddBotOverride(bot) + RefoselBots.AddBotControllerOverride(bot, bot.ControllerBot) + MsgN("[RefoselBots] Bot " .. name .. " with strategy " .. bot.BotStrategy .. " added!") +end + +--[[ DEFAULT DM AI ]]-- + +function RefoselBots.AddBotOverride(bot) + if math.random(2) == 1 then + timer.Simple(math.random(1, 4), function() + RefoselBots.TalkToMe(bot, "join") + end) + end +end + +function RefoselBots.AddBotControllerOverride(bot, controller) +end + +function RefoselBots.PlayerSpawn(bot) +end + +function RefoselBots.Think() + for _, bot in pairs(player.GetBots()) do + if bot:IsLBot() then + if RefoselBots.RespawnAllowed and bot.NextSpawnTime and !bot:Alive() and bot.NextSpawnTime < CurTime() then + bot:Spawn() + return + end + + local wep = bot:GetActiveWeapon() + if IsValid(wep) then + local ammoty = wep:GetPrimaryAmmoType() or wep.Primary.Ammo + bot:SetAmmo(999, ammoty) + end + end + end +end + +function RefoselBots.PostPlayerDeath(bot) +end + +function RefoselBots.PlayerHurt(ply, bot, hp, dmg) + if bot:IsPlayer() then + if hp <= dmg and math.random(3) == 1 and bot:IsLBot() then + RefoselBots.TalkToMe(bot, "taunt") + end + + local controller = ply:GetController() + + controller.LookAtTime = CurTime() + 2 + controller.LookAt = ((bot:GetPos() + VectorRand() * 128) - ply:GetPos()):Angle() + end +end + +function RefoselBots.StartCommand(bot, cmd) + local buttons = IN_SPEED + local botWeapon = bot:GetActiveWeapon() + local controller = bot.ControllerBot + local target = controller.Target + + if !IsValid(controller) then return end + + if RefoselBots.NoSprint then + buttons = 0 + end + + if IsValid(botWeapon) and (botWeapon:Clip1() == 0 or !IsValid(target) and botWeapon:Clip1() <= botWeapon:GetMaxClip1() / 2) then + buttons = buttons + IN_RELOAD + end + + if IsValid(target) and math.random(2) == 1 then + buttons = buttons + IN_ATTACK + end + + if bot:GetMoveType() == MOVETYPE_LADDER then + local pos = controller.goalPos + local ang = ((pos + bot:GetCurrentViewOffset()) - bot:GetShootPos()):Angle() + + if pos.z > controller:GetPos().z then + controller.LookAt = Angle(-30, ang.y, 0) + else + controller.LookAt = Angle(30, ang.y, 0) + end + + controller.LookAtTime = CurTime() + 0.1 + controller.NextJump = -1 + buttons = buttons + IN_FORWARD + end + + if controller.NextDuck > CurTime() then + buttons = buttons + IN_DUCK + elseif controller.NextJump == 0 then + controller.NextJump = CurTime() + 1 + buttons = buttons + IN_JUMP + end + + if !bot:IsOnGround() and controller.NextJump > CurTime() then + buttons = buttons + IN_DUCK + end + + bot:SelectWeapon((IsValid(controller.Target) and controller.Target:GetPos():DistToSqr(controller:GetPos()) < 129000 and "weapon_shotgun") or "weapon_smg1") + cmd:ClearButtons() + cmd:ClearMovement() + cmd:SetButtons(buttons) +end + +function RefoselBots.PlayerMove(bot, cmd, mv) + local controller = bot.ControllerBot + + if !IsValid(controller) then + bot.ControllerBot = ents.Create("refoselbots_navigator") + bot.ControllerBot:Spawn() + bot.ControllerBot:SetOwner(bot) + controller = bot.ControllerBot + end + + --[[local min, max = controller:GetModelBounds() + debugoverlay.Box(controller:GetPos(), min, max, 0.1, Color(255, 0, 0, 0), true)]] + + -- force a recompute + if controller.PosGen and controller.P and controller.TPos ~= controller.PosGen then + controller.TPos = controller.PosGen + controller.P:Compute(controller, controller.PosGen) + end + + if controller:GetPos() ~= bot:GetPos() then + controller:SetPos(bot:GetPos()) + end + + if controller:GetAngles() ~= bot:EyeAngles() then + controller:SetAngles(bot:EyeAngles()) + end + + mv:SetForwardSpeed(1200) + + if (bot.NextSpawnTime and bot.NextSpawnTime + 1 > CurTime()) or !IsValid(controller.Target) or controller.ForgetTarget < CurTime() or controller.Target:Health() < 1 then + controller.Target = nil + end + + if !IsValid(controller.Target) then + for _, ply in ipairs(player.GetAll()) do + if ply ~= bot and ((ply:IsPlayer() and (!RefoselBots.TeamPlay or (RefoselBots.TeamPlay and (ply:Team() ~= bot:Team())))) or ply:IsNPC()) and ply:GetPos():DistToSqr(bot:GetPos()) < 2250000 then + --[[local targetpos = ply:EyePos() - Vector(0, 0, 10) + local trace = util.TraceLine({ + start = bot:GetShootPos(), + endpos = targetpos, + filter = function(ent) return ent == ply end + })]] + + if ply:Alive() and controller:CanSee(ply) then + controller.Target = ply + controller.ForgetTarget = CurTime() + 2 + end + end + end + elseif controller.ForgetTarget < CurTime() and controller:CanSee(controller.Target) then + controller.ForgetTarget = CurTime() + 2 + end + + local dt = util.QuickTrace(bot:EyePos(), bot:GetForward() * 45, bot) + + if IsValid(dt.Entity) and dt.Entity:GetClass() == "prop_door_rotating" then + dt.Entity:Fire("OpenAwayFrom", bot, 0) + end + + if !IsValid(controller.Target) and (!controller.PosGen or bot:GetPos():DistToSqr(controller.PosGen) < 1000 or controller.LastSegmented < CurTime()) then + -- find a random spot on the map, and in 10 seconds do it again! + controller.PosGen = controller:FindSpot("random", {radius = 12500}) + controller.LastSegmented = CurTime() + 10 + elseif IsValid(controller.Target) then + -- move to our target + local distance = controller.Target:GetPos():DistToSqr(bot:GetPos()) + controller.PosGen = controller.Target:GetPos() + + -- back up if the target is really close + -- TODO: find a random spot rather than trying to back up into what could just be a wall + -- something like controller.PosGen = controller:FindSpot("random", {pos = bot:GetPos() - bot:GetForward() * 350, radius = 1000})? + if distance <= 90000 then + mv:SetForwardSpeed(-1200) + end + end + + -- movement also has a similar issue, but it's more severe... + if !controller.P then + return + end + + local segments = controller.P:GetAllSegments() + + if !segments then return end + + local cur_segment = controller.cur_segment + local curgoal = segments[cur_segment] + + -- got nowhere to go, why keep moving? + if !curgoal then + mv:SetForwardSpeed(0) + return + end + + -- think every step of the way! + if segments[cur_segment + 1] and Vector(bot:GetPos().x, bot:GetPos().y, 0):DistToSqr(Vector(curgoal.pos.x, curgoal.pos.y)) < 100 then + controller.cur_segment = controller.cur_segment + 1 + curgoal = segments[controller.cur_segment] + end + + local goalpos = curgoal.pos + + -- waaay too slow during gamplay + --[[if bot:GetVelocity():Length2DSqr() <= 225 and controller.NextCenter == 0 and controller.NextCenter < CurTime() then + controller.NextCenter = CurTime() + math.Rand(0.5, 0.65) + end + + if controller.NextCenter ~= 0 and controller.NextCenter < CurTime() then + if bot:GetVelocity():Length2DSqr() <= 225 then + controller.strafeAngle = ((controller.strafeAngle == 1 and 2) or 1) + end + + controller.NextCenter = 0 + end]] + + if bot:GetVelocity():Length2DSqr() <= 225 then + if controller.NextCenter < CurTime() then + controller.strafeAngle = ((controller.strafeAngle == 1 and 2) or 1) + controller.NextCenter = CurTime() + math.Rand(0.3, 0.65) + elseif controller.nextStuckJump < CurTime() then + if !bot:Crouching() then + controller.NextJump = 0 + end + controller.nextStuckJump = CurTime() + math.Rand(1, 2) + end + end + + if controller.NextCenter > CurTime() then + if controller.strafeAngle == 1 then + mv:SetSideSpeed(1500) + elseif controller.strafeAngle == 2 then + mv:SetSideSpeed(-1500) + else + mv:SetForwardSpeed(-1500) + end + end + + -- jump + if controller.NextJump ~= 0 and curgoal.type > 1 and controller.NextJump < CurTime() then + controller.NextJump = 0 + end + + -- duck + if curgoal.area:GetAttributes() == NAV_MESH_CROUCH then + controller.NextDuck = CurTime() + 0.1 + end + + controller.goalPos = goalpos + + if GetConVar("developer"):GetBool() then + controller.P:Draw() + end + + -- eyesight + local lerp = FrameTime() * math.random(8, 10) + local lerpc = FrameTime() * 8 + + if !RefoselBots.LerpAim then + lerp = 1 + lerpc = 1 + end + + local mva = ((goalpos + bot:GetCurrentViewOffset()) - bot:GetShootPos()):Angle() + + mv:SetMoveAngles(mva) + + if IsValid(controller.Target) then + bot:SetEyeAngles(LerpAngle(lerp, bot:EyeAngles(), (controller.Target:EyePos() - bot:GetShootPos()):Angle())) + return + else + if controller.LookAtTime > CurTime() then + local ang = LerpAngle(lerpc, bot:EyeAngles(), controller.LookAt) + bot:SetEyeAngles(Angle(ang.p, ang.y, 0)) + else + local ang = LerpAngle(lerpc, bot:EyeAngles(), mva) + bot:SetEyeAngles(Angle(ang.p, ang.y, 0)) + end + end +end + +--[[ HOOKS ]]-- + +hook.Add("PlayerDisconnected", "RefoselBots_Disconnect", function(bot) + if IsValid(bot.ControllerBot) then + bot.ControllerBot:Remove() + end +end) + +hook.Add("SetupMove", "RefoselBots_Control", function(bot, mv, cmd) + if bot:IsLBot() then + RefoselBots.PlayerMove(bot, cmd, mv) + end +end) + +hook.Add("StartCommand", "RefoselBots_Control", function(bot, cmd) + if bot:IsLBot() then + RefoselBots.StartCommand(bot, cmd) + end +end) + +hook.Add("PostPlayerDeath", "RefoselBots_Death", function(bot) + if bot:IsLBot() then + RefoselBots.PostPlayerDeath(bot) + end +end) + +hook.Add("EntityTakeDamage", "RefoselBots_Hurt", function(ply, dmgi) + local bot = dmgi:GetAttacker() + local hp = ply:Health() + local dmg = dmgi:GetDamage() + + if IsValid(ply) and ply:IsPlayer() and ply:IsLBot() then + RefoselBots.PlayerHurt(ply, bot, hp, dmg) + end +end) + +hook.Add("Think", "RefoselBots_Think", function() + RefoselBots.Think() +end) + +hook.Add("PlayerSpawn", "RefoselBots_Spawn", function(bot) + if bot:IsLBot() then + RefoselBots.PlayerSpawn(bot) + end +end) + +--[[ META ]]-- + +local player_meta = FindMetaTable("Player") +local oldInfo = player_meta.GetInfo + +function player_meta.IsLBot(self, realbotsonly) + if realbotsonly == true then + return self.RefoselBots and self:IsBot() or false + end + + return self.RefoselBots or false +end + +function player_meta.LBGetStrategy(self) + if self.RefoselBots_Config then + return self.RefoselBots_Config[4] + else + return 0 + end +end + +function player_meta.LBGetModel(self) + if self.RefoselBots_Config then + return self.RefoselBots_Config[1] + else + return "kleiner" + end +end + +function player_meta.LBGetColor(self, weapon) + if self.RefoselBots_Config then + if weapon == true then + return self.RefoselBots_Config[3] + else + return self.RefoselBots_Config[2] + end + else + return Vector(0, 0, 0) + end +end + +function player_meta.GetInfo(self, convar) + if self:IsBot() and self:IsLBot() then + if convar == "cl_playermodel" then + return self:LBGetModel() --self.RefoselBots_Config[1] + elseif convar == "cl_playercolor" then + return self:LBGetColor() --self.RefoselBots_Config[2] + elseif convar == "cl_weaponcolor" then + return self:LBGetColor(true) --self.RefoselBots_Config[3] + else + return "" + end + else + return oldInfo(self, convar) + end +end + +function player_meta.GetController(self) + if self:IsLBot() then + return self.ControllerBot + end +end \ No newline at end of file diff --git a/lua/refoselbots/gamemodes/cod_custom.lua b/lua/refoselbots/gamemodes/cod_custom.lua new file mode 100644 index 0000000..9cd4f04 --- /dev/null +++ b/lua/refoselbots/gamemodes/cod_custom.lua @@ -0,0 +1,6 @@ +LeadBot.RespawnAllowed = true +LeadBot.SetModel = false +LeadBot.Gamemode = "cod_custom" +LeadBot.TeamPlay = true +LeadBot.LerpAim = true +LeadBot.NoFlashlight = true diff --git a/lua/refoselbots/modules/afk/cl_afk.lua b/lua/refoselbots/modules/afk/cl_afk.lua new file mode 100644 index 0000000..e81b110 --- /dev/null +++ b/lua/refoselbots/modules/afk/cl_afk.lua @@ -0,0 +1,139 @@ +surface.CreateFont("LeadBot_AFK", { + font = "Roboto", + size = 34, + weight = 500 +}) + +surface.CreateFont("LeadBot_AFK2", { + font = "Roboto", + size = 30, + weight = 500 +}) + +hook.Add("HUDPaint", "AFKT", function() + if LocalPlayer():GetNWBool("LeadBot_AFK") then + draw.SimpleTextOutlined("You are AFK.", "LeadBot_AFK", ScrW() / 2, ScrH() / 2.85, Color(255, 255, 255, 235), TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM, 1, Color(0, 0, 0, 255)) + draw.SimpleTextOutlined("Press any key to rejoin.", "LeadBot_AFK2", ScrW() / 2, ScrH() / 2.675, Color(255, 255, 255, 235), TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM, 1, Color(0, 0, 0, 255)) + end +end) + +local tp = false +local last_TP = false +local newangle = Angle(0, 0, 0) +local scroll = 0 +local lastsend = CurTime() +local tp_Gamemodes = {} +local NoAFKCamera = {} +tp_Gamemodes["sandbox"] = true +tp_Gamemodes["darkestdays"] = true +NoAFKCamera["assassins"] = true +NoAFKCamera["cavefight"] = true + +hook.Add("CreateMove", "LeadBot_AFK", function(cmd) + local ply = LocalPlayer() + + if ply:GetNWBool("LeadBot_AFK") then + if lastsend < CurTime() and cmd:GetButtons() ~= 0 and !cmd:KeyDown(IN_ATTACK) and !cmd:KeyDown(IN_WALK) and !cmd:KeyDown(IN_SCORE) then + net.Start("LeadBot_AFK_Off") + net.SendToServer() + lastsend = CurTime() + 0.1 + end + + if cmd:KeyDown(IN_ATTACK) and tp_Gamemodes[engine.ActiveGamemode()] then + if !last_TP then + tp = !tp + last_TP = true + end + else + last_TP = false + end + + if input.WasMousePressed(MOUSE_WHEEL_DOWN) then + scroll = math.Clamp(scroll + 4, -35, 45) + elseif input.WasMousePressed(MOUSE_WHEEL_UP) then + scroll = math.Clamp(scroll - 4, -35, 45) + end + + if tp then + local s = GetConVar("m_pitch"):GetFloat() + newangle.pitch = math.Clamp(newangle.pitch + cmd:GetMouseY() * s, -90, 90) + newangle.yaw = newangle.yaw - cmd:GetMouseX() * s + end + + cmd:ClearButtons() + cmd:ClearMovement() + cmd:SetImpulse(0) + + cmd:SetMouseX(0) + cmd:SetMouseY(0) + end +end) + +hook.Add("InputMouseApply", "LeadBot_AFK", function(cmd) + if LocalPlayer():GetNWBool("LeadBot_AFK") then + cmd:SetMouseX(0) + cmd:SetMouseY(0) + return true + end +end) + +hook.Add("HUDShouldDraw", "LeadBot_AFK", function(hud) + if hud == "CHudWeaponSelection" and LocalPlayer():GetNWBool("LeadBot_AFK") then + return false + end +end) + +local ang +local lerp = 0 + +hook.Add("CalcView", "LeadBot_AFK", function(ply, origin, angles) + if !ang then ang = angles end + + if ply:ShouldDrawLocalPlayer() or !ply:GetNWBool("LeadBot_AFK") or NoAFKCamera[engine.ActiveGamemode()] then return end + + local view = {} + + if tp or lerp ~= 0 then + if tp then + lerp = math.Clamp(lerp + FrameTime() * 5, 0, 1) + else + lerp = math.Clamp(lerp - FrameTime() * 5, 0, 1) + end + + local pos = ply:EyePos() + local trace = util.TraceHull({ + start = pos + newangle:Forward() * Lerp(lerp, 0, -5), + endpos = pos + newangle:Forward() * Lerp(lerp, 0, -75 - scroll), + filter = ply, + mins = Vector(-8, -8, -8), + maxs = Vector(8, 8, 8), + }) + + view.angles = newangle + view.origin = trace.HitPos + view.drawviewer = true + else + ang = LerpAngle(FrameTime() * 16, ang, angles) + ang = Angle(ang.p, ang.y, 0) + newangle = ang + view.angles = ang + end + + return view +end) + +hook.Add("CalcViewModelView", "LeadBot_AFK", function(wep, vm, oldpos, oldang, newpos, newang) + local ply = LocalPlayer() + + if ply:ShouldDrawLocalPlayer() or !ply:GetNWBool("LeadBot_AFK") or NoAFKCamera[engine.ActiveGamemode()] then return end + + if wep.GetViewModelPosition then + newpos, newang = wep:GetViewModelPosition(newpos, newang) + end + + if wep.CalcViewModelView then + newpos, newang = wep:CalcViewModelView(vm, oldpos, oldang, newpos, newang) + end + + return newpos, ang +end) \ No newline at end of file diff --git a/lua/refoselbots/modules/afk/sv_afk.lua b/lua/refoselbots/modules/afk/sv_afk.lua new file mode 100644 index 0000000..ea35cfb --- /dev/null +++ b/lua/refoselbots/modules/afk/sv_afk.lua @@ -0,0 +1,73 @@ +concommand.Add("refoselbots_afk", function(ply, _, args) RefoselBots.Botize(ply) end, nil, "Adds a RefoselBots ;)") + +local time = CreateConVar("refoselbots_afk_timetoafk", "300", {FCVAR_ARCHIVE}) +local meta = FindMetaTable("Player") +local oldFunc = meta.IsBot + +util.AddNetworkString("RefoselBots_AFK_Off") + +net.Receive("RefoselBots_AFK_Off", function(_, ply) + RefoselBots.Botize(ply, false) + ply.LastAFKCheck = CurTime() + time:GetFloat() +end) + +hook.Add("PlayerTick", "RefoselBots_AFK", function(ply) + if !time:GetBool() then return end + + ply.LastAFKCheck = ply.LastAFKCheck or CurTime() + time:GetFloat() + + if ply:KeyDown(IN_FORWARD) or ply:KeyDown(IN_BACK) or ply:KeyDown(IN_MOVELEFT) or ply:KeyDown(IN_MOVERIGHT) or ply:KeyDown(IN_ATTACK) then + ply.LastAFKCheck = CurTime() + time:GetFloat() + end + + if ply.LastAFKCheck < CurTime() and !ply:IsBot() and !ply:GetNWBool("RefoselBots_AFK") then + RefoselBots.Botize(ply, true) + end +end) + +function RefoselBots.Botize(ply, togg) + if togg == nil then togg = !ply.RefoselBots end + + if ((!togg and ply:IsLBot()) or (togg and !ply:IsLBot())) and RefoselBots.SuicideAFK and ply:Alive() then + ply:Kill() + end + + if !togg then + ply:SetNWBool("RefoselBots_AFK", false) + ply.RefoselBots = false + if IsValid(ply.ControllerBot) then + ply.ControllerBot:Remove() + end + ply.LastSegmented = CurTime() + ply.CurSegment = 2 + else + ply:SetNWBool("RefoselBots_AFK", true) + ply.RefoselBots = true + ply.BotColor = ply:GetPlayerColor() + ply.BotSkin = ply:GetSkin() + ply.BotModel = ply:GetModel() + ply.BotWColor = ply:GetWeaponColor() + ply.ControllerBot = ents.Create("refoselbots_navigator") + ply.ControllerBot:Spawn() + ply.ControllerBot:SetOwner(ply) + ply.ControllerBot:SetFOV(ply:GetFOV()) + ply.LastSegmented = CurTime() + ply.CurSegment = 2 + if GetConVar("refoselbots_strategy"):GetBool() then + ply.BotStrategy = math.random(0, RefoselBots.Strategies) + end + ply.RefoselBots_Config = {} + ply.RefoselBots_Config[1] = ply.BotModel + ply.RefoselBots_Config[2] = ply.BotColor + ply.RefoselBots_Config[3] = ply.BotWColor + ply.RefoselBots_Config[4] = ply.BotStrategy + end +end + +function meta:IsBot() + if self:IsLBot() and RefoselBots.AFKBotOverride then + return true + else + return oldFunc(self) + end +end \ No newline at end of file diff --git a/lua/refoselbots/modules/avatar/cl_avatar.lua b/lua/refoselbots/modules/avatar/cl_avatar.lua new file mode 100644 index 0000000..f4bd767 --- /dev/null +++ b/lua/refoselbots/modules/avatar/cl_avatar.lua @@ -0,0 +1,111 @@ +-- TODO: store avatar in dhtml png images or something, alpha fade out is weird with dmodelpanel + +local meta = FindMetaTable("Panel") +local oldfunc = meta.SetPlayer +local disabledGamemodes = {} +disabledGamemodes["assassins"] = true + +function meta:SetPlayer(ply, size) + if !IsValid(ply) or !ply:IsPlayer() then return end + if disabledGamemodes[engine.ActiveGamemode()] then return oldfunc(self, ply, size) end + + if ply:IsBot() and ply:GetNWString("LeadBot_AvatarModel", "none") ~= "none" then + function self:Paint(w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(255, 255, 255)) + end + + local background = vgui.Create("DPanel", self) + background:Dock(FILL) + -- background.Texture = Material("entities/monster_scientist.png") + + function background:Paint(w, h) + if !ispanel(self:GetParent()) then + self:Remove() + return + end + + draw.RoundedBox(0, 0, 0, w, h, Color(255, 255, 255)) + + --[[surface.SetMaterial(self.Texture) + surface.SetDrawColor(255, 255, 255) + surface.DrawTexturedRect(0, 0, w, h)]] + end + + local model = vgui.Create("DModelPanel", self) + model:SetModel("models/player.mdl") + model:Dock(FILL) + model.Player = ply + + if !ply.AvatarSeq then + ply.AvatarSeq = table.Random({"death_01", "death_02", "death_03", "death_04"}) + ply.AvatarCycle = math.Rand(0.025, (ply.AvatarSeq == "death_04" and 0.06) or (ply.AvatarSeq == "death_01" and 0.3) or 0.1) + end + + local playermodel = ply:GetNWString("LeadBot_AvatarModel", ply:GetModel()) + + function model:LayoutEntity(ent) + if !ispanel(self:GetParent()) then + self:Remove() + return + end + + if !IsValid(self.Player) or !IsValid(ent) or !self.Player:IsPlayer() then return end + + self.ModelCache = self.ModelCache or "" + + if !ent.GetPlayerColor then + ent.Player = self.Player + function ent:GetPlayerColor() + if !IsValid(self.Player) then return Vector(1, 1, 1) end + return self.Player:GetNWVector("LeadBot_AvatarColor", self.Player:GetPlayerColor()) + end + end + + ent:SetRenderMode(RENDERMODE_TRANSALPHA) + ent:SetLOD(0) + + -- voicechat fix + local alpha = 255 + if ispanel(self:GetParent()) and ispanel(self:GetParent():GetParent()) then + alpha = self:GetParent():GetParent():GetAlpha() + end + + self:SetColor(Color(255, 255, 255, alpha)) + + if self.ModelCache ~= playermodel then + self:SetModel(playermodel) + + ent = self:GetEntity() + + local seq_name = self.Player.AvatarSeq + local seq = ent:LookupSequence(seq_name or 0) -- "menu_walk") + if seq < 0 then + seq = ent:LookupSequence("menu_walk") -- "reload_dual_original") + end + + ent:SetSequence(seq) -- table.Random({"swimming_duel", "zombie_slump_idle_02"}))) + ent:SetCycle(self.Player.AvatarCycle) + + local att = ent:LookupAttachment("eyes") + + if att > 0 then + att = ent:GetAttachment(att) + if seq_name ~= "death_05" then + self:SetFOV(23) + self:SetCamPos(att.Pos + att.Ang:Forward() * 36 + att.Ang:Up() * 2) + self:SetLookAt(att.Pos - Vector(0, 0, 1)) + else + self:SetFOV(27) + self:SetCamPos(att.Pos + att.Ang:Forward() * 36) + self:SetLookAt(att.Pos - Vector(0, 0, 3)) + end + end + + + self.ModelCache = self.Player:GetModel() + end + end + else + oldfunc(self, ply, size) + end +end \ No newline at end of file diff --git a/lua/refoselbots/modules/avatar/sv_avatar.lua b/lua/refoselbots/modules/avatar/sv_avatar.lua new file mode 100644 index 0000000..531041c --- /dev/null +++ b/lua/refoselbots/modules/avatar/sv_avatar.lua @@ -0,0 +1,12 @@ +local oldAddBot = LeadBot.AddBotOverride + +function LeadBot.AddBotOverride(bot) + oldAddBot(bot) + + timer.Simple(0, function() + if IsValid(bot) then + bot:SetNWString("LeadBot_AvatarModel", player_manager.TranslatePlayerModel(bot:LBGetModel())) + bot:SetNWVector("LeadBot_AvatarColor", bot:LBGetColor()) + end + end) +end \ No newline at end of file diff --git a/lua/refoselbots/modules/flashlight/cl_flashlight.lua b/lua/refoselbots/modules/flashlight/cl_flashlight.lua new file mode 100644 index 0000000..b4ce8e8 --- /dev/null +++ b/lua/refoselbots/modules/flashlight/cl_flashlight.lua @@ -0,0 +1,10 @@ +net.Receive("refoselbots_Flashlight", function() + local flashlights = {} + for _, ply in pairs(player.GetAll()) do + flashlights[ply] = render.GetLightColor(ply:EyePos()) + end + + net.Start("refoselbots_Flashlight") + net.WriteTable(flashlights) + net.SendToServer() +end) \ No newline at end of file diff --git a/lua/refoselbots/modules/flashlight/sv_flashlight.lua b/lua/refoselbots/modules/flashlight/sv_flashlight.lua new file mode 100644 index 0000000..8af62a2 --- /dev/null +++ b/lua/refoselbots/modules/flashlight/sv_flashlight.lua @@ -0,0 +1,52 @@ +util.AddNetworkString("refoselbots_Flashlight") + +local convar = CreateConVar("refoselbots_flashlight", "1", {FCVAR_ARCHIVE}, "Flashlight for bots") +local updateply +local updatetime +local updatetime_2 = 0 + +hook.Add("Think", "refoselbots_Flashlight", function() + if !convar:GetBool() or RefoselBots.NoFlashlight then return end + + local tab = player.GetHumans() + if updatetime_2 < CurTime() and #tab > 0 then + local ply = table.Random(tab) + + updateply = ply + updatetime = CurTime() + 0.5 + + net.Start("refoselbots_Flashlight") + net.Send(ply) + + updatetime_2 = CurTime() + 0.5 + end +end) + +net.Receive("refoselbots_Flashlight", function(_, ply) + if ply ~= updateply then return end + if updatetime < CurTime() then return end + + local tab = net.ReadTable() + if !istable(tab) then return end + + for bot, light in pairs(tab) do + bot.LastLight2 = bot.LastLight2 or 0 + light = Vector(math.Round(light.x, 2), math.Round(light.y, 2), math.Round(light.z, 2)) + + local lighton = light == Vector(0, 0, 0) + + if lighton then + bot.LastLight2 = math.Clamp(bot.LastLight2 + 1, 0, 3) + else + bot.LastLight2 = 0 + end + + bot.FlashlightOn = lighton and bot.LastLight2 == 3 + end +end) + +hook.Add("StartCommand", "refoselbots_Flashlight", function(ply, cmd) + if ply:IsLBot() and updatetime_2 - 0.1 < CurTime() and (ply.FlashlightOn and !ply:FlashlightIsOn() or !ply.FlashlightOn and ply:FlashlightIsOn()) then + cmd:SetImpulse(100) + end +end) \ No newline at end of file diff --git a/lua/refoselbots/modules/ping/sh_ping.lua b/lua/refoselbots/modules/ping/sh_ping.lua new file mode 100644 index 0000000..30cea5c --- /dev/null +++ b/lua/refoselbots/modules/ping/sh_ping.lua @@ -0,0 +1,30 @@ +local meta = FindMetaTable("Player") +local oldFunc = meta.Ping +local ping = 0 +local fakeping = true +local convar = CreateConVar("refoselbots_fakeping", "0", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, "Enables fakeping (Do not use this in public servers!)\n2 to make it say BOT") + +function meta:Ping() + if convar:GetBool() and self:IsBot() then + if fakeping then + self.FakePing = self.FakePing or math.random(35, 105) + self.OFakePing = self.OFakePing or self.FakePing + + if math.random(125) == 1 then + self.FakePing = self.OFakePing + math.random(3) + elseif math.random(125) == 1 then + self.FakePing = self.OFakePing - math.random(3) + end + + if convar:GetInt() == 2 then + self.FakePing = "BOT" + end + + return self.FakePing + end + + return ping + else + return oldFunc(self) + end +end \ No newline at end of file diff --git a/lua/refoselbots/modules/quota/sv_quota.lua b/lua/refoselbots/modules/quota/sv_quota.lua new file mode 100644 index 0000000..6e6b2cf --- /dev/null +++ b/lua/refoselbots/modules/quota/sv_quota.lua @@ -0,0 +1,46 @@ +local convar1 = CreateConVar("refoselbots_quota", "0", {FCVAR_ARCHIVE}, "TF2 Style Quota for bots\nUse refoselbots_add if you want unkickable bots") +local nextCheck = 0 + +cvars.AddChangeCallback("refoselbots_quota", function(_, oldval, val) + oldval = tonumber(oldval) + val = tonumber(val) + + if oldval and val and oldval > 0 and val < 1 then + RunConsoleCommand("refoselbots_kick", "all") + end +end) + +hook.Add("Think", "RefoselBots_Quota", function() + if !convar1:GetBool() or RefoselBots.AFKBotOverride then return end + + if nextCheck < CurTime() then + local bots = {} + local max = convar1:GetInt() - #player.GetHumans() + + for _, ply in pairs(player.GetBots()) do + if ply:IsLBot(true) then + table.insert(bots, ply) + end + end + + for i = 1, #bots do + if i >= convar1:GetInt() then + bots[i]:Kick() + end + end + + if #bots < max then + nextCheck = CurTime() + 0.5 + + for i = 1, max - #bots do + timer.Simple(0.1 + (i * 0.5), function() + RefoselBots.AddBot() + end) + + nextCheck = nextCheck + 0.5 + end + else + nextCheck = CurTime() + 1 + end + end +end) \ No newline at end of file diff --git a/lua/refoselbots/modules/voice/cl_voice.lua b/lua/refoselbots/modules/voice/cl_voice.lua new file mode 100644 index 0000000..4e575f7 --- /dev/null +++ b/lua/refoselbots/modules/voice/cl_voice.lua @@ -0,0 +1,67 @@ +net.Receive("botVoiceStart", function() + local ply = net.ReadEntity() + local soundn = net.ReadString() + local time = SoundDuration(soundn) -- - 0.1 + + if !IsValid(ply) or !ply:IsBot() or (IsValid(ply.ChattingS) and ply.ChattingS:GetState() == GMOD_CHANNEL_PLAYING) then return end + + -- tts tests + -- sound.PlayURL([[https://translate.google.com/translate_tts?ie=UTF-8&tl=en-us&client=tw-ob&q="]] .. voiceline .. [["]], "mono", function(station) + sound.PlayFile("sound/" .. soundn, "mono", function(station) + if IsValid(station) then + ply.ChattingS = station + station:SetPlaybackRate(math.random(95, 105) * 0.01) + station:Play() + hook.Call("PlayerStartVoice", gmod.GetGamemode(), ply) + + timer.Simple(time, function() + if IsValid(ply) then + hook.Call("PlayerEndVoice", gmod.GetGamemode(), ply) + station:Stop() + --ply.ChattingB = false + end + end) + end + end) +end) + +local voice = Material("voice/icntlk_pl") +-- is there no way to force this on? +hook.Add("PostPlayerDraw", "LeadBot_VoiceIcon", function(ply) + if !IsValid(ply) or !ply:IsPlayer() or !ply:IsBot() or !IsValid(ply.ChattingS) or !GetConVar("mp_show_voice_icons"):GetBool() then return end + + local ang = EyeAngles() + local pos = ply:GetPos() + ply:GetCurrentViewOffset() + Vector(0, 0, 14) + ang:RotateAroundAxis(ang:Up(), -90) + ang:RotateAroundAxis(ang:Forward(), 90) + + cam.Start3D2D(pos, ang, 1) + surface.SetMaterial(voice) + surface.SetDrawColor(255, 255, 255) + surface.DrawTexturedRect(-8, -8, 16, 16) + cam.End3D2D() +end) + +local meta = FindMetaTable("Player") +local oldFunc = meta.VoiceVolume +local oldFunc2 = meta.IsSpeaking + +function meta:VoiceVolume() + if self:IsBot() then + if IsValid(self.ChattingS) then + return self.ChattingS:GetLevel() * 0.6 + else + return 0 + end + else + return oldFunc(self) + end +end + +function meta:IsSpeaking() + if self:IsBot() then + return IsValid(self.ChattingS) or false + else + return oldFunc2(self) + end +end \ No newline at end of file diff --git a/lua/refoselbots/modules/voice/sv_voice.lua b/lua/refoselbots/modules/voice/sv_voice.lua new file mode 100644 index 0000000..9b54a50 --- /dev/null +++ b/lua/refoselbots/modules/voice/sv_voice.lua @@ -0,0 +1,171 @@ +util.AddNetworkString("botVoiceStart") + +RefoselBots.VoicePreset = {} +RefoselBots.VoiceModels = {} + +local convar = CreateConVar("refoselbots_voice", "random", {FCVAR_ARCHIVE}, "Voice Preset.\nOptions are: \n- \"random\"\n- \"" .. table.concat(table.GetKeys(RefoselBots.VoicePreset), "\"\n- \"") .. "\"") + +function RefoselBots.TalkToMe(ply, type) + if !ply:IsLBot(true) then return end + + local hear = {} + local sound = "" + local selectedvoice = "metropolice" + local voice = convar:GetString() + + if voice == "random" then + if !ply.RefoselBots_Voice then + local model = ply:Nick() + if RefoselBots.VoiceModels[model] then + ply.RefoselBots_Voice = RefoselBots.VoiceModels[model] + else + local _, selectedplyvoice = table.Random(RefoselBots.VoicePreset) + ply.RefoselBots_Voice = selectedplyvoice + end + end + + if !RefoselBots.VoicePreset[ply.RefoselBots_Voice] then + ply.RefoselBots_Voice = "metropolice" + end + + selectedvoice = ply.RefoselBots_Voice + elseif RefoselBots.VoicePreset[voice] then + selectedvoice = voice + elseif voice == "" then + return + end + + for k, v in pairs(player.GetAll()) do + if hook.Call("PlayerCanHearPlayersVoice", gmod.GetGamemode(), v, ply) then + table.insert(hear, v) + end + end + + if type and RefoselBots.VoicePreset[selectedvoice][type] then + sound = table.Random(RefoselBots.VoicePreset[selectedvoice][type]) + end + + net.Start("botVoiceStart") + net.WriteEntity(ply) + net.WriteString(sound) + net.Send(hear) +end + +-- Valve Games + +if IsMounted("cstrike") then + RefoselBots.VoicePreset["css"] = {} + RefoselBots.VoicePreset["css"]["join"] = {"bot/its_a_party.wav", "bot/oh_boy2.wav", "bot/whoo.wav"} + RefoselBots.VoicePreset["css"]["taunt"] = {"bot/do_not_mess_with_me.wav", "bot/i_am_dangerous.wav", "bot/i_am_on_fire.wav", "bot/i_wasnt_worried_for_a_minute.wav", "bot/nice2.wav", "bot/owned.wav", "bot/made_him_cry.wav", "bot/they_never_knew_what_hit_them.wav", "bot/who_wants_some_more.wav", "bot/whos_the_man.wav", "bot/yea_baby.wav", "bot/wasted_him.wav"} + RefoselBots.VoicePreset["css"]["help"] = {"bot/help.wav", "bot/i_could_use_some_help.wav", "bot/i_could_use_some_help_over_here.wav", "bot/im_in_trouble.wav", "bot/need_help.wav", "bot/need_help2.wav", "bot/the_actions_hot_here.wav"} + RefoselBots.VoicePreset["css"]["downed"] = RefoselBots.VoicePreset["css"]["help"] +end + +-- Citizens + +RefoselBots.VoicePreset["male"] = {} +RefoselBots.VoicePreset["male"]["join"] = {"vo/npc/male01/hi01.wav", "vo/npc/male01/hi02.wav", "vo/npc/male01/yeah02.wav", "vo/npc/male01/squad_reinforce_single04.wav", "vo/npc/male01/squad_affirm06.wav", "vo/npc/male01/readywhenyouare01.wav", "vo/npc/male01/okimready03.wav", "vo/npc/male01/letsgo02.wav"} +RefoselBots.VoicePreset["male"]["taunt"] = {"vo/coast/odessa/male01/nlo_cheer03.wav", "vo/npc/male01/gotone02.wav", "vo/npc/male01/likethat.wav", "vo/npc/male01/nice01.wav", "vo/npc/male01/question17.wav", "vo/npc/male01/yeah02.wav", "vo/npc/male01/vquestion01.wav"} +RefoselBots.VoicePreset["male"]["pain"] = {"vo/npc/male01/help01.wav", "vo/npc/male01/startle01.wav", "vo/npc/male01/startle02.wav", "vo/npc/male01/uhoh.wav"} + +RefoselBots.VoicePreset["female"] = {} +RefoselBots.VoicePreset["female"]["join"] = {"vo/npc/female01/hi01.wav", "vo/npc/female01/hi02.wav", "vo/npc/female01/yeah02.wav", "vo/npc/female01/squad_reinforce_single04.wav", "vo/npc/female01/squad_affirm06.wav", "vo/npc/female01/readywhenyouare01.wav", "vo/npc/female01/okimready03.wav", "vo/npc/female01/letsgo02.wav"} +RefoselBots.VoicePreset["female"]["taunt"] = {"vo/coast/odessa/female01/nlo_cheer03.wav", "vo/npc/female01/gotone02.wav", "vo/npc/female01/likethat.wav", "vo/npc/female01/nice01.wav", "vo/npc/female01/question17.wav", "vo/npc/female01/yeah02.wav", "vo/npc/female01/vquestion01.wav"} +RefoselBots.VoicePreset["female"]["pain"] = {"vo/npc/female01/help01.wav", "vo/npc/female01/startle01.wav", "vo/npc/female01/startle02.wav", "vo/npc/female01/uhoh.wav"} + +-- Main Characters + +RefoselBots.VoicePreset["alyx"] = {} +RefoselBots.VoicePreset["alyx"]["join"] = {"vo/npc/alyx/al_excuse03.wav", "vo/npc/alyx/getback01.wav", "vo/npc/alyx/lookout01.wav", "vo/npc/alyx/getback02.wav"} +RefoselBots.VoicePreset["alyx"]["taunt"] = {"vo/npc/alyx/al_excuse03.wav", "vo/npc/alyx/lookout01.wav", "vo/npc/alyx/lookout03.wav", "vo/npc/alyx/brutal02.wav", "vo/npc/alyx/youreload02.wav"} +RefoselBots.VoicePreset["alyx"]["pain"] = {"vo/npc/alyx/gasp03.wav", "vo/npc/alyx/ohgod01.wav", "vo/npc/alyx/ohno_startle01.wav"} + +RefoselBots.VoicePreset["barney"] = {} +RefoselBots.VoicePreset["barney"]["join"] = {"vo/npc/barney/ba_ohyeah.wav", "vo/npc/barney/ba_yell.wav", "vo/npc/barney/ba_bringiton.wav"} +RefoselBots.VoicePreset["barney"]["taunt"] = {"vo/npc/barney/ba_downyougo.wav", "vo/npc/barney/ba_losttouch.wav", "vo/npc/barney/ba_yell.wav", "vo/npc/barney/ba_getaway.wav"} +RefoselBots.VoicePreset["barney"]["help"] = {"vo/npc/barney/ba_no01.wav", "vo/npc/barney/ba_no02.wav", "vo/npc/barney/ba_damnit.wav"} + +RefoselBots.VoicePreset["grigori"] = {} +RefoselBots.VoicePreset["grigori"]["join"] = {"vo/ravenholm/monk_death07.wav", "vo/ravenholm/exit_nag02.wav", "vo/ravenholm/engage04.wav", "vo/ravenholm/engage05.wav", "vo/ravenholm/engage06.wav", "vo/ravenholm/engage07.wav", "vo/ravenholm/engage08.wav", "vo/ravenholm/engage09.wav"} +RefoselBots.VoicePreset["grigori"]["taunt"] = {"vo/ravenholm/madlaugh01.wav", "vo/ravenholm/madlaugh02.wav", "vo/ravenholm/madlaugh03.wav", "vo/ravenholm/madlaugh04.wav", "vo/ravenholm/monk_kill01.wav", "vo/ravenholm/monk_kill02.wav", "vo/ravenholm/monk_kill03.wav", "vo/ravenholm/monk_kill04.wav", "vo/ravenholm/monk_kill05.wav", "vo/ravenholm/monk_kill06.wav", "vo/ravenholm/monk_kill07.wav", "vo/ravenholm/monk_kill08.wav", "vo/ravenholm/monk_kill09.wav", "vo/ravenholm/firetrap_vigil.wav"} +RefoselBots.VoicePreset["grigori"]["pain"] = {"vo/ravenholm/monk_pain12.wav", "vo/ravenholm/monk_rant13.wav", "vo/ravenholm/monk_pain06.wav", "vo/ravenholm/engage08.wav"} + +-- Enemies + +RefoselBots.VoicePreset["metropolice"] = {} +RefoselBots.VoicePreset["metropolice"]["join"] = {"npc/metropolice/vo/lookingfortrouble.wav", "npc/metropolice/vo/pickupthecan1.wav", "npc/metropolice/vo/youwantamalcomplianceverdict.wav", "npc/metropolice/vo/unitisonduty10-8.wav", "npc/metropolice/vo/unitis10-8standingby.wav", "npc/metropolice/vo/readytoamputate.wav", "npc/metropolice/vo/prepareforjudgement.wav", "npc/metropolice/vo/readytoprosecutefinalwarning.wav"} +RefoselBots.VoicePreset["metropolice"]["taunt"] = {"npc/metropolice/vo/finalverdictadministered.wav", "npc/metropolice/vo/firstwarningmove.wav", "npc/metropolice/vo/isaidmovealong.wav", "npc/metropolice/vo/nowgetoutofhere.wav", "npc/metropolice/vo/pickupthecan2.wav", "npc/metropolice/vo/pickupthecan3.wav", "npc/metropolice/vo/putitinthetrash1.wav", "npc/metropolice/vo/putitinthetrash2.wav", "npc/metropolice/vo/suspectisbleeding.wav", "npc/metropolice/vo/thisisyoursecondwarning.wav"} +RefoselBots.VoicePreset["metropolice"]["pain"] = {"npc/metropolice/vo/11-99officerneedsassistance.wav", "npc/metropolice/vo/wehavea10-108.wav", "npc/metropolice/vo/runninglowonverdicts.wav", "npc/metropolice/pain4.wav"} + +-- Player Config + +RefoselBots.VoiceModels["Alyx Vance"] = "alyx" +RefoselBots.VoiceModels["Isaac Kleiner"] = "male" +RefoselBots.VoiceModels["Dr. Wallace Breen"] = "male" +RefoselBots.VoiceModels["The G-Man"] = "male" +RefoselBots.VoiceModels["Odessa Cubbage"] = "male" +RefoselBots.VoiceModels["Eli Vance"] = "male" +RefoselBots.VoiceModels["Father Grigori"] = "grigori" +RefoselBots.VoiceModels["Judith Mossman"] = "female" +RefoselBots.VoiceModels["Barney Calhoun"] = "barney" + +RefoselBots.VoiceModels["Magnusson"] = "male" + +RefoselBots.VoiceModels["American Soldier"] = "male" +RefoselBots.VoiceModels["German Soldier"] = "male" + +RefoselBots.VoiceModels["GIGN"] = "css" +RefoselBots.VoiceModels["Elite Crew"] = "css" +RefoselBots.VoiceModels["Artic Avengers"] = "css" +RefoselBots.VoiceModels["SEAL Team Six"] = "css" +RefoselBots.VoiceModels["GSG-9"] = "css" +RefoselBots.VoiceModels["SAS"] = "css" +RefoselBots.VoiceModels["Phoenix Connexion"] = "css" +RefoselBots.VoiceModels["Guerilla Warfare"] = "css" +RefoselBots.VoiceModels["Cohrt"] = "male" + +RefoselBots.VoiceModels["Chell"] = "female" + +RefoselBots.VoiceModels["Civil Protection"] = "metropolice" +RefoselBots.VoiceModels["Combine Soldier"] = "metropolice" +RefoselBots.VoiceModels["Combine Prison Guard"] = "metropolice" +RefoselBots.VoiceModels["Elite Combine Soldier"] = "metropolice" +RefoselBots.VoiceModels["Stripped Combine Soldier"] = "metropolice" + +RefoselBots.VoiceModels["Zombie"] = "css" +RefoselBots.VoiceModels["Fast Zombie"] = "css" +RefoselBots.VoiceModels["Zombine"] = "css" +RefoselBots.VoiceModels["Corpse"] = "css" +RefoselBots.VoiceModels["Charple"] = "css" +RefoselBots.VoiceModels["Skeleton"] = "css" + +RefoselBots.VoiceModels["Van"] = "male" +RefoselBots.VoiceModels["Ted"] = "male" +RefoselBots.VoiceModels["Joe"] = "male" +RefoselBots.VoiceModels["Eric"] = "male" +RefoselBots.VoiceModels["Art"] = "male" +RefoselBots.VoiceModels["Sandro"] = "male" +RefoselBots.VoiceModels["Mike"] = "male" +RefoselBots.VoiceModels["Vance"] = "male" +RefoselBots.VoiceModels["Erdin"] = "male" +RefoselBots.VoiceModels["Van"] = "male" +RefoselBots.VoiceModels["Ted"] = "male" +RefoselBots.VoiceModels["Joe"] = "male" +RefoselBots.VoiceModels["Eric"] = "male" +RefoselBots.VoiceModels["Art"] = "male" +RefoselBots.VoiceModels["Sandro"] = "male" +RefoselBots.VoiceModels["Mike"] = "male" +RefoselBots.VoiceModels["Vance"] = "male" +RefoselBots.VoiceModels["Erdin"] = "male" +RefoselBots.VoiceModels["Joey"] = "female" +RefoselBots.VoiceModels["Kanisha"] = "female" +RefoselBots.VoiceModels["Kim"] = "female" +RefoselBots.VoiceModels["Chau"] = "female" +RefoselBots.VoiceModels["Naomi"] = "female" +RefoselBots.VoiceModels["Lakeetra"] = "female" +RefoselBots.VoiceModels["Joey"] = "female" +RefoselBots.VoiceModels["Kanisha"] = "female" +RefoselBots.VoiceModels["Kim"] = "female" +RefoselBots.VoiceModels["Chau"] = "female" +RefoselBots.VoiceModels["Naomi"] = "female" +RefoselBots.VoiceModels["Lakeetra"] = "female" \ No newline at end of file