Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/vscripts/tf2ware_ultimate.nut
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ IncludeScript("tf2ware_ultimate/vcd", ROOT)
IncludeScript("tf2ware_ultimate/util", ROOT)
IncludeScript("tf2ware_ultimate/config", ROOT)
IncludeScript("tf2ware_ultimate/location", ROOT)
IncludeScript("tf2ware_ultimate/bot", ROOT)
IncludeScript("tf2ware_ultimate/sdr", ROOT)
IncludeScript("tf2ware_ultimate/dev", ROOT)
IncludeScript("tf2ware_ultimate/plugin", ROOT)
Expand Down
164 changes: 164 additions & 0 deletions scripts/vscripts/tf2ware_ultimate/bot.nut
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// by ficool2

class Ware_BotData
{
function constructor(entity)
{
me = entity
minigame_timers = []
}

me = null
minigame_timers = null
}

if (!("Ware_Bots" in this))
{
Ware_Bots <- []
}

SetConvarValue("tf_bot_difficulty", 0)
SetConvarValue("tf_bot_melee_only", 1)
SetConvarValue("tf_bot_sniper_melee_range", -1)
SetConvarValue("tf_bot_reevaluate_class_in_spawnroom", 0)
SetConvarValue("tf_bot_keep_class_after_death", 1)

Ware_BotMinigameBehaviors <-
{
type_word = {}
}

Ware_BotMinigameBehavior <- null

function Ware_BotLoadBehaviors()
{
foreach (minigame, scope in Ware_BotMinigameBehaviors)
{
local file_name = "tf2ware_ultimate/botbehavior/minigames/" + minigame
try
{
IncludeScript(file_name, scope)
}
catch (e)
{
Ware_Error("Failed to load '%s.nut'. Missing from disk or syntax error", path)
}
}
}

function Ware_BotSetup(bot)
{
// disables visibility of enemies
bot.AddBotAttribute(IGNORE_ENEMIES)
bot.SetMaxVisionRangeOverride(0.01)
// makes spies not attempt to cloak
bot.SetMissionTarget(Ware_IncursionDummy)
// set MISSION_SNIPER which effectively does nothing
bot.SetMission(3, true)
bot.GetScriptScope().bot_data <- Ware_BotData(bot)

if (Ware_Bots.find(bot) == null)
Ware_Bots.append(bot)
}

function Ware_BotDestroy(bot)
{
local bot_data = bot.GetScriptScope().bot_data
foreach (timer in bot_data.minigame_timers)
KillTimer(timer)
}

function Ware_BotUpdate()
{
if (Ware_Minigame
&& Ware_BotMinigameBehavior
&& "OnUpdate" in Ware_BotMinigameBehavior)
{
foreach (bot in Ware_Bots)
Ware_BotMinigameBehavior.OnUpdate(bot)
}
else
{
// TODO roam around
}
}

function Ware_BotOnMinigameStart()
{
if (Ware_Minigame.file_name in Ware_BotMinigameBehaviors)
{
Ware_BotMinigameBehavior = Ware_BotMinigameBehaviors[Ware_Minigame.file_name]

if ("OnStart" in Ware_BotMinigameBehavior)
{
foreach (bot in Ware_Bots)
Ware_BotMinigameBehavior.OnStart(bot)
}
}
else
{
Ware_BotMinigameBehavior = null
}
}

function Ware_BotOnMinigameEnd()
{
foreach (bot in Ware_Bots)
{
local bot_data = bot.GetScriptScope().bot_data
foreach (timer in bot_data.minigame_timers)
KillTimer(timer)
bot_data.minigame_timers.clear()
}
}

function Ware_BotCreateMinigameTimer(bot, callback, delay)
{
local timer = CreateTimer(callback, delay)
bot.GetScriptScope().bot_data.minigame_timers.append(timer)
return timer
}

function Ware_BotTryWordTypo(bot, text, chance)
{
// TODO higher typo chance with longer word

if (RandomFloat(0.0, 1.0) > chance)
return text

if (text.len() < 2)
return text

local i = RandomInt(0, text.len() - 2)
local chars = text.toupper() == text ? text.tolower() : text
local type = RandomInt(0, 3)

switch (type)
{
case 0: // swap
{
local tmp = chars[i]
chars = chars.slice(0, i) + chars[i + 1].tochar() + tmp.tochar() + chars.slice(i + 2)
break;
}
case 1: // omit
{
chars = chars.slice(0, i) + chars.slice(i + 1)
break
}
case 2: // repeat
{
chars = chars.slice(0, i) + chars[i].tochar() + chars[i].tochar() + chars.slice(i + 1)
break
}
case 3: // wrong
{
local keyboard = "abcdefghijklmnopqrstuvwxyz"
local wrongChar = keyboard[RandomInt(0, keyboard.len() - 1)]
chars = chars.slice(0, i) + wrongChar.tochar() + chars.slice(i + 1)
break
}
}

return chars
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function OnStart(bot)
{
// TODO scale by difficulty
local chance = 0.3
local delay = RandomFloat(1.5, 5.0)

Ware_BotCreateMinigameTimer(bot, function()
{
local word = Ware_BotTryWordTypo(bot, Ware_MinigameScope.word, chance)
Say(bot, word, false)
}, delay)
}
49 changes: 26 additions & 23 deletions scripts/vscripts/tf2ware_ultimate/events.nut
Original file line number Diff line number Diff line change
Expand Up @@ -329,29 +329,17 @@ function OnGameEvent_recalculate_truce(params)
}
}

