#include <new>
#include <engine/e_server_interface.h>
#include <engine/e_config.h>
#include <game/server/gamecontext.hpp>
#include <game/mapitems.hpp>
#include <game/server/statistics.hpp>
#include <string.h>

#include "character.hpp"
#include "laser.hpp"
#include "projectile.hpp"
#include <game/server/gamemodes/fb.hpp>

struct INPUT_COUNT
{
	int presses;
	int releases;
};

static INPUT_COUNT count_input(int prev, int cur)
{
	INPUT_COUNT c = {0,0};
	prev &= INPUT_STATE_MASK;
	cur &= INPUT_STATE_MASK;
	int i = prev;
	while(i != cur)
	{
		i = (i+1)&INPUT_STATE_MASK;
		if(i&1)
			c.presses++;
		else
			c.releases++;
	}

	return c;
}


MACRO_ALLOC_POOL_ID_IMPL(CHARACTER, MAX_CLIENTS)

// player
CHARACTER::CHARACTER()
: ENTITY(NETOBJTYPE_CHARACTER)
{
	proximity_radius = phys_size;
}

void CHARACTER::reset()
{
	player->reset();
	destroy();
}

bool CHARACTER::spawn(PLAYER *player, vec2 pos, int team)
{
	player_state = PLAYERSTATE_UNKNOWN;
	emote_stop = -1;
	last_action = -1;
	active_weapon = WEAPON_GUN;
	last_weapon = WEAPON_HAMMER;
	queued_weapon = -1;
	
	if(game.controller->is_instagib())
	{
		active_weapon = WEAPON_RIFLE;
		last_weapon = WEAPON_RIFLE;
		queued_weapon = -1;
	}
	
	ball = NULL;
	goal_camp_tick = 0;

	//clear();
	this->player = player;
	this->pos = pos;
	this->team = team;
	
	core.reset();
	core.world = &game.world.core;
	core.pos = pos;
	game.world.core.characters[player->client_id] = &core;

	reckoning_tick = 0;
	mem_zero(&sendcore, sizeof(sendcore));
	mem_zero(&reckoningcore, sizeof(reckoningcore));
	
	game.world.insert_entity(this);
	alive = true;
	player->force_balanced = false;

	game.controller->on_character_spawn(this);

	return true;
}

void CHARACTER::destroy()
{
	game.world.core.characters[player->client_id] = 0;
	alive = false;
}

void CHARACTER::set_weapon(int w)
{
	if(w == active_weapon)
		return;
		
	last_weapon = active_weapon;
	queued_weapon = -1;
	active_weapon = w;
	if(active_weapon < 0 || active_weapon >= NUM_WEAPONS)
		active_weapon = 0;
	
	game.create_sound(pos, SOUND_WEAPON_SWITCH);
}

bool CHARACTER::is_grounded()
{
	if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5)))
		return true;
	if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5)))
		return true;
	return false;
}


