CoD4/ Game announce Bot
 

CoD4/ Game announce Bot

Started by Ph@te, 06 March, 2010, 13:08:05

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Ph@te

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.

Dreams

You should read the forum, look at http://forum.ptokax.org/index.php?topic=8549.0
But the plugin isnt ready yet from HLSW.. so i belive you have to wait. :)

Ph@te

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.."&#124;")
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.


bastya_elvtars

#3
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]
Everything could have been anything else and it would have just as much meaning.

SMF spam blocked by CleanTalk