
#include "nx.h"
#include "player.fdh"

Player *player = NULL;
static void InitWeapon(int wpn, int l1, int l2, int l3, int maxammo=0);

bool pinputs[INPUT_COUNT];
bool lastpinputs[INPUT_COUNT];

void PInitFirstTime()
{
	player->dir = RIGHT;
	player->hp = player->maxHealth = 3;
	player->nxflags |= NXFLAG_FOLLOW_SLOPE;
	
	player->ninventory = 0;
	
	memset(player->weapons, 0, sizeof(player->weapons));
	
	InitWeapon(WPN_POLARSTAR, 		10, 20, 10);
	InitWeapon(WPN_MGUN, 			30, 40, 10, 100);
	InitWeapon(WPN_MISSILE,			10, 20, 10, 10);
	InitWeapon(WPN_FIREBALL,		10, 20, 20);
	InitWeapon(WPN_BLADE,			15, 18, 0);
	InitWeapon(WPN_BUBBLER,			10, 20, 5);
	InitWeapon(WPN_SUPER_MISSILE,	30, 60, 10, 10);
	InitWeapon(WPN_SNAKE,			30, 40, 16);
	InitWeapon(WPN_SPUR,			40, 60, 200);
	InitWeapon(WPN_NEMESIS,			1,  1,  0);
	
	player->weapons[WPN_MGUN].SetFireRate(6, 6, 6);
	player->weapons[WPN_MGUN].SetRechargeRate(5, 5, 5);
	
	player->weapons[WPN_BUBBLER].SetFireRate(0, 7, 7);
	player->weapons[WPN_BUBBLER].SetRechargeRate(20, 1, 1);
	
	player->curWeapon = WPN_NONE;
	
	if (player->XPText) delete player->XPText;
	player->XPText = new FloatText(SPR_WHITENUMBERS);
	
	// initialize player repel points
	PInitRepel();
}


static void InitWeapon(int wpn, int l1, int l2, int l3, int maxammo)
{
	player->weapons[wpn].max_xp[0] = l1;
	player->weapons[wpn].max_xp[1] = l2;
	player->weapons[wpn].max_xp[2] = l3;
	player->weapons[wpn].maxammo = maxammo;
}


void InitPlayer(void)
{
	player->lookaway = false;
	player->walking = false;
	player->dead = false;
	player->drowned = false;
	player->disabled = false;
	
	player->hurt_time = 0;
	player->hurt_flash_state = 0;
	player->water_shield_frame = 0;
	player->movementmode = MOVEMODE_NORMAL;
	player->inputs_locked_lasttime = true;
	
	player->booststate = BOOST_OFF;
	player->lastbooststate = BOOST_OFF;
	player->boosterfuel = BOOSTER_FUEL_QTY;
	
	player->xinertia = 0;
	player->yinertia = 0;
	
	player->riding = NULL;
	player->lastriding = NULL;
	player->cannotride = NULL;
	
	player->DamageText->Reset();
	player->XPText->Reset();
	statusbar.xpflashcount = 0;
	
	PResetWeapons();
	PSelectSprite();
	
	// this prevents a splash if we start underwater, and prevents us
	// from drowning immediately since our air isn't yet set up
	player->touchattr = TA_WATER;
	player->airleft = 1000;
	player->airshowtimer = 0;
}

Player::~Player()
{
	if (XPText)
	{
		delete XPText;
		XPText = NULL;
	}
}

/*
void c------------------------------() {}
*/

void HandlePlayer(void)
{
	// freeze player for the split-second between <TRA to a new map and the
	// start of the on-entry script for that map. (Fixes: player could shoot during
	// end sequence if he holds key down).
	if (game.switchstage.mapno != -1)
		return;
	
	PUpdateInput();
	
	if (!player->dead)
	{
		PHandleAttributes();			// handle special tile attributes
		PHandleSolidMushyObjects();		// handle objects like bugs marked "solid / mushy"
		
		PDoWeapons();	// p_arms.cpp
		PDoHurtFlash();
		
		switch((inputs[DEBUG_MOVE_KEY] && settings->enable_debug_keys) ? MOVEMODE_DEBUG : \
			   player->movementmode)
		{
			case MOVEMODE_NORMAL:
			{
				PDoBooster();
				PDoBoosterEnd();
				PDoWalking();
				PDoLooking();
				PDoJumping();
				PDoFalling();
				PSelectFrame();
			}
			break;
			
			case MOVEMODE_ZEROG:		// Ironhead battle/UNI 1
			{
				PHandleZeroG();
			}
			break;
			
			case MOVEMODE_DEBUG:
			{
				player->xinertia = player->yinertia = 0;
				player->blockl = player->blockr = player->blockd = player->blocku = 0;
				
				if (inputs[DOWNKEY]) player->y += 0x1000;
				if (inputs[UPKEY]) player->y -= 0x1000;
				if (inputs[LEFTKEY]) { player->x -= 0x1000; player->dir = LEFT; }
				if (inputs[RIGHTKEY]) { player->x += 0x1000; player->dir = RIGHT; }
				
				map_scroll_jump(player->x, player->y);
				
				player->frame = 2;
			}
			break;
			
			default:
			{
				player->xinertia = player->yinertia = 0;
			}
			break;
		}
		
		// handle some special features, like damage and bouncy, of
		// 100% solid objects such as moving blocks. It's put at the end
		// so that we can see the desired inertia of the player before
		// it's canceled out by any block points that are set. That way
		// we can tell if the player is trying to move into it.
		PHandleSolidBrickObjects();
	}
	
	// apply inertia
	PDoPhysics();
	
	// thud sound when land on some objects
	if (player->riding && !player->lastriding &&
		(player->riding->nxflags & NXFLAG_THUD_ON_RIDING))
	{
		sound(SND_THUD);
	}
}