int CHARACTER::handle_ninja()
{
	if(active_weapon != WEAPON_NINJA || ball)
		return 0;
	
	vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y));

	if ((game_tick() - ninja.activationtick) > (data->weapons.ninja.duration * server_tickspeed() / 1000) && !player->frozen)
	{
		// time's up, return
		weapons[WEAPON_NINJA].got = false;
		active_weapon = last_weapon;
		if(active_weapon == WEAPON_NINJA)
			active_weapon = WEAPON_GUN;
		set_weapon(active_weapon);
		return 0;
	}
	
	// force ninja weapon
	set_weapon(WEAPON_NINJA);

	ninja.currentmovetime--;

	if (ninja.currentmovetime == 0)
	{
		// reset player velocity
		core.vel *= 0.2f;
		//return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON;
	}

	if (ninja.currentmovetime > 0)
	{
		// Set player velocity
		core.vel = ninja.activationdir * data->weapons.ninja.velocity;
		vec2 oldpos = pos;
		move_box(&core.pos, &core.vel, vec2(phys_size, phys_size), 0.0f);
		// reset velocity so the client doesn't predict stuff
		core.vel = vec2(0.0f,0.0f);
		if ((ninja.currentmovetime % 2) == 0)
		{
			//create_smoke(pos);
		}

		// check if we hit anything along the way
		{
			CHARACTER *ents[64];
			vec2 dir = pos - oldpos;
			float radius = phys_size * 2.0f; //length(dir * 0.5f);
			vec2 center = oldpos + dir * 0.5f;
			int num = game.world.find_entities(center, radius, (ENTITY**)ents, 64, NETOBJTYPE_CHARACTER);

			for (int i = 0; i < num; i++)
			{
				// Check if entity is a player
				if (ents[i] == this)
					continue;
				// make sure we haven't hit this object before
				bool balreadyhit = false;
				for (int j = 0; j < numobjectshit; j++)
				{
					if (hitobjects[j] == ents[i])
						balreadyhit = true;
				}
				if (balreadyhit)
					continue;

				// check so we are sufficiently close
				if (distance(ents[i]->pos, pos) > (phys_size * 2.0f))
					continue;

				// hit a player, give him damage and stuffs...
				game.create_sound(ents[i]->pos, SOUND_NINJA_HIT);
				// set his velocity to fast upward (for now)
				if(numobjectshit < 10)
					hitobjects[numobjectshit++] = ents[i];
					
				ents[i]->take_damage(vec2(0,10.0f), data->weapons.ninja.base->damage, player->client_id,WEAPON_NINJA);
			}
		}
		return 0;
	}

	return 0;
}

//	handle ball as weapon
void CHARACTER::shoot_ball (vec2 direction)
{
	if (!ball) return;

	//	drop ball
	game.create_sound_global (SOUND_CTF_DROP);
	ball->drop_tick = server_tick ();
	ball->last_carrier = player;
	ball->last_carrier_team = team;
	ball->carrier = 0;
	ball->vel = direction;
        ball = NULL;

	// stop ninja-ball-mode
	weapons[WEAPON_NINJA].got = false;
	active_weapon = last_weapon;
	if (active_weapon == WEAPON_NINJA)
		active_weapon = WEAPON_GUN;
	set_weapon (active_weapon);
}

void CHARACTER::capture_ball(BALL* b) 
{
	ball = b;
	b->carrier = this;
	b->last_carrier_team = team;
	b->pos = core.pos;
	b->at_stand = 0;

	if (team == b->team)
		game.create_sound_global(SOUND_CTF_GRAB_EN);
	else
		game.create_sound_global(SOUND_CTF_GRAB_PL);

	//      switch to ninja-ball-mode
	ninja.activationtick = game_tick();
	weapons[WEAPON_NINJA].got = true;
	weapons[WEAPON_NINJA].ammo = -1;
	last_weapon = active_weapon;
	active_weapon = WEAPON_NINJA;
}

void CHARACTER::do_weaponswitch()
{
	if(reload_timer != 0) // make sure we have reloaded
		return;
		
	if(queued_weapon == -1) // check for a queued weapon
		return;

	if(weapons[WEAPON_NINJA].got) // if we have ninja, no weapon selection is possible
		return;

	// switch weapon
	set_weapon(queued_weapon);
}

void CHARACTER::handle_weaponswitch()
{
	int wanted_weapon = active_weapon;
	if(queued_weapon != -1)
		wanted_weapon = queued_weapon;
	
	// select weapon
	int next = count_input(latest_previnput.next_weapon, latest_input.next_weapon).presses;
	int prev = count_input(latest_previnput.prev_weapon, latest_input.prev_weapon).presses;

	if(next < 128) // make sure we only try sane stuff
	{
		while(next) // next weapon selection
		{
			wanted_weapon = (wanted_weapon+1)%NUM_WEAPONS;
			if(weapons[wanted_weapon].got)
				next--;
		}
	}

	if(prev < 128) // make sure we only try sane stuff
	{
		while(prev) // prev weapon selection
		{
			wanted_weapon = (wanted_weapon-1)<0?NUM_WEAPONS-1:wanted_weapon-1;
			if(weapons[wanted_weapon].got)
				prev--;
		}
	}

	// direct weapon selection
	if(latest_input.wanted_weapon)
		wanted_weapon = input.wanted_weapon-1;

	// check for insane values
	if(wanted_weapon >= 0 && wanted_weapon < NUM_WEAPONS && wanted_weapon != active_weapon && weapons[wanted_weapon].got)
		queued_weapon = wanted_weapon;
	
	do_weaponswitch();
}

