Left 4 Dead 2

Left 4 Dead 2

Left 4 Bots 2
Optimal default settings and script changes that I think should be in the base mod
I'm the author of the Left 4 Bots 2 Smart Vocalizer mod and I've had a lot of experience with the settings and scripts of this mod. I've made some changes within the vocalizer mod as well as some personal changes that I think should be in here by default.

Basic settings changes I've made:
  • team_min_vomitjars 0 -> 1
    • I think a balanced team should have at least 1 of each throwable. I consider the bile bomb to be the strongest, so it seems odd to me that it was left out.
  • Throwable preference order: "pipe_bomb,molotov,vomitjar" -> "vomitjar,pipe_bomb,molotov"
    • As above, I think bile bombs are the strongest, so in the case where 1 of each throwable is already chosen, I think bile bombs should be the tiebreaker instead of pipe bombs.
  • orders_no_queue 0 -> 1
    • Queuing orders in my experience often leads to the bots not listening when I give them a new order (since the new order can just get queued if its deemed less valuable). I prefer my bots to be immediately responsive, and prioritize newer commands over older ones, so I think the order queue should be off by default.
  • userlevel_give_medkit, userlevel_give_weapons, userlevel_orders 1 -> 0
    • I think its a little crazy to bar the people you're playing with from using the bots. I know that you can manually whitelist people with the friends config in Left4Lib, but its a bit of an annoyance to do and I'd say a majority of people realize that it's even an option. I'd rather manually block griefers than manually add everyone else.