// player aftermove routine
void HandlePlayer_am(void)
{
	//debug("xinertia: %s", strhex(player->xinertia));
	//debug("yinertia: %s", strhex(player->yinertia));
	//debug("booststate: %d", player->booststate);
	//debug("y: %d", player->y>>CSF);
	//debug("riding %x", player->riding);
	//debug("block: %d%d%d%d", player->blockl, player->blockr, player->blocku, player->blockd);
	
	// if player is riding some sort of platform apply it's inertia to him
	if (player->riding)
	{
		player->apply_xinertia(player->riding->xinertia);
		player->apply_yinertia(player->riding->yinertia);
	}
	
	// keep player out of blocks "SMB1 style"
	PDoRepel();
	
	// handle landing and bonking head
	if (player->blockd && player->yinertia > 0)
	{
		if (player->yinertia > 0x400 && !player->hide)
			sound(SND_THUD);
		
		player->yinertia = 0;
		player->jumping = 0;
	}
	else if (player->blocku && player->yinertia < 0)
	{
		// he behaves a bit differently when bonking his head on a
		// solid-brick object vs. bonking his head on the map.
		
		// bonk-head star effect
		if (player->yinertia < -0x200 && !player->hide && \
			player->blocku == BLOCKED_MAP)
		{
			sound(SND_BONK_HEAD);
			effect(player->CenterX(), player->y, EFFECT_BONKPLUS);
		}
		
		// bounces off ceiling with booster 0.8
		if (player->booststate == BOOST_08)
		{
			player->yinertia = 0x200;
		}
		else if (player->bopped_object && player->bopped_object->yinertia != 0)
		{
			// no clear yinertia when bop head on OBJ_BLOCK_MOVEV in labyrinth.
		}
		else
		{
			player->yinertia = 0;
		}
		
		player->jumping = false;
	}
	
	player->lastwalking = player->walking;
	player->lastriding = player->riding;
	player->inputs_locked_lasttime = player->inputs_locked;
	memcpy(lastpinputs, pinputs, sizeof(lastpinputs));
}

/*
void c------------------------------() {}
*/

void PDoPhysics(void)
{
	if (player->xinertia > 0x5ff)  player->xinertia = 0x5ff;
	if (player->xinertia < -0x5ff) player->xinertia = -0x5ff;
	if (player->yinertia > 0x5ff)  player->yinertia = 0x5ff;
	if (player->yinertia < -0x5ff) player->yinertia = -0x5ff;
	
	if (player->blockd && player->yinertia > 0)
		player->yinertia = 0;
	
	player->apply_yinertia(player->yinertia);
	
	// if xinertia is less than the decel speed then maintain the value but don't actually
	// move anything. It seems a bit odd...but that's the best I can figure to make it
	// behave like the original.
	if (player->xinertia > player->decelspeed || player->xinertia < -player->decelspeed)
	{
		player->apply_xinertia(player->xinertia);
	}
}

void PUpdateInput(void)
{
int i;

	if (player->inputs_locked || player->disabled)
	{
		memset(pinputs, 0, sizeof(pinputs));
	}
	else
	{
		memcpy(pinputs, inputs, sizeof(pinputs));
		
		// prevent jumping/shooting when leaving a messagebox
		if (player->inputs_locked_lasttime)
		{
			for(i=0;i<INPUT_COUNT;i++)
				lastpinputs[i] |= pinputs[i];
		}
		
		// allow entering inventory
		if (justpushed(INVENTORYKEY))
		{
			if (!game.frozen && !player->dead && GetCurrentScript() == -1)
			{
				game.setmode(GM_INVENTORY);
			}
		}
		
		// Map System
		if (justpushed(MAPSYSTEMKEY))
		{
			if (!game.frozen && !player->dead && GetCurrentScript() == -1)
			{
				if (fade.getstate() == FS_NO_FADE && game.switchstage.mapno == -1)
				{
					game.setmode(GM_MAP_SYSTEM, game.mode);
				}
			}
		}
	}
}


// handles tile attributes of tiles player is touching
void PHandleAttributes(void)
{
static const Point pattrpoints[] = { {8, 8}, {8, 14} };
unsigned int attr;
int tile;

	// get attributes of tiles player it touching.
	// first, we'll check the top pattrpoint alone; this is the point at
	// which you go underwater, when that point is lower than the water level.
	// ** There is a spot in Labyrinth W just after the Shop where the positioning
	// of this point is a minor element in the gameplay, and so it must be set
	// correctly. If set too high you will not be underwater after climbing up the
	// small slope and you can just jump over the wall that you shouldn't be able to.
	attr = player->GetAttributes(&pattrpoints[0], 1, &tile);
	
	// water handler -- water uses only the top pattrpoint
	if (attr & TA_WATER)
	{
		// check if we just entered the water
		if (!(player->touchattr & TA_WATER))
		{
			// splash on entering water quick enough
			if ((player->yinertia > 0x200 && !player->blockd) || \
				(player->xinertia < -0x200 || player->xinertia > 0x200))
			{
				int x = player->CenterX();
				int y = player->CenterY();
				int splashtype = !(player->touchattr & TA_HURTS_PLAYER) ? \
									OBJ_WATER_DROPLET : OBJ_LAVA_DROPLET;
				
				for(int i=0;i<8;i++)
				{
					Object *o = CreateObject(x + (random(-8, 8) << CSF), y, splashtype);
					o->xinertia = random(-0x200, 0x200) + player->xinertia;
					o->yinertia = random(-0x200, 0x80) - (player->yinertia >> 1);
				}
				
				sound(SND_SPLASH);
			}
		}
		
		// setup physics constants for water
		player->walkspeed = 0x196;
		player->fallspeed = 0x2ff;
		
		player->fallaccel = 0x28;
		player->jumpfallaccel = 0x10;
		
		player->walkaccel = 0x2a;
		player->jumpwalkaccel = 0x10;
		
		player->decelspeed = 0x19;
		// was set at 0x280 but I believe that makes it impossible to clear one of the long
		// spike jumps in River
		player->jumpvelocity = 0x2c0;
		
		// decrement air left
		if (player->equipmask & EQUIP_AIRTANK)
		{
			player->airleft = 1000;
			player->airshowtimer = 0;
		}
		else
		{
			player->airshowtimer = 60;
			if (!player->drowned)
			{
				if (!player->inputs_locked) player->airleft--;
				
				if (player->airleft <= 0 && !game.debug.god)
				{	// player drowned
					// if flag 4000 is set, then we do not drown, but are in the Almond
					// level after Core battle, and should instead execute script 1100.
					if (game.flags[4000])
					{	// "your senses dim and the world grows dark"
						StartScript(1100);
					}
					else
					{	// nope sorry buddy, no such luck this time
						Object *o = CreateObject(player->x, player->y, OBJ_NULL);
						o->sprite = SPR_PDROWNED;
						o->dir = player->dir;
						
						killplayer(SCRIPT_DROWNED);
					}
					
					player->drowned = 1;
				}
			}
		}
	}
	else
	{
		// setup normal physics constants
		player->walkspeed = 0x32c;////0x030e;
		player->fallspeed = 0x5ff;
		
		player->fallaccel = 0x50;
		player->jumpfallaccel = 0x20;
		
		player->walkaccel = 0x55;
		player->jumpwalkaccel = 0x20;
		
		player->decelspeed = 0x33;
		player->jumpvelocity = 0x500;
		
		// reset air supply
		player->airleft = 1000;
		if (player->airshowtimer) player->airshowtimer--;
	}
	
	// add in the bottom pattrpoint, but don't let it set the "water" bit.
	// only the top pattrpoint can set "water".
	attr |= (player->GetAttributes(&pattrpoints[1], 1, &tile) & ~TA_WATER);
	
	if (attr & TA_HURTS_PLAYER)
		hurtplayer(10);
	
	// water current/wind:
	// for water currents--get the sum total of several points on the player to see
	// all the directions he's getting blown around at (support multiple directions)
	DoWaterCurrents();
	player->touchattr = attr;
}