void CHARACTER::fire_weapon()
{
	if(reload_timer != 0)
		return;
		
	do_weaponswitch();
	
	vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y));
	
	bool fullauto = false;
	if(active_weapon == WEAPON_GRENADE || active_weapon == WEAPON_SHOTGUN || active_weapon == WEAPON_RIFLE)
		fullauto = true;


	// check if we gonna fire
	bool will_fire = false;
	if(count_input(latest_previnput.fire, latest_input.fire).presses) will_fire = true;
	if(fullauto && (latest_input.fire&1) && weapons[active_weapon].ammo) will_fire = true;
	if(col_is_melt(pos.x, pos.y) && config.sv_melt_tiles_noshoot)
	{
		if(game_tick()%server_tickspeed() == 0)
			game.send_broadcast("Too hot to shoot...", player->client_id);

		will_fire = false;
	}
	if(!will_fire)
		return;
		
	// check for ammo
	if(!weapons[active_weapon].ammo)
	{
		// 125ms is a magical limit of how fast a human can click
		reload_timer = 125 * server_tickspeed() / 1000;;
		game.create_sound(pos, SOUND_WEAPON_NOAMMO);
		return;
	}
	
	vec2 projectile_startpos = pos+direction*phys_size*0.75f;
	
	switch(active_weapon)
	{
		case WEAPON_HAMMER:
		{
			// reset objects hit
			numobjectshit = 0;
			game.create_sound(pos, SOUND_HAMMER_FIRE);
			
			CHARACTER *ents[64];
			int hits = 0;
			int num = game.world.find_entities(pos+direction*phys_size*0.75f, phys_size*0.5f, (ENTITY**)ents, 64, NETOBJTYPE_CHARACTER);

			for (int i = 0; i < num; i++)
			{
				CHARACTER *target = ents[i];
				if (target == this)
					continue;
					
				// hit a player, give him damage and stuffs...
				vec2 fdir = normalize(ents[i]->pos - pos);

				// set his velocity to fast upward (for now)
				game.create_hammerhit(pos);
				ents[i]->take_damage(vec2(0,-1.0f), data->weapons.hammer.base->damage, player->client_id, active_weapon);
				vec2 dir;
				if (length(target->pos - pos) > 0.0f)
					dir = normalize(target->pos - pos);
				else
					dir = vec2(0,-1);
					
				target->core.vel += normalize(dir + vec2(0,-1.1f)) * 10.0f;
				hits++;
			}
			
			// if we hit anything, we have to wait for the reload
			if(hits)
				reload_timer = server_tickspeed()/3;
			
		} break;

		case WEAPON_GUN:
		{
			player->s_shots++;

			PROJECTILE *proj = new PROJECTILE(WEAPON_GUN,
				player->client_id,
				projectile_startpos,
				direction,
				(int)(server_tickspeed()*tuning.gun_lifetime),
				1, 0, 0, -1, WEAPON_GUN);
				
			// pack the projectile and send it to the client directly
			NETOBJ_PROJECTILE p;
			proj->fill_info(&p);
			
			msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0);
			msg_pack_int(1);
			for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++)
				msg_pack_int(((int *)&p)[i]);
			msg_pack_end();
			server_send_msg(player->client_id);
							
			game.create_sound(pos, SOUND_GUN_FIRE);
		} break;
		
		case WEAPON_SHOTGUN:
		{
			player->s_shots++;

			int shotspread = 2;

			msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0);
			msg_pack_int(shotspread*2+1);
			
			for(int i = -shotspread; i <= shotspread; i++)
			{
				float spreading[] = {-0.185f, -0.070f, 0, 0.070f, 0.185f};
				float a = get_angle(direction);
				a += spreading[i+2];
				float v = 1-(abs(i)/(float)shotspread);
				float speed = mix((float)tuning.shotgun_speeddiff, 1.0f, v);
				PROJECTILE *proj = new PROJECTILE(WEAPON_SHOTGUN,
					player->client_id,
					projectile_startpos,
					vec2(cosf(a), sinf(a))*speed,
					(int)(server_tickspeed()*tuning.shotgun_lifetime),
					1, 0, 0, -1, WEAPON_SHOTGUN);
					
				// pack the projectile and send it to the client directly
				NETOBJ_PROJECTILE p;
				proj->fill_info(&p);
				
				for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++)
					msg_pack_int(((int *)&p)[i]);
			}

			msg_pack_end();
			server_send_msg(player->client_id);					
			
			game.create_sound(pos, SOUND_SHOTGUN_FIRE);
		} break;

		case WEAPON_GRENADE:
		{
			player->s_shots++;

			if(config.sv_grenade_split)
			{
				for(float i = -0.5f * weapons[active_weapon].ammo; i < 0.5f * weapons[active_weapon].ammo; i++)
				{
					float a = get_angle(direction);
					a += i * 0.1f;

					vec2 ndir(direction.x + 0.5f * i, direction.y + 0.5f * i);

					PROJECTILE *proj = new PROJECTILE(WEAPON_GRENADE,
						player->client_id,
						projectile_startpos,
						vec2(cosf(a), sinf(a)),
						(int)(server_tickspeed()*tuning.grenade_lifetime),
						1, PROJECTILE::PROJECTILE_FLAGS_EXPLODE, 0, SOUND_GRENADE_EXPLODE, WEAPON_GRENADE);

					// pack the projectile and send it to the client directly
					NETOBJ_PROJECTILE p;
					proj->fill_info(&p);
			
					msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0);
					msg_pack_int(1);
					for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++)
						msg_pack_int(((int *)&p)[i]);
					msg_pack_end();
					server_send_msg(player->client_id);
				}
			}
			else
			{
				PROJECTILE *proj = new PROJECTILE(WEAPON_GRENADE,
					player->client_id,
					projectile_startpos,
					direction,
					(int)(server_tickspeed()*tuning.grenade_lifetime),
					1, PROJECTILE::PROJECTILE_FLAGS_EXPLODE, 0, SOUND_GRENADE_EXPLODE, WEAPON_GRENADE);

				// pack the projectile and send it to the client directly
				NETOBJ_PROJECTILE p;
				proj->fill_info(&p);
			
				msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0);
				msg_pack_int(1);
				for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++)
					msg_pack_int(((int *)&p)[i]);
				msg_pack_end();
				server_send_msg(player->client_id);
			}

			game.create_sound(pos, SOUND_GRENADE_FIRE);
		} break;
		
		case WEAPON_RIFLE:
		{
			player->s_shots++;
			statistics_shoot(player->client_id);

			if(player->award)
			{
				if(config.sv_killingspree_award_lasers == 1)
				{
					new LASER(pos, normalize(direction), tuning.laser_reach, player->client_id);
				}
				else
				{
					for(float i = -0.5f * config.sv_killingspree_award_lasers; i < 0.5f * config.sv_killingspree_award_lasers; i++)
					{
						float a = get_angle(direction);
						a += i * 0.01f * (float)config.sv_killingspree_award_lasers_split;
						new LASER(pos, normalize(vec2(cosf(a), sinf(a))), tuning.laser_reach, player->client_id);
					}
				}
			}
			else
			{
				if(config.sv_lasers == 1)
				{
					new LASER(pos, normalize(direction), tuning.laser_reach, player->client_id);
				}
				else
				{
					for(float i = -0.5f * config.sv_lasers; i < 0.5f * config.sv_lasers; i++)
					{
						float a = get_angle(direction);
						a += i * 0.01f * (float)config.sv_lasers_split;
						new LASER(pos, normalize(vec2(cosf(a), sinf(a))), tuning.laser_reach, player->client_id);
					}
				}
			}

			game.create_sound(pos, SOUND_RIFLE_FIRE);
		} break;
		
		case WEAPON_NINJA:
		{
			if (ball)
			{
				shoot_ball(normalize (vec2 (latest_input.target_x, latest_input.target_y)) *
					static_cast<float> (config.sv_fb_ball_velocity / 10.0f));  //20.75f;
				reload_timer = data->weapons.id[active_weapon].firedelay * server_tickspeed() / 1000;	//	prevent shooting
				return;
			}
			else
			{
				attack_tick = game_tick();
				ninja.activationdir = direction;
				ninja.currentmovetime = data->weapons.ninja.movetime * server_tickspeed() / 1000;

				//reload_timer = data->weapons.ninja.base->firedelay * server_tickspeed() / 1000 + server_tick();
			
				// reset hit objects
				numobjectshit = 0;

				game.create_sound(pos, SOUND_NINJA_FIRE);
			}
			
		} break;
		
	}

	if(weapons[active_weapon].ammo > 0 && !(config.sv_grenade_split && active_weapon == WEAPON_GRENADE)) // -1 == unlimited
		weapons[active_weapon].ammo--;
	attack_tick = game_tick();
	if(!reload_timer)
	{
		int firedelay;

		switch(active_weapon)
		{
		case WEAPON_RIFLE:
			firedelay = config.sv_laser_reload;
			break;
		case WEAPON_GRENADE:
			firedelay = config.sv_grenade_reload;
			break;
		default:
			firedelay = data->weapons.id[active_weapon].firedelay;
			break;
		}

		if(player->award)
			firedelay *= ((float)config.sv_killingspree_award_reload / (float)100);

		reload_timer = firedelay * server_tickspeed() / 1000;
	}
}

