Yea i updated all the commands and left out a couple of the fake ones (sv_hasreserve, sv_afkimmune). They are both still in, and also sv_ejectimmunity (i think), but that might be in the readme.
<!--quoteo(post=2041170:date=Dec 6 2012, 04:11 PM:name=xDragon)--><div class='quotetop'>QUOTE (xDragon @ Dec 6 2012, 04:11 PM) <a href="index.php?act=findpost&pid=2041170"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Yea i updated all the commands and left out a couple of the fake ones (sv_hasreserve, sv_afkimmune). They are both still in, and also sv_ejectimmunity (i think), but that might be in the readme.<!--QuoteEnd--></div><!--QuoteEEnd-->
Ok cool so we grant sv_ejectimmunity to people who should be immune from a com kick, very nice!
JektJoin Date: 2012-02-05Member: 143714Members, Squad Five Blue, Reinforced - Shadow
Can you improve the tournament mode messages to be similar to NS2 Stats? Being able to set the name of who says the messages in chat, showing the player name instead of the ID on ready up and periodically tell players to ready up.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->[Server] Script Error #1: lua/plugins/plugin_baseadmincommands.lua:613: attempt to call global 'CreateServerAdminCommand' (a nil value) Call stack: #1: lua/plugins/plugin_baseadmincommands.lua:613 PrintHelpForCommand = function GetPlayerList = function AllPlayers = function GetPlayerMatchingSteamId = function GetPlayerMatchingName = function GetPlayerMatching = function PrintStatus = function OnCommandChangeMap = function OnCommandSVReset = function OnCommandSVrrall = function OnCommandSVRandomall = function SwitchTeam = function Eject = function Kick = function GetChatMessage = function Say = function TeamSay = function PlayerSay = function Slay = function SetPassword = function bannedPlayers = { } bannedPlayersFileName = "config://BannedPlayers.json" LoadBannedPlayers = function SaveBannedPlayers = function OnConnectCheckBan = function Ban = function UnBan = function ListBans = function PLogAll = function PEndLog = function AutoBalance = function EnableEventTesting = function #2: scriptLoad [C]:-1 #3: Load lua/EventTester.lua:185 fileName = "lua/plugins/plugin_baseadmincommands.lua" #4: LoadPlugins lua/DAKLoader_ServerAdminCommands.lua:117 (for index) = 1 (for limit) = 7 (for step) = 1 i = 1 filename = "lua/plugins/plugin_baseadmincommands.lua" #5: lua/DAKLoader_ServerAdminCommands.lua:124 OnCommandRCON = function OnCommandAllTalk = function OnCommandListPlugins = function OnCommandListMap = function OnCommandCheats = function OnCommandKillServer = function LoadPlugins = function #6: scriptLoad [C]:-1 #7: Load lua/EventTester.lua:185 fileName = "lua/DAKLoader_ServerAdminCommands.lua" #8: lua/DAKLoader_Server.lua:71 #9: scriptLoad [C]:-1 #10: Load lua/EventTester.lua:185 fileName = "lua/DAKLoader_Server.lua" #11: lua/DAKLoader.lua:45 #12: scriptLoad [C]:-1 #13: Load lua/EventTester.lua:185 fileName = "lua/DAKLoader.lua" #14: lua/Server.lua:25<!--c2--></div><!--ec2-->
Just updated to latest version. I can confirm that everything works perfectly (as usual). Bug with messages being overwritten is fixed and works fine. Map vote works better as well, starts much quicker without delays. Reserved slots work as planned. Periodic messages are awesome with the new first message delay set.
Awesome job xDragon.
I have a request as well.
xDragon, Is there any way it can pull messages and MOTD from a web site as well? I already made a script that uses DAK to integrate with IP Board (and phpbb3 coming very soon) and i'd like to add the ability to generate the MOTD and messages on the fly.
Even better, if it can pull the ENTIRE config file remotely i can make a kickass php script that admins can use as a web interface to configure DAK. All free / open source as usual :).
As an admin i'm simply in love with the script ! It works great and do amazing things !
Although i'm clueless when it comes to coding , i'm pretty curious by nature and browsed the lua pages of the wiki, i came across a "Client.ShowWebpage" function. Is there a way to make a website popup in the steamoverlay of the client if he types something in the console or the chat ? That would be great tool for communities forums etc
Anything Client. is something that would need to be executed client side, which would require a client side portion of the mod (there is currently, but it has no function). Since many people load this mod in a way that the client side portion wouldnt be used, I havent commited much work into that yet.
<!--quoteo(post=2043399:date=Dec 11 2012, 05:37 AM:name=wireaudio)--><div class='quotetop'>QUOTE (wireaudio @ Dec 11 2012, 05:37 AM) <a href="index.php?act=findpost&pid=2043399"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->I have a request as well.
xDragon, Is there any way it can pull messages and MOTD from a web site as well? I already made a script that uses DAK to integrate with IP Board (and phpbb3 coming very soon) and i'd like to add the ability to generate the MOTD and messages on the fly.
Even better, if it can pull the ENTIRE config file remotely i can make a kickass php script that admins can use as a web interface to configure DAK. All free / open source as usual :).<!--QuoteEnd--></div><!--QuoteEEnd-->
Its probably not something super hard to setup in the mod, but might be better done without the mod. I say that because then you can configure everything in the script easily and not have to worry, currently it would require you to set a specific field in the config to get the config from a webpage (sorta confusing).
Maybe you can you add a DAKWebconfig.json used just to set the web concerning address? And if address is empty quotes server can just use local config?
<!--quoteo(post=2043605:date=Dec 11 2012, 10:29 AM:name=xDragon)--><div class='quotetop'>QUOTE (xDragon @ Dec 11 2012, 10:29 AM) <a href="index.php?act=findpost&pid=2043605"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Anything Client. is something that would need to be executed client side, which would require a client side portion of the mod (there is currently, but it has no function). Since many people load this mod in a way that the client side portion wouldnt be used, I havent commited much work into that yet.<!--QuoteEnd--></div><!--QuoteEEnd-->
Exactly, until the mod is whitelisted we are very limited in what we can do else our server will be harder to find and become populated.
<strike>Can you add an explanation of how the tournamentmode plugin works to the readme? Specifically, it would be good if you could detail any differences from how NS2Stats tournamentmode works, thanks!</strike>
Also, I noticed that the default config file has a few variables that are not mentioned in the readme, one in particular is "kPregameLength" in the "MapVote" plugin, I'm guessing this is self-explanatory but it would be good to have it in the readme anyway.
Edit: I see the tournamentmode variables are explained in the readme, ignore that question, but answer this one if you'd be so kind:
If tournamentmode is on pub mode with min players set to 3, will the game still start 1v1, 2v2, 3v3 if there are <7 players on the server?
The pub mode sets the players required on each team, so if there is 6 players in the game or 20 it doesnt matter, once there is that many players on each team (the kTournamentModePubMinPlayers setting) then the game will start.
<!--quoteo(post=2047159:date=Dec 17 2012, 04:40 PM:name=xDragon)--><div class='quotetop'>QUOTE (xDragon @ Dec 17 2012, 04:40 PM) <a href="index.php?act=findpost&pid=2047159"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->The pub mode sets the players required on each team, so if there is 6 players in the game or 20 it doesnt matter, once there is that many players on each team (the kTournamentModePubMinPlayers setting) then the game will start.<!--QuoteEnd--></div><!--QuoteEEnd-->
Yeh so my question is, if the min players is set to 3, and there are only 2 players on the server, will the round never start? Thanks.
<!--quoteo(post=2047177:date=Dec 17 2012, 06:34 PM:name=YoungTrotsky)--><div class='quotetop'>QUOTE (YoungTrotsky @ Dec 17 2012, 06:34 PM) <a href="index.php?act=findpost&pid=2047177"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Yeh so my question is, if the min players is set to 3, and there are only 2 players on the server, will the round never start? Thanks.<!--QuoteEnd--></div><!--QuoteEEnd-->
Dude its obvious. If you set it to 3 it wont start until there are 3 players on each team. In you want it to start at 2 or 1 just set the minimum to that.
xDragon, i really need your help. I'm getting errors left and right when using some admin commands. I did a fresh install, added 1 admin, i didn't change anything from default configuration. I have 5 servers with the same issue, running on both windows and linux.
I am using the latest release from here: <a href="https://github.com/xToken/DAK" target="_blank">https://github.com/xToken/DAK</a> and 233 server version (latest as of now). Fresh install, no other mods, no other changes. All files from DAK LUA folder are in the ns2/lua folder and server.lua is loading DAKLoader.lua.
UPDATE: Not all commands fail. Ban for example works fine. sv_listadmins works as well. It's kick, slay and a few others that don't work.
For example, when you do KICK it gives this error:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->[Server] Script Error #1: lua/DAKLoader_ServerAdmin.lua:128: attempt to call method 'GetUserId' (a nil value) Call stack: #1: DAKGetClientLevel lua/DAKLoader_ServerAdmin.lua:128 client = ReadyRoomPlayer-1890 {buncha vars i removed} #2: DAKGetClientLevelSufficient lua/DAKLoader_ServerAdmin.lua:136 client = ServerClient { } targetclient = ReadyRoomPlayer-1890 {buncha vars i removed} #3: lua/plugins/plugin_baseadmincommands.lua:270 client = ServerClient { } playerId = "1" player = ReadyRoomPlayer-1890 {buncha vars i removed} #4: (tail call):-1<!--c2--></div><!--ec2-->
lua/DAKLoader_ServerAdmin.lua:128 has the following function <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->function DAKGetClientLevel(client) local steamId = client:GetUserId() (<- THIS IS LINE 128) if steamId == nil then return 0 end return DAKGetSteamIDLevel(steamId) end<!--c2--></div><!--ec2-->
#2 is DAKLoader_ServerAdmin.lua:136 with the following code: <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->function DAKGetClientLevelSufficient(client, targetclient) if client == nil then return true end if targetclient == nil then return false end return DAKGetClientLevel(client) >= DAKGetClientLevel(targetclient) (<- THIS IS LINE 136) end<!--c2--></div><!--ec2-->
#3 is plugins/plugin_baseadmincommands.lua:270 <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local function Kick(client, playerId)
local player = GetPlayerMatching(playerId) if not DAKGetClientLevelSufficient(client, player) then (<- THIS IS LINE 270) return end if player then Server.DisconnectClient(Server.GetOwner(player)) else ServerAdminPrint(client, "No matching player") end
My config file is default: <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->{ "VoteSurrender": { "kVoteSurrenderAlertDelay": 20, "kVoteSurrenderVotingTime": 120, "kVoteSurrenderMinimumPercentage": 60 }, "AFKKicker": { "kAFKKickWarning1": 30, "kAFKKickMessage": "%s kicked from the server for idling more than %d seconds.", "kAFKKickDelay": 150, "kAFKKickClientMessage": "You are being kicked for idling for more than %d seconds.", "kAFKKickWarning2": 10, "kAFKKickDisconnectReason": "Kicked from the server for idling more than %d seconds.", "kAFKKickWarningMessage1": "You will be kicked in %d seconds for idling.", "kAFKKickReturnMessage": "You are no longer flagged as idle.", "kAFKKickWarningMessage2": "You will be kicked in %d seconds for idling.", "kAFKKickMinimumPlayers": 5, "kAFKKickCheckDelay": 5 }, "VoteRandom": { "kVoteRandomInstantly": false, "kVoteRandomConnectAlert": "Random teams are enabled, you are being randomed to a team.", "kVoteRandomMinimumPercentage": 60, "kVoteRandomDuration": 30, "kVoteRandomVoteCountAlert": "%s voted for random teams. (%s votes, needed %s).", "kVoteRandomEnabledDuration": "Random teams have been enabled for the next %s Minutes", "kVoteRandomEnabled": "Random teams have been enabled, the round will restart." }, "DAKLoader": { "GamerulesExtensions": true, "GamerulesClassName": "NS2Gamerules", "OverrideInterp": { "kInterp": 100, "kEnabled": false }, "LoadFromServerLUA": true, "ServerAdmin": { "kQueryURL": "", "kMapChangeDelay": 5, "kUpdateDelay": 60 }, "kPluginsList": [ "afkkick", "baseadmincommands", "mapvote", "motd", "unstuck", "voterandom", "votesurrender" ], "kDelayedServerUpdate": 1, "kDelayedClientConnect": 2 }, "MapVote": { "kVoteMapRockTheVote": "%s rock'd the vote. (%s votes, needed %s).", "kMapsToSelect": 7, "kPregameLength": 15, "kDontRepeatFor": 4, "kVoteMapExtended": "****** Voting has ended, extending current map for %s minutes. ", "kVoteMapNoWinner": "****** Voting has ended, no map won. ", "kVoteMapStarted": "******* Map vote has begun. (%s%% votes needed to win) ******", "kVoteStartDelay": 8, "kVoteMapBeginning": "****** Map vote will begin in %s seconds. ******", "kVoteMapHowToVote": "****** You can vote for the map you want by typing vote # ******", "kVoteMapWinner": "****** Voting has ended, %s won with %s votes. ", "kExtendDuration": 15, "kVoteMapMapListing": "****** vote %s for %s ", "kVoteMapTimeLeft": "****** %.1f seconds are left to vote ******", "kVoteNotifyDelay": 6, "kVotingDuration": 30, "kVoteMapCurrentMapVotes": "****** %s votes for %s (to vote, type vote %s) ******", "kVoteMapCancelled": "****** Map vote has been cancelled. ******", "kVoteMapAutomaticChange": "****** Advancing to next map in mapcycle. ", "kVoteMapInsufficientMaps": "****** Not enough maps for a vote. ******", "kVoteChangeDelay": 4, "kVoteMapMinimumNotMet": "******%s had the most votes with %s, but the minimum required is %s.******", "kVoteMapTie": "****** Voting has ended with a tie, A new vote will start in %s seconds ******", "kRoundEndDelay": 2, "kVoteMinimumPercentage": 25, "kMaximumExtends": 3, "kRTVMinimumPercentage": 50 }, "MOTD": { "kMOTDMessageRevision": 1, "kMOTDMessagesPerTick": 5, "kMOTDMessageDelay": 6, "kMOTDMessage": [ "********************************************************************", "* Commands: These can be entered via chat or the console (~) ", "* rtv: To initiate a map vote aka Rock The Vote ", "* random: To vote for auto-random teams for next 30 minutes ", "* timeleft: To display the time until next map vote ", "* surrender: To initiate or vote in a surrender vote for your team. ", "* acceptmotd: To accept and suppress this message ", "* stuck: To have your player teleported to be unstuck. ", "********************************************************************" ] }, "Unstuck": { "kMinimumWaitTime": 5, "kTimeBetweenUntucks": 30, "kUnstuckAmount": 0.5 }, "BaseAdminCommands": [ ] }<!--c2--></div><!--ec2-->
Additional info: It loads through server.lua. Server is stock, no other mods.
Here's my Server.lua:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// ======= Copyright (c) 2003-2012, Unknown Worlds Entertainment, Inc. All rights reserved. ===== // // lua\Server.lua // // Created by: Charlie Cleveland (charlie@unknownworlds.com) // // ========= For more information, visit us at http://www.unknownworlds.com =====================
// Set the name of the VM for debugging decoda_name = "Server"
// map name, group name and values keys for all map entities loaded to // be created on game reset Server.mapLoadLiveEntityValues = table.array(100)
// Game entity indices created from mapLoadLiveEntityValues. They are all deleted // on and rebuilt on map reset. Server.mapLiveEntities = table.array(32)
// Map entities are stored here in order of their priority so they are loaded // in the correct order (Structure assumes that Gamerules exists upon loading for example). Server.mapPostLoadEntities = table.array(32)
// Recent chat messages are stored on the server. Server.recentChatMessages = CreateRingBuffer(20) local chatMessageCount = 0
function Server.AddChatToHistory(message, playerName, steamId, teamNumber, teamOnly)
chatMessageCount = chatMessageCount + 1 Server.recentChatMessages:Insert({ id = chatMessageCount, message = message, player = playerName, steamId = steamId, team = teamNumber, teamOnly = teamOnly })
end
/** * Map entities with a higher priority are loaded first. */ local kMapEntityLoadPriorities = { } kMapEntityLoadPriorities[NS2Gamerules.kMapName] = 1 local function GetMapEntityLoadPriority(mapName)
local priority = 0
if kMapEntityLoadPriorities[mapName] then priority = kMapEntityLoadPriorities[mapName] end
return priority
end
// filter the entities which are explore mode only // MUST BE GLOBAL - overridden by mods function GetLoadEntity(mapName, groupName, values) return values.onlyexplore ~= true end
// MUST BE GLOBAL - overridden by mods function GetCreateEntityOnStart(mapName, groupName, values)
return mapName ~= "prop_static" and mapName ~= "light_point" and mapName ~= "light_spot" and mapName ~= "light_ambient" and mapName ~= "color_grading" and mapName ~= "cinematic" and mapName ~= "skybox" and mapName ~= "pathing_settings" and mapName ~= ReadyRoomSpawn.kMapName and mapName ~= "ambient_sound" and mapName ~= Reverb.kMapName and mapName ~= Hive.kMapName and mapName ~= CommandStation.kMapName and mapName ~= Cyst.kMapName and mapName ~= InfantryPortal.kMapName
end
// MUST BE GLOBAL - overridden by mods function GetLoadSpecial(mapName, groupName, values)
local success = false
if mapName == Hive.kMapName or mapName == CommandStation.kMapName then
success = values.startsOnMessage ~= nil and values.startsOnMessage ~= "" if success then
PrecacheAsset(values.cinematicName) local entity = Server.CreateEntity(ServerParticleEmitter.kMapName, values) if entity then entity:SetMapEntity() end
end
end
return success
end
local function DumpServerEntity(mapName, groupName, values)
local function LoadServerMapEntity(mapName, groupName, values)
if not GetLoadEntity(mapName, groupName, values) then return end
// Skip the classes that are not true entities and are handled separately // on the client. if GetCreateEntityOnStart(mapName, groupName, values) then
local entity = Server.CreateEntity(mapName, values) if entity then
entity:SetMapEntity()
// Map Entities with LiveMixin can be destroyed during the game. if HasMixin(entity, "Live") then
// Insert into table so we can re-create them all on map post load (and game reset) table.insert(Server.mapLoadLiveEntityValues, {mapName, groupName, values})
// Delete it because we're going to recreate it on map reset table.insert(Server.mapLiveEntities, entity:GetId())
end
// $AS FIXME: We are special caasing techPoints for pathing right now :/ if (mapName == "tech_point") or values.pathInclude == true then
local coords = values.angles:GetCoords(values.origin) Pathing.CreatePathingObject(entity:GetModelName(), coords)
end
local renderModelCommAlpha = GetAndCheckValue(values.commAlpha, 0, 1, "commAlpha", 1, true) local blocksPlacement = groupName == kCommanderInvisibleGroupName or groupName == kCommanderNoBuildGroupName
if HasMixin(entity, "Model") and (renderModelCommAlpha < 1 or blocksPlacement) then entity:SetPhysicsGroup(PhysicsGroup.CommanderPropsGroup) end
end
//DumpServerEntity(mapName, groupName, values)
end
if not GetLoadSpecial(mapName, groupName, values) then
// Allow the MapEntityLoader to load it if all else fails. LoadMapEntity(mapName, groupName, values)
end
end
/** * Called as the map is being loaded to create the entities. */ function OnMapLoadEntity(mapName, groupName, values)
local priority = GetMapEntityLoadPriority(mapName) if Server.mapPostLoadEntities[priority] == nil then Server.mapPostLoadEntities[priority] = { } end
if mapName == "tech_point" then Pathing.AddFillPoint(values.origin) end
// Any geometry in kCommanderInvisibleGroupName or kCommanderNoBuildGroupName shouldn't interfere with selection or other commander actions Shared.PreLoadSetGroupPhysicsId(kCommanderInvisibleGroupName, PhysicsGroup.CommanderPropsGroup) Shared.PreLoadSetGroupPhysicsId(kCommanderNoBuildGroupName, PhysicsGroup.CommanderPropsGroup)
// Don't have bullets collide with collision geometry Shared.PreLoadSetGroupPhysicsId(kCollisionGeometryGroupName, PhysicsGroup.CollisionGeometryGroup)
// Delete any map entities that have been created for index, mapEntId in ipairs(Server.mapLiveEntities) do
local ent = Shared.GetEntity(mapEntId) if ent then DestroyEntity(ent) end
end
Server.mapLiveEntities = { }
end
function CreateLiveMapEntities()
// Create new Live map entities for index, triple in ipairs(Server.mapLoadLiveEntityValues) do
// {mapName, groupName, keyvalues} local entity = Server.CreateEntity(triple[1], triple[3])
// Store so we can track it during the game and delete it on game reset if not dead yet table.insert(Server.mapLiveEntities, entity:GetId())
end
end
local function CheckForDuplicateLocations()
local locations = GetLocations() for _, checkLocation in ipairs(locations) do
for _, dupLocation in ipairs(locations) do
// Don't check the same exact location against itself. if checkLocation ~= dupLocation then
if checkLocation:GetOrigin() == dupLocation:GetOrigin() then Print("Duplicate location detected: " .. dupLocation:GetName()) end
end
end
end
end
/** * Callback handler for when the map is finished loading. */ local function OnMapPostLoad()
// Higher priority entities are loaded first. local highestPriority = 0 for k, v in pairs(kMapEntityLoadPriorities) do if v > highestPriority then highestPriority = v end end
for i = highestPriority, 0, -1 do
if Server.mapPostLoadEntities[i] then
for k, entityData in ipairs(Server.mapPostLoadEntities[i]) do LoadServerMapEntity(entityData.MapName, entityData.GroupName, entityData.Values) end
end
end
Server.mapPostLoadEntities = { }
InitializePathing() CheckForDuplicateLocations()
GetGamerules():OnMapPostLoad()
end
function GetTechTree(teamNumber)
if GetGamerules() then
local team = GetGamerules():GetTeam(teamNumber) if team and team.GetTechTree then return team:GetTechTree() end
end
return nil
end
/** * Called by the engine to test if a player (represented by the entity they are * controlling) can hear another player for the purposes of voice chat. */ local function OnCanPlayerHearPlayer(listener, speaker) return GetGamerules():GetCanPlayerHearPlayer(listener, speaker) end
<!--quoteo(post=2047184:date=Dec 17 2012, 05:58 PM:name=wireaudio)--><div class='quotetop'>QUOTE (wireaudio @ Dec 17 2012, 05:58 PM) <a href="index.php?act=findpost&pid=2047184"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Dude its obvious. If you set it to 3 it wont start until there are 3 players on each team. In you want it to start at 2 or 1 just set the minimum to that.<!--QuoteEnd--></div><!--QuoteEEnd-->
Dude thanks for the totally sarcastic response, radical! There is always the chance that, like with many such things, there is a mechanism to disable this function when the server has few users connected. Why not just let someone who knows answer. I would like for the value to be maybe 4, but for that only to kick in if there are 10+ people on the server. Even better would be if it be set to start the round when 50% or 80% of connected users have joined a team ( i.e. min players per team = 25% or 40% connected users).
Bud, if your initial question made sense i would answer otherwise, but you are asking same question over and over. However, your question above now finally makes sense.
I replaced DAKGetClientLevelSufficient(client, player) with DAKGetClientLevelSufficientID(client, playerId) in plugin_baseadmincommands just like in the BAN command. The commands now work fine. Haven't tested admin levels if they work.
I did this because i noticed all functions where DAKGetClientLevelSufficient(client, player) was used always pass a playerId to them. I didn't see a reason for DAKGetClientLevelSufficient(client, player) so i replaced it.
Most likely i am wrong, but i'm waiting to see what xDragon says.
Hmmm yea its passing player objects and not clients, ill try to get that fixed soonish. As for the tournament pub mode thing, Ill add a minimum player req. for it to enable and prevent game start. Ive also been re-working some other things slightly to improve other parts of the admin system, so it may take me a little additional time to fix this.
I just published the update to GitHub for this, should fix the admin levels issue, fix banning by steamid and also allows you to edit the ban file while the server is running. This also updates some of the plugins and the ServerUpdate event to work slightly differently. There are some new configuration options added (can adjust chat commands for plugins via config, also can change the name on the messages sent to players). I have fully updated the readme now so that should be current.
This is a little bit of a testing version so I didnt update steam workshop just yet, but I dont (*hoping*) think there is any issues.
any chance you could make it possible to use a common banlist?
I have 5 servers on different machines or with different config folders and manually moving bans is a pain in the ass.
Or even better, allow it to pull bans from the web just like it does for the admins. and you can have it load an url when a new ban is added and parse variables to it. (www.website.com/ban.php?var1=bla&var2=bla etc).
the variables are name, admin name, ban time, reason, ns2 steam id.
When I am back in the states I can create a php script which should allow for bans to be placed into a sql database. Then the bans made in the server can be added to the sourcebans system (for example) and then DAK could prase a php output page listing all current bans that are in effect.
Comments
Ok cool so we grant sv_ejectimmunity to people who should be immune from a com kick, very nice!
sv_commban
sv_uncommban
sv_listcommbans
to call global 'CreateServerAdminCommand' (a nil value)
Call stack:
#1: lua/plugins/plugin_baseadmincommands.lua:613
PrintHelpForCommand = function
GetPlayerList = function
AllPlayers = function
GetPlayerMatchingSteamId = function
GetPlayerMatchingName = function
GetPlayerMatching = function
PrintStatus = function
OnCommandChangeMap = function
OnCommandSVReset = function
OnCommandSVrrall = function
OnCommandSVRandomall = function
SwitchTeam = function
Eject = function
Kick = function
GetChatMessage = function
Say = function
TeamSay = function
PlayerSay = function
Slay = function
SetPassword = function
bannedPlayers = { }
bannedPlayersFileName = "config://BannedPlayers.json"
LoadBannedPlayers = function
SaveBannedPlayers = function
OnConnectCheckBan = function
Ban = function
UnBan = function
ListBans = function
PLogAll = function
PEndLog = function
AutoBalance = function
EnableEventTesting = function
#2: scriptLoad [C]:-1
#3: Load lua/EventTester.lua:185
fileName = "lua/plugins/plugin_baseadmincommands.lua"
#4: LoadPlugins lua/DAKLoader_ServerAdminCommands.lua:117
(for index) = 1
(for limit) = 7
(for step) = 1
i = 1
filename = "lua/plugins/plugin_baseadmincommands.lua"
#5: lua/DAKLoader_ServerAdminCommands.lua:124
OnCommandRCON = function
OnCommandAllTalk = function
OnCommandListPlugins = function
OnCommandListMap = function
OnCommandCheats = function
OnCommandKillServer = function
LoadPlugins = function
#6: scriptLoad [C]:-1
#7: Load lua/EventTester.lua:185
fileName = "lua/DAKLoader_ServerAdminCommands.lua"
#8: lua/DAKLoader_Server.lua:71
#9: scriptLoad [C]:-1
#10: Load lua/EventTester.lua:185
fileName = "lua/DAKLoader_Server.lua"
#11: lua/DAKLoader.lua:45
#12: scriptLoad [C]:-1
#13: Load lua/EventTester.lua:185
fileName = "lua/DAKLoader.lua"
#14: lua/Server.lua:25<!--c2--></div><!--ec2-->
Awesome job xDragon.
I have a request as well.
xDragon, Is there any way it can pull messages and MOTD from a web site as well? I already made a script that uses DAK to integrate with IP Board (and phpbb3 coming very soon) and i'd like to add the ability to generate the MOTD and messages on the fly.
Even better, if it can pull the ENTIRE config file remotely i can make a kickass php script that admins can use as a web interface to configure DAK. All free / open source as usual :).
Although i'm clueless when it comes to coding , i'm pretty curious by nature and browsed the lua pages of the wiki, i came across a "Client.ShowWebpage" function. Is there a way to make a website popup in the steamoverlay of the client if he types something in the console or the chat ? That would be great tool for communities forums etc
<!--quoteo(post=2043399:date=Dec 11 2012, 05:37 AM:name=wireaudio)--><div class='quotetop'>QUOTE (wireaudio @ Dec 11 2012, 05:37 AM) <a href="index.php?act=findpost&pid=2043399"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->I have a request as well.
xDragon, Is there any way it can pull messages and MOTD from a web site as well? I already made a script that uses DAK to integrate with IP Board (and phpbb3 coming very soon) and i'd like to add the ability to generate the MOTD and messages on the fly.
Even better, if it can pull the ENTIRE config file remotely i can make a kickass php script that admins can use as a web interface to configure DAK. All free / open source as usual :).<!--QuoteEnd--></div><!--QuoteEEnd-->
And if address is empty quotes server can just use local config?
Exactly, until the mod is whitelisted we are very limited in what we can do else our server will be harder to find and become populated.
Also, I noticed that the default config file has a few variables that are not mentioned in the readme, one in particular is "kPregameLength" in the "MapVote" plugin, I'm guessing this is self-explanatory but it would be good to have it in the readme anyway.
Edit: I see the tournamentmode variables are explained in the readme, ignore that question, but answer this one if you'd be so kind:
If tournamentmode is on pub mode with min players set to 3, will the game still start 1v1, 2v2, 3v3 if there are <7 players on the server?
Yeh so my question is, if the min players is set to 3, and there are only 2 players on the server, will the round never start? Thanks.
Dude its obvious. If you set it to 3 it wont start until there are 3 players on each team. In you want it to start at 2 or 1 just set the minimum to that.
I'm getting errors left and right when using some admin commands.
I did a fresh install, added 1 admin, i didn't change anything from default configuration.
I have 5 servers with the same issue, running on both windows and linux.
I am using the latest release from here: <a href="https://github.com/xToken/DAK" target="_blank">https://github.com/xToken/DAK</a> and 233 server version (latest as of now). Fresh install, no other mods, no other changes. All files from DAK LUA folder are in the ns2/lua folder and server.lua is loading DAKLoader.lua.
UPDATE: Not all commands fail. Ban for example works fine. sv_listadmins works as well. It's kick, slay and a few others that don't work.
For example, when you do KICK it gives this error:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->[Server] Script Error #1: lua/DAKLoader_ServerAdmin.lua:128: attempt to call method 'GetUserId' (a nil value)
Call stack:
#1: DAKGetClientLevel lua/DAKLoader_ServerAdmin.lua:128
client = ReadyRoomPlayer-1890 {buncha vars i removed}
#2: DAKGetClientLevelSufficient lua/DAKLoader_ServerAdmin.lua:136
client = ServerClient { }
targetclient = ReadyRoomPlayer-1890 {buncha vars i removed}
#3: lua/plugins/plugin_baseadmincommands.lua:270
client = ServerClient { }
playerId = "1"
player = ReadyRoomPlayer-1890 {buncha vars i removed}
#4: (tail call):-1<!--c2--></div><!--ec2-->
lua/DAKLoader_ServerAdmin.lua:128 has the following function
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->function DAKGetClientLevel(client)
local steamId = client:GetUserId() (<- THIS IS LINE 128)
if steamId == nil then return 0 end
return DAKGetSteamIDLevel(steamId)
end<!--c2--></div><!--ec2-->
#2 is DAKLoader_ServerAdmin.lua:136 with the following code:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->function DAKGetClientLevelSufficient(client, targetclient)
if client == nil then return true end
if targetclient == nil then return false end
return DAKGetClientLevel(client) >= DAKGetClientLevel(targetclient) (<- THIS IS LINE 136)
end<!--c2--></div><!--ec2-->
#3 is plugins/plugin_baseadmincommands.lua:270
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local function Kick(client, playerId)
local player = GetPlayerMatching(playerId)
if not DAKGetClientLevelSufficient(client, player) then (<- THIS IS LINE 270)
return
end
if player then
Server.DisconnectClient(Server.GetOwner(player))
else
ServerAdminPrint(client, "No matching player")
end
end<!--c2--></div><!--ec2-->
My ServerAdmins was setup like this:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->{
"groups": {
"trial_admin": {
"commands": [ "sv_hasreserve", "sv_changemap", "sv_ban", "sv_reset", "sv_kick", "sv_switchteam", "sv_randomall", "sv_rrall", "sv_eject", "sv_help", "sv_status", "sv_statusip", "sv_say", "sv_tsay", "sv_psay", "sv_listplugins", "sv_maps", "sv_randomon", "sv_randomoff", "sv_votemap", "sv_cancelmapvote", "sv_listadmins", "sv_surrendervote", "sv_cancelsurrendervote", "sv_afkimmune", "sv_alltalk" ],
"level": 5,
"type": "allowed"
},
"basic_admin": {
"commands": [ ],
"level": 7,
"type": "disallowed"
},
"admin_group": {
"commands": [ ],
"level": 10,
"type": "disallowed"
},
"simple_reserved": {
"commands": [ "sv_listadmins", "sv_hasreserve" ],
"level": 2,
"type": "allowed"
}
},
"users": {
"Tech": {
"id": 5922566,
"groups": [ "admin_group" ]
}
}
}<!--c2--></div><!--ec2-->
My config file is default:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->{
"VoteSurrender": {
"kVoteSurrenderAlertDelay": 20,
"kVoteSurrenderVotingTime": 120,
"kVoteSurrenderMinimumPercentage": 60
},
"AFKKicker": {
"kAFKKickWarning1": 30,
"kAFKKickMessage": "%s kicked from the server for idling more than %d seconds.",
"kAFKKickDelay": 150,
"kAFKKickClientMessage": "You are being kicked for idling for more than %d seconds.",
"kAFKKickWarning2": 10,
"kAFKKickDisconnectReason": "Kicked from the server for idling more than %d seconds.",
"kAFKKickWarningMessage1": "You will be kicked in %d seconds for idling.",
"kAFKKickReturnMessage": "You are no longer flagged as idle.",
"kAFKKickWarningMessage2": "You will be kicked in %d seconds for idling.",
"kAFKKickMinimumPlayers": 5,
"kAFKKickCheckDelay": 5
},
"VoteRandom": {
"kVoteRandomInstantly": false,
"kVoteRandomConnectAlert": "Random teams are enabled, you are being randomed to a team.",
"kVoteRandomMinimumPercentage": 60,
"kVoteRandomDuration": 30,
"kVoteRandomVoteCountAlert": "%s voted for random teams. (%s votes, needed %s).",
"kVoteRandomEnabledDuration": "Random teams have been enabled for the next %s Minutes",
"kVoteRandomEnabled": "Random teams have been enabled, the round will restart."
},
"DAKLoader": {
"GamerulesExtensions": true,
"GamerulesClassName": "NS2Gamerules",
"OverrideInterp": {
"kInterp": 100,
"kEnabled": false
},
"LoadFromServerLUA": true,
"ServerAdmin": {
"kQueryURL": "",
"kMapChangeDelay": 5,
"kUpdateDelay": 60
},
"kPluginsList": [ "afkkick", "baseadmincommands", "mapvote", "motd", "unstuck", "voterandom", "votesurrender" ],
"kDelayedServerUpdate": 1,
"kDelayedClientConnect": 2
},
"MapVote": {
"kVoteMapRockTheVote": "%s rock'd the vote. (%s votes, needed %s).",
"kMapsToSelect": 7,
"kPregameLength": 15,
"kDontRepeatFor": 4,
"kVoteMapExtended": "****** Voting has ended, extending current map for %s minutes. ",
"kVoteMapNoWinner": "****** Voting has ended, no map won. ",
"kVoteMapStarted": "******* Map vote has begun. (%s%% votes needed to win) ******",
"kVoteStartDelay": 8,
"kVoteMapBeginning": "****** Map vote will begin in %s seconds. ******",
"kVoteMapHowToVote": "****** You can vote for the map you want by typing vote # ******",
"kVoteMapWinner": "****** Voting has ended, %s won with %s votes. ",
"kExtendDuration": 15,
"kVoteMapMapListing": "****** vote %s for %s ",
"kVoteMapTimeLeft": "****** %.1f seconds are left to vote ******",
"kVoteNotifyDelay": 6,
"kVotingDuration": 30,
"kVoteMapCurrentMapVotes": "****** %s votes for %s (to vote, type vote %s) ******",
"kVoteMapCancelled": "****** Map vote has been cancelled. ******",
"kVoteMapAutomaticChange": "****** Advancing to next map in mapcycle. ",
"kVoteMapInsufficientMaps": "****** Not enough maps for a vote. ******",
"kVoteChangeDelay": 4,
"kVoteMapMinimumNotMet": "******%s had the most votes with %s, but the minimum required is %s.******",
"kVoteMapTie": "****** Voting has ended with a tie, A new vote will start in %s seconds ******",
"kRoundEndDelay": 2,
"kVoteMinimumPercentage": 25,
"kMaximumExtends": 3,
"kRTVMinimumPercentage": 50
},
"MOTD": {
"kMOTDMessageRevision": 1,
"kMOTDMessagesPerTick": 5,
"kMOTDMessageDelay": 6,
"kMOTDMessage": [ "********************************************************************", "* Commands: These can be entered via chat or the console (~) ", "* rtv: To initiate a map vote aka Rock The Vote ", "* random: To vote for auto-random teams for next 30 minutes ", "* timeleft: To display the time until next map vote ", "* surrender: To initiate or vote in a surrender vote for your team. ", "* acceptmotd: To accept and suppress this message ", "* stuck: To have your player teleported to be unstuck. ", "********************************************************************" ]
},
"Unstuck": {
"kMinimumWaitTime": 5,
"kTimeBetweenUntucks": 30,
"kUnstuckAmount": 0.5
},
"BaseAdminCommands": [ ]
}<!--c2--></div><!--ec2-->
Additional info: It loads through server.lua. Server is stock, no other mods.
Here's my Server.lua:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// ======= Copyright (c) 2003-2012, Unknown Worlds Entertainment, Inc. All rights reserved. =====
//
// lua\Server.lua
//
// Created by: Charlie Cleveland (charlie@unknownworlds.com)
//
// ========= For more information, visit us at http://www.unknownworlds.com =====================
// Set the name of the VM for debugging
decoda_name = "Server"
Script.Load("lua/Shared.lua")
Script.Load("lua/MapEntityLoader.lua")
Script.Load("lua/Button.lua")
Script.Load("lua/TechData.lua")
Script.Load("lua/TargetCache.lua")
Script.Load("lua/MarineTeam.lua")
Script.Load("lua/AlienTeam.lua")
Script.Load("lua/DAKLoader.lua")
Script.Load("lua/Bot.lua")
Script.Load("lua/VoteManager.lua")
Script.Load("lua/ServerConfig.lua")
Script.Load("lua/ServerAdmin.lua")
Script.Load("lua/ServerAdminCommands.lua")
Script.Load("lua/ServerWebInterface.lua")
Script.Load("lua/MapCycle.lua")
Script.Load("lua/ConsistencyConfig.lua")
Script.Load("lua/ConsoleCommands_Server.lua")
Script.Load("lua/NetworkMessages_Server.lua")
Script.Load("lua/dkjson.lua")
Script.Load("lua/InfestationMap.lua")
Script.Load("lua/NetworkDebug.lua")
Server.readyRoomSpawnList = table.array(32)
Server.infantryPortalSpawnPoints = table.array(10)
Server.cystSpawnPoints = table.array(32)
// map name, group name and values keys for all map entities loaded to
// be created on game reset
Server.mapLoadLiveEntityValues = table.array(100)
// Game entity indices created from mapLoadLiveEntityValues. They are all deleted
// on and rebuilt on map reset.
Server.mapLiveEntities = table.array(32)
// Map entities are stored here in order of their priority so they are loaded
// in the correct order (Structure assumes that Gamerules exists upon loading for example).
Server.mapPostLoadEntities = table.array(32)
// Recent chat messages are stored on the server.
Server.recentChatMessages = CreateRingBuffer(20)
local chatMessageCount = 0
function Server.AddChatToHistory(message, playerName, steamId, teamNumber, teamOnly)
chatMessageCount = chatMessageCount + 1
Server.recentChatMessages:Insert({ id = chatMessageCount, message = message, player = playerName,
steamId = steamId, team = teamNumber, teamOnly = teamOnly })
end
/**
* Map entities with a higher priority are loaded first.
*/
local kMapEntityLoadPriorities = { }
kMapEntityLoadPriorities[NS2Gamerules.kMapName] = 1
local function GetMapEntityLoadPriority(mapName)
local priority = 0
if kMapEntityLoadPriorities[mapName] then
priority = kMapEntityLoadPriorities[mapName]
end
return priority
end
// filter the entities which are explore mode only
// MUST BE GLOBAL - overridden by mods
function GetLoadEntity(mapName, groupName, values)
return values.onlyexplore ~= true
end
// MUST BE GLOBAL - overridden by mods
function GetCreateEntityOnStart(mapName, groupName, values)
return mapName ~= "prop_static"
and mapName ~= "light_point"
and mapName ~= "light_spot"
and mapName ~= "light_ambient"
and mapName ~= "color_grading"
and mapName ~= "cinematic"
and mapName ~= "skybox"
and mapName ~= "pathing_settings"
and mapName ~= ReadyRoomSpawn.kMapName
and mapName ~= "ambient_sound"
and mapName ~= Reverb.kMapName
and mapName ~= Hive.kMapName
and mapName ~= CommandStation.kMapName
and mapName ~= Cyst.kMapName
and mapName ~= InfantryPortal.kMapName
end
// MUST BE GLOBAL - overridden by mods
function GetLoadSpecial(mapName, groupName, values)
local success = false
if mapName == Hive.kMapName or mapName == CommandStation.kMapName then
table.insert(Server.mapLoadLiveEntityValues, { mapName, groupName, values })
success = true
elseif mapName == ReadyRoomSpawn.kMapName then
local entity = ReadyRoomSpawn()
entity:OnCreate()
LoadEntityFromValues(entity, values)
table.insert(Server.readyRoomSpawnList, entity)
success = true
elseif mapName == InfantryPortal.kMapName then
table.insert(Server.infantryPortalSpawnPoints, values.origin)
success = true
elseif mapName == Cyst.kMapName then
table.insert(Server.cystSpawnPoints, values.origin)
success = true
elseif mapName == "pathing_settings" then
ParsePathingSettings(values)
success = true
elseif mapName == "cinematic" then
success = values.startsOnMessage ~= nil and values.startsOnMessage ~= ""
if success then
PrecacheAsset(values.cinematicName)
local entity = Server.CreateEntity(ServerParticleEmitter.kMapName, values)
if entity then
entity:SetMapEntity()
end
end
end
return success
end
local function DumpServerEntity(mapName, groupName, values)
Print("------------ %s ------------", ToString(mapName))
for key, value in pairs(values) do
Print("[%s] %s", ToString(key), ToString(value))
end
Print("---------------------------------------------")
end
local function LoadServerMapEntity(mapName, groupName, values)
if not GetLoadEntity(mapName, groupName, values) then
return
end
// Skip the classes that are not true entities and are handled separately
// on the client.
if GetCreateEntityOnStart(mapName, groupName, values) then
local entity = Server.CreateEntity(mapName, values)
if entity then
entity:SetMapEntity()
// Map Entities with LiveMixin can be destroyed during the game.
if HasMixin(entity, "Live") then
// Insert into table so we can re-create them all on map post load (and game reset)
table.insert(Server.mapLoadLiveEntityValues, {mapName, groupName, values})
// Delete it because we're going to recreate it on map reset
table.insert(Server.mapLiveEntities, entity:GetId())
end
// $AS FIXME: We are special caasing techPoints for pathing right now :/
if (mapName == "tech_point") or values.pathInclude == true then
local coords = values.angles:GetCoords(values.origin)
Pathing.CreatePathingObject(entity:GetModelName(), coords)
end
local renderModelCommAlpha = GetAndCheckValue(values.commAlpha, 0, 1, "commAlpha", 1, true)
local blocksPlacement = groupName == kCommanderInvisibleGroupName or
groupName == kCommanderNoBuildGroupName
if HasMixin(entity, "Model") and (renderModelCommAlpha < 1 or blocksPlacement) then
entity:SetPhysicsGroup(PhysicsGroup.CommanderPropsGroup)
end
end
//DumpServerEntity(mapName, groupName, values)
end
if not GetLoadSpecial(mapName, groupName, values) then
// Allow the MapEntityLoader to load it if all else fails.
LoadMapEntity(mapName, groupName, values)
end
end
/**
* Called as the map is being loaded to create the entities.
*/
function OnMapLoadEntity(mapName, groupName, values)
local priority = GetMapEntityLoadPriority(mapName)
if Server.mapPostLoadEntities[priority] == nil then
Server.mapPostLoadEntities[priority] = { }
end
if mapName == "tech_point" then
Pathing.AddFillPoint(values.origin)
end
table.insert(Server.mapPostLoadEntities[priority], { MapName = mapName, GroupName = groupName, Values = values })
end
function OnMapPreLoad()
Shared.PreLoadSetGroupNeverVisible(kCollisionGeometryGroupName)
Shared.PreLoadSetGroupPhysicsId(kNonCollisionGeometryGroupName, 0)
Shared.PreLoadSetGroupNeverVisible(kCommanderBuildGroupName)
Shared.PreLoadSetGroupPhysicsId(kCommanderBuildGroupName, PhysicsGroup.CommanderBuildGroup)
// Any geometry in kCommanderInvisibleGroupName or kCommanderNoBuildGroupName shouldn't interfere with selection or other commander actions
Shared.PreLoadSetGroupPhysicsId(kCommanderInvisibleGroupName, PhysicsGroup.CommanderPropsGroup)
Shared.PreLoadSetGroupPhysicsId(kCommanderNoBuildGroupName, PhysicsGroup.CommanderPropsGroup)
// Don't have bullets collide with collision geometry
Shared.PreLoadSetGroupPhysicsId(kCollisionGeometryGroupName, PhysicsGroup.CollisionGeometryGroup)
// Clear spawn points
Server.readyRoomSpawnList = {}
Server.mapLoadLiveEntityValues = {}
Server.mapLiveEntities = {}
end
function DestroyLiveMapEntities()
// Delete any map entities that have been created
for index, mapEntId in ipairs(Server.mapLiveEntities) do
local ent = Shared.GetEntity(mapEntId)
if ent then
DestroyEntity(ent)
end
end
Server.mapLiveEntities = { }
end
function CreateLiveMapEntities()
// Create new Live map entities
for index, triple in ipairs(Server.mapLoadLiveEntityValues) do
// {mapName, groupName, keyvalues}
local entity = Server.CreateEntity(triple[1], triple[3])
// Store so we can track it during the game and delete it on game reset if not dead yet
table.insert(Server.mapLiveEntities, entity:GetId())
end
end
local function CheckForDuplicateLocations()
local locations = GetLocations()
for _, checkLocation in ipairs(locations) do
for _, dupLocation in ipairs(locations) do
// Don't check the same exact location against itself.
if checkLocation ~= dupLocation then
if checkLocation:GetOrigin() == dupLocation:GetOrigin() then
Print("Duplicate location detected: " .. dupLocation:GetName())
end
end
end
end
end
/**
* Callback handler for when the map is finished loading.
*/
local function OnMapPostLoad()
// Higher priority entities are loaded first.
local highestPriority = 0
for k, v in pairs(kMapEntityLoadPriorities) do
if v > highestPriority then highestPriority = v end
end
for i = highestPriority, 0, -1 do
if Server.mapPostLoadEntities[i] then
for k, entityData in ipairs(Server.mapPostLoadEntities[i]) do
LoadServerMapEntity(entityData.MapName, entityData.GroupName, entityData.Values)
end
end
end
Server.mapPostLoadEntities = { }
InitializePathing()
CheckForDuplicateLocations()
GetGamerules():OnMapPostLoad()
end
function GetTechTree(teamNumber)
if GetGamerules() then
local team = GetGamerules():GetTeam(teamNumber)
if team and team.GetTechTree then
return team:GetTechTree()
end
end
return nil
end
/**
* Called by the engine to test if a player (represented by the entity they are
* controlling) can hear another player for the purposes of voice chat.
*/
local function OnCanPlayerHearPlayer(listener, speaker)
return GetGamerules():GetCanPlayerHearPlayer(listener, speaker)
end
Event.Hook("MapPreLoad", OnMapPreLoad)
Event.Hook("MapPostLoad", OnMapPostLoad)
Event.Hook("MapLoadEntity", OnMapLoadEntity)
Event.Hook("CanPlayerHearPlayer", OnCanPlayerHearPlayer)<!--c2--></div><!--ec2-->
Dude thanks for the totally sarcastic response, radical! There is always the chance that, like with many such things, there is a mechanism to disable this function when the server has few users connected. Why not just let someone who knows answer. I would like for the value to be maybe 4, but for that only to kick in if there are 10+ people on the server. Even better would be if it be set to start the round when 50% or 80% of connected users have joined a team ( i.e. min players per team = 25% or 40% connected users).
I did this because i noticed all functions where DAKGetClientLevelSufficient(client, player) was used always pass a playerId to them. I didn't see a reason for DAKGetClientLevelSufficient(client, player) so i replaced it.
Most likely i am wrong, but i'm waiting to see what xDragon says.
This is a little bit of a testing version so I didnt update steam workshop just yet, but I dont (*hoping*) think there is any issues.
I have 5 servers on different machines or with different config folders and manually moving bans is a pain in the ass.
Or even better, allow it to pull bans from the web just like it does for the admins. and you can have it load an url when a new ban is added and parse variables to it. (www.website.com/ban.php?var1=bla&var2=bla etc).
the variables are name, admin name, ban time, reason, ns2 steam id.