// handes player being blown around by water currents
void DoWaterCurrents(void)
{
static Point currentpoints[] = { {7, 8},
								 {1, 2}, {1, 8}, {1, 14},
								 {7, 2}, {7, 14},
								 {15,2}, {15, 8}, {15, 14} };
int i;
static const int current_dir[] = { LEFTMASK, UPMASK, RIGHTMASK, DOWNMASK };
uint8_t currentmask;
int tile;

	// check each point in currentpoints[] for a water current, and if found,
	// add it to the list of directions we're being blown
	currentmask = 0;
	for(i=0;i<9;i++)
	{
		//DebugCrosshair(player->x+(currentpoints[i].x<<CSF),player->y+(currentpoints[i].y<<CSF), 255,0,0);
		
		if (player->GetAttributes(&currentpoints[i], 1, &tile) & TA_CURRENT)
		{
			currentmask |= current_dir[tilecode[tile] & 3];
		}
		
		// if the center point (the first one) has no current, then don't
		// bother checking the rest. as during 90% of the game you are NOT underwater.
		if (!currentmask) return;
	}
	
	// these constants are very critical for Waterway to work properly.
	// please be careful with them.
	if (currentmask & LEFTMASK)  player->xinertia -= 0x88;
	if (currentmask & RIGHTMASK) player->xinertia += 0x88;
	if (currentmask & UPMASK)    player->yinertia -= 0x80;
	if (currentmask & DOWNMASK)  player->yinertia += 0x50;
}


void PDoWalking(void)
{
int walk_accel;
int limit;
	
	walk_accel = (player->blockd) ? player->walkaccel : player->jumpwalkaccel;
	
	// walking/moving
	if (pinputs[LEFTKEY] || pinputs[RIGHTKEY])
	{
		// we check both without an else so that both keys down=turn right & walk in place
		if (pinputs[LEFTKEY])
		{
			player->walking = true;
			player->dir = LEFT;
			
			if (player->xinertia > -player->walkspeed)
			{
				player->xinertia -= walk_accel;
				
				if (player->xinertia < -player->walkspeed)
					player->xinertia = -player->walkspeed;
			}
		}
		
		if (pinputs[RIGHTKEY])
		{
			player->walking = true;
			player->dir = RIGHT;
			
			if (player->xinertia < player->walkspeed)
			{
				player->xinertia += walk_accel;
				
				if (player->xinertia > player->walkspeed)
					player->xinertia = player->walkspeed;
			}
		}
		
		if (player->walking && !player->lastwalking)
			player->walkanimframe = 1;
	}
	else
	{
		player->walking = false;
		player->walkanimframe = 0;
		player->walkanimtimer = 0;
		// tap sound when stopped walking
		if (player->lastwalking && player->blockd)
			sound(SND_PLAYER_WALK);
	}
	
	// deceleration
	if (player->blockd && player->yinertia >= 0)
	{	// deceleration on ground...
		// always move towards zero at decelspeed
		if (player->xinertia > 0)
		{
			if (player->blockr && !pinputs[RIGHTKEY])
			{
				player->xinertia = 0;
			}
			else if (player->xinertia > player->decelspeed)
			{
				player->xinertia -= player->decelspeed;
			}
			else
			{
				player->xinertia = 0;
			}
		}
		else if (player->xinertia < 0)
		{
			if (player->blockl && !pinputs[LEFTKEY])
			{
				player->xinertia = 0;
			}
			else if (player->xinertia < -player->decelspeed)
			{
				player->xinertia += player->decelspeed;
			}
			else
			{
				player->xinertia = 0;
			}
		}
	}
	else		// deceleration in air...
	{
		// implements 2 things
		//	1) if player partially hits a brick while in air, his inertia is lesser after he passes it
		//	2) but, if he's trying to turn around, let him! don't "stick" him to it just because
		//		of a high inertia when he hit it
		if (player->blockr)
		{
			limit = (player->dir == RIGHT) ? 0x180 : 0;
			if (player->xinertia > limit) player->xinertia = limit;
		}
		
		if (player->blockl)
		{
			limit = (player->dir == LEFT) ? -0x180 : 0;
			if (player->xinertia < limit) player->xinertia = limit;
		}
	}
}

