/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <base/math.hpp>

#include <engine/e_config.h>
#include <engine/e_server_interface.h>
#include <engine/e_perl.h>
extern "C"
{
	#include <engine/e_memheap.h>
}
#include <game/version.hpp>
#include <game/collision.hpp>
#include <game/layers.hpp>

#include <game/gamecore.hpp>
#include <game/server/statistics.hpp>

#include "gamecontext.hpp"
#include "gamemodes/dm.hpp"
#include "gamemodes/tdm.hpp"
#include "gamemodes/ctf.hpp"
#include "gamemodes/mod.hpp"
#include "gamemodes/freeze.hpp"
#include "gamemodes/fb.hpp"

TUNING_PARAMS tuning;

static void check_pure_tuning()
{
	// might not be created yet during start up
	if(!game.controller)
		return;
	
	if(	strcmp(game.controller->gametype, "DM")==0 ||
		strcmp(game.controller->gametype, "TDM")==0 ||
		strcmp(game.controller->gametype, "CTF")==0)
	{
		TUNING_PARAMS p;
		if(memcmp(&p, &tuning, sizeof(TUNING_PARAMS)) != 0)
		{
			dbg_msg("server", "resetting tuning due to pure server");
			tuning = p;
		}
	}	
}

struct VOTEOPTION
{
	VOTEOPTION *next;
	VOTEOPTION *prev;
	char* command;
};

static VOTEOPTION *voteoption_first = 0;

void send_tuning_params(int cid)
{
	check_pure_tuning();
	
	msg_pack_start(NETMSGTYPE_SV_TUNEPARAMS, MSGFLAG_VITAL);
	int *params = (int *)&tuning;
	for(unsigned i = 0; i < sizeof(tuning)/sizeof(int); i++)
		msg_pack_int(params[i]);
	msg_pack_end();
	server_send_msg(cid);
}

// Server hooks
void mods_client_direct_input(int client_id, void *input)
{
	if(game.players[client_id]->team == -1)
		game.players[client_id]->last_input = server_tick();

	if(!game.players[client_id]->frozen)
		game.players[client_id]->on_direct_input((NETOBJ_PLAYER_INPUT *)input);
}

void mods_client_predicted_input(int client_id, void *input)
{
	if(game.players[client_id]->team == -1)
		game.players[client_id]->last_input = server_tick();

	if(!game.world.paused && !game.players[client_id]->frozen)
		game.players[client_id]->on_predicted_input((NETOBJ_PLAYER_INPUT *)input);
}

// Server hooks
void mods_tick()
{
	check_pure_tuning();
	
	game.tick();

#ifdef CONF_DEBUG
	if(config.dbg_dummies)
	{
		for(int i = 0; i < config.dbg_dummies ; i++)
		{
			NETOBJ_PLAYER_INPUT input = {0};
			input.direction = (i&1)?-1:1;
			game.players[MAX_CLIENTS-i-1]->on_predicted_input(&input);
		}
	}
#endif
}

void mods_snap(int client_id)
{
	game.snap(client_id);
}

void mods_client_enter(int client_id)
{
	//game.world.insert_entity(&game.players[client_id]);
	game.players[client_id]->respawn();
	dbg_msg("game", "join player='%d:%s'", client_id, server_clientname(client_id));


	char buf[512];
	str_format(buf, sizeof(buf), "%s entered and joined the %s", server_clientname(client_id), game.controller->get_team_name(game.players[client_id]->team));
	game.send_chat(-1, GAMECONTEXT::CHAT_ALL, buf); 

	game.players[client_id]->last_input = server_tick();

	dbg_msg("game", "team_join player='%d:%s' team=%d", client_id, server_clientname(client_id), game.players[client_id]->team);

	statistics_join(client_id);

	perl_client_enter(client_id);
}

void mods_connected(int client_id)
{
	game.players[client_id] = new(client_id) PLAYER(client_id);
	//game.players[client_id].init(client_id);
	//game.players[client_id].client_id = client_id;
	
	game.players[client_id]->team = game.controller->get_auto_team(client_id);
		
	(void) game.controller->check_team_balance();

	// send motd
	NETMSG_SV_MOTD msg;
	msg.message = config.sv_motd;
	msg.pack(MSGFLAG_VITAL);
	server_send_msg(client_id);
}

