/*
	Beats of Rage
	Side-scrolling beat-'em-up
*/


#include <malloc.h>
#include <stdarg.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <direct.h>

#include "gamelib3\types.h"
#include "gamelib3\video.h"
#include "gamelib3\vga.h"
#include "gamelib3\screen.h"
#include "gamelib3\loadimg.h"
#include "gamelib3\bitmap.h"
#include "gamelib3\sprite.h"
#include "gamelib3\spriteq.h"
#include "gamelib3\font.h"
#include "gamelib3\timer.h"
#include "gamelib3\savepcx.h"
#include "gamelib3\rand32.h"
#include "gamelib3\soundmix.h"
#include "gamelib3\sblaster.h"
#include "gamelib3\keyboard.h"
#include "gamelib3\joy.h"
#include "gamelib3\control.h"
#include "gamelib3\draw.h"
#include "gamelib3\packfile.h"
#include "gamelib3\palette.h"
#include "gamelib3\anigif.h"
#include "gamelib3\texture.h"




#pragma pack (4)

#define		VERSION			0x00020043
#define		COMPATIBLEVERSION	0x00020033


// #define		GAME_SPEED		200
#define		THINK_SPEED		2
#define		COUNTER_SPEED		(GAME_SPEED*2)


#define		MAX_SPRITES		4000
#define		MAX_ANIMS		1000
#define		MAX_MODELS		200
#define		MAX_ENTS		350
#define		MAX_PANELS		52
#define		ANI_MAX_FRAMES		64
#define		MAX_WEAPONS		5
#define		MAX_COLOUR_MAPS		14
#define		MAX_NAME_LEN		40
#define		LEVEL_MAX_SPAWNS	600
#define		LEVEL_MAX_PANELS	2000
#define		LEVEL_MAX_HOLES		40
#define		LEVEL_MAX_WALLS		40
#define		MAX_LEVELS		100
#define		MAX_DIFFICULTIES	10
#define		MAX_SOUND_CACHE		128
#define		MAX_SPECIALS	8	// Added for customizable freespecials


#define		FLAG_ESC		0x00000001
#define		FLAG_START		0x00000002
#define		FLAG_MOVELEFT		0x00000004
#define		FLAG_MOVERIGHT		0x00000008
#define		FLAG_MOVEUP		0x00000010
#define		FLAG_MOVEDOWN		0x00000020
#define		FLAG_ATTACK		0x00000040
#define		FLAG_JUMP		0x00000080
#define		FLAG_SPECIAL		0x00000100
#define		FLAG_SCREENSHOT		0x00000200
#define		FLAG_ANYBUTTON		(FLAG_START|FLAG_SPECIAL|FLAG_ATTACK|FLAG_JUMP)
#define		FLAG_FORWARD	0	// Flags to be able to distinguish forward/backward movement
#define		FLAG_BACKWARD	1	// Flags to be able to distinguish forward/backward movement


#define		SDID_MOVEUP		0
#define		SDID_MOVEDOWN		1
#define		SDID_MOVELEFT		2
#define		SDID_MOVERIGHT		3
#define		SDID_SPECIAL		4
#define		SDID_ATTACK		5
#define		SDID_JUMP		6
#define		SDID_START		7
#define		SDID_SCREENSHOT		8


#define		TYPE_NONE		0
#define		TYPE_PLAYER		1
#define		TYPE_ENEMY		2
#define		TYPE_ITEM		4
#define		TYPE_OBSTACLE		8
#define		TYPE_STEAMER		16
#define		TYPE_SHOT		32	// 7-1-2005 type to use for player projectiles
#define		TYPE_TRAP		64	// 7-1-2005 lets face it enemies are going to just let you storm in without setting a trap or two!
#define		TYPE_TEXTBOX	128	// New textbox type for displaying messages
#define		TYPE_ENDLEVEL	256	// New endlevel type that ends the level when touched


#define		SUBTYPE_NONE		0
#define		SUBTYPE_BIKER		1
#define		SUBTYPE_NOTGRAB		2	//7-1-2005 new subtype for those ungrabbable enemies
#define		SUBTYPE_ARROW		3	//7-1-2005  subtype for an "enemy" that flies across the screen and dies
#define		SUBTYPE_TOUCH		4	// ltb 1-18-05  new Item subtype for a more platformer feel.
#define		SUBTYPE_EWEAPON		5	// Added to distinguish regular enemies from projectiles
#define		SUBTYPE_WEAPON		6
#define		SUBTYPE_NOSKIP		7	// Text type that can't be skipped
#define		SUBTYPE_FLYDIE		8	// Now obstacles can be hit and fly like on Simpsons/TMNT
#define		SUBTYPE_BOTH		9	// Used with TYPE_ENDLEVEL to force both players to reach the point before ending level


// Note: the minimum Z coordinate of the player is important
// for several other drawing operations.
// movement restirctions are here!
int PLAYER_MIN_Z = 160;	// 2-10-05  adjustable walking area
int PLAYER_MAX_Z = 232;	// 2-10-05  adjustable walking area
int BGHEIGHT = 160;	// 2-10-05  adjustable BGHeight
//

#define		FRONTPANEL_Z	(PLAYER_MAX_Z+50)
#define		HOLE_Z			(PLAYER_MIN_Z-46)
#define		NEONPANEL_Z		(PLAYER_MIN_Z-47)
#define		SHADOW_Z		(PLAYER_MIN_Z-48)
#define		SCREENPANEL_Z	(PLAYER_MIN_Z-49)
#define		PANEL_Z			(PLAYER_MIN_Z-50)
#define		PIT_DEPTH		-250
#define		P2_STATS_DIST		180


// Distance to make contact
#define		CONTACT_DIST_H		30
#define		CONTACT_DIST_V		12

// Grabbing ents will be placed this far apart.
#define		GRAB_DIST		36
// #define		THROW_DAMAGE		21

#define		GRAB_STALL		(GAME_SPEED * 8 / 10)


#define		ATK_NORMAL		0
#define		ATK_BLAST		1
#define		ATK_BURN		2
#define		ATK_FREEZE		3
#define		ATK_SHOCK		4
#define		ATK_STEAL		5	// Steal opponents life

#define		SCROLL_RIGHT	0
#define		SCROLL_DOWN		1
#define		SCROLL_LEFT		2
#define		SCROLL_UP		3
#define		SCROLL_BOTH		4




#define		ANI_IDLE		0
#define		ANI_WALK		1
#define		ANI_JUMP		2
#define		ANI_LAND		3
#define		ANI_PAIN		4
#define		ANI_FALL		5
#define		ANI_RISE		6
#define		ANI_ATTACK1		7
#define		ANI_ATTACK2		8
#define		ANI_ATTACK3		9
#define		ANI_UPPER		10
#define		ANI_BLOCK		11	// New block animation
#define		ANI_JUMPATTACK		12
#define		ANI_JUMPATTACK2		13
#define		ANI_GET			14
#define		ANI_GRAB		15
#define		ANI_GRABATTACK		16
#define		ANI_GRABATTACK2		17
#define		ANI_THROW		18
#define		ANI_SPECIAL		19
#define		ANI_FREESPECIAL		21
#define		ANI_SPAWN		22 	// 26-12-2004 new animation added here ani_spawn
#define		ANI_DIE			23	// 29-12-2004 new animation added here ani_die
#define		ANI_PICK		24	// 7-1-2005 used when players select their character at the select screen
#define		ANI_FREESPECIAL2	25
#define		ANI_JUMPATTACK3		26
#define		ANI_FREESPECIAL3	27
#define		ANI_UP				28	// Mar 2, 2005 - Animation for when going up
#define		ANI_DOWN			29	// Mar 2, 2005 - Animation for when going down
#define		ANI_SHOCK		30	// Animation played when knocked down by shock attack
#define		ANI_BURN		31	// Animation played when knocked down by burn attack
#define		ANI_SHOCKPAIN	32	// Animation played when not knocked down by shock attack
#define		ANI_BURNPAIN	33	// Animation played when not knocked down by shock attack
#define		ANI_GRABBED		34	// Animation played when grabbed
#define		ANI_SPECIAL2	35	// Animation played for when pressing forward special
#define		ANI_RUN			36	// Animation played when a player is running
#define		ANI_RUNATTACK	37	// Animation played when a player is running and presses attack
#define		ANI_RUNJUMPATTACK	38	// Animation played when a player is running and jumps and presses attack
#define		ANI_ATTACKUP	39	// u u animation
#define		ANI_ATTACKDOWN	40	// d d animation
#define		ANI_ATTACKFORWARD	41	// f f animation
#define		ANI_ATTACKBACKWARD	42	// Used for attacking backwards
#define		ANI_FREESPECIAL4	43	// More freespecials added
#define		ANI_FREESPECIAL5	44	// More freespecials added
#define		ANI_FREESPECIAL6	45	// More freespecials added
#define		ANI_FREESPECIAL7	46	// More freespecials added
#define		ANI_FREESPECIAL8	47	// More freespecials added
#define		ANI_RISEATTACK	48	// Attack used for enemies when players are crowding around after knocking them down
#define		ANI_DODGE		49	// Used for up up / down down SOR3 dodge moves for players
#define		ANI_ATTACKBOTH		50	// Used for when a player holds down attack and presses jump
#define		ANI_GRABFORWARD	51	//	New grab attack for when a player holds down forward/attack
#define		ANI_GRABFORWARD2	52	// New second grab attack for when a player holds down forward/attack
#define		ANI_JUMPFORWARD	53	// Attack when a player is moving and jumps
#define		ANI_GRABDOWN 54	// Attack when a player has grabbed an opponent and presses down/attack
#define		ANI_GRABDOWN2 55	// Attack when a player has grabbed an opponent and presses down/attack
#define		ANI_GRABUP 56	// Attack when a player has grabbed an opponent and presses up/attack
#define		ANI_GRABUP2 57	// Attack when a player has grabbed an opponent and presses up/attack
#define		ANI_SELECT 58	// Animation that is displayed at the select screen

#define		MAX_ANIS		59	// max_anis increased for new ANIs






typedef struct{
    unsigned long	version;
    unsigned long	compatibleversion;
    int		gamma;
    int		brightness;
    int		usesound;		// Use SB
    int		soundrate;		// SB freq
    int		soundvol;		// SB volume
    int		usemusic;		// Play music
    int		musicvol;		// Music volume
    int		effectvol;		// Sound fx volume
    int		soundbits;		// SB bits
    int		usejoy;
    int		mode;			// Mode now saves
	unsigned long highsc[10];	// 02-Mar-05 drikobruschi High score table code
	char	hscoren[10][MAX_NAME_LEN+1];

    // Just here for compatibility
    int		unused1;
    int		unused2[2][8];

    int		windowpos;
    int		times_completed;
    int		keys[2][9];
    int		showtitles;
}s_savedata;



typedef struct{
    int		numframes;
    int 	loop;
    int		throwframe;
    int		tossframe;	// Used to determine which frame will toss a bomb/grenade
    int		throwa;	//	Used for setting the "a" at which weapons are spawned
    int		pshotframe;	// player need projectiles too
    char	custpshot[MAX_NAME_LEN+1];
    char	custknife[MAX_NAME_LEN+1];
    char	custfireb[MAX_NAME_LEN+1];
    char	custstar[MAX_NAME_LEN+1];
    char	custbomb[MAX_NAME_LEN+1];	// Used for new projectile bomb
    int		energycost;	// 1-10-05 to adjust the amount of energy used for specials
    int		shootframe;
    int		jumpframe;
    int		moveflag;	// So movement forward can be specified for jumpframes
    int		soundframe;
    int		soundtoplay;
    int		fastattack;	// Flag to determine if the opponent uses their pain time
    int		hitsound;	// Sound effect to be played when attack hits opponent
	char	hitflash[MAX_NAME_LEN+1];	// Custom flash for each animation
    int		sprite[ANI_MAX_FRAMES];
    int		delay[ANI_MAX_FRAMES];
    int		move[ANI_MAX_FRAMES];
    int		seta[ANI_MAX_FRAMES];	// Now characters can have a custom "a" value
    int		vulnerable[ANI_MAX_FRAMES];
    int		bbox_coords[ANI_MAX_FRAMES][4];
    int		attack_coords[ANI_MAX_FRAMES][4];
    int		attack_force[ANI_MAX_FRAMES];
    int		attack_drop[ANI_MAX_FRAMES];
    int		attack_type[ANI_MAX_FRAMES];
    int		attack_length[ANI_MAX_FRAMES];	// How long an attack effect lasts
    int		no_block[ANI_MAX_FRAMES];	// Flag to determine if an attack is blockable (default 0 - blockable)
    int		no_flash[ANI_MAX_FRAMES];	// Flag to determine if an attack spawns a flash or not
    int		pause_add[ANI_MAX_FRAMES];	// Flag to determine if an attack adds a pause before updating the animation
    float		platform[ANI_MAX_FRAMES][8];	// Now entities can have others land on them
    int		range[2];			// Use for attacks
}s_anim;



typedef struct{
    char	name[MAX_NAME_LEN+1];
    int		health;
	int 	nolife;	// Feb 25, 2005 - Variable flag to show life 0 = no, else yes
	int		makeinv;	// Option to spawn player invincible
	int		specpower;	// Mar 12, 2005 - Specify enemy damage by smartbomb
	int		spectype;	// Apr 15, 2005 - Specify which type of attack will be used
	int		speclen;	// Apr 15, 2005 - Specify the duration of a freeze smartbomb
	int		dofreeze;	// Flag to freeze all enemies/players while special is executed
	int		noquake;	// Flag to make the screen shake when entity lands 1 = no, else yes
	int		hitenemy;	// Flag to determine if enemy projectiles will hit enemies as well
	int		ground;		// Flag to determine if enemy projectiles only hit the enemy when hitting the ground
	int		nocost;	// If set, special will not cost life unless an enemy is hit
    int		score;
    int		multiple;	// So you can control how many points are given for hitting opponents
    int		type;
    int		subtype;
    int		icon;
    int   	iconpain;   //20-1-2005   New icons
    int   	iconget;     //20-1-2005   New icons
    int   	icondie;     //20-1-2005   New icons
    int		parrow[2][3];	// Image to be displayed when player spawns invincible
    int		shadow;
    int		aironly;	// Used to determine if shadows will be shown when jumping only
    int		setlayer;	// Used for forcing enities to be displayed behind
    int		fmap;	// Corresponds to which remap to use for when a character is frozen
    int		toflip;	// Flag to determine if flashes flip or not
    int		thold;	// The entities threshold for block
    int		nomove;	// Flag for static enemies
    int		noflip;	// Flag to determine if static enemies flip or stay facing the same direction
    int		nodrop;	// Flag to determine if enemies can be knocked down
    int		nodieblink;	// Flag to determine if blinking while playing die animation
    int		blockodds;	// Odds that an enemy will block an attack (1 : blockodds)
    float	runspeed;	// The speed the character runs at
    float	runjumpheight;	// The height the character jumps when running
    float	runjumpdist;	// The distance the character jumps when running
    int		runupdown;	// Flag to determine if a player will continue to run while pressing up or down
    int		runhold;	// Flag to determine if a player will continue to run if holding down forward when landing
    int		remove;	// Flag to remove a projectile on contact or not
    int		noatflash;	// Flag to determine if attacking characters attack spawns a flash
    float	throwheight;	// The height at which an opponent can now be adjusted
    float	throwdist;	// The distance an opponent can now be adjusted
    int		special[MAX_SPECIALS][5];	// Stores freespecials
    int		specials_loaded;	// Stores how many specials have been loaded
    int		valid_special;	// Used for setting when a valid special has been found
    int		diesound;
    int		secret;
    int		weapnum;
    char		weapon[MAX_WEAPONS][MAX_NAME_LEN+1];
    char		rider[MAX_NAME_LEN+1];	// 7-1-2005 now every "biker" can have a new driver!
    char		playshot[MAX_NAME_LEN+1]; // 7-1-2005 now every player can have a different projectile
    char		knife[MAX_NAME_LEN+1];	// 7-1-2005 now every enemy can have their own "knife" projectile
    char		fireb[MAX_NAME_LEN+1];	// 7-1-2005 now every enemy can have their own "fireball" projectile
    char		star[MAX_NAME_LEN+1];	// 7-1-2005 now every enemy can have their own "ninja star" projectiles
    char		bomb[MAX_NAME_LEN+1];	// New projectile type for exploding bombs/grenades/dynamite
    char		flash[MAX_NAME_LEN+1];	// Now each entity can have their own flash
    char		bflash[MAX_NAME_LEN+1];	// Flash that plays when an attack is blocked
    int		height;	// Used to set height of player in pixels
    float		speed;
    float		grabdistance;	// 30-12-2004	grabdistance varirable adder per character
    float		jumpheight;	// 28-12-2004	Jump height variable added per character
    int 		cantgrab;	// 27-12-2004	can't grab variable added per character
    int		grabback;	// Flag to determine if entities grab images display behind opponenets
    int     falldie; // 6-2-2005 fall and then play die animation?
    int		throwdamage;	// 1-14-05  adjust throw damage
    char *		colourmap[MAX_COLOUR_MAPS];
    char *	map;
    s_anim *	animation[MAX_ANIS];
}s_model;


// Cache list entry for models
typedef struct{
    char		name[MAX_NAME_LEN+1];
    char		path[256];
}s_modelcache;


typedef struct entity{
    int		exists;
    int		health;
    int		score;	// So the score value can be overridden for enemies/obstacles
    int		multiple;	// So the multiple can be overridden for enemies/obstacles
    int		oldhealth;
    int		maxhealth;
    int		type;
    int		playerindex;
    char		name[MAX_NAME_LEN+1];
    char		item[MAX_NAME_LEN+1];
    int		if2p;
    int		itemmap;	// Now items spawned can have their properties changed
    int		itemhealth;	// Now items spawned can have their properties changed
    char	itemalias[MAX_NAME_LEN+1];	// Now items spawned can have their properties changed

    float		x;		// X
    float		z;		// Depth
    float		a;		// Altitude
    float		xdir;
    float		zdir;

    float		base;		// Default altitude
    float		tossv;		// Effect of gravity
    unsigned long	toss_time;	// Used by gravity code
    int		hithead;	// Flag for when a player jumps and hits head on the bottom of a platform

    int		direction;	// 0=left 1=right
    int		layer;	// Used to improve layering when entities are on the same "z"

    int		boss;
	int		dying;	// Coresponds with which remap is to be used for the dying flash
	int		per1;	// Used to store at what health value the entity begins to flash
	int		per2;	// Used to store at what health value the entity flashes more rapidly
	int		nolife;	// Flag to determine if health/icon/name is displayed when hit
	int		noquake;	// Flag to determine if an entity will cause the screen to shake upon landing
    int		makeinv;	// Option to spawn player invincible (1 = yes, else no)
    int		nograb;		// Some enemies cannot be grabbed (bikes) - now used with cantgrab as well

    unsigned long	stalltime;
    unsigned long	combotime;	// For multiple-hit combo
    int		combostep;
    int		combostep2;	// Now you can have forward attack grab attacks
    int		combostep3;	// Now you can have up attack grab attacks
    int		combostep4;	// Now you can have down attack grab attacks
    unsigned long	movetime;	// For special move
    unsigned long	lastmove;
    int		movestep;
    unsigned long	releasetime;

    int		jumping;	// Stuff useful for AI
    int		attacking;
    int		getting;
    int		running;	// Flag to determine if a player is running
    int		projectile;
    int   	blasted;
    int		frozen;	// Flag to determine if an entity is frozen
    int		freezetime;	// Used to store at what point the a frozen entity becomes unfrozen
    int		toexplode;	// Needed to determine if the projectile is a type that will explode (bombs, dynamite, etc)
    int		damage_on_landing;

    s_model *	model;

    s_anim *	animation;
    int		animpos;
    int		lastanimpos;	// Used by AI
    unsigned long	nextanim;
    int		currentsprite;
    int		animating;	// Set by animation code
    int		autokill;	// Kill on end animation

    void		(*think)();
    unsigned long	nextthink;
    void		(*takedamage)(void*,int,int,int);
    int		attack_id;
    int		hit_by_attack_id;
    int		remove_on_attack;

    unsigned long	pain_time;
    int		blink;
	int		invincible;	// Flag used to determine if player is currently invincible
	int		invinctime;	// Used to set time for invincibility to expire
	int		arrowon;	// Flag to display parrow/parrow2 or not
	int		tocost;	// Flag to determine if special costs life if doesn't hit an enemy
    int		screen;
    char *		colourmap;
//    char *		map;	// Stores the colourmap for restoring purposes

    struct entity * link;		// Used to link 2 entities together.
    struct entity * owner;	// Added for "hitenemy" flag so projectile recognizes its owner
}entity;



typedef struct{
    s_model *	model;
    s_anim *	animation[MAX_ANIS];
    int		colourmap;
    unsigned long	score;
    int		lives;
	int		credits;
    int		iconmode;
    int		weapon;
    entity *	ent;
    entity *	opponent;
    unsigned long	keys;
    unsigned long	newkeys;
    unsigned long	playkeys;
    int		spawnhealth;
    int		joining;
    int		hasplayed;
}s_player;


typedef struct{
    s_sprite * sprite_normal;
    s_sprite * sprite_neon;
    s_sprite * sprite_screen;
}s_panel;


typedef struct{
    int		at;
    int		wait;
    int		groupmin;
    int		groupmax;
    char	name[MAX_NAME_LEN+1];
    char	alias[MAX_NAME_LEN+1];
    char	item[MAX_NAME_LEN+1];
    int		itemhealth;
    int		itemmap;
    char	itemalias[MAX_NAME_LEN+1];
    int		if2p;
    int		spawn2p;	// If set to 1, will only be spawned if 2 players are playing
    int		health2p;	// If set to 1, will change the health to increase the challenge for 2 players
    int		health;
    int		score;	// So score can be overridden for enemies/obstacles
    int		multiple;	// So score can be overridden for enemies/obstacles
    int		x;
    int		z;
    int		a;
    int		colourmap;
	int		dying;	// Used for the dying flash animation
	int		per1;	// Used to store at what health value the entity begins to flash
	int		per2;	// Used to store at what health value the entity flashes more rapidly
	int		nolife;	// So nolife can be overriden for all characters
    int		boss;
    int		flip;
}s_spawn_entry;


typedef struct{
    char	filename[128];
    int		gonext;
    int		is_scene;
    int		z_coords[3];	// Used for setting custom "z"
}s_level_entry;


typedef struct{
    int		numspawns;
    s_spawn_entry	spawnpoints[LEVEL_MAX_SPAWNS];
    int		numpanels;
    int		order[LEVEL_MAX_PANELS];
    int		numholes;
    int		numwalls;	// Stores number of walls loaded
    float		holes[LEVEL_MAX_HOLES][7];
    float		walls[LEVEL_MAX_WALLS][9];	// Now you can have walls for different walkable areas
    int		exit_blocked;
    int		exit_hole;
    int		scrolldir;
    int		width;
    int		rocking;
    float	bgspeed;	// Used to make autoscrolling backgrounds
    int		bgdir;	// Used to set which direction the backgrounds scroll for autoscrolling backgrounds
    int		mirror;
    char	bossmusic[256];
    int 	settime;	// Set time limit per level
    int		notime;	// Used to specify if the time is displayed 1 = no, else yes
    int 	type;	// Used to specify which level type (1 = bonus, else regular)
    int		nospecial;	// Used to specify if you can use your special during bonus levels
    int		nohurt;	// Used to specify if you can hurt the other player during bonus levels
    int		noslow;	// Flag so the level doesn't slow down after a boss is defeated
    int		spawn[2][4];	// Used to determine the spawn position of players
}s_level;


typedef struct{
    int		index;
    char		filename[256];
}s_soundcache;


typedef struct{
    int   index;
    char    filename[256];
}s_pakfiles;


s_savedata savedata;



char *packfile = "bor.pak";

char ** stringtable;

char pal[768];
char * lut_mul = NULL;
char * lut_screen = NULL;
s_screen * vscreen = NULL;
s_screen * background = NULL;
s_screen * rescreen = NULL;
s_level *level = NULL;
s_bitmap *texture = NULL;

int fade = 24;   // 13-1-2005 fade speed selection
int paks=0;
int credits;
int GAME_SPEED = 200;  // 13-1-2005 Game speed is now changable.
int gosound = 0;// 26-12-2004 - Used to prevent go sound playing too frequently,
int musicoverlap = 0;
int colorbars=0;
int slo;
int levelpos;
float advancex;
float advancey;
unsigned long advancetime;

int current_spawn;
int level_waiting;
int groupmin, groupmax;
int selectScreen;	// Flag to determine if at select screen (used for setting animations)

int quake = 0;
unsigned long quaketime;
unsigned long go_time;

int level_completed;
int	tospeedup;	// If set will speed the level back up after a boss hits the ground
int reached[2];	// Used with TYPE_ENDLEVEL to determine which players have reached the point
int noslowfx;	// Flag to determine if sound speed when hitting opponent slows or not
int	hiscorebg;	// If set to 1, will look for a background image to display at the highscore screen
int unlockbg;	// If set to 1, will look for a different background image after defeating the game
int pause;
int endgame;
int forcecheatsoff=0;
int cheats=0;
int livescheat=0;
int creditscheat=0;
int healthcheat=0;
int showtimeover;
int sameplayer = 0;		// 7-1-2005  flag to determine if players can use the same character
int PLAYER_LIVES = 3;		// 7-1-2005  default setting for Lives
int colourselect = 0;  //6-2-2005 Colour select is optional
int autoland = 0;	// Default set to no autoland and landing is valid with u j combo
int ajspecial = 0;	// Flag to determine if holding down attack and pressing jump executes special
int	nocost = 0;	// Flag to determine if health is drained if special doesn't hit anyone
int CONTINUES = 5;		// 7-1-2005  default setting for continues
int	freezeall = 0;	// Flag to determine if all entities should not be able to move while a special is executed
int	updatehigh = 0;	// Flat to determine if high score table should be updated
int	plife[2][2];	// Used for customizable player lifebar
int	picon[2][2];	// Used for customizable player icon
int lbarsize[3];	// Used for customizable lifebar size
int	timeloc[5];		// Used for customizable timeclock location/size
int	elife[2][4];	// Used for customizable enemy lifebar
int	eicon[2][2];	// Used for customizable enemy icon
int	noshare;		// Used for when you want to keep p1 & p2 credits separate
int nodropen;	// Drop or not when spawning is now a modder option


int gfx_y_offset = 0;


unsigned long time = 0;
int timeleft;
int	holez;	// Used for setting spawn points
int setwait;	// Used so able to go backwards when using SCROLL_BOTH
int lastwait;	// Used so able to go backwards when using SCROLL_BOTH


int allow_secret_chars = 0;


s_level_entry *levelorder[MAX_DIFFICULTIES][MAX_LEVELS];
unsigned int num_levels[MAX_DIFFICULTIES];
unsigned int ifcomplete[MAX_DIFFICULTIES];
char set_names[MAX_DIFFICULTIES][MAX_NAME_LEN+1];
unsigned int num_difficulties;
unsigned int difflives[MAX_DIFFICULTIES];	// 7-1-2005 what too easy?  change the # of lives players get
unsigned int custfade[MAX_DIFFICULTIES];  //8-2-2005 custom fade
unsigned int diffcreds[MAX_DIFFICULTIES];	// 7-1-2005 What still to easy - lets see how they do without continues!
unsigned int diffoverlap[MAX_DIFFICULTIES]; // 9-2-2005 music overlap
int single[MAX_DIFFICULTIES];  //  7-1-2005 Single player
int same[MAX_DIFFICULTIES];	// ltb 1-13-05   sameplayer
int z_coords[3];	// Used for setting customizable walkable area


s_panel panels[MAX_PANELS];
unsigned int panels_loaded = 0;
int panel_width = 0;

s_sprite *frontpanels[MAX_PANELS];
unsigned int frontpanels_loaded = 0;

s_sprite *sprites[2][MAX_SPRITES];
unsigned int sprites_loaded = 0;

s_model * model_list[MAX_MODELS];
unsigned int models_loaded = 0;

s_modelcache * model_cache[MAX_MODELS];
unsigned int models_cached = 0;

s_anim * anim_list[MAX_ANIMS];
unsigned int anims_loaded = 0;

entity * ent_list[MAX_ENTS];
entity * self;

s_soundcache soundcache[MAX_SOUND_CACHE];

s_pakfiles pakfiles[500];

s_player player[2];
unsigned long bothkeys, bothnewkeys;


s_playercontrols playercontrols1;
s_playercontrols playercontrols2;
s_playercontrols * playercontrolpointers[] = {
            &playercontrols1,
            &playercontrols2
        };




// Funny neon lights
char neontable[256];
unsigned long neon_time = 0;


float lasthitx, lasthitz, lasthita;



// Special sprites
int shadowsprites[6];
int gosprite;
int golsprite; // ltb 1-17-05 new sprite for left direction
int holesprite;



// 7-1-2005 Some colours used here and there   added more colors
int color_black = 0;
int color_red = 0;
int color_orange = 0;
int color_yellow = 0;
int color_white = 0;
int color_blue = 0;
int color_green = 0;
int color_pink = 0;
int color_purple = 0;



// Change to array
int smp_go;							// 26-12-2004 - New sample created - Go sample
int smp_beat;
int	smp_block;	// Sample for when an attack is blocked (defaults to the same as smp_beat)
int smp_indirect;
int smp_get;
int smp_get2;
int smp_fall;
int smp_jump;
int smp_punch;
int smp_1up;
int smp_timeover;
int smp_beep;
int smp_beep2;
int smp_bike;



// Required prototypes
void shutdown(char *, ...);
void debug_printf(char *, ...);


int findmods(void){
    int i = 0;
    DIR * dirp;
    struct dirent * direntp;

    dirp = opendir(".");
    if(dirp==NULL) return 0;

    while(direntp = readdir(dirp)){

        if(!(direntp->d_attr & (_A_SUBDIR | _A_VOLID | _A_SUBDIR))){

            // This is a file. Is it a PAK file?
            if(strstr(direntp->d_name, ".pak") || strstr(direntp->d_name, ".PAK")){
                // Found one!
                strncpy(pakfiles[i].filename, direntp->d_name, (strlen(direntp->d_name)+15));
                i++;
                paks = i;
            }
        }
    }
    closedir(dirp);


    return 1;
}


// ------------------------ Save/load -----------------------------

void clearsettings(){
    savedata.version = VERSION;
    savedata.compatibleversion = COMPATIBLEVERSION;
    savedata.gamma = 0;
    savedata.brightness = 0;
    savedata.usesound = 1;
    savedata.soundrate = 22050;
    savedata.soundvol = 14;
    savedata.usemusic = 1;
    savedata.musicvol = 88;
    savedata.effectvol = 108;
    savedata.soundbits = 16;
    savedata.usejoy = 1;
    savedata.mode = 0;	// Mode now saved in settings.sav
    savedata.highsc[0] = 0;	// Resets all the highscores to 0
    savedata.highsc[1] = 0;
    savedata.highsc[2] = 0;
    savedata.highsc[3] = 0;
    savedata.highsc[4] = 0;
    savedata.highsc[5] = 0;
    savedata.highsc[6] = 0;
    savedata.highsc[7] = 0;
    savedata.highsc[8] = 0;
    savedata.highsc[9] = 0;
	strcpy(savedata.hscoren[0], "None");	// Resets all the highscore names to "None"
	strcpy(savedata.hscoren[1], "None");
	strcpy(savedata.hscoren[2], "None");
	strcpy(savedata.hscoren[3], "None");
	strcpy(savedata.hscoren[4], "None");
	strcpy(savedata.hscoren[5], "None");
	strcpy(savedata.hscoren[6], "None");
	strcpy(savedata.hscoren[7], "None");
	strcpy(savedata.hscoren[8], "None");
	strcpy(savedata.hscoren[9], "None");
    savedata.keys[0][SDID_MOVEUP]    = CONTROL_DEFAULT_UP;
    savedata.keys[0][SDID_MOVEDOWN]  = CONTROL_DEFAULT_DOWN;
    savedata.keys[0][SDID_MOVELEFT]  = CONTROL_DEFAULT_LEFT;
    savedata.keys[0][SDID_MOVERIGHT] = CONTROL_DEFAULT_RIGHT;
    savedata.keys[0][SDID_SPECIAL]   = CONTROL_DEFAULT_FIRE3;
    savedata.keys[0][SDID_ATTACK]    = CONTROL_DEFAULT_FIRE1;
    savedata.keys[0][SDID_JUMP]      = CONTROL_DEFAULT_FIRE2;
    savedata.keys[0][SDID_START]     = CONTROL_DEFAULT_START;
    savedata.keys[0][SDID_SCREENSHOT]= CONTROL_DEFAULT_SCREENSHOT;

    savedata.keys[1][SDID_MOVEUP]    = CONTROL_JOY_UP;
    savedata.keys[1][SDID_MOVEDOWN]  = CONTROL_JOY_DOWN;
    savedata.keys[1][SDID_MOVELEFT]  = CONTROL_JOY_LEFT;
    savedata.keys[1][SDID_MOVERIGHT] = CONTROL_JOY_RIGHT;
    savedata.keys[1][SDID_SPECIAL]   = CONTROL_JOY_3;
    savedata.keys[1][SDID_ATTACK]    = CONTROL_JOY_1;
    savedata.keys[1][SDID_JUMP]      = CONTROL_JOY_2;
    savedata.keys[1][SDID_START]     = CONTROL_JOY_4;
    savedata.keys[1][SDID_SCREENSHOT]= CONTROL_DEFAULT_SCREENSHOT;

    savedata.windowpos = 0;
    savedata.times_completed = 0;
    savedata.showtitles = 0;
}



void savesettings(){
    int handle;
    handle = open("settings.sav", O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0);
    if(handle<0) return;
    write(handle, &savedata, sizeof(s_savedata));
    close(handle);
}



void loadsettings(){
    int handle;

    clearsettings();

    handle = open("settings.sav", O_RDONLY|O_BINARY);
    if(handle<0) return;

    read(handle, &savedata, sizeof(s_savedata));
    close(handle);

    if(savedata.compatibleversion != COMPATIBLEVERSION) clearsettings();
}




// ----------------------- Sound ------------------------------


void music(char *filename, int loop){
    char t[64];
    char a[64];

    if(!savedata.usemusic) return;

    if(!sound_open_music(filename, packfile, savedata.musicvol, loop)){
        debug_printf("Can't play music file '%s'", filename);
    }
    else if(savedata.showtitles && sound_query_music(a,t)){
        if(a[0] && t[0]) debug_printf("Playing \"%s\" by %s", t, a);
        else if(a[0]) debug_printf("Playing unknown song by %s", a);
        else if(t[0]) debug_printf("Playing \"%s\" by unknown artist", t);
        else debug_printf("");
    }
}



// Load a sound or return index from cache
int loadcache_sound(char *filename){
    int i;

    for(i=0; i<MAX_SOUND_CACHE; i++){
        if(strcmp(filename, soundcache[i].filename)==0) return soundcache[i].index;
    }

    for(i=0; i<MAX_SOUND_CACHE; i++){
        if(!soundcache[i].filename[0]) break;
    }
    if(i==MAX_SOUND_CACHE) return -1;

    soundcache[i].index = sound_load_sample(filename, packfile);
    if(soundcache[i].index < 0) return -1;
    strcpy(soundcache[i].filename, filename);
    return soundcache[i].index;
}



// ----------------------- General ------------------------------

char ** read_string_table(char * filename, int max_lines){

	char ** stringtable;
	char * buf;
	int size;
	int handle;
	int i;
	int foundline, stringi;


	// Alloc memory for string table
	stringtable = (char**)malloc((max_lines+1) * sizeof(char*));
	if(!stringtable) return NULL;
	memset(stringtable, 0, (max_lines+1) * sizeof(char*));

stringtable[0] = "Start";
stringtable[1] = "Options";
stringtable[2] = "How To Play";
stringtable[3] = "Quit";
stringtable[4] = "Frequency:";
stringtable[5] = "Bits:";
stringtable[6] = "Apply";
stringtable[7] = "Discard";
stringtable[8] = "Back";
stringtable[9] = "GAME OVER";
stringtable[10] = "Setup controls for player";
stringtable[11] = "Brightness:";
stringtable[12] = "Gamma:";
stringtable[13] = "Setup input devices...";
stringtable[14] = "Setup sound card...";
stringtable[15] = "Sound options...";
stringtable[16] = "Window offset";
stringtable[17] = "10-20 needed for some TV's";
stringtable[18] = "Mode 1 - Players CAN'T attack each other";
stringtable[19] = "Mode 2 - Players CAN attack each other";
stringtable[20] = "Enemies don't blink when they die";
stringtable[21] = "Enemies blink when they die";
stringtable[22] = "Enemies fall down when you respawn";
stringtable[23] = "Enemies don't fall down when you respawn";
stringtable[24] = "Game Speed";
stringtable[25] = "Input options";
stringtable[26] = "Gamepad enabled";
stringtable[27] = "* device is not ready";
stringtable[28] = "Gamepad disabled";
stringtable[29] = "Setup controls P1...";
stringtable[30] = "Setup controls P2...";
stringtable[31] = "Move up";
stringtable[32] = "Move down";
stringtable[33] = "Move left";
stringtable[34] = "Move right";
stringtable[35] = "Special";
stringtable[36] = "Attack";
stringtable[37] = "Jump";
stringtable[38] = "Start";
stringtable[39] = "Screenshot";
stringtable[40] = "OK";
stringtable[41] = "Cancel";
stringtable[42] = "Options";
stringtable[43] = "Sound volume:";
stringtable[44] = "Sound effects volume:";
stringtable[45] = "Background music:";
stringtable[46] = "Music volume:";
stringtable[47] = "Show music titles:";
stringtable[48] = "Enabled";
stringtable[49] = "Disabled";
stringtable[50] = "Yes";
stringtable[51] = "No";
stringtable[52] = "pause";
stringtable[53] = "Continue";
stringtable[54] = "End game";
stringtable[55] = "select hero";
stringtable[56] = "Press start";
stringtable[57] = "Credit";
stringtable[58] = "Loading...";
stringtable[59] = "PRESS START";
stringtable[60] = "Difficulty";
stringtable[61] = "- finish game";
stringtable[62] = "times to unlock";
stringtable[63] = "- finish game to unlock";
stringtable[64] = "Player";
stringtable[65] = "Choose a Different Character!";
stringtable[66] = "ready!";
stringtable[67] = "Stage";
stringtable[68] = "complete!";
stringtable[69] = "Clear bonus";
stringtable[70] = "Life bonus";
stringtable[71] = "Total score";
stringtable[72] = "PRESS START";
stringtable[73] = "Hall of Fame";



	// Try to open file and determine size
	if((handle=openpackfile(filename,packfile)) < 0){
		// free(stringtable);	NO!
		// return NULL;	NO!
		return stringtable;	// BETTER!
	}
	size = seekpackfile(handle,0,SEEK_END);
	seekpackfile(handle,0,SEEK_SET);


	// Alloc buffer
	buf = (char*)malloc(size+1);
	if(!buf){
		free(stringtable);
		closepackfile(handle);
		return NULL;
	}
	memset(buf, 0, size+1);


	// Read entire file
	readpackfile(handle, buf, size);

	// OK, close it.
	closepackfile(handle);


	// Find lines
	stringi = 0;
	foundline = 1;
	for(i=0; buf[i]; i++){
		if(foundline){
			stringtable[stringi] = buf + i ;
			stringi++;
			foundline = 0;
		}
    if(buf[i] == '\r') buf[i] = 0;
		if(buf[i] == '\n'){
			buf[i] = 0;	// Terminate string
			if(stringi >= max_lines) break;
			foundline = 1;
		}
	}


	return stringtable;
}



#define		ARG_MAX_LEN		512

char * findarg(char *command, int which){
    int d;
    int argc;
    int inarg;
    int argstart;
    static char arg[ARG_MAX_LEN];


    // Copy the command line, replacing spaces by zeroes,
    // finally returning a pointer to the requested arg.
    d = 0;
    inarg = 0;
    argstart = 0;
    argc = -1;
    while(d<ARG_MAX_LEN-1 && command[d]){
        // Zero out whitespace
        if(command[d]==' ' || command[d]=='\t'){
            arg[d] = 0;
            inarg = 0;
            if(argc == which) return arg + argstart;
        }
        else if(command[d]==0 || command[d]=='\n' || command[d]=='\r' || command[d]=='#'){
            // End of line
            arg[d] = 0;
            if(argc == which) return arg + argstart;
            return arg + d;
        }
        else{
            if(!inarg){
                // if(argc==-1 && command[d]=='#') return arg;
                inarg = 1;
                argstart = d;
                argc++;
            }
            arg[d] = command[d];
        }
        ++d;
    }

    return arg;
}



float diff(float a, float b){
    if(a<b) return b-a;
    return a-b;
}



int inair(entity *e){
	return (diff(e->a, e->base) >= 0.1);
}



float randf(float max){
    float f;
    if(max==0) return 0;
    f = (rand32()%1000);
    f /= (1000/max);
    return f;
}



// ----------------------- Loaders ------------------------------




// Creates a remapping table from two images
int load_colourmap(s_model * model, char *image1, char *image2){
    int i, j, k;
    char *map;
    s_bitmap *bitmap1;
    s_bitmap *bitmap2;

    // Can't use same image twice!
    if(stricmp(image1,image2)==0) return 0;

    // Find an empty slot... ;)
    for(k=0; k<MAX_COLOUR_MAPS && model->colourmap[k]; k++);
    if(k>=MAX_COLOUR_MAPS) return 0;

    map = (char*)malloc(256);
    if(map==NULL) return 0;

    bitmap1 = loadbitmap(image1, packfile);
    if(bitmap1==NULL){
        free(map);
        return 0;
    }
    bitmap2 = loadbitmap(image2, packfile);
    if(bitmap2==NULL){
        freebitmap(bitmap1);
        free(map);
        return 0;
    }

    // Create the colour map
    for(i=0;i<256;i++) map[i] = i;
    for(j=0; j<bitmap1->height && j<bitmap2->height; j++){
        for(i=0; i<bitmap1->width && i<bitmap2->width; i++){
            map[bitmap1->data[j*bitmap1->width+i]] = bitmap2->data[j*bitmap2->width+i];
        }
    }

    freebitmap(bitmap1);
    freebitmap(bitmap2);

    model->colourmap[k] = map;
    return 1;
}



// Load colour 0-127 from data/pal.act
void standard_palette(){
    int handle;
    handle = openpackfile("data/pal.act", packfile);
    readpackfile(handle, pal, 128*3);
    closepackfile(handle);
    pal[0] = pal[1] = pal[2] = 0;
    palette_set_corrected(pal, savedata.gamma,savedata.gamma,savedata.gamma, savedata.brightness,savedata.brightness,savedata.brightness);
}



void unload_background(){
    if(background) freescreen(background);
    background = NULL;
    if(lut_mul) free(lut_mul);
    lut_mul = NULL;
    if(lut_screen) free(lut_screen);
    lut_screen = NULL;
}



// ltb 1-17-05   new function for lifebar colors
void lifebar_colors(){
    char * filename = "data/lifebar.txt";
    int handle;
    char *buf;
    unsigned int size;
    int pos;

    char * command;
    char * value;
    char * value2;
    char * value3;



    if((handle=openpackfile(filename,packfile)) < 0){
        color_black = 0;
        color_red = 0;
        color_orange = 0;
        color_yellow = 0;
        color_white = 0;
        color_blue = 0;
        color_green = 0;
        color_pink = 0;
        color_purple = 0;
        return;
    }
    size = seekpackfile(handle,0,SEEK_END);
    seekpackfile(handle,0,SEEK_SET);

    buf = (char*)malloc(size+1);
    if(buf==NULL){
        closepackfile(handle);
        shutdown("Not enough memory to allocate %i-byte buffer for file %s", size, filename);
    }
    if(readpackfile(handle, buf, size) != size){
        free(buf);
        closepackfile(handle);
        shutdown("Read error accessing file %s", filename);
    }
    buf[size] = 0;		// Terminate string (important!)
    closepackfile(handle);


    pos = 0;
    colorbars=1;
    while(pos<size){
        command = findarg(buf+pos, 0);
        if(command[0]){
            if(stricmp(command, "blackbox")==0){
                value = findarg(buf+pos, 1);
                value2 = findarg(buf+pos, 2);
                value3 = findarg(buf+pos, 3);
                color_black = palette_find(pal, atoi(value),atoi(value2),atoi(value3));
            }
            else if(stricmp(command, "whitebox")==0){
                value = findarg(buf+pos, 1);
                value2 = findarg(buf+pos, 2);
                value3 = findarg(buf+pos, 3);
                color_white = palette_find(pal, atoi(value),atoi(value2),atoi(value3));
            }
            else if(stricmp(command, "color300")==0){
                value = findarg(buf+pos, 1);
                value2 = findarg(buf+pos, 2);
                value3 = findarg(buf+pos, 3);
                color_orange = palette_find(pal, atoi(value),atoi(value2),atoi(value3));
            }
            else if(stricmp(command, "color25")==0){
                value = findarg(buf+pos, 1);
                value2 = findarg(buf+pos, 2);
                value3 = findarg(buf+pos, 3);
                color_red = palette_find(pal, atoi(value),atoi(value2),atoi(value3));
            }
            else if(stricmp(command, "color50")==0){
                value = findarg(buf+pos, 1);
                value2 = findarg(buf+pos, 2);
                value3 = findarg(buf+pos, 3);
                color_yellow = palette_find(pal, atoi(value),atoi(value2),atoi(value3));
            }
            else if(stricmp(command, "color100")==0){
                value = findarg(buf+pos, 1);
                value2 = findarg(buf+pos, 2);
                value3 = findarg(buf+pos, 3);
                color_green = palette_find(pal, atoi(value),atoi(value2),atoi(value3));
            }
            else if(stricmp(command, "color200")==0){
                value = findarg(buf+pos, 1);
                value2 = findarg(buf+pos, 2);
                value3 = findarg(buf+pos, 3);
                color_blue = palette_find(pal, atoi(value),atoi(value2),atoi(value3));
            }
            else if(stricmp(command, "color400")==0){
                value = findarg(buf+pos, 1);
                value2 = findarg(buf+pos, 2);
                value3 = findarg(buf+pos, 3);
                color_pink = palette_find(pal, atoi(value),atoi(value2),atoi(value3));
            }
            else if(stricmp(command, "color500")==0){
                value = findarg(buf+pos, 1);
                value2 = findarg(buf+pos, 2);
                value3 = findarg(buf+pos, 3);
                color_purple = palette_find(pal, atoi(value),atoi(value2),atoi(value3));
            }
            else{
                free(buf);
                return;
            }
        }

        // Go to next line
        while(buf[pos] && buf[pos]!='\n' && buf[pos]!='\r') ++pos;
        while(buf[pos]=='\n' || buf[pos]=='\r') ++pos;
    }
    free(buf);
}

// ltb 1-17-05 end new lifebar colors



void load_background(char *filename, int createtables){
    s_screen * screen;

    unload_background();
    video_clearscreen();

    background = loadscreen(filename, packfile, pal);
    if(background==NULL) shutdown("Error loading file '%s'", filename);
    pal[0] = pal[1] = pal[2] = 0;
    palette_set_corrected(pal, savedata.gamma,savedata.gamma,savedata.gamma, savedata.brightness,savedata.brightness,savedata.brightness);

    clearscreen(vscreen);
    spriteq_clear();
    font_printf(120,110, 0, "%s",stringtable[58]); //Loading...
    spriteq_draw(vscreen);
    if(rescreen){
        scalescreen(rescreen, vscreen);
        video_copy_screen(rescreen);
    }
    else video_copy_screen(vscreen);

    lifebar_colors();	// ltb 1-17-05  load custom colors

    if(!color_black) color_black = palette_find(pal, 0,0,0);		// black boxes 500-600HP
    if(!color_red) color_red = palette_find(pal, 255,0,0);		// 1% - 25% Full Health
    if(!color_orange) color_orange = palette_find(pal, 255,150,0);	// 200-300HP
    if(!color_yellow) color_yellow = palette_find(pal, 0xF8,0xB8,0x40);	// 26%-50% Full health
    if(!color_white) color_white = palette_find(pal, 255,255,255);	// white boxes 600+ HP
    if(!color_blue) color_blue = palette_find(pal, 0,0,255);		// 100-200 HP
    if(!color_green) color_green = palette_find(pal, 0,255,0);		// 51% - 100% full health
    if(!color_pink) color_pink = palette_find(pal, 255,0,255);		// 300-400HP
    if(!color_purple) color_purple = palette_find(pal, 128,48,208);	// transbox 400-500HP


    if(createtables){
        standard_palette();

        lut_mul = palette_table_multiply(pal);
        if(lut_mul==NULL) shutdown("Failed to create colour conversion table! (Out of memory?)");
        lut_screen = palette_table_screen(pal);
        if(lut_screen==NULL) shutdown("Failed to create colour conversion table! (Out of memory?)");
    }
}



void unload_texture(){
    if(texture) freebitmap(texture);
    texture = NULL;
}



void load_texture(char *filename){
    unload_texture();
    texture = loadbitmap(filename, packfile);
    if(texture==NULL) shutdown("Error loading file '%s'", filename);
}



void freepanels(){
    int i;
    for(i=0; i<MAX_PANELS; i++){
        if(panels[i].sprite_normal) free(panels[i].sprite_normal);
        panels[i].sprite_normal = NULL;
        if(panels[i].sprite_neon) free(panels[i].sprite_neon);
        panels[i].sprite_neon = NULL;
        if(panels[i].sprite_screen) free(panels[i].sprite_screen);
        panels[i].sprite_screen = NULL;

        if(frontpanels[i]) free(frontpanels[i]);
        frontpanels[i] = NULL;
    }
    panels_loaded = 0;
    frontpanels_loaded = 0;
    panel_width = 0;
}



s_sprite * loadpanel2(char *filename){
    int size;
    s_bitmap *bitmap;
    s_sprite *sprite;
    int clipl, clipr, clipt, clipb;

    bitmap = loadbitmap(filename, packfile);
    if(!bitmap) return NULL;
    if(bitmap->width > panel_width) panel_width = bitmap->width;
    clipbitmap(bitmap, &clipl, &clipr, &clipt, &clipb);
    size = fakey_encodesprite(bitmap);
    sprite = (s_sprite*)malloc(size);
    if(!sprite){
        freebitmap(bitmap);
        return NULL;
    }
    encodesprite(-clipl, -clipt, bitmap, sprite);
    freebitmap(bitmap);

    return sprite;
}



int loadpanel(char *filename_normal, char *filename_neon, char *filename_screen){

    int i = 0;

    if(panels_loaded >= MAX_PANELS) return 0;

    if(stricmp(filename_normal,"none")!=0 && *filename_normal){
        panels[panels_loaded].sprite_normal = loadpanel2(filename_normal);
        if(panels[panels_loaded].sprite_normal == NULL) return 0;
        i++;
    }
    if(stricmp(filename_neon,"none")!=0 && *filename_neon){
        panels[panels_loaded].sprite_neon = loadpanel2(filename_neon);
        if(panels[panels_loaded].sprite_neon == NULL) return 0;
        i++;
    }
    if(stricmp(filename_screen,"none")!=0 && *filename_screen){
        panels[panels_loaded].sprite_screen = loadpanel2(filename_screen);
        if(panels[panels_loaded].sprite_screen == NULL) return 0;
        i++;
    }
    if(i<1) return 0;	// Nothing was loaded!

    ++panels_loaded;

    return 1;
}



int loadfrontpanel(char *filename){

    int size;
    s_bitmap *bitmap;
    int clipl, clipr, clipt, clipb;


    if(frontpanels_loaded >= MAX_PANELS) return 0;
    bitmap = loadbitmap(filename, packfile);
    if(!bitmap) return 0;

    clipbitmap(bitmap, &clipl, &clipr, &clipt, &clipb);

    size = fakey_encodesprite(bitmap);
    frontpanels[frontpanels_loaded] = (s_sprite*)malloc(size);
    if(!frontpanels[frontpanels_loaded]){
        freebitmap(bitmap);
        return 0;
    }
    encodesprite(-clipl, -clipt, bitmap, frontpanels[frontpanels_loaded]);

    freebitmap(bitmap);
    ++frontpanels_loaded;

    return 1;
}



void freesprites(){
    int i;
    for(i=0; i<MAX_SPRITES; i++){
        if(sprites[0][i]) free(sprites[0][i]);
        sprites[0][i] = NULL;
        if(sprites[1][i]) free(sprites[1][i]);
        sprites[1][i] = NULL;
    }
    sprites_loaded = 0;
}



// Returns sprite index.
// Does not return on error, as it would shut the program down.
int loadsprite(char *filename, int ofsx, int ofsy){

    int size;
    s_bitmap *bitmap;
    int clipl, clipr, clipt, clipb;


    if(sprites_loaded >= MAX_SPRITES) shutdown("Too many sprites (max. %u)", MAX_SPRITES);
    bitmap = loadbitmap(filename, packfile);
    if(!bitmap) shutdown("Unable to load file '%s' (may be out of memory)", filename);

    clipbitmap(bitmap, &clipl, &clipr, &clipt, &clipb);

    size = fakey_encodesprite(bitmap);
    sprites[1][sprites_loaded] = (s_sprite*)malloc(size);
    if(!sprites[1][sprites_loaded]){
        freebitmap(bitmap);
        shutdown("Out of memory!");
    }
    encodesprite(ofsx-clipl, ofsy-clipt, bitmap, sprites[1][sprites_loaded]);

    // Now flip it
    flipbitmap(bitmap);
    size = fakey_encodesprite(bitmap);
    sprites[0][sprites_loaded] = (s_sprite*)malloc(size);
    if(!sprites[0][sprites_loaded]){
        freebitmap(bitmap);
        shutdown("Out of memory!");
    }
    encodesprite((bitmap->width+clipl)-ofsx-1, ofsy-clipt, bitmap, sprites[0][sprites_loaded]);

    freebitmap(bitmap);
    ++sprites_loaded;

    return sprites_loaded-1;
}



s_model * find_model(char *name){
    int i;
    for(i=0; i<models_loaded; i++){
        if(stricmp(model_list[i]->name, name)==0){
            return model_list[i];
        }
    }
    return NULL;
}



// Use by player select menus
s_model * nextplayermodel(void *current){
    int i;
    int curindex = -1;
    int loops;

    if(current){
        // Find index of current player model
        for(i=0; i<models_loaded; i++){
            if(model_list[i] == current){
                curindex = i;
                break;
            }
        }
    }

    // Find next player model (first one after current index)

    for(i=curindex+1, loops=0; loops<models_loaded; i++, loops++){
        if(i >= models_loaded) i = 0;
        if(model_list[i]->type==TYPE_PLAYER && (allow_secret_chars || !model_list[i]->secret)){
            return model_list[i];
        }
    };

    shutdown("Fatal: can't find any player models!");
    return NULL;
}



// Use by player select menus
s_model * prevplayermodel(void *current){
    int i;
    int curindex = -1;
    int loops;

    if(current){
        // Find index of current player model
        for(i=0; i<models_loaded; i++){
            if(model_list[i] == current){
                curindex = i;
                break;
            }
        }
    }

    // Find next player model (first one after current index)
    for(i=curindex-1, loops=0; loops<models_loaded; i--, loops++){
        if(i < 0) i = models_loaded-1;
        if(model_list[i]->type==TYPE_PLAYER && (allow_secret_chars || !model_list[i]->secret)){
            return model_list[i];
        }
    };

    shutdown("Fatal: can't find any player models!");
    return NULL;
}



// Unload all models and animations
void free_models(){
    int i;
    int j;
    for(i=0; i<MAX_MODELS; i++){
        if(model_list[i]){
            for(j=0;j<MAX_COLOUR_MAPS;j++){
                if(model_list[i]->colourmap[j]) free(model_list[i]->colourmap[j]);
                if(model_list[i]->map) free(model_list[i]->map);
            }
            free(model_list[i]);
            model_list[i] = NULL;
        }
    }
    models_loaded = 0;

    for(i=0; i<MAX_ANIMS; i++){
        if(anim_list[i]){
            free(anim_list[i]);
            anim_list[i] = NULL;
        }
    }
    anims_loaded = 0;
}



s_anim * alloc_anim(){
    s_anim * new;

    if(anims_loaded >= MAX_ANIMS) return NULL;
    new = (s_anim *)malloc(sizeof(s_anim));
    if(new == NULL) return NULL;
    memset(new, 0, sizeof(s_anim));

    anim_list[anims_loaded] = new;
    ++anims_loaded;

    return new;
}



// Add another frame to an animation (if possible)
int addframe(s_anim * a, int spriteindex, int delay, int *bbox, int *attack, int attackforce, int attackdrop,
int attacktype, int move, int seta, int attacklength, int noblock, int noflash, int pauseadd, int* platform){	// Added attacklength for freeze attacks
    if(a->numframes >= ANI_MAX_FRAMES) return ANI_MAX_FRAMES;
    a->sprite[a->numframes] = spriteindex;
    a->delay[a->numframes] = delay * GAME_SPEED / 100;
    if((bbox[2]-bbox[0]) && (bbox[3]-bbox[1])){
        a->bbox_coords[a->numframes][0] = bbox[0];
        a->bbox_coords[a->numframes][1] = bbox[1];
        a->bbox_coords[a->numframes][2] = bbox[2];
        a->bbox_coords[a->numframes][3] = bbox[3];
        a->vulnerable[a->numframes] = 1;
    }
    a->attack_coords[a->numframes][0] = attack[0];
    a->attack_coords[a->numframes][1] = attack[1];
    a->attack_coords[a->numframes][2] = attack[2];
    a->attack_coords[a->numframes][3] = attack[3];
    a->attack_force[a->numframes] = attackforce;
    a->attack_drop[a->numframes] = attackdrop;
    a->attack_type[a->numframes] = attacktype;
    a->attack_length[a->numframes] = attacklength;	// Freezetime corresponds with the respective frame
    a->no_block[a->numframes] = noblock;	// Determine if an attack is blockable (default 0 - blockable)
	a->no_flash[a->numframes] = noflash;	// Determine if the attack spawns a flash or not
	a->pause_add[a->numframes] = pauseadd;	// Determine if the attack adds a pause before updating animation
    a->move[a->numframes] = move;
    a->seta[a->numframes] = seta;	// Sets the "a" for the character on a frame/frame basis
    a->platform[a->numframes][0] = platform[0];	// Used so entity can be landed on
    a->platform[a->numframes][1] = platform[1];	// Used so entity can be landed on
    a->platform[a->numframes][2] = platform[2];	// Used so entity can be landed on
    a->platform[a->numframes][3] = platform[3];	// Used so entity can be landed on
    a->platform[a->numframes][4] = platform[4];	// Used so entity can be landed on
    a->platform[a->numframes][5] = platform[5];	// Used so entity can be landed on
    ++a->numframes;
    return a->numframes;
}



void cache_model(char *name, char *path){
    if(models_cached >= MAX_MODELS) shutdown("Too many models, unable to cache '%s'", name);
    printf("Cacheing '%s'\n", name);

    model_cache[models_cached] = malloc(sizeof(s_modelcache));
    if(model_cache[models_cached] == NULL) shutdown("Out of memory cacheing '%s'", name);

    strncpy(model_cache[models_cached]->name, name, MAX_NAME_LEN);
    strncpy(model_cache[models_cached]->path, path, 255);

    ++models_cached;
}



void remove_from_cache(char * name){
    int i;
    void *tp;

    for(i=0; i<models_cached; i++){
        if(stricmp(name, model_cache[i]->name)==0){
            tp = model_cache[i];
            model_cache[i] = model_cache[models_cached-1];
            free(tp);
            model_cache[models_cached-1] = NULL;
            models_cached--;
            return;
        }
    }
}



void free_modelcache(){
    int i;
    for(i=0; i<MAX_MODELS; i++){
        if(model_cache[i]) free(model_cache[i]);
        model_cache[i] = NULL;
    }
    models_cached = 0;
}



char *get_cached_model_path(char * name){
    int i;

    for(i=0; i<models_cached; i++){
        if(stricmp(name, model_cache[i]->name)==0){
            return model_cache[i]->path;
        }
    }
    return NULL;
}



void load_cached_model(char * name){

    char *filename;

    int handle;
    char *buf;
    unsigned int size;
    int pos;

    s_model * newchar;
    s_anim * newanim;
    int index;

    char * command;
    char * value;
    char * value2;

    int curframe = 0;

    int delay = 0;
    int bbox[4] = { 0,0,0,0 };
    int attack[4] = { 0,0,0,0 };
    int platform[8] = { 0,0,0,0,0,0,0,0 };
    int attackforce = 0;
    int attackdrop = 0;
    int attacktype = 0;
    int attacklength = 0;	// Default attacklength set to 0
    int noblock = 0;	// Default set to blockable
    int	noflash = 0;	// Default noflash set to 1 (does spawn flash)
    int pauseadd = 0;	// How much of a pause to add when an attack hits an opponent
    int offset[2] = { 0,0 };
    int move = 0;
    int seta = -1;	// Used for setting custom "a". Set to -1 to distinguish between disabled and setting "a" to 0

    int bbox_con[4];
    int attack_con[4];
    int platform_con[6];
    int i = 0;
    int weap, last;


    if(find_model(name)) return;		// Model already loaded


    filename = get_cached_model_path(name);
    if(filename == NULL){
        shutdown("Fatal: no cache entry for '%s'", name);
    }


    if(models_loaded >= MAX_MODELS){
        shutdown("Cannot load model from '%s' - too many models", filename);
    }
    // printf("Loading model %s...\n", filename);

    if((handle=openpackfile(filename,packfile)) < 0) shutdown("Unable to open file '%s'", filename);
    size = seekpackfile(handle,0,SEEK_END);
    seekpackfile(handle,0,SEEK_SET);

    buf = (char*)malloc(size+1);
    if(buf==NULL){
        closepackfile(handle);
        shutdown("Unable to create buffer for file '%s' (%i bytes)", filename, size);
    }
    if(readpackfile(handle, buf, size) != size){
        free(buf);
        closepackfile(handle);
        shutdown("Read error accessing file '%s'", filename);
    }
    buf[size] = 0;		// Terminate string (important!)
    closepackfile(handle);


    // Alloc space for game model
    newchar = (s_model *)malloc(sizeof(s_model));
    if(newchar == NULL){
        free(buf);
        shutdown("Out of memory loading model from '%s'", filename);
    }
    model_list[models_loaded] = newchar;
    memset(newchar,0,sizeof(s_model));
    newchar->falldie = 0;   // 6-2-2005 Falldie code
    newchar->jumpheight = 4;// 28-12-2004   Set default jump height to 4, if not specified
    newchar->runjumpheight = 4;	// Default jump height if a player is running
    newchar->runjumpdist = 1;	// Default jump distane if a player is running
    newchar->grabdistance = 36;//  30-12-2004 Default grabdistance is same as originally set
    newchar->throwdamage = 21;	// default throw damage
    newchar->icon = -1;
    newchar->diesound = -1;
	newchar->nolife = 0;	// default show life = 1 (yes)
	newchar->remove = 1;	// Flag set to weapons are removed upon hitting an opponent
	newchar->blockodds = 1;	// Odds are about 1:1 that the enemy will block your shot
	newchar->throwdist = 2.5;
	newchar->throwheight = 0;
    strncpy(newchar->rider, "K'", MAX_NAME_LEN);
    strncpy(newchar->star, "star", MAX_NAME_LEN);
    strncpy(newchar->fireb, "shot", MAX_NAME_LEN);
    strncpy(newchar->knife, "knife", MAX_NAME_LEN);
    strncpy(newchar->bomb, "bomb", MAX_NAME_LEN);	// Each character can have a custom bomb
    strncpy(newchar->flash, "flash", MAX_NAME_LEN);	// Now each character can have their own flash
    strncpy(newchar->bflash, "flash", MAX_NAME_LEN);	// Flash for when an attack is blocked
    models_loaded++;


    // Now interpret the contents of buf line by line
    pos = 0;
    while(pos<size){
        command = findarg(buf+pos, 0);
        if(command[0]){
            if(stricmp(command, "name")==0){
                value = findarg(buf+pos, 1);
                if(find_model(value)){
                    free(buf);
                    shutdown("Duplicate model name '%s'", value);
                }
                strncpy(newchar->name, value, MAX_NAME_LEN);
            }
			else if(stricmp(command, "type")==0){	// Moved here to be able to access the character type later on
                value = findarg(buf+pos, 1);
                if(stricmp(value, "none")==0){
                    newchar->type = TYPE_NONE;
                }
                else if(stricmp(value, "player")==0){
                    newchar->type = TYPE_PLAYER;
                }
                else if(stricmp(value, "enemy")==0){
                    newchar->type = TYPE_ENEMY;
                }
                else if(stricmp(value, "item")==0){
                    newchar->type = TYPE_ITEM;
                }
                else if(stricmp(value, "obstacle")==0){
                    newchar->type = TYPE_OBSTACLE;
                }
                else if(stricmp(value, "steamer")==0){
                    newchar->type = TYPE_STEAMER;
                }
                // my new types   7-1-2005
                else if(stricmp(value, "pshot")==0){
                    newchar->type = TYPE_SHOT;
                }
                else if(stricmp(value, "trap")==0){
                    newchar->type = TYPE_TRAP;
                }
                else if(stricmp(value, "text")==0){	// Used for displaying text/images and freezing the screen
					newchar->type = TYPE_TEXTBOX;
                }
				else if(stricmp(value, "endlevel")==0){	// Used for ending the level when the players reach a certain point
					newchar->type = TYPE_ENDLEVEL;
				}
                else{
                    free(buf);
                    shutdown("Model '%s' has invalid type: '%s'", filename, value);
                }
            }
            else if(stricmp(command, "subtype")==0){
                value = findarg(buf+pos, 1);
                if(stricmp(value, "biker")==0){
                    newchar->subtype = SUBTYPE_BIKER;
                }
                else if(stricmp(value, "arrow")==0){  // 7-1-2005 Arrow type
                    newchar->subtype = SUBTYPE_ARROW;   // 7-1-2005 Arrow type
                }																		// 7-1-2005 Arrow type
                else if(stricmp(value, "notgrab")==0){  // 7-1-2005 notgrab type
                    newchar->subtype = SUBTYPE_NOTGRAB;   // 7-1-2005 notgrab type
                }
                //	ltb 1-18-05  Item Subtype
                else if(stricmp(value, "touch")==0){  // 7-1-2005 notgrab type
                    newchar->subtype = SUBTYPE_TOUCH;   // 7-1-2005 notgrab type
                }
                else if(stricmp(value, "weapon")==0){  // 7-1-2005 notgrab type
                    newchar->subtype = SUBTYPE_WEAPON;   // 7-1-2005 notgrab type
                }
                else if(stricmp(value, "noskip")==0){	// Text animation cannot be skipped if subtype noskip
					newchar->subtype = SUBTYPE_NOSKIP;
				}
                else if(stricmp(value, "flydie")==0){	// Obstacle will fly across the screen when hit if subtype flydie
					newchar->subtype = SUBTYPE_FLYDIE;
				}
				else if(stricmp(value, "both")==0){
					newchar->subtype = SUBTYPE_BOTH;
				}
                //	end new subtype
                else{
                    free(buf);
                    shutdown("Model '%s' has invalid subtype: '%s'", filename, value);
                }
            }
            else if(stricmp(command, "health")==0){
                value = findarg(buf+pos, 1);
                newchar->health = atoi(value);
            }
			else if(stricmp(command, "nolife")==0){	// Feb 25, 2005 - Flag to display enemy life or not
				newchar->nolife = 1;
			}
			else if(stricmp(command, "makeinv")==0){	// Mar 12, 2005 - If a value is supplied, corresponds to amount of time the player spawns invincible
				newchar->makeinv = atoi(findarg(buf+pos, 1)) * GAME_SPEED;
			}
            else if(stricmp(command, "load")==0){
                value = findarg(buf+pos, 1);
                load_cached_model(value);
            }
            else if(stricmp(command, "score")==0){
                newchar->score = atoi(findarg(buf+pos, 1));
                newchar->multiple = atoi(findarg(buf+pos, 2));	// New var multiple for force/scoring
            }
            else if(stricmp(command, "bomb")==0){	// Mar 12, 2005 - Special can now be a smart bomb
				newchar->specpower = atoi(findarg(buf+pos, 1));	// Special force
				newchar->spectype = atoi(findarg(buf+pos, 2));	// Special attack type

				if(newchar->type == TYPE_PLAYER){
					newchar->dofreeze = atoi(findarg(buf+pos, 3));	// Are all animations frozen during special
					newchar->speclen = atoi(findarg(buf+pos, 4)) * GAME_SPEED;
				}
				else{
					newchar->dofreeze = 0;	// Items don't animate
					newchar->speclen = atoi(findarg(buf+pos, 3)) * GAME_SPEED;
				}
			}
			else if(stricmp(command, "noquake")==0){	// Mar 12, 2005 - Flag to determine if entity shakes screen
				newchar->noquake = 1;
			}
			else if(stricmp(command, "hitenemy")==0){	// Flag to determine if an enemy projectile will hit enemies
				newchar->hitenemy = atoi(findarg(buf+pos, 1));
				newchar->ground = atoi(findarg(buf+pos, 2));	// Added to determine if enemies are damaged with mid air projectiles or ground only
			}
            else if(stricmp(command, "secret")==0){
                value = findarg(buf+pos, 1);
                newchar->secret = atoi(value);
            }
            // weapons
            else if(stricmp(command, "weapnum")==0){
                value = findarg(buf+pos, 1);
                newchar->weapnum = atoi(value);
            }
            else if(stricmp(command, "weapons")==0){
                for(weap = 0; weap<(MAX_WEAPONS); weap++){
                    value = findarg(buf+pos, weap+1);
                    if(value){
                        strncpy(newchar->weapon[weap], value, MAX_NAME_LEN);
                        last = weap;
                    }
                    else strncpy(newchar->weapon[weap], newchar->weapon[last], MAX_NAME_LEN);
                }
            }
            // end weapons
            else if(stricmp(command, "rider")==0){   //7-1-2005 new parts start here Playshot/knife/fireball/star
                value = findarg(buf+pos, 1);
                find_model(value);
                strncpy(newchar->rider, value, MAX_NAME_LEN);
            }
            else if(stricmp(command, "playshot")==0){
                value = findarg(buf+pos, 1);
                find_model(value);
                strncpy(newchar->playshot, value, MAX_NAME_LEN);
            }
            else if(stricmp(command, "knife")==0){
                value = findarg(buf+pos, 1);
                find_model(value);
                strncpy(newchar->knife, value, MAX_NAME_LEN);
            }
            else if(stricmp(command, "fireb")==0){
                value = findarg(buf+pos, 1);
                find_model(value);
                strncpy(newchar->fireb, value, MAX_NAME_LEN);
            }
            else if(stricmp(command, "star")==0){
                value = findarg(buf+pos, 1);
                find_model(value);
                strncpy(newchar->star, value, MAX_NAME_LEN);
            }
            else if(stricmp(command, "bomb")==0){
                value = findarg(buf+pos, 1);
                find_model(value);
                strncpy(newchar->bomb, value, MAX_NAME_LEN);
            }
            else if(stricmp(command, "flash")==0){	// Now all characters can have their own flash - even projectiles (useful for blood)
                value = findarg(buf+pos, 1);
                find_model(value);
                strncpy(newchar->flash, value, MAX_NAME_LEN);
            }
            else if(stricmp(command, "bflash")==0){	// Flash that is spawned if an attack is blocked
                value = findarg(buf+pos, 1);
                find_model(value);
                strncpy(newchar->bflash, value, MAX_NAME_LEN);
            }

            // 7-1-2005 ends here

            else if(stricmp(command, "cantgrab")==0){  //27-12-2004 Finds can't grab variable in character.txt
                newchar->cantgrab = 1;				 //27-12-2004 stores variable for character
            }
            else if(stricmp(command, "grabback")==0){
				newchar->grabback = atoi(findarg(buf+pos, 1));
			}
            else if(stricmp(command, "falldie")==0){  //6-2-2005 fall and then play die animation?
                newchar->falldie = 1;         //6-2-2005 fall and then play die animation?
            }
            else if(stricmp(command, "notgrab")==0){  //27-12-2004 Finds can't grab variable in character.txt
                newchar->cantgrab = 1;				 //27-12-2004 stores variable for character
            }
            else if(stricmp(command, "speed")==0){
                value = findarg(buf+pos, 1);
                newchar->speed = atoi(value);
                newchar->speed /= 10;

                if(newchar->speed < 0.5) newchar->speed = 0.5;
                if(newchar->speed > 30) newchar->speed = 30;
            }
            else if(stricmp(command, "height")==0){
				newchar->height = atoi(findarg(buf+pos, 1));
			}
            else if(stricmp(command, "jumpheight")==0){		// 28-12-2004 if string for jump height found
                value = findarg(buf+pos, 1);								// 28-12-2004 find value
                newchar->jumpheight = atoi(value);					// 28-12-2004 and store for character
            }
            else if(stricmp(command, "grabdistance")==0){		// 30-12-2004 if string for grabdistance found
                newchar->grabdistance = atoi(findarg(buf+pos, 1));					// 30-12-2004 and store for character
            }
            // 1-18-05 adjust throwdamage
            else if(stricmp(command, "throwdamage")==0){
                newchar->throwdamage = atoi(findarg(buf+pos, 1));
            }
            //
            else if(stricmp(command, "shadow")==0){
                newchar->shadow = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "aironly")==0){	// Shadows display in air only?
				newchar->aironly = 1;
			}
            else if(stricmp(command, "fmap")==0){	// Map that corresponds with the remap when a character is frozen
				newchar->fmap = atoi(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "setlayer")==0){
				newchar->setlayer = HOLE_Z + atoi(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "toflip")==0){	// Flag to determine if flashes images will be flipped or not
				newchar->toflip = 1;
			}
			else if(stricmp(command, "nodieblink")==0){	// Added to determine if dying animation blinks or not
				newchar->nodieblink = atoi(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "noatflash")==0){	// Flag to determine if an opponents attack spawns their flash or not
				newchar->noatflash = 1;
			}
			else if(stricmp(command, "nomove")==0){	// If set, enemy will be static (speed must be set to 0 or left blank)
				newchar->nomove = atoi(findarg(buf+pos, 1));
				newchar->noflip = atoi(findarg(buf+pos, 2));	// If set, static enemy will not flip directions
			}
			else if(stricmp(command, "nodrop")==0){
				newchar->nodrop = 1;
			}
			else if(stricmp(command, "thold")==0){	// Threshold for enemies/players block
				newchar->thold = atoi(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "running")==0){	// The speed at which the player runs
				newchar->runspeed = atoi(findarg(buf+pos, 1));
                newchar->runspeed /= 10;

				if(newchar->runspeed < 0.5) newchar->runspeed = 0.5;

                newchar->runjumpheight = atoi(findarg(buf+pos, 2));	// The height at which a player jumps when running
                newchar->runjumpdist = atoi(findarg(buf+pos, 3));	// The distance a player jumps when running
                newchar->runupdown = atoi(findarg(buf+pos, 4));
                newchar->runhold = atoi(findarg(buf+pos, 5));
			}
			else if(stricmp(command, "blockodds")==0){	// Odds that an attack will hit an enemy (1 : blockodds)
				newchar->blockodds = atoi(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "throw")==0){
				newchar->throwdist = atoi(findarg(buf+pos, 1));
				newchar->throwheight = atoi(findarg(buf+pos, 2));
			}
            else if(stricmp(command, "diesound")==0){
                newchar->diesound = loadcache_sound(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "icon")==0){
                value = findarg(buf+pos, 1);

                if(newchar->icon > -1){
                    free(buf);
                    shutdown("Error: model '%s' has multiple icons defined", filename);
                }

                newchar->icon = loadsprite(value,0,0);
                newchar->iconpain = loadsprite(value,0,0);   // 20-1-2005 load icon into empty values
                newchar->icondie = loadsprite(value,0,0);   // 20-1-2005 load icon into empty values
                newchar->iconget = loadsprite(value,0,0);  // 20-1-2005 load icon into empty values
            }
            else if(stricmp(command, "iconpain")==0){    // 20-1-2005 New icons here
                value = findarg(buf+pos, 1);
                newchar->iconpain = loadsprite(value,0,0);
            }
            else if(stricmp(command, "icondie")==0){
                value = findarg(buf+pos, 1);
                newchar->icondie = loadsprite(value,0,0);
            }
            else if(stricmp(command, "iconget")==0){
                value = findarg(buf+pos, 1);
                newchar->iconget = loadsprite(value,0,0);
            }                                     // 20-1-2005 New icons finish here
            else if(stricmp(command, "parrow")==0){	// Image that is displayed when player 1 spawns invincible
				value = findarg(buf+pos, 1);
				newchar->parrow[0][0] = loadsprite(value,0,0);
				newchar->parrow[0][1] = atoi(findarg(buf+pos, 2));
				newchar->parrow[0][2] = atoi(findarg(buf+pos, 3));
			}
            else if(stricmp(command, "parrow2")==0){	// Image that is displayed when player 2 spawns invincible
				value = findarg(buf+pos, 1);
				newchar->parrow[1][0] = loadsprite(value,0,0);
				newchar->parrow[1][1] = atoi(findarg(buf+pos, 2));
				newchar->parrow[1][2] = atoi(findarg(buf+pos, 3));
			}

			// Section for custom freespecials starts here

            else if(stricmp(command, "com")==0){
				int i;
				int t;
				for(i = 0, t = 1; i < 4; i++, t++){
					value = findarg(buf+pos, t);
					if(stricmp(value, "u")==0){
						newchar->special[newchar->specials_loaded][i] = FLAG_MOVEUP;
					}
					else if(stricmp(value, "d")==0){
						newchar->special[newchar->specials_loaded][i] = FLAG_MOVEDOWN;
					}
					else if(stricmp(value, "f")==0){
						newchar->special[newchar->specials_loaded][i] = FLAG_FORWARD;
					}
					else if(stricmp(value, "b")==0){
						newchar->special[newchar->specials_loaded][i] = FLAG_BACKWARD;
					}
					else if(stricmp(value, "a")==0){
						newchar->special[newchar->specials_loaded][i] = FLAG_ATTACK;
					}
					else if(stricmp(value, "j")==0){
						newchar->special[newchar->specials_loaded][i] = FLAG_JUMP;
					}
					else if(stricmp(value, "s")==0){
						newchar->special[newchar->specials_loaded][i] = FLAG_SPECIAL;
					}
					else if(stricmp(value, "k")==0){
						newchar->special[newchar->specials_loaded][i] = FLAG_SPECIAL;
					}
					else if(stricmp(value, "freespecial")==0){
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL;
					}
					else if(stricmp(value, "freespecial2")==0){
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL2;
					}
					else if(stricmp(value, "freespecial3")==0){
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL3;
					}
					else if(stricmp(value, "freespecial4")==0){
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL4;
					}
					else if(stricmp(value, "freespecial5")==0){
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL5;
					}
					else if(stricmp(value, "freespecial6")==0){
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL6;
					}
					else if(stricmp(value, "freespecial7")==0){
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL7;
					}
					else if(stricmp(value, "freespecial8")==0){
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL8;
					}
					else{
						free(buf);
					    shutdown("Invalid freespecial command '%s'", value);
                	}
				}
				newchar->specials_loaded++;
			}

            // End section for custom freespecials

            else if(stricmp(command, "remap")==0){
                value = findarg(buf+pos, 1);
                value2 = findarg(buf+pos, 2);
                if(!load_colourmap(newchar, value, value2)){
                    free(buf);
                    shutdown("Failed to create colourmap from images\n\t'%s'\nand\n\t'%s'.", value, value2);
                }
            }
            else if(stricmp(command, "remove")==0){
				newchar->remove = 1;
			}
            else if(stricmp(command, "anim")==0){
                value = findarg(buf+pos, 1);
                // Create new animation
                newanim = alloc_anim();
                if(newanim==NULL){
                    free(buf);
                    shutdown("Not enough memory for animations!");
                }
                if(stricmp(value, "idle")==0){
                    newchar->animation[ANI_IDLE] = newanim;
                }
                else if(stricmp(value, "waiting")==0){
					newchar->animation[ANI_SELECT] = newanim;
				}
                else if(stricmp(value, "walk")==0){
                    newchar->animation[ANI_WALK] = newanim;
                }
                else if(stricmp(value, "run")==0){
                    newchar->animation[ANI_RUN] = newanim;
                }
				else if(stricmp(value, "up")==0){	// Mar 2, 2005 - Used for when going up
					newchar->animation[ANI_UP] = newanim;
				}
				else if(stricmp(value, "down")==0){	// Mar 2, 2005 - Used for when going down
					newchar->animation[ANI_DOWN] = newanim;
				}
                else if(stricmp(value, "jump")==0){
                    newchar->animation[ANI_JUMP] = newanim;
                    newchar->animation[ANI_JUMP]->range[0] = 50;	// Used for enemies that jump on walls
                    newchar->animation[ANI_JUMP]->range[1] = 60;	// Used for enemies that jump on walls
                }
                else if(stricmp(value, "land")==0){
                    newchar->animation[ANI_LAND] = newanim;
                }
                else if(stricmp(value, "pain")==0){
                    newchar->animation[ANI_PAIN] = newanim;
                }
                else if(stricmp(value, "spain")==0){	// If shock attacks don't knock opponent down, play this
                    newchar->animation[ANI_SHOCKPAIN] = newanim;
                }
                else if(stricmp(value, "bpain")==0){	// If burn attacks don't knock opponent down, play this
                    newchar->animation[ANI_BURNPAIN] = newanim;
                }
                else if(stricmp(value, "fall")==0){
                    newchar->animation[ANI_FALL] = newanim;   // If no new animation, load fall animation into both "respawn" & "fall"
                }
                else if(stricmp(value, "shock")==0){	// If shock attacks do knock opponent down, play this
					newchar->animation[ANI_SHOCK] = newanim;
				}
                else if(stricmp(value, "burn")==0){	// If burn attacks do knock opponent down, play this
					newchar->animation[ANI_BURN] = newanim;
				}
                else if(stricmp(value, "rise")==0){
                    newchar->animation[ANI_RISE] = newanim;
                }
                else if(stricmp(value, "riseattack")==0){	// New riseattack that is executed when a player is near by when rising
					newchar->animation[ANI_RISEATTACK] = newanim;
				}
                else if(stricmp(value, "select")==0){     //  7-1-2005 Selection animation.
                    newchar->animation[ANI_PICK] = newanim;//  7-1-2005
                }//  7-1-2005
                else if(stricmp(value, "attack1")==0){
                    newchar->animation[ANI_ATTACK1] = newanim;
                }
                else if(stricmp(value, "attack2")==0){
                    newchar->animation[ANI_ATTACK2] = newanim;
                }
                else if(stricmp(value, "attack3")==0){
                    newchar->animation[ANI_ATTACK3] = newanim;
                }
                else if(stricmp(value, "upper")==0){
                    newchar->animation[ANI_UPPER] = newanim;
                }
                else if(stricmp(value, "dodge")==0){	// Executed by pressing up up or down down (like on SOR3)
					newchar->animation[ANI_DODGE] = newanim;
				}
                else if(stricmp(value, "special")==0){
                    newchar->animation[ANI_SPECIAL] = newanim;
                    newchar->animation[ANI_SPECIAL]->energycost = 6;	// Default energycost moved here so others default to 0
                }
                else if(stricmp(value, "special2")==0){
					newchar->animation[ANI_SPECIAL2] = newanim;	// New special that is executed by pressing forward special
				}
                else if(stricmp(value, "freespecial")==0){
                    newchar->animation[ANI_FREESPECIAL] = newanim;
                    if(!is_set(newchar, ANI_FREESPECIAL)){	// Set defaults if not set
						newchar->special[newchar->specials_loaded][0] = FLAG_FORWARD;
						newchar->special[newchar->specials_loaded][1] = FLAG_FORWARD;
						newchar->special[newchar->specials_loaded][2] = FLAG_ATTACK;
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL;
						newchar->specials_loaded++;
					}
                }
                else if(stricmp(value, "freespecial2")==0){
                    newchar->animation[ANI_FREESPECIAL2] = newanim;
					if(!is_set(newchar, ANI_FREESPECIAL2)){	// Set defaults if not set
						newchar->special[newchar->specials_loaded][0] = FLAG_MOVEDOWN;
						newchar->special[newchar->specials_loaded][1] = FLAG_MOVEDOWN;
						newchar->special[newchar->specials_loaded][2] = FLAG_ATTACK;
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL2;
						newchar->specials_loaded++;
					}
                }
                else if(stricmp(value, "freespecial3")==0){
                    newchar->animation[ANI_FREESPECIAL3] = newanim;
                    if(!is_set(newchar, ANI_FREESPECIAL3)){	// Set defaults if not set
						newchar->special[newchar->specials_loaded][0] = FLAG_MOVEUP;
						newchar->special[newchar->specials_loaded][1] = FLAG_MOVEUP;
						newchar->special[newchar->specials_loaded][2] = FLAG_ATTACK;
						newchar->special[newchar->specials_loaded][3] = ANI_FREESPECIAL3;
						newchar->specials_loaded++;
					}
                }

                // New freespecials here freespecial4 - freespecial8

                else if(stricmp(value, "freespecial4")==0){
                    newchar->animation[ANI_FREESPECIAL4] = newanim;
                }
                else if(stricmp(value, "freespecial5")==0){
                    newchar->animation[ANI_FREESPECIAL5] = newanim;
                }
                else if(stricmp(value, "freespecial6")==0){
                    newchar->animation[ANI_FREESPECIAL6] = newanim;
                }
                else if(stricmp(value, "freespecial7")==0){
                    newchar->animation[ANI_FREESPECIAL7] = newanim;
                }
                else if(stricmp(value, "freespecial8")==0){
                    newchar->animation[ANI_FREESPECIAL8] = newanim;
                }
                else if(stricmp(value, "jumpattack")==0){
                    newchar->animation[ANI_JUMPATTACK] = newanim;
                }
                else if(stricmp(value, "jumpattack2")==0){
                    newchar->animation[ANI_JUMPATTACK2] = newanim;
                }
                else if(stricmp(value, "jumpattack3")==0){
                    newchar->animation[ANI_JUMPATTACK3] = newanim;
                }
                else if(stricmp(value, "jumpforward")==0){
					newchar->animation[ANI_JUMPFORWARD] = newanim;
				}
				else if(stricmp(value, "runjumpattack")==0){
					newchar->animation[ANI_RUNJUMPATTACK] = newanim;
                }
                else if(stricmp(value, "runattack")==0){
                    newchar->animation[ANI_RUNATTACK] = newanim;	// New attack for when a player is running
                }
                else if(stricmp(value, "attackup")==0){
                    newchar->animation[ANI_ATTACKUP] = newanim;	// New attack for when a player presses u u
                }
                else if(stricmp(value, "attackdown")==0){
                    newchar->animation[ANI_ATTACKDOWN] = newanim;	// New attack for when a player presses d d
                }
                else if(stricmp(value, "attackforward")==0){
                    newchar->animation[ANI_ATTACKFORWARD] = newanim;	// New attack for when a player presses f f
                }
                else if(stricmp(value, "attackbackward")==0){
                    newchar->animation[ANI_ATTACKBACKWARD] = newanim;	// New attack for when a player presses b a
                }
                else if(stricmp(value, "attackboth")==0){	// Attack that is executed by holding down j and pressing a
					newchar->animation[ANI_ATTACKBOTH] = newanim;
				}
                else if(stricmp(value, "get")==0){
                    newchar->animation[ANI_GET] = newanim;
                }
                else if(stricmp(value, "grab")==0){
                    newchar->animation[ANI_GRAB] = newanim;
                }
                else if(stricmp(value, "grabbed")==0){	// New grabbed animation for when grabbed
                    newchar->animation[ANI_GRABBED] = newanim;
                }
                else if(stricmp(value, "grabattack")==0){
                    newchar->animation[ANI_GRABATTACK] = newanim;
                }
                else if(stricmp(value, "grabattack2")==0){
                    newchar->animation[ANI_GRABATTACK2] = newanim;
                }
                else if(stricmp(value, "grabforward")==0){	// New grab attack for when pressing forward attack
                    newchar->animation[ANI_GRABFORWARD] = newanim;
                }
                else if(stricmp(value, "grabforward2")==0){	// New grab attack for when pressing forward attack
                    newchar->animation[ANI_GRABFORWARD2] = newanim;
                }
                else if(stricmp(value, "grabup")==0){	// New grab attack for when pressing up attack
                    newchar->animation[ANI_GRABUP] = newanim;
                }
                else if(stricmp(value, "grabup2")==0){	// New grab attack for when pressing up attack
                    newchar->animation[ANI_GRABUP2] = newanim;
                }
                else if(stricmp(value, "grabdown")==0){	// New grab attack for when pressing down attack
                    newchar->animation[ANI_GRABDOWN] = newanim;
                }
                else if(stricmp(value, "grabdown2")==0){	// New grab attack for when pressing down attack
                    newchar->animation[ANI_GRABDOWN2] = newanim;
                }
                else if(stricmp(value, "respawn")==0){      //  If new animation is present, overwrite new_anim with it.
                    newchar->animation[ANI_SPAWN] = newanim;
                }
                else if(stricmp(value, "spawn")==0){      //  7-1-2005  Allow compatibility with lord balls code - If new animation is present, overwrite new_anim with it.
                    newchar->animation[ANI_SPAWN] = newanim;
                }
                else if(stricmp(value, "Death")==0){      //  If new animation is present, overwrite new_anim with it.
                    newchar->animation[ANI_DIE] = newanim;
                }
                else if(stricmp(value, "throw")==0){
                    newchar->animation[ANI_THROW] = newanim;
                }
                else if(stricmp(value, "block")==0){	// Now enemies can block attacks on occasion
                    newchar->animation[ANI_BLOCK] = newanim;
                }
                else{
                    free(buf);
                    shutdown("Invalid animation name '%s'", value);
                }

                // Reset vars
                curframe = 0;
                memset(bbox, 0, 4*sizeof(int));
                memset(attack, 0, 4*sizeof(int));
                memset(offset, 0, 2*sizeof(int));
                memset(platform, 0, 8*sizeof(int));
                attackforce = 0;
                move = 0;
                seta = -1;

                if(!newanim->range[0]) newanim->range[0] = -10;

                newanim->range[1] = newchar->jumpheight*20; // 30-12-2004 default range affected by jump height
                newanim->moveflag = 0;	// Default disabled
                newanim->fastattack = 0;
                newanim->throwframe = -1;
                newanim->pshotframe = -1;  // 7-1-2005 Pshot frame added
                newanim->shootframe = -1;
                newanim->jumpframe = -1;
                newanim->soundtoplay = -1;
                newanim->hitsound = -1;
                strncpy(newanim->hitflash, "flash", MAX_NAME_LEN);	// Custom flash for each animation
                strncpy(newanim->custpshot, newchar->playshot, MAX_NAME_LEN);	// Use for setting custom pshots
                strncpy(newanim->custknife, newchar->knife, MAX_NAME_LEN);	// Used for setting custom knife
                strncpy(newanim->custfireb, newchar->fireb, MAX_NAME_LEN);	// Used for setting custom fireballs
                strncpy(newanim->custstar, newchar->star, MAX_NAME_LEN);	// Used for setting custom stars
                strncpy(newanim->custbomb, newchar->bomb, MAX_NAME_LEN);	// Used for setting custom bombs

                //newanim->energycost = 6; // default 6 - Removed so all freespecials can cost energy but don't unless specified
            }
            else if(stricmp(command, "loop")==0){
                if(newanim == NULL){
                    free(buf);
                    shutdown("Can't set loop: no animation specified!");
                }

                newanim->loop = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "delay")==0){
                delay = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "offset")==0){
                offset[0] = atoi(findarg(buf+pos, 1));
                offset[1] = atoi(findarg(buf+pos, 2));
            }
            // 1-10-05  adjust the energycost of specials
            else if(stricmp(command, "energycost")==0){
                newanim->energycost = atoi(findarg(buf+pos, 1));
                if(newanim->energycost < 0) newanim->energycost = 0;	// No more negative energycosts

            }
            else if(stricmp(command, "throwframe")==0){
                newanim->throwframe = atoi(findarg(buf+pos, 1));
                newanim->throwa = atoi(findarg(buf+pos, 2));

                if(!newanim->throwa) newanim->throwa = 70;
                else if(newanim->throwa == -1) newanim->throwa = 0;
            }
            else if(stricmp(command, "tossframe")==0){	// Frame at which bombs are thrown
                newanim->tossframe = atoi(findarg(buf+pos, 1));
                newanim->throwa = atoi(findarg(buf+pos, 2));

                if(!newanim->throwa) newanim->throwa = 70;
                else if(newanim->throwa == -1) newanim->throwa = 0;
            }
            // 7-1-2005 players need to throw their projectile!
            else if(stricmp(command, "pshotframe")==0){
                newanim->pshotframe = atoi(findarg(buf+pos, 1));
                newanim->throwa = atoi(findarg(buf+pos, 2));

                if(!newanim->throwa) newanim->throwa = 70;
                else if(newanim->throwa == -1) newanim->throwa = 0;
            }
            // 7-1-2005
			else if(stricmp(command, "custpshot")==0){
				value = findarg(buf+pos, 1);
				find_model(value);
				strncpy(newanim->custpshot, value, MAX_NAME_LEN);
			}
			else if(stricmp(command, "custknife")==0){
				value = findarg(buf+pos, 1);
				find_model(value);
				strncpy(newanim->custknife, value, MAX_NAME_LEN);
			}
			else if(stricmp(command, "custfireb")==0){
				value = findarg(buf+pos, 1);
				find_model(value);
				strncpy(newanim->custfireb, value, MAX_NAME_LEN);
			}
			else if(stricmp(command, "custstar")==0){
				value = findarg(buf+pos, 1);
				find_model(value);
				strncpy(newanim->custstar, value, MAX_NAME_LEN);
			}
			else if(stricmp(command, "custbomb")==0){
				value = findarg(buf+pos, 1);
				find_model(value);
				strncpy(newanim->custbomb, value, MAX_NAME_LEN);
			}
            else if(stricmp(command, "shootframe")==0){
                newanim->shootframe = atoi(findarg(buf+pos, 1));
                newanim->throwa = atoi(findarg(buf+pos, 2));

                if(newanim->throwa == -1) newanim->throwa = 0;	// Incase user enters -1
            }
            else if(stricmp(command, "jumpframe")==0){
                newanim->jumpframe = atoi(findarg(buf+pos, 1));
                newanim->moveflag = atoi(findarg(buf+pos, 2));	// Added so movement can be customized for jumpframes
            }
            else if(stricmp(command, "sound")==0){
                newanim->soundframe = curframe;
                newanim->soundtoplay = loadcache_sound(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "hitfx")==0){
				newanim->hitsound = loadcache_sound(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "hitflash")==0){
				value = findarg(buf+pos, 1);
				find_model(value);
                strncpy(newanim->hitflash, value, MAX_NAME_LEN);
			}
			else if(stricmp(command, "fastattack")==0){
				newanim->fastattack = 1;
			}
            else if(stricmp(command, "bbox")==0){
                bbox[0] = atoi(findarg(buf+pos, 1));
                bbox[1] = atoi(findarg(buf+pos, 2));
                bbox[2] = atoi(findarg(buf+pos, 3));
                bbox[3] = atoi(findarg(buf+pos, 4));

                // printf("Bounding box at %i,%i size %i,%i\n", bbox[0], bbox[1], bbox[2], bbox[3]);
            }
            else if(stricmp(command, "platform")==0){
				platform[0] = atoi(findarg(buf+pos, 1));
				platform[1] = atoi(findarg(buf+pos, 2));
				platform[2] = atoi(findarg(buf+pos, 3));
				platform[3] = atoi(findarg(buf+pos, 4));
				platform[4] = atoi(findarg(buf+pos, 5));
				platform[5] = atoi(findarg(buf+pos, 6));
				platform[6] = atoi(findarg(buf+pos, 7));
				platform[7] = atoi(findarg(buf+pos, 8));
			}
            else if(stricmp(command, "attack")==0){
                attack[0] = atoi(findarg(buf+pos, 1));
                attack[1] = atoi(findarg(buf+pos, 2));
                attack[2] = atoi(findarg(buf+pos, 3));
                attack[3] = atoi(findarg(buf+pos, 4));
                attackforce = atoi(findarg(buf+pos, 5));
                attackdrop = atoi(findarg(buf+pos, 6));
                noblock = atoi(findarg(buf+pos, 7));
                noflash = atoi(findarg(buf+pos, 8));
                pauseadd = atoi(findarg(buf+pos, 9));
                attacktype = ATK_NORMAL;
            }
            else if(stricmp(command, "blast")==0){
                attack[0] = atoi(findarg(buf+pos, 1));
                attack[1] = atoi(findarg(buf+pos, 2));
                attack[2] = atoi(findarg(buf+pos, 3));
                attack[3] = atoi(findarg(buf+pos, 4));
                attackforce = atoi(findarg(buf+pos, 5));
                noblock = atoi(findarg(buf+pos, 6));
                noflash = atoi(findarg(buf+pos, 7));
                pauseadd = atoi(findarg(buf+pos, 8));
                attackdrop = 1;
                attacktype = ATK_BLAST;
            }
            else if(stricmp(command, "shock")==0){	// New attack type shocks opponent playing either shock or spain animations
                attack[0] = atoi(findarg(buf+pos, 1));
                attack[1] = atoi(findarg(buf+pos, 2));
                attack[2] = atoi(findarg(buf+pos, 3));
                attack[3] = atoi(findarg(buf+pos, 4));
                attackforce = atoi(findarg(buf+pos, 5));
                attackdrop = atoi(findarg(buf+pos, 6));
                noblock = atoi(findarg(buf+pos, 7));
                noflash = atoi(findarg(buf+pos, 8));
                pauseadd = atoi(findarg(buf+pos, 9));
                attacktype = ATK_SHOCK;
            }
            else if(stricmp(command, "burn")==0){	// New attack type burns opponent playing either burn or bpain animations
                attack[0] = atoi(findarg(buf+pos, 1));
                attack[1] = atoi(findarg(buf+pos, 2));
                attack[2] = atoi(findarg(buf+pos, 3));
                attack[3] = atoi(findarg(buf+pos, 4));
                attackforce = atoi(findarg(buf+pos, 5));
                attackdrop = atoi(findarg(buf+pos, 6));
                noblock = atoi(findarg(buf+pos, 7));
                noflash = atoi(findarg(buf+pos, 8));
                pauseadd = atoi(findarg(buf+pos, 9));
                attacktype = ATK_BURN;
            }
            else if(stricmp(command, "freeze")==0){	// New attack type freeze prevents opponnent from moving for specified time or until hit
                attack[0] = atoi(findarg(buf+pos, 1));
                attack[1] = atoi(findarg(buf+pos, 2));
                attack[2] = atoi(findarg(buf+pos, 3));
                attack[3] = atoi(findarg(buf+pos, 4));
                attackforce = atoi(findarg(buf+pos, 5));
                attacklength = atoi(findarg(buf+pos, 6)) * GAME_SPEED;
                noblock = atoi(findarg(buf+pos, 7));
				noflash = atoi(findarg(buf+pos, 8));
				pauseadd = atoi(findarg(buf+pos, 9));
                attackdrop = 0;
                attacktype = ATK_FREEZE;
            }
            else if(stricmp(command, "steal")==0){	// New attack type steals life from opponent and adds it to theirs
                attack[0] = atoi(findarg(buf+pos, 1));
                attack[1] = atoi(findarg(buf+pos, 2));
                attack[2] = atoi(findarg(buf+pos, 3));
                attack[3] = atoi(findarg(buf+pos, 4));
                attackforce = atoi(findarg(buf+pos, 5));
                attackdrop = atoi(findarg(buf+pos, 6));
                noblock = atoi(findarg(buf+pos, 7));
                noflash = atoi(findarg(buf+pos, 8));
                pauseadd = atoi(findarg(buf+pos, 9));
                attacktype = ATK_STEAL;
            }
            else if(stricmp(command, "move")==0){
                move = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "seta")==0){
				seta = atoi(findarg(buf+pos, 1));
			}
            else if(stricmp(command, "range")==0){
                if(newanim==NULL){
                    free(buf);
                    shutdown("Cannot set range: no animation!");
                }
                newanim->range[0] = atoi(findarg(buf+pos, 1));
                newanim->range[1] = atoi(findarg(buf+pos, 2));
            }
            else if(stricmp(command, "frame")==0){
                if(newanim==NULL){
                    free(buf);
                    shutdown("Cannot add frame: animation not specified!");
                }
                value = findarg(buf+pos, 1);
                // printf("Load sprite '%s'...\n", value);
                index = loadsprite(value, offset[0], offset[1]);

                // Adjust coords: add offsets and change size to coords
                bbox_con[0] = bbox[0] - offset[0];
                bbox_con[1] = bbox[1] - offset[1];
                bbox_con[2] = bbox[2] + bbox_con[0];
                bbox_con[3] = bbox[3] + bbox_con[1];
                attack_con[0] = attack[0] - offset[0];
                attack_con[1] = attack[1] - offset[1];
                attack_con[2] = attack[2] + attack_con[0];
                attack_con[3] = attack[3] + attack_con[1];
                platform_con[0] = platform[0] - offset[0];
                platform_con[1] = platform[1] - offset[0];
                platform_con[2] = platform[2] - offset[0];
                platform_con[3] = platform[3] - offset[0];
                platform_con[4] = platform[4];
                platform_con[5] = platform[5];

                curframe = addframe(newanim, index, delay, bbox_con, attack_con, attackforce, attackdrop, attacktype, move, seta, attacklength, noblock, noflash, pauseadd, platform_con);	// Added attacklength for freeze attacks and noblock for unblockable attacks
            }
            else{
                free(buf);
                shutdown("Command '%s' not understood in file '%s'!", command, filename);
            }
        }

        // Go to next line
        while(buf[pos] && buf[pos]!='\n' && buf[pos]!='\r') ++pos;
        while(buf[pos]=='\n' || buf[pos]=='\r') ++pos;
    }
    free(buf);

    // If all models are loaded, the cache becomes obsolete
    // if(models_loaded >= models_cached) free_modelcache();
    remove_from_cache(name);
}

int is_set(s_model * model, int m){	// New function to determine if a freespecial has been set
	int i;

	for(i = 0; i < model->specials_loaded; i++){
		if(model->special[i][3] == m){
			return 1;
		}
	}

	return 0;
}


// Load / cache all models
int load_models(){
    char * filename = "data/models.txt";

    int handle;
    char *buf;
    unsigned int size;
    int pos;

    char * command;
    char value1[128];
    char value2[128];



    // Read file

    if((handle=openpackfile(filename,packfile)) < 0) shutdown("Error loading model list from %s", filename);
    size = seekpackfile(handle,0,SEEK_END);
    seekpackfile(handle,0,SEEK_SET);

    buf = (char*)malloc(size+1);
    if(buf==NULL){
        closepackfile(handle);
        shutdown("Not enough memory to allocate %i-byte buffer for file %s", size, filename);
    }
    if(readpackfile(handle, buf, size) != size){
        free(buf);
        closepackfile(handle);
        shutdown("Read error accessing file %s", filename);
    }
    buf[size] = 0;		// Terminate string (important!)
    closepackfile(handle);



    // Now interpret the contents of buf line by line
    pos = 0;
    while(pos<size){
        command = findarg(buf+pos, 0);
        if(command[0]){
            if(stricmp(command, "load")==0){
                // Add path to cache list
                strncpy(value1, findarg(buf+pos, 1), 127);
                strncpy(value2, findarg(buf+pos, 2), 127);
                cache_model(value1, value2);

                // Now load the cached model
                load_cached_model(value1);
            }
            else if(stricmp(command, "colourselect")==0){   // 6-2-2005 if string for colourselect found
                colourselect =  atoi(findarg(buf+pos, 1));          //  6-2-2005
            }
            else if(stricmp(command, "autoland")==0){	// New flag to determine if a player auto lands when thrown by another player (2 completely disables the ability to land)
				autoland = atoi(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "ajspecial")==0){	// Flag to determine if a + j executes special
				ajspecial = atoi(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "nocost")==0){	// Nocost set in models.txt
				nocost = atoi(findarg(buf+pos, 1));
			}
            else if(stricmp(command, "nocheats")==0){   // 6-2-2005 if string for colourselect found
                forcecheatsoff =  atoi(findarg(buf+pos, 1));          //  6-2-2005
            }
            else if(stricmp(command, "nodropen")==0){
				nodropen = 1;
			}
            else if(stricmp(command, "know")==0){
                // Just add path to cache list
                strncpy(value1, findarg(buf+pos, 1), 127);
                strncpy(value2, findarg(buf+pos, 2), 127);
                cache_model(value1, value2);
            }
            else{
                free(buf);
                shutdown("Command '%s' not understood in file '%s'!", command, filename);
            }
        }

        // Go to next line
        while(buf[pos] && buf[pos]!='\n' && buf[pos]!='\r') ++pos;
        while(buf[pos]=='\n' || buf[pos]=='\r') ++pos;
    }


    free(buf);
    return 1;
}




void unload_levelorder(){
    int i, j;
    for(j=0; j<MAX_DIFFICULTIES; j++){
        for(i=0; i<MAX_LEVELS; i++){
            if(levelorder[j][i]) free(levelorder[j][i]);
            levelorder[j][i] = NULL;
        }
        num_levels[j] = 0;
        strcpy(set_names[j], "");
    }
    num_difficulties = 0;
}



// Add a level to the level order
void add_level(char *filename, int diff){
	int Zs[3] = {0,0,0};
	if(z_coords[0] > 0){
		Zs[0] = z_coords[0];
	}
	else Zs[0] = 160;

	if(z_coords[1] > 0){
		Zs[1] = z_coords[1];
	}
	else Zs[1] = 232;

	if(z_coords[2] > 0){
		Zs[2] = z_coords[2];
	}
	else Zs[2] = 160;

	if(diff > MAX_DIFFICULTIES) return;
	if(num_levels[diff] >= MAX_LEVELS) shutdown("Too many entries in level order (max. %i)!", MAX_LEVELS);

    levelorder[diff][num_levels[diff]] = (s_level_entry*)malloc(sizeof(s_level_entry));

    if(levelorder[diff][num_levels[diff]] == NULL) shutdown("Out of memory loading level order!");

    memset(levelorder[diff][num_levels[diff]], 0, sizeof(s_level_entry));
    strncpy(levelorder[diff][num_levels[diff]]->filename, filename, 127);
    levelorder[diff][num_levels[diff]]->z_coords[0] = Zs[0];
	levelorder[diff][num_levels[diff]]->z_coords[1] = Zs[1];
	levelorder[diff][num_levels[diff]]->z_coords[2] = Zs[2];
    num_levels[diff]++;
}



// Add a scene to the level order
void add_scene(char *filename, int diff){
    if(diff > MAX_DIFFICULTIES) return;
    if(num_levels[diff] >= MAX_LEVELS) shutdown("Too many entries in level order (max. %i)!", MAX_LEVELS);

    levelorder[diff][num_levels[diff]] = (s_level_entry*)malloc(sizeof(s_level_entry));

    if(levelorder[diff][num_levels[diff]] == NULL) shutdown("Out of memory loading level order!");

    memset(levelorder[diff][num_levels[diff]], 0, sizeof(s_level_entry));
    strncpy(levelorder[diff][num_levels[diff]]->filename, filename, 127);
    levelorder[diff][num_levels[diff]]->is_scene = 1;
    num_levels[diff]++;
}



// Load list of levels
void load_levelorder(){
    char * filename = "data/levels.txt";

    int handle;
    char *buf;
    unsigned int size;
    int pos;
    int current_set;

    char * command;
    char value1[128];
    char value2[128];

    unload_levelorder();


    // Read file

    if((handle=openpackfile(filename,packfile)) < 0) shutdown("Error loading level list from %s", filename);
    size = seekpackfile(handle,0,SEEK_END);
    seekpackfile(handle,0,SEEK_SET);

    buf = (char*)malloc(size+1);

    if(buf==NULL){
        closepackfile(handle);
        shutdown("Not enough memory to allocate %i-byte buffer for file %s", size, filename);
    }

    if(readpackfile(handle, buf, size) != size){
        free(buf);
        closepackfile(handle);
        shutdown("Read error accessing file %s", filename);
    }

    buf[size] = 0;		// Terminate string (important!)
    closepackfile(handle);



    // Now interpret the contents of buf line by line
    pos = 0;
    current_set = -1;

	// Custom lifebar/timebox/icon positioning and size

	plife[0][0] = 20;
	plife[1][0] = 20 + P2_STATS_DIST;
	plife[1][1] = plife[0][1] = 10;
	elife[0][0] = 20;
	elife[1][0] = 20 + P2_STATS_DIST;
	elife[1][1] = elife[0][1] = 27;

	picon[0][0] = 2;
	picon[1][0] = 2 + P2_STATS_DIST;
	picon[0][1] = picon[1][1] = 2;
	eicon[0][0] = 2;
	eicon[1][0] = 2 + P2_STATS_DIST;
	eicon[0][1] = eicon[1][1] = 19;

	lbarsize[0] = 100;
	lbarsize[1] = 5;
	lbarsize[2] = 0;
	timeloc[0] = 149;
	timeloc[1] = 4;
	timeloc[2] = 21;
	timeloc[3] = 20;
	timeloc[4] = 0;

    while(pos<size){
        command = findarg(buf+pos, 0);

        if(command[0]){
            if(stricmp(command, "set")==0){
				if(num_difficulties>=MAX_DIFFICULTIES){
					free(buf);
					shutdown("Too many sets of levels (max %u)!", MAX_DIFFICULTIES);
				}
				++num_difficulties;
				++current_set;
				strncpy(set_names[current_set], findarg(buf+pos, 1), MAX_NAME_LEN);
				ifcomplete[current_set] = 0;
            }
            else if(stricmp(command, "ifcomplete")==0){
                if(current_set<0){
                    free(buf);
                    shutdown("Error in level order: a set must be specified.");
                }
                ifcomplete[current_set] = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "file")==0){
                if(current_set<0){
                    free(buf);
                    shutdown("Error in level order: a set must be specified.");
                }
                strncpy(value1, findarg(buf+pos, 1), 127);
                add_level(value1, current_set);
            }
            else if(stricmp(command, "scene")==0){
                if(current_set<0){
                    free(buf);
                    shutdown("Error in level order: a set must be specified.");
                }
                strncpy(value1, findarg(buf+pos, 1), 127);
                add_scene(value1, current_set);
            }
            else if(stricmp(command, "next")==0){
                if(current_set<0){
                    free(buf);
                    shutdown("Error in level order: a set must be specified.");
                }
                // Set 'gonext' flag of last loaded level
                if(num_levels[current_set]<1){
                    free(buf);
                    shutdown("Error in level order (next before file)!");
                }
                levelorder[current_set][num_levels[current_set]-1]->gonext = 1;
            }
            // 7-1-2005  credits/lives/singleplayer start here
            // used to read the new # of lives/credits from the levels.txt
            else if(stricmp(command, "lives")==0){
                if(current_set<0){
                    free(buf);
                    shutdown("Error in level order: a set must be specified.");
                }
                difflives[current_set] = atoi(findarg(buf+pos, 1));
            }
            //	2-10-05  adjust the walkable coordinates
			else if(stricmp(command, "z")==0){
				if(current_set<0){
					free(buf);
					shutdown("Error in level order: a set must be specified.");
				}

				z_coords[0] = atoi(findarg(buf+pos, 1));
				z_coords[1] = atoi(findarg(buf+pos, 2));
				z_coords[2] = atoi(findarg(buf+pos, 3));
			}
            //

    		else if(stricmp(command, "p1life")==0){
				if(atoi(findarg(buf+pos, 1))) plife[0][0] = atoi(findarg(buf+pos, 1));
				if(atoi(findarg(buf+pos, 2))) plife[0][1] = atoi(findarg(buf+pos, 2));
			}
    		else if(stricmp(command, "p2life")==0){
				if(atoi(findarg(buf+pos, 1))) plife[1][0] = atoi(findarg(buf+pos, 1));
				if(atoi(findarg(buf+pos, 2))) plife[1][1] = atoi(findarg(buf+pos, 2));
			}
    		else if(stricmp(command, "e1life")==0){
				if(atoi(findarg(buf+pos, 1))) elife[0][0] = atoi(findarg(buf+pos, 1));
				if(atoi(findarg(buf+pos, 2))) elife[0][1] = atoi(findarg(buf+pos, 2));
			}
    		else if(stricmp(command, "e2life")==0){
				if(atoi(findarg(buf+pos, 1))) elife[1][0] = atoi(findarg(buf+pos, 1));
				if(atoi(findarg(buf+pos, 2))) elife[1][1] = atoi(findarg(buf+pos, 2));
			}
			else if(stricmp(command, "p1icon")==0){
				if(atoi(findarg(buf+pos, 1))) picon[0][0] = atoi(findarg(buf+pos, 1));
				if(atoi(findarg(buf+pos, 2))) picon[0][1] = atoi(findarg(buf+pos, 2));
			}
			else if(stricmp(command, "p2icon")==0){
				if(atoi(findarg(buf+pos, 1))) picon[1][0] = atoi(findarg(buf+pos, 1));
				if(atoi(findarg(buf+pos, 2))) picon[1][1] = atoi(findarg(buf+pos, 2));
			}
			else if(stricmp(command, "e1icon")==0){
				if(atoi(findarg(buf+pos, 1))) eicon[0][0] = atoi(findarg(buf+pos, 1));
				if(atoi(findarg(buf+pos, 2))) eicon[0][1] = atoi(findarg(buf+pos, 2));
			}
			else if(stricmp(command, "e2icon")==0){
				if(atoi(findarg(buf+pos, 1))) eicon[1][0] = atoi(findarg(buf+pos, 1));
				if(atoi(findarg(buf+pos, 2))) eicon[1][1] = atoi(findarg(buf+pos, 2));
			}
    		else if(stricmp(command, "lbarsize")==0){
				if(atoi(findarg(buf+pos, 1))) lbarsize[0] = atoi(findarg(buf+pos, 1));
				if(atoi(findarg(buf+pos, 2))) lbarsize[1] = atoi(findarg(buf+pos, 2));
				if(atoi(findarg(buf+pos, 3))) lbarsize[2] = atoi(findarg(buf+pos, 3));
			}
			else if(stricmp(command, "timeloc")==0){
				if(atoi(findarg(buf+pos, 1))) timeloc[0] = atoi(findarg(buf+pos, 1));
				if(atoi(findarg(buf+pos, 2))) timeloc[1] = atoi(findarg(buf+pos, 2));
				if(atoi(findarg(buf+pos, 3))) timeloc[2] = atoi(findarg(buf+pos, 3));
				if(atoi(findarg(buf+pos, 4))) timeloc[3] = atoi(findarg(buf+pos, 4));
				if(atoi(findarg(buf+pos, 5))) timeloc[4] = atoi(findarg(buf+pos, 5));
			}
            else if(stricmp(command, "musicoverlap")==0){
                if(current_set<0){
                    free(buf);
                    shutdown("Error in level order: a set must be specified.");
                }
                diffoverlap[current_set] = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "noslowfx")==0){
				noslowfx = 1;
			}
			else if(stricmp(command, "hiscorebg")==0){	// If set to 1, will look for a background image
				hiscorebg = 1;
			}
			else if(stricmp(command, "unlockbg")==0){
				unlockbg = 1;
			}
			else if(stricmp(command, "noshare")==0){
				noshare = 1;
			}

            //8-2-2005 custom fade
            else if(stricmp(command, "custfade")==0){
                if(current_set<0){
                    free(buf);
                    shutdown("Error in level order: a set must be specified.");
                }
                custfade[current_set] = atoi(findarg(buf+pos, 1));
            }
            //8-2-2005 custom fade

            else if(stricmp(command, "credits")==0){
                if(current_set<0){
                    free(buf);
                    shutdown("Error in level order: a set must be specified.");
                }
                diffcreds[current_set] = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "nosame")==0){
                if(current_set<0){
                    free(buf);
                    shutdown("Error in level order: a set must be specified.");
                }
                same[current_set] = atoi(findarg(buf+pos, 1));
            }
            //
            else{
                free(buf);
                shutdown("Command '%s' not understood in level order!", command);
            }
        }

        // Go to next line
        while(buf[pos] && buf[pos]!='\n' && buf[pos]!='\r') ++pos;
        while(buf[pos]=='\n' || buf[pos]=='\r') ++pos;
    }
    free(buf);

    if(current_set<0) shutdown("No levels were loaded!");
}





void unload_level(){
    unload_background();
    unload_texture();
    freepanels();

    if(level) free(level);

    level = NULL;
    free(holesprite);

    levelpos = 0;
    advancex = 0;
    advancey = 0;
    advancetime = 0;
    quake = 0;
    quaketime = 0;
    level_waiting = 0;
    current_spawn = 0;
    groupmin = 100;
    groupmax = 100;
    level_completed = 0;
    tospeedup = 0;	// Reset so it sets to normal speed for the next level
    reached[0] = reached[1] = 0;	// TYPE_ENDLEVEL values reset after level completed
    player[0].iconmode = player[1].iconmode = 0;	// Incase player clears the level while being hit
    showtimeover = 0;
    pause = 0;
    endgame = 0;
    go_time = 0;
    neon_time = 0;
    time = 0;
    gfx_y_offset = 0;	// Added so select screen graphics display correctly
}



void load_level(char *filename){
    int handle;
    char * buf;
    int size;
    int pos;
    char * command;
    char * value;
    s_spawn_entry next;
    int i, j;


    unload_level();


    memset(&next, 0, sizeof(s_spawn_entry));
    // timeleft = 100 * COUNTER_SPEED; - This line moved down a bit to set custom level times


    level = (s_level*)malloc(sizeof(s_level));

    if(level==NULL) shutdown("FATAL: Out of memory!");
    memset(level, 0, sizeof(s_level));

    if((handle=openpackfile(filename,packfile)) < 0) shutdown("Unable to load level file '%s'", filename);
    size = seekpackfile(handle,0,SEEK_END);
    seekpackfile(handle,0,SEEK_SET);

    buf = (char*)malloc(size+1);

    if(buf==NULL){
        closepackfile(handle);
        shutdown("FATAL: out of memory!");
    }

    if(readpackfile(handle, buf, size) != size){
        free(buf);
        closepackfile(handle);
        shutdown("FATAL: read error accessing file '%s'", filename);
    }

    buf[size] = 0;		// Terminate string (important!)
    closepackfile(handle);

	level->settime = 100;	// Feb 25, 2005 - Default time limit set to 100
	level->nospecial = 0;	// Default set to specials can be used during bonus levels
	level->nohurt = 0;	// Default set to players can hurt each other during bonus levels
	level->spawn[0][2] = level->spawn[1][2] = 300;	// Set the default spawn a to 300

    // Now interpret the contents of buf line by line
    pos = 0;
    while(pos<size){
        command = findarg(buf+pos, 0);
        if(command[0]){
            if(stricmp(command, "background")==0){
                load_background(findarg(buf+pos, 1), 1);
                standard_palette();
            }
            else if(stricmp(command, "water")==0){
                load_texture(findarg(buf+pos, 1));
                i = atoi(findarg(buf+pos, 2));
                if(i<2) i = 2;
                texture_set_wave(i);
            }
            else if(stricmp(command, "direction")==0){
                value = findarg(buf+pos, 1);
                if(stricmp(value, "up")==0) level->scrolldir = SCROLL_UP;
                else if(stricmp(value, "down")==0) level->scrolldir = SCROLL_DOWN;
                else if(stricmp(value, "left")==0) level->scrolldir = SCROLL_LEFT;
                else if(stricmp(value, "both")==0) level->scrolldir = SCROLL_BOTH;
                else level->scrolldir = SCROLL_RIGHT;
            }
            else if(stricmp(command, "rock")==0){
                level->rocking = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "bgspeed")==0){
                level->bgspeed = atoi(findarg(buf+pos, 1));
                level->bgdir = atoi(findarg(buf+pos, 2));
            }
            else if(stricmp(command, "mirror")==0){
                level->mirror = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "music")==0){
                music(findarg(buf+pos, 1), 1);
            }
            else if(stricmp(command, "bossmusic")==0){
                strncpy(level->bossmusic, findarg(buf+pos, 1), 255);
            }
			else if(stricmp(command, "settime")==0){	// If settime is found, overwrite the default 100 for time limit
				if(atoi(findarg(buf+pos, 1)) > 100 || atoi(findarg(buf+pos, 1)) < 0) level->settime = 100;
				else level->settime = atoi(findarg(buf+pos, 1)); // Feb 25, 2005 - Time limit loaded from individual .txt file
			}
			else if(stricmp(command, "notime")==0){	// Flag to if the time should be displayed 1 = no, else yes
				level->notime = atoi(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "noslow")==0){	// If set, level will not slow down when bosses are defeated
				level->noslow = atoi(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "type")==0){
				level->type = atoi(findarg(buf+pos, 1));	// Level type - 1 = bonus, else regular

				if(atoi(findarg(buf+pos, 2))) level->nospecial = atoi(findarg(buf+pos, 2));	// Can use specials during bonus levels (default 0 - yes)
				if(atoi(findarg(buf+pos, 3))) level->nohurt = atoi(findarg(buf+pos, 3));	// Can hurt other players during bonus levels (default 0 - yes)
			}
			else if(stricmp(command, "spawn1")==0){
				level->spawn[0][0] = atoi(findarg(buf+pos, 1));
				level->spawn[0][1] = atoi(findarg(buf+pos, 2));
				level->spawn[0][2] = atoi(findarg(buf+pos, 3));

				if(level->spawn[0][1] > 232 || level->spawn[0][1] < 0) level->spawn[0][1] = 232;
				if(level->spawn[0][2] > 300 || level->spawn[0][2] < 0) level->spawn[0][2] = 300;
			}
			else if(stricmp(command, "spawn2")==0){
				level->spawn[1][0] = atoi(findarg(buf+pos, 1));
				level->spawn[1][1] = atoi(findarg(buf+pos, 2));
				level->spawn[1][2] = atoi(findarg(buf+pos, 3));

				if(level->spawn[1][1] > 232 || level->spawn[0][1] < 0) level->spawn[0][1] = 232;
				if(level->spawn[1][2] > 300 || level->spawn[0][2] < 0) level->spawn[0][2] = 300;
			}
            else if(stricmp(command, "frontpanel")==0){
                value = findarg(buf+pos, 1);

                if(!loadfrontpanel(value)){
                    free(buf);
                    shutdown("Unable to load '%s'!", value);
                }
            }
            else if(stricmp(command, "panel")==0){
                if(!loadpanel(findarg(buf+pos, 1), findarg(buf+pos, 2), findarg(buf+pos, 3))){
                    free(buf);
                    shutdown("Panel load error in '%s'!", filename);
                }
            }
            else if(stricmp(command, "order")==0){
                // Append to order
                if(panels_loaded<1){
                    free(buf);
                    shutdown("You must load the panels before entering the level layout!");
                }
                value = findarg(buf+pos, 1);
                i = 0;

                while(value[i] && level->numpanels < LEVEL_MAX_PANELS){
                    j = value[i];

                    if(j>='A' && j<='Z') j-='A';
                    else if(j>='a' && j<='z') j-='a';
                    else{
                        free(buf);
                        shutdown("Illegal character in panel order: '%c' (%02Xh)", j, j);
                    }

                    if(j >= panels_loaded){
                        free(buf);
                        shutdown("Illegal panel index: %i (only %i panels loaded)", j, panels_loaded);
                    }
                    level->order[level->numpanels] = j;
                    level->numpanels++;
                    i++;
                }
            }
            else if(stricmp(command, "hole")==0){
                value = findarg(buf+pos, 1);	// ltb	1-18-05  adjustable hole sprites
                if(openpackfile(value, packfile) >=0) holesprite = loadsprite(value,0,0);// ltb 1-18-05  load new hole sprite
                else holesprite = loadsprite("data/sprites/hole",0,0);	// ltb 1-18-05  no new sprite load the default
                if(level->numholes >= LEVEL_MAX_HOLES) shutdown("Too many holes in level (max %i)!", LEVEL_MAX_HOLES);
                level->holes[level->numholes][0] = atoi(findarg(buf+pos, 1));
                level->holes[level->numholes][1] = atoi(findarg(buf+pos,2));
                level->holes[level->numholes][2] = atoi(findarg(buf+pos,3));
                level->holes[level->numholes][3] = atoi(findarg(buf+pos,4));
                level->holes[level->numholes][4] = atoi(findarg(buf+pos,5));
                level->holes[level->numholes][5] = atoi(findarg(buf+pos,6));
                level->holes[level->numholes][6] = atoi(findarg(buf+pos,7));
                if(!level->holes[level->numholes][1]) level->holes[level->numholes][1] = 240;
                if(!level->holes[level->numholes][2]) level->holes[level->numholes][2] = 12;
                if(!level->holes[level->numholes][3]) level->holes[level->numholes][3] = 1;
                if(!level->holes[level->numholes][4]) level->holes[level->numholes][4] = 200;
                if(!level->holes[level->numholes][5]) level->holes[level->numholes][5] = 287;
                if(!level->holes[level->numholes][6]) level->holes[level->numholes][6] = 45;
                level->numholes++;
            }
            else if(stricmp(command, "wall")==0){
                level->walls[level->numwalls][0] = atoi(findarg(buf+pos, 1));
                level->walls[level->numwalls][1] = atoi(findarg(buf+pos,2));
                level->walls[level->numwalls][2] = atoi(findarg(buf+pos,3));
                level->walls[level->numwalls][3] = atoi(findarg(buf+pos,4));
                level->walls[level->numwalls][4] = atoi(findarg(buf+pos,5));
                level->walls[level->numwalls][5] = atoi(findarg(buf+pos,6));
                level->walls[level->numwalls][6] = atoi(findarg(buf+pos,7));
                level->walls[level->numwalls][7] = atoi(findarg(buf+pos,8));
                level->numwalls++;
			}
            else if(stricmp(command, "blocked")==0){
                level->exit_blocked = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "endhole")==0){
                level->exit_hole = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "wait")==0){
                // Clear spawn thing, set wait state instead
                memset(&next,0,sizeof(s_spawn_entry));
                next.wait = 1;
            }
            else if(stricmp(command, "group")==0){
                // Clear spawn thing, set group instead
                memset(&next,0,sizeof(s_spawn_entry));
                next.groupmin = atoi(findarg(buf+pos, 1));
                next.groupmax = atoi(findarg(buf+pos, 2));
                if(next.groupmax < 1) next.groupmax = 1;
                if(next.groupmin < 1) next.groupmin = 100;
            }
            else if(stricmp(command, "spawn")==0){
                // Back to defaults
                memset(&next,0,sizeof(s_spawn_entry));
                // Name of entry to be spawned
                strncpy(next.name, findarg(buf+pos, 1), MAX_NAME_LEN);
                // Load model (if not loaded already)
                load_cached_model(next.name);
            }
            else if(stricmp(command, "2pspawn")==0){
                // Entity only for 2p game
                next.spawn2p = 1;
            }
            else if(stricmp(command, "boss")==0){
                next.boss = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "flip")==0){
                next.flip = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "health")==0){
                next.health = atoi(findarg(buf+pos, 1));
            }
            else if(stricmp(command, "2phealth")==0){	// Health the spawned entity will have if 2 people are playing
				next.health2p = atoi(findarg(buf+pos, 1));
			}
            else if(stricmp(command, "score")==0){	// So score can be overriden in the levels .txt file
                next.score = atoi(findarg(buf+pos, 1));
				if(next.score < 0) next.score = 0;	// So negative values cannot be added
                next.multiple = atoi(findarg(buf+pos, 2));
                if(next.multiple < 0) next.multiple = 0;	// So negative values cannot be added
            }
            else if(stricmp(command, "nolife")==0){	// Flag to determine if entity life is shown when hit
				next.nolife = atoi(findarg(buf+pos, 1));
			}
            else if(stricmp(command, "alias")==0){
                // Alias (name displayed) of entry to be spawned
                strncpy(next.alias, findarg(buf+pos, 1), MAX_NAME_LEN);
            }
            else if(stricmp(command, "map")==0){
                // Colourmap for new entry
                next.colourmap = atoi(findarg(buf+pos, 1));
            }
			else if(stricmp(command, "dying")==0){	// Used to store which remake corresponds with the dying flash
				next.dying = atoi(findarg(buf+pos, 1));
				next.per1 = atoi(findarg(buf+pos, 2));
				next.per2 = atoi(findarg(buf+pos, 3));
			}
            else if(stricmp(command, "item")==0){
                // Item to be contained by new entry
                next.if2p = 0;
                strncpy(next.item, findarg(buf+pos, 1), MAX_NAME_LEN);
                // Load model (if not loaded already)
                load_cached_model(next.item);
            }
            else if(stricmp(command, "2pitem")==0){
                // Item only for 2p game
                next.if2p = 1;
                strncpy(next.item, findarg(buf+pos, 1), MAX_NAME_LEN);
            }
            else if(stricmp(command, "itemmap")==0){
				next.itemmap = atoi(findarg(buf+pos, 1));
			}
            else if(stricmp(command, "itemhealth")==0){
				next.itemhealth = atoi(findarg(buf+pos, 1));
			}
			else if(stricmp(command, "itemalias")==0){
				strncpy(next.itemalias, findarg(buf+pos, 1), MAX_NAME_LEN);
			}
            else if(stricmp(command, "coords")==0){
                next.x = atoi(findarg(buf+pos, 1));
                next.z = atoi(findarg(buf+pos, 2));
                next.a = atoi(findarg(buf+pos, 3));
            }
            else if(stricmp(command, "at")==0){
                // Place entry on queue
                next.at = atoi(findarg(buf+pos, 1));

                if(level->numspawns >= LEVEL_MAX_SPAWNS) shutdown("Level error: too many entries (max. %i)", LEVEL_MAX_SPAWNS);
                memcpy(&level->spawnpoints[level->numspawns], &next, sizeof(s_spawn_entry));
                level->numspawns++;

                // And clear...
                memset(&next,0,sizeof(s_spawn_entry));
            }
            else{
                free(buf);
                shutdown("Command '%s' not understood!", command);
            }
        }

        // Go to next line
        while(buf[pos] && buf[pos]!='\n' && buf[pos]!='\r') ++pos;
        while(buf[pos]=='\n' || buf[pos]=='\r') ++pos;
    }
    free(buf);

	timeleft = level->settime * COUNTER_SPEED;	// Feb 24, 2005 - This line moved here to set custom time
    level->width = level->numpanels * panel_width;

    if(level->scrolldir==SCROLL_LEFT) advancex = level->width-320;

    if(level->numpanels < 1) shutdown("Level error: level has no panels");
}






// ---------------------------------- Status --------------------------------


//    new lifebar colors!---------------------------------------------------------------------
void drawlifebar(int x, int y, int h, int maxh){

    if (colorbars==1) {

        int overflow = 0;
        int overflo2 = 0;
        int overflo3 = 0;
        int overflo4 = 0;
        int overflo5 = 0;
        int overflo6 = 0;
        int overflo7 = 0;

        if(maxh<=0) return;
        if(h<0) h = 0;
        if(maxh < h) maxh = h;
        if(h > lbarsize[0]){
            overflow = h % lbarsize[0];
            if(h > 200) overflo2 = (h - 100) % lbarsize[0];
            if(h > 300) overflo3 = (h - 200) % lbarsize[0];
            if(h > 400) overflo4 = (h - 300) % lbarsize[0];
            if(h > 500) overflo5 = (h - 400) % lbarsize[0];
            if(h > 600) overflo6 = (h - 500) % lbarsize[0];
            if(h > 700) overflo7 = (h - 600) % lbarsize[0];
            h = lbarsize[0];
        }
        if(maxh > lbarsize[0]) maxh = lbarsize[0];

        if(!lbarsize[2]){
        	line(x+1,      y+lbarsize[1] + 2, x+maxh+2, y+lbarsize[1] + 2, color_black, vscreen);
        	line(x+maxh+2, y+1, x+maxh+2, y+lbarsize[1] + 2, color_black, vscreen);

        	line(x,        y,   x+maxh+1, y,   color_white, vscreen);
        	line(x,        y,   x,        y+lbarsize[1] + 1, color_white, vscreen);
        	line(x,        y+lbarsize[1] + 1, x+maxh+1, y+lbarsize[1] + 1, color_white, vscreen);
        	line(x+maxh+1, y,   x+maxh+1, y+lbarsize[1] + 1, color_white, vscreen);
		}

        drawbox_trans(x+h+1, y+1, maxh-h, lbarsize[1], color_purple, vscreen, lut_mul);
        drawbox(x+1, y+1, h, lbarsize[1], color_green, vscreen);

        if(h <= maxh*.5) drawbox(x+1, y+1, h, lbarsize[1], color_yellow, vscreen);   // if players are between 26%- 50% life change the HP Bar color
        if(h <= maxh*.25) drawbox(x+1, y+1, h, lbarsize[1], color_red, vscreen);     // if player are below 26% life change the color again
        if(overflow) drawbox(x+1, y+1, overflow, lbarsize[1], color_blue, vscreen);
        if(overflo2){
            drawbox(x+1, y+1, h, lbarsize[1], color_blue, vscreen);
            drawbox(x+1, y+1, overflo2, lbarsize[1], color_orange, vscreen);
        }
        if(overflo3){
            drawbox(x+1, y+1, h, lbarsize[1], color_orange, vscreen);
            drawbox(x+1, y+1, overflo3, lbarsize[1], color_pink, vscreen);
        }
        if(overflo4){
            drawbox(x+1, y+1, h, lbarsize[1], color_pink, vscreen);
            drawbox(x+1, y+1, overflo4, lbarsize[1], color_purple, vscreen);
        }
        if(overflo5){
            drawbox(x+1, y+1, h, lbarsize[1], color_purple, vscreen);
            drawbox(x+1, y+1, overflo5, lbarsize[1], color_black, vscreen);
        }
        if(overflo6){
            drawbox(x+1, y+1, h, lbarsize[1], color_black, vscreen);
            drawbox(x+1, y+1, overflo6, lbarsize[1], color_white, vscreen);
        }
        if(overflo7){
            drawbox(x+1, y+1, h, lbarsize[1], color_white, vscreen);
            drawbox(x+1, y+1, overflo7, lbarsize[1], color_white, vscreen);
        }
    }
    else {
        int overflow = 0;

        if(maxh<=0) return;
        if(h<0) h = 0;
        if(maxh < h) maxh = h;

        if(h > lbarsize[0]){
            overflow = h % lbarsize[0];
            h = lbarsize[0];
        }

        if(maxh > lbarsize[0]) maxh = lbarsize[0];

        if(!lbarsize[2]){
	        line(x+1,      y+lbarsize[1] + 2, x+maxh+2, y+lbarsize[1] + 2, color_black, vscreen);
	        line(x+maxh+2, y+1, x+maxh+2, y+lbarsize[1] + 2, color_black, vscreen);

	        line(x,        y,   x+maxh+1, y,   color_white, vscreen);
	        line(x,        y,   x,        y+lbarsize[1] + 1, color_white, vscreen);
	        line(x,        y+lbarsize[1] + 1, x+maxh+1, y+lbarsize[1] + 1, color_white, vscreen);
	        line(x+maxh+1, y,   x+maxh+1, y+lbarsize[1] + 1, color_white, vscreen);
		}

        drawbox_trans(x+h+1, y+1, maxh-h, lbarsize[1], color_red, vscreen, lut_mul);
        drawbox(x+1, y+1, h, lbarsize[1], color_yellow, vscreen);

        if(overflow) drawbox(x+1, y+1, overflow, lbarsize[1], color_orange, vscreen);
    }

}

// end new lifebar colors!---------------------------------------------------------------------



void spawnplayer(int);
void drop_all_enemies();
void kill_all_enemies();
void smart_bomb(entity *e, int p, int t, int l);	// New function
void ent_set_colourmap(entity *ent, unsigned int which);




void predrawstatus(){

    int dt;
    int icon;
    int i;
    static int pauselector = 0;

    for(i=0; i<2; i++){
        if(player[i].ent){
            font_printf(plife[i][0] + 1, savedata.windowpos+picon[i][1], 0, "%s - %u", player[i].ent->name, player[i].score);
            if(player[i].iconmode == 0) icon = player[i].ent->model->icon;    // 20-1-2005 New icons here
            if(player[i].iconmode == 1) icon = player[i].ent->model->iconpain;// 20-1-2005 New icons here
            if(player[i].iconmode == 2) icon = player[i].ent->model->icondie;// 20-1-2005 New icons here
            if(player[i].iconmode == 3) icon = player[i].ent->model->iconget;// 20-1-2005 New icons here
            if(icon>=0) spriteq_add(picon[i][0],savedata.windowpos+picon[i][1],10000, sprites[1][icon], SFX_REMAP, player[i].ent->model->map);

            font_printf(plife[i][0] + lbarsize[0] + 4,savedata.windowpos+picon[i][1]+7, 0, "x");
            font_printf(plife[i][0] + lbarsize[0] + 11,savedata.windowpos+picon[i][1], 3, "%i", player[i].lives);

            if(player[i].opponent && !player[i].opponent->nolife){	// Displays life unless overridden by flag
                font_printf(elife[i][0] + 1, savedata.windowpos+eicon[i][1], 0, player[i].opponent->name);
                icon = player[i].opponent->model->icon;
                if(icon>=0) spriteq_add(eicon[i][0],savedata.windowpos+eicon[i][1],10000, sprites[1][icon],SFX_REMAP, player[i].opponent->model->map);	// Feb 26, 2005 - Changed to opponent->map so icons don't pallete swap with die animation
            }
        }
        else if(player[i].joining && player[i].model){
            font_printf(plife[i][0] + 1, savedata.windowpos+picon[i][1], 0, player[i].model->name);
            font_printf(plife[i][0] + 1, savedata.windowpos+picon[i][1] + 10, 0, "%s",stringtable[55]); //select hero
            icon = player[i].model->icon;
            player[i].weapon = 0;

            if(icon>=0) spriteq_add(picon[i][0],savedata.windowpos+picon[i][1],10000, sprites[1][icon], 0, NULL);

            if(player[i].playkeys & FLAG_ANYBUTTON && !freezeall){	// Can't join while animations are frozen

                if((!player[0].ent && !player[1].ent) || player[0].model != player[1].model || !sameplayer){	// Fixed so players can be selected if other player is no longer valid
                    player[i].playkeys = player[i].newkeys = 0;
                    player[i].lives = PLAYER_LIVES;			// to address new lives settings
                    player[i].joining = 0;
                    player[i].hasplayed = 1;

                    if(!colourselect) player[i].colourmap = i;

                    player[i].spawnhealth = player[i].model->health;
                    spawnplayer(i);

                    if(!nodropen) drop_all_enemies();   //27-12-2004  If drop enemies is on, drop all enemies

                    timeleft = level->settime * COUNTER_SPEED;	// Feb 24, 2005 - This line moved here to set custom time
                }
            }
            else if(player[i].playkeys & FLAG_MOVELEFT){
                player[i].model = prevplayermodel(player[i].model);
                player[i].playkeys = 0;
            }
            else if(player[i].playkeys & FLAG_MOVERIGHT){
                player[i].model = nextplayermodel(player[i].model);
                player[i].playkeys = 0;
            }
            // don't like a characters color try a new one!
            else if(player[i].playkeys & FLAG_MOVEUP && (colourselect == 1)){
                player[i].colourmap = player[i].colourmap + 1;

                if(player[i].colourmap > MAX_COLOUR_MAPS) player[i].colourmap = 0;

                player[i].playkeys = 0;
            }
            else if(player[i].playkeys & FLAG_MOVEDOWN && (colourselect == 1)){
                player[i].colourmap = player[i].colourmap -1;

                if(player[i].colourmap < 0) player[i].colourmap = MAX_COLOUR_MAPS;

                player[i].playkeys = 0;
            }
            //
        }
        else if(player[i].credits || credits || (!player[i].hasplayed && noshare)){
            if(player[i].credits && (time/(GAME_SPEED*2)) & 1) font_printf(plife[i][0], savedata.windowpos+plife[i][1], 0, "%s %i",stringtable[57], player[i].credits); //Credit
            else if(credits && (time/(GAME_SPEED*2)) & 1) font_printf(plife[i][0], savedata.windowpos+plife[i][1], 0, "%s %i",stringtable[57], credits); //Credit
            else font_printf(plife[i][0], savedata.windowpos+plife[i][1], 0, "%s",stringtable[56]); //Press start

            if(player[i].playkeys & FLAG_START){
                player[i].lives = 0;
                player[i].model = nextplayermodel(NULL);
                player[i].joining = 1;
                player[i].playkeys = 0;
                timeleft = level->settime * COUNTER_SPEED;	// Feb 24, 2005 - This line moved here to set custom time

				if(!player[i].hasplayed && noshare) player[i].credits = CONTINUES;

                if(!creditscheat){
                	if(noshare) --player[i].credits;
					else --credits;
				}
            }
        }
        else font_printf(plife[i][0], savedata.windowpos+plife[i][1], 0, "%s", stringtable[9]); //GAME OVER
    }

    dt = timeleft/COUNTER_SPEED;
    if(dt>99) dt = 99;
    if(dt<0) dt = 0;
    if(!level->notime) font_printf(timeloc[0] + 2,savedata.windowpos+timeloc[1] + 2, 3, "%02i", dt);
    if(showtimeover) font_printf(113,110, 3, "TIME OVER");
    if(dt<99) showtimeover = 0;

    if(go_time>time){
        dt = (go_time-time)%GAME_SPEED;

        if(dt < GAME_SPEED/2){
            if(level->scrolldir==SCROLL_LEFT){

                if(golsprite) spriteq_add(40,60,10000, sprites[1][golsprite], 0, NULL); // new sprite for left direction
                else spriteq_add(40,60,10000, sprites[0][gosprite], 0, NULL);

                if(gosound == 0 ){

                    if(smp_go >= 0) sound_play_sample(smp_go, 0, savedata.effectvol,savedata.effectvol, 100);// 26-12-2004 Play go sample as arrow flashes

                    gosound = 1;				// 26-12-2004 Sets sample as already played - stops sample repeating too much
                }
            }
            else{
                spriteq_add(280,60,10000, sprites[1][gosprite], 0, NULL);

                if(gosound == 0 ){

                    if(smp_go >= 0) sound_play_sample(smp_go, 0, savedata.effectvol,savedata.effectvol, 100); // 26-12-2004 Play go sample as arrow flashes

                    gosound = 1;  // 26-12-2004 Sets sample as already played - stops sample repeating too much
                }
            }
        }
        else gosound = 0;    //26-12-2004 Resets go sample after loop so it can be played again next time
    }


    if(pause){
        font_printf(130,100, 3, "%s",stringtable[52]);
        font_printf(130,120, (pauselector==0), "%s",stringtable[53]);
        font_printf(130,132, (pauselector==1), "%s",stringtable[54]);

        if(bothnewkeys & (FLAG_MOVEUP|FLAG_MOVEDOWN)) pauselector ^= 1;

        if(bothnewkeys & FLAG_ANYBUTTON){
            if(pauselector){
                player[0].lives = player[1].lives = 0;
                endgame = 1;
            }
            pause = 0;
            sound_pause_music(0);
        }
        if(bothnewkeys & FLAG_ESC){
            pause = 0;
            sound_pause_music(0);
        }

        bothnewkeys = 0;
    }
    else if(
        (player[0].ent && (player[0].newkeys & FLAG_START)) ||
        (player[1].ent && (player[1].newkeys & FLAG_START)) ||
        (bothnewkeys & FLAG_ESC)){

		pause = 1;
        // sound_pause_music(1);
        pauselector = 0;
    }
}



void drawstatus(){
    // Health bars
    if(player[0].ent){
        drawlifebar(plife[0][0], savedata.windowpos+plife[0][1], player[0].ent->oldhealth, player[0].ent->maxhealth);
		if(player[0].opponent && !player[0].opponent->nolife)
			drawlifebar(elife[0][0], savedata.windowpos+elife[0][1], player[0].opponent->oldhealth, player[0].opponent->maxhealth);	// Tied in with the nolife flag
    }
    if(player[1].ent){
        drawlifebar(plife[1][0], savedata.windowpos+plife[1][1], player[1].ent->oldhealth, player[1].ent->maxhealth);
        if(player[1].opponent && !player[1].opponent->nolife)
        	drawlifebar(elife[1][0], savedata.windowpos+elife[1][1], player[1].opponent->oldhealth, player[1].opponent->maxhealth);	// Tied in with the nolife flag
    }

    // Time box
    if(!level->notime && !timeloc[4])	// Only draw if notime is set to 0 or not specified
    {
		line(timeloc[0], savedata.windowpos+timeloc[1], timeloc[0]+timeloc[2], savedata.windowpos+timeloc[1],   color_black, vscreen);
		line(timeloc[0], savedata.windowpos+timeloc[1], timeloc[0], savedata.windowpos+timeloc[1]+timeloc[3],  color_black, vscreen);
		line(timeloc[0]+timeloc[2], savedata.windowpos+timeloc[1], timeloc[0]+timeloc[2], savedata.windowpos+timeloc[1]+timeloc[3],  color_black, vscreen);
		line(timeloc[0], savedata.windowpos+timeloc[1]+timeloc[3], timeloc[0]+timeloc[2], savedata.windowpos+timeloc[1]+timeloc[3], color_black, vscreen);
		line(timeloc[0] - 1, savedata.windowpos+timeloc[1] - 1, timeloc[0]+timeloc[2] - 1, savedata.windowpos+timeloc[1] - 1,   color_white, vscreen);
		line(timeloc[0] - 1, savedata.windowpos+timeloc[1] - 1, timeloc[0] - 1, savedata.windowpos+timeloc[1]+timeloc[3] - 1,  color_white, vscreen);
		line(timeloc[0]+timeloc[2] - 1, savedata.windowpos+timeloc[1] - 1, timeloc[0]+timeloc[2] - 1, savedata.windowpos+timeloc[1]+timeloc[3] - 1,  color_white, vscreen);
		line(timeloc[0] - 1, savedata.windowpos+timeloc[1]+timeloc[3] - 1, timeloc[0]+timeloc[2] - 1, savedata.windowpos+timeloc[1]+timeloc[3] - 1, color_white, vscreen);
	}
}




void addscore(int playerindex, unsigned long add){
    unsigned long s = player[playerindex&1].score;
    unsigned long next1up;

    playerindex &= 1;

    next1up = ((s/50000)+1) * 50000;

    s += add;
    if(s>999999999) s=999999999;

    while(s>next1up){

        if(smp_1up >= 0) sound_play_sample(smp_1up, 0, savedata.effectvol,savedata.effectvol, 100);

        player[playerindex].lives++;
        next1up += 50000;
    }

    player[playerindex].score = s;
}




// ---------------------------- Object handling ------------------------------




void free_ents(){
    int i;
    for(i=0; i<MAX_ENTS; i++){
        if(ent_list[i]) free(ent_list[i]);
        ent_list[i] = NULL;
    }
}


int alloc_ents(){
    int i;
    for(i=0; i<MAX_ENTS; i++){
        ent_list[i] = (entity*)malloc(sizeof(entity));
        if(!ent_list[i]){
            free_ents();
            return 0;
        }
        memset(ent_list[i], 0, sizeof(entity));
    }
    return 1;
}


int checkwall(int x, int z);
int checkhole(int x, int z);
int player_trymove(float xdir, float zdir);
int enemy_trymove(float xdir, float zdir);
entity * check_platform(int x, int z);
void enemy_fall(void);
void toss(entity *ent, float lift);	// Needs to be declared so it can be used in the update_ents() section
void pshot_spawn(float x, float z, float a, int direction, int pi);	// Needs to be declared so it can be used in the update_ents() section
void knife_spawn(float x, float z, float a, int direction, entity* enem);	// Needs to be declared so it can be used in the update_ents() section
void bomb_spawn(float x, float z, float a, int direction, entity* enem);	// Needs to be declared so it can be used in the update_ents() section
void fireball_spawn(float x, float z, float a, int direction, entity* enem);	// Needs to be declared so it can be used in the update_ents() section
void enemy_jumpattack(void);	// Needs to be declared so it can be used in the update_ents() section
void enemy_block(void);	//	Moved here so they can be used in the do_attack function
void enemy_attack(void);	//	Moved here so they can be used in the do_attack function
void enemy_pain(void);	//	Moved here so they can be used in the do_attack function
void player_think(void);
void enemy_think(void);



void ent_set_anim(entity *ent, int aninum){
    s_anim *ani;
//    entity *other;
//    int wall;

    int temp = 0;	// Temporary variable added for animation purposes

    if(ent==NULL) shutdown("FATAL: tried to set animation with invalid address (no such object)");
    if(aninum<0 || aninum>=MAX_ANIS) shutdown("FATAL: tried to set animation with invalid index (%i)", aninum);

    ani = ent->model->animation[aninum];

    if(ani==NULL) shutdown("FATAL: tried to set animation with invalid address (%s, %i)", ent->name, aninum);
    if(ent->animation == ani) return;
	if(ent->animation == ent->model->animation[ANI_GRAB]) temp = 1;	// Flag incase going from grab animation to up/down/walk

    ent->animation = ani;

	if(
		((aninum == ANI_UP ||
		aninum == ANI_DOWN ||
		aninum == ANI_WALK) &&
		(temp == 0 &&
		ent->type == TYPE_PLAYER))
		&& (ent->model->animation[ANI_UP] ||
		ent->model->animation[ANI_DOWN])
	)
		return;	// Used to help animation be more fluid

    ent->animpos = 0;
    ent->lastanimpos = -1;
    ent->currentsprite = ani->sprite[0];
    ent->nextanim = time + ani->delay[0];

// Removed unless bugs arise

/*
    if(ent->base != -1000){	// So the character's falling into holes doesn't get overridden
		wall = checkwall(ent->x, ent->z);	// Checks to see if they are on a platform
		other = check_platform(ent->x, ent->z);	// Checks to see if they are on an obstacle

		if(other && other != ent && ent->type != TYPE_SHOT && ent->model->subtype != SUBTYPE_EWEAPON &&
		ent->type != TYPE_NONE && ent->a >= other->a + other->animation->platform[other->animpos][5])

			ent->base = (ent->animation->seta[0] >= 0) * ent->animation->seta[0] +
			other->a + other->animation->platform[other->animpos][5];

		else if(wall >= 0 && ent->type != TYPE_SHOT && ent->model->subtype != SUBTYPE_EWEAPON && ent->type != TYPE_NONE)

			ent->base = (ent->animation->seta[0] >= 0) * ent->animation->seta[0] +
			(ent->a >= level->walls[wall][7]) * level->walls[wall][7];

		else if(ent->animation->seta[0] >= 0) ent->base = ent->animation->seta[0];
		else if(ent->type != TYPE_SHOT && ent->model->subtype != SUBTYPE_EWEAPON && ent->type != TYPE_NONE) ent->base = 0;
	}
*/

	if(ent->animation->move[0]){
		if(ent->type == TYPE_PLAYER){
			if(ent->direction) player_trymove(ent->animation->move[0], 0);
			else player_trymove(-ent->animation->move[0], 0);
		}
		else if(
			(ent->type == TYPE_ENEMY &&
			!ent->model->subtype) ||
			(ent->type == TYPE_ENEMY &&
			ent->model->subtype == SUBTYPE_NOTGRAB)
		){

			if(ent->direction) enemy_trymove(ent->animation->move[0], 0);
			else enemy_trymove(-ent->animation->move[0], 0);
		}
		else{
			if(ent->direction) ent->x += ent->animation->move[0];
			else ent->x -= ent->animation->move[0];
		}
	}

    if(ent->animation->soundtoplay>0 && ent->animation->soundframe==0) sound_play_sample(ent->animation->soundtoplay, 0, savedata.effectvol,savedata.effectvol, 100);

	if(ent->animation->jumpframe == 0){
		if(ent->animation->moveflag > 0) toss(ent, ent->animation->moveflag);	// Set custom jumpheight for jumpframes
		else{
			if(ent->type == TYPE_PLAYER) toss(ent, ent->model->jumpheight/2);
			else toss(ent, ent->model->jumpheight);
		}
	}

	if(ent->type == TYPE_PLAYER && ent->animpos == ent->animation->pshotframe){
		pshot_spawn(ent->x, ent->z, ent->a + ent->animation->throwa, ent->direction, ent->playerindex);
	}
	else if(ent->type == TYPE_ENEMY && ent->animpos == ent->animation->throwframe && ent->think != enemy_jumpattack){
		knife_spawn(ent->x, ent->z, ent->a + ent->animation->throwa, ent->direction, ent);
	}
	else if(ent->type == TYPE_ENEMY && ent->animpos == ent->animation->shootframe){
		fireball_spawn(ent->x, ent->z, ent->a + ent->animation->throwa, ent->direction, ent);
	}
	else if(ent->type == TYPE_ENEMY && ent->animpos == ent->animation->tossframe){
		bomb_spawn(ent->x, ent->z, ent->a + ent->animation->throwa, ent->direction, ent);
	}

    ent->animating = 1;
}


void ent_reset_anim(entity *ent, int aninum){
    s_anim *ani;
//    entity *other;
//    int wall;

    if(ent==NULL) shutdown("FATAL: tried to set animation with invalid address (no such object)");
    if(aninum<0 || aninum>=MAX_ANIS) shutdown("FATAL: tried to set animation with invalid index (%i)", aninum);

    ani = ent->model->animation[aninum];

    if(ani==NULL) shutdown("FATAL: tried to set animation with invalid address (%s, %i)", ent->name, aninum);

    ent->animation = ani;
    ent->animpos = 0;
    ent->currentsprite = ani->sprite[0];
    ent->nextanim = time + ani->delay[0];

// Removed unless bugs arise

/*
    if(ent->base != -1000){	// So the character's falling into holes doesn't get overridden
		wall = checkwall(ent->x, ent->z);	// Checks to see if they are on a platform
		other = check_platform(ent->x, ent->z);	// Checks to see if they are on an obstacle

		if(
			other &&
			other != ent &&
			ent->type != TYPE_SHOT &&
			ent->model->subtype != SUBTYPE_EWEAPON &&
			ent->a >= other->a + other->animation->platform[other->animpos][5]
		)

			ent->base = (ent->animation->seta[0] >= 0) * ent->animation->seta[0] +
			other->a + other->animation->platform[other->animpos][5];

		else if(wall >= 0 && ent->type != TYPE_SHOT && ent->model->subtype != SUBTYPE_EWEAPON)

			ent->base = (ent->animation->seta[0] >= 0) * ent->animation->seta[0] +
			(ent->a >= level->walls[wall][7]) * level->walls[wall][7];

		else if(ent->animation->seta[0] >= 0) ent->base = ent->animation->seta[0];
		else if(ent->type != TYPE_SHOT && ent->model->subtype != SUBTYPE_EWEAPON && ent->type != TYPE_NONE) ent->base = 0;
	}
*/

	if(ent->animation->move[0]){
		if(ent->type == TYPE_PLAYER){
			if(ent->direction) player_trymove(ent->animation->move[0], 0);
			else player_trymove(-ent->animation->move[0], 0);
		}
		else if((ent->type == TYPE_ENEMY && !ent->model->subtype) ||
		(ent->type == TYPE_ENEMY && ent->model->subtype == SUBTYPE_NOTGRAB)){
			if(ent->direction) enemy_trymove(ent->animation->move[0], 0);
			else enemy_trymove(-ent->animation->move[0], 0);
		}
		else{
			if(ent->direction) ent->x += ent->animation->move[0];
			else ent->x -= ent->animation->move[0];
		}
	}

    if(ent->animation->soundtoplay>0 && ent->animation->soundframe==0)
    	sound_play_sample(ent->animation->soundtoplay, 0, savedata.effectvol,savedata.effectvol, 100);

	if(ent->animation->jumpframe == 0){
		if(ent->animation->moveflag > 0) toss(ent, ent->animation->moveflag);	// Set custom jumpheight for jumpframes
		else{
			if(ent->type == TYPE_PLAYER) toss(ent, ent->model->jumpheight/2);
			else toss(ent, ent->model->jumpheight);
		}
	}

	if(ent->type == TYPE_PLAYER && ent->animpos == ent->animation->pshotframe){
		pshot_spawn(ent->x, ent->z, ent->a + ent->animation->throwa, ent->direction, ent->playerindex);
	}
	else if(ent->type == TYPE_ENEMY && ent->animpos == ent->animation->throwframe && ent->think != enemy_jumpattack){
		knife_spawn(ent->x, ent->z, ent->a + ent->animation->throwa, ent->direction, ent);
	}
	else if(ent->type == TYPE_ENEMY && ent->animpos == ent->animation->shootframe){
		fireball_spawn(ent->x, ent->z, ent->a + ent->animation->throwa, ent->direction, ent);
	}
	else if(ent->type == TYPE_ENEMY && ent->animpos == ent->animation->tossframe){
		bomb_spawn(ent->x, ent->z, ent->a + ent->animation->throwa, ent->direction, ent);
	}

    ent->animating = 1;
}


// 0 = none, 1+ = alternative
void ent_set_colourmap(entity *ent, unsigned int which){
    if(which>MAX_COLOUR_MAPS) which = 0;

    if(which==0) ent->colourmap = NULL;
    else ent->colourmap = ent->model->colourmap[which-1];
}



void ent_set_model(entity * ent, char * modelname){
    s_model *m;

    if(ent==NULL) shutdown("FATAL: tried to change model of invalid object");

    m = find_model(modelname);
    if(m==NULL) shutdown("Model not found: '%s'", modelname);
    ent->model = m;

    if(!selectScreen && ent->model->animation[ANI_SPAWN]) ent_set_anim(ent, ANI_SPAWN); // use new playerselect spawn anim
    else if(selectScreen && ent->model->animation[ANI_SELECT]) ent_set_anim(ent, ANI_SELECT);
    else ent_set_anim(ent, ANI_IDLE);

    ent_set_colourmap(ent, 0);
}






entity * spawn(float x, float z, float a, char * name){   // 7-1-2005
    entity *e;
    int i;
    s_model * model;

    model = find_model(name);
    if(model==NULL){
        // Be a bit more tolerant...
        return NULL;
        // shutdown("FATAL: attempt to spawn object with invalid model (%s)!", name);
    }

    for(i=0; i<MAX_ENTS; i++){
        if(!ent_list[i]->exists){
            e = ent_list[i];
            memset(e, 0, sizeof(entity));
            e->exists = 1;
            e->model = model;
            e->health = model->health;
            e->maxhealth = model->health;
            e->score = model->score;	// Added so score can be overriden
            e->multiple = model->multiple;	// Added so multiple can be overriden
            e->nolife = model->nolife;	// Added so nolife can be overriden
            e->x = x;
            e->z = z;
            e->a = a;
            e->nextthink = time + 1;
            e->type = model->type;

            if(!selectScreen && e->model->animation[ANI_SPAWN]) ent_set_anim(e, ANI_SPAWN); // use new spawn anim, but only if midlevel
            else if(selectScreen && e->model->animation[ANI_SELECT]) ent_set_anim(e, ANI_SELECT);	// If at select screen an anim_select exists
            else ent_set_anim(e, ANI_IDLE);

            strncpy(e->name, e->model->name, MAX_NAME_LEN);
            return e;
        }
    }
    return NULL;
}



// Break the link an entity has with another one
void ent_unlink(entity *e){
    if(e->link){
        e->link->link = NULL;
    }
    e->link = NULL;
}



// Link two entities together
void ents_link(entity *e1, entity *e2){
    ent_unlink(e1);
    ent_unlink(e2);
    e1->link = e2;
    e2->link = e1;
}



void kill(entity *victim){
    if(!victim) return;

    ent_unlink(victim);
    victim->health = 0;
    victim->exists = 0;

    if(victim==player[0].ent) player[0].ent = NULL;
    if(victim==player[0].opponent) player[0].opponent = NULL;
    if(victim==player[1].ent) player[1].ent = NULL;
    if(victim==player[1].opponent) player[1].opponent = NULL;
}



void kill_all(){
    int i;
    for(i=0; i<MAX_ENTS; i++) kill(ent_list[i]);
    time = 0;
}



int checkhit(entity *attacker, entity *target){
    int * coords1;
    int * coords2;
    int x1, x2, y1, y2;
    float medx, medy;
    int debug_coords[2][4];
    int topleast, bottomleast, leftleast, rightleast;

    if(attacker == target) return 0;
    if(diff(attacker->z,target->z) > (self->model->grabdistance/3)) return 0;
    if(!target->animation->vulnerable[target->animpos]) return 0;

    coords1 = attacker->animation->attack_coords[attacker->animpos];
    coords2 = target->animation->bbox_coords[target->animpos];
    x1 = attacker->x;
    y1 = attacker->z - attacker->a;
    x2 = target->x;
    y2 = target->z - target->a;


    if(attacker->direction==0){
        debug_coords[0][0] = x1-coords1[2];
        debug_coords[0][1] = y1+coords1[1];
        debug_coords[0][2] = x1-coords1[0];
        debug_coords[0][3] = y1+coords1[3];
    }
    else{
        debug_coords[0][0] = x1+coords1[0];
        debug_coords[0][1] = y1+coords1[1];
        debug_coords[0][2] = x1+coords1[2];
        debug_coords[0][3] = y1+coords1[3];
    }
    if(target->direction==0){
        debug_coords[1][0] = x2-coords2[2];
        debug_coords[1][1] = y2+coords2[1];
        debug_coords[1][2] = x2-coords2[0];
        debug_coords[1][3] = y2+coords2[3];
    }
    else{
        debug_coords[1][0] = x2+coords2[0];
        debug_coords[1][1] = y2+coords2[1];
        debug_coords[1][2] = x2+coords2[2];
        debug_coords[1][3] = y2+coords2[3];
    }

    if(debug_coords[0][0] > debug_coords[1][2]) return 0;
    if(debug_coords[1][0] > debug_coords[0][2]) return 0;
    if(debug_coords[0][1] > debug_coords[1][3]) return 0;
    if(debug_coords[1][1] > debug_coords[0][3]) return 0;


    // Find center of attack area
    leftleast = debug_coords[0][0];
    if(leftleast < debug_coords[1][0]) leftleast = debug_coords[1][0];
    topleast = debug_coords[0][1];
    if(topleast < debug_coords[1][1]) topleast = debug_coords[1][1];
    rightleast = debug_coords[0][2];
    if(rightleast > debug_coords[1][2]) rightleast = debug_coords[1][2];
    bottomleast = debug_coords[0][3];
    if(bottomleast > debug_coords[1][3]) bottomleast = debug_coords[1][3];

    medx = (leftleast + rightleast) / 2;
    medy = (topleast + bottomleast) / 2;

    // Now convert these coords to 3D
    lasthitx = medx;

    if(attacker->z > target->z) lasthitz = attacker->z + 1;	// Changed so flashes always spawn in front
    else lasthitz = target->z + 1;

    lasthita = lasthitz - medy;

    return 1;
}



// Check if two attacks overlap (needed for countermoves)
int checkhithit(entity *attacker, entity *target){
    int * coords1;
    int * coords2;
    int x1, x2, y1, y2;
    float medx, medy;
    int debug_coords[2][4];
    int topleast, bottomleast, leftleast, rightleast;

    if(attacker == target) return 0;
    if(diff(attacker->z,target->z) > (self->model->grabdistance/3)) return 0;
    if(!target->animation->vulnerable[target->animpos]) return 0;

    coords1 = attacker->animation->attack_coords[attacker->animpos];
    coords2 = target->animation->attack_coords[target->animpos];
    x1 = attacker->x;
    y1 = attacker->z - attacker->a;
    x2 = target->x;
    y2 = target->z - target->a;


    if(attacker->direction==0){
        debug_coords[0][0] = x1-coords1[2];
        debug_coords[0][1] = y1+coords1[1];
        debug_coords[0][2] = x1-coords1[0];
        debug_coords[0][3] = y1+coords1[3];
    }
    else{
        debug_coords[0][0] = x1+coords1[0];
        debug_coords[0][1] = y1+coords1[1];
        debug_coords[0][2] = x1+coords1[2];
        debug_coords[0][3] = y1+coords1[3];
    }
    if(target->direction==0){
        debug_coords[1][0] = x2-coords2[2];
        debug_coords[1][1] = y2+coords2[1];
        debug_coords[1][2] = x2-coords2[0];
        debug_coords[1][3] = y2+coords2[3];
    }
    else{
        debug_coords[1][0] = x2+coords2[0];
        debug_coords[1][1] = y2+coords2[1];
        debug_coords[1][2] = x2+coords2[2];
        debug_coords[1][3] = y2+coords2[3];
    }

    if(debug_coords[0][0] > debug_coords[1][2]) return 0;
    if(debug_coords[1][0] > debug_coords[0][2]) return 0;
    if(debug_coords[0][1] > debug_coords[1][3]) return 0;
    if(debug_coords[1][1] > debug_coords[0][3]) return 0;


    // Find center of attack area
    leftleast = debug_coords[0][0];
    if(leftleast < debug_coords[1][0]) leftleast = debug_coords[1][0];
    topleast = debug_coords[0][1];
    if(topleast < debug_coords[1][1]) topleast = debug_coords[1][1];
    rightleast = debug_coords[0][2];
    if(rightleast > debug_coords[1][2]) rightleast = debug_coords[1][2];
    bottomleast = debug_coords[0][3];
    if(bottomleast > debug_coords[1][3]) bottomleast = debug_coords[1][3];

    medx = (leftleast + rightleast) / 2;
    medy = (topleast + bottomleast) / 2;

    // Now convert these coords to 3D
    lasthitx = medx;

    if(attacker->z > target->z) lasthitz = attacker->z + 1;
    else lasthitz = target->z + 1;

    lasthita = lasthitz - medy;

    return 1;
}



// Calculates the coef relative to the bottom left point. This is done by figuring out how far the entity is from
// the bottom of the platform and multiplying the result by the difference of the bottom left point and the top
// left point divided by depth of the platform. The same is done for the right side, and checks to see if they are
// within the bottom/top and the left/right area.  If so, returns "1" (found), otherwise returns "0" (not found)

int checkhole(int x, int z){
    float coef1, coef2;
    int i;

    if(level==NULL) return 0;

    if(level->exit_hole){
        if(x > level->width-(PLAYER_MAX_Z-z)) return 2;
    }

    for(i=0; i<level->numholes; i++){

		if(z < level->holes[i][1] && z > level->holes[i][1] - level->holes[i][6]){

			coef1 = (level->holes[i][1] - z) * ((level->holes[i][2] - level->holes[i][3]) / level->holes[i][6]);
			coef2 = (level->holes[i][1] - z) * ((level->holes[i][4] - level->holes[i][5]) / level->holes[i][6]);

			if(x > level->holes[i][0] + level->holes[i][3] + coef1 && x < level->holes[i][0] + level->holes[i][5] + coef2){
				holez = i;
				return 1;
			}

		}

    }

    return 0;
}



// Calculates the coef relative to the bottom left point. This is done by figuring out how far the entity is from
// the bottom of the platform and multiplying the result by the difference of the bottom left point and the top
// left point divided by depth of the platform. The same is done for the right side, and checks to see if they are
// within the bottom/top and the left/right area.  If so, returns the first wall found, otherwise returns -1 (none)

int checkwall(int x, int z){
    float coef1, coef2;
    int i;

    if(level==NULL) return -1;

    for(i=0; i<level->numwalls; i++){

		if(z < level->walls[i][1] && z > level->walls[i][1] - level->walls[i][6]){

			coef1 = (level->walls[i][1] - z) * ((level->walls[i][2] - level->walls[i][3]) / level->walls[i][6]);
			coef2 = (level->walls[i][1] - z) * ((level->walls[i][4] - level->walls[i][5]) / level->walls[i][6]);

			if(x > level->walls[i][0] + level->walls[i][3] + coef1 && x < level->walls[i][0] + level->walls[i][5] + coef2) return i;
		}
    }

    return -1;
}



// Calculates the coef relative to the bottom left point. This is done by figuring out how far the entity is from
// the bottom of the platform and multiplying the result by the difference of the bottom left point and the top
// left point divided by depth of the platform. The same is done for the right side, and checks to see if they are
// within the bottom/top and the left/right area.  If so, returns the first entity found, otherwise returns NULL

entity * check_platform(int x, int z){
    float coef1, coef2;
    entity *self;
    int i;

    if(level==NULL) return NULL;

    for(i=0; i<MAX_ENTS; i++){
        if(ent_list[i]->exists && ent_list[i]->animation->platform[ent_list[i]->animpos][5]){
			self = ent_list[i];

			if(z <= self->z + 2 && z > self->z - self->animation->platform[self->animpos][4]){

				coef1 = (self->z - z) * ((self->animation->platform[self->animpos][0] -
				self->animation->platform[self->animpos][1]) / self->animation->platform[self->animpos][4]);

				coef2 = (self->z - z) * ((self->animation->platform[self->animpos][2] -
				self->animation->platform[self->animpos][3]) / self->animation->platform[self->animpos][4]);

				if(x > self->x + self->animation->platform[self->animpos][1] + coef1 && x < self->x +
				self->animation->platform[self->animpos][3] + coef2) return self;

			}
		}
	}

    return NULL;
}



void do_attack(entity *e){
    int them;
    int i;
    int force;
    entity *temp;
    entity *flash;	// Used so new flashes can be used
    int didhit = 0;
    int didblock = 0;	// So a different sound effect can be played when an attack is blocked
    int current_attack_id;
    static unsigned int new_attack_id = 1;

    // Can't get hit after this
    if(level_completed) return;
    // define "them" according to new types and settings
    if(e->projectile > 0) them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
    else if(e->type == TYPE_PLAYER && !savedata.mode) them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
    else if(e->type == TYPE_SHOT && !savedata.mode) them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
    else if(e->type == TYPE_ENEMY && e->model->subtype == SUBTYPE_ARROW) them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;	// Added so arrows can damage enemies, obstacles and players
    else if(e->model->hitenemy) them = TYPE_PLAYER | TYPE_ENEMY;	// Enemy projectiles can now hit enemies
    else if(e->type == TYPE_ENEMY) them = TYPE_PLAYER;
    else if(e->type == TYPE_PLAYER && savedata.mode) them = TYPE_ENEMY | TYPE_OBSTACLE;
    else if(e->type == TYPE_SHOT && savedata.mode) them = TYPE_ENEMY | TYPE_OBSTACLE;
    else if(e->type == TYPE_TRAP) them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
    else if(e->type == TYPE_OBSTACLE) them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;	// Added so obstacles can explode and damage enemies/players/obstacles
    else return;
    //

    // Every attack gets a unique ID to make sure no one
    // gets hit more than once by the same attack
    current_attack_id = e->attack_id;
    if(!current_attack_id){
        ++new_attack_id;
        if(new_attack_id==0) new_attack_id = 1;
        e->attack_id = current_attack_id = new_attack_id;
    }


    force = e->animation->attack_force[e->animpos];

    for(i=0; i<MAX_ENTS; i++){
        if(ent_list[i]->exists &&
                (ent_list[i]->type & them) &&
                (ent_list[i]->pain_time<time || e->animation->fastattack) &&
                ent_list[i]->takedamage &&
                ent_list[i]->hit_by_attack_id != current_attack_id &&
                checkhit(e, ent_list[i])){

            didhit = 1;
            // don't hit the player if they are the owner of the projectile
            if(!savedata.mode && e->type == TYPE_SHOT && ent_list[i]->type == TYPE_PLAYER){
                if(e->playerindex == ent_list[i]->playerindex) didhit = 0;
            }

            if((e->owner == ent_list[i] && e->animation == e->model->animation[ANI_IDLE]) ||
            (e->model->subtype == SUBTYPE_EWEAPON && ent_list[i]->model->subtype == SUBTYPE_EWEAPON) ||
            (e->model->ground && inair(e))) didhit = 0;	// Added so enemy projectiles don't hit the owner and so projectiles don't collide

            temp = self;
            self = ent_list[i];
            //			don't hit the dead
            if(self->health <= 0 || self->invincible) didhit = 0;	// Mar 2, 2005 - Used to make sure player can't be hit
            if(e->model->subtype == SUBTYPE_ARROW && self->model->subtype == SUBTYPE_ARROW) didhit = 0;	// So 2 arrows don't collide and destroy each other
            if(e->link && e->link != self) didhit = 0;	// So grab attacks don't hit enemies not being grabbed

            if(didhit){

				// New blocking code section

				if(self->toexplode == 1) self->toexplode = 2;	// Used so the bomb type entity explodes when hit
				if(e->toexplode == 1) e->toexplode = 2;	// Used so the bomb type entity explodes when hitting

				if(
					self->type == TYPE_ENEMY &&
					!(self->link ||
					inair(self) ||
					self->frozen ||
					self->direction == e->direction ||
					self->think == enemy_attack ||
					self->think == enemy_pain) &&	// Can't block if linked/in the air or frozen or opponent is a projectile
					self->model->animation[ANI_BLOCK] &&
					!e->animation->no_block[e->animpos] &&	// If unblockable, will automatically hit
					(rand32()&self->model->blockodds) == 1 && // Randomly blocks depending on blockodds (1 : blockodds ratio)
					(!self->model->thold ||
					(self->model->thold > 0 &&
					self->model->thold > force))
				){	// New threshold - can only block up to specified force

					self->think = enemy_block;
					ent_set_anim(self, ANI_BLOCK);
					didblock = 1;	// Used for when playing the block.wav sound

	                // Spawn a flash
	                if(!e->animation->no_flash[e->animpos]) flash = spawn(lasthitx,lasthitz,lasthita,ent_list[i]->model->bflash);	// New block flash that can be smaller
				}
				else if(
					self->type == TYPE_PLAYER &&
					!(self->direction == e->direction ||
					self->frozen) &&	// Can't block if facing the wrong direction or frozen in the block animation or opponent is a projectile
					self->animation == self->model->animation[ANI_BLOCK] &&
					!e->animation->no_block[e->animpos] &&	// Make sure you are actually blocking and that the attack is blockable
					(!self->model->thold ||
					(self->model->thold > 0 &&
					self->model->thold > force))
				){	// Only block if the attack is less than the players threshold

					didblock = 1;	// Used for when playing the block.wav sound

	                // Spawn a flash
	                if(!e->animation->no_flash[e->animpos]) flash = spawn(lasthitx,lasthitz,lasthita,ent_list[i]->model->bflash);	// New block flash that can be smaller
				}
				else{	// Didn't block so go ahead and take the damage
					self->takedamage(e, force,
									 e->animation->attack_drop[e->animpos],
									 e->animation->attack_type[e->animpos]);

					// Spawn a flash
					if(!e->animation->no_flash[e->animpos]){
						if(!self->model->noatflash) flash = spawn(lasthitx,lasthitz,lasthita,e->animation->hitflash);
						else flash = spawn(lasthitx,lasthitz,lasthita,ent_list[i]->model->flash);
					}

					if(e->type==TYPE_PLAYER) e->combotime = time + (GAME_SPEED/2);

					if(e->animpos != e->lastanimpos || inair(e)){	// Adds pause to the current animation
						e->toss_time += e->animation->pause_add[e->animpos];	// So jump height pauses in midair
						e->nextanim += e->animation->pause_add[e->animpos];	// Animations pause for a bit when attacks hit
						e->nextthink += e->animation->pause_add[e->animpos];	// So anything that auto moves will pause
					}

					e->lastanimpos = e->animpos;

					self->toss_time += e->animation->pause_add[e->animpos];	// So jump height pauses in midair
					self->nextanim += e->animation->pause_add[e->animpos];	// Animations pause for a bit when attacks hit
					self->nextthink += e->animation->pause_add[e->animpos];	// So anything that auto moves will pause

				}

				if(flash && !e->animation->no_flash[e->animpos]){
					if(flash->model->toflip) flash->direction = (e->x > ent_list[i]->x);	// Now the flash will flip depending on which side the attacker is on
					flash->base = lasthita;
					flash->autokill = 1;
				}

				self = temp;
				ent_list[i]->hit_by_attack_id = current_attack_id;
			}
        }

    }



    // Special case: uppercut.
    // With the uppercut, an enemy can hit a player
    // through their attacks... like a counter.
    if(e->animation == e->model->animation[ANI_UPPER]){
        for(i=0; i<MAX_ENTS; i++){
            if(ent_list[i]->exists &&
                    (ent_list[i]->type & them) &&
                    (ent_list[i]->pain_time<time || e->animation->fastattack) &&
                    ent_list[i]->takedamage &&
                    !ent_list[i]->invincible &&		// Added so invincible players don't get uppercutted
                    checkhithit(e, ent_list[i])){

                temp = self;
                self = ent_list[i];
                self->takedamage(e, force, 1, e->animation->attack_type[e->animpos]);
                self = temp;

                // Spawn a flash

				if(!e->animation->no_flash[e->animpos]){
					if(!self->model->noatflash) flash = spawn(lasthitx,lasthitz,lasthita,e->animation->hitflash);
					else flash = spawn(lasthitx,lasthitz,lasthita,ent_list[i]->model->flash);
				}

                if(flash && !e->animation->no_flash[e->animpos]){
					if(flash->model->toflip) flash->direction = (e->x > ent_list[i]->x);	// Now the flash will flip depending on which side the attacker is on
                    flash->base = lasthita;
                    flash->autokill = 1;
                }

                if(e->type==TYPE_PLAYER) e->combotime = time + (GAME_SPEED/2);

                didhit = 1;
            }
        }
    }

    if(didhit){
        // Play a sound
        if(e->animation == e->model->animation[ANI_SPECIAL] && nocost && !healthcheat) e->tocost = 1;	// Set flag so life is subtracted when animation is finished

		if(didblock && smp_block >= 0) sound_play_sample(smp_block, 0, savedata.effectvol,savedata.effectvol, 100);	// New block sound effect
        else if(e->projectile > 0 && smp_indirect >= 0) sound_play_sample(smp_indirect, 0, savedata.effectvol,savedata.effectvol, 100);
        else{
			if(noslowfx){
				if(e->animation->hitsound>=0) sound_play_sample(e->animation->hitsound, 0, savedata.effectvol,savedata.effectvol, 100);
				else if(smp_beat >= 0) sound_play_sample(smp_beat, 0, savedata.effectvol,savedata.effectvol, 100);
			}
			else{
				if(e->animation->hitsound>=0) sound_play_sample(e->animation->hitsound, 0, savedata.effectvol,savedata.effectvol, 105 - force);
				else if(smp_beat >= 0) sound_play_sample(smp_beat, 0, savedata.effectvol,savedata.effectvol, 105 - force);
			}
		}

        if(e->remove_on_attack) kill(e);
    }
}



// Update all entities that wish to think or animate in this cycle.
// All loops are separated because "self" might die during a pass.
void update_ents(){
    int f;
    int i;
    int holetype;
    int wall;
    entity *other;

    if(level){
		// Lost ents
		for(i=0; i<MAX_ENTS; i++){
			if(ent_list[i]->exists){
				self = ent_list[i];
				if(self->x < advancex-1000 || self->a<PIT_DEPTH*2) kill(self);
			}
		}

			// Gravity
			for(i=0; i<MAX_ENTS; i++){
				if(ent_list[i]->exists){
					self = ent_list[i];

				if((!freezeall && !self->frozen) || (freezeall && self->animation == self->model->animation[ANI_SPECIAL])){	// Incase an entity is in the air, don't update animations
					if((self->tossv>0 || inair(self)) && self->toss_time <= time){

						other = check_platform(self->x, self->z);

						if(
							other &&
							self->a < other->a &&
							self->a + self->model->height >= other->a + 4 +	(self->z - other->z) &&
							self->hithead == 0
						){
							self->tossv = 0;
							self->hithead = 1;
						}

						self->a += self->tossv;

						if(self->tossv > -6) self->tossv -= 0.1;

						if(self->a <= self->base){
							self->a = self->base;
							self->hithead = 0;
							if(self->animation != self->model->animation[ANI_JUMP]) self->running = 0;	// Incase player was in the air and was running set to not running
						}
						if(self->a < PIT_DEPTH){
							if(!self->takedamage) kill(self);
							else self->takedamage(self,10000,0,ATK_NORMAL);
							continue;
						}

						self->toss_time = time + (GAME_SPEED/100);
					}
					// Check for holes
					if(self->base==0 && !inair(self) && (holetype=checkhole(self->x, self->z))){
						self->base = -1000;
						ent_unlink(self);	// Release grabs
						if(holetype==2){
							// place behind panels
							self->a -= self->z - (PANEL_Z-1);
							self->z = PANEL_Z-1;
						}
					}
				}
			}
		}
	}


    // A.I.
    for(i=0; i<MAX_ENTS; i++){
        if(ent_list[i]->exists){
            self = ent_list[i];
            if(self->nextthink == time){
                if(self->think){
                    self->nextthink = time + THINK_SPEED;
                    self->think();
                }
            }
        }
    }



	// Animation
    for(i=0; i<MAX_ENTS; i++){
        if(ent_list[i]->exists){
            self = ent_list[i];

			if(freezeall){
				if(self->invinctime > 0) self->invinctime += 1;	// Extends invincible time if animations are frozen
				if(self->freezetime > 0) self->freezetime += 1;	// Extends frozen time if animations are frozen
			}

			if(self->invincible && time >= self->invinctime){	// Invincible time has run out, turn off
				self->invincible = 0;
				self->blink = 0;
				self->invinctime = 0;
				self->arrowon = 0;
			}

			if(self->dying && !self->frozen){	// Code for doing dying flash
				if((self->health <= self->per1 && self->health > self->per2 && (time %(GAME_SPEED / 10)) < (GAME_SPEED / 40)) ||
					(self->health <= self->per2)){

					if(self->colourmap == self->model->colourmap[self->dying - 1] || self->health <= 0 || freezeall){
						self->colourmap = self->model->map;
					}
					else ent_set_colourmap(self, self->dying);
				}
			}


			// Used so all entities can have a spawn animation, and then just changes to the idle animation when done

			if(self->animation == self->model->animation[ANI_SPAWN] && !self->animating && !inair(self)) ent_set_anim(self, ANI_IDLE);

			if(
				self->nextanim == time ||
            	(freezeall &&
            	self->type == TYPE_TEXTBOX &&
            	self->model->subtype != SUBTYPE_NOSKIP &&	// Textbox will autoupdate if a valid player presses an action button
            	((player[0].ent &&
            	(player[0].playkeys&(FLAG_JUMP|FLAG_ATTACK|FLAG_SPECIAL) ||
            	player[0].newkeys&(FLAG_JUMP|FLAG_ATTACK|FLAG_SPECIAL))) ||
            	(player[1].ent &&
            	(player[1].playkeys&(FLAG_JUMP|FLAG_ATTACK|FLAG_SPECIAL) ||
            	player[1].newkeys&(FLAG_JUMP|FLAG_ATTACK|FLAG_SPECIAL)))))
            ){	// Now you can display text and cycle through with any jump/attack/special unless SUBTYPE_NOSKIP

                if(freezeall && player[0].ent && player[0].playkeys&(FLAG_JUMP|FLAG_ATTACK|FLAG_SPECIAL))
					player[0].playkeys = player[0].newkeys = 0;	// So the player doesn't execute an attack after skipping
                if(freezeall && player[1].ent && player[1].playkeys&(FLAG_JUMP|FLAG_ATTACK|FLAG_SPECIAL))
                	player[1].playkeys = player[1].newkeys = 0;	// So the player doesn't execute an attack after skipping

                f = self->animpos + self->animating;

                if((unsigned)f >= (unsigned)self->animation->numframes){

                    if(f<0) f = self->animation->numframes-1;
                    else f = 0;

                    if(!self->animation->loop){
                        self->animating = 0;
                        if(self->autokill){
                            kill(self);
                            continue;
                        }
                    }
                }

                if(self->animating){
                    self->nextanim = time + (self->animation->delay[f]);


					// Need to still allow textboxes to update, or players executing a smartbomb that animates

                    if(
						!freezeall ||
						(freezeall &&
                    	(self->type == TYPE_TEXTBOX ||
                    	self->animation == self->model->animation[ANI_SPECIAL]))
                    ){	// Only the one doing the special will update

						if(!self->frozen || time >= self->freezetime){	// Won't update animation if frozen
							if(self->frozen && self->colourmap == self->model->colourmap[self->model->fmap - 1])
								self->colourmap = self->model->map;

							self->frozen = 0;
							self->freezetime = 0;
							self->currentsprite = self->animation->sprite[f];
							self->animpos = f;

							if(self->animation->move[f]){

								if(self->type == TYPE_PLAYER){
							    	if(self->direction) player_trymove(self->animation->move[f], 0);
									else player_trymove(-self->animation->move[f], 0);
								}
								else if(
									(self->type == TYPE_ENEMY &&
									!self->model->subtype) ||
									(self->type == TYPE_ENEMY &&
									self->model->subtype == SUBTYPE_NOTGRAB)
								){

									if(self->direction) enemy_trymove(self->animation->move[f], 0);
									else enemy_trymove(-self->animation->move[f], 0);
								}
								else{
									if(self->direction) self->x += self->animation->move[f];
									else self->x -= self->animation->move[f];
								}
							}

							if(self->animation->soundtoplay>0 && self->animation->soundframe==f) sound_play_sample(self->animation->soundtoplay, 0, savedata.effectvol,savedata.effectvol, 100);

							if(self->animpos == self->animation->jumpframe){

								if(self->animation->moveflag > 0) toss(self, self->animation->moveflag);	// Set custom jumpheight for jumpframes
								else{
									if(self->type == TYPE_PLAYER) toss(self, self->model->jumpheight/2);
									else toss(self, self->model->jumpheight);
								}
						    }

						    if(self->type == TYPE_PLAYER && self->animpos == self->animation->pshotframe){
						        pshot_spawn(self->x, self->z, self->a + self->animation->throwa, self->direction, self->playerindex);
						    }
						    else if(self->type == TYPE_ENEMY && self->animpos == self->animation->throwframe && self->think != enemy_jumpattack){
        						knife_spawn(self->x, self->z, self->a + self->animation->throwa, self->direction, self);
    						}
    						else if(self->type == TYPE_ENEMY && self->animpos == self->animation->shootframe){
    						    fireball_spawn(self->x, self->z, self->a + self->animation->throwa, self->direction, self);
    						}
    						else if(self->type == TYPE_ENEMY && self->animpos == self->animation->tossframe){
    						    bomb_spawn(self->x, self->z, self->a + self->animation->throwa, self->direction, self);
    						}
						}
					}
                }
            }


            // Checks to see if entity is over a wall and or obstacle, and adjusts the base accordingly

			wall = checkwall(self->x, self->z);
			other = check_platform(self->x, self->z);

			if(self->base != -1000){

				if(	// Obstacles have a higher priority incase they are on a wall
					other &&
					other != self &&
					self->type != TYPE_SHOT &&	// Base of pshots should only be changed by seta command
					self->type != TYPE_NONE &&	// Base of flashes should only be changed by seta command
					(self->model->subtype != SUBTYPE_EWEAPON ||
					(self->model->subtype == SUBTYPE_EWEAPON &&
					(self->animation == self->model->animation[ANI_FALL] ||	// Base needs to be adjusted if projectile has been hit
					self->toexplode == 1))) &&	// Base needs to be adjusted if a grenade/bomb
					self->a >= other->animation->platform[other->animpos][5] &&
					(self->type != TYPE_ENEMY ||	// So enemies continue to move through obstacles unless no bbox set
					(self->type == TYPE_ENEMY &&
					(!self->projectile ||
					!other->animation->vulnerable[other->animpos])))
				)

					self->base = (self->animation->seta[self->animpos] >= 0) * self->animation->seta[self->animpos] +
					(other->a + other->animation->platform[other->animpos][5]);

				else if(
					wall >= 0 &&
					self->type != TYPE_SHOT &&
					self->type != TYPE_NONE &&
					(self->model->subtype != SUBTYPE_EWEAPON ||
					(self->model->subtype == SUBTYPE_EWEAPON &&
					(self->animation == self->model->animation[ANI_FALL] ||
					self->toexplode == 1)))
				)

					self->base = (self->animation->seta[self->animpos] >= 0) * self->animation->seta[self->animpos] +
					(self->a >= level->walls[wall][7]) * level->walls[wall][7];

				else if(self->animation->seta[self->animpos] >= 0) self->base = self->animation->seta[self->animpos];
				else if(
					self->type != TYPE_SHOT &&
					self->type != TYPE_NONE &&
					(self->model->subtype != SUBTYPE_EWEAPON ||
					(self->model->subtype == SUBTYPE_EWEAPON &&
					(self->animation == self->model->animation[ANI_FALL] ||
					self->toexplode == 1)))
				)

					self->base = 0;	// No obstacle/wall or seta, so just set to 0

			}


			// Code for when entities move (useful for moving platforms, etc)

			if(other && other != self && other->nextanim == time){
				f = other->animpos + other->animating;
				if((unsigned)f >= (unsigned)other->animation->numframes){
					if(f<0) f = other->animation->numframes-1;
					else f = 0;
				}

				if(self->a <= other->a + other->animation->platform[other->animpos][5] &&
				self->a + self->model->height >= other->a){
					if(self->type == TYPE_PLAYER) player_trymove(other->animation->move[f] * -1, 0);
					else enemy_trymove(other->animation->move[f] * -1, 0);
				}
			}
        }
    }


    // Collission detection (for a part of the entities)
    for(i=(time&1); i<MAX_ENTS; i+=2){
        if(ent_list[i]->exists){
            self = ent_list[i];

            // Attack stuff
            f = self->animpos;
            if((self->attacking || self->projectile > 0) && self->animation->attack_force[f] && !self->frozen){	// Can't hit an opponent if you are frozen
                do_attack(self);
            }
            else self->attack_id = 0;
        }
    }


    // Update displayed health
    for(i=0; i<MAX_ENTS; i++){
        if(ent_list[i]->exists){
            self = ent_list[i];

            if(self->oldhealth < self->health) self->oldhealth++;
            else if(self->oldhealth > self->health) self->oldhealth--;
        }
    }

}


entity * find_ent_here(entity *exclude, float x, float z, int types);


void display_ents(){
    unsigned f;
    int i, x, y, z, wall;
    entity *e;
    entity *other;
    int layer = 0;

    int use_mirror = (level && level->mirror);


	// Loop through, checking to see if there are multiple entities on the same "z".  If so, offsets it 1 per entity
	// so there are not any that will overlap, resulting in "flickering" back and forth when updating

	for(x = PLAYER_MAX_Z; x >= PLAYER_MIN_Z; x--){
		for(i = 0; i < MAX_ENTS; i++){
			if(ent_list[i]->exists && ent_list[i]->z == x && !ent_list[i]->model->setlayer){

				ent_list[i]->layer = layer;
				layer++;
			}
		}

		if(layer > 0) layer--;
	}

    for(i=0; i<MAX_ENTS; i++){
		if(ent_list[i]->exists){

			e = ent_list[i];

			if(freezeall || !(e->blink && (time%(GAME_SPEED/10))<(GAME_SPEED/20))){	// If special is being executed, display all entities regardless
            	f = e->currentsprite;

           		other = check_platform(e->x, e->z);
           		wall = checkwall(e->x, e->z);

            	if(f<sprites_loaded){

					// var "z" takes into account whether it has a setlayer set, whether there are other entities on
					// the same "z", in which case there is a layer offset, whether the entity is on an obstacle, and
					// whether the entity is grabbing someone and has grabback set

					z = e->z * (!e->model->setlayer) + e->model->setlayer - e->layer;	// Setlayer takes precedence

					if(other && other != e && e->a >= other->a + other->animation->platform[other->animpos][5])
						z += other->z + other->layer - e->z + e->layer + 1;

					if(e->link && e->animation == e->model->animation[ANI_GRAB] && e->model->grabback)
						z -= e->link->layer + 2;

					if(e->colourmap){
						spriteq_add(e->x - advancex, e->z-e->a + gfx_y_offset, z, sprites[e->direction&1][f],
						SFX_REMAP, e->colourmap);
					}
					else if(e->screen){
						spriteq_add(e->x - advancex, e->z-e->a + gfx_y_offset, z, sprites[e->direction&1][f],
						SFX_BLEND, lut_screen);
					}
					else{
						spriteq_add(e->x - advancex, e->z-e->a + gfx_y_offset, z, sprites[e->direction&1][f], 0, NULL);
					}

				}

				if(use_mirror){

					z = (e->z - PANEL_Z) - e->layer;

					if(other && other != e && e->a >= other->a + other->animation->platform[other->animpos][5]){
						z += PANEL_Z - other->z - other->layer + PANEL_Z - e->z - e->layer + PANEL_Z + 15 + 1;
					}
					else z = PANEL_Z + 15 - z;

					if(e->link && e->animation == e->model->animation[ANI_GRAB] && e->model->grabback){
						z -= e->link->layer - 2;
					}

					if(e->colourmap){
						spriteq_add(e->x-advancex, (PLAYER_MIN_Z-10)+(PLAYER_MIN_Z - e->z)-e->a+gfx_y_offset, z,
						sprites[e->direction&1][f], SFX_REMAP, e->colourmap);
					}
					else{
						spriteq_add(e->x-advancex, (PLAYER_MIN_Z-10)+(PLAYER_MIN_Z - e->z)-e->a+gfx_y_offset, z,
						sprites[e->direction&1][f], 0, NULL);
					}
				}

				if(e->model->shadow && e->a>=0 && !checkhole(e->x, e->z) && (!e->model->aironly || (e->model->aironly && inair(e)))){

					if(other){
						spriteq_add(e->x - advancex, e->z - e->base + gfx_y_offset, other->z + 1,
						sprites[e->direction&1][shadowsprites[e->model->shadow-1]], SFX_BLEND, lut_mul);
					}
					else{
						spriteq_add(e->x - advancex, e->z - e->base + gfx_y_offset, SHADOW_Z,
						sprites[e->direction&1][shadowsprites[e->model->shadow-1]], SFX_BLEND, lut_mul);
					}
				}
			}

			if(e->arrowon){	// Display the players image while invincible to indicate player number

				if(e->model->parrow[e->playerindex][0] && e->invincible)

					spriteq_add(e->x - advancex + e->model->parrow[e->playerindex][1], e->z-e->a+gfx_y_offset +
					e->model->parrow[e->playerindex][2], e->z, sprites[1][e->model->parrow[e->playerindex][0]], 0, NULL);
			}
		}
	}
}



void toss(entity *ent, float lift){
    ent->toss_time = time + 1;
    ent->tossv = lift;
    ent->a += 0.5;		// Get some altitude (needed for checks)
}



entity * findent(int types){
    int i;
    for(i=0; i<MAX_ENTS; i++){
        if(ent_list[i]->exists && (ent_list[i]->type & types) &&
        (ent_list[i]->model->nodieblink != 3 ||
        ((ent_list[i]->model->nodieblink == 3 &&
		(ent_list[i]->health > 0 ||
        (ent_list[i]->health <= 0 && (ent_list[i]->animating || inair(ent_list[i])))))))){
            return ent_list[i];
        }
    }
    return NULL;
}



int count_ents(int types){
    int i;
    int count = 0;
    for(i=0; i<MAX_ENTS; i++){
        if(ent_list[i]->exists && (ent_list[i]->type & types) &&
        (ent_list[i]->model->nodieblink != 3 ||
        ((ent_list[i]->model->nodieblink == 3 &&
		(ent_list[i]->health > 0 ||
        (ent_list[i]->health <= 0 && (ent_list[i]->animating || inair(ent_list[i])))))))){
            ++count;
        }
    }
    return count;
}



entity * find_ent_here(entity *exclude, float x, float z, int types){
    int i;
    for(i=0; i<MAX_ENTS; i++){
        if(		ent_list[i]->exists
                && ent_list[i] != exclude
                && (ent_list[i]->type & types)
                && diff(ent_list[i]->x,x)<(self->model->grabdistance*0.83333)
                && diff(ent_list[i]->z,z)<(self->model->grabdistance/3)
                && ent_list[i]->animation->vulnerable[ent_list[i]->animpos]
          ){
            return ent_list[i];
        }
    }
    return NULL;
}



void make_quake(int amount){
    if(amount>4) amount = 4;
    if(quake < amount) quake = amount;
}


// ---------------------------------- A.I. ------------------------------------

void suicide(void);

//void player_think(void);
void player_jump(void);
void player_land(void);
void player_grab(void);
void player_grabattack(void);
void player_grabbed(void);
void player_throw(void);
void player_pain(void);
void player_fall(void);
void player_rise(void);
void player_blink(void);
void dojump(int speedmul);

void enemy_drop(void);		// Enter field
void enemy_prepare(void);
void enemy_runoff(void);
void enemy_rise(void);
void enemy_lie(void);
void enemy_grabbed(void);
void enemy_throw(void);
void enemy_grab(void);
void enemy_takedamage(entity * other, int force, int drop, int type);	// Added so enemy projectiles can be knocked down

void weapon_think(void);
void arrow_fly(void);  // 7-1-2005  new types here
void biker_drive(void); // 7-1-2005  new types here
void biker_takedamage(entity *other, int force, int drop, int type); // 7-1-2005  new types here



void player_weapon(int wpnum){
    entity *tempent;
    s_model * newmodel;
    int w;
	int colormap;

    if(!player[self->playerindex].ent) return;

    tempent = self;
	colormap = player[self->playerindex].colourmap;

    if((wpnum > 0) && (wpnum < MAX_WEAPONS)) newmodel = find_model(player[self->playerindex].model->weapon[wpnum-1]);
    else newmodel = find_model(self->name);

    if(!newmodel->health) newmodel->health = tempent->model->health;
    if(!newmodel->icon) newmodel->icon = tempent->model->icon;
    if(!newmodel->iconpain) newmodel->iconpain = tempent->model->iconpain;
    if(!newmodel->iconget) newmodel->iconget = tempent->model->iconget;
    if(!newmodel->icondie) newmodel->icondie = tempent->model->icondie;
    if(!newmodel->shadow) newmodel->shadow = tempent->model->shadow;
    if(!newmodel->diesound) newmodel->diesound = tempent->model->diesound;

    if(newmodel->playshot) strncpy(player[self->playerindex].model->playshot, newmodel->playshot, MAX_NAME_LEN);
    else strncpy(player[self->playerindex].model->playshot, NULL, MAX_NAME_LEN);

    for(w=0; w<MAX_ANIS; w++){
        if(!newmodel->animation[w]) newmodel->animation[w] = player[self->playerindex].animation[w];
    }

    for(w=0; w<MAX_WEAPONS; w++) strncpy(newmodel->weapon[w], tempent->model->weapon[w], MAX_NAME_LEN);

    ent_set_model(player[self->playerindex].ent, newmodel->name);
    strncpy(player[self->playerindex].ent->name, tempent->name, MAX_NAME_LEN);
    player[self->playerindex].ent->type = tempent->type;
    player[self->playerindex].ent->health = tempent->health;
    player[self->playerindex].ent->maxhealth = tempent->maxhealth;
    player[self->playerindex].ent->oldhealth = tempent->oldhealth;
	player[self->playerindex].colourmap = colormap;
	ent_set_colourmap(player[self->playerindex].ent, player[self->playerindex].colourmap);
}



// for player projectiles   7-1-2005 start
void range_think(void){
	int wall;

	if(freezeall) return;	// Don't update animation if special is being executed
    if(self->x < advancex - 80 || self->x > advancex + 400){
        kill(self);
        return;
    }
    if(self->direction) self->x += self->model->speed;	// Now projectiles can have custom speed
    else self->x -= self->model->speed;

	if(self->model->animation[ANI_FALL]){	// Added so projectiles bounce off blocked exits
		if((level->exit_blocked && self->x > level->width-30-(PLAYER_MAX_Z-self->z)) ||
		((wall = checkwall(self->x, self->z)) >= 0 && self->a < level->walls[wall][7])){
			self->attacking = 0;
			self->health -= 100000;
			self->projectile = 0;
			self->think = enemy_fall;
			self->damage_on_landing = 0;
			self->base = 0;
			toss(self, 2.5 + randf(1));
			ent_reset_anim(self, ANI_FALL);
		}
	}

    self->nextthink = time + THINK_SPEED / 2;
}



void pshot_spawn(float x, float z, float a, int direction, int pi){
    entity *e;

	e = spawn(x, z, a, player[pi].ent->animation->custpshot);
    if(e==NULL) return;

    e->attacking = 1;
    e->direction = direction;
    e->think = range_think;
    e->remove_on_attack = e->model->remove;
    e->playerindex = pi;
	e->base = a;
    if(!e->model->speed) e->model->speed = 2;	// If not already set, go ahead and set it to 2
}
// for player projectiles   7-1-2005 end



void suicide(void){

	if(freezeall){	// Extend time if a special is being executed
		self->nextthink = time + GAME_SPEED * 3;
		return;
	}
    level_completed |= self->boss;
		kill(self);
}



// Re-enter playfield
// Used by player_fall and player_takedamage
void player_die(){
	int wall;
	entity *other;

	if (!livescheat) --player[self->playerindex].lives;

    player[self->playerindex].weapon = 0;
    player_weapon(player[self->playerindex].weapon);
    player[self->playerindex].iconmode = 0;   // 20-1-2005 New icons here

    if(player[self->playerindex].lives <= 0){
        kill(self);
        player[self->playerindex].iconmode = 0;   // 20-1-2005 New icons here
        player[self->playerindex].ent = NULL;
        if(!player[0].ent && !player[1].ent){
            timeleft = 10 * COUNTER_SPEED;
            if((!noshare && credits < 1) || (noshare && player[0].credits < 1 && player[1].credits < 1)) timeleft = COUNTER_SPEED/2;
        }
        return;
    }

    ent_unlink(self);
    self->blink = 0;

	if(level->scrolldir == SCROLL_LEFT){
		if(level->spawn[self->playerindex][0]) self->x = advancex + (320 - level->spawn[self->playerindex][0]);
		else self->x = advancex + 290;
		self->direction = 0;
	}
	else{
		if(level->spawn[self->playerindex][0]) self->x = advancex + level->spawn[self->playerindex][0];
		else self->x = advancex + 20;
		self->direction = 1;
	}

	if(level->spawn[self->playerindex][1]){
		if(!checkhole(self->x, PLAYER_MIN_Z + level->spawn[self->playerindex][1])){
			self->z = PLAYER_MIN_Z + level->spawn[self->playerindex][1];
		}
		else if(!checkhole(self->x, (level->holes[holez][1] - level->holes[holez][6] + PLAYER_MIN_Z) / 2)){
			self->z = (level->holes[holez][1] - level->holes[holez][6] + PLAYER_MIN_Z) / 2;
		}
		else self->z = (level->holes[holez][1] + PLAYER_MAX_Z) / 2;
	}
	else self->z = ((PLAYER_MIN_Z+PLAYER_MAX_Z)/2) - 10;

	if(checkhole(self->x, self->z)){
		self->z = (level->holes[holez][1] * 2 - level->holes[holez][6]) / 2;

		if(level->scrolldir == SCROLL_LEFT)
			self->x = (advancex * 2 + level->holes[holez][2] + level->holes[holez][3]) / 2 + 5;
		else
			self->x = (advancex * 2 + level->holes[holez][4] + level->holes[holez][5]) / 2 + 5;
	}

	wall = checkwall(self->x, self->z);
	other = check_platform(self->x, self->z);

	if(other){
		if(level->spawn[self->playerindex][2] > other->a + other->animation->platform[other->animpos][5]){
			self->a = level->spawn[self->playerindex][2];
		}
		else self->a = other->a + other->animation->platform[other->animpos][5];

		self->base = other->a + other->animation->platform[other->animpos][5];
	}
	else{
		if(level->spawn[self->playerindex][2] > level->walls[wall][7]) self->a = level->spawn[self->playerindex][2];
		else self->a = level->walls[wall][7];

		self->base = level->walls[wall][7];
	}

	self->tossv = 0;	// Added so players don't spawn at different rates
	self->xdir = 0;	// Added so players don't spawn moving
	self->running = 0;
	self->jumping = 1;
	self->attacking = 0;
	self->projectile = 0;
	self->blasted = 0;
	self->frozen = 0;	// Added so players don't spawn frozen

	if(self->model->animation[ANI_SPAWN]) ent_set_anim(self, ANI_SPAWN); // use new playerselect spawn anim
	else ent_set_anim(self, ANI_JUMP);

	self->health = self->maxhealth;
	self->think = player_jump;

	if(!nodropen) drop_all_enemies();   //27-12-2004  If drop enemies is on, drop all enemies

	if(self->model->makeinv > 0){	// If spawn invincible, sets invincible for specified time
		self->invincible = 1;
		self->blink = 1;
		self->invinctime = time + self->model->makeinv;
		self->arrowon = 1;
	}

	timeleft = level->settime * COUNTER_SPEED;	// Feb 24, 2005 - This line moved here to set custom time

}



int player_trymove(float xdir, float zdir){
    entity *other;
    entity *endlevel;
    int wall;
    float coef1;
    float coef2;

	if((other = find_ent_here(self, self->x, self->z, TYPE_ENDLEVEL)) && self->a -
	(self->animation->seta[self->animpos] >= 0) * self->animation->seta[self->animpos] == other->a){

		if(!reached[0] && !reached[1]) addscore(self->playerindex, other->score);

		reached[self->playerindex] = 1;

		if(
			!other->model->subtype ||
			(other->model->subtype == SUBTYPE_BOTH &&
			player[0].ent && player[1].ent &&
			reached[0] == 1 && reached[1] == 1) ||
			(!player[0].ent || !player[1].ent)
		)
			level_completed = 1;
	}

	if(xdir && (other = find_ent_here(self, self->x + xdir*2, self->z, TYPE_OBSTACLE))){
		if(!other->animation->platform[other->animpos][5]){
			if(xdir>0 ? other->x>self->x : other->x<self->x) xdir = 0;
		}
	}
	if(zdir && (other=find_ent_here(self, self->x, self->z + zdir*2, TYPE_OBSTACLE))){
		if(!other->animation->platform[other->animpos][5]){
			if(zdir>0 ? other->z>self->z : other->z<self->z) zdir = 0;
		}
	}

	// Check for obstacles with platform code and adjust base accordingly
	if(
		((!self->animation->move[self->animpos] &&
		(other = check_platform(self->x + xdir*10, self->z + zdir*10))) ||
		(self->animation->move[self->animpos] &&
		(other = check_platform(self->x + xdir, self->z + zdir)))) &&
		self->a < other->a + other->animation->platform[other->animpos][5] &&
		self->a + self->model->height >= other->a
	){

		if(xdir>0 ? other->x>self->x : other->x<self->x) xdir = 0;
		if(zdir>0 ? other->z>self->z : other->z<self->z) zdir = 0;

	}

// Removed unless bugs arise

//	else if((other = check_platform(self->x, self->z)) &&
//	self->a >= other->a + other->animation->platform[other->animpos][5]){

//		self->base = (self->animation->seta[self->animpos] >= 0) *
//		self->animation->seta[self->animpos] + other->a + other->animation->platform[other->animpos][5];

//	}
//	else if(self->base != -1000) self->base = (self->animation->seta[self->animpos] >= 0) * self->animation->seta[self->animpos];

	// Check for walls and adjust base accordingly

	if((wall = checkwall(self->x + xdir*4, self->z + zdir*4)) >= 0 && self->a < level->walls[wall][7]){

		coef1 = (level->walls[wall][1] - self->z) * ((level->walls[wall][2] - level->walls[wall][3]) / level->walls[wall][6]);
		coef2 = (level->walls[wall][1] - self->z) * ((level->walls[wall][4] - level->walls[wall][5]) / level->walls[wall][6]);

		if(self->z > level->walls[wall][1] || self->z < level->walls[wall][1] - level->walls[wall][6]){
			zdir = 0;
		}
		else{

			if(self->x < (level->walls[wall][0] * 2 + level->walls[wall][3] + level->walls[wall][5]) / 2 && self->x > level->walls[wall][0] + level->walls[wall][3] + coef1){
				self->x = level->walls[wall][0] + level->walls[wall][3] + coef1;
			}
			else if(self->x > (level->walls[wall][0] * 2 + level->walls[wall][3] + level->walls[wall][5]) / 2 && self->x < level->walls[wall][0] + level->walls[wall][5] + coef2){
				self->x = level->walls[wall][0] + level->walls[wall][5] + coef2;
			}

			xdir = 0;
		}
	}
//	else if(
//		(other &&
//		self->a < other->a + other->animation->platform[other->animpos][5]) ||
//		(!other &&
//		self->a >= level->walls[wall][7])
//	){

//		if(self->base != -1000) self->base = (self->animation->seta[self->animpos] >= 0) * self->animation->seta[self->animpos] + level->walls[wall][7];
//	}





    // Can't grab anyone while (they're) in mid-air...
    // And we can't grab anyone behind us???
    //  && (self->direction ? other->x>self->x : other->x<self->x)

    if(
		self->health > 0 &&
		!(inair(self) ||
		self->jumping ||
		self->animation->move[self->animpos] > 0 ||
		self->animation == self->model->animation[ANI_DODGE]) &&
    	(other = find_ent_here(self, self->x + xdir, self->z + zdir, (TYPE_ENEMY | (TYPE_PLAYER && !savedata.mode)))) &&
    	!(other->link ||
    	inair(other) ||
    	other->nograb ||
    	other->invincible) &&
    	other->a == self->a &&
    	(!find_ent_here(self, other->x, other->z, TYPE_ENDLEVEL) ||
    	((endlevel = find_ent_here(self, other->x, other->z, TYPE_ENDLEVEL)) &&
    	other->a - (other->animation->seta[other->animpos] >= 0) * other->animation->seta[other->animpos] != endlevel->a))
    ){

        // Reposition both on average height and such
        zdir = (self->z + other->z) / 2 - (self->z == PLAYER_MAX_Z || other->z == PLAYER_MAX_Z);
		other->z = zdir;
		self->z = zdir+1;	// Grabber up front

		if(
			(wall == -1 ||
			(wall >= 0 &&
			self->a != level->walls[wall][7])) &&
			(checkwall(self->x - self->model->grabdistance/2, self->z) >=0 ||
			checkwall(other->x - self->model->grabdistance/2, other->z) >= 0)
		){

			if(self->x < other->x) other->x = self->x + self->model->grabdistance;
			else self->x = other->x + self->model->grabdistance;
		}
		else if(
			(wall == -1 ||
			(wall >= 0 && self->a != level->walls[wall][7])) &&
			(checkwall(self->x + self->model->grabdistance/2, self->z) >= 0 ||
			checkwall(other->x + self->model->grabdistance/2, other->z) >= 0)
		){

			if(self->x > other->x) other->x = self->x - self->model->grabdistance;
			else self->x = other->x - self->model->grabdistance;
		}
		else{
	        xdir = (self->x + other->x) / 2;

	        if(self->x < other->x){
	            self->x = xdir - (self->model->grabdistance/2);  // 30-12-2004 changed GRAB_DIST to grabdistance per player
	            other->x = xdir + (self->model->grabdistance/2);// 30-12-2004 changed GRAB_DIST to grabdistance per player
	        }
	        else{
	            self->x = xdir + (self->model->grabdistance/2);// 30-12-2004 changed GRAB_DIST to grabdistance per player
	            other->x = xdir - (self->model->grabdistance/2);// 30-12-2004 changed GRAB_DIST to grabdistance per player
	        }
		}

		self->direction = (self->x < other->x);

        ents_link(self, other);
        player[self->playerindex].opponent = other;
        other->attacking = 0;

        self->combostep = self->combostep2 = self->combostep3 = self->combostep4 = 0;	// Reset combosteps for grabbing purposes
        self->think = player_grab;

        if(other->type==TYPE_ENEMY){
            other->think = enemy_grabbed;
            other->playerindex = self->playerindex;
            other->stalltime = time + GRAB_STALL;
        }
        if(other->type==TYPE_PLAYER){
            other->think = player_grabbed;
            player[other->playerindex].opponent = self;
        }

		if(other->model->animation[ANI_GRABBED]) ent_set_anim(other, ANI_GRABBED);	// New grabbed animation
		else ent_set_anim(other, ANI_PAIN);
        ent_set_anim(self, ANI_GRAB);

        return 0;
    }

    self->x += xdir;
    self->z += zdir;


    if(self->z < PLAYER_MIN_Z) self->z = PLAYER_MIN_Z;
    if(self->z > PLAYER_MAX_Z) self->z = PLAYER_MAX_Z;
    if(self->x < advancex+10) self->x = advancex+10;
    if(self->x > advancex+310) self->x = advancex+310;


    // End of level is blocked?
    if(level->exit_blocked){
        if(self->x > level->width-30-(PLAYER_MAX_Z-self->z)){
			self->x = level->width-30-(PLAYER_MAX_Z-self->z);
		}
    }

    return 1;
}



// Check keys for special move. Used several times, so I func'd it.
// 1-10-05 changed self->health>6 to self->health > self->model->animation[ANI_SPECIAL]->energycost

int player_check_special(){
    if(self->health > self->model->animation[ANI_SPECIAL]->energycost &&
    self->model->animation[ANI_SPECIAL] && !level->nospecial){

    	if((!ajspecial || (ajspecial && !self->model->animation[ANI_BLOCK])) &&
    	(player[self->playerindex].playkeys & FLAG_SPECIAL)){
			player[self->playerindex].playkeys -= FLAG_SPECIAL;
		}
    	else if(ajspecial && ((player[self->playerindex].playkeys & FLAG_JUMP) &&
    	(player[self->playerindex].keys & FLAG_ATTACK))){	// Now a j can be used for specials as well
			player[self->playerindex].playkeys -= FLAG_JUMP;
			self->stalltime = 0;
		}
		else return 0;

		self->attacking = 1;
		self->combostep = 0;
		ent_unlink(self);
		player[self->playerindex].iconmode = 0;	// Added incase smartbomb overrides pain/grabbed animation

		if(self->model->specpower && !self->model->dofreeze)
			smart_bomb(self, self->model->specpower, self->model->spectype, self->model->speclen);	// If specpower is used, will execute a smartbomb

		self->running = 0;	// If special is executed while running, ceases to run
		ent_set_anim(self, ANI_SPECIAL);

		if(self->model->dofreeze) freezeall = 1;	// Freezes the animations of all enemies/players while special is executed
		if(!nocost && !healthcheat) self->health -= self->model->animation[ANI_SPECIAL]->energycost;

		return 1;
    }
    return 0;
}



void player_throw(void){
    if(self->animating) return;
    self->think = player_think;
    ent_set_anim(self, ANI_IDLE);
}



void player_pain(void){
    if(player_check_special()) {
        self->think = player_think;
        return;
    }

    if(self->animating) {
        player[self->playerindex].iconmode = 1;   // 20-1-2005 New icons here
        return;
    }
    player[self->playerindex].iconmode = 0;    // 20-1-2005 New icons here

    if(self->link){
        self->think = player_grabbed;
        if(self->model->animation[ANI_GRABBED]) ent_set_anim(self, ANI_GRABBED);	// New grabbed animation
        else ent_set_anim(self, ANI_PAIN);
    }
    else{
        self->think = player_think;
        ent_set_anim(self, ANI_IDLE);
    }
}



void player_land(void){
    if(self->animating) return;
    self->think = player_think;
    ent_set_anim(self, ANI_IDLE);
}



void player_fall(void){
	if(freezeall) return;	// Added incase player is in midair
    player[self->playerindex].iconmode=1;  // 20-1-2005 New icons here

    // Still falling?
	self->stalltime = 0;	// Disable the attack3 if falling

	if(self->frozen){	// If player is falling, will become unfrozen
		if(self->colourmap == self->model->colourmap[self->model->fmap - 1]) self->colourmap = self->model->map;
		self->frozen = 0;
		self->freezetime = 0;
	}

    if(inair(self)){
        if(self->projectile){
            if(self->direction) player_trymove(-2.5, 0);
            else player_trymove(2.5, 0);
        }
        else if(self->projectile == 2){
			if(self->direction) player_trymove(-self->model->throwdist, 0);
			else player_trymove(self->model->throwdist, 0);
		}
        else{
            if(self->direction) player_trymove(-1, 0);
            else player_trymove(1, 0);
        }
        return;
    }


    if(self->projectile > 0){
        self->projectile = 0;

        if(!self->blasted){
            if(
				(autoland == 1 &&
				self->damage_on_landing == -1) ||
            	(autoland != 2 &&
            	(player[self->playerindex].keys & (FLAG_MOVEUP|FLAG_JUMP)) == (FLAG_MOVEUP|FLAG_JUMP))
            ){	// Added autoland option for landing

                player[self->playerindex].playkeys ^= (FLAG_MOVEUP|FLAG_JUMP);
                if(self->model->animation[ANI_LAND]){
                    ent_set_anim(self, ANI_LAND);
                    player[self->playerindex].iconmode=0;	// Safely landed, set to regular icon
                    self->think = player_land;
                    self->direction = !self->direction;
                }
                else{
                    ent_set_anim(self, ANI_IDLE);
                    self->think = player_think;
                }
                return;
            }
        }
        if(!level->nohurt && !healthcheat) self->health -= self->damage_on_landing;	// Added so can't die in bonus levels or with healthcheat enabled
        self->damage_on_landing = 0;
        if(!self->model->noquake) make_quake(4);	// Only shake if noquake not set
    }

    if(self->animation == self->model->animation[ANI_SHOCK] || self->animation == self->model->animation[ANI_BURN]){ // New animations
		ent_set_anim(self, ANI_FALL);	// Set to normal fall animation and change to last frame
		self->currentsprite = self->animation->sprite[self->animation->numframes - 1];
		self->animpos = self->animation->numframes - 1;
	}

    // Hard landing? Quake and bounce!
    if(self->tossv < -2){
        if(!self->model->noquake) make_quake(2);	// Only shake if noquake not set

        if(smp_fall >= 0) sound_play_sample(smp_fall, 0, savedata.effectvol,savedata.effectvol, 100);

        toss(self, self->model->jumpheight/4);					// 27-12-2004
        return;
    }

    if(self->health <= 0){
        if(self->model->falldie) ent_set_anim(self, ANI_DIE);  //6-2-2005 fall and then play die animation?

        player[self->playerindex].iconmode=2;   //20-1-2005   New icons

        if(self->model->diesound>=0) sound_play_sample(self->model->diesound, 0, savedata.effectvol,savedata.effectvol, 100); // 29-12-2004 Diesound fixed for player

		if(!self->model->nodieblink || (self->model->nodieblink == 1 && !self->animating)){	// Now have the option to blink or not			self->think = player_blink;

			self->think = player_blink;
			self->blink = 1;
			self->stalltime = time + GAME_SPEED * 2;
		}
		else if(self->model->nodieblink == 2 && !self->animating) player_die();

        return;
    }

	self->think = player_rise;
	ent_set_anim(self, ANI_RISE);

}



void player_blink(void){
	if(freezeall){
		self->stalltime = time + GAME_SPEED;
		return;
	}
    if(time >= self->stalltime) player_die();
}



void player_rise(void){
    if(self->animating) return;
    self->pain_time = time + (GAME_SPEED/5);
    self->think = player_think;
    player[self->playerindex].iconmode=0;  // 20-1-2005 New icons here
    ent_set_anim(self, ANI_IDLE);
    player_think;
}



void player_grabbed(void){
	self->running = 0;	// Quits running if grabbed by opponent
    if(player_check_special()) {
    	self->think = player_think;
	    return;
	}

    // Just check if we're still grabbed...
    if(self->link) return;

    ent_set_anim(self, ANI_IDLE);
    self->think = player_think;
}



void player_grab(void){
    entity * other = self->link;

	self->running = 0;	// Quits running if grabbing an opponent

    if(player_check_special()) return;

    if(other == NULL){
        self->think = player_think;
        return;
    }

    if(self->a != other->a){
		ent_unlink(self);
		self->think = player_think;
		return;
	}

    if(self->direction ?
            (player[self->playerindex].keys & FLAG_MOVELEFT) :
            (player[self->playerindex].keys & FLAG_MOVERIGHT)){

        if(time > self->releasetime){
            // Release
            ent_unlink(self);
            self->think = player_think;
        }
 	}
    else self->releasetime = time + (GAME_SPEED/2);


    if((player[self->playerindex].playkeys & FLAG_ATTACK) &&
            (self->direction ?
             (player[self->playerindex].keys & FLAG_MOVELEFT) :
             (player[self->playerindex].keys & FLAG_MOVERIGHT))){

        player[self->playerindex].playkeys -= FLAG_ATTACK;

		if(other->model->throwheight) toss(other, other->model->throwheight);
        else toss(other, self->model->jumpheight);

        other->direction = self->direction;
        other->projectile = 2;

        if(other->type == TYPE_ENEMY){
            other->think = enemy_fall;
            other->damage_on_landing = player[other->playerindex].ent->model->throwdamage;
        }

        if(other->type == TYPE_PLAYER){
            other->think = player_fall;

			if(autoland == 1) other->damage_on_landing = -1;	// Sets damage to -1 so it can tell player was thrown by a player
	        else other->damage_on_landing = (player[other->playerindex].ent->model->throwdamage/2);
        }

		ent_set_anim(other, ANI_FALL);
        ent_unlink(self);
        ent_set_anim(self, ANI_THROW);
        self->think = player_throw;
    }

    // New forward attack grab animation

	else if((player[self->playerindex].playkeys & FLAG_ATTACK) &&
			self->model->animation[ANI_GRABFORWARD] &&
			(!self->direction ?
			(player[self->playerindex].keys & FLAG_MOVELEFT) :
			(player[self->playerindex].keys & FLAG_MOVERIGHT))){

		player[self->playerindex].playkeys -= FLAG_ATTACK;
		self->attacking = 1;

		++self->combostep2;
        if(self->combostep2 < 3){
            ent_set_anim(self, ANI_GRABFORWARD);
            self->think = player_grabattack;
        }
        else{
            if(self->model->animation[ANI_GRABFORWARD2]) ent_set_anim(self, ANI_GRABFORWARD2);
            else ent_set_anim(self, ANI_ATTACK3);

            self->think = player_think;
            ent_unlink(self);
            self->combostep = 0;
        }

	}
	else if((player[self->playerindex].playkeys & FLAG_ATTACK) &&
			self->model->animation[ANI_GRABUP] && (player[self->playerindex].keys & FLAG_MOVEUP)){

		player[self->playerindex].playkeys -= FLAG_ATTACK;
		self->attacking = 1;

		++self->combostep3;
        if(self->combostep3 < 3){
            ent_set_anim(self, ANI_GRABUP);
            self->think = player_grabattack;
        }
        else{
            if(self->model->animation[ANI_GRABUP2]) ent_set_anim(self, ANI_GRABUP2);
            else ent_set_anim(self, ANI_ATTACK3);

            self->think = player_think;
            ent_unlink(self);
            self->combostep = 0;
        }

	}
	else if((player[self->playerindex].playkeys & FLAG_ATTACK) &&
			self->model->animation[ANI_GRABDOWN] && (player[self->playerindex].keys & FLAG_MOVEDOWN)){

		player[self->playerindex].playkeys -= FLAG_ATTACK;
		self->attacking = 1;

		++self->combostep4;
        if(self->combostep4 < 3){
            ent_set_anim(self, ANI_GRABDOWN);
            self->think = player_grabattack;
        }
        else{
            if(self->model->animation[ANI_GRABDOWN2]) ent_set_anim(self, ANI_GRABDOWN2);
            else ent_set_anim(self, ANI_ATTACK3);

            self->think = player_think;
            ent_unlink(self);
            self->combostep = 0;
        }

	}
    else if((player[self->playerindex].playkeys & FLAG_ATTACK) &&
            self->model->animation[ANI_GRABATTACK]){

        player[self->playerindex].playkeys -= FLAG_ATTACK;
        self->attacking = 1;

        ++self->combostep;
        if(self->combostep < 3){
            ent_set_anim(self, ANI_GRABATTACK);
            self->think = player_grabattack;
        }
        else{
            if(self->model->animation[ANI_GRABATTACK2]) ent_set_anim(self, ANI_GRABATTACK2);
            else ent_set_anim(self, ANI_ATTACK3);

            self->think = player_think;
            ent_unlink(self);
            self->combostep = 0;
        }

    }
    else if(player[self->playerindex].playkeys & (FLAG_JUMP|FLAG_ATTACK)){
        player[self->playerindex].playkeys -= player[self->playerindex].playkeys&(FLAG_JUMP|FLAG_ATTACK);
        self->attacking = 1;
        self->think = player_think;
        ent_unlink(self);
        self->combostep = 0;

		// Perform final blow
		if(self->model->animation[ANI_GRABATTACK2]) ent_set_anim(self, ANI_GRABATTACK2);
		else if(self->model->animation[ANI_ATTACK3]) ent_set_anim(self, ANI_ATTACK3);
		else{
			dojump(1);
			return;
		}
    }
}



void player_grabattack(void){

	if(self->animating) return;

    self->attacking = 0;

    if(self->link){
        ent_set_anim(self, ANI_GRAB);
        self->think = player_grab;
    }
    else{
        ent_set_anim(self, ANI_IDLE);
        self->think = player_think;
    }
}



void player_jump(){
	if(freezeall) return;

    player_trymove(self->xdir, 0);

    if(player[self->playerindex].playkeys & FLAG_ATTACK){
        player[self->playerindex].playkeys -= FLAG_ATTACK;
        self->attacking = 1;

        if((player[self->playerindex].playkeys & FLAG_MOVEDOWN) && self->model->animation[ANI_JUMPATTACK2]) ent_set_anim(self, ANI_JUMPATTACK2);
        else if((player[self->playerindex].playkeys & FLAG_MOVEUP) && self->model->animation[ANI_JUMPATTACK3]) ent_set_anim(self, ANI_JUMPATTACK3);
        else if(self->model->animation[ANI_RUNJUMPATTACK] && self->running) ent_set_anim(self, ANI_RUNJUMPATTACK);	// Added so an extra strong jump attack can be executed
        else if(self->model->animation[ANI_JUMPFORWARD] && self->xdir != 0) ent_set_anim(self, ANI_JUMPFORWARD);	// If moving and set, do this attack
        else if(self->model->animation[ANI_JUMPATTACK]) ent_set_anim(self, ANI_JUMPATTACK);
    }

    if(!inair(self)){
        self->tossv = 0;
        self->a = self->base;

        if(!self->model->runhold || !(player[self->playerindex].keys & FLAG_MOVELEFT || player[self->playerindex].keys & FLAG_MOVERIGHT)) self->running = 0;	// So players can continue to run if holding down forward when landing
        self->jumping = 0;
        self->attacking = 0;
        ent_set_anim(self, ANI_IDLE);
        self->think = player_think;
        self->stalltime = 0;
    }
}



void dojump(int speedmul){

    if(smp_jump >= 0) sound_play_sample(smp_jump, 0, savedata.effectvol,savedata.effectvol, 100);

    self->jumping = 1;
    self->attacking = 1;	// This is needed for Mighty
    ent_set_anim(self, ANI_JUMP);

	if(self->running) toss(self, self->model->runjumpheight);	// Jump higher if running/jumping
    else toss(self, self->model->jumpheight);

 	if(player[self->playerindex].keys & FLAG_MOVELEFT){
        self->direction = 0;
        self->xdir = -self->model->speed;

        if(self->xdir > -1) self->xdir = -1;

        self->xdir *= speedmul;
    }
    else if(player[self->playerindex].keys & FLAG_MOVERIGHT){
        self->direction = 1;
        self->xdir = self->model->speed;

        if(self->xdir < 1) self->xdir = 1;

        self->xdir *= speedmul;
    }
    else self->xdir = 0;

    self->think = player_jump;
}


// Function to check custom combos. If movestep is 0, means ready to check to see if the second step in the combo is
// valid. If so, returns 1, setting each valid combo in the list so far to 1, otherwise 0. If movestep is > 0, means
// ready to check the action button step of the combo. Loops through the "valid combo" list and sees if the action
// button step is valid, returning 1 if true, otherwise 0.

int check_combo(entity *e, int m){	// New function to check combos to make sure they are valid
	int i;
	int found = 0;	// Default not found unless overridden by finding a valid combo

	if(time > self->movetime) return 0;	// Too much time passed so return 0

	if(!self->movestep){	// movestep set to 0 and got past the time > self->movetime, so must be the second step in the combo
		for(i = 0; i < e->model->specials_loaded; i++){
			if(
				(e->model->special[i][0] == e->lastmove &&
				e->model->special[i][1] == m) ||
				(e->model->special[i][0] == e->lastmove &&
				e->model->special[i][0] != FLAG_FORWARD &&
				e->model->special[i][1] == FLAG_FORWARD &&
				(m == FLAG_FORWARD || m == FLAG_BACKWARD)) ||
				(e->model->special[i][1] == m &&
				e->model->special[i][0] == FLAG_FORWARD &&
				(e->lastmove == FLAG_FORWARD || e->lastmove == FLAG_BACKWARD))
			){

				e->model->special[i][4] = 1;	// Marks all valid directional combos with a 1
				found = 1;	// There is at least 1 valid combo, so return found
			}
			else e->model->special[i][4] = 0;	// Marks all invalid directional combos with a 0
		}

		return found;	// Returns 1 if found, otherwise returns 0
	}
	else{	// movestep greater than 0 and got past time > self->movetime, so must be about to execute the action button part of the combo
		for(i = 0; i < e->model->specials_loaded; i++){
			if(e->model->special[i][4] && e->model->special[i][2] == m && e->model->animation[e->model->special[i][4]]){	// Checks only valid directional combos to see if the action button matches

				if(e->health > e->model->animation[e->model->special[i][3]]->energycost){	// Compares the action button with the 3rd element in the array
					e->health -= e->model->animation[e->model->special[i][3]]->energycost;	// Now all freespecials can cost health
					e->model->valid_special = i;	// Says which one is valid and returns that it was found
					return 1;	// Valid combo found, go ahead and return
				}
				else return 0;	// Found, but cost more health than the player had
			}
		}

		return 0;	// No valid combos found, return 0
	}
}



// Function that causes the player to continue to move up or down until the animation has finished playing

void player_dodge(){

	if(freezeall) return;	// Pause all movement/animation if freezeall is on

	if(self->animating){	// Continues to move as long as the player is animating
		if(self->lastmove == FLAG_MOVEUP) player_trymove(0, -self->model->speed*1.75);
		else player_trymove(0, self->model->speed*1.75);
	}
	else{	// Once done animating, returns to thinking
		self->think = player_think;
		ent_set_anim(self, ANI_IDLE);
	}
}



// Function simply continues to return until the animation is done playing

void player_block(){	// New method for player blocking

	if(self->animating) return;
	self->think = player_think;
	ent_set_anim(self, ANI_IDLE);
}



// Function created to combine the action taken if either picking up an item, or running into an item that is a
// SUBTYPE_TOUCH, executing the appropriate action based on which type of item is picked up

void didfind_item(entity *self, entity *other){	// Function that takes care of items when picked up

	player[self->playerindex].opponent = other;

	if(other->score){
		addscore(self->playerindex, other->score);

		if(smp_get2 >= 0) sound_play_sample(smp_get2, 0, savedata.effectvol,savedata.effectvol, 100);
	}
	else if(other->health){
		self->health += other->health;

		if(self->health > self->model->health) self->health = self->model->health;
		other->health = 0;

		if(smp_get >= 0) sound_play_sample(smp_get, 0, savedata.effectvol,savedata.effectvol, 100);
	}
	else if(stricmp(other->model->name, "Time")==0){
		timeleft = level->settime * COUNTER_SPEED;	// Feb 24, 2005 - This line moved here to set custom time

		if(smp_get2 >= 0) sound_play_sample(smp_get2, 0, savedata.effectvol,savedata.effectvol, 100);
	}
	else if(other->model->makeinv){	// Mar 2, 2005 - New item makes player invincible
		self->invincible = 1;
		self->invinctime = time + other->model->makeinv;
		self->blink = 1;

		if(smp_get2 >= 0) sound_play_sample(smp_get2, 0, savedata.effectvol,savedata.effectvol, 100);
	}
	else if(other->model->specpower){	// Damages everything on the screen
		smart_bomb(self, other->model->specpower, other->model->spectype, other->model->speclen);

		if(smp_get2 >= 0) sound_play_sample(smp_get2, 0, savedata.effectvol,savedata.effectvol, 100);
	}
	else if(other->model->subtype == SUBTYPE_WEAPON){
		player[self->playerindex].weapon = other->model->weapnum;
		player_weapon(player[self->playerindex].weapon);

		if(smp_get >= 0) sound_play_sample(smp_get, 0, savedata.effectvol,savedata.effectvol, 100);
	}
	else{
		// Must be a 1up then.
		player[self->playerindex].lives++;

		if(smp_1up >= 0) sound_play_sample(smp_1up, 0, savedata.effectvol,savedata.effectvol, 100);
	}

	other->think = suicide;
	other->nextthink = time + GAME_SPEED * 3;
	other->z = 100000;
}



void player_think(void){
    int walking = 0;
    int up = 0;	// New up animation variable
    int down = 0;	// New down animation variable
    int	running = 0;	// New running animation variable
    entity *other;

    if(endgame || self->frozen || (freezeall && self->animating)) return;	// Can't think if frozen or special is being executed

	// Changed the way combos are checked so combos can be customized

    if(player[self->playerindex].playkeys & FLAG_MOVEUP && !inair(self)){
        player[self->playerindex].playkeys -= FLAG_MOVEUP;

        if(time < self->movetime && self->lastmove==FLAG_MOVEUP && self->model->animation[ANI_ATTACKUP]){	// New u u combo attack
			player[self->playerindex].playkeys -= FLAG_SPECIAL;
			self->attacking = 1;
			self->combostep = 0;
			ent_set_anim(self, ANI_ATTACKUP);
			return;
		}
		else if(time < self->movetime && self->lastmove==FLAG_MOVEUP && self->model->animation[ANI_DODGE] &&
		!self->attacking && !inair(self)){	// New dodge move like on SOR3
			player[self->playerindex].playkeys -= FLAG_SPECIAL;
			self->combostep = 0;
			self->getting = 0;
			self->think = player_dodge;
			ent_set_anim(self, ANI_DODGE);
			return;
		}
		else if(check_combo(self, FLAG_MOVEUP)) ++self->movestep;	// Check the combo and increase movestep if valid
		else self->movestep = 0;

        self->lastmove = FLAG_MOVEUP;
        self->movetime = time + (GAME_SPEED/4);
    }

	if(player[self->playerindex].playkeys & FLAG_MOVEDOWN && !inair(self)){
		player[self->playerindex].playkeys -= FLAG_MOVEDOWN;
		if(self->lastmove==FLAG_MOVEDOWN && self->model->animation[ANI_ATTACKDOWN] && time < self->movetime){	// New d d combo attack
			player[self->playerindex].playkeys -= FLAG_SPECIAL;
			self->attacking = 1;
			self->combostep = 0;
			ent_set_anim(self, ANI_ATTACKDOWN);
			return;
		}
		else if(time < self->movetime && self->lastmove==FLAG_MOVEDOWN && self->model->animation[ANI_DODGE] &&
		!self->attacking && !inair(self)){	// New dodge move like on SOR3
			player[self->playerindex].playkeys -= FLAG_SPECIAL;
			self->combostep = 0;
			self->getting = 0;
			self->think = player_dodge;
			ent_set_anim(self, ANI_DODGE);
			return;
		}
		else if(check_combo(self, FLAG_MOVEDOWN))  ++self->movestep;	// Check the combo and increase movestep if valid
		else self->movestep = 0;

		self->lastmove = FLAG_MOVEDOWN;
		self->movetime = time + (GAME_SPEED/4);
    }

	// Left/right movement for combos is more complicated because forward/backward is relative to the direction the player is facing
	// Checks on current direction have to be made before and after executing the combo to make sure they get the right one

    if((player[self->playerindex].playkeys & FLAG_MOVELEFT) && !inair(self)){
        player[self->playerindex].playkeys -= FLAG_MOVELEFT;

		if(self->model->animation[ANI_RUN] && time < self->movetime && (self->lastmove==FLAG_FORWARD ||
		self->lastmove==FLAG_BACKWARD) && !self->direction) self->running = 1;	// Player begins to run

		else if(self->model->animation[ANI_ATTACKFORWARD] && time < self->movetime &&
		(self->lastmove == FLAG_FORWARD || self->lastmove == FLAG_BACKWARD) && !self->direction){
			player[self->playerindex].playkeys -= FLAG_SPECIAL;
			self->attacking = 1;
			self->combostep = 0;
			ent_set_anim(self, ANI_ATTACKFORWARD);
			return;
		}
		else if(!self->direction && check_combo(self, FLAG_FORWARD))  ++self->movestep;	// Check direction to distinguish forward/backward movements
		else if(self->direction && check_combo(self, FLAG_BACKWARD))  ++self->movestep;	// Check direction to distinguish forward/backward movements
		else self->movestep = 0;

		if(self->direction) self->lastmove = FLAG_BACKWARD;
		else self->lastmove = FLAG_FORWARD;

        self->movetime = time + (GAME_SPEED/4);
    }

	if((player[self->playerindex].playkeys & FLAG_MOVERIGHT) && !inair(self)){
		player[self->playerindex].playkeys -= FLAG_MOVERIGHT;

		if(self->model->animation[ANI_RUN] && time < self->movetime && (self->lastmove==FLAG_FORWARD ||
		self->lastmove==FLAG_BACKWARD) && self->direction) self->running = 1;	// Player begins to run

		else if(self->model->animation[ANI_ATTACKFORWARD] && time < self->movetime &&
		(self->lastmove == FLAG_FORWARD || self->lastmove == FLAG_BACKWARD) && self->direction){
			player[self->playerindex].playkeys -= FLAG_SPECIAL;
			self->attacking = 1;
			self->combostep = 0;
			ent_set_anim(self, ANI_ATTACKFORWARD);
			return;
		}
		else if(!self->direction && check_combo(self, FLAG_BACKWARD))  ++self->movestep;	// Check direction to distinguish forward/backward movements
		else if(self->direction && check_combo(self, FLAG_FORWARD))  ++self->movestep;	// Check direction to distinguish forward/backward movements
		else self->movestep = 0;

		if(!self->direction) self->lastmove = FLAG_BACKWARD;
		else self->lastmove = FLAG_FORWARD;

		self->movetime = time + (GAME_SPEED/4);
    }

    if(self->attacking){
        if(inair(self) && !freezeall){	// Don't want to move if frozen

            if(self->direction) player_trymove((self->animation->moveflag == 0), 0);
            else player_trymove((self->animation->moveflag == 0) * -1, 0);

            self->nextthink = time + (THINK_SPEED/2);
            return;	// No more moving/attacking if executing a jumpframe
        }

        if(self->attacking = self->animating) return;

        if(self->tocost){	// Enemy was hit with a special so go ahead and subtract life
			self->health -= self->model->animation[ANI_SPECIAL]->energycost;
			self->tocost = 0;	// Life is subtracted, so go ahead and reset the flag
		}

        if(freezeall){	// Player is done with the special animation, so unfreeze and execute a smart bomb
			freezeall = 0;
			smart_bomb(self, self->model->specpower, self->model->spectype, self->model->speclen);
		}
        ent_set_anim(self, ANI_IDLE);
    }

    if(self->getting){
        if(self->getting = self->animating) return;
        player[self->playerindex].iconmode = 0;  //20-1-2005   New icons
        ent_set_anim(self, ANI_IDLE);
    }

    if(!ajspecial && (player[self->playerindex].playkeys & FLAG_JUMP) && self->model->animation[ANI_ATTACKBOTH] && !inair(self)){
		if(player[self->playerindex].keys & FLAG_ATTACK){
			player[self->playerindex].playkeys -= FLAG_JUMP;
			self->attacking = 1;
			self->combostep = 0;
			self->movestep = 0;
			self->stalltime = 0;	// If attack is pressed, holding down attack to execute attack3 is no longer valid
			ent_set_anim(self, ANI_ATTACKBOTH);
			return;
		}
	}

	if(player[self->playerindex].playkeys & FLAG_SPECIAL && !inair(self)){	//	The special button can now be used for freespecials

		if(
			self->model->animation[ANI_SPECIAL2] &&
			self->health > self->model->animation[ANI_SPECIAL2]->energycost &&
			!level->nospecial &&
			(!self->direction ?
			(player[self->playerindex].keys & FLAG_MOVELEFT) :
			(player[self->playerindex].keys & FLAG_MOVERIGHT))
		){

			player[self->playerindex].playkeys -= FLAG_SPECIAL;
			self->health -= self->model->animation[ANI_SPECIAL2]->energycost;
			self->attacking = 1;
			self->combostep = 0;
			self->movestep = 0;
			ent_set_anim(self, ANI_SPECIAL2);
			return;
		}

		if(self->movestep >= 1 && check_combo(self, FLAG_SPECIAL)){
			player[self->playerindex].playkeys -= FLAG_SPECIAL;

			if(!self->model->animation[self->model->special[self->model->valid_special][3]]){
				// This is for Mighty
				self->combostep = 0;
				dojump(2);
				return;
			}

			self->attacking = 1;
			self->combostep = 0;
			self->movestep = 0;
			ent_set_anim(self, self->model->special[self->model->valid_special][3]);
			return;
		}

		if(self->model->animation[ANI_BLOCK]){	// New block code for players
			player[self->playerindex].playkeys -= FLAG_SPECIAL;

			self->attacking = 0;
			self->combostep = 0;
			self->movestep = 0;
			self->think = player_block;
			ent_set_anim(self, ANI_BLOCK);
			return;
		}
	}

	if(!inair(self)){	// So you don't perform specials falling off the edge
		if(player_check_special()) return;
	}

    if(!(player[self->playerindex].keys & FLAG_ATTACK) && !inair(self)){

        if(self->stalltime
                && self->stalltime+(GAME_SPEED*2) < time
                && self->model->animation[ANI_ATTACK3]){
            self->attacking = 1;
            self->combostep = 0;
            ent_set_anim(self, ANI_ATTACK3);

            if(smp_punch >= 0) sound_play_sample(smp_punch, 0, savedata.effectvol,savedata.effectvol, 100);

            self->stalltime = 0;
            return;
        }

        self->stalltime = 0;
    }

    if((player[self->playerindex].playkeys & FLAG_ATTACK) && !inair(self)){
		player[self->playerindex].playkeys -= FLAG_ATTACK;
        self->stalltime = 0;	// Disable the attack3 stalltime

		if(self->running && self->model->animation[ANI_RUNATTACK]){	// New run attack code section
			player[self->playerindex].playkeys -= FLAG_SPECIAL;
			self->attacking = 1;
            self->combostep = 0;
            self->running = 0;
			ent_set_anim(self, ANI_RUNATTACK);
			return;
		}

        // Perform special move, Now checks custom combos
        if(self->movestep >= 1 && check_combo(self, FLAG_ATTACK)){

            if(!self->model->animation[self->model->special[self->model->valid_special][3]]){
                // This is for Mighty
                self->combostep = 0;
                dojump(2);
                return;
            }
            player[self->playerindex].playkeys -= FLAG_SPECIAL;
            self->attacking = 1;
            self->combostep = 0;
            self->movestep = 0;
            ent_set_anim(self, self->model->special[self->model->valid_special][3]);
            return;
        }

		if(self->model->animation[ANI_ATTACKBACKWARD] && time < self->movetime - (GAME_SPEED/10) &&
		!self->movestep && self->lastmove == FLAG_BACKWARD){	// New back attacks

			self->attacking = 1;

			if(self->direction && (player[self->playerindex].keys & FLAG_MOVERIGHT)) self->direction = 0;	// Since the back part of the combo will flip the player, need to flip them back around
			else if(!self->direction && (player[self->playerindex].keys & FLAG_MOVELEFT)) self->direction = 1;	// Since the back part of the combo will flip the player, need to flip them back around

			self->combostep = 0;
			ent_set_anim(self, ANI_ATTACKBACKWARD);

			return;
		}

		if(
			(other = find_ent_here(self, self->x, self->z, TYPE_ITEM)) &&
			other->model->subtype != SUBTYPE_TOUCH &&
			self->a - (self->animation->seta[self->animpos] >= 0) * self->animation->seta[self->animpos] == other->a
		){

			didfind_item(self, other);
            self->getting = 1;
            ent_set_anim(self, ANI_GET);
            player[self->playerindex].iconmode = 3;  //20-1-2005   New icons
            return;
        }

        // Use stalltime to charge end-move
        self->stalltime = time;
 		self->attacking = 1;

        if(self->combotime > time) self->combostep++;
        else self->combostep = 0;

        if(!self->model->animation[ANI_ATTACK1]){
            // This is for Mighty
            self->combostep = 0;
            dojump(1);
            return;
        }

        if(self->combostep < 2){
            ent_set_anim(self, ANI_ATTACK1);
        }
        else if(self->combostep == 2){
            ent_set_anim(self, ANI_ATTACK2);
        }
        else{
            ent_set_anim(self, ANI_ATTACK3);
            self->combostep = 0;
        }

        if(smp_punch >= 0) sound_play_sample(smp_punch, 0, savedata.effectvol,savedata.effectvol, 100);

        return;
    }
    // 7-1-2005 spawn projectile end

    // Mighty hass no attack animations, he just jumps.
    if(player[self->playerindex].playkeys & FLAG_JUMP && !inair(self)){	// Added !inair(self) so players can't jump when falling into holes
        player[self->playerindex].playkeys -= FLAG_JUMP;

        if(self->running) dojump(self->model->runjumpdist);	// Player jumps a further distance if running/jumping
        else{
			if(self->movestep >= 1 && check_combo(self, FLAG_JUMP)){	// Jump can now be used with freespecials
				if(!self->model->animation[self->model->special[self->model->valid_special][3]]){
					// This is for Mighty
					self->combostep = 0;
					dojump(2);
					return;
				}

				player[self->playerindex].playkeys -= FLAG_SPECIAL;
				self->attacking = 1;
				self->combostep = 0;
				self->movestep = 0;
				ent_set_anim(self, self->model->special[self->model->valid_special][3]);
				return;
			}
			else dojump(1);
		}
        return;
    }

	if(PLAYER_MIN_Z != PLAYER_MAX_Z){	// More of a platform feel
		if(player[self->playerindex].keys & FLAG_MOVEUP && !inair(self)){
			if(!self->model->runupdown || self->model->animation[ANI_UP]) self->running = 0;	// Quits running if player presses up or the up animation exists

			if(self->model->animation[ANI_UP]) up = player_trymove(0, -self->model->speed/2);	// Used for up animation
			else if(self->running) running = player_trymove(0, -self->model->runspeed/2);	// Moves up at a faster rate running
			else walking = player_trymove(0, -self->model->speed/2);
		}
		else if(player[self->playerindex].keys & FLAG_MOVEDOWN && !inair(self)){
			if(!self->model->runupdown || self->model->animation[ANI_DOWN]) self->running = 0;	// Quits running if player presses down or the down animation exists

			if(self->model->animation[ANI_DOWN]) down = player_trymove(0, self->model->speed/2);	// Used for down animation
			else if(self->running) running = player_trymove(0, self->model->runspeed/2);	// Moves down at a faster rate running
			else walking = player_trymove(0, self->model->speed/2);
		}
	}

    if(self->link) return;		// Grabbed someone

    if(player[self->playerindex].keys & FLAG_MOVELEFT){
		if(self->direction) self->running = 0;	// Quits running if player changes direction

        self->direction = 0;

        if(self->running) running = player_trymove(-self->model->runspeed, 0);	// If running, player moves at a faster rate
        else walking = player_trymove(-self->model->speed, 0);
    }
    else if(player[self->playerindex].keys & FLAG_MOVERIGHT){
		if(!self->direction) self->running = 0;	// Quits running if player changes direction

        self->direction = 1;

        if(self->running) running = player_trymove(self->model->runspeed, 0);	// If running, player moves at a faster rate
        else walking = player_trymove(self->model->speed, 0);
    }
    else if(
		self->running &&
		!((player[self->playerindex].keys & FLAG_MOVELEFT) ||
    	(player[self->playerindex].keys & FLAG_MOVERIGHT))
    )

    	self->running = 0;	// Player let go of left/right and so quits running

    if(self->link) return;		// Grabbed someone

    //	ltb 1-18-05  new Item get code to address new subtype

    if(
		(other = find_ent_here(self, self->x, self->z, TYPE_ITEM)) &&
		other->model->subtype == SUBTYPE_TOUCH &&
    	self->a - (self->animation->seta[self->animpos] >= 0) * self->animation->seta[self->animpos] == other->a &&
    	(walking || up || down || running)
    ){

		didfind_item(self, other);	// Added function to clean code up a bit
	}

 	if(up) ent_set_anim(self, ANI_UP);	// Set to up animation if exists
 	else if(down) ent_set_anim(self, ANI_DOWN);	// Set to down animation if exists
 	else if(running) ent_set_anim(self, ANI_RUN);	// Set to run animation if exists
 	else if(walking) ent_set_anim(self, ANI_WALK);	// If neither up nor down exist, set to walk
	else ent_set_anim(self, ANI_IDLE);
}



void player_takedamage(entity *other, int force, int drop, int type){

    self->attacking = 0;
    self->jumping = 0;
    self->getting = 0;	// Added so players can move if being hurt while getting an item
    self->running = 0;	// Can't continue to run if being hurt

    if(self->a < PIT_DEPTH){
        player_die();
        return;
    }

	if(healthcheat || level->nohurt) force = 0;	// Players can't be hurt if cheating or specified in the level->type
    if((other != self->link) || drop) ent_unlink(self);
    if(other != self && other->type != TYPE_SHOT) player[self->playerindex].opponent = other;	// Need to display the health bar of the one that's attacking the player if it's an enemy
    if(other->type == TYPE_PLAYER || other->type == TYPE_SHOT) player[other->playerindex].opponent = self; // Show health bar of the one being attacked if it's a player/pshot attacking
    if(other->type == TYPE_SHOT) player[self->playerindex].opponent = player[other->playerindex].ent;	// Need to display the health bar of the owner of the pshot

    self->pain_time = time + (GAME_SPEED / 5);

    if(type != ATK_FREEZE || (type == ATK_FREEZE && self->frozen)) self->direction = (self->x < other->x);	// Can't turn to player if frozen
    self->xdir = -self->xdir;

    if(other->type==TYPE_PLAYER || other->type==TYPE_SHOT) force /= 2;	// Include player projectiles as well

	if(inair(self)) drop = 1;	// If in the air, automatically fall (freeze will override later if not frozen)

    if(type == ATK_BLAST){
        self->projectile = 1;
        self->blasted = 1;
        drop = 1;
    }
	if(type == ATK_STEAL){	// Takes life from opponent and adds it to yours
		if(self->health >= force) other->health += force;
		else other->health += self->health;
		if(other->health > other->maxhealth) other->health = other->maxhealth;
	}
    if(type == ATK_FREEZE && !self->frozen){	// Only freeze if not already frozen
		if(self->model->fmap) ent_set_colourmap(self, self->model->fmap);	// Changes to frozen map and sets freezetime
		self->frozen = 1;
		self->freezetime = time + other->animation->attack_length[other->animpos];
		self->stalltime = 0;	// Disable the attack3 if frozen
		drop = 0;
	}
	else if(self->frozen){	// If already frozen, instanly drop regardless of attack
		drop = 1;
		ent_unlink(self);	// Added so entities don't continue to be linked
	}

    self->health -= force;

    if(drop || self->health <= 0 || inair(self)){
        if(self->health <= 0 && !self->model->falldie && self->model->animation[ANI_DIE]){  // 6-2-2005
            if(self->model->diesound>=0) sound_play_sample(self->model->diesound, 0, savedata.effectvol,savedata.effectvol, 100);
            player[self->playerindex].iconmode=2;   // 20-1-2005 New icons here
            ent_set_anim(self, ANI_DIE);	//  LTB 1-13-05  Die before toss
        }
        else{
            player[self->playerindex].iconmode=2;   //20-1-2005   New icons
            toss(self, self->model->jumpheight/1.333);

            if(type == ATK_SHOCK && self->model->animation[ANI_SHOCK]) ent_reset_anim(self, ANI_SHOCK);	// Apr 14, 2005 - New animation
            else if(type == ATK_BURN && self->model->animation[ANI_BURN]) ent_reset_anim(self, ANI_BURN);	// Apr 14, 2005 - New animation
            else ent_reset_anim(self, ANI_FALL);
        }

        self->damage_on_landing = 0;
        self->think = player_fall;
        return;
    }

	if(!self->frozen){	// Only play ANI_PAIN if not frozen so frozen in current animation

		if(type == ATK_SHOCK && self->model->animation[ANI_SHOCKPAIN]) ent_reset_anim(self, ANI_SHOCKPAIN);	// New pain when shocked animation
		else if(type == ATK_BURN && self->model->animation[ANI_BURNPAIN]) ent_reset_anim(self, ANI_BURNPAIN);	// New pain when burned animation
		else ent_reset_anim(self, ANI_PAIN);

	    self->think = player_pain;
	}
}






////////////////////////////////



// Called when player re-enters the game.
// Drop all enemies EXCEPT for the linked/frozen ones.
void drop_all_enemies(){
    int i;
    for(i=0; i<MAX_ENTS; i++){
        if(ent_list[i]->exists &&
                ent_list[i]->health>0 &&
                ent_list[i]->type==TYPE_ENEMY &&
                ent_list[i]->model->subtype != SUBTYPE_EWEAPON &&	// Don't want to knock down a projectile
                ent_list[i]->model->subtype != SUBTYPE_ARROW &&	// Don't want to knock down an arrow
                !ent_list[i]->link &&
                !ent_list[i]->frozen &&	// Don't want to unfreeze a frozen enemy
                !ent_list[i]->model->nomove &&
                !ent_list[i]->model->nodrop &&
                ent_list[i]->model->animation[ANI_FALL]
          ){
            ent_list[i]->attacking = 0;
            ent_list[i]->projectile = 0;
            ent_list[i]->think = enemy_fall;
            ent_list[i]->damage_on_landing = 0;
            toss(ent_list[i], 2.5 + randf(1));
            ent_reset_anim(ent_list[i], ANI_FALL);
        }
    }
}



// Called when boss dies
void kill_all_enemies(){
    int i;
    entity * tmpself;

    tmpself = self;
    for(i=0; i<MAX_ENTS; i++){
        if(		ent_list[i]->exists
                && ent_list[i]->health>0
                && ent_list[i]->type==TYPE_ENEMY
                && ent_list[i]->takedamage){
            self = ent_list[i];
            self->takedamage(tmpself, 1000000, 1, ATK_NORMAL);
        }
    }
    self = tmpself;
}



void smart_bomb(entity* e, int p, int t, int l){	// New method for smartbombs
    int i;

    entity * tmpself;

    tmpself = self;
    for(i=0; i<MAX_ENTS; i++){
        if(		ent_list[i]->exists
                && ent_list[i]->health>0
                && ent_list[i]->type==TYPE_ENEMY
                && ent_list[i]->takedamage
                && !ent_list[i]->model->nomove
                && ent_list[i]->think != enemy_lie){
            self = ent_list[i];
            if(t == ATK_FREEZE) self->freezetime = time + l;
			self->takedamage(e, p, 1, t);
        }
    }
    self = tmpself;
}

////////////////////////////////



void anything_walk(void){
    if(freezeall) return;
    if(self->x < advancex - 80 || self->x > advancex + 400){
        kill(self);
        return;
    }
    self->x += self->xdir;
}



void knife_think(void){
	int wall;

	if(freezeall) return;	// Don't update animation if special is being executed
    if(self->x < advancex - 180 || self->x > advancex + 500){
        kill(self);
        return;
    }
    if(self->direction) self->x += self->model->speed;	// Now projectiles can have custom speeds
    else self->x -= self->model->speed;

	if(self->model->animation[ANI_FALL]){	// Added so projectiles bounce off blocked exits
		if((level->exit_blocked && self->x > level->width-30-(PLAYER_MAX_Z-self->z)) ||
		((wall = checkwall(self->x, self->z)) >= 0 && self->a < level->walls[wall][7])){
			self->attacking = 0;
			self->health -= 100000;
			self->projectile = 0;
			self->think = enemy_fall;
			self->damage_on_landing = 0;
			toss(self, 2.5 + randf(1));
			ent_set_anim(self, ANI_FALL);
		}
	}

    self->nextthink = time + THINK_SPEED / 2;
}



void knife_spawn(float x, float z, float a, int direction, entity* enem){
    entity *e;

    e = spawn(x, z, a, enem->animation->custknife);
    if(e==NULL) return;

	e->takedamage = enemy_takedamage;	// Players can now hit projectiles
	e->owner = enem;	// Added so enemy projectiles don't hit the owner
    e->attacking = 1;
    if(!e->model->speed) e->model->speed = 2;
    e->model->subtype = SUBTYPE_EWEAPON;	// Flag so projectiles don't fall when players spawn
    e->nograb = 1;	// Prevents trying to grab a projectile
    e->direction = direction;
    e->think = knife_think;
    e->remove_on_attack = e->model->remove;
	e->base = a;
}



void bomb_explode(void){
	if(!self->animating) kill(self);
	return;
}



void bomb_think(void){

	if(freezeall) return;

 	if(!(level->exit_blocked && self->x > level->width-30-(PLAYER_MAX_Z-self->z))){
    	if(self->direction) self->x += self->model->speed;	// Now projectiles can have custom speeds
    	else self->x -= self->model->speed;
	}

	self->nextthink = time + THINK_SPEED / 2;

	if(inair(self) && self->toexplode == 1) return;

	self->tossv = 0;	// Stop moving up/down
	self->base = self->a;	// Stop moving up/down

	if(self->model->diesound>=0) sound_play_sample(self->model->diesound, 0, savedata.effectvol,savedata.effectvol, 100);

	if(self->toexplode == 2 && self->model->animation[ANI_ATTACK2]) ent_set_anim(self, ANI_ATTACK2);	// If bomb never reaces the ground, play this
	else ent_set_anim(self, ANI_ATTACK1);

	self->think = bomb_explode;
}



void bomb_spawn(float x, float z, float a, int direction, entity* enem){
	entity *e;

	e = spawn(x, z, a, enem->animation->custbomb);
	if(e==NULL) return;

	e->takedamage = enemy_takedamage;	// Players can now hit projectiles
	e->owner = enem;	// Added so enemy projectiles don't hit the owner
	e->attacking = 1;

    if(!e->model->speed) e->model->speed = 2;

    e->model->subtype = SUBTYPE_EWEAPON;	// Flag so projectiles don't fall when players spawn
    e->nograb = 1;	// Prevents trying to grab a projectile
    e->toexplode = 1;	// Set to distinguish exploding projectiles and also so stops falling when hitting an opponent
    e->direction = direction;
 	toss(e, e->model->jumpheight);
 	e->think = bomb_think;
    e->remove_on_attack = 0;
}



void fireball_think(void){
	int wall;

	if(freezeall) return;	// Don't update animation if special is being executed

    if(self->x < advancex - 180 || self->x > advancex + 500){
        kill(self);
        return;
    }

    if(self->direction) self->x += self->model->speed;	// Now projectiles can have custom speeds
    else self->x -= self->model->speed;

	if((level->exit_blocked && self->x > level->width-30-(PLAYER_MAX_Z-self->z)) ||
	((wall = checkwall(self->x, self->z)) >= 0 && self->a < level->walls[wall][7])){
		kill(self);
		return;
	}

    self->nextthink = time + THINK_SPEED / 2;
}



void fireball_spawn(float x, float z, float a, int direction, entity* enem){
    entity *e;

    e = spawn(x, z, a, enem->animation->custfireb);
    if(e==NULL) return;

    e->takedamage = enemy_takedamage;	// Players can now hit projectiles
    e->owner = enem;	// Added so enemy projectiles don't hit the owner
    e->attacking = 1;

    if(!e->model->speed) e->model->speed = 2;

    e->model->subtype = SUBTYPE_EWEAPON;	// Flag so projectiles don't fall when players spawn
    e->nograb = 1;	// Prevents trying to grab a projectile
    e->direction = direction;
    e->think = fireball_think;
	e->base = a;
    e->remove_on_attack = e->model->remove;
}



void star_think(void){
	int wall;

	if(freezeall) return;	// Don't update animation if special is being executed

    if(self->x<advancex-80 || self->x>advancex+400 || self->base<0){
        kill(self);
        return;
    }

    self->x += self->xdir;
    self->base -= 2;
    self->a = self->base;

	if(self->model->animation[ANI_FALL]){	// Added so projectiles bounce off blocked exits

		if((level->exit_blocked && self->x > level->width-30-(PLAYER_MAX_Z-self->z)) ||
		((wall = checkwall(self->x, self->z)) >= 0 && self->a < level->walls[wall][7])){
			self->attacking = 0;
			self->health -= 100000;
			self->projectile = 0;
			self->think = enemy_fall;
			self->damage_on_landing = 0;
			toss(self, 2.5 + randf(1));
			ent_set_anim(self, ANI_FALL);
		}
	}

    self->nextthink = time + THINK_SPEED / 2;
}



// Spawn 3 stars
void star_spawn(float x, float z, float a, int direction, entity* enem){ // added entity to know which star to load
    entity *e;
    float fd = (direction ? 2 : -2);

    e = spawn(x, z, 70, enem->animation->custstar); //use any star

    if(e==NULL) return;

    e->takedamage = enemy_takedamage;	// Players can now hit projectiles
    e->owner = enem;	// Added so enemy projectiles don't hit the owner
    e->attacking = 1;
    e->model->subtype = SUBTYPE_EWEAPON;	// Flag so projectiles don't fall when players spawn
    e->nograb = 1;	// Prevents trying to grab a projectile
    e->xdir = fd/2;
    e->think = star_think;
    e->remove_on_attack = e->model->remove;
    e->base = a;
    e->a = a;

    e = spawn(x, z, 70, enem->animation->custstar); //use any star

    if(e==NULL) return;

    e->takedamage = enemy_takedamage;	// Players can now hit projectiles
    e->owner = enem;	// Added so enemy projectiles don't hit the owner
    e->attacking = 1;
    e->model->subtype = SUBTYPE_EWEAPON;	// Flag so projectiles don't fall when players spawn
    e->nograb = 1;	// Prevents trying to grab a projectile
    e->xdir = fd;
    e->think = star_think;
    e->remove_on_attack = e->model->remove;
    e->base = a;
    e->a = a;

    e = spawn(x, z, 70, enem->animation->custstar); //use any star

    if(e==NULL) return;

    e->takedamage = enemy_takedamage;	// Players can now hit projectiles
    e->owner = enem;	// Added so enemy projectiles don't hit the owner
    e->attacking = 1;
    e->model->subtype = SUBTYPE_EWEAPON;	// Flag so projectiles don't fall when players spawn
    e->nograb = 1;	// Prevents trying to grab a projectile
    e->xdir = fd * 1.5;
    e->think = star_think;
    e->remove_on_attack = e->model->remove;
    e->base = a;
    e->a = a;
}



void steam_think(void){
	if(freezeall) return;	// Don't update animation if special is being executed

    if(!self->animating){
        kill(self);
        return;
    }

    self->base += 1;
    self->a = self->base;
}



// for the "trap" type   7-1-2005  trap start
void trap_think(void){
	if(freezeall) return;	// Don't update animation if special is being executed

    if(self->x < advancex-80 || self->x > advancex+400){
        //		kill(self);   // 6-2-2005 removed temporarily
        return;
    }

    self->attacking = 1;
    self->nextthink = time + 1;
}
//    7-1-2005  trap end



void steam_spawn(float x, float z, float a){
    entity *e;

    e = spawn(x, z, a, "Steam");

    if(e==NULL) return;

    e->base = a;
    e->think = steam_think;
    e->screen = 1;
}



void steamer_think(void){
	if(freezeall) return;	// Don't update animation if special is being executed

    if(self->x < advancex-80 || self->x > advancex+400){
        kill(self);
        return;
    }

    steam_spawn(self->x, self->z, self->a);
    self->nextthink = time + (GAME_SPEED/10) + (rand32()&31);
}



void text_think(void){	// New function so text can be displayed
	if(!self->animating){
		freezeall = 0;	// Done displaying, unfreeze the animations and destroy the textbox
		kill(self);
	}

	return;
}

////////////////////////////////



/*

A brief description of enemy behaviour:

The enemy moves about quite randomly.
If a player is located on the same level (close-to-same Z coordinate),
the enemy will be inclined to move forward and backward only
(still facing the player!).
All of the movement patterns are subject to some randomness.

If, at any time, a player is spotted at a proper location for attack
(e.g. right in front of the enemy), the enemy waits for a half a second
or so, and attacks.

If the player is in mid-air, an uppercut attack should be preferred.

*/


entity * enemy_find_target(){
    float dx1, dz1;
    float dx2, dz2;


    // One or less players present?
    if(!player[0].ent) return player[1].ent;
    if(!player[1].ent) return player[0].ent;


    dx1 = self->x - player[0].ent->x;
    dz1 = self->z - player[0].ent->z;
    dx2 = self->x - player[1].ent->x;
    dz2 = self->z - player[1].ent->z;


    // Is self located between two players (x-wise)?
    if((dx1<0 && dx2>0) || (dx1>0 && dx2<0)){
        // Stay targeted at the one in front of self
        if(self->direction ? dx1<0 : dx1>0) return player[0].ent;
        return player[1].ent;
    }

    // Target the closest opponent
    if(dx1<0) dx1 = -dx1;
    if(dz1<0) dz1 = -dz1;
    if(dx2<0) dx2 = -dx2;
    if(dz2<0) dz2 = -dz2;

    if(dx1+dz1 < dx2+dz2) return player[0].ent;
    return player[1].ent;
}



// Returns 1 if a player was thrown!
int enemy_trymove(float xdir, float zdir){

    entity *other;
    int wall;
    float coef1;
    float coef2;

	if(!self->projectile){

        // Walking

        // Out of bounds? Return to level!
        if(!inair(self)){
        	if(self->zdir && self->z < PLAYER_MIN_Z) self->zdir = self->model->speed/2;
        	if(self->zdir && self->z > PLAYER_MAX_Z) self->zdir = -self->model->speed/2;
		}

        if((self->xdir || self->zdir) && !inair(self)){
            // Don't walk into a hole
           	if(checkhole(self->x + xdir*5, self->z)) xdir = 0;
           	if(checkhole(self->x, self->z + zdir*5)) zdir = 0;

			if(xdir && (other = find_ent_here(self, self->x + xdir*2, self->z, (TYPE_OBSTACLE | TYPE_TRAP)))){
				if(!other->animation->platform[other->animpos][5]){
					if(xdir>0 ? other->x>self->x : other->x<self->x) xdir = 0;
				}
			}
			if(zdir && (other = find_ent_here(self, self->x, self->z + zdir*2, (TYPE_OBSTACLE | TYPE_TRAP)))){
				if(!other->animation->platform[other->animpos][5]){
					if(zdir>0 ? other->z>self->z : other->z<self->z) zdir = 0;
				}
			}
		}

		if((other = check_platform(self->x + xdir*10, self->z + zdir*10)) &&
		self->a < other->a + other->animation->platform[other->animpos][5] &&
		self->a + self->model->height >= other->a){

			if(xdir>0 ? other->x>self->x : other->x<self->x) xdir = 0;
			if(zdir>0 ? other->z>self->z : other->z<self->z) zdir = 0;

		}

// Removed unless bugs arise

//		else if((other = check_platform(self->x, self->z)) &&
//		self->a >= other->a + other->animation->platform[other->animpos][5]){
//			self->base = (self->animation->seta[self->animpos] >= 0) *
//			self->animation->seta[self->animpos] + other->a + other->animation->platform[other->animpos][5];
//		}
//		else if(self->base != -1000) self->base = (self->animation->seta[self->animpos] >= 0) * self->animation->seta[self->animpos];

    }


	if((wall = checkwall(self->x + xdir*4, self->z + zdir*4)) >= 0 && self->a < level->walls[wall][7]){

		coef1 = (level->walls[wall][1] - self->z) * ((level->walls[wall][2] - level->walls[wall][3]) / level->walls[wall][6]);
		coef2 = (level->walls[wall][1] - self->z) * ((level->walls[wall][4] - level->walls[wall][5]) / level->walls[wall][6]);

		if(self->z > level->walls[wall][1] || self->z < level->walls[wall][1] - level->walls[wall][6]){
			zdir = 0;
		}
		else{

			if(self->x < (level->walls[wall][0] * 2 + level->walls[wall][3] + level->walls[wall][5]) / 2 &&
			self->x > level->walls[wall][0] + level->walls[wall][3] + coef1){

				self->x = level->walls[wall][0] + level->walls[wall][3] + coef1;
			}
			else if(self->x > (level->walls[wall][0] * 2 + level->walls[wall][3] + level->walls[wall][5]) / 2 &&
			self->x < level->walls[wall][0] + level->walls[wall][5] + coef2){

				self->x = level->walls[wall][0] + level->walls[wall][5] + coef2;
			}

			xdir = 0;
		}

	}
//	else if((other && self->a - self->animation->seta[self->animpos] == other->a) ||
//	(!other && self->a >= level->walls[wall][7])){

//		if(self->base != -1000) self->base = (self->animation->seta[self->animpos] >= 0) *
//		self->animation->seta[self->animpos] + level->walls[wall][7];
//	}


    self->x += xdir;
    self->z += zdir;

	if(self->z > PLAYER_MAX_Z) self->z = PLAYER_MAX_Z;	// Added so enemies don't move out of range
	if(self->z < PLAYER_MIN_Z) self->z = PLAYER_MIN_Z;	// Added so enemies don't move out of range

    // Do a throw?
    if(		self->health>0 &&
            self->model->animation[ANI_THROW] &&
            !inair(self) &&
            !self->animation->move[self->animpos] &&
            self->animation != self->model->animation[ANI_FALL] &&	// No more grabbing while falling
            self->animation != self->model->animation[ANI_SHOCK] &&	// No more grabbing while falling
            self->animation != self->model->animation[ANI_BURN] &&	// No more grabbing while falling
            (other = find_ent_here(self, self->x + xdir, self->z + zdir, TYPE_PLAYER)) &&
            !inair(other) &&
            self->a == other->a &&
            !other->invincible &&
            other->animation != other->model->animation[ANI_DODGE] &&	// Can't throw a dodging player
            (self->direction ? other->x>self->x : other->x<self->x)
      ){

        // Reposition both on average height and such
        zdir = (self->z + other->z) / 2 - (self->z == PLAYER_MAX_Z || other->z == PLAYER_MAX_Z);
		other->z = zdir;
		self->z = zdir+1;	// Grabber up front

		if((wall == -1 || (wall >= 0 && self->a != level->walls[wall][7])) &&
		(checkwall(self->x - self->model->grabdistance/2, self->z) >= 0 ||
		checkwall(other->x - self->model->grabdistance/2, other->z) >= 0)){
			if(self->x < other->x) other->x = self->x + self->model->grabdistance;
			else self->x = other->x + self->model->grabdistance;
		}
		else if((wall == -1 || (wall >= 0 && self->a != level->walls[wall][7])) &&
		(checkwall(self->x + self->model->grabdistance/2, self->z) >= 0 ||
		checkwall(other->x + self->model->grabdistance/2, other->z) >= 0)){
			if(self->x > other->x) other->x = self->x - self->model->grabdistance;
			else self->x = other->x - self->model->grabdistance;
		}
		else{
	        xdir = (self->x + other->x) / 2;
	        if(self->x < other->x){
	            self->x = xdir - (self->model->grabdistance/2);  // 30-12-2004 changed GRAB_DIST to grabdistance per player
	            other->x = xdir + (self->model->grabdistance/2);// 30-12-2004 changed GRAB_DIST to grabdistance per player
	        }
	        else{
	            self->x = xdir + (self->model->grabdistance/2);// 30-12-2004 changed GRAB_DIST to grabdistance per player
	            other->x = xdir - (self->model->grabdistance/2);// 30-12-2004 changed GRAB_DIST to grabdistance per player
	        }
		}

		self->direction = (self->x < other->x);

        player[other->playerindex].opponent = self;
        other->attacking = 0;

        self->think = enemy_throw;
        ent_set_anim(self, ANI_THROW);
        ent_unlink(self);

		if(other->model->throwheight) toss(other, other->model->throwheight);
        else toss(other, other->model->jumpheight);

        other->direction = self->direction;
		other->projectile = 2;

        other->think = player_fall;
        other->damage_on_landing = player[other->playerindex].ent->model->throwdamage;	// use adjusted throwdamage
        ent_set_anim(other, ANI_FALL);
        ent_unlink(other);

        return 1;
    }


    // Do a grab?
    if(		self->health>0 &&
            self->model->animation[ANI_GRAB] &&
            !inair(self) &&
            !self->animation->move[self->animpos] &&
            self->animation != self->model->animation[ANI_FALL] &&	// No more grabbing while falling
            self->animation != self->model->animation[ANI_SHOCK] &&	// No more grabbing while falling
            self->animation != self->model->animation[ANI_BURN] &&	// No more grabbing while falling
            (other = find_ent_here(self, self->x + xdir, self->z + zdir, TYPE_PLAYER)) &&
            !inair(other) &&
            self->a == other->a &&
            !other->invincible &&
            other->animation != other->model->animation[ANI_DODGE] &&	// Can't grab a dodging player
            (self->direction ? other->x>self->x : other->x<self->x)
      ){

        // Reposition both on average height and such
        zdir = (self->z + other->z) / 2 - (self->z == PLAYER_MAX_Z || other->z == PLAYER_MAX_Z);
		other->z = zdir;
		self->z = zdir+1;	// Grabber up front

		if(
			(wall == -1 ||
			(wall >= 0 &&
			self->a != level->walls[wall][7])) &&
			(checkwall(self->x - self->model->grabdistance/2, self->z) >=0 ||
			checkwall(other->x - self->model->grabdistance/2, other->z) >= 0)
		){

			if(self->x < other->x) other->x = self->x + self->model->grabdistance;
			else self->x = other->x + self->model->grabdistance;
		}
		else if(
			(wall == -1 ||
			(wall >= 0 &&
			self->a != level->walls[wall][7])) &&
			(checkwall(self->x + self->model->grabdistance/2, self->z) >= 0 ||
			checkwall(other->x + self->model->grabdistance/2, other->z) >= 0)
		){

			if(self->x > other->x) other->x = self->x - self->model->grabdistance;
			else self->x = other->x - self->model->grabdistance;
		}
		else{
	        xdir = (self->x + other->x) / 2;
	        if(self->x < other->x){
	            self->x = xdir - (self->model->grabdistance/2);  // 30-12-2004 changed GRAB_DIST to grabdistance per player
	            other->x = xdir + (self->model->grabdistance/2);// 30-12-2004 changed GRAB_DIST to grabdistance per player
	        }
	        else{
	            self->x = xdir + (self->model->grabdistance/2);// 30-12-2004 changed GRAB_DIST to grabdistance per player
	            other->x = xdir - (self->model->grabdistance/2);// 30-12-2004 changed GRAB_DIST to grabdistance per player
	        }
		}

		self->direction = (self->x < other->x);
		other->direction = !self->direction;

        player[other->playerindex].opponent = self;
        other->attacking = 0;

        self->think = enemy_grab;
        self->attacking = 1;
        ent_set_anim(self, ANI_GRAB);
        ents_link(self, other);

        other->think = player_grabbed;
        player[other->playerindex].opponent = self;

        if(other->model->animation[ANI_GRABBED]) ent_set_anim(other, ANI_GRABBED);	// New grabbed animation
        else ent_set_anim(other, ANI_PAIN);

        return 1;
    }


    // End of level is blocked?
    if(level->exit_blocked){
        if(self->x > level->width-30-(PLAYER_MAX_Z-self->z)) self->x = level->width-30-(PLAYER_MAX_Z-self->z);
    }

    return 0;
}


void enemy_jump(void){
	if(freezeall || self->frozen) return;

    enemy_trymove(self->xdir, 0);
    if(inair(self)) return;

    self->jumping = 0;
    self->xdir = 0;
    ent_set_anim(self, ANI_IDLE);
    self->think = enemy_think;
}


void enemy_think(void){
    entity *target = enemy_find_target();
    int rnum;
    int walk;
    int wall;
    int xdir;


    if(!target || self->frozen || freezeall) return;	// There are no players/enemy is frozen/special is being executed?

	if(self->model->animation[ANI_JUMP]){

		// Check to see if there is a wall within jumping distance and within a jumping height

		if(self->direction) xdir = self->x + self->model->animation[ANI_JUMP]->range[0];
		else xdir = self->x - self->model->animation[ANI_JUMP]->range[0];

		if(
			(wall = checkwall(xdir, self->z)) >= 0 &&
			level->walls[wall][7] <= self->a + self->model->animation[ANI_JUMP]->range[1] &&
			!inair(self) &&
			self->a < level->walls[wall][7]
		){

			if(
				(self->direction &&
				self->x < (level->walls[wall][0] * 2 + level->walls[wall][1] + level->walls[wall][4]) / 2) ||
				(!self->direction &&
				self->x > (level->walls[wall][0] * 2 + level->walls[wall][1] + level->walls[wall][4]) / 2)
			){

				ent_set_anim(self, ANI_JUMP);

				if(self->direction) self->xdir = 1.3;
				else self->xdir = -1.3;

				self->jumping = 1;
				self->think = enemy_jump;
				toss(self, self->model->jumpheight);
			}
		}
	}

	if(!self->model->noflip) self->direction = (self->x < target->x);	// Don't flip if set

    if(self->animating && (self->animation == self->model->animation[ANI_WALK] ||
    self->animation == self->model->animation[ANI_UP] ||
    self->animation == self->model->animation[ANI_DOWN])){	// Include ANI_UP, ANI_DOWN animations

        if(self->direction ? self->xdir<0 : self->xdir>0){
            // Walking backwards
            self->animating = -1;
        }
        else self->animating = 1;
    }


    // Target in range?
    if(diff(self->z, target->z) < (self->model->grabdistance/3)){

        // Target jumping? Try uppercut!
        if(target->jumping && self->model->animation[ANI_UPPER] &&
                (self->direction ?
                 self->x<target->x-10 && target->x<self->x+120 :
                 self->x>target->x+10 && target->x>self->x-120 )){

            // Don't waste any time!
            ent_set_anim(self, ANI_UPPER);
            self->think = enemy_attack;
            return;
        }

        if(self->direction ?
                (self->model->animation[ANI_ATTACK1] && target->x > self->x+self->model->animation[ANI_ATTACK1]->range[0] &&
                target->x < self->x+self->model->animation[ANI_ATTACK1]->range[1]) ||
                (self->model->animation[ANI_ATTACK2] && target->x > self->x+self->model->animation[ANI_ATTACK2]->range[0] &&
                target->x < self->x+self->model->animation[ANI_ATTACK2]->range[1]) ||
                (self->model->animation[ANI_ATTACK3] && target->x > self->x+self->model->animation[ANI_ATTACK3]->range[0] &&
                target->x < self->x+self->model->animation[ANI_ATTACK3]->range[1])
                :
                (self->model->animation[ANI_ATTACK1] && target->x < self->x-self->model->animation[ANI_ATTACK1]->range[0] &&
                target->x > self->x-self->model->animation[ANI_ATTACK1]->range[1]) ||
                (self->model->animation[ANI_ATTACK2] && target->x < self->x-self->model->animation[ANI_ATTACK2]->range[0] &&
                target->x > self->x-self->model->animation[ANI_ATTACK2]->range[1]) ||
                (self->model->animation[ANI_ATTACK3] && target->x < self->x-self->model->animation[ANI_ATTACK3]->range[0] &&
                target->x > self->x-self->model->animation[ANI_ATTACK3]->range[1])
          ){

            ent_set_anim(self, ANI_IDLE);
            self->think = enemy_prepare;
            self->stalltime = time + (GAME_SPEED/4) + (rand32()%(GAME_SPEED/10));
            return;
        }

        if((self->model->animation[ANI_JUMPATTACK] || self->model->animation[ANI_JUMPATTACK2])
                && (self->direction ?
                    (target->x > self->x+(self->model->animation[ANI_JUMPATTACK]->range[1]-10) &&
                    target->x < self->x+(self->model->animation[ANI_JUMPATTACK]->range[1]+5)):  //30-12-2004  changed to be affected by range
                    (target->x < self->x-(self->model->animation[ANI_JUMPATTACK]->range[1]-10) &&
                    target->x > self->x-(self->model->animation[ANI_JUMPATTACK]->range[1]+5)))  //30-12-2004  Attack distance for jump attack
          ){

            rnum = 0;
            if(!self->model->animation[ANI_JUMPATTACK]) rnum = 1;
            else if(self->model->animation[ANI_JUMPATTACK2] && (rand32()&1)) rnum = 1;

            if(rnum==0){
                ent_set_anim(self, ANI_JUMPATTACK);
                if(self->direction) self->xdir = 1.3;
                else self->xdir = -1.3;
            }
            else{
                ent_set_anim(self, ANI_JUMPATTACK2);
                self->xdir = 0;
            }

            self->think = enemy_jumpattack;
            self->attacking = 1;
            self->jumping = 1;

            toss(self, self->model->jumpheight);

            return;
        }

    }

    if(self->stalltime < time){
        // Decide next direction... randomly.

        walk = 0;
        self->xdir = self->zdir = 0;

        rnum = rand32();
        if(((rnum & 7) < 3) || diff(self->x, target->x) > 160){
            // Walk forward
            if(self->direction==1) self->xdir = self->model->speed;
            else self->xdir = -self->model->speed;
            walk = 1;
        }
        else if((rnum & 7) > 4){
            // Walk backward
            if(self->direction==1) self->xdir = -self->model->speed/2;
            else self->xdir = self->model->speed/2;
            walk = -1;
        }

        rnum = rand32();
        if((rnum & 7) < 2){
            // Move up
            self->zdir = -self->model->speed/2;
            walk |= 1;
        }
        else if((rnum & 7) > 5){
            // Move down
            self->zdir = self->model->speed/2;
            walk |= 1;
        }

 		if(walk && !self->model->nomove){
 			if(self->z - target->z > 5 && self->model->animation[ANI_UP]){	// Used for enemy up animation
 				ent_set_anim(self, ANI_UP);
 			}
 			else if(target->z - self->z > 5 && self->model->animation[ANI_DOWN]){	// Used for enemy down animation
 				ent_set_anim(self, ANI_DOWN);
 			}
 			else ent_set_anim(self, ANI_WALK);
 			// if(walk<0) self->animating = -1;
 		}
		else ent_set_anim(self, ANI_IDLE);

        self->stalltime = time + GAME_SPEED;
    }

    enemy_trymove(self->xdir, self->zdir);
}



void enemy_drop(void){
    if(inair(self)) return;
    self->think = enemy_think;
    if(self->health<=0) kill(self);
}



//  1-10-05   new enemy spawning function
void enemy_spawn(void){
    if(self->animating) return;
    self->think = enemy_think;
}
//



void enemy_jumpattack(void){
	if(freezeall || self->frozen) return;

    if(self->animpos != self->lastanimpos && self->animpos == self->animation->throwframe){
        star_spawn(self->x + (self->direction ? 56 : -56), self->z, self->a+67, self->direction, self);
    }

    self->lastanimpos = self->animpos;

    enemy_trymove(self->xdir, 0);
    if(inair(self)) return;

    self->attacking = 0;
    self->jumping = 0;
    self->xdir = 0;
    ent_set_anim(self, ANI_IDLE);
    self->think = enemy_think;
}



void enemy_throw(void){
    if(self->animating) return;
    self->think = enemy_think;
    ent_set_anim(self, ANI_IDLE);
}



void enemy_grab(void){
	entity *other = self->link;
    // if(self->link) return;
    if(self->animating && other && self->a == other->a) return;
    ent_unlink(self);
    self->think = enemy_think;
    self->attacking = 0;
    ent_set_anim(self, ANI_IDLE);
}



void enemy_grabbed(void){
    // Escape?
    if(time >= self->stalltime && self->model->animation[ANI_SPECIAL]){
        ent_unlink(self);
        ent_set_anim(self, ANI_SPECIAL);
        self->attacking = 1;
        self->think = enemy_attack;
        return;
    }

    // Just check if we're still grabbed...
    if(self->link) return;

    ent_set_anim(self, ANI_IDLE);
    self->xdir = self->zdir = 0;
    self->think = enemy_think;
}



void enemy_runoff(){
    entity *target = enemy_find_target();

    if(!target || self->frozen || freezeall) return;	// There are no players?
	if(!self->model->noflip) self->direction = (self->x < target->x);
    if(self->direction) self->xdir = -self->model->speed/2;
    else self->xdir = self->model->speed/2;
    enemy_trymove(self->xdir, 0);

    if(time > self->stalltime) self->think = enemy_think;
}



void enemy_attack(){
    entity *target;

	if(self->frozen || freezeall) return;

    self->attacking = 1;

/*
    if(self->animpos != self->lastanimpos && self->animpos == self->animation->throwframe){
        knife_spawn(self->x, self->z, self->a + self->animation->throwa, self->direction, self);
    }
    else if(self->animpos != self->lastanimpos && self->animpos == self->animation->shootframe){
        fireball_spawn(self->x, self->z, self->a + self->animation->throwa, self->direction, self);
    }

    	// Moved to update_ents section
    else if(self->animpos != self->lastanimpos && self->animpos == self->animation->jumpframe){
        if(self->animation->moveflag > 0) toss(self, self->animation->moveflag);
        else toss(self, self->model->jumpheight);
    }
	*/

    //self->lastanimpos = self->animpos;

	if(inair(self) && !freezeall){	// Now enemies can move with their jumpframes
		if(self->direction) enemy_trymove((self->animation->moveflag > 0) * 1.3, 0);	// If moveflag > 0 will move 1.3 else 0
		else enemy_trymove((self->animation->moveflag > 0) * -1.3, 0);
		return;
	}

    if(self->animating) return;

    self->attacking = 0;
    self->xdir = 0;
    self->zdir = 0;

    target = enemy_find_target();


    if(target && !self->model->nomove && diff(self->x, target->x)<80 && (rand32()&3)){
		ent_set_anim(self, ANI_WALK);
        self->think = enemy_runoff;
    }
    else{
        ent_set_anim(self, ANI_IDLE);
        self->think = enemy_think;
    }


    self->stalltime = time + GAME_SPEED;
}



// Idle a bit before attacking
void enemy_prepare(){
    entity *target = enemy_find_target();
    int pickable[3];

    if(!target || self->frozen){	// Added so enemies don't attack while frozen
        // There are no players/self frozen?
        self->think = enemy_think;
        return;
    }
    if(!self->model->noflip) self->direction = (self->x < target->x);

    // Wait...
    if(time < self->stalltime) return;


    // Pick an attack
    pickable[0] = pickable[1] = pickable[2] = 0;

    if(self->model->animation[ANI_ATTACK1] &&
            (self->direction ? target->x > self->x+self->model->animation[ANI_ATTACK1]->range[0] &&
            target->x < self->x+self->model->animation[ANI_ATTACK1]->range[1]
            : target->x < self->x-self->model->animation[ANI_ATTACK1]->range[0] &&
            target->x > self->x-self->model->animation[ANI_ATTACK1]->range[1])){

        pickable[0] = 1;
    }
    if(self->model->animation[ANI_ATTACK2] &&
            (self->direction ? target->x > self->x+self->model->animation[ANI_ATTACK2]->range[0] &&
            target->x < self->x+self->model->animation[ANI_ATTACK2]->range[1]
            : target->x < self->x-self->model->animation[ANI_ATTACK2]->range[0] &&
            target->x > self->x-self->model->animation[ANI_ATTACK2]->range[1])){

        pickable[1] = 1;
        if((rand32() & 31) < 10) pickable[0] = 0;
    }
    if(self->model->animation[ANI_ATTACK3] &&
            (self->direction ? target->x > self->x+self->model->animation[ANI_ATTACK3]->range[0] &&
            target->x < self->x+self->model->animation[ANI_ATTACK3]->range[1]
            : target->x < self->x-self->model->animation[ANI_ATTACK3]->range[0] &&
            target->x > self->x-self->model->animation[ANI_ATTACK3]->range[1])){

        pickable[2] = 1;
        if((rand32() & 31) < 10) pickable[1] = 0;
    }

    if(pickable[0]){
        ent_set_anim(self, ANI_ATTACK1);
        self->think = enemy_attack;
        return;
    }
    if(pickable[1]){
        ent_set_anim(self, ANI_ATTACK2);
        self->think = enemy_attack;
        return;
    }
    if(pickable[2]){
        ent_set_anim(self, ANI_ATTACK3);
        self->think = enemy_attack;
        return;
    }

    // No attack to perform, return to normal
    self->think = enemy_think;
}



void enemy_fall(void){
	if(freezeall) return;	// Added incase enemy is in midair when a special is executed

	if(self->frozen){	// If falling, unfreeze enemy
		if(self->colourmap == self->model->colourmap[self->model->fmap - 1]) self->colourmap = self->model->map;
		self->frozen = 0;
		self->freezetime = 0;
	}

	if(self->projectile == 1){
        if(self->direction) enemy_trymove(-2.5, 0);
        else enemy_trymove(2.5, 0);
    }
    else if(self->projectile == 2){
		if(self->direction) enemy_trymove(-self->model->throwdist, 0);
		else enemy_trymove(self->model->throwdist, 0);
	}
    else{
        if(self->direction) enemy_trymove(-1.2, 0);
        else enemy_trymove(1.2, 0);
    }

    // Still falling?
    if(inair(self)){
        if(self->a < PIT_DEPTH && self->health<=0) kill(self);
        return;
    }

    // Landed
    if(self->projectile > 0){
        if(!self->model->noquake) make_quake(4);	// Don't shake if specified
        if(self->takedamage && self->damage_on_landing){
            self->takedamage(self, self->damage_on_landing, 0, ATK_NORMAL);
        }
        self->damage_on_landing = 0;
        self->projectile = 0;
    }

    if(self->animation == self->model->animation[ANI_SHOCK] || self->animation == self->model->animation[ANI_BURN]){ // New animations
		ent_set_anim(self, ANI_FALL);	// Set to normal fall animation and change to last frame
		self->currentsprite = self->animation->sprite[self->animation->numframes - 1];
		self->animpos = self->animation->numframes - 1;
	}

    // Hard landing? Quake and bounce!
    if(self->tossv < -2){
        if(!self->model->noquake) make_quake(2);	// Don't shake if specified
        if(self->model->subtype != SUBTYPE_EWEAPON && self->type != TYPE_SHOT && smp_fall >= 0) sound_play_sample(smp_fall, 0, savedata.effectvol,savedata.effectvol, 100);
        toss(self, self->model->jumpheight/4);
        return;
    }

	if(self->boss && level_completed) tospeedup = 1;

    // Pause a bit...
    self->think = enemy_lie;
    self->stalltime = time + GAME_SPEED;
}



void enemy_lie(void){
    entity * target;

    // Died?
    if(self->health <= 0){

        if(self->model->falldie) ent_set_anim(self, ANI_DIE);  //6-2-2005 fall and then play die animation?

	    if(!self->model->nodieblink || (self->model->nodieblink == 1 && !self->animating)){	// Now have the option to blink or not
    		self->think = suicide;
    		self->blink = 1;
    		self->nextthink = time + GAME_SPEED * 2;
		}
		else if(self->model->nodieblink == 2 && !self->animating) self->think = suicide;

        return;
    }

     // Stall
    if(time < self->stalltime){
		if(freezeall) self->stalltime += 2;
		return;
	}

	// Used to pick a target for a riseattack

	if(player[1].ent && diff(self->z, player[1].ent->z) < (self->model->grabdistance/3) &&	// Used so closer player is attacked
	diff(self->x, player[1].ent->x) < diff(self->x, player[0].ent->x)) target = player[1].ent;
	else target = player[0].ent;

	self->think = enemy_rise;

    // Get up again

    // New riseattack section for a quicker counter when a player is hanging around waiting to cheap-shot the enemy
	if(
		self->model->animation[ANI_RISEATTACK] &&
		diff(self->z, target->z) < (self->model->grabdistance/3) &&	// If the target is at the correct z
		((target->x > self->x+self->model->animation[ANI_RISEATTACK]->range[0] &&
		target->x < self->x+self->model->animation[ANI_RISEATTACK]->range[1]) ||
		(target->x < self->x-self->model->animation[ANI_RISEATTACK]->range[0] &&
		target->x > self->x-self->model->animation[ANI_RISEATTACK]->range[1]))
	){

		self->direction = (target->x > self->x);	// Stands up and swings in the right direction depending on chosen target
		self->attacking = 1;
		ent_set_anim(self, ANI_RISEATTACK);
	}
	else ent_set_anim(self, ANI_RISE);

	self->xdir = self->zdir = 0;
}




void enemy_rise(void){
    if(self->animating) return;
    ent_set_anim(self, ANI_IDLE);
    self->xdir = self->zdir = 0;
    self->think = enemy_think;
}




void enemy_pain(void){
    if(self->animating) return;
    if(self->link){
		if(self->model->animation[ANI_GRABBED]) ent_set_anim(self, ANI_GRABBED);	// New grabbed animation
		else ent_set_anim(self, ANI_PAIN);
        self->think = enemy_grabbed;
    }
    else{
        ent_set_anim(self, ANI_IDLE);
        self->think = enemy_think;
    }
}



void enemy_block(void){	// New function for when an enemy blocks an attack
	if(self->animating) return;
	ent_set_anim(self, ANI_IDLE);
	self->think = enemy_think;
}



void enemy_takedamage(entity *other, int force, int drop, int type){
    entity *item;	// 7-1-2005 Enemies drop items
    int i;

    if(self->a<=PIT_DEPTH && self->health<=0){
        // Don't scream twice
        kill(self);
        return;
    }

    self->pain_time = time + (GAME_SPEED / 5);

	if((type != ATK_FREEZE || (type == ATK_FREEZE && self->frozen)) && !self->model->noflip && !inair(self)){	// Added !inair(self) so enemies don't change directions mid-air when a boss is defeated
    	if(self->x < other->x) self->direction = 1;
    	else if(self->x > other->x) self->direction = 0;
	}

    self->attacking = 0;

    if(other != self->link) ent_unlink(self);

	if(inair(self)) drop = 1;	// If in the air, automatically fall (freeze will override later if not frozen)

    if(type == ATK_BLAST){
        self->projectile = 1;
        drop = 1;
    }
	if(type == ATK_STEAL){	// New steal attack adds health removed from opponent
		if(self->health >= force) other->health += force;
		else other->health += self->health;
		if(other->health > other->maxhealth) other->health = other->maxhealth;
	}
    if(type == ATK_FREEZE && !self->frozen && self->model->subtype != SUBTYPE_EWEAPON && !self->model->nomove){	// New freeze attack - If not frozen, freeze entity unless it's a projectile
		self->frozen = 1;
		if(self->freezetime == 0) self->freezetime = time + other->animation->attack_length[other->animpos];
		if(self->model->fmap) ent_set_colourmap(self, self->model->fmap);
		drop = 0;
	}
	else if(self->frozen) drop = 1; // If frozen and any other attack is executed, drop the opponent

	if(self->model->nomove || self->model->nodrop) drop = 0;	// Static enemies/nodrop enemies cannot be knocked down

    self->health -= force;

    if(other->type == TYPE_PLAYER || other->type == TYPE_OBSTACLE || other->type == TYPE_SHOT){	// Added obstacle so explosions can hurt enemies
        player[other->playerindex].opponent = self;
        self->playerindex = other->playerindex;
        addscore(other->playerindex, force*self->multiple);	// New multiple variable
    }

	if(self->toexplode == 2) return;

    if(self->health <= 0){
		addscore(other->playerindex, self->score);	// Add score to the player
        self->nograb = 1;

        if(self->model->diesound>=0) sound_play_sample(self->model->diesound, 0, savedata.effectvol,savedata.effectvol, 100);

        // enemies can spawn item - including traps and other enemies!   7-1-2005  ltb 1-18-05 cleaned
        if(self->item[0] && (!self->if2p || count_ents(TYPE_PLAYER)>1)){
            item = spawn(self->x, self->z + 1, self->a, self->item);

            if(item->type == TYPE_ENEMY){
                item->boss = self->boss;
                self->boss = 0;

                if(self->itemhealth > 0) item->health = self->itemhealth;

                ent_set_colourmap(item, self->itemmap);

                if(self->itemalias[0]) strncpy(item->name, self->itemalias, MAX_NAME_LEN);

                if(item->model->animation[ANI_SPAWN]) item->think = enemy_spawn;
				else item->think = enemy_drop;

                item->takedamage = enemy_takedamage;

                if(!item->model->speed && !item->model->nomove) item->model->speed = 1;
                else if(item->model->nomove) item->model->speed = 0;

                if(item->model->subtype==SUBTYPE_NOTGRAB) item->nograb = 1;
                else if(item->model->subtype==SUBTYPE_BIKER){
                    item->think = biker_drive;
                    item->boss = self->boss;
                    self->boss = 0;
                    item->takedamage = biker_takedamage;
                }
                else if(item->model->subtype==SUBTYPE_ARROW) item->think = arrow_fly;
            }
            else if(item->type == TYPE_TRAP){
                item->think = trap_think;
            }
        }
        // end

        for(i=0; i<MAX_ENTS; i++){	// ltb 1-17-05  multiple bosses
            if((ent_list[i]->boss) && (ent_list[i] != self)) self->boss = 0;
        }

        if(self->boss){
            kill_all_enemies();
            level_completed = 1;
        }

        // Fell in a hole
        if(self->a < PIT_DEPTH){
            kill(self);
            return;
        }
    }


    // Don't animate or fall if hurt by self, since
    // it means self fell to the ground already. :)
    if(other==self){
        addscore(other->playerindex, force);
        return;
    }

    if(drop || self->health <= 0 || (inair(self) && !self->frozen)){	// So enemies frozen are frozen in midair
        ent_unlink(self);
        if(self->health <= 0 && !self->model->falldie && self->model->animation[ANI_DIE]) ent_set_anim(self, ANI_DIE); //  LTB 1-13-05  // 6-2-2005 fall and then play die animation?
        else{
			if(!self->model->animation[ANI_FALL]){	// Now if no fall/die animations exist, entity simply disapears
				kill(self);
				return;
			}

            toss(self, self->model->jumpheight/1.6 + randf(self->model->jumpheight/4));

            if(type == ATK_SHOCK && self->model->animation[ANI_SHOCK]) ent_reset_anim(self, ANI_SHOCK);	// New shock animation
            else if(type == ATK_BURN && self->model->animation[ANI_BURN]) ent_reset_anim(self, ANI_BURN);	// New burn animation
            else ent_reset_anim(self, ANI_FALL);
        }

        self->think = enemy_fall;
    }
    else if(!self->frozen){	// Don't change to pain animation if frozen
		if(type == ATK_SHOCK && self->model->animation[ANI_SHOCKPAIN]) ent_reset_anim(self, ANI_SHOCKPAIN);	// New pain when shocked animation
		else if(type == ATK_BURN && self->model->animation[ANI_BURNPAIN]) ent_reset_anim(self, ANI_BURNPAIN);	// New pain when burned animation
		else ent_reset_anim(self, ANI_PAIN);
        self->think = enemy_pain;
    }
}


//  for the arrow type  7-1-2005 Arrow start
void arrow_fly(void){
	int wall;

	if(freezeall) return;	// Don't move if special is being executed

    self->attacking = 1;

    if((!self->direction && self->x < advancex - 200) || (self->direction && self->x > advancex + 520)){
        kill(self);
        return;
    }

	if(!inair(self)){
	    if(self->direction) self->x += self->model->speed;	// Now arrows can have custom speeds
	    else self->x -= self->model->speed;	// Now arrows can have custom speeds
	}

	if(self->model->animation[ANI_FALL]){	// Added so projectiles bounce off blocked exits
		if((level->exit_blocked && self->x > level->width-30-(PLAYER_MAX_Z-self->z)) ||
		((wall = checkwall(self->x, self->z)) >= 0 && self->a < level->walls[wall][7])){
			self->attacking = 0;
			self->health -= 100000;
			self->projectile = 0;
			self->think = enemy_fall;
			self->damage_on_landing = 0;
			toss(self, 2.5 + randf(1));
			ent_reset_anim(self, ANI_FALL);
		}
	}

    self->nextthink = time + THINK_SPEED / 2;
}
//   7-1-2005 Arrow end.


void biker_drive(void){
	if(freezeall) return;	// Don't move if a special is being executed
    self->attacking = 1;
    if(self->direction){
        self->x += self->xdir;
        if(self->x > advancex+520){
            self->z = PLAYER_MIN_Z + randf(PLAYER_MAX_Z-PLAYER_MIN_Z);
            self->direction = 0;
            self->attack_id = 0;

            if(smp_bike >= 0) sound_play_sample(smp_bike, 0, savedata.effectvol,savedata.effectvol, 100);

            self->xdir = 1.7 + randf(0.6);
        }
    }
    else{
        self->x -= self->xdir;
        if(self->x < advancex-200){
            self->z = PLAYER_MIN_Z + randf(PLAYER_MAX_Z-PLAYER_MIN_Z);
            self->direction = 1;
            self->attack_id = 0;

            if(smp_bike >= 0) sound_play_sample(smp_bike, 0, savedata.effectvol,savedata.effectvol, 100);

            self->xdir = 1.7 + randf(0.6);
        }
    }

	self->nextthink = time + THINK_SPEED / 2;
}



void bike_crash(void){
    if(self->direction) self->x += 2;
    else self->x -= 2;

    self->nextthink = time + THINK_SPEED / 2;

    if(self->x < advancex-100 || self->x > advancex+420) kill(self);
}



void biker_takedamage(entity *other, int force, int drop, int type){  // 7-1-2005 - whole section replaced with lord ball's drop item code

    entity *driver;

    if(other->type == TYPE_PLAYER || other->type == TYPE_OBSTACLE || other->type == TYPE_SHOT){	// Added so obstacles can damage bikers
        player[other->playerindex].opponent = self;
        self->playerindex = other->playerindex;
        addscore(other->playerindex, force*self->multiple);	// Added force multiple
    }

	if(self->boss){
		kill_all_enemies();
		level_completed = 1;
	}

    // Fell in a hole
    if(self->a < PIT_DEPTH){
        if(self->model->diesound>=0) sound_play_sample(self->model->diesound, 0, savedata.effectvol,savedata.effectvol, 100);
        kill(self);
        return;
    }

    ent_reset_anim(self, ANI_PAIN);
    self->attacking = 1;
    self->think = bike_crash;
    self->nextthink = time + THINK_SPEED;

    if(driver = spawn(self->x, self->z, 10, self->model->rider)){
		if(!driver->model->speed && !driver->model->nomove) driver->model->speed = 1;
		else if(driver->model->nomove) driver->model->speed = 0;

        //	ltb 1-18-05 new biker item code
        strncpy(driver->item, self->item, MAX_NAME_LEN);
        //	end new code

        driver->maxhealth = self->maxhealth;
        driver->health = self->health - (force*3);
        if(driver->health <= 0){
            if(self->model->diesound>=0) sound_play_sample(self->model->diesound, 0, savedata.effectvol,savedata.effectvol, 100);
        }

        toss(driver, 2.5 + randf(1));

        if(self->model->animation[ANI_SHOCK] && type == ATK_SHOCK) ent_reset_anim(driver, ANI_SHOCK);	// Apr 14, 2005 - New animation
        else if(self->model->animation[ANI_BURN] && type == ATK_BURN) ent_reset_anim(driver, ANI_BURN);	// Apr 14, 2005 - New animation
        else ent_reset_anim(driver, ANI_FALL);

        driver->takedamage = enemy_takedamage;
        driver->think = enemy_fall;

        if(driver->model->subtype == SUBTYPE_NOTGRAB) driver->nograb = 1;
    }

    driver->boss = self->boss;	// so bikers can be bosses or rather their drivers
    self->boss = 0;	// because the bike isn't really the boss

    self->health = 0;
    addscore(other->playerindex, self->score);	// Add score to player
}



void obstacle_fall(void){
	if(freezeall) return;	// Added incase obstacle is in the air when special is executed

    if(inair(self)) self->x += self->xdir;
    else if((!self->animating && self->model->animation[ANI_DIE]) || !self->model->animation[ANI_DIE]) kill(self);	// Fixed so ANI_DIE can be used
}



void obstacle_fly(void){	// Now obstacles can fly when hit like on Simpsons/TMNT
	if(freezeall) return;

    self->x += self->xdir * 4;	// Equivelant of speed 40
    if(self->x > advancex+520 || self->x < advancex-200) kill(self);

    self->nextthink = time + 2;
}



void obstacle_takedamage(entity *other, int force, int unused1, int unused2){
    entity *item;

    if(self->a <= PIT_DEPTH){
        kill(self);
        return;
    }

	self->pain_time = time + (GAME_SPEED / 5);	// Added so obstacles don't receive extra damage by falling entities/multiple simultaneous attacks

    if(other->type==TYPE_PLAYER || other->type == TYPE_SHOT || other->type == TYPE_OBSTACLE) player[other->playerindex].opponent = self;

    self->health -= force;
	self->playerindex = other->playerindex;	// Added so points go to the correct player
    addscore(other->playerindex, force*self->multiple);	// Points can now be given for hitting an obstacle

    if(self->health<=0){
		addscore(other->playerindex, self->score);	// Additional score can be added

        if(self->model->diesound>=0) sound_play_sample(self->model->diesound, 0, savedata.effectvol,savedata.effectvol, 100);

        if(self->item[0] && (!self->if2p || count_ents(TYPE_PLAYER)>1)){

            item = spawn(self->x, self->z + 1, self->a, self->item);

            if(item){
				if(item->type == TYPE_ITEM) item->direction = 1;
				else if(item->type == TYPE_ENEMY){	// Obstacle spawning move here to fix "bouncing enemy" bug

					if(item->model->animation[ANI_SPAWN]) item->think = enemy_spawn;
					else item->think = enemy_drop;

					item->boss = self->boss;
					self->boss = 0;

	                if(self->itemhealth > 0) item->health = self->itemhealth;
	                ent_set_colourmap(item, self->itemmap);

	                if(self->itemalias[0]) strncpy(item->name, self->itemalias, MAX_NAME_LEN);

					if(!item->model->speed && !item->model->nomove) item->model->speed = 1;	// Sets default speed if not set and not static
					else if(item->model->nomove) item->model->speed = 0;

					item->takedamage = enemy_takedamage;

					if(item->model->subtype==SUBTYPE_NOTGRAB) item->nograb = 1;
					else if(item->model->subtype==SUBTYPE_BIKER){
						item->think = biker_drive;
						item->boss = self->boss;
						self->boss = 0;
						item->takedamage = biker_takedamage;
					}
					else if(item->model->subtype==SUBTYPE_ARROW) item->think = arrow_fly;
				}
				else if(item->type == TYPE_TRAP) item->think = trap_think;
			//  7-1-2005 - new section ends
			}
        }

        if(other->x < self->x) self->xdir = 1;
        else self->xdir = -1;

        self->attacking = 1;	// So obstacles can explode and hurt players/enemies

		if(self->model->subtype == SUBTYPE_FLYDIE){	// Now obstacles can fly like on Simpsons/TMNT
			self->think = obstacle_fly;
			ent_set_anim(self, ANI_FALL);
		}
		else{
			self->think = obstacle_fall;

			if(self->model->animation[ANI_DIE]) ent_set_anim(self, ANI_DIE);	//  LTB 1-13-05  Die before toss
			else{
				toss(self, self->model->jumpheight/1.333);
				ent_set_anim(self, ANI_FALL);
			}

			if(!self->model->nodieblink) self->blink = 1;
		}
  	}

    self->nextthink = time + 1;
}



entity * smartspawn(s_spawn_entry * props){   // 7-1-2005 Entire section replaced with lord balls code
    entity *e;
    entity *other;

    int dodrop;
    int wall;

    if(!props || (props->spawn2p && count_ents(TYPE_PLAYER)<2)) return NULL;	// Now you can make it so enemies/obstacles/etc only spawn if there are 2 players

	wall = checkwall(props->x + (int)advancex, props->z);
	other = check_platform(props->x + (int)advancex, props->z);

	if(other) e = spawn(props->x + (int)advancex, props->z, props->a + other->a + other->animation->platform[other->animpos][5], props->name);
    else e = spawn(props->x + (int)advancex, props->z, props->a + level->walls[wall][7], props->name);

    if(e==NULL) return NULL;

    // Alias?
    if(props->alias[0]) strncpy(e->name, props->alias, MAX_NAME_LEN);

	if(props->itemalias[0]) strncpy(e->itemalias, props->itemalias, MAX_NAME_LEN);
	if(props->itemmap) e->itemmap = props->itemmap;
	if(props->itemhealth) e->itemhealth = props->itemhealth;

    if(props->health != 0){
        e->health = props->health;
        e->maxhealth = props->health;
    }

    if(props->health2p != 0 && count_ents(TYPE_PLAYER)>1){	// Now health can be increased for 2 players
		e->health = props->health2p;
		e->maxhealth = props->health2p;
	}

    if(props->score != 0) e->score = props->score;	// Overwrite score if exists in the level's. txt file
    if(props->multiple != 0) e->multiple = props->multiple;	// Overwrite multiple if exists in the level's .txt file

    ent_set_colourmap(e, props->colourmap);

    if(props->colourmap) e->model->map = e->model->colourmap[props->colourmap - 1];	// Used for restoring the map after changing it
    else e->model->map = NULL;	// No map exists

	// Feb 26, 2005 - Store the original map to be able to restore with dying flash
	if(props->dying){
		e->dying = props->dying;	// Feb 26, 2005 - Used to define which colourmap is used for the dying flash
		e->per1 = props->per1;	// Mar 21, 2005 - Used to store custom percentages
		e->per2 = props->per2;	// Mar 21, 2005 - Used to store custom percentages
	}

	if(props->nolife) e->nolife = props->nolife;	// Overwrite whether live is visible or not

    if(props->boss && level && level->bossmusic[0]){
        music(level->bossmusic, 1);
    }

    e->direction = props->flip;

    switch(e->type){
    case TYPE_ITEM:
        //			e->direction = 1;	//  so items are not flipped
        e->boss = props->boss;	// item bosses?
        break;

    case TYPE_PLAYER:
        e->direction = (level->scrolldir != SCROLL_LEFT);
        e->takedamage = player_takedamage;

        if(time){
            // Mid-level spawn
			if(level->spawn[e->playerindex][2] > e->a) e->a = level->spawn[e->playerindex][2];

            e->jumping = 1;
            ent_set_anim(e, ANI_JUMP);
            e->think = player_jump;

			if(e->model->makeinv > 0){	// Spawn invincible code
				e->invincible = 1;
				e->blink = 1;
				e->invinctime = time + e->model->makeinv;
				e->arrowon = 1;	// Display the image above the player
			}
        }
        else{
    		ent_set_anim(e, ANI_IDLE);
            e->think = player_think;
        }

        break;

    case TYPE_ENEMY:
        strncpy(e->item, props->item, MAX_NAME_LEN);
        e->if2p = props->if2p;
        e->boss = props->boss;

        if(e->model->subtype==SUBTYPE_BIKER){
            e->takedamage = biker_takedamage;
            e->think = biker_drive;
            e->nograb = 1;
            e->attacking = 1;
            if(e->x < 0) e->direction = 0;
            else e->direction = 1;
            e->xdir = 2;
            break;
        }
        // define new subtypes
        else if(e->model->subtype==SUBTYPE_ARROW){
            e->takedamage = enemy_takedamage;
            e->health = 1;

            if(!e->model->speed) e->model->speed = 2;	// Set default speed to 2 for arrows
            e->think = arrow_fly;
            e->nograb = 1;
            e->attacking = 1;
            break;
        }
        else{	// Must just be a regular enemy, set defaults accordingly

	       	if(!e->model->speed && !e->model->nomove) e->model->speed = 1;
        	else if(e->model->nomove) e->model->speed = 0;

        	if(!e->multiple) e->multiple = 5;
		}

        if(e->model->subtype==SUBTYPE_NOTGRAB) e->nograb = 1;

        if(e->model->cantgrab) e->nograb = 1;	//27-12-2004 if can't grab is on, character is ungrabbable

        e->takedamage = enemy_takedamage;

        // 1-10-05  no more enemies dropping in unless they don't have a ANI_SPWN
        if(e->model->animation[ANI_SPAWN]) e->think = enemy_spawn;
		else{
            e->think = enemy_drop;
            dodrop = (level && (level->scrolldir==SCROLL_UP || level->scrolldir==SCROLL_DOWN));

            if((dodrop && e->x > -30 && e->x < 350) || (props->x > 0 && props->x < 320)){
                e->a += 240 + randf(40);

                if(e->health<1){
                    // Funny: drop dead right away
                    ent_set_anim(e, ANI_FALL);
                    e->think = enemy_fall;
                }
            }
        }
        break;
        // define trap type
    case TYPE_TRAP:
        e->think = trap_think;
        e->takedamage =  enemy_takedamage;
        break;
        //

    case TYPE_OBSTACLE:
        strncpy(e->item, props->item, MAX_NAME_LEN);
        e->if2p = props->if2p;
        e->takedamage = obstacle_takedamage;
        e->boss = props->boss;	//  LTB 1-13-05   Obstacle bosses
        ent_set_colourmap(e, props->colourmap);
        break;

    case TYPE_STEAMER:
        e->think = steamer_think;
        e->base = e->a;
        break;

	case TYPE_TEXTBOX:	// New type for displaying text purposes
		e->think = text_think;
		freezeall = 1;	// Pause all animations while text is displayed
		break;

    case TYPE_NONE:
        if(e->model->animation[ANI_WALK]){
            if(props->x < 160) e->xdir = e->model->speed + randf(3);
            else e->xdir = -(e->model->speed + randf(3));
            e->think = anything_walk;
            ent_set_anim(e, ANI_WALK);
        }
        break;
    }
    return e;
}   // 7-1-2005 replaced section ends here



void spawnplayer(int index){
    s_spawn_entry p;
    int w;
    index &= 1;

    if(!player[index].model) return;

    memset(&p, 0, sizeof(s_spawn_entry));
    strncpy(&p.name, player[index].model->name, MAX_NAME_LEN);
    p.colourmap = player[index].colourmap;

	if(level->scrolldir==SCROLL_LEFT){
		if(level->spawn[index][0]) p.x = 320 - level->spawn[index][0];
		else p.x = 300 - 30*index;
	}
	else{
		if(level->spawn[index][0]) p.x = level->spawn[index][0];
		else p.x = 20 + 30*index;
	}

	if(level->spawn[index][1]){

		if(!checkhole(advancex + p.x, PLAYER_MIN_Z + level->spawn[index][1])){
			p.z = PLAYER_MIN_Z + level->spawn[index][1];
		}
		else if(!checkhole(advancex + p.x, (level->holes[holez][1] - level->holes[holez][6] + PLAYER_MIN_Z) / 2)){
			p.z = (level->holes[holez][1] - level->holes[holez][6] + PLAYER_MIN_Z) / 2;
		}
		else{
			p.z = (level->holes[holez][1] + PLAYER_MAX_Z) / 2;
		}
	}
	else p.z = PLAYER_MIN_Z + 5;  // 6-2-2005 players no longer spawn in holes

	if(checkhole(advancex + p.x, p.z)){
		p.z = (level->holes[holez][1] * 2 - level->holes[holez][6]) / 2;

		if(level->scrolldir == SCROLL_LEFT)
			p.x = (level->holes[holez][2] + level->holes[holez][3]) / 2 + 5;
		else
			p.x = (level->holes[holez][4] + level->holes[holez][5]) / 2 + 5;
	}

    player[index].ent = smartspawn(&p);

    if(player[index].ent==NULL) shutdown("Fatal: unable to spawn player from '%s'", &p.name);

    player[index].ent->playerindex = index;

    if(player[index].spawnhealth) player[index].ent->health = player[index].spawnhealth + 5;

    if(player[index].ent->health > player[index].ent->model->health) player[index].ent->health = player[index].ent->model->health;

    for(w=0; w<MAX_ANIS; w++){
        player[index].animation[w] = player[index].model->animation[w];
    }
}





void time_over(){
	if(level->type == 1) level_completed = 1;		//	Feb 25, 2005 - Used for bonus levels so a life isn't taken away if time expires.level->type == 1 means bonus level, else regular
	else{
		if(!player[0].ent && !player[1].ent) endgame = 1;

		if(player[0].ent && player[0].ent->takedamage){
			self = player[0].ent;
			self->takedamage(self, 1000, 1, 0);
		}
		if(player[1].ent && player[1].ent->takedamage){
			self = player[1].ent;
			self->takedamage(self, 1000, 1, 0);
		}

		if(smp_timeover >= 0) sound_play_sample(smp_timeover, 0, savedata.effectvol,savedata.effectvol, 100);

		timeleft = level->settime * COUNTER_SPEED;	// Feb 24, 2005 - This line moved here to set custom time
		showtimeover = 1;
	}
}







// ----------------------- Update functions ------------------------------


// Used by screenshot func
int fileexists(char *fnam){
    int handle;
    if((handle=open(fnam, O_RDONLY|O_BINARY))<0) return 0;
    close(handle);
    return 1;
}


void screenshot(){
    static int shotnum = 0;
    static char shotname[20];
    do{
        sprintf(shotname, "s%04u.pcx", shotnum);
        ++shotnum;
    }while(fileexists(shotname) && shotnum<1000000);
    if(shotnum<10000) savepcx(shotname, vscreen, pal);
    debug_printf("Saved %s.", shotname);
}



void update_scroller(){
    float to;

    if(time < advancetime || freezeall) return;	// Added freezeall so backgrounds/scrolling don't update if animations are frozen

    //advancetime = time + (GAME_SPEED/100);	// Changed so scrolling speeds up for faster players
    advancetime = time - ((player[0].ent && (player[0].ent->model->speed >= 12 || player[0].ent->model->runspeed >= 12)) ||
    	(player[1].ent && (player[1].ent->model->speed >= 12 || player[1].ent->model->runspeed >= 12)));	// Changed so if your player is faster the backgrounds scroll faster

    if(level_completed) return;

	if(current_spawn>=level->numspawns && !findent(TYPE_ENEMY)){
		if(!findent(TYPE_ENDLEVEL) && ((!findent(TYPE_ITEM) && !findent(TYPE_OBSTACLE) && level->type) || level->type != 1)){	// Feb 25, 2005 - Added so obstacles
			level_completed = 1;												// can be used for bonus levels
		}
	}
    else if(count_ents(TYPE_ENEMY) < groupmin){
        while(count_ents(TYPE_ENEMY) < groupmax &&
        	current_spawn<level->numspawns &&
        	levelpos >= level->spawnpoints[current_spawn].at
		){

            if(level->spawnpoints[current_spawn].wait){
				setwait = levelpos;
                level_waiting = 1;
                go_time = 0;
            }
            else if(level->spawnpoints[current_spawn].groupmin || level->spawnpoints[current_spawn].groupmax){
                groupmin = level->spawnpoints[current_spawn].groupmin;
                groupmax = level->spawnpoints[current_spawn].groupmax;
            }
            else smartspawn(&level->spawnpoints[current_spawn]);
            ++current_spawn;
        }
    }

    if(level_waiting){
        // Wait for all enemies to be defeated
        if(!findent(TYPE_ENEMY)){

	        level_waiting = 0;
	        timeleft = level->settime * COUNTER_SPEED;	// Feb 24, 2005 - This line moved here to set custom time
	        go_time = time + 3*GAME_SPEED;
		}
    }

    if(level->scrolldir==SCROLL_RIGHT || level->scrolldir==SCROLL_BOTH){
		if(player[0].ent && player[1].ent) to = (player[0].ent->x + player[1].ent->x) / 2;
		else if(player[0].ent) to = player[0].ent->x;
		else if(player[1].ent) to = player[1].ent->x;
		else return;
		to -= 160;

        if(!level_waiting || (level_waiting && levelpos <= setwait)){

			if(to > advancex){
				if(to > advancex+1) to = advancex+1;
				advancex = to;
			}
			if(advancex > level->width-320){
				advancex = level->width-320;
			}
			if(advancex < 0) advancex = 0;

			levelpos = advancex;
		}

        if(level->scrolldir==SCROLL_BOTH){

			if(to < advancex){
				if(to < advancex-1) to = advancex-1;
				advancex = to;
			}
			if(advancex > level->width-320){
				advancex = level->width-320;
			}
			if(advancex < 0) advancex = 0;

	        levelpos = advancex;
		}
    }
    else if(level->scrolldir==SCROLL_LEFT){
        if(player[0].ent && player[1].ent) to = (player[0].ent->x + player[1].ent->x) / 2;
        else if(player[0].ent) to = player[0].ent->x;
        else if(player[1].ent) to = player[1].ent->x;
        else return;
        to -= 160;

        if(to < advancex){
            if(to < advancex-1) to = advancex-1;
            advancex = to;
        }
        if(advancex > level->width-320){
            advancex = level->width-320;
        }
        if(advancex < 0) advancex = 0;

        levelpos = (level->width-320) - advancex;
    }
    else{
        advancey += 0.5;
        levelpos = advancey;
    }
}




void draw_scrolled_bg(){
    int i;
    int inta;
    int poop;
    int index;
    static int neon_count = 0;
    static int rockpos = 0;
    static int rockoffs[32] = {
                                  2, 2, 3, 4, 5, 6, 7, 7,
                                  8, 8, 9, 9, 9, 9, 8, 8,
                                  7, 7, 6, 5, 4, 3, 2, 2,
                                  1, 1, 0, 0, 0, 0, 1, 1
                              };
    if(advancex < 0) advancex = 0;

    if(background){
        switch(level->scrolldir){
        case SCROLL_DOWN:
            inta = (advancey+0.5)/2;
            inta %= background->height;

            for(i=-inta; i<240; i+=background->height){
                copyscreen_o(vscreen, background, 0, i);
            }
            break;
        case SCROLL_UP:
            inta = (advancey+0.5)/2;
            inta %= background->height;

            for(i=inta-background->height; i<240; i+=background->height){
                copyscreen_o(vscreen, background, 0, i);
            }
            break;
        default:
            inta = (advancex+0.5)/2;
            if(level && level->rocking) inta += (time/(GAME_SPEED/30));
            if(level && level->bgspeed > 0) inta += (time/(30 / level->bgspeed) * 2);
            inta %= background->width;

            if(!level->bgdir){
            	for(i=-inta; i<320; i+=background->width){
            	    copyscreen_o(vscreen, background, i, 0);
            	}
			}
			else{
				for(i=inta-background->width; i<320; i+=background->width){
            	    copyscreen_o(vscreen, background, i, 0);
				}
			}

        }



        // Append bg with texture?
        if(texture && level){
            inta = advancex/2;
            //	2-10-05  changed 160 to BGHEIGHT
            if(level->rocking){
                inta += (time/(GAME_SPEED/30));
                texture_plane(vscreen, 0,background->height, vscreen->width,BGHEIGHT-background->height, inta*256, 10, texture);
            }
            else texture_wave(vscreen, 0,background->height, vscreen->width, BGHEIGHT-background->height, inta,0, texture, time, 5);
            //
        }
    }

    if(level==NULL) return;

    if(level->rocking){
        rockpos = (time/(GAME_SPEED/8)) & 31;
        gfx_y_offset = quake - 4 - rockoffs[rockpos];
    }
    else if(time){
        gfx_y_offset = quake - 4;
    }


    // Draw 3 layers: screen, normal and neon
    if(panels_loaded && panel_width){
        if(time>=neon_time && !freezeall){	// Added freezeall so neon lights don't update if animations are frozen
            for(i=0; i<8; i++){
                neontable[128+i] = 128 + ((i+neon_count) & 7);
            }
            neon_time = time + (GAME_SPEED/3);
            neon_count += 2;
        }

        if(level->scrolldir==SCROLL_UP || level->scrolldir==SCROLL_DOWN){
            inta = 0;
        }
        else inta = advancex;
        poop = inta / panel_width;
        inta %= panel_width;
        for(i=-inta; i<=320 && poop>=0 && poop<level->numpanels; i+=panel_width){
            index = level->order[poop];
            if(panels[index].sprite_normal) spriteq_add(i,gfx_y_offset, PANEL_Z, panels[index].sprite_normal, 0, NULL);
            if(panels[index].sprite_neon)   spriteq_add(i,gfx_y_offset, NEONPANEL_Z, panels[index].sprite_neon, SFX_REMAP, neontable);
            if(panels[index].sprite_screen) spriteq_add(i,gfx_y_offset, SCREENPANEL_Z, panels[index].sprite_screen, SFX_BLEND, lut_screen);
            poop++;
        }
    }


    for(i=0; i<level->numholes; i++){
        spriteq_add(level->holes[i][0]-advancex,level->holes[i][1] - level->holes[i][6] + 4 + gfx_y_offset, HOLE_Z, sprites[1][holesprite], 0, NULL);
    }

    if(frontpanels_loaded){
        if(level->scrolldir==SCROLL_UP || level->scrolldir==SCROLL_DOWN){
            inta = 0;
        }
        else inta = advancex * 1.4;
        poop = inta / frontpanels[0]->width;
        inta %= frontpanels[0]->width;
        for(i=-inta; i<=320; i+=frontpanels[0]->width){
            poop %= frontpanels_loaded;
            spriteq_add(i,gfx_y_offset, FRONTPANEL_Z, frontpanels[poop], 0, NULL);
            poop++;
        }
    }

    if(quake>0 && time>=quaketime){
        --quake;
        quaketime = time + (GAME_SPEED/25);
    }
}





static char debug_msg[2048];
unsigned long debug_time = 0xFFFFFFFF;


void debug_printf(char *format, ...){
    va_list arglist;

    va_start(arglist, format);
    vsprintf(debug_msg, format, arglist);
    va_end(arglist);

    debug_time = 0xFFFFFFFF;
}





void update(int ingame, int usevwait){

    int slowmo = 0;
    unsigned long newtime;
    int p;

    unsigned long interval;

    display_ents();

    if(ingame){
        draw_scrolled_bg();
        predrawstatus();
        slowmo = level_completed;
    }
    else if(background) copyscreen(vscreen, background);

    spriteq_draw(vscreen);
    if(ingame) drawstatus();

    if(bothnewkeys & FLAG_SCREENSHOT) screenshot();

    // Debug stuff, should not appear on screenshot
    if(debug_time==0xFFFFFFFF) debug_time = time + GAME_SPEED * 5;
    if(time<debug_time && debug_msg[0]){
        spriteq_clear();
        font_printf(0,230, 0, debug_msg);
        spriteq_draw(vscreen);
    }
    else{
        debug_msg[0] = 0;
#ifdef DEBUG_MODE
        if(levelpos) debug_printf("Position: %i, width: %i, spawn: %i, offsets: %i/%i", levelpos, level->width, current_spawn, quake, gfx_y_offset);
#endif
    }


    if(usevwait) vga_vwait();
    if(rescreen){
        scalescreen(rescreen, vscreen);
        video_copy_screen(rescreen);
    }
    else video_copy_screen(vscreen);

    spriteq_clear();

    control_update(playercontrolpointers, 2);


    bothkeys = 0;
    bothnewkeys = 0;

    for(p=0; p<2; p++){
        player[p].keys = playercontrolpointers[p]->keyflags;
        player[p].newkeys = playercontrolpointers[p]->newkeyflags;
        player[p].playkeys |= player[p].newkeys;
        player[p].playkeys &= player[p].keys;

        bothkeys |= player[p].keys;
        bothnewkeys |= player[p].newkeys;
    }

    interval = timer_getinterval(GAME_SPEED);

    if(interval > GAME_SPEED) interval = GAME_SPEED/200;
    if(interval > GAME_SPEED/4) interval = GAME_SPEED/4;

    if(slowmo && !level->noslow && !tospeedup){
		if(slo==2) newtime = time + interval;
    }
    else newtime = time + interval;

    slo++;

    if(slo==3) slo = 0;
    if(pause) newtime = time;
    if(newtime > time + 100) newtime = time + 100;

    while(time < newtime){
        update_ents();

        if(ingame){

            update_scroller();

            if(!freezeall){
				if(level->settime > 0 || (!player[0].ent && !player[1].ent)){	// Now select player time limit works
            		if(timeleft>0) --timeleft;
            		else if(!player[0].joining && !player[1].joining) time_over();	// Now exits the game if no one selects a character
				}
			}
        }

        ++time;
    }

    sound_update_music();
}




// ----------------------------------------------------------------------



// Simple palette fade
void fade_out(){
    int i, j;
    int x, y;
    int b, g;
    int xd, yd, d;
    char r=0, s=0;
    unsigned long interval;

    timer_getinterval(fade);   // 13-1-2005 fade speed selection
    for(i=0, j=0; j<64; ){
        while(j<=i){
            b = ((savedata.brightness+256) * (64-j) / 64) - 256;
            g = 256 - ((savedata.gamma+256) * (64-j) / 64);
            vga_vwait();
            palette_set_corrected(pal, g,savedata.gamma,savedata.gamma, b,b,b);
            j++;
        }

        if(rescreen){
            scalescreen(rescreen, vscreen);
            video_copy_screen(rescreen);
        }
        else video_copy_screen(vscreen);

        sound_update_music();
        if (musicoverlap==0) sound_volume_music(savedata.musicvol*(64-j)/64, savedata.musicvol*(64-j)/64);
        interval = timer_getinterval(fade);
        if(interval > fade) interval = fade/60;
        if(interval > fade/4) interval = fade/4;

        i += interval;  // 13-1-2005 fade speed selection
    }
    if (musicoverlap==0) sound_close_music();

    clearscreen(vscreen);
    if(rescreen){
        scalescreen(rescreen, vscreen);
        video_copy_screen(rescreen);
    }
    else video_copy_screen(vscreen);
    vga_vwait();
    palette_set_corrected(pal, savedata.gamma,savedata.gamma,savedata.gamma, savedata.brightness,savedata.brightness,savedata.brightness);
}



void apply_controls(){
    int p;

    for(p=0; p<2; p++){
        control_setkey(playercontrolpointers[p], FLAG_ESC, 	  CONTROL_ESC);
        control_setkey(playercontrolpointers[p], FLAG_MOVEUP,     savedata.keys[p][SDID_MOVEUP]);
        control_setkey(playercontrolpointers[p], FLAG_MOVEDOWN,   savedata.keys[p][SDID_MOVEDOWN]);
        control_setkey(playercontrolpointers[p], FLAG_MOVELEFT,   savedata.keys[p][SDID_MOVELEFT]);
        control_setkey(playercontrolpointers[p], FLAG_MOVERIGHT,  savedata.keys[p][SDID_MOVERIGHT]);
        control_setkey(playercontrolpointers[p], FLAG_SPECIAL,    savedata.keys[p][SDID_SPECIAL]);
        control_setkey(playercontrolpointers[p], FLAG_ATTACK,     savedata.keys[p][SDID_ATTACK]);
        control_setkey(playercontrolpointers[p], FLAG_JUMP,       savedata.keys[p][SDID_JUMP]);
        control_setkey(playercontrolpointers[p], FLAG_START,      savedata.keys[p][SDID_START]);
        control_setkey(playercontrolpointers[p], FLAG_SCREENSHOT, savedata.keys[p][SDID_SCREENSHOT]);
    }
}



// ----------------------------------------------------------------------





void shutdown(char *msg, ...){
    static char buf[2048];
    va_list arglist;

    va_start(arglist, msg);
    vsprintf(buf, msg, arglist);
    va_end(arglist);

    savesettings();
    video_set_mode(0,0);

    printf("Release level data...\n");
    unload_levelorder();
    unload_level();

    printf("Release graphics data...\n");
    freescreen(vscreen);
    freescreen(rescreen);
    freesprites();
    font_unload(0);
    font_unload(1);
    font_unload(2);
    font_unload(3);    // 5-1-2005 Last font wasn't being unloaded


    printf("Release game data...\n");
    free_ents();
    free_models();
    free_modelcache();

    printf("Release timer...\n");
    timer_exit();

    printf("Release input hardware...\n");
    control_exit();

    printf("Release sound system...\n");
    sound_exit();

    printf("Done.\n\n\n\n");
    printf(buf);

    exit(0);
}


void guistartup(){
    int i;

    printf("Beats of Rage Unofficial V%X.%04X, compile date: " __DATE__ "\n\n", VERSION>>16, VERSION&0xFFFF);

    loadsettings();

    printf("Screen allocation...\n");
    if((vscreen = allocscreen(320, 240)) == NULL){
        shutdown("Not enough memory!\n");
    }
    clearscreen(vscreen);

    printf("Loading font...\n");
    if(!font_load(3, "gui/font4", packfile)) shutdown("Unable to load PAK selector FONT4.GIF\n");

    printf("Timer init...\n");
    timer_init();

    if(savedata.usesound && sound_init(6)){
        printf("Soundcard initialized.\n");
        if(!sound_start_playback(0,savedata.soundbits,savedata.soundrate)){
            printf("Warning: can't play sound at %u Hz!\n", savedata.soundrate);
        }
        SB_setvolume(SB_MASTERVOL, 15);
        SB_setvolume(SB_VOICEVOL, savedata.soundvol);


    }
    else{
        printf("Sound disabled.\n");
    }

    printf("Object engine init...\n");
    if(!alloc_ents()){
        shutdown("Not enough memory for game objects!\n");
    }



    printf("Input init...\n");
    control_init(savedata.usejoy);

    apply_controls();

    printf("Video mode...\n");
    if(rescreen){
        if(!video_set_mode(rescreen->width, rescreen->height)){
            shutdown("Unable to set video mode: %ix%i!\n", rescreen->width, rescreen->height);
        }
    }
    else if(!video_set_mode(320, 240)){
        shutdown("Unable to set video mode: 320x240!\n");
    }

    for(i=0; i<256; i++) neontable[i] = i;
}

void startup(){
    int i;
    video_set_mode(0,0);

    printf("Beats of Rage Unofficial V%X.%04X, compile date: " __DATE__ "\n\n", VERSION>>16, VERSION&0xFFFF);
    printf;
    printf("Weapons Version - WARNING: weapons require more memory\n");
    printf("Mod creators are responsible for memory issues\n");

    loadsettings();

    printf("Screen allocation...\n");
    if((vscreen = allocscreen(320, 240)) == NULL){
        shutdown("Not enough memory!\n");
    }
    clearscreen(vscreen);

    printf("Loading font...\n");
    printf("Loading mod: %s \n",packfile);
    if(!font_load(0, "data/sprites/font", packfile)) shutdown("Unable to load font #1!\n");
    if(!font_load(1, "data/sprites/font2", packfile)) shutdown("Unable to load font #2!\n");
    if(!font_load(2, "data/sprites/font3", packfile)) shutdown("Unable to load font #3!\n");
    if(!font_load(3, "data/sprites/font4", packfile)) shutdown("Unable to load font #4!\n");

    printf("Timer init...\n");
    timer_init();

    if(savedata.usesound && sound_init(6)){
        printf("Soundcard initialized.\n");
        if(!sound_start_playback(0,savedata.soundbits,savedata.soundrate)){
            printf("Warning: can't play sound at %u Hz!\n", savedata.soundrate);
        }
        SB_setvolume(SB_MASTERVOL, 15);
        SB_setvolume(SB_VOICEVOL, savedata.soundvol);

        printf("Loading sounds...\n");
        smp_go = loadcache_sound("data/sounds/go.wav");   //26-12-2004  - Loads "Go.wav" sample into smp_go
        smp_beat = loadcache_sound("data/sounds/beat1.wav");
        smp_block = loadcache_sound("data/sounds/block.wav");	// New block sound effect
        smp_fall = loadcache_sound("data/sounds/fall.wav");
        smp_get =  loadcache_sound("data/sounds/get.wav");
        smp_get2 = loadcache_sound("data/sounds/money.wav");
        smp_jump = loadcache_sound("data/sounds/jump.wav");
        smp_indirect = loadcache_sound("data/sounds/indirect.wav");
        smp_punch = loadcache_sound("data/sounds/punch.wav");
        smp_1up = loadcache_sound("data/sounds/1up.wav");
        smp_timeover = loadcache_sound("data/sounds/timeover.wav");
        smp_beep = loadcache_sound("data/sounds/beep.wav");
        smp_beep2 = loadcache_sound("data/sounds/beep2.wav");
        smp_bike = loadcache_sound("data/sounds/bike.wav");
    }
    else{
        printf("Sound disabled.\n");
    }

    printf("Object engine init...\n");
    if(!alloc_ents()){
        shutdown("Not enough memory for game objects!\n");
    }

    printf("Loading sprites...\n");
    shadowsprites[0] = loadsprite("data/sprites/shadow1",9,3);
    shadowsprites[1] = loadsprite("data/sprites/shadow2",14,5);
    shadowsprites[2] = loadsprite("data/sprites/shadow3",19,6);
    shadowsprites[3] = loadsprite("data/sprites/shadow4",24,8);
    shadowsprites[4] = loadsprite("data/sprites/shadow5",29,9);
    shadowsprites[5] = loadsprite("data/sprites/shadow6",34,11);
    gosprite = loadsprite("data/sprites/arrow",35,23);

    //	ltb 1-17-05  check to see if a go left sprite exists and use it if so
    if(openpackfile("data/sprites/arrowl.gif", packfile) >=0) golsprite = loadsprite("data/sprites/arrowl",35,23);
    //

    //	holesprite = loadsprite("data/sprites/hole",0,0);

    printf("Loading models...\n");
    load_models();

    printf("Loading level order...\n");
    load_levelorder();


    printf("Input init...\n");
    control_init(savedata.usejoy);

    apply_controls();

    printf("Video mode...\n");
    if(rescreen){
        if(!video_set_mode(rescreen->width, rescreen->height)){
            shutdown("Unable to set video mode: %ix%i!\n", rescreen->width, rescreen->height);
        }
    }
    else if(!video_set_mode(320, 240)){
        shutdown("Unable to set video mode: 320x240!\n");
    }

    for(i=0; i<256; i++) neontable[i] = i;
}






// ----------------------------------------------------------------------------


// Returns 0 on error, -1 on escape
int playgif(char *filename, int x, int y){
    int code;
    int delay;
    unsigned long milliseconds;
    unsigned long nextframe;
    unsigned long lasttime;
    int done;
    int frame = 0;
    int synctosound = 0;


    // Clear background
    unload_background();
    background = allocscreen(320, 240);
    if(background==NULL) shutdown("Out of memory!");
    clearscreen(background);


    if(!anigif_open(filename, packfile, pal)) return 0;


    time = 0;
    lasttime = 0;
    milliseconds = 0;
    nextframe = 0;
    delay = 100;
    code = ANIGIF_DECODE_RETRY;
    done = 0;
    synctosound = (sound_getinterval() != 0xFFFFFFFF);

    while(!done){
        if(milliseconds >= nextframe){
            if(code != ANIGIF_DECODE_END){
                while((code = anigif_decode(background, &delay, x, y)) == ANIGIF_DECODE_RETRY);
                // if(code == ANIGIF_DECODE_FRAME){
                // Set time for next frame
                nextframe += delay * 10;
                // }
            }
            else done = 1;
        }
        if(code == ANIGIF_DECODE_END) break;

        if(frame==0){
            vga_vwait();
            pal[0] = pal[1] = pal[2] = 0;
            palette_set_corrected(pal, savedata.gamma,savedata.gamma,savedata.gamma, savedata.brightness,savedata.brightness,savedata.brightness);
            update(0,0);
        }
        else update(0,1);

        ++frame;

        if(synctosound){
            milliseconds += sound_getinterval();
            if(milliseconds==0xFFFFFFFF) synctosound = 0;
        }
        if(!synctosound) milliseconds += (time-lasttime) * 1000 / GAME_SPEED;
        lasttime = time;

        if(bothnewkeys & (FLAG_ESC | FLAG_ANYBUTTON)) done = 1;
    }
    anigif_close();
    if(bothnewkeys & (FLAG_ESC | FLAG_ANYBUTTON)) return -1;
    return 1;
}



void playscene(char *filename){

    int handle;
    char *buf;
    unsigned int size;
    int pos;
    char * command = NULL;

    char giffile[256];
    int silence = 0;
    int x=0, y=0;

    int closing = 0;


    // Read file
    if((handle=openpackfile(filename,packfile)) < 0) return;
    size = seekpackfile(handle,0,SEEK_END);
    seekpackfile(handle,0,SEEK_SET);

    buf = (char*)malloc(size+1);
    if(buf==NULL){
        closepackfile(handle);
        return;
    }
    if(readpackfile(handle, buf, size) != size){
        free(buf);
        closepackfile(handle);
        return;
    }
    buf[size] = 0;		// Terminate string (important!)
    closepackfile(handle);


    // Now interpret the contents of buf line by line
    pos = 0;
    while(buf[pos]){
        command = findarg(buf+pos, 0);
        if(command[0]){
            if(!closing && stricmp(command, "music")==0){
                music(findarg(buf+pos, 1), atoi(findarg(buf+pos, 2)));
            }
            else if(!closing && stricmp(command, "animation")==0){
                strcpy(giffile, findarg(buf+pos, 1));
                x = atoi(findarg(buf+pos, 2));
                y = atoi(findarg(buf+pos, 3));
                if(playgif(giffile, x, y) == -1) closing = 1;
            }
            else if(stricmp(command, "silence")==0){
                sound_close_music();
            }
        }
        // Go to next non-blank line
        while(buf[pos] && buf[pos]!='\n' && buf[pos]!='\r') ++pos;
        while(buf[pos]=='\n' || buf[pos]=='\r') ++pos;
    }
    free(buf);
}




// ----------------------------------------------------------------------------




void gameover(){    //7-1-2005 replace all of gameover with this new code
    int i;
    int done = 0;

    unload_background();
    background = allocscreen(320, 240);
    if(background==NULL) shutdown("Out of memory!");
    clearscreen(background);


    music("data/music/gameover.bor", 0);

    time = 0;
    while(!done){
        //	ltb 1-17-05 check to see if scene exists if so use it instead of text
        if(openpackfile("data/scenes/gameover.txt", packfile) >=0) playscene("data/scenes/gameover.txt"); // 1-17-05
        else font_printf(113,110, 3, "%s",stringtable[9]); // Game over
        done |= (time>GAME_SPEED*8 && !sound_query_music(NULL,NULL));
        done |= (bothnewkeys & (FLAG_ESC|FLAG_ANYBUTTON));
        update(0,0);
    }

    freescreen(background);
    background = NULL;
    for(i=0; i<2; i++){
        player[i].weapon = 0;
        player[i].hasplayed = 0;
        player_weapon(player[i].weapon);
    }
}



// START 02-Mar-05 drikobruschi High score table code
void hallfame(){
	int done = 0;
	int marcador[10] = { 0,0,0,0,0,0,0,0,0,0 };	// 02-Mar-05 drikobruschi High score table code
	unsigned long aux;
	char naux[MAX_NAME_LEN+1];
	int i, p, y;

	if(hiscorebg) load_background("data/bgs/hiscore", 0);
	else{
		unload_background();
		background = allocscreen(320, 240);
		if(background==NULL) shutdown("Out of memory!");
		clearscreen(background);
	}

	if(updatehigh){

		for(p = 0; p < 2; p++){

			if(player[p].score > savedata.highsc[9]){
				savedata.highsc[9] = player[p].score;
				strcpy(savedata.hscoren[9], player[p].model->name);
				marcador[9] = 1;

				for(i = 8; i >= 0 && player[p].score > savedata.highsc[i]; i--){
					aux = savedata.highsc[i];
					strcpy(naux, savedata.hscoren[i]);
					savedata.highsc[i] = player[p].score;
					strcpy(savedata.hscoren[i], player[p].model->name);
					marcador[i] = 1;
					savedata.highsc[i + 1] = aux;
					strcpy(savedata.hscoren[i + 1], naux);
					marcador[i + 1] = 0;
				}
			}
		}

		updatehigh = 0;
	}

	time = 0;

	while(!done)
	{
		y = 56;
		if(!hiscorebg) font_printf(113,y-36, 3, "%s",stringtable[73]); // Hall of fame

		for(i = 0; i < 10; i++){
			font_printf(113,y,marcador[i]," %i - %s - %lu",i+1,savedata.hscoren[i],savedata.highsc[i]);
			y += 16;
		}

		update(0,0);
		done |= (time>GAME_SPEED*8);
		done |= (bothnewkeys & (FLAG_START+FLAG_ESC));
	}

	freescreen(background);
	background = NULL;
}
// END 02-Mar-05 drikobruschi High score table code



// Level completed, show bonus stuff
void showcomplete(int num){
    int done = 0;
    int clearbonus[2] = { 10000, 10000 };
    int lifebonus[2];
    int scores[2];
    int i;
    unsigned long nexttime = 0;
    unsigned long finishtime = 0;
    int chan = 0;

    unload_background();

    background = allocscreen(320, 240);
    if(background==NULL) shutdown("Out of memory!");
    clearscreen(background);

    music("data/music/complete.bor", 0);

    lifebonus[0] = player[0].lives * 1000;
    lifebonus[1] = player[1].lives * 1000;

    update(0,0);

    time = 0;
    while(!done){
        font_printf(75,60, 3, "%s %i %s",stringtable[67], num,stringtable[68]); //Stage %i complete!

        font_printf(10,100, 0, "%s",stringtable[69]);//Clear bonus
        if(player[0].lives > 0) font_printf(100,100, 0, "%i", clearbonus[0]);
        if(player[1].lives > 0) font_printf(200,100, 0, "%i", clearbonus[1]);

        font_printf(10,120, 0, "%s",stringtable[70]);//Life bonus
        if(player[0].lives > 0) font_printf(100,120, 0, "%i", lifebonus[0]);
        if(player[1].lives > 0) font_printf(200,120, 0, "%i", lifebonus[1]);

        font_printf(10,140, 0, "%s",stringtable[71]);//Total score
        if(player[0].lives > 0) font_printf(100,140, 0, "%i", player[0].score);
        if(player[1].lives > 0) font_printf(200,140, 0, "%i", player[1].score);

        while(time > nexttime){
            if(!finishtime)	finishtime = time + 4 * GAME_SPEED;

            for(i=0; i<2; i++){
				if(player[i].lives > 0){
					if(clearbonus[i] > 0){
						addscore(i, 10);
						clearbonus[i] -= 10;
						finishtime = 0;
					}
					else if(lifebonus[i] > 0){
						addscore(i, 10);
						lifebonus[i] -= 10;
						finishtime = 0;
					}
				}
            }

            if(!finishtime && !(nexttime&15)){
                sound_stop_sample(chan);
                chan = sound_play_sample(smp_beep, 0, savedata.effectvol/2,savedata.effectvol/2, 100);
            }

            nexttime++;
        }

        if(bothnewkeys & (FLAG_ANYBUTTON|FLAG_ESC)) done = 1;
        if(finishtime && time>finishtime) done = 1;

        update(0,0);
    }

    // Add remainder of score, incase player skips counter
    for(i=0; i<2; i++){
        if(player[i].lives > 0){
        	addscore(i, clearbonus[i]);
        	addscore(i, lifebonus[i]);
		}
    }

    freescreen(background);
    background = NULL;
}




int playlevel(char *filename){

    kill_all();
    load_level(filename);
    time = 0;

    if(player[0].lives > 0){
		player[0].newkeys = player[0].playkeys = 0;	// Fixes the start level executing last button bug
		spawnplayer(0);
	}
    if(player[1].lives > 0){
		player[1].newkeys = player[1].playkeys = 0;	// Fixes the start level executing last button bug
		spawnplayer(1);
	}

    while(!endgame){
        update(1,0);
        if(level_completed) endgame |= (!findent(TYPE_ENEMY) || level->type || findent(TYPE_ENDLEVEL));	// Ends when all enemies die or a bonus level
    }
    fade_out();

    if(player[0].ent) player[0].spawnhealth = player[0].ent->health;
    if(player[1].ent) player[1].spawnhealth = player[1].ent->health;

    if (musicoverlap==0) sound_close_music();

    kill_all();
    unload_level();

    return (player[0].lives > 0 || player[1].lives > 0);
}




int selectplayer(int p1on, int p2on){   // 7-1-2005 entire section replaced with lord balls code

    int i;
    int tperror = 0; // used to report error if player try to use the same character without the same player mode on
    int exit = 0;
    int ready[2] = { 0, 0 };
    int escape = 0;
    entity * example[2] = { NULL, NULL };
    int players_busy = 0;
    int players_ready = 0;
    int immediate[2];
    selectScreen = 1;	// Set so able knows it is at the select screen

    if(unlockbg && savedata.times_completed > 0) load_background("data/bgs/unlockbg", 1);
	else load_background("data/bgs/select", 1);

    kill_all();

    music("data/music/menu.bor", 1);

    memset(&player[0], 0, sizeof(s_player));
    memset(&player[1], 0, sizeof(s_player));

    immediate[0] = p1on;
    immediate[1] = p2on;

    while(!(exit || escape)){
        players_busy = 0;
        players_ready = 0;
        for(i=0; i<2; i++){
            // you can't have that character!
            if((tperror == 1) && (ready[i] == 0)) font_printf(65,123,0,"%s %i %s",stringtable[64], i+1,stringtable[65]); // Player x Choose a Different Character!
            //
            if(!ready[i]){
                if(player[i].lives <= 0 && ((player[i].newkeys & FLAG_ANYBUTTON) || immediate[i])){

				    if(!noshare) credits = CONTINUES;
				    else{
						player[i].credits = CONTINUES;
						player[i].hasplayed = 1;
					}

                    if(!creditscheat){
						if(noshare) --player[i].credits;
						else --credits;
					}

                    player[i].lives = PLAYER_LIVES;
                    player[i].weapon = 0;
                    example[i] = spawn(83+(i*155),230,0, nextplayermodel(NULL)->name);

                    if(example[i]==NULL) shutdown("Failed to create player selection object!");

                    example[i]->direction = !i;
                    player[i].colourmap = i;  // 6-2-2005 make player 2 different colour automatically
                    ent_set_colourmap(example[i], player[i].colourmap);

                    if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
                }
                else if(player[i].newkeys & FLAG_MOVELEFT && example[i]){	// Added && example[i] so it wouldn't crash

                    if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);

                    ent_set_model(example[i], prevplayermodel(example[i]->model)->name);
                    ent_set_colourmap(example[i], player[i].colourmap);
                    tperror = 0;	// all is good
                }
                else if(player[i].newkeys & FLAG_MOVERIGHT && example[i]){	// Added && example[i] so it wouldn't crash

                    if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);

                    ent_set_model(example[i], nextplayermodel(example[i]->model)->name);
                    ent_set_colourmap(example[i], player[i].colourmap);
                    tperror = 0;	// all is good
                }
                // oooh pretty colors! - selectable color scheme for player characters
                else if(player[i].newkeys & FLAG_MOVEUP && colourselect && example[i]){  // Added && example[i] so it wouldn't crash
                    player[i].colourmap = player[i].colourmap + 1;
                    if(player[i].colourmap > MAX_COLOUR_MAPS) player[i].colourmap = 0;
                    ent_set_colourmap(example[i], player[i].colourmap);
                }
                else if(player[i].newkeys & FLAG_MOVEDOWN && colourselect && example[i]){ // 	// Added && example[i] so it wouldn't crash
                    player[i].colourmap = player[i].colourmap - 1;
                    if(player[i].colourmap < 0) player[i].colourmap = MAX_COLOUR_MAPS;
                    ent_set_colourmap(example[i], player[i].colourmap);
                }
                //
                else if(player[i].newkeys & FLAG_ANYBUTTON){

                    if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

                    player[i].model = example[i]->model;

                    // reports error if players try to use the same character and sameplay mode is off
                    if(player[0].model == player[1].model && sameplayer) tperror = 1;
                    //
                    else{
                        time = 0;
                        // yay you picked me!
                        if(example[i]->model->animation[ANI_PICK]) ent_set_anim(example[i], ANI_PICK);
                        //
                        while((!ready[i]) && (((ready[0] == 1) || (ready[1] == 1)) || ((example[0]==NULL) || (example[1]==NULL)))){
                            update(0,0);
                            ready[i] |= (time>GAME_SPEED*2);
                        }
                        ready[i] = 1;
                    }
                }
            }

            else font_printf(53+(i*152),173,0,"%s %i %s",stringtable[64], i+1,stringtable[66]);


            if(example[i] != NULL) players_busy++;
            if(ready[i]) players_ready++;
        }

        if(players_busy && players_busy==players_ready) exit = 1;

        update(0,0);

        if(bothnewkeys & FLAG_ESC) escape = 1;
    }

    selectScreen = 0;	// No longer at the select screen
    kill_all();
    sound_close_music();


    return (!escape);
}




void playgame(int p1on, int p2on, unsigned which_set){
    int current_level;
    int stage;

    if(which_set>=num_difficulties) return;
    // shutdown("Illegal set chosen: index %i (there are only %i sets)!", which_set, num_difficulties);

    allow_secret_chars = ifcomplete[which_set];
    // ltb 1-13-05  use settings loaded from levels.txt
    PLAYER_LIVES = difflives[which_set];
    musicoverlap=diffoverlap[which_set]; // 9-2-2005 music overlap
    fade=custfade[which_set];   //8-2-2005 custom fade
    CONTINUES = diffcreds[which_set];
    if(PLAYER_LIVES == 0) PLAYER_LIVES = 3;
    if(CONTINUES == 0) CONTINUES = 5;
    if(fade == 0) fade = 24;    //8-2-2005 custom fade
    sameplayer = same[which_set];

    if(selectplayer(p1on, p2on)){
		current_level = 0;
		stage = 1;
		while(current_level < num_levels[which_set]){
			PLAYER_MIN_Z = levelorder[which_set][current_level]->z_coords[0];
			PLAYER_MAX_Z = levelorder[which_set][current_level]->z_coords[1];
			BGHEIGHT = levelorder[which_set][current_level]->z_coords[2];

			if(levelorder[which_set][current_level]->is_scene){
				playscene(levelorder[which_set][current_level]->filename);
			}
			else if(!playlevel(levelorder[which_set][current_level]->filename)){
				if(player[0].lives <= 0 && player[1].lives <= 0){
					gameover();
					updatehigh = 1;
					hallfame();
				}
				break;
			}
			if(levelorder[which_set][current_level]->gonext){
				showcomplete(stage);
				//	FREE MEMORY HERE
				player[0].spawnhealth = 0;
				player[1].spawnhealth = 0;
				++stage;
			}
			++current_level;
		}
		if(current_level >= num_levels[which_set]){
			savedata.times_completed++;
			fade_out();
			updatehigh = 1;
			hallfame();
		}
    }

    sound_close_music();
}





int choose_difficulty(){
    int quit = 0;
    int selector = 0;
    int i;

    bothnewkeys = 0;

    while(!quit){
        font_printf(120,110, 2, "%s",stringtable[60]);//Difficulty
        for(i=0; i<num_difficulties; i++){
            if(savedata.times_completed >= ifcomplete[i]) font_printf(120,130+i*10, (selector==i), set_names[i]);
            else{
                if(ifcomplete[i]>1) font_printf(120,130+i*10, (selector==i), "%s %s %i %s", set_names[i],stringtable[61], ifcomplete[i],stringtable[62]); //- finish game, times to unlock
                font_printf(120,130+i*10, (selector==i), "%s %s", set_names[i],stringtable[63]); //- finish game to unlock
            }
        }

        font_printf(120,135+i*10, (selector==i), "%s",stringtable[8]);//Back
        update(0,0);

        if(bothnewkeys & FLAG_ESC) quit = 1;
        if(bothnewkeys & FLAG_MOVEUP){
            --selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(bothnewkeys & FLAG_MOVEDOWN){
            ++selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(selector<0) selector = i;
        if(selector>i) selector = 0;

        if(bothnewkeys & FLAG_ANYBUTTON){

            if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

            if(selector==i) quit = 1;
            else if(savedata.times_completed >= ifcomplete[selector]) return selector;
        }
    }
    bothnewkeys = 0;
    return -1;
}




// ----------------------------------------------------------------------------


void soundcard_options(){
    int quit = 0;
    int selector = 0;

    savesettings();

    bothnewkeys = 0;

    while(!quit){
        font_printf(60,90, 2, "Sound card options");
        font_printf(60,120, (selector==0), "%s %i",stringtable[4], savedata.soundrate);   //Frequency
        font_printf(60,130, (selector==1), "%s %i",stringtable[5], savedata.soundbits);   //Bits
        font_printf(60,150, (selector==2), "%s",stringtable[6]);	//Apply
        font_printf(60,160, (selector==3), "%s",stringtable[7]);	//Discard
        font_printf(60,210, (selector==4), "%s",stringtable[8]);	//Back
        update(0,0);

        if(bothnewkeys & FLAG_ESC) quit = 1;
        if(bothnewkeys & FLAG_MOVEUP){
            --selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(bothnewkeys & FLAG_MOVEDOWN){
            ++selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(selector<0) selector = 4;
        selector %= 5;
        if(bothnewkeys & (FLAG_MOVELEFT|FLAG_MOVERIGHT|FLAG_ANYBUTTON)){

            if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

            switch(selector){
				case 0:
					if(bothnewkeys & FLAG_MOVELEFT) savedata.soundrate >>= 1;
					if(bothnewkeys & FLAG_MOVERIGHT) savedata.soundrate <<= 1;
					if(savedata.soundrate < 11025) savedata.soundrate = 44100;
					if(savedata.soundrate > 44100) savedata.soundrate = 11025;
					break;
				case 1:
					savedata.soundbits = (savedata.soundbits ^ 8+16);
					if(savedata.soundbits!=8 && savedata.soundbits!=16) savedata.soundbits = 8;
					break;
				case 2:
					if(!(bothnewkeys & FLAG_ANYBUTTON)) break;
					// Apply new hardware settings
					sound_stop_playback();
					if(!sound_start_playback(0, savedata.soundbits, savedata.soundrate)){
						savedata.soundbits = 8;
						savedata.soundrate = 11025;
						sound_start_playback(0, savedata.soundbits, savedata.soundrate);
					}
					music("data/music/remix.bor", 1);
					savesettings();
					break;
				case 3:
					if(bothnewkeys & FLAG_ANYBUTTON) loadsettings();
					break;
				default:
					quit = (bothnewkeys & FLAG_ANYBUTTON);
			}
        }
    }
    loadsettings();
    bothnewkeys = 0;
}



// Set key or button safely (with switching)
void safe_set(int *arr, int index, int newkey, int oldkey){
    int i;

    for(i=0; i<9; i++){
        if(arr[i]==newkey) arr[i] = oldkey;
    }

    arr[index] = newkey;
}


void keyboard_setup(int player){
    int quit = 0;
    int selector = 0;
    int setting = -1;
    int k, ok;

    savesettings();

    bothnewkeys = 0;

    while(!quit){
        font_printf(60,90, 2, "%s %i",stringtable[10], player+1); // Setup controls for player
        font_printf(60,110, (selector==0), "%s",stringtable[31]);//Move up
        font_printf(160,110, (selector==0), control_getkeyname(savedata.keys[player][SDID_MOVEUP]));
        font_printf(60,120, (selector==1), "%s",stringtable[32]);//Move down
        font_printf(160,120, (selector==1), control_getkeyname(savedata.keys[player][SDID_MOVEDOWN]));
        font_printf(60,130, (selector==2), "%s",stringtable[33]);//Move left
        font_printf(160,130, (selector==2), control_getkeyname(savedata.keys[player][SDID_MOVELEFT]));
        font_printf(60,140, (selector==3), "%s",stringtable[34]);//Move right
        font_printf(160,140, (selector==3), control_getkeyname(savedata.keys[player][SDID_MOVERIGHT]));
        font_printf(60,150, (selector==4), "%s",stringtable[35]);//Special
        font_printf(160,150, (selector==4), control_getkeyname(savedata.keys[player][SDID_SPECIAL]));
        font_printf(60,160, (selector==5), "%s",stringtable[36]);//Attack
        font_printf(160,160, (selector==5), control_getkeyname(savedata.keys[player][SDID_ATTACK]));
        font_printf(60,170, (selector==6), "%s",stringtable[37]);//Jump
        font_printf(160,170, (selector==6), control_getkeyname(savedata.keys[player][SDID_JUMP]));
        font_printf(60,180, (selector==7), "%s",stringtable[38]);//Start
        font_printf(160,180, (selector==7), control_getkeyname(savedata.keys[player][SDID_START]));
        font_printf(60,190, (selector==8), "%s",stringtable[39]);//Screenshot
        font_printf(160,190, (selector==8), control_getkeyname(savedata.keys[player][SDID_SCREENSHOT]));
        font_printf(60,210, (selector==9), "%s",stringtable[40]);//OK
        font_printf(60,220, (selector==10), "%s",stringtable[41]);//Cancel
        update(0,0);


        if(setting > -1){
            if(bothnewkeys & FLAG_ESC){
                savedata.keys[player][setting] = ok;

                if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 50);

                setting = -1;
            }
            if(setting > -1){
                if(k = control_scankey()){
                    safe_set(savedata.keys[player], setting, k, ok);

                    if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

                    setting = -1;
                    // Prevent accidental screenshot
                    bothnewkeys = 0;
                }
            }
        }
        else{
            if(bothnewkeys & FLAG_ESC) quit = 1;
            if(bothnewkeys & FLAG_MOVEUP){
                --selector;

                if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
            }
            if(bothnewkeys & FLAG_MOVEDOWN){
                ++selector;

                if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
            }
            if(selector<0) selector = 10;
            if(selector>10) selector = 0;

            if(bothnewkeys & FLAG_ANYBUTTON){

                if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

                if(selector==9) quit = 2;
                else if(selector==10) quit = 1;
                else{
                    setting = selector;
                    ok = savedata.keys[player][setting];
                    savedata.keys[player][setting] = 0;
                    keyboard_getlastkey();
                }
            }
        }
    }

    if(quit==2){
        apply_controls();
        savesettings();
    }
    else loadsettings();

    update(0,0);
    bothnewkeys = 0;
}







void input_options(){
    int quit = 0;
    int selector = 0;

    bothnewkeys = 0;

    while(!quit){
        font_printf(60,90, 2, "%s",stringtable[25]); //Input options
        if(savedata.usejoy){
            font_printf(60,120, (selector==0), "%s",stringtable[26]); //Gamepad enabled
            if(!control_getjoyenabled()){
                font_printf(160,120, (selector==0), "%s",stringtable[27]); //* device is not ready
                // control_usejoy(1);
            }
        }
        else font_printf(60,120, (selector==0), "%s",stringtable[28]); //Gamepad disabled
        font_printf(60,130, (selector==1), "%s",stringtable[29]); // Setup controls P1...
        font_printf(60,140, (selector==2), "%s",stringtable[30]); // Setup controls P2...
        font_printf(60,210, (selector==3), "%s",stringtable[8]); //back
        update(0,0);

        if(bothnewkeys & FLAG_ESC) quit = 1;
        if(bothnewkeys & FLAG_MOVEUP){
            --selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(bothnewkeys & FLAG_MOVEDOWN){
            ++selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(selector<0) selector = 3;
        if(selector>3) selector = 0;
        if(bothnewkeys & (FLAG_MOVELEFT|FLAG_MOVERIGHT|FLAG_ANYBUTTON)){

            if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

            switch(selector){
            case 0:
                savedata.usejoy = !savedata.usejoy;
                control_usejoy(savedata.usejoy);
                break;
            case 1:
                keyboard_setup(0);
                break;
            case 2:
                keyboard_setup(1);
                break;
            default:
                quit = (bothnewkeys & FLAG_ANYBUTTON);
            }
        }
    }
    savesettings();
    bothnewkeys = 0;
}



void soundvol_options(){

    int quit = 0;
    int selector = 0;
    int dir;

    bothnewkeys = 0;

    while(!quit){
        font_printf(60,90, 2, "%s",stringtable[42]); //Options

        font_printf(60,120, (selector==0), "%s",stringtable[43]);//Sound volume:
        font_printf(160,120, (selector==0), "%i", savedata.soundvol);
        font_printf(60,130, (selector==1), "%s",stringtable[44]);//Sound effects volume:
        font_printf(160,130, (selector==1), "%i", savedata.effectvol);
        font_printf(60,140, (selector==2), "%s",stringtable[45]);//Background music:
        font_printf(160,140, (selector==2), "%s", (savedata.usemusic ? stringtable[48] : stringtable[49]));
        font_printf(60,150, (selector==3), "%s",stringtable[46]);//Music volume:
        font_printf(160,150, (selector==3), "%i", savedata.musicvol);
        font_printf(60,160, (selector==4), "%s",stringtable[47]);//Show music titles:
        font_printf(160,160, (selector==4),"%s", (savedata.showtitles ? stringtable[50] : stringtable[51]));
        font_printf(60,210, (selector==5), "%s",stringtable[8]);

        update(0,0);

        if(bothnewkeys & FLAG_ESC) quit = 1;
        if(bothnewkeys & FLAG_MOVEUP){
            --selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(bothnewkeys & FLAG_MOVEDOWN){
            ++selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(selector<0) selector = 5;
        if(selector>5) selector = 0;

        if(bothnewkeys & (FLAG_MOVELEFT|FLAG_MOVERIGHT|FLAG_ANYBUTTON)){
            dir = 0;
            if(bothnewkeys & FLAG_MOVELEFT) dir = -1;
            else if(bothnewkeys & FLAG_MOVERIGHT) dir = 1;

            if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

            switch(selector){
				case 0:
					savedata.soundvol += dir;
					if(savedata.soundvol < 0) savedata.soundvol = 0;
					if(savedata.soundvol > 15) savedata.soundvol = 15;
					SB_setvolume(SB_VOICEVOL, savedata.soundvol);
					break;
				case 1:
					savedata.effectvol += dir;
					if(savedata.effectvol < 0) savedata.effectvol = 0;
					if(savedata.effectvol > 512) savedata.effectvol = 512;
					break;
				case 2:
					if(!dir) break;
					if(!savedata.usemusic){
						savedata.usemusic = 1;
						music("data/music/remix.bor", 1);
					}
					else{
						savedata.usemusic = 0;
						sound_close_music();
					}
					break;
				case 3:
					savedata.musicvol += dir;
					if(savedata.musicvol < 0) savedata.musicvol = 0;
					if(savedata.musicvol > 512) savedata.musicvol = 512;
					sound_volume_music(savedata.musicvol, savedata.musicvol);
					break;
				case 4:
					savedata.showtitles = !savedata.showtitles;
					break;
				default:
					quit = 1;
            }
        }
    }
    savesettings();
    bothnewkeys = 0;
}

void cheatoptions(){	//  LTB 1-13-05 took out sameplayer option
    int quit = 0;
    int selector = 0;
    int dir;

    bothnewkeys = 0;

    while(!quit){
        font_printf(60,60, 2, "%s",stringtable[42]); // Options
        font_printf(60,90, (selector==0), "%s",stringtable[11]);  //Brightness:
        font_printf(160,90, (selector==0), "%i", savedata.brightness);
        font_printf(60,100, (selector==1), "%s",stringtable[12]); //Gamma:
        font_printf(160,100, (selector==1), "%i", savedata.gamma);
        font_printf(60,110, (selector==2), "%s",stringtable[13]); // Setup input devices...
        font_printf(60,120, (selector==3), "%s",stringtable[14]); //Setup sound card...
        font_printf(60,130, (selector==4), "%s",stringtable[15]); //Sound options...
        font_printf(60,140, (selector==5), "%s",stringtable[16]); // Window offset
        font_printf(160,140, (selector==5), "%i   ",savedata.windowpos); // 10-20 needed for some TV's
        // change some mode flags    7-1-2005 New options added
        if(savedata.mode){	// Option now saves
            font_printf(60,150, (selector==6), "%s",stringtable[18]);//Mode 1 - Players CAN'T attack each other
        }
        else if(!savedata.mode){	// Option now saves
            font_printf(60,150, (selector==6), "%s",stringtable[19]);//Mode 2 - Players CAN attack each other
        }

		font_printf(60,160, (selector==7), "%s %i",stringtable[24],GAME_SPEED);   // 13-1-2005 game speed options

        if(livescheat){
            font_printf(60,170, (selector==8), "Infinite Lives On");
        }
        else if(!livescheat){
            font_printf(60,170, (selector==8), "Infinite Lives Off");
        }
        if(creditscheat){
            font_printf(60,180, (selector==9), "Infinite Credits On"); // Enemies fall down when you respawn
        }
        else if(!creditscheat){
            font_printf(60,180, (selector==9), "Infinite Credits Off");//Enemies don't fall down when you respawn
        }
		if(healthcheat){
            font_printf(60,190, (selector==10), "Infinite Health On"); // Enemies fall down when you respawn
        }
        else if(!healthcheat){
            font_printf(60,190, (selector==10), "Infinite Health Off");//Enemies don't fall down when you respawn
        }
        //
        font_printf(60,230, (selector==11), "%s",stringtable[8]); // Back
        //   7-1-2005 new options end here
        update(0,0);

        if(bothnewkeys & FLAG_ESC) quit = 1;
        if(bothnewkeys & FLAG_MOVEUP){
            --selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(bothnewkeys & FLAG_MOVEDOWN){
            ++selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(selector<0) selector = 11;  // 7-1-2005 //13-1-2005 changed to 11
        if(selector>11) selector = 0;//	7-1-2005 6 changed to 10 //13-1-2005 changed to 11

        if(bothnewkeys & (FLAG_MOVELEFT|FLAG_MOVERIGHT|FLAG_ANYBUTTON)){
            dir = 0;

            if(bothnewkeys & FLAG_MOVELEFT) dir = -1;
            else if(bothnewkeys & FLAG_MOVERIGHT) dir = 1;

            if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

            switch(selector){
            case 0:
                savedata.brightness += dir;

                if(savedata.brightness < -256) savedata.brightness = -256;
                if(savedata.brightness > 256) savedata.brightness = 256;

                vga_vwait();
                palette_set_corrected(pal, savedata.gamma,savedata.gamma,savedata.gamma, savedata.brightness,savedata.brightness,savedata.brightness);
                break;
            case 1:
                savedata.gamma += dir;

                if(savedata.gamma < -256) savedata.gamma = -256;
                if(savedata.gamma > 256) savedata.gamma = 256;

                vga_vwait();
                palette_set_corrected(pal, savedata.gamma,savedata.gamma,savedata.gamma, savedata.brightness,savedata.brightness,savedata.brightness);
                break;
            case 2:
                input_options();
                break;
            case 3:
                soundcard_options();
                break;
            case 4:
                soundvol_options();
                break;
            case 5:
                savedata.windowpos += dir;
                if(savedata.windowpos < -2) savedata.windowpos = -2;
                if(savedata.windowpos > 20) savedata.windowpos = 20;
                break;
                // changing the mode flag    7-1-2005 mode setting changes here
            case 6:
                if(!savedata.mode) savedata.mode = 1;	// Option now saves
                else if(savedata.mode) savedata.mode = 0;	// Option now saves
                break;
            case 7:				// 13-1-2005 Game speed selection
                GAME_SPEED += (dir*10);   // 13-1-2005 Game speed selection
                if(GAME_SPEED == 0) GAME_SPEED = 10;
                break;	   // 13-1-2005 Game speed selection
            case 8:
                if(!livescheat) livescheat = 1;
                else if(livescheat) livescheat = 0;
                break;
            case 9:
                if(!creditscheat) creditscheat = 1;
                else if(creditscheat) creditscheat = 0;
                break;
            case 10:
                if(!healthcheat) healthcheat = 1;
                else if(healthcheat) healthcheat = 0;
                break;
                //			 7-1-2005 mode setting changes end.
            default:
                quit = 1;
            }
        }
    }
    savesettings();
    bothnewkeys = 0;
}



void options(){	//  LTB 1-13-05 took out sameplayer option
    int quit = 0;
    int selector = 0;
    int dir;

    bothnewkeys = 0;

    while(!quit){
        font_printf(60,60, 2, "%s",stringtable[42]); // Options
        font_printf(60,90, (selector==0), "%s",stringtable[11]);  //Brightness:
        font_printf(160,90, (selector==0), "%i", savedata.brightness);
        font_printf(60,100, (selector==1), "%s",stringtable[12]); //Gamma:
        font_printf(160,100, (selector==1), "%i", savedata.gamma);
        font_printf(60,110, (selector==2), "%s",stringtable[13]); // Setup input devices...
        font_printf(60,120, (selector==3), "%s",stringtable[14]); //Setup sound card...
        font_printf(60,130, (selector==4), "%s",stringtable[15]); //Sound options...
        font_printf(60,140, (selector==5), "%s",stringtable[16]); // Window offset
        font_printf(160,140, (selector==5), "%i   ",savedata.windowpos); // 10-20 needed for some TV's
        // change some mode flags    7-1-2005 New options added
        if(savedata.mode){
            font_printf(60,150, (selector==6), "%s",stringtable[18]);//Mode 1 - Players CAN'T attack each other
        }
        else if(!savedata.mode){
            font_printf(60,150, (selector==6), "%s",stringtable[19]);//Mode 2 - Players CAN attack each other
        }
        font_printf(60,180, (selector==7), "%s %i",stringtable[24],GAME_SPEED);   // 13-1-2005 game speed options
        //
        font_printf(60,210, (selector==8), "%s",stringtable[8]); // Back
        //   7-1-2005 new options end here
        update(0,0);

        if(bothnewkeys & FLAG_ESC) quit = 1;
        if(bothnewkeys & FLAG_MOVEUP){
            --selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(bothnewkeys & FLAG_MOVEDOWN){
            ++selector;

            if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        if(selector<0) selector = 8;  // 7-1-2005 //13-1-2005 changed to 11
        if(selector>8) selector = 0;//	7-1-2005 6 changed to 10 //13-1-2005 changed to 11

        if(bothnewkeys & (FLAG_MOVELEFT|FLAG_MOVERIGHT|FLAG_ANYBUTTON)){
            dir = 0;
            if(bothnewkeys & FLAG_MOVELEFT) dir = -1;
            else if(bothnewkeys & FLAG_MOVERIGHT) dir = 1;

            if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

            switch(selector){
            case 0:
                savedata.brightness += dir;
                if(savedata.brightness < -256) savedata.brightness = -256;
                if(savedata.brightness > 256) savedata.brightness = 256;
                vga_vwait();
                palette_set_corrected(pal, savedata.gamma,savedata.gamma,savedata.gamma, savedata.brightness,savedata.brightness,savedata.brightness);
                break;
            case 1:
                savedata.gamma += dir;
                if(savedata.gamma < -256) savedata.gamma = -256;
                if(savedata.gamma > 256) savedata.gamma = 256;
                vga_vwait();
                palette_set_corrected(pal, savedata.gamma,savedata.gamma,savedata.gamma, savedata.brightness,savedata.brightness,savedata.brightness);
                break;
            case 2:
                input_options();
                break;
            case 3:
                soundcard_options();
                break;
            case 4:
                soundvol_options();
                break;
            case 5:
                savedata.windowpos += dir;
                if(savedata.windowpos < -2) savedata.windowpos = -2;
                if(savedata.windowpos > 20) savedata.windowpos = 20;
                break;
                // changing the mode flag    7-1-2005 mode setting changes here
            case 6:
                if(!savedata.mode) savedata.mode = 1;	// Option now saves
                else if(savedata.mode) savedata.mode = 0;	// Option now saves
                break;
            case 7:				// 13-1-2005 Game speed selection
                GAME_SPEED += (dir*10);   // 13-1-2005 Game speed selection
                if(GAME_SPEED == 0) GAME_SPEED = 10;
                break;	   // 13-1-2005 Game speed selection

                //			 7-1-2005 mode setting changes end.
            default:
                quit = 1;
            }
        }
    }
    savesettings();
    bothnewkeys = 0;
}


// ----------------------------------------------------------------------------

void main(int argc, char **argv){

    int quit = 0;
    char * ext;
    int relback = 1;
    int selector = 0;
    int hres, vres;
    unsigned long introtime = 0;
    int started = 0;
    int diff;
    int i;
    int sshot;
    int shotnumber;
    int nogui=0;
    char pakname[20];
    char gifname1[20];
    char gifname2[20];
    char gifname3[20];
    char gifname4[20];
    char ininame[20];



stringtable = read_string_table("langpack.txt", 200);

    // Go go Gadget command-line!
    for(i=1; i<argc; i++){
        if(stricmp(argv[i], "-res")==0 && argc>=i+3){
            hres = atoi(argv[i+1]);
            vres = atoi(argv[i+2]);
            if((rescreen = allocscreen(hres, vres)) == NULL){
                printf("Not enough memory for %ix%i mode!\n", hres, vres);
                exit(1);
            }
            i+=2;
        }
        else if(stricmp(argv[i], "-pak")==0 && argc>=i+2){
            packfile = argv[i+1];
            i++;
        }
        else if(stricmp(argv[i], "-pokes")==0){
            cheats = 1;
            i++;
        }
        else{
            printf(
                "Option '%s' not recognized or improperly used.\n"
                "\n"
                "Avaliable options:\n"
                "-pak [filename]        Use alternative PAK file\n"
                "-res [width] [height]  Use alternative resolution\n"
                "\n"
                "Example:\n"
                "bor.exe -pak custom.pak\n"
                "bor.exe -res 640 480\n"
                "\n",
                argv[i]
            );
            exit(1);
        }
    }

    guistartup();
    findmods();
    if (paks==0) (quit=1);
    load_background("gui/logo", 0);
    while(!quit){
        started=1;
        if(bothnewkeys & FLAG_ESC) {
            quit = 1;
            nogui=1;
        }
        strcpy(pakname, pakfiles[i].filename);
        strcpy(gifname1, pakfiles[i].filename);
        strcpy(gifname2, pakfiles[i].filename);
        strcpy(gifname3, pakfiles[i].filename);
        strcpy(gifname4, pakfiles[i].filename);
        strcpy(ininame, pakfiles[i].filename);
        ext = strstr(gifname1, ".pak");
        if(ext) strcpy(ext, "1.gif");
        ext = strstr(gifname1, ".PAK");
        if(ext) strcpy(ext, "1.gif");
        ext = strstr(gifname2, ".pak");
        if(ext) strcpy(ext, "2.gif");
        ext = strstr(gifname2, ".PAK");
        if(ext) strcpy(ext, "2.gif");
        ext = strstr(gifname3, ".pak");
        if(ext) strcpy(ext, "3.gif");
        ext = strstr(gifname3, ".PAK");
        if(ext) strcpy(ext, "3.gif");
        ext = strstr(gifname4, ".pak");
        if(ext) strcpy(ext, "4.gif");
        ext = strstr(gifname4, ".PAK");
        if(ext) strcpy(ext, "4.gif");
        ext = strstr(ininame, ".PAK");
        if(ext) strcpy(ext, ".ini");
        ext = strstr(ininame, ".pak");
        if(ext) strcpy(ext, ".ini");

        if(!started){
            if((time%GAME_SPEED) < (GAME_SPEED/2)) font_printf(117,212, 0, "PRESS START");  // ltb 1-18-05  moved
            if(bothnewkeys&(FLAG_ANYBUTTON)){
                started = 1;
                relback = 1;
            }
        }
        else{
            drawbox(0, 0, 320, 10, color_green, vscreen);
            font_printf(100,40, 3, "%s", pakname);  // ltb 1-18-05  moved
            introtime = time + GAME_SPEED * 20;
            if(shotnumber==1){
                if (fileexists(gifname1)){
                    sshot=loadsprite(gifname1,0,0);
                    spriteq_add(74,63,10000,sprites[1][sshot], 0, NULL);
                }
                else shotnumber--;
            }
            if(shotnumber==2){
                if (fileexists(gifname2)){
                    sshot=loadsprite(gifname2,0,0);
                    spriteq_add(74,63,10000,sprites[1][sshot], 0, NULL);
                }
                else shotnumber--;
            }
            if(shotnumber==3){
                if (fileexists(gifname3)){
                    sshot=loadsprite(gifname3,0,0);
                    spriteq_add(74,63,10000,sprites[1][sshot], 0, NULL);
                }
                else shotnumber--;
            }
            if(shotnumber==4){
                if (fileexists(gifname4)){
                    sshot=loadsprite(gifname4,0,0);
                    spriteq_add(74,63,10000,sprites[1][sshot], 0, NULL);
                }
                else shotnumber--;
            }
            if(bothnewkeys & FLAG_MOVELEFT){
                freesprites();
                i--;

                if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
            }
            if(bothnewkeys & FLAG_MOVERIGHT){
                freesprites();
                i++;

                if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
            }
            if(bothnewkeys & FLAG_MOVEUP){
                freesprites();
                shotnumber--;

                if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
            }
            if(bothnewkeys & FLAG_MOVEDOWN){
                freesprites();
                shotnumber++;

                if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
            }
            if(shotnumber<1) (shotnumber=4);
            if(shotnumber>4) shotnumber = 1;

            if(i<0) i = (paks-1);
            if(i>(paks-1)) i = 0;
            if(paks==1){
                strncpy(packfile, pakfiles[i].filename, strlen(pakfiles[i].filename));
                quit = 1;
            }
            if(bothnewkeys&(FLAG_ANYBUTTON)){

                if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

                strncpy(packfile, pakfiles[i].filename, strlen(pakfiles[i].filename));
                quit = 1;
                introtime = time + GAME_SPEED * 20;
            }
        }
        update(0,0);
    }

    // while(time<GAME_SPEED*6 && !(bothnewkeys&(FLAG_ANYBUTTON|FLAG_ESC))) update(0,0);
    quit = 0;
    relback = 1;
    selector = 0;
    introtime = 0;
    started = 0;

// unload everything just in case.

    unload_levelorder();
    unload_level();
    freescreen(vscreen);
    freescreen(rescreen);
    freesprites();
    font_unload(0);
    font_unload(1);
    font_unload(2);
    font_unload(3);
    free_ents();
    free_models();
    free_modelcache();
    timer_exit();
    control_exit();
    sound_exit();

// unload everything just in case.

    if(!nogui)	{
        startup();
        load_background("data/bgs/logo", 0);
        while(time<GAME_SPEED*6 && !(bothnewkeys&(FLAG_ANYBUTTON|FLAG_ESC))) update(0,0);
        music("data/music/remix.bor", 1);
        playscene("data/scenes/logo.txt");
    }
    if(nogui) quit=1;
    while(!quit){
        if(time >= introtime){
            playscene("data/scenes/intro.txt");
            update(0,0);
            introtime = time + GAME_SPEED * 20;
            relback = 1;
            started = 0;
        }

        if(bothnewkeys & FLAG_ESC) quit = 1;

        if(!started){
            if((time%GAME_SPEED) < (GAME_SPEED/2)) font_printf(117,212, 0, "%s",stringtable[72]);  // ltb 1-18-05  moved
            if(bothnewkeys&(FLAG_ANYBUTTON)){
                started = 1;
                relback = 1;
                freezeall = 0;	// Added incase game is exited while the animations are frozen
            }
        }
        else{
			font_printf(122,100, (selector==0), "%s",stringtable[0]);  // ltb 1-18-05  moved  Menu
			font_printf(122,110, (selector==1), "%s",stringtable[1]);  // ltb 1-18-05  moved  Options
			font_printf(122,120, (selector==2), "%s",stringtable[2]);  // ltb 1-18-05  moved  How To Play
			font_printf(122,130, (selector==3), "%s",stringtable[73]);  // ltb 1-18-05  moved  Hall of Fame
			font_printf(122,140, (selector==4), "%s",stringtable[3]);  // ltb 1-18-05  moved  Quit
			font_printf(10,220, 0, "Original Coding By Senile team");	// - For New Code Credit see Readme.txt");


            if(bothnewkeys) introtime = time + GAME_SPEED * 20;

            if(bothnewkeys & FLAG_MOVEUP){
                --selector;

                if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
            }
            if(bothnewkeys & FLAG_MOVEDOWN){
                ++selector;

                if(smp_beep >= 0) sound_play_sample(smp_beep, 0, savedata.effectvol,savedata.effectvol, 100);
            }
            if(selector<0) selector = 4;
            selector %= 5;

            if(bothnewkeys&(FLAG_ANYBUTTON)){

                if(smp_beep2 >= 0) sound_play_sample(smp_beep2, 0, savedata.effectvol,savedata.effectvol, 100);

                switch(selector){
                case 0:
                    playgame(player[0].newkeys&(FLAG_ANYBUTTON), player[1].newkeys&(FLAG_ANYBUTTON), choose_difficulty());
                    started = 0;
                    relback = 1;
                    break;
                case 1:
                    if(!cheats) options();
                    else{
						if (!forcecheatsoff) cheatoptions();
						else options();
					}
                    break;
                case 2:
                    playscene("data/scenes/howto.txt");
                    relback = 1;
                    break;
				case 3:
					hallfame();
					relback = 1;
					break;
                default:
                    quit = 1;
                }
                introtime = time + GAME_SPEED * 20;
            }
        }
        if(relback){
            if(started) load_background("data/bgs/titleb", 0);
            else load_background("data/bgs/title", 0);
            if(!sound_query_music(NULL,NULL)) music("data/music/remix.bor", 1);
            relback = 0;
        }
        update(0,0);

    }

    shutdown(
        "Beats of Rage V%X.%04X, compile date: " __DATE__ "\n"
        "Presented by Team Senile.\n"
        "This Version is unofficial and based on the Senile Source Code.\n"
        "\n"
        "Special thanks to SEGA and SNK.\n",
        VERSION>>16,
        VERSION&0xFFFF
    );
}