int CHARACTER::handle_weapons()
{
	vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y));

	/*
	if(config.dbg_stress)
	{
		for(int i = 0; i < NUM_WEAPONS; i++)
		{
			weapons[i].got = true;
			weapons[i].ammo = 10;
		}

		if(reload_timer) // twice as fast reload
			reload_timer--;
	} */

	//if(active_weapon == WEAPON_NINJA)
	handle_ninja();


	// check reload timer
	if(reload_timer)
	{
		reload_timer--;
		return 0;
	}
	
	/*
	if (active_weapon == WEAPON_NINJA)
	{
		// don't update other weapons while ninja is active
		return handle_ninja();
	}*/

	// fire weapon, if wanted
	fire_weapon();

	// ammo regen
	int ammoregentime = data->weapons.id[active_weapon].ammoregentime;
	if(ammoregentime)
	{
		// If equipped and not active, regen ammo?
		if (reload_timer <= 0)
		{
			if (weapons[active_weapon].ammoregenstart < 0)
				weapons[active_weapon].ammoregenstart = game_tick();

			if ((game_tick() - weapons[active_weapon].ammoregenstart) >= ammoregentime * server_tickspeed() / 1000)
			{
				// Add some ammo
				weapons[active_weapon].ammo = min(weapons[active_weapon].ammo + 1, 10);
				weapons[active_weapon].ammoregenstart = -1;
			}
		}
		else
		{
			weapons[active_weapon].ammoregenstart = -1;
		}
	}
	
	return 0;
}