void mods_client_drop(int client_id)
{
	perl_client_disconnect(client_id);
	game.players[client_id]->on_disconnect();
	delete game.players[client_id];
	game.players[client_id] = 0;
	
	(void) game.controller->check_team_balance();
}

/*static bool is_separator(char c) { return c == ';' || c == ' ' || c == ',' || c == '\t'; }

static const char *liststr_find(const char *str, const char *needle)
{
	int needle_len = strlen(needle);
	while(*str)
	{
		int wordlen = 0;
		while(str[wordlen] && !is_separator(str[wordlen]))
			wordlen++;
		
		if(wordlen == needle_len && strncmp(str, needle, needle_len) == 0)
			return str;
		
		str += wordlen+1;
	}
	
	return 0;
}*/

void send_vote_option(const VOTEOPTION *option, int client_id)
{
	NETMSG_SV_VOTE_OPTION optionmsg;
	optionmsg.command = option->command;
	optionmsg.pack(MSGFLAG_VITAL);
	server_send_msg(client_id);
}

void send_vote_options(int client_id)
{
	NETMSG_SV_VOTE_CLEAROPTIONS clearmsg;
	clearmsg.pack(MSGFLAG_VITAL);
	server_send_msg(client_id);
	VOTEOPTION *current = voteoption_first;
	while(current)
	{
		send_vote_option(current, client_id);
		current = current->next;
	}
}

