#include<stdio.h>#include<string.h>#include<baselib/system.h>#include<engine/interface.h>//#include "socket.h"
#include<engine/packet.h>#include<engine/snapshot.h>#include<engine/lzw.h>#include<engine/versions.h>namespacebaselib{}usingnamespacebaselib;intnet_addr4_cmp(constNETADDR4*a,constNETADDR4*b){if(a->ip[0]!=b->ip[0]||a->ip[1]!=b->ip[1]||a->ip[2]!=b->ip[2]||a->ip[3]!=b->ip[3]||a->port!=b->port)return1;return0;}// --- string handling (MOVE THESE!!) ---
voidsnap_encode_string(constchar*src,int*dst,intlength,intmax_length){constunsignedchar*p=(constunsignedchar*)src;// handle whole int
for(inti=0;i<length/4;i++){*dst=(p[0]<<24|p[1]<<16|p[2]<<8|p[3]);p+=4;dst++;}// take care of the left overs
intleft=length%4;if(left){unsignedlast=0;switch(left){case3:last|=p[2]<<8;case2:last|=p[1]<<16;case1:last|=p[0]<<24;}*dst=last;}}classsnapshot_builder{public:staticconstintMAX_ITEMS=512;//static const int MAX_DATA_SIZE=1*1024;
chardata[MAX_SNAPSHOT_SIZE];intdata_size;intoffsets[MAX_ITEMS];intnum_items;inttop_size;inttop_items;intsnapnum;snapshot_builder(){top_size=0;top_items=0;snapnum=0;}voidstart(){data_size=0;num_items=0;}intfinish(void*snapdata){snapnum++;// collect some data
/*
int change = 0;
if(data_size > top_size)
{
change++;
top_size = data_size;
}
if(num_items > top_items)
{
change++;
top_items = num_items;
}
if(change)
{
dbg_msg("snapshot", "new top, items=%d size=%d", top_items, top_size);
}*/// flattern and make the snapshot
snapshot*snap=(snapshot*)snapdata;snap->num_items=num_items;intoffset_size=sizeof(int)*num_items;mem_copy(snap->offsets,offsets,offset_size);mem_copy(snap->data_start(),data,data_size);returnsizeof(int)+offset_size+data_size;}void*new_item(inttype,intid,intsize){snapshot::item*obj=(snapshot::item*)(data+data_size);obj->type_and_id=(type<<16)|id;offsets[num_items]=data_size;data_size+=sizeof(int)+size;num_items++;dbg_assert(data_size<MAX_SNAPSHOT_SIZE,"too much data");dbg_assert(num_items<MAX_ITEMS,"too many items");return&obj->data;}};staticsnapshot_builderbuilder;void*snap_new_item(inttype,intid,intsize){dbg_assert(type>=0&&type<=0xffff,"incorrect type");dbg_assert(id>=0&&id<=0xffff,"incorrect id");returnbuilder.new_item(type,id,size);}//
classclient{public:enum{STATE_EMPTY=0,STATE_CONNECTING=1,STATE_INGAME=2,};// connection state info
intstate;// (ticks) if lastactivity > 5 seconds kick him
int64lastactivity;connectionconn;charname[MAX_NAME_LENGTH];charclan[MAX_CLANNAME_LENGTH];/*
client()
{
state = STATE_EMPTY;
name[0] = 0;
clan[0] = 0;
}
~client()
{
dbg_assert(state == STATE_EMPTY, "client destoyed while in use");
}*/boolis_empty()const{returnstate==STATE_EMPTY;}boolis_ingame()const{returnstate==STATE_INGAME;}constnetaddr4&address()const{returnconn.address();}};staticclientclients[MAX_CLIENTS];staticintcurrent_tick=0;staticintsend_heartbeats=1;intserver_tick(){returncurrent_tick;}intserver_tickspeed(){return50;}intserver_init(){for(inti=0;i<MAX_CLIENTS;i++){clients[i].state=client::STATE_EMPTY;clients[i].name[0]=0;clients[i].clan[0]=0;clients[i].lastactivity=0;}current_tick=0;return0;}intserver_getclientinfo(intclient_id,client_info*info){dbg_assert(client_id>=0&&client_id<MAX_CLIENTS,"client_id is not valid"
constchar*map_name;constchar*server_name;int64lasttick;int64lastheartbeat;netaddr4master_server;intbiggest_snapshot;boolrun(constchar*servername,constchar*mapname){biggest_snapshot=0;net_init();// For Windows compatibility.
map_name=mapname;server_name=servername;// load map
if(!map_load(mapname)){dbg_msg("server","failed to load map. mapname='%s'");returnfalse;}// start server
if(!game_socket.open(8303)){dbg_msg("network/server","couldn't open socket");returnfalse;}for(inti=0;i<MAX_CLIENTS;i++)dbg_msg("network/server","\t%d: %d",i,clients[i].state);if(net_host_lookup(MASTER_SERVER_ADDRESS,MASTER_SERVER_PORT,&master_server)!=0){// TODO: fix me
//master_server = netaddr4(0, 0, 0, 0, 0);
}mods_init();int64time_per_tick=time_freq()/SERVER_TICK_SPEED;int64time_per_heartbeat=time_freq()*30;int64starttime=time_get();//int64 lasttick = starttime;
lasttick=starttime;lastheartbeat=0;int64reporttime=time_get();int64reportinterval=time_freq()*3;int64simulationtime=0;int64snaptime=0;int64networktime=0;while(1){int64t=time_get();if(t-lasttick>time_per_tick){{int64start=time_get();tick();simulationtime+=time_get()-start;}{int64start=time_get();snap();snaptime+=time_get()-start;}// Check for client timeouts
for(inti=0;i<MAX_CLIENTS;i++){if(clients[i].state!=client::STATE_EMPTY){// check last activity time
if(((lasttick-clients[i].lastactivity)/time_freq())>SERVER_CLIENT_TIMEOUT)client_timeout(i);}}lasttick+=time_per_tick;}if(send_heartbeats){if(t>lastheartbeat+time_per_heartbeat){if(master_server.port!=0){intplayers=0;for(inti=0;i<MAX_CLIENTS;i++)if(!clients[i].is_empty())players++;// TODO: fix me
netaddr4me(127,0,0,0,8303);send_heartbeat(0,&me,players,MAX_CLIENTS,server_name,mapname);}lastheartbeat=t+time_per_heartbeat;}}{int64start=time_get();pump_network();networktime+=time_get()-start;}if(reporttime<time_get()){int64totaltime=simulationtime+snaptime+networktime;dbg_msg("server/report","sim=%.02fms snap=%.02fms net=%.02fms total=%.02fms load=%.02f%%",simulationtime/(float)reportinterval*1000,snaptime/(float)reportinterval*1000,networktime/(float)reportinterval*1000,totaltime/(float)reportinterval*1000,(simulationtime+snaptime+networktime)/(float)reportinterval*100.0f);unsignedsent_total=0,recv_total=0;for(inti=0;i<MAX_CLIENTS;i++)if(!clients[i].is_empty()){unsigneds,r;clients[i].conn.counter_get(&s,&r);clients[i].conn.counter_reset();sent_total+=s;recv_total+=r;}dbg_msg("server/report","biggestsnap=%d send=%d recv=%d",biggest_snapshot,sent_total/3,recv_total/3);simulationtime=0;snaptime=0;networktime=0;reporttime+=reportinterval;}thread_sleep(1);}mods_shutdown();map_unload();}voidtick(){current_tick++;mods_tick();}voidsnap(){mods_presnap();for(inti=0;i<MAX_CLIENTS;i++){if(clients[i].is_ingame()){chardata[MAX_SNAPSHOT_SIZE];charcompdata[MAX_SNAPSHOT_SIZE];builder.start();mods_snap(i);// finish snapshot
intsnapshot_size=builder.finish(data);// compress it
intcompsize=lzw_compress(data,snapshot_size,compdata);snapshot_size=compsize;if(snapshot_size>biggest_snapshot)biggest_snapshot=snapshot_size;constintmax_size=MAX_SNAPSHOT_PACKSIZE;intnumpackets=(snapshot_size+max_size-1)/max_size;for(intn=
// do version check
if(p->version()!=TEEWARS_NETVERSION)
{
// send an empty packet back.
// this will allow the client to check the version
packetp;
game_socket.send(from,p.data(),p.size());
return;
}
if(p->msg()==NETMSG_CLIENT_CONNECT){// we got no state for this client yet
constchar*version;constchar*name;constchar*clan;constchar*password;constchar*skin;version=p->read_str();name=p->read_str();clan=p->read_str();password=p->read_str();skin=p->read_str();if(p->is_good()){
/*
// check version
if(strcmp(version, TEEWARS_NETVERSION) != 0)
{
dbg_msg("network/server", "wrong version connecting '%s'", version);
// TODO: send error
return;}*/// look for empty slot, linear search
intid=-1;for(inti=0;i<MAX_CLIENTS;i++)if(clients[i].is_empty()){id=i;break;}if(id!=-1){// slot found
// TODO: perform correct copy here
mem_copy(clients[id].name,name,MAX_NAME_LENGTH);mem_copy(clients[id].clan,clan,MAX_CLANNAME_LENGTH);clients[id].state=client::STATE_CONNECTING;clients[id].conn.init(&game_socket,from);clients[id].lastactivity=lasttick;clients[id].name[MAX_NAME_LENGTH-1]=0;clients[id].clan[MAX_CLANNAME_LENGTH-1]=0;dbg_msg("network/server","client connected. '%s' on slot %d",name,id);// TODO: return success
send_accept(&clients[id],map_name);}else{// no slot found
// TODO: send error
dbg_msg("network/server","client connected but server is full");for(inti=0;i<MAX_CLIENTS;i++)dbg_msg("network/server","\t%d: %d",i,clients[i].state);}}}else{intcid=find_client(from);if(cid>=0){if(clients[cid].conn.feed(p)){// packet is ok
unsignedmsg=p->msg();// client found, check state
if(((msg>>16)&0xff)&clients[cid].state){// state is ok
client_process_packet(cid,p);}else{// invalid state, disconnect the client
drop(cid,"invalid message at this state");}}else{drop(cid,"connection error");}}elsedbg_msg("network/server","packet from strange address.");}}voidclient_timeout(intclientId){drop(clientId,"client timedout");}voidpump_network(){while(1){packetp;netaddr4from;//int bytes = net_udp4_recv(
intbytes=game_socket.recv(&from,p.data(),p.max_size());//int bytes = game_socket.recv(&from, p.data(), p.max_size());
if(bytes<=0)break;process_packet(&p,&from);}// TODO: check for client timeouts
}char*write_int(char*buffer,intinteger){*buffer++=integer>>24;*buffer++=integer>>16;*buffer++=integer>>8;*buffer++=integer;returnbuffer;}char*write_netaddr4(char*buffer,NETADDR4*address){*buffer++=address->ip[0];*buffer++=address->ip[1];*buffer++=address->ip[2];*buffer++=address->ip[3];returnwrite_int(buffer,address->port);}voidsend_heartbeat(intversion,netaddr4*address,intplayers,intmax_players,constchar*name,constchar*map_name){charbuffer[216]={0};char*d=buffer;d=write_int(d,'TWHB');d=write_int(d,version);d=write_netaddr4(d,address);d=write_int(d,players);d=write_int(d,max_players);intlen=strlen(name);if(len>128)len=128;memcpy(d,name,len);d+=128;len=strlen(map_name);if(len>64)len=64;memcpy(d,map_name,len);d+=64;
game_socket.send(&master_server,buffer,sizeof(buffer));}};intmain(intargc,char**argv){dbg_msg("server","starting...");constchar*mapname="data/demo.map";constchar*servername=0;// parse arguments
for(inti=1;i<argc;i++){if(argv[i][0]=='-'&&argv[i][1]=='m'&&argv[i][2]==0&&argc-i>1){// -m map
i++;mapname=argv[i];}elseif(argv[i][0]=='-'&&argv[i][1]=='n'&&argv[i][2]==0&&argc-i>1){// -n server name
i++;servername=argv[i];}elseif(argv[i][0]=='-'&&argv[i][1]=='p'&&argv[i][2]==0){// -p (private server)
send_heartbeats=0;}}if(!mapname){dbg_msg("server","no map given (-m MAPNAME)");return0;}if(!servername){dbg_msg("server","no server name given (-n \"server name\")");return0;}server_init();servers;s.run(servername,mapname);return0;}