frogmod

Started by vampirefrog, July 25, 2009, 10:35:23 AM

Previous topic - Next topic

vampirefrog

Hey guys. I need a place to write technical stuff about frogmod and the sauer protocol. This might be interesting, but I'm writing it mostly for me to understand stuff. I'm also thinking of rewriting frogmod from scratch, in such a fashion that it would support both sauer and bloodfrontier (and maybe more).




Server messages and sizes (defined in game.h). Length zero means variable size.

// Initial messages
SV_SERVINFO, 5
    this is the first message that occurs. it's sent from server to client:
    sendf(ci->clientnum, 1, "ri5", SV_SERVINFO, ci->clientnum, PROTOCOL_VERSION, ci->sessionid, serverpass[0] ? 1 : 0);
    the parameters are:
        client number
        protocol version
        session id
        whether or not the server requires password (methinks)


SV_CONNECT, 0
SV_WELCOME, 2
SV_INITCLIENT, 0

SV_POS, 0,
    player position gets updated

SV_TEXT, 0,
    player chat

SV_SOUND, 2,
SV_CDIS, 2,
SV_SHOOT, 0,
SV_EXPLODE, 0,
SV_SUICIDE, 1,
SV_DIED, 4,
SV_DAMAGE, 6,
SV_HITPUSH, 7
SV_SHOTFX, 9,
SV_TRYSPAWN, 1,
SV_SPAWNSTATE, 14,
SV_SPAWN, 3,
SV_FORCEDEATH, 2,
SV_GUNSELECT, 2,
SV_TAUNT, 1,
SV_MAPCHANGE, 0,
SV_MAPVOTE, 0,
SV_ITEMSPAWN, 2,
SV_ITEMPICKUP, 2,
SV_ITEMACC, 3,

//pinging
SV_PING, 2,
SV_PONG, 2,
SV_CLIENTPING, 2,

SV_TIMEUP, 2,
SV_MAPRELOAD, 1,
SV_FORCEINTERMISSION, 1,