void mods_message(int msgtype, int client_id)
{
	void *rawmsg = netmsg_secure_unpack(msgtype);
	PLAYER *p = game.players[client_id];
	
	if(!rawmsg)
	{
		dbg_msg("server", "dropped weird message '%s' (%d), failed on '%s'", netmsg_get_name(msgtype), msgtype, netmsg_failed_on());
		return;
	}
	
	if(msgtype == NETMSGTYPE_CL_SAY)
	{
		NETMSG_CL_SAY *msg = (NETMSG_CL_SAY *)rawmsg;
		int team = msg->team;
		if(team)
			team = p->team;
		else 
			team = GAMECONTEXT::CHAT_ALL;
		
		if(config.sv_spamprotection && p->last_chat+time_freq() > time_get())
			return;
	
		p->last_chat = time_get();
		p->last_input = server_tick();

		game.send_chat(client_id, team, msg->message);
	}
	else if(msgtype == NETMSGTYPE_CL_CALLVOTE)
	{
		int64 now = time_get();
		if(game.vote_closetime)
		{
			game.send_chat_target(client_id, "Wait for current vote to end before calling a new one.");
			return;
		}
		
		int64 timeleft = p->last_votecall + time_freq()*60 - now;
		if(timeleft > 0)
		{
			char chatmsg[512] = {0};
			str_format(chatmsg, sizeof(chatmsg), "You must wait %d seconds before making another vote", (timeleft/time_freq())+1);
			game.send_chat_target(client_id, chatmsg);
			return;
		}
		
		NETMSG_CL_CALLVOTE *msg = (NETMSG_CL_CALLVOTE *)rawmsg;
		if(str_comp_nocase(msg->type, "option") == 0)
		{
			VOTEOPTION *option = voteoption_first;
			while(option && str_comp_nocase(msg->value, option->command) != 0)
				option = option->next;
			
			if(!option)
			{
                        	char chatmsg[512];
				str_format(chatmsg, sizeof(chatmsg), "'%s' isn't an option on this server", msg->value);
				game.send_chat_target(client_id, chatmsg);
				return;
			}

                        c_start_vote(client_id, msg->value);
		}
		else if(str_comp_nocase(msg->type, "kick") == 0)
		{
			if(!config.sv_vote_kick)
			{
				game.send_chat_target(client_id, "Server does not allow voting to kick players");
				return;
			}
			
			int kick_id = atoi(msg->value);
			if(kick_id < 0 || kick_id >= MAX_CLIENTS || !game.players[kick_id])
			{
				game.send_chat_target(client_id, "Invalid client id to kick");
				return;
			}

                        char cmd[10];
			if (!config.sv_vote_kick_bantime)
				str_format(cmd, sizeof(cmd), "kick %d", kick_id);
			else
				str_format(cmd, sizeof(cmd), "ban %d %d", kick_id, config.sv_vote_kick_bantime);
                        c_start_vote(client_id, cmd);
		}
	}
	else if(msgtype == NETMSGTYPE_CL_VOTE)
	{
		if(!game.vote_closetime)
			return;

		NETMSG_CL_VOTE *msg = (NETMSG_CL_VOTE *)rawmsg;
		p->vote = msg->vote;
		game.send_vote_status(-1);
	}
	else if (msgtype == NETMSGTYPE_CL_SETTEAM && !game.world.paused)
	{
		NETMSG_CL_SETTEAM *msg = (NETMSG_CL_SETTEAM *)rawmsg;
		
		if(config.sv_spamprotection && p->last_setteam+time_freq()*3 > time_get())
			return;

		p->last_input = server_tick();

		if(p->frozen)
		{
			game.send_broadcast("You can't switch teams while frozen.", client_id);
			return;
		}

		if(game.controller->locked[msg->team] && msg->team >= 0 && msg->team <= 1)
		{
			game.send_broadcast("The team is currently locked.", client_id);
			return;
		}

		// Switch team on given client and kill/respawn him
		if(game.controller->can_join_team(msg->team, client_id))
		{
			if(game.controller->can_change_team(p, msg->team))
                                perl_server_set_team(client_id, msg->team);
			else
				game.send_broadcast("Teams must be balanced, please join other team", client_id);
		}
		else
		{
			char buf[128];
			str_format(buf, sizeof(buf), "Only %d active players are allowed", config.sv_max_clients-config.sv_spectator_slots);
			game.send_broadcast(buf, client_id);
		}
	}
	else if (msgtype == NETMSGTYPE_CL_CHANGEINFO || msgtype == NETMSGTYPE_CL_STARTINFO)
	{
		NETMSG_CL_CHANGEINFO *msg = (NETMSG_CL_CHANGEINFO *)rawmsg;
		
		if(config.sv_spamprotection && p->last_changeinfo+time_freq()*5 > time_get())
			return;
			
		p->last_changeinfo = time_get();
		
		p->use_custom_color = msg->use_custom_color;
		p->color_body = msg->color_body;
		p->color_feet = msg->color_feet;

		// check for invalid chars
		unsigned char *name = (unsigned char *)msg->name;
		while (*name)
		{
			if(*name < 32)
				*name = ' ';
			name++;
		}

		// copy old name
		char oldname[MAX_NAME_LENGTH];
		str_copy(oldname, server_clientname(client_id), MAX_NAME_LENGTH);
		
		server_setclientname(client_id, msg->name);
		str_copy(p->start_name, server_clientname(client_id), MAX_NAME_LENGTH);

		if(msgtype == NETMSGTYPE_CL_CHANGEINFO && strcmp(oldname, server_clientname(client_id)) != 0)
		{
			char chattext[256];
			str_format(chattext, sizeof(chattext), "%s changed name to %s", oldname, server_clientname(client_id));
			statistics_change_name(client_id);
			game.send_chat(-1, GAMECONTEXT::CHAT_ALL, chattext);
		}

		// set skin
		if(strcmp(p->skin_name, msg->skin) != 0)
		{
			str_copy(p->skin_name, msg->skin, sizeof(p->skin_name));
			if(msgtype == NETMSGTYPE_CL_CHANGEINFO)
				statistics_change_skin(client_id);
		}
		
		game.controller->on_player_info_change(p);
		
		if(msgtype == NETMSGTYPE_CL_STARTINFO)
		{
			send_vote_options(client_id);
			
			// send tuning parameters to client
			send_tuning_params(client_id);

			//
			NETMSG_SV_READYTOENTER m;
			m.pack(MSGFLAG_VITAL|MSGFLAG_FLUSH);
			server_send_msg(client_id);
		}
	}
	else if (msgtype == NETMSGTYPE_CL_EMOTICON)
	{
		NETMSG_CL_EMOTICON *msg = (NETMSG_CL_EMOTICON *)rawmsg;
		
		if(config.sv_spamprotection && p->last_emote+time_freq()*3 > time_get())
			return;
			
		p->last_emote = time_get();
		
		game.send_emoticon(client_id, msg->emoticon);
	}
	else if (msgtype == NETMSGTYPE_CL_KILL && !game.world.paused)
	{
		p->last_input = server_tick();
		if(p->last_kill+time_freq()*3 > time_get())
			return;
		
		p->last_kill = time_get();
		p->kill_character(WEAPON_SELF);
		p->respawn_tick = game_tick()+server_tickspeed()*3;
	}
}

