Hey so ive been learning LUA and hub scripting for the past couple of weeks and so far its all been pretty good. I help run a Hub at a university with about 200-300 people on a local network. We're running fresh stuff 3 and some other scripts for something, but theres a feature i would like to implement. Call of Duty 4: Modern Warfare. Basically thats the game everyone on the hub plays. But currently its annoying as you ask in hub chat if anyone is playing etc etc. Basically im wondering if there are any scripts/bots that can sit on a lan and watch for game servers. Even something where you tell it the IP's to watch and it reports back if there is a server running would be good. Last year somone had a user script where you typed "!cod" and it would reply to main chat if there was a server and how many people were playing on a certain IP address, so i know it can be done, but its beyond by abilities so far and also that script could have done with being server sided so it could have stopped the main chat being clogged up with "!cod" spam. So in summary im looking for a script or any help in making a script that watches for local CoD4 servers. Any help would be much appreciated. Thanks.
You should read the forum, look at http://forum.ptokax.org/index.php?topic=8549.0 (http://forum.ptokax.org/index.php?topic=8549.0)
But the plugin isnt ready yet from HLSW.. so i belive you have to wait. :)
Ok so after looking around and finding nothing i could use i finally got my act together and managed to code my own announce script. Its a bit of a hybrid of languages using Java, LUA and batch scripts to do something i know can be done alot more elegantly but it works for what we use it for.
I suppose ill post the code first then a short description of how it works.
Servers LUA code:
botName = "CoD4Bot" -- The bots name
directory = Core.GetPtokaXPath().."/scripts/Cod/" -- The location of the script Assosiated data
function OpConnected(user) -- Opperator connects, do the same as any User
UserConnected(user)
end
function RegConnected(user) -- Registered connects, do the same as any User
UserConnected(user)
end
function UserConnected(user)
AddUSC(user, "1 2", "CoD4 Servers", "<%[mynick]> !cod") -- Add the right click command
end
function AddUSC(user, mode, menu, cstr) -- Shortcut funtion for adding right click commands
Core.SendToUser(user, "$UserCommand "..mode.." "..menu.."$"..cstr.."|")
end
function ChatArrival(user, data)
if (string.sub(data, 1, 1) == "<" ) then
data=string.sub(data, 1, string.len(data) -1)
t = {}
for k in string.gmatch(data, "%s+([^%s]+)") do
table.insert(t, k)
end -- The above takes the main chat message and converts it into the array 't'
local validComment = false
if t[1]=="!cod" then
validComment = true
cod(user,1)
end
if t[1]=="!cod4" then
validComment = true
cod(user,2)
end
if validComment then -- If a command was said then dont echo in main chat
return true
end
end
end
function cod(user,adv)
a,tempIPs = pcall(UpdateIps)
if a then
Ips = tempIPs
else
Ips = {}
end
checkIps()
if adv==1 then
text = "\n\n Current CoD4 Servers: "..#Servers.."\n\n"
if #Servers < 1 then
text = text.."None currently operational\n"
else
for key,value in pairs(Servers) do
text = text.."["..key.."] "..Servers[key]["ip"].." - "..Servers[key]["serverName"]
text = text.." - Players: "..Servers[key]["cPlayers"].."/"..Servers[key]["maxPlayers"].."\n"
end
end
Core.SendPmToNick(user.sNick, botName, text)
end
if adv==2 then
text = "\n\n Current CoD4 Servers: "..#Servers.."\n\n"
if #Servers < 1 then
text = text.."None currently operational\n"
else
for key,value in pairs(Servers) do
text = text.."-["..key.."]-\n"
text = text.."IP : "..Servers[key]["ip"].." \n"
text = text.."Name : "..Servers[key]["serverName"].." \n"
text = text.."Players : "..Servers[key]["cPlayers"].."/"..Servers[key]["maxPlayers"].."\n"
text = text.."Map : "..Servers[key]["map"].." \n"
text = text.."Gametype : "..Servers[key]["gameType"].." \n"
text = text.."Hardcore : "..Servers[key]["hardCore"].." \n"
text = text.."F-Fire : "..Servers[key]["friendlyFire"].." \n"
text = text.."\n"
end
end
Core.SendPmToNick(user.sNick, botName, text)
end
end
function checkIps()
Servers = {}
for key,ip in pairs(Ips) do
b,m = pcall(checkIP,ip)
if b then
if m == "ohi" then
else
table.insert(Servers, m)
end
end
end
end
function checkIP(ip)
io.input(directory..ip..".txt")
local t = {}
for line in io.lines() do
table.insert(t, line)
end
io.input():close()
if #t < 2 then
return "ohi"
end
local m = {}
local f = 1
for i = 1, string.len(t[2]) do
if string.sub(t[2], i, i) == "\\" then
f = f+1
else
if m[f] then
m[f] = m[f]..string.sub(t[2], i, i)
else
m[f] = ""..string.sub(t[2], i, i)
end
end
end
local gameType = ""
local serverName = ""
local map = ""
local maxPlayers = 0
local cPlayers = 0
local hardCore = "No"
local friendlyFire = "Disabled"
for key,value in pairs(m) do
if value == "ff" then
friendlyFire = getFriendlyFire(m[key+1].."")
end
if value == "hostname" then
serverName = m[key+1]
end
if value == "sv_maxclients" then
maxPlayers = m[key+1]
end
if value == "mapname" then
map = getMap(m[key+1])
end
if value == "clients" then
cPlayers = m[key+1]
end
if value == "hc" then
hardCore = "Yes"
end
if value == "gametype" then
gameType = getGameType(m[key+1])
end
end
entry = {
["serverName"] = serverName,
["ip"] = ip,
["gameType"] = gameType,
["map"] = map,
["maxPlayers"] = maxPlayers,
["cPlayers"] = cPlayers,
["hardCore"] = hardCore,
["friendlyFire"]=friendlyFire,
}
return entry
end
function getFriendlyFire(ff)
if ff == "1" then
return "Enabled"
end
if ff == "2" then
return "Reflect"
end
if ff == "3" then
return "Shared"
end
return ff
end
function getMap(map)
if map == "mp_backlot" then
return "Backlot"
end
if map == "mp_bloc" then
return "Bloc"
end
if map == "mp_bog" then
return "Bog"
end
if map == "mp_cargoship" then
return "Wet Work"
end
if map == "mp_citystreets" then
return "District"
end
if map == "mp_convoy" then
return "Ambush"
end
if map == "mp_countdown" then
return "Countdown"
end
if map == "mp_crash" then
return "Crash"
end
if map == "mp_crossfire" then
return "Crossfire"
end
if map == "mp_farm" then
return "Downpour"
end
if map == "mp_overgrown" then
return "Overgrown"
end
if map == "mp_pipeline" then
return "Pipeline"
end
if map == "mp_shipment" then
return "Shipment"
end
if map == "mp_showdown" then
return "Showdown"
end
if map == "mp_strike" then
return "Strike"
end
if map == "mp_vacant" then
return "Vacant"
end
if map == "mp_crash_snow" then
return "Winter Crash"
end
if map == "mp_broadcast" then
return "Broadcast"
end
if map == "mp_creek" then
return "Creek"
end
if map == "mp_killhouse" then
return "Kill House"
end
if map == "mp_carentan" then
return "Chinatown"
end
return map
end
function getGameType(gameType)
if gameType == "war" then
gameType = "Team Deathmatch"
end
if gameType == "dm" then
gameType = "Deathmatch"
end
if gameType == "dom" then
gameType = "Domination"
end
if gameType == "koth" then
gameType = "Headquarters"
end
if gameType == "sab" then
gameType = "Sabotage"
end
if gameType == "sd" then
gameType = "Search & Destroy"
end
return gameType
end
function UpdateIps()
io.input(directory.."IPS.txt")
tempIPs = {}
for line in io.lines() do
table.insert(tempIPs, line)
end
io.input():close()
return tempIPs
end
The batch script:
echo ????getinfo | nc -u -w 1 %1 28960 >%1.txt
There are three other files this script needs, IPS.txt is a txt file with every IP to check on a line.
Example IPS.txt:
10.8.27.52
10.8.26.224
10.8.48.111
10.8.33.10
Also required is a Java file i made myself , in my case called CoD4, but you can compile it and call it what ever you like. It has 3 classed called:
Main:
import javax.swing.*;
import java.io.IOException;
public class main
{
public static final String HEADER_TEXT = "CoD4 Updater";
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
GUI a = new GUI();
}
});
}
}
ReadFile:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ReadFile {
public static String[] readLines(String filename) throws IOException {
FileReader fileReader = new FileReader(filename);
BufferedReader bufferedReader = new BufferedReader(fileReader);
List<String> lines = new ArrayList<String>();
String line = null;
while ((line = bufferedReader.readLine()) != null) {
lines.add(line);
}
bufferedReader.close();
return lines.toArray(new String[lines.size()]);
}
}
and GUI:
import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Scanner;
public class GUI
{
public JFrame frame;
public GridBagConstraints a;
private static String[] IP;
public GUI()
{
frame = new JFrame(main.HEADER_TEXT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLayout(new GridBagLayout());
a = new GridBagConstraints();
frame.add(new JPanel());
LoadIps();
Update();
start();
}
private void start()
{
JLabel text = new JLabel("Working");
frame.add(text,a);
frame.setVisible(true);
int delay = 60000; //milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
Update();
}
};
new Timer(delay, taskPerformer).start();
}
private void Update()
{
for (int i = 0; i < IP.length; i++)
{
execute(IP[i]);
}
}
private void LoadIps()
{
try
{
IP = ReadFile.readLines("IPS.txt");
}
catch(IOException e){}
}
public static void execute(String ip)
{
try
{
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("cod.bat "+ip);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
And the last requirement is a file called nc.exe, known to the linux people as netcat. Do a google search if you want it.
All of the files, except the LUA script sould be put in a folder called "Cod" in the scripts directory of Ptokax.
Alright, so thats all the scripts etc thats needed, now a short explanation. Firstly the IPS.txt file. People have asked why you need to have this and why it just cant scan every IP and find which are running a server. Well here at Uni we all have a similar start to our IP's 10.8 but then different subnets, eg two people could have the IP's 10.8.27.52 and 10.8.54.34. Hence to scan all available IP's it would have to scan the range 10.8.1.1-10.8.255.255, which is a total of 65025 IP's. Which just isnt feasable. Hence its easier to just get anyone who's running a dedicated server to tell the hub admin and they can just add there IP to the list.
Secondly the Java program. It loads all of the IP's in IPS.txt and then once every minute executes a windows command to run cod.bat also passing cod.bat the ip it wants information on. cod.bat then calls nc.exe asking it to query the IP and return all server data to a txt file with the same name as the ip it queried.
For example. Lets say we had only 1 IP to monitor. 10.8.27.52 (if you're wondering why im using that its because its my internal IP here at the Uni :P). You run the java program (CoD4) and it loads the IP (10.8.27.52) from IPS.txt and then once a minute (and on startup) it runs cod.bat which basically pings that ip asking for any information on the Cod4 server its running and then takes that output and puts it in a file called 10.8.27.52.txt. Simple really. If the IP isnt running a cod4 server or if it doesnt respond within 1 second (the "-w 1" part of cod.bat say how many seconds to wait for a reply) then the .txt file will still be created but be empty. If it does respond then we end up with a whole lot of jumbled text seperated by back slashes, example from my server:
????infoResponse
\protocol\6\hostname\TheKinkyMudkip.com\mapname\mp_farm\clients\11\sv_maxclients\24\gametype\war\pure\1\kc\1\hc\1\hw\2\mod\0\voice\1\pb\0
Which looks a bit of a mess but its quite easy to understand if you spend some time looking at it and know a tiny bit about the options CoD4 multiplayer has.
Lastly we've got the CoD4BotV3.LUA file. Or the hub script in simple terms. This script sits there doing nothing untill someone either uses the usercommand or types in "!cod" or "!cod4". It then looks to find the IPS.txt file, and loads all of them. It then goes through the IP's and looks for .txt files called that ip (10.8.27.52.txt for example, or [IP].txt). it then loads the contence of that file and process the contence sorting out what things mean. It then tells the User through a PM about all the servers running, Just as a side note, "!cod" is the simple form and just says the IP's, server names and players on a server while "!cod4" is a more in depth approach that tells some important options. Another side note, sometimes if the java file/batch script is currently updating an IP's information (writing to the [IP].txt file) then the LUA script will not be able to update the information it has, and hence will either have to rely on the data it had about it on the last call or if it doesnt have that data it will just assume no server is running on that IP.
And so there concludes my CoD4 server announce script. Its taken me 3 revisions and alot of pain but it works for the application we use it for and i hope now ive posted it here it can help someone else. Im aware that alot of the code is a mess and could do with either a heck of a lot of optimisation or even just a complete rewrite but im currently working on getting a propper CoD4 server admin tool working (written in java). Please feel free to use this code, rewrite it and comment on it as you please. But just remember to try and give credit where credit is due.
Lastly a MASSIVE thank you to Mappy, the person who listened to all my stupid questions and without who's help i would never have been able to make this hodge-bodge script :P. Merrychristmas to all.
Thanks for the post. Some remarks:
1) You can just redefine the OpConnected and RegConnected functions. Instead of [code]function OpConnected(user) -- Opperator connects, do the same as any User
UserConnected(user)
end
function RegConnected(user) -- Registered connects, do the same as any User
UserConnected(user)
end
you can just use OpConnected = UserConnected
RegConnected = UserConnected
2) In ChatArrival() the first character is always <. The function ChatArrival(user, data)
if (string.sub(data, 1, 1) == "<" ) then
is an old remnant from where it only was DataArrival() whose second argument passed any raw data to the script.
3) You can just return Validcomment, there is no need for checking whether it is really true. From the return point of view, false and nil are the same.
4) The getMap() function is starving for an associative table.
OK, this is all for now, just some basic hints on how the code could be made better. If this post makes any questions arise, feel free to ask (it makes me happy to discuss such things), but I'll not be able to respond quickly so please bear wih me.[/code]