void PDoFalling(void)
{
	if (player->disabled)
		return;
	
	if (player->booststate)
		return;
	
	if (game.curmap == STAGE_KINGS_TABLE && \
		fade.getstate() == FS_FADING)
		return;
	
	// needed to be able to see the falling blocks during
	// good-ending Helicopter cutscene (otherwise your
	// invisible character falls and the blocks spawn too low).
	if (player->hide)
	{
		player->xinertia = 0;
		player->yinertia = 0;
		return;
	}
	
	// use jump gravity as long as Jump Key is down and we're moving up,
	// regardless of whether a jump was ever actually initiated.
	// this is for the fans that blow up--you can push JUMP to climb higher.
	if (player->yinertia < 0 && pinputs[JUMPKEY])
	{	// use jump gravity
		if (player->yinertia < player->fallspeed)
		{
			player->yinertia += player->jumpfallaccel;
			if (player->yinertia > player->fallspeed) player->yinertia = player->fallspeed;
		}
	}
	else
	{	// use normal gravity
		if (player->yinertia < player->fallspeed)
		{
			player->yinertia += player->fallaccel;
			if (player->yinertia > player->fallspeed) player->yinertia = player->fallspeed;
		}
		
		// if we no longer qualify for jump gravity then the jump is over
		player->jumping = 0;
	}
}


void PDoJumping(void)
{
	// jumping
	if (pinputs[JUMPKEY] && !lastpinputs[JUMPKEY])
	{
		if (player->blockd)
		{
			if (!player->jumping)
			{
				player->jumping = true;
				player->yinertia -= player->jumpvelocity;
				sound(SND_PLAYER_JUMP);
			}
		}
		else if ((player->equipmask & (EQUIP_BOOSTER08 | EQUIP_BOOSTER20)))
		{
			PStartBooster();
		}
	}
}


void PDoLooking(void)
{
int lookscroll_want;
int i, key;

	// looking/aiming up and down
	player->look = lookscroll_want = 0;
	
	if (pinputs[DOWNKEY])
	{
		if (!player->blockd)
		{
			player->look = DOWN;
		}
		else if (!lastpinputs[DOWNKEY])
		{	// activating scripts/talking to NPC's
			
			if (!player->walking && !player->lookaway && \
				!pinputs[JUMPKEY] && !pinputs[FIREKEY])
			{
				if (!inputs[DEBUG_MOVE_KEY] || !settings->enable_debug_keys)
				{
					player->lookaway = true;
					player->xinertia = 0;
					PTryActivateScript();
				}
			}
		}
		
		// can still scroll screen down while standing, even though
		// it doesn't show any different frame.
		lookscroll_want = DOWN;
	}
	
	if (pinputs[UPKEY])
	{
		player->look = lookscroll_want = UP;
	}
	
	// when looking, pause a second to be sure they really want to do it
	// before triggering any real screen scrolling
	if (player->lookscroll != lookscroll_want)
	{
		if (player->lookscroll_timer >= 4 || !lookscroll_want)
		{
			player->lookscroll = lookscroll_want;
		}
		else
		{
			player->lookscroll_timer++;
		}
	}
	else
	{
		player->lookscroll_timer = 0;
	}
	
	// deactivation of lookaway
	if (player->lookaway)
	{
		// keys which deactivate lookaway when you are facing away from player
		static const char actionkeys[] = \
			{ LEFTKEY, RIGHTKEY, UPKEY, JUMPKEY, FIREKEY, -1 };
		
		// stop looking away if any keys are pushed
		for(i=0;;i++)
		{
			key = actionkeys[i];
			if (key == -1) break;
			
			if (pinputs[key])
			{
				player->lookaway = false;
				break;
			}
		}
		
		if (!player->blockd)
			player->lookaway = false;
	}
}

/*
void c------------------------------() {}
*/

// called when the player has just turned on the booster
void PStartBooster(void)
{
	if (player->boosterfuel <= 0)
		return;
	
	// booster 2.0 lets you pick a direction and tacks inertia
	// solid in that direction when first activated
	if ((player->equipmask & EQUIP_BOOSTER20))
	{
		// default boost direction if no key is pressed
		player->booststate = BOOST_UP;
		
		// in order of precedence
		if (inputs[LEFTKEY] || inputs[RIGHTKEY])
			player->booststate = BOOST_HOZ;
		
		if (inputs[DOWNKEY])
			player->booststate = BOOST_DOWN;
		
		if (inputs[UPKEY])
			player->booststate = BOOST_UP;
		
		// set initial inertia full on
		if (player->booststate == BOOST_UP || player->booststate == BOOST_DOWN)
			player->xinertia = 0;
		
		switch(player->booststate)
		{
			case BOOST_UP:
				player->yinertia = -0x5ff;
			break;
			
			case BOOST_DOWN:
				player->yinertia = 0x5ff;
			break;
			
			case BOOST_HOZ:
			{
				player->yinertia = 0;
				
				if (inputs[LEFTKEY])
					player->xinertia = -0x5ff;
				else
					player->xinertia = 0x5ff;
			}
			break;
		}
	}
	else
	{
		player->booststate = BOOST_08;
		
		// help it overcome gravity
		if (player->yinertia > 0x100)
			player->yinertia >>= 1;
	}
	
	PBoosterSmokePuff();
}