static void con_tune_param(void *result, void *user_data)
{
	const char *param_name = console_arg_string(result, 0);
	float new_value = console_arg_float(result, 1);

	if(tuning.set(param_name, new_value))
	{
		dbg_msg("tuning", "%s changed to %.2f", param_name, new_value);
		send_tuning_params(-1);
	}
	else
		console_print("No such tuning parameter");
}

static void con_tune_reset(void *result, void *user_data)
{
	TUNING_PARAMS p;
	tuning = p;
	send_tuning_params(-1);
	console_print("tuning reset");
}

static void con_tune_dump(void *result, void *user_data)
{
	for(int i = 0; i < tuning.num(); i++)
	{
		float v;
		tuning.get(i, &v);
		dbg_msg("tuning", "%s %.2f", tuning.names[i], v);
	}
}


static void con_change_map(void *result, void *user_data)
{
	game.controller->change_map(console_arg_string(result, 0));
}

static void con_restart(void *result, void *user_data)
{
	if(console_arg_num(result))
		game.controller->do_warmup(console_arg_int(result, 0));
	else
		game.controller->startround();
}

static void con_broadcast(void *result, void *user_data)
{
	game.send_broadcast(console_arg_string(result, 0), -1);
}

static void con_say(void *result, void *user_data)
{
	game.send_chat(-1, GAMECONTEXT::CHAT_ALL, console_arg_string(result, 0));
}

static void con_set_team(void *result, void *user_data)
{
	int client_id = clamp(console_arg_int(result, 0), 0, (int)MAX_CLIENTS);
	int team = clamp(console_arg_int(result, 1), -1, 1);
	
	dbg_msg("", "%d %d", client_id, team);
	
	if(!game.players[client_id])
		return;
	
	game.players[client_id]->set_team(team);
	(void) game.controller->check_team_balance();
}

static void con_lock_team(void *result, void *user_data)
{
	int team = clamp(console_arg_int(result, 0), 0, 1);
	int type = clamp(console_arg_int(result, 1), 0, 1);
	
	char buf[128];
	str_format(buf, sizeof(buf), "%s has been %s.", game.controller->is_teamplay() ? (team ? "Blue team" : "Red team") : "Game", type ? "locked" : "unlocked");

	if(game.controller->is_teamplay())
		type = 0;

	game.controller->locked[team] = type;
	game.send_broadcast(buf, -1);
}

static void con_addvote(void *result, void *user_data)
{
	VOTEOPTION *last = voteoption_first;
        if(last)
        	while(1)
                {
                	if(str_comp_nocase(last->command, static_cast<const char*>(result)) == 0)
                        	return;
                        if(!last->next)
                        	break;
                        last = last->next;
                }

        VOTEOPTION *option = new VOTEOPTION;
        if(last)
        	last->next = option;
        else
        	voteoption_first = option;
        option->prev = last;
	option->next = 0;

        const char *command = static_cast<const char *>(result);
	int len = strlen(command);
	option->command = new char[len+1];
	mem_copy(option->command, command, len+1);

	send_vote_option(option, -1);

	dbg_msg("server", "added option '%s'", option->command);
}

extern "C" void addvote(const char* command)
{
	con_addvote(const_cast<char*>(command), NULL);
}

static void con_delvote(void *result, void *user_data)
{
	dbg_msg("test", "delvote %s", (const char*)result);
	VOTEOPTION *option = voteoption_first;
	while(option && str_comp_nocase(option->command, static_cast<const char*>(result)) != 0)
		option = option->next;
	if(!option)
		return;
	dbg_msg("test", "vote found, deleting");
	
	if(option->next)
		option->next->prev = option->prev;
	if(option->prev)
		option->prev->next = option->next;
	if(option == voteoption_first)
		voteoption_first = option->next;
	delete[] option->command;
	delete option;

	send_vote_options(-1);
}

extern "C" void delvote(const char* command)
{
	con_delvote(const_cast<char*>(command), NULL);
}