Advanced settings changes I've made:
  • ::Left4Bots.GetBotByName <- function (name) { local n = name.tolower(); foreach (bot in Bots) { if (bot.IsValid() && bot.GetPlayerName().tolower() == n) return bot; } return null; }
    ->
    ::Left4Bots.GetBotByName <- function (name) { local n = name.tolower(); foreach (surv in Survivors) { if (surv.IsValid() && Left4Utils.GetCharacterName(surv).tolower() == n) return surv; } return null; }
    • This avoids a glitch where follow, goto, and heal commands fail to work properly when selecting a player as a target.
  • ::Left4Bots.Cmd_wait <- function (player, allBots = false, tgtBot = null, param = null) { Logger.Debug("Cmd_wait - player: " + player.GetPlayerName() + " - allBots: " + allBots + " - tgtBot: " + tgtBot + " - param: " + param); local waitPos = null; if (param) { if (param.tolower() == "here") waitPos = player.GetOrigin(); else if (param.tolower() == "there") waitPos = Left4Utils.GetLookingPosition(player, Settings.tracemask_others); if (!waitPos) { Logger.Warning("Invalid wait position: " + param); return; } } if (allBots) { foreach (bot in Bots) BotOrderAdd(bot, "wait", player, null, waitPos != null ? waitPos : bot.GetOrigin(), null, 0, !Settings.wait_nopause); } else { if (!tgtBot) tgtBot = GetFirstAvailableBotForOrder("wait"); if (tgtBot) BotOrderAdd(tgtBot, "wait", player, null, waitPos != null ? waitPos : tgtBot.GetOrigin(), null, 0, !Settings.wait_nopause); else Logger.Warning("No available bot for order of type: wait"); } }
    ->
    ::Left4Bots.Cmd_wait <- function (player, allBots = false, tgtBot = null, param = null) { Logger.Debug("Cmd_wait - player: " + player.GetPlayerName() + " - allBots: " + allBots + " - tgtBot: " + tgtBot + " - param: " + param); local waitPos = null; local waitSurv = null; if (param) { if (param.tolower() == "here") waitPos = player.GetOrigin(); else if (param.tolower() == "there") waitPos = Left4Utils.GetLookingPosition(player, Settings.tracemask_others); else waitSurv = GetBotByName(param); if (waitSurv) { waitPos = waitSurv.GetOrigin(); } if (!waitPos) { Logger.Warning("Invalid wait position: " + param); return; } } if (allBots) { foreach (bot in Bots) BotOrderAdd(bot, "wait", player, null, waitPos != null ? waitPos : bot.GetOrigin(), null, 0, !Settings.wait_nopause); } else { if (!tgtBot) tgtBot = GetFirstAvailableBotForOrder("wait"); if (tgtBot) BotOrderAdd(tgtBot, "wait", player, null, waitPos != null ? waitPos : tgtBot.GetOrigin(), null, 0, !Settings.wait_nopause); else Logger.Warning("No available bot for order of type: wait"); } }
    • This allows survivor positions to be selected as destinations for the wait command. E.g., "Francis wait Louis" will make Francis wait by Louis.
  • ::Left4Bots.Cmd_warp <- function (player, allBots = false, tgtBot = null, param = null) { Logger.Debug("Cmd_warp - player: " + player.GetPlayerName() + " - allBots: " + allBots + " - tgtBot: " + tgtBot + " - param: " + param); local warpPos = null; if (param) { if (param.tolower() == "here") //lxc fix "warp" pos warpPos = player.IsHangingFromLedge() ? NetProps.GetPropVector(player, "m_hangStandPos") : player.GetOrigin(); else if (param.tolower() == "there") warpPos = Left4Utils.GetLookingPosition(player, Settings.tracemask_others); else if (param.tolower() == "move") warpPos = "move"; if (!warpPos) { Logger.Warning("Invalid wait position: " + param); return; } } else //lxc fix "warp" pos warpPos = player.IsHangingFromLedge() ? NetProps.GetPropVector(player, "m_hangStandPos") : player.GetOrigin(); if (allBots) { foreach (bot in Bots) { if (warpPos == "move") { local movepos = bot.GetScriptScope().MovePos; if (movepos) bot.SetOrigin(movepos); } else bot.SetOrigin(warpPos); } } else { if (!tgtBot) tgtBot = GetPickerBot(player); // player, radius = 999999, threshold = 0.95, visibleOnly = false if (tgtBot) { if (warpPos == "move") { local movepos = tgtBot.GetScriptScope().MovePos; if (movepos) tgtBot.SetOrigin(movepos); } else tgtBot.SetOrigin(warpPos); } else Logger.Warning("No available bot for order of type: warp"); } }
    ->
    ::Left4Bots.Cmd_warp <- function (player, allBots = false, tgtBot = null, param = null) { Logger.Debug("Cmd_warp - player: " + player.GetPlayerName() + " - allBots: " + allBots + " - tgtBot: " + tgtBot + " - param: " + param); local warpPos = null; local warpSurv = null; if (param) { if (param.tolower() == "here") //lxc fix "warp" pos warpPos = player.IsHangingFromLedge() ? NetProps.GetPropVector(player, "m_hangStandPos") : player.GetOrigin(); else if (param.tolower() == "there") warpPos = Left4Utils.GetLookingPosition(player, Settings.tracemask_others); else if (param.tolower() == "move") warpPos = "move"; else warpSurv = GetBotByName(param); if (warpSurv) { warpPos = warpSurv.IsHangingFromLedge() ? NetProps.GetPropVector(warpSurv, "m_hangStandPos") : warpSurv.GetOrigin(); } if (!warpPos) { Logger.Warning("Invalid wait position: " + param); return; } } else //lxc fix "warp" pos warpPos = player.IsHangingFromLedge() ? NetProps.GetPropVector(player, "m_hangStandPos") : player.GetOrigin(); if (allBots) { foreach (bot in Bots) { if (warpPos == "move") { local movepos = bot.GetScriptScope().MovePos; if (movepos) bot.SetOrigin(movepos); } else bot.SetOrigin(warpPos); } } else { if (!tgtBot) tgtBot = GetPickerBot(player); // player, radius = 999999, threshold = 0.95, visibleOnly = false if (tgtBot) { if (warpPos == "move") { local movepos = tgtBot.GetScriptScope().MovePos; if (movepos) tgtBot.SetOrigin(movepos); } else tgtBot.SetOrigin(warpPos); } else Logger.Warning("No available bot for order of type: warp"); } }
    • This allows survivor positions to be selected as destinations for the warp command. E.g., "Francis warp Louis" will make Francis warp to Louis.
  • Now I haven't done this one due to the sheer amount of code I'd have to rewrite for the BotUpdatePickupToSearch function , but I would like a team_min_assault_rifles and a team_min_sniper_rifles option to complement the existing team_min_shotguns config.
    • I like to have my team equip a balanced arsenal of the T2 weapons, and this would be very helpful to make sure at least one of each type of gun is present.

Misc vocalizer changes I've made:
  • vocalizer_goto_stop: "PlayerAnswerLostCall,PlayerLostCall" -> "PlayerAnswerLostCall"
    • I've found it weird that when you order them to go somewhere they have a chance to all shout that they're alone -- even if they all went as a group!
  • PlayerLeadOn: "bots automation all,botname lead" -> "bots lead,botname lead"
  • PlayerImWithYou: None -> "bots automation all,botname automation all"
  • PlayerWaitHere: "bots wait,botname wait" -> "bots wait here,botname wait here"
  • PlayerEmphaticGo: "bots use,botname use" -> "bots goto,botname goto"
  • PlayerAlertGiveItem: None -> "bot use,botname use"
  • PlayerStayTogether: "bots cancel,botname cancel" -> "bots come,botname come"
  • PlayerFollowMe: "bots cancel,botname cancel" -> "bots follow me,botname follow me"
Last edited by Cheetos; 4 May @ 1:41pm
< >
Showing 1-1 of 1 comments
smilzo  [developer] 9 May @ 6:32am 
Hey! Thanks for the input.

My concern about the vomitjar is that they aren't that good at using it. With the pipe bomb it isn't a big problem (unless they accidentally throw it at you while playing expert) but with the vomitjar it can be annoying at any difficulty because they may summon a horde in situations where it's the last thing you want. So this is why i set team_min_vomitjars to 0 and i swap as soon as i see a bot with a vomitjar, this way most of the time i am the one holding it.

The other 2 settings can be a good change, especially for orders_no_queue, considering most players are used to the L4B1 system which had no queue.

For the vscript changes, pls consider a pull request on the github repo as it's easier for me to review and merge the changes.
Thank you.
< >
Showing 1-1 of 1 comments
Per page: 1530 50