// called every tick to run the booster
void PDoBooster(void)
{
	/*static const char *statedesc[] = { "OFF", "UP", "DN", "HOZ", "0.8" };
	debug("fuel: %d", player->boosterfuel);
	debug("booststate: %s", statedesc[player->booststate]);
	debug("xinertia: %d", player->xinertia);
	debug("yinertia: %d", player->yinertia);*/
	
	if (!(player->equipmask & (EQUIP_BOOSTER08 | EQUIP_BOOSTER20)))
	{
		player->booststate = BOOST_OFF;
		return;
	}
	
	if (!pinputs[JUMPKEY])
	{
		player->booststate = BOOST_OFF;
		
		if (player->blockd)
			player->boosterfuel = BOOSTER_FUEL_QTY;
		
		return;
	}
	
	if (!player->booststate)
		return;
	
	// player seems to want it active...check the fuel
	if (player->boosterfuel <= 0)
	{
		player->booststate = BOOST_OFF;
		return;
	}
	else
	{
		player->boosterfuel--;
	}
	
	// ok so then, booster is active right now
	bool sputtering = false;
	
	switch(player->booststate)
	{
		case BOOST_HOZ:
		{
			if ((player->dir == LEFT && player->blockl) || \
				(player->dir == RIGHT && player->blockr))
			{
				player->yinertia = -0x100;
			}
			
			// this probably isn't the right way to do this, but this
			// bit makes the hurt-hop work if you get hit during a sideways boost
			//if (player->hitwhileboosting)
			//	player->yinertia = -0x400;
			
			if (player->dir == LEFT)  player->xinertia -= 0x20;
			if (player->dir == RIGHT) player->xinertia += 0x20;
		}
		break;
		
		case BOOST_UP:
		{
			player->yinertia -= 0x20;
		}
		break;
		
		case BOOST_DOWN:
		{
			player->yinertia += 0x20;
		}
		break;
		
		case BOOST_08:
		{
			// top speed and sputtering
			if (player->yinertia < -0x400)
			{
				player->yinertia += 0x20;
				sputtering = true;	// no sound/smoke this frame
			}
			else
			{
				player->yinertia -= 0x20;
			}
		}
		break;
	}
	
	// don't land if we booster through a one-tile high corridor,
	// but do land if we're, well, landing on something (yinertia not negative).
	// must be done after booster inertia applied to work properly.
	// for 1) there's a place in the village next to Mahin that is good for testing this,
	// for 2) the gaps in outer wall by the little house.
	if (player->blockd)
	{
		if (player->yinertia < 0)
			player->blockd = false;
		else
		{
			player->booststate = BOOST_OFF;
			return;
		}
	}
	
	// smoke and sound effects
	if ((player->boosterfuel % 3) == 1 && !sputtering)
	{
		PBoosterSmokePuff();
	}
}

// called every tick just after PDoBooster returns.
// tones down player's inertia a bit once the Booster 2.0 stops firing
void PDoBoosterEnd()
{
	// put here to be sure it catches all the different ways the Booster can get turned off
	//if (!player->booststate)
		//player->hitwhileboosting = false;
	
	if (player->booststate != player->lastbooststate)
	{
		if (player->booststate == BOOST_OFF && (player->equipmask & EQUIP_BOOSTER20))
		{
			switch(player->lastbooststate)
			{
				case BOOST_HOZ:
					player->xinertia >>= 1;
				break;
				
				case BOOST_UP:
					player->yinertia >>= 1;
				break;
			}
		}
	}
	
	// in the original touching a slope while boosting horizontally
	// disables the booster. In that case, we don't want to half the xinertia,
	// which is why it's here.
	//if (player->booststate == BOOST_HOZ && CheckStandOnSlope(player))
		//player->booststate = BOOST_OFF;
	
	player->lastbooststate = player->booststate;
}

// spawn a Booster smoke puff
void PBoosterSmokePuff()
{
	// these are the directions the SMOKE is traveling, not the player
	//                                 RT   LT    UP    DN
	static const int smoke_xoffs[] = { 10,   4,   7,    7  };
	static const int smoke_yoffs[] = { 10,  10,   0,   14  };
	int smokedir;
	
	switch(player->booststate)
	{
		case BOOST_HOZ: smokedir = (player->dir ^ 1); break;
		case BOOST_UP:	smokedir = DOWN; break;
		case BOOST_DOWN:smokedir = UP; break;
		case BOOST_08:	smokedir = DOWN; break;
		default:		return;
	}
	
	int x = player->x + (smoke_xoffs[smokedir] << CSF);
	int y = player->y + (smoke_yoffs[smokedir] << CSF);
	
	Caret *smoke = effect(x, y, EFFECT_SMOKETRAIL_SLOW);
	smoke->MoveAtDir(smokedir, 0x200);
	
	sound(SND_BOOSTER);
}

/*
void c------------------------------() {}
*/

// handle some special characteristics of solid-brick objects,
// such as bouncy and damage. Unlike with FLAG_SOLID_MUSHY; the
// block/l/r/u/d flags for these objects have already been set in
// UpdateBlockStates, so we don't have to worry about those.
void PHandleSolidBrickObjects(void)
{
int i;
SIFSprite *sprite = player->Sprite();
Object *o;

	// calculate total inertia of player--this is needed so that
	// the forcefields in the Monster X arena will damage you if
	// the treads carry you into them.
	int p_xinertia = player->xinertia;
	int p_yinertia = player->yinertia;
	if (player->riding)
	{
		p_xinertia += player->riding->xinertia;
		p_yinertia += player->riding->yinertia;
	}
	
	for(i=0;i<nOnscreenObjects;i++)
	{
		o = onscreen_objects[i];
		if (!(o->flags & FLAG_SOLID_BRICK)) continue;
		
		// left, right, and up contact damage
		if (o->damage > 0)
		{
			if (player->blockl && player->CheckSolidIntersect(o, &sprite->block_l))
			{
				if (p_xinertia < 0 || o->xinertia > 0)
					o->DealContactDamage();
			}
			
			if (player->blockr && player->CheckSolidIntersect(o, &sprite->block_r))
			{
				if (p_xinertia > 0 || o->xinertia < 0)
					o->DealContactDamage();
			}
			
			if (player->blocku && player->CheckSolidIntersect(o, &sprite->block_u))
			{
				if (p_yinertia < 0 || o->yinertia > 0)
					o->DealContactDamage();
			}
		}
		
		// stuff for when you are standing on it
		if (player->blockd && player->CheckSolidIntersect(o, &sprite->block_d))
		{
			if (o->damage && (player->yinertia >= 0 || o->yinertia < 0))
				o->DealContactDamage();
			
			// don't do weird glitchy shit if we jump while being carried upward
			// by an object moving faster than us. handles if you jump while flying
			// momorin's rocket.
			if (player->yinertia < 0 && o->yinertia < player->yinertia)
				player->yinertia = 0;
			
			// handle FLAG_BOUNCY--used eg by treads on Monster X when tipped up
			if (o->flags & FLAG_BOUNCY)
			{
				if (player->yinertia > (o->yinertia - 0x200))
					player->yinertia = (o->yinertia - 0x200);
			}
			else if (o->yinertia <= player->yinertia)
			{
				// snap his Y right on top if it
				player->y = o->SolidTop() - (sprites[player->sprite].block_d[0].y << CSF);
			}
		}
	}
}