static void con_vote(void *result, void *user_data)
{
	if(str_comp_nocase(console_arg_string(result, 0), "yes") == 0)
		game.vote_enforce = GAMECONTEXT::VOTE_ENFORCE_YES;
	else if(str_comp_nocase(console_arg_string(result, 0), "no") == 0)
		game.vote_enforce = GAMECONTEXT::VOTE_ENFORCE_NO;
	dbg_msg("server", "forcing vote %s", console_arg_string(result, 0));
}

static void con_playerlist(void *result, void *user_data)
{
	char buf[128];

	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		if(game.players[i])
		{
			str_format(buf, sizeof(buf), "%d: %s", i, server_clientname(i));
			console_print(buf);	
		}
	}
}

static void con_all_kick(void *result, void *user_data)
{
	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		if(game.players[i])
		{
			server_kick(i, "Kicked by admin (all_kick)");
		}
	}

	char buf[128];

	str_format(buf, sizeof(buf), "Admin/Referee kicked all players.");
	game.send_broadcast(buf, -1);
	game.send_chat(-1, GAMECONTEXT::CHAT_ALL, buf);
}

static void con_kick_r(void *result, void *user_data)
{
	int player_id = clamp(console_arg_int(result, 0), 0, (int)MAX_CLIENTS);

	if(game.players[player_id])
	{
		char buf[128];
		str_format(buf, sizeof(buf), "Kicked: %s", console_arg_string(result, 1));
		server_kick(player_id, buf);
	}
}

static void con_melt(void *result, void *user_data)
{
	if(!game.controller->is_freeze)
		return;

	int player_id = clamp(console_arg_int(result, 0), -1, (int)MAX_CLIENTS);

	if(player_id == -1)
	{
		for(int i = 0; i < MAX_CLIENTS; i++)
		{
			if(game.players[i])
				game.players[i]->remove_chr(true);
		}
	}
	else
	{
		if(game.players[player_id])
			game.players[player_id]->remove_chr(true);
	}
}

static void con_kill(void *result, void *user_data)
{
	int player_id = clamp(console_arg_int(result, 0), -1, (int)MAX_CLIENTS);

	if(player_id == -1)
	{
		for(int i = 0; i < MAX_CLIENTS; i++)
		{
			if(game.players[i])
			{
				if(game.players[i]->get_character())
					game.players[i]->get_character()->die(i, WEAPON_SELF);
			}
		}
	}
	else
	{
		if(game.players[player_id])
		{
			if(game.players[player_id]->get_character())
				game.players[player_id]->get_character()->die(player_id, WEAPON_SELF);
		}
	}
}

static void con_all_spect(void *result, void *user_data)
{
	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		if(game.players[i])
		{
			if(game.players[i]->team >= 0)
				game.players[i]->set_team(-1);
		}
	}

	char buf[128];

	str_format(buf, sizeof(buf), "Admin/Referee forced all to spectate.");
	game.send_broadcast(buf, -1);
	game.send_chat(-1, GAMECONTEXT::CHAT_ALL, buf);
}

static void con_give(void *result, void *user_data)
{
	int player_id = clamp(console_arg_int(result, 0), -1, (int)MAX_CLIENTS);
	int type = clamp(console_arg_int(result, 1), 1, 2);

	char name[128];

	if(player_id == -1)
	{
		for(int i = 0; i < MAX_CLIENTS; i++)
		{
			if(game.players[i])
				game.players[i]->give(type);
		}
		
		str_format(name, sizeof(name), "everyone");
	}
	else
	{
		if(!game.players[player_id])
			return;

		game.players[player_id]->give(type);

		str_format(name, sizeof(name), server_clientname(player_id));
	}

	char buf[128];
	char c_type[128];

	switch(type)
	{
	case 1:
		str_format(c_type, sizeof(c_type), "grenade launcher");
		break;

	case 2:
		str_format(c_type, sizeof(c_type), "killingspree award");
		break;
	}

	str_format(buf, sizeof(buf), "Admin/Referee gave %s the %s.", name, c_type);
	game.send_broadcast(buf, -1);
	game.send_chat(-1, GAMECONTEXT::CHAT_ALL, buf);
}