void CHARACTER::on_predicted_input(NETOBJ_PLAYER_INPUT *new_input)
{
	if(player->frozen)
		return;

	// check for changes
	if(mem_comp(&input, new_input, sizeof(NETOBJ_PLAYER_INPUT)) != 0)
	{
		if(last_action != -1)
			player->last_input = server_tick();
 		last_action = game_tick();
	}
		
	// copy new input
	mem_copy(&input, new_input, sizeof(input));
	num_inputs++;
	
	// or are not allowed to aim in the center
	if(input.target_x == 0 && input.target_y == 0)
		input.target_y = -1;	
}

void CHARACTER::on_direct_input(NETOBJ_PLAYER_INPUT *new_input)
{
	if(game.world.paused || player->frozen)
	{
		player_state = new_input->player_state;
		return;
	}

	mem_copy(&latest_previnput, &latest_input, sizeof(latest_input));
	mem_copy(&latest_input, new_input, sizeof(latest_input));
	
	if(num_inputs > 2 && team != -1)
	{
		handle_weaponswitch();
		fire_weapon();
	}
	
	mem_copy(&latest_previnput, &latest_input, sizeof(latest_input));
}

void CHARACTER::tick()
{

	if(player->force_balanced)
	{
		char buf[128];
		str_format(buf, sizeof(buf), "You were moved to %s due to team balancing", game.controller->get_team_name(team));
		game.send_broadcast(buf, player->client_id);
		
		player->force_balanced = false;
	}


	//player_core core;
	//core.pos = pos;
	//core.jumped = jumped;
	core.input = input;
	core.tick(true);

        // direct pass of ball
	if (config.sv_fb_direct_pass && ball && core.triggered_events & COREEVENT_HOOK_ATTACH_PLAYER && core.hooked_player != -1)
	{
		// find catcher. Is there an easier way to do this?
		CHARACTER* catcher = (CHARACTER *)game.world.find_first(NETOBJTYPE_CHARACTER);
		while (catcher && &catcher->core != core.world->characters[core.hooked_player])
			catcher = (CHARACTER *)catcher->typenext();

		if (catcher)
		{

			BALL* b = ball;
			shoot_ball(vec2(0, 0));
			catcher->capture_ball(b);

			// release hook
			core.hooked_player = -1;
			core.hook_state = HOOK_RETRACTED;
			core.hook_pos = core.pos;
		};
	};

	// hook the flag
	if (game.controller->is_flagball() && config.sv_fb_hook_ball && !ball && (core.hook_state == HOOK_FLYING || core.triggered_events & (COREEVENT_HOOK_ATTACH_GROUND + COREEVENT_HOOK_HIT_NOHOOK)))
	{
		// Find the closest ball
		BALL* closest_ball = NULL;
		for (int i = 0; i < FB_MAX_BALLS; ++i)
		{
			BALL* b = static_cast<GAMECONTROLLER_FB *>(game.controller)->balls[i];
			if (b && !b->carrier)
			{
				// It may be not a good idea to pass this to
				// config, but we need some easy way to test
				// these values
				/* static */ vec2 ball_offset(config.sv_fb_ball_offset_x, -config.sv_fb_ball_offset_y);
				vec2 ball_pos = b->pos + ball_offset;
				vec2 closest_point = closest_point_on_line(core.pos, core.hook_pos, ball_pos);
				if (distance(ball_pos, closest_point) < config.sv_fb_ball_radius && (!closest_ball || distance(pos, ball_pos) < distance(pos, closest_ball->pos)))
				{
					closest_ball = b;
				};
			};
		};
		if (closest_ball)
		{
			capture_ball(closest_ball);
			// retract hook
			core.hook_state = HOOK_RETRACTED;
			core.hook_pos = core.pos;
		};
	};
	

	float phys_size = 28.0f;
	// handle death-tiles
	if(col_get((int)(pos.x+phys_size/2), (int)(pos.y-phys_size/2))&COLFLAG_DEATH ||
			col_get((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2))&COLFLAG_DEATH ||
			col_get((int)(pos.x-phys_size/2), (int)(pos.y-phys_size/2))&COLFLAG_DEATH ||
			col_get((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2))&COLFLAG_DEATH)
	{
		die(player->client_id, WEAPON_WORLD);
	}

	// handle weapons
	handle_weapons();

	player_state = input.player_state;

	// Previnput
	previnput = input;
	return;
}