void PHandleSolidMushyObjects(void)
{
	for(int i=0;i<nOnscreenObjects;i++)
	{
		Object *o = onscreen_objects[i];
		
		if (o->flags & FLAG_SOLID_MUSHY)
			PRunSolidMushy(o);
	}
}

// handle "solid mushy" objects, such as bugs. These objects are solid but not 100% super
// solid like a brick. Their solidity is more of an "it repels the player" kind of way.
// NOTE: This is also responsible for the horizontal motion you see when hit by many kinds
// of enemies. The hurtplayer damage routine makes you hop vertically, but it is this that
// throws you away horizontally.
void PRunSolidMushy(Object *o)
{
	// cache these, so we're not calling the same functions over and over again
	const int p_left = player->SolidLeft();
	const int p_right = player->SolidRight();
	const int p_top = player->SolidTop();
	const int p_bottom = player->SolidBottom();
	
	const int o_left = o->SolidLeft();
	const int o_right = o->SolidRight();
	const int o_top = o->SolidTop();
	const int o_bottom = o->SolidBottom();
	
	static const int MUSHY_MARGIN = (3<<CSF);
	static const int STAND_MARGIN = (1<<CSF);
	static const int REPEL_FORCE = 0x200;
	
	// hitting sides of object
	if ((p_top < (o_bottom - MUSHY_MARGIN)) && (p_bottom > (o_top + MUSHY_MARGIN)))
	{
		// left side
		if ((p_right > o_left) && (p_right < o->CenterX()))
		{
			if (player->xinertia > -REPEL_FORCE)
				player->xinertia -= REPEL_FORCE;
		}
		
		// right side
		if ((p_left < o_right) && (p_left > o->CenterX()))
		{
			if (player->xinertia < REPEL_FORCE)
				player->xinertia += REPEL_FORCE;
		}
	}
	
	// bonking head on object or standing on object
	
	// to tell if we are within horizontal bounds to be standing on the object,
	// we will check if we have NOT FALLEN OFF the object.
	if (p_left > (o_right - STAND_MARGIN) || p_right < (o_left + STAND_MARGIN))
	{ }
	else
	{
		// standing on object
		if (p_bottom >= o_top && p_bottom <= o->CenterY())
		{
			if (o->flags & FLAG_BOUNCY)
			{
				if (player->yinertia > (o->yinertia - 0x200))
					player->yinertia = (o->yinertia - 0x200);
			}
			else
			{
				// force to top of sprite if we're REALLY far into it
				int em_fline = o->SolidTop() + (3 << CSF);
				if (player->SolidBottom() > em_fline)
				{
					int over_amt = (em_fline - player->SolidBottom());
					int dec_amt = (3 << CSF);
					
					if (over_amt < dec_amt) dec_amt = over_amt;
					if (dec_amt < (1<<CSF)) dec_amt = (1<<CSF);
					
					player->apply_yinertia(-dec_amt);
				}
				
				player->blockd = true;
				player->riding = o;
			}
		}
		else if (p_top < o_bottom && p_top > o->CenterY())
		{
			// hit bottom of object with head
			if (player->yinertia < 0)
				player->yinertia = 0;
		}
	}
}

/*
void c------------------------------() {}
*/

// does "damage" points of damage to the player
// if even_if_controls_locked is true the damage is
// dealt even if the player's input is locked.
void hurtplayer(int damage)
{
	if (damage == 0) return;
	if (!player || !player->hp) return;
	if (settings->enable_debug_keys && (game.debug.god || inputs[DEBUG_MOVE_KEY])) return;
	
	if (player->hurt_time)
		return;
	
	if (player->hide)
		return;
	
	player->hp -= damage;
	player->DamageText->AddQty(damage);
	
	player->lookaway = 0;
	player->hurt_time = 128;
	
	if (player->equipmask & EQUIP_WHIMSTAR)
		remove_whimstar(&player->whimstar);
	
	//if (player->booststate)
		//player->hitwhileboosting = true;
	
	if (player->hp <= 0)
	{
		sound(SND_PLAYER_DIE);
		SmokeClouds(player, 64, 16, 16);
		
		killplayer(SCRIPT_DIED);
	}
	else
	{
		sound(SND_PLAYER_HURT);
		
		// hop
		if (player->movementmode != MOVEMODE_ZEROG)
			player->yinertia = -0x400;
	}
	
	// decrement weapon XP.
	if (player->equipmask & EQUIP_ARMS_BARRIER)
		SubXP(damage);
	else
		SubXP(damage * 2);
}

// set the player state to "dead" and execute script "script"
void killplayer(int script)
{
/*	Replay::end_record();
	Replay::end_playback();*/
	
	player->hp = 0;
	player->dead = true;
	player->hide = true;
	player->xinertia = 0;
	player->yinertia = 0;
	player->riding = NULL;			// why exactly did I say this? i dunno, but not touching for safety
	StopLoopSounds();				// important for Almond
	StartScript(script);
}

/*
void c------------------------------() {}
*/