static void con_stats(void *result, void *user_data)
{
	char buf[128];

	console_print("_____________________________________________________________________________________________");

	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		if(game.players[i])
		{
			str_format(buf, sizeof(buf), "| ID: %d | Name: %s | Score: %d | Shots: %d | Hits: %d | Kills: %d | Accuracy: %f | Deaths: %d | Kills/Deaths: %f |", i, server_clientname(i), game.players[i]->score, game.players[i]->s_shots, game.players[i]->s_hits, game.players[i]->s_kills, game.players[i]->s_shots ? (((game.controller->is_instagib() ? (float)game.players[i]->s_kills : (float)game.players[i]->s_hits) / (float)game.players[i]->s_shots) * 100.0f) : 0.0f, game.players[i]->s_deaths, game.players[i]->s_deaths ? (float)(game.players[i]->s_kills / game.players[i]->s_deaths) : 0);
			console_print(buf);
		}
	}
}

void mods_console_init()
{
	MACRO_REGISTER_COMMAND("tune", "si", CFGFLAG_SERVER, con_tune_param, 0, "");
	MACRO_REGISTER_COMMAND("tune_reset", "", CFGFLAG_SERVER, con_tune_reset, 0, "");
	MACRO_REGISTER_COMMAND("tune_dump", "", CFGFLAG_SERVER, con_tune_dump, 0, "");

	MACRO_REGISTER_COMMAND("change_map", "r", CFGFLAG_SERVER, con_change_map, 0, "");
	MACRO_REGISTER_COMMAND("restart", "?i", CFGFLAG_SERVER, con_restart, 0, "");
	MACRO_REGISTER_COMMAND("broadcast", "r", CFGFLAG_SERVER, con_broadcast, 0, "");
	MACRO_REGISTER_COMMAND("say", "r", CFGFLAG_SERVER, con_say, 0, "");
	MACRO_REGISTER_COMMAND("set_team", "ii", CFGFLAG_SERVER, con_set_team, 0, "");

	MACRO_REGISTER_COMMAND("addvote", "r", CFGFLAG_SERVER, con_addvote, 0, "");
	MACRO_REGISTER_COMMAND("vote", "r", CFGFLAG_SERVER, con_vote, 0, "");

	// Insta plus

	 // Player infos
	  MACRO_REGISTER_COMMAND("playerlist", "", CFGFLAG_SERVER, con_playerlist, 0,"");
	  MACRO_REGISTER_COMMAND("stats", "", CFGFLAG_SERVER, con_stats, 0,"");

	 // Freeze
	  MACRO_REGISTER_COMMAND("melt", "i", CFGFLAG_SERVER, con_melt, 0,"");

	 // Teams
	  MACRO_REGISTER_COMMAND("lock_team", "ii", CFGFLAG_SERVER, con_lock_team, 0, "");
	  MACRO_REGISTER_COMMAND("all_spect", "", CFGFLAG_SERVER, con_all_spect, 0,"");

     // Kicking
	  MACRO_REGISTER_COMMAND("all_kick", "", CFGFLAG_SERVER, con_all_kick, 0,"");
	  MACRO_REGISTER_COMMAND("kick_r", "is", CFGFLAG_SERVER, con_kick_r, 0, "");

     // Grenade
	  MACRO_REGISTER_COMMAND("give", "ii", CFGFLAG_SERVER, con_give, 0,"");

     // Killing
	  MACRO_REGISTER_COMMAND("kill", "i", CFGFLAG_SERVER, con_kill, 0,"");
}