::Ware_PlayerInit <- function()
::Ware_PlayerInitWrapper <- function()
{
local player = self
MarkForPurge(player)

// don't include SourceTV because it's not a real player
if (IsPlayerSourceTV(player))
return

player.ValidateScriptScope()
local scope = player.GetScriptScope()
scope.ware_data <- Ware_PlayerData(player)
scope.ware_minidata <- {}
scope.ware_specialdata <- {}
Ware_Players.append(player)
Ware_PlayersData.append(scope.ware_data)
if (Ware_SpecialRound && Ware_SpecialRound.cb_on_player_connect.IsValid())
Ware_SpecialRound.cb_on_player_connect(player)
Ware_PlayerInit(self)
}

::Ware_PlayerPostSpawn <- function()
{
Ware_UpdatePlayerVoicePitch(self)

if (self.IsBotOfType(TF_BOT_TYPE))
Ware_BotSetup(self)

local melee = ware_data.special_melee
if (melee == null)
Expand Down Expand Up @@ -382,7 +370,7 @@ function OnGameEvent_recalculate_truce(params)
}

if (Ware_SpecialRound && Ware_SpecialRound.cb_on_player_postspawn.IsValid())
Ware_SpecialRound.cb_on_player_postspawn(self)
Ware_SpecialRound.cb_on_player_postspawn(self)
}

function OnGameEvent_player_spawn(params)
Expand All @@ -393,8 +381,15 @@ function OnGameEvent_player_spawn(params)

if (params.team == TEAM_UNASSIGNED)
{
// delay this to end of frame as SourceTV won't be registered here yet
EntityEntFire(player, "CallScriptFunction", "Ware_PlayerInit", 0.0)
if (player.IsBotOfType(TF_BOT_TYPE))
{
Ware_PlayerInit(player)
}
else
{
// delay this to end of frame as SourceTV won't be registered here yet
EntityEntFire(player, "CallScriptFunction", "Ware_PlayerInitWrapper", 0.0)
}
return
}

Expand Down Expand Up @@ -529,11 +524,19 @@ function OnGameEvent_player_disconnect(params)

idx = Ware_Players.find(player)
if (idx != null)
{
Ware_Players.remove(idx)

idx = Ware_PlayersData.find(data)
if (idx != null)
Ware_PlayersData.remove(idx)
}

if (player.IsBotOfType(TF_BOT_TYPE))
{
Ware_BotDestroy(player)

idx = Ware_Bots.find(player)
if (idx != null)
Ware_Bots.remove(idx)
}

if (Ware_Minigame == null)
return
Expand Down
36 changes: 35 additions & 1 deletion scripts/vscripts/tf2ware_ultimate/main.nut
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,11 @@ if (!("Ware_Precached" in this))
}

// this shuts up incursion distance warnings from the nav mesh
CreateEntitySafe("base_boss").KeyValueFromString("classname", "point_commentary_viewpoint")
Ware_IncursionDummy <- CreateEntitySafe("base_boss")
Ware_IncursionDummy.KeyValueFromString("classname", "point_commentary_viewpoint")
Ware_IncursionDummy.AddSolidFlags(FSOLID_NOT_SOLID)
// bots now use this as a unreachable dummy target
Ware_IncursionDummy.SetAbsOrigin(Vector(16000, 16000, 16000))
}

function Ware_SetupMap()
Expand Down Expand Up @@ -789,6 +793,26 @@ function Ware_FixupPlayerWeaponSwitch()
self.Weapon_Switch(activator)
}

function Ware_PlayerInit(player)
{
MarkForPurge(player)

// don't include SourceTV because it's not a real player
if (IsPlayerSourceTV(player))
return

player.ValidateScriptScope()
local scope = player.GetScriptScope()
scope.ware_data <- Ware_PlayerData(player)
scope.ware_minidata <- {}
scope.ware_specialdata <- {}
Ware_Players.append(player)
Ware_PlayersData.append(scope.ware_data)
if (Ware_SpecialRound && Ware_SpecialRound.cb_on_player_connect.IsValid())
Ware_SpecialRound.cb_on_player_connect(player)
}


function Ware_CheckPlayerArray()
{
// failsafe: if a player entity gets deleted without disconnecting (such as via external plugins)
Expand All @@ -800,6 +824,9 @@ function Ware_CheckPlayerArray()
{
Ware_Players.remove(i)
Ware_PlayersData.remove(i)
local idx = Ware_Bots.find(player)
if (idx != null)
Ware_Bots.remove(idx)
}
}
for (local i = Ware_MinigamePlayers.len() - 1; i >= 0; i--)
Expand Down Expand Up @@ -1693,6 +1720,8 @@ function Ware_StartMinigameInternal(is_boss)

if (Ware_SpecialRound)
Ware_SpecialRound.cb_on_minigame_start()

Ware_BotOnMinigameStart()

Ware_MinigamePreEndTimer = CreateTimer(function()
{
Expand Down Expand Up @@ -2024,6 +2053,8 @@ function Ware_FinishMinigameInternal()

if (Ware_SpecialRound)
Ware_SpecialRound.cb_on_minigame_end()

Ware_BotOnMinigameEnd()

Ware_Minigame = null
Ware_MinigameScope.clear()
Expand Down Expand Up @@ -2234,6 +2265,8 @@ function Ware_OnUpdate()

Ware_UpdateNav()

Ware_BotUpdate()

if (Ware_SpecialRound)
Ware_SpecialRound.cb_on_update()

Expand Down Expand Up @@ -2476,4 +2509,5 @@ IncludeScript("tf2ware_ultimate/api/specialround", ROOT)
Ware_SetupMap()
Ware_SetupLocations()
Ware_PrecacheEverything()
Ware_BotLoadBehaviors()
Ware_CheckPlayerArray()