void CHARACTER::tick_defered()
{
	// advance the dummy
	{
		WORLD_CORE tempworld;
		reckoningcore.world = &tempworld;
		reckoningcore.tick(false);
		reckoningcore.move();
		reckoningcore.quantize();
	}
	
	//lastsentcore;
	/*if(!dead)
	{*/
		vec2 start_pos = core.pos;
		vec2 start_vel = core.vel;
		bool stuck_before = test_box(core.pos, vec2(28.0f, 28.0f));
		
		core.move();
		bool stuck_after_move = test_box(core.pos, vec2(28.0f, 28.0f));
		core.quantize();
		bool stuck_after_quant = test_box(core.pos, vec2(28.0f, 28.0f));
		pos = core.pos;
		
		if(!stuck_before && (stuck_after_move || stuck_after_quant))
		{
			dbg_msg("player", "STUCK!!! %d %d %d %f %f %f %f %x %x %x %x", 
				stuck_before,
				stuck_after_move,
				stuck_after_quant,
				start_pos.x, start_pos.y,
				start_vel.x, start_vel.y,
				*((unsigned *)&start_pos.x), *((unsigned *)&start_pos.y),
				*((unsigned *)&start_vel.x), *((unsigned *)&start_vel.y));
		}

		int events = core.triggered_events;
		int mask = cmask_all_except_one(player->client_id);
		
		if(events&COREEVENT_GROUND_JUMP) game.create_sound(pos, SOUND_PLAYER_JUMP, mask);
		
		//if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos);
		if(events&COREEVENT_HOOK_ATTACH_PLAYER) game.create_sound(pos, SOUND_HOOK_ATTACH_PLAYER, cmask_all());
		if(events&COREEVENT_HOOK_ATTACH_GROUND) game.create_sound(pos, SOUND_HOOK_ATTACH_GROUND, mask);
		if(events&COREEVENT_HOOK_HIT_NOHOOK) game.create_sound(pos, SOUND_HOOK_NOATTACH, mask);
		//if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos);
	//}
	
	if(team == -1)
	{
		pos.x = input.target_x;
		pos.y = input.target_y;
	}
	
	// update the sendcore if needed
	{
		NETOBJ_CHARACTER predicted;
		NETOBJ_CHARACTER current;
		mem_zero(&predicted, sizeof(predicted));
		mem_zero(&current, sizeof(current));
		reckoningcore.write(&predicted);
		core.write(&current);

		// only allow dead reackoning for a top of 3 seconds
		if(reckoning_tick+server_tickspeed()*3 < server_tick() || mem_comp(&predicted, &current, sizeof(NETOBJ_CHARACTER)) != 0)
		{
			reckoning_tick = server_tick();
			sendcore = core;
			reckoningcore = core;
		}
	}
}