// this is basically a replacement for most of the player code,
// used when the player is in <UNI0001 (the Ironhead battle).
void PHandleZeroG(void)
{
	if (!player->inputs_locked)
	{
		if (inputs[LEFTKEY] || inputs[RIGHTKEY])
		{
			if (inputs[LEFTKEY])  player->xinertia -= 0x100;
			if (inputs[RIGHTKEY]) player->xinertia += 0x100;
		}
		else
		{	// decel
			if (player->xinertia < 0x80 || player->xinertia > -0x80)
			{
				player->xinertia = 0;
			}
			else
			{
				player->xinertia += (player->xinertia > 0) ? -0x80 : 0x80;
			}
		}
		
		if (inputs[UPKEY] || inputs[DOWNKEY])
		{
			if (inputs[UPKEY]) player->yinertia -= 0x100;
			if (inputs[DOWNKEY]) player->yinertia += 0x100;
		}
		else
		{	// decel
			if (player->yinertia < 0x80 || player->yinertia > -0x80)
			{
				player->yinertia = 0;
			}
			else
			{
				player->xinertia += (player->xinertia > 0) ? -0x80 : 0x80;
			}
		}
	}
	else
	{	// decel for when inputs locked after victory
		if (player->xinertia < 0x80 && player->xinertia > -0x40)
		{
			player->xinertia = 0;
		}
		else
		{
			player->xinertia += (player->xinertia > 0) ? -0x80 : 0x80;
		}
		
		if (player->yinertia < 0x80 && player->yinertia > -0x40)
		{
			player->yinertia = 0;
		}
		else
		{
			player->yinertia += (player->yinertia > 0) ? -0x80 : 0x80;
		}
	}
	
	if (player->xinertia > 0x400)  player->xinertia = 0x400;
	if (player->xinertia < -0x400) player->xinertia = -0x400;
	if (player->yinertia > 0x400)  player->yinertia = 0x400;
	if (player->yinertia < -0x400) player->yinertia = -0x400;
	
	player->frame = (player->yinertia > 0) ? 1 : 2;
}

/*
void c------------------------------() {}
*/

void PInitRepel(void)
{
const int s = SPR_MYCHAR;
int i;

	player->nrepel_l = sprites[s].block_l.count;
	player->nrepel_r = sprites[s].block_r.count;
	player->nrepel_d = sprites[s].block_d.count;
	player->nrepel_u = sprites[s].block_u.count;
	
	for(i=0;i<player->nrepel_l;i++)
	{
		player->repel_l[i].x = sprites[s].block_l[i].x + 1;
		player->repel_l[i].y = sprites[s].block_l[i].y;
	}
	
	for(i=0;i<player->nrepel_r;i++)
	{
		player->repel_r[i].x = sprites[s].block_r[i].x - 1;
		player->repel_r[i].y = sprites[s].block_r[i].y;
	}
	
	for(i=0;i<player->nrepel_d;i++)
	{
		player->repel_d[i].x = sprites[s].block_d[i].x;
		player->repel_d[i].y = sprites[s].block_d[i].y - 1;
	}
	
	for(i=0;i<player->nrepel_u;i++)
	{
		player->repel_u[i].x = sprites[s].block_u[i].x;
		player->repel_u[i].y = sprites[s].block_u[i].y + 1;
	}
}

// the player's block points are assymetrical--block u/d are closer together than block l/r.
// So it's quite possible to get e.g. your blockl points embedded in a wall by
// falling off the top of it. This function implements a SMB1-style "repel" that
// allows this to happen but then pushes the player out of the block over the next
// few frames.
void PDoRepel(void)
{
	// since this function is called from the aftermove, regular player->blockl etc
	// won't be updated until the following frame, so we always check the attributes
	// directly here.
	static const int REPEL_SPEED =	(1<<CSF);
	
	if (settings->enable_debug_keys && inputs[DEBUG_MOVE_KEY])
		return;
	
	// pushes player out of walls if he become embedded in them, ala Super Mario 1.
	// this can happen for example because his R,L block points are further out than
	// his D block points so it's possible to fall really close to a block and
	// embed the R or L points further into the block than they should be
	if (player->CheckAttribute(player->repel_r, player->nrepel_r, TA_SOLID_PLAYER))
	{
		if (!player->CheckAttribute(&sprites[player->sprite].block_l, TA_SOLID_PLAYER))
		{
			player->x -= REPEL_SPEED;
			//debug("REPEL [to left]");
		}
	}
	
	if (player->CheckAttribute(player->repel_l, player->nrepel_l, TA_SOLID_PLAYER))
	{
		if (!player->CheckAttribute(&sprites[player->sprite].block_r, TA_SOLID_PLAYER))
		{
			player->x += REPEL_SPEED;
			//debug("REPEL [to right]");
		}
	}
	
	// vertical repel doesn't happen normally, but if we get embedded in a
	// block somehow, it can happen.
	/*
	// do repel down
	if (player->CheckAttribute(player->repel_u, player->nrepel_u, TA_SOLID_PLAYER))
	{
		if (!player->CheckAttribute(&sprites[player->sprite].block_d, TA_SOLID_PLAYER))
		{
			player->y += REPEL_SPEED;
			//debug("REPEL [down]");
		}
	}
	
	// do repel up
	if (player->CheckAttribute(player->repel_d, player->nrepel_d, TA_SOLID_PLAYER))
	{
		if (!player->CheckAttribute(&sprites[player->sprite].block_u, TA_SOLID_PLAYER))
		{
			player->y -= REPEL_SPEED;
			//debug("REPEL [up]");
		}
	}
	*/
}

/*
void c------------------------------() {}
*/

// called when you press down.
// Tries to find an SCRIPTONACTIVATE object you are standing near and activate it.
// if it can't find anything to activate, spawns the "question mark" effect.
void PTryActivateScript()
{
	if (RunScriptAtX(player->CenterX()))
		return;
	
	if (player->dir == RIGHT)
	{
		if (RunScriptAtX(player->Right()) || RunScriptAtX(player->Left()))
			return;
	}
	else
	{
		if (RunScriptAtX(player->Left()) || RunScriptAtX(player->Right()))
			return;
	}
	
	// e.g. Plantation Rocket
	if (player->riding && (player->riding->flags & FLAG_SCRIPTONACTIVATE))
	{
		StartScript(player->riding->id2);
		return;
	}
	
	effect(player->CenterX(), player->CenterY(), EFFECT_QMARK);
}

static bool RunScriptAtX(int x)
{
	if (RunScriptAtLocation(x, player->y + (8 << CSF)) || \
		RunScriptAtLocation(x, player->y + (14 << CSF)) || \
		RunScriptAtLocation(x, player->y + (2 << CSF)))
	{
		return true;
	}
	
	return false;
}