SV_SERVMSG, 0,
    a server text message (gets displayed in the clients' console)

SV_ITEMLIST, 0,
SV_RESUME, 0,

// editing commands
SV_EDITMODE, 2,
SV_EDITENT, 11,
SV_EDITF, 16,
SV_EDITT, 16,
SV_EDITM, 16,
SV_FLIP, 14,
SV_COPY, 14,
SV_PASTE, 14,
SV_ROTATE, 15,
SV_REPLACE, 16,
SV_DELCUBE, 14,

SV_REMIP, 1,
SV_NEWMAP, 2,
SV_GETMAP, 1,
SV_SENDMAP, 0,
SV_EDITVAR, 0,
SV_MASTERMODE, 2,
SV_KICK, 2,
SV_CLEARBANS, 1,
SV_CURRENTMASTER, 3,
SV_SPECTATOR, 3,
SV_SETMASTER, 0,
SV_SETTEAM, 0,
SV_BASES, 0,
SV_BASEINFO, 0,
SV_BASESCORE, 0,
SV_REPAMMO, 1,
SV_BASEREGEN, 6,
SV_ANNOUNCE, 2,

// demos
SV_LISTDEMOS, 1,
SV_SENDDEMOLIST, 0,
SV_GETDEMO, 2,
SV_SENDDEMO, 0,
SV_DEMOPLAYBACK, 3,
SV_RECORDDEMO, 2,
SV_STOPDEMO, 1,
SV_CLEARDEMOS, 2,

// flag
SV_TAKEFLAG, 2,
SV_RETURNFLAG, 3,
SV_RESETFLAG, 4,
SV_INVISFLAG, 3,
SV_TRYDROPFLAG, 1,
SV_DROPFLAG, 6,
SV_SCOREFLAG, 6,
SV_INITFLAGS, 6,

// team speak
SV_SAYTEAM, 0,

SV_CLIENT, 0,

// auth
SV_AUTHTRY, 0,
SV_AUTHCHAL, 0,
SV_AUTHANS, 0,
SV_REQAUTH, 0,

SV_PAUSEGAME, 2,

// bots
SV_ADDBOT, 2,
SV_DELBOT, 1,
SV_INITAI, 0,
SV_FROMAI, 2,
SV_BOTLIMIT, 2,
SV_BOTBALANCE, 2,

SV_MAPCRC, 0,
SV_CHECKMAPS, 1,
SV_SWITCHNAME, 0,
SV_SWITCHMODEL, 2,
SV_SWITCHTEAM, 0,[/][/color]

vampirefrog

#1
List of components, current and planned:

  • Sauerbraten server
  • cubescript execution
  • IRC client
  • http client
  • http server (planned - will show game data, and provide some control and services)
  • GeoIP
  • fine integration with mappinghell.net (it will use its http server feature). integration will include user logins, blacklist and other stuff
  • modular -- uses event loop
The event loop will support:

  • solving DNS
  • IRC connections (which will be scriptable)
  • http client
  • http server
  • sauerbraten server
  • other servers (it will be very modular)
Must separate sauerbraten code from event loop. Possible event loop libraries: libevent, glib, liboop and libev. libevent already has dns and a http server built in, but libev has better documentation. glib is overkill, and liboop is outdated.

The reason I'm making the server modular is because later on it will easily support other games, such as BloodFrontier, and my own game, which I'm planning on.

vampirefrog

#2
I've made a small hack, using libevent, python and IRC:


#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>

#include <event2/event.h>
#include <event2/dns.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/http.h>

#include <Python.h>

#include <vector>

struct event_base *base;
struct evdns_base *dnsbase;
struct evhttp *http;

void evirc_readcb(struct bufferevent *buf, void *arg);
void evirc_writecb(struct bufferevent *buf, void *arg);
void evirc_eventcb(struct bufferevent *buf, short what, void *ctx);

struct evircchan {
char name[256];
};

struct evircnet {
char *host;
int port;
char *nick;
enum {
None,
Connecting,
Connected,
SentIdent,
Active
};
int state;
bufferevent *buf;
char line[1024]; int l; //FIXME: replace with one of those fancy shmancy libevent buffer thingamajigs
std::vector <evircchan> channels;
void init(char *_host, int _port, const char *_nick) {
host = strdup(_host);
port = _port;
nick = strdup(_nick);
state = None;
buf = bufferevent_socket_new(base, -1, (bufferevent_options)0);
bufferevent_setcb(buf, evirc_readcb, evirc_writecb, evirc_eventcb, this);
channels.clear();
}
void joinchan(char *chan) {
bool add = true;
for(int i = 0; i < channels.size(); i++) {
if(!strcmp(channels[i].name, chan)) add=false; // don't join an existing channel
}
if(add) {
evircchan c;
strcpy(c.name, chan);
channels.push_back(c);
}
if(state == Active) {
char str[256];
sprintf(str, "JOIN %s\r\n", chan);
bufferevent_write(buf, str, strlen(str));
}
}
void speak(const char *msg) {
for(int i = 0; i < channels.size(); i++) {
char str[512];
sprintf(str, "PRIVMSG %s :%s\r\n", channels[i].name, msg);
puts(str);
bufferevent_write(buf, str, strlen(str));
}
}
void process_line() {
printf("LINE %s\n", line);
char arg[512];
char from[256];
char command[256];
char to[256];
int n;
if(sscanf(line, ":%s PRIVMSG %s :%n", from, to, &n) >= 2) {
printf("%s says: %s\n", from, line + n);
if(line[n] == '@') {
PyRun_SimpleString(line + n + 1);
}
} else if(sscanf(line, ":%s %s %s :%n", from, command, to, &n) >= 3) {
if(isdigit(command[0])) {
int numeric = strtol(command, NULL, 10);
switch(numeric) {
case 001:
printf("Hello %d\n", __LINE__);
if(state != Active) {
state = Active;
for(int i = 0; i < channels.size(); i++) {
joinchan(channels[i].name);
}
}
printf("Hello %d\n", __LINE__);
break;
case 372:
case 376:
printf("* %s\n", line+n);
break;
case 433:
{
struct evbuffer *eb = evbuffer_new();
int nl = strlen(nick);
nick[nl] = '_';
nick[nl+1] = 0;
evbuffer_add_printf(eb, "USER %s 0 * :%s\r\nNICK %s\r\n", nick, nick, nick);
bufferevent_write_buffer(buf, eb);
evbuffer_free(eb);
break;
}
default:
printf("numeric \"%s\" \"%s\" \"%s\" :\"%s\"\n", from, command, to, line+n);
}
} else printf("command %s\n", line);
} else if(sscanf(line, "PING :%s", arg)) {
char str[256];
sprintf(str, "PONG :%s\r\n", arg);
printf("PING/PONG %s\n", arg);
bufferevent_write(buf, str, strlen(str));
} else printf("%s\n", line);
}
};
std::vector <evircnet> nets;

void evirc_readcb(struct bufferevent *buf, void *arg) {
evircnet *n = (evircnet *)arg;
if(n->state < evircnet::Connected) return;
printf("readcb\n");
char data[1024];

int er;

while((er = bufferevent_read(buf, data, 1023)) > 0) {
data[er] = 0;
// printf("received %d bytes: %s\n", er, data);
for(int i = 0; i < er; i++) {
switch(data[i]) {
case '\r':
// ignore //
break;
case '\n':
n->line[n->l] = 0;
n->process_line();
n->l = 0;
break;
default:
if(n->l >= sizeof(n->line) - 1) { n->line[sizeof(n->line) - 1] = 0; n->process_line(); n->l = 0; printf("overflow\n"); }
else {
n->line[n->l] = data[i];
n->line[n->l+1] = 0;
n->l++;
}
}
}
}
}

void evirc_writecb(struct bufferevent *buf, void *arg) {
evircnet *n = (evircnet *)arg;
printf("writecb\n");
return;
}

void evirc_eventcb(struct bufferevent *buf, short what, void *arg) {
evircnet *n = (evircnet *)arg;
printf("eventcb what=%d", what);
#define checkerr(s) { if(what & BEV_EVENT_##s) printf(" %s", #s); }
checkerr(READING);
checkerr(WRITING);
checkerr(EOF);
checkerr(ERROR);
checkerr(TIMEOUT);
checkerr(CONNECTED);
printf(" errno=%d: %s\n", errno, strerror(errno));
if(what == BEV_EVENT_CONNECTED) {
printf("connected\n");
n->state = evircnet::Connected;
bufferevent_enable(buf, EV_READ);
struct evbuffer *eb = evbuffer_new();
evbuffer_add_printf(eb, "USER %s 0 * :%s\r\nNICK %s\r\n", n->nick, n->nick, n->nick);
bufferevent_write_buffer(buf, eb);
evbuffer_free(eb);
n->state = evircnet::SentIdent;
} else {
event_base_loopbreak(base);
}
}

void evirc_dnscb(int result, char type, int count, int ttl, void *addresses, void *arg) {
evircnet *n = (evircnet *)arg;
if(result == DNS_ERR_NONE) {
if(type == DNS_IPv4_A) {
struct sockaddr_in addr;
addr.sin_addr.s_addr = ((in_addr_t *)addresses)[0];
addr.sin_port = htons(n->port);
addr.sin_family = AF_INET;

n->state = evircnet::Connecting;
bufferevent_socket_connect(n->buf, (sockaddr *)&addr, sizeof(struct sockaddr_in));
}
}
}

void evirc_addnetwork(char *host, int port = 6667, const char *nick = "user") {
int s = nets.size();
nets.resize(s + 1);
evircnet *n = &nets[s];
n->init(host, port, nick);
evdns_base_resolve_ipv4(dnsbase, host, 0, evirc_dnscb, n);
}

void evirc_joinchan(char *network, char *chan) {
evircnet *n;
for(int i = 0; i < nets.size(); i++) {
if(!strcmp(nets[i].host, network)) {
nets[i].joinchan(chan);
return;
}
}
}

static PyObject *evirc_py_addnetwork(PyObject *self, PyObject* args)
{
char *host, *nick = "user"; //fixme: how the hell do I free these?
int port = 6667;
PyArg_ParseTuple(args, "s|is", &host, &port, &nick);
evirc_addnetwork(host, port, nick);
return Py_BuildValue("");
}

static PyObject *evirc_py_join(PyObject *self, PyObject* args)
{
char *network, *chan;
PyArg_ParseTuple(args, "ss", &network, &chan);
evirc_joinchan(network, chan);
return Py_BuildValue("");
}

static PyObject *evirc_py_speak(PyObject *self, PyObject* args) {
char *msg;
PyArg_ParseTuple(args, "s", &msg);
for(int i = 0; i < nets.size(); i++) {
nets[i].speak(msg);
}
return Py_BuildValue("");
}

static PyMethodDef evirc_methods[] = {
{"addnetwork", evirc_py_addnetwork, METH_VARARGS, "Add an IRC network."},
{"join", evirc_py_join, METH_VARARGS, "Join an IRC channel."},
{"speak", evirc_py_speak, METH_VARARGS, "Speak on all IRC channels."},
{NULL, NULL} /* sentinel */
};

void httpcb(struct evhttp_request *req, void *arg) {
evbuffer *buf = evbuffer_new();

evbuffer_add_printf(buf, "<h1>evirc</h1>\n");
for(int i = 0; i < nets.size(); i++) {
evbuffer_add_printf(buf, "%s:%d <b>%s</b><br />\n", nets[i].host, nets[i].port, nets[i].nick);
}

evhttp_send_reply(req, 200, "OK", buf);
evbuffer_free(buf);
}

void http404cb(struct evhttp_request *req, void *arg) {
evhttp_send_error(req, 404, "Not found.");
}

int main(int argc, char **argv) {
base = event_base_new();
dnsbase = evdns_base_new(base, 1);
http = evhttp_new(base);

Py_Initialize();

PyImport_AddModule("evirc");
Py_InitModule("evirc", evirc_methods);

FILE *fp = fopen("init.py", "r");
if(fp) PyRun_SimpleFile(fp, "init.py");

evhttp_bind_socket(http, "mihai.grigoriada.net", 8765);
evhttp_set_cb(http, "/", httpcb, NULL);
evhttp_set_gencb(http, http404cb, NULL);

event_base_dispatch(base);

Py_Finalize();

evhttp_free(http);
evdns_base_free(dnsbase, 0);
event_base_free(base);
return 0;
}


It reads init.py at startup, and that's how it connects to the first irc network:


import evirc

evirc.addnetwork('irc.quakenet.org', 6667, 'Fooey');
evirc.join('irc.quakenet.org', '#frogmod');


It can execute python code on the fly, even though it doesn't offer too many commands. It also offers an http server feature -- you access mihai.grigoriada.net:8765 and see a generated web page (a list of the connected irc networks). As you can see, this is an awesome feature to have in frogmod.

I'll have to figure out how to combine libevent (which this program uses) with enet (which is the UDP networking library at the base of sauer).

vampirefrog

I've managed to integrate libevent with frogmod, thanks to the hopmod code. now there is a http server feature, if you set the httpport variable to something other than zero (ie 28888), and access that URL from your browser (shows mapp, game mode and players).

I've also made the IRC code to work, but it still has a long way to go before it will become usable, and Jim Dandy will have to wait until then, cause you can't administer players from IRC right now.