bool CHARACTER::increase_health(int amount)
{
	if(health >= 10)
		return false;
	health = clamp(health+amount, 0, 10);
	return true;
}

bool CHARACTER::increase_armor(int amount)
{
	if(armor >= 10)
		return false;
	armor = clamp(armor+amount, 0, 10);
	return true;
}

void CHARACTER::die(int killer, int weapon)
{
	if(player && game.controller->is_freeze)
	{
		if(player->frozen && weapon != WEAPON_GAME && weapon != WEAPON_WORLD)
			return;

		if(weapon == WEAPON_WORLD && config.sv_void_frozen)
			player->join_frozen = true;
	}

	// set attacker's face to happy (taunt!)
	if (killer >= 0 && killer != player->client_id && game.players[killer])
	{
		CHARACTER *chr = game.players[killer]->get_character();
		if (chr)
		{
			chr->emote_type = EMOTE_HAPPY;
			chr->emote_stop = game_tick() + server_tickspeed();
		}
	}

	/*if (dead || team == -1)
		return;*/
	int mode_special = game.controller->on_character_death(this->player, game.players[killer], weapon);

	dbg_msg("game", "kill killer='%d:%s' victim='%d:%s' weapon=%d special=%d",
		killer, server_clientname(killer),
		player->client_id, server_clientname(player->client_id), weapon, mode_special);


	// send the kill message
	NETMSG_SV_KILLMSG msg;
	msg.killer = killer;
	msg.victim = player->client_id;
	msg.weapon = weapon;
	msg.mode_special = mode_special;
	msg.pack(MSGFLAG_VITAL);
	server_send_msg(-1);

	// a nice sound
	game.create_sound(pos, SOUND_PLAYER_DIE);

	// set dead state
	// TODO: do stuff here
	/*
	die_pos = pos;
	dead = true;
	*/
	
	last_action = 0;
	num_inputs = 0;

	NETOBJ_PLAYER_INPUT buff_input = {0};

	// these are non-heldback inputs
	latest_previnput = buff_input;
	latest_input = buff_input;

	// input
	previnput = buff_input;
	input = buff_input;

	if(game.controller->is_freeze && weapon != WEAPON_GAME && weapon != WEAPON_WORLD)
		return;

	if(player)
		player->remove_chr(true);


}