static bool RunScriptAtLocation(int x, int y)
{
	// top-to-bottom scan
	for(int i=nOnscreenObjects-1; i>=0; i--)
	{
		Object *o = onscreen_objects[i];
		
		if (o->flags & FLAG_SCRIPTONACTIVATE)
		{
			if (x >= o->Left() && x <= o->Right() && \
				y >= o->Top() && y <= o->Bottom())
			{
				StartScript(o->id2);
				return true;
			}
		}
	}
	
	return false;
}

/*
void c------------------------------() {}
*/

// does the invincibility flash when the player has recently been hurt
void PDoHurtFlash(void)
{
	// note that hurt_flash_state is NOT cleared when timer reaches 0,
	// but this is ok because the number of blinks are and always should be even.
	// (if not it wouldn't look right when he unhurts).
	if (player->hurt_time)
	{
		player->hurt_time--;
		player->hurt_flash_state = (player->hurt_time & 2);
	}
}

// decides which player frame to show
void PSelectFrame(void)
{
	if (player->lookaway)
	{	// looking away
		player->frame = 11;
	}
	else if (!player->blockd || player->yinertia < 0)
	{	// jumping/falling
		player->frame = (player->yinertia > 0) ? 1 : 2;
	}
	else if (player->walking)
	{	// do walk animation
		static const uint8_t pwalkanimframes[] = { 0, 1, 0, 2 };
		
		if (++player->walkanimtimer >= 5)
		{
			player->walkanimtimer = 0;
			if (++player->walkanimframe >= 4) player->walkanimframe = 0;
			if (pwalkanimframes[player->walkanimframe]==0) sound(SND_PLAYER_WALK);
		}
		
		player->frame = pwalkanimframes[player->walkanimframe];
	}
	else
	{	// standing
		player->frame = 0;
	}
	
	// switch frames to "up" or "down" versions if we're looking
	if (player->look)
	{
		if (player->look == UP)
		{
			if (!player->blockd || player->yinertia < 0)
				player->frame = 4;
			else
				player->frame += 3;
		}
		else
		{
			player->frame += 6;
		}
	}
	
	// mimiga mask support-- it would be better to make equipmask private,
	// and funnel all player->equipmask changes through a setter function,
	// then I'd feel safe doing this only when equipped items are changed.
	PSelectSprite();
}

// mimiga mask support
void PSelectSprite(void)
{
	player->sprite = (player->equipmask & EQUIP_MIMIGA_MASK) ? \
					SPR_MYCHAR_MIMIGA : SPR_MYCHAR;
}

/*
void c------------------------------() {}
*/


// returns the sprite and frame # to be used for drawing the given weapon
void GetSpriteForGun(int wpn, int look, int *spr, int *frame)
{
int s;
	
	switch(wpn)
	{
		case WPN_SUPER_MISSILE: s = SPR_SUPER_MLAUNCHER; break;
		case WPN_NEMESIS: s = SPR_NEMESIS; break;
		case WPN_BUBBLER: s = SPR_BUBBLER; break;
		case WPN_SPUR: s = SPR_SPUR; break;
		
		default:
			s = SPR_WEAPONS_START + (wpn * 2);
		break;
	}
	
	if (look)
	{
		s++;
		*frame = (look == DOWN);
	}
	else
	{
		*frame = 0;
	}
	
	*spr = s;
}


// returns the point that a player's shot should be centered on when firing
void GetPlayerShootPoint(int *x_out, int *y_out)
{
int spr, frame;
int x, y;

	GetSpriteForGun(player->curWeapon, player->look, &spr, &frame);
	
	// we have to figure out where the gun is being carried, then figure out where the
	// gun's sprite is drawn relative to that, then finally we can offset in the
	// shoot point of the gun's sprite.
	x = player->x + (sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.x << CSF);
	x -= sprites[spr].frame[frame].dir[player->dir].drawpoint.x << CSF;
	x += sprites[spr].frame[frame].dir[player->dir].actionpoint.x << CSF;
	
	y = player->y + (sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.y << CSF);
	y -= sprites[spr].frame[frame].dir[player->dir].drawpoint.y << CSF;
	y += sprites[spr].frame[frame].dir[player->dir].actionpoint.y << CSF;
	
	*x_out = x;
	*y_out = y;
}

// draws the player
void DrawPlayer(void)
{
int scr_x, scr_y;

	if (player->hide || player->disabled)
		return;
	
	// keep his floattext position linked--do NOT update this if he is hidden
	// so that floattext doesn't follow him after he dies.
	player->DamageText->UpdatePos(player);
	player->XPText->UpdatePos(player);
	
	// get screen position to draw him at
	scr_x = (player->x >> CSF) - (map.displayed_xscroll >> CSF);
	scr_y = (player->y >> CSF) - (map.displayed_yscroll >> CSF);
	
	// draw his gun
	if (player->curWeapon != WPN_NONE && player->curWeapon != WPN_BLADE)
	{
		int spr, frame;
		GetSpriteForGun(player->curWeapon, player->look, &spr, &frame);
		
		// draw the gun at the player's Action Point. Since guns have their Draw Point set
		// to point at their handle, this places the handle in the player's hand.
		draw_sprite_at_dp(scr_x + sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.x, \
						  scr_y + sprites[player->sprite].frame[player->frame].dir[player->dir].actionpoint.y, \
						  spr, frame, player->dir);
	}
	
	// draw the player sprite
	if (!player->hurt_flash_state)
	{
		draw_sprite(scr_x, scr_y, player->sprite, player->frame, player->dir);
		
		// draw the air bubble shield if we have it on
		if (((player->touchattr & TA_WATER) && (player->equipmask & EQUIP_AIRTANK)) || \
			player->movementmode == MOVEMODE_ZEROG)
		{
			draw_sprite_at_dp(scr_x, scr_y, SPR_WATER_SHIELD, \
							  player->water_shield_frame, player->dir);
			
			if (++player->water_shield_timer > 1)
			{
				player->water_shield_frame ^= 1;
				player->water_shield_timer = 0;
			}
		}
	}
	
	if (player->equipmask & EQUIP_WHIMSTAR)
		draw_whimstars(&player->whimstar);
}