void mods_init()
{
	//if(!data) /* only load once */
		//data = load_data_from_memory(internal_data);
		
	for(int i = 0; i < NUM_NETOBJTYPES; i++)
		snap_set_staticsize(i, netobj_get_size(i));

	layers_init();
	col_init();

	// reset everything here
	//world = new GAMEWORLD;
	//players = new PLAYER[MAX_CLIENTS];
	if(!strcmp(config.sv_gametype, "ctf") || !strcmp(config.sv_gametype, "CTF"))
	{
		game.controller = new GAMECONTROLLER_CTF;
		game.controller->make_vanilla("CTF+");
		game.controller->is_ctf = true;
		game.controller->is_freeze = false;
	}
	else if(!strcmp(config.sv_gametype, "tdm") || !strcmp(config.sv_gametype, "TDM"))
	{
		game.controller = new GAMECONTROLLER_TDM;
		game.controller->make_vanilla("TDM+");
		game.controller->is_ctf = false;
		game.controller->is_freeze = false;
	}
	else if(!strcmp(config.sv_gametype, "dm") || !strcmp(config.sv_gametype, "DM"))
	{
		game.controller = new GAMECONTROLLER_DM;
		game.controller->make_vanilla("DM+");
		game.controller->is_ctf = false;
		game.controller->is_freeze = false;
	}
	else if(!strcmp(config.sv_gametype, "ictf") || !strcmp(config.sv_gametype, "ictf+") || !strcmp(config.sv_gametype, "iCTF") || !strcmp(config.sv_gametype, "iCTF+"))
	{
		game.controller = new GAMECONTROLLER_CTF;
		game.controller->make_instagib("iCTF+");
		game.controller->is_ctf = true;
		game.controller->is_freeze = false;
	}
	else if(!strcmp(config.sv_gametype, "itdm") || !strcmp(config.sv_gametype, "itdm+") || !strcmp(config.sv_gametype, "iTDM") || !strcmp(config.sv_gametype, "iTDM+"))
	{
		game.controller = new GAMECONTROLLER_TDM;
		game.controller->make_instagib("iTDM+");
		game.controller->is_ctf = false;
		game.controller->is_freeze = false;
	}
	else if(!strcmp(config.sv_gametype, "idm") || !strcmp(config.sv_gametype, "idm+") || !strcmp(config.sv_gametype, "iDM") || !strcmp(config.sv_gametype, "iDM+"))
	{
		game.controller = new GAMECONTROLLER_DM;
		game.controller->make_instagib("iDM+");
		game.controller->is_ctf = false;
		game.controller->is_freeze = false;
	}
	else if(!strcmp(config.sv_gametype, "ifrzctf") || !strcmp(config.sv_gametype, "iFrzCtf+") || !strcmp(config.sv_gametype, "iFrzCtf+"))
	{
		game.controller = new GAMECONTROLLER_FREEZE;
		game.controller->make_instagib("iFrzCtf+");
		game.controller->game_flags = GAMEFLAG_TEAMS|GAMEFLAG_FLAGS;
		game.controller->is_ctf = true;
		game.controller->is_freeze = true;
	}
	else if(!strcmp(config.sv_gametype, "ifreeze"))
	{
		game.controller = new GAMECONTROLLER_FREEZE;
		game.controller->make_instagib("iFreeze+");
		game.controller->game_flags = GAMEFLAG_TEAMS;
		game.controller->is_ctf = false;
		game.controller->is_freeze = true;
	}
        else
        {
                game.controller = new GAMECONTROLLER_FB;
                game.controller->make_instagib("iFB+");
                game.controller->game_flags = GAMEFLAG_TEAMS|GAMEFLAG_FLAGS;
                game.controller->is_ctf = false;
                game.controller->is_freeze = false;
        }


	// setup core world
	//for(int i = 0; i < MAX_CLIENTS; i++)
	//	game.players[i].core.world = &game.world.core;

	// create all entities from the game layer
	MAPITEM_LAYER_TILEMAP *tmap = layers_game_layer();
	TILE *tiles = (TILE *)map_get_data(tmap->data);
	
	/*
	num_spawn_points[0] = 0;
	num_spawn_points[1] = 0;
	num_spawn_points[2] = 0;
	*/
	
	for(int y = 0; y < tmap->height; y++)
	{
		for(int x = 0; x < tmap->width; x++)
		{
			int index = tiles[y*tmap->width+x].index;
			
			if(index >= ENTITY_OFFSET)
			{
				vec2 pos(x*32.0f+16.0f, y*32.0f+16.0f);
				game.controller->on_entity(index-ENTITY_OFFSET, pos);
			}
		}
	}

	//game.world.insert_entity(game.controller);

#ifdef CONF_DEBUG
	if(config.dbg_dummies)
	{
		for(int i = 0; i < config.dbg_dummies ; i++)
		{
			mods_connected(MAX_CLIENTS-i-1);
			mods_client_enter(MAX_CLIENTS-i-1);
			if(game.controller->is_teamplay())
				game.players[MAX_CLIENTS-i-1]->team = i&1;
		}
	}
#endif
}

void mods_shutdown()
{
	statistics_end_round();
	delete game.controller;
	game.controller = 0;
	game.clear();
}

void mods_presnap() {}
void mods_postsnap()
{
	game.events.clear();
}

extern "C" const char *mods_net_version() { return GAME_NETVERSION; }
extern "C" const char *mods_version() { return GAME_VERSION; }