bool CHARACTER::take_damage(vec2 force, int dmg, int from, int weapon)
{
	core.vel += force;

	if(game.controller->is_friendly_fire(player->client_id, from) && !config.sv_teamdamage)
		return false;
	
	if(game.controller->is_instagib() && weapon == WEAPON_GAME)
		return false;

	if(((!config.sv_grenade_selfdamage && from == player->client_id) || player->frozen) && weapon == WEAPON_GRENADE)
		return false;

	// player only inflicts half damage on self
	if(from == player->client_id)
		dmg = max(1, dmg/2);

	if(weapon != WEAPON_HAMMER && weapon != WEAPON_NINJA && from != player->client_id && game.players[from])
	{
		game.players[from]->s_hits++;
	}

	if(game.controller->is_instagib() && weapon == WEAPON_RIFLE)
	{
		if(player && game.players[from])
		{
			if(!player->frozen)
				game.create_sound_global(SOUND_HIT, from);
		}
		die(from, weapon);
		return true;
	}

	// create healthmod indicator
	if(game_tick() < damage_taken_tick+25)
	{
		// make sure that the damage indicators doesn't group together
		game.create_damageind(pos, damage_taken*0.25f, dmg);
	}
	else
	{
		damage_taken = 0;
		game.create_damageind(pos, 0, dmg);
	}

	if(dmg)
	{
		if(armor)
		{
			if(dmg > 1)
			{
				health--;
				dmg--;
			}

			if(dmg > armor)
			{
				dmg -= armor;
				armor = 0;
			}
			else
			{
				armor -= dmg;
				dmg = 0;
			}
		}

		health -= dmg;
	}

	damage_taken_tick = game_tick();

	// do damage hit sound
	if(from >= 0 && from != player->client_id && game.players[from])
		game.create_sound(game.players[from]->view_pos, SOUND_HIT, cmask_one(from));

	// check for death
	if(health <= 0)
	{
		die(from, weapon);
		
		// set attacker's face to happy (taunt!)
		if (from >= 0 && from != player->client_id && game.players[from])
		{
			CHARACTER *chr = game.players[from]->get_character();
			if (chr)
			{
				chr->emote_type = EMOTE_HAPPY;
				chr->emote_stop = game_tick() + server_tickspeed();
			}
		}
	
		return false;
	}
	else
	{
		// lose flag on every damage
		if (ball && config.sv_fb_ball_damage_lose)
			shoot_ball (vec2(0.0f,3.0f) * 3.75f);
	}

	if (dmg > 2)
		game.create_sound(pos, SOUND_PLAYER_PAIN_LONG);
	else
		game.create_sound(pos, SOUND_PLAYER_PAIN_SHORT);

	emote_type = EMOTE_PAIN;
	emote_stop = game_tick() + 500 * server_tickspeed() / 1000;

	// spawn blood?
	return true;
}

void CHARACTER::snap(int snapping_client)
{
	if(networkclipped(snapping_client))
		return;
	
	NETOBJ_CHARACTER *character = (NETOBJ_CHARACTER *)snap_new_item(NETOBJTYPE_CHARACTER, player->client_id, sizeof(NETOBJ_CHARACTER));
	
	// write down the core
	if(game.world.paused)
	{
		// no dead reckoning when paused because the client doesn't know
		// how far to perform the reckoning
		character->tick = 0;
		core.write(character);
	}
	else
	{
		character->tick = reckoning_tick;
		sendcore.write(character);
	}
	
	// set emote
	if (emote_stop < game_tick())
	{
		emote_type = EMOTE_NORMAL;
		emote_stop = -1;
	}

	character->emote = emote_type;

	character->ammocount = 0;
	character->health = 0;
	character->armor = 0;
	
	character->weapon = active_weapon;
	character->attacktick = attack_tick;

	character->direction = input.direction;

	if(player->client_id == snapping_client)
	{
		character->health = health;
		character->armor = armor;
		if(weapons[active_weapon].ammo > 0)
			character->ammocount = weapons[active_weapon].ammo;
	}

	if (character->emote == EMOTE_NORMAL)
	{
		if(250 - ((game_tick() - last_action)%(250)) < 5)
			character->emote = EMOTE_BLINK;
	}

	character->player_state = player_state;
}
