/*
 * OpenBOR - http://www.chronocrash.com
 * -----------------------------------------------------------------------
 * 
 * All rights reserved, see LICENSE in OpenBOR root for details.
 *
 * Copyright (c) OpenBOR Team
 */

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

#include "openbor.h"
#include "source/openborscript/commands.h"
#include "source/gamelib/models.h"
#include "source/gamelib/translation.h"
#include "source/gamelib/soundmix.h"

#define NaN 0xAAAAAAAA

static const char *E_OUT_OF_MEMORY = "Error: Could not allocate sufficient memory.\n";
static int DEFAULT_OFFSCREEN_KILL = 3000;


s_sprite_list *sprite_list;
s_sprite_map *sprite_map;

s_savelevel *savelevel;
s_savescore savescore;
s_savedata savedata;

/////////////////////////////////////////////////////////////////////////////
//  Global Variables                                                        //
/////////////////////////////////////////////////////////////////////////////

a_playrecstatus *playrecstatus = NULL;
s_anim_list *anim_list = NULL;
s_modelcache *model_cache = NULL;

s_set_entry *levelsets = NULL;
int        num_difficulties;

int no_cmd_compatible = 0;

int		skiptoset = -1;
//when there are more entities than this, those with lower priority will be erased
int spawnoverride = 999999;
int maxentities = 999999;

int	global_model = MODEL_INDEX_NONE;
#define global_model_scripts ((global_model>=0 && model_cache[global_model].model)?model_cache[global_model].model->scripts:NULL)

s_level            *level               = NULL;
s_filestream *filestreams = NULL;
int numfilestreams = 0;
s_screen           *vscreen             = NULL;
s_screen           *background          = NULL;
s_videomodes        videomodes;
int sprite_map_max_items = 0;
int cache_map_max_items = 0;

int startup_done = 0; // startup is only called when a game is loaded. so when exitting from the menu we need a way to figure out which resources to free.
List *modelcmdlist = NULL;
List *modelstxtcmdlist = NULL;
List *levelcmdlist = NULL;
List *levelordercmdlist = NULL;

int atkchoices[MAX_ANIS]; //tempory values for ai functions, should be well enough LOL

//see types.h
const s_drawmethod plainmethod =
{
    .table      = NULL,
    .fillcolor  = 0,
    .flag       = 1,
    .alpha      = BLEND_MODE_MODEL,
    .remap      = -1,
    .flipx      = 0,
    .flipy      = 0,
    .transbg    = 0,
    .fliprotate = 0,
    .rotate     = 0,
    .scalex     = 256,
    .scaley     = 256,
    .shiftx     = 0,
    .centerx    = 0,
    .centery    = 0,
    .xrepeat    = 1,
    .yrepeat    = 1,
    .xspan      = 0,
    .yspan      = 0,
    .channelr   = 255,
    .channelg   = 255,
    .channelb   = 255,
    .tintmode   = 0,
    .tintcolor  = 0,
    .clipx      = 0,
    .clipy      = 0,
    .clipw      = 0,
    .cliph      = 0,
    .water = {{.beginsize = 0.0}, {.endsize = 0.0}, 0, {.wavespeed = 0}, 0}
};

// Caskey, Damon V.
// 2019-12-13
// Need default values for projectile animation 
// settings, and projectiles in general.
const s_projectile projectile_default_config = {
	
	.bomb = MODEL_INDEX_NONE,
	.color_set_adjust = COLORSET_ADJUST_NONE,
	.direction_adjust = DIRECTION_ADJUST_SAME,
	.flash = MODEL_INDEX_NONE,
	.knife = MODEL_INDEX_NONE,
	.offense = PROJECTILE_OFFENSE_SELF,
	.placement = PROJECTILE_PLACEMENT_PARENT,

    /*
    * X position defaults are different for stars
    * vs. other projectiles and we want players to 
    * have 0 as an option, so we start with a silly
    * value here. If creator doesn't change it, then
    * we'll apply real defaults in the projectile 
    * spawn functions.
    */

	.position = {.x = PROJECTILE_LEGACY_COMPATABILITY_POSITION_X, 
					.y = PROJECTILE_DEFAULT_POSITION_Y,
					.z = PROJECTILE_DEFAULT_POSITION_Z},
	.shootframe = FRAME_NONE,
	.throwframe = FRAME_NONE,
	.tossframe = FRAME_NONE,
	.star		= MODEL_INDEX_NONE,
	.star_velocity = {0.f, 
						1.f, 
						2.f},
	.velocity = {.x = PROJECTILE_DEFAULT_SPEED_X,
					.y = PROJECTILE_DEFAULT_SPEED_Y,
					.z = PROJECTILE_DEFAULT_SPEED_Z }
};

const s_defense default_defense =
{
    .block_damage_adjust    = 0,
    .block_damage_max       = MAX_INT,
    .block_damage_min       = MIN_INT,
    .blockpower             = 0.f,
    .blockthreshold         = 0.f,
    .blockratio             = DEFENSE_BLOCKRATIO_COMPATABILITY_DEFAULT,
    .blocktype              = BLOCK_TYPE_GLOBAL,
    .death_config_flags     = DEATH_CONFIG_MACRO_DEFAULT,
    .damage_adjust          = 0,
    .damage_max             = MAX_INT,
    .damage_min             = MIN_INT,
    .factor                 = 1.f,
    .knockdown              = 1.f,
    .pain                   = 0.f
};

const s_offense default_offense =
{
    .damage_adjust  = 0,
    .damage_max     = MAX_INT,
    .damage_min     = MIN_INT,
    .factor         = 1.f    
};

const s_hitbox empty_collision_coords = {   .x      = 0,
                                            .y      = 0,
                                            .width  = 0,
                                            .height = 0,
                                            .z_background     = 0,
                                            .z_foreground     = 0};

const s_collision_body empty_collision_body = { .coords = NULL,
                                            .index = 0,
                                            .body = NULL,
                                            .meta_data = NULL,
                                            .meta_tag = 0 };

const s_body empty_body = { .defense = NULL,
                            .flash_layer_adjust = 0,
                            .flash_layer_source = 0,
                            .flash_z_source = 0
};

const s_collision_entity empty_entity_collision =   {   .coords     = NULL,
                                                        .index      = 0,
                                                        .meta_data  = NULL,
                                                        .meta_tag   = 0};

// Recursive damage (dot).
const s_damage_recursive empty_recursive = { .force = 0,
                                                .index = 0,
                                                .mode = 0,
                                                .rate = 0,
                                                .tick = 0,
                                                .time = 0,
                                                .owner = NULL,
                                                .next = NULL,
                                                .meta_data = NULL,
                                                .meta_tag = 0};

// unknockdown attack
const s_attack emptyattack =
{
    .attack_drop        = 0,
    .attack_force       = 0,
    .attack_type        = ATK_NORMAL,
    .blast              = 0,
    .blockflash         = MODEL_INDEX_NONE,
    .blocksound         = SAMPLE_ID_NONE,
    .counterattack      = 0,
    .damage_on_landing.attack_force =  0,
    .damage_on_landing.attack_type = ATK_NONE,
    .dropv              = { .x = 0,
                            .y = 0,
                            .z = 0},
    .flash_layer_adjust = 0,
    .flash_layer_source = 0,
    .flash_z_source     = 0,
    .force_direction    = DIRECTION_ADJUST_NONE,
    .forcemap           = MAP_TYPE_NONE,
    .freeze             = 0,
    .freezetime         = 0,
    .grab               = 0,
    .grab_distance      = 0,
    .guardcost          = 0,
    .hitflash           = MODEL_INDEX_NONE,
    .hitsound           = SAMPLE_ID_NONE,
    .jugglecost         = 0,
    .maptime            = 0,
    .no_block           = 0,
    .no_flash           = 0,
    .no_kill            = 0,
    .no_pain            = 0,
    .otg                = OTG_NONE,
    .next_hit_time      = 0,
    .pause_add          = 0,
    .recursive          = NULL,
    .seal               = 0,
    .sealtime           = 0,
    .staydown           = { .rise               = 0,
                            .riseattack         = 0,
                            .riseattack_stall   = 0},
    .steal              = 0,
    .meta_data          = NULL,
    .meta_tag           = 0
};

// Default values for knockdown velocity.
s_axis_principal_float default_model_dropv =
{
    .x = 1.2f,
    .y = 3.f,
    .z = 0.f
};

//default values
float default_level_maxtossspeed = 100.0f;
float default_level_maxfallspeed = -6.0f;
float default_level_gravity = -0.1f;

float default_model_jumpheight = 4.0f;
float default_model_jumpspeed = -1;
float default_model_grabdistance = 36.0f;

// AI attack debug stuff for development purpose,
// Don't open them to modders yet
float move_noatk_factor = 3.0f;
float group_noatk_factor = 0.01f;
float agg_noatk_factor = 0.0f;
float min_noatk_chance = 0.0f;
float max_noatk_chance = 0.6f;
float offscreen_noatk_factor = 0.5f;
float noatk_duration = 0.75f;

char                *custScenes = NULL;
char                *custBkgrds = NULL;
char                *custLevels = NULL;
char                *custModels = NULL;
char                rush_names[2][MAX_NAME_LEN];
char				skipselect[MAX_PLAYERS][MAX_NAME_LEN];
char                branch_name[MAX_NAME_LEN + 1];  // Used for branches
char                allowselect_args[MAX_ALLOWSELECT_LEN]; // stored allowselect players
int					useSave = 0;
int					useSet = -1;
unsigned char       pal[MAX_PAL_SIZE] = {""};
int                 blendfx[MAX_BLENDINGS] = {0, 1, 0, 0, 0, 0};
char                blendfx_is_set = 0;
int                 fontmonospace[MAX_FONTS] = {0, 0, 0, 0, 0, 0, 0, 0};
int                 fontmbs[MAX_FONTS] = {0, 0, 0, 0, 0, 0, 0, 0};

// function pointers to create the blending tables
blend_table_function blending_table_functions32[MAX_BLENDINGS] = {create_screen32_tbl, create_multiply32_tbl, create_overlay32_tbl, create_hardlight32_tbl, create_dodge32_tbl, create_half32_tbl};

int                 current_set = 0;
int                 current_level = 0;
int                 current_stage = 1;

int					timevar;
float               bgtravelled;
float               vbgtravelled;
int                 traveltime;
int                 texttime;
int					timetoshow;
int                 is_total_timeover = 0;
float               advancex;
float               advancey;

float               scrolldx;                       // advancex changed previous loop
float               scrolldy;                       // advancey .....................
float               scrollminz;                     // Limit level z-scroll
float               scrollmaxz;
float               blockade;                    // Limit x scroll back
float				scrollminx;
float				scrollmaxx;

s_lasthit           lasthit;  //Last collision variables. 2013-12-15, moved to struct.

int					combodelay = GAME_SPEED / 2;		// avoid annoying 112112... infinite combo

//Use for gfx_shadow
s_axis_plane_vertical_int light = {   .x = 128,
                        .y = 64};

int                 shadowcolor = 0;
int                 shadowalpha = BLEND_MULTIPLY + 1;
int                 shadowopacity = 255;

u64 totalram = 0;
u64 usedram = 0;
u64 freeram = 0;
u32 interval = 0;
//extern u64 seed;

/*
* Hard coded sound sample IDs.
*/
s_global_sample global_sample_list = {
    .beat = SAMPLE_ID_NONE,
    .beep = SAMPLE_ID_NONE,
    .beep_2 = SAMPLE_ID_NONE,
    .bike = SAMPLE_ID_NONE,
    .block = SAMPLE_ID_NONE,
    .fall = SAMPLE_ID_NONE,       
    .get = SAMPLE_ID_NONE,
    .get_2 = SAMPLE_ID_NONE,
    .go = SAMPLE_ID_NONE, 
    .indirect = SAMPLE_ID_NONE,
    .jump = SAMPLE_ID_NONE,
    .one_up = SAMPLE_ID_NONE,
    .pause = SAMPLE_ID_NONE,    
    .punch = SAMPLE_ID_NONE,    
    .time_over = SAMPLE_ID_NONE    
};

// 2016-11-01
// Caskey, Damon V.
//
// Collision indexes. Only using globals while
// building multiple collision box support.
// Once we get this working, variables should
// be moved into a structure. Globals BAD!
int                 max_collisons       = MAX_COLLISIONS;
int                 *collisions         = NULL;


int                 max_downs           = MAX_DOWNS;
int                 max_ups             = MAX_UPS;
int                 max_backwalks       = MAX_BACKWALKS;
int                 max_walks           = MAX_WALKS;
int                 max_idles           = MAX_IDLES;
int                 max_attack_types    = MAX_ATKS;
int                 max_freespecials    = MAX_SPECIALS;
int                 max_follows         = MAX_FOLLOWS;
int                 max_attacks         = MAX_ATTACKS;
int                 max_animations      = MAX_ANIS;

// -------dynamic animation indexes-------
e_animations	*animdowns           = NULL;
e_animations    *animups             = NULL;
e_animations    *animbackwalks       = NULL;
e_animations	*animwalks           = NULL;
e_animations    *animidles           = NULL;
e_animations    *animpains           = NULL;
e_animations    *animbackpains       = NULL;
e_animations    *animdies            = NULL;
e_animations    *animbackdies        = NULL;
e_animations    *animfalls           = NULL;
e_animations    *animbackfalls       = NULL;
e_animations    *animrises           = NULL;
e_animations    *animbackrises       = NULL;
e_animations    *animriseattacks     = NULL;
e_animations    *animbackriseattacks = NULL;
e_animations    *animblkpains        = NULL;
e_animations    *animbackblkpains    = NULL;
e_animations    *animattacks         = NULL;
e_animations    *animfollows         = NULL;
e_animations    *animspecials        = NULL;

// system default values
int                 downs[MAX_DOWNS]        = {ANI_DOWN};
int                 ups[MAX_UPS]            = {ANI_UP};
int                 backwalks[MAX_BACKWALKS] = {ANI_BACKWALK};
int                 walks[MAX_WALKS]        = {ANI_WALK};
int                 idles[MAX_IDLES]        = {ANI_IDLE};

int                 falls[MAX_ATKS] =
{
    ANI_FALL,  ANI_FALL2, ANI_FALL3,  ANI_FALL4,
    ANI_FALL,  ANI_BURN,  ANI_FALL,   ANI_SHOCK,
    ANI_FALL,  ANI_FALL5, ANI_FALL6,  ANI_FALL7,
    ANI_FALL8, ANI_FALL9, ANI_FALL10, ANI_FALL,
    ANI_FALL,  ANI_FALL,  ANI_FALL,   ANI_FALLLOSE,
    ANI_FALL
};

int                 backfalls[MAX_ATKS] =
{
    ANI_BACKFALL,  ANI_BACKFALL2, ANI_BACKFALL3,  ANI_BACKFALL4,
    ANI_BACKFALL,  ANI_BACKBURN,  ANI_BACKFALL,   ANI_BACKSHOCK,
    ANI_BACKFALL,  ANI_BACKFALL5, ANI_BACKFALL6,  ANI_BACKFALL7,
    ANI_BACKFALL8, ANI_BACKFALL9, ANI_BACKFALL10, ANI_BACKFALL,
    ANI_BACKFALL,  ANI_BACKFALL,  ANI_BACKFALL,   ANI_FALLLOSE,
    ANI_BACKFALL
};

int                 rises[MAX_ATKS] =
{
    ANI_RISE,  ANI_RISE2, ANI_RISE3,  ANI_RISE4,
    ANI_RISE,  ANI_RISEB,  ANI_RISE,  ANI_RISES,
    ANI_RISE,  ANI_RISE5, ANI_RISE6,  ANI_RISE7,
    ANI_RISE8, ANI_RISE9, ANI_RISE10, ANI_RISE,
    ANI_RISE,  ANI_RISE,  ANI_RISE,   ANI_RISE,
    ANI_RISE
};

int                 backrises[MAX_ATKS] =
{
    ANI_BACKRISE,  ANI_BACKRISE2, ANI_BACKRISE3,  ANI_BACKRISE4,
    ANI_BACKRISE,  ANI_BACKRISEB, ANI_BACKRISE,   ANI_BACKRISES,
    ANI_BACKRISE,  ANI_BACKRISE5, ANI_BACKRISE6,  ANI_BACKRISE7,
    ANI_BACKRISE8, ANI_BACKRISE9, ANI_BACKRISE10, ANI_BACKRISE,
    ANI_BACKRISE,  ANI_BACKRISE,  ANI_BACKRISE,   ANI_BACKRISE,
    ANI_BACKRISE
};

int                 riseattacks[MAX_ATKS] =
{
    ANI_RISEATTACK,  ANI_RISEATTACK2, ANI_RISEATTACK3,  ANI_RISEATTACK4,
    ANI_RISEATTACK,  ANI_RISEATTACKB, ANI_RISEATTACK,   ANI_RISEATTACKS,
    ANI_RISEATTACK,  ANI_RISEATTACK5, ANI_RISEATTACK6,  ANI_RISEATTACK7,
    ANI_RISEATTACK8, ANI_RISEATTACK9, ANI_RISEATTACK10, ANI_RISEATTACK,
    ANI_RISEATTACK,  ANI_RISEATTACK,  ANI_RISEATTACK,   ANI_RISEATTACK,
    ANI_RISEATTACK
};

int                 backriseattacks[MAX_ATKS] =
{
    ANI_BACKRISEATTACK,  ANI_BACKRISEATTACK2, ANI_BACKRISEATTACK3,  ANI_BACKRISEATTACK4,
    ANI_BACKRISEATTACK,  ANI_BACKRISEATTACKB, ANI_BACKRISEATTACK,   ANI_BACKRISEATTACKS,
    ANI_BACKRISEATTACK,  ANI_BACKRISEATTACK5, ANI_BACKRISEATTACK6,  ANI_BACKRISEATTACK7,
    ANI_BACKRISEATTACK8, ANI_BACKRISEATTACK9, ANI_BACKRISEATTACK10, ANI_BACKRISEATTACK,
    ANI_BACKRISEATTACK,  ANI_BACKRISEATTACK,  ANI_BACKRISEATTACK,   ANI_BACKRISEATTACK,
    ANI_BACKRISEATTACK
};

int                 pains[MAX_ATKS] =
{
    ANI_PAIN,  ANI_PAIN2,    ANI_PAIN3,  ANI_PAIN4,
    ANI_PAIN,  ANI_BURNPAIN, ANI_PAIN,   ANI_SHOCKPAIN,
    ANI_PAIN,  ANI_PAIN5,    ANI_PAIN6,  ANI_PAIN7,
    ANI_PAIN8, ANI_PAIN9,    ANI_PAIN10, ANI_PAIN,
    ANI_PAIN,  ANI_PAIN,     ANI_PAIN,   ANI_PAIN,
    ANI_PAIN
};

int                 backpains[MAX_ATKS] =
{
    ANI_BACKPAIN,  ANI_BACKPAIN2,    ANI_BACKPAIN3,  ANI_BACKPAIN4,
    ANI_BACKPAIN,  ANI_BACKBURNPAIN, ANI_BACKPAIN,   ANI_BACKSHOCKPAIN,
    ANI_BACKPAIN,  ANI_BACKPAIN5,    ANI_BACKPAIN6,  ANI_BACKPAIN7,
    ANI_BACKPAIN8, ANI_BACKPAIN9,    ANI_BACKPAIN10, ANI_BACKPAIN,
    ANI_BACKPAIN,  ANI_BACKPAIN,     ANI_BACKPAIN,   ANI_BACKPAIN,
    ANI_BACKPAIN
};

int                 deaths[MAX_ATKS] =
{
    ANI_DIE,   ANI_DIE2,     ANI_DIE3,  ANI_DIE4,
    ANI_DIE,   ANI_BURNDIE,  ANI_DIE,   ANI_SHOCKDIE,
    ANI_DIE,   ANI_DIE5,     ANI_DIE6,  ANI_DIE7,
    ANI_DIE8,  ANI_DIE9,     ANI_DIE10, ANI_DIE,
    ANI_DIE,   ANI_DIE,      ANI_DIE,   ANI_LOSE,
    ANI_DIE
};

int                 backdeaths[MAX_ATKS] =
{
    ANI_BACKDIE,   ANI_BACKDIE2,     ANI_BACKDIE3,  ANI_BACKDIE4,
    ANI_BACKDIE,   ANI_BACKBURNDIE,  ANI_BACKDIE,   ANI_BACKSHOCKDIE,
    ANI_BACKDIE,   ANI_BACKDIE5,     ANI_BACKDIE6,  ANI_BACKDIE7,
    ANI_BACKDIE8,  ANI_BACKDIE9,     ANI_BACKDIE10, ANI_BACKDIE,
    ANI_BACKDIE,   ANI_BACKDIE,      ANI_BACKDIE,   ANI_LOSE,
    ANI_BACKDIE
};

int                 blkpains[MAX_ATKS] =
{
    ANI_BLOCKPAIN,  ANI_BLOCKPAIN2, ANI_BLOCKPAIN3,  ANI_BLOCKPAIN4,
    ANI_BLOCKPAIN,  ANI_BLOCKPAINB, ANI_BLOCKPAIN,   ANI_BLOCKPAINS,
    ANI_BLOCKPAIN,  ANI_BLOCKPAIN5, ANI_BLOCKPAIN6,  ANI_BLOCKPAIN7,
    ANI_BLOCKPAIN8, ANI_BLOCKPAIN9, ANI_BLOCKPAIN10, ANI_BLOCKPAIN,
    ANI_BLOCKPAIN,  ANI_BLOCKPAIN,  ANI_BLOCKPAIN,   ANI_BLOCKPAIN,
    ANI_BLOCKPAIN
};

int                 backblkpains[MAX_ATKS] =
{
    ANI_BACKBLOCKPAIN,  ANI_BACKBLOCKPAIN2, ANI_BACKBLOCKPAIN3,  ANI_BACKBLOCKPAIN4,
    ANI_BACKBLOCKPAIN,  ANI_BACKBLOCKPAINB, ANI_BACKBLOCKPAIN,   ANI_BACKBLOCKPAINS,
    ANI_BACKBLOCKPAIN,  ANI_BACKBLOCKPAIN5, ANI_BACKBLOCKPAIN6,  ANI_BACKBLOCKPAIN7,
    ANI_BACKBLOCKPAIN8, ANI_BACKBLOCKPAIN9, ANI_BACKBLOCKPAIN10, ANI_BACKBLOCKPAIN,
    ANI_BACKBLOCKPAIN,  ANI_BACKBLOCKPAIN,  ANI_BACKBLOCKPAIN,   ANI_BACKBLOCKPAIN,
    ANI_BACKBLOCKPAIN
};

int                 normal_attacks[MAX_ATTACKS] =
{
    ANI_ATTACK1, ANI_ATTACK2, ANI_ATTACK3, ANI_ATTACK4
};

int                 grab_attacks[GRAB_ACTION_SELECT_MAX][2] =
{
    [GRAB_ACTION_SELECT_ATTACK] = {ANI_GRABATTACK, ANI_GRABATTACK2},
	[GRAB_ACTION_SELECT_BACKWARD] = {ANI_GRABBACKWARD, ANI_GRABBACKWARD2},
	[GRAB_ACTION_SELECT_FORWARD] = {ANI_GRABFORWARD, ANI_GRABFORWARD2},
    [GRAB_ACTION_SELECT_DOWN] = {ANI_GRABDOWN, ANI_GRABDOWN2},
	[GRAB_ACTION_SELECT_UP] = {ANI_GRABUP, ANI_GRABUP2}
};

int                 freespecials[MAX_SPECIALS] =
{
    ANI_FREESPECIAL,   ANI_FREESPECIAL2,  ANI_FREESPECIAL3,
    ANI_FREESPECIAL4,  ANI_FREESPECIAL5,  ANI_FREESPECIAL6,
    ANI_FREESPECIAL7,  ANI_FREESPECIAL8
};

int                 follows[MAX_FOLLOWS] =
{
    ANI_FOLLOW1, ANI_FOLLOW2, ANI_FOLLOW3, ANI_FOLLOW4
};

// background cache to speed up in-game menus
#ifdef CACHE_BACKGROUNDS
s_screen           *bg_cache[MAX_CACHED_BACKGROUNDS] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
unsigned char		bg_palette_cache[MAX_CACHED_BACKGROUNDS][MAX_PAL_SIZE];
#endif

s_debug_xy_msg      debug_xy_msg;
int                 cameratype          = 0;
int					defaultmaxplayers	= 2;

u32                 go_time             = 0;
u32                 _time               = 0;
u32                 newtime             = 0;
s_slow_motion       slowmotion          = { .toggle     = SLOW_MOTION_OFF,
                                            .counter    = 0,
                                            .duration   = 2};
int                 disablelog          = 0;
int                 currentspawnplayer  = 0;
int					ent_list_size		= 0;
int                 PLAYER_MIN_Z        = 160;
int                 PLAYER_MAX_Z        = 232;
int                 BGHEIGHT            = 160;
int                 MAX_WALL_HEIGHT     = 1000;					// Max wall height that an entity can be spawned on
int                 saveslot            = 0;
int                 current_palette     = 0;
int                 fade                = 24;
int                 credits             = 0;
int                 gosound             = 0;					// Used to prevent go sound playing too frequently,
int                 musicoverlap        = 0;
int                 colorbars           = 0;
int                 current_spawn       = 0;
int                 level_completed     = 0;
int                 level_completed_defeating_boss     = 0;
int                 nojoin              = 0;					// dont allow new hero to join in, use "Please Wait" instead of "Select Hero"
int                 groupmin            = 0;
int					groupmax            = 0;
e_screen_status     screen_status       = IN_SCREEN_NONE;       // Caskey, Damon V. (2022-04-21) - Current screen status. Replaces the previous 16+ "inscreen" flag variables.
char				*currentScene		= NULL;
int                 tospeedup           = 0;          			// If set will speed the level back up after a boss hits the ground
int                 reached[MAX_PLAYERS]          = {0, 0, 0, 0};			// Used with TYPE_ENDLEVEL to determine which players have reached the point //4player
int                 noslowfx			= 0;           			// Flag to determine if sound speed when hitting opponent slows or not
int                 equalairpause 		= 0;         			// If set to 1, there will be no extra pausetime for players who hit multiple enemies in midair
int                 hiscorebg			= 0;					// If set to 1, will look for a background image to display at the highscore screen
int                 completebg			= 0;           			// If set to 1, will look for a background image to display at the showcomplete screen
s_loadingbar        loadingbg[2] = {{0, 0, {0, 0}, {0, 0}, 0, 0}, {0, 0, {0, 0}, {0, 0}, 0, 0}}; // If set to 1, will look for a background image to display at the loading screen
int					loadingmusic        = 0;
int                 unlockbg            = 0;         			// If set to 1, will look for a different background image after defeating the game
int                 _pause              = 0;
int                 goto_mainmenu_flag  = 0;
int                 escape_flag         = 0;                    // Kratus (20-04-21) Added the new "escape" flag in the select screen, has the same effect as the esc key but now accessible by the "gotomainmenu" function
int					nofadeout			= 0;
int					nosave				= 0;
int                 nopause             = 0;                    // OX. If set to 1 , pausing the game will be disabled.
int                 noscreenshot        = 0;                    // OX. If set to 1 , taking screenshots is disabled.
int                 endgame             = 0;
int                 nodebugoptions      = 0;

int                 keyscriptrate       = 0;
int                 showtimeover        = 0;
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                 CONTINUES           = 5;					// 7-1-2005  default setting for continues
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                 nolost				= 0;					// variable to control if drop weapon when grab a enemy by tails
int                 nocost				= 0;					// If set, special will not cost life unless an enemy is hit
int                 mpstrict			= 0;					// If current system will check all animation's energy cost when set new animations
int                 magic_type			= 0;					// use for restore mp by time by tails
entity             *textbox				= NULL;
entity             *smartbomber			= NULL;
entity				*stalker			= NULL;					// an enemy (usually) tries to go behind the player
entity				*firstplayer		= NULL;
int					stalking			= 0;
int					nextplan			= 0;
int                 plife[MAX_PLAYERS][2]         = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; // Used for customizable player lifebar
int                 plifeX[MAX_PLAYERS][3]        = {{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}}; // Used for customizable player lifebar 'x'
int                 plifeN[MAX_PLAYERS][3]        = {{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}}; // Used for customizable player lifebar number of lives
int                 picon[MAX_PLAYERS][2]         = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; // Used for customizable player icon
int                 piconw[MAX_PLAYERS][2]        = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; // Used for customizable player weapon icons
int                 mpicon[MAX_PLAYERS][2]        = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; // Used for customizable magicbar player icon
int                 pnameJ[MAX_PLAYERS][7]        = {{0, 0, 0, 0, 0, 0, -1}, {0, 0, 0, 0, 0, 0, -1}, {0, 0, 0, 0, 0, 0, -1}, {0, 0, 0, 0, 0, 0, -1}}; // Used for customizable player name, Select Hero, (Credits, Press Start, Game Over) when joining
int                 pscore[MAX_PLAYERS][7]        = {{0, 0, 0, 0, 0, 0, -1}, {0, 0, 0, 0, 0, 0, -1}, {0, 0, 0, 0, 0, 0, -1}, {0, 0, 0, 0, 0, 0, -1}}; // Used for customizable player name, dash, score
int                 pshoot[MAX_PLAYERS][3]        = {{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}}; // Used for customizable player shootnum
int                 prush[MAX_PLAYERS][8]         = {{0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}}; // Used for customizable player combo/rush system
int                 psmenu[MAX_PLAYERS][4]        = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; // Used for customizable player placement in select menu
int                 mpcolourtable[11]   = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int                 hpcolourtable[11]   = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int                 ldcolourtable[11]   = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
char                musicname[MAX_BUFFER_LEN]      = {""};
char                currentmusic[MAX_BUFFER_LEN]    = {""};
float               musicfade[2]        = {0, 0};
int                 musicloop           = 0;
u32                 musicoffset         = 0;
int					alwaysupdate		= 0; //execute update/updated scripts whenever it has a chance

s_global_config global_config =
{
    .ajspecial = AJSPECIAL_KEY_SPECIAL,
    .block_type = BLOCK_TYPE_GLOBAL,
    .cheats = CHEAT_OPTIONS_ALL_MENU,    
    .flash_layer_adjust = 1,
    .flash_layer_source = 255,
    .flash_z_source = 0,
    .showgo = 0    
};

s_barstatus loadingbarstatus =
{
    .size           = { .x = 0,
                        .y = 10},
    .graph_position    = { .x = 0,
                        .y = 0},
    .name_position = {  .x = 0,
                        .y = 0},
    .config_flags   = STATUS_CONFIG_GRAPH_RATIO,
    .barlayer       = 0,
    .backlayer      = 0,
    .borderlayer    = 0,
    .shadowlayer    = 0,
    .colourtable    = &ldcolourtable
};

s_barstatus lbarstatus, olbarstatus =
{
    .size           = { .x = 0,
                        .y = 0},
    .graph_position    = { .x = 0,
                        .y = 0},
    .name_position  = { .x = 0,
                        .y = 0},
    .config_flags   = STATUS_CONFIG_DEFAULT,
    .barlayer       = 0,
    .backlayer      = 0,
    .borderlayer    = 0,
    .shadowlayer    = 0,
    .colourtable    = &hpcolourtable
};

s_barstatus mpbarstatus =
{
    .size           = { .x = 0,
                        .y = 0},
    .graph_position    = { .x = 0,
                        .y = 0},
    .name_position  = { .x = 0,
                        .y = 0},
    .config_flags   = STATUS_CONFIG_DEFAULT,
    .barlayer       = 0,
    .backlayer      = 0,
    .borderlayer    = 0,
    .shadowlayer    = 0,
    .colourtable    = &mpcolourtable
};

int                 timeloc[6]			= {0, 0, 0, 0, 0, -1};		// Used for customizable timeclock location/size
int                 timeicon			= -1;
int                 timeicon_offsets[2] = {0, 0};
char                timeicon_path[MAX_BUFFER_LEN]  = {""};
int                 bgicon   			= -1;
int                 bgicon_offsets[3]	= {0, 0, 0};
char                bgicon_path[MAX_BUFFER_LEN]    = {""};
int                 olicon    			= -1;
int                 olicon_offsets[3]	= {0, 0, 0};
char                olicon_path[MAX_BUFFER_LEN]    = {""};
int                 elife[4][2]         = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; // Used for customizable enemy lifebar
int                 ename[4][3]         = {{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}}; // Used for customizable enemy name
int                 eicon[4][2]         = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; // Used for customizable enemy icon
int                 scomplete[6]		= {0, 0, 0, 0, 0, 0};		// Used for customizable Stage # Complete
int                 cbonus[10]          = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Used for customizable clear bonus
int                 lbonus[10]          = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Used for customizable life bonus
int                 rbonus[10]          = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Used for customizable rush bonus
int                 tscore[10]          = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Used for customizable total score
int                 scbonuses[4]        = {10000, 1000, 100, 0};//Stage complete bonus multipliers
int                 showrushbonus       = 0;
int                 noshare				= 0;					// Used for when you want to keep p1 & p2 credits separate
int                 nodropen			= 0;					// Drop or not when spawning is now a modder option
int					nodropspawn			= 0;					// don't spawn from the sky if the modder doesn't set it
int                 gfx_x_offset		= 0;                    //2011_04_03, DC: Enable X offset adjustment by modders.
int                 gfx_y_offset		= 0;
int                 gfx_y_offset_adj    = 0;                    //2011_04_03, DC: Enable Y offset adjustment by modders.

// 2011/10/22 UT: temporary solution for custom viewport
int					viewportx			= 0;
int					viewporty			= 0;
int					viewportw			= 0;
int					viewporth			= 0;


int                 timeleft			= 0;
int                 oldtime             = 0;                    // One second back from time left.
int                 holez				= 0;					// Used for setting spawn points
int                 allow_secret_chars	= 0;
unsigned int        lifescore			= 50000;				// Number of points needed to earn a 1-up
unsigned int        credscore			= 0;					// Number of points needed to earn a credit
int                 nochipdeath			= 0;					// Prevents entities from dying due to chip damage (damage while blocking)
int                 noaircancel			= 0;					// Now, you can make jumping attacks uncancellable!
int                 nomaxrushreset[5]	= {0, 0, 0, 0, 0};
int			        mpbartext[4]		= { -1, 0, 0, 0};			// Array for adjusting MP status text (font, Xpos, Ypos, Display type).
int			        lbartext[4]			= { -1, 0, 0, 0};			// Array for adjusting HP status text (font, Xpos, Ypos, Display type).
int                 pmp[4][2]			= {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; // Used for customizable player mpbar
int                 spdirection[4]		= {1, 0, 1, 0};			// Used for Select Player Direction for select player screen
int                 bonus				= 0;					// Used for unlocking Bonus difficulties
int                 versusdamage		= 2;					// Used for setting mode. (ability to hit other players)
int                 z_coords[3]			= {0, 0, 0};				// Used for setting customizable walkable area
int                 rush[6]				= {0, 2, 3, 3, 3, 3};
int                 pauseoffset[7]  	= {0, 1, 0, 0, 3, 0, 0};		// Used for customizable pause menu location (font0, font1, xpos, ypos, font_pause, xpos_pause, ypos_pause)
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;
int                 color_magic			= 0;
int                 color_magic2		= 0;
int                 lifebarfgalpha      = 0;
int                 lifebarbgalpha      = 2;
int                 shadowsprites[6]	= { -1, -1, -1, -1, -1, -1};
int                 gosprite			= -1;
int                 golsprite			= -1;
//int                 holesprite			= -1;
int                 videoMode			= 0;
int                 scoreformat			= 0;					// If set fill score values with 6 Zeros

// Funny neon lights
unsigned char       neontable[MAX_PAL_SIZE];
unsigned int        neon_time			= 0;

int                 panel_width			= 0;
int                 panel_height		= 0;
int                 frontpanels_loaded	= 0;

unsigned int        sprites_loaded		= 0;
unsigned int        anims_loaded		= 0;

unsigned int        models_loaded		= 0;
unsigned int        models_cached		= 0;

entity            **ent_list;
entity            **ent_stack; //temporary list, reference only
int					ent_stack_size = 0;
entity             *self;
int                 ent_count			= 0;					// log count of entites
int                 ent_max				= 0;

s_player            player[MAX_PLAYERS];
unsigned long long  bothkeys;
unsigned long long  bothnewkeys;

s_playercontrols    playercontrols1;
s_playercontrols    playercontrols2;
s_playercontrols    playercontrols3;
s_playercontrols    playercontrols4;
s_playercontrols   *playercontrolpointers[] = {&playercontrols1, &playercontrols2, &playercontrols3, &playercontrols4};
s_playercontrols    default_control;
int default_keys[MAX_BTN_NUM];

//global script
Script level_script;		//execute when level start
Script endlevel_script;		//execute when level finished
Script update_script;		//execute when ingame update
Script updated_script;		//execute when ingame update finished
Script loading_script;		// in loading screen
Script input_script_all;  //keyscript for all players
Script key_script_all;		//keyscript for all players
Script timetick_script;		//time tick script.

//player script
Script score_script[MAX_PLAYERS];     //execute when add score, 4 players
Script key_script[MAX_PLAYERS];       //key listeners
Script join_script[MAX_PLAYERS];      //player join scripts
Script respawn_script[MAX_PLAYERS];   //player respawn scripts
Script pdie_script[MAX_PLAYERS];      //player death scripts

extern Script *pcurrentscript;//used by local script functions
//-------------------------methods-------------------------------

void setDrawMethod(s_anim *a, ptrdiff_t index, s_drawmethod *m)
{
    assert(index >= 0);
    assert(a != NULL);
    assert(m != NULL);
    assert(index < a->numframes);
    a->drawmethods[index] = m;
}

s_drawmethod *getDrawMethod(s_anim *a, ptrdiff_t index)
{
    assert(index >= 0);
    assert(a != NULL);
    assert(index < a->numframes);
    return a->drawmethods[index];
}

int isLoadingScreenTypeBg(e_loadingScreenType what)
{
    return (what & LS_TYPE_BACKGROUND) == LS_TYPE_BACKGROUND;
}

int isLoadingScreenTypeBar(e_loadingScreenType what)
{
    return (what & LS_TYPE_BAR) == LS_TYPE_BAR;
}

char *fill_s_loadingbar(s_loadingbar *s, e_loadingScreenType set, int bx, int by, int bsize, int tx, int ty, int tf, int ms)
{
    switch (set)
    {
        case LS_TYPE_BOTH:
            s->set = (LS_TYPE_BACKGROUND | LS_TYPE_BAR);
            break;
        case LS_TYPE_BACKGROUND:
            s->set = LS_TYPE_BACKGROUND;
            break;
        case LS_TYPE_BAR:
            s->set = LS_TYPE_BAR;
            break;
        case LS_TYPE_NONE:
            s->set = LS_TYPE_NONE;
            break;
        default:
            s->set = LS_TYPE_NONE;
            printf("invalid loadingbg type %d!\n", set);
    }
    s->tf = tf;
    s->bar_position.x = bx;
    s->bar_position.y = by;
    s->bsize = bsize;
    s->text_position.x = tx;
    s->text_position.y = ty;
    s->refreshMs = (ms ? ms : 100);
    return NULL;
}


static int buffer_file(char *filename, char **pbuffer, size_t *psize)
{
    FILE *handle;
    *psize = 0;
    *pbuffer = NULL;
    // Read file
#ifdef VERBOSE
    printf("file requested: %s.\n", filename);
#endif

    if(!(handle = fopen(filename, "rb")) )
    {
#ifdef VERBOSE
        printf("couldnt get handle!\n");
#endif
        return 0;
    }
    fseek(handle, 0, SEEK_END);
    *psize = ftell(handle);
    fseek(handle, 0, SEEK_SET);

    *pbuffer = (char *)malloc(*psize + 1);
    if(*pbuffer == NULL)
    {
        *psize = 0;
        fclose(handle);
        borShutdown(1, "Can't create buffer for file '%s'", filename);
        return 0;
    }
    if(fread(*pbuffer, 1, *psize, handle) != *psize)
    {
        if(*pbuffer != NULL)
        {
            free(*pbuffer);
            *pbuffer = NULL;
            *psize = 0;
        }
        fclose(handle);
        borShutdown(1, "Can't read from file '%s'", filename);
        return 0;
    }
    (*pbuffer)[*psize] = 0;        // Terminate string (important!)
    fclose(handle);
    return 1;
}


// returns: 1 - succeeded 0 - failed
int buffer_pakfile(char *filename, char **pbuffer, size_t *psize)
{
    int handle;
    *psize = 0;
    *pbuffer = NULL;

    if(buffer_file(filename, pbuffer, psize) == 1)
    {
        return 1;
    }

    // Read file
#ifdef VERBOSE
    printf("pakfile requested: %s.\n", filename); //ASDF
#endif

    if((handle = openpackfile(filename, packfile)) < 0)
    {
#ifdef VERBOSE
        printf("couldnt get handle!\n");
#endif
        return 0;
    }
    *psize = seekpackfile(handle, 0, SEEK_END);
    seekpackfile(handle, 0, SEEK_SET);

    *pbuffer = (char *)malloc(*psize + 1);
    if(*pbuffer == NULL)
    {
        *psize = 0;
        closepackfile(handle);
        borShutdown(1, "Can't create buffer for packfile '%s'", filename);
        return 0;
    }
    if(readpackfile(handle, *pbuffer, *psize) != *psize)
    {
        if(*pbuffer != NULL)
        {
            free(*pbuffer);
            *pbuffer = NULL;
            *psize = 0;
        }
        closepackfile(handle);
        borShutdown(1, "Can't read from packfile '%s'", filename);
        return 0;
    }
    (*pbuffer)[*psize] = 0;        // Terminate string (important!)
    closepackfile(handle);
    return 1;
}

int buffer_append(char **buffer, const char *str, size_t n, size_t *bufferlen, size_t *len)
{
    size_t appendlen = strlen(str);
    if(appendlen > n)
    {
        appendlen = n;
    }
    if(appendlen + *len + 1 > *bufferlen)
    {
        //printf("*Debug* reallocating buffer...\n");
        *buffer = realloc(*buffer, *bufferlen = appendlen + *len + 1024);
        if(*buffer == NULL)
        {
            borShutdown(1, "Unalbe to resize buffer.\n");
        }
    }
    strncpy(*buffer + *len, str, appendlen);
    *len = *len + appendlen;
    (*buffer)[*len] = 0;
    return *len;
}

int handle_txt_include(char *command, ArgList *arglist, char **fn, char *namebuf, char **buf, ptrdiff_t *pos, size_t *len)
{
    char *incfile, *filename = *fn, *buf2, *endstr = "\r\n@end";
    size_t size, t;
    if(stricmp(command, "@include") == 0)
    {
        incfile = GET_ARGP(1);
        buffer_pakfile(incfile, &buf2, &size) ;
        if(buf2)
        {
            *buf = realloc(*buf, *len + size + strlen(incfile) + strlen(filename) + 100); //leave enough memory for jump command
            if(*buf == NULL)
            {
                borShutdown(1, "Unalbe to resize buffer. (handle_txt_include)\n");
                free(buf2);
                return 0;
            }
            sprintf((*buf) + *len - 1, "%s\r\n@filename %s\r\n", endstr, incfile);
            strcat((*buf) + *len, buf2);
            t = strlen(*buf);
            sprintf((*buf) + t, "\r\n@filename %s\r\n@jump %d\r\n", filename, (int)(*pos));
            (*buf)[*pos] = '#';
            *pos = *len + strlen(endstr); //continue from the new file position
            *len = strlen(*buf);
            free(buf2);
            //printf(*buf);
            return 1;
        }
        borShutdown(1, "Can't find file '%s' to include.\n", incfile);
    }
    else if(stricmp(command, "@jump") == 0)
    {
        *pos = GET_INT_ARGP(1);
        return 2;
    }
    else if(stricmp(command, "@end") == 0)
    {
        *pos = *len;
        return 3;
    }
    else if(stricmp(command, "@filename") == 0)
    {
        strcpy(namebuf, GET_ARGP(1));
        *fn = namebuf;
        return 4;
    }
    return 0;
}

int load_script(Script *script, char *file)
{
    size_t size = 0;
    int failed = 0;
    char *buf = NULL;

    if(buffer_pakfile(file, &buf, &size) != 1)
    {
        return 0;
    }

    failed = !Script_AppendText(script, buf, file);

    if(buf != NULL)
    {
        free(buf);
        buf = NULL;
    }

    // text loaded but parsing failed, shutdown
    if(failed)
    {
        borShutdown(1, "Failed to parse script file: '%s'!\n", file);
    }
    return !failed;
}

// this method is used by load_scripts, don't call it
void init_scripts()
{
    int i;
    Script_Global_Init();
    Script_Init(&update_script,     "update",  NULL,  1);
    Script_Init(&updated_script,    "updated",  NULL, 1);
    Script_Init(&level_script,      "level",    NULL,  1);
    Script_Init(&endlevel_script,   "endlevel",  NULL, 1);
	Script_Init(&input_script_all, "inputall", NULL, 1);
    Script_Init(&key_script_all,    "keyall",   NULL,  1);
    Script_Init(&timetick_script,   "timetick",  NULL, 1);
    Script_Init(&loading_script,    "loading",   NULL, 1);
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Init(&score_script[i],    "score",    NULL,  1);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Init(&key_script[i],      "key",      NULL,  1);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Init(&join_script[i],     "join",      NULL, 1);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Init(&respawn_script[i],  "respawn",   NULL, 1);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Init(&pdie_script[i],     "die",       NULL, 1);
    }
}

// This method is called once when the engine starts, do not use it multiple times
// It should be calld after load_script_setting
void load_scripts()
{
    int i;
    init_scripts();
    //Script_Clear's second parameter set to 2, because the script fails to load,
    //and will never have another chance to be loaded, so just clear the variable list in it
    if(!load_script(&update_script,     "data/scripts/update.c"))
    {
        Script_Clear(&update_script,        2);
    }
    if(!load_script(&updated_script,    "data/scripts/updated.c"))
    {
        Script_Clear(&updated_script,       2);
    }
    if(!load_script(&level_script,      "data/scripts/level.c"))
    {
        Script_Clear(&level_script,         2);
    }
    if(!load_script(&endlevel_script,   "data/scripts/endlevel.c"))
    {
        Script_Clear(&endlevel_script,      2);
    }
	if (!load_script(&input_script_all, "data/scripts/inputall.c"))
	{
		Script_Clear(&input_script_all, 2);
	}
    if(!load_script(&key_script_all,    "data/scripts/keyall.c"))
    {
        Script_Clear(&key_script_all,       2);
    }
    if(!load_script(&timetick_script,   "data/scripts/timetick.c"))
    {
        Script_Clear(&timetick_script,      2);
    }
    if(!load_script(&loading_script,    "data/scripts/loading.c"))
    {
        Script_Clear(&loading_script,       2);
    }
    if(!load_script(&score_script[0],   "data/scripts/score1.c"))
    {
        Script_Clear(&score_script[0],      2);
    }
    if(!load_script(&score_script[1],   "data/scripts/score2.c"))
    {
        Script_Clear(&score_script[1],      2);
    }
    if(!load_script(&score_script[2],   "data/scripts/score3.c"))
    {
        Script_Clear(&score_script[2],      2);
    }
    if(!load_script(&score_script[3],   "data/scripts/score4.c"))
    {
        Script_Clear(&score_script[3],      2);
    }
    if(!load_script(&key_script[0],     "data/scripts/key1.c"))
    {
        Script_Clear(&key_script[0],        2);
    }
    if(!load_script(&key_script[1],     "data/scripts/key2.c"))
    {
        Script_Clear(&key_script[1],        2);
    }
    if(!load_script(&key_script[2],     "data/scripts/key3.c"))
    {
        Script_Clear(&key_script[2],        2);
    }
    if(!load_script(&key_script[3],     "data/scripts/key4.c"))
    {
        Script_Clear(&key_script[3],        2);
    }
    if(!load_script(&join_script[0],    "data/scripts/join1.c"))
    {
        Script_Clear(&join_script[0],       2);
    }
    if(!load_script(&join_script[1],    "data/scripts/join2.c"))
    {
        Script_Clear(&join_script[1],       2);
    }
    if(!load_script(&join_script[2],    "data/scripts/join3.c"))
    {
        Script_Clear(&join_script[2],       2);
    }
    if(!load_script(&join_script[3],    "data/scripts/join4.c"))
    {
        Script_Clear(&join_script[3],       2);
    }
    if(!load_script(&respawn_script[0], "data/scripts/respawn1.c"))
    {
        Script_Clear(&respawn_script[0],    2);
    }
    if(!load_script(&respawn_script[1], "data/scripts/respawn2.c"))
    {
        Script_Clear(&respawn_script[1],    2);
    }
    if(!load_script(&respawn_script[2], "data/scripts/respawn3.c"))
    {
        Script_Clear(&respawn_script[2],    2);
    }
    if(!load_script(&respawn_script[3], "data/scripts/respawn4.c"))
    {
        Script_Clear(&respawn_script[3],    2);
    }
    if(!load_script(&pdie_script[0],    "data/scripts/die1.c"))
    {
        Script_Clear(&pdie_script[0],       2);
    }
    if(!load_script(&pdie_script[1],    "data/scripts/die2.c"))
    {
        Script_Clear(&pdie_script[1],       2);
    }
    if(!load_script(&pdie_script[2],    "data/scripts/die3.c"))
    {
        Script_Clear(&pdie_script[2],       2);
    }
    if(!load_script(&pdie_script[3],    "data/scripts/die4.c"))
    {
        Script_Clear(&pdie_script[3],       2);
    }
    Script_Compile(&update_script);
    Script_Compile(&updated_script);
    Script_Compile(&level_script);
    Script_Compile(&endlevel_script);
	Script_Compile(&input_script_all);
    Script_Compile(&key_script_all);
    Script_Compile(&timetick_script);
    Script_Compile(&loading_script);
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Compile(&score_script[i]);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Compile(&key_script[i]);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Compile(&join_script[i]);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Compile(&respawn_script[i]);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Compile(&pdie_script[i]);
    }
}

void unfrozen(entity *e)
{
    ent_set_colourmap(e, e->map);
    e->frozen = 0;
    e->freezetime = 0;
}

int is_frozen(entity *e)
{
    return ((textbox && e->modeldata.type != TYPE_TEXTBOX) ||
						 (smartbomber && e != smartbomber && e->modeldata.type != TYPE_TEXTBOX) || (self->frozen && self->freezetime > _time));
}

// This method is called once when the engine is shutting down, do not use it multiple times
void clear_scripts()
{
    int i;
    //Script_Clear's second parameter set to 2, because the script fails to load,
    //and will never have another chance to be loaded, so just clear the variable list in it
    Script_Clear(&update_script,    2);
    Script_Clear(&updated_script,   2);
    Script_Clear(&level_script,     2);
    Script_Clear(&endlevel_script,  2);
	Script_Clear(&input_script_all, 2);
    Script_Clear(&key_script_all,   2);
    Script_Clear(&timetick_script,  2);
    Script_Clear(&loading_script,   2);
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Clear(&score_script[i],      2);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Clear(&key_script[i],        2);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Clear(&join_script[i],       2);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Clear(&respawn_script[i],    2);
    }
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        Script_Clear(&pdie_script[i],       2);
    }
    Script_Global_Clear();
}

#define scripts_membercount (sizeof(s_scripts) / sizeof(Script*))

void alloc_all_scripts(s_scripts **s)
{
    size_t i;

    if(!(*s))
    {
        *s = (s_scripts *)malloc(sizeof(s_scripts));
        for (i = 0; i < scripts_membercount; i++)
        {
            (((Script **) (*s))[i]) = alloc_script();
        }
    }
}

void clear_all_scripts(s_scripts *s, int method)
{
    size_t i;
    Script **ps = (Script **) s;

    for (i = 0; i < scripts_membercount; i++)
    {
        Script_Clear(ps[i],   method);
    }
}

void free_all_scripts(s_scripts **s)
{
    size_t i;
    Script **ps = (Script **) (*s);

    for (i = 0; i < scripts_membercount; i++)
    {
        if (ps[i])
        {
            free(ps[i]);
            ps[i] = NULL;
        }
    }
    free(*s);
    *s = NULL;
}

void copy_all_scripts(s_scripts *src, s_scripts *dest, int method)
{
    size_t i;
    Script **ps = (Script **) src;
    Script **pd = (Script **) dest;

    for (i = 0; i < scripts_membercount; i++)
    {
        Script_Copy(pd[i], ps[i], method);
    }
}

void execute_animation_script(entity *ent)
{
    ScriptVariant tempvar;
    int is1 = 0, is2 = 0;
    char *namelist[] = {"self", "animnum", "frame", "animhandle", ""};
    int handle = 0;
    Script *cs = ent->scripts->animation_script;
    Script *s1 = ent->model->scripts->animation_script;
    Script *s2 = ent->defaultmodel->scripts->animation_script;
    is1 = Script_IsInitialized(s1);
    is2 = Script_IsInitialized(s2);
    if(is1 || is2)
    {
        if(cs->pinterpreter && cs->pinterpreter->bReset)
        {
            handle = Script_Save_Local_Variant(cs, namelist);
        }
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self",    &tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
        tempvar.lVal = (LONG)ent->animnum;
        Script_Set_Local_Variant(cs, "animnum", &tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
        tempvar.lVal = (LONG)ent->animpos;
        Script_Set_Local_Variant(cs, "frame",   &tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
        tempvar.lVal = (LONG)ent->animation->index;
        Script_Set_Local_Variant(cs, "animhandle",   &tempvar);
        if(is1)
        {
            Script_Copy(cs, s1, 0);
            Script_Execute(cs);
        }
        if(ent->model != ent->defaultmodel && is2)
        {
            Script_Copy(cs, s2, 0);
            Script_Execute(cs);
        }
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",    &tempvar);
        Script_Set_Local_Variant(cs, "animnum", &tempvar);
        Script_Set_Local_Variant(cs, "frame",   &tempvar);
        Script_Set_Local_Variant(cs, "animhandle", &tempvar);
        if(handle)
        {
            Script_Load_Local_Variant(cs, handle);
        }
    }
}

void execute_takedamage_script(entity *ent, entity *other, s_attack *attack)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->takedamage_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);

        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self",        &tempvar);

        tempvar.ptrVal = (VOID *)other;
        Script_Set_Local_Variant(cs, "attacker",    &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);

        tempvar.lVal = (LONG)attack->attack_force;
        Script_Set_Local_Variant(cs, "damage",      &tempvar);

        tempvar.lVal = (LONG)attack->attack_drop;
        Script_Set_Local_Variant(cs, "drop",        &tempvar);

        tempvar.lVal = (LONG)attack->attack_type;
        Script_Set_Local_Variant(cs, "attacktype",  &tempvar);

        tempvar.lVal = (LONG)attack->no_block;
        Script_Set_Local_Variant(cs, "noblock",     &tempvar);

        tempvar.lVal = (LONG)attack->guardcost;
        Script_Set_Local_Variant(cs, "guardcost",   &tempvar);

        tempvar.lVal = (LONG)attack->jugglecost;
        Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);

        tempvar.lVal = (LONG)attack->pause_add;
        Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);

        tempvar.lVal = (LONG)attack->meta_tag;
        Script_Set_Local_Variant(cs, "tag",    &tempvar);


        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        Script_Set_Local_Variant(cs, "attacker",    &tempvar);
        Script_Set_Local_Variant(cs, "damage",      &tempvar);
        Script_Set_Local_Variant(cs, "drop",        &tempvar);
        Script_Set_Local_Variant(cs, "attacktype",  &tempvar);
        Script_Set_Local_Variant(cs, "noblock",     &tempvar);
        Script_Set_Local_Variant(cs, "guardcost",   &tempvar);
        Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);
        Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);
        Script_Set_Local_Variant(cs, "tag",    &tempvar);
    }
}

// Caskey, Damon V.
// 2018-08-30
//
// Run on the bind target when updating a bind.
void execute_on_bind_update_other_to_self(entity *ent, entity *other, s_bind *bind)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->on_bind_update_other_to_self_script;

    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);

        tempvar.ptrVal = (entity *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);

        tempvar.ptrVal = (entity *)other;
        Script_Set_Local_Variant(cs, "other", &tempvar);

        tempvar.ptrVal = (s_bind *)bind;
        Script_Set_Local_Variant(cs, "bind", &tempvar);

        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",	&tempvar);
        Script_Set_Local_Variant(cs, "other",	&tempvar);
        Script_Set_Local_Variant(cs, "bind",	&tempvar);
    }
}

// Caskey, Damon V.
// 2018-08-30
//
// Run on bound entity when updating bind.
void execute_on_bind_update_self_to_other(entity *ent, entity *other, s_bind *bind)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->on_bind_update_self_to_other_script;

    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);

        tempvar.ptrVal = (entity *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);

        tempvar.ptrVal = (entity *)other;
        Script_Set_Local_Variant(cs, "other", &tempvar);

        tempvar.ptrVal = (s_bind *)bind;
        Script_Set_Local_Variant(cs, "bind", &tempvar);

        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",	&tempvar);
        Script_Set_Local_Variant(cs, "other",	&tempvar);
        Script_Set_Local_Variant(cs, "bind",	&tempvar);
    }
}

void execute_onpain_script(entity *ent, int iType, int iReset)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onpain_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        tempvar.lVal = (LONG)iType;
        Script_Set_Local_Variant(cs, "attacktype",   &tempvar);
        tempvar.lVal = (LONG)iReset;
        Script_Set_Local_Variant(cs, "reset",       &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        Script_Set_Local_Variant(cs, "type",        &tempvar);
        Script_Set_Local_Variant(cs, "reset",       &tempvar);
    }
}

void execute_onfall_script(entity *ent, entity *other, s_attack *attack)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onfall_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self",        &tempvar);

        tempvar.ptrVal = (VOID *)other;
        Script_Set_Local_Variant(cs, "attacker",    &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);

        tempvar.lVal = (LONG)attack->attack_force;
        Script_Set_Local_Variant(cs, "damage",      &tempvar);

        tempvar.lVal = (LONG)attack->attack_drop;
        Script_Set_Local_Variant(cs, "drop",        &tempvar);

        tempvar.lVal = (LONG)attack->attack_type;
        Script_Set_Local_Variant(cs, "attacktype",  &tempvar);

        tempvar.lVal = (LONG)attack->no_block;
        Script_Set_Local_Variant(cs, "noblock",     &tempvar);

        tempvar.lVal = (LONG)attack->guardcost;
        Script_Set_Local_Variant(cs, "guardcost",   &tempvar);

        tempvar.lVal = (LONG)attack->jugglecost;
        Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);

        tempvar.lVal = (LONG)attack->pause_add;
        Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);

        tempvar.lVal = (LONG)attack->meta_tag;
        Script_Set_Local_Variant(cs, "tag",    &tempvar);

        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        Script_Set_Local_Variant(cs, "attacker",    &tempvar);
        Script_Set_Local_Variant(cs, "damage",      &tempvar);
        Script_Set_Local_Variant(cs, "drop",        &tempvar);
        Script_Set_Local_Variant(cs, "attacktype",  &tempvar);
        Script_Set_Local_Variant(cs, "noblock",     &tempvar);
        Script_Set_Local_Variant(cs, "guardcost",   &tempvar);
        Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);
        Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);
        Script_Set_Local_Variant(cs, "tag",         &tempvar);
    }
}

void execute_onblocks_script(entity *ent)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onblocks_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
    }
}

void execute_onblockw_script(entity *ent, s_terrain *wall, int index, e_plane plane)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onblockw_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_DECIMAL);
        tempvar.dblVal = (DOUBLE)wall->height;
        Script_Set_Local_Variant(cs, "height", &tempvar);

        tempvar.dblVal = (DOUBLE)wall->depth;
        Script_Set_Local_Variant(cs, "depth", &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);

        tempvar.lVal = (LONG)wall->type;
        Script_Set_Local_Variant(cs, "type", &tempvar);

        tempvar.lVal = (LONG)index;
        Script_Set_Local_Variant(cs, "index", &tempvar);

        tempvar.lVal = (LONG)plane;
        Script_Set_Local_Variant(cs, "plane", &tempvar);

        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Set_Local_Variant(cs, "plane", &tempvar);
        Script_Set_Local_Variant(cs, "height", &tempvar);
        Script_Set_Local_Variant(cs, "index", &tempvar);
        Script_Set_Local_Variant(cs, "depth", &tempvar);
        Script_Set_Local_Variant(cs, "type", &tempvar);
    }
}

void execute_inhole_script(entity *ent, s_terrain *hole, int index)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->inhole_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_DECIMAL);
        tempvar.dblVal = (DOUBLE)hole->height;
        Script_Set_Local_Variant(cs, "height", &tempvar);

        tempvar.dblVal = (DOUBLE)hole->depth;
        Script_Set_Local_Variant(cs, "depth", &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);

        tempvar.lVal = (LONG)hole->type;
        Script_Set_Local_Variant(cs, "type", &tempvar);

        tempvar.lVal = (LONG)index;
        Script_Set_Local_Variant(cs, "index", &tempvar);

        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Set_Local_Variant(cs, "height", &tempvar);
        Script_Set_Local_Variant(cs, "index", &tempvar);
        Script_Set_Local_Variant(cs, "depth", &tempvar);
        Script_Set_Local_Variant(cs, "type", &tempvar);
    }
}

void execute_onblockp_script(entity *ent, int plane, entity *platform)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onblockp_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
        tempvar.lVal = (LONG)plane;
        Script_Set_Local_Variant(cs, "plane",      &tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)platform;
        Script_Set_Local_Variant(cs, "platform",      &tempvar);
        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Set_Local_Variant(cs, "plane", &tempvar);
        Script_Set_Local_Variant(cs, "platform", &tempvar);
    }
}

void execute_onentitycollision_script(entity *ent, entity *target, s_collision_entity *ebox_ent, s_collision_entity *ebox_target)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onentitycollision_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)target;
        Script_Set_Local_Variant(cs, "target", &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ebox_ent;
        Script_Set_Local_Variant(cs, "self_ebox_handler", &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ebox_target;
        Script_Set_Local_Variant(cs, "target_ebox_handler", &tempvar);

        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Set_Local_Variant(cs, "target", &tempvar);
        Script_Set_Local_Variant(cs, "self_ebox_handler", &tempvar);
        Script_Set_Local_Variant(cs, "target_ebox_handler", &tempvar);
    }
}

void execute_onblocko_script(entity *ent, int plane, entity *other)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onblocko_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
        tempvar.lVal = (LONG)plane;
        Script_Set_Local_Variant(cs, "plane",      &tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)other;
        Script_Set_Local_Variant(cs, "obstacle",    &tempvar);
        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        Script_Set_Local_Variant(cs, "plane", &tempvar);
        Script_Set_Local_Variant(cs, "obstacle",    &tempvar);
    }
}

void execute_onblockz_script(entity *ent)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onblockz_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
    }
}

void execute_onblocka_script(entity *ent, entity *other)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onblocka_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        tempvar.ptrVal = (VOID *)other;
        Script_Set_Local_Variant(cs, "obstacle",    &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        Script_Set_Local_Variant(cs, "obstacle",    &tempvar);
    }
}

void execute_onmovex_script(entity *ent)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onmovex_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
    }
}

void execute_onmovez_script(entity *ent)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onmovez_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
    }
}

void execute_onmovea_script(entity *ent)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onmovea_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Execute(cs);

        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
    }
}

void execute_ondeath_script(entity *ent, entity *other, s_attack *attack)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->ondeath_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);

        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self",        &tempvar);

        tempvar.ptrVal = (VOID *)other;
        Script_Set_Local_Variant(cs, "attacker",    &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);

        tempvar.lVal = (LONG)attack->attack_force;
        Script_Set_Local_Variant(cs, "damage",      &tempvar);

        tempvar.lVal = (LONG)attack->attack_drop;
        Script_Set_Local_Variant(cs, "drop",        &tempvar);

        tempvar.lVal = (LONG)attack->attack_type;
        Script_Set_Local_Variant(cs, "attacktype",  &tempvar);

        tempvar.lVal = (LONG)attack->no_block;
        Script_Set_Local_Variant(cs, "noblock",     &tempvar);

        tempvar.lVal = (LONG)attack->guardcost;
        Script_Set_Local_Variant(cs, "guardcost",   &tempvar);

        tempvar.lVal = (LONG)attack->jugglecost;
        Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);

        tempvar.lVal = (LONG)attack->pause_add;
        Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);

        tempvar.lVal = (LONG)attack->meta_tag;
        Script_Set_Local_Variant(cs, "tag",    &tempvar);

        Script_Execute(cs);
        //clear to save variant space

        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        Script_Set_Local_Variant(cs, "attacker",    &tempvar);
        Script_Set_Local_Variant(cs, "damage",      &tempvar);
        Script_Set_Local_Variant(cs, "drop",        &tempvar);
        Script_Set_Local_Variant(cs, "attacktype",  &tempvar);
        Script_Set_Local_Variant(cs, "noblock",     &tempvar);
        Script_Set_Local_Variant(cs, "guardcost",   &tempvar);
        Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);
        Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);
        Script_Set_Local_Variant(cs, "tag",         &tempvar);
    }
}

void execute_onkill_script(entity *ent, e_kill_entity_trigger trigger)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onkill_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        
        ScriptVariant_ChangeType(&tempvar, VT_PTR);

        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);

        tempvar.lVal = (e_kill_entity_trigger)trigger;
        Script_Set_Local_Variant(cs, "trigger", &tempvar);

        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Set_Local_Variant(cs, "trigger", &tempvar);
    }
}

void execute_didblock_script(entity *ent, entity *other, s_attack *attack)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->didblock_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);

        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self",        &tempvar);

        tempvar.ptrVal = (VOID *)other;
        Script_Set_Local_Variant(cs, "attacker",    &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);

        tempvar.lVal = (LONG)attack->attack_force;
        Script_Set_Local_Variant(cs, "damage",      &tempvar);

        tempvar.lVal = (LONG)attack->attack_drop;
        Script_Set_Local_Variant(cs, "drop",        &tempvar);

        tempvar.lVal = (LONG)attack->attack_type;
        Script_Set_Local_Variant(cs, "attacktype",  &tempvar);

        tempvar.lVal = (LONG)attack->no_block;
        Script_Set_Local_Variant(cs, "noblock",     &tempvar);

        tempvar.lVal = (LONG)attack->guardcost;
        Script_Set_Local_Variant(cs, "guardcost",   &tempvar);

        tempvar.lVal = (LONG)attack->jugglecost;
        Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);

        tempvar.lVal = (LONG)attack->pause_add;
        Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);

        tempvar.lVal = (LONG)attack->meta_tag;
        Script_Set_Local_Variant(cs, "tag",    &tempvar);

        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        Script_Set_Local_Variant(cs, "attacker",    &tempvar);
        Script_Set_Local_Variant(cs, "damage",      &tempvar);
        Script_Set_Local_Variant(cs, "drop",        &tempvar);
        Script_Set_Local_Variant(cs, "attacktype",  &tempvar);
        Script_Set_Local_Variant(cs, "noblock",     &tempvar);
        Script_Set_Local_Variant(cs, "guardcost",   &tempvar);
        Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);
        Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);
        Script_Set_Local_Variant(cs, "tag",         &tempvar);
    }
}

void execute_ondoattack_script(entity *ent, entity *other, s_attack *attack, e_exchange which, int attack_id)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->ondoattack_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self",        &tempvar);

        tempvar.ptrVal = (VOID *)other;
        Script_Set_Local_Variant(cs, "other",    &tempvar);

        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);

        tempvar.lVal = (LONG)attack->attack_force;
        Script_Set_Local_Variant(cs, "damage",      &tempvar);

        tempvar.lVal = (LONG)attack->attack_drop;
        Script_Set_Local_Variant(cs, "drop",        &tempvar);

        tempvar.lVal = (LONG)attack->attack_type;
        Script_Set_Local_Variant(cs, "attacktype",  &tempvar);

        tempvar.lVal = (LONG)attack->no_block;
        Script_Set_Local_Variant(cs, "noblock",     &tempvar);

        tempvar.lVal = (LONG)attack->guardcost;
        Script_Set_Local_Variant(cs, "guardcost",   &tempvar);

        tempvar.lVal = (LONG)attack->jugglecost;
        Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);

        tempvar.lVal = (LONG)attack->pause_add;
        Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);

        tempvar.lVal = (LONG)attack->meta_tag;
        Script_Set_Local_Variant(cs, "tag",    &tempvar);

        tempvar.lVal = (LONG)which;
        Script_Set_Local_Variant(cs, "which",    &tempvar);

        tempvar.lVal = (LONG)attack_id;
        Script_Set_Local_Variant(cs, "attack_id",    &tempvar);

        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",        &tempvar);
        Script_Set_Local_Variant(cs, "other",		&tempvar);
        Script_Set_Local_Variant(cs, "damage",      &tempvar);
        Script_Set_Local_Variant(cs, "drop",        &tempvar);
        Script_Set_Local_Variant(cs, "attacktype",  &tempvar);
        Script_Set_Local_Variant(cs, "noblock",     &tempvar);
        Script_Set_Local_Variant(cs, "guardcost",   &tempvar);
        Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);
        Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);
        Script_Set_Local_Variant(cs, "which",		&tempvar);
        Script_Set_Local_Variant(cs, "attackid",	&tempvar);
        Script_Set_Local_Variant(cs, "tag",	        &tempvar);
    }
}

void execute_updateentity_script(entity *ent)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->update_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
    }
}

void execute_think_script(entity *ent)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->think_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
    }
}

static void _execute_didhit_script(Script *cs, entity *ent, entity *other, s_attack *attack, int blocked)
{
    ScriptVariant tempvar;
    ScriptVariant_Init(&tempvar);

    ScriptVariant_ChangeType(&tempvar, VT_PTR);

    tempvar.ptrVal = (VOID *)ent;
    Script_Set_Local_Variant(cs, "self",        &tempvar);

    tempvar.ptrVal = (VOID *)other;
    Script_Set_Local_Variant(cs, "damagetaker", &tempvar);

    ScriptVariant_ChangeType(&tempvar, VT_INTEGER);

    tempvar.lVal = (LONG)attack->attack_force;
    Script_Set_Local_Variant(cs, "damage",      &tempvar);

    tempvar.lVal = (LONG)attack->attack_drop;
    Script_Set_Local_Variant(cs, "drop",        &tempvar);

    tempvar.lVal = (LONG)attack->attack_type;
    Script_Set_Local_Variant(cs, "attacktype",  &tempvar);

    tempvar.lVal = (LONG)attack->no_block;
    Script_Set_Local_Variant(cs, "noblock",     &tempvar);

    tempvar.lVal = (LONG)attack->guardcost;
    Script_Set_Local_Variant(cs, "guardcost",   &tempvar);

    tempvar.lVal = (LONG)attack->jugglecost;
    Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);

    tempvar.lVal = (LONG)attack->pause_add;
    Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);

    tempvar.lVal = (LONG)attack->meta_tag;
    Script_Set_Local_Variant(cs, "tag",    &tempvar);

    tempvar.lVal = (LONG)blocked;
    Script_Set_Local_Variant(cs, "blocked",    &tempvar);


    Script_Execute(cs);
    //clear to save variant space
    ScriptVariant_Clear(&tempvar);
    Script_Set_Local_Variant(cs, "self",        &tempvar);
    Script_Set_Local_Variant(cs, "damagetaker", &tempvar);
    Script_Set_Local_Variant(cs, "damage",      &tempvar);
    Script_Set_Local_Variant(cs, "drop",        &tempvar);
    Script_Set_Local_Variant(cs, "attacktype",  &tempvar);
    Script_Set_Local_Variant(cs, "noblock",     &tempvar);
    Script_Set_Local_Variant(cs, "guardcost",   &tempvar);
    Script_Set_Local_Variant(cs, "jugglecost",  &tempvar);
    Script_Set_Local_Variant(cs, "pauseadd",    &tempvar);
    Script_Set_Local_Variant(cs, "blocked",     &tempvar);
    Script_Set_Local_Variant(cs, "tag",         &tempvar);
}

void execute_didhit_script(entity *ent, entity *other, s_attack *attack, int blocked)
{
    Script *cs;
    s_scripts *gs = global_model_scripts;
    if(gs && (cs = gs->didhit_script) && Script_IsInitialized(cs))
    {
        _execute_didhit_script(cs, ent, other, attack, blocked);
    }
    if(Script_IsInitialized(cs = ent->scripts->didhit_script))
    {
        _execute_didhit_script(cs, ent, other, attack, blocked);
    }
}

static void _execute_onspawn_script(Script *cs, entity *ent)
{
    ScriptVariant tempvar;
    ScriptVariant_Init(&tempvar);
    ScriptVariant_ChangeType(&tempvar, VT_PTR);
    tempvar.ptrVal = (VOID *)ent;
    Script_Set_Local_Variant(cs, "self", &tempvar);
    Script_Execute(cs);
    //clear to save variant space
    ScriptVariant_Clear(&tempvar);
    Script_Set_Local_Variant(cs, "self", &tempvar);
}

void execute_onspawn_script(entity *ent)
{
    Script *cs;
    s_scripts *gs = global_model_scripts;
    if(gs && (cs = gs->onspawn_script) && Script_IsInitialized(cs))
    {
        _execute_onspawn_script(cs, ent);
    }
    if(Script_IsInitialized(cs = ent->scripts->onspawn_script))
    {
        _execute_onspawn_script(cs, ent);
    }
}

void execute_onmodelcopy_script(entity *ent, entity *old)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->onmodelcopy_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);
        tempvar.ptrVal = (VOID *)old;
        Script_Set_Local_Variant(cs, "old", &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Set_Local_Variant(cs, "old", &tempvar);
    }
}

void execute_ondraw_script(entity *ent)
{
    ScriptVariant tempvar;
    Script *cs = ent->scripts->ondraw_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self", &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self", &tempvar);
    }
}

void execute_entity_key_script(entity *ent)
{
    ScriptVariant tempvar;
    Script *cs ;
    if(!ent)
    {
        return;
    }
    cs = ent->scripts->key_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_PTR);
        tempvar.ptrVal = (VOID *)ent;
        Script_Set_Local_Variant(cs, "self",    &tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
        tempvar.lVal = (LONG)ent->playerindex;
        Script_Set_Local_Variant(cs, "player",  &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "self",    &tempvar);
        Script_Set_Local_Variant(cs, "player",  &tempvar);
    }
}

void execute_spawn_script(s_spawn_entry *p, entity *e)
{
    ScriptVariant tempvar;
    Script *cs;
    cs = &p->spawnscript;
    if(Script_IsInitialized(cs))
    {
        if(e)
        {
            ScriptVariant_Init(&tempvar);
            ScriptVariant_ChangeType(&tempvar, VT_PTR);
            tempvar.ptrVal = (VOID *)e;
            Script_Set_Local_Variant(cs, "self", &tempvar);
            ScriptVariant_ChangeType(&tempvar, VT_DECIMAL);
            tempvar.dblVal = (DOUBLE)p->position.x;
            Script_Set_Local_Variant(cs, "spawnx", &tempvar);
            tempvar.dblVal = (DOUBLE)p->position.z;
            Script_Set_Local_Variant(cs, "spawnz", &tempvar);
            tempvar.dblVal = (DOUBLE)p->position.y;
            Script_Set_Local_Variant(cs, "spawna", &tempvar);
            ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
            tempvar.lVal = (LONG)p->at;
            Script_Set_Local_Variant(cs, "spawnat", &tempvar);
        }
        Script_Execute(cs);
        if(e)
        {
            ScriptVariant_Clear(&tempvar);
            Script_Set_Local_Variant(cs, "self", &tempvar);
            Script_Set_Local_Variant(cs, "spawnx", &tempvar);
            Script_Set_Local_Variant(cs, "spawnz", &tempvar);
            Script_Set_Local_Variant(cs, "spawna", &tempvar);
            Script_Set_Local_Variant(cs, "spawnat", &tempvar);
        }
    }
}

void execute_level_key_script(int player)
{
    ScriptVariant tempvar;
    Script *cs = &(level->key_script);
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
        tempvar.lVal = (LONG)player;
        Script_Set_Local_Variant(cs, "player", &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "player", &tempvar);
    }
}

void execute_input_script_all(int player)
{
	ScriptVariant tempvar;
	Script *cs = &input_script_all;
	if (Script_IsInitialized(cs))
	{
		ScriptVariant_Init(&tempvar);

		//ScriptVariant_ChangeType(&tempvar, VT_PTR);

		//tempvar.ptrVal = (VOID *)player_object;
		//Script_Set_Local_Variant(cs, "player_object", &tempvar);

		ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
		tempvar.lVal = (LONG)player;
		
		Script_Set_Local_Variant(cs, "player", &tempvar);
		
		Script_Execute(cs);
		
		//clear to save variant space
		ScriptVariant_Clear(&tempvar);
		Script_Set_Local_Variant(cs, "player", &tempvar);
		//Script_Set_Local_Variant(cs, "player_object", &tempvar);
	}
}


void execute_key_script_all(int player)
{
    ScriptVariant tempvar;
    Script *cs = &key_script_all;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
        tempvar.lVal = (LONG)player;
        Script_Set_Local_Variant(cs, "player", &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "player", &tempvar);
    }
}

void execute_timetick_script(int time, int gotime)
{
    ScriptVariant tempvar;
    Script *cs = &timetick_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
        tempvar.lVal = (LONG)time;
        Script_Set_Local_Variant(cs, "time",    &tempvar);
        tempvar.lVal = (LONG)gotime;
        Script_Set_Local_Variant(cs, "gotime", &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "time",    &tempvar);
        Script_Set_Local_Variant(cs, "gotime",  &tempvar);
    }
}

void execute_loading_script(int value, int max)
{
    ScriptVariant tempvar;
    Script *cs = &loading_script;
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&tempvar);
        ScriptVariant_ChangeType(&tempvar, VT_INTEGER);
        tempvar.lVal = (LONG)value;
        Script_Set_Local_Variant(cs, "value",    &tempvar);
        tempvar.lVal = (LONG)max;
        Script_Set_Local_Variant(cs, "max", &tempvar);
        Script_Execute(cs);
        //clear to save variant space
        ScriptVariant_Clear(&tempvar);
        Script_Set_Local_Variant(cs, "value",    &tempvar);
        Script_Set_Local_Variant(cs, "max",  &tempvar);
    }
}

void execute_key_script(int index)
{
    if(Script_IsInitialized(&key_script[index]))
    {
        Script_Execute(&key_script[index]);
    }
}

void execute_join_script(int index)
{
    if(Script_IsInitialized(&join_script[index]))
    {
        Script_Execute(&join_script[index]);
    }
}

void execute_respawn_script(int index)
{
    if(Script_IsInitialized(&respawn_script[index]))
    {
        Script_Execute(&respawn_script[index]);
    }
}

void execute_pdie_script(int index)
{
    if(Script_IsInitialized(&pdie_script[index]))
    {
        Script_Execute(&pdie_script[index]);
    }
}

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

void clearbuttons(int player)
{
    savedata.joyrumble[player] = 0;

    if (player == 0)
    {
        savedata.keys[0][SDID_MOVEUP]    = CONTROL_DEFAULT1_UP; //Kratus (22-04-21) Maintain the key config only for player 1 because other modules like PSP will not work with CONTROL_NONE
        savedata.keys[0][SDID_MOVEDOWN]  = CONTROL_DEFAULT1_DOWN;
        savedata.keys[0][SDID_MOVELEFT]  = CONTROL_DEFAULT1_LEFT;
        savedata.keys[0][SDID_MOVERIGHT] = CONTROL_DEFAULT1_RIGHT;
        savedata.keys[0][SDID_ATTACK]    = CONTROL_DEFAULT1_FIRE1;
        savedata.keys[0][SDID_ATTACK2]   = CONTROL_DEFAULT1_FIRE2;
        savedata.keys[0][SDID_ATTACK3]   = CONTROL_DEFAULT1_FIRE3;
        savedata.keys[0][SDID_ATTACK4]   = CONTROL_DEFAULT1_FIRE4;
        savedata.keys[0][SDID_JUMP]      = CONTROL_DEFAULT1_FIRE5;
        savedata.keys[0][SDID_SPECIAL]   = CONTROL_DEFAULT1_FIRE6;
        savedata.keys[0][SDID_START]     = CONTROL_DEFAULT1_START;
        savedata.keys[0][SDID_SCREENSHOT] = CONTROL_DEFAULT1_SCREENSHOT;
        #ifdef SDL
            //savedata.keys[0][SDID_ESC]       = CONTROL_DEFAULT1_ESC;
        #endif

        /* *************** SET DEFAULT KEYS *************** */
        // White Dragon: These are default keys: for Android is the touchpad and for Win/Linux etc. is the keyboard
        default_keys[SDID_MOVEUP]    = CONTROL_DEFAULT1_UP;
        default_keys[SDID_MOVEDOWN]  = CONTROL_DEFAULT1_DOWN;
        default_keys[SDID_MOVELEFT]  = CONTROL_DEFAULT1_LEFT;
        default_keys[SDID_MOVERIGHT] = CONTROL_DEFAULT1_RIGHT;
        default_keys[SDID_ATTACK]    = CONTROL_DEFAULT1_FIRE1;
        default_keys[SDID_ATTACK2]   = CONTROL_DEFAULT1_FIRE2;
        default_keys[SDID_ATTACK3]   = CONTROL_DEFAULT1_FIRE3;
        default_keys[SDID_ATTACK4]   = CONTROL_DEFAULT1_FIRE4;
        default_keys[SDID_JUMP]      = CONTROL_DEFAULT1_FIRE5;
        default_keys[SDID_SPECIAL]   = CONTROL_DEFAULT1_FIRE6;
        default_keys[SDID_START]     = CONTROL_DEFAULT1_START;
        default_keys[SDID_SCREENSHOT] = CONTROL_DEFAULT1_SCREENSHOT;

        control_setkey(&default_control, FLAG_ESC,        CONTROL_ESC);
        control_setkey(&default_control, FLAG_MOVEUP,     default_keys[SDID_MOVEUP]);
        control_setkey(&default_control, FLAG_MOVEDOWN,   default_keys[SDID_MOVEDOWN]);
        control_setkey(&default_control, FLAG_MOVELEFT,   default_keys[SDID_MOVELEFT]);
        control_setkey(&default_control, FLAG_MOVERIGHT,  default_keys[SDID_MOVERIGHT]);
        control_setkey(&default_control, FLAG_ATTACK,     default_keys[SDID_ATTACK]);
        control_setkey(&default_control, FLAG_ATTACK2,    default_keys[SDID_ATTACK2]);
        control_setkey(&default_control, FLAG_ATTACK3,    default_keys[SDID_ATTACK3]);
        control_setkey(&default_control, FLAG_ATTACK4,    default_keys[SDID_ATTACK4]);
        control_setkey(&default_control, FLAG_JUMP,       default_keys[SDID_JUMP]);
        control_setkey(&default_control, FLAG_SPECIAL,    default_keys[SDID_SPECIAL]);
        control_setkey(&default_control, FLAG_START,      default_keys[SDID_START]);
        control_setkey(&default_control, FLAG_SCREENSHOT, default_keys[SDID_SCREENSHOT]);
    }
    else if (player == 1)
    {
        savedata.keys[1][SDID_MOVEUP]    = CONTROL_NONE; //Kratus (20-04-21) Used to clear all keys
        savedata.keys[1][SDID_MOVEDOWN]  = CONTROL_NONE;
        savedata.keys[1][SDID_MOVELEFT]  = CONTROL_NONE;
        savedata.keys[1][SDID_MOVERIGHT] = CONTROL_NONE;
        savedata.keys[1][SDID_ATTACK]    = CONTROL_NONE;
        savedata.keys[1][SDID_ATTACK2]   = CONTROL_NONE;
        savedata.keys[1][SDID_ATTACK3]   = CONTROL_NONE;
        savedata.keys[1][SDID_ATTACK4]   = CONTROL_NONE;
        savedata.keys[1][SDID_JUMP]      = CONTROL_NONE;
        savedata.keys[1][SDID_SPECIAL]   = CONTROL_NONE;
        savedata.keys[1][SDID_START]     = CONTROL_NONE;
        savedata.keys[1][SDID_SCREENSHOT] = CONTROL_NONE;
        #ifdef SDL
            //savedata.keys[1][SDID_ESC]       = CONTROL_DEFAULT2_ESC;
        #endif
	    
	
	//pvmk
	savedata.keys[1][SDID_MOVEUP]    = CONTROL_DEFAULT2_UP; //Kratus (22-04-21) Maintain the key config only for player 1 because other modules like PSP will not work with CONTROL_NONE
        savedata.keys[1][SDID_MOVEDOWN]  = CONTROL_DEFAULT2_DOWN;
        savedata.keys[1][SDID_MOVELEFT]  = CONTROL_DEFAULT2_LEFT;
        savedata.keys[1][SDID_MOVERIGHT] = CONTROL_DEFAULT2_RIGHT;
        savedata.keys[1][SDID_ATTACK]    = CONTROL_DEFAULT2_FIRE1;
        savedata.keys[1][SDID_ATTACK2]   = CONTROL_DEFAULT2_FIRE2;
        savedata.keys[1][SDID_ATTACK3]   = CONTROL_DEFAULT2_FIRE3;
        savedata.keys[1][SDID_ATTACK4]   = CONTROL_DEFAULT2_FIRE4;
        savedata.keys[1][SDID_JUMP]      = CONTROL_DEFAULT2_FIRE5;
        savedata.keys[1][SDID_SPECIAL]   = CONTROL_DEFAULT2_FIRE6;
        savedata.keys[1][SDID_START]     = CONTROL_DEFAULT2_START;
        savedata.keys[1][SDID_SCREENSHOT] = CONTROL_DEFAULT2_SCREENSHOT;
	savedata.keys[1][SDID_ESC]       = CONTROL_DEFAULT2_ESC;
	
    }
    else if (player == 2)
    {
        savedata.keys[2][SDID_MOVEUP]    = CONTROL_NONE; //Kratus (20-04-21) Used to clear all keys
        savedata.keys[2][SDID_MOVEDOWN]  = CONTROL_NONE;
        savedata.keys[2][SDID_MOVELEFT]  = CONTROL_NONE;
        savedata.keys[2][SDID_MOVERIGHT] = CONTROL_NONE;
        savedata.keys[2][SDID_ATTACK]    = CONTROL_NONE;
        savedata.keys[2][SDID_ATTACK2]   = CONTROL_NONE;
        savedata.keys[2][SDID_ATTACK3]   = CONTROL_NONE;
        savedata.keys[2][SDID_ATTACK4]   = CONTROL_NONE;
        savedata.keys[2][SDID_JUMP]      = CONTROL_NONE;
        savedata.keys[2][SDID_SPECIAL]   = CONTROL_NONE;
        savedata.keys[2][SDID_START]     = CONTROL_NONE;
        savedata.keys[2][SDID_SCREENSHOT] = CONTROL_NONE;
        #ifdef SDL
            //savedata.keys[2][SDID_ESC]       = CONTROL_DEFAULT3_ESC;
        #endif
	    
	    //pvmk
	savedata.keys[2][SDID_MOVEUP]    = CONTROL_DEFAULT3_UP; //Kratus (22-04-21) Maintain the key config only for player 1 because other modules like PSP will not work with CONTROL_NONE
        savedata.keys[2][SDID_MOVEDOWN]  = CONTROL_DEFAULT3_DOWN;
        savedata.keys[2][SDID_MOVELEFT]  = CONTROL_DEFAULT3_LEFT;
        savedata.keys[2][SDID_MOVERIGHT] = CONTROL_DEFAULT3_RIGHT;
        savedata.keys[2][SDID_ATTACK]    = CONTROL_DEFAULT3_FIRE1;
        savedata.keys[2][SDID_ATTACK2]   = CONTROL_DEFAULT3_FIRE2;
        savedata.keys[2][SDID_ATTACK3]   = CONTROL_DEFAULT3_FIRE3;
        savedata.keys[2][SDID_ATTACK4]   = CONTROL_DEFAULT3_FIRE4;
        savedata.keys[2][SDID_JUMP]      = CONTROL_DEFAULT3_FIRE5;
        savedata.keys[2][SDID_SPECIAL]   = CONTROL_DEFAULT3_FIRE6;
        savedata.keys[2][SDID_START]     = CONTROL_DEFAULT3_START;
        savedata.keys[2][SDID_SCREENSHOT] = CONTROL_DEFAULT3_SCREENSHOT;
	savedata.keys[2][SDID_ESC]       = CONTROL_DEFAULT3_ESC;
    }
    else if (player == 3)
    {
        savedata.keys[3][SDID_MOVEUP]    = CONTROL_NONE; //Kratus (20-04-21) Used to clear all keys
        savedata.keys[3][SDID_MOVEDOWN]  = CONTROL_NONE;
        savedata.keys[3][SDID_MOVELEFT]  = CONTROL_NONE;
        savedata.keys[3][SDID_MOVERIGHT] = CONTROL_NONE;
        savedata.keys[3][SDID_ATTACK]    = CONTROL_NONE;
        savedata.keys[3][SDID_ATTACK2]   = CONTROL_NONE;
        savedata.keys[3][SDID_ATTACK3]   = CONTROL_NONE;
        savedata.keys[3][SDID_ATTACK4]   = CONTROL_NONE;
        savedata.keys[3][SDID_JUMP]      = CONTROL_NONE;
        savedata.keys[3][SDID_SPECIAL]   = CONTROL_NONE;
        savedata.keys[3][SDID_START]     = CONTROL_NONE;
        savedata.keys[3][SDID_SCREENSHOT] = CONTROL_NONE;
        #ifdef SDL
            //savedata.keys[3][SDID_ESC]       = CONTROL_DEFAULT4_ESC;
        #endif
	    
	    //pvmk
	savedata.keys[3][SDID_MOVEUP]    = CONTROL_DEFAULT4_UP; //Kratus (22-04-21) Maintain the key config only for player 1 because other modules like PSP will not work with CONTROL_NONE
        savedata.keys[3][SDID_MOVEDOWN]  = CONTROL_DEFAULT4_DOWN;
        savedata.keys[3][SDID_MOVELEFT]  = CONTROL_DEFAULT4_LEFT;
        savedata.keys[3][SDID_MOVERIGHT] = CONTROL_DEFAULT4_RIGHT;
        savedata.keys[3][SDID_ATTACK]    = CONTROL_DEFAULT4_FIRE1;
        savedata.keys[3][SDID_ATTACK2]   = CONTROL_DEFAULT4_FIRE2;
        savedata.keys[3][SDID_ATTACK3]   = CONTROL_DEFAULT4_FIRE3;
        savedata.keys[3][SDID_ATTACK4]   = CONTROL_DEFAULT4_FIRE4;
        savedata.keys[3][SDID_JUMP]      = CONTROL_DEFAULT4_FIRE5;
        savedata.keys[3][SDID_SPECIAL]   = CONTROL_DEFAULT4_FIRE6;
        savedata.keys[3][SDID_START]     = CONTROL_DEFAULT4_START;
        savedata.keys[3][SDID_SCREENSHOT] = CONTROL_DEFAULT4_SCREENSHOT;
	savedata.keys[3][SDID_ESC]       = CONTROL_DEFAULT4_ESC;
    }
}

void clearsettings()
{
    int i = 0;

    savedata.compatibleversion = COMPATIBLEVERSION;
    savedata.gamma = 0;
    savedata.brightness = 0;
    global_config.cheats = CHEAT_OPTIONS_ALL_MENU;
    savedata.soundvol = 100; //Kratus (02-2023) Changed the default master volume
    savedata.usemusic = 1;
    savedata.musicvol = 100; //Kratus (02-2023) Changed the default music volume
    savedata.effectvol = 100; //Kratus (02-2023) Changed the default effect volume
    savedata.usejoy = 1;
    savedata.mode = 0;
    savedata.showtitles = 0;
    savedata.windowpos = 0;
    savedata.logo = 0;
    savedata.uselog = 1;
    savedata.debuginfo = 0;
    savedata.fullscreen = 0;
    savedata.vsync = 1;
    savedata.fpslimit = 1; // Kratus (01-2023) Added a FPS limit option in the video settings

	#if WII
    savedata.stretch = 1;
	#else
    savedata.stretch = 0;
	#endif

    savedata.swfilter = 0;

    #ifdef SDL
    savedata.usegl = 1;
    savedata.hwfilter = 1;
        #ifdef ANDROID
        savedata.hwscale = 0.0;
        #else
        savedata.hwscale = 1.0;
        #endif
    #endif

    #ifdef ANDROID
    savedata.is_touchpad_vibration_enabled = 0;
    #endif

    for (i = 0; i < MAX_PLAYERS; i++)
    {
        clearbuttons(i);
    }
}


void savesettings()
{
#ifndef DC
    FILE *handle = NULL;
    char path[MAX_BUFFER_LEN] = {""};
    char tmpname[MAX_BUFFER_LEN] = {""};
    getBasePath(path, "Saves", 0);
    getPakName(tmpname, 4);
    strcat(path, tmpname);
    handle = fopen(path, "wb");
    if(handle == NULL)
    {
        return;
    }
    fwrite(&savedata, 1, sizeof(savedata), handle);
    fclose(handle);
#endif
}

void saveasdefault()
{
#ifndef DC
    FILE *handle = NULL;
    char path[MAX_BUFFER_LEN] = {""};
    getBasePath(path, "Saves", 0);
    strcat(path, "default.cfg");
    handle = fopen(path, "wb");
    if(handle == NULL)
    {
        return;
    }
    fwrite(&savedata, 1, sizeof(savedata), handle);
    fclose(handle);
#endif
}


void loadsettings()
{
#ifndef DC
    FILE *handle = NULL;
    char path[MAX_BUFFER_LEN] = {""};
    char tmpname[MAX_BUFFER_LEN] = {""};
    getBasePath(path, "Saves", 0);
    getPakName(tmpname, 4);
    strcat(path, tmpname);
    if(!(fileExists(path)))
    {
        loadfromdefault();
        return;
    }
    clearsettings();
    handle = fopen(path, "rb");
    if(handle == NULL)
    {
        return;
    }
    fread(&savedata, 1, sizeof(savedata), handle);
    fclose(handle);
    if(savedata.compatibleversion != COMPATIBLEVERSION)
    {
        clearsettings();
    }
#else
    clearsettings();
#endif
}

void loadfromdefault()
{
#ifndef DC
    FILE *handle = NULL;
    char path[MAX_BUFFER_LEN] = {""};
    getBasePath(path, "Saves", 0);
    strcat(path, "default.cfg");
    clearsettings();
    handle = fopen(path, "rb");
    if(handle == NULL)
    {
        return;
    }
    fread(&savedata, 1, sizeof(savedata), handle);
    fclose(handle);
    if(savedata.compatibleversion != COMPATIBLEVERSION)
    {
        clearsettings();
    }
#else
    clearsettings();
#endif
}




void clearSavedGame()
{
    memset(savelevel, 0, sizeof(*savelevel)*num_difficulties);
}



void clearHighScore()
{
    int i;
    savescore.compatibleversion = CV_HIGH_SCORE;
    for(i = 0; i < 10; i++)
    {
        savescore.highsc[i] = 0;    // Resets all the highscores to 0
        strcpy(savescore.hscoren[i], "None");    // Resets all the highscore names to "None"
    }
}



int saveGameFile()
{
#ifndef DC
    FILE *handle = NULL;
    char path[MAX_BUFFER_LEN] = {""};
    char tmpname[MAX_BUFFER_LEN] = {""};

    getBasePath(path, "Saves", 0);
    getPakName(tmpname, 0);
    strcat(path, tmpname);
    //if(!savelevel[saveslot].level) return;
    handle = fopen(path, "wb");

    if(handle == NULL)
    {
        return 0;
    }

    fwrite(savelevel, sizeof(*savelevel), num_difficulties, handle);

    fclose(handle);

    return 1;
#else
    return 1;
#endif
}


int loadGameFile()
{
#ifndef DC
    int result = 1, i;
    FILE *handle = NULL;
    char path[MAX_BUFFER_LEN] = {""};
    char tmpname[MAX_BUFFER_LEN] = {""};
    //size_t filesize = 0;

    getBasePath(path, "Saves", 0);
    getPakName(tmpname, 0);
    strcat(path, tmpname);
    handle = fopen(path, "rb");

    if(handle == NULL)
    {
        return 0;
    }

    //fseek(handle, 0L, SEEK_END);
    //filesize = ftell(handle);
    //fseek(handle, 0L, SEEK_SET); // or rewind(handle);
    //(filesize != sizeof(*savelevel)*num_difficulties)

    if( (fread(savelevel, sizeof(*savelevel), num_difficulties, handle) >= sizeof(*savelevel) && savelevel[0].compatibleversion != CV_SAVED_GAME) )
    {
        clearSavedGame();
        result = 0;
    }
    else
    {
        bonus = 0;
        for(i = 0; i < num_difficulties; i++) if(savelevel[i].times_completed > 0)
            {
                bonus += savelevel[i].times_completed;
            }
    }

    fclose(handle);

    return result;
#else
    clearSavedGame();
    return 0;
#endif
}


int saveHighScoreFile()
{
#ifndef DC
    FILE *handle = NULL;
    char path[MAX_BUFFER_LEN] = {""};
    char tmpname[MAX_BUFFER_LEN] = {""};
    getBasePath(path, "Saves", 0);
    getPakName(tmpname, 1);
    strcat(path, tmpname);
    handle = fopen(path, "wb");
    if(handle == NULL)
    {
        return 0;
    }
    fwrite(&savescore, 1, sizeof(savescore), handle);
    fclose(handle);
    return 1;
#else
    return 1;
#endif
}


int loadHighScoreFile()
{
#ifndef DC
    FILE *handle = NULL;
    char path[MAX_BUFFER_LEN] = {""};
    char tmpname[MAX_BUFFER_LEN] = {""};
    getBasePath(path, "Saves", 0);
    getPakName(tmpname, 1);
    strcat(path, tmpname);
    clearHighScore();
    handle = fopen(path, "rb");
    if(handle == NULL)
    {
        return 0;
    }
    fread(&savescore, 1, sizeof(savescore), handle);
    fclose(handle);
    if(savescore.compatibleversion != CV_HIGH_SCORE)
    {
        clearHighScore();
        return 0;
    }
    return 1;
#else
    clearHighScore();
    return 0;
#endif
}


#ifndef DC
static void vardump(ScriptVariant *var, char buffer[])
{
    char *tmpstr;
    int l, t, c;
    buffer[0] = 0;
    switch(var->vt)
    {

    case VT_STR:
        strcpy(buffer, "\"");
        tmpstr = StrCache_Get(var->strVal);
        l = strlen(tmpstr);
        for(c = 0; c < l; c++)
        {
            if(tmpstr[c] == '\n')
            {
                strcat(buffer, "\\n");
            }
            else if(tmpstr[c] == '\r')
            {
                strcat(buffer, "\\r");
            }
            else if(tmpstr[c] == '\\')
            {
                strcat(buffer, "\\\\");
            }
            else
            {
                t = strlen(buffer);
                buffer[t] = tmpstr[c];
                buffer[t + 1] = 0;
            }
        }
        strcat(buffer, "\"");
        break;
    case VT_DECIMAL:
        sprintf(buffer, "%lf", (double)var->dblVal);
        break;
    case VT_INTEGER:
        sprintf(buffer, "%ld", (long)var->lVal);
        break;
    default:
        strcpy(buffer, "NULL()");
        break;
    }
}

#endif


int saveScriptFile()
{
#ifndef DC
#define _writestr(v) fwrite(v, strlen(v), 1, handle);
#define _writetmp  _writestr(tmpvalue)
#define _writeconst(s) strcpy(tmpvalue,s);_writetmp
    FILE *handle = NULL;
    int i, l, size;
    ScriptVariant *var;
    char path[MAX_BUFFER_LEN] = {""};
    char tmpvalue[MAX_BUFFER_LEN] = {""};
    getBasePath(path, "Saves", 0);
    getPakName(tmpvalue, 2);//.scr
    strcat(path, tmpvalue);
    l = strlen(path); //s00, s01, s02 etc
    path[l - 2] = '0' + (current_set / 10);
    path[l - 1] = '0' + (current_set % 10);
    handle = fopen(path, "wb");
    if(handle == NULL)
    {
        return 0;
    }

    _writeconst("void main() {\n");
    size = List_GetSize(global_var_list.list);
    for(i = 0, List_Reset(global_var_list.list); i < size; List_GotoNext(global_var_list.list), i++)
    {
        var = (ScriptVariant *)List_Retrieve(global_var_list.list);
        if( var->vt != VT_EMPTY && var->vt != VT_PTR)
        {
            _writeconst("\tsetglobalvar(\"")
            _writestr(List_GetName(global_var_list.list))
            _writeconst("\",")
            vardump(var, tmpvalue);
            _writetmp
            _writeconst(");\n")
        }
    }
    // indexed list
    for(i = 1; i <= global_var_list.vars->lVal; i++)
    {
        if(global_var_list.vars[i].vt != VT_PTR && global_var_list.vars[i].vt != VT_EMPTY)
        {
            _writeconst("\tsetglobalvar(")
            sprintf(tmpvalue, "%d", i - 1);
            _writetmp
            _writeconst(",")
            vardump(global_var_list.vars + i, tmpvalue);
            _writetmp
            _writeconst(");\n")
        }
    }
    //allow select
    for(i = 0; i < models_cached; i++)
    {
        if(model_cache[i].selectable)
        {
            _writeconst("\tchangemodelproperty(\"")
            _writestr(model_cache[i].name)
            _writeconst("\",4,1);\n")
        }
        /*
        if(model_cache[i].model) {
        	_writeconst("\tloadmodel(\"")
        	_writestr(model_cache[i].name)
        	sprintf(tmpvalue, "\",%d,%d);\n", model_cache[i].model->unload, model_cache[i].selectable);
        	_writetmp
        }*/
    }
    _writeconst("}\n");

    fclose(handle);
    return 1;
#undef _writestr
#undef _writetmp
#undef _writeconst
#else
    return 1;
#endif
}


int loadScriptFile()
{
#ifndef DC
    Script script;
    int result = 0;
    char *buf = NULL;
    ptrdiff_t l;
    size_t len;
    FILE *handle = NULL;

    char path[MAX_BUFFER_LEN] = {""};
    char tmpname[MAX_BUFFER_LEN] = {""};
    getBasePath(path, "Saves", 0);
    getPakName(tmpname, 2);//.scr
    strcat(path, tmpname);
    l = strlen(path); //s00, s01, s02 etc
    path[l - 2] = '0' + (current_set / 10);
    path[l - 1] = '0' + (current_set % 10);

    handle = fopen(path, "rb");
    if(handle == NULL)
    {
        return 0;
    }

    fseek(handle, 0, SEEK_END);
    len = ftell(handle);
    fseek(handle, 0, SEEK_SET);
    buf = malloc(len + 1);

    if(!buf)
    {
        return 0;
    }

    fread(buf, 1, len, handle);
    buf[len - 1] = 0;

    Script_Init(&script, "loadScriptFile",  NULL, 1);

    result = (Script_AppendText(&script, buf, path) &&
              Script_Compile(&script) &&
              Script_Execute(&script) );

    Script_Clear(&script, 2);
    free(buf);
    return result;
#else
    return 0;
#endif
}

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

int music(char *filename, int loop, long offset)
{
    char t[64];
    char a[64];
    int res = 1;

    if(!savedata.usemusic)
    {
        return 0;
    }
    if(!sound_open_music(filename, packfile, savedata.musicvol, loop, offset))
    {
        printf("\nCan't play music file '%s'\n", filename);
        res = 0;
    }
    if(savedata.showtitles && sound_query_music(a, t))
    {
        debug_xy_msg.font_index = 0;
        //debug_xy_msg.x = videomodes.hRes/2 - videomodes.hShift - (fontmonowidth(debug_xy_msg.font_index)*16);
        //debug_xy_msg.y = videomodes.vRes - videomodes.vShift - fontheight(debug_xy_msg.font_index);
        debug_xy_msg.x = fontmonowidth(debug_xy_msg.font_index);
        debug_xy_msg.y = videomodes.vRes - fontheight(debug_xy_msg.font_index)*2;
        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("");
        }
    }
    strncpy(currentmusic, filename, sizeof(currentmusic) - 1);
    return res;
}

void check_music()
{
    if(musicfade[1] > 0)
    {
        musicfade[1] -= musicfade[0];
        sound_volume_music((int)musicfade[1], (int)musicfade[1]);
    }
    else if(musicname[0])
    {
        music(musicname, musicloop, musicoffset);
        sound_volume_music(savedata.musicvol, savedata.musicvol);
        musicname[0] = 0;
    }
}

// ----------------------- General ------------------------------
// atof and atoi return a valid number, if only the first char is one.
// so we only check that.
int isNumeric(char *text)
{
    char *p = text;
    assert(p);
    if(!*p)
    {
        return 0;
    }
    switch(*p)
    {
    case '-':
    case '+':
        p++;
        break;
    default:
        break;
    }
    switch (*p)
    {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
        return 1;
    default:
        return 0;
    }
    return 1;
}


int getValidInt(char *text, char *file, char *cmd)
{
    static const char *WARN_NUMBER_EXPECTED = "WARNING: %s tries to load a non-numeric value at %s, where a number is expected!\nerroneus string: %s\n";
    if(!text || !*text)
    {
        return 0;
    }
    if(isNumeric(text))
    {
        return atoi(text);
    }
    else
    {
        printf(WARN_NUMBER_EXPECTED, file, cmd, text);
        return 0;
    }

}

float getValidFloat(char *text, char *file, char *cmd)
{
    static const char *WARN_NUMBER_EXPECTED = "WARNING: %s tries to load a non-numeric value at %s, where a number is expected!\nerroneus string: %s\n";
    if(!text || !*text)
    {
        return 0.0f;
    }
    if(isNumeric(text))
    {
        if(text[strlen(text) - 1] == '%')
        {
            return atof(text) / 100.0f;
        }
        return atof(text);
    }
    else
    {
        printf(WARN_NUMBER_EXPECTED, file, cmd, text);
        return 0.0f;
    }
}

size_t ParseArgs(ArgList *list, char *input, char *output)
{
    assert(list);
    assert(input);
    assert(output);

    memset(output,0,MAX_ARG_LEN);

    size_t pos = 0;
    size_t wordstart = 0;
    size_t item = 0;
    // flags
    int done_flag = 0;
    int space_flag = 0; // can find more spaces
    int double_apex_flag = 0;
    int single_apex_flag = 0;

    while(pos < MAX_ARG_LEN - 1 && item < MAX_ARG_COUNT)
    {
        switch(input[pos])
        {
            // read strings
            case '"':
                if ( (pos > 0 && input[pos-1] != '\\') || pos <= 0 )
                {
                    if (space_flag && !double_apex_flag)
                    {
                        double_apex_flag = 1;
                        space_flag = 0;
                        wordstart = pos;
                        output[pos] = input[pos];
                        break;
                    }
                    else if (double_apex_flag)
                    {
                        double_apex_flag = 0;
                        output[pos] = input[pos];
                        // continue to get inputs
                        break;
                    }
                    else
                    {
                        if(space_flag)
                        {
                            wordstart = pos;
                        }
                        output[pos] = input[pos];
                        space_flag = 0;
                        break;
                    }
                }
                else
                {
                    if(space_flag)
                    {
                        wordstart = pos;
                    }
                    output[pos] = input[pos];
                    space_flag = 0;
                    break;
                }
            case '\'':
                if ( (pos > 0 && input[pos-1] != '\\') || pos <= 0 )
                {
                    if (space_flag && !single_apex_flag)
                    {
                        single_apex_flag = 1;
                        space_flag = 0;
                        wordstart = pos;
                        output[pos] = input[pos];
                        break;
                    }
                    else if (single_apex_flag)
                    {
                        single_apex_flag = 0;
                        output[pos] = input[pos];
                        // continue to get inputs
                        break;
                    }
                    else
                    {
                        if(space_flag)
                        {
                            wordstart = pos;
                        }
                        output[pos] = input[pos];
                        space_flag = 0;
                        break;
                    }
                }
                else
                {
                    if(space_flag)
                    {
                        wordstart = pos;
                    }
                    output[pos] = input[pos];
                    space_flag = 0;
                    break;
                }

            // complete item
            case '\r':
            case '\n':
            case '#':
                if (double_apex_flag || single_apex_flag)
                {
                    output[pos] = input[pos];
                    break;
                }
            case '\0':
                done_flag = 1;

            // skip spaces
            case ' ':
            case '\t':
                if (!double_apex_flag && !single_apex_flag)
                {
                    output[pos] = '\0';
                    if(!space_flag && wordstart != pos)
                    {
                        list->args[item] = output + wordstart;
                        list->arglen[item] = pos - wordstart;
                        item++;
                    }
                    space_flag = 1;
                    break;
                }

            // read character
            default:
                if(space_flag && !double_apex_flag && !single_apex_flag)
                {
                    wordstart = pos;
                }
                output[pos] = input[pos];
                space_flag = 0;
        }

        if(done_flag)
        {
            break;
        }
        pos++;
    }
    list->count = item;

    // TEST
    /*printf("found: ");
    int i;
    for (i = 0; i < list->count; i++) {
        printf("|%s|:%d",list->args[i],list->arglen[i]);
        if (i < list->count - 1) printf(" ");
    }
    printf("\n");*/

    return item;
}

int readByte(char *buf)
{
    int num = 0;

    num = (unsigned int)buf[0]&0xFF;

    return num;
}

char *findarg(char *command, int which)
{
    static const char comment_mark[] = {"#"};
    int d;
    int argc;
    int inarg;
    int argstart;
    static char arg[MAX_ARG_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 < MAX_ARG_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' ||
                strcmp(command + d, comment_mark) == 0)
        {
            // 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;
    }
    arg[d] = 0;

    return arg;
}




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



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

int inair_range(entity *e)
{
    return (diff(e->position.y, e->base) > T_WALKOFF);
}


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


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

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

    __realloc(model->colourmap, model->maps_loaded);
    k = model->maps_loaded++;

    if((map = malloc(MAX_PAL_SIZE / 4)) == NULL)
    {
        return -2;
    }
    if((bitmap1 = loadbitmap(image1, packfile, PIXEL_8)) == NULL)
    {
        free(map);
        map = NULL;
        return -3;
    }
    if((bitmap2 = loadbitmap(image2, packfile, PIXEL_8)) == NULL)
    {
        freebitmap(bitmap1);
        free(map);
        map = NULL;
        return -4;
    }

    // Create the colour map
    for(i = 0; i < MAX_PAL_SIZE / 4; 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[(unsigned)(bitmap1->data[j * bitmap1->width + i])] = bitmap2->data[j * bitmap2->width + i];
        }
    }

    freebitmap(bitmap1);
    freebitmap(bitmap2);

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

//PIXEL_x8
// This function is used to enable remap command in 24bit mode
// So old mods can still run under 16/24/32bit color system
// This function should be called when all colourmaps are loaded, e.g.,
// at the end of load_cached_model
// map flag is used to determine whether a colourmap is a real colourmap
int convert_map_to_palette(s_model *model, unsigned mapflag[])
{
    int i, c;
    unsigned char *newmap, *oldmap;
    unsigned char *p1, *p2;
    unsigned pb = pixelbytes[(int)PIXEL_32];
    if(model->palette == NULL)
    {
        return 0;
    }
    for(c = 0; c < model->maps_loaded; c++)
    {
        if(mapflag[c] == 0)
        {
            continue;
        }
        if((newmap = malloc(PAL_BYTES)) == NULL)
        {
            borShutdown(1, "Error convert_map_to_palette for model: %s\n", model->name);
        }
        // Create new colour map
        memcpy(newmap, model->palette, PAL_BYTES);
        oldmap = model->colourmap[c];
        for(i = 0; i < MAX_PAL_SIZE / 4; i++)
        {
            if(oldmap[i] == i)
            {
                continue;
            }
            p1 = newmap + i * pb;
            p2 = model->palette + oldmap[i] * pb;
            memcpy(p1, p2, pb);
        }
        model->colourmap[c] = newmap;
        free(oldmap);
        oldmap = NULL;
    }
    return 1;
}

//load a 256 colors' palette
int load_palette(unsigned char *palette, char *filename)
{
    char *fileext;
    int handle, i;
    unsigned *dp;
    unsigned char tpal[3];

    // Determine whether the author is using an .act or image file, and
    // verify the file content is valid to load a color table from.
    fileext = strrchr(filename, '.');
    if(fileext != NULL && stricmp(fileext, ".act") == 0)
    {
        handle = openpackfile(filename, packfile);
        if(handle < 0)
        {
            return 0;
        }
        memset(palette, 0, MAX_PAL_SIZE);
        dp = (unsigned *)palette;
        for(i = 0; i < MAX_PAL_SIZE / 4; i++)
        {
            if(readpackfile(handle, tpal, 3) != 3)
            {
                closepackfile(handle);
                return 0;
            }
            dp[i] = colour32(tpal[0], tpal[1], tpal[2]);

        }
        closepackfile(handle);
        dp[0] = 0;

        return 1;
    }
    else
    {
        return loadimagepalette(filename, packfile, palette);
    }
}

void create_blend_tables_x8(unsigned char *tables[])
{
    int i;
    for(i = 0; i < MAX_BLENDINGS; i++)
    {
        tables[i] = blending_table_functions32[i] ? (blending_table_functions32[i])() : NULL;
    }

}


//change system palette by index
void change_system_palette(int palindex)
{
    if(palindex < 0)
    {
        palindex = 0;
    }
    //if(current_palette == palindex ) return;


    if(!level || palindex == 0 || palindex > level->numpalettes)
    {
        current_palette = 0;
    }
    else if(level)
    {
        current_palette = palindex;
    }
}

// Load colour 0-127 from data/pal.act
void standard_palette(int immediate)
{
    unsigned char pp[MAX_PAL_SIZE] = {0};
    if(load_palette(pp, "data/pal.act"))
    {
        memcpy(pal, pp, (PAL_BYTES) / 2);
    }
    if(immediate)
    {
        change_system_palette(0);
    }
}


void unload_background()
{
    if (background)
    {
        clearscreen(background);
    }
}


int _makecolour(int r, int g, int b)
{
    return colour32(r, g, b);
}

// parses a color string in the format "R_G_B" or as a raw integer
int parsecolor(const char *string)
{
    int r, g, b;
    if(strchr(string, '_') != strrchr(string, '_'))
    {
        // 2 underscores; color is in "R_G_B" format
        r = atoi(string);
        g = atoi(strchr(string, '_') + 1);
        b = atoi(strrchr(string, '_') + 1);
        return _makecolour(r, g, b);
    }
    else
    {
        return atoi(string);    // raw integer
    }
}

// ltb 1-17-05   new function for lifebar colors
/*
    Kratus (03-2023) Added an alternative location for the lifebar file, now it's possible to use in the external "saves" folder
    Now the modder can load exported lifebar files by using "filestream" script functions
    Useful for creating custom lifebar colors according to certain script functions without unpacking the game
    The default engine lifebar location will be maintained for backward compatibility
    Don't forget that the external file will bypass the internal file!!
*/
void lifebar_colors()
{
    char *filename = "saves/lifebar.txt";
    char *buf;
    size_t size;
    int pos;
    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";

    char *command;

    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        goto default_file;
    }
    else
    {
        goto proceed;
    }

default_file:

    if(buffer_pakfile("data/lifebar.txt", &buf, &size) != 1)
    {
        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;
        color_magic = 0;
        color_magic2 = 0;
        shadowcolor = 0;
        shadowalpha = BLEND_MULTIPLY + 1;
        shadowopacity = 255;
        return;
    }
    else
    {
        goto proceed;
    }

proceed:

    pos = 0;
    colorbars = 1;
    while(pos < size)
    {
        if(ParseArgs(&arglist, buf + pos, argbuf))
        {
            command = GET_ARG(0);
            if(command && command[0])
            {
                if(stricmp(command, "blackbox") == 0)
                {
                    color_black = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                else if(stricmp(command, "whitebox") == 0)
                {
                    color_white = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                else if(stricmp(command, "color300") == 0)
                {
                    color_orange = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                else if(stricmp(command, "color25") == 0)
                {
                    color_red = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                else if(stricmp(command, "color50") == 0)
                {
                    color_yellow = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                else if(stricmp(command, "color100") == 0)
                {
                    color_green = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                else if(stricmp(command, "color200") == 0)
                {
                    color_blue = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                else if(stricmp(command, "color400") == 0)
                {
                    color_pink = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                else if(stricmp(command, "color500") == 0)
                {
                    color_purple = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                //magic bars color declarations by tails
                else if(stricmp(command, "colormagic") == 0)
                {
                    color_magic = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                else if(stricmp(command, "colormagic2") == 0)
                {
                    color_magic2 = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                //end of magic bars color declarations by tails
                else if(stricmp(command, "shadowcolor") == 0)
                {
                    shadowcolor = _makecolour(GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3));
                }
                else if(stricmp(command, "shadowalpha") == 0) //gfxshadow alpha
                {
                    shadowalpha = GET_INT_ARG(1);
                }
                else if(stricmp(command, "shadowopacity") == 0)
                {
                    shadowopacity = GET_INT_ARG(1);
                }
                else if(command && command[0])
                {
                    printf("Warning: Unknown command in lifebar.txt: '%s'.\n", command);
                }
            }
        }

        // Go to next line
        pos += getNewLineStart(buf + pos);
    }
    if(buf != NULL)
    {
        free(buf);
        buf = NULL;
    }
}
// ltb 1-17-05 end new lifebar colors

void init_colourtable()
{
    mpcolourtable[0]  = color_magic2;
    mpcolourtable[1]  = color_magic;
    mpcolourtable[2]  = color_magic;
    mpcolourtable[3]  = color_magic;
    mpcolourtable[4]  = color_magic2;
    mpcolourtable[5]  = color_magic;
    mpcolourtable[6]  = color_magic2;
    mpcolourtable[7]  = color_magic;
    mpcolourtable[8]  = color_magic2;
    mpcolourtable[9]  = color_magic;
    mpcolourtable[10] = color_magic2;

    hpcolourtable[0]  = color_purple;
    hpcolourtable[1]  = color_red;
    hpcolourtable[2]  = color_yellow;
    hpcolourtable[3]  = color_green;
    hpcolourtable[4]  = color_blue;
    hpcolourtable[5]  = color_orange;
    hpcolourtable[6]  = color_pink;
    hpcolourtable[7]  = color_purple;
    hpcolourtable[8]  = color_black;
    hpcolourtable[9]  = color_white;
    hpcolourtable[10] = color_white;

    memcpy(ldcolourtable, hpcolourtable, 11 * sizeof(*hpcolourtable));
}

void load_background(char *filename)
{
    // Clean up any previous background.
    unload_background();

    // Attempt to load 8bit color depth background. If it fails,
    // then attempt to load 24bit color depth background. If THAT
    // fails, something is wrong and we better shut down to avoid
    // a crash.
    if(!loadscreen(filename, packfile, NULL, PIXEL_x8, &background))
    {
        if (loadscreen32(filename, packfile, &background))
        {
            printf("Loaded 32-bit background '%s'\n", filename);
        }
        else
        {
            borShutdown(1, "Error loading background (PIXEL_x8/PIXEL_32) file '%s'", filename);
        }
    }

    // If background is 8bit color depth, use its color
    // table to populate the global and global neon palettes.
    if (background->pixelformat == PIXEL_x8)
    {
        memcpy(pal, background->palette, PAL_BYTES);
        memcpy(neontable, pal, PAL_BYTES);
    }

    lifebar_colors();
    if(!color_black)
    {
        color_black = _makecolour(0, 0, 0);    // black boxes 500-600HP
    }
    if(!color_red)
    {
        color_red = _makecolour(255, 0, 0);    // 1% - 25% Full Health
    }
    if(!color_orange)
    {
        color_orange = _makecolour(255, 150, 0);    // 200-300HP
    }
    if(!color_yellow)
    {
        color_yellow = _makecolour(0xF8, 0xB8, 0x40);    // 26%-50% Full health
    }
    if(!color_white)
    {
        color_white = _makecolour(255, 255, 255);    // white boxes 600+ HP
    }
    if(!color_blue)
    {
        color_blue = _makecolour(0, 0, 255);    // 100-200 HP
    }
    if(!color_green)
    {
        color_green = _makecolour(0, 255, 0);    // 51% - 100% full health
    }
    if(!color_pink)
    {
        color_pink = _makecolour(255, 0, 255);    // 300-400HP
    }
    if(!color_purple)
    {
        color_purple = _makecolour(128, 48, 208);    // transbox 400-500HP
    }
    if(!color_magic)
    {
        color_magic = _makecolour(98, 180, 255);    // 1st magic bar color by tails
    }
    if(!color_magic2)
    {
        color_magic2 = _makecolour(24, 48, 143);    // 2sec magic bar color by tails
    }
    if(!shadowcolor)
    {
        shadowcolor =  _makecolour(64, 64, 64);
    }
    init_colourtable();

    video_clearscreen();
    pal[0] = pal[1] = pal[2] = 0;
    //palette_set_corrected(pal, savedata.gamma,savedata.gamma,savedata.gamma, savedata.brightness,savedata.brightness,savedata.brightness);
    change_system_palette(0);
}

void load_cached_background(char *filename)
{
#ifndef CACHE_BACKGROUNDS
    load_background(filename);
#else
    int index = -1;
    unload_background();

    if(strcmp(filename, "data/bgs/logo") == 0)
    {
        index = 0;
    }
    else if(strcmp(filename, "data/bgs/title") == 0)
    {
        index = 1;
    }
    else if(strcmp(filename, "data/bgs/titleb") == 0)
    {
        index = 2;
    }
    else if(strcmp(filename, "data/bgs/loading") == 0)
    {
        index = 3;
    }
    else if(strcmp(filename, "data/bgs/loading2") == 0)
    {
        index = 4;
    }
    else if(strcmp(filename, "data/bgs/hiscore") == 0)
    {
        index = 5;
    }
    else if(strcmp(filename, "data/bgs/complete") == 0)
    {
        index = 6;
    }
    else if(strcmp(filename, "data/bgs/unlockbg") == 0)
    {
        index = 7;
    }
    else if(strcmp(filename, "data/bgs/select") == 0)
    {
        index = 8;
    }

    if((index == -1) || (bg_cache[index] == NULL))
    {
        borShutdown(1, "Error: can't load cached background '%s'", filename);
    }

    if(background)
    {
        freescreen(&background);
    }
    background = allocscreen(videomodes.hRes, videomodes.vRes, bg_cache[index]->pixelformat);
    copyscreen(background, bg_cache[index]);

    if(background->pixelformat == PIXEL_8)
    {
        memcpy(pal, bg_palette_cache[index], PAL_BYTES);
    }
    else if(background->pixelformat == PIXEL_x8)
    {
        memcpy(background->palette, bg_cache[index]->palette, PAL_BYTES);
        memcpy(pal, background->palette, PAL_BYTES);
    }

    video_clearscreen();
    pal[0] = pal[1] = pal[2] = 0;
    //palette_set_corrected(pal, savedata.gamma,savedata.gamma,savedata.gamma, savedata.brightness,savedata.brightness,savedata.brightness);
    change_system_palette(0);
    printf("use cached bg\n");
#endif
}

#ifdef CACHE_BACKGROUNDS
void cache_background(char *filename)
{
    s_screen *bg = allocscreen(videomodes.hRes, videomodes.vRes, pixelformat);
    int index = -1;

    if(pixelformat == PIXEL_8)
    {
        if(!loadscreen(filename, packfile, pal, pixelformat, &bg))
        {
            freescreen(&bg);
            bg = NULL;
        }
    }
    else if(pixelformat == PIXEL_x8)
    {
        if(!loadscreen(filename, packfile, NULL, pixelformat, &bg))
        {
            if(!loadscreen32(filename, packfile, &bg))
            {
                freescreen(&bg);
                bg = NULL;
            }
        }
    }
    else
    {
        borShutdown(1, "Error caching background, Unknown Pixel Format!\n");
    }

    if(strcmp(filename, "data/bgs/logo") == 0)
    {
        index = 0;
    }
    else if(strcmp(filename, "data/bgs/title") == 0)
    {
        index = 1;
    }
    else if(strcmp(filename, "data/bgs/titleb") == 0)
    {
        index = 2;
    }
    else if(strcmp(filename, "data/bgs/loading") == 0)
    {
        index = 3;
    }
    else if(strcmp(filename, "data/bgs/loading2") == 0)
    {
        index = 4;
    }
    else if(strcmp(filename, "data/bgs/hiscore") == 0)
    {
        index = 5;
    }
    else if(strcmp(filename, "data/bgs/complete") == 0)
    {
        index = 6;
    }
    else if(strcmp(filename, "data/bgs/unlockbg") == 0)
    {
        index = 7;
    }
    else if(strcmp(filename, "data/bgs/select") == 0)
    {
        index = 8;
    }
    else
    {
        borShutdown(1, "Error: unknown cached background '%s'", filename);
    }

    bg_cache[index] = bg;

    if(pixelformat == PIXEL_8)
    {
        memcpy(bg_palette_cache[index], pal, PAL_BYTES);
    }

    change_system_palette(0);
}

void cache_all_backgrounds()
{
    cache_background("data/bgs/logo");
    cache_background("data/bgs/title");
    cache_background("data/bgs/titleb");
    cache_background("data/bgs/loading2");
    cache_background("data/bgs/hiscore");
    cache_background("data/bgs/complete");
    cache_background("data/bgs/unlockbg");
    cache_background("data/bgs/select");
}
#endif

void load_layer(char *filename, char *maskfilename, int index)
{
    if(!level)
    {
        return;
    }

    if(filename && level->layers[index].gfx.handle == NULL)
    {
        if(*maskfilename || ((level->layers[index].drawmethod.alpha > 0 || level->layers[index].drawmethod.transbg) && !level->layers[index].drawmethod.water.watermode))
        {
            // assume sprites are faster than screen when transparency or alpha are specified
            level->layers[index].gfx.sprite = loadsprite2(filename, &(level->layers[index].size.x), &(level->layers[index].size.y));
            if (*maskfilename)
            {
                level->layers[index].gfx.sprite->mask = loadsprite2(maskfilename, &(level->layers[index].size.x), &(level->layers[index].size.y));
                *maskfilename = 0; // clear mask filename so mask is only used for this one sprite
            }
        }
        else
        {
            // use screen for water effect for now, it should be faster than sprite
            // otherwise, a screen should be fine, especially in 8bit mode, it is super fast,
            //            or, at least it is not slower than a sprite
            if(loadscreen(filename, packfile, NULL, pixelformat, &level->layers[index].gfx.screen))
            {
                level->layers[index].size.y = level->layers[index].gfx.screen->height;
                level->layers[index].size.x = level->layers[index].gfx.screen->width;
            }
        }
    }

    if(filename && level->layers[index].gfx.handle == NULL)
    {
        borShutdown(1, "Error loading file '%s'", filename);
    }
    else
    {
        if(level->layers[index].drawmethod.xrepeat < 0)
        {
            level->layers[index].offset.x -= level->layers[index].size.x * 20000;
            level->layers[index].drawmethod.xrepeat = 40000;
        }
        if(level->layers[index].drawmethod.yrepeat < 0)
        {
            level->layers[index].offset.z -= level->layers[index].size.y * 20000;
            level->layers[index].drawmethod.yrepeat = 40000;
        }
        //printf("bglayer width=%d height=%d xoffset=%d zoffset=%d xrepeat=%d zrepeat%d\n", level->layers[index].size.x, level->layers[index].size.y, level->layers[index].offset.x, level->layers[index].offset.z, level->layers[index].xrepeat, level->layers[index].zrepeat);
    }

}


s_sprite *loadsprite2(char *filename, int *width, int *height)
{
    size_t size;
    s_bitmap *bitmap = NULL;
    s_sprite *sprite = NULL;
    int clip_left;
    int clip_right;
    int clip_top;
    int clip_bottom;

    // Load raw bitmap (image) file from pack. If this
    // fails, then we return NULL.
    bitmap = loadbitmap(filename, packfile, pixelformat);

    if(!bitmap)
    {
        return NULL;
    }

    // Apply width and height adjustments, if any.
    if(width)
    {
        *width = bitmap->width;
    }

    if(height)
    {
        *height = bitmap->height;
    }

    // Trim empty pixels from the bitmap to save memory.
    // We will pass the arguments by reference - they will
    // be modified by the clipping function to tell us
    // exactly how much trim work on each axis was done.
    clipbitmap(bitmap, &clip_left, &clip_right, &clip_top, &clip_bottom);

    // Get size of trimmed bitmap and allocate memory for
    // use as a sprite. If this fails, then free the memory
    // bitmap occupies, and return NULL.
    size = fakey_encodesprite(bitmap);
    sprite = (s_sprite *)malloc(size);

    if(!sprite)
    {
        freebitmap(bitmap);
        return NULL;
    }

    // Transpose bitmap to a sprite, using the memory
    // we allocated for it above. The trim arguments
    // from our trimming function will be used as an
    // offset. We'll also store/ the bitmap's trimmed
    // dimensions for later use.
    encodesprite(-clip_left, -clip_top, bitmap, sprite);
    sprite->offsetx = clip_left;
    sprite->offsety = clip_top;
    sprite->srcwidth = bitmap->clipped_width;
    sprite->srcheight = bitmap->clipped_height;

    // Delete the raw bitmap, we don't need it
    // any more.
    freebitmap(bitmap);

    // Return encoded sprite.
    return sprite;
}


// Added to conserve memory
void resourceCleanUp()
{
    freesprites();
    free_models();
    free_modelcache();
    load_special_sounds();
    load_script_setting();
    load_special_sprites();
    load_levelorder();
    load_models();
}

void freesprites()
{
    unsigned i;
    s_sprite_list *head;
    for(i = 0; i <= sprites_loaded; i++)
    {
        if(sprite_list != NULL)
        {
            free(sprite_list->sprite);
            sprite_list->sprite = NULL;
            free(sprite_list->filename);
            sprite_list->filename = NULL;
            head = sprite_list->next;
            free(sprite_list);
            sprite_list = head;
        }
    }
    if(sprite_map != NULL)
    {
        free(sprite_map);
        sprite_map = NULL;
    }
    sprites_loaded = 0;
}

// allocate enough members for sprite_map
void prepare_sprite_map(size_t size)
{
    if(sprite_map == NULL || size + 1 > sprite_map_max_items )
    {
#ifdef VERBOSE
        printf("%s %p\n", "prepare_sprite_map was", sprite_map);
#endif
        sprite_map_max_items = (((size + 1) >> 8) + 1) << 8;
        sprite_map = realloc(sprite_map, sizeof(*sprite_map) * sprite_map_max_items);
        if(sprite_map == NULL)
        {
            borShutdown(1, "Out Of Memory!  Failed to create a new sprite_map\n");
        }
    }
}

void cachesound(int index, int load)
{
    if(index < 0)
    {
        return;
    }
    if(load)
    {
        sound_reload_sample(index);
    }
    else
    {
        sound_unload_sample(index);
    }
}

// Cachesprite
// Unknown original date & author
// Rewrite by Caskey, Damon V.
// 2018-03-19
//
// Add or remove a sprite to the the sprite list
// by index.
//
// index: Target index in the sprite list.
// load: Load 1, or unload 0 the target sprite index.
void cachesprite(int index, int load)
{
    s_sprite *sprite;           // Sprite placeholder.
    s_sprite_list *map_node;    // Sprite map node placeholder.

    // Valid sprite list?
    if(sprite_map)
    {
        // Index argument valid?
        if(index >= 0)
        {
            // Index argument should be more than
            // the number of sprites loaded.
            if(index < sprites_loaded)
            {
                // Get the sprite list node from sprite maps
                // using our target index.
                map_node = sprite_map[index].node;

                // If load is true, then we want to load
                // a sprite and assign it the target index.
                // Otherwise, we want to free a sprite with
                // target index.
                if(load)
                {
                    // Make sure there is not already
                    // a sprite with our target index.
                    sprite = map_node->sprite;

                    if(!sprite)
                    {
                        // Load the sprite file, then assign its
                        // new pointer to the sprite map using our
                        // index for the sprite map position.
                        sprite = loadsprite2(map_node->filename, NULL, NULL);
                        map_node->sprite = sprite;
                    }
                }
                else if(!load)
                {
                    // Does the target sprite exist?
                    sprite = map_node->sprite;

                    if(sprite)
                    {
                        // Free the target sprite's resources, then remove
                        // its pointer from sprite map.
                        free(sprite);
                        map_node->sprite = NULL;

                        //printf("uncached sprite: %s\n", map_node->filename);
                    }
                }
            }
        }
    }
}

// Returns sprite index.
// Does not return on error, as it would shut the program down.
// UT:
// bmpformat - In 24bit mode, a sprite can have a 24bit palette(e.g., panel),
//             so add this paramter to let sprite encoding function know.
//             Actually the sprite pixel encoding method is the same, but a
//             24bit palettte sprite should have a palette allocated at the end of
//             pixel data, and the information is carried by the bitmap paramter.
int loadsprite(char *filename, int ofsx, int ofsy, int bmpformat)
{
    ptrdiff_t i, size, len;
    s_bitmap *bitmap = NULL;
    int clipl, clipr, clipt, clipb;
    s_sprite_list *curr = NULL, *head = NULL, *toshare = NULL;

    for(i = 0; i < sprites_loaded; i++)
    {
        if(sprite_map && sprite_map[i].node)
        {
            if(stricmp(sprite_map[i].node->filename, filename) == 0)
            {
                if(!sprite_map[i].node->sprite)
                {
                    sprite_map[i].node->sprite = loadsprite2(filename, NULL, NULL);
                }
                if(sprite_map[i].centerx + sprite_map[i].node->sprite->offsetx == ofsx &&
                        sprite_map[i].centery + sprite_map[i].node->sprite->offsety == ofsy)
                {
                    return i;
                }
                else
                {
                    toshare = sprite_map[i].node;
                }
            }
        }
    }

    if(toshare)
    {
        prepare_sprite_map(sprites_loaded + 1);
        sprite_map[sprites_loaded].node = toshare;
        sprite_map[sprites_loaded].centerx = ofsx - toshare->sprite->offsetx;
        sprite_map[sprites_loaded].centery = ofsy - toshare->sprite->offsety;
        ++sprites_loaded;
        return sprites_loaded - 1;
    }

    bitmap = loadbitmap(filename, packfile, bmpformat);
    if(bitmap == NULL)
    {
        borShutdown(1, "Unable to load file '%s'\n", filename);
    }

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

    len = strlen(filename);
    size = fakey_encodesprite(bitmap);
    curr = malloc(sizeof(*curr));
    curr->sprite = malloc(size);
    curr->filename = malloc(len + 1);
    if(curr == NULL || curr->sprite == NULL || curr->filename == NULL)
    {
        freebitmap(bitmap);
        borShutdown(1, "loadsprite() Out of memory!\n");
    }
    memcpy(curr->filename, filename, len);
    curr->filename[len] = 0;
    encodesprite(ofsx - clipl, ofsy - clipt, bitmap, curr->sprite);
    if(sprite_list == NULL)
    {
        sprite_list = curr;
        sprite_list->next = NULL;
    }
    else
    {
        head = sprite_list;
        sprite_list = curr;
        sprite_list->next = head;
    }
    prepare_sprite_map(sprites_loaded + 1);
    sprite_map[sprites_loaded].node = sprite_list;
    sprite_map[sprites_loaded].centerx = ofsx - clipl;
    sprite_map[sprites_loaded].centery = ofsy - clipt;
    sprite_list->sprite->offsetx = clipl;
    sprite_list->sprite->offsety = clipt;
    sprite_list->sprite->srcwidth = bitmap->clipped_width;
    sprite_list->sprite->srcheight = bitmap->clipped_height;
    freebitmap(bitmap);
    ++sprites_loaded;
    return sprites_loaded - 1;
}

void load_special_sprites()
{
    memset(shadowsprites, -1, sizeof(*shadowsprites) * 6);
    golsprite = gosprite = -1;
    if (testpackfile("data/sprites/shadow1.gif", packfile) >= 0 ||
        testpackfile("data/sprites/shadow1.png", packfile) >= 0)
    {
        shadowsprites[0] = loadsprite("data/sprites/shadow1", 9, 3, pixelformat);
    }
    if (testpackfile("data/sprites/shadow2.gif", packfile) >= 0 ||
        testpackfile("data/sprites/shadow2.png", packfile) >= 0)
    {
        shadowsprites[1] = loadsprite("data/sprites/shadow2", 14, 5, pixelformat);
    }
    if (testpackfile("data/sprites/shadow3.gif", packfile) >= 0 ||
        testpackfile("data/sprites/shadow3.png", packfile) >= 0)
    {
        shadowsprites[2] = loadsprite("data/sprites/shadow3", 19, 6, pixelformat);
    }
    if (testpackfile("data/sprites/shadow4.gif", packfile) >= 0 ||
        testpackfile("data/sprites/shadow4.png", packfile) >= 0)
    {
        shadowsprites[3] = loadsprite("data/sprites/shadow4", 24, 8, pixelformat);
    }
    if (testpackfile("data/sprites/shadow5.gif", packfile) >= 0 ||
        testpackfile("data/sprites/shadow5.png", packfile) >= 0)
    {
        shadowsprites[4] = loadsprite("data/sprites/shadow5", 29, 9, pixelformat);
    }
    if (testpackfile("data/sprites/shadow6.gif", packfile) >= 0 ||
        testpackfile("data/sprites/shadow6.png", packfile) >= 0)
    {
        shadowsprites[5] = loadsprite("data/sprites/shadow6", 34, 11, pixelformat);
    }
    if (testpackfile("data/sprites/arrow.gif", packfile) >= 0 ||
        testpackfile("data/sprites/arrow.png", packfile) >= 0)
    {
        gosprite  = loadsprite("data/sprites/arrow", 35, 23, pixelformat);
    }
    if (testpackfile("data/sprites/arrowl.gif", packfile) >= 0 ||
        testpackfile("data/sprites/arrowl.png", packfile) >= 0)
    {
        golsprite = loadsprite("data/sprites/arrowl", 35, 23, pixelformat);
    }
    if(timeicon_path[0])
    {
        timeicon = loadsprite(timeicon_path, 0, 0, pixelformat);
    }
    if(bgicon_path[0])
    {
        bgicon = loadsprite(bgicon_path, 0, 0, pixelformat);
    }
    if(olicon_path[0])
    {
        olicon = loadsprite(olicon_path, 0, 0, pixelformat);
    }
}

void unload_all_fonts()
{
    int i;
    for(i = 0; i < MAX_FONTS; i++)
    {
        font_unload(i);
    }
}

void load_all_fonts()
{
    char path[MAX_BUFFER_LEN];
    int i;

    for(i = 0; i < MAX_FONTS; i++)
    {
        if(i == 0)
        {
            strcpy(path, "data/sprites/font");
        }
        else
        {
            sprintf(path, "%s%d", "data/sprites/font", i + 1);
        }
        if(font_load(i, path, packfile, fontmonospace[i] | fontmbs[i]))
        {
            // Plombo 3/1/2013: allow fonts to have alpha masks
            if(i == 0)
            {
                strcpy(path, "data/sprites/fontmask");
            }
            else
            {
                sprintf(path, "%s%d", "data/sprites/fontmask", i + 1);
            }
            if(font_loadmask(i, path, packfile, fontmonospace[i] | fontmbs[i]))
            {
                printf("%d(m) ", i + 1);
            }
            else
            {
                printf("%d ", i + 1);
            }
        }
    }
}

int translate_SDID(char *value)
{
    if(stricmp(value, "moveup") == 0)
    {
        return SDID_MOVEUP;
    }
    else if(stricmp(value, "movedown") == 0)
    {
        return SDID_MOVEDOWN;
    }
    else if(stricmp(value, "moveleft") == 0)
    {
        return SDID_MOVELEFT;
    }
    else if(stricmp(value, "moveright") == 0)
    {
        return SDID_MOVERIGHT;
    }
    else if(stricmp(value, "attack") == 0)
    {
        return SDID_ATTACK;
    }
    else if(stricmp(value, "attack2") == 0)
    {
        return SDID_ATTACK2;
    }
    else if(stricmp(value, "attack3") == 0)
    {
        return SDID_ATTACK3;
    }
    else if(stricmp(value, "attack4") == 0)
    {
        return SDID_ATTACK4;
    }
    else if(stricmp(value, "jump") == 0)
    {
        return SDID_JUMP;
    }
    else if(stricmp(value, "special") == 0)
    {
        return SDID_SPECIAL;
    }
    else if(stricmp(value, "start") == 0)
    {
        return SDID_START;
    }
    else if(stricmp(value, "screenshot") == 0)
    {
        return SDID_SCREENSHOT;
    }
    else if(stricmp(value, "esc") == 0)
    {
        return SDID_ESC;
    }

    return -1;
}

void load_menu_txt()
{
    char *filename = "translation/menu.txt";
    int pos, i;
    char *buf, *command;
    size_t size;
    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";

    /*
        Kratus (10-2021) Added an alternative location for the translation file, now it's possible to use in an external folder
        Now the modder can load exported translation files by using "filestream" script functions
        Useful for creating custom translations without unpack the game
        The default engine translation location will be maintained for backward compatibility

        Kratus (11-2021) Inverted the path priority, now the external file will override the internal file
        Useful to maintain the english translation intact inside the pak file if no other language file is found in the external path
        Otherwise you will need to rollback the english file every time another language is used and then removed
        This operation is needed only if the english translation file uses some custom menu texts for english language too
    */
    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        goto default_file;
    }
    else
    {
        goto proceed;
    }

default_file:

    if(buffer_pakfile("data/menu.txt", &buf, &size) != 1)
    {
        return;
    }
    else
    {
        goto proceed;
    }

proceed:

    // Now interpret the contents of buf line by line
    pos = 0;
    while(pos < size)
    {
        if(ParseArgs(&arglist, buf + pos, argbuf))
        {
            command = GET_ARG(0);
            if(command && command[0])
            {
                if(stricmp(command, "fontmonospace") == 0)
                {
                    for(i = 0; i < MAX_FONTS; i++)
                    {
                        fontmonospace[i] = GET_INT_ARG((i + 1)) ? FONT_MONO : 0;
                    }
                }
                else if(stricmp(command, "fontmbs") == 0)
                {
                    for(i = 0; i < MAX_FONTS; i++)
                    {
                        fontmbs[i] = GET_INT_ARG((i + 1)) ? FONT_MBS : 0;
                    }
                }
            }
        }

        // Go to next line
        pos += getNewLineStart(buf + pos);
    }

    if(buf != NULL)
    {
        free(buf);
        buf = NULL;
    }
}

int load_special_sounds()
{
    sound_unload_all_samples();
    global_sample_list.go = sound_load_sample("data/sounds/go.wav",		packfile,	0);
    global_sample_list.beat = sound_load_sample("data/sounds/beat1.wav",	packfile,	0);
    global_sample_list.block = sound_load_sample("data/sounds/block.wav",	packfile,	0);
    global_sample_list.fall = sound_load_sample("data/sounds/fall.wav",		packfile,	0);
    global_sample_list.get = sound_load_sample("data/sounds/get.wav",		packfile,	0);
    global_sample_list.get_2 = sound_load_sample("data/sounds/money.wav",	packfile,	0);
    global_sample_list.jump = sound_load_sample("data/sounds/jump.wav",		packfile,	0);
    global_sample_list.indirect = sound_load_sample("data/sounds/indirect.wav",	packfile,	0);
    global_sample_list.punch = sound_load_sample("data/sounds/punch.wav",	packfile,	0);
    global_sample_list.one_up = sound_load_sample("data/sounds/1up.wav",		packfile,	0);
    global_sample_list.time_over = sound_load_sample("data/sounds/timeover.wav", packfile,	0);
    global_sample_list.beep = sound_load_sample("data/sounds/beep.wav",		packfile,	0);
    global_sample_list.beep_2 = sound_load_sample("data/sounds/beep2.wav",	packfile,	0);
    global_sample_list.pause = sound_load_sample("data/sounds/pause.wav",	packfile,	0);
    global_sample_list.bike = sound_load_sample("data/sounds/bike.wav",		packfile,	0);

    if (global_sample_list.pause < 0 ) global_sample_list.pause = global_sample_list.beep_2;
    if(global_sample_list.go < 0 || global_sample_list.beat < 0 || global_sample_list.block < 0 ||
        global_sample_list.fall < 0 || global_sample_list.get < 0 || global_sample_list.get_2 < 0 ||
        global_sample_list.jump < 0 || global_sample_list.indirect < 0 || global_sample_list.punch < 0 ||
        global_sample_list.one_up < 0 || global_sample_list.time_over < 0 || global_sample_list.beep < 0 ||
        global_sample_list.beep_2 < 0 || global_sample_list.pause < 0 || global_sample_list.bike < 0)
    {
        return 0;
    }
    return 1;
}

// Caskey, Damon V.
// 2019-01-02
//
// Return true if map_index matches a special purpose
// map or falls within author defined hidden map range, 
// unless any of the above are same as default map (0).
int is_map_hidden(s_model *model, int map_index)
{
	// Have frozen map and it isn't same as default?
	// If we do and it matches, return true.
	if (model->colorsets.frozen > 0)
	{
		if (map_index == model->colorsets.frozen)
		{
			return 1;
		}
	}

	// Check KO map. Same logic as frozen.
	if (model->colorsets.ko > 0)
	{
		if (map_index == model->colorsets.ko)
		{
			return 1;
		}
	}

	// Hidden map range. Both should be
	// something other than default. If 
	// they are and map index is in range
	// we return true.
	if (model->colorsets.hide_start > 0
		&& model->colorsets.hide_end > 0)
	{
		if (map_index >= model->colorsets.hide_start
			&& map_index <= model->colorsets.hide_end)
		{
			return 1;
		}
	}

	// If we got this far, there's no match. 
	return 0;
}

// Return model's next selectable map index in line.
int nextcolourmap(s_model *model, int map_index)
{
	// Increment to next color set, or return to 0 
	// if we go past number of available sets. 
	// Continue until we find an index that
	// isn't hidden.
    do
    {
		map_index++;

        if(map_index > model->maps_loaded)
        {
			map_index = 0;
        }
    }
    while(is_map_hidden(model, map_index));

    return map_index;
}

// Increment to next map in player's (player_index) model
// while avoiding the map another player with same 
// is using.
int nextcolourmapn(s_model *model, int map_index, int player_index)
{
	// Increment to next index.
	map_index = nextcolourmap(model, map_index);

    s_set_entry *set = levelsets + current_set;

	// If color selection is allowed but identical map is 
	// not (nosame 2), then let's make sure anohter player 
	// with same model isn't already using this map.
	// If they are we'll find the next map available.
    if (colourselect && (set->nosame & 2))
    {
		int i = 0;
		int j = 0;
        int maps_count = model->maps_loaded + 1;
        int used_colors_map[maps_count];
        int used_color_count = 0;

        // Reset local used map array elements to 0.
		for (i = 0; i < maps_count; i++)
		{
			used_colors_map[i] = 0;
		}

        // Deduct hidden maps from map count.
		if (model->colorsets.frozen > 0)
		{
			--maps_count;
		}

		if (model->colorsets.ko > 0)
		{
			--maps_count;
		}

		if (model->colorsets.hide_start > 0)
		{
			maps_count -= model->colorsets.hide_end - model->colorsets.hide_start + 1;
		}

        // This logic attempts to populate used_colors_map array with
		// every color in use by other players who picking same
		// character. If there are aren't enough unused map indexes to
		// go around (i.e. three players select a character that only
		// has two maps), then we return initial map selection.

        for(i = 0; i < MAX_PLAYERS; i++)
        {			
			// Compare every player index to player_index argument. If
			// it's a different index but that index's model matches
			// player_index's model, then it's another player choosing 
			// (or about to choose) the same character.

            if (player_index != i 
				&& 
				stricmp(player[player_index].name, player[i].name) == 0)
            {
				// Use the map index as an array element index, and mark it true.
				// Now we now this map index is in use.
                used_colors_map[player[i].colourmap] = 1;
                
				// Increment number of used map indexes.
				++used_color_count;
                
				// If all the map indexes are used, we'll just
				// have to settle for one we already picked.
				if (used_color_count >= maps_count)
				{
					return map_index;
				}
            }
        }

		// Now that we have a list of used maps, let's employ it to
		// find the first free map.
		//
        // Loop to number of maps for the model. If our used_colors_map
		// array element matching the map index doesn't have a true
		// value, we can return the index.

        for(i = map_index, j = 0; j < maps_count; j++)
        {
            if (!used_colors_map[i])
            {
				return i;
            }

            i = nextcolourmap(model, i);
        }
    }

	// If we got here, then we couldn't find a free map index,
	// so just return initial selection.
    return map_index;
}

// Return model's previous selectable map index in line.
int prevcolourmap(s_model *model, int map_index)
{
	// Decrement to previous color set, or return 
	// to last set if we go below 0. Continue until
	// we find an index that isn't hidden.
    do
    {
		map_index--;
        if(map_index < 0)
        {
			map_index = model->maps_loaded;
        }
    }
    while(is_map_hidden(model, map_index));

    return map_index;
}

// Decrement to previous map in player's (player_index) model
// while avoiding the map another player with same 
// is using.
int prevcolourmapn(s_model *model, int map_index, int player_index)
{
	// Decrement to previous index.
	map_index = prevcolourmap(model, map_index);

	s_set_entry *set = levelsets + current_set;

	// If color selection is allowed but identical map is 
	// not (nosame 2), then let's make sure anohter player 
	// with same model isn't already using this map.
	// If they are we'll find the next map available.
	if (colourselect && (set->nosame & 2))
	{
		int i = 0;
		int j = 0;
		int maps_count = model->maps_loaded + 1;
		int used_colors_map[maps_count];
		int used_color_count = 0;

		// Reset local used map array elements to 0.
		for (i = 0; i < maps_count; i++)
		{
			used_colors_map[i] = 0;
		}

		// Deduct hidden maps from map count.
		if (model->colorsets.frozen > 0)
		{
			--maps_count;
		}

		if (model->colorsets.ko > 0)
		{
			--maps_count;
		}

		if (model->colorsets.hide_start > 0)
		{
			maps_count -= model->colorsets.hide_end - model->colorsets.hide_start + 1;
		}

		// This logic attempts to populate used_colors_map array with
		// every color in use by other players who picking same
		// character. If there are aren't enough unused map indexes to
		// go around (i.e. three players select a character that only
		// has two maps), then we return initial map selection.

		for (i = 0; i < MAX_PLAYERS; i++)
		{
			// Compare every player index to player_index argument. If
			// it's a different index but that index's model matches
			// player_index's model, then it's another player choosing 
			// (or about to choose) the same character.

			if (player_index != i
				&&
				stricmp(player[player_index].name, player[i].name) == 0)
			{
				// Use the map index as an array element index, and mark it true.
				// Now we now this map index is in use.
				used_colors_map[player[i].colourmap] = 1;

				// Increment number of used map indexes.
				++used_color_count;

				// If all the map indexes are used, we'll just
				// have to settle for one we already picked.
				if (used_color_count >= maps_count)
				{
					return map_index;
				}
			}
		}

		// Now that we have a list of used maps, let's employ it to
		// find the first free map.
		//
		// Loop to number of maps for the model. If our used_colors_map
		// array element matching the map index doesn't have a true
		// value, we can return the index.

		for (i = map_index, j = 0; j < maps_count; j++)
		{
			if (!used_colors_map[i])
			{
				return i;
			}

			i = prevcolourmap(model, i);
		}
	}

	// If we got here, then we couldn't find a free map index,
	// so just return initial selection.
	return map_index;
}

// Caskey, Damon V.
// 2019-01-02
//
// Return true if a model cache element is selectable by player.
int is_model_cache_index_selectable(int cache_index)
{
	// Must have selectable flag.
	if (!model_cache[cache_index].selectable)
	{
		return 0;
	}

	// Element must contain a valid model.
	if (!model_cache[cache_index].model)
	{
		return 0;
	}
	
	// Element's model must be selectable.
	if (!is_model_selectable(model_cache[cache_index].model))
	{
		return 0;
	}

	// All checks passed. Return true.
	return 1;
}

// Caskey, Damon V.
// 2019-01-02
//
// Return true if a model is selectable by player.
int is_model_selectable(s_model *model)
{
	// Must be a player type.
	if (model->type != TYPE_PLAYER)
	{
		return 0;
	}

	// If model is marked secret, then secret
	// characters must be allowed.
	if (model->secret)
	{
		if (!allow_secret_chars)
		{
			return 0;
		}
	}

	// 2019-01-02 DC: Not sure what this is. 
	// TO DO - Document clearcount vs. bonus.
	if (model->clearcount > bonus)
	{
		return 0;
	}

	// Got this far, we can return true.
	return 1;
}

// Caskey, Damon V.
// 2019-01-03
//
// Return current number of player selectable models.
int find_selectable_model_count()
{
	int result;
	int i;

	result = 0;

	// Loop over model cache and increment
	// count each time we find a selectable
	// model.
	for (i = 0; i < models_cached; i++)
	{
		if (is_model_cache_index_selectable(i))
		{
			++result;
		}
	}

	return result;
}

// Use by player select menus
s_model *nextplayermodel(s_model *current)
{
    int i;
    int curindex = -1;
    int loops;
    
	// Do we have a model?
	if(current)
    {
        // Find index of current player model
        for(i = 0; i < models_cached; i++)
        {
            if(model_cache[i].model == current)
            {
                curindex = i;
                break;
            }
        }
    }

    // Find next player model (first one after current index)
    for(i = curindex + 1, loops = 0; loops < models_cached; i++, loops++)
    {
		// Return to 0 if we've gone past the last model.
        if(i >= models_cached)
        {
            i = 0;
        }

		// If valid and selectable, return the model.
        if(is_model_cache_index_selectable(i))
        {
			//printf("next %s\n", model_cache[i].model->name);
			return model_cache[i].model;            
        }
    }
    borShutdown(1, "Fatal: can't find any player models!");
    return NULL;
}

s_model *nextplayermodeln(s_model *current, int player_index)
{
    int i;
    s_set_entry *set = levelsets + current_set;
    s_model *model = nextplayermodel(current);

    if(set->nosame & 1)
    {
		int used_player_count = 0;
		int player_count = 0;

		// Get number of selectable models.
		player_count = find_selectable_model_count();

        // count all used player
        for(i = 0; model && i < MAX_PLAYERS; i++)
        {
            if(i != player_index 
				&& stricmp(player[player_index].name, player[i].name) == 0)
            {
                ++used_player_count;
                // all busy players? return the next natural
				if (used_player_count >= player_count)
				{
					return model;
				}
            }
        }

        // search the first free player
        for(i = 0; model && i < MAX_PLAYERS; i++)
        {
            if(i != player_index && stricmp(model->name, player[i].name) == 0)
            {
                i = -1;
                model = nextplayermodel(model);
            }
        }
    }

    return model;
}

// Use by player select menus
s_model *prevplayermodel(s_model *current)
{
    int i;
    int curindex = -1;
    int loops;
    if(current)
    {
        // Find index of current player model
        for(i = 0; i < models_cached; i++)
        {
            if(model_cache[i].model == current)
            {
                curindex = i;
                break;
            }
        }
    }
    // Find next player model (first one after current index)
    for(i = curindex - 1, loops = 0; loops < models_cached; i--, loops++)
    {
        if(i < 0)
        {
            i = models_cached - 1;
        }

		// If valid and selectable, return the model.
        if(is_model_cache_index_selectable(i))
        {
            //printf("prev %s\n", model_cache[i].model->name);
            return model_cache[i].model;
        }
    }
    borShutdown(1, "Fatal: can't find any player models!");
    return NULL;
}

s_model *prevplayermodeln(s_model *current, int player_index)
{
    int i;
    s_set_entry *set = levelsets + current_set;
    s_model *model = prevplayermodel(current);

    if(set->nosame & 1)
    {
		int used_player_count = 0; 
		int player_count = 0;

		// Get number of selectable models.
		player_count = find_selectable_model_count();

        // count all used player
        for(i = 0; model && i < MAX_PLAYERS; i++)
        {
            if(i != player_index && stricmp(player[player_index].name, player[i].name) == 0)
            {
                ++used_player_count;
                // all busy players? return the prev natural
                if (used_player_count >= player_count) return model;
            }
        }

        // search the first free player
        for(i = 0; model && i < MAX_PLAYERS; i++)
        {
            if(i != player_index && stricmp(model->name, player[i].name) == 0)
            {
                i = -1;
                model = prevplayermodel(model);
            }
        }
    }

    return model;
}

// Reset All Player Models to on/off for Select Screen.
static void reset_playable_list(char which)
{
    int i;
    for(i = 0; i < models_cached; i++)
    {
        if(!which || (model_cache[i].model && model_cache[i].model->type == TYPE_PLAYER))
        {
            model_cache[i].selectable = which;
        }
    }
}

// Specify which Player Models are allowable for selecting
static void load_playable_list(char *buf)
{
    int i, index;
    char *value;
    s_model *playermodels = NULL;
    ArgList arglist;
    char argbuf[MAX_ALLOWSELECT_LEN] = "";

    ParseArgs(&arglist, buf, argbuf);

    // avoid to load characters if there isn't an allowselect
    if ( stricmp(value = GET_ARG(0), "allowselect") != 0 ) return;

    reset_playable_list(0);

    for(i = 0; i < sizeof(argbuf); i++) allowselect_args[i] = ' ';
    for(i = 0; i < sizeof(argbuf); i++)
    {
        if ( argbuf[i] != '\0' ) allowselect_args[i] = argbuf[i]; // store allowselect players for savefile
        else allowselect_args[i] = ' ';
    }
    allowselect_args[sizeof(argbuf)-1] = '\0';

    for(i = 1; (value = GET_ARG(i))[0]; i++)
    {
        playermodels = findmodel(value);
        //if(playermodels == NULL) borShutdown(1, "Player model '%s' is not loaded.\n", value);
        index = get_cached_model_index(playermodels->name);
        if(index == -1)
        {
            borShutdown(1, "Player model '%s' is not cached.\n", value);
        }
        model_cache[index].selectable = 1;
    }

    return;
}

void alloc_specials(s_model *newchar)
{
    newchar->special = realloc(newchar->special, sizeof(s_com) * (newchar->specials_loaded + 1));
    memset(newchar->special + newchar->specials_loaded, 0, sizeof(s_com));
}

void alloc_frames(s_anim *anim, int fcount)
{
    anim->sprite = malloc(fcount * sizeof(*anim->sprite));
    anim->delay = malloc(fcount * sizeof(*anim->delay));
    anim->vulnerable = malloc(fcount * sizeof(*anim->vulnerable));
    memset(anim->sprite, 0, fcount * sizeof(*anim->sprite));
    memset(anim->delay, 0, fcount * sizeof(*anim->delay));
    memset(anim->vulnerable, 0, fcount * sizeof(*anim->vulnerable));
}

void free_frames(s_anim *anim)
{
    int i, instance;

    if(anim->offset)
    {
        for(i = 0; i < anim->numframes; i++)
        {
            if(anim->offset[i])
            {
                free(anim->offset[i]);
                anim->offset[i] = NULL;
            }
        }
        free(anim->offset);
        anim->offset = NULL;
    }

    if(anim->idle)
    {
        free(anim->idle);
        anim->idle = NULL;
    }

    if(anim->move)
    {
        for(i = 0; i < anim->numframes; i++)
        {
            if(anim->move[i])
            {
                free(anim->move[i]);
                anim->move[i] = NULL;
            }
        }
        free(anim->move);
        anim->move = NULL;
    }

    if(anim->delay)
    {
        free(anim->delay);
        anim->delay = NULL;
    }
    if(anim->sprite)
    {
        free(anim->sprite);
        anim->sprite = NULL;
    }
    if(anim->platform)
    {
        free(anim->platform);
        anim->platform = NULL;
    }
    if(anim->vulnerable)
    {
        free(anim->vulnerable);
        anim->vulnerable = NULL;
    }

    if (anim->collision_attack)
    {
        collision_attack_free_list(*anim->collision_attack);
        anim->collision_attack = NULL;
    }

    if (anim->collision_body)
    {
        collision_body_free_list(*anim->collision_body);
        anim->collision_body = NULL;
    }

    if (anim->child_spawn)
    {
        child_spawn_free_list(*anim->child_spawn);
        anim->child_spawn = NULL;
    }

    if(anim->collision_entity)
    {
        for(i = 0; i < anim->numframes; i++)
        {
            if(anim->collision_entity[i])
            {
                // Check each instance and free memory as needed.
                // Momma always said put your toys away when you're done!
                for(instance = 0; instance < max_collisons; instance++)
                {
                    if(anim->collision_entity[i]->instance[instance])
                    {
                        // First free any pointers allocated
                        // for sub structures.

                        // Coords.
                        if(anim->collision_entity[i]->instance[instance]->coords)
                        {
                            free(anim->collision_entity[i]->instance[instance]->coords);
                            anim->collision_entity[i]->instance[instance]->coords = NULL;
                        }

                        free(anim->collision_entity[i]->instance[instance]);
                        anim->collision_entity[i]->instance[instance] = NULL;
                    }
                }

                free(anim->collision_entity[i]);
                anim->collision_entity[i] = NULL;
            }
        }
        free(anim->collision_entity);
        anim->collision_entity = NULL;
    }
    if(anim->shadow)
    {
        free(anim->shadow);
        anim->shadow = NULL;
    }
    if(anim->shadow_coords)
    {
        free(anim->shadow_coords);
        anim->shadow_coords = NULL;
    }
    if(anim->soundtoplay)
    {
        free(anim->soundtoplay);
        anim->soundtoplay = NULL;
    }
    
    if(anim->drawmethods)
    {
        for(i = 0; i < anim->numframes; i++)
        {
            if(anim->drawmethods[i])
            {
                free(anim->drawmethods[i]);
                anim->drawmethods[i] = NULL;
            }
        }
        free(anim->drawmethods);
        anim->drawmethods = NULL;
    }
}

#if 0
s_anim_list *anim_list_delete(s_anim_list *list, int index)
{
    if(list == NULL)
    {
        return NULL;
    }
    if(list->anim->model_index == index)
    {
        s_anim_list *next;
        next = list->next;
        free_anim(list->anim);
        free(list);
        --anims_loaded;
        return next;
    }
    list->next = anim_list_delete(list->next, index);
    return list;
}
#endif

void anim_list_delete(int index)
{
    s_anim_list head;
    head.next = anim_list;
    s_anim_list *list = &head;
    while(list && list->next)
    {
        if(list->next->anim->model_index == index)
        {
            s_anim_list *next = list->next->next;
            free_anim(list->next->anim);
            if(list->next == anim_list)
            {
                anim_list = next;
            }
            free(list->next);
            --anims_loaded;
            list->next = next;
        }
        else
        {
            list = list->next;
        }
    }
}

void free_anim(s_anim *anim)
{
    if(!anim)
    {
        return;
    }
    free_frames(anim);
    
	if (anim->projectile)
	{
		free(anim->projectile);
		anim->projectile = NULL;
	}
	if (anim->sub_entity_spawn)
	{
		free(anim->sub_entity_spawn);
		anim->sub_entity_spawn = NULL;
	}
	if (anim->sub_entity_summon)
	{
		free(anim->sub_entity_summon);
		anim->sub_entity_summon = NULL;
	}
	if (anim->weaponframe)
	{
		free(anim->weaponframe);
		anim->weaponframe = NULL;
	}
   
    free(anim);
}

int hasFreetype(s_model *m, e_ModelFreetype t)
{
    assert(m);
    return (m->freetypes & t) == t;
}

void addFreeType(s_model *m, e_ModelFreetype t)
{
    assert(m);
    m->freetypes |= t;
}

// Caskey, Damon V.
// 2020-03-30
// Load/unload sound IDs assigned to a list of 
// attack collisions.
void cache_attack_hit_sounds(s_collision_attack* head, int load)
{
    s_collision_attack* cursor;

    cursor = head;

    while (cursor != NULL && cursor->attack)
    {
        cachesound(cursor->attack->hitsound, load);
        cachesound(cursor->attack->blocksound, load);   
    
        cursor = cursor->next;
    }
}

void cache_model_sprites(s_model *m, int ld)
{
    int i, f;
    s_anim *anim;
    cachesprite(m->icon.def, ld);
    cachesprite(m->icon.die, ld);
    cachesprite(m->icon.get, ld);
    cachesprite(m->icon.mphigh, ld);
    cachesprite(m->icon.mplow, ld);
    cachesprite(m->icon.mpmed, ld);
    cachesprite(m->icon.pain, ld);
    cachesprite(m->icon.weapon, ld);
    cachesound(m->diesound, ld);
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        cachesprite(m->player_arrow[i].sprite, ld);
    }

    //if(hasFreetype(model, MF_ANIMLIST)){
    for(i = 0; i < max_animations; i++)
    {
        anim = m->animation[i];
        if(anim)
        {
            for(f = 0; f < anim->numframes; f++)
            {
                cachesprite(anim->sprite[f], ld);
                if(anim->soundtoplay)
                {
                    cachesound(anim->soundtoplay[f], ld);
                }
                
                // Hit sounds.
                if(anim->collision_attack && anim->collision_attack[f])
                {
                    cache_attack_hit_sounds(anim->collision_attack[f], ld);
                }
            }
        }
    }
}


// Unload single model from memory
int free_model(s_model *model)
{
    int i;
    if(!model)
    {
        return 0;
    }
    printf("Unload '%s' ", model->name);

    if(hasFreetype(model, MF_ANIMLIST))
    {
        anim_list_delete(model->index);
    }

    printf(".");

    if(hasFreetype(model, MF_COLOURMAP))
    {
        for(i = 0; i < model->maps_loaded; i++)
        {
            if(model->colourmap[i] != NULL)
            {
                free(model->colourmap[i]);
                model->colourmap[i] = NULL;
            }
        }
        if(model->colourmap)
        {
            free(model->colourmap);
        }
        model->colourmap = NULL;
        model->maps_loaded = 0;
    }

    printf(".");

    if(hasFreetype(model, MF_PALETTE) && model->palette)
    {
        free(model->palette);
        model->palette = NULL;
    }
    printf(".");
    if(hasFreetype(model, MF_WEAPONS) && model->weapon_properties.weapon_list && model->weapon_properties.weapon_state & WEAPON_STATE_HAS_LIST)
    {
        free(model->weapon_properties.weapon_list);
        model->weapon_properties.weapon_list = NULL;
    }
    printf(".");
    if(hasFreetype(model, MF_BRANCH) && model->branch)
    {
        free(model->branch);
        model->branch = NULL;
    }
    printf(".");
    if(hasFreetype(model, MF_ANIMATION) && model->animation)
    {
        free(model->animation);
        model->animation = NULL;
    }
    printf(".");
    if(hasFreetype(model, MF_DEFENSE) && model->defense)
    {
        defense_free_object(model->defense);
        model->defense = NULL;
    }
    printf(".");
    if(hasFreetype(model, MF_OFF_FACTORS) && model->offense)
    {
        offense_free_object(model->offense);
        model->offense = NULL;
    }
    printf(".");
    if(hasFreetype(model, MF_SPECIAL) && model->special)
    {
        free(model->special);
        model->special = NULL;
    }
    printf(".");
    if(hasFreetype(model, MF_SMARTBOMB) && model->smartbomb)
    {
        free(model->smartbomb);
        model->smartbomb = NULL;
    }
    printf(".");

    if (hasFreetype(model, MF_CHILD_FOLLOW) && model->child_follow)
    {
        free(model->child_follow);
        model->child_follow = NULL;
    }
    printf(".");

    if(hasFreetype(model, MF_SCRIPTS))
    {
        clear_all_scripts(model->scripts, 2);
        free_all_scripts(&model->scripts);
    }
    printf(".");

    model_cache[model->index].model = NULL;
    deleteModel(model->name);
    printf(".");

    printf("Done.\n");

    return models_loaded--;
}

// Unload all models and animations memory
void free_models()
{
    s_model *temp;

    while((temp = getFirstModel()))
    {
        free_model(temp);
    }

    // free animation ids
    if(animdowns)
    {
        free(animdowns);
        animdowns          = NULL;
    }
    if(animups)
    {
        free(animups);
        animups            = NULL;
    }
    if(animbackwalks)
    {
        free(animbackwalks);
        animbackwalks      = NULL;
    }
    if(animwalks)
    {
        free(animwalks);
        animwalks          = NULL;
    }
    if(animidles)
    {
        free(animidles);
        animidles          = NULL;
    }
    if(animspecials)
    {
        free(animspecials);
        animspecials       = NULL;
    }
    if(animattacks)
    {
        free(animattacks);
        animattacks        = NULL;
    }
    if(animfollows)
    {
        free(animfollows);
        animfollows        = NULL;
    }
    if(animpains)
    {
        free(animpains);
        animpains          = NULL;
    }
    if(animbackpains)
    {
        free(animbackpains);
        animbackpains      = NULL;
    }
    if(animfalls)
    {
        free(animfalls);
        animfalls          = NULL;
    }
    if(animbackfalls)
    {
        free(animbackfalls);
        animbackfalls      = NULL;
    }
    if(animrises)
    {
        free(animrises);
        animrises          = NULL;
    }
    if(animbackrises)
    {
        free(animbackrises);
        animbackrises          = NULL;
    }
    if(animriseattacks)
    {
        free(animriseattacks);
        animriseattacks    = NULL;
    }
    if(animbackriseattacks)
    {
        free(animbackriseattacks);
        animbackriseattacks    = NULL;
    }
    if(animblkpains)
    {
        free(animblkpains);
        animblkpains       = NULL;
    }
    if(animbackblkpains)
    {
        free(animbackblkpains);
        animbackblkpains       = NULL;
    }
    if(animdies)
    {
        free(animdies);
        animdies           = NULL;
    }
    if(animbackdies)
    {
        free(animbackdies);
        animbackdies        = NULL;
    }
}


s_anim *alloc_anim()
{
    static int animindex = 0;
    s_anim_list *curr = NULL, *head = NULL;
    curr = malloc(sizeof(*curr));
    curr->anim = malloc(sizeof(*curr->anim));
    if(curr == NULL || curr->anim == NULL)
    {
        return NULL;
    }
    memset(curr->anim, 0, sizeof(*curr->anim));
    curr->anim->index = animindex++;
    if(anim_list == NULL)
    {
        anim_list = curr;
        anim_list->next = NULL;
    }
    else
    {
        head = anim_list;
        anim_list = curr;
        anim_list->next = head;
    }
    ++anims_loaded;
    return anim_list->anim;
}

// Caskey, Damon V.
// 2020-04-09
// 
// Remove a meta data list from memory.
//
// TODO (2020-04-09): List not yet implemented. 
void meta_data_free_list(s_meta_data* head)
{
    free(head);
}

// Allocate a collision entity instance, copy
// property data if present, and return pointer.
s_collision_entity *collision_alloc_entity_instance(s_collision_entity *properties)
{
    s_collision_entity    *result;
    size_t              alloc_size;

    // Get amount of memory we'll need.
    alloc_size = sizeof(*result);

    // Allocate memory and get pointer.
    result = malloc(alloc_size);

    // If previous data is provided,
    // copy into new allocation.
    if(properties)
    {
        memcpy(result, properties, alloc_size);
    }

    // return result.
    return result;
}

// Allocate an empty collision entity list.
s_collision_entity **collision_alloc_entity_list()
{
    s_collision_entity **result;
    size_t             alloc_size;

    // Get amount of memory we'll need.
    alloc_size = sizeof(*result);

    // Allocate memory and get pointer.
    result = malloc(alloc_size);

    // Make sure the list is blank.
    memset(result, 0, alloc_size);

    // return result.
    return result;
}

/*
* Caskey, Damon V.
* 2022-06-22
* 
* Accept string and return function reference for 
* damage taking behavior.
*/
int (*takedamage_get_reference_from_argument(char* value))(struct entity* attacking_entity, s_attack* attack_object, int fall_flag, s_defense* defense_object)
{
    int (*result)(struct entity* attacking_entity, s_attack * attack_object, int fall_flag, s_defense * defense_object) = NULL;

    if (stricmp(value, "none") == 0)
    {
        result = NULL;
    }
    else if (stricmp(value, "arrow") == 0)
    {
        result = &arrow_takedamage;
    }
    else if (stricmp(value, "biker") == 0)
    {
        result = &biker_takedamage;
    }
    else if (stricmp(value, "common") == 0)
    {
        result = &common_takedamage;
    }
    else if (stricmp(value, "obstacle") == 0)
    {
        result = &obstacle_takedamage;
    }
    else if (stricmp(value, "player") == 0)
    {
        result = &player_takedamage;
    }

    return result;
}

/* **** Child Follow **** */
s_child_follow* child_follow_getsert_property(s_child_follow** acting_object)
{
    if (!(*acting_object))
    {
        *acting_object = child_follow_allocate_object();
    }

    return *acting_object;
}


s_child_follow* child_follow_allocate_object()
{
    s_child_follow* result;
    const size_t alloc_size = sizeof(*result);

    /* Allocate memory and get pointer. */
    result = calloc(1, alloc_size);

    *result = (s_child_follow){
        .direction_adjust_config = DIRECTION_ADJUST_TOWARD,
        .direction_adjust_range = {
            .base = {.max = MAX_INT, .min = MIN_INT },
            .x = {.max = MAX_INT, .min = MIN_INT },
            .y = {.max = MAX_INT, .min = MIN_INT },
            .z = {.max = MAX_INT, .min = MIN_INT }
        },
        .recall_animation = ANI_RESPAWN,
        .recall_range = {

            /*
            * X and Z  min / max defaults are overwritten
            * by Idle range X max for legacy compatability.
            */

            .base = {.max = MAX_INT, .min = MIN_INT },
            .x = {.max = MAX_INT, .min = MIN_INT },
            .y = {.max = MAX_INT, .min = MIN_INT },
            .z = {.max = MAX_INT, .min = MIN_INT }
        },
        .follow_range = {

            /*
            * X and Z  min / max defaults are overwritten
            * by Idle range X min for legacy compatability.
            */

            .base = {.max = MAX_INT, .min = MIN_INT },
            .x = {.max = MAX_INT, .min = MIN_INT },
            .y = {.max = MAX_INT, .min = MIN_INT },
            .z = {.max = MAX_INT, .min = MIN_INT }
        },
        .follow_run_range = {
            .base = {.max = MAX_INT, .min = MIN_INT },
            .x = {.max = MAX_INT, .min = MIN_INT },
            .y = {.max = MAX_INT, .min = MIN_INT },
            .z = {.max = MAX_INT, .min = MIN_INT }
        }
    };

    return result;
}

/* **** Child Spawn **** */

/*
* Caskey, Damon V.
* 2023-02-01
*
* Read a text argument for child color
* and get constant or color index.
*/
int child_spawn_get_color_from_argument(char* filename, char* command, char* value)
{
    e_model_copy result = MODEL_COPY_FLAG_NONE;

    if (stricmp(value, "parent_index") == 0)
    {
        result = COLORSET_INDEX_PARENT_INDEX;
    }
    else if (stricmp(value, "parent_table") == 0)
    {
        result = COLORSET_INDEX_PARENT_TABLE;
    }    
    else
    {
        result = getValidInt(value, filename, command);
    }

    return result;
}


/*
* Caskey, Damon V.
* 2022-05-27
*
* Read a text argument for child spawn config
* flag and output appropriate constant.
*/
e_child_spawn_config child_spawn_get_config_bit_from_argument(char* value)
{
    e_child_spawn_config result = CHILD_SPAWN_CONFIG_NONE;

    if (stricmp(value, "none") == 0)
    {
        result = CHILD_SPAWN_CONFIG_NONE;
    }
    else if (stricmp(value, "autokill_animation") == 0)
    {
        result = CHILD_SPAWN_CONFIG_AUTOKILL_ANIMATION;
    }
    else if (stricmp(value, "autokill_hit") == 0)
    {
        result = CHILD_SPAWN_CONFIG_AUTOKILL_HIT;
    }
    else if (stricmp(value, "behavior_bomb") == 0)
    {
        result = CHILD_SPAWN_CONFIG_BEHAVIOR_BOMB;
    }
    else if (stricmp(value, "behavior_shot") == 0)
    {
        result = CHILD_SPAWN_CONFIG_BEHAVIOR_SHOT;
    }
    else if (stricmp(value, "explode") == 0)
    {
        result = CHILD_SPAWN_CONFIG_EXPLODE;
    }
    else if (stricmp(value, "faction_damage_parameter") == 0)
    {
        result = CHILD_SPAWN_CONFIG_FACTION_DAMAGE_PARAMETER;
    }
    else if (stricmp(value, "faction_damage_parent") == 0)
    {
        result = CHILD_SPAWN_CONFIG_FACTION_DAMAGE_PARENT;
    }
    else if (stricmp(value, "faction_hostile_parameter") == 0)
    {
        result = CHILD_SPAWN_CONFIG_FACTION_HOSTILE_PARAMETER;
    }
    else if (stricmp(value, "faction_hostile_parent") == 0)
    {
        result = CHILD_SPAWN_CONFIG_FACTION_HOSTILE_PARENT;
    }
    else if (stricmp(value, "faction_indirect_parameter") == 0)
    {
        result = CHILD_SPAWN_CONFIG_FACTION_INDIRECT_PARAMETER;
    }
    else if (stricmp(value, "faction_indirect_parent") == 0)
    {
        result = CHILD_SPAWN_CONFIG_FACTION_INDIRECT_PARENT;
    }
    else if (stricmp(value, "faction_member_parameter") == 0)
    {
        result = CHILD_SPAWN_CONFIG_FACTION_MEMBER_PARAMETER;
    }
    else if (stricmp(value, "faction_member_parent") == 0)
    {
        result = CHILD_SPAWN_CONFIG_FACTION_MEMBER_PARENT;
    }
    else if (stricmp(value, "gravity_off") == 0)
    {
        result = CHILD_SPAWN_CONFIG_GRAVITY_OFF;
    }
    else if (stricmp(value, "launch_throw") == 0)
    {
        result = CHILD_SPAWN_CONFIG_LAUNCH_THROW;
    }
    else if (stricmp(value, "launch_toss") == 0)
    {
        result = CHILD_SPAWN_CONFIG_LAUNCH_TOSS;
    }
    else if (stricmp(value, "offense_parent") == 0)
    {
        result = CHILD_SPAWN_CONFIG_OFFENSE_PARENT;
    }
    else if (stricmp(value, "position_level") == 0)
    {
        result = CHILD_SPAWN_CONFIG_POSITION_LEVEL;
    }
    else if (stricmp(value, "takedamage_parameter") == 0)
    {
        result = CHILD_SPAWN_CONFIG_TAKEDAMAGE_PARAMETER;
    }
    else if (stricmp(value, "relationship_child") == 0)
    {
        result = CHILD_SPAWN_CONFIG_RELATIONSHIP_CHILD;
    }
    else if (stricmp(value, "relationship_owner") == 0)
    {
        result = CHILD_SPAWN_CONFIG_RELATIONSHIP_OWNER;
    }
    else if (stricmp(value, "relationship_parent") == 0)
    {
        result = CHILD_SPAWN_CONFIG_RELATIONSHIP_PARENT;
    }
    else
    {
        printf("\n\n Unknown child_spawn_config argument (%s). Ignoring.\n", value);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-05-27
*
* Read text argument lists, updates bit flags
* and outputs integer. Accepts existing
* argument as a default.
*/
e_child_spawn_config child_spawn_get_config_argument(ArgList* arglist, e_child_spawn_config config_current)
{
    e_child_spawn_config result = config_current;
    int i;
    char* value;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= child_spawn_get_config_bit_from_argument(value);
    }

    return result;
}



/*
* Caskey, Damon V.
* 2022-05-26
*
* Allocate a blank child spawn object
* and return its pointer. Does not
* allocate sub-objects.
*/
s_child_spawn* child_spawn_allocate_object()
{
    s_child_spawn* result;
    const size_t alloc_size = sizeof(*result);

    /* Allocate memory and get pointer. */
    result = calloc(1, alloc_size);

    result->aimove = AIMOVE_SPECIAL_DEFAULT;

    result->next = NULL;
    return result;
}

/*
* Caskey, Damon V.
* 2022-05-26
*
* Allocate new child spawn node and append it to
* end of child_spawn linked list. If no lists exists
* yet, the new node becomes head of a new list.
*
* First step in adding another child_spawn instance.
*
* Returns pointer to new node.
*/
s_child_spawn* child_spawn_append_node(s_child_spawn* head)
{
    /* Allocate node. */
    s_child_spawn* new_node = child_spawn_allocate_object();
    s_child_spawn* last = head;

    /*
    * New node is going to be the last node in
    * list, so set its next as NULL.
    */
    new_node->next = NULL;

    /*
    * If there wasn't already a list, the
    * new node is our head. We are done and
    * can return the new node pointer.
    */

    if (head == NULL)
    {
        head = new_node;

        return new_node;
    }

    /*
    * If we got here, there was already a
    * list in place. Iterate to its last
    * node.
    */

    while (last->next != NULL)
    {
        last = last->next;
    }

    /*
    * Populate existing last node's next
    * with new node pointer. The new node
    * is now the last node in list.
    */

    last->next = new_node;

    return new_node;
}

/*
* Caskey, Damon V
* 2022-05-26
*
* Allocate new child spawn list with same 
* values as source. Returns pointer to head 
* of new list.
*/
s_child_spawn* child_spawn_clone_list(s_child_spawn* source_head)
{
    s_child_spawn* source_cursor = NULL;
    s_child_spawn* clone_head = NULL;
    s_child_spawn* clone_node = NULL;

    /* Head is null? Get out now. */
    if (source_head == NULL)
    {
        return source_cursor;
    }

    source_cursor = source_head;

    while (source_cursor != NULL)
    {
        clone_node = child_spawn_append_node(clone_head);
        
        /*
        * Populate head if NULL so we
        * have one for the next cycle.
        */
        if (clone_head == NULL)
        {
            clone_head = clone_node;
        }

        /* 
        * Copy the values. We start with
        * a memcopy to get most properties
        * and then manually update the
        * rest as needed.
        */

        *clone_node = *source_cursor;
                
        /* 
        * Clear clone's next value.
        */
        clone_node->next = NULL;
        
        
        source_cursor = source_cursor->next;
    }

    return clone_head;
}

/*
* Caskey, Damon V
* 2020-03-10
*
* Send all child spawn list data to log for debugging.
*/
void child_spawn_dump_list(s_child_spawn* head)
{
    printf("\n\n -- Child Spawn List (head: %p) Dump --", head);

    s_child_spawn* cursor;
    int count = 0;

    cursor = head;

    while (cursor != NULL)
    {
        count++;

        printf("\n\n\t Node: %p", cursor);
              
        child_spawn_dump_object(cursor);

        cursor = cursor->next;
    }

    printf("\n\n %d nodes.", count);
    printf("\n\n -- Child Spawn List (head: %p) dump complete! -- \n", head);
}

/*
* Caskey, Damon V
* 2020-03-10
*
* Send child spawn object data to log for debugging.
*/
void child_spawn_dump_object(s_child_spawn* object)
{
    const int space_label = 20;

    printf("\n\n -- Child Spawn object (%p) Dump --", object);   

    printf("\n\t\t %-*s %d", space_label, "->aimove:", object->aimove);
    printf("\n\t\t %-*s %d", space_label, "->autokill:", object->autokill);
    printf("\n\t\t %-*s %p", space_label, "->bind:", object->bind);

    if (object->bind)
    {
        //bind_dump_object(cursor->bind);
    }
    
    printf("\n\t\t %-*s %d", space_label, "->candamage:", object->candamage);
    printf("\n\t\t %-*s %d", space_label, "->color:", object->color);
    printf("\n\t\t %-*s %d", space_label, "->config:", object->config);
    printf("\n\t\t %-*s %d", space_label, "->direction_adjust:", object->direction_adjust);
    printf("\n\t\t %-*s %d", space_label, "->hostile:", object->hostile);
    printf("\n\t\t %-*s %d", space_label, "->index:", object->index);
    printf("\n\t\t %-*s %d", space_label, "->model_index:", object->model_index);
    printf("\n\t\t %-*s %d", space_label, "->move_config_flags:", object->move_config_flags);
    printf("\n\t\t %-*s %p", space_label, "->next:", object->next);
    printf("\n\t\t %-*s %d, %d, %d", space_label, "->position:", object->position.x, object->position.y, object->position.z);
    printf("\n\t\t %-*s %d", space_label, "->projectilehit:", object->projectilehit);
    printf("\n\t\t %-*s %p", space_label, "->takedamage:", object->takedamage);
    printf("\n\t\t %-*s %f, %f, %f", space_label, "->velocity:", object->velocity.x, object->velocity.y, object->velocity.z);
    

    printf("\n\n -- Child Spawn object (%p) dump complete! -- \n", object);
}

/*
* Caskey, Damon V.
* 2022-05-26
*
* Find a child spawn node by index and return 
* pointer, or NULL if no match found.
*/
s_child_spawn* child_spawn_find_node_index(s_child_spawn* head, int index)
{
    s_child_spawn* current = NULL;

    /*
    * Starting from head node, iterate through
    * all nodes and compare their index
    * property to index argument.
    * 
    * If we found a match, return the node
    * pointer. 
    */

    current = head;

    while (current != NULL)
    {        
        if (current->index == index)
        {
            return current;
        }
        
        current = current->next;
    }

    /*
    * If we got here, find failed.
    * Just return NULL.
    */
    return NULL;
}

/*
* Caskey, Damon V.
* 2022-05-26
*
* Clear a child spawn linked list from memory.
*/
void child_spawn_free_list(s_child_spawn* head)
{
    s_child_spawn* cursor = NULL;
    s_child_spawn* next = NULL;

    /*
    * Starting from head node, iterate through
    * all nodes and free them.
    */
    cursor = head;

    while (cursor != NULL)
    {
        /*
        * We still need the next member after we
        * delete object, so we'll store it in a 
        * temp var.
        */

        next = cursor->next;

        /* Free the current object. */
        child_spawn_free_node(cursor);

        cursor = next;
    }
}

/*
* Caskey, Damon V.
* 2022-05-26
*
* Clear a single child spawn object from memory.
* Note this does NOT remove node from list.
* Be careful not to create a dangling pointer!
*/
void child_spawn_free_node(s_child_spawn* target)
{
    /* Free sub objects. */

    if (target->bind)
    {
        //bind_free_object(target->bind);
        //target->attack = NULL;
    }        

    /* Free the structure. */
    free(target);
}

/*
* 2020-02-23
* Caskey, Damon V
*
* Get pointer to object for modification. Used when
* loading a model and reading in attack properties.
*/
s_child_spawn* child_spawn_upsert_property(s_child_spawn** head, int index)
{
    // printf("\n\t child_spawn_upsert_bind_property(%p, %d)", *head, index);

    s_child_spawn* temp_object_current;

    /*
    * 1. First we need to know index.
                *  -- temp_collision_index

                * 2. Look for index and get pointer (found or allocated).

                * Get the node we want to work on by searching
                * for a matched index. In most cases, this will
                * just be the head node.
    */

    temp_object_current = child_spawn_upsert_index(*head, index);

    /*
    * If head is NULL, this must be the first allocated
    * collision for current frame. Populate head with
    * current so we have a head for the next pass.
    */

    if (*head == NULL)
    {
        *head = temp_object_current;
    } 

    /* Return pointer to the attack structure. */
    return temp_object_current;
}

/*
* Caskey, Damon V.
* 2022-05-27
*
* Find a child spawn node by index, or append a new node
* with target index if no match is found. Returns pointer
* to found or appended node.
*/
s_child_spawn* child_spawn_upsert_index(s_child_spawn* head, int index)
{
    s_child_spawn* result = NULL;

    /* Run index search. */
    result = child_spawn_find_node_index(head, index);

    /*
    * If we couldn't find an index match, lets add
    * a node and apply the index we wanted.
    */
    if (!result)
    {
        result = child_spawn_append_node(head);
        result->index = index;
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-05-27
*
* Allocate and apply child spawn settings to target frame.
*/
void child_spawn_initialize_frame_property(s_addframe_data* data, ptrdiff_t frame)
{
    s_child_spawn* temp_object;
    size_t memory_size;

    if (!data->child_spawn)
    {
        return;
    }
    
    /*
    * If object is not allocated yet, we need to allocate
    * an array of object pointers (one element for each
    * animation frame). If the frame has an object, its
    * object property is populated with pointer to head
    * of a linked list of objects.
    */
    if (!data->animation->child_spawn)
    {
        memory_size = data->framecount * sizeof(*data->animation->child_spawn);

        data->animation->child_spawn = malloc(memory_size);
        memset(data->animation->child_spawn, 0, memory_size);
    }

    //printf("\n\n child_spawn_initialize_frame_property");

    child_spawn_dump_list(data->child_spawn);

    /*
    * Clone source list and populate frame's object
    * property with the pointer to clone list head.
    */
    temp_object = child_spawn_clone_list(data->child_spawn);
        
    /* Frame object property is head of object list. */
    data->animation->child_spawn[frame] = temp_object;

    
    //printf("\n\t data->animation->child_spawn[%d]: %p \n", frame, data->animation->child_spawn[frame]);
}

/*
* Caskey, Damon V.
* 2022-05-29
* 
* Accept head of a lisst of child spawns.
* Iterate through list and apply properties 
* to spawn child entities.
*/
void child_spawn_execute_list(s_child_spawn* head, entity* parent)
{
    s_child_spawn* cursor = NULL;
    s_child_spawn* next = NULL;

    /*
    * Starting from head node, iterate through
    * all nodes and free them.
    */
    cursor = head;

    while (cursor != NULL)
    {
        /*
        * We still need the next member after we
        * delete object, so we'll store it in a
        * temp var.
        */

        next = cursor->next;

        /* Free the current object. */
        child_spawn_execute_object(cursor, parent);

        cursor = next;
    }
}

/*
* Caskey, Damon V.
* 2022-05-29
* 
* Accept pointer to node in list of child
* spawns. Apply properties to to spawn
* a child entity. Returns pointer to
* spawned entity.
*/
entity* child_spawn_execute_object(s_child_spawn* object, entity* parent)
{
    printf("\n\n child_spawn_execute_object(object: %p, parent: %p)", object, parent);
    
    int i = 0;
    entity* child_entity = NULL;
    s_axis_principal_float position;
    e_direction direction = DIRECTION_RIGHT;
    s_model* child_model = NULL;

    if (object->model_index == MODEL_INDEX_NONE || !parent)
    {
        return NULL;
    }  
    
    child_model = model_cache[object->model_index].model;

    printf("\n\t object->model_index: %d", object->model_index);
    printf("\n\t child_model: %p", child_model);
    
    
    position.x = parent->position.x;
    position.y = parent->position.y + object->position.y;
    position.z = parent->position.z + object->position.z;

    printf("\n\t Child: x: %f, y: %f, z: %f", position.x, position.y, position.z);
    
    /*
    * Spawn entity using model pointer. If the spawn 
    * fails then we exit immediately.
    */

    child_entity = spawn(position.x, position.z, position.y, direction, NULL, MODEL_INDEX_NONE, child_model);

    printf("\n\t child_entity: %p", child_entity);

    if (!child_entity)
    {
        return NULL;
    }

    /*
    * Let's set up the spawn position. Reverse X when
    * parent faces left.
    *
    * Apply default X position if creator did not give
    * us a value.
    */
    direction = direction_get_adjustment_result(child_entity, parent, object->direction_adjust);

    printf("\n\t direction: %d", direction);
    printf("\n\t Parent: x: %f, y: %f, z: %f", parent->position.x, parent->position.y, parent->position.z);

    if (direction == DIRECTION_RIGHT && object->config & ~(CHILD_SPAWN_CONFIG_POSITION_LEVEL | CHILD_SPAWN_CONFIG_POSITION_SCREEN))
    {
        position.x = parent->position.x + object->position.x;
    }
    else
    {
        position.x = parent->position.x - object->position.x;
    }

    child_entity->direction = direction;
    child_entity->position.x = position.x;
    

    child_entity->spawntype = SPAWN_TYPE_CHILD;

    /*
    * Populate relationship properties as
    * requested.
    */

    if (object->config & CHILD_SPAWN_CONFIG_RELATIONSHIP_CHILD)
    {
        parent->subentity = parent;
    }

    if (object->config & CHILD_SPAWN_CONFIG_RELATIONSHIP_OWNER)
    {
        child_entity->owner = parent;
    }

    if (object->config & CHILD_SPAWN_CONFIG_RELATIONSHIP_PARENT)
    {
        child_entity->parent = parent;
    }

    /*
    * Handle initial velocity. We will start with
    * an initial velocity. Then may copy velocity
    * to child's speed settings, or apply a toss 
    * effect (to throw in an arc assuming gravity).
    */

    child_entity->velocity = object->velocity;
        
    if (object->config & CHILD_SPAWN_CONFIG_LAUNCH_THROW)
    {
        /* For throw effect, we copy velocity to child's model speed. */
        child_entity->modeldata.speed = object->velocity;
    }

    if (object->config & CHILD_SPAWN_CONFIG_LAUNCH_TOSS && object->velocity.y)
    {
        /* To toss, we use toss function with Y velocity. */
        toss(child_entity, object->velocity.y);
    }

    /*
    * Set up basic behavior packages.
    */

    if (object->config & CHILD_SPAWN_CONFIG_BEHAVIOR_SHOT)
    {
        child_entity->modeldata.aimove = AIMOVE1_ARROW;
        child_entity->attacking = ATTACKING_ACTIVE;
        //->takedamage = arrow_takedamage;
        child_entity->modeldata.aiattack = AIATTACK1_NOATTACK;
        child_entity->nograb = 1;                
    }

    if (object->config & CHILD_SPAWN_CONFIG_BEHAVIOR_BOMB)
    {
        child_entity->modeldata.aimove = AIMOVE1_BOMB;
        child_entity->attacking = ATTACKING_ACTIVE;
        //child_entity->takedamage = common_takedamage;
        child_entity->modeldata.aiattack = AIATTACK1_NOATTACK;
        child_entity->nograb = 1;
        child_entity->toexplode |= (EXPLODE_PREPARE_TOUCH | EXPLODE_PREPARE_GROUND);               
    }

    //if (object->config & CHILD_SPAWN_CONFIG_TAKEDAMAGE_PARAMETER)
    //{
    //    child_entity->takedamage = object->takedamage;
    //}

    /*
    * If requested, apply AI Flags.
    */
    if (!(object->aimove & AIMOVE_SPECIAL_DEFAULT))
    {
        child_entity->modeldata.aimove = object->aimove;
    }

    printf("\n\t child_entity->modeldata.aimove: %d", child_entity->modeldata.aimove);

    /*
    * Copy offense values from parent offense settings
    * to projectile enity if requested.
    */ 
    if (object->config & CHILD_SPAWN_CONFIG_OFFENSE_PARENT)
    {
        /*
        * Parent might not have offense. In that 
        * case, if the child also has no offense
        * we can just do nothing and not waste
        * any memory. If the child does have an
        * offense, we'll need to overwrite it
        * with default so it matches parent.
        */
        
        if (!parent->offense)
        {
            if (child_entity->offense)
            {
                memcpy(child_entity->offense, &default_offense, sizeof(*child_entity->offense) * max_attack_types);
            }
        }
        else
        {
            /*
            * Make sure child has memory allocated.
            */

            if (!child_entity->offense)
            {
                child_entity->offense = offense_allocate_object();
            }
                        
            memcpy(child_entity->offense, parent->offense, sizeof(*child_entity->offense) * max_attack_types);
            
        }
    }
    
    /* Apply color adjustment. */
    if (object->color == COLORSET_INDEX_PARENT_INDEX)
    {        
        for (i = 0; i < parent->modeldata.maps_loaded; i++)
        {
            if (parent->colourmap == parent->modeldata.colourmap[i])
            {
                ent_set_colourmap(child_entity, i);
                break;
            }
        }
    }
    else if (object->color == COLORSET_INDEX_PARENT_TABLE)
    {
        child_entity->colourmap = parent->colourmap;
    }
    else
    {
        ent_set_colourmap(child_entity, object->color);
    }

    /* Populate common behavior flags. */
    child_entity->think = common_think;
    child_entity->nextthink = _time + 1;
    child_entity->trymove = NULL;    
    child_entity->takeaction = NULL;
    child_entity->speedmul = 2;

    /* Populate autokill. */
    if (object->config & CHILD_SPAWN_CONFIG_AUTOKILL_ANIMATION)
    {
        child_entity->autokill |= AUTOKILL_ANIMATION_COMPLETE;
    }

    if (object->config & CHILD_SPAWN_CONFIG_AUTOKILL_HIT)
    {
        child_entity->autokill |= AUTOKILL_ATTACK_HIT;
    }

    /* 
    * Handle type based damage and hostilty. Copy from
    * parent or parameter on request.
    * 
    * If player damage turned off, remove player type.
    */

    if (object->config & CHILD_SPAWN_CONFIG_FACTION_DAMAGE_PARAMETER)
    {
        child_entity->faction.type_damage_direct = object->candamage;
    }

    if (object->config & CHILD_SPAWN_CONFIG_FACTION_DAMAGE_PARENT)
    {
        child_entity->faction.type_damage_direct = parent->faction.type_damage_direct;
    }

    if (object->config & CHILD_SPAWN_CONFIG_FACTION_HOSTILE_PARAMETER)
    {
        child_entity->faction.type_hostile = object->hostile;
    }

    if (object->config & CHILD_SPAWN_CONFIG_FACTION_HOSTILE_PARENT)
    {
        child_entity->faction.type_hostile = parent->faction.type_hostile;
    }

    if (object->config & CHILD_SPAWN_CONFIG_FACTION_INDIRECT_PARAMETER)
    {
        child_entity->faction.type_damage_indirect = object->projectilehit;
    }

    if (object->config & CHILD_SPAWN_CONFIG_FACTION_INDIRECT_PARENT)
    {
        child_entity->faction.type_damage_indirect = parent->faction.type_damage_indirect;
    }
    
    if ((parent->modeldata.type & TYPE_PLAYER) && ((level && level->nohit == DAMAGE_FROM_PLAYER_OFF) || savedata.mode))
    {
        child_entity->faction.type_hostile &= ~TYPE_PLAYER;
        child_entity->faction.type_damage_direct &= ~TYPE_PLAYER;
    }

    printf("\n\t child_entity->faction.type_hostile: %d", child_entity->faction.type_hostile);
    printf("\n\t child_entity->faction.type_damage_direct: %d", child_entity->faction.type_damage_direct);
    printf("\n\t child_entity->faction.type_damage_indirect: %d", child_entity->faction.type_damage_indirect);

    /* 
    * Apply any move constraints. 
    */

    if (object->config & CHILD_SPAWN_CONFIG_MOVE_CONFIG_PARAMETER)
    {
        child_entity->modeldata.move_config_flags = object->move_config_flags;
    }

    if (object->config & CHILD_SPAWN_CONFIG_MOVE_CONFIG_PARENT)
    {
        child_entity->modeldata.move_config_flags = parent->modeldata.move_config_flags;
    }

    printf("\n\t child_entity->modeldata.move_config_flags: %d", child_entity->modeldata.move_config_flags);

    /*
    * Execute event scripts.
    */

    // execute_on_pre_child_spawn_script(parent, child_entity, object);
    execute_onspawn_script(child_entity);
    // execute_on_post_child_spawn_script(parent, child_entity, object);

    printf("\n\t return: %p", child_entity);

    return child_entity;
}


/* **** Collision Attack **** */

/*
* Caskey, Damon V.
* 2020-02-10
*
* Allocate a blank collision object 
* and return its pointer. Does not 
* allocate sub-objects (attack, body, etc.).
*/
s_collision_attack* collision_attack_allocate_object()
{
    s_collision_attack* result;
    size_t       alloc_size;

    /* Get amount of memory we'll need. */
    alloc_size = sizeof(*result);

    /* Allocate memoryand get pointer. */
    result = malloc(alloc_size);

    /*
    * Make sure the data members are 
    * zero'd and that "next" member 
    * is NULL.
    */
    
    memset(result, 0, alloc_size);

    result->next = NULL;

    
    return result;
}

/*
* Caskey, Damon V.
* 2020-02-10
*
* Allocate new collision node and append it to
* end of collision linked list. If no lists exists
* yet, the new node becomes head of a new list.
*
* First step in adding another collision instance
* of any type (body, space, or attack).
*
* Returns pointer to new node.
*/
s_collision_attack* collision_attack_append_node(struct s_collision_attack* head)
{
    /* Allocate node. */
    struct s_collision_attack* new_node = NULL;
    struct s_collision_attack* last = NULL;

    /*
    * Allocate memory and get pointer for new
    * collision node, then default last to head.
    */
    new_node = collision_attack_allocate_object();
    last = head;

    /*
    * New node is going to be the last node in
    * list, so set its next as NULL.
    */
    new_node->next = NULL;

    /*
    * If there wasn't already a list, the
    * new node is our head. We are done and
    * can return the new node pointer.
    */

    if (head == NULL)
    {
        head = new_node;

        return new_node;
    }

    /*
    * If we got here, there was already a
    * list in place. Iterate to its last
    * node.
    */

    while (last->next != NULL)
    {
        last = last->next;
    }

    /*
    * Populate existing last node's next
    * with new node pointer. The new node
    * is now the last node in list.
    */

    last->next = new_node;

    return new_node;
}

/*
* Caskey, Damon V
* 2020-03-10
*
* Return FALSE if a collision object
* has coordinates set, FALSE otherwise.
*/
int collision_attack_check_has_coords(s_collision_attack* target)
{
    /*
    * If target missing or coordinates
    * are not allocated then return FALSE.
    */

    if (!target)
    {
        return FALSE;
    }

    if (!target->coords)
    {
        return FALSE;
    }

    /*
    * If any one coordinate property has a value
    * then return TRUE instantly.
    */
    if (target->coords->x || target->coords->y || target->coords->height || target->coords->width)
    {
        return TRUE;
    }

    return FALSE;
}

/*
* Caskey, Damon V
* 2020-03-09
*
* Allocate new collision list with same values as source.
* Returns pointer to head of new list.
*/
s_collision_attack* collision_attack_clone_list(s_collision_attack* source_head, int check_coords)
{
    s_collision_attack* source_cursor = NULL;
    s_collision_attack* clone_head = NULL;
    s_collision_attack* clone_node = NULL;

    /* Head is null? Get out now. */
    if (source_head == NULL)
    {
        return source_cursor;
    }

    source_cursor = source_head;    

    while (source_cursor != NULL)
    {
        /*
        * If check coords flag set, we only clone
        * collisions with valid coordinates.
        */

        if (check_coords && !collision_attack_check_has_coords(source_cursor))
        {
            source_cursor = source_cursor->next;
            continue;
        }

        /*
        * Allocate the clone node we will copy
        * values to. 
        */

        clone_node = collision_attack_append_node(clone_head);
        
        /*
        * Populate head if NULL so we
        * have one for the next cycle.
        */

        if (clone_head == NULL)
        {
            clone_head = clone_node;
        }

        /* Copy the values. */
        clone_node->attack = attack_clone_object(source_cursor->attack);

        if (source_cursor->coords != NULL)
        {
            clone_node->coords = collision_allocate_coords(source_cursor->coords);
        }

        clone_node->index = source_cursor->index;
        clone_node->meta_data = source_cursor->meta_data;
        clone_node->meta_tag = source_cursor->meta_tag;

        source_cursor = source_cursor->next;
    }

    return clone_head;
}

/*
* Caskey, Damon V
* 2020-03-10
*
* Send all collision list data to log for debugging.
*/
void collision_attack_dump_list(s_collision_attack* head)
{
    printf("\n\n -- Collision Attack List (head: %p) Dump --", head);

    s_collision_attack* cursor;
    int count = 0;

    cursor = head;

    while (cursor != NULL)
    {
        count++;

        printf("\n\n\t Node: %p", cursor);
        printf("\n\t\t ->attack: %p", cursor->attack);

        if (cursor->attack)
        {
            attack_dump_object(cursor->attack);
        }        

        printf("\n\t\t ->coords: %p", cursor->coords);

        if (cursor->coords)
        {
            printf("\n\t\t\t ->height: %d", cursor->coords->height);
            printf("\n\t\t\t ->width: %d", cursor->coords->width);
            printf("\n\t\t\t ->x: %d", cursor->coords->x);
            printf("\n\t\t\t ->y: %d", cursor->coords->y);
            printf("\n\t\t\t ->z_background: %d", cursor->coords->z_background);
            printf("\n\t\t\t ->z_foreground: %d", cursor->coords->z_foreground);
        }
        
        printf("\n\t\t ->index: %d", cursor->index);
        printf("\n\t\t ->meta_data: %p", cursor->meta_data);
        printf("\n\t\t ->meta_tag: %d", cursor->meta_tag);
        printf("\n\t\t ->next: %p", cursor->next);

        cursor = cursor->next;
    }

    printf("\n\n %d nodes.", count);
    printf("\n\n -- Collision attack list (head: %p) dump complete! -- \n", head);
}

/*
* Caskey, Damon V.
* 2020-03-30
*
* Return first valid attack collision in animation
* frame that has no_block enabled. If no match
* found, return NULL.
*/
s_collision_attack* collision_attack_find_no_block_on_frame(s_anim* animation, int frame, int block)
{
    s_collision_attack* cursor;

    /* Return NULL if there's no collision on this frame. */
    if (!animation->collision_attack || !animation->collision_attack[frame])
    {
        return NULL;
    }

    cursor = animation->collision_attack[frame];

    /* Check all collisions for attack type. */
    while (cursor != NULL)
    {        
        if (cursor->attack->no_block < block)
        {
            return cursor;
        }

        cursor = cursor->next;
    }

    /* Loop didn't find a collision with attacking type. */
    return NULL;
}

/*
* Caskey, Damon V.
* 2020-02-17
*
* Find a collision node by index and return pointer, or
* NULL if no match found.
*/
s_collision_attack* collision_attack_find_node_index(s_collision_attack* head, int index)
{
    s_collision_attack* current = NULL;

    /*
    * Starting from head node, iterate through
    * all collision nodes and free them.
    */
    current = head;

    while (current != NULL)
    {
        /* If we found a collision index match, return the pointer. */
        if (current->index == index)
        {
            return current;
        }

        /* Go to next node. */
        current = current->next;
    }

    /*
    * If we got here, find failed.
    * Just return NULL.
    */
    return NULL;
}

/*
* Caskey, Damon V.
* 2020-02-17
*
* Clear a collision linked list from memory.
*/
void collision_attack_free_list(s_collision_attack* head)
{
    s_collision_attack* cursor = NULL;
    s_collision_attack* next = NULL;

    /*
    * Starting from head node, iterate through
    * all collision nodes and free them.
    */
    cursor = head;

    while (cursor != NULL)
    {
        /*
        * We still need the next member after we
        * delete collision object, so we'll store
        * it in a temp var.
        */

        next = cursor->next;

        /* Free the current collision object. */
        collision_attack_free_node(cursor);

        cursor = next;
    }
}

/*
* Caskey, Damon V.
* 2020-02-17
*
* Clear a single collision object from memory.
* Note this does NOT remove node from list.
* Be careful not to create a dangling pointer!
*/
void collision_attack_free_node(s_collision_attack* target)
{
    /* Free sub objects. */

    if (target->attack)
    {
        attack_free_object(target->attack);
        target->attack = NULL;
    }

    if (target->coords)
    {
        free(target->coords);
        target->coords = NULL;
    }

    /* To Do: Free tag function. */
    if (target->meta_data)
    {
        meta_data_free_list(target->meta_data);
        target->meta_data = NULL;
    }

    /* Free the collision structure. */
    free(target);
}

/*
* Caskey, Damon V.
* 2020-03-07
*
* Allocate and apply collision settings to target frame.
*/
void collision_attack_initialize_frame_property(s_addframe_data* data, ptrdiff_t frame)
{
    s_collision_attack* temp_collision;
    size_t memory_size;

    if (!data->collision)
    {
        return;
    }

    /*
    * If collision is not allocated yet, we need to allocate
    * an array of collision pointers (one element for each
    * animation frame). If the frame has a collision, its
    * collision property is populated with pointer to head
    * of a linked list of collision objects.
    */
    if (!data->animation->collision_attack)
    {
        memory_size = data->framecount * sizeof(*data->animation->collision_attack);

        data->animation->collision_attack = malloc(memory_size);
        memset(data->animation->collision_attack, 0, memory_size);
    }

    /*
    * Clone source list and populate frame's collision
    * property with the pointer to clone list head.
    */
    temp_collision = collision_attack_clone_list(data->collision, 1);

    /* Apply final adjustments to any collision coordinates. */
    collision_attack_prepare_coordinates_for_frame(temp_collision, data->model, data);

    /* Frame collision property is head of collision list. */
    data->animation->collision_attack[frame] = temp_collision;
}

/*
* Caskey, Damon V.
* 2020-03-10
*
* Apply final adjustments to collision coordinates
* with defaults settings for required properties 
* author did not provide values for.
*/
void collision_attack_prepare_coordinates_for_frame(s_collision_attack* collision_head, s_model* model, s_addframe_data* add_frame_data)
{
    s_collision_attack* cursor;
    s_hitbox* coords;

    cursor = collision_head;

    while (cursor != NULL)
    {
        coords = cursor->coords;

        if (coords)
        {
            /* Position includes offset.Size includes position. */
            coords->x = coords->x - add_frame_data->offset->x;
            coords->y = coords->y - add_frame_data->offset->y;
            coords->width = coords->width + coords->x;
            coords->height = coords->height + coords->y;

            /*
            * We may need to apply a stand in for Z depth. 
            * Legacy behavior calculates based on the model's
            * grabdistance property. IMO it's not very logical 
            * and doesn't allow creators to use 0 values, but 
            * we need to keep it for backward compatabilty.
            */
            
            if (!coords->z_background && !coords->z_foreground)
            {
                coords->z_background = coords->z_foreground = (int)(model->grabdistance / 3 + 1);
            }            
        }

        cursor = cursor->next;
    }
}

/*
* Caskey, Damon V.
* 2020-03-10
*
* Receives a reference (pointer to pointer) to the head
* of a list, deletes all occurrence of undefined collision
* coordinates (no coords pointer or X/Y/H/W are all 0).
*
* This is to replicate legacy behavior of removing a collision
* box during read in from text when all 0 values are provided
* by author.
*
* Reference pointer is swapped for new head pointer if head
* is deleted.
*/
void collision_attack_remove_undefined_coordinates(s_collision_attack** head)
{
    s_collision_attack* cursor = NULL;
    s_collision_attack* prev = NULL;

    /* Start with head. */
    cursor = *head;
    prev = *head;

    /*
    * If head node or mutiple nodes lack defined collision
    * cordinates.
    */
    while (cursor != NULL && !collision_attack_check_has_coords(cursor))
    {
        /* Update head value. */
        *head = cursor->next;

        /* Free collision memory. */
        collision_attack_free_node(cursor);

        /* Change cursor to head. */
        cursor = *head;
    }

    /* Delete occurrences other than head. */
    while (cursor != NULL)
    {
        /*
        * Search for and delete nodes without collision
        * coordinates defined. Keep track of the previous
        * node as we need to change 'prev->next'.
        */
        while (cursor != NULL && collision_attack_check_has_coords(cursor))
        {
            prev = cursor;
            cursor = cursor->next;

        }

        /*
        * If we didn't find any blank coordinate sets
        * then just get out now.
        */
        if (cursor == NULL)
        {
            return;
        }

        /* Unlink the node from linked list. */
        prev->next = cursor->next;

        /* Free collision memory. */
        collision_attack_free_node(cursor);

        /* Update cursor for next iteration of outer loop.  */
        cursor = prev->next;
    }
}

/*
* 2020-02-23
* Caskey, Damon V.
*
* Used when building a list of attack objects on
* a frame during model load. Locates or allocates
* an object matching index parameter, and returns
* the resulting object pointer.
*/
s_hitbox* collision_attack_upsert_coordinates_property(s_collision_attack** head, int index)
{
    s_collision_attack* temp_collision_current;

    /*
    * 1. First we need to know index.
    *  -- temp_collision_index

    * 2. Look for index and get pointer (found or allocated).

    * Get the node we want to work on by searching
    * for a matched index. In most cases, this will
    * just be the head node.
    */
    temp_collision_current = collision_attack_upsert_index(*head, index);

    /*
    * If head is NULL, this must be the first allocated
    * collision for current frame. Populate head with
    * current so we have a head for the next pass.
    */
    if (*head == NULL)
    {
        *head = temp_collision_current;
    }

    /* 3. Get attack pointer (find or allocate). */

    /* Have collision coordinates ? If not we'll need to allocate them. */
    if (!temp_collision_current->coords)
    {
        temp_collision_current->coords = collision_allocate_coords(temp_collision_current->coords);
    }

    /* Return pointer to the coords structure. */
    return temp_collision_current->coords;
}

/*
* Caskey, Damon V.
* 2020-02-17
*
* Find a collision node by index, or append a new node
* with target index if no match is found. Returns pointer
* to found or appended node.
*/
s_collision_attack* collision_attack_upsert_index(s_collision_attack* head, int index)
{
    s_collision_attack* result = NULL;

    /* Run index search. */
    result = collision_attack_find_node_index(head,index);

    /*
    * If we couldn't find an index match, lets add
    * a node and apply the index we wanted.
    */
    if (!result)
    {
        result = collision_attack_append_node(head);
        result->index = index;
    }

    return result;
}

/*
* 2020-02-23
* Caskey, Damon V
*
* Get pointer to attack object for modification. Used when
* loading a model and reading in attack properties.
*
* 1. Receive pointer to head node of collision list. If
* the head node is NULL a new collision list is allocated
* and the head property value is populated with head node.
*
* 2. Search collision list for an attack enabled node
* with index matching received index property. New node
* allocated if not found. See collision_attack_upsert_index().
*
* 3. Find or allocate attack object on collision node.
* Returns pointer to attack object.
*/
s_attack* collision_attack_upsert_property(s_collision_attack** head, int index)
{
    // printf("\n\t collision_attack_upsert_property(%p, %d)", *head, index);

    s_collision_attack* temp_collision_current;

    /*
    * 1. First we need to know index.
                *  -- temp_collision_index

                * 2. Look for index and get pointer (found or allocated).

                * Get the node we want to work on by searching
                * for a matched index. In most cases, this will
                * just be the head node.
    */

    temp_collision_current = collision_attack_upsert_index(*head, index);

    /*
    * If head is NULL, this must be the first allocated
    * collision for current frame. Populate head with
    * current so we have a head for the next pass.
    */

    if (*head == NULL)
    {
        *head = temp_collision_current;
    }

    /* 3. Get attack pointer (find or allocate). */

    // printf("\n\t\t temp_collision_current->attack (pre check): %p", temp_collision_current->attack);

    /* Have an attack? if not we'll need to allocate it.*/
    if (!temp_collision_current->attack)
    {
        temp_collision_current->attack = attack_allocate_object();
    }

    // printf("\n\t\t result: %p", temp_collision_current->attack);

    /* Return pointer to the attack structure. */
    return temp_collision_current->attack;
}

/*
* 2020-03-10
* Caskey, Damon V
*
* Create or update a recursive attack property.
* Same principal as collision_attack_upsert_property.
*/
s_damage_recursive* collision_attack_upsert_recursive_property(s_collision_attack** head, int index)
{
    s_attack* cursor;

    /*
    * Run attack upsert to make sure we have a valid
    * collision node for requested index, and that
    * it has an attack property.
    */
    cursor = collision_attack_upsert_property(head, index);


    /* Have a recursive property? If not we'll need to allocate it. */
    if (!cursor->recursive)
    {
        cursor->recursive = recursive_damage_allocate_object();
    }

    /* Return pointer to the recrisve structure. */
    return cursor->recursive;
}


/* **** Collision Body **** */

/*
* Caskey, Damon V.
* 2021-08-22
*
* Allocate a blank collision object
* and return its pointer. Does not
* allocate sub-objects.
*/
s_collision_body* collision_body_allocate_object()
{
    s_collision_body* result;
    size_t       alloc_size;

    /* Get amount of memory we'll need. */
    alloc_size = sizeof(*result);

    /* Allocate memoryand get pointer. */
    result = malloc(alloc_size);

    /*
    * Make sure the data members are
    * zero'd and that "next" member
    * is NULL.
    */

    memset(result, 0, alloc_size);

    result->next = NULL;

    return result;
}

/*
* Caskey, Damon V.
* 2021-08-22
*
* Allocate new collision node and append it to
* end of collision linked list. If no lists exists
* yet, the new node becomes head of a new list.
*
* First step in adding another collision instance.
*
* Returns pointer to new node.
*/
s_collision_body* collision_body_append_node(struct s_collision_body* head)
{
    /* Allocate node. */
    struct s_collision_body* new_node = NULL;
    struct s_collision_body* last = NULL;

    /*
    * Allocate memory and get pointer for new
    * collision node, then default last to head.
    */
    new_node = collision_body_allocate_object();
    last = head;

    /*
    * New node is going to be the last node in
    * list, so set its next as NULL.
    */
    new_node->next = NULL;

    /*
    * If there wasn't already a list, the
    * new node is our head. We are done and
    * can return the new node pointer.
    */

    if (head == NULL)
    {
        head = new_node;

        return new_node;
    }

    /*
    * If we got here, there was already a
    * list in place. Iterate to its last
    * node.
    */

    while (last->next != NULL)
    {
        last = last->next;
    }

    /*
    * Populate existing last node's next
    * with new node pointer. The new node
    * is now the last node in list.
    */

    last->next = new_node;

    return new_node;
}

/*
* Caskey, Damon V
* 2021-08-22
*
* Return TRUE if a collision object
* has coordinates set, FALSE otherwise.
*/
int collision_body_check_has_coords(s_collision_body* target)
{
    /*
    * If target missing or coordinates
    * are not allocated then return FALSE.
    */

    if (!target)
    {
        return FALSE;
    }

    if (!target->coords)
    {
        return FALSE;
    }

    /*
    * If any one coordinate property has a value
    * then return TRUE instantly.
    */
    if (target->coords->x || target->coords->y || target->coords->height || target->coords->width)
    {
        return TRUE;
    }

    return FALSE;
}

/*
* Caskey, Damon V
* 2021-08-22
*
* Allocate new collision list with same values as source.
* Returns pointer to head of new list.
*/
s_collision_body* collision_body_clone_list(s_collision_body* source_head, int check_coords)
{
    s_collision_body* source_cursor = NULL;
    s_collision_body* clone_head = NULL;
    s_collision_body* clone_node = NULL;

    /* Head is null? Get out now. */
    if (source_head == NULL)
    {
        return source_cursor;
    }

    source_cursor = source_head;

    while (source_cursor != NULL)
    {
        /*
        * If check coords flag set, only
        * clone nodes with valid coordinates.
        */

        if (check_coords && !collision_body_check_has_coords(source_cursor))
        {
            source_cursor = source_cursor->next;
            continue;
        }

        clone_node = collision_body_append_node(clone_head);

        /*
        * Populate head if NULL so we
        * have one for the next cycle.
        */
        if (clone_head == NULL)
        {
            clone_head = clone_node;
        }

        /* Copy the values. */
        clone_node->body = body_clone_object(source_cursor->body);

        if (source_cursor->coords != NULL)
        {
            clone_node->coords = collision_allocate_coords(source_cursor->coords);
        }

        clone_node->index = source_cursor->index;
        clone_node->meta_data = source_cursor->meta_data;
        clone_node->meta_tag = source_cursor->meta_tag;

        source_cursor = source_cursor->next;
    }

    return clone_head;
}

/*
* Caskey, Damon V
* 2021-08-22
*
* Send all collision body list data to log for debugging.
*/
void collision_body_dump_list(s_collision_body* head)
{
    printf("\n\n -- Collision Body List (head: %p) Dump --", head);

    s_collision_body* cursor;
    int count = 0;

    cursor = head;

    while (cursor != NULL)
    {
        count++;

        printf("\n\n\t Node: %p", cursor);
        printf("\n\t\t ->body: %p", cursor->body);

        if (cursor->body)
        {
            body_dump_object(cursor->body);
        }

        printf("\n\t\t ->coords: %p", cursor->coords);

        if (cursor->coords)
        {
            printf("\n\t\t\t ->height: %d", cursor->coords->height);
            printf("\n\t\t\t ->width: %d", cursor->coords->width);
            printf("\n\t\t\t ->x: %d", cursor->coords->x);
            printf("\n\t\t\t ->y: %d", cursor->coords->y);
            printf("\n\t\t\t ->z_background: %d", cursor->coords->z_background);
            printf("\n\t\t\t ->z_foreground: %d", cursor->coords->z_foreground);
        }

        printf("\n\t\t ->index: %d", cursor->index);
        printf("\n\t\t ->meta_data: %p", cursor->meta_data);
        printf("\n\t\t ->meta_tag: %d", cursor->meta_tag);
        printf("\n\t\t ->next: %p", cursor->next);

        cursor = cursor->next;
    }

    printf("\n\n %d nodes.", count);
    printf("\n\n -- Collision body list (head: %p) dump complete! -- \n", head);
}

/*
* Caskey, Damon V.
* 2021-08-22
*
* Find a collision node by index and return pointer, or
* NULL if no match found.
*/
s_collision_body* collision_body_find_node_index(s_collision_body* head, int index)
{
    s_collision_body* current = NULL;

    /*
    * Starting from head node, iterate through
    * all collision nodes and free them.
    */
    current = head;

    while (current != NULL)
    {
        /* If we found a collision index match, return the pointer. */
        if (current->index == index)
        {
            return current;
        }

        /* Go to next node. */
        current = current->next;
    }

    /*
    * If we got here, find failed.
    * Just return NULL.
    */
    return NULL;
}

/*
* Caskey, Damon V.
* 2021-08-22
*
* Clear a collision linked list from memory.
*/
void collision_body_free_list(s_collision_body* head)
{
    s_collision_body* cursor = NULL;
    s_collision_body* next = NULL;

    /*
    * Starting from head node, iterate through
    * all collision nodes and free them.
    */
    cursor = head;

    while (cursor != NULL)
    {
        /*
        * We still need the next member after we
        * delete collision object, so we'll store
        * it in a temp var.
        */

        next = cursor->next;

        /* Free the current collision object. */
        collision_body_free_node(cursor);

        cursor = next;
    }
}

/*
* Caskey, Damon V.
* 2021-08-22
*
* Clear a single collision object from memory.
* Note this does NOT remove node from list.
* Be careful not to create a dangling pointer!
*/
void collision_body_free_node(s_collision_body* target)
{
    /* Free sub objects. */

    if (target->body)
    {
        body_free_object(target->body);
        target->body = NULL;
    }

    if (target->coords)
    {
        free(target->coords);
        target->coords = NULL;
    }

    /* To Do: Free tag function. */
    if (target->meta_data)
    {
        meta_data_free_list(target->meta_data);
        target->meta_data = NULL;
    }

    /* Free the collision structure. */
    free(target);
}

/*
* Caskey, Damon V.
* 2021-08-22
*
* Allocate and apply collision settings to target frame.
*/
void collision_body_initialize_frame_property(s_addframe_data* data, ptrdiff_t frame)
{
    s_collision_body* temp_collision;
    size_t memory_size;

    if (!data->collision_body)
    {
        return;
    }

    /*
    * If collision is not allocated yet, we need to allocate
    * an array of collision pointers (one element for each
    * animation frame). If the frame has a collision, its
    * collision property is populated with pointer to head
    * of a linked list of collision objects.
    */
    if (!data->animation->collision_body)
    {
        memory_size = data->framecount * sizeof(*data->animation->collision_body);

        data->animation->collision_body = malloc(memory_size);
        memset(data->animation->collision_body, 0, memory_size);
    }

    /*
    * Clone source list and populate frame's collision
    * property with the pointer to clone list head.
    */
    temp_collision = collision_body_clone_list(data->collision_body, 1);

    /* Apply final adjustments to any collision coordinates. */
    collision_body_prepare_coordinates_for_frame(temp_collision, data->model, data);

    /* Frame collision property is head of collision list. */
    data->animation->collision_body[frame] = temp_collision;

    /* Turn on vulnerability so we can detect collisions. */
    data->animation->vulnerable[frame] = 1;
}

/*
* Caskey, Damon V.
* 2021-08-22
*
* Apply final adjustments to collision coordinates
* with defaults settings for required properties
* author did not provide values for.
*/
void collision_body_prepare_coordinates_for_frame(s_collision_body* collision_head, s_model* model, s_addframe_data* add_frame_data)
{
    s_collision_body* cursor;
    s_hitbox* coords;

    cursor = collision_head;

    while (cursor != NULL)
    {
        coords = cursor->coords;

        if (coords)
        {
            /* Position includes offset.Size includes position. */
            coords->x = coords->x - add_frame_data->offset->x;
            coords->y = coords->y - add_frame_data->offset->y;
            coords->width = coords->width + coords->x;
            coords->height = coords->height + coords->y;

            /*
            * We aren't forgetting about Z depth. We just don't 
            * need to worry about it because body box Z depth 
            * defaults to 0.
            */
        }

        cursor = cursor->next;
    }
}

/*
* Caskey, Damon V.
* 2021-08-22
*
* Receives a reference (pointer to pointer) to the head
* of a list, deletes all occurrence of undefined collision
* coordinates (no coords pointer or X/Y/H/W are all 0).
*
* This is to replicate legacy behavior of removing a collision
* box during read in from text when all 0 values are provided
* by author.
*
* Reference pointer is swapped for new head pointer if head
* is deleted.
*/
void collision_body_remove_undefined_coordinates(s_collision_body** head)
{
    s_collision_body* cursor = NULL;
    s_collision_body* prev = NULL;

    /* Start with head. */
    cursor = *head;
    prev = *head;

    /*
    * If head node or mutiple nodes lack defined collision
    * cordinates.
    */
    while (cursor != NULL && !collision_body_check_has_coords(cursor))
    {
        /* Update head value. */
        *head = cursor->next;

        /* Free collision memory. */
        collision_body_free_node(cursor);

        /* Change cursor to head. */
        cursor = *head;
    }

    /* Delete occurrences other than head. */
    while (cursor != NULL)
    {
        /*
        * Search for and delete nodes without collision
        * coordinates defined. Keep track of the previous
        * node as we need to change 'prev->next'.
        */
        while (cursor != NULL && collision_body_check_has_coords(cursor))
        {
            prev = cursor;
            cursor = cursor->next;

        }

        /*
        * If we didn't find any blank coordinate sets
        * then just get out now.
        */
        if (cursor == NULL)
        {
            return;
        }

        /* Unlink the node from linked list. */
        prev->next = cursor->next;

        /* Free collision memory. */
        collision_body_free_node(cursor);

        /* Update cursor for next iteration of outer loop.  */
        cursor = prev->next;
    }
}

/*
* 2021-08-22
* Caskey, Damon V.
*
* Used when building a list of body objects on
* a frame during model load. Locates or allocates
* an object matching index parameter, and returns
* the resulting object pointer.
*/
s_hitbox* collision_body_upsert_coordinates_property(s_collision_body** head, int index)
{
    s_collision_body* temp_collision_current;

    /*
    * 1. First we need to know index.
    *  -- temp_collision_index

    * 2. Look for index and get pointer (found or allocated).

    * Get the node we want to work on by searching
    * for a matched index. In most cases, this will
    * just be the head node.
    */
    temp_collision_current = collision_body_upsert_index(*head, index);

    /*
    * If head is NULL, this must be the first allocated
    * collision for current frame. Populate head with
    * current so we have a head for the next pass.
    */
    if (*head == NULL)
    {
        *head = temp_collision_current;
    }

    /* 3. Get attack pointer (find or allocate). */

    /* Have collision coordinates ? If not we'll need to allocate them. */
    if (!temp_collision_current->coords)
    {
        temp_collision_current->coords = collision_allocate_coords(temp_collision_current->coords);
    }

    /* Return pointer to the coords structure. */
    return temp_collision_current->coords;
}

/*
* Caskey, Damon V.
* 2021-08-22
*
* Find a collision node by index, or append a new node
* with target index if no match is found. Returns pointer
* to found or appended node.
*/
s_collision_body* collision_body_upsert_index(s_collision_body* head, int index)
{
    s_collision_body* result = NULL;

    /* Run index search. */
    result = collision_body_find_node_index(head, index);

    /*
    * If we couldn't find an index match, lets add
    * a node and apply the index we wanted.
    */
    if (!result)
    {
        result = collision_body_append_node(head);
        result->index = index;
    }

    return result;
}

/*
* 2021-08-22
* Caskey, Damon V
*
* Get pointer to body object for modification. Used when
* loading a model and reading in body properties.
*
* 1. Receive pointer to head node of collision list. If
* the head node is NULL a new collision list is allocated
* and the head property value is populated with head node.
*
* 2. Search collision list for an body node with index 
* matching received index property. New node allocated if 
* not found. See collision_body_upsert_index().
*
* 3. Find or allocate body object on collision node.
* Returns pointer to body object.
*/
s_body* collision_body_upsert_property(s_collision_body** head, int index)
{
    // printf("\n\t collision_body_upsert_property(%p, %d)", *head, index);

    s_collision_body* temp_collision_current;

    /*
    * 1. First we need to know index.
                *  -- temp_collision_index

                * 2. Look for index and get pointer (found or allocated).

                * Get the node we want to work on by searching
                * for a matched index. In most cases, this will
                * just be the head node.
    */

    temp_collision_current = collision_body_upsert_index(*head, index);

    /*
    * If head is NULL, this must be the first allocated
    * collision for current frame. Populate head with
    * current so we have a head for the next pass.
    */

    if (*head == NULL)
    {
        *head = temp_collision_current;
    }

    /* 3. Get body pointer (find or allocate). */

    // printf("\n\t\t temp_collision_current->body (pre check): %p", temp_collision_current->body);

    /* Have a body? if not we'll need to allocate it.*/
    if (!temp_collision_current->body)
    {
        temp_collision_current->body = body_allocate_object();
    }

    // printf("\n\t\t result: %p", temp_collision_current->body);

    /* Return pointer to the body structure. */
    return temp_collision_current->body;
}



/*
* Caskey, Damon V.
* 2016-11-26
*
* Allocate collision coordinates, copy coords
* data if present, and return pointer.
*/
s_hitbox *collision_allocate_coords(s_hitbox *coords)
{
    s_hitbox    *result;
    size_t      alloc_size;

    // Get amount of memory we'll need.
    alloc_size = sizeof(*result);

    // Allocate memory and get pointer.
    result = malloc(alloc_size);

    // 0 out valules.
    memset(result, 0, sizeof(*result));

    // If previous data is provided,
    // copy into new allocation.
    if(coords)
    {
        memcpy(result, coords, alloc_size);
    }

    // Return result.
    return result;
}

/* 
* Caskey, Damon V.
* 2020-02-11
* 
* Allocate an attack property structure and return pointer.
*/
s_attack* attack_allocate_object()
{
    s_attack* result;

    /* Allocate memory and get the pointer. */
    result = malloc(sizeof(*result));

    /* 
    * Default values.
    *
    * -- Copy the universal empty attack structure. This
    * takes care of most default values in one shot.
    */
    memcpy(result, &emptyattack, sizeof(*result));

    /* -- Apply default hit sound effect (for legacy compatability). */
    result->hitsound = global_sample_list.beat;
    
    /* -- Apply default drop velocity. */
    result->dropv.x = default_model_dropv.x;
    result->dropv.y = default_model_dropv.y;
    result->dropv.z = default_model_dropv.z;

    return result;
}

/* 
* Caskey, Damon V.
* 2020-03-09
*
* Allocate new attack object with same values (but not same 
* pointers) as received attack object. Returns pointer to
* new object.
*/
s_attack* attack_clone_object(s_attack* source)
{
    s_attack* result = NULL;

    if (!source)
    {
        return result;
    }

    result = attack_allocate_object();

    /* 
    * Attack has a ton of members. Rather than do everything 
    * piecemeal, we'll memcopy to get all the basic values, 
    * and then overwrite members individually as needed.
    */

    memcpy(result, source, sizeof(*result));

    /* 
    * Clone sub objects. Same principal as parent. We want 
    * new pointers allocated with the same data as the source 
    * pointers.
    */

    /* -- Recursive damage. */
    if (source->recursive && source->recursive->mode)
    {
        result->recursive = malloc(sizeof(*result->recursive));
        memcpy(result->recursive, source->recursive, sizeof(*result->recursive));
    }

    return result;
}

/*
* Caskey, Damon V
* 2020-03-12
*
* Send all attack data to log for debugging.
*/
void attack_dump_object(s_attack* attack)
{
    printf("\n\n -- Attack (%p) dump --", attack);

    if (attack)
    {
        printf("\n\t ->attack_drop: %d", attack->attack_drop);
        printf("\n\t ->attack_force: %d", attack->attack_force);
        printf("\n\t ->attack_type: %d", attack->attack_type);
        printf("\n\t ->blockflash: %d", attack->blockflash);
        printf("\n\t ->blocksound: %d", attack->blocksound);
        printf("\n\t ->counterattack: %d", attack->counterattack);
        printf("\n\t ->damage_on_landing.attack_force: %d", attack->damage_on_landing.attack_force);
        printf("\n\t ->damage_on_landing.attack_type: %d", attack->damage_on_landing.attack_type);
        printf("\n\t ->dropv.x: %f", attack->dropv.x);
        printf("\n\t ->dropv.y: %f", attack->dropv.y);
        printf("\n\t ->dropv.z: %f", attack->dropv.z);
        printf("\n\t ->flash_layer_adjust: %d", attack->flash_layer_adjust);
        printf("\n\t ->flash_layer_source: %d", attack->flash_layer_source);
        printf("\n\t ->flash_z_source: %d", attack->flash_z_source);
        printf("\n\t ->forcemap: %d", attack->forcemap);
        printf("\n\t ->force_direction: %d", attack->force_direction);
        printf("\n\t ->freeze: %d", attack->freeze);
        printf("\n\t ->freezetime: %d", attack->freezetime);
        printf("\n\t ->grab: %d", attack->grab);
        printf("\n\t ->grab_distance: %d", attack->grab_distance);
        printf("\n\t ->guardcost: %d", attack->guardcost);
        printf("\n\t ->hitflash: %d", attack->hitflash);
        printf("\n\t ->hitsound: %d", attack->hitsound);
        printf("\n\t ->jugglecost: %d", attack->jugglecost);
        printf("\n\t ->maptime: %d", attack->maptime);
        printf("\n\t ->next_hit_time: %d", attack->next_hit_time);
        printf("\n\t ->no_block: %d", attack->no_block);
        printf("\n\t ->otg: %d", attack->otg);
        printf("\n\t ->pause_add: %d", attack->pause_add);
        printf("\n\t ->recursive: %d", attack->recursive);

        if (attack->recursive)
        {
            recursive_damage_dump_object(attack->recursive);
        }

        printf("\n\t ->seal: %d", attack->seal);
        printf("\n\t ->sealtime: %d", attack->sealtime);
        printf("\n\t ->staydown.rise: %d", attack->staydown.rise);
        printf("\n\t ->staydown.riseattack: %d", attack->staydown.riseattack);
        printf("\n\t ->staydown.riseattack_stall: %d", attack->staydown.riseattack_stall);
    }

    printf("\n\n -- Attack (%p) dump complete... -- \n", attack);
}

/*
* Caskey, Damon V.
* 2020-03-10
* 
* Free attack properties from memory.
*/
void attack_free_object(s_attack * target)
{
    if (target->recursive)
    {
        free(target->recursive);
        target->recursive = NULL;
    }
   
    free(target);
}

/*
* Caskey, Damon V.
* 2021-08-08
*
* Allocate a body property structure and return pointer.
*/
s_body* body_allocate_object()
{
    s_body* result;

    /* Allocate memory and get the pointer. */
    result = malloc(sizeof(*result));

    /*
    * Default values.
    *
    * -- Copy the universal empty body structure. This
    * takes care of most default values in one shot.
    */
    memcpy(result, &empty_body, sizeof(*result));

    return result;
}

/*
* Caskey, Damon V.
* 2021-08-08
*
* Allocate new body object with same values (but not same
* pointers) as received body object. Returns pointer to
* new object.
*/
s_body* body_clone_object(s_body* source)
{
    s_body* result = NULL;

    if (!source)
    {
        return result;
    }

    result = body_allocate_object();

    /*
    * Rather than do everything piecemeal, we'll memcopy
    * to get all the basic values, and then overwrite
    * members individually as needed.
    */

    memcpy(result, source, sizeof(*result));

    return result;
}

/*
* Caskey, Damon V
* 2020-03-12
*
* Send all body data to log for debugging.
*/
void body_dump_object(s_body* body)
{
    printf("\n\n -- Body (%p) dump --", body);

    if (body)
    {        
        printf("\n\t ->body_defense: %d", body->defense);
        printf("\n\t ->flash_layer_adjust: %d", body->flash_layer_adjust);
        printf("\n\t ->flash_layer_source: %d", body->flash_layer_source);
        printf("\n\t ->flash_z_source: %d", body->flash_z_source);

    }

    printf("\n\n -- Body (%p) dump complete... -- \n", body);
}

/*
* Caskey, Damon V.
* 2021-08-21
*
* Free body properties from memory.
*/
void body_free_object(s_body* target)
{
    if (target->defense)
    {
        defense_free_object(target->defense);
        target->defense = NULL;
    }

    free(target);
}

/*
* 2020-03-10
* Caskey, Damon V
*
* allocate a recursive damage object and return
* its pointer.
*/
s_damage_recursive* recursive_damage_allocate_object()
{
    s_damage_recursive* result;
    size_t memory_size;

    memory_size = sizeof(*result);

    result = malloc(memory_size);

    /*
    * 0 The property values, then set
    * specific default values.
    */
    memset(result, 0, memory_size);

    result->type = ATK_NONE;
    result->next = NULL;
    result->owner = NULL;

    return result;
}

/*
* Caskey, Damon V.
* 2019-01-15
*
* If attack has any recursive effects, apply
* them to entity accordingly.
*/
void recursive_damage_check_apply(entity* ent, entity* other, s_attack* attack)
{
    s_damage_recursive* previous;
    s_damage_recursive* cursor;

    /*
    * If the recursive head pointer is
    * null, there's no recursive, so exit.
    */
    if (!attack->recursive)
    {
        return;
    }

    /*
    * Let's see if we have a allocated any elements
    * for recursive damage already.
    */
    if (ent->recursive_damage)
    {
        /*
        * Iterate over linked list and try to find an index
        * member matching index member from attack. If we
        * find one, exit loop - we can use the target pointer.
        *
        * If we don't find a match, we'll need to create a new
        * node in the list and its pointer instead.
        */

        cursor = ent->recursive_damage;

        while (cursor != NULL)
        {
            previous = cursor;

            /*
            * Found index match, so we can use the cursor
            * as is. Get out now.
            */
            if (cursor->index == attack->recursive->index)
            {
                break;
            }

            /* Move to next node in list (if any). */
            cursor = cursor->next;
        }

        /* Add new node to list. */
        if (!cursor)
        {
            /* Allocate the memoryand get pointer. */
            cursor = recursive_damage_allocate_object();

            /* Link previous node's next to our new node. */
            previous->next = cursor;
        }
    }
    else
    {
        /*
        * Entity didn't have recursive damage at all.
        * Let's allocate a head node.
        */

        cursor = recursive_damage_allocate_object();

        /* Assign to entity. */
        ent->recursive_damage = cursor;
    }

    /*
    * Now we have a target recursive element to populate with
    * attack's recursive values.
    */

    cursor->meta_tag = attack->recursive->meta_tag;
    cursor->meta_data = attack->recursive->meta_data;
    cursor->mode = attack->recursive->mode;
    cursor->index = attack->recursive->index;
    cursor->time = _time + (attack->recursive->time * GAME_SPEED / 100);
    cursor->force = attack->recursive->force;
    cursor->rate = attack->recursive->rate;

    /*
    * If recursive type is none, that means
    * use same type as original attack. Note
    * that ATK_NONE is the default value of
    * a newly allocated recursive node for
    * legacy compatibility.
    */
    if (attack->recursive->type == ATK_NONE)
    {
        cursor->type = attack->attack_type;
    }
    else
    {
        cursor->type = attack->recursive->type;
    }

    cursor->type = attack->attack_type;
    cursor->owner = other;
}


/*
* Caskey, Damon V
* 2020-03-12
*
* Send all recursive damage data to log for debugging.
*/
void recursive_damage_dump_object(s_damage_recursive* recursive)
{
    printf("\n\n -- Recursive (%p) dump --", recursive);

    if (recursive)
    {
        printf("\n\t ->force: %d", recursive->force);
        printf("\n\t ->index: %d", recursive->index);
        printf("\n\t ->meta_data: %p", recursive->meta_data);
        printf("\n\t ->meta_tag: %d", recursive->meta_tag);
        printf("\n\t ->mode: %d", recursive->mode);
        printf("\n\t ->next: %p", recursive->next);
        printf("\n\t ->owner: %p", recursive->owner);
        printf("\n\t ->rate: %d", recursive->rate);
        printf("\n\t ->tick: %d", recursive->tick);
        printf("\n\t ->time: %d", recursive->time);
        printf("\n\t ->type: %d", recursive->type);
    }

    printf("\n\n -- Recursive (%p) dump complete. -- \n", recursive);
}


/*
* Caskey, Damon V.
* 2019-01-18
*
* Free all members of a recursive damage list.
*/
void recursive_damage_free_list(s_damage_recursive* head)
{
    s_damage_recursive* cursor = NULL;
    s_damage_recursive* next = NULL;

    /*
    * Starting from head node, iterate through
    * all collision nodes and free them.
    */
    cursor = head;

    while (cursor != NULL)
    {
        /*
        * We still need the next node after we
        * delete current one, so we'll store
        * it in a temp var.
        */
        next = cursor->next;

        /* Free the current node. */
        recursive_damage_free_object(cursor);

        /* Set cursor to next. */
        cursor = next;
    }
}

/*
* Caskey, Damon V.
* 2019-01-20
*
* Remove single node from a recursive damage linked list.
*/
void recursive_damage_free_node(s_damage_recursive** list, s_damage_recursive* node)
{
    s_damage_recursive* cursor;
    s_damage_recursive* previous;

    /* Initialize previous. */
    previous = NULL;

    /*
    * Iterate each node of list. On each iteration, previous is
    * set to cursor before cursor iterates.
    */
    for (cursor = *list; cursor != NULL; previous = cursor, cursor = cursor->next)
    {
        /* Are we at target element ? */
        if (cursor == node)
        {
            /* If previous is NULL we're at the head. */
            if (previous == NULL)
            {
                /* Move node from head's next to head. */
                *list = cursor->next;
            }
            else
            {
                /*
                * Move previous next to cursor next. This
                * effectivly "skips" cursor in sequence.
                */
                previous->next = cursor->next;
            }

            /* Deallocate the node. */
            recursive_damage_free_object(cursor);

            return;
        }
    }
}

/*
* Caskey, Damon V.
* 2020-03-13
*
* Wrapper for deleting a recrusive object's data.
*/
void recursive_damage_free_object(s_damage_recursive* target)
{
    free(target);
}

/*
* Caskey, Damon V.
* 2021-08-24
* 
* Return recrisive mode flag constant from 
* a string argument.
*/
e_damage_recursive_logic recursive_damage_get_mode_flag_from_argument(char* value)
{
    e_damage_recursive_logic result = DAMAGE_RECURSIVE_MODE_NONE;

    if (stricmp(value, "none") == 0)
    {
        result = DAMAGE_RECURSIVE_MODE_NONE;
    }
    else if (stricmp(value, "hp") == 0)
    {
        result = DAMAGE_RECURSIVE_MODE_HP;
    }
    else if (stricmp(value, "mp") == 0)
    {
        result = DAMAGE_RECURSIVE_MODE_MP;
    }
    else if (stricmp(value, "non-lethal") == 0)
    {
        result = DAMAGE_RECURSIVE_MODE_NON_LETHAL;
    }

    return result;    
}

/*
* Caskey, Damon V.
* 2021-08-24
*
* Reads text arguments from recursive mode
* command and outputs integer with appropriate
* bits toggled.
*/
e_damage_recursive_logic recursive_damage_get_mode_setup_from_arg_list(ArgList* arglist)
{
    e_damage_recursive_logic result = 0;

    int i;
    char* value;

    /*
    * Read all arguments left to right. We send each arg
    * to function that interprets the value to get appropriate
    * bit to toggle.
    */

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= recursive_damage_get_mode_flag_from_argument(value);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2021-08-24
*
* Interpret integer input and return mode 
* value with appropriate bit flags toggled. 
* an integer argument. This is for legacy
* support of the very poorly conceived mode
* flag originally coded by yours truly.
*/
e_damage_recursive_logic recursive_damage_get_mode_setup_from_legacy_argument(e_damage_recursive_cmd_read value)
{
    e_damage_recursive_logic result = DAMAGE_RECURSIVE_MODE_NONE;

    switch (value)
    {
    case DAMAGE_RECURSIVE_CMD_READ_NONLETHAL_HP:
        result |= DAMAGE_RECURSIVE_MODE_HP;
        result |= DAMAGE_RECURSIVE_MODE_NON_LETHAL;
        break;
    case DAMAGE_RECURSIVE_CMD_READ_MP:
        result |= DAMAGE_RECURSIVE_MODE_MP;
        break;
    case DAMAGE_RECURSIVE_CMD_READ_MP_NONLETHAL_HP:
        result |= DAMAGE_RECURSIVE_MODE_HP;
        result |= DAMAGE_RECURSIVE_MODE_MP;
        result |= DAMAGE_RECURSIVE_MODE_NON_LETHAL;
        break;
    case DAMAGE_RECURSIVE_CMD_READ_HP:
        result |= DAMAGE_RECURSIVE_MODE_HP;
        break;
    case DAMAGE_RECURSIVE_CMD_READ_HP_MP:
        result |= DAMAGE_RECURSIVE_MODE_HP;
        result |= DAMAGE_RECURSIVE_MODE_MP;
        break;
    }

    return result;
}

/*
* Caskey, Damon V.
* 2009-06-17
* --2018-01-02 retooled from former common_dot.
* --2019-01-16 Replace recursion array with linked list.
*
* Apply recursive damage (damage over time (dot)).
*/
void recursive_damage_update(entity* ent)
{
    s_attack attack = emptyattack;      // Attack structure.
    s_defense* defense_object = NULL;   // Defense properties.
    s_damage_recursive* cursor = NULL;  // Iteration cursor.

    /* Iterate target's recursive damage nodes. */
    for (cursor = ent->recursive_damage; cursor != NULL; cursor = cursor->next)
    {
        /*
        * If time has expired, destroy node and exit
        * this loop iteration.
        */
        if (_time > cursor->time)
        {
            /*
            * If this is the head and there are no other
            * recursive damage nodes, we need to delete
            * the head AND set it to NULL. Otherwise, we
            * only delete the node.
            */
            if (cursor == ent->recursive_damage && cursor->next == NULL)
            {
                free(cursor);
                ent->recursive_damage = NULL;
            }
            else
            {
                recursive_damage_free_node(&ent->recursive_damage, cursor);
            }

            continue;
        }

        /*
        * If it is not yet time for a tick, exit
        * this iteration of loop.
        */
        if (_time < cursor->tick)
        {
            continue;
        }

        /* If target is not alive, exit this iteration of loop. */
        if (ent->energy_state.health_current <= 0)
        {
            continue;
        }

        /* Reset next tick time. */
        cursor->tick = _time + (cursor->rate * GAME_SPEED / 100);

        /* Does this recursive damage affect HP ? */
        if (cursor->mode & DAMAGE_RECURSIVE_MODE_HP)
        {
            /*
            * Recursive HP Damage Logic:
            *
            * Normally it is preferable to apply takedamage(),
            * any time we want to damage a target, but because
            * it breaks grabs and would spam the HUD,
            * takedamage() is not tenable for every tick
            * of a recursive damage effect. However, we DO want
            * the owner to get credit, grabs to be broken, HUD
            * to react, etc., if the target is KO'd.
            *
            * To handle both needs, we will first factor offense
            * and defense manually to get a calculated force. If
            * the calculated force is sufficient to KO target, and
            * this recursive tick is allowed to KO, we will go ahead
            * and apply takedamage() using the original recursive
            * force (takedamage() automatically calculates offense
            * and defense). This way the engine will treat KO tick as
            * if it were a direct hit with all appropriate reactions
            * and credit. Otherwise, we'll just subtract the calculated
            * force directly from target's HP for a 'silent' damage effect.
            */

            /*
            * Populate local attack structure with recursive
            * damage values and apply any damage mitigation.
            */

            attack.attack_type = cursor->type;
            attack.attack_force = cursor->force;
            attack.dropv = default_model_dropv;

            /*
            * Get force after defense. We're sending NULL as the body 
            * object. This means the calculation will always use model 
            * defense or global default.
            */
            defense_object = defense_find_current_object(ent, NULL, attack.attack_type);

            attack.attack_force = calculate_force_damage(ent, cursor->owner, &attack, defense_object);

            /*
            * Is calculated force enough to KO target?
            * Is this recursive damage allowed to KO?
            */
            if (attack.attack_force >= ent->energy_state.health_current)
            {
                /* Is this recursive damage allowed to KO ? */
                if (!(cursor->mode & DAMAGE_RECURSIVE_MODE_NON_LETHAL))
                {
                    /*
                    * Does target have a takedamage structure? If so
                    * we can use takedamage() for the finishing damage.
                    * Otherwise it must be a none type or some other
                    * exceptional entity like a projectile. In that case
                    * we will just kill it.
                    */
                    if (ent->takedamage)
                    {
                        /*
                        * Populate attack structure with our 
                        * recursive damage values. Then we apply 
                        * takedamage(). The takedamage logic will 
                        * handle everything else.
                        */

                        ent->takedamage(cursor->owner, &attack, 0, defense_object);
                    }
                    else
                    {
                        kill_entity(ent, KILL_ENTITY_TRIGGER_RECURSIVE_DAMAGE);
                    }
                }
                else
                {
                    /*
                    * Recursive damage is not allowed to KO.
                    * Just set target's HP to minimum value.
                    */
                    ent->energy_state.health_current = 1;

                    /* Execute the target's takedamage script. */
                    execute_takedamage_script(ent, cursor->owner, &attack);
                }
            }
            else
            {
                /*
                * Calculated damage is insufficient to KO.
                * Subtract directly from target's HP.
                */
                ent->energy_state.health_current -= attack.attack_force;

                /* Execute the target's takedamage script. */
                execute_takedamage_script(ent, cursor->owner, &attack);
            }
        }

        /* Does this recursive damage affect MP ? */
        if (cursor->mode & DAMAGE_RECURSIVE_MODE_MP)
        {
            /* Recursive MP Damage Logic : 
            *
            * Could not be more simple. Subtract
            * recursive force from MP. If MP would
            * end with negative value, set 0.
            */

            ent->energy_state.mp_current -= cursor->force;

            if (ent->energy_state.mp_current < 0)
            {
                ent->energy_state.mp_current = 0;
            }
        }
    }
}

/*
* Caskey, Damon V. (original author unknown, 
* reworked to the point it's essentially a 
* new funciton)
* 2020-03-04 (Previous reworks 2016).
*
* Allocate a frame to animation and add frame
* properties as needed. In OpenBOR, frames are 
* a bottom up structure: There is no single frame 
* structure with sub properties. Instead, each 
* "frame" property is an arrayed animation 
* property array with number of elements matched 
* to number of desired frames.
*/
int addframe(s_addframe_data* data)
{
    int     i;
    size_t  size_col_on_frame,
            size_col_on_frame_struct;

    s_collision_entity  *collision_entity;

    ptrdiff_t currentframe;
    if(data->framecount > 0)
    {
        alloc_frames(data->animation, data->framecount);
    }
    else
    {
        data->framecount = -data->framecount;    // for alloc method, use a negative value
    }

    currentframe = data->animation->numframes;
    ++data->animation->numframes;

    data->animation->sprite[currentframe] = data->spriteindex;
    data->animation->delay[currentframe] = data->delay * GAME_SPEED / 100;

    // Allocate entity boxes.
    if((data->entity_coords->width - data->entity_coords->x)
        && (data->entity_coords->height - data->entity_coords->y))
    {
        if(!data->animation->collision_entity)
        {
            size_col_on_frame = data->framecount * sizeof(*data->animation->collision_entity);

            data->animation->collision_entity = malloc(size_col_on_frame);
            memset(data->animation->collision_entity, 0, size_col_on_frame);
        }

        size_col_on_frame_struct = sizeof(**data->animation->collision_entity);
        data->animation->collision_entity[currentframe] = malloc(size_col_on_frame_struct);

        data->animation->collision_entity[currentframe]->instance = collision_alloc_entity_list();

        for(i=0; i<max_collisons; i++)
        {
            collision_entity = collision_alloc_entity_instance(data->ebox);
            data->animation->collision_entity[currentframe]->instance[i] = collision_entity;

            collision_entity->index = i;

            // Coordinates.
            if(!collision_entity->coords)
            {
                collision_entity->coords = collision_allocate_coords(data->entity_coords);
            }
        }
    }

    /* Allocate collision. */
    collision_attack_initialize_frame_property(data, currentframe);
    collision_body_initialize_frame_property(data, currentframe);
    
    /* Child spawns. */
    child_spawn_initialize_frame_property(data, currentframe);

    // Drawmethod (graphic settings)
    if(data->drawmethod->flag)
    {
        if(!data->animation->drawmethods)
        {
            data->animation->drawmethods = malloc(data->framecount * sizeof(*data->animation->drawmethods));
            memset(data->animation->drawmethods, 0, data->framecount * sizeof(*data->animation->drawmethods));
        }
        setDrawMethod(data->animation, currentframe, malloc(sizeof(**data->animation->drawmethods)));
        //data->animation->drawmethods[currenframe] = malloc(sizeof(s_drawmethod));
        memcpy(getDrawMethod(data->animation, currentframe), data->drawmethod, sizeof(**data->animation->drawmethods));
        //memcpy(data->animation->drawmethods[currentframe], data->drawmethod, sizeof(s_drawmethod));
    }

    // Idle flag.
    if(data->idle && !data->animation->idle)
    {
        data->animation->idle = malloc(data->framecount * sizeof(*data->animation->idle));
        memset(data->animation->idle, 0, data->framecount * sizeof(*data->animation->idle));
    }
    if(data->animation->idle)
    {
        data->animation->idle[currentframe] = data->idle;
    }

    // Movement
    if(data->move)
    {
        if(!data->animation->move)
        {
            data->animation->move = malloc(data->framecount * sizeof(*data->animation->move));
            memset(data->animation->move, 0, data->framecount * sizeof(*data->animation->move));
        }
        data->animation->move[currentframe] = malloc(sizeof(**data->animation->move));
        memcpy(data->animation->move[currentframe], data->move, sizeof(**data->animation->move));
    }

    // Shadow effects.
    if(data->frameshadow >= 0 && !data->animation->shadow)
    {
        data->animation->shadow = malloc(data->framecount * sizeof(*data->animation->shadow));
        memset(data->animation->shadow, FRAME_SHADOW_NONE, data->framecount * sizeof(*data->animation->shadow));
    }

    if(data->animation->shadow)
    {
        data->animation->shadow[currentframe] = data->frameshadow;    // shadow index for each frame
    }

    if(data->shadow_coords[0] || data->shadow_coords[1])
    {
        if(!data->animation->shadow_coords)
        {
            data->animation->shadow_coords = malloc(data->framecount * sizeof(*data->animation->shadow_coords));
            memset(data->animation->shadow_coords, 0, data->framecount * sizeof(*data->animation->shadow_coords));
        }
        memcpy(data->animation->shadow_coords[currentframe], data->shadow_coords, sizeof(*data->animation->shadow_coords));
    }    

    // Offset
    if(data->offset->x || data->offset->y)
    {
        if(!data->animation->offset)
        {
            data->animation->offset = malloc(data->framecount * sizeof(*data->animation->offset));
            memset(data->animation->offset, 0, data->framecount * sizeof(*data->animation->offset));
        }
        data->animation->offset[currentframe] = malloc(sizeof(**data->animation->offset));
        memcpy(data->animation->offset[currentframe], data->offset, sizeof(**data->animation->offset));
    }

    // Platform
    if(data->platform[PLATFORM_HEIGHT]) //height
    {
        if(!data->animation->platform)
        {
            data->animation->platform = malloc(data->framecount * sizeof(*data->animation->platform));
            memset(data->animation->platform, 0, data->framecount * sizeof(*data->animation->platform));
        }
        memcpy(data->animation->platform[currentframe], data->platform, sizeof(*data->animation->platform));// Used so entity can be landed on
    }

    // Sound effect
    if(data->soundtoplay >= 0)
    {
        if(!data->animation->soundtoplay)
        {
            data->animation->soundtoplay = malloc(data->framecount * sizeof(*data->animation->soundtoplay));
            memset(data->animation->soundtoplay, SAMPLE_ID_NONE, data->framecount * sizeof(*data->animation->soundtoplay)); // default to SAMPLE_ID_NONE
        }
        data->animation->soundtoplay[currentframe] = data->soundtoplay;
    }

    return data->animation->numframes;
}


// ok this func only seems to overwrite the name which was assigned from models.txt with the one
// in the models own text file.
// it does so in the cache.
void _peek_model_name(int index)
{
    size_t size = 0;
    ptrdiff_t pos = 0, len;
    char *buf = NULL;
    char *command, *value;
    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";
    modelCommands cmd;

    if(buffer_pakfile(model_cache[index].path, &buf, &size) != 1)
    {
        return;
    }

    while(pos < size)
    {
        ParseArgs(&arglist, buf + pos, argbuf);
        command = GET_ARG(0);

        if(command && command[0])
        {
            cmd = getModelCommand(modelcmdlist, command);
            if(cmd == CMD_MODEL_NAME)
            {
                value = GET_ARG(1);
                free(model_cache[index].name);
                model_cache[index].name = NULL;
                len = strlen(value);
                model_cache[index].name = malloc(len + 1);
                strcpy(model_cache[index].name, value);
                model_cache[index].name[len] = 0;
                break;
            }
        }
        pos += getNewLineStart(buf + pos);
    }

    if(buf != NULL)
    {
        free(buf);
        buf = NULL;
    }
}

void prepare_cache_map(size_t size)
{
    if(model_cache == NULL || size + 1 > cache_map_max_items )
    {
#ifdef VERBOSE
        printf("%s %p\n", "prepare_cache_map was", model_cache);
#endif
        do
        {
            cache_map_max_items += 128;
        }
        while (size + 1 > cache_map_max_items);

        model_cache = realloc(model_cache, sizeof(*model_cache) * cache_map_max_items);
        if(model_cache == NULL)
        {
            borShutdown(1, "Out Of Memory!  Failed to create a new cache_map\n");
        }
    }
}

void cache_model(char *name, char *path, int flag)
{
    int len;
    printf("Cacheing '%s' from %s\n", name, path);
    prepare_cache_map(models_cached + 1);
    memset(&model_cache[models_cached], 0, sizeof(model_cache[models_cached]));

    len = strlen(name);
    model_cache[models_cached].name = malloc(len + 1);
    strcpy(model_cache[models_cached].name, name);
    model_cache[models_cached].name[len] = 0;

    len = strlen(path);
    model_cache[models_cached].path = malloc(len + 1);
    strcpy(model_cache[models_cached].path, path);
    model_cache[models_cached].path[len] = 0;

    model_cache[models_cached].loadflag = flag;

    _peek_model_name(models_cached);
    ++models_cached;
}


void free_modelcache()
{
    if(model_cache != NULL)
    {
        while(models_cached)
        {
            --models_cached;
            free(model_cache[models_cached].name);
            model_cache[models_cached].name = NULL;
            free(model_cache[models_cached].path);
            model_cache[models_cached].path = NULL;
        }
        free(model_cache);
        model_cache = NULL;
    }
}


int get_cached_model_index(char *name)
{
    int i;
    for(i = 0; i < models_cached; i++)
    {
        if(stricmp(name, model_cache[i].name) == 0)
        {
            return i;
        }
    }
    return MODEL_INDEX_NONE;
}

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;
}

static void _readbarstatus(char *, s_barstatus *);

static int translate_attack_type(char *command)
{
    int atk_id = -1, tempInt;

    modelCommands cmd = getModelCommand(modelcmdlist, command);

    switch(cmd)
    {
    case CMD_MODEL_COLLISION:
    case CMD_MODEL_COLLISION1:
        atk_id = ATK_NORMAL;
        break;
    case CMD_MODEL_COLLISION2:
        atk_id   = ATK_NORMAL2;
        break;
    case CMD_MODEL_COLLISION3:
        atk_id  = ATK_NORMAL3;
        break;
    case CMD_MODEL_COLLISION4:
        atk_id  = ATK_NORMAL4;
        break;
    case CMD_MODEL_COLLISION5:
        atk_id  = ATK_NORMAL5;
        break;
    case CMD_MODEL_COLLISION6:
        atk_id  = ATK_NORMAL6;
        break;
    case CMD_MODEL_COLLISION7:
        atk_id  = ATK_NORMAL7;
        break;
    case CMD_MODEL_COLLISION8:
        atk_id  = ATK_NORMAL8;
        break;
    case CMD_MODEL_COLLISION9:
        atk_id  = ATK_NORMAL9;
        break;
    case CMD_MODEL_COLLISION10:
        atk_id  = ATK_NORMAL10;
        break;
    case CMD_MODEL_SHOCK:
        atk_id  = ATK_SHOCK;
        break;
    case CMD_MODEL_BURN:
        atk_id  = ATK_BURN;
        break;
    case CMD_MODEL_STEAL:
        atk_id  = ATK_STEAL;
        break;
    case CMD_MODEL_FREEZE:
        atk_id  = ATK_FREEZE;
        break;
    case CMD_MODEL_ITEMBOX:
        atk_id  = ATK_ITEM;
        break;
    case CMD_MODEL_LOSE:
        atk_id  = ATK_LOSE;
        break;
    case CMD_MODEL_COLLISION_ETC:
        tempInt = atoi(command + 6); // White Dragon: 6 is "ATTACK" string length
		
		if(tempInt < MAX_ATKS - STA_ATKS + 1)
        {
            tempInt = MAX_ATKS - STA_ATKS + 1;
        }
        atk_id = tempInt + STA_ATKS - 1;
        break;
    default:
        break;
    }

    return atk_id;
}

//move here to ease animation name to id logic
static int translate_ani_id(const char *value, s_model *newchar, s_anim *newanim)
{
    int ani_id = -1, tempInt;
    //those are dummy values to simplify code
    static s_model mdl;
    static s_anim ani;
    if(!newchar)
    {
        newchar = &mdl;
    }
    if(!newanim)
    {
        newanim = &ani;
    }
    
    if(starts_with_num(value, "idle"))
    {
        get_tail_number(tempInt, value, "idle");
        ani_id = animidles[tempInt - 1];
    }
    else if(stricmp(value, "waiting") == 0)
    {
        ani_id = ANI_SELECT;
    }
	else if (stricmp(value, "selectin") == 0)
	{		
		ani_id = ANI_SELECTIN;
	}
	else if (stricmp(value, "selectout") == 0)
	{
		ani_id = ANI_SELECTOUT;
	}
    else if(starts_with_num(value, "walk"))
    {
        get_tail_number(tempInt, value, "walk");
        ani_id = animwalks[tempInt - 1];
        newanim->sync = ANI_WALK;

    }
    else if(stricmp(value, "sleep") == 0)
    {
        ani_id = ANI_SLEEP;
    }
    else if(stricmp(value, "run") == 0)
    {
        ani_id = ANI_RUN;
    }
    else if(stricmp(value, "backrun") == 0)
    {
        ani_id = ANI_BACKRUN;
    }
    else if(starts_with_num(value, "up"))
    {
        get_tail_number(tempInt, value, "up");
        ani_id = animups[tempInt - 1];
        newanim->sync = ANI_WALK;
    }
    else if(starts_with_num(value, "down"))
    {
        get_tail_number(tempInt, value, "down");
        ani_id = animdowns[tempInt - 1];
        newanim->sync = ANI_WALK;
    }
    else if(starts_with_num(value, "backwalk"))
    {
        get_tail_number(tempInt, value, "backwalk");
        ani_id = animbackwalks[tempInt - 1];
        newanim->sync = ANI_WALK;
    }
    else if(stricmp(value, "jump") == 0)
    {
        ani_id = ANI_JUMP;
        newanim->range.x.min = 50;
        newanim->range.x.max = 60;
    }
    else if(stricmp(value, "duck") == 0)
    {
        ani_id = ANI_DUCK;
    }
    else if(stricmp(value, "land") == 0)
    {
        ani_id = ANI_LAND;
    }
    else if(starts_with_num(value, "pain"))
    {
        get_tail_number(tempInt, value, "pain");
        if(tempInt == 1)
        {
            ani_id = ANI_PAIN;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_PAIN2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_PAIN3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_PAIN4;
        }
        else if(tempInt == 5)
        {
            ani_id = ANI_PAIN5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_PAIN6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_PAIN7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_PAIN8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_PAIN9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_PAIN10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animpains[tempInt + STA_ATKS - 1];
        }
    }
    else if(starts_with_num(value, "backpain"))
    {
        get_tail_number(tempInt, value, "backpain");
        if(tempInt == 1)
        {
            ani_id = ANI_BACKPAIN;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_BACKPAIN2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_BACKPAIN3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_BACKPAIN4;
        }
        else if(tempInt == 5)
        {
            ani_id = ANI_BACKPAIN5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_BACKPAIN6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_BACKPAIN7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_BACKPAIN8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_BACKPAIN9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_BACKPAIN10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animbackpains[tempInt + STA_ATKS - 1];
        }
    }
    else if(stricmp(value, "spain") == 0)   // If shock attacks don't knock opponent down, play this
    {
        ani_id = ANI_SHOCKPAIN;
    }
    else if(stricmp(value, "bpain") == 0)   // If burn attacks don't knock opponent down, play this
    {
        ani_id = ANI_BURNPAIN;
    }
    else if(stricmp(value, "backspain") == 0)   // If shock attacks don't knock opponent down, play this
    {
        ani_id = ANI_BACKSHOCKPAIN;
    }
    else if(stricmp(value, "backbpain") == 0)   // If burn attacks don't knock opponent down, play this
    {
        ani_id = ANI_BACKBURNPAIN;
    }
    else if(starts_with_num(value, "fall"))
    {
        get_tail_number(tempInt, value, "fall");
        if(tempInt == 1)
        {
            ani_id = ANI_FALL;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_FALL2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_FALL3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_FALL4;
        }
        else if(tempInt == 5)
        {
            ani_id = ANI_FALL5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_FALL6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_FALL7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_FALL8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_FALL9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_FALL10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animfalls[tempInt + STA_ATKS - 1];
        }
        newanim->bounce_factor = ANIMATION_BOUNCE_FACTOR_DEFAULT;
    }
    else if(starts_with_num(value, "backfall"))
    {
        get_tail_number(tempInt, value, "backfall");
        if(tempInt == 1)
        {
            ani_id = ANI_BACKFALL;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_BACKFALL2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_BACKFALL3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_BACKFALL4;
        }
        else if(tempInt == 5)
        {
            ani_id = ANI_BACKFALL5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_BACKFALL6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_BACKFALL7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_BACKFALL8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_BACKFALL9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_BACKFALL10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animbackfalls[tempInt + STA_ATKS - 1];
        }
        newanim->bounce_factor = ANIMATION_BOUNCE_FACTOR_DEFAULT;
    }
    else if(stricmp(value, "shock") == 0)   // If shock attacks do knock opponent down, play this
    {
        ani_id = ANI_SHOCK;
        newanim->bounce_factor = ANIMATION_BOUNCE_FACTOR_DEFAULT;
    }
    else if(stricmp(value, "backshock") == 0)   // If shock attacks do knock opponent down, play this
    {
        ani_id = ANI_BACKSHOCK;
        newanim->bounce_factor = ANIMATION_BOUNCE_FACTOR_DEFAULT;
    }
    else if(stricmp(value, "burn") == 0)   // If burn attacks do knock opponent down, play this
    {
        ani_id = ANI_BURN;
        newanim->bounce_factor = ANIMATION_BOUNCE_FACTOR_DEFAULT;
    }
    else if(stricmp(value, "backburn") == 0)   // If burn attacks do knock opponent down, play this
    {
        ani_id = ANI_BACKBURN;
        newanim->bounce_factor = ANIMATION_BOUNCE_FACTOR_DEFAULT;
    }
    else if(starts_with_num(value, "death"))
    {
        get_tail_number(tempInt, value, "death");
        if(tempInt == 1)
        {
            ani_id = ANI_DIE;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_DIE2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_DIE3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_DIE4;
        }
        else if(tempInt == 5)
        {
            ani_id = ANI_DIE5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_DIE6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_DIE7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_DIE8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_DIE9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_DIE10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animdies[tempInt + STA_ATKS - 1];
        }
    }
    else if(starts_with_num(value, "backdeath"))
    {
        get_tail_number(tempInt, value, "backdeath");
        if(tempInt == 1)
        {
            ani_id = ANI_BACKDIE;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_BACKDIE2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_BACKDIE3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_BACKDIE4;
        }
        else if(tempInt == 5)
        {
            ani_id = ANI_BACKDIE5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_BACKDIE6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_BACKDIE7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_BACKDIE8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_BACKDIE9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_BACKDIE10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animbackdies[tempInt + STA_ATKS - 1];
        }
    }
    else if(stricmp(value, "sdie") == 0)
    {
        ani_id = ANI_SHOCKDIE;
    }
    else if(stricmp(value, "bdie") == 0)
    {
        ani_id = ANI_BURNDIE;
    }
    else if(stricmp(value, "backsdie") == 0)
    {
        ani_id = ANI_BACKSHOCKDIE;
    }
    else if(stricmp(value, "backbdie") == 0)
    {
        ani_id = ANI_BACKBURNDIE;
    }
    else if(stricmp(value, "chipdeath") == 0)
    {
        ani_id = ANI_CHIPDEATH;
    }
    else if(stricmp(value, "guardbreak") == 0)
    {
        ani_id = ANI_GUARDBREAK;
    }
    else if(stricmp(value, "riseb") == 0)
    {
        ani_id = ANI_RISEB;
    }
    else if(stricmp(value, "backriseb") == 0)
    {
        ani_id = ANI_BACKRISEB;
    }
    else if(stricmp(value, "rises") == 0)
    {
        ani_id = ANI_RISES;
    }
    else if(stricmp(value, "backrises") == 0)
    {
        ani_id = ANI_BACKRISES;
    }
    else if(starts_with_num(value, "rise"))
    {
        get_tail_number(tempInt, value, "rise");
        if(tempInt == 1)
        {
            ani_id = ANI_RISE;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_RISE2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_RISE3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_RISE4;
        }
        else if(tempInt == 5)
        {
            ani_id = ANI_RISE5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_RISE6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_RISE7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_RISE8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_RISE9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_RISE10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animrises[tempInt + STA_ATKS - 1];
        }
    }
    else if(starts_with_num(value, "backrise"))
    {
        get_tail_number(tempInt, value, "backrise");
        if(tempInt == 1)
        {
            ani_id = ANI_BACKRISE;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_BACKRISE2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_BACKRISE3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_BACKRISE4;
        }
        else if(tempInt == 5)
        {
            ani_id = ANI_BACKRISE5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_BACKRISE6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_BACKRISE7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_BACKRISE8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_BACKRISE9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_BACKRISE10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animbackrises[tempInt + STA_ATKS - 1];
        }
    }
    else if(stricmp(value, "riseattackb") == 0)
    {
        ani_id = ANI_RISEATTACKB;
    }
    else if(stricmp(value, "backriseattackb") == 0)
    {
        ani_id = ANI_BACKRISEATTACKB;
    }
    else if(stricmp(value, "riseattacks") == 0)
    {
        ani_id = ANI_RISEATTACKS;
    }
    else if(stricmp(value, "backriseattacks") == 0)
    {
        ani_id = ANI_BACKRISEATTACKS;
    }
    else if(starts_with_num(value, "riseattack"))
    {
        get_tail_number(tempInt, value, "riseattack");
        if(tempInt == 1)
        {
            ani_id = ANI_RISEATTACK;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_RISEATTACK2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_RISEATTACK3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_RISEATTACK4;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_RISEATTACK5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_RISEATTACK6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_RISEATTACK7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_RISEATTACK8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_RISEATTACK9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_RISEATTACK10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animriseattacks[tempInt + STA_ATKS - 1];
        }
    }
    else if(starts_with_num(value, "backriseattack"))
    {
        get_tail_number(tempInt, value, "backriseattack");
        if(tempInt == 1)
        {
            ani_id = ANI_BACKRISEATTACK;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_BACKRISEATTACK2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_BACKRISEATTACK3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_BACKRISEATTACK4;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_BACKRISEATTACK5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_BACKRISEATTACK6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_BACKRISEATTACK7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_BACKRISEATTACK8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_BACKRISEATTACK9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_BACKRISEATTACK10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animbackriseattacks[tempInt + STA_ATKS - 1];
        }
    }
    else if(stricmp(value, "select") == 0)
    {
        ani_id = ANI_PICK;
    }
    else if(starts_with_num(value, "attack"))
    {
        get_tail_number(tempInt, value, "attack");
        ani_id = animattacks[tempInt - 1];
    }
    else if(stricmp(value, "throwattack") == 0)
    {
        ani_id = ANI_THROWATTACK;
    }
    else if(stricmp(value, "upper") == 0)
    {
        ani_id = ANI_UPPER;
        newanim->range.x.min = -10;
        newanim->range.x.max = 120;
    }
    else if(stricmp(value, "cant") == 0)
    {
        ani_id = ANI_CANT;
    }
    else if(stricmp(value, "jumpcant") == 0)
    {
        ani_id = ANI_JUMPCANT;
    }
    else if(stricmp(value, "charge") == 0)
    {
        ani_id = ANI_CHARGE;
    }
    else if(stricmp(value, "faint") == 0)
    {
        ani_id = ANI_FAINT;
    }
    else if(stricmp(value, "dodge") == 0)
    {
        ani_id = ANI_DODGE;
    }
    else if(stricmp(value, "special") == 0 || stricmp(value, "special1") == 0)
    {
        ani_id = ANI_SPECIAL;
        newanim->energy_cost.cost = ENERGY_COST_DEFAULT_COST;
    }
    else if(stricmp(value, "special2") == 0)
    {
        ani_id = ANI_SPECIAL2;
    }
    else if(stricmp(value, "special3") == 0 || stricmp(value, "jumpspecial") == 0)
    {
        ani_id = ANI_JUMPSPECIAL;
    }
    else if(starts_with_num(value, "freespecial"))
    {
        get_tail_number(tempInt, value, "freespecial");
        ani_id = animspecials[tempInt - 1];
    }
    else if(stricmp(value, "jumpattack") == 0)
    {
        ani_id = ANI_JUMPATTACK;
        if(newchar->jumpheight == 4)
        {
            newanim->range.x.min = 150;
            newanim->range.x.max = 200;
        }
    }
    else if(stricmp(value, "jumpattack2") == 0)
    {
        ani_id = ANI_JUMPATTACK2;
    }
    else if(stricmp(value, "jumpattack3") == 0)
    {
        ani_id = ANI_JUMPATTACK3;
    }
    else if(stricmp(value, "jumpforward") == 0)
    {
        ani_id = ANI_JUMPFORWARD;
    }
    else if(stricmp(value, "runjumpattack") == 0)
    {
        ani_id = ANI_RUNJUMPATTACK;
    }
    else if(stricmp(value, "runattack") == 0)
    {
        ani_id = ANI_RUNATTACK;    // New attack for when a player is running
    }
    else if(stricmp(value, "attackup") == 0)
    {
        ani_id = ANI_ATTACKUP;    // New attack for when a player presses u u
    }
    else if(stricmp(value, "attackdown") == 0)
    {
        ani_id = ANI_ATTACKDOWN;    // New attack for when a player presses d d
    }
    else if(stricmp(value, "attackforward") == 0)
    {
        ani_id = ANI_ATTACKFORWARD;    // New attack for when a player presses f f
    }
    else if(stricmp(value, "attackbackward") == 0)
    {
        ani_id = ANI_ATTACKBACKWARD;    // 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
    {
        ani_id = ANI_ATTACKBOTH;
    }
    else if(stricmp(value, "get") == 0)
    {
        ani_id = ANI_GET;
    }
    else if(stricmp(value, "grab") == 0)
    {
        ani_id = ANI_GRAB;
    }
    else if(stricmp(value, "backgrab") == 0) // Kratus (10-2021) Added the new backgrab animation
    {
        ani_id = ANI_BACKGRAB;
    }
    else if(stricmp(value, "vault") == 0) // Kratus (10-2021) Added the new vault animation
    {
        ani_id = ANI_VAULT;
    }
    else if(stricmp(value, "vault2") == 0) // Kratus (10-2021) Added the new vault2 animation
    {
        ani_id = ANI_VAULT2;
    }
    else if(stricmp(value, "grabwalk") == 0)
    {
        ani_id = ANI_GRABWALK;
        newanim->sync = ANI_GRABWALK;
    }
    else if(stricmp(value, "grabwalkup") == 0)
    {
        ani_id = ANI_GRABWALKUP;
        newanim->sync = ANI_GRABWALK;
    }
    else if(stricmp(value, "grabwalkdown") == 0)
    {
        ani_id = ANI_GRABWALKDOWN;
        newanim->sync = ANI_GRABWALK;
    }
    else if(stricmp(value, "grabbackwalk") == 0)
    {
        ani_id = ANI_GRABBACKWALK;
        newanim->sync = ANI_GRABWALK;
    }
    else if(stricmp(value, "grabturn") == 0)
    {
        ani_id = ANI_GRABTURN;
    }
    else if(stricmp(value, "grabbed") == 0)   // New grabbed animation for when grabbed
    {
        ani_id = ANI_GRABBED;
    }
    else if(stricmp(value, "grabbedwalk") == 0)   // New animation for when grabbed and forced to walk
    {
        ani_id = ANI_GRABBEDWALK;
        newanim->sync = ANI_GRABBEDWALK;
    }
    else if(stricmp(value, "grabbedwalkup") == 0)
    {
        ani_id = ANI_GRABWALKUP;
        newanim->sync = ANI_GRABBEDWALK;
    }
    else if(stricmp(value, "grabbedwalkdown") == 0)
    {
        ani_id = ANI_GRABWALKDOWN;
        newanim->sync = ANI_GRABBEDWALK;
    }
    else if(stricmp(value, "grabbedbackwalk") == 0)
    {
        ani_id = ANI_GRABBEDBACKWALK;
        newanim->sync = ANI_GRABBEDWALK;
    }
    else if(stricmp(value, "grabbedturn") == 0)
    {
        ani_id = ANI_GRABBEDTURN;
    }
    else if(stricmp(value, "grabattack") == 0)
    {
        ani_id = ANI_GRABATTACK;
        newanim->attack_one = 1; // default to 1, attack one one opponent
    }
    else if(stricmp(value, "grabattack2") == 0)
    {
        ani_id = ANI_GRABATTACK2;
        newanim->attack_one = 1;
    }
    else if(stricmp(value, "grabforward") == 0)   // New grab attack for when pressing forward attack
    {
        ani_id = ANI_GRABFORWARD;
        newanim->attack_one = 1;
    }
    else if(stricmp(value, "grabforward2") == 0)   // New grab attack for when pressing forward attack
    {
        ani_id = ANI_GRABFORWARD2;
        newanim->attack_one = 1;
    }
    else if(stricmp(value, "grabbackward") == 0)   // New grab attack for when pressing backward attack
    {
        ani_id = ANI_GRABBACKWARD;
        newanim->attack_one = 1;
    }
    else if(stricmp(value, "grabbackward2") == 0)   // New grab attack for when pressing backward attack
    {
        ani_id = ANI_GRABBACKWARD2;
        newanim->attack_one = 1;
    }
    else if(stricmp(value, "grabup") == 0)   // New grab attack for when pressing up attack
    {
        ani_id = ANI_GRABUP;
        newanim->attack_one = 1;
    }
    else if(stricmp(value, "grabup2") == 0)   // New grab attack for when pressing up attack
    {
        ani_id = ANI_GRABUP2;
        newanim->attack_one = 1;
    }
    else if(stricmp(value, "grabdown") == 0)   // New grab attack for when pressing down attack
    {
        ani_id = ANI_GRABDOWN;
        newanim->attack_one = 1;
    }
    else if(stricmp(value, "grabdown2") == 0)   // New grab attack for when pressing down attack
    {
        ani_id = ANI_GRABDOWN2;
        newanim->attack_one = 1;
    }
    else if(stricmp(value, "spawn") == 0)     //  spawn/respawn works separately now
    {
        ani_id = ANI_SPAWN;
    }
    else if(stricmp(value, "respawn") == 0)     //  spawn/respawn works separately now
    {
        ani_id = ANI_RESPAWN;
    }
    else if(stricmp(value, "throw") == 0)
    {
        ani_id = ANI_THROW;
    }
    else if(stricmp(value, "block") == 0)   // Now enemies can block attacks on occasion
    {
        ani_id = ANI_BLOCK;
        newanim->range.x.min = 1;
        newanim->range.x.max = 100;
    }
	else if (stricmp(value, "blockrelease") == 0) 
	{
		ani_id = ANI_BLOCKRELEASE;
	}
	else if (stricmp(value, "blockstart") == 0)
	{
		ani_id = ANI_BLOCKSTART;
	}
    else if(starts_with_num(value, "follow"))
    {
        get_tail_number(tempInt, value, "follow");
        ani_id = animfollows[tempInt - 1];
    }
    else if(stricmp(value, "chargeattack") == 0)
    {
        ani_id = ANI_CHARGEATTACK;
    }
    else if(stricmp(value, "turn") == 0)
    {
        ani_id = ANI_TURN;
    }
    else if(stricmp(value, "forwardjump") == 0)
    {
        ani_id = ANI_FORWARDJUMP;
    }
    else if(stricmp(value, "runjump") == 0)
    {
        ani_id = ANI_RUNJUMP;
    }
    else if(stricmp(value, "jumpland") == 0)
    {
        ani_id = ANI_JUMPLAND;
    }
    else if(stricmp(value, "jumpdelay") == 0)
    {
        ani_id = ANI_JUMPDELAY;
    }
    else if(stricmp(value, "hitobstacle") == 0)
    {
        ani_id = ANI_HITOBSTACLE;
    }
    else if(stricmp(value, "hitplatform") == 0)
    {
        ani_id = ANI_HITPLATFORM;
    }
    else if(stricmp(value, "hitwall") == 0)
    {
        ani_id = ANI_HITWALL;
    }
    else if(stricmp(value, "slide") == 0)
    {
        ani_id = ANI_SLIDE;
    }
    else if(stricmp(value, "runslide") == 0)
    {
        ani_id = ANI_RUNSLIDE;
    }
    else if(stricmp(value, "blockpainb") == 0)
    {
        ani_id = ANI_BLOCKPAINB;
    }
    else if(stricmp(value, "blockpains") == 0)
    {
        ani_id = ANI_BLOCKPAINS;
    }
    else if(starts_with_num(value, "blockpain"))
    {
        get_tail_number(tempInt, value, "blockpain");
        if(tempInt == 1)
        {
            ani_id = ANI_BLOCKPAIN;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_BLOCKPAIN2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_BLOCKPAIN3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_BLOCKPAIN4;
        }
        else if(tempInt == 5)
        {
            ani_id = ANI_BLOCKPAIN5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_BLOCKPAIN6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_BLOCKPAIN7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_BLOCKPAIN8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_BLOCKPAIN9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_BLOCKPAIN10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animblkpains[tempInt + STA_ATKS - 1];
        }
    }
    else if(stricmp(value, "backblockpainb") == 0)
    {
        ani_id = ANI_BACKBLOCKPAINB;
    }
    else if(stricmp(value, "backblockpains") == 0)
    {
        ani_id = ANI_BACKBLOCKPAINS;
    }
    else if(starts_with_num(value, "backblockpain"))
    {
        get_tail_number(tempInt, value, "backblockpain");
        if(tempInt == 1)
        {
            ani_id = ANI_BACKBLOCKPAIN;
        }
        else if(tempInt == 2)
        {
            ani_id = ANI_BACKBLOCKPAIN2;
        }
        else if(tempInt == 3)
        {
            ani_id = ANI_BACKBLOCKPAIN3;
        }
        else if(tempInt == 4)
        {
            ani_id = ANI_BACKBLOCKPAIN4;
        }
        else if(tempInt == 5)
        {
            ani_id = ANI_BACKBLOCKPAIN5;
        }
        else if(tempInt == 6)
        {
            ani_id = ANI_BACKBLOCKPAIN6;
        }
        else if(tempInt == 7)
        {
            ani_id = ANI_BACKBLOCKPAIN7;
        }
        else if(tempInt == 8)
        {
            ani_id = ANI_BACKBLOCKPAIN8;
        }
        else if(tempInt == 9)
        {
            ani_id = ANI_BACKBLOCKPAIN9;
        }
        else if(tempInt == 10)
        {
            ani_id = ANI_BACKBLOCKPAIN10;
        }
        else
        {
            if(tempInt < MAX_ATKS - STA_ATKS + 1)
            {
                tempInt = MAX_ATKS - STA_ATKS + 1;
            }
            ani_id = animbackblkpains[tempInt + STA_ATKS - 1];
        }
    }
    else if(stricmp(value, "duckattack") == 0)
    {
        ani_id = ANI_DUCKATTACK;
    }
    else if(stricmp(value, "walkoff") == 0)
    {
        ani_id = ANI_WALKOFF;
    }
    else if(stricmp(value, "edge") == 0)
    {
        ani_id = ANI_EDGE;
    }
    else if(stricmp(value, "backedge") == 0)
    {
        ani_id = ANI_BACKEDGE;
    }
    else if(stricmp(value, "ducking") == 0)
    {
        ani_id = ANI_DUCKING;
    }
    else if(stricmp(value, "duckrise") == 0)
    {
        ani_id = ANI_DUCKRISE;
    }
    else if(stricmp(value, "victory") == 0)
    {
        ani_id = ANI_VICTORY;
    }
    else if(stricmp(value, "lose") == 0)
    {
        ani_id = ANI_LOSE;
    }

    return ani_id;

}

void lcmHandleCommandName(ArgList *arglist, s_model *newchar, int cacheindex)
{
    char *value = GET_ARGP(1);
    s_model *tempmodel;
    if((tempmodel = findmodel(value)) && tempmodel != newchar)
    {
        borShutdown(1, "Duplicate model name '%s'", value);
    }
    /*if((tempmodel=find_model(value))) {
    	return tempmodel;
    }*/
    model_cache[cacheindex].model = newchar;
    newchar->name = model_cache[cacheindex].name;
    if(stricmp(newchar->name, "steam") == 0)
    {
        newchar->alpha = BLEND_MODE_ALPHA;
    }
}

void lcmHandleCommandType(ArgList *arglist, s_model *newchar, char *filename)
{
    char *value = GET_ARGP(1);
    int i;
    if(stricmp(value, "none") == 0)
    {
        newchar->type = TYPE_NONE;
    }
    else if(stricmp(value, "player") == 0)
    {
        newchar->type = TYPE_PLAYER;
        newchar->block_config_flags |= BLOCK_CONFIG_ACTIVE;

        for(i = 0; i < MAX_ATCHAIN; i++)
        {
            if(i < 2 || i > 3)
            {
                newchar->atchain[i] = 1;
            }
            else
            {
                newchar->atchain[i] = i;
            }
        }

        newchar->chainlength            = 4;
        newchar->bounce                 = 1;
        newchar->move_config_flags        |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z | MOVE_CONFIG_SUBJECT_TO_OBSTACLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_SCREEN | MOVE_CONFIG_SUBJECT_TO_WALL);
        newchar->move_config_flags         &= ~MOVE_CONFIG_NO_ADJUST_BASE;
    }
    else if(stricmp(value, "enemy") == 0)
    {
        newchar->type                   = TYPE_ENEMY;
        newchar->bounce                 = 1;
        newchar->move_config_flags        |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z | MOVE_CONFIG_SUBJECT_TO_OBSTACLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);
        newchar->move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;
    }
    else if(stricmp(value, "item") == 0)
    {
        newchar->type                   = TYPE_ITEM;
        newchar->move_config_flags        |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z | MOVE_CONFIG_SUBJECT_TO_OBSTACLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);
        newchar->move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;

    }
    else if(stricmp(value, "obstacle") == 0)
    {
        newchar->type                   = TYPE_OBSTACLE;
        if(newchar->aimove == AIMOVE1_NONE)
        {
            newchar->aimove = AIMOVE1_NORMAL;
        }
        newchar->aimove |= AIMOVE1_NOMOVE;
        if(newchar->aimove == AIMOVE1_NONE)
        {
            newchar->aiattack = 0;
        }
        newchar->aimove |= AIATTACK1_NOATTACK;
        newchar->move_config_flags |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);
        newchar->move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;
    }
    else if(stricmp(value, "steamer") == 0)
    {
        newchar->offscreenkill = 80;
        newchar->type = TYPE_STEAMER;
    }
	else if(stricmp(value, "projectile") == 0)
	{
		newchar->type |= TYPE_PROJECTILE;

        if (newchar->aimove == AIMOVE1_NONE)
        {
            newchar->aimove = AIMOVE1_NORMAL;
        }

        //newchar->aimove |= AIMOVE1_NORMAL;
        		
		if (!newchar->offscreenkill)
		{			
			newchar->offscreenkill = (int)(videomodes.hRes * 0.5);
		}

		// Note when using as a projectile, these may
		// be modified. See knife_spawn and bomb_spawn.

		newchar->move_config_flags |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_PROJECTILE_WALL_BOUNCE | MOVE_CONFIG_PROJECTILE_BASE_DIE | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);
		newchar->move_config_flags &= ~(MOVE_CONFIG_SUBJECT_TO_SCREEN);
	}
    // my new types   7-1-2005
    else if(stricmp(value, "pshot") == 0)
    {
        newchar->type = TYPE_SHOT;
        if(newchar->aimove == AIMOVE1_NONE)
        {
            newchar->aimove = AIMOVE1_NORMAL;
        }
        newchar->aimove |= AIMOVE1_ARROW;
        if(!newchar->offscreenkill)
        {
            newchar->offscreenkill = 200;
        }

		// Note when using as a projectile, most of these
		// are modified. See knife_spawn and bomb_spawn.
        newchar->move_config_flags |= (MOVE_CONFIG_NO_ADJUST_BASE | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z | MOVE_CONFIG_PROJECTILE_WALL_BOUNCE);
        newchar->move_config_flags &= ~(MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_SCREEN | MOVE_CONFIG_SUBJECT_TO_WALL);
    }
    else if(stricmp(value, "trap") == 0)
    {
        newchar->type                   = TYPE_TRAP;
        newchar->move_config_flags |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);
        newchar->move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;
    }
    else if(stricmp(value, "text") == 0)   // Used for displaying text/images and freezing the screen
    {
        newchar->type                   = TYPE_TEXTBOX;
        newchar->move_config_flags        &= ~MOVE_CONFIG_SUBJECT_TO_GRAVITY;
        newchar->move_config_flags        |= (MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z);
    }
    else if(stricmp(value, "endlevel") == 0)   // Used for ending the level when the players reach a certain point
    {
        newchar->type               = TYPE_ENDLEVEL;
        newchar->move_config_flags    |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_OBSTACLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);
    }
    else if(stricmp(value, "npc") == 0)   // NPC type
    {
        newchar->type                   = TYPE_NPC;
        newchar->bounce                 = 1;
        newchar->move_config_flags |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z | MOVE_CONFIG_SUBJECT_TO_OBSTACLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);
        newchar->move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;
    }
    else if(stricmp(value, "panel") == 0)   // NPC type
    {
        newchar->type                   = TYPE_PANEL;
        newchar->antigravity            = 1.0; //float type
        newchar->move_config_flags |= (MOVE_CONFIG_NO_ADJUST_BASE | MOVE_CONFIG_SUBJECT_TO_GRAVITY);
    }
    else
    {
        borShutdown(1, "Model '%s' has invalid type: '%s'", filename, value);
    }
}

void lcmHandleCommandSubtype(ArgList *arglist, s_model *newchar, char *filename)
{
    char *value = GET_ARGP(1);
    int i;
    if(stricmp(value, "biker") == 0)
    {
        newchar->subtype                                        = SUBTYPE_BIKER;
        if(newchar->aimove == AIMOVE1_NONE)
        {
            newchar->aimove                 = AIMOVE1_NORMAL;
        }
        newchar->aimove |= AIMOVE1_BIKER;
        if(!newchar->offscreenkill)
        {
            newchar->offscreenkill = 300;
        }
        
        /* Bikers deault to taking double damage. */
        for(i = 0; i < max_attack_types; i++)
        {
            newchar->defense[i].factor = 2.f;
        }

        newchar->move_config_flags |= (MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z);
        newchar->move_config_flags &= ~(MOVE_CONFIG_NO_ADJUST_BASE | MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_SCREEN | MOVE_CONFIG_SUBJECT_TO_WALL);
    }
    else if(stricmp(value, "arrow") == 0) // 7-1-2005 Arrow type
    {
        newchar->subtype = SUBTYPE_ARROW;   // 7-1-2005 Arrow type
        if(newchar->aimove == AIMOVE1_NONE)
        {
            newchar->aimove = AIMOVE1_NORMAL;
        }
        newchar->aimove |= AIMOVE1_ARROW;
        if(!newchar->offscreenkill)
        {
            newchar->offscreenkill = 200;
        }
        
        newchar->move_config_flags |= (MOVE_CONFIG_NO_ADJUST_BASE | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z);
        newchar->move_config_flags &= ~(MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_SCREEN | MOVE_CONFIG_SUBJECT_TO_WALL);
    }
    else if(stricmp(value, "notgrab") == 0)
    {
        newchar->subtype = SUBTYPE_NOTGRAB;
    }
    //    ltb 1-18-05  Item Subtype
    else if(stricmp(value, "touch") == 0)
    {
        newchar->subtype = SUBTYPE_TOUCH;
    }
    else if(stricmp(value, "weapon") == 0)
    {
        newchar->subtype = SUBTYPE_WEAPON;
    }
    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;
    }
    else if(stricmp(value, "project") == 0)
    {
        newchar->subtype = SUBTYPE_PROJECTILE;
    }
    else if(stricmp(value, "follow") == 0)
    {
        newchar->subtype = SUBTYPE_FOLLOW;
        child_follow_getsert_property(&newchar->child_follow);
    }
    else if(stricmp(value, "chase") == 0)
    {
        newchar->subtype = SUBTYPE_CHASE;
    }
    //    end new subtype
    else
    {
        borShutdown(1, "Model '%s' has invalid subtype: '%s'", filename, value);
    }
}

void lcmHandleCommandSmartbomb(ArgList *arglist, s_model *newchar, char *filename)
{
    //smartbomb now use a normal attack box
    if(!newchar->smartbomb)
    {
        newchar->smartbomb = malloc(sizeof(*newchar->smartbomb));
        *(newchar->smartbomb) = emptyattack;
    }
    else
    {
        borShutdown(1, "Model '%s' has multiple smartbomb commands defined.", filename);
    }

    newchar->smartbomb->attack_force = atoi(GET_ARGP(1));			// Special force
    newchar->smartbomb->attack_type = atoi(GET_ARGP(2));			// Special attack type
    newchar->smartbomb->attack_drop = 1; //by default
    newchar->smartbomb->dropv.y = default_model_dropv.y;

    if(newchar->smartbomb->attack_type == ATK_BLAST)
    {
        newchar->smartbomb->blast = 1;
        newchar->smartbomb->dropv.x = default_model_dropv.x * 2.083f;
    }
    else
    {
        newchar->smartbomb->dropv.x = default_model_dropv.x;
    }

    if(newchar->smartbomb->attack_type == ATK_FREEZE)
    {
        newchar->smartbomb->freeze = 1;
        newchar->smartbomb->forcemap = MAP_TYPE_FREEZE;
        newchar->smartbomb->attack_drop = 0;
    }
    else if(newchar->smartbomb->attack_type == ATK_STEAL)
    {
        newchar->smartbomb->steal = 1;
    }

    if(newchar->type == TYPE_ITEM)
    {
        newchar->dofreeze = 0;								// Items don't animate
        newchar->smartbomb->freezetime = atoi(GET_ARGP(3)) * GAME_SPEED;
    }
    else
    {
        newchar->dofreeze = atoi(GET_ARGP(3));		// Are all animations frozen during special
        newchar->smartbomb->freezetime = atoi(GET_ARGP(4)) * GAME_SPEED;
    }
}

/*
* Caskey, Damon V.
* 2019-11-22
* 
* Return type flag from string input.
*/
e_entity_type get_type_from_string(const char* value)
{
    static const struct
    {
        const char* text_name;
        e_entity_type flag;
    } item_lookup_table[] = {
        {"end_level", TYPE_ENDLEVEL},
        {"enemy", TYPE_ENEMY},
        {"item", TYPE_ITEM},
        {"no_copy", TYPE_NO_COPY},
        {"none", TYPE_NONE},
        {"npc", TYPE_NPC},
        {"obstacle", TYPE_OBSTACLE},
        {"playa", TYPE_PLAYER}, // :)
        {"player", TYPE_PLAYER},
        {"projectile", TYPE_PROJECTILE},
        {"shot", TYPE_SHOT},
        {"steamer", TYPE_STEAMER},
        {"text_box", TYPE_TEXTBOX},
        {"trap", TYPE_TRAP},
    };

    const size_t list_count = sizeof(item_lookup_table) / sizeof(*item_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, item_lookup_table[i].text_name) == 0)
        {
            return item_lookup_table[i].flag;
        }
    }

    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * unknown flag.
    */

    printf("\n\n Unknown type (%s). \n", value);
    return TYPE_UNKNOWN;
}

/*
* Caskey, Damon V.
* 2022-06-14
*
* Get arguments for type and output final
* bitmask so we can have a reusable function.
*/
e_entity_type get_type_from_arglist(ArgList* arglist)
{
    int i = 0;
    char* value = "";

    e_entity_type result = TYPE_UNDELCARED;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= get_type_from_string(value);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-04-26
*
* Backport weapon loss conditions values 
* to legacy weapon loss for backward
* compatability.
*/
e_weapon_loss_condition_legacy weapon_loss_condition_interpret_to_legacy(e_weapon_loss_condition weapon_loss_condition_value)
{
    e_weapon_loss_condition_legacy result = WEAPLOSS_TYPE_ANY;

    if ((weapon_loss_condition_value & WEAPON_LOSS_CONDITION_DEFAULT) == WEAPON_LOSS_CONDITION_DEFAULT)
    {
        result |= WEAPLOSS_TYPE_ANY;
        return result;
    }

    if (weapon_loss_condition_value & WEAPON_LOSS_CONDITION_DEATH)
    {
        result |= WEAPLOSS_TYPE_DEATH;
        return result;
    }

    if (weapon_loss_condition_value & WEAPLOSS_TYPE_KNOCKDOWN)
    {
        result |= WEAPON_LOSS_CONDITION_FALL;
        return result;
    }

    if (weapon_loss_condition_value & WEAPLOSS_TYPE_CHANGE)
    {
        result |= WEAPON_LOSS_CONDITION_STAGE;
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-04-26
*
* Interpret legacy weaploss commands into
* weapon loss bits per orginal documentation.
*/
e_weapon_loss_condition weapon_loss_condition_interpret_from_legacy_weaploss(e_weapon_loss_condition weapon_loss_condition_value, e_weapon_loss_condition_legacy legacy_value)
{
    switch (legacy_value)
    {
    case WEAPLOSS_TYPE_ANY:

        weapon_loss_condition_value |= WEAPON_LOSS_CONDITION_DEFAULT;
        break;

    case WEAPLOSS_TYPE_KNOCKDOWN:

        weapon_loss_condition_value |= WEAPON_LOSS_CONDITION_FALL;
        break;

    case WEAPLOSS_TYPE_DEATH:

        weapon_loss_condition_value |= WEAPON_LOSS_CONDITION_PAIN;
        break;

    case WEAPLOSS_TYPE_CHANGE:

        weapon_loss_condition_value |= WEAPON_LOSS_CONDITION_STAGE;
        break;

    default:
        
        weapon_loss_condition_value |= WEAPON_LOSS_CONDITION_DEFAULT;

        break;
    }

    return weapon_loss_condition_value;
}

/*
* Caskey, Damon V.
* 2022-05-02
*
* Accept string input and return
* matching constant. 
*/
e_weapon_loss_condition get_weapon_loss_from_argument(char* value)
{
    e_weapon_loss_condition result;

    if (stricmp(value, "none") == 0)
    {
        result = WEAPON_LOSS_CONDITION_NONE;
    }
    if (stricmp(value, "damage") == 0)
    {
        result = WEAPON_LOSS_CONDITION_DAMAGE;
    }
    else if (stricmp(value, "death") == 0)
    {
        result = WEAPON_LOSS_CONDITION_DEATH;
    }
    else if (stricmp(value, "fall") == 0)
    {
        result = WEAPON_LOSS_CONDITION_FALL;
    }
    else if (stricmp(value, "grabbed") == 0)
    {
        result = WEAPON_LOSS_CONDITION_GRABBED;
    }
    else if (stricmp(value, "grabbing") == 0)
    {
        result = WEAPON_LOSS_CONDITION_GRABBING;
    }
    else if (stricmp(value, "land_damage") == 0)
    {
        result = WEAPON_LOSS_CONDITION_LAND_DAMAGE;
    }
    else if (stricmp(value, "pain") == 0)
    {
        result = WEAPON_LOSS_CONDITION_PAIN;
    }
    else if (stricmp(value, "stage") == 0)
    {
        result = WEAPON_LOSS_CONDITION_STAGE;
    }
    else if (stricmp(value, "default") == 0)
    {
        result = WEAPON_LOSS_CONDITION_DEFAULT;
    }
    else
    {        
        result = WEAPON_LOSS_CONDITION_DEFAULT;
        printf("\n\n Unknown weapon loss config value (%s), using 'default'. \n", value);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-05-02
*
* Populate weapon loss model property
* from text arguments.
*/
void lcmHandleCommandWeaponLossCondition(ArgList* arglist, s_model* newchar)
{
    int i;
    char* value;
    newchar->weapon_properties.loss_condition = WEAPON_LOSS_CONDITION_NONE;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        newchar->weapon_properties.loss_condition |= get_weapon_loss_from_argument(value);
    }
}

/*
* Caskey, Damon V.
* 2022-05-24
*
* Read a text argument for model copy flag
* and output appropriate constant. If input 
* is legacy integer, we just pass it on.
*/
e_model_copy get_model_flag_from_argument(char* filename, char* command, char* value)
{
    e_model_copy result = MODEL_COPY_FLAG_NONE;

    if (stricmp(value, "none") == 0)
    {
        result = MODEL_COPY_FLAG_NONE;
    }
    else if (stricmp(value, "no_basic") == 0)
    {
        result = MODEL_COPY_FLAG_NO_BASIC;
    }
    else if (stricmp(value, "no_weapon") == 0)
    {
        result = MODEL_COPY_FLAG_NO_WEAPON;
    }
    else if (stricmp(value, "no_script") == 0)
    {
        result = MODEL_COPY_FLAG_NO_SCRIPT;
    }    
    else
    {
        /*
        * For legacy compatability. Get integer
        * value and then change it to current
        * constants.
        */

        result = getValidInt(value, filename, command);
        result = get_model_flag_from_legacy_int(result);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2023-01-30
* 
* Accept legacy integer model flag int
* and output current constant.
*/
e_model_copy get_model_flag_from_legacy_int(int legacy_int)
{
    e_model_copy result = MODEL_COPY_FLAG_NONE;

    switch (legacy_int)
    {
    case 0:
    default:
        result = MODEL_COPY_FLAG_NONE;
    case 1:
        result = (MODEL_COPY_FLAG_NO_BASIC);
        break;
    case 3:
        result = (MODEL_COPY_FLAG_NO_BASIC | MODEL_COPY_FLAG_NO_WEAPON);
        break;
    case 4:
        result = (MODEL_COPY_FLAG_NO_BASIC | MODEL_COPY_FLAG_NO_WEAPON | MODEL_COPY_FLAG_NO_SCRIPT);
        break;        
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-05-24
*
* Populate model flag property
* from text arguments.
*/
void lcmHandleCommandModelFlag(char* filename, char* command, ArgList* arglist, s_model* newchar)
{
    int i;
    char* value;
    newchar->model_flag = MODEL_COPY_FLAG_NONE;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        newchar->model_flag |= get_model_flag_from_argument(filename, command, value);
    }
}

/*
* Caskey, Damon V.
* 2022-04-26
* 
* Backport air control values to legacy
* jumpmove for backward compatability.
*/
e_air_control_legacy_x air_control_interpret_to_legacy_jumpmove_x(e_air_control air_control_value)
{
    e_air_control_legacy_x result = AIR_CONTROL_LEGACY_X_NONE;

    if (air_control_value & AIR_CONTROL_JUMP_TURN)
    {
        result |= AIR_CONTROL_LEGACY_X_FLIP;
    }

    if (air_control_value & AIR_CONTROL_JUMP_X_ADJUST)
    {
        result |= AIR_CONTROL_LEGACY_X_ADJUST;
    }

    if (air_control_value & AIR_CONTROL_JUMP_X_MOVE)
    {
        result |= AIR_CONTROL_LEGACY_X_MOVE;
    }       

    return result;
}

/*
* Caskey, Damon V.
* 2022-04-26
*
* Backport air control values to legacy
* jumpmove for backward compatability.
*/
e_air_control_legacy_z air_control_interpret_to_legacy_jumpmove_z(e_air_control air_control_value)
{
    e_air_control_legacy_z result = AIR_CONTROL_LEGACY_Z_NONE;
    
    if (air_control_value & AIR_CONTROL_JUMP_Z_INITIAL)
    {
        result |= AIR_CONTROL_LEGACY_Z_MOMENTUM;
    }
    
    if (air_control_value & AIR_CONTROL_JUMP_Z_ADJUST)
    {
        result |= AIR_CONTROL_LEGACY_Z_ADJUST;
    }
    
    if (air_control_value & AIR_CONTROL_JUMP_Z_INITIAL)
    {
        result |= AIR_CONTROL_LEGACY_Z_MOMENTUM;
    }

    if (air_control_value & AIR_CONTROL_JUMP_TURN && air_control_value & AIR_CONTROL_JUMP_Z_INITIAL)
    {
        result = AIR_CONTROL_LEGACY_Z_MOMENTUM_AND_FLIP;
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-04-26
*
* Backport air control values to legacy
* jumpmove for backward compatability.
*/
e_air_control_legacy_x air_control_interpret_to_legacy_walkoffmove_x(e_air_control air_control_value)
{
    e_air_control_legacy_x result = AIR_CONTROL_LEGACY_X_NONE;

    if (air_control_value & AIR_CONTROL_WALKOFF_TURN)
    {
        result |= AIR_CONTROL_LEGACY_X_FLIP;
    }

    if (air_control_value & AIR_CONTROL_WALKOFF_X_ADJUST)
    {
        result |= AIR_CONTROL_LEGACY_X_ADJUST;
    }

    if (air_control_value & AIR_CONTROL_WALKOFF_X_MOVE)
    {
        result |= AIR_CONTROL_LEGACY_X_MOVE;
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-04-26
*
* Backport air control values to legacy
* jumpmove for backward compatability.
*/
e_air_control_legacy_z air_control_interpret_to_legacy_walkoffmove_z(e_air_control air_control_value)
{
    e_air_control_legacy_z result = AIR_CONTROL_LEGACY_Z_NONE;

    if (air_control_value & AIR_CONTROL_JUMP_Z_INITIAL)
    {
        result |= AIR_CONTROL_LEGACY_Z_MOMENTUM;
    }

    if (air_control_value & AIR_CONTROL_JUMP_Z_ADJUST)
    {
        result |= AIR_CONTROL_LEGACY_Z_ADJUST;
    }

    if (air_control_value & AIR_CONTROL_JUMP_Z_INITIAL)
    {
        result |= AIR_CONTROL_LEGACY_Z_MOMENTUM;
    }

    if (air_control_value & AIR_CONTROL_JUMP_TURN && air_control_value & AIR_CONTROL_JUMP_Z_INITIAL)
    {
        result = AIR_CONTROL_LEGACY_Z_MOMENTUM_AND_FLIP;
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-04-26
* 
* Interpret legacy Jumpmove commands into
* air control bits per orginal documentaiton.
*
* low byte: 0 default 1 flip in air, 2 move in air, 3 flip and move                 
*/
e_air_control air_control_interpret_from_legacy_jumpmove_x(e_air_control air_control_value, e_air_control_legacy_x legacy_value)
{
    switch (legacy_value)
    {
    case AIR_CONTROL_LEGACY_X_NONE:

        air_control_value &= ~(AIR_CONTROL_JUMP_TURN | AIR_CONTROL_JUMP_X_ADJUST | AIR_CONTROL_JUMP_X_MOVE);
        break;

    case AIR_CONTROL_LEGACY_X_FLIP:

        air_control_value |= AIR_CONTROL_JUMP_TURN;
        break;

    case AIR_CONTROL_LEGACY_X_ADJUST:

        air_control_value |= AIR_CONTROL_JUMP_X_ADJUST;
        break;

    case AIR_CONTROL_LEGACY_X_ADJUST_AND_FLIP:

        air_control_value |= AIR_CONTROL_JUMP_TURN;
        air_control_value |= AIR_CONTROL_JUMP_X_ADJUST;
        break;

    case AIR_CONTROL_LEGACY_X_MOVE:
    case AIR_CONTROL_LEGACY_X_MOVE_ALT:

        air_control_value |= AIR_CONTROL_JUMP_X_MOVE;
        break;

    case AIR_CONTROL_LEGACY_X_MOVE_AND_FLIP:
    
        air_control_value |= AIR_CONTROL_JUMP_TURN;
        air_control_value |= AIR_CONTROL_JUMP_X_MOVE;
        break;

    default:

        /*
        * Handle non-existent duplicate options
        * listed in the legacy manual.
        */

        if (legacy_value > AIR_CONTROL_LEGACY_X_MOVE_ALT)
        {
            air_control_value |= AIR_CONTROL_JUMP_TURN;
            air_control_value |= AIR_CONTROL_JUMP_X_MOVE;
        }

        break;
    }

    return air_control_value;
}

/*
* Caskey, Damon V.
* 2022-04-26
*
* See interpret_legacy_jumpmove_x();
*/ 
e_air_control air_control_interpret_from_legacy_jumpmove_z(e_air_control air_control_value, e_air_control_legacy_z legacy_value)
{
    switch (legacy_value)
    {
    case AIR_CONTROL_LEGACY_Z_NONE:

        air_control_value &= ~(AIR_CONTROL_JUMP_TURN | AIR_CONTROL_JUMP_Z_ADJUST | AIR_CONTROL_JUMP_Z_INITIAL);
        break;

    case AIR_CONTROL_LEGACY_Z_MOMENTUM:

        air_control_value |= AIR_CONTROL_JUMP_Z_INITIAL;
        break;

    case AIR_CONTROL_LEGACY_Z_ADJUST:
    
        air_control_value |= AIR_CONTROL_JUMP_Z_INITIAL;
        air_control_value |= AIR_CONTROL_JUMP_Z_ADJUST;
        break; 

    case AIR_CONTROL_LEGACY_Z_MOMENTUM_AND_ADJUST:

        air_control_value |= AIR_CONTROL_JUMP_Z_INITIAL;
        air_control_value |= AIR_CONTROL_JUMP_Z_ADJUST;
        break;

    case AIR_CONTROL_LEGACY_Z_MOMENTUM_AND_FLIP:

        air_control_value |= AIR_CONTROL_JUMP_TURN;
        air_control_value |= AIR_CONTROL_JUMP_Z_MOVE;

        break;
    
    default:

        /*
        * Handle non-existent duplicate options
        * listed in the legacy manual.
        */

        if (legacy_value > AIR_CONTROL_LEGACY_Z_MOMENTUM_AND_FLIP)
        {
            air_control_value |= AIR_CONTROL_JUMP_TURN;
            air_control_value |= AIR_CONTROL_JUMP_Z_MOVE;
        }

        break;
    
    }

    return air_control_value;
}

/*
* Caskey, Damon V.
* 2022-04-26
*
* Interpret legacy walkmove commands into
* air control bits per orginal documentaiton.
*
* low byte: 0 default 1 flip in air, 2 move in air, 3 flip and move
*/
e_air_control air_control_interpret_from_legacy_walkoffmove_x(e_air_control air_control_value, e_air_control_legacy_x legacy_value)
{
    switch (legacy_value)
    {
    case AIR_CONTROL_LEGACY_X_NONE:

        air_control_value &= ~(AIR_CONTROL_WALKOFF_TURN | AIR_CONTROL_WALKOFF_X_ADJUST);
        break;

    case AIR_CONTROL_LEGACY_X_FLIP:

        air_control_value |= AIR_CONTROL_WALKOFF_TURN;
        break;

    case AIR_CONTROL_LEGACY_X_ADJUST:

        air_control_value |= AIR_CONTROL_WALKOFF_X_ADJUST;
        break;

    case AIR_CONTROL_LEGACY_X_ADJUST_AND_FLIP:
    case AIR_CONTROL_LEGACY_X_MOVE_AND_FLIP:

        air_control_value |= AIR_CONTROL_WALKOFF_TURN;
        air_control_value |= AIR_CONTROL_WALKOFF_X_ADJUST;
        break;

    case AIR_CONTROL_LEGACY_X_MOVE:
    case AIR_CONTROL_LEGACY_X_MOVE_ALT:

        air_control_value |= AIR_CONTROL_WALKOFF_X_ADJUST;
        break;

    default:

        /*
        * Handle non-existent duplicate options
        * listed in the legacy manual.
        */

        if (legacy_value > AIR_CONTROL_LEGACY_X_MOVE_ALT)
        {
            air_control_value |= AIR_CONTROL_WALKOFF_TURN;
            air_control_value |= AIR_CONTROL_WALKOFF_X_ADJUST;
        }

        break;
    }

    return air_control_value;
}

/*
* Caskey, Damon V.
* 2022-04-26
*
* Interpret legacy walkmove commands into
* air control bits per orginal documentaiton.
*
* low byte: 0 default 1 flip in air, 2 move in air, 3 flip and move
*/
e_air_control air_control_interpret_from_legacy_walkoffmove_z(e_air_control air_control_value, e_air_control_legacy_z legacy_value)
{
    switch (legacy_value)
    {
    case AIR_CONTROL_LEGACY_Z_NONE:

        air_control_value &= ~AIR_CONTROL_WALKOFF_Z_ADJUST;
        break;

    case AIR_CONTROL_LEGACY_Z_MOMENTUM:

        air_control_value |= AIR_CONTROL_WALKOFF_Z_ADJUST;
        break;

    case AIR_CONTROL_LEGACY_Z_ADJUST:

        air_control_value |= AIR_CONTROL_WALKOFF_Z_ADJUST;
        break;

    case AIR_CONTROL_LEGACY_Z_MOMENTUM_AND_ADJUST:

        air_control_value |= AIR_CONTROL_WALKOFF_Z_ADJUST;
        break;

    case AIR_CONTROL_LEGACY_Z_MOMENTUM_AND_FLIP:

        air_control_value |= AIR_CONTROL_JUMP_TURN;
        air_control_value |= AIR_CONTROL_WALKOFF_Z_ADJUST;

        break;

    default:

        /*
        * Handle non-existent duplicate options
        * listed in the legacy manual.
        */

        if (legacy_value > AIR_CONTROL_LEGACY_Z_MOMENTUM_AND_FLIP)
        {
            air_control_value |= AIR_CONTROL_JUMP_TURN;
            air_control_value |= AIR_CONTROL_JUMP_Z_MOVE;
        }

        break;

    }

    return air_control_value;
}

/*
* Caskey, Damon V.
* 2022-04-28
*
* Accept string input and return
* matching constant.
*/
e_air_control find_air_control_from_string(const char* value)
{
    static const struct
    {
        const char* text_name;
        e_air_control flag;
    } item_lookup_table[] = {
        {"none", AIR_CONTROL_NONE},
        {"jump_disable", AIR_CONTROL_JUMP_DISABLE},
        {"jump_turn", AIR_CONTROL_JUMP_TURN},
        {"jump_x_adjust", AIR_CONTROL_JUMP_X_ADJUST},
        {"jump_x_move", AIR_CONTROL_JUMP_X_MOVE},
        {"jump_x_stop", AIR_CONTROL_JUMP_X_STOP},
        {"jump_y_stop", AIR_CONTROL_JUMP_Y_STOP},
        {"jump_z_adjust", AIR_CONTROL_JUMP_Z_ADJUST},
        {"jump_z_initial", AIR_CONTROL_JUMP_Z_INITIAL},
        {"jump_z_move", AIR_CONTROL_JUMP_Z_MOVE},
        {"jump_z_stop", AIR_CONTROL_JUMP_Z_STOP},
        {"walkoff_turn", AIR_CONTROL_WALKOFF_TURN},
        {"walkoff_x_adjust", AIR_CONTROL_WALKOFF_X_ADJUST},
        {"walkoff_x_move", AIR_CONTROL_WALKOFF_X_MOVE},
        {"walkoff_x_stop", AIR_CONTROL_WALKOFF_X_STOP},
        {"walkoff_z_adjust", AIR_CONTROL_WALKOFF_Z_ADJUST},
        {"walkoff_z_move", AIR_CONTROL_WALKOFF_Z_MOVE},
        {"walkoff_z_stop", AIR_CONTROL_WALKOFF_Z_STOP},
    };

    const size_t list_count = sizeof(item_lookup_table) / sizeof(*item_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, item_lookup_table[i].text_name) == 0)
        {
            return item_lookup_table[i].flag;
        }
    }

    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * unknown flag.
    */

    printf("\n\n  Unknown air control flag (%s). \n", value);
    return AIR_CONTROL_NONE;
}

/*
* Caskey, Damon V.
* 2022-04-28
*
* Populate air control model property
* from text arguments.
*/
void lcmHandleCommandAirControl(const ArgList* arglist, s_model* newchar)
{
    int i;
    char* value;
    newchar->air_control = AIR_CONTROL_NONE;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        newchar->air_control |= find_air_control_from_string(value);
    }
}

/*
* Caskey, Damon V.
* 2019-11-22
* 
* Accept string input and return 
* matching constant.
*/
e_move_config_flags find_move_config_flags_from_string(const char* value)
{
    static const struct
    {
        const char* text_name;
        e_move_config_flags flag;
    } item_lookup_table[] = {
        {"none", MOVE_CONFIG_NONE},
        {"no_adjust_base", MOVE_CONFIG_NO_ADJUST_BASE},
        {"no_flip", MOVE_CONFIG_NO_FLIP},
        {"no_hit_head", MOVE_CONFIG_NO_HIT_HEAD},
        {"no_move", MOVE_CONFIG_NO_MOVE},
        {"projectile_base_die", MOVE_CONFIG_PROJECTILE_BASE_DIE},
        {"projectile_wall_bounce", MOVE_CONFIG_PROJECTILE_WALL_BOUNCE},
        {"subject_to_basemap", MOVE_CONFIG_SUBJECT_TO_BASEMAP},
        {"subject_to_gravity", MOVE_CONFIG_SUBJECT_TO_GRAVITY},
        {"subject_to_hole", MOVE_CONFIG_SUBJECT_TO_HOLE},
        {"subject_to_max_z", MOVE_CONFIG_SUBJECT_TO_MAX_Z},
        {"subject_to_min_z", MOVE_CONFIG_SUBJECT_TO_MIN_Z},
        {"subject_to_obstacle", MOVE_CONFIG_SUBJECT_TO_OBSTACLE},
        {"subject_to_platform", MOVE_CONFIG_SUBJECT_TO_PLATFORM},
        {"subject_to_screen", MOVE_CONFIG_SUBJECT_TO_SCREEN},
        {"subject_to_wall", MOVE_CONFIG_SUBJECT_TO_WALL}
    };

    const size_t list_count = sizeof(item_lookup_table) / sizeof(*item_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, item_lookup_table[i].text_name) == 0)
        {
            return item_lookup_table[i].flag;
        }
    }

    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * unknown flag.
    */

    printf("\n\n  Unknown move config flag (%s). \n", value);
    return MOVE_CONFIG_NONE;
}

/*
* Caskey, Damon V.
* 2021-08-24
*
* Read a text argument for ko map type and 
* output appropriate constant. If input is 
* legacy integer, we just pass it on.
*/
e_ko_colorset_config komap_type_get_value_from_argument(char* filename, char* command, char* value)
{
    e_ko_colorset_config result = KO_COLORSET_CONFIG_INSTANT;

    if (stricmp(value, "instant") == 0)
    {
        result = KO_COLORSET_CONFIG_INSTANT;
    }
    else if (stricmp(value, "finish") == 0)
    {
        result = KO_COLORSET_CONFIG_COMPLETE;
    }
    else
    {
        result = getValidInt(value, filename, command);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-06-14
*
* Get arguments for move constraint and 
* output final bitmask so we can have a 
* reusable function.
*/
e_move_config_flags get_move_config_flags_from_arguments(ArgList* arglist)
{
    int i = 0;
    char* value = "";

    e_move_config_flags result = MOVE_CONFIG_NONE;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= find_move_config_flags_from_string(value);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2022-04-28
*
* Accept string input and return
* matching constant.
*/
e_cheat_options find_cheat_options_from_string(const char* value)
{
    static const struct
    {
        const char* text_name;
        e_cheat_options flag;
    } item_lookup_table[] = {
        {"none", CHEAT_OPTIONS_NONE},
        {"credits_active", CHEAT_OPTIONS_CREDITS_ACTIVE},
        {"credits_menu", CHEAT_OPTIONS_CREDITS_MENU},
        {"energy_active", CHEAT_OPTIONS_ENERGY_ACTIVE},
        {"energy_menu", CHEAT_OPTIONS_ENERGY_MENU},
        {"health_active", CHEAT_OPTIONS_HEALTH_ACTIVE},
        {"health_menu", CHEAT_OPTIONS_HEALTH_MENU},
        {"implacable_active", CHEAT_OPTIONS_IMPLACABLE_ACTIVE},
        {"implacable_menu", CHEAT_OPTIONS_IMPLACABLE_MENU},
        {"master_menu", CHEAT_OPTIONS_MASTER_MENU},
        {"lives_active", CHEAT_OPTIONS_LIVES_ACTIVE},
        {"lives_menu", CHEAT_OPTIONS_LIVES_MENU},
        {"multihit_active", CHEAT_OPTIONS_MULTIHIT_ACTIVE},
        {"multihit_menu", CHEAT_OPTIONS_MULTIHIT_MENU},
        {"touch_of_death_active", CHEAT_OPTIONS_TOD_ACTIVE},
        {"touch_of_death_menu", CHEAT_OPTIONS_TOD_MENU}
    };

    const size_t list_count = sizeof(item_lookup_table) / sizeof(*item_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, item_lookup_table[i].text_name) == 0)
        {
            return item_lookup_table[i].flag;
        }
    }

    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * unknown flag.
    */

    printf("\n\n  Unknown cheat option (%s). \n", value);
    return CHEAT_OPTIONS_NONE;

}

/*
* Caskey, Damon V.
* 2022-04-28
*
* Populate global config cheats 
* property from text arguments.
*/
void lcmHandleCommandGlobalConfigCheats(ArgList* arglist)
{
    int i;
    char* value;
    global_config.cheats = CHEAT_OPTIONS_NONE;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        global_config.cheats |= find_cheat_options_from_string(value);    
    }
}

/*
* Caskey, Damon V.
* 2022-06-08
*
* Accept string input and return
* matching constant.
*/
e_aimove get_aimove_constant_from_string(const char* value)
{
    static const struct
    {
        const char* text_name;
        e_aimove flag;
    } item_lookup_table[] = {
        {"none", AIMOVE1_NONE},
        {"default", AIMOVE_SPECIAL_DEFAULT},
        {"chase", AIMOVE1_CHASE},
        {"chasez", AIMOVE1_CHASEZ},
        {"chasex", AIMOVE1_CHASEX},
        {"avoid", AIMOVE1_AVOID},
        {"avoidz", AIMOVE1_AVOIDZ},
        {"avoidx", AIMOVE1_AVOIDX},
        {"wander", AIMOVE1_WANDER},
        {"biker", AIMOVE1_BIKER},
        {"arrow", AIMOVE1_ARROW},
        {"star", AIMOVE1_STAR},
        {"bomb", AIMOVE1_BOMB},
        {"nomove", AIMOVE1_NOMOVE},
        {"ignoreholes", AIMOVE2_IGNOREHOLES},
        {"notargetidle", AIMOVE2_NOTARGETIDLE}
    };

    const size_t list_count = sizeof(item_lookup_table) / sizeof(*item_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, item_lookup_table[i].text_name) == 0)
        {
            return item_lookup_table[i].flag;
        }
    }

    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * unknown flag.
    */

    printf("\n\n  Unknown AIMove flag (%s). \n", value);
    return AIMOVE1_NONE;
    
}

/*
* Caskey, Damon V.
* 2022-06-08
* 
* Get arguments for Aimove and output final 
* bitmask. Replaces lcmHandleCommandAiMove 
* so we can have a reusable function.
*/
e_aimove get_aimove_from_arguments(const ArgList *arglist, e_aimove default_value)
{    
    int i = 0;
    char* value = "";

    e_aimove result = default_value;
    
    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= get_aimove_constant_from_string(value);
    }    

    return result;
}

void lcmHandleCommandAiattack(ArgList *arglist, s_model *newchar, int *aiattackset, char *filename)
{
    char *value = GET_ARGP(1);
    if(!*aiattackset)
    {
        newchar->aiattack = 0;
        *aiattackset = 1;
    }

    //main A.I. move switches
    if(value && value[0])
    {
        if(stricmp(value, "normal") == 0)
        {
            newchar->aiattack |= AIATTACK1_NORMAL;
        }
        else if(stricmp(value, "always") == 0)
        {
            newchar->aiattack |= AIATTACK1_ALWAYS;
        }
        else if(stricmp(value, "noattack") == 0)
        {
            newchar->aiattack |= AIATTACK1_NOATTACK;
        }
        else
        {
            printf("Model '%s' has invalid A.I. attack switch: '%s'\n", filename, value);
        }
    }
    /*
    value = GET_ARGP(2);
    //sub A.I. move switches
    if(value && value[0])
    {

    }*/
}

void lcmHandleCommandWeapons(ArgList *arglist, s_model *newchar)
{
    int weapon_index = 0;
    char *value;
    
    for(weapon_index = 0; ; weapon_index++)
    {
        value = GET_ARGP(weapon_index + 1);
        if(!value[0])
        {
            break;
        }
    }

    if(!weapon_index)
    {
        return;
    }

    newchar->weapon_properties.weapon_count = weapon_index;

    if(!newchar->weapon_properties.weapon_list)
    {
        newchar->weapon_properties.weapon_list = malloc(sizeof(*newchar->weapon_properties.weapon_list) * newchar->weapon_properties.weapon_count);
        memset(newchar->weapon_properties.weapon_list, 0xFF, sizeof(*newchar->weapon_properties.weapon_list) * newchar->weapon_properties.weapon_count);
        newchar->weapon_properties.weapon_state |= WEAPON_STATE_HAS_LIST;
    }

    /*
    * Weapon list arguments left to right, until we
    * reach this model's number of weapons. If none,
    * populate wiith "none" index. Otherwise we find
    * a model index to populate with.
    */

    for(weapon_index = 0; weapon_index < newchar->weapon_properties.weapon_count; weapon_index++)
    {
        value = GET_ARGP(weapon_index + 1);

        if(stricmp(value, "none") != 0)
        {
            newchar->weapon_properties.weapon_list[weapon_index] = get_cached_model_index(value);
        }
        else
        {
            newchar->weapon_properties.weapon_list[weapon_index] = MODEL_INDEX_NONE;
        }
    }
}

//fetch string between next @script and @end_script
//return end position of @end_script, count from current position
static size_t fetchInlineScript(char *buf, char **scriptbuf, ptrdiff_t *ppos, size_t *plen)
{
    ptrdiff_t pos = *ppos;
    size_t len;
    while(!starts_with(buf + pos, "@script"))
    {
        pos++;
    }
    pos += strclen("@script");
    len = 0;
    while(!starts_with(buf + pos, "@end_script"))
    {
        len++;
        pos++;
    }
    *scriptbuf = malloc(sizeof(**scriptbuf) * len + 1);
    strncpy(*scriptbuf, buf + pos - len, len);
    (*scriptbuf)[len] = 0;
    pos += strclen("@end_script");

    *ppos = pos;
    *plen = len;
    return pos;
}

size_t lcmHandleCommandScripts(ArgList *arglist, char *buf, Script *script, char *scriptname, char *filename, int compile, int first)
{
    ptrdiff_t pos = 0;
    size_t len = 0;
    int result = 0;
    char *scriptbuf = NULL;
    Script_Init(script, scriptname, filename, first);
    if(stricmp(GET_ARGP(1), "@script") == 0)
    {
        fetchInlineScript(buf, &scriptbuf, &pos, &len);
        if(scriptbuf)
        {
            result = Script_AppendText(script, scriptbuf, filename);
            free(scriptbuf);
        }
    }
    else
    {
        result = load_script(script, GET_ARGP(1));
    }
    if(result)
    {
        if(compile)
        {
            Script_Compile(script);
        }
    }
    else
    {
        borShutdown(1, "Unable to load %s '%s' in file '%s'.\n", scriptname, GET_ARGP(1), filename);
    }
    return pos;
}

ptrdiff_t lcmScriptAvoidComment(char *buf, size_t buf_len, ptrdiff_t pos)
{
    if ( buf && buf[0] )
    {
        unsigned char c = '\0';
        ptrdiff_t pre_pos = pos;

        c = buf[pos];
        if (c == '/')
        {
            ++pos;
            if ( pos < buf_len-1 )
            {
                c = buf[pos];
                if (c == '/')
                {
                    while( pos < buf_len && c != 0x0D && c != 0x0A )
                    {
                        ++pos;
                        c = buf[pos];
                    }
                    if (pos < buf_len) ++pos;
                    pos = lcmScriptAvoidComment(buf,buf_len,pos);
                    return pos;
                }
                else if (c == '*')
                {
                    ++pos;
                    if ( pos >= buf_len ) return pos;
                    c = buf[pos];
                    while( pos+1 < buf_len )
                    {
                        if ( c == '*' )
                        {
                            unsigned char c2 = buf[pos+1];

                            if ( c2 == '/' )
                            {
                                pos += 2;
                                pos = lcmScriptAvoidComment(buf,buf_len,pos);
                                return pos;
                            }
                        }
                        ++pos;
                        c = buf[pos];
                    }
                }
                else
                {
                    return pre_pos;
                }
            }
        } else return pre_pos;
    }

    return pos;
}

ptrdiff_t lcmScriptGetMainPos(char *buf, size_t *buf_len)
{
    if ( buf && buf[0] )
    {
        size_t len = *buf_len;
        ptrdiff_t pos = 0;
        enum {START,PRE0,PRE1,PRE2,PRE3,M0,M1,M2,M3,P0,P1,END} current_state = START;
        unsigned char c = '\0';
        int index_res = -1;

        pos = lcmScriptAvoidComment(buf,len,pos);
        c = (unsigned char)tolower((int)buf[pos]);
        while ( pos < len && current_state != END )
        {
            switch(current_state)
            {
                case START:
                {
                    if ( c == 'v' )
                    {
                        index_res = pos; // store void main() start pos
                        current_state = PRE0;
                    }
                    else if ( c == ' ' || c == '\n' || c == '\r' || c == 0x0D || c == 0x0A || c == '\t' ) current_state = START;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case PRE0:
                {
                    if ( c == 'o' ) current_state = PRE1;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case PRE1:
                {
                    if ( c == 'i' ) current_state = PRE2;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case PRE2:
                {
                    if ( c == 'd' ) current_state = PRE3;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case PRE3:
                {
                    if ( c == 'm' ) current_state = M0;
                    else if ( c == ' ' || c == '\n' || c == '\r' || c == 0x0D || c == 0x0A || c == '\t' ) current_state = PRE3;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case M0:
                {
                    if ( c == 'a' ) current_state = M1;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case M1:
                {
                    if ( c == 'i' ) current_state = M2;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case M2:
                {
                    if ( c == 'n' ) current_state = M3;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case M3:
                {
                    if ( c == '(' ) current_state = P0;
                    else if ( c == ' ' || c == '\n' || c == '\r' || c == 0x0D || c == 0x0A || c == '\t' ) current_state = M3;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case P0:
                {
                    if ( c == ')' ) current_state = P1;
                    else if ( c == ' ' || c == '\n' || c == '\r' || c == 0x0D || c == 0x0A || c == '\t' ) current_state = P0;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case P1:
                {
                    if ( c == '{' )
                    {
                        current_state = END;
                        if (++pos < len)
                        {
                            *buf_len = pos-index_res;
                            return index_res;
                        }
                        else return -1;
                    }
                    else if ( c == ' ' || c == '\n' || c == '\r' || c == 0x0D || c == 0x0A || c == '\t' ) current_state = P1;
                    else if ( c == '\0' ) return -1;
                    else current_state = START;
                    break;
                }
                case END:
                default:
                    break;
            }
            ++pos; // at the end position after the '{'
            if (current_state == END)
            {
                if (pos < len)
                {
                    *buf_len = pos-index_res;
                    return index_res;
                }
                else return -1;
            }
            pos = lcmScriptAvoidComment(buf,len,pos);
            c = (unsigned char)tolower((int)buf[pos]);
        } // end while loop

        if (current_state == END)
        {
            if (pos < len)
            {
                *buf_len = pos-index_res;
                return index_res;
            }
            else return -1;
        } else return -1;
    }

    return -1;
}

// add main (or add vars in main) - used for animationscript file
size_t lcmScriptAddMain(char **buf)
{
    size_t len = 0, len2 = 0, buf_len = 0;
    ptrdiff_t pos = 0;
    char* newbuf = NULL;

    if ( (*buf) && (*buf)[0] )
    {
        buf_len = len = strlen(*buf);

        pos = lcmScriptGetMainPos(*buf,&buf_len);
        if ( pos == -1 ) // main() not found!
        {
            char mtxt[] = "\n\nvoid main()\n{\n    int frame = getlocalvar(\"frame\");\n    int animhandle = getlocalvar(\"animhandle\");\n\n}\n\n";

            pos += buf_len;
            pos = len; // pos before '\0' (at last char)
            len2 = strlen(mtxt);
            newbuf = calloc(sizeof(**buf)*len + sizeof(mtxt)*len2 + 1, sizeof(char));
            strncpy(newbuf, *buf, pos);
            strcpy(newbuf+pos, mtxt);
            newbuf[len+len2] = '\0';

            free( (*buf) );
            (*buf) = newbuf;

            //printf("written main in buffer\n%s\n",newbuf);
        }
        else
        {
            int pos2 = 0;
            char mtxt[] = "\n\nvoid main()\n{\n    int frame = getlocalvar(\"frame\");\n    int animhandle = getlocalvar(\"animhandle\");\n\n";

            pos2 = pos + buf_len;
            len2 = strlen(mtxt);

            newbuf = calloc(sizeof(**buf)*pos + sizeof(mtxt)*len2 + sizeof(**buf)*(len-pos2) + 1, sizeof(char));
            strncpy(newbuf, *buf, pos);
            strcpy(newbuf+pos, mtxt);
            strncpy(newbuf+pos+len2, *buf+pos2, len-pos2);
            newbuf[pos+len2+len-pos2] = '\0';

            free( (*buf) );
            (*buf) = newbuf;

            //printf("search for main in buffer\n%s pos:%d\n",*buf,pos);
        }

        //printf("search for main in buffer\n%s pos:%d\n",*buf,pos);
    }

    return len;
}

// used to join animationscript @script/@cmd width animationscript file main()
size_t lcmScriptJoinMain(char **buf, char *first_buf)
{
    size_t len = 0, len2 = 0, buf_len = 0;
    ptrdiff_t pos = 0;
    int pcount = 0;
    char* newbuf = NULL;

    if ( (*buf) && first_buf && (*buf)[0] && first_buf[0] )
    {
        buf_len = len = strlen(*buf);
        len2 = strlen(first_buf);

        pos = lcmScriptGetMainPos(*buf,&buf_len);
        pos += buf_len;

        pcount = 1;
        while(pcount > 0)
        {
            if( (*buf)[pos] == '{' ) ++pcount;
            else if( (*buf)[pos] == '}' ) --pcount;
            pos++;
        }
        --pos; // back to last '}' of main()

        newbuf = calloc(sizeof(**buf)*len + sizeof(*first_buf)*len2 + 1,sizeof(char));
        strncpy(newbuf, *buf, pos);
        strcpy(newbuf+pos, first_buf);
        strncpy(newbuf+pos+len2, *buf+pos, len-pos);
        newbuf[len+len2] = '\0';

        free( (*buf) );
        (*buf) = newbuf;

        //printf("test joined buffer\n%s\n",*buf);
    }

    return len;
}

size_t lcmScriptCopyBuffer(ArgList *arglist, char *buf, char **script_buffer)
{
    ptrdiff_t pos = 0;
    size_t len = 0;

    if(stricmp(GET_ARGP(1), "@script") == 0)
    {
        fetchInlineScript(buf, script_buffer, &pos, &len);
    }
    else
    {
        buffer_pakfile(GET_ARGP(1), script_buffer, &len);
    }

    //printf("test buffer\n%s\n",*script_buffer);
    return pos;
}

size_t lcmScriptDeleteMain(char **buf)
{
    size_t len = 0, i = 0;
    ptrdiff_t pos = 0;
    char *newbuf = NULL;

    if ((*buf) && (*buf)[0])
    {
        len = strlen(*buf);

        while(!starts_with((*buf) + pos, "main()")) pos++;
        pos += strclen("main()");
        while(!starts_with(*buf + pos, "{")) pos++;
        pos += strclen("{");
        while(!starts_with(*buf + pos, "int frame = getlocalvar(\"frame\");")) pos++;
        pos += strclen("int frame = getlocalvar(\"frame\");");
        while(!starts_with(*buf + pos, "int animhandle = getlocalvar(\"animhandle\");\n")) pos++;
        pos += strclen("int animhandle = getlocalvar(\"animhandle\");\n");

        for(i = len-1; ; i--)
        {
            if ( (*buf)[i] == '}' )
            {
                len = i;
                break;
            } else continue;
            if (i <= 0) break;
        }

        len = len-pos;
        newbuf = malloc(sizeof(newbuf) * (len) + 1);
        strncpy(newbuf, *buf+pos, len);
        newbuf[len] = '\0';

        free( (*buf) );
        (*buf) = newbuf;

        //printf("test delete main\n%s\n",newbuf);
    }

    return len;
}

//alloc a new model, and everything thats required,
//set all values to defaults
s_model *init_model(const int cacheindex, const int unload)
{
    //to free: newchar, newchar->offense_factors, newchar->special, newchar->animation - OK
    int i;

    s_model* const newchar = calloc(1, sizeof(*newchar));
    if(!newchar)
    {
        borShutdown(1, (char *)E_OUT_OF_MEMORY);
    }
    newchar->object_type = OBJECT_TYPE_MODEL;
    newchar->test_pointer = model_cache[cacheindex].name;    
    strcpy(newchar->test_fixed, model_cache[cacheindex].name);

    newchar->name = model_cache[cacheindex].name; // well give it a name for sort method
    newchar->index = cacheindex;
    newchar->isSubclassed = 0;
    newchar->freetypes = MF_ALL;

    newchar->priority = 1;

    newchar->defense = defense_allocate_object();
    newchar->offense = offense_allocate_object();

    newchar->special                = calloc(1, sizeof(s_com));

    alloc_all_scripts(&newchar->scripts);
    
    

    newchar->death_config_flags = DEATH_CONFIG_MACRO_DEFAULT;

    newchar->edelay = (s_edelay){
        .cap = {
            .max = MAX_INT,
            .min = 0
        },
        .range = {
            .max = MAX_INT,
            .min = 0
        },
        .factor = 1.0f
    };

    newchar->unload             = unload;
    newchar->jumpspecial        = 0; // Kratus (10-2021) Added new property to kill or not the default jumpspecial movement
    newchar->jumpspeed          = default_model_jumpspeed;
    newchar->jumpheight         = default_model_jumpheight; // 28-12-2004   Set default jump height to 4, if not specified
    newchar->runjumpheight      = default_model_jumpheight; // Default jump height if a player is running
    newchar->runjumpdist        = 1; // Default jump distane if a player is running
    newchar->grabdistance       = default_model_grabdistance; //  30-12-2004 Default grabdistance is same as originally set
    newchar->grabflip		    = 3;
    newchar->throwdamage        = 21; //21 // default throw damage
    
    newchar->icon = (s_icon){
        .def = ICON_NONE,
        .die = ICON_NONE,
        .get = ICON_NONE,
        .mphigh = ICON_NONE,
        .mplow = ICON_NONE,
        .mpmed = ICON_NONE,
        .pain = ICON_NONE,
        .weapon = ICON_NONE
    };

    newchar->diesound           = SAMPLE_ID_NONE;
    newchar->nolife             = 0;			    // default show life = 1 (yes)
    newchar->shadow_config_flags = SHADOW_CONFIG_DEFAULT;
    newchar->remove             = 1;			    // Flag set to weapons are removed upon hitting an opponent
    newchar->throwdist          = default_model_jumpheight * 0.625f;
    newchar->aimove             = AIMOVE1_NONE;
    newchar->aiattack           = -1;
    newchar->throwframewait     = FRAME_NONE;               // makes sure throw animations run normally unless throwfram is specified, added by kbandressen 10/20/06
    newchar->path               = model_cache[cacheindex].path;         // Record path, so script can get it without looping the whole model collection.
    newchar->edgerange.x        = 0;
    newchar->edgerange.z        = 0;

    newchar->colorsets = (s_colorset){
        .burn = COLORSET_INDEX_NONE,
        .frozen = COLORSET_INDEX_NONE,
        .hide_end = COLORSET_INDEX_NONE,
        .hide_start = COLORSET_INDEX_NONE,
        .ko = COLORSET_INDEX_NONE,
        .kotype = KO_COLORSET_CONFIG_INSTANT,
        .shock = COLORSET_INDEX_NONE
    };

    // Default Attack1 in chain must be referenced if not used.
    for(i = 0; i < MAX_ATCHAIN; i++)
    {
        newchar->atchain[i] = 1;
    }
    newchar->chainlength = 1;

    if(magic_type == 1)
    {
        newchar->mprate = 1;
    }
    else
    {
        newchar->mprate                 = 2;
    }
    newchar->chargerate = newchar->guardrate = 2;
    newchar->risetime.rise              = -1;
    newchar->sleepwait                  = 1000;

    newchar->jugglepoints = (s_status_points){
        .current = 0,
        .max = 0,
        .min = 0
    };

    newchar->guardpoints = (s_status_points){
        .current = 0,
        .max = 0,
        .min = 0
    };

    newchar->mpswitch                   = -1;       // switch between reduce mp or gain mp for mpstabletype 4
    
    newchar->weapon_properties = (s_weapon){
        .loss_condition = WEAPON_LOSS_CONDITION_DEFAULT,
        .loss_count = 3,
        .loss_index = MODEL_INDEX_NONE
    };
    
    newchar->lifespan                   = LIFESPAN_DEFAULT;
    newchar->summonkill                 = 1;
    
    /* 
    * Faction data. Faction type properties 
    * get defaults set downstream depending 
    * on the the model's own type.
    */

    newchar->faction = (s_faction){
        .damage_direct = FACTION_GROUP_DEFAULT,
        .damage_indirect = FACTION_GROUP_DEFAULT,
        .hostile = FACTION_GROUP_DEFAULT,
        .member = FACTION_GROUP_DEFAULT,
        .type_damage_direct = TYPE_UNDELCARED,
        .type_damage_indirect = TYPE_UNDELCARED,
        .type_hostile = TYPE_UNDELCARED
    };

    newchar->move_config_flags            = MOVE_CONFIG_NONE;
    newchar->pshotno                    = MODEL_INDEX_NONE;
    newchar->project                    = MODEL_INDEX_NONE;
    
    newchar->dust = (s_dust){
        .fall_land = MODEL_INDEX_NONE,
        .jump_land = MODEL_INDEX_NONE,
        .jump_start = MODEL_INDEX_NONE
    };
    
    newchar->bomb                       = MODEL_INDEX_NONE;
    newchar->star                       = MODEL_INDEX_NONE;
    newchar->knife                      = MODEL_INDEX_NONE;
    newchar->stealth.hide               = 0;
    newchar->stealth.detect             = 0;
    newchar->attackthrottle				= 0.0f;
    newchar->attackthrottletime			= noatk_duration * GAME_SPEED;

    newchar->animation = calloc(max_animations, sizeof(*newchar->animation));
    if(!newchar->animation)
    {
        borShutdown(1, (char *)E_OUT_OF_MEMORY);
    }

    // default string value, only by reference
    newchar->rider = get_cached_model_index("K'");
    newchar->flash = newchar->bflash = get_cached_model_index("flash");

    //Default sight ranges.
    newchar->sight = (s_sight){
        .max = {.x = MAX_INT, .y = MAX_INT, .z = MAX_INT },
        .min = {.x = MIN_INT, .y = MIN_INT, .z = MIN_INT }
    };

    return newchar;
}

void update_model_loadflag(s_model *model, char unload)
{
    model->unload = unload;
}

s_model *load_cached_model(char *name, char *owner, char unload)
{
    #define LOG_CMD_TITLE   "%-20s"

    s_model* newchar = NULL;
    s_model* tempmodel = NULL;

    s_anim *newanim = NULL;

    char* filename = NULL;
    char* buf = NULL;
    char* animscriptbuf = NULL;
    char* scriptbuf = NULL;
    char* command = NULL;
    char* value = NULL;
    char* value2 = NULL;
    char* value3 = NULL;

    char fnbuf[MAX_BUFFER_LEN] = { "" };
    char namebuf[MAX_BUFFER_LEN] = { "" };
    char argbuf[MAX_ARG_LEN + 1] = { "" };

    ArgList arglist;

    int ani_id = ANI_NONE;
    int script_id = -1;
    int frm_id = -1;
    int i = 0;
    int j = 0;
    int tempInt = 0;
    int framecount = 0;
    int frameset = 0;
    int peek = 0;
    int cacheindex = 0;
    int curframe = 0;
    int delay = 0;
    int errorVal = 0;
    int shadow_set = 0;
    int idle = 0;
    int frameshadow = FRAME_SHADOW_NONE;    // FRAME_SHADOW_NONE will use default shadow for this entity, otherwise will use this value.
    int soundtoplay = SAMPLE_ID_NONE;
    int aiattackset = 0;
    int maskindex = -1;
    int nopalette = 0;
    
    size_t size = 0;
    size_t line = 0;
    size_t len = 0;
    size_t sbsize = 0;
    size_t scriptlen = 0;

    ptrdiff_t pos = 0;
    ptrdiff_t index = 0;

    s_addframe_data add_frame_data; 

    s_hitbox            ebox = {    .x      = 0,
                                    .y      = 0,
                                    .width  = 0,
                                    .height = 0,
                                    .z_background     = 0,
                                    .z_foreground     = 0};
    
    s_axis_plane_vertical_int         offset = { .x = 0,
                                                 .y = 0 };
    int shadow_xz[2] = {0, 0};
    int shadow_coords[2] = {0, 0};

    float platform[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
    float platform_con[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

    s_move                  move = {    {   .x = 0,
                                            .y = 0,
                                            .z = 0},
                                        .base = -1    //-1 = Disabled, 0+ base set
                                    };

    s_collision_entity  ebox_con;
    s_hitbox            entity_coords;
    s_drawmethod        drawmethod;
    s_drawmethod        dm;

    /*
    * Caskey, Damon V.
    * 2021-08-23
    * 
    * Temporary list heads. As we read in commands 
    * for "mutiple X per frame" properties, functions
    * build a linked list with these variables as 
    * head node. When we add frame to model, the
    * lists are cloned with relevant frame property 
    * as head node. Then we destroy temporary list.
    */
    int temp_collision_index = 0;
    s_collision_attack* temp_collision_head = NULL;     // Attack boxes.
    s_collision_body* temp_collision_body_head = NULL;  // Body boxes.
    
    int temp_child_spawn_index = 0;
    s_child_spawn* temp_child_spawn_head = NULL;         // Spawning sub entities.

    char* shutdownmessage = NULL;

    unsigned* mapflag = NULL;  // in 24bit mode, we need to know whether a colourmap is a common map or a palette

    static const char pre_text[] =   // this is the skeleton of frame function
    {
        "void main()\n"
        "{\n"
        "    int frame = getlocalvar(\"frame\");\n"
        "    int animhandle = getlocalvar(\"animhandle\");\n"
        "\n}\n"
    };

    static const char sur_text[] =  // end of function text
    {
        "\n}\n"
    };

    static const char ifid_text[] =  // if expression to check animation id
    {
        "    if(animhandle==%d)\n"
        "    {\n"
        "        return;\n"
        "    }\n"
    };

    static const char endifid_text[] =  // end of if
    {
        "        return;\n"
        "    }\n"
    };

    static const char if_text[] =  // this is the if expression of frame function
    {
        "        if(frame==%d)\n"
        "        {\n"
    };

    static const char endif_return_text[] =   //return to reduce unecessary checks
    {
        "            return;\n"
    };

    static const char endif_text[] =  // end of if
    {
        "        }\n"
    } ;

    static const char comma_text[] =  // arguments separator
    {
        ", "
    };

    static const char call_text[] =  //begin of function call
    {
        "            %s("
    };

    static const char endcall_text[] =  //end of function call
    {
        ");\n"
    };

    modelCommands cmd;
    s_scripts* tempscripts;

#ifdef DEBUG
    printf("load_cached_model: %s, unload: %d\n", name, unload);
#endif

    // Start up the standard log entry.
    printf("Loading '%s'", name);

    // Model already loaded but we might want to unload after level is completed.
    if((tempmodel = findmodel(name)) != NULL)
    {
        update_model_loadflag(tempmodel, unload);
        cache_model_sprites(tempmodel, 1);
        return tempmodel;
    }

    cacheindex = get_cached_model_index(name);
    if(cacheindex < 0)
    {
        borShutdown(1, "Fatal: No cache entry for '%s' within '%s'\n\n", name, owner);
    }

    // Get the text file name of model.
    filename = model_cache[cacheindex].path;
    printf(" from %s \n", filename);

    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        borShutdown(1, "Unable to open file '%s'\n\n", filename);
    }

    sbsize = size + 1;
    scriptbuf = (char *)malloc(sbsize);

    if(scriptbuf == NULL)
    {
        borShutdown(1, "Unable to create script buffer for file '%s' (%i bytes)", filename, size * 2);
    }
    scriptbuf[0] = 0;

    //_peek_model_name(cacheindex);
    newchar = init_model(cacheindex, unload);
    //newchar->name = name;

    //attention, we increase models_loaded here, this can be dangerous if we access that value later on,
    //since recursive calls will change it!
    models_loaded++;
    addModel(newchar);
        
    ebox_con = empty_entity_collision;

    drawmethod = plainmethod;  // better than memset it to 0

    newchar->hitwalltype = -1; // init to -1

    //char* test = "load   knife 0";
    //ParseArgs(&arglist,test,argbuf);

    // Now interpret the contents of buf line by line
    while(pos < size)
    {
        //command = GET_ARG(0);
        line++;
        if(ParseArgs(&arglist, buf + pos, argbuf))
        {
            command = GET_ARG(0);
            cmd = getModelCommand(modelcmdlist, command);

            //if (cmd != CMD_MODEL_FRAME) framenum = 0;

            switch(cmd)
            {            
            case CMD_MODEL_SUBCLASS:
                //inherit everything from an existing, cached model
                tempmodel = findmodel(GET_ARG(1));
                if (!tempmodel)
                {
                    shutdownmessage = "tried to subclass a non-existing/not previously loaded model!";
                    goto lCleanup;
                }
                tempscripts = newchar->scripts;
                *newchar = *tempmodel;
                newchar->scripts = tempscripts;
                copy_all_scripts(tempmodel->scripts, newchar->scripts, 1);
                newchar->isSubclassed = 1;
                newchar->freetypes = MF_SCRIPTS;
                break;
            case CMD_MODEL_NAME:
                lcmHandleCommandName(&arglist, newchar, cacheindex);
                break;
            case CMD_MODEL_TYPE:
                lcmHandleCommandType(&arglist, newchar, filename);
                break;
            case CMD_MODEL_SUBTYPE:
                lcmHandleCommandSubtype(&arglist, newchar, filename);
                break;
            case CMD_MODEL_HEALTH:
                value = GET_ARG(1);
                newchar->health = atoi(value);
                break;
            case CMD_MODEL_PRIORITY:
                value = GET_ARG(1);
                newchar->priority = atoi(value);
                break;
            case CMD_MODEL_SCROLL:
                value = GET_ARG(1);
                newchar->scroll = atof(value);
                break;
            case CMD_MODEL_MP: //Left for backward compatability. See mpset. // mp values to put max mp for player by tails
                value = GET_ARG(1);
                newchar->mp = atoi(value);
                break;
            case CMD_MODEL_NOLIFE:	// Feb 25, 2005 - Flag to display enemy life or not
                newchar->nolife = GET_INT_ARG(1);
                break;
            case CMD_MODEL_MAKEINV:	// Mar 12, 2005 - If a value is supplied, corresponds to amount of time the player spawns invincible
                newchar->makeinv = GET_FLOAT_ARG(1) * GAME_SPEED;
                if(GET_INT_ARG(2))
                {
                    newchar->makeinv = -newchar->makeinv;
                }
                break;
            case CMD_MODEL_RISEINV:
                newchar->riseinv = GET_FLOAT_ARG(1) * GAME_SPEED;
                if(GET_INT_ARG(2))
                {
                    newchar->riseinv = -newchar->riseinv;
                }
                break;
            case CMD_MODEL_LOAD:
                value = GET_ARG(1);
                tempmodel = findmodel(value);
                if(!tempmodel)
                {
                    load_cached_model(value, name, GET_INT_ARG(2));
                }
                else
                {
                    update_model_loadflag(tempmodel, GET_INT_ARG(2));
                }
                break;
            case CMD_MODEL_SCORE:
                newchar->score = GET_INT_ARG(1);
                newchar->multiple = GET_INT_ARG(2);			// New var multiple for force/scoring
                break;
            case CMD_MODEL_SMARTBOMB:
                lcmHandleCommandSmartbomb(&arglist, newchar, filename);
                break;
            case CMD_MODEL_BOUNCE:
                newchar->bounce = GET_INT_ARG(1);
                break;
            case CMD_MODEL_NOQUAKE:  // Mar 12, 2005 - Flag to determine if entity shakes screen
                newchar->quake_config |= GET_INT_ARG(1) ? QUAKE_CONFIG_DISABLE_SCREEN : 0;
                newchar->quake_config |= GET_INT_ARG(2) ? QUAKE_CONFIG_DISABLE_SELF : 0;
                break;
            case CMD_MODEL_BLOCK_CONFIG:

                newchar->block_config_flags = block_get_config_flags_from_arguments(&arglist);
                break;

            case CMD_MODEL_BLOCKBACK:
                
                /* Legacy value to bitmask. */

                tempInt = GET_INT_ARG(1);

                if (tempInt)
                {
                    newchar->block_config_flags |= BLOCK_CONFIG_BACK;
                }
                else
                {
                    newchar->block_config_flags &= ~BLOCK_CONFIG_BACK;
                }

                break;

            case CMD_MODEL_CHILD_FOLLOW_DIRECTION_ADJUST_CONFIG:

                child_follow_getsert_property(&newchar->child_follow)->direction_adjust_config = direction_get_adjustment_from_argument(filename, command, GET_ARG(1));
                break;
            case CMD_MODEL_CHILD_FOLLOW_DIRECTION_ADJUST_RANGE_BASE_MAX:
                child_follow_getsert_property(&newchar->child_follow)->direction_adjust_range.base.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_DIRECTION_ADJUST_RANGE_BASE_MIN:
                child_follow_getsert_property(&newchar->child_follow)->direction_adjust_range.base.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_DIRECTION_ADJUST_RANGE_X_MAX:
                child_follow_getsert_property(&newchar->child_follow)->direction_adjust_range.x.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_DIRECTION_ADJUST_RANGE_X_MIN:
                child_follow_getsert_property(&newchar->child_follow)->direction_adjust_range.x.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_DIRECTION_ADJUST_RANGE_Y_MAX:
                child_follow_getsert_property(&newchar->child_follow)->direction_adjust_range.y.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_DIRECTION_ADJUST_RANGE_Y_MIN:
                child_follow_getsert_property(&newchar->child_follow)->direction_adjust_range.y.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_DIRECTION_ADJUST_RANGE_Z_MAX:
                child_follow_getsert_property(&newchar->child_follow)->direction_adjust_range.z.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_DIRECTION_ADJUST_RANGE_Z_MIN:
                child_follow_getsert_property(&newchar->child_follow)->direction_adjust_range.z.min = GET_INT_ARG(1);
                break;

            case CMD_MODEL_CHILD_FOLLOW_OFFSET_X:

                child_follow_getsert_property(&newchar->child_follow)->follow_offset.x = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_OFFSET_Y:
                child_follow_getsert_property(&newchar->child_follow)->follow_offset.y = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_OFFSET_Z:
                child_follow_getsert_property(&newchar->child_follow)->follow_offset.z = GET_INT_ARG(1);
                break;

            case CMD_MODEL_CHILD_FOLLOW_RANGE_RECALL_BASE_MAX:
                child_follow_getsert_property(&newchar->child_follow)->recall_range.base.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RECALL_BASE_MIN:
                child_follow_getsert_property(&newchar->child_follow)->recall_range.base.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RECALL_X_MAX:
                child_follow_getsert_property(&newchar->child_follow)->recall_range.x.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RECALL_X_MIN:
                child_follow_getsert_property(&newchar->child_follow)->recall_range.x.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RECALL_Y_MAX:
                child_follow_getsert_property(&newchar->child_follow)->recall_range.y.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RECALL_Y_MIN:
                child_follow_getsert_property(&newchar->child_follow)->recall_range.y.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RECALL_Z_MAX:
                child_follow_getsert_property(&newchar->child_follow)->recall_range.z.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RECALL_Z_MIN:
                child_follow_getsert_property(&newchar->child_follow)->recall_range.z.min = GET_INT_ARG(1);
                break;

            case CMD_MODEL_CHILD_FOLLOW_RANGE_RUN_BASE_MAX:
                child_follow_getsert_property(&newchar->child_follow)->follow_run_range.base.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RUN_BASE_MIN:
                child_follow_getsert_property(&newchar->child_follow)->follow_run_range.base.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RUN_X_MAX:
                child_follow_getsert_property(&newchar->child_follow)->follow_run_range.x.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RUN_X_MIN:
                child_follow_getsert_property(&newchar->child_follow)->follow_run_range.x.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RUN_Y_MAX:
                child_follow_getsert_property(&newchar->child_follow)->follow_run_range.y.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RUN_Y_MIN:
                child_follow_getsert_property(&newchar->child_follow)->follow_run_range.y.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RUN_Z_MAX:
                child_follow_getsert_property(&newchar->child_follow)->follow_run_range.z.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_RUN_Z_MIN:
                child_follow_getsert_property(&newchar->child_follow)->follow_run_range.z.min = GET_INT_ARG(1);
                break;

            case CMD_MODEL_CHILD_FOLLOW_RANGE_BASE_MAX:
                child_follow_getsert_property(&newchar->child_follow)->follow_range.base.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_BASE_MIN:
                child_follow_getsert_property(&newchar->child_follow)->follow_range.base.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_X_MAX:
                child_follow_getsert_property(&newchar->child_follow)->follow_range.x.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_X_MIN:
                child_follow_getsert_property(&newchar->child_follow)->follow_range.x.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_Y_MAX:
                child_follow_getsert_property(&newchar->child_follow)->follow_range.y.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_Y_MIN:
                child_follow_getsert_property(&newchar->child_follow)->follow_range.y.min = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_Z_MAX:
                child_follow_getsert_property(&newchar->child_follow)->follow_range.z.max = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RANGE_Z_MIN:
                child_follow_getsert_property(&newchar->child_follow)->follow_range.z.min = GET_INT_ARG(1);
                break;

            case CMD_MODEL_CHILD_FOLLOW_RECALL_OFFSET_X:

                child_follow_getsert_property(&newchar->child_follow)->recall_offset.x = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RECALL_OFFSET_Y:
                child_follow_getsert_property(&newchar->child_follow)->recall_offset.y = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_FOLLOW_RECALL_OFFSET_Z:
                child_follow_getsert_property(&newchar->child_follow)->recall_offset.z = GET_INT_ARG(1);
                break;

            case CMD_MODEL_HITENEMY:	// Flag to determine if an enemy projectile will hit enemies
                value = GET_ARG(1);
                if(atoi(value) == 1)
                {
                    newchar->faction.type_damage_direct = newchar->faction.type_hostile = TYPE_PLAYER | TYPE_ENEMY;
                }
                else if(atoi(value) == 2)
                {
                    newchar->faction.type_damage_direct = newchar->faction.type_hostile = TYPE_PLAYER;
                }
                
                newchar->ground = GET_INT_ARG(2);    // Added to determine if enemies are damaged with mid air projectiles or ground only
                break;
            
            /* Faction set up. */
            case CMD_MODEL_FACTION_GROUP_DAMAGE_DIRECT:
                newchar->faction.damage_direct = faction_get_flags_from_arglist(&arglist);
                break;

            case CMD_MODEL_FACTION_GROUP_DAMAGE_INDIRECT:
                newchar->faction.damage_indirect = faction_get_flags_from_arglist(&arglist);
                break;

            case CMD_MODEL_FACTION_GROUP_HOSTILE:
                newchar->faction.hostile = faction_get_flags_from_arglist(&arglist);
                break;

            case CMD_MODEL_FACTION_GROUP_MEMBER:
                newchar->faction.member = faction_get_flags_from_arglist(&arglist);
                break;

            /* Legacy type based faction */
            case CMD_MODEL_FACTION_TYPE_HOSTILE:
            case CMD_MODEL_HOSTILE:
                newchar->faction.type_hostile = get_type_from_arglist(&arglist);
                break;

            case CMD_MODEL_FACTION_TYPE_DAMAGE_DIRECT:
            case CMD_MODEL_CANDAMAGE:
                newchar->faction.type_damage_direct = get_type_from_arglist(&arglist);
                break;

            case CMD_MODEL_FACTION_TYPE_DAMAGE_INDIRECT:
            case CMD_MODEL_PROJECTILEHIT:
                newchar->faction.type_damage_indirect = get_type_from_arglist(&arglist);
                break;

            case CMD_MODEL_AIMOVE:
                newchar->aimove = get_aimove_from_arguments(&arglist, AIMOVE1_NORMAL);
                break;
            case CMD_MODEL_AIATTACK:
                lcmHandleCommandAiattack(&arglist, newchar, &aiattackset, filename);
                break;
            case CMD_MODEL_MOVE_CONFIG:
                newchar->move_config_flags = get_move_config_flags_from_arguments(&arglist);
                break;
            case CMD_MODEL_SUBJECT_TO_BASEMAP:

                /* Legacy code allowed -1 or 0 for False.  */
                if (GET_INT_ARG(1) > 0)
                {
                    newchar->move_config_flags |= MOVE_CONFIG_SUBJECT_TO_BASEMAP;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_BASEMAP;
                }
                
                break;
            case CMD_MODEL_SUBJECT_TO_WALL:

                if (GET_INT_ARG(1))
                {
                    newchar->move_config_flags |= MOVE_CONFIG_SUBJECT_TO_WALL;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_WALL;
                }

                break;
            case CMD_MODEL_SUBJECT_TO_HOLE:
                
                if (GET_INT_ARG(1))
                {
                    newchar->move_config_flags |= MOVE_CONFIG_SUBJECT_TO_HOLE;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_HOLE;
                }

                break;
            case CMD_MODEL_SUBJECT_TO_PLATFORM:
                
                if (GET_INT_ARG(1))
                {
                    newchar->move_config_flags |= MOVE_CONFIG_SUBJECT_TO_PLATFORM;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_PLATFORM;
                }

                break;
            case CMD_MODEL_SUBJECT_TO_OBSTACLE:
                
                if (GET_INT_ARG(1))
                {
                    newchar->move_config_flags |= MOVE_CONFIG_SUBJECT_TO_OBSTACLE;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_OBSTACLE;
                }

                break;
            case CMD_MODEL_SUBJECT_TO_GRAVITY:
                
                /* Legacy code allowed -1 or 0 for False.  */
                if (GET_INT_ARG(1) > 0)
                {
                    newchar->move_config_flags |= MOVE_CONFIG_SUBJECT_TO_GRAVITY;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_GRAVITY;
                }

                break;
            case CMD_MODEL_SUBJECT_TO_SCREEN:
                
                if (GET_INT_ARG(1))
                {
                    newchar->move_config_flags |= MOVE_CONFIG_SUBJECT_TO_SCREEN;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_SCREEN;
                }

                break;
            case CMD_MODEL_SUBJECT_TO_MINZ:
                
                if (GET_INT_ARG(1))
                {
                    newchar->move_config_flags |= MOVE_CONFIG_SUBJECT_TO_MIN_Z;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_MIN_Z;
                }

                break;
            case CMD_MODEL_SUBJECT_TO_MAXZ:

                if (GET_INT_ARG(1))
                {
                    newchar->move_config_flags |= MOVE_CONFIG_SUBJECT_TO_MAX_Z;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_MAX_Z;
                }

                break;
            case CMD_MODEL_NO_ADJUST_BASE:

                /* Legacy code allowed -1 or 0 for False.  */
                if (GET_INT_ARG(1) > 0)
                {
                    newchar->move_config_flags |= MOVE_CONFIG_NO_ADJUST_BASE;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;
                }

                break;
            case CMD_MODEL_INSTANTITEMDEATH:
                newchar->instantitemdeath = GET_INT_ARG(1);
                break;
            case CMD_MODEL_SECRET:
                newchar->secret = GET_INT_ARG(1);
                newchar->clearcount = GET_INT_ARG(2);
                break;
            case CMD_MODEL_MODELFLAG: // Legacy model copy flag.                             
                
                
                lcmHandleCommandModelFlag(filename, command, &arglist, newchar);

                break;
                // weapons
            case CMD_MODEL_WEAPLOSS:
                
                /* Legacy weapon loss. */
                
                newchar->weapon_properties.loss_condition = weapon_loss_condition_interpret_from_legacy_weaploss(WEAPON_LOSS_CONDITION_NONE, GET_INT_ARG(1));
                newchar->weapon_properties.loss_index = GET_INT_ARG(2);
                break;
            
            case CMD_MODEL_WEAPON_LOSS_CONFIG:
                
                lcmHandleCommandWeaponLossCondition(&arglist, newchar);
                break;

            case CMD_MODEL_WEAPON_LOSS_INDEX:

                /* Weapon list entry we revert to when losing weapon. */
                
                newchar->weapon_properties.loss_index = GET_INT_ARG(1);
                
                break;

            case CMD_MODEL_WEAPNUM:
                newchar->weapon_properties.weapon_index = GET_INT_ARG(1);
                break;
            case CMD_MODEL_PROJECT: // New projectile subtype
                value = GET_ARG(1);
                if(stricmp(value, "none") == 0)
                {
                    newchar->project = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->project = get_cached_model_index(value);
                }
                break;
            case CMD_MODEL_WEAPONS:
                lcmHandleCommandWeapons(&arglist, newchar);
                break;
            case CMD_MODEL_SHOOTNUM: 
                newchar->weapon_properties.use_count = GET_INT_ARG(1);
                break;
            case CMD_MODEL_RELOAD:
                newchar->weapon_properties.use_add = GET_INT_ARG(1);
                break;
            case CMD_MODEL_TYPESHOT:
                if (GET_INT_ARG(1))
                {
                    newchar->weapon_properties.weapon_state |= WEAPON_STATE_LIMITED_USE;
                }
                
                break;
            case CMD_MODEL_COUNTER:
                newchar->weapon_properties.loss_count = GET_INT_ARG(1);
                break;
            case CMD_MODEL_ANIMAL:
                if (GET_INT_ARG(1))
                {
                    newchar->weapon_properties.weapon_state |= WEAPON_STATE_ANIMAL;
                }
                break;
            case CMD_MODEL_RIDER:
                value = GET_ARG(1);
                if(stricmp(value, "none") == 0)
                {
                    newchar->rider = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->rider = get_cached_model_index(value);
                }
                break;
            case CMD_MODEL_KNIFE:
            case CMD_MODEL_FIREB:
            case CMD_MODEL_PLAYSHOT:
            case CMD_MODEL_PLAYSHOTW:
                value = GET_ARG(1);
                if(stricmp(value, "none") == 0)
                {
                    newchar->knife = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->knife = get_cached_model_index(value);
                }
                break;
            case CMD_MODEL_PLAYSHOTNO:
                value = GET_ARG(1);
                if(stricmp(value, "none") == 0)
                {
                    newchar->pshotno = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->pshotno = get_cached_model_index(value);
                }
                break;
            case CMD_MODEL_STAR:
                value = GET_ARG(1);
                if(stricmp(value, "none") == 0)
                {
                    newchar->star = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->star = get_cached_model_index(value);
                }
                break;
            case CMD_MODEL_BOMB:
            case CMD_MODEL_PLAYBOMB:
                value = GET_ARG(1);
                if(stricmp(value, "none") == 0)
                {
                    newchar->bomb = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->bomb = get_cached_model_index(value);
                }
                break;
            case CMD_MODEL_FLASH:	 // Now all characters can have their own flash - even projectiles (useful for blood)
                value = GET_ARG(1);
                if(stricmp(value, "none") == 0)
                {
                    newchar->flash = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->flash = get_cached_model_index(value);
                }
                break;
            case CMD_MODEL_BFLASH:	// Flash that is spawned if an attack is blocked
                value = GET_ARG(1);
                if(stricmp(value, "none") == 0)
                {
                    newchar->bflash = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->bflash = get_cached_model_index(value);
                }
                break;
            case CMD_MODEL_DUST:	// Spawned when hitting the ground to "kick up dust"
                value = GET_ARG(1);
                if(stricmp(value, "none") == 0)
                {
                    newchar->dust.fall_land = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->dust.fall_land = get_cached_model_index(value);
                }
                value = GET_ARG(2);
                if(stricmp(value, "none") == 0)
                {
                    newchar->dust.jump_land = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->dust.jump_land = get_cached_model_index(value);
                }
                value = GET_ARG(3);
                if(stricmp(value, "none") == 0)
                {
                    newchar->dust.jump_start = MODEL_INDEX_NONE;
                }
                else
                {
                    newchar->dust.jump_start = get_cached_model_index(value);
                }
                break;
            case CMD_MODEL_BRANCH: // for endlevel item's level branch
                value = GET_ARG(1);
                if(!newchar->branch)
                {
                    newchar->branch = malloc(MAX_NAME_LEN + 1);
                    newchar->branch[0] = 0;
                }
                strncpy(newchar->branch, value, MAX_NAME_LEN);
                break;
            case CMD_MODEL_CANTGRAB:
            case CMD_MODEL_NOTGRAB:
                tempInt = GET_INT_ARG(1);
                if(tempInt == 2)
                {
                    newchar->grab_force = -999999;
                }
                else
                {
                    newchar->grab_resistance = 1;
                }
                break;
            case CMD_MODEL_ANTIGRAB: // a can grab b: a->grab_resistance - b->grab_force <=0
                newchar->grab_resistance = GET_INT_ARG(1);
                break;
            case CMD_MODEL_GRABFORCE:
                newchar->grab_force = GET_INT_ARG(1);
                break;
            case CMD_MODEL_GRABBACK:
                newchar->grabback = GET_INT_ARG(1);
                break;
            case CMD_MODEL_OFFSCREENKILL:
                newchar->offscreenkill = GET_INT_ARG(1);
                break;
            case CMD_MODEL_ONAF:
                newchar->offscreen_noatk_factor = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_FALLDIE:
            case CMD_MODEL_DEATH:

                tempInt = GET_INT_ARG(1);

                newchar->death_config_flags = death_config_get_value_from_falldie(newchar->death_config_flags, tempInt);

                break;

            case CMD_MODEL_DEATH_CONFIG:

                newchar->death_config_flags = death_get_config_flags_from_arguments(&arglist, 1);
                break;

            case CMD_MODEL_SPEED:
                value = GET_ARG(1);
                newchar->speed.x = atof(value);
                newchar->speed.x /= 10;
                if(newchar->speed.x < 0.5)
                {
                    newchar->speed.x = 0.5;
                }
                if(newchar->speed.x > 30)
                {
                    newchar->speed.x = 30;
                }
                break;
            case CMD_MODEL_SPEEDF:
                value = GET_ARG(1);
                newchar->speed.x = atof(value);
                break;           
            case CMD_MODEL_JUMPSPECIAL: // Kratus (10-2021) Added new jumpspecial property
                newchar->jumpspecial = GET_INT_ARG(1);
                break;
            case CMD_MODEL_JUMPSPEED:
                value = GET_ARG(1);
                newchar->jumpspeed = atof(value);
                newchar->jumpspeed /= 10;
                break;
            case CMD_MODEL_JUMPSPEEDF:
                value = GET_ARG(1);
                newchar->jumpspeed = atof(value);
                break;
            case CMD_MODEL_ANTIGRAVITY:
                value = GET_ARG(1);
                newchar->antigravity = atof(value);
                newchar->antigravity /= 100;
                break;
            case CMD_MODEL_STEALTH:
                newchar->stealth.hide = GET_INT_ARG(1);
                newchar->stealth.detect = GET_INT_ARG(2);
                break;
            case CMD_MODEL_JUGGLEPOINTS:
                value = GET_ARG(1);
                newchar->jugglepoints.current = atoi(value);
                newchar->jugglepoints.max = atoi(value);
                break;
            case CMD_MODEL_RISEATTACKTYPE:
                value = GET_ARG(1);
                newchar->riseattacktype = atoi(value);
                break;
            case CMD_MODEL_GUARDPOINTS:
                value = GET_ARG(1);
                newchar->guardpoints.current = atoi(value);
                newchar->guardpoints.max = atoi(value);
                break;
            case CMD_MODEL_DEFENSE:

                /*
                * DEFENSE_PARAMETER_LEGACY triggers muti-parameter read
                * from 3.0 builds. See function for details.
                */

                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_LEGACY);
            break;    
            case CMD_MODEL_DEFENSE_BLOCK_DAMAGE_ADJUST:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_BLOCK_DAMAGE_ADJUST);
                break;
            case CMD_MODEL_DEFENSE_BLOCK_DAMAGE_MAX:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_BLOCK_DAMAGE_MAX);
                break;
            case CMD_MODEL_DEFENSE_BLOCK_DAMAGE_MIN:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_BLOCK_DAMAGE_MIN);
                break;
            case CMD_MODEL_DEFENSE_BLOCK_POWER:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_BLOCK_POWER);
                break;
            case CMD_MODEL_DEFENSE_BLOCK_RATIO:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_BLOCK_RATIO);
                break;
            case CMD_MODEL_DEFENSE_BLOCK_THRESHOLD:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_BLOCK_THRESHOLD);
                break;
            case CMD_MODEL_DEFENSE_BLOCK_TYPE:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_BLOCK_TYPE);
                break;
            case CMD_MODEL_DEFENSE_DAMAGE_ADJUST:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_DAMAGE_ADJUST);
                break;
            case CMD_MODEL_DEFENSE_DAMAGE_MAX:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_DAMAGE_MAX);
                break;
            case CMD_MODEL_DEFENSE_DAMAGE_MIN:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_DAMAGE_MIN);
                break;
            case CMD_MODEL_DEFENSE_DEATH_CONFIG:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_DEATH_CONFIG);
                break;
            case CMD_MODEL_DEFENSE_FACTOR:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_FACTOR);
                break;
            case CMD_MODEL_DEFENSE_KNOCKDOWN:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_KNOCKDOWN);
                break;
            case CMD_MODEL_DEFENSE_PAIN:
                defense_setup_from_arg(filename, command, newchar->defense, &arglist, DEFENSE_PARAMETER_PAIN);
                break;
            case CMD_MODEL_OFFENSE:
                offense_setup_from_arg(filename, command, newchar->offense, &arglist, OFFENSE_PARAMETER_LEGACY);
                break;
            case CMD_MODEL_OFFENSE_DAMAGE_ADJUST:
                offense_setup_from_arg(filename, command, newchar->offense, &arglist, OFFENSE_PARAMETER_DAMAGE_ADJUST);
                break;
            case CMD_MODEL_OFFENSE_DAMAGE_MAX:
                offense_setup_from_arg(filename, command, newchar->offense, &arglist, OFFENSE_PARAMETER_DAMAGE_MAX);
                break;
            case CMD_MODEL_OFFENSE_DAMAGE_MIN:
                offense_setup_from_arg(filename, command, newchar->offense, &arglist, OFFENSE_PARAMETER_DAMAGE_MIN);
                break;
            case CMD_MODEL_OFFENSE_FACTOR:
                offense_setup_from_arg(filename, command, newchar->offense, &arglist, OFFENSE_PARAMETER_FACTOR);
                break;

            break;
            case CMD_MODEL_HEIGHT:
                newchar->size.y = GET_INT_ARG(1);
                break;
            case CMD_MODEL_JUMPHEIGHT:
                newchar->jumpheight = GET_FLOAT_ARG(1);
                break;

            case CMD_MODEL_AIR_CONTROL:
                
                lcmHandleCommandAirControl(&arglist, newchar);
                break;

            case CMD_MODEL_JUMPMOVE:
                
                newchar->air_control = air_control_interpret_from_legacy_jumpmove_x(newchar->air_control, GET_INT_ARG(1));
                newchar->air_control = air_control_interpret_from_legacy_jumpmove_z(newchar->air_control, GET_INT_ARG(2));                
                break;

            case CMD_MODEL_WALKOFFMOVE:

                newchar->air_control = air_control_interpret_from_legacy_walkoffmove_x(newchar->air_control, GET_INT_ARG(1));
                newchar->air_control = air_control_interpret_from_legacy_walkoffmove_z(newchar->air_control, GET_INT_ARG(2));;
                break;

            case CMD_MODEL_KNOCKDOWNCOUNT:
                newchar->knockdowncount = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_GRABDISTANCE:
                newchar->grabdistance = GET_FLOAT_ARG(1);                    // 30-12-2004 and store for character
                break;
            case CMD_MODEL_GRABFLIP:
                newchar->grabflip = GET_INT_ARG(1);
                break;
            case CMD_MODEL_GRABFINISH:
                newchar->grabfinish = GET_INT_ARG(1);
                break;
            case CMD_MODEL_THROWDAMAGE:
                newchar->throwdamage = GET_INT_ARG(1);
                break;
            case CMD_MODEL_SHADOW:
                newchar->shadow = GET_INT_ARG(1);
                
                tempInt= GET_INT_ARG(2);

                newchar->shadow_config_flags = shadow_get_config_from_legacy_shadowbase(newchar->shadow_config_flags, tempInt);

                break;
            case CMD_MODEL_SHADOW_CONFIG:                

                newchar->shadow_config_flags = shadow_get_config_flags_from_arguments(&arglist);

                break;
            case CMD_MODEL_GFXSHADOW:

                /* Gfxshadow. */
                tempInt = GET_INT_ARG(1);
                newchar->shadow_config_flags = shadow_get_config_from_legacy_gfxshadow(newchar->shadow_config_flags, tempInt);

                /* Shadowbase. */
                tempInt = GET_INT_ARG(2);
                newchar->shadow_config_flags = shadow_get_config_from_legacy_shadowbase(newchar->shadow_config_flags, tempInt);

                break;

            case CMD_MODEL_AIRONLY:	// Shadows display in air only?

                tempInt = GET_INT_ARG(1);

                newchar->shadow_config_flags = shadow_get_config_from_legacy_aironly(newchar->shadow_config_flags, tempInt);
                
                break;
            case CMD_MODEL_FMAP:	// Map that corresponds with the remap when a character is frozen
                newchar->colorsets.frozen = GET_INT_ARG(1);
                break;
            case CMD_MODEL_KOMAP:
                newchar->colorsets.ko = GET_INT_ARG(1);  //Remap.
                newchar->colorsets.kotype = komap_type_get_value_from_argument(filename, command, GET_ARG(2));
                break;
            case CMD_MODEL_MAP_BURN_INDEX:
                newchar->colorsets.burn = GET_INT_ARG(1);
                break;            
            case CMD_MODEL_MAP_KO_INDEX:
                newchar->colorsets.ko = GET_INT_ARG(1);
                break;
            case CMD_MODEL_MAP_KO_TYPE:
                newchar->colorsets.kotype = komap_type_get_value_from_argument(filename, command, GET_ARG(1));
                break;
            case CMD_MODEL_MAP_FREEZE_INDEX:
                newchar->colorsets.frozen = GET_INT_ARG(1);
                break;
            case CMD_MODEL_MAP_SHOCK_INDEX:
                newchar->colorsets.shock = GET_INT_ARG(1);
                break;            
            case CMD_MODEL_HMAP:	// Maps range unavailable to player in select screen.
                newchar->colorsets.hide_start = GET_INT_ARG(1); //First unavailable map.
                newchar->colorsets.hide_end = GET_INT_ARG(2); //Last unavailable map.
                break;
            case CMD_MODEL_SETLAYER:
                newchar->setlayer = GET_INT_ARG(1);
                break;
            case CMD_MODEL_TOFLIP:	  // Flag to determine if flashes images will be flipped or not
                newchar->toflip = GET_INT_ARG(1);
                break;
            case CMD_MODEL_NODIEBLINK:
                // Added to determine if dying animation blinks or not

                tempInt = GET_INT_ARG(1);

                newchar->death_config_flags = death_config_get_value_from_nodieblink(newchar->death_config_flags, tempInt);

                break;
            case CMD_MODEL_NOATFLASH:	 // Flag to determine if an opponents attack spawns their flash or not
                newchar->noatflash = GET_INT_ARG(1);
                break;
            case CMD_MODEL_NOMOVE:
                
                /*
                * Legacy no move. Set move constraint flags 
                * to match the old nomove parameters.
                */

                if (GET_INT_ARG(1))
                {
                    newchar->move_config_flags |= MOVE_CONFIG_NO_MOVE;
                    newchar->pain_config_flags |= PAIN_CONFIG_FALL_DISABLE;
                }

                if (GET_INT_ARG(2))
                {
                    newchar->move_config_flags |= MOVE_CONFIG_NO_FLIP;
                }                
                
                break;
            case CMD_MODEL_NODROP:

                tempInt = GET_INT_ARG(1);                

                newchar->pain_config_flags &= ~(PAIN_CONFIG_FALL_DISABLE | PAIN_CONFIG_FALL_DISABLE_AIR);

                switch (tempInt)
                {
                default:
                case 0:                    
                    break;
                case 1:
                    newchar->pain_config_flags |= PAIN_CONFIG_FALL_DISABLE;
                    break;
                case 2:
                    newchar->pain_config_flags |= (PAIN_CONFIG_FALL_DISABLE | PAIN_CONFIG_FALL_DISABLE_AIR);
                    break;
                }

                break;
            case CMD_MODEL_THOLD:
                // Threshold for enemies/players block
                newchar->thold = GET_INT_ARG(1);
                break;

            case CMD_MODEL_RUN_CONFIG:
                newchar->run_config_flags = run_get_config_flags_from_arguments(&arglist, 1);
                break;
            case CMD_MODEL_RUNNING:
                // The speed at which the player runs
                newchar->runspeed = GET_FLOAT_ARG(1);
                newchar->runspeed /= 10;
                newchar->runjumpheight = GET_FLOAT_ARG(2);    // The height at which a player jumps when running
                newchar->runjumpdist = GET_FLOAT_ARG(3);    // The distance a player jumps when running
                
                tempInt = GET_INT_ARG(4);

                newchar->run_config_flags |= (RUN_CONFIG_X_LEFT_ENABLED | RUN_CONFIG_X_LEFT_INITIAL | RUN_CONFIG_X_RIGHT_ENABLED | RUN_CONFIG_X_RIGHT_INITIAL);
                                
                if (tempInt)
                {
                    newchar->run_config_flags |= (RUN_CONFIG_Z_DOWN_ENABLED | RUN_CONFIG_Z_UP_ENABLED);
                }
                else
                {
                    newchar->run_config_flags &= ~(RUN_CONFIG_Z_DOWN_ENABLED | RUN_CONFIG_Z_UP_ENABLED);
                }
                                
                tempInt = GET_INT_ARG(5);

                if (tempInt)
                {
                    newchar->run_config_flags |= RUN_CONFIG_LAND;
                }
                else
                {
                    newchar->run_config_flags &= ~RUN_CONFIG_LAND;
                }

                break;
            case CMD_MODEL_RUNNING_CONTINUE:
                tempInt = GET_INT_ARG(1);

                if (tempInt)
                {
                    newchar->run_config_flags |= RUN_CONFIG_LAND;
                }
                else
                {
                    newchar->run_config_flags &= ~RUN_CONFIG_LAND;
                }
                break;
            case CMD_MODEL_RUNNING_JUMP_VELOCITY_X:
                newchar->runjumpdist = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_RUNNING_JUMP_VELOCITY_Y:
                newchar->runjumpheight = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_RUNNING_SPEED:
                newchar->runspeed = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_RUNNING_Z_MOVE:
                tempInt = GET_INT_ARG(1);

                if (tempInt)
                {
                    newchar->run_config_flags |= (RUN_CONFIG_Z_DOWN_ENABLED | RUN_CONFIG_Z_UP_ENABLED);
                }
                else
                {
                    newchar->run_config_flags &= ~(RUN_CONFIG_Z_DOWN_ENABLED | RUN_CONFIG_Z_UP_ENABLED);
                }

                break;
            case CMD_MODEL_BLOCKODDS:
                // Odds that an attack will hit an enemy (1 : blockodds)
                newchar->blockodds = GET_INT_ARG(1);
                break;
            case CMD_MODEL_HOLDBLOCK:
                
                tempInt = GET_INT_ARG(1);

                newchar->block_config_flags &= ~(BLOCK_CONFIG_HOLD_IMPACT | BLOCK_CONFIG_HOLD_INFINITE);

                switch (tempInt)
                {
                default:
                case 0:
                    /* Do nothing. */
                    break;
                case 1:
                    newchar->block_config_flags |= BLOCK_CONFIG_HOLD_IMPACT;
                    break;
                case 2:
                    newchar->block_config_flags |= (BLOCK_CONFIG_HOLD_IMPACT | BLOCK_CONFIG_HOLD_INFINITE);
                    break;
                }
                
                break;
            case CMD_MODEL_BLOCKPAIN:
                newchar->blockpain = GET_INT_ARG(1);
                break;
            case CMD_MODEL_NOPASSIVEBLOCK:
                tempInt = GET_INT_ARG(1);

                if (tempInt)
                {
                    newchar->block_config_flags |= BLOCK_CONFIG_ACTIVE;
                }
                else
                {
                    newchar->block_config_flags &= ~BLOCK_CONFIG_ACTIVE;
                }

                break;
            case CMD_MODEL_EDELAY:
                tempInt = GET_INT_ARG(1);

                if (tempInt == EDELAY_MODE_MULTIPLY)
                {
                    newchar->edelay.factor = GET_FLOAT_ARG(2);
                }
                else
                {
                    newchar->edelay.modifier = GET_INT_ARG(2);
                }
                
                newchar->edelay.cap.min     = GET_INT_ARG(3);
                newchar->edelay.cap.max     = GET_INT_ARG(4);
                newchar->edelay.range.min   = GET_INT_ARG(5);
                newchar->edelay.range.max   = GET_INT_ARG(6);
                break;

            case CMD_MODEL_ENHANCED_DELAY_CAP_MAX:

                newchar->edelay.cap.max = GET_INT_ARG(1);
                break;

            case CMD_MODEL_ENHANCED_DELAY_CAP_MIN:

                newchar->edelay.cap.min = GET_INT_ARG(1);
                break;

            case CMD_MODEL_ENHANCED_DELAY_MODIFIER:

                newchar->edelay.modifier = GET_INT_ARG(1);
                break;

            case CMD_MODEL_ENHANCED_DELAY_MULTIPLIER:

                newchar->edelay.factor = GET_FLOAT_ARG(1);
                break;

            case CMD_MODEL_ENHANCED_DELAY_RANGE_MAX:

                newchar->edelay.range.max = GET_INT_ARG(1);
                break;

            case CMD_MODEL_ENHANCED_DELAY_RANGE_MIN:

                newchar->edelay.range.min = GET_INT_ARG(1);
                break;           

            case CMD_MODEL_PAIN_BACK:

                tempInt = GET_INT_ARG(1);

                if (tempInt)
                {
                    newchar->pain_config_flags |= PAIN_CONFIG_BACK_PAIN;
                }
                else
                {
                    newchar->pain_config_flags &= ~PAIN_CONFIG_BACK_PAIN;
                }               

                break;
            case CMD_MODEL_PAIN_CONFIG:

                tempInt = GET_INT_ARG(1);

                newchar->pain_config_flags = pain_get_config_flags_from_arguments(&arglist);

                break;

            case CMD_MODEL_PAINGRAB:
                newchar->paingrab = GET_INT_ARG(1);
                break;
            case CMD_MODEL_THROW:
                newchar->throwdist = GET_FLOAT_ARG(1);
                newchar->throwheight = GET_FLOAT_ARG(2);
                break;
            case CMD_MODEL_EDGERANGE:
                newchar->edgerange.x = GET_FLOAT_ARG(1);
                newchar->edgerange.z = GET_FLOAT_ARG(2);
                break;
            case CMD_MODEL_ENTITYPUSHING:
                newchar->entitypushing = GET_INT_ARG(1);
                break;
            case CMD_MODEL_PUSHINGFACTOR:
                newchar->pushingfactor = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_GRABWALK:
                newchar->grabwalkspeed = GET_FLOAT_ARG(1);
                newchar->grabwalkspeed /= 10;
                if(newchar->grabwalkspeed < 0.5)
                {
                    newchar->grabwalkspeed = 0.5;
                }
                break;
            case CMD_MODEL_GRABTURN:
                newchar->grabturn = GET_INT_ARG(1);
                break;
            case CMD_MODEL_THROWFRAMEWAIT:
                newchar->throwframewait = GET_INT_ARG(1);
                break;
            case CMD_MODEL_DIESOUND:
                newchar->diesound = sound_load_sample(GET_ARG(1), packfile, 1);
                break;
            case CMD_MODEL_ICON:
                value = GET_ARG(1);
                if(newchar->icon.def > -1)
                {
                    shutdownmessage = "model has multiple icons defined";
                    goto lCleanup;
                }
                newchar->icon.def = loadsprite(value, 0, 0, pixelformat); //use same palette as the owner
                newchar->icon.pain = newchar->icon.def;
                newchar->icon.die = newchar->icon.def;
                newchar->icon.get = newchar->icon.def;
                newchar->icon.usemap = GET_INT_ARG(2); //be more friendly to some old mods which don't care about icon remap
                break;
            case CMD_MODEL_ICONPAIN:
                value = GET_ARG(1);
                newchar->icon.pain = loadsprite(value, 0, 0, pixelformat);
                break;
            case CMD_MODEL_ICONDIE:
                value = GET_ARG(1);
                newchar->icon.die = loadsprite(value, 0, 0, pixelformat);
                break;
            case CMD_MODEL_ICONGET:
                value = GET_ARG(1);
                newchar->icon.get = loadsprite(value, 0, 0, pixelformat);
                break;
            case CMD_MODEL_ICONW:
                value = GET_ARG(1);
                newchar->icon.weapon = loadsprite(value, 0, 0, pixelformat);
                break;
            case CMD_MODEL_ICONMPHIGH:
                value = GET_ARG(1);
                newchar->icon.mphigh = loadsprite(value, 0, 0, pixelformat);
                break;
            case CMD_MODEL_ICONMPHALF:
                value = GET_ARG(1);
                newchar->icon.mpmed = loadsprite(value, 0, 0, pixelformat);
                break;
            case CMD_MODEL_ICONMPLOW:
                value = GET_ARG(1);
                newchar->icon.mplow = loadsprite(value, 0, 0, pixelformat);
                break;
            case CMD_MODEL_PARROW:
                // Image that is displayed when player 1 spawns invincible
                value = GET_ARG(1);
                newchar->player_arrow[0].sprite = loadsprite(value, 0, 0, pixelformat);
                newchar->player_arrow[0].position.x = GET_INT_ARG(2);
                newchar->player_arrow[0].position.y = GET_INT_ARG(3);
                break;
            case CMD_MODEL_PARROW2:
                // Image that is displayed when player 2 spawns invincible
                value = GET_ARG(1);
                newchar->player_arrow[1].sprite = loadsprite(value, 0, 0, pixelformat);
                newchar->player_arrow[1].position.x = GET_INT_ARG(2);
                newchar->player_arrow[1].position.y = GET_INT_ARG(3);
                break;
            case CMD_MODEL_PARROW3:
                value = GET_ARG(1);
                newchar->player_arrow[2].sprite = loadsprite(value, 0, 0, pixelformat);
                newchar->player_arrow[2].position.x = GET_INT_ARG(2);
                newchar->player_arrow[2].position.y = GET_INT_ARG(3);
                break;
            case CMD_MODEL_PARROW4:
                value = GET_ARG(1);
                newchar->player_arrow[3].sprite = loadsprite(value, 0, 0, pixelformat);
                newchar->player_arrow[3].position.x = GET_INT_ARG(2);
                newchar->player_arrow[3].position.y = GET_INT_ARG(3);
                break;
            case CMD_MODEL_ATCHAIN:
                newchar->chainlength = 0;
                for(i = 0; i < MAX_ATCHAIN; i++)
                {
                    newchar->atchain[i] = GET_INT_ARG(i + 1);
                    if(newchar->atchain[i] < 0)
                    {
                        newchar->atchain[i] = 0;
                    }
                    if(newchar->atchain[i] > max_attacks)
                    {
                        newchar->atchain[i] = max_attacks;
                    }
                    if(newchar->atchain[i])
                    {
                        newchar->chainlength = i + 1;
                    }
                }
                break;
            case CMD_MODEL_COMBOSTYLE:
                newchar->combostyle = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CREDIT:
                newchar->credit = GET_INT_ARG(1);
                break;
            case CMD_MODEL_NOPAIN:
                tempInt = GET_INT_ARG(1);

                if (tempInt)
                {
                    newchar->pain_config_flags |= PAIN_CONFIG_PAIN_DISABLE;
                }
                else
                {
                    newchar->pain_config_flags &= ~PAIN_CONFIG_PAIN_DISABLE;
                }

                break;
            case CMD_MODEL_ESCAPEHITS:
                // How many times an enemy can be hit before retaliating
                newchar->escapehits = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHARGERATE:
                // How much mp does this character gain while recharging?
                newchar->chargerate = GET_INT_ARG(1);
                break;
            case CMD_MODEL_MPRATE:
                newchar->mprate = GET_INT_ARG(1);
                break;
            case CMD_MODEL_MPSET:
                // Mp bar wax/wane.
                newchar->mp             = GET_INT_ARG(1); //Max MP.
                newchar->mpstable       = GET_INT_ARG(2); //MP stable setting.
                newchar->mpstableval    = GET_INT_ARG(3); //MP stable value (% Mp bar will try and maintain).
                newchar->mprate         = GET_INT_ARG(4); //Rate MP value rises over time.
                newchar->mpdroprate     = GET_INT_ARG(5); //Rate MP value drops over time.
                newchar->chargerate     = GET_INT_ARG(6); //MP Chargerate.
                break;
            case CMD_MODEL_SLEEPWAIT:
                newchar->sleepwait = GET_INT_ARG(1);
                break;
            case CMD_MODEL_GUARDRATE:
                newchar->guardrate = GET_INT_ARG(1);
                break;
            case CMD_MODEL_AGGRESSION:
                newchar->aggression = GET_INT_ARG(1);
                break;
            case CMD_MODEL_ATTACKTHROTTLE:
                newchar->attackthrottle = GET_FLOAT_ARG(1);
                if(arglist.count >= 2)
                {
                    newchar->attackthrottletime = GET_FLOAT_ARG(2) * GAME_SPEED;
                }
                break;
            case CMD_MODEL_RISETIME:
                newchar->risetime.rise = GET_INT_ARG(1);
                newchar->risetime.riseattack = GET_INT_ARG(2);
                break;
            case CMD_MODEL_FACING:
                newchar->facing = GET_INT_ARG(1);
                break;
            case CMD_MODEL_TURNDELAY:
                newchar->turndelay = GET_INT_ARG(1);
                break;
            case CMD_MODEL_LIFESPAN:
                newchar->lifespan = GET_FLOAT_ARG(1) * GAME_SPEED;
                break;
            case CMD_MODEL_SUMMONKILL:
                newchar->summonkill = GET_INT_ARG(1);
                break;
            case CMD_MODEL_LIFEPOSITION:

                /*
                * Allocate object if we need it first.
                */

                if (!newchar->hud_popup)
                {
                    newchar->hud_popup = bar_status_allocate_object();
                }
                
                if ((value = GET_ARG(1))[0])
                {
                    newchar->hud_popup->graph_position.x = atoi(value);
                }
                if ((value = GET_ARG(2))[0])
                {
                    newchar->hud_popup->graph_position.y = atoi(value);
                }

                break;
            case CMD_MODEL_LIFEBARSTATUS:

                /*
                * Allocate object if we need it first.
                */

                if (!newchar->hud_popup)
                {
                    newchar->hud_popup = bar_status_allocate_object();
                }

                _readbarstatus(buf + pos, newchar->hud_popup);
                newchar->hud_popup->colourtable = &hpcolourtable;
                break;
            case CMD_MODEL_ICONPOSITION:
                if((value = GET_ARG(1))[0])
                {
                    newchar->icon.position.x = atoi(value);
                }
                if((value = GET_ARG(2))[0])
                {
                    newchar->icon.position.y = atoi(value);
                }
                break;
            case CMD_MODEL_NAMEPOSITION:

                /*
                * Allocate object if we need it first.
                */

                if (!newchar->hud_popup)
                {
                    newchar->hud_popup = bar_status_allocate_object();
                }

                if((value = GET_ARG(1))[0])
                {
                    newchar->hud_popup->name_position.x = atoi(value);
                }
                if((value = GET_ARG(2))[0])
                {
                    newchar->hud_popup->name_position.y = atoi(value);
                }
                break;
            case CMD_MODEL_COM:
            {
                // Section for custom freespecials starts here
                int i, t;
                int add_flag = 0;
                alloc_specials(newchar);
                newchar->special[newchar->specials_loaded].numkeys = 0;
                for(i = 0, t = 1; i < MAX_SPECIAL_INPUTS - 3; i++, t++)
                {
                    value = GET_ARG(t);
                    if(!value[0])
                    {
                        break;
                    }
                    if(stricmp(value, "u") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_MOVEUP;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_MOVEUP;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "d") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_MOVEDOWN;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_MOVEDOWN;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "f") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_FORWARD;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_FORWARD;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "b") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_BACKWARD;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_BACKWARD;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "a") == 0 || stricmp(value, "a1") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_ATTACK;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_ATTACK;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "a2") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_ATTACK2;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_ATTACK2;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "a3") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_ATTACK3;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_ATTACK3;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "a4") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_ATTACK4;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_ATTACK4;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "j") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_JUMP;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_JUMP;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "s") == 0 || stricmp(value, "k") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_SPECIAL;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_SPECIAL;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(starts_with_num(value, "freespecial"))
                    {
                        tempInt = atoi(value + 11);
                        if(tempInt < 1)
                        {
                            tempInt = 1;
                        }
                        newchar->special[newchar->specials_loaded].anim = animspecials[tempInt - 1];
                    }
                    else if(stricmp(value, "+") == 0 && i >= 1)
                    {
                        add_flag = 1;
                        i -= 2;
                        continue;
                    }
                    else if(stricmp(value, "->") == 0 && i > 0)
                    {
                        // just for better reading
                        --i;
                        continue;
                    }
                    else
                    {
                        shutdownmessage = "Invalid freespecial command";
                        goto lCleanup;
                    }
                    add_flag = 0;
                    //printf("insert:%s in %d, numkeys:%d for special n.:%d\n",value,i,newchar->special[newchar->specials_loaded].numkeys,newchar->specials_loaded);
                }
                newchar->special[newchar->specials_loaded].steps = i - 1; // max steps
                newchar->specials_loaded++;
            }
            // End section for custom freespecials
            break;
            case CMD_MODEL_REMAP:
            {
                // This command should not be used under 24bit mode, but for old mods, just give it a default palette
                value = GET_ARG(1);
                value2 = GET_ARG(2);
                __realloc(mapflag, newchar->maps_loaded);
                errorVal = load_colourmap(newchar, value, value2);

                if(0 >= errorVal)
                {
                    switch(errorVal)
                    {
                    case 0: // uhm wait, we just tested for !errorVal...
                        value2 = "Failed to create colourmap. Image Used Twice!";
                        break;
                    case -1: //should not happen now
                        value2 = "Failed to create colourmap. Color maps full error (color maps are unlimited by engine - check memory limits of console)!";
                        break;
                    case -2:
                        value2 = "Failed to create colourmap. Failed to allocate memory!";
                        break;
                    case -3:
                        value2 = "Failed to create colourmap. Failed to load file 1";
                        break;
                    case -4:
                        value2 = "Failed to create colourmap. Failed to load file 2";
                        break;
                    }
                    printf("Warning: %s\n", value2);
                }
                else
                {
                    if(pixelformat == PIXEL_x8 && newchar->palette == NULL)
                    {
                        newchar->palette = malloc(PAL_BYTES);
                        if(loadimagepalette(value, packfile, newchar->palette) == 0)
                        {
                            shutdownmessage = "Failed to load palette!";
                            goto lCleanup;
                        }
                    }
                    mapflag[newchar->maps_loaded - 1] = 1;
                }
            }
            break;
            case CMD_MODEL_PALETTE:

                if(newchar->palette == NULL)
                {

                    // Command title for log. Details will be added blow accordingly.
                    // Forced character length is to line up with Alternatepal logs.
                    //printf("\t\t\tPalette: \t");

                    // Get argument.
                    value = GET_ARG(1);

                    // If "none" then set nopalette, meaning each
                    // frame retains its own color table as is.
                    // Otherwise we will set up the model level
                    // color table.
                    if(stricmp(value, "none") == 0)
                    {
                        nopalette = 1;

                        //printf("%s\n", "'None' option active. All sprites for this model will be loaded with independent color tables.");
                    }
                    else
                    {
                        // Set up the model level color table.
                        // We can load either directly from
                        // an .act file or read the color table
                        // from an image.

                        // Allocate space for the color table.
                        newchar->palette = malloc(PAL_BYTES);

                        if(load_palette(newchar->palette, value) == 0)
                        {
                            //printf("%s%s\n", "Failed to load color table from file: ", value);
                            goto lCleanup;
                        }

                        //printf("%s%s\n", "Loaded color selection 0: ", value);
                    }
                }

                break;
            case CMD_MODEL_ALTERNATEPAL:

                // Command title for log. Details will be added blow accordingly.
                //printf("\t"LOG_CMD_TITLE"%s", "Alternatepal", " - ");

                __realloc(mapflag, newchar->maps_loaded);
                __realloc(newchar->colourmap, newchar->maps_loaded);
                value = GET_ARG(1);

                newchar->colourmap[newchar->maps_loaded] = malloc(PAL_BYTES);

                if(load_palette(newchar->colourmap[newchar->maps_loaded], value) == 0)
                {
                    //printf("%s%s", "Failed to load color table from file: ", value);
                    goto lCleanup;
                }

                newchar->maps_loaded++;

                //printf("Loaded color selection %i: %s", newchar->maps_loaded, value);

                break;
            case CMD_MODEL_GLOBALMAP:

                // Command title for log. Details will be added blow accordingly.
                //printf("\t"LOG_CMD_TITLE"%s", "Globalmap", " - ");

                // use global palette under 24bit mode, so some entity/panel/bg can still use palette feature, that saves some memory
                newchar->globalmap = GET_INT_ARG(1);

                //printf("%i: %s\n", newchar->globalmap, value);

                break;
            case CMD_MODEL_ALPHA:
                newchar->alpha = GET_INT_ARG(1);
                break;
            case CMD_MODEL_REMOVE:
                newchar->remove = GET_INT_ARG(1);
                break;
            case CMD_MODEL_SCRIPT:
                //load the update script
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->update_script, "updateentityscript", filename, 1, 0);
                break;
            case CMD_MODEL_THINKSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->think_script, "thinkscript", filename, 1, 0);
                break;
            case CMD_MODEL_TAKEDAMAGESCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->takedamage_script, "takedamagescript", filename, 1, 0);
                break;
            case CMD_MODEL_ON_BIND_UPDATE_OTHER_TO_SELF_SCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->on_bind_update_other_to_self_script, "on_bind_update_other_to_self_script", filename, 1, 0);
                break;
            case CMD_MODEL_ON_BIND_UPDATE_SELF_TO_OTHER_SCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->on_bind_update_self_to_other_script, "on_bind_update_self_to_other_script", filename, 1, 0);
                break;
            case CMD_MODEL_ONFALLSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onfall_script, "onfallscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONPAINSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onpain_script, "onpainscript", filename, 1, 0);
                break;
            case CMD_MODEL_INHOLESCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->inhole_script, "inholescript", filename, 1, 0);
                break;
            case CMD_MODEL_ONBLOCKSSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onblocks_script, "onblocksscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONBLOCKWSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onblockw_script, "onblockwscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONBLOCKPSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onblockp_script, "onblockpscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONBLOCKOSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onblocko_script, "onblockoscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONBLOCKZSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onblockz_script, "onblockzscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONBLOCKASCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onblocka_script, "onblockascript", filename, 1, 0);
                break;
            case CMD_MODEL_ONMOVEXSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onmovex_script, "onmovexscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONMOVEZSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onmovez_script, "onmovezscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONMOVEASCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onmovea_script, "onmoveascript", filename, 1, 0);
                break;
            case CMD_MODEL_ONDEATHSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->ondeath_script, "ondeathscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONENTITYCOLLISIONSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onentitycollision_script, "onentitycollisionscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONKILLSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onkill_script, "onkillscript", filename, 1, 0);
                break;
            case CMD_MODEL_DIDBLOCKSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->didblock_script, "didblockscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONDOATTACKSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->ondoattack_script, "ondoattackscript", filename, 1, 0);
                break;
            case CMD_MODEL_DIDHITSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->didhit_script, "didhitscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONSPAWNSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onspawn_script, "onspawnscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONMODELCOPYSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->onmodelcopy_script, "onmodelcopyscript", filename, 1, 0);
                break;
            case CMD_MODEL_ONDRAWSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->ondraw_script, "ondrawscript", filename, 1, 0);
                break;
            case CMD_MODEL_ANIMATIONSCRIPT:
                //pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->animation_script, "animationscript", filename, 0, 0);
                pos += lcmScriptCopyBuffer(&arglist, buf + pos, &animscriptbuf);
                //dont compile, until at end of this function
                break;
            case CMD_MODEL_KEYSCRIPT:
                pos += lcmHandleCommandScripts(&arglist, buf + pos, newchar->scripts->key_script, "entitykeyscript", filename, 1, 0);
                break;
            case CMD_MODEL_ANIM:
            {
                value = GET_ARG(1);
                frameset = 0;
                framecount = 0;
                // Create new animation
                newanim = alloc_anim();
                if(!newanim)
                {
                    shutdownmessage = "Not enough memory for animations!";
                    goto lCleanup;
                }
                newanim->model_index = newchar->index;
                // Reset vars
                curframe = 0;
                
                /*
                * Caskey, Damon V.
                * 2021-08-23
                * 
                * Prepare temporary lists for input.
                */
                collision_attack_free_list(temp_collision_head);
                collision_body_free_list(temp_collision_body_head);
                temp_collision_head = NULL;
                temp_collision_body_head = NULL;
                temp_collision_index = 0;

                child_spawn_free_list(temp_child_spawn_head);
                temp_child_spawn_head = NULL;
                temp_child_spawn_index = 0;

                //printf("\n\n anim: %p", newanim);
                //child_spawn_dump_list(temp_child_spawn_head);

                memset(&ebox, 0, sizeof(ebox));
                memset(&offset, 0, sizeof(offset));
                memset(shadow_coords, 0, sizeof(shadow_coords));
                memset(shadow_xz, 0, sizeof(shadow_xz));
                memset(platform, 0, sizeof(platform));

                shadow_set                      = 0;
                ebox_con                        = empty_entity_collision;
                drawmethod                      = plainmethod;
                idle                            = 0;
                move.base                       = -1;
                move.axis.x                     = 0;
                move.axis.y                     = 0;
                move.axis.z                     = 0;
                frameshadow                     = FRAME_SHADOW_NONE;
                soundtoplay                     = SAMPLE_ID_NONE;

                /*
                * Other than Min X, default ranges are 
                * based on jump height or grabdistance. 
                * This is partialy for legacy compatability.
                * It works well enough that beginners won't 
                * know the difference and advanced creators 
                * will always want to adjust for their specfic 
                * needs.
                */

                const int range_default_grabdistance = (int)newchar->grabdistance / 3;
                const int range_default_jumpheight_max = (int)newchar->jumpheight * 20;
                const int range_default_jumpheight_min = (int)newchar->jumpheight * 10;

                newanim->range = (s_range){
                    .base = {.max = range_default_jumpheight_max, .min = -range_default_jumpheight_min },
                    .x = {.max = range_default_jumpheight_max, .min = (newanim->range.x.min) ? newanim->range.x.min : -10 },
                    .y = {.max = range_default_jumpheight_max, .min = -range_default_jumpheight_min },
                    .z = {.max = range_default_grabdistance, .min = -range_default_grabdistance }
                };

                		
                newanim->energy_cost.cost       = 0;
				newanim->energy_cost.disable	= 0;
				newanim->energy_cost.mponly		= COST_TYPE_MP_THEN_HP;
                newanim->charge_time            = ANIMATION_CHARGE_TIME_DEFAULT;
				newanim->projectile				= NULL;
				newanim->flipframe              = FRAME_NONE;
                newanim->attack_one             = 0;
                newanim->move_config_flags        |= MOVE_CONFIG_SUBJECT_TO_GRAVITY;
                newanim->followup.animation     = 0;			// Default disabled
                newanim->followup.condition     = FOLLOW_CONDITION_NONE;
                newanim->sub_entity_unsummon          = FRAME_NONE;
                newanim->landframe.frame		= FRAME_NONE;
				newanim->landframe.model_index	= FRAME_SET_MODEL_INDEX_DEFAULT;
                
                newanim->jumpframe = (s_onframe_move){
                    .frame = FRAME_NONE,
                    .model_index = MODEL_INDEX_NONE,
                    .velocity = default_model_dropv
                };
                
                newanim->dropframe.frame        = FRAME_NONE;
				newanim->dropframe.model_index	= FRAME_SET_MODEL_INDEX_DEFAULT;
                newanim->cancel                 = ANIMATION_CANCEL_DISABLED;  // OX. For cancelling anims into a freespecial.
                newanim->hit_count              = 0; //OX counts hits on a per anim basis for cancels.
                newanim->sub_entity_model_index = MODEL_INDEX_NONE;
				newanim->sub_entity_spawn		= NULL;
				newanim->sub_entity_summon		= NULL;
                newanim->quakeframe.framestart  = 0;
                newanim->sync                   = FRAME_NONE;

                if((ani_id = translate_ani_id(value, newchar, newanim)) < 0)
                {
                    shutdownmessage = "Invalid animation name!";
                    goto lCleanup;
                }

                newchar->animation[ani_id] = newanim;
            }
            break;
            case CMD_MODEL_LOOP:
                if(!newanim)
                {
                    shutdownmessage = "Can't set loop: no animation specified!";
                    goto lCleanup;
                }
                newanim->loop.mode      = GET_INT_ARG(1); //0 = Off, 1 = on.
                newanim->loop.frame.min = GET_INT_ARG(2); //Loop to frame.
                newanim->loop.frame.max = GET_INT_ARG(3); //Loop end frame.
                break;
            case CMD_MODEL_ANIMHEIGHT:
                newanim->size.y = GET_INT_ARG(1);
                break;
            case CMD_MODEL_SYNC:
                //if you want to remove default sync setting for idle or walk, use none
                newanim->sync = translate_ani_id(GET_ARG(1), NULL, NULL);
                break;
            case CMD_MODEL_DELAY:

                value = GET_ARG(1);
                
                if (stricmp(value, "infinite") == 0)
                {
                    delay = DELAY_INFINITE;
                }
                else
                {
                    delay = GET_INT_ARG(1);
                }

                break;
            case CMD_MODEL_OFFSET:
                offset.x = GET_INT_ARG(1);
                offset.y = GET_INT_ARG(2);
                break;
            case CMD_MODEL_SHADOWCOORDS:
                shadow_xz[0] = GET_INT_ARG(1);
                shadow_xz[1] = GET_INT_ARG(2);
                shadow_set = 1;
                break;
            case CMD_MODEL_ENERGY_COST:
            case CMD_MODEL_MPCOST:
               
                newanim->energy_cost.cost    = GET_INT_ARG(1);
                newanim->energy_cost.mponly  = GET_INT_ARG(2);
				newanim->energy_cost.disable = GET_INT_ARG(3);

				// Caskey, Damon V.
				// 2019-11-22
				// 
				// Convert the idiotic arbitrary numeric value in text into bitwise 
				// logic with entity types. I was a buffoon when I first coded this 
				// feature and should have used bitwise logic to begin with.
				switch (newanim->energy_cost.disable)
				{
				case 1:
					newanim->energy_cost.disable |= TYPE_ENEMY;
					newanim->energy_cost.disable |= TYPE_NPC;
					newanim->energy_cost.disable |= TYPE_PLAYER;
					break;
				case 2:
					newanim->energy_cost.disable |= TYPE_ENEMY;
					newanim->energy_cost.disable |= TYPE_NPC;
					break;
				case 3:
					newanim->energy_cost.disable |= TYPE_NPC;
					newanim->energy_cost.disable |= TYPE_PLAYER;
					break;
				case 4:
					newanim->energy_cost.disable |= TYPE_ENEMY;
					newanim->energy_cost.disable |= TYPE_PLAYER;
					break;
				}

                break;
            case CMD_MODEL_MPONLY:
                newanim->energy_cost.mponly = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHARGETIME:
                newanim->charge_time = GET_INT_ARG(1);
                break;            

                /* 2022-05-27
                * Caskey, Damon V.
                *
                * Index needs to come before any other child spawn
                * read in command so we know which object index
                * we want the child spawn commands to affect.
                * 
                * The next in order are presets, so the creator
                * can use a preset, then customize individual
                * settings downstream.
                */
            case CMD_MODEL_CHILD_SPAWN_INDEX:
                temp_child_spawn_index = GET_INT_ARG(1);                
                break;
            case CMD_MODEL_CHILD_SPAWN_PRESET_BOMB:

                /*
                * Shortcut for creator to set up a bomb projectile.
                */

                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->config = (CHILD_SPAWN_CONFIG_AUTOKILL_HIT | CHILD_SPAWN_CONFIG_BEHAVIOR_BOMB | CHILD_SPAWN_CONFIG_FACTION_DAMAGE_PARENT | CHILD_SPAWN_CONFIG_FACTION_HOSTILE_PARENT | CHILD_SPAWN_CONFIG_FACTION_INDIRECT_PARENT | CHILD_SPAWN_CONFIG_LAUNCH_TOSS | CHILD_SPAWN_CONFIG_MOVE_CONFIG_PARAMETER | CHILD_SPAWN_CONFIG_RELATIONSHIP_OWNER);
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->aimove = AIMOVE1_BOMB;
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->direction_adjust = DIRECTION_ADJUST_SAME;
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->move_config_flags = (MOVE_CONFIG_NO_ADJUST_BASE | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z | MOVE_CONFIG_SUBJECT_TO_PLATFORM);
                
                break;

            case CMD_MODEL_CHILD_SPAWN_PRESET_SHOT:

                /*
                * Shortcut for creator to set up a shot projectile.
                */

                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->config = (CHILD_SPAWN_CONFIG_AUTOKILL_HIT | CHILD_SPAWN_CONFIG_BEHAVIOR_SHOT | CHILD_SPAWN_CONFIG_FACTION_DAMAGE_PARENT | CHILD_SPAWN_CONFIG_FACTION_HOSTILE_PARENT | CHILD_SPAWN_CONFIG_FACTION_INDIRECT_PARENT | CHILD_SPAWN_CONFIG_GRAVITY_OFF | CHILD_SPAWN_CONFIG_LAUNCH_THROW | CHILD_SPAWN_CONFIG_MOVE_CONFIG_PARAMETER | CHILD_SPAWN_CONFIG_RELATIONSHIP_OWNER);
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->aimove = AIMOVE1_ARROW;
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->direction_adjust = DIRECTION_ADJUST_SAME;
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->move_config_flags = (MOVE_CONFIG_NO_ADJUST_BASE | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL | MOVE_CONFIG_SUBJECT_TO_MAX_Z | MOVE_CONFIG_SUBJECT_TO_MIN_Z | MOVE_CONFIG_SUBJECT_TO_PLATFORM);
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->takedamage = arrow_takedamage;

                break;
            
            case CMD_MODEL_CHILD_SPAWN_AIMOVE:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->aimove = get_aimove_from_arguments(&arglist, AIMOVE1_NONE);
                break;
            case CMD_MODEL_CHILD_SPAWN_CANDAMAGE:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->candamage = get_type_from_arglist(&arglist);
                break;
            case CMD_MODEL_CHILD_SPAWN_COLOR:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->color = child_spawn_get_color_from_argument(filename, command, GET_ARG(1));
                break;
            case CMD_MODEL_CHILD_SPAWN_CONFIG:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->config = child_spawn_get_config_argument(&arglist, 0);
                break;
            case CMD_MODEL_CHILD_SPAWN_DIRECTION_ADJUST:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->direction_adjust = direction_get_adjustment_from_argument(filename, command, GET_ARG(1));
                break;
            case CMD_MODEL_CHILD_SPAWN_HOSTILE:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->hostile = get_type_from_arglist(&arglist);
                break;
            case CMD_MODEL_CHILD_SPAWN_MODEL:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->model_index = get_cached_model_index(GET_ARG(1));
                break;
            case CMD_MODEL_CHILD_SPAWN_MOVE_CONSTRAINT:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->move_config_flags = get_move_config_flags_from_arguments(&arglist);
                break;
            case CMD_MODEL_CHILD_SPAWN_OFFSET_X:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->position.x = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_SPAWN_OFFSET_Y:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->position.y = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_SPAWN_OFFSET_Z:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->position.z = GET_INT_ARG(1);
                break;
            case CMD_MODEL_CHILD_SPAWN_PROJECTILEHIT:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->projectilehit = get_type_from_arglist(&arglist);
                break;
            case CMD_MODEL_CHILD_SPAWN_TAKEDAMAGE:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->takedamage = takedamage_get_reference_from_argument(GET_ARG(1));
                break;
            case CMD_MODEL_CHILD_SPAWN_VELOCITY_X:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->velocity.x = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_CHILD_SPAWN_VELOCITY_Y:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->velocity.y = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_CHILD_SPAWN_VELOCITY_Z:
                child_spawn_upsert_property(&temp_child_spawn_head, temp_child_spawn_index)->velocity.z = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_COLLISIONONE:
                newanim->attack_one = GET_INT_ARG(1);
                break;

                /* 2020-03-02
                * Caskey, Damon V.
                *
                * This needs to come before any other collision
                * read in command so we know which collision index 
                * we want the collision commands to affect.
                */
            case CMD_MODEL_COLLISION_INDEX:
                temp_collision_index = GET_INT_ARG(1);
                break;
            case CMD_MODEL_COUNTERATTACK:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->counterattack = GET_INT_ARG(1);
                
                break;
            case CMD_MODEL_THROWFRAME:
            case CMD_MODEL_PSHOTFRAME:
            case CMD_MODEL_PSHOTFRAMEW:
            case CMD_MODEL_PSHOTFRAMENO:

				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

                newanim->projectile->throwframe = GET_FRAME_ARG(1);
                
				if (GET_INT_ARG(2))
				{
					newanim->projectile->position.y = GET_INT_ARG(2);
				}
				break;


            case CMD_MODEL_SHOOTFRAME:
                
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

				newanim->projectile->shootframe = GET_FRAME_ARG(1);

				if (GET_INT_ARG(2))
				{
					newanim->projectile->position.y = GET_INT_ARG(2);
				}
				break;

            case CMD_MODEL_TOSSFRAME:
            case CMD_MODEL_PBOMBFRAME:
               
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

				newanim->projectile->tossframe = GET_FRAME_ARG(1);

				if (GET_INT_ARG(2))
				{
					newanim->projectile->position.y = GET_INT_ARG(2);
				}

				// For legacy compatability. See bomb_spawn() for details.
				newanim->projectile->velocity.y = MODEL_SPEED_NONE;
				break;
            case CMD_MODEL_CUSTKNIFE:
            case CMD_MODEL_CUSTPSHOT:
            case CMD_MODEL_CUSTPSHOTW:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

                newanim->projectile->knife = get_cached_model_index(GET_ARG(1));
                break;
            case CMD_MODEL_CUSTPSHOTNO:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

                newanim->projectile->flash = get_cached_model_index(GET_ARG(1));
                break;
            case CMD_MODEL_CUSTBOMB:
            case CMD_MODEL_CUSTPBOMB:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

                newanim->projectile->bomb = get_cached_model_index(GET_ARG(1));
				
                break;
            case CMD_MODEL_CUSTSTAR:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

                newanim->projectile->star = get_cached_model_index(GET_ARG(1));
                break;
			case CMD_MODEL_PROJECTILE_COLOR_SET_ADJUST:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

				value = GET_ARG(1);

				if (stricmp(value, "none") == 0)
				{
					tempInt = COLORSET_ADJUST_NONE;
				}
				else if (stricmp(value, "parent_index") == 0)
				{
					tempInt = COLORSET_ADJUST_PARENT_INDEX;
				}
				else if (stricmp(value, "parent_table") == 0)
				{
					tempInt = COLORSET_ADJUST_PARENT_TABLE;
				}
				else
				{
					tempInt = GET_INT_ARG(1);
				}

				newanim->projectile->color_set_adjust = tempInt;
				break;
			case CMD_MODEL_PROJECTILE_DIRECTION_ADJUST:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

                tempInt = direction_get_adjustment_from_argument(filename, command, GET_ARG(1));
				
				newanim->projectile->direction_adjust = tempInt;
				break;
			case CMD_MODEL_PROJECTILE_OFFENSE:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

				value = GET_ARG(1);

				if (stricmp(value, "parent") == 0)
				{
					tempInt = PROJECTILE_OFFENSE_PARENT;
				}
				else if (stricmp(value, "self") == 0)
				{
					tempInt = PROJECTILE_OFFENSE_SELF;
				}

				newanim->projectile->offense = tempInt;
				break;
			case CMD_MODEL_PROJECTILE_POSITION_X:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

				newanim->projectile->position.x = GET_INT_ARG(1);
				break;
			case CMD_MODEL_PROJECTILE_POSITION_Y:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

				newanim->projectile->position.y = GET_INT_ARG(1);
				break;
			case CMD_MODEL_PROJECTILE_POSITION_Z:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

				newanim->projectile->position.z = GET_INT_ARG(1);
				break;
			case CMD_MODEL_PROJECTILE_VELOCITY_X:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

				newanim->projectile->velocity.x = GET_FLOAT_ARG(1);
				break;
			case CMD_MODEL_PROJECTILE_VELOCITY_Y:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

				newanim->projectile->velocity.y = GET_FLOAT_ARG(1);
				break;
			case CMD_MODEL_PROJECTILE_VELOCITY_Z:
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}

				newanim->projectile->velocity.z = GET_FLOAT_ARG(1);
				break;
			case CMD_MODEL_STAR_VELOCITY:
								
				// If we don't have a projectile allcated, do it now.
				if (!newanim->projectile)
				{
					newanim->projectile = allocate_projectile();
				}
				
				newanim->projectile->star_velocity[0] = GET_FLOAT_ARG(1);
				newanim->projectile->star_velocity[1] = GET_FLOAT_ARG(2);
				newanim->projectile->star_velocity[2] = GET_FLOAT_ARG(3);
				break;

				// Legacy dive attacks. Turn off animation level subject_to_gravity
				// and then use jumpframe to fly down at an angle.
            case CMD_MODEL_DIVE: 
                newanim->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_GRAVITY;

                newanim->jumpframe = (s_onframe_move){
                    .frame = 0,
                    .model_index = MODEL_INDEX_NONE,
                    .velocity = {.x = GET_FLOAT_ARG(1), .y = -GET_FLOAT_ARG(2), .z = 0 }
                };
                break;

            case CMD_MODEL_DIVE1:
                newanim->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_GRAVITY;

                newanim->jumpframe = (s_onframe_move){
                    .frame = 0,
                    .model_index = MODEL_INDEX_NONE,
                    .velocity = {.x = GET_FLOAT_ARG(1), .y = 0, .z = 0 }
                };
                break;
            case CMD_MODEL_DIVE2:
                newanim->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_GRAVITY;

                newanim->jumpframe = (s_onframe_move){
                    .frame = 0,
                    .model_index = MODEL_INDEX_NONE,
                    .velocity = {.x = 0, .y = -GET_FLOAT_ARG(2), .z = 0 }
                };
                break;
            case CMD_MODEL_JUMPFRAME:
            {
                memset(&newanim->jumpframe.velocity, 0, sizeof(s_axis_principal_float));

                newanim->jumpframe.frame        = GET_FRAME_ARG(1);
                newanim->jumpframe.velocity.y   = GET_FLOAT_ARG(2);

                value = GET_ARG(3);
                if(value[0])
                {
                    newanim->jumpframe.velocity.x = GET_FLOAT_ARG(3);
                    newanim->jumpframe.velocity.z = GET_FLOAT_ARG(4);
                }
                else // k, only for backward compatibility :((((((((((((((((
                {
                    if(newanim->jumpframe.velocity.y <= 0)
                    {
                        if(newchar->type == TYPE_PLAYER)
                        {
                            newanim->jumpframe.velocity.y = newchar->jumpheight / 2;
                            newanim->jumpframe.velocity.z = 0;
                            newanim->jumpframe.velocity.x = 2;
                        }
                        else
                        {
                            newanim->jumpframe.velocity.y = newchar->jumpheight;
                            newanim->jumpframe.velocity.z = newanim->jumpframe.velocity.x = 0;
                        }
                    }
                    else
                    {
                        if(newchar->type != TYPE_ENEMY && newchar->type != TYPE_NPC)
                        {
                            newanim->jumpframe.velocity.z = newanim->jumpframe.velocity.x = 0;
                        }
                        else
                        {
                            newanim->jumpframe.velocity.z = 0;
                            newanim->jumpframe.velocity.x = (float)1.3;
                        }
                    }
                }

                value = GET_ARG(5);
                if(value[0])
                {
                    newanim->jumpframe.model_index = get_cached_model_index(value);
                }
                else
                {
                    newanim->jumpframe.model_index = MODEL_INDEX_NONE;
                }
            }
            break;
            case CMD_MODEL_BOUNCEFACTOR:
                newanim->bounce_factor = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_LANDFRAME:				
                // Landing frame.
                newanim->landframe.frame = GET_FRAME_ARG(1);

                // Entity to spawn when land frame triggers.
                value = GET_ARG(2);
                if(value[0])
                {
                    newanim->landframe.model_index = get_cached_model_index(value);
                }

                break;
            case CMD_MODEL_DROPFRAME:

                newanim->dropframe.frame = GET_FRAME_ARG(1);

				// Entity to spawn when drop frame triggers.
				value = GET_ARG(2);
				if (value[0])
				{
					newanim->dropframe.model_index = get_cached_model_index(value);
				}

                break;
            case CMD_MODEL_CANCEL:
            {
                int i, t;
                int add_flag = 0;
                alloc_specials(newchar);
                newanim->cancel = ANIMATION_CANCEL_ENABLED;
                newchar->special[newchar->specials_loaded].numkeys = 0;
                for(i = 0, t = 4; i < MAX_SPECIAL_INPUTS - 6; i++, t++)
                {
                    value = GET_ARG(t);
                    if(!value[0])
                    {
                        break;
                    }
                    if(stricmp(value, "u") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_MOVEUP;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_MOVEUP;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "d") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_MOVEDOWN;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_MOVEDOWN;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "f") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_FORWARD;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_FORWARD;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "b") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_BACKWARD;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_BACKWARD;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "a") == 0 || stricmp(value, "a1") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_ATTACK;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_ATTACK;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "a2") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_ATTACK2;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_ATTACK2;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "a3") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_ATTACK3;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_ATTACK3;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "a4") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_ATTACK4;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_ATTACK4;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "j") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_JUMP;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_JUMP;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(stricmp(value, "s") == 0 || stricmp(value, "k") == 0)
                    {
                        if (!add_flag) newchar->special[newchar->specials_loaded].input[i] = FLAG_SPECIAL;
                        else newchar->special[newchar->specials_loaded].input[i] |= FLAG_SPECIAL;
                        ++newchar->special[newchar->specials_loaded].numkeys;
                    }
                    else if(starts_with_num(value, "freespecial"))
                    {
                        get_tail_number(tempInt, value, "freespecial");
                        newchar->special[newchar->specials_loaded].anim = animspecials[tempInt - 1];
                        newchar->special[newchar->specials_loaded].frame.min = GET_INT_ARG(1); // stores start frame
                        newchar->special[newchar->specials_loaded].frame.max = GET_INT_ARG(2); // stores end frame
                        newchar->special[newchar->specials_loaded].cancel = ani_id;                    // stores current anim
                        newchar->special[newchar->specials_loaded].hits = GET_INT_ARG(3);// stores hits
                    }
                    else if(stricmp(value, "+") == 0 && i > 1)
                    {
                        add_flag = 1;
                        i -= 2;
                        continue;
                    }
                    else if(stricmp(value, "->") == 0 && i > 0)
                    {
                        // just for better reading
                        --i;
                        continue;
                    }
                    else
                    {
                        shutdownmessage = "Invalid cancel command!";
                        goto lCleanup;
                    }
                    add_flag = 0;
                }
                newchar->special[newchar->specials_loaded].steps = i - 1; // max steps
                newchar->specials_loaded++;
            }
            break;
            case CMD_MODEL_SOUND:
                soundtoplay = sound_load_sample(GET_ARG(1), packfile, 1);
                break;
            case CMD_MODEL_HITFX:

                value = GET_ARG(1);

                if(stricmp(value, "none") == 0)
                {
                    tempInt = SAMPLE_ID_NONE;                    
                }
                else
                {
                    tempInt = sound_load_sample(value, packfile, 1);
                }

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->hitsound = tempInt;

                tempInt = 0;

                break;

            case CMD_MODEL_HITFLASH:

                /* First find out which flash model index (if any). */

                value = GET_ARG(1);
                                
                if (stricmp(value, "none") == 0)
                {
                    tempInt = MODEL_INDEX_NONE;
                }
                else
                {
                    tempInt = get_cached_model_index(value);
                }

                /* Apply model index to frame attack box or smartbomb? */

                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->hitflash = tempInt;
                }
                else
                {                    
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->hitflash = tempInt;
                }

                break;

            case CMD_MODEL_BLOCKFLASH:
                
                value = GET_ARG(1);

                if (stricmp(value, "none") == 0)
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blockflash = MODEL_INDEX_NONE;
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blockflash = get_cached_model_index(value);
                }

                break;

            case CMD_MODEL_BLOCKFX:
                
                value = GET_ARG(1);

                if (stricmp(value, "none") == 0)
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blocksound = SAMPLE_ID_NONE;
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blocksound = sound_load_sample(value, packfile, 1);
                }

                break;

            case CMD_MODEL_FASTATTACK:
                
                if(GET_INT_ARG(1))
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->next_hit_time = GAME_SPEED / 20;
                }

                break;

            case CMD_MODEL_IGNOREATTACKID:
                
                if(GET_INT_ARG(1))
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->ignore_attack_id = 1;
                }
                
                break;

            case CMD_MODEL_BBOX:   

                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index);

                value = GET_ARG(1);
                if (stricmp(value, "none") == 0)
                {
                    collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->x = 0;
                    collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->y = 0;
                    collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->width = 0;
                    collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->height = 0;

                    break;
                }

                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->x = GET_INT_ARG(1);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->y = GET_INT_ARG(2);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->width = GET_INT_ARG(3);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->height = GET_INT_ARG(4);
                
                /*
                * 2023-01-13: If only the first Z depth provided, 
                * use it for both directions. We verify a numeric
                * instead of checking for empty because the creator
                * might intentionally apply a 0 value to foreground
                * depth and a simple empty check would override it.
                */

                tempInt = GET_INT_ARG(5);

                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->z_background = tempInt;

                value = GET_ARG(6);

                if (isNumeric(value))
                {
                    tempInt = GET_INT_ARG(6);
                }                

                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->z_foreground = tempInt;

                break;
            case CMD_MODEL_BBOX_INDEX:
                // Does nothing. Do not modify.
                break;
            case CMD_MODEL_BBOX_EFFECT_HIT_FLASH_LAYER_ADJUST:
                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index)->flash_layer_adjust = GET_INT_ARG(1);
                break;
            case CMD_MODEL_BBOX_EFFECT_HIT_FLASH_LAYER_SOURCE:
                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index)->flash_layer_source = GET_INT_ARG(1);
                break;
            case CMD_MODEL_BBOX_EFFECT_HIT_FLASH_Z_SOURCE:
                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index)->flash_z_source = GET_INT_ARG(1);
                break;
            case CMD_MODEL_BBOX_POSITION_X:   

                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->x = GET_INT_ARG(1);

                break;
            case CMD_MODEL_BBOX_POSITION_Y:

                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->y = GET_INT_ARG(1);

                break;
            case CMD_MODEL_BBOX_SIZE_X:

                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->width = GET_INT_ARG(1);

                break;
            case CMD_MODEL_BBOX_SIZE_Y:

                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->height = GET_INT_ARG(1);

                break;
            case CMD_MODEL_BBOX_SIZE_Z_1:
            case CMD_MODEL_BBOX_SIZE_Z_BACKGROUND:

                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->z_background = GET_INT_ARG(1);

                break;
            case CMD_MODEL_BBOX_SIZE_Z_2:
            case CMD_MODEL_BBOX_SIZE_Z_FOREGROUND:

                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->z_foreground = GET_INT_ARG(1);

                break;
            case CMD_MODEL_BBOXZ:

                collision_body_upsert_property(&temp_collision_body_head, temp_collision_index);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->z_background = GET_INT_ARG(1);
                collision_body_upsert_coordinates_property(&temp_collision_body_head, temp_collision_index)->z_foreground = GET_INT_ARG(2);

                break;
            case CMD_MODEL_EBOX:
                ebox.x = GET_INT_ARG(1);
                ebox.y = GET_INT_ARG(2);
                ebox.width = GET_INT_ARG(3);
                ebox.height = GET_INT_ARG(4);
                ebox.z_background = GET_INT_ARG(5);
                ebox.z_foreground = GET_INT_ARG(6);
                break;
            case CMD_MODEL_EBOX_INDEX:
                // Nothing yet - for future support of multiple boxes.
                break;
            case CMD_MODEL_EBOX_POSITION_X:
                ebox.x = GET_INT_ARG(1);
                break;
            case CMD_MODEL_EBOX_POSITION_Y:
                ebox.y = GET_INT_ARG(1);
                break;
            case CMD_MODEL_EBOX_SIZE_X:
                ebox.width = GET_INT_ARG(1);
                break;
            case CMD_MODEL_EBOX_SIZE_Y:
                ebox.height = GET_INT_ARG(1);
                break;
            case CMD_MODEL_EBOX_SIZE_Z_1:
                ebox.z_background = GET_INT_ARG(1);
                break;
            case CMD_MODEL_EBOX_SIZE_Z_2:
                ebox.z_foreground = GET_INT_ARG(1);
                break;
            case CMD_MODEL_EBOXZ:
                ebox.z_background = GET_INT_ARG(1);
                ebox.z_foreground = GET_INT_ARG(2);
                break;
            case CMD_MODEL_PLATFORM:
                newchar->hasPlatforms = 1;
                //for(i=0;(GET_ARG(i+1)[0]; i++);
                for(i = 0; i < arglist.count && arglist.args[i] && arglist.args[i][0]; i++);
                if(i < 8)
                {
                    for(i = 0; i < 6; i++)
                    {
                        platform[i + 2] = GET_FLOAT_ARG(i + 1);
                    }
                    platform[PLATFORM_X] = PLATFORM_DEFAULT_X;
                }
                else for(i = 0; i < 8; i++)
                    {
                        platform[i] = GET_FLOAT_ARG(i + 1);
                    }
                break;
            case CMD_MODEL_DRAWMETHOD:
                value = GET_ARG(1);
                if(isNumeric(value))
                {
                    // special effects
                    drawmethod.scalex = GET_INT_ARG(1);
                    drawmethod.scaley = GET_INT_ARG(2);
                    drawmethod.flipx = GET_INT_ARG(3);
                    drawmethod.flipy = GET_INT_ARG(4);
                    drawmethod.shiftx = GET_INT_ARG(5);
                    drawmethod.alpha = GET_INT_ARG(6);
                    drawmethod.remap = GET_INT_ARG(7);
                    drawmethod.fillcolor = parsecolor(GET_ARG(8));
                    drawmethod.rotate = GET_INT_ARG(9);
                    drawmethod.fliprotate = GET_INT_ARG(10);
                }
                else if (0 == stricmp(value, "scale"))
                {
                    drawmethod.scalex = GET_FLOAT_ARG(2) * 256;
                    drawmethod.scaley = arglist.count > 3 ? GET_FLOAT_ARG(3) * 256 : drawmethod.scalex;
                }
                else if (0 == stricmp(value, "scalex"))
                {
                    drawmethod.scalex = GET_FLOAT_ARG(2) * 256;
                }
                else if (0 == stricmp(value, "scaley"))
                {
                    drawmethod.scaley = GET_FLOAT_ARG(2) * 256;
                }
                else if (0 == stricmp(value, "xrepeat"))
                {
                    drawmethod.xrepeat = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "yrepeat"))
                {
                    drawmethod.yrepeat = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "xspan"))
                {
                    drawmethod.xspan = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "yspan"))
                {
                    drawmethod.yspan = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "flipx"))
                {
                    drawmethod.flipx = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "flipy"))
                {
                    drawmethod.flipy = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "shiftx"))
                {
                    drawmethod.shiftx = GET_FLOAT_ARG(2) * 256;
                }
                else if (0 == stricmp(value, "rotate"))
                {
                    drawmethod.rotate = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "fliprotate"))
                {
                    drawmethod.fliprotate = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "fillcolor"))
                {
                    drawmethod.fillcolor = parsecolor(GET_ARG(2));
                }
                else if (0 == stricmp(value, "remap"))
                {
                    drawmethod.remap = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "channel"))
                {
                    drawmethod.channelr = GET_FLOAT_ARG(2) * 255;
                    drawmethod.channelg = arglist.count > 3 ? GET_FLOAT_ARG(3) * 255 : drawmethod.channelr;
                    drawmethod.channelb = arglist.count > 4 ? GET_FLOAT_ARG(4) * 255 : drawmethod.channelr;
                }
                else if (0 == stricmp(value, "channelr"))
                {
                    drawmethod.channelr = GET_FLOAT_ARG(2) * 255;
                }
                else if (0 == stricmp(value, "channelg"))
                {
                    drawmethod.channelg = GET_FLOAT_ARG(2) * 255;
                }
                else if (0 == stricmp(value, "channelb"))
                {
                    drawmethod.channelb = GET_FLOAT_ARG(2) * 255;
                }
                else if (0 == stricmp(value, "tintmode"))
                {
                    drawmethod.tintmode = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "tintcolor"))
                {
                    drawmethod.tintcolor = parsecolor(GET_ARG(2));
                }
                else if (0 == stricmp(value, "alpha"))
                {
                    drawmethod.alpha = GET_INT_ARG(2);
                }
                else if (0 == stricmp(value, "clip"))
                {
                    drawmethod.clipx = GET_INT_ARG(2);
                    drawmethod.clipy = GET_INT_ARG(3);
                    drawmethod.clipw = GET_INT_ARG(4);
                    drawmethod.cliph = GET_INT_ARG(5);
                }
                if(drawmethod.scalex < 0)
                {
                    drawmethod.scalex = -drawmethod.scalex;
                    drawmethod.flipx = !drawmethod.flipx;
                }
                if(drawmethod.scaley < 0)
                {
                    drawmethod.scaley = -drawmethod.scaley;
                    drawmethod.flipy = !drawmethod.flipy;
                }
                if(drawmethod.rotate)
                {
                    drawmethod.rotate = ((int)drawmethod.rotate % 360 + 360) % 360;
                }
                if(!blendfx_is_set)
                {
                    if(drawmethod.alpha > 0 && drawmethod.alpha <= MAX_BLENDINGS)
                    {
                        blendfx[drawmethod.alpha - 1] = 1;
                    }
                }
                drawmethod.flag = 1;
                break;
            case CMD_MODEL_NODRAWMETHOD:
                //disable special effects
                drawmethod.flag = 0;
                break;

            // 2016-10-11
            // Caskey, Damon
            // Broken down attack commands.
            case CMD_MODEL_COLLISION_BLOCK_COST:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->guardcost = GET_INT_ARG(1);               
                
                break;

            case CMD_MODEL_COLLISION_BLOCK_PENETRATE:
                
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->no_block = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_COUNTER:
                
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->counterattack = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_DAMAGE_FORCE:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_force = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_DAMAGE_LAND_FORCE:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->damage_on_landing.attack_force = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_DAMAGE_LAND_MODE:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blast = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_DAMAGE_LETHAL_DISABLE:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->no_kill = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_DAMAGE_STEAL:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->steal = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_DAMAGE_TYPE:

				value = GET_ARG(1);

                if (stricmp(value, "blast") == 0)
                {
                    tempInt = ATK_BLAST;
                }
				else if (stricmp(value, "burn") == 0)
				{
                    tempInt = ATK_BURN;
                }
				else if(stricmp(value, "freeze") == 0)
				{
                    tempInt = ATK_FREEZE;
				}
				else if (stricmp(value, "shock") == 0)
				{
                    tempInt = ATK_SHOCK;
				}
				else if (stricmp(value, "steal") == 0)
				{
                    tempInt = ATK_STEAL;
				}
				else
				{
                    tempInt = GET_INT_ARG(1);
				}

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = tempInt;

                tempInt = 0;

                break;

            case CMD_MODEL_COLLISION_DAMAGE_RECURSIVE_FORCE:
          
                collision_attack_upsert_recursive_property(&temp_collision_head, temp_collision_index)->force = GET_INT_ARG(1);                    

                break;

            case CMD_MODEL_COLLISION_DAMAGE_RECURSIVE_INDEX:
                
                collision_attack_upsert_recursive_property(&temp_collision_head, temp_collision_index)->index = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_DAMAGE_RECURSIVE_MODE:

                /*
                * Caskey, Damon V.
                * 2021-08-24
                *
                * For legacy support of mode, we have to handle
                * integer values differently because like a bonehead,
                * I didn�t originally set them up with bitwise
                * logic in mind.
                */

                value = GET_ARG(1);                
                
                if (isNumeric(value))
                {
                    /*
                    * Numeric is legacy. Interpret the number as
                    * a list of modes.
                    */

                    tempInt = GET_INT_ARG(1);

                    tempInt  = recursive_damage_get_mode_setup_from_legacy_argument(tempInt);                    
                }
                else
                {
                    /*
                    * Toggle bits based on items provided in argument list.
                    */

                    tempInt = recursive_damage_get_mode_setup_from_arg_list(&arglist);
                }


                /* Send resulting bitwise integer to mode value. */
                collision_attack_upsert_recursive_property(&temp_collision_head, temp_collision_index)->mode = tempInt;
                    
                tempInt = 0;

                break;

			case CMD_MODEL_COLLISION_DAMAGE_RECURSIVE_TAG:
                
                collision_attack_upsert_recursive_property(&temp_collision_head, temp_collision_index)->meta_tag = GET_INT_ARG(1);

				break;

            case CMD_MODEL_COLLISION_DAMAGE_RECURSIVE_TYPE:

                value = GET_ARG(1);

                if (stricmp(value, "same") == 0)
                {
                    tempInt = ATK_NONE;
                }
                else if (stricmp(value, "blast") == 0)
                {
                    tempInt = ATK_BLAST;
                }
                else if (stricmp(value, "burn") == 0)
                {
                    tempInt = ATK_BURN;
                }
                else if (stricmp(value, "freeze") == 0)
                {
                    tempInt = ATK_FREEZE;
                }
                else if (stricmp(value, "shock") == 0)
                {
                    tempInt = ATK_SHOCK;
                }
                else if (stricmp(value, "steal") == 0)
                {
                    tempInt = ATK_STEAL;
                }
                else
                {
                    tempInt = GET_INT_ARG(1);
                }

                collision_attack_upsert_recursive_property(&temp_collision_head, temp_collision_index)->type = tempInt;

                tempInt = 0;

                break;

            case CMD_MODEL_COLLISION_DAMAGE_RECURSIVE_TIME_RATE:
                
                collision_attack_upsert_recursive_property(&temp_collision_head, temp_collision_index)->rate = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_DAMAGE_RECURSIVE_TIME_EXPIRE:
                
                collision_attack_upsert_recursive_property(&temp_collision_head, temp_collision_index)->time = GET_INT_ARG(1);
                
                break;

            case CMD_MODEL_COLLISION_REACTION_FALL_FORCE:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_drop = GET_INT_ARG(1);
                
                break;

            case CMD_MODEL_COLLISION_REACTION_FALL_VELOCITY_X:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->dropv.x = GET_FLOAT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_REACTION_FALL_VELOCITY_Y:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->dropv.y = GET_FLOAT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_REACTION_FALL_VELOCITY_Z:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->dropv.z = GET_FLOAT_ARG(1);

                break;

			case CMD_MODEL_COLLISION_REACTION_REPOSITION_DIRECTION:

                tempInt = direction_get_adjustment_from_argument(filename, command, GET_ARG(1));

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->force_direction = tempInt;

                tempInt = 0;
                				
				break;

            case CMD_MODEL_COLLISION_EFFECT_BLOCK_FLASH:

                value = GET_ARG(1);

                if(stricmp(value, "none") == 0 || value == 0)
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blockflash = MODEL_INDEX_NONE;
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blockflash = get_cached_model_index(value);
                }

                break;

            case CMD_MODEL_COLLISION_EFFECT_BLOCK_SOUND:

                value = GET_ARG(1);

                if(stricmp(value, "none") == 0)
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blocksound = SAMPLE_ID_NONE;
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blocksound = sound_load_sample(value, packfile, 1);
                }

                break;

            case CMD_MODEL_COLLISION_EFFECT_HIT_FLASH:

                value = GET_ARG(1);

                if(stricmp(value, "none") == 0 || value == 0)
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->hitflash = MODEL_INDEX_NONE;
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->hitflash = get_cached_model_index(value);
                }
                break;

            case CMD_MODEL_COLLISION_EFFECT_HIT_FLASH_DISABLE:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->no_flash = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_EFFECT_HIT_FLASH_LAYER_ADJUST:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->flash_layer_adjust = GET_INT_ARG(1);

                break;
           
            case CMD_MODEL_COLLISION_EFFECT_HIT_FLASH_LAYER_SOURCE:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->flash_layer_source = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_EFFECT_HIT_FLASH_Z_SOURCE:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->flash_z_source = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_EFFECT_HIT_SOUND:

                value = GET_ARG(1);

                if (stricmp(value, "none") == 0)
                {
                    tempInt = SAMPLE_ID_NONE;
                }
                else
                {
                    tempInt = sound_load_sample(value, packfile, 1);
                }

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->hitsound = tempInt;

                tempInt = 0;
                
                break;

            case CMD_MODEL_COLLISION_GROUND:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->otg = GET_INT_ARG(1);
                
                break;

            case CMD_MODEL_COLLISION_MAP_INDEX:

                /*
                * Translate text value into a pre-defined forcemap constant.
                * Wen applying a pre-defined forcemap, we�ll look at the model
                * and try to find its appropriate index. For example, if the
                * pre-defined BURN is used, forcemap will apply the model�s
                * designated burn. This allows use of effect maps without the
                * need to match all model palettes up (i.e. all having their
                * second palette a burn palette).
                *
                * If the creator provides a numeric value, pass it straight
                * through.
                */

                value = GET_ARG(1);
                if (stricmp(value, "none") == 0)
                {
                    tempInt = MAP_TYPE_NONE;
                }
                else if (stricmp(value, "burn") == 0)
                {
                    tempInt = MAP_TYPE_BURN;
                }
                else if (stricmp(value, "freeze") == 0)
                {
                    tempInt = MAP_TYPE_FREEZE;
                }
                else if (stricmp(value, "ko") == 0)
                {
                    tempInt = MAP_TYPE_SHOCK;
                }
                else if (stricmp(value, "shock") == 0)
                {
                    tempInt = MAP_TYPE_SHOCK;
                }
                else
                {
                    tempInt = GET_INT_ARG(1);
                }                

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->forcemap = tempInt;
                
                break;

            case CMD_MODEL_COLLISION_MAP_TIME:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->maptime = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_POSITION_X:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->x = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_POSITION_Y:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->y = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_REACTION_FREEZE_MODE:
                
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->freeze = GET_INT_ARG(1);
                
                break;

            case CMD_MODEL_COLLISION_REACTION_FREEZE_TIME:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->freezetime = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_REACTION_INVINCIBLE_TIME:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->next_hit_time = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_REACTION_REPOSITION_DISTANCE:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->grab_distance = GET_INT_ARG(1);
                                
                break;

            case CMD_MODEL_COLLISION_REACTION_REPOSITION_MODE:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->grab = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_REACTION_PAIN_SKIP:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->no_pain = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_REACTION_PAUSE_TIME:
                
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->pause_add = GET_INT_ARG(1);
                
                break;

            case CMD_MODEL_COLLISION_SEAL_COST:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->seal = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_SEAL_TIME:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->sealtime = GET_INT_ARG(1);

                break;
            
            case CMD_MODEL_COLLISION_SIZE_X:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->width = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_SIZE_Y:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->height = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_SIZE_Z_1:
            case CMD_MODEL_COLLISION_SIZE_Z_BACKGROUND:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->z_background = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_SIZE_Z_2:
            case CMD_MODEL_COLLISION_SIZE_Z_FOREGROUND:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->z_foreground = GET_INT_ARG(1);
                                
                break;

            case CMD_MODEL_COLLISION_STAYDOWN_RISE:
                
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->staydown.rise = GET_INT_ARG(1);
                                
                break;

            case CMD_MODEL_COLLISION_STAYDOWN_RISEATTACK:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->staydown.riseattack = GET_INT_ARG(1);

                break;

            case CMD_MODEL_COLLISION_TAG:
                
                // To do - Add tag system.
                
                break;

            case CMD_MODEL_COLLISION:
            case CMD_MODEL_COLLISION1:
            case CMD_MODEL_COLLISION2:
            case CMD_MODEL_COLLISION3:
            case CMD_MODEL_COLLISION4:
            case CMD_MODEL_COLLISION5:
            case CMD_MODEL_COLLISION6:
            case CMD_MODEL_COLLISION7:
            case CMD_MODEL_COLLISION8:
            case CMD_MODEL_COLLISION9:
            case CMD_MODEL_COLLISION10:
            case CMD_MODEL_SHOCK:
            case CMD_MODEL_BURN:
            case CMD_MODEL_STEAL:
            case CMD_MODEL_FREEZE:
            case CMD_MODEL_ITEMBOX:
            case CMD_MODEL_LOSE:
            case CMD_MODEL_COLLISION_ETC:

                // 2020-03-08, 

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index);

                value = GET_ARG(1);
                if (stricmp(value, "none") == 0)
                {
                    collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->x = 0;
                    collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->y = 0;
                    collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->width = 0;
                    collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->height = 0;

                    break;
                }
               
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->x = GET_INT_ARG(1);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->y = GET_INT_ARG(2);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->width = GET_INT_ARG(3);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->height = GET_INT_ARG(4);
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_force = GET_INT_ARG(5);
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_drop = GET_INT_ARG(6);
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->no_block = GET_INT_ARG(7);
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->no_flash = GET_INT_ARG(8);
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->pause_add = GET_INT_ARG(9);

                // -- Not a typo - legacy Z sets identical value to back/fore.
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->z_background = GET_INT_ARG(10);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->z_foreground = GET_INT_ARG(10);
               
                       

                switch(cmd)
                {
                case CMD_MODEL_COLLISION:
                case CMD_MODEL_COLLISION1:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_NORMAL;

                    break;

                case CMD_MODEL_COLLISION2:
                    
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_NORMAL2;
                    
                    break;

                case CMD_MODEL_COLLISION3:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_NORMAL3;
                                        
                    break;

                case CMD_MODEL_COLLISION4:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_NORMAL4;

                    break;

                case CMD_MODEL_COLLISION5:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_NORMAL5;

                    break;

                case CMD_MODEL_COLLISION6:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_NORMAL6;

                    break;

                case CMD_MODEL_COLLISION7:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_NORMAL7;

                    break;

                case CMD_MODEL_COLLISION8:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_NORMAL8;

                    break;

                case CMD_MODEL_COLLISION9:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_NORMAL9;

                    break;

                case CMD_MODEL_COLLISION10:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_NORMAL10;
                                        
                    break;

                case CMD_MODEL_SHOCK:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_SHOCK;

                    break;

                case CMD_MODEL_BURN:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_BURN;

                    break;

                case CMD_MODEL_STEAL:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->steal = 1;
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_STEAL;

                    break;

                case CMD_MODEL_FREEZE:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_FREEZE;
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->freeze = 1;
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->freezetime = GET_FLOAT_ARG(6) * GAME_SPEED;
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->forcemap = MAP_TYPE_FREEZE;
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_drop = 0;

                    break;

                case CMD_MODEL_ITEMBOX:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_ITEM;

                    break;

                case CMD_MODEL_LOSE:

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_LOSE;
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_drop = 0;

                    break;

                default:
                    tempInt = atoi(command + 6);
                    if(tempInt < MAX_ATKS - STA_ATKS + 1)
                    {
                        tempInt = MAX_ATKS - STA_ATKS + 1;
                    }

                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = tempInt + STA_ATKS - 1;
                }
                break;

                

            case CMD_MODEL_HITWALLTYPE:
                value = GET_ARG(1);
                newchar->hitwalltype = atoi(value);
                break;
            case CMD_MODEL_COLLISIONZ:
            case CMD_MODEL_HITZ:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->z_background = GET_INT_ARG(1);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->z_foreground = GET_INT_ARG(2);

                break;

            case CMD_MODEL_BLAST:

                // 2020-03-08, 

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->x = GET_INT_ARG(1);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->y = GET_INT_ARG(2);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->width = GET_INT_ARG(3);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->height = GET_INT_ARG(4);
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_force = GET_INT_ARG(5);
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->no_block = GET_INT_ARG(6);
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->no_flash = GET_INT_ARG(7);
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->pause_add = GET_INT_ARG(8);

                // -- Not a typo - legacy Z sets identical value to back/fore.
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->z_background = GET_INT_ARG(9);
                collision_attack_upsert_coordinates_property(&temp_collision_head, temp_collision_index)->z_foreground = GET_INT_ARG(9);

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_drop = 1;
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_type = ATK_BLAST;
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blast = 1;
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->dropv.x = default_model_dropv.x * 2.083f;
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->dropv.y = default_model_dropv.y;
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->dropv.z = default_model_dropv.z;
                
                break;

            case CMD_MODEL_DROPV:

                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->dropv.y = GET_FLOAT_ARG(1);
                    newchar->smartbomb->dropv.x = GET_FLOAT_ARG(2);
                    newchar->smartbomb->dropv.z = GET_FLOAT_ARG(3);
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->dropv.y = GET_FLOAT_ARG(1);
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->dropv.x = GET_FLOAT_ARG(2);
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->dropv.z = GET_FLOAT_ARG(3);
                }
                
                break;

            case CMD_MODEL_OTG:
                // Over The Ground hit.
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->otg = GET_INT_ARG(1);
                
                break;

            case CMD_MODEL_JUGGLECOST:
                
                // if cost >= opponents jugglepoints , we can juggle
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->jugglecost = GET_INT_ARG(1);
                
                break;

            case CMD_MODEL_GUARDCOST:
                
                // if cost >= opponents guardpoints , opponent will play guardcrush anim
                collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->guardcost = GET_INT_ARG(1);
                
                break;

            case CMD_MODEL_STUN:
                
                //Like Freeze, but no auto remap.
                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->freeze = 1;
                    newchar->smartbomb->freezetime = GET_FLOAT_ARG(1) * GAME_SPEED;
                    newchar->smartbomb->attack_drop = 0;
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->freeze = 1;
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->freezetime = GET_FLOAT_ARG(1) * GAME_SPEED;
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->attack_drop = 0;
                }
                                
                break;

            case CMD_MODEL_GRABIN:
                
                // fake grab distanse efffect, not link
                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->grab = GET_INT_ARG(1);
                    newchar->smartbomb->grab_distance = GET_FLOAT_ARG(2);
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->grab = GET_INT_ARG(1);
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->grab_distance = GET_FLOAT_ARG(2);
                }

                break;

            case CMD_MODEL_NOREFLECT:
                
                // only cost target's hp, don't knock down or cause pain, unless the target is killed
                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->no_pain = GET_INT_ARG(1);
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->no_pain = GET_INT_ARG(1);
                }

                break;
                                
            case CMD_MODEL_NOKILL:
                
                // don't kill the target, leave 1 hp
                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->no_kill = GET_INT_ARG(1);
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->no_kill = GET_INT_ARG(1);
                }
                
                break;

            case CMD_MODEL_FORCEDIRECTION:
                
                tempInt = direction_get_adjustment_from_argument(filename, command, GET_ARG(1));

                // the attack direction
                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->force_direction = tempInt;
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->force_direction = tempInt;
                }               

                tempInt = 0;

                break;

            case CMD_MODEL_DAMAGEONLANDING:
                
                // fake throw damage on landing
                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->damage_on_landing.attack_force = GET_INT_ARG(1);
                    newchar->smartbomb->blast = GET_INT_ARG(2);
                    newchar->smartbomb->damage_on_landing.attack_type = translate_attack_type(GET_ARG(3));
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->damage_on_landing.attack_force = GET_INT_ARG(1);
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->blast = GET_INT_ARG(2);
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->damage_on_landing.attack_type = translate_attack_type(GET_ARG(3));
                }
                                
                break;

            case CMD_MODEL_SEAL:
                
                // Disable special moves for specified time.
                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->sealtime = GET_INT_ARG(1) * GAME_SPEED;
                    newchar->smartbomb->seal = GET_INT_ARG(2);
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->sealtime = GET_INT_ARG(1) * GAME_SPEED;
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->seal = GET_INT_ARG(2);
                }
                              
                break;

            case CMD_MODEL_STAYDOWN:
                
                // Disable special moves for specified time.
                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->staydown.rise = GET_INT_ARG(1);
                    newchar->smartbomb->staydown.riseattack = GET_INT_ARG(2);
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->staydown.rise = GET_INT_ARG(1);
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->staydown.riseattack = GET_INT_ARG(2);
                }

                break;

            case CMD_MODEL_DOT:

                collision_attack_upsert_property(&temp_collision_head, temp_collision_index);

                collision_attack_upsert_recursive_property(&temp_collision_head, temp_collision_index)->index  = GET_INT_ARG(1);  //Index.
                collision_attack_upsert_recursive_property(&temp_collision_head, temp_collision_index)->time   = GET_INT_ARG(2);  //Time to expiration.
                		
                /*
                * Caskey, Damon V.
                * 2021-08-24
                *
                * For legacy support of mode, we have to handle
                * integer values differently because like a bonehead,
                * I didn�t originally set them up with bitwise
                * logic in mind.
                */

                value = GET_ARG(1);

                if (isNumeric(value))
                {
                    /*
                    * Numeric is legacy. Interpret the number as
                    * a list of modes.
                    */

                    tempInt = GET_INT_ARG(1);

                    tempInt = recursive_damage_get_mode_setup_from_legacy_argument(tempInt);
                }
                else
                {
                    /*
                    * Toggle bits based on items provided in argument list.
                    */

                    tempInt = recursive_damage_get_mode_setup_from_arg_list(&arglist);
                }

                /* Send resulting bitwise integer to mode value. */
                collision_attack_upsert_recursive_property(&temp_collision_head, temp_collision_index)->mode = tempInt;

                tempInt = 0;

                break;

            case CMD_MODEL_FORCEMAP:
                
                /*
                * Translate text value into a pre-defined forcemap constant.
                * Wen applying a pre-defined forcemap, we�ll look at the model
                * and try to find its appropriate index. For example, if the
                * pre-defined BURN is used, forcemap will apply the model�s
                * designated burn. This allows use of effect maps without the
                * need to match all model palettes up (i.e. all having their
                * second palette a burn palette).
                *
                * If the creator provides a numeric value, pass it straight
                * through.
                */

                value = GET_ARG(1);

                if (stricmp(value, "none") == 0)
                {
                    tempInt = MAP_TYPE_NONE;
                }
                else if (stricmp(value, "burn") == 0)
                {
                    tempInt = MAP_TYPE_BURN;
                }
                else if (stricmp(value, "freeze") == 0)
                {
                    tempInt = MAP_TYPE_FREEZE;
                }
                else if (stricmp(value, "ko") == 0)
                {
                    tempInt = MAP_TYPE_SHOCK;
                }
                else if (stricmp(value, "shock") == 0)
                {
                    tempInt = MAP_TYPE_SHOCK;
                }
                else
                {
                    tempInt = GET_INT_ARG(1);
                }           

                // force color map change for specified time
                if (!newanim && newchar->smartbomb)
                {
                    newchar->smartbomb->forcemap = tempInt;
                    newchar->smartbomb->maptime = GET_FLOAT_ARG(2) * GAME_SPEED;
                }
                else
                {
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->forcemap = tempInt;
                    collision_attack_upsert_property(&temp_collision_head, temp_collision_index)->maptime = GET_FLOAT_ARG(2) * GAME_SPEED;
                }

                break;

            case CMD_MODEL_IDLE:
                idle = GET_INT_ARG(1);
                break;
            case CMD_MODEL_SETA:
                move.base = GET_INT_ARG(1);
                break;
            case CMD_MODEL_MOVE:
                move.axis.x = GET_INT_ARG(1);
                break;
            case CMD_MODEL_MOVEA:
                move.axis.y = GET_INT_ARG(1);
                break;
            case CMD_MODEL_MOVEZ:
                move.axis.z = GET_INT_ARG(1);
                break;
            case CMD_MODEL_FSHADOW:

                value = GET_ARG(1);
                if (stricmp(value, "none") == 0)
                {
                    tempInt = FRAME_SHADOW_NONE;
                }
                else
                {
                    tempInt = GET_INT_ARG(1);
                }
                frameshadow = tempInt;
                break;
            case CMD_MODEL_RANGE:
                if(!newanim)
                {
                    shutdownmessage = "Cannot set range: no animation!";
                    goto lCleanup;
                }
                newanim->range.x.min = GET_INT_ARG(1);
                newanim->range.x.max = GET_INT_ARG(2);
                if(newanim->range.x.min == newanim->range.x.max)
                {
                    newanim->range.x.min--;
                }
                break;
            case CMD_MODEL_RANGEZ:
                if(!newanim)
                {
                    shutdownmessage = "Cannot set rangez: no animation!";
                    goto lCleanup;
                }
                newanim->range.z.min = GET_INT_ARG(1);
                newanim->range.z.max = GET_INT_ARG(2);
                break;
            case CMD_MODEL_RANGEA:
                if(!newanim)
                {
                    shutdownmessage = "Cannot set rangea: no animation!";
                    goto lCleanup;
                }
                newanim->range.y.min = GET_INT_ARG(1);
                newanim->range.y.max = GET_INT_ARG(2);
                break;
            case CMD_MODEL_RANGEB:
                if(!newanim)
                {
                    shutdownmessage = "Cannot set rangeb: no animation!";
                    goto lCleanup;
                }
                newanim->range.base.min = GET_INT_ARG(1);
                newanim->range.base.max = GET_INT_ARG(2);
                break;
            case CMD_MODEL_PATHFINDSTEP:
                newchar->pathfindstep = GET_FLOAT_ARG(1);
                break;
            case CMD_MODEL_FRAME:
            {
                // Command title for log. Details will be added blow accordingly.
                //printf("\t\t\tFrame: ");

                if(!newanim)
                {
                    shutdownmessage = "Cannot add frame: animation not specified!";
                    goto lCleanup;
                }                

                peek = 0;
                if(frameset && framecount >= 0)
                {
                    framecount = -framecount;
                }
                while(!frameset)
                {
                    value3 = findarg(buf + pos + peek, 0);
                    if(stricmp(value3, "frame") == 0)
                    {
                        framecount++;
                    }
                    if((stricmp(value3, "anim") == 0) || (pos + peek >= size))
                    {
                        frameset = 1;
                    }
                    // Go to next line
                    while(buf[pos + peek] && buf[pos + peek] != '\n' && buf[pos + peek] != '\r')
                    {
                        ++peek;
                    }
                    while(buf[pos + peek] == '\n' || buf[pos + peek] == '\r')
                    {
                        ++peek;
                    }
                }
                value = GET_ARG(1);

                // Log info.
                //printf("%d", ++framenum);
                //printf("\tSprite Path: %s\n", value);

                index = stricmp(value, "none") == 0 ? -1 : loadsprite(value, offset.x, offset.y, nopalette ? PIXEL_x8 : PIXEL_8); //don't use palette for the sprite since it will one palette from the entity's remap list in 24bit mode
                if(index >= 0)
                {
                    // If the model does not have a designated palette
                    // yet and the author did not specify palette none
                    // or global palette, then we will use this frame's
                    // sprite to load a color table. Effectively the first
                    // frame of a model becomes its palette base.
                    if(pixelformat == PIXEL_x8 && !nopalette)
                    {
                        // No master color table assigned yet?
                        if(newchar->palette == NULL)
                        {
                            //printf("\t\t\tAuto Palette - 'Palette' not defined. Attempting to load color table from this frame: ");

                            // Allocate memory for color table.
                            newchar->palette = malloc(PAL_BYTES);
                            //
                            if(loadimagepalette(value, packfile, newchar->palette) == 0)
                            {
                                //printf("\t\t\t%s%s\n", "Failed to load color table from image: ", value);
                                goto lCleanup;
                            }
                        }

                        //printf("\t\t\t%s\n", "Success. Loaded color selection 0 from frame.");

                        // Assign the color table to sprite.
                        if(!nopalette)
                        {
                            sprite_map[index].node->sprite->palette = newchar->palette;
                            sprite_map[index].node->sprite->pixelformat = pixelformat;
                        }
                    }

                    if(maskindex >= 0)
                    {
                        sprite_map[index].node->sprite->mask = sprite_map[maskindex].node->sprite;
                        maskindex = -1;
                    }
                }
                
                entity_coords.x      = ebox.x - offset.x;
                entity_coords.y      = ebox.y - offset.y;
                entity_coords.width  = ebox.width + entity_coords.x;
                entity_coords.height = ebox.height + entity_coords.y;
                entity_coords.z_background     = ebox.z_background;
                entity_coords.z_foreground     = ebox.z_foreground;                                
               
                if(platform[PLATFORM_X] == PLATFORM_DEFAULT_X) // old style
                {
                    platform_con[PLATFORM_X] = 0;
                    platform_con[PLATFORM_Z] = 3;
                    platform_con[PLATFORM_UPPERLEFT] = platform[PLATFORM_UPPERLEFT] - offset.x;
                    platform_con[PLATFORM_LOWERLEFT] = platform[PLATFORM_LOWERLEFT] - offset.x;
                    platform_con[PLATFORM_UPPERRIGHT] = platform[PLATFORM_UPPERRIGHT] - offset.x;
                    platform_con[PLATFORM_LOWERRIGHT] = platform[PLATFORM_LOWERRIGHT] - offset.x;
                    platform_con[PLATFORM_DEPTH] = platform[PLATFORM_DEPTH] + 3;
                }
                else // wall style
                {
                    platform_con[PLATFORM_X] = platform[PLATFORM_X] - offset.x;
                    platform_con[PLATFORM_Z] = platform[PLATFORM_Z] - offset.y;
                    platform_con[PLATFORM_UPPERLEFT] = platform[PLATFORM_UPPERLEFT];
                    platform_con[PLATFORM_LOWERLEFT] = platform[PLATFORM_LOWERLEFT];
                    platform_con[PLATFORM_UPPERRIGHT] = platform[PLATFORM_UPPERRIGHT];
                    platform_con[PLATFORM_LOWERRIGHT] = platform[PLATFORM_LOWERRIGHT];
                    platform_con[PLATFORM_DEPTH] = platform[PLATFORM_DEPTH];
                }
                platform_con[PLATFORM_HEIGHT] = platform[PLATFORM_HEIGHT];
                if(shadow_set)
                {
                    shadow_coords[0] = shadow_xz[0] - offset.x;
                    shadow_coords[1] = shadow_xz[1] - offset.y;
                }
                else
                {
                    shadow_coords[0] = shadow_coords[1] = 0;
                }

                if(drawmethod.flag)
                {
                    dm = drawmethod;
                    if(dm.clipw)
                    {
                        dm.clipx -= offset.x;
                        dm.clipy -= offset.y;
                    }
                }
                else
                {
                    dm.flag = 0;
                }

                add_frame_data.animation = newanim;
                add_frame_data.spriteindex = index;
                add_frame_data.framecount = framecount;
                add_frame_data.delay = delay;
                add_frame_data.idle = idle;
                add_frame_data.ebox = &ebox_con;
                add_frame_data.move = &move;
                add_frame_data.platform = platform_con;
                add_frame_data.frameshadow = frameshadow;
                add_frame_data.shadow_coords = shadow_coords;
                add_frame_data.soundtoplay = soundtoplay;
                add_frame_data.drawmethod = &dm;
                add_frame_data.offset = &offset;
                add_frame_data.entity_coords = &entity_coords;
                
                add_frame_data.model = newchar;                

                /*
                * Delete nodes from frame object lists
                * that don't have valid data (i.e no 
                * coordinates defined at all or the 
                * coordinates X/Y/H/W are all 0).
                * 
                * 2023-01-09 - Disabled. Clone functions now
                * verify coordinates before copying temp 
                * to frame. This is to preserve legacy behavior
                * of keeping the read in values frame to frame
                * even after a collision box is closed.
                */
                //collision_attack_remove_undefined_coordinates(&temp_collision_head);
                //collision_body_remove_undefined_coordinates(&temp_collision_body_head);
                                
                /*
                * Multiple per frame object heads may have 
                * changed, so this needs to be right before 
                * addframe function.
                */
                add_frame_data.collision = temp_collision_head;
                add_frame_data.collision_body = temp_collision_body_head;
                add_frame_data.child_spawn = temp_child_spawn_head;

                /* 
                * Send data to frame function so it
                * can build a new animation frame.
                */
                curframe = addframe(&add_frame_data);
                
                /* 
                * Clear any data tha we don't want
                * retained for the next frame. Also 
                * reset temp indexes.
                */
                                
                temp_collision_index = 0;

                child_spawn_free_list(temp_child_spawn_head);
                temp_child_spawn_head = NULL;
                temp_child_spawn_index = 0;

                soundtoplay = SAMPLE_ID_NONE;
                frm_id = -1;
            }
            break;
            case CMD_MODEL_ALPHAMASK:
                if(!newanim)
                {
                    shutdownmessage = "Cannot add alpha mask: animation not specified!";
                    goto lCleanup;
                }
                if(maskindex >= 0)
                {
                    shutdownmessage = "Cannot add alpha mask: a mask has already been specified for this frame!";
                    goto lCleanup;
                }
                value = GET_ARG(1);
                //printf("frame count: %d\n",framecount);
                //printf("Load sprite '%s'...\n", value);
                index = loadsprite(value, offset.x, offset.y, PIXEL_8); //don't use palette for the mask
                maskindex = index;
                break;
            case CMD_MODEL_FLIPFRAME:
                newanim->flipframe = GET_FRAME_ARG(1);
                break;
            case CMD_MODEL_FOLLOWANIM:
                newanim->followup.animation = GET_INT_ARG(1);
                if(newanim->followup.animation > max_follows)
                {
                    newanim->followup.animation = max_follows;
                }
                break;
            case CMD_MODEL_FOLLOWCOND:
				
				// 2019-11-24
				// Caskey, Damon V.
				//
				// Follow up condition is now handled by bitwise logic.
				// We need to set up our bit flags based on the old int 
				// arg for backward compatability.
				
				// Get legacy style condition arg.
				tempInt = GET_INT_ARG(1);
				
				// Make sure condition is reset.
				newanim->followup.condition = FOLLOW_CONDITION_NONE;

				switch (tempInt)
				{
				default:
				case FOLLOW_CONDITION_CMD_READ_ALWAYS:
					newanim->followup.condition |= FOLLOW_CONDITION_ANY;
					break;
				case FOLLOW_CONDITION_CMD_READ_HOSTILE:
					newanim->followup.condition |= FOLLOW_CONDITION_HOSTILE_TARGET_TRUE;
					break;
				case FOLLOW_CONDITION_CMD_READ_HOSTILE_NOKILL_NOBLOCK:
					newanim->followup.condition |= FOLLOW_CONDITION_HOSTILE_TARGET_TRUE;
					newanim->followup.condition |= FOLLOW_CONDITION_BLOCK_FALSE;
					newanim->followup.condition |= FOLLOW_CONDITION_LETHAL_FALSE;
					break;
				case FOLLOW_CONDITION_CMD_READ_HOSTILE_NOKILL_NOBLOCK_NOGRAB:
					newanim->followup.condition |= FOLLOW_CONDITION_HOSTILE_TARGET_TRUE;
					newanim->followup.condition |= FOLLOW_CONDITION_BLOCK_FALSE;
					newanim->followup.condition |= FOLLOW_CONDITION_GRAB_TRUE;
					newanim->followup.condition |= FOLLOW_CONDITION_LETHAL_FALSE;
					break;
				case FOLLOW_CONDITION_CMD_READ_HOSTILE_NOKILL_BLOCK:
					newanim->followup.condition |= FOLLOW_CONDITION_HOSTILE_TARGET_TRUE;
					newanim->followup.condition |= FOLLOW_CONDITION_BLOCK_TRUE;
					newanim->followup.condition |= FOLLOW_CONDITION_LETHAL_FALSE;
					break;
				}				
                
            case CMD_MODEL_COUNTERRANGE:
                newanim->counter_action.frame.min    = GET_FRAME_ARG(1);
                newanim->counter_action.frame.max    = GET_FRAME_ARG(2);
				
				// 2019-12-04
				// Caskey, Damon V.
				//
				// Counter action condition is now handled by bitwise logic.
				// We need to set up our bit flags based on the old int 
				// arg for backward compatability.

				// Get legacy style condition arg.
				tempInt = GET_INT_ARG(3);

				// Make sure condition is reset.
				newanim->counter_action.condition = COUNTER_ACTION_CONDITION_NONE;

				switch (tempInt)
				{
				default:
				case COUNTER_ACTION_CONDITION_CMD_READ_ALWAYS:
					newanim->counter_action.condition |= COUNTER_ACTION_CONDITION_DAMAGE_LETHAL_FALSE;
					break;
				case COUNTER_ACTION_CONDITION_CMD_READ_HOSTILE:
					newanim->counter_action.condition |= COUNTER_ACTION_CONDITION_DAMAGE_LETHAL_FALSE;
					newanim->counter_action.condition |= COUNTER_ACTION_CONDITION_HOSTILE_TARGET_TRUE;
					break;
				case COUNTER_ACTION_CONDITION_CMD_READ_HOSTILE_FRONT_NOFREEZE:
					newanim->counter_action.condition |= COUNTER_ACTION_CONDITION_BACK_TRUE;
					newanim->counter_action.condition |= COUNTER_ACTION_CONDITION_BLOCK_TRUE;
					newanim->counter_action.condition |= COUNTER_ACTION_CONDITION_DAMAGE_LETHAL_FALSE;
					newanim->counter_action.condition |= COUNTER_ACTION_CONDITION_FREEZE_FALSE;
					newanim->counter_action.condition |= COUNTER_ACTION_CONDITION_HOSTILE_TARGET_TRUE;
					break;
				case COUNTER_ACTION_CONDITION_CMD_READ_ALWAYS_RAGE:
					newanim->counter_action.condition ^= FOLLOW_CONDITION_ANY;
					break;
				}

                newanim->counter_action.damaged      = GET_INT_ARG(4);
                break;
            case CMD_MODEL_WEAPONFRAME:
                if(!newanim->weaponframe)
                {
                    newanim->weaponframe = malloc(sizeof(*newanim->weaponframe) * 3);
                }
                newanim->weaponframe[0] = GET_FRAME_ARG(1);
                newanim->weaponframe[1] = GET_INT_ARG(2);
                newanim->weaponframe[2] = GET_INT_ARG(3);
                break;
            case CMD_MODEL_QUAKEFRAME:
                newanim->quakeframe.framestart  = GET_FRAME_ARG(1);
                newanim->quakeframe.repeat      = GET_INT_ARG(2);
                newanim->quakeframe.v           = GET_INT_ARG(3);
                newanim->quakeframe.cnt         = 0;
                break;
            case CMD_MODEL_SUBENTITY:
            case CMD_MODEL_CUSTENTITY:
                value = GET_ARG(1);
                if(value[0])
                {
                    newanim->sub_entity_model_index = get_cached_model_index(value);
                }
                break;
            case CMD_MODEL_SPAWNFRAME:
                
				newanim->sub_entity_spawn = allocate_sub_entity();

				newanim->sub_entity_spawn->frame = GET_FRAME_ARG(1);
				newanim->sub_entity_spawn->position.x = GET_FLOAT_ARG(2);
				newanim->sub_entity_spawn->position.z = GET_FLOAT_ARG(3);
				newanim->sub_entity_spawn->position.y = GET_FLOAT_ARG(4);
				newanim->sub_entity_spawn->placement = GET_INT_ARG(5);
                break;

            case CMD_MODEL_SUMMONFRAME:
				
				newanim->sub_entity_summon = allocate_sub_entity();

				newanim->sub_entity_summon->frame = GET_FRAME_ARG(1);
				newanim->sub_entity_summon->position.x = GET_FLOAT_ARG(2);
				newanim->sub_entity_summon->position.z = GET_FLOAT_ARG(3);
				newanim->sub_entity_summon->position.y = GET_FLOAT_ARG(4);
				newanim->sub_entity_summon->placement = GET_INT_ARG(5);
                break;

            
            case CMD_MODEL_UNSUMMONFRAME:
                newanim->sub_entity_unsummon = GET_FRAME_ARG(1);
                break;
            case CMD_MODEL_NOHITHEAD:                               
                
                if (GET_INT_ARG(1))
                {
                    newchar->move_config_flags |= MOVE_CONFIG_NO_HIT_HEAD;
                }
                else
                {
                    newchar->move_config_flags &= ~MOVE_CONFIG_NO_HIT_HEAD;
                }

                break;
            case CMD_MODEL_AT_SCRIPT:
                if(!scriptbuf[0])  // if empty, paste the main function text here
                {
                    buffer_append(&scriptbuf, pre_text, 0xffffff, &sbsize, &scriptlen);
                }
                scriptbuf[scriptlen - strclen(sur_text)] = 0; // cut last chars
                scriptlen = strlen(scriptbuf);
                if(ani_id >= 0)
                {
                    if(script_id != ani_id)  // if expression 1
                    {
                        sprintf(namebuf, ifid_text, newanim->index);
                        buffer_append(&scriptbuf, namebuf, 0xffffff, &sbsize, &scriptlen);
                        script_id = ani_id;
                    }
                    scriptbuf[scriptlen - strclen(endifid_text)] = 0; // cut last chars
                    scriptlen = strlen(scriptbuf);
                }
                while(!starts_with(buf + pos, "@script"))
                {
                    pos++;
                }
                pos += strclen("@script");
                len = 0;
                while(!starts_with(buf + pos, "@end_script"))
                {
                    len++;
                    pos++;
                }
                buffer_append(&scriptbuf, buf + pos - len, len, &sbsize, &scriptlen);
                pos += strclen("@end_script");

                if(ani_id >= 0)
                {
                    buffer_append(&scriptbuf, endifid_text, 0xffffff, &sbsize, &scriptlen);// put back last  chars
                }
                buffer_append(&scriptbuf, sur_text, 0xffffff, &sbsize, &scriptlen);// put back last  chars
                break;
            case CMD_MODEL_AT_CMD:
                //translate @cmd into script function call
                if(ani_id < 0)
                {
                    shutdownmessage = "command '@cmd' must follow an animation!";
                    goto lCleanup;
                }
                if(!scriptbuf[0])  // if empty, paste the main function text here
                {
                    buffer_append(&scriptbuf, pre_text, 0xffffff, &sbsize, &scriptlen);
                }
                scriptbuf[scriptlen - strclen(sur_text)] = 0; // cut last chars
                scriptlen = strlen(scriptbuf);
                if(script_id != ani_id)  // if expression 1
                {
                    sprintf(namebuf, ifid_text, newanim->index);
                    buffer_append(&scriptbuf, namebuf, 0xffffff, &sbsize, &scriptlen);
                    script_id = ani_id;
                }
                j = 1;
                value = GET_ARG(j);
                scriptbuf[scriptlen - strclen(endifid_text)] = 0; // cut last chars
                scriptlen = strlen(scriptbuf);
                if(value && value[0])
                {
                    /*
                     //no_cmd_compatible will try to optimize if(frame==n)
                     //which means merging extra if statements within the same frame
                     //some old mod will have problem if this is enabled, however.
                     //
                     //     @cmd f
                     //     @cmd f
                     //     frame
                     //
                     //   When no_cmd_compatible is 1
                     //
                     //   if(frame==n) {
                     //       f();
                     //       f();
                     //       return;
                     //    }
                     //
                     //    When no_cmd_compatible is 0
                     //
                     //   if(frame==n) {
                     //       f();
                     //    }
                     //   if(frame==n) {
                     //       f();
                     //    }
                     */
                    if(!no_cmd_compatible || frm_id != curframe)
                    {
                        sprintf(namebuf, if_text, curframe);//only execute in current frame
                        buffer_append(&scriptbuf, namebuf, 0xffffff, &sbsize, &scriptlen);
                        frm_id = curframe;
                    }
                    else //no_cmd_compatible==1
                    {
                        scriptbuf[scriptlen - strclen(endif_text)] = 0; // cut last chars
                        scriptlen = strlen(scriptbuf);
                        scriptbuf[scriptlen - strclen(endif_return_text)] = 0; // cut last chars
                        scriptlen = strlen(scriptbuf);
                    }
                    sprintf(namebuf, call_text, value);
                    buffer_append(&scriptbuf, namebuf, 0xffffff, &sbsize, &scriptlen);

                    do  //argument and comma
                    {
                        j++;
                        value = GET_ARG(j);
                        if(value && value[0])
                        {
                            if(j != 2)
                            {
                                buffer_append(&scriptbuf, comma_text, 0xffffff, &sbsize, &scriptlen);
                            }
                            buffer_append(&scriptbuf, value, 0xffffff, &sbsize, &scriptlen);
                        }
                    }
                    while(value && value[0]);
                }

                buffer_append(&scriptbuf, endcall_text, 0xffffff, &sbsize, &scriptlen);
                if(no_cmd_compatible)
                {
                    buffer_append(&scriptbuf, endif_return_text, 0xffffff, &sbsize, &scriptlen);    //return
                }
                buffer_append(&scriptbuf, endif_text, 0xffffff, &sbsize, &scriptlen);//end of if
                buffer_append(&scriptbuf, endifid_text, 0xffffff, &sbsize, &scriptlen); // put back last  chars
                buffer_append(&scriptbuf, sur_text, 0xffffff, &sbsize, &scriptlen); // put back last  chars
                break;
            default:
                if(command && command[0])
                {
                    if(!handle_txt_include(command, &arglist, &filename, fnbuf, &buf, &pos, &size))
                    {
                        printf("Command '%s' not understood in file '%s'!\n", command, filename);
                    }
                }
            }

        }
        // Go to next line
        pos += getNewLineStart(buf + pos);
    }


    tempInt = 1;

    if(scriptbuf && animscriptbuf && scriptbuf[0] && animscriptbuf[0])
    {
        writeToScriptLog("\n#### animationscript function main #####\n# ");
        writeToScriptLog(filename);
        writeToScriptLog("\n########################################\n");
        writeToScriptLog(scriptbuf);

        lcmScriptDeleteMain(&scriptbuf);
        lcmScriptAddMain(&animscriptbuf);
        lcmScriptJoinMain(&animscriptbuf,scriptbuf);

        if(!Script_IsInitialized(newchar->scripts->animation_script))
        {
            Script_Init(newchar->scripts->animation_script, newchar->name, filename, 0);
        }
        tempInt = Script_AppendText(newchar->scripts->animation_script, animscriptbuf, filename);
    }
    else if(animscriptbuf && animscriptbuf[0])
    {
        lcmScriptAddMain(&animscriptbuf);

        if(!Script_IsInitialized(newchar->scripts->animation_script))
        {
            Script_Init(newchar->scripts->animation_script, newchar->name, filename, 0);
        }
        tempInt = Script_AppendText(newchar->scripts->animation_script, animscriptbuf, filename);
    }
    else if(scriptbuf && scriptbuf[0])
    {
        //printf("\n%s\n", scriptbuf);
        if(!Script_IsInitialized(newchar->scripts->animation_script))
        {
            Script_Init(newchar->scripts->animation_script, newchar->name, filename, 0);
        }
        tempInt = Script_AppendText(newchar->scripts->animation_script, scriptbuf, filename);
        //Interpreter_OutputPCode(newchar->scripts->animation_script.pinterpreter, "code");
        writeToScriptLog("\n#### animationscript function main #####\n# ");
        writeToScriptLog(filename);
        writeToScriptLog("\n########################################\n");
        writeToScriptLog(scriptbuf);
    }

    if(!newchar->isSubclassed)
    {
        Script_Compile(newchar->scripts->animation_script);
    }

    if(!tempInt)// parse script failed
    {
        shutdownmessage = "Error parsing function main of animation script in file '%s'!";
        goto lCleanup;
    }

    // We need a little more work to initialize the new A.I. types if they are not loaded from file
    if(newchar->aiattack == -1)
    {
        newchar->aiattack = 0;
    }
    if(newchar->aimove == AIMOVE1_NONE)
    {
        newchar->aimove = AIMOVE1_NORMAL;
    }
    //if(!newchar->offscreenkill) newchar->offscreenkill = 1000;

    //temporary patch for conflicting moves
    if(newchar->animation[ANI_FREESPECIAL] && !is_set(newchar, ANI_FREESPECIAL))
    {
        alloc_specials(newchar);
        newchar->special[newchar->specials_loaded].input[0] = FLAG_FORWARD;
        newchar->special[newchar->specials_loaded].input[1] = FLAG_FORWARD;
        newchar->special[newchar->specials_loaded].input[2] = FLAG_ATTACK;
        newchar->special[newchar->specials_loaded].anim = ANI_FREESPECIAL;
        newchar->special[newchar->specials_loaded].steps = 3;
        newchar->specials_loaded++;
    }
    if(newchar->animation[ANI_FREESPECIAL2] && !is_set(newchar, ANI_FREESPECIAL2))
    {
        alloc_specials(newchar);
        newchar->special[newchar->specials_loaded].input[0] = FLAG_MOVEDOWN;
        newchar->special[newchar->specials_loaded].input[1] = FLAG_MOVEDOWN;
        newchar->special[newchar->specials_loaded].input[2] = FLAG_ATTACK;
        newchar->special[newchar->specials_loaded].anim = ANI_FREESPECIAL2;
        newchar->special[newchar->specials_loaded].steps = 3;
        newchar->specials_loaded++;
    }
    if(newchar->animation[ANI_FREESPECIAL3] && !is_set(newchar, ANI_FREESPECIAL3))
    {
        alloc_specials(newchar);
        newchar->special[newchar->specials_loaded].input[0] = FLAG_MOVEUP;
        newchar->special[newchar->specials_loaded].input[1] = FLAG_MOVEUP;
        newchar->special[newchar->specials_loaded].input[2] = FLAG_ATTACK;
        newchar->special[newchar->specials_loaded].anim = ANI_FREESPECIAL3;
        newchar->special[newchar->specials_loaded].steps = 3;
        newchar->specials_loaded++;
    }

    if(newchar->risetime.rise == -1)
    {
        if(newchar->type == TYPE_PLAYER)
        {
            if(newchar->animation[ANI_RISEATTACK])
            {
                newchar->risetime.rise = GAME_SPEED / 2;
            }
            else
            {
                newchar->risetime.rise = GAME_SPEED;
            }
        }
        else if(newchar->type == TYPE_ENEMY || newchar->type == TYPE_NPC)
        {
            newchar->risetime.rise = 0;
        }
    }

    if(newchar->faction.type_hostile == TYPE_UNDELCARED) // not been initialized, so initialize it
    {
        switch (newchar->type)
        {
        default:
            //Do nothing.
            break;
        case TYPE_ENEMY:
            newchar->faction.type_hostile = TYPE_PLAYER;
            break;
        case TYPE_PLAYER: // dont really needed, since you don't need A.I. control for players
            newchar->faction.type_hostile = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
            break;
        case TYPE_TRAP:
            newchar->faction.type_hostile = TYPE_ENEMY | TYPE_PLAYER;
        case TYPE_OBSTACLE:
            newchar->faction.type_hostile = TYPE_UNDELCARED;
            break;
		case TYPE_PROJECTILE:
			// We want a clean slate so the projectile 
			// spawn functions will copy owner settings 
			// by default.
			newchar->faction.type_hostile = TYPE_UNDELCARED;
			break;
        case TYPE_SHOT:  // only target enemies
            newchar->faction.type_hostile = TYPE_ENEMY ;
            break;
        case TYPE_NPC: // default npc behivior
            newchar->faction.type_hostile = TYPE_ENEMY ;
            break;
        }
    }

    if(newchar->faction.type_damage_direct == TYPE_UNDELCARED) // not been initialized, so initialize it
    {
        switch (newchar->type)
        {
        default:
            //Do nothing.
            break;
        case TYPE_ENEMY:
            newchar->faction.type_damage_direct = TYPE_PLAYER | TYPE_SHOT;
            if(newchar->subtype == SUBTYPE_ARROW)
            {
                newchar->faction.type_damage_direct |= TYPE_OBSTACLE;
            }
            break;
        case TYPE_PLAYER:
            newchar->faction.type_damage_direct = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
            break;
        case TYPE_TRAP:
            newchar->faction.type_damage_direct = TYPE_ENEMY | TYPE_PLAYER | TYPE_OBSTACLE;
        case TYPE_OBSTACLE:
            newchar->faction.type_damage_direct = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
            break;
		case TYPE_PROJECTILE:
			// We want a clean slate so the projectile 
			// spawn functions will copy owner settings 
			// by default.
			newchar->faction.type_damage_direct = TYPE_UNDELCARED;
			break;
        case TYPE_SHOT:
            newchar->faction.type_damage_direct = TYPE_ENEMY | TYPE_PLAYER | TYPE_OBSTACLE;
            break;
        case TYPE_NPC:
            newchar->faction.type_damage_direct = TYPE_ENEMY | TYPE_OBSTACLE;
            break;
        case TYPE_ITEM:
            newchar->faction.type_damage_direct = TYPE_PLAYER;
            break;
        }
    }

    if(newchar->faction.type_damage_indirect == TYPE_UNDELCARED) // not been initialized, so initialize it
    {
        switch (newchar->type)
        {
        default:
            //Do nothing.
            break;
        case TYPE_ENEMY:
            newchar->faction.type_damage_indirect = TYPE_ENEMY | TYPE_OBSTACLE;
            break;
        case TYPE_PLAYER:
            newchar->faction.type_damage_indirect = TYPE_PLAYER | TYPE_OBSTACLE;
            break;
        case TYPE_TRAP: // hmm, don't really needed
            newchar->faction.type_damage_indirect = TYPE_ENEMY | TYPE_PLAYER | TYPE_OBSTACLE;
        case TYPE_OBSTACLE: // hmm, don't really needed
            newchar->faction.type_damage_indirect = TYPE_ENEMY | TYPE_PLAYER | TYPE_OBSTACLE;
            break;
		case TYPE_PROJECTILE:
			// We want a clean slate so the projectile 
			// spawn functions will copy owner settings 
			// by default.
			newchar->faction.type_damage_indirect = TYPE_UNDELCARED;
			break;
        case TYPE_SHOT: // hmm, don't really needed
            newchar->faction.type_damage_indirect = TYPE_ENEMY | TYPE_PLAYER | TYPE_OBSTACLE;
            break;
        case TYPE_NPC:
            newchar->faction.type_damage_indirect = TYPE_ENEMY | TYPE_PLAYER | TYPE_OBSTACLE;
            break;
        }
    }

    if(newchar->jumpspeed < 0)
    {
        newchar->jumpspeed = MAX(newchar->speed.x, 1);
    }

    if(blendfx_is_set == 0)
    {
        if(newchar->alpha)
        {
            blendfx[newchar->alpha - 1] = 1;
        }
        if(newchar->shadow_config_flags & SHADOW_CONFIG_GRAPHIC_ALL || newchar->shadow)
        {
            blendfx[BLEND_MULTIPLY] = 1;
        }
    }

    // we need to convert 8bit colourmap into 32bit palette
    if(pixelformat == PIXEL_x8)
    {
        convert_map_to_palette(newchar, mapflag);
    }

lCleanup:

    if(buf != NULL)
    {
        free(buf);
        buf = NULL;
    }
    if(scriptbuf)
    {
        free(scriptbuf);
        scriptbuf = NULL;
    }
    if(animscriptbuf)
    {
        free(animscriptbuf);
        animscriptbuf = NULL;
    }
    if(mapflag)
    {
        free(mapflag);
        mapflag = NULL;
    }

    if(!shutdownmessage)
    {
        printf(" ...done!\n");
        return newchar;
    }

    borShutdown(1, "Fatal Error in load_cached_model, file: %s, line %d, message: %s\n", filename, line, shutdownmessage);
    return NULL;

    #undef LOG_CMD_TITLE
}



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].anim == m)
        {
            return 1;
        }
    }

    return 0;
}

int load_script_setting()
{
    char *filename = "data/script.txt";
    char *buf, *command;
    ptrdiff_t pos = 0;
    size_t size = 0;
    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";

    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        return 0;
    }

    while(pos < size)
    {
        if(ParseArgs(&arglist, buf + pos, argbuf))
        {
            command = GET_ARG(0);
            if(command && command[0])
            {
                if(stricmp(command, "maxscriptvars") == 0) // each script can have a variable list that can be accessed by index
                {
                    max_script_vars = GET_INT_ARG(1) ;
                    if(max_script_vars < 0)
                    {
                        max_script_vars = 0;
                    }
                }
                else if(stricmp(command, "maxentityvars") == 0) // each entity can have a variable list that can be accessed by index
                {
                    max_entity_vars = GET_INT_ARG(1) ;
                    if(max_entity_vars < 0)
                    {
                        max_entity_vars = 0;
                    }
                }
                else if(stricmp(command, "maxindexedvars") == 0) // a global variable list that can be accessed by index
                {
                    max_indexed_vars = GET_INT_ARG(1);
                    if(max_indexed_vars < 0)
                    {
                        max_indexed_vars = 0;
                    }
                }
                else if(stricmp(command, "keyscriptrate") == 0) // Rate that keyscripts fire when holding a key.
                {
                    keyscriptrate = GET_INT_ARG(1);
                }
                else if(stricmp(command, "alwaysupdate") == 0) //execute update script whenever update() is called
                {
                    alwaysupdate = GET_INT_ARG(1);
                }
                else if(stricmp(command, "nonestedscript") == 0) // don't call a script if it is being executed
                {
                    no_nested_script = GET_INT_ARG(1);
                }
                else if(stricmp(command, "nocmdcompatible") == 0) // don't call a script if it is being executed
                {
                    no_cmd_compatible = GET_INT_ARG(1);
                }
            }
        }
        // Go to next line
        pos += getNewLineStart(buf + pos);
    }

    if(buf != NULL)
    {
        free(buf);
        buf = NULL;
    }
    return 1;
}

void load_model_constants()
{
    char filename[MAX_BUFFER_LEN] = "data/models.txt";
    int i;
    char *buf;
    size_t size;
    ptrdiff_t pos;
    char *command;
    int line = 0;
    int maxanim = MAX_ANIS; // temporary counter
    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";
    modelstxtCommands cmd;

    // reload default values
    max_idles        = MAX_IDLES;
    max_walks        = MAX_WALKS;
    max_ups          = MAX_UPS;
    max_downs        = MAX_DOWNS;
    max_backwalks    = MAX_BACKWALKS;
    max_attack_types = MAX_ATKS;
    max_freespecials = MAX_SPECIALS;
    max_follows      = MAX_FOLLOWS;
    max_attacks      = MAX_ATTACKS;
    max_animations   = MAX_ANIS;

    max_collisons    = MAX_COLLISIONS;

    // free old values
    if(animspecials)
    {
        free(animspecials);
        animspecials = NULL;
    }
    if(animattacks)
    {
        free(animattacks);
        animattacks = NULL;
    }
    if(animfollows)
    {
        free(animfollows);
        animfollows = NULL;
    }
    if(animpains)
    {
        free(animpains);
        animpains = NULL;
    }
    if(animbackpains)
    {
        free(animbackpains);
        animbackpains = NULL;
    }
    if(animfalls)
    {
        free(animfalls);
        animfalls = NULL;
    }
    if(animbackfalls)
    {
        free(animbackfalls);
        animbackfalls = NULL;
    }
    if(animrises)
    {
        free(animrises);
        animrises = NULL;
    }
    if(animbackrises)
    {
        free(animbackrises);
        animbackrises = NULL;
    }
    if(animriseattacks)
    {
        free(animriseattacks);
        animriseattacks = NULL;
    }
    if(animbackriseattacks)
    {
        free(animbackriseattacks);
        animbackriseattacks = NULL;
    }
    if(animblkpains)
    {
        free(animblkpains);
        animblkpains = NULL;
    }
    if(animbackblkpains)
    {
        free(animbackblkpains);
        animbackblkpains = NULL;
    }
    if(animdies)
    {
        free(animdies);
        animdies = NULL;
    }
    if(animbackdies)
    {
        free(animbackdies);
        animbackdies = NULL;
    }
    if(animwalks)
    {
        free(animwalks);
        animwalks = NULL;
    }
    if(animbackwalks)
    {
        free(animbackwalks);
        animbackwalks = NULL;
    }
    if(animidles)
    {
        free(animidles);
        animidles = NULL;
    }
    if(animups)
    {
        free(animups);
        animups = NULL;
    }
    if(animdowns)
    {
        free(animdowns);
        animdowns = NULL;
    }

    // Read file
    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        borShutdown(1, "Error loading model list from %s", filename);
    }

    pos = 0;
    while(pos < size) // peek global settings
    {
        line++;
        if(ParseArgs(&arglist, buf + pos, argbuf))
        {
            command = GET_ARG(0);
            cmd = (modelstxtCommands)getModelCommand(modelstxtcmdlist, command);
            switch(cmd)
            {
            case CMD_MODELSTXT_MAX_COLLISIONS:
                // max collision_attack boxes.
                max_collisons = GET_INT_ARG(1);
                if(max_collisons < MAX_COLLISIONS)
                {
                    max_collisons = MAX_COLLISIONS;
                }
                break;
            case CMD_MODELSTXT_MAXIDLES:
                // max idle stances
                max_idles = GET_INT_ARG(1);
                if(max_idles < MAX_IDLES)
                {
                    max_idles = MAX_IDLES;
                }
                break;
            case CMD_MODELSTXT_MAXWALKS:
                max_walks = GET_INT_ARG(1);
                if(max_walks < MAX_WALKS)
                {
                    max_walks = MAX_WALKS;
                }
                break;
            case CMD_MODELSTXT_MAXBACKWALKS:
                // max backward walks
                max_backwalks = GET_INT_ARG(1);
                if(max_backwalks < MAX_BACKWALKS)
                {
                    max_backwalks = MAX_BACKWALKS;
                }
                break;
            case CMD_MODELSTXT_MAXUPS:
                // max up walks
                max_ups = GET_INT_ARG(1);
                if(max_ups < MAX_UPS)
                {
                    max_ups = MAX_UPS;
                }
                break;
            case CMD_MODELSTXT_MAXDOWNS:
                // max down walks
                max_downs = GET_INT_ARG(1);
                if(max_downs < MAX_DOWNS)
                {
                    max_downs = MAX_DOWNS;
                }
                break;
            case CMD_MODELSTXT_MAXATTACKTYPES:
                // max attacktype/pain/fall/die
                max_attack_types = GET_INT_ARG(1) + STA_ATKS;
                if(max_attack_types < MAX_ATKS)
                {
                    max_attack_types = MAX_ATKS;
                }
                break;
            case CMD_MODELSTXT_MAXFOLLOWS:
                // max follow-ups
                max_follows = GET_INT_ARG(1);
                if(max_follows < MAX_FOLLOWS)
                {
                    max_follows = MAX_FOLLOWS;
                }
                break;
            case CMD_MODELSTXT_MAXFREESPECIALS:
                // max freespecials
                max_freespecials = GET_INT_ARG(1);
                if(max_freespecials < MAX_SPECIALS)
                {
                    max_freespecials = MAX_SPECIALS;
                }
                break;
            case CMD_MODELSTXT_MAXATTACKS:
                max_attacks = GET_INT_ARG(1);
                if(max_attacks < MAX_ATTACKS)
                {
                    max_attacks = MAX_ATTACKS;
                }
                break;
            default:
                if(cmd >= CMD_MODELSTXT_THE_END)
                {
                    printf("command %s not understood in %s, line %d\n", command, filename, line);
                }
                break;
            }
        }

        // Go to next line
        pos += getNewLineStart(buf + pos);
    }

    // calculate max animations
    max_animations += (max_attack_types - MAX_ATKS) * 12 +// multply by 11: fall/die/pain/backpain/backfalls/backdies/rise/backrise/blockpain/backblockpain/riseattack/backriseattck
                      (max_follows - MAX_FOLLOWS) +
                      (max_freespecials - MAX_SPECIALS) +
                      (max_attacks - MAX_ATTACKS) +
                      (max_idles - MAX_IDLES) +
                      (max_walks - MAX_WALKS) +
                      (max_ups - MAX_UPS) +
                      (max_downs - MAX_DOWNS) +
                      (max_backwalks - MAX_BACKWALKS);

    // alloc indexed animation ids
    animdowns = malloc(sizeof(*animdowns) * max_downs);
    animups = malloc(sizeof(*animups) * max_ups);
    animbackwalks = malloc(sizeof(*animbackwalks) * max_backwalks);
    animwalks = malloc(sizeof(*animwalks) * max_walks);
    animidles = malloc(sizeof(*animidles) * max_idles);
    animpains = malloc(sizeof(*animpains) * max_attack_types);
    animbackpains = malloc(sizeof(*animbackpains) * max_attack_types);
    animdies = malloc(sizeof(*animdies) * max_attack_types);
    animbackdies = malloc(sizeof(*animbackdies) * max_attack_types);
    animfalls = malloc(sizeof(*animfalls) * max_attack_types);
    animbackfalls = malloc(sizeof(*animbackfalls) * max_attack_types);
    animrises = malloc(sizeof(*animrises) * max_attack_types);
    animbackrises = malloc(sizeof(*animbackrises) * max_attack_types);
    animriseattacks = malloc(sizeof(*animriseattacks) * max_attack_types);
    animbackriseattacks = malloc(sizeof(*animbackriseattacks) * max_attack_types);
    animblkpains = malloc(sizeof(*animblkpains) * max_attack_types);
    animbackblkpains = malloc(sizeof(*animbackblkpains) * max_attack_types);
    animattacks = malloc(sizeof(*animattacks) * max_attacks);
    animfollows = malloc(sizeof(*animfollows) * max_follows);
    animspecials = malloc(sizeof(*animspecials) * max_freespecials);

    // copy default values and new animation ids
    memcpy(animdowns, downs, sizeof(*animdowns)*MAX_DOWNS);
    for(i = MAX_DOWNS; i < max_downs; i++)
    {
        animdowns[i] = maxanim++;
    }
    memcpy(animups, ups, sizeof(*animups)*MAX_UPS);
    for(i = MAX_UPS; i < max_ups; i++)
    {
        animups[i] = maxanim++;
    }
    memcpy(animbackwalks, backwalks, sizeof(*animbackwalks)*MAX_BACKWALKS);
    for(i = MAX_BACKWALKS; i < max_backwalks; i++)
    {
        animbackwalks[i] = maxanim++;
    }
    memcpy(animwalks, walks, sizeof(*animwalks)*MAX_WALKS);
    for(i = MAX_WALKS; i < max_walks; i++)
    {
        animwalks[i] = maxanim++;
    }
    memcpy(animidles, idles, sizeof(*animidles)*MAX_IDLES);
    for(i = MAX_IDLES; i < max_idles; i++)
    {
        animidles[i] = maxanim++;
    }
    memcpy(animspecials, freespecials,   sizeof(*animspecials)*MAX_SPECIALS);
    for(i = MAX_SPECIALS; i < max_freespecials; i++)
    {
        animspecials[i] = maxanim++;
    }
    memcpy(animattacks,  normal_attacks, sizeof(*animattacks)*MAX_ATTACKS);
    for(i = MAX_ATTACKS; i < max_attacks; i++)
    {
        animattacks[i] = maxanim++;
    }
    memcpy(animfollows,  follows,        sizeof(*animfollows)*MAX_FOLLOWS);
    for(i = MAX_FOLLOWS; i < max_follows; i++)
    {
        animfollows[i] = maxanim++;
    }
    memcpy(animpains,    pains,          sizeof(*animpains)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animpains[i] = maxanim++;
    }
    memcpy(animbackpains,    backpains,          sizeof(*animbackpains)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animbackpains[i] = maxanim++;
    }
    memcpy(animfalls,    falls,          sizeof(*animfalls)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animfalls[i] = maxanim++;
    }
    memcpy(animbackfalls,    backfalls,          sizeof(*animbackfalls)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animbackfalls[i] = maxanim++;
    }
    memcpy(animrises,    rises,          sizeof(*animrises)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animrises[i] = maxanim++;
    }
    memcpy(animbackrises,    backrises,          sizeof(*animbackrises)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animbackrises[i] = maxanim++;
    }
    memcpy(animriseattacks,    riseattacks,          sizeof(*animriseattacks)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animriseattacks[i] = maxanim++;
    }
    memcpy(animbackriseattacks,    backriseattacks,          sizeof(*animbackriseattacks)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animbackriseattacks[i] = maxanim++;
    }
    memcpy(animblkpains,    blkpains,    sizeof(*animblkpains)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animblkpains[i] = maxanim++;
    }
    memcpy(animbackblkpains,    backblkpains,    sizeof(*animbackblkpains)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animbackblkpains[i] = maxanim++;
    }
    memcpy(animdies,     deaths,         sizeof(*animdies)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animdies[i] = maxanim++;
    }
    memcpy(animbackdies,     backdeaths,         sizeof(*animbackdies)*MAX_ATKS);
    for(i = MAX_ATKS; i < max_attack_types; i++)
    {
        animbackdies[i] = maxanim++;
    }

    if(buf)
    {
        free(buf);
    }
}

// Load / cache all models
int load_models()
{
    char filename[MAX_BUFFER_LEN] = "data/models.txt";
    int i;
    char *buf;
    size_t size;
    ptrdiff_t pos;
    char *command;
    int line = 0;

    char tmpBuff[MAX_BUFFER_LEN] = {""};

    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";
    modelstxtCommands cmd;
    int modelLoadCount = 0;

    char* value = NULL;
    int tempInt = 0;

    free_modelcache();

    if(isLoadingScreenTypeBg(loadingbg[0].set))
    {
        // New alternative background path for PSP
        if(custBkgrds != NULL)
        {
            strcpy(tmpBuff, custBkgrds);
            strcat(tmpBuff, "loading");
            load_background(tmpBuff);
        }
        else
        {
            load_background("data/bgs/loading");
        }
        standard_palette(1);
    }
    if(isLoadingScreenTypeBar(loadingbg[0].set))
    {
        lifebar_colors();
        init_colourtable();
    }

    update_loading(&loadingbg[0], -1, 1); // initialize the update screen

    if(custModels != NULL)
    {
        strcpy(filename, "data/");
        strcat(filename, custModels);
    }

    // Read file
    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        borShutdown(1, "Error loading model list from %s", filename);
    }

    pos = 0;
    while(pos < size) // peek global settings
    {
        line++;
        if(ParseArgs(&arglist, buf + pos, argbuf))
        {
            command = GET_ARG(0);
            cmd = (modelstxtCommands)getModelCommand(modelstxtcmdlist, command);
            switch(cmd)
            {
            case CMD_MODELSTXT_COMBODELAY:
                combodelay = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_MUSIC:
                music(GET_ARG(1), 1, atol(GET_ARG(2)));
                break;
            case CMD_MODELSTXT_LOAD:
                // Add path to cache list
                modelLoadCount++;
                cache_model(GET_ARG(1), GET_ARG(2), 1);
                break;
            case CMD_MODELSTXT_COLOURSELECT:
                // 6-2-2005 if string for colourselect found
                colourselect =  GET_INT_ARG(1);          //  6-2-2005
                break;
            case CMD_MODELSTXT_SPDIRECTION:
                // Select Player Direction for select player screen
                spdirection[0] =  GET_INT_ARG(1);
                spdirection[1] =  GET_INT_ARG(2);
                spdirection[2] =  GET_INT_ARG(3);
                spdirection[3] =  GET_INT_ARG(4);
                break;
            case CMD_MODELSTXT_AUTOLAND:
                // New flag to determine if a player auto lands when thrown by another player (2 completely disables the ability to land)
                autoland = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_NOLOST:
                // this is use for dont lost your weapon if you grab a enemy flag it to 1 to no drop by tails
                nolost = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_AJSPECIAL:
                
                value = GET_ARG(1);

                if (stricmp(value, "special") == 0)
                {
                    tempInt = AJSPECIAL_KEY_SPECIAL;
                }
                else if (stricmp(value, "double") == 0)
                {
                    tempInt = AJSPECIAL_KEY_DOUBLE;
                }
                else if (stricmp(value, "attack2") == 0)
                {
                    tempInt = AJSPECIAL_KEY_ATTACK2;
                }
                else if (stricmp(value, "attack3") == 0)
                {
                    tempInt = AJSPECIAL_KEY_ATTACK3;
                }
                else if (stricmp(value, "attack4") == 0)
                {
                    tempInt = AJSPECIAL_KEY_ATTACK4;
                }
                else
                {
                    tempInt = GET_INT_ARG(1);
                }
                
                global_config.ajspecial = tempInt;

                break;
            case CMD_MODELSTXT_NOCOST:
                // Nocost set in models.txt
                nocost = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_NOCHEATS:
                
                /* Disable access to cheats menu. */

                if (GET_INT_ARG(1))
                {
                    global_config.cheats &= ~CHEAT_OPTIONS_MASTER_MENU;
                }

                break;

            case CMD_MODELSTXT_NODEBUG:
                //disable debug option in menu
                nodebugoptions =  GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_NODROPEN:
                nodropen = 1;
                break;
            case CMD_MODELSTXT_NODROPSPAWN:
                nodropspawn = 1;
                break;
            case CMD_MODELSTXT_KNOW:
                // Just add path to cache list
                cache_model(GET_ARG(1), GET_ARG(2), 0);
                break;
            case CMD_MODELSTXT_NOAIRCANCEL:
                noaircancel = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_NOMAXRUSHRESET:
                nomaxrushreset[4] = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_MPBLOCK:
                // Take from MP first?
                global_config.block_type = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_BLOCKRATIO:
                global_config.block_ratio = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_NOCHIPDEATH:
                nochipdeath = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_LIFESCORE:
                lifescore =  GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_CREDSCORE:
                // Number of points needed to earn a 1-up
                credscore =  GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_VERSUSDAMAGE:
                // Number of points needed to earn a credit
                versusdamage =  GET_INT_ARG(1);
                if(versusdamage == 0 || versusdamage == 1)
                {
                    savedata.mode = versusdamage ^ 1;
                }
                break;
            case CMD_MODELSTXT_DROPV:
                default_model_dropv.y =  GET_FLOAT_ARG(1);
                default_model_dropv.x =  GET_FLOAT_ARG(2);
                default_model_dropv.z =  GET_FLOAT_ARG(3);
                break;
            case CMD_MODELSTXT_JUMPSPEED:
                default_model_jumpspeed =  GET_FLOAT_ARG(1);
                break;
            case CMD_MODELSTXT_JUMPHEIGHT:
                default_model_jumpheight =  GET_FLOAT_ARG(1);
                break;
            case CMD_MODELSTXT_GLOBAL_CONFIG_CHEATS:

                lcmHandleCommandGlobalConfigCheats(&arglist);
                break;
            case CMD_MODELSTXT_GLOBAL_CONFIG_FLASH_LAYER_ADJUST:
                global_config.flash_layer_adjust = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_GLOBAL_CONFIG_FLASH_LAYER_SOURCE:
                global_config.flash_layer_source = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_GLOBAL_CONFIG_FLASH_Z_SOURCE:
                global_config.flash_z_source = GET_INT_ARG(1);
                break;
            case CMD_MODELSTXT_GRABDISTANCE:
                default_model_grabdistance =  GET_FLOAT_ARG(1);
                break;
            case CMD_MODELSTXT_DEBUG_MNAF:
                move_noatk_factor =  GET_FLOAT_ARG(1);
                break;
            case CMD_MODELSTXT_DEBUG_GNAF:
                group_noatk_factor =  GET_FLOAT_ARG(1);
                break;
            case CMD_MODELSTXT_DEBUG_ANAF:
                agg_noatk_factor =  GET_FLOAT_ARG(1);
                break;
            case CMD_MODELSTXT_DEBUG_MINNA:
                min_noatk_chance =  GET_FLOAT_ARG(1);
                break;
            case CMD_MODELSTXT_DEBUG_MAXNA:
                max_noatk_chance =  GET_FLOAT_ARG(1);
                break;
            case CMD_MODELSTXT_DEBUG_OSNAF:
                offscreen_noatk_factor =  GET_FLOAT_ARG(1);
                break;
            case CMD_MODELSTXT_DEBUG_NAD:
                noatk_duration =  GET_FLOAT_ARG(1);
                break;
            default:
                if(cmd >= CMD_MODELSTXT_THE_END)
                {
                    printf("command %s not understood in %s, line %d\n", command, filename, line);
                }
                break;
            }
        }

        // Go to next line
        pos += getNewLineStart(buf + pos);
    }

    // Defer load_cached_model, so you can define models after their nested model.
    printf("\n");

    for(i = 0, pos = 0; i < models_cached; i++)
    {
        //printf("Checking '%s' '%s'\n", model_cache[i].name, model_cache[i].path);
        if(stricmp(model_cache[i].name, "global_model") == 0)
        {
            global_model = i;
        }
        if(model_cache[i].loadflag)
        {
            load_cached_model(model_cache[i].name, "models.txt", 0);
            update_loading(&loadingbg[0], ++pos, modelLoadCount);
        }
    }
    printf("\nLoading models...............\tDone!\n");


    if(buf)
    {
        free(buf);
    }

    return 1;
}




void unload_levelorder()
{
    int i, j, t;
    s_level_entry *le;
    s_set_entry *se;

    if(levelsets)
    {
        for(i = 0; i < num_difficulties; i++)
        {
            se = levelsets + i;
            if(se->name)
            {
                free(se->name);
            }
            if(se->numlevels)
            {
                for(j = 0; j < se->numlevels; j++)
                {
                    le = se->levelorder + j;
                    if(le->branchname)
                    {
                        free(le->branchname);
                    }
                    if(le->filename)
                    {
                        free(le->filename);
                    }
                    //if(le->skipselect)
                    //{
                        for(t = 0; t < MAX_PLAYERS; t++)
                        {
                            if(le->skipselect[t])
                            {
                                free(le->skipselect[t]);
                            }
                        }
                    //}
                }
                free(se->levelorder);
            }
        }

        free(levelsets);
        levelsets = NULL;
    }

    num_difficulties = 0;
}



// Add a level to the level order
s_level_entry *add_level(char *filename, s_set_entry *set)
{
    s_level_entry *le = NULL;
    int Zs[3] = {0, 0, 0};

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

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

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

    set->levelorder = realloc(set->levelorder, (++set->numlevels) * sizeof(*set->levelorder));
    le = set->levelorder + set->numlevels - 1;
    memset(le, 0, sizeof(*le));
    if(branch_name[0])
    {
        le->branchname = NAME(branch_name);
    }
    le->filename = NAME(filename);
    le->z_coords[0] = Zs[0];
    le->z_coords[1] = Zs[1];
    le->z_coords[2] = Zs[2];

    return le;
}



// Add a scene to the level order
s_level_entry *add_scene(char *filename, s_set_entry *set)
{
    s_level_entry *le = NULL;

    set->levelorder = realloc(set->levelorder, (++set->numlevels) * sizeof(*set->levelorder));
    le = set->levelorder + set->numlevels - 1;
    memset(le, 0, sizeof(*le));
    if(branch_name[0])
    {
        le->branchname = NAME(branch_name);
    }
    le->filename = NAME(filename);
    le->type = LE_TYPE_CUT_SCENE;
    return le;
}

// Add a select screen file to the level order
s_level_entry *add_select(char *filename, s_set_entry *set)
{
    s_level_entry *le = NULL;

    set->levelorder = realloc(set->levelorder, (++set->numlevels) * sizeof(*set->levelorder));
    le = set->levelorder + set->numlevels - 1;
    memset(le, 0, sizeof(*le));
    if(branch_name[0])
    {
        le->branchname = NAME(branch_name);
    }
    le->filename = NAME(filename);
    le->type = LE_TYPE_SELECT_SCREEN;
    return le;
}

s_level_entry *add_skipselect(ArgList arglist, s_set_entry *set)
{
    s_level_entry *le = NULL;
    char *arg;
    int i = 0;

    set->levelorder = realloc(set->levelorder, (++set->numlevels) * sizeof(*set->levelorder));
    le = set->levelorder + set->numlevels - 1;
    memset(le, 0, sizeof(*le));
    if(branch_name[0])
    {
        le->branchname = NAME(branch_name);
    }

    if(arglist.count == 1)
    {
        le->noselect = 1;
    }
    else
    {
        for(i = 0; i < MAX_PLAYERS; i++)
        {
            if((arg = GET_ARG(i + 1))[0])
            {
                le->skipselect[i] = NAME(arg);
            }
        }
    }

    le->type = LE_TYPE_SKIP_SELECT;
    return le;
}

s_level_entry *load_skipselect(char *buf, s_set_entry *set)
{
    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1];

    ParseArgs(&arglist, buf, argbuf);

    return add_skipselect(arglist, set);
}

void save_skipselect(char *buf, char skipselect[MAX_PLAYERS][MAX_NAME_LEN])
{
    int i = 0;

    strcpy(buf,"skipselect");

    for (i = 0; i < MAX_PLAYERS; i++)
    {
        strcat(buf," "); strcat(buf,skipselect[i]);
    }

    return;
}

static void _readbarstatus(char *buf, s_barstatus *pstatus)
{
    char *value;
    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";

    ParseArgs(&arglist, buf, argbuf);
    if((value = GET_ARG(1))[0])
    {
        pstatus->size.x       = atoi(value);
    }
    else
    {
        return;
    }
    if((value = GET_ARG(2))[0])
    {
        pstatus->size.y       = atoi(value);
    }
    else
    {
        return;
    }
    if((value = GET_ARG(3))[0])
    {
        if (atoi(value))
        {
            pstatus->config_flags |= STATUS_CONFIG_BORDER_DISABLE;
        }
    }
    else
    {
        return;
    }
    if((value = GET_ARG(4))[0])
    {
        if (atoi(value))
        {
            pstatus->config_flags |= STATUS_CONFIG_GRAPH_RATIO;
        }
    }
    else
    {
        return;
    }
    if((value = GET_ARG(5))[0])
    {
        if (atoi(value))
        {
            pstatus->config_flags |= STATUS_CONFIG_GRAPH_VERTICAL;
        }
    }
    else
    {
        return;
    }
    if((value = GET_ARG(6))[0])
    {
        pstatus->borderlayer = atoi(value);
    }
    else
    {
        return;
    }
    if((value = GET_ARG(7))[0])
    {
        pstatus->shadowlayer = atoi(value);
    }
    else
    {
        return;
    }
    if((value = GET_ARG(8))[0])
    {
        pstatus->barlayer    = atoi(value);
    }
    else
    {
        return;
    }
    if((value = GET_ARG(9))[0])
    {
        pstatus->backlayer   = atoi(value);
    }
    else
    {
        return;
    }
}

s_set_entry *add_set()
{
    s_set_entry *set = NULL;
    ++num_difficulties;
    if(levelsets)
    {
        levelsets = realloc(levelsets, sizeof(*levelsets) * num_difficulties);
    }
    else
    {
        levelsets = calloc(1, sizeof(*levelsets));
    }
    set = levelsets + num_difficulties - 1;
    memset(set, 0, sizeof(*set));
    set->maxplayers = defaultmaxplayers;
    return set;
}

// Load list of levels
void load_levelorder()
{
    static const char *defaulterr = "Error in level order: a set must be specified.";
#define CHKDEF if(!set) { errormessage = (char*) defaulterr; goto lCleanup; }
    char filename[MAX_BUFFER_LEN] = "";
    int i = 0, j = 0, err = 0;
    char *buf;
    size_t size;
    int pos;
    s_set_entry *set = NULL;
    s_level_entry *le = NULL;
    char *command;
    char *arg;
    char *errormessage = NULL;
    int plifeUsed[2]  = {0, 0};
    int elifeUsed[2]  = {0, 0};
    int piconUsed[2]  = {0, 0};
    int piconwUsed[2] = {0, 0};
    int eiconUsed[4]  = {0, 0, 0, 0};
    int pmpUsed[4]    = {0, 0, 0, 0};
    int plifeXused[4] = {0, 0, 0, 0};     // 4-7-2006 New custimizable variable for players 'x'
    int plifeNused[4] = {0, 0, 0, 0};     // 4-7-2006 New custimizable variable for players 'lives'
    int enameused[4]  = {0, 0, 0, 0};     // 4-7-2006 New custimizable variable for enemy names
    int pnameJused[4] = {0, 0, 0, 0};     // 1-8-2006 New custimizable variable for players name Select Hero
    int pscoreUsed[4] = {0, 0, 0, 0};     // 1-8-2006 New custimizable variable for players name Select Hero

    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";
    levelOrderCommands cmd;
    int line = 0;

    unload_levelorder();

    if(custLevels != NULL)
    {
        strcpy(filename, "data/");
        strcat(filename, custLevels);
    }
    else
    {
        strcpy(filename, "data/levels.txt");
    }

    // Read file

    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        borShutdown(1, "Error loading level list from %s", filename);
    }

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

    // Custom lifebar/timebox/icon positioning and size
    picon[0][0] = piconw[0][0] = picon[2][0] = piconw[2][0] = eicon[0][0] = eicon[2][0] = 2;
    picon[1][0] = piconw[1][0] = picon[3][0] = piconw[3][0] = eicon[1][0] = eicon[3][0] = 2 + P2_STATS_DIST;
    picon[0][1] = piconw[0][1] = picon[1][1] = piconw[1][1] = 2;
    picon[2][1] = piconw[2][1] = picon[3][1] = piconw[3][1] = 202;
    plife[0][0] = pmp[0][0] = plife[2][0] = pmp[2][0] = elife[0][0] = elife[2][0] = 20;
    plife[1][0] = pmp[1][0] = plife[3][0] = pmp[3][0] = elife[1][0] = elife[3][0] = 20 + P2_STATS_DIST;
    plife[0][1] = plife[1][1] = 10;
    plife[2][1] = plife[3][1] = 210;
    pmp[0][1] = pmp[1][1] = 18;
    pmp[2][1] = pmp[3][1] = 218;

    memset(psmenu, 0, sizeof(psmenu));

    eicon[0][1] = eicon[1][1] = 19;
    eicon[2][1] = eicon[3][1] = 220;
    elife[0][1] = elife[1][1] = 27;
    elife[2][1] = elife[3][1] = 227;

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

    lbarstatus.size.x  = mpbarstatus.size.x = 100;
    lbarstatus.size.y  = 5;
    mpbarstatus.size.y = 3;
    lbarstatus.config_flags = STATUS_CONFIG_DEFAULT;
    mpbarstatus.config_flags = STATUS_CONFIG_DEFAULT;

    // Show Complete Default Values
    scomplete[0] = 75;
    scomplete[1] = 60;
    scomplete[2] = 0;
    scomplete[3] = 0;
    scomplete[4] = 0;
    scomplete[5] = 0;

    // Show Complete Y Values
    cbonus[0] = lbonus[0] = rbonus[0] = tscore[0] = 10;
    cbonus[1] = cbonus[3] = cbonus[5] = cbonus[7] = cbonus[9] = 100;
    lbonus[1] = lbonus[3] = lbonus[5] = lbonus[7] = lbonus[9] = 120;
    rbonus[1] = rbonus[3] = rbonus[5] = rbonus[7] = rbonus[9] = 140;
    tscore[1] = tscore[3] = tscore[5] = tscore[7] = tscore[9] = 160;

    // Show Complete X Values
    cbonus[2] = lbonus[2] = rbonus[2] = tscore[2] = 100;
    cbonus[4] = lbonus[4] = rbonus[4] = tscore[4] = 155;
    cbonus[6] = lbonus[6] = rbonus[6] = tscore[6] = 210;
    cbonus[8] = lbonus[8] = rbonus[8] = tscore[8] = 265;

    while(pos < size)
    {
        line++;
        ParseArgs(&arglist, buf + pos, argbuf);
        command = GET_ARG(0);
        cmd = getLevelOrderCommand(levelordercmdlist, command);
        switch(cmd)
        {
        case CMD_LEVELORDER_BLENDFX:
            for(i = 0; i < MAX_BLENDINGS; i++)
            {
                if(GET_INT_ARG(i + 1))
                {
                    blendfx[i] = 1;
                }
                else
                {
                    blendfx[i] = 0;
                }
            }
            blendfx_is_set = 1;
            break;
        case CMD_LEVELORDER_SET:
            set = add_set();
            set->name = NAME(GET_ARG(1));
            set->ifcomplete = 0;
            set->saveflag  = 1; // default to 1, so the level can be saved
            branch_name[0] = 0;
            le = NULL;
            break;
        case CMD_LEVELORDER_IFCOMPLETE:
            CHKDEF;
            set->ifcomplete = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_SKIPSELECT:
            CHKDEF;
            le = add_skipselect(arglist, set);
            break;
        case CMD_LEVELORDER_FILE:
            CHKDEF;
            le = add_level(GET_ARG(1), set);
            break;
        case CMD_LEVELORDER_SCENE:
            CHKDEF;
            le = add_scene(GET_ARG(1), set);
            break;
        case CMD_LEVELORDER_SELECT:
            CHKDEF;
            le = add_select(GET_ARG(1), set);
            break;
        case CMD_LEVELORDER_NEXT:
            CHKDEF;
            // Set 'gonext' flag of last loaded level
            if(le)
            {
                le->gonext = 1;
            }
            break;
        case CMD_LEVELORDER_END:
            CHKDEF;
            // Set endgame flag of last loaded level
            if(le)
            {
                le->gonext = 2;
            }
            break;
        case CMD_LEVELORDER_LIVES:
            // 7-1-2005  credits/lives/singleplayer start here
            // used to read the new # of lives/credits from the levels.txt
            CHKDEF;
            set->lives = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_DISABLEHOF:
            CHKDEF;
            set->noshowhof = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_DISABLEGAMEOVER:
            CHKDEF;
            set->noshowgameover = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_CANSAVE:
            // 07-12-31
            // 0 this set can't be saved
            // 1 save level only
            // 2 save player info and level, can't choose player in select menu
            CHKDEF;
            set->saveflag = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_Z:
            //    2-10-05  adjust the walkable coordinates
            CHKDEF;
            z_coords[0] = GET_INT_ARG(1);
            z_coords[1] = GET_INT_ARG(2);
            z_coords[2] = GET_INT_ARG(3);
            break;
        case CMD_LEVELORDER_BRANCH:
            //    2007-2-22 level branch name
            CHKDEF;
            strncpy(branch_name, GET_ARG(1), MAX_NAME_LEN);
            break;
        case CMD_LEVELORDER_P1LIFE:
        case CMD_LEVELORDER_P2LIFE:
        case CMD_LEVELORDER_P3LIFE:
        case CMD_LEVELORDER_P4LIFE:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1LIFE:
                i = 0;
                break;
            case CMD_LEVELORDER_P2LIFE:
                i = 1;
                break;
            case CMD_LEVELORDER_P3LIFE:
                i = 2;
                plifeUsed[0] = 1;
                break;
            case CMD_LEVELORDER_P4LIFE:
                i = 3;
                plifeUsed[1] = 1;
                break;
            default:
                assert(0);
            }
            if((arg = GET_ARG(1))[0])
            {
                plife[i][0] = atoi(arg);
            }
            if((arg = GET_ARG(2))[0])
            {
                plife[i][1] = atoi(arg);
            }
            break;
        case CMD_LEVELORDER_P1MP:
        case CMD_LEVELORDER_P2MP:
        case CMD_LEVELORDER_P3MP:
        case CMD_LEVELORDER_P4MP:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1MP:
                i = 0;
                break;
            case CMD_LEVELORDER_P2MP:
                i = 1;
                break;
            case CMD_LEVELORDER_P3MP:
                i = 2;
                break;
            case CMD_LEVELORDER_P4MP:
                i = 3;
                break;
            default:
                assert(0);
            }
            if((arg = GET_ARG(1))[0])
            {
                pmp[i][0] = atoi(arg);
            }
            if((arg = GET_ARG(2))[0])
            {
                pmp[i][1] = atoi(arg);
            }
            pmpUsed[i] = 1;
            break;
        case CMD_LEVELORDER_P1LIFEX:
        case CMD_LEVELORDER_P2LIFEX:
        case CMD_LEVELORDER_P3LIFEX:
        case CMD_LEVELORDER_P4LIFEX:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1LIFEX:
                j = 0;
                break;
            case CMD_LEVELORDER_P2LIFEX:
                j = 1;
                break;
            case CMD_LEVELORDER_P3LIFEX:
                j = 2;
                break;
            case CMD_LEVELORDER_P4LIFEX:
                j = 3;
                break;
            default:
                assert(0);
            }
            for(i = 0; i < 3; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    plifeX[j][i] = atoi(arg);
                }
            plifeXused[j] = 1;
            break;
        case CMD_LEVELORDER_P1LIFEN:
        case CMD_LEVELORDER_P2LIFEN:
        case CMD_LEVELORDER_P3LIFEN:
        case CMD_LEVELORDER_P4LIFEN:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1LIFEN:
                j = 0;
                break;
            case CMD_LEVELORDER_P2LIFEN:
                j = 1;
                break;
            case CMD_LEVELORDER_P3LIFEN:
                j = 2;
                break;
            case CMD_LEVELORDER_P4LIFEN:
                j = 3;
                break;
            default:
                assert(0);
            }
            for(i = 0; i < 3; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    plifeN[j][i] = atoi(arg);
                }
            plifeNused[j] = 1;
            break;
        case CMD_LEVELORDER_E1LIFE:
        case CMD_LEVELORDER_E2LIFE:
        case CMD_LEVELORDER_E3LIFE:
        case CMD_LEVELORDER_E4LIFE:
            switch(cmd)
            {
            case CMD_LEVELORDER_E1LIFE:
                i = 0;
                break;
            case CMD_LEVELORDER_E2LIFE:
                i = 1;
                break;
            case CMD_LEVELORDER_E3LIFE:
                i = 2;
                elifeUsed[0] = 1;
                break;
            case CMD_LEVELORDER_E4LIFE:
                i = 3;
                elifeUsed[1] = 1;
                break;
            default:
                assert(0);
            }
            if((arg = GET_ARG(1))[0])
            {
                elife[i][0] = atoi(arg);
            }
            if((arg = GET_ARG(2))[0])
            {
                elife[i][1] = atoi(arg);
            }
            break;
        case CMD_LEVELORDER_P1ICON:
        case CMD_LEVELORDER_P2ICON:
        case CMD_LEVELORDER_P3ICON:
        case CMD_LEVELORDER_P4ICON:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1ICON:
                i = 0;
                break;
            case CMD_LEVELORDER_P2ICON:
                i = 1;
                break;
            case CMD_LEVELORDER_P3ICON:
                i = 2;
                piconUsed[0] = 1;
                break;
            case CMD_LEVELORDER_P4ICON:
                i = 3;
                piconUsed[1] = 1;
                break;
            default:
                assert(0);
            }
            if((arg = GET_ARG(1))[0])
            {
                picon[i][0] = atoi(arg);
            }
            if((arg = GET_ARG(2))[0])
            {
                picon[i][1] = atoi(arg);
            }
            break;
        case CMD_LEVELORDER_P1ICONW:
        case CMD_LEVELORDER_P2ICONW:
        case CMD_LEVELORDER_P3ICONW:
        case CMD_LEVELORDER_P4ICONW:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1ICONW:
                i = 0;
                break;
            case CMD_LEVELORDER_P2ICONW:
                i = 1;
                break;
            case CMD_LEVELORDER_P3ICONW:
                i = 2;
                piconwUsed[0] = 1;
                break;
            case CMD_LEVELORDER_P4ICONW:
                i = 3;
                piconwUsed[1] = 1;
                break;
            default:
                assert(0);
            }
            if((arg = GET_ARG(1))[0])
            {
                piconw[i][0] = atoi(arg);
            }
            if((arg = GET_ARG(2))[0])
            {
                piconw[i][1] = atoi(arg);
            }
            break;
        case CMD_LEVELORDER_MP1ICON:
        case CMD_LEVELORDER_MP2ICON:
        case CMD_LEVELORDER_MP3ICON:
        case CMD_LEVELORDER_MP4ICON:
            switch(cmd)
            {
            case CMD_LEVELORDER_MP1ICON:
                i = 0;
                break;
            case CMD_LEVELORDER_MP2ICON:
                i = 1;
                break;
            case CMD_LEVELORDER_MP3ICON:
                i = 2;
                break;
            case CMD_LEVELORDER_MP4ICON:
                i = 3;
                break;
            default:
                assert(0);
            }
            if((arg = GET_ARG(1))[0])
            {
                mpicon[i][0] = atoi(arg);
            }
            if((arg = GET_ARG(2))[0])
            {
                mpicon[i][1] = atoi(arg);
            }
            break;
        case CMD_LEVELORDER_P1NAMEJ:
        case CMD_LEVELORDER_P2NAMEJ:
        case CMD_LEVELORDER_P3NAMEJ:
        case CMD_LEVELORDER_P4NAMEJ:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1NAMEJ:
                j = 0;
                break;
            case CMD_LEVELORDER_P2NAMEJ:
                j = 1;
                break;
            case CMD_LEVELORDER_P3NAMEJ:
                j = 2;
                break;
            case CMD_LEVELORDER_P4NAMEJ:
                j = 3;
                break;
            default:
                assert(0);
            }
            for(i = 0; i < 7; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    pnameJ[j][i] = atoi(arg);
                }
            pnameJused[j] = 1;
            break;
        case CMD_LEVELORDER_P1SCORE:
        case CMD_LEVELORDER_P2SCORE:
        case CMD_LEVELORDER_P3SCORE:
        case CMD_LEVELORDER_P4SCORE:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1SCORE:
                j = 0;
                break;
            case CMD_LEVELORDER_P2SCORE:
                j = 1;
                break;
            case CMD_LEVELORDER_P3SCORE:
                j = 2;
                break;
            case CMD_LEVELORDER_P4SCORE:
                j = 3;
                break;
            default:
                assert(0);
            }
            for(i = 0; i < 7; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    pscore[j][i] = atoi(arg);
                }
            pscoreUsed[j] = 1;
            break;
        case CMD_LEVELORDER_P1SHOOT:
        case CMD_LEVELORDER_P2SHOOT:
        case CMD_LEVELORDER_P3SHOOT:
        case CMD_LEVELORDER_P4SHOOT:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1SHOOT:
                j = 0;
                break;
            case CMD_LEVELORDER_P2SHOOT:
                j = 1;
                break;
            case CMD_LEVELORDER_P3SHOOT:
                j = 2;
                break;
            case CMD_LEVELORDER_P4SHOOT:
                j = 3;
                break;
            default:
                assert(0);
            }
            for(i = 0; i < 3; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    pshoot[j][i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_P1RUSH:
        case CMD_LEVELORDER_P2RUSH:
        case CMD_LEVELORDER_P3RUSH:
        case CMD_LEVELORDER_P4RUSH:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1RUSH:
                j = 0;
                break;
            case CMD_LEVELORDER_P2RUSH:
                j = 1;
                break;
            case CMD_LEVELORDER_P3RUSH:
                j = 2;
                break;
            case CMD_LEVELORDER_P4RUSH:
                j = 3;
                break;
            default:
                assert(0);
            }
            for(i = 0; i < 8; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    prush[j][i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_E1ICON:
        case CMD_LEVELORDER_E2ICON:
        case CMD_LEVELORDER_E3ICON:
        case CMD_LEVELORDER_E4ICON:
            switch(cmd)
            {
            case CMD_LEVELORDER_E1ICON:
                i = 0;
                break;
            case CMD_LEVELORDER_E2ICON:
                i = 1;
                break;
            case CMD_LEVELORDER_E3ICON:
                i = 2;
                eiconUsed[0] = 1;
                break;
            case CMD_LEVELORDER_E4ICON:
                i = 3;
                eiconUsed[1] = 1;
                break;
            default:
                assert(0);
            }
            if((arg = GET_ARG(1))[0])
            {
                eicon[i][0] = atoi(arg);
            }
            if((arg = GET_ARG(2))[0])
            {
                eicon[i][1] = atoi(arg);
            }
            break;
        case CMD_LEVELORDER_E1NAME:
        case CMD_LEVELORDER_E2NAME:
        case CMD_LEVELORDER_E3NAME:
        case CMD_LEVELORDER_E4NAME:
            switch(cmd)
            {
            case CMD_LEVELORDER_E1NAME:
                j = 0;
                break;
            case CMD_LEVELORDER_E2NAME:
                j = 1;
                break;
            case CMD_LEVELORDER_E3NAME:
                j = 2;
                break;
            case CMD_LEVELORDER_E4NAME:
                j = 3;
                break;
            default:
                assert(0);
            }
            for(i = 0; i < 3; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    ename[j][i] = atoi(arg);
                }
            enameused[j] = 1;
            break;
        case CMD_LEVELORDER_P1SMENU:
        case CMD_LEVELORDER_P2SMENU:
        case CMD_LEVELORDER_P3SMENU:
        case CMD_LEVELORDER_P4SMENU:
            switch(cmd)
            {
            case CMD_LEVELORDER_P1SMENU:
                j = 0;
                break;
            case CMD_LEVELORDER_P2SMENU:
                j = 1;
                break;
            case CMD_LEVELORDER_P3SMENU:
                j = 2;
                break;
            case CMD_LEVELORDER_P4SMENU:
                j = 3;
                break;
            default:
                assert(0);
            }
            for(i = 0; i < MAX_PLAYERS; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    psmenu[j][i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_TIMEICON:
            strncpy(timeicon_path, GET_ARG(1), 127);
            timeicon = loadsprite(timeicon_path, 0, 0, pixelformat);
            if((arg = GET_ARG(2))[0])
            {
                timeicon_offsets[0] = atoi(arg);
            }
            if((arg = GET_ARG(3))[0])
            {
                timeicon_offsets[1] = atoi(arg);
            }
            break;
        case CMD_LEVELORDER_BGICON:
            strncpy(bgicon_path, GET_ARG(1), 127);
            bgicon = loadsprite(bgicon_path, 0, 0, pixelformat);
            if((arg = GET_ARG(2))[0])
            {
                bgicon_offsets[0] = atoi(arg);
            }
            if((arg = GET_ARG(3))[0])
            {
                bgicon_offsets[1] = atoi(arg);
            }
            if((arg = GET_ARG(4))[0])
            {
                bgicon_offsets[2] = atoi(arg);
            }
            else
            {
                bgicon_offsets[2] = HUD_Z / 2;
            }
            break;
        case CMD_LEVELORDER_OLICON:
            strncpy(olicon_path, GET_ARG(1), 127);
            olicon = loadsprite(olicon_path, 0, 0, pixelformat);
            if((arg = GET_ARG(2))[0])
            {
                olicon_offsets[0] = atoi(arg);
            }
            if((arg = GET_ARG(3))[0])
            {
                olicon_offsets[1] = atoi(arg);
            }
            if((arg = GET_ARG(4))[0])
            {
                olicon_offsets[2] = atoi(arg);
            }
            else
            {
                olicon_offsets[2] = HUD_Z * 3;
            }
            break;
        case CMD_LEVELORDER_TIMELOC:
            for(i = 0; i < 6; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    timeloc[i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_PAUSEOFFSET:
            for(i = 0; i < 7; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    pauseoffset[i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_LBARSIZE:
            _readbarstatus(buf + pos, &lbarstatus);
            break;
        case CMD_LEVELORDER_OLBARSIZE:
            _readbarstatus(buf + pos, &olbarstatus);
            break;
        case CMD_LEVELORDER_MPBARSIZE:
            _readbarstatus(buf + pos, &mpbarstatus);
            break;
        case CMD_LEVELORDER_LBARTEXT:
            for(i = 0; i < 4; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    lbartext[i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_MPBARTEXT:
            for(i = 0; i < 4; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    mpbartext[i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_SHOWCOMPLETE:
            for(i = 0; i < 6; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    scomplete[i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_CLEARBONUS:
            for(i = 0; i < 10; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    cbonus[i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_RUSHBONUS:
            for(i = 0; i < 10; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    rbonus[i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_LIFEBONUS:
            for(i = 0; i < 10; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    lbonus[i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_SCBONUSES:
            for(i = 0; i < 4; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    scbonuses[i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_TOTALSCORE:
            for(i = 0; i < 10; i++)
                if((arg = GET_ARG(i + 1))[0])
                {
                    tscore[i] = atoi(arg);
                }
            break;
        case CMD_LEVELORDER_MUSICOVERLAP:
            CHKDEF;
            set->musicoverlap = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_SHOWRUSHBONUS:
            showrushbonus = 1;
            break;
        case CMD_LEVELORDER_NOSLOWFX:
            noslowfx = 1;
            break;
        case CMD_LEVELORDER_EQUALAIRPAUSE:
            equalairpause = 1;
            break;
        case CMD_LEVELORDER_HISCOREBG:
            hiscorebg = 1;
            break;
        case CMD_LEVELORDER_COMPLETEBG:
            completebg = 1;
            break;
        case CMD_LEVELORDER_LOADINGBG:
            errormessage = fill_s_loadingbar(&loadingbg[0], GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3), GET_INT_ARG(4), GET_INT_ARG(5), GET_INT_ARG(6), GET_INT_ARG(7), GET_INT_ARG(8));
            if(errormessage)
            {
                goto lCleanup;
            }
            break;
        case CMD_LEVELORDER_LOADINGBG2:
            errormessage = fill_s_loadingbar(&loadingbg[1], GET_INT_ARG(1), GET_INT_ARG(2), GET_INT_ARG(3), GET_INT_ARG(4), GET_INT_ARG(5), GET_INT_ARG(6), GET_INT_ARG(7), GET_INT_ARG(8));
            if(errormessage)
            {
                goto lCleanup;
            }
            break;
        case CMD_LEVELORDER_LOADINGMUSIC:
            loadingmusic = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_UNLOCKBG:
            unlockbg = 1;
            break;
        case CMD_LEVELORDER_NOSHARE:
            noshare = 1;
            break;
        case CMD_LEVELORDER_CUSTFADE:
            //8-2-2005 custom fade
            CHKDEF;
            set->custfade = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_CONTINUESCORE:
            //8-2-2005 custom fade end
            //continuescore
            CHKDEF;
            set->continuescore = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_CREDITS:
            CHKDEF;
            set->credits = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_TYPEMP:
            //typemp for change for mp restored by time (0) to by enemys (1) or no restore (2) by tails
            CHKDEF;
            set->typemp = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_SINGLE:
            if(set)
            {
                set->maxplayers = 1;
            }
            else
            {
                defaultmaxplayers = 1;
            }
            break;
        case CMD_LEVELORDER_MAXPLAYERS:
            if(set)
            {
                set->maxplayers = GET_INT_ARG(1);
            }
            else
            {
                defaultmaxplayers = GET_INT_ARG(1);
            }
            break;
        case CMD_LEVELORDER_NOSAME:
            CHKDEF;
            set->nosame |= GET_INT_ARG(1) != 0;
            set->nosame |= GET_INT_ARG(2) != 0 ? 2 : 0;
            break;
        case CMD_LEVELORDER_RUSH:
            rush[0] = GET_INT_ARG(1);
            rush[1] = GET_INT_ARG(2);
            strncpy(rush_names[0], GET_ARG(3), MAX_NAME_LEN - 1);
            rush[2] = GET_INT_ARG(4);
            rush[3] = GET_INT_ARG(5);
            strncpy(rush_names[1], GET_ARG(6), MAX_NAME_LEN - 1);
            rush[4] = GET_INT_ARG(7);
            rush[5] = GET_INT_ARG(8);
            break;
        case CMD_LEVELORDER_MAXWALLHEIGHT:
            MAX_WALL_HEIGHT = GET_INT_ARG(1);
            if(MAX_WALL_HEIGHT < 0)
            {
                MAX_WALL_HEIGHT = 1000;
            }
            break;
        case CMD_LEVELORDER_SCOREFORMAT:
            scoreformat = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_GRAVITY:
            default_level_gravity = GET_FLOAT_ARG(1);
            default_level_maxfallspeed = GET_FLOAT_ARG(2);
            default_level_maxtossspeed = GET_FLOAT_ARG(3);
            break;
        case CMD_LEVELORDER_SKIPTOSET:
            skiptoset = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_NOSHOWCOMPLETE:
            CHKDEF;
            set->noshowcomplete = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_SPAWNOVERRIDE:
            spawnoverride = GET_INT_ARG(1);
            break;
        case CMD_LEVELORDER_MAXENTITIES:
            maxentities = GET_INT_ARG(1);
            break;
        default:
            if (command && command[0])
            {
                if (err <= 0) printf("\n");
                ++err;
                printf("Command '%s' not understood in level order!\n", command);
            }
        }

        // Go to next line
        pos += getNewLineStart(buf + pos);
    }

#undef CHKDEF

    // Variables without defaults will be auto populated.
    if(olbarstatus.size.x == 0)
    {
        olbarstatus = lbarstatus;
    }

    if(!plifeUsed[0])
    {
        plife[2][0] = plife[0][0];
        plife[2][1] = plife[2][1] + (plife[0][1] - 10);
    }
    if(!plifeUsed[1])
    {
        plife[3][0] = plife[1][0];
        plife[3][1] = plife[3][1] + (plife[1][1] - 10);
    }

    if(!elifeUsed[0])
    {
        elife[2][0] = elife[0][0];
        elife[2][1] = elife[2][1] + (elife[0][1] - 27);
    }
    if(!elifeUsed[1])
    {
        elife[3][0] = elife[1][0];
        elife[3][1] = elife[3][1] + (elife[1][1] - 27);
    }

    if(!piconUsed[0])
    {
        picon[2][0] = picon[0][0];
        picon[2][1] = picon[2][1] + (picon[0][1] - 2);
    }
    if(!piconUsed[1])
    {
        picon[3][0] = picon[1][0];
        picon[3][1] = picon[3][1] + (picon[1][1] - 2);
    }

    if(!piconwUsed[0])
    {
        piconw[2][0] = piconw[0][0];
        piconw[2][1] = piconw[2][1] + (piconw[0][1] - 2);
    }
    if(!piconwUsed[1])
    {
        piconw[3][0] = piconw[1][0];
        piconw[3][1] = piconw[3][1] + (piconw[1][1] - 2);
    }

    if(!eiconUsed[0])
    {
        eicon[2][0] = eicon[0][0];
        eicon[2][1] = eicon[2][1] + (eicon[0][1] - 19);
    }
    if(!eiconUsed[1])
    {
        eicon[3][0] = eicon[1][0];
        eicon[3][1] = eicon[3][1] + (eicon[1][1] - 19);
    }

    if(!pmpUsed[0])
    {
        pmp[0][0] = plife[0][0];
        pmp[0][1] = plife[0][1] + 8;
    }
    if(!pmpUsed[1])
    {
        pmp[1][0] = plife[1][0];
        pmp[1][1] = plife[1][1] + 8;
    }
    if(!pmpUsed[2])
    {
        pmp[2][0] = pmp[0][0];
        pmp[2][1] = pmp[2][1] + (pmp[0][1] - 18);
    }
    if(!pmpUsed[3])
    {
        pmp[3][0] = pmp[1][0];
        pmp[3][1] = pmp[1][1] + (pmp[1][1] - 18);
    }

    if(!plifeXused[0])
    {
        plifeX[0][0] = plife[0][0] + lbarstatus.size.x + 4;
        plifeX[0][1] = picon[0][1] + 7;
    }
    if(!plifeXused[1])
    {
        plifeX[1][0] = plife[1][0] + lbarstatus.size.x + 4;
        plifeX[1][1] = picon[1][1] + 7;
    }
    if(!plifeXused[2])
    {
        plifeX[2][0] = plife[2][0] + lbarstatus.size.x + 4;
        plifeX[2][1] = picon[2][1] + 7;
    }
    if(!plifeXused[3])
    {
        plifeX[3][0] = plife[3][0] + lbarstatus.size.x + 4;
        plifeX[3][1] = picon[3][1] + 7;
    }
    for(i = 0; i < MAX_PLAYERS; i++) if(plifeX[i][2] == -1)
        {
            plifeX[i][2] = 0;
        }

    if(!plifeNused[0])
    {
        plifeN[0][0] = plife[0][0] + lbarstatus.size.x + 11;
        plifeN[0][1] = picon[0][1];
    }
    if(!plifeNused[1])
    {
        plifeN[1][0] = plife[1][0] + lbarstatus.size.x + 11;
        plifeN[1][1] = picon[1][1];
    }
    if(!plifeNused[2])
    {
        plifeN[2][0] = plifeN[0][0];
        plifeN[2][1] = picon[2][1];
    }
    if(!plifeNused[3])
    {
        plifeN[3][0] = plifeN[1][0];
        plifeN[3][1] = picon[3][1];
    }
    for(i = 0; i < MAX_PLAYERS; i++) if(plifeN[i][2] == -1)
        {
            plifeN[i][2] = 3;
        }

    if(!pnameJused[0])
    {
        pnameJ[0][2] = pnameJ[0][4] = pnameJ[0][0] = plife[0][0] + 1;
        pnameJ[0][5] = pnameJ[0][1] = picon[0][1];
        pnameJ[0][3] = 10 + pnameJ[0][5];
    }
    if(!pnameJused[1])
    {
        pnameJ[1][2] = pnameJ[1][4] = pnameJ[1][0] = plife[1][0] + 1;
        pnameJ[1][5] = pnameJ[1][1] = picon[1][1];
        pnameJ[1][3] = 10 + pnameJ[1][5];
    }
    if(!pnameJused[2])
    {
        pnameJ[2][2] = pnameJ[2][4] = pnameJ[2][0] = plife[2][0] + 1;
        pnameJ[2][5] = pnameJ[2][1] = picon[2][1];
        pnameJ[2][3] = 10 + pnameJ[2][5];
    }
    if(!pnameJused[3])
    {
        pnameJ[3][2] = pnameJ[3][4] = pnameJ[3][0] = plife[3][0] + 1;
        pnameJ[3][5] = pnameJ[3][1] = picon[3][1];
        pnameJ[3][3] = 10 + pnameJ[3][5];
    }
    for(i = 0; i < MAX_PLAYERS; i++) if(pnameJ[i][6] == -1)
        {
            pnameJ[i][6] = 0;
        }

    if(!pscoreUsed[0])
    {
        pscore[0][0] = plife[0][0] + 1;
        pscore[0][1] = picon[0][1];
    }
    if(!pscoreUsed[1])
    {
        pscore[1][0] = plife[1][0] + 1;
        pscore[1][1] = picon[1][1];
    }
    if(!pscoreUsed[2])
    {
        pscore[2][0] = plife[2][0] + 1;
        pscore[2][1] = picon[2][1];
    }
    if(!pscoreUsed[3])
    {
        pscore[3][0] = plife[3][0] + 1;
        pscore[3][1] = picon[3][1];
    }
    for(i = 0; i < MAX_PLAYERS; i++) if(pscore[i][6] == -1)
        {
            pscore[i][6] = 0;
        }

    if(!enameused[0])
    {
        ename[0][0] = elife[0][0] + 1;
        ename[0][1] = eicon[0][1];
    }
    if(!enameused[1])
    {
        ename[1][0] = elife[1][0] + 1;
        ename[1][1] = eicon[1][1];
    }
    if(!enameused[2])
    {
        ename[2][0] = ename[0][0];
        ename[2][1] = eicon[2][1];
    }
    if(!enameused[3])
    {
        ename[3][0] = ename[1][0];
        ename[3][1] = eicon[3][1];
    }
    for(i = 0; i < MAX_PLAYERS; i++) if(ename[i][2] == -1)
        {
            ename[i][2] = 0;
        }

    branch_name[0] = 0; //clear up branch name, so we can use it in game

    for(i = 0; i < MAX_PLAYERS; i++) if(pshoot[i][2] == -1)
        {
            pshoot[i][2] = 2;
        }
    if(timeloc[5] == -1)
    {
        timeloc[5] = 3;
    }

    if(!set)
    {
        errormessage = "No levels were loaded!";
    }

    //assume old mods have same maxplayers for all sets
    else if(!psmenu[0][0] && !psmenu[0][1])
    {
        for(i = 0; i < set->maxplayers; i++)
        {
            psmenu[i][0] = (set->maxplayers > 2) ? ((111 - (set->maxplayers * 14)) + ((i * (320 - (166 / set->maxplayers)) / set->maxplayers) + videomodes.hShift)) :
                           (83 + (videomodes.hShift / 2) + (i * (155 + videomodes.hShift)));
            psmenu[i][1] = 230 + videomodes.vShift;
            psmenu[i][2] = (set->maxplayers > 2) ? ((95 - (set->maxplayers * 14)) + ((i * (320 - (166 / set->maxplayers)) / set->maxplayers) + videomodes.hShift)) :
                           (67 + (videomodes.hShift / 2) + (i * (155 + videomodes.hShift)));
            psmenu[i][3] = 225 + videomodes.vShift;
        }
    }

lCleanup:

    if(buf)
    {
        free(buf);
    }

    if(!savelevel)
    {
        savelevel = calloc(num_difficulties, sizeof(*savelevel));
    }

    if(errormessage)
    {
        borShutdown(1, "load_levelorder ERROR in %s at %d, msg: %s\n", filename, line, errormessage);
    }
}


void free_level(s_level *lv)
{
    int i;

    if(!lv)
    {
        return;
    }

    //offload layers
    for(i = 1; i < lv->numlayers; i++)
    {
        if(lv->layers[i].gfx.handle && lv->layers[i].gfx.handle != background)
        {
            if(lv->layers[i].gfx.sprite->magic == sprite_magic &&
               lv->layers[i].gfx.sprite->mask != NULL)
            {
                free(lv->layers[i].gfx.sprite->mask);
            }
            free(lv->layers[i].gfx.handle);
            lv->layers[i].gfx.handle = NULL;
        }
    }

    //offload textobjs
    for(i = 0; i < lv->numtextobjs; i++)
    {
        if(lv->textobjs[i].text)
        {
            free(lv->textobjs[i].text);
            lv->textobjs[i].text = NULL;
        }
    }

    //offload basemaps
    for(i = 0; i < lv->numbasemaps; i++)
    {
        if(lv->basemaps[i].map)
        {
            free(lv->basemaps[i].map);
        }
    }

    //offload scripts
    Script_Clear(&(lv->update_script), 2);
    Script_Clear(&(lv->updated_script), 2);
    Script_Clear(&(lv->key_script), 2);
    Script_Clear(&(lv->level_script), 2);
    Script_Clear(&(lv->endlevel_script), 2);

    for(i = 0; i < lv->numspawns; i++)
    {
        Script_Clear(&(lv->spawnpoints[i].spawnscript), 2);
    }

    if(lv->spawnpoints)
    {
        free(lv->spawnpoints);
    }
    if(lv->layers)
    {
        free(lv->layers);
    }
    if(lv->layersref)
    {
        free(lv->layersref);
    }
    if(lv->panels)
    {
        free(lv->panels);
    }
    if(lv->frontpanels)
    {
        free(lv->frontpanels);
    }
    if(lv->bglayers)
    {
        free(lv->bglayers);
    }
    if(lv->fglayers)
    {
        free(lv->fglayers);
    }
    if(lv->genericlayers)
    {
        free(lv->genericlayers);
    }
    if(lv->waters)
    {
        free(lv->waters);
    }
    if(lv->textobjs)
    {
        free(lv->textobjs);
    }
    if(lv->holes)
    {
        free(lv->holes);
    }
    if(lv->spawn)
    {
        free(lv->spawn);
    }
    if(lv->walls)
    {
        free(lv->walls);
    }
    if(lv->basemaps)
    {
        free(lv->basemaps);
    }
    if(lv->palettes)
    {
        free(lv->palettes);
    }

    free(lv);
    lv = NULL;
}


void unload_level()
{
    s_model *temp;
    int i;

    kill_all();
    unload_background();

    if(level)
    {

        level->pos = 0;
        level->advancetime = 0;
        level->quake = 0;
        level->quaketime = 0;
        level->waiting = 0;

        printf("Level Unloading: '%s'\n", level->name);
        getRamStatus(BYTES);
        free(level->name);
        level->name = NULL;
        free_level(level);
        level = NULL;
        temp = getFirstModel();
        do
        {
            if(!temp)
            {
                break;
            }
            if((temp->unload & 2))
            {
                cache_model_sprites(temp, 0);
            }
            if((temp->unload & 1))
            {
                free_model(temp);
                temp = getCurrentModel();
            }
            else
            {
                temp = getNextModel();
            }
        }
        while(temp);
        printf("RAM Status:\n");
        getRamStatus(BYTES);


    }

    advancex = 0;
    advancey = 0;
    nojoin = 0;
    is_total_timeover = 0;
    current_spawn = 0;
    groupmin = 100;
    groupmax = 100;
    scrollminz = 0;
    scrollmaxz = 0;
    scrollminx = 0;
    scrollmaxx = 0;
    blockade = 0;
    level_completed = 0;
    level_completed_defeating_boss = 0;
    tospeedup = 0;    // Reset so it sets to normal speed for the next level
    for (i = 0; i < MAX_PLAYERS; i++) reached[i] = 0; // TYPE_ENDLEVEL values reset after level completed //4player
    showtimeover = 0;
    _pause = 0;
    endgame = 0;
    go_time = 0;
    debug_time = 0;
    debug_xy_msg.x = -1;
    debug_xy_msg.y = -1;
    neon_time = 0;
    _time = 0;
    cameratype = 0;
    light.x = 128;
    light.y = 64;
    gfx_y_offset = gfx_x_offset = gfx_y_offset_adj = 0;    // Added so select screen graphics display correctly
}


static void addhole(float x, float z, float x1, float x2, float x3, float x4, float depth, float alt, int type)
{
    __realloc(level->holes, level->numholes);
    level->holes[level->numholes].x = x;
    level->holes[level->numholes].z = z;
    level->holes[level->numholes].upperleft = x1;
    level->holes[level->numholes].lowerleft = x2;
    level->holes[level->numholes].upperright = x3;
    level->holes[level->numholes].lowerright = x4;
    level->holes[level->numholes].depth = depth;
    level->holes[level->numholes].height = alt;
    level->holes[level->numholes].type = type;

    level->numholes++;
}

static void addwall(float x, float z, float x1, float x2, float x3, float x4, float depth, float alt, int type)
{
    __realloc(level->walls, level->numwalls);
    level->walls[level->numwalls].x = x;
    level->walls[level->numwalls].z = z;
    level->walls[level->numwalls].upperleft = x1;
    level->walls[level->numwalls].lowerleft = x2;
    level->walls[level->numwalls].upperright = x3;
    level->walls[level->numwalls].lowerright = x4;
    level->walls[level->numwalls].depth = depth;
    level->walls[level->numwalls].height = alt;
    level->walls[level->numwalls].type = type;

    level->numwalls++;
}

static void addbasemap(float rx, float rz, float x_size, float z_size, float min_y, float max_y, int x_cont)
{
    __realloc(level->basemaps, level->numbasemaps);
    level->numbasemaps++;
    generate_basemap(level->numbasemaps-1, rx, rz, x_size, z_size, min_y, max_y, x_cont);
}

void generate_basemap(int map_index, float rx, float rz, float x_size, float z_size, float min_y, float max_y, int x_cont) {
    float x, z;
	float delta, y, tmp;
	int dir = 0;

    if(map_index >= level->numbasemaps)
    {
        __reallocto(level->basemaps, level->numbasemaps, map_index + 1);
        level->numbasemaps = map_index + 1;
    }

    level->basemaps[map_index].position.x = rx;
    level->basemaps[map_index].position.z = rz-z_size;
    level->basemaps[map_index].size.x = x_size;
    level->basemaps[map_index].size.z = z_size;

    if(!level->basemaps[map_index].map)
    {
        level->basemaps[map_index].map = calloc( 1, (int)(sizeof(*(level->basemaps[map_index].map)) * (x_size+1)*(z_size+1)) );
    }

    if (min_y <= max_y) dir = 1;
    else
    {
        dir = 0;
        tmp = min_y;
        min_y = max_y;
        max_y = tmp;
    }

	delta = (max_y - min_y) / ( (x_size <= 0) ? 1 : (x_size-1) );

	for( x = 0; x < x_size; x++)
    {
		if ( dir )
        {
            if ( x == x_size-1 ) y = max_y;
            else y = x*delta + min_y;
        }
		else
        {
            y = max_y - (x*delta);
        }

		if ( x_cont != 0 )
        {
            if ( dir > 0 )
            {
                if ( x+rx >= x_cont ) y = max_y; // connect with the wall more smoothly
            }
            else
            {
                if ( x+rx <= x_cont ) y = max_y;
            }
		}

		for ( z = 0; z < z_size; z++)
        {
			level->basemaps[map_index].map[(int)(x + z*x_size)] = y;
			//printf("map[%d] = %f\n",(int)(x + z*x_size),y);
		}
		//printf("x:%f y:%f delta:%f\n",x,y,delta);
		//printf("y: %f\n",y);
	}

	return;
}

void load_level(char *filename)
{
    char *buf = NULL;
    size_t size, len, sblen;
    ptrdiff_t pos, oldpos;
    char *command;
    char *value;
    char *scriptbuf = NULL;
    char string[MAX_BUFFER_LEN] = {""};
    s_spawn_entry next;
    s_model *tempmodel, *cached_model;

    int i = 0, j = 0, crlf = 0;
    int player_max = MAX_PLAYERS;
    char bgPath[MAX_BUFFER_LEN] = {""}, fnbuf[MAX_BUFFER_LEN];
    s_loadingbar bgPosi = {0, 0, {0,0}, {0,0}, 0, 0};
    char musicPath[MAX_BUFFER_LEN] = {""};
    u32 musicOffset = 0;

    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";

    ArgList arglist2;
    char argbuf2[MAX_ARG_LEN + 1] = "";

    levelCommands cmd;
    levelCommands cmd2;
    int line = 0;
    char *errormessage = NULL;
    char *scriptname = NULL;
    Script *tempscript = NULL;
    s_drawmethod *dm;
    s_layer *bgl;
    int (*panels)[3] = NULL;
    int *order = NULL;
    int panelcount = 0;
    int exit_blocked = 0, exit_hole = 0;
    char maskPath[MAX_BUFFER_LEN] = {""};

    unload_level();

    printf("Level Loading:   '%s'\n", filename);



    getRamStatus(BYTES);

    if(isLoadingScreenTypeBg(loadingbg[1].set))
    {
        if(custBkgrds)
        {
            strcpy(string, custBkgrds);
            strcat(string, "loading2");
            load_background(string);
        }
        else
        {
            load_cached_background("data/bgs/loading2");
        }
        clearscreen(vscreen);
        spriteq_clear();
        standard_palette(1);
    }

    if(isLoadingScreenTypeBar(loadingbg[1].set))
    {
        lifebar_colors();
        init_colourtable();
    }

    update_loading(&loadingbg[1], -1, 1); // initialize the update screen

    memset(&next, 0, sizeof(next));

    level = calloc(1, sizeof(*level));
    if(!level)
    {
        errormessage = "load_level() #1 FATAL: Out of memory!";
        goto lCleanup;
    }
    len = strlen(filename);
    level->name = malloc(len + 1);

    if(!level->name)
    {
        errormessage = "load_level() #1 FATAL: Out of memory!";
        goto lCleanup;
    }
    strcpy(level->name, filename);

    // Allocate memory for player spawn - only as much as we need.
    player_max = levelsets[current_set].maxplayers;
    level->spawn = calloc(player_max, sizeof(*level->spawn));

    // Default player spawn Y position just above the screen top.
    for(i = 0; i < player_max && level->spawn; i++)
    {
        level->spawn[i].y = videomodes.vRes + 60;
    }

    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        errormessage = "Unable to load level file!";
        goto lCleanup;
    }

    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           = DAMAGE_FROM_ENEMY_ON;
    level->nohit            = DAMAGE_FROM_PLAYER_ON;        // Default able to hit the other player
    level->setweap          = 0;
    level->maxtossspeed     = default_level_maxtossspeed;
    level->maxfallspeed     = default_level_maxfallspeed;
    level->gravity          = default_level_gravity;
    level->scrolldir        = SCROLL_RIGHT;
    level->scrollspeed      = 1;
    level->cameraxoffset    = 0;
    level->camerazoffset    = 0;
    level->boss_slow        = BOSS_SLOW_ON;
    level->bossescount      = 0;
    level->numbosses        = 0;
    blendfx[BLEND_MULTIPLY] = 1;
    bgtravelled             = 0;
    vbgtravelled            = 0;
    traveltime              = 0;
    texttime                = 0;
    nopause                 = 0;
    nofadeout               = 0;
    noscreenshot            = 0;

    panel_width = panel_height = frontpanels_loaded = 0;

    //reset_playable_list(1);

    // Now interpret the contents of buf line by line
    pos = 0;
    while(pos < size)
    {
        line++;
        ParseArgs(&arglist, buf + pos, argbuf);
        command = GET_ARG(0);
        cmd = getLevelCommand(levelcmdlist, command);
        switch(cmd)
        {
        case CMD_LEVEL_LOADINGBG:
            load_background(GET_ARG(1));
            errormessage = fill_s_loadingbar(&bgPosi, GET_INT_ARG(2), GET_INT_ARG(3), GET_INT_ARG(4), GET_INT_ARG(5), GET_INT_ARG(6), GET_INT_ARG(7), GET_INT_ARG(8), GET_INT_ARG(9));
            if (errormessage)
            {
                goto lCleanup;
            }
            standard_palette(1);
            lifebar_colors();
            init_colourtable();
            update_loading(&bgPosi, -1, 1); // initialize the update screen
            break;
        case CMD_LEVEL_MUSICFADE:
            memset(&next, 0, sizeof(next));
            next.musicfade = GET_FLOAT_ARG(1);
            break;
        case CMD_LEVEL_MUSIC:
            value = GET_ARG(1);
            strncpy(string, value, MAX_BUFFER_LEN - 1);
            musicOffset = atol(GET_ARG(2));
            if(loadingmusic)
            {
                music(string, 1, musicOffset);
                musicPath[0] = 0;
            }
            else
            {
                oldpos = pos;
                // Go to next line
                pos += getNewLineStart(buf + pos);
#define GET_ARG2(z) arglist2.count > z ? arglist2.args[z] : ""
                if(pos < size)
                {
                    ParseArgs(&arglist2, buf + pos, argbuf2);
                    command = GET_ARG2(0);
                    cmd2 = getLevelCommand(levelcmdlist, command);
                }
                else
                {
                    cmd2 = (levelCommands) 0;
                }

                if(cmd2 == CMD_LEVEL_AT)
                {
                    if(next.musicfade == 0)
                    {
                        memset(&next, 0, sizeof(next));
                    }
                    strncpy(next.music, string, MAX_BUFFER_LEN);
                    next.musicoffset = musicOffset;
                }
                else
                {
                    strncpy(musicPath, string, MAX_BUFFER_LEN);
                }
                pos = oldpos;
#undef GET_ARG2
            }
            break;
        case CMD_LEVEL_ALLOWSELECT:
            load_playable_list(buf + pos);
            break;
        case CMD_LEVEL_LOAD:
#ifdef DEBUG
            printf("load_level: load %s, %s\n", GET_ARG(1), filename);
#endif
            tempmodel = findmodel(GET_ARG(1));
            if (!tempmodel)
            {
                load_cached_model(GET_ARG(1), filename, GET_INT_ARG(2));
            }
            else
            {
                update_model_loadflag(tempmodel, GET_INT_ARG(2));
            }
            break;
        case CMD_LEVEL_ALPHAMASK:
            strncpy(maskPath, GET_ARG(1), MAX_BUFFER_LEN - 1);
            break;
        case CMD_LEVEL_BACKGROUND:
        case CMD_LEVEL_BGLAYER:
        case CMD_LEVEL_LAYER:
        case CMD_LEVEL_FGLAYER:
            __realloc(level->layers, level->numlayers);
            bgl = &(level->layers[level->numlayers]);

            if(cmd == CMD_LEVEL_BACKGROUND || cmd == CMD_LEVEL_BGLAYER)
            {
                i = 0;
                bgl->z = MIN_INT;
            }
            else
            {
                i = 1;
                bgl->z =  GET_FLOAT_ARG(2);
                if(cmd == CMD_LEVEL_FGLAYER)
                {
                    bgl->z += FRONTPANEL_Z;
                }
            }

            if(cmd == CMD_LEVEL_BACKGROUND)
            {
                if(bgPath[0])
                {
                    errormessage = "Background is already defined!";
                    goto lCleanup;
                }
                value = GET_ARG(1);
                strcpy(bgPath, value);
                bgl->oldtype = BGT_BACKGROUND;
            }
            else if(cmd == CMD_LEVEL_BGLAYER)
            {
                bgl->oldtype = BGT_BGLAYER;
            }
            else if(cmd == CMD_LEVEL_FGLAYER)
            {
                bgl->oldtype = BGT_FGLAYER;
            }
            else if(cmd == CMD_LEVEL_LAYER)
            {
                bgl->oldtype = BGT_GENERIC;
            }

            dm = &(bgl->drawmethod);
            *dm = plainmethod;

            bgl->ratio.x = GET_FLOAT_ARG(i + 2); // x ratio
            bgl->ratio.z = GET_FLOAT_ARG(i + 3); // z ratio
            bgl->offset.x = GET_INT_ARG(i + 4); // x start
            bgl->offset.z = GET_INT_ARG(i + 5); // z start
            bgl->spacing.x = GET_INT_ARG(i + 6); // x spacing
            bgl->spacing.z = GET_INT_ARG(i + 7); // z spacing
            dm->xrepeat = GET_INT_ARG(i + 8); // x repeat
            dm->yrepeat = GET_INT_ARG(i + 9); // z repeat
            dm->transbg = GET_INT_ARG(i + 10); // transparency
            dm->alpha = GET_INT_ARG(i + 11); // alpha
            dm->water.watermode = GET_INT_ARG(i + 12); // water
            if(dm->water.watermode == WATER_MODE_SHEAR)
            {
                dm->water.beginsize = GET_FLOAT_ARG(i + 13); // beginsize
                dm->water.endsize = GET_FLOAT_ARG(i + 14); // endsize
                dm->water.perspective = GET_INT_ARG(i + 15); // perspective
            }
            else
            {
                dm->water.amplitude = GET_INT_ARG(i + 13); // amplitude
                dm->water.wavelength = GET_FLOAT_ARG(i + 14); // wavelength
                dm->water.wavespeed = GET_FLOAT_ARG(i + 15); // waterspeed
            }
            bgl->bgspeedratio = GET_FLOAT_ARG(i + 16); // moving
            bgl->quake = GET_INT_ARG(i + 17); // quake
            bgl->neon = GET_INT_ARG(i + 18); // neon
            bgl->enabled = 1; // enabled

            if((GET_ARG(i + 2))[0] == 0)
            {
                bgl->ratio.x = (cmd == CMD_LEVEL_FGLAYER ? 1.5 : 0.5);
            }
            if((GET_ARG(i + 3))[0] == 0)
            {
                bgl->ratio.z = (cmd == CMD_LEVEL_FGLAYER ? 1.5 : 0.5);
            }

            if((GET_ARG(i + 8))[0] == 0)
            {
                dm->xrepeat = -1;
            }
            if((GET_ARG(i + 9))[0] == 0)
            {
                dm->yrepeat = -1;
            }
            if(cmd == CMD_LEVEL_BACKGROUND && (GET_ARG(i + 16))[0] == 0)
            {
                bgl->bgspeedratio = 1.0;
            }

            if(blendfx_is_set == 0 && dm->alpha)
            {
                blendfx[dm->alpha - 1] = 1;
            }

            if(cmd != CMD_LEVEL_BACKGROUND)
            {
                load_layer(GET_ARG(1), maskPath, level->numlayers);
            }
            level->numlayers++;
            break;
        case CMD_LEVEL_WATER:
            __realloc(level->layers, level->numlayers);
            bgl = &(level->layers[level->numlayers]);
            dm = &(bgl->drawmethod);
            *dm = plainmethod;

            bgl->oldtype = BGT_WATER;
            bgl->z = MIN_INT + 1;

            bgl->ratio.x = 0.5; // x ratio
            bgl->ratio.z = 0.5; // z ratio
            bgl->offset.x = 0; // x start
            bgl->offset.z = NaN; // z start
            bgl->spacing.x = 0; // x spacing
            bgl->spacing.z = 0; // z spacing
            dm->xrepeat = -1; // x repeat
            dm->yrepeat = 1; // z repeat
            dm->transbg = 0; // transparency
            dm->alpha = BLEND_MODE_NONE; // alpha
            dm->water.watermode = WATER_MODE_SINE;
            dm->water.amplitude = GET_INT_ARG(2); // amplitude
            dm->water.wavelength = 40; // wavelength
            dm->water.wavespeed = 1.0; // waterspeed
            bgl->bgspeedratio = 0; // moving
            bgl->enabled = 1; // enabled

            if(dm->water.amplitude < 1)
            {
                dm->water.amplitude = 1;
            }

            load_layer(GET_ARG(1), maskPath, level->numlayers);
            level->numlayers++;
            break;
        case CMD_LEVEL_DIRECTION:
            value = GET_ARG(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 || stricmp(value, "rightleft") == 0)
            {
                level->scrolldir = SCROLL_BOTH;
            }
            else if(stricmp(value, "leftright") == 0)
            {
                level->scrolldir = SCROLL_LEFTRIGHT;
            }
            else if(stricmp(value, "right") == 0)
            {
                level->scrolldir = SCROLL_RIGHT;
            }
            else if(stricmp(value, "in") == 0)
            {
                level->scrolldir = SCROLL_INWARD;
            }
            else if(stricmp(value, "out") == 0)
            {
                level->scrolldir = SCROLL_OUTWARD;
            }
            else if(stricmp(value, "inout") == 0)
            {
                level->scrolldir = SCROLL_INOUT;
            }
            else if(stricmp(value, "outin") == 0)
            {
                level->scrolldir = SCROLL_OUTIN;
            }
            break;
        case CMD_LEVEL_FACING:
            level->facing = GET_INT_ARG(1);
            break;

        case CMD_LEVEL_ROCK:
            level->rocking = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_BGSPEED:
            level->bgspeed = GET_FLOAT_ARG(1);
            if(GET_INT_ARG(2))
            {
                level->bgspeed *= -1;
            }
            break;
        case CMD_LEVEL_VBGSPEED:
            level->vbgspeed = GET_FLOAT_ARG(1);
            if(GET_INT_ARG(2))
            {
                level->vbgspeed *= -1;
            }
            break;
        case CMD_LEVEL_SCROLLSPEED:
            level->scrollspeed = GET_FLOAT_ARG(1);
            break;
        case CMD_LEVEL_MIRROR:
            level->mirror = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_BOSSMUSIC:
            strncpy(level->bossmusic, GET_ARG(1), 255);
            level->bossmusic_offset = atol(GET_ARG(2));
            break;
        case CMD_LEVEL_NOSAVE:
            nosave = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_NOFADEOUT:
            nofadeout = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_NOPAUSE:
            nopause = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_NOSCREENSHOT:
            noscreenshot = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_SETTIME:
            // If settime is found, overwrite the default 100 for time limit
            level->settime = GET_INT_ARG(1);
            if(level->settime > 100 || level->settime < 0)
            {
                level->settime = 100;
            }
            // Feb 25, 2005 - time limit loaded from individual .txt file
            break;
        case CMD_LEVEL_SETWEAP:
            // Specify a weapon for each level
            level->setweap = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_NOTIME:
            // Flag to if the time should be displayed 1 = no, else yes
            level->notime = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_NORESET:
            // Flag to if the time should be reset when players respawn 1 = no, else yes
            level->noreset = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_NOSLOW:

            if(GET_INT_ARG(1))
            {
                level->boss_slow = BOSS_SLOW_OFF;
            }

            break;
        case CMD_LEVEL_TYPE:
            level->type = GET_INT_ARG(1);    // Level type - 1 = bonus, else regular
            level->nospecial = GET_INT_ARG(2);    // Can use specials during bonus levels (default 0 - yes)

            if(GET_INT_ARG(3))
            {
                level->nohurt = DAMAGE_FROM_ENEMY_OFF;
            }

            break;
        case CMD_LEVEL_NOHIT:

            if(GET_INT_ARG(1))
            {
                level->nohit = DAMAGE_FROM_PLAYER_OFF;
            }

            break;
        case CMD_LEVEL_GRAVITY:
            level->gravity = GET_FLOAT_ARG(1);
            level->gravity /= 100;
            break;
        case CMD_LEVEL_MAXFALLSPEED:
            level->maxfallspeed = GET_FLOAT_ARG(1);
            level->maxfallspeed /= 10;
            break;
        case CMD_LEVEL_MAXTOSSSPEED:
            level->maxtossspeed = GET_FLOAT_ARG(1);
            level->maxtossspeed /= 10;
            break;
        case CMD_LEVEL_CAMERATYPE:
            cameratype = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_CAMERAOFFSET:
            level->cameraxoffset = GET_INT_ARG(1);
            level->camerazoffset = GET_INT_ARG(2);
            break;
        case CMD_LEVEL_SPAWN1:
        case CMD_LEVEL_SPAWN2:
        case CMD_LEVEL_SPAWN3:
        case CMD_LEVEL_SPAWN4:
            switch(cmd)
            {
            case CMD_LEVEL_SPAWN1:
                i = 0;
                break;
            case CMD_LEVEL_SPAWN2:
                i = 1;
                break;
            case CMD_LEVEL_SPAWN3:
                i = 2;
                break;
            case CMD_LEVEL_SPAWN4:
                i = 3;
                break;
            default:
                assert(0);
            }

            // Verify specified player index exists,
            // then set values.
            if(level->spawn && i < player_max)
            {
                level->spawn[i].x = GET_INT_ARG(1);
                level->spawn[i].z = GET_INT_ARG(2);
                level->spawn[i].y = GET_INT_ARG(3);

                if(level->spawn[i].y < 0) level->spawn[i].y = videomodes.vRes + 60;
            }

            break;
        case CMD_LEVEL_FRONTPANEL:
        case CMD_LEVEL_PANEL:
            if(level->numlayers == 0)
            {
                __realloc(level->layers, level->numlayers);
                level->numlayers = 1; // reserve for background
            }

            __realloc(level->layers, level->numlayers);
            bgl = &(level->layers[level->numlayers]);
            dm = &(bgl->drawmethod);
            *dm = plainmethod;

            bgl->oldtype = (cmd == CMD_LEVEL_FRONTPANEL ? BGT_FRONTPANEL : BGT_PANEL);

            if(bgl->oldtype == BGT_PANEL)
            {
                bgl->order = panelcount + 1;
                __realloc(panels, panelcount);
                panels[panelcount++][0] = level->numlayers;
                bgl->z = PANEL_Z;
                bgl->ratio.x = 0; // x ratio
                bgl->ratio.z = 0; // z ratio
                dm->xrepeat = 1; // x repeat
            }
            else
            {
                frontpanels_loaded++;
                bgl->z = FRONTPANEL_Z;
                bgl->ratio.x = -0.4; // x ratio
                bgl->ratio.z = 1; // z ratio
                dm->xrepeat = -1; // x repeat
            }

            bgl->bgspeedratio = 0;
            bgl->offset.z = 0;
            dm->yrepeat = 1; // z repeat
            dm->transbg = 1; // transparency
            bgl->enabled = 1; // enabled
            bgl->quake = 1; // accept quake and rock

            load_layer(GET_ARG(1), maskPath, level->numlayers);
            level->numlayers++;

            if(stricmp(GET_ARG(2), "none") != 0 && GET_ARG(2)[0])
            {
                __realloc(level->layers, level->numlayers);
                bgl = &(level->layers[level->numlayers]);
                *bgl = *(bgl - 1);
                panels[panelcount - 1][1] = level->numlayers;

                bgl->z = NEONPANEL_Z;
                bgl->neon = 1;
                bgl->gfx.handle = NULL;
                load_layer(GET_ARG(2), maskPath, level->numlayers);
                level->numlayers++;
            }

            if(stricmp(GET_ARG(3), "none") != 0 && GET_ARG(3)[0])
            {
                __realloc(level->layers, level->numlayers);
                bgl = &(level->layers[level->numlayers]);
                *bgl = *(bgl - 1);
                panels[panelcount - 1][2] = level->numlayers;
                dm = &(bgl->drawmethod);

                bgl->z = SCREENPANEL_Z;
                bgl->neon = 0;
                dm->alpha = BLEND_MODE_ALPHA;
                bgl->gfx.handle = NULL;
                load_layer(GET_ARG(3), maskPath, level->numlayers);
                level->numlayers++;
            }
            break;
        case CMD_LEVEL_STAGENUMBER:
            current_stage = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_ORDER:
            // Append to order
            value = GET_ARG(1);
            i = 0;
            while(value[i] )
            {
                j = value[i];
                // WTF ?
                if(j >= 'A' && j <= 'Z')
                {
                    j -= 'A';
                }
                else if(j >= 'a' && j <= 'z')
                {
                    j -= 'a';
                }
                else
                {
                    errormessage = "Illegal character in panel order!";
                    goto lCleanup;
                }
                __realloc(order, level->numpanels);
                __realloc(level->panels, level->numpanels);
                order[level->numpanels] = j;
                level->numpanels++;
                i++;
            }
            break;
        case CMD_LEVEL_HOLE:
            /*value = GET_ARG(1);    // ltb    1-18-05  adjustable hole sprites

            if(holesprite < 0)
            {
                if(testpackfile(value, packfile) >= 0)
                {
                    holesprite = loadsprite(value, 0, 0, pixelformat);    // ltb 1-18-05  load new hole sprite
                }
                else
                {
                    holesprite = loadsprite("data/sprites/hole", 0, 0, pixelformat);    // ltb 1-18-05  no new sprite load the default
                }
            }*/

            addhole(GET_FLOAT_ARG(1), GET_FLOAT_ARG(2), GET_FLOAT_ARG(3), GET_FLOAT_ARG(4), GET_FLOAT_ARG(5), GET_FLOAT_ARG(6), GET_FLOAT_ARG(7), GET_FLOAT_ARG(8), GET_FLOAT_ARG(9));
            break;
        case CMD_LEVEL_WALL:
            addwall(GET_FLOAT_ARG(1), GET_FLOAT_ARG(2), GET_FLOAT_ARG(3), GET_FLOAT_ARG(4), GET_FLOAT_ARG(5), GET_FLOAT_ARG(6), GET_FLOAT_ARG(7), GET_FLOAT_ARG(8), GET_FLOAT_ARG(9));
            break;
        case CMD_LEVEL_BASEMAP:
            addbasemap(GET_FLOAT_ARG(1), GET_FLOAT_ARG(2), GET_FLOAT_ARG(3), GET_FLOAT_ARG(4), GET_FLOAT_ARG(5), GET_FLOAT_ARG(6), GET_FLOAT_ARG(7));
            break;
        case CMD_LEVEL_PALETTE:
            __realloc(level->palettes, level->numpalettes);
            if(!load_palette(level->palettes[level->numpalettes], GET_ARG(1)))
            {
                errormessage = "Failed to create colour conversion tables for level! (Out of memory?)";
                goto lCleanup;
            }
            level->numpalettes++;
            break;
        case CMD_LEVEL_UPDATESCRIPT:
        case CMD_LEVEL_UPDATEDSCRIPT:
        case CMD_LEVEL_KEYSCRIPT:
        case CMD_LEVEL_LEVELSCRIPT:
        case CMD_LEVEL_ENDLEVELSCRIPT:
            switch(cmd)
            {
            case CMD_LEVEL_UPDATESCRIPT:
                tempscript = &(level->update_script);
                scriptname = "levelupdatescript";
                break;
            case CMD_LEVEL_UPDATEDSCRIPT:
                tempscript = &(level->updated_script);
                scriptname = "levelupdatedscript";
                break;
            case CMD_LEVEL_KEYSCRIPT:
                tempscript = &(level->key_script);
                scriptname = "levelkeyscript";
                break;
            case CMD_LEVEL_LEVELSCRIPT:
                tempscript = &(level->level_script);
                scriptname = "levelscript";
                break;
            case CMD_LEVEL_ENDLEVELSCRIPT:
                tempscript = &(level->endlevel_script);
                scriptname = "endlevelscript";
                break;
            default:
                assert(0);

            }
            // this function is for model script, but it is OK for now
            pos += lcmHandleCommandScripts(&arglist, buf + pos, tempscript, scriptname, filename, 1, 1);
            break;
        case CMD_LEVEL_BLOCKED:
            exit_blocked = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_ENDHOLE:
            exit_hole = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_WAIT:
            // Clear spawn thing, set wait state instead
            memset(&next, 0, sizeof(next));
            next.wait = 1;
            break;
        case CMD_LEVEL_NOJOIN:
            // Clear spawn thing, set nojoin state instead
            memset(&next, 0, sizeof(next));
            next.nojoin = 1;
            break;
        case CMD_LEVEL_CANJOIN:
            memset(&next, 0, sizeof(next));
            next.nojoin = -1;
            break;
        case CMD_LEVEL_SHADOWCOLOR:
            memset(&next, 0, sizeof(next));
            next.shadowcolor = parsecolor(GET_ARG(1));
            break;
        case CMD_LEVEL_SHADOWALPHA:
            memset(&next, 0, sizeof(next));
            next.shadowalpha = GET_INT_ARG(1);
            if(blendfx_is_set == 0 && next.shadowalpha > 0)
            {
                blendfx[next.shadowalpha - 1] = 1;
            }
            break;
        case CMD_LEVEL_SHADOWOPACITY:
            memset(&next, 0, sizeof(next));
            next.shadowopacity = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_LIGHT:
            memset(&next, 0, sizeof(next));
            next.light.x = GET_INT_ARG(1);
            next.light.y = GET_INT_ARG(2);
            if(next.light.y == 0)
            {
                next.light.y = 64;
            }
            break;
        case CMD_LEVEL_SCROLLZ:
            memset(&next, 0, sizeof(next));
            next.scrollminz = GET_INT_ARG(1);
            next.scrollmaxz = GET_INT_ARG(2);
            next.scrollminz |= 0x80000000;
            break;
        case CMD_LEVEL_SCROLLX:
            //shall we keep blockade?
            memset(&next, 0, sizeof(next));
            next.scrollminx = GET_INT_ARG(1);
            next.scrollmaxx = GET_INT_ARG(2);
            next.scrollminx |= 0x80000000;
            break;
        case CMD_LEVEL_BLOCKADE:
            // now x scroll can be limited by this
            memset(&next, 0, sizeof(next));
            next.blockade = GET_INT_ARG(1);
            if(next.blockade == 0)
            {
                next.blockade = -1;
            }
            break;
        case CMD_LEVEL_SETPALETTE:
            // change system palette
            memset(&next, 0, sizeof(next));
            next.palette = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_GROUP:
            // Clear spawn thing, set group instead
            memset(&next, 0, sizeof(next));
            next.groupmin = GET_INT_ARG(1);
            next.groupmax = GET_INT_ARG(2);
            if(next.groupmax < 1)
            {
                next.groupmax = 1;
            }
            if(next.groupmin < 1)
            {
                next.groupmin = 100;
            }
            break;
        case CMD_LEVEL_SPAWN:
            // Back to defaults
            next.spawnplayer_count = 0;
            memset(&next, 0, sizeof(next));
            next.index = next.item_properties.index = next.weaponindex = -1;
            // Name of entry to be spawned
            // Load model (if not loaded already)
            cached_model = findmodel(GET_ARG(1));
#ifdef DEBUG
            printf("load_level: spawn %s, %s, cached: %p\n", GET_ARG(1), filename, cached_model);
#endif
            if(cached_model)
            {
                tempmodel = cached_model;
            }
            else
            {
                tempmodel = load_cached_model(GET_ARG(1), filename, 0);
            }
            if(tempmodel)
            {
                next.name = tempmodel->name;
                next.index = get_cached_model_index(next.name);
                next.spawntype = SPAWN_TYPE_LEVEL;  //2011_03_23, DC; Spawntype SPAWN_TYPE_LEVEL.

                crlf = 1;
            }
            break;
        case CMD_LEVEL_2PSPAWN:
            // Entity only for 2p game
            next.spawnplayer_count = 1;
            break;
        case CMD_LEVEL_3PSPAWN:
            // Entity only for 3p game
            next.spawnplayer_count = 2;
            break;
        case CMD_LEVEL_4PSPAWN:
            // Entity only for 4p game
            next.spawnplayer_count = 3;
            break;
        case CMD_LEVEL_BOSS:
            next.boss = GET_INT_ARG(1);
            level->bossescount += next.boss ? 1 : 0;
            level->numbosses = level->bossescount;
            break;

        case CMD_LEVEL_FACTION_GROUP_DAMAGE_DIRECT:

            next.faction.damage_direct = faction_get_flags_from_arglist(&arglist);
            break;

        case CMD_LEVEL_FACTION_GROUP_DAMAGE_INDIRECT:

            next.faction.damage_indirect = faction_get_flags_from_arglist(&arglist);
            break;

        case CMD_LEVEL_FACTION_GROUP_HOSTILE:

            next.faction.hostile = faction_get_flags_from_arglist(&arglist);
            break;

        case CMD_LEVEL_FACTION_GROUP_MEMBER:

            next.faction.member = faction_get_flags_from_arglist(&arglist);
            break;

        case CMD_LEVEL_FACTION_TYPE_DAMAGE_DIRECT:

            next.faction.type_damage_direct = get_type_from_arglist(&arglist);
            break;

        case CMD_LEVEL_FACTION_TYPE_DAMAGE_INDIRECT:

            next.faction.type_damage_indirect = get_type_from_arglist(&arglist);
            break;

        case CMD_LEVEL_FACTION_TYPE_HOSTILE:

            next.faction.type_hostile = get_type_from_arglist(&arglist);
            break;

        case CMD_LEVEL_FLIP:
            next.flip = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_HEALTH:
            next.health[0] = next.health[1] = next.health[2] = next.health[3] = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_2PHEALTH:
            // Health the spawned entity will have if 2 people are playing
            next.health[1] = next.health[2] = next.health[3] = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_3PHEALTH:
            // Health the spawned entity will have if 2 people are playing
            next.health[2] = next.health[3] = GET_INT_ARG(1);  //4player
            break;
        case CMD_LEVEL_4PHEALTH:
            // Health the spawned entity will have if 2 people are playing
            next.health[3] = GET_INT_ARG(1);  //4player
            break;
        case CMD_LEVEL_MP:
            // mp values to put max mp for player by tails
            next.mp = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_SCORE:
            // So score can be overriden in the levels .txt file
            next.score = GET_INT_ARG(1);
            if(next.score == -1)
            {
                next.score = 0;    // So negative values cannot be added
            }
            next.multiple = GET_INT_ARG(2);
            if(next.multiple == -1)
            {
                next.multiple = 0;    // So negative values cannot be added
            }
            break;
        case CMD_LEVEL_NOLIFE:
            // Flag to determine if entity life is shown when hit
            next.nolife = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_ALIAS:
            // Alias (name displayed) of entry to be spawned
            strncpy(next.alias, GET_ARG(1), MAX_NAME_LEN - 1);
            break;
        case CMD_LEVEL_MAP:
            // Colourmap for new entry
            next.colourmap = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_ALPHA:
            // Item to be contained by new entry
            next.alpha = GET_INT_ARG(1);
            if(blendfx_is_set == 0 && next.alpha)
            {
                blendfx[next.alpha - 1] = 1;
            }
            break;
        case CMD_LEVEL_DYING:
            // Used to store which remake corresponds with the dying flash
            next.dying = GET_INT_ARG(1);
            next.per1 = GET_INT_ARG(2);
            next.per2 = GET_INT_ARG(3);
            next.dying2 = GET_INT_ARG(4);
            break;
        case CMD_LEVEL_ITEM:
        case CMD_LEVEL_2PITEM:
        case CMD_LEVEL_3PITEM:
        case CMD_LEVEL_4PITEM:
            switch(cmd)
            {
                // Item to be contained by new entry
            case CMD_LEVEL_ITEM:
                next.item_properties.player_count = 0;
                break;
            case CMD_LEVEL_2PITEM:
                next.item_properties.player_count = 1;
                break;
            case CMD_LEVEL_3PITEM:
                next.item_properties.player_count = 2;
                break;
            case CMD_LEVEL_4PITEM:
                next.item_properties.player_count = 3;
                break;
            default:
                assert(0);
            }
            // Load model (if not loaded already)
            cached_model = findmodel(GET_ARG(1));
            if(cached_model)
            {
                tempmodel = cached_model;
            }
            else
            {
                tempmodel = load_cached_model(GET_ARG(1), filename, 0);
            }
            if(tempmodel)
            {
                next.item = tempmodel->name;
                next.item_properties.index = get_cached_model_index(next.item);
            }
            break;
        case CMD_LEVEL_ITEMMAP:
            next.item_properties.colorset = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_ITEMHEALTH:
            next.item_properties.health = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_ITEMALIAS:
            strncpy(next.item_properties.alias, GET_ARG(1), MAX_NAME_LEN - 1);
            break;
        case CMD_LEVEL_WEAPON:
            //spawn with a weapon 2007-2-12 by UTunnels
            // Load model (if not loaded already)
            cached_model = findmodel(GET_ARG(1));
            if(cached_model)
            {
                tempmodel = cached_model;
            }
            else
            {
                tempmodel = load_cached_model(GET_ARG(1), filename, 0);
            }
            if(tempmodel)
            {
                next.weapon = tempmodel->name;
                next.weaponindex = get_cached_model_index(next.weapon);
            }
            break;
        case CMD_LEVEL_AGGRESSION:
            // Aggression can be set per spawn.
            next.aggression = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_CREDIT:
            next.credit = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_ITEMTRANS:
        case CMD_LEVEL_ITEMALPHA:
            next.item_properties.alpha = GET_INT_ARG(1);
            break;
        case CMD_LEVEL_COORDS:
            next.position.x = GET_FLOAT_ARG(1);
            next.position.z = GET_FLOAT_ARG(2);
            next.position.y = GET_FLOAT_ARG(3);
            break;
        case CMD_LEVEL_SPAWNSCRIPT:
            pos += lcmHandleCommandScripts(&arglist, buf + pos, &next.spawnscript, "Level spawn entry script", filename, 0, 1);
            break;
        case CMD_LEVEL_AT_SCRIPT:
            if(!Script_IsInitialized(&next.spawnscript))
            {
                Script_Init(&next.spawnscript, "Level spawn entry script", filename, 1);
            }
            fetchInlineScript(buf, &scriptbuf, &pos, &sblen);
            if(!Script_AppendText(&next.spawnscript, scriptbuf, filename))
            {
                errormessage = "Unable to parse level spawn entry script.\n";
                goto lCleanup;
            }
            free(scriptbuf);
            scriptbuf = NULL;
            break;
        case CMD_LEVEL_AT:
            // Place entry on queue
            next.at = GET_INT_ARG(1);

            if(Script_IsInitialized(&next.spawnscript))
            {
                Script_Compile(&next.spawnscript);
            }

            __realloc(level->spawnpoints, level->numspawns);
            memcpy(&level->spawnpoints[level->numspawns], &next, sizeof(next));
            level->numspawns++;

            // And clear...
            memset(&next, 0, sizeof(next));
            break;
        default:
            if(command && command[0])
            {
                if(!handle_txt_include(command, &arglist, &filename, fnbuf, &buf, &pos, &size))
                {
                    printf("Command '%s' not understood in file '%s'!\n", command, filename);
                }
            }
        }

        // Go to next line
        pos += getNewLineStart(buf + pos);

        if(isLoadingScreenTypeBar(bgPosi.set) || isLoadingScreenTypeBg(bgPosi.set))
        {
            update_loading(&bgPosi, pos, size);
        }
        //update_loading(bgPosi[0]+videomodes.hShift, bgPosi[1]+videomodes.vShift, bgPosi[2], bgPosi[3]+videomodes.hShift, bgPosi[4]+videomodes.vShift, pos, size, bgPosi[5]);
        else
        {
            update_loading(&loadingbg[1], pos, size);
        }
    }

    if(level->numpanels < 1)
    {
        errormessage = "Level error: level has no panels";
        goto lCleanup;
    }

    if(bgPath[0])
    {
        clearscreen(vscreen);
        spriteq_clear();
        load_background(bgPath);
    }
    else if(background)
    {
        unload_background();
    }

    if(level->numlayers)
    {

        for(i = 0; i < level->numlayers; i++)
        {
            bgl = &(level->layers[i]);
            switch(bgl->oldtype)
            {
            case BGT_WATER: // default water hack
                bgl->offset.z = background ? background->height : level->layers[0].size.y;
                dm = &(bgl->drawmethod);
                if(level->rocking)
                {
                    dm->water.watermode = WATER_MODE_SHEAR;
                    dm->water.beginsize = 1.0;
                    dm->water.endsize = 1 + bgl->size.y / 11.0;
                    dm->water.perspective = WATER_PERSPECTIVE_NONE;
                    bgl->bgspeedratio = 2;
                }
                break;
            case BGT_PANEL:
                panel_width = bgl->size.x;
                panel_height = bgl->size.y;
            case BGT_FRONTPANEL:
                if(level->scrolldir & (SCROLL_UP | SCROLL_DOWN))
                {
                    bgl->ratio.z = 1;
                }
                break;
            case BGT_BACKGROUND:
                bgl->gfx.screen = background;
                bgl->size.x = background->width;
                bgl->size.y = background->height;
                level->background = bgl;
                break;
            default:
                break;
            }
            load_layer(NULL, NULL, i);
        }

        if(level->background)
        {
            __realloc(level->layersref, level->numlayersref);
            level->layersref[level->numlayersref] = *(level->background);
            level->background = (s_layer *)(size_t)level->numlayersref++;
        }


        // non-panel type layers
        for(i = 0; i < level->numlayers; i++)
        {
            bgl = &(level->layers[i]);
            if(bgl->oldtype != BGT_PANEL && bgl->oldtype != BGT_BACKGROUND)
            {
                __realloc(level->layersref, level->numlayersref);
                level->layersref[level->numlayersref] = *bgl;
                bgl = &(level->layersref[level->numlayersref]);
                switch(bgl->oldtype)
                {
                case BGT_BGLAYER:
                    __realloc(level->bglayers, level->numbglayers);
                    level->bglayers[level->numbglayers++] = (s_layer *)(size_t)level->numlayersref;
                    break;
                case BGT_FGLAYER:
                    __realloc(level->fglayers, level->numfglayers);
                    level->fglayers[level->numfglayers++] = (s_layer *)(size_t)level->numlayersref;
                    break;
                case BGT_WATER:
                    __realloc(level->waters, level->numwaters);
                    level->waters[level->numwaters++] = (s_layer *)(size_t)level->numlayersref;
                    break;
                case BGT_GENERIC:
                    __realloc(level->genericlayers, level->numgenericlayers);
                    level->genericlayers[level->numgenericlayers++] = (s_layer *)(size_t)level->numlayersref;
                    break;
                case BGT_FRONTPANEL:
                    bgl->offset.x = level->numfrontpanels * bgl->size.x;
                    bgl->spacing.x = (frontpanels_loaded - 1) * bgl->size.x;
                    __realloc(level->frontpanels, level->numfrontpanels);
                    level->frontpanels[level->numfrontpanels++] = (s_layer *)(size_t)level->numlayersref;
                    break;
                default:
                    break;
                }
                level->numlayersref++;
            }
        }

        //panels, normal neon screen
        for(i = 0; i < level->numpanels; i++)
        {
            for(j = 0; j < 3; j++)
            {
                if(panels[order[i]][j])
                {
                    __realloc(level->layersref, level->numlayersref);
                    level->layersref[level->numlayersref] = level->layers[panels[order[i]][j]];
                    bgl = &(level->layersref[level->numlayersref]);
                    bgl->offset.x = panel_width * i;
                    level->panels[i][j] = (s_layer *)(size_t)level->numlayersref;
                    level->numlayersref++;
                }
            }
        }

        //fix realloc junk pointers
        bgl = level->layersref;
        level->background = bgl + (size_t)level->background;
        for(i = 0; i < level->numpanels; i++)
            for(j = 0; j < 3; j++)
            {
                level->panels[i][j] = bgl + (size_t)level->panels[i][j];
            }
        for(i = 0; i < level->numfrontpanels; i++)
        {
            level->frontpanels[i] = bgl + (size_t)level->frontpanels[i];
        }
        for(i = 0; i < level->numbglayers; i++)
        {
            level->bglayers[i] = bgl + (size_t)level->bglayers[i];
        }
        for(i = 0; i < level->numfglayers; i++)
        {
            level->fglayers[i] = bgl + (size_t)level->fglayers[i];
        }
        for(i = 0; i < level->numgenericlayers; i++)
        {
            level->genericlayers[i] = bgl + (size_t)level->genericlayers[i];
        }
        for(i = 0; i < level->numwaters; i++)
        {
            level->waters[i] = bgl + (size_t)level->waters[i];
        }

    }

    if(musicPath[0])
    {
        music(musicPath, 1, musicOffset);
    }

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

    if(level->width < videomodes.hRes)
    {
        level->width = videomodes.hRes;
    }

    scrollmaxx = level->width - videomodes.hRes;
    scrollmaxz = panel_height;

    if(level->scrolldir & SCROLL_LEFT)
    {
        advancex = (float)(level->width - videomodes.hRes);
    }
    else if(level->scrolldir & SCROLL_INWARD)
    {
        advancey = (float)(panel_height - videomodes.vRes);
    }

    if(exit_blocked)
    {
        addwall(level->width - 30, PLAYER_MAX_Z, -panel_height, 0, 1000, 1000, panel_height, MAX_WALL_HEIGHT + 1, 0);
    }
    if(exit_hole)
    {
        addhole(level->width, PLAYER_MAX_Z, -panel_height, 0, 1000, 1000, panel_height, 0, 0);
    }

    if(crlf)
    {
        printf("\n");
    }
    printf("Level Loaded:    '%s'\n", level->name);
    totalram = getSystemRam(BYTES);
    freeram = getFreeRam(BYTES);
    usedram = getUsedRam(BYTES);
    printf("Total Ram: %11"PRIu64" Bytes ( %5"PRIu64" MB )\n Free Ram: %11"PRIu64" Bytes ( %5"PRIu64" MB )\n Used Ram: %11"PRIu64" Bytes ( %5"PRIu64" MB )\n",
        totalram,
        totalram >> 20,
        freeram,
        freeram >> 20,
        usedram,
        usedram >> 20);
    printf("Total sprites mapped: %d\n\n", sprites_loaded);

lCleanup:

    if (panels)
    {
        free(panels);
    }
    if (order)
    {
        free(order);
    }

    if(buf)
    {
        free(buf);
    }
    if(scriptbuf)
    {
        free(scriptbuf);
    }

    if(errormessage)
    {
        borShutdown(1, "ERROR: load_level, file %s, line %d, message: %s", filename, line, errormessage);
    }
}





/////////////////////////////////////////////////////////////////////////////
//  Status                                                                  //
/////////////////////////////////////////////////////////////////////////////
void bar(int x, int y, int value, int maxvalue, s_barstatus *pstatus)
{
    int max = 100;
    int len;
    int alphabg = 0;
    int bgindex;
    int colourindex;
    int forex;
    int forey;
    int forew; 
    int foreh; 
    int bkw; 
    int bkh;
    s_drawmethod dm = plainmethod;
        
    if(pstatus->config_flags & STATUS_CONFIG_GRAPH_VERTICAL)
    {
        max = pstatus->size.y;
    }
    else
    {
        max = pstatus->size.x;
    }

    if (value > maxvalue)
    {
        value = maxvalue;
    }

    if(pstatus->config_flags & STATUS_CONFIG_GRAPH_RATIO)
    {
        colourindex = colorbars ? (value * 5 / maxvalue + 1) : 2;
        bgindex = colorbars ? 8 : 1;
        len = value * max / maxvalue;
        if (!len && value)
        {
            len = 1;
        }
        alphabg = BLEND_MULTIPLY + 1;
    }
    else
    {
        if (max > maxvalue)
        {
            max = maxvalue;
        }
        if (colorbars)
        {
            if (value <= max / 4)
            {
                bgindex = 0;
                colourindex = 1;
            }
            else if (value <= max / 2)
            {
                bgindex = 0;
                colourindex = 2;
            }
            else if (value <= max)
            {
                bgindex = 0;
                colourindex = 3;
            }
            else
            {
                colourindex = value / (max + 1) + 3;
                bgindex = colourindex - 1;
            }
            if (colourindex > 10)
            {
                colourindex = bgindex = 10;
            }
        }
        else
        {
            colourindex = 2;
            bgindex = value > max ? 5 : 1;
        }

        len = value % max;
        if (!len && value)
        {
            len = max;
        }
        alphabg = value > max ? 0 : (BLEND_MULTIPLY + 1);        
    }

    if(pstatus->config_flags & STATUS_CONFIG_GRAPH_VERTICAL)
    {
        forex = x;
        forey = pstatus->config_flags & STATUS_CONFIG_GRAPH_INVERT ? y : (y + max - len);
        bkw = forew = pstatus->size.x;
        foreh = len;
        bkh = max;
    }
    else
    {
        forex = pstatus->config_flags & STATUS_CONFIG_GRAPH_INVERT ? (x + max - len) : x;
        forey = y;
        forew = len;
        bkw = max;
        bkh = foreh = pstatus->size.y;
    }

    if(!pstatus->colourtable)
    {
        pstatus->colourtable = &hpcolourtable;
    }

    dm.alpha = alphabg;
    spriteq_add_box(x + 1, y + 1, bkw, bkh, HUD_Z + 1 + pstatus->backlayer, (*pstatus->colourtable)[bgindex], &dm);
    spriteq_add_box(forex + 1, forey + 1, forew, foreh, HUD_Z + 2 + pstatus->barlayer, (*pstatus->colourtable)[colourindex], NULL);

    if(!(pstatus->config_flags & STATUS_CONFIG_BORDER_DISABLE))
    {
        spriteq_add_line(x, y, x + bkw + 1, y, HUD_Z + 3 + pstatus->borderlayer, color_white, NULL); //Top border.
        spriteq_add_line(x, y + bkh + 1, x + bkw + 1, y + bkh + 1, HUD_Z + 3 + pstatus->borderlayer, color_white, NULL); //Bottom border.
        spriteq_add_line(x, y + 1, x, y + bkh, HUD_Z + 3 + pstatus->borderlayer, color_white, NULL); //Left border.
        spriteq_add_line(x + bkw + 1, y + 1, x + bkw + 1, y + bkh, HUD_Z + 3 + pstatus->borderlayer, color_white, NULL); //Right border.
        spriteq_add_line(x, y + bkh + 2, x + bkw + 1, y + bkh + 2, HUD_Z + pstatus->borderlayer, color_black, NULL); //Bottom shadow.
        spriteq_add_line(x + bkw + 2, y + 1, x + bkw + 2, y + bkh + 2, HUD_Z + pstatus->borderlayer, color_black, NULL); //Right shadow.
    }
}

void goto_mainmenu(int flag)
{
    goto_mainmenu_flag = 1|(flag<<1);
    escape_flag = flag; //Kratus (20-04-21) Added the new "escape" flag in the select screen, has the same effect as the esc key but now accessible by the "gotomainmenu" function
}

void static backto_mainmenu()
{
    int i = 0;
    s_screen *pausebuffer = allocscreen(videomodes.hRes, videomodes.vRes, PIXEL_32);

    copyscreen(pausebuffer, vscreen);
    spriteq_draw(pausebuffer, 0, MIN_INT, MAX_INT, 0, 0);
    spriteq_clear();
    spriteq_add_screen(0, 0, MIN_INT, pausebuffer, NULL, 0);
    spriteq_lock();

    sound_pause_music(1);
    sound_pause_sample(1);
    _pause = 2;

    if ( (goto_mainmenu_flag&1) ) goto_mainmenu_flag -= 1;

    update(1, 0);

    for(i = 0; i < MAX_PLAYERS; i++)
    {
        player[i].lives = 0;
    }
    endgame = 2;
    //sound_pause_music(0);
    //sound_pause_sample(0);

    _pause = 0;
    bothnewkeys = 0;
    spriteq_unlock();
    spriteq_clear();
    freescreen(&pausebuffer);

    return;
}

void pausemenu()
{
    int pauselector = 0;
    int quit = 0;
    int controlp = 0, i;
    int newkeys;
    s_set_entry *set = levelsets + current_set;
    s_screen *pausebuffer = allocscreen(videomodes.hRes, videomodes.vRes, PIXEL_32);

    copyscreen(pausebuffer, vscreen);
    spriteq_draw(pausebuffer, 0, MIN_INT, MAX_INT, 0, 0);
    spriteq_clear();
    spriteq_add_screen(0, 0, MIN_INT, pausebuffer, NULL, 0);
    spriteq_lock();

    for(i = 0; i < set->maxplayers; i++)
    {
        if(player[i].ent && (player[i].newkeys & FLAG_START))
        {
            controlp = i;
            break;
        }
    }

    _pause = 2;
    bothnewkeys = 0;
    while(!quit)
    {
        _menutextmshift(pauseoffset[4], -2, 0, pauseoffset[5], pauseoffset[6], Tr("Pause"));
        _menutextmshift((pauselector == 0)?pauseoffset[1]:pauseoffset[0], -1, 0, pauseoffset[2], pauseoffset[3], Tr("Continue"));
        _menutextmshift((pauselector == 1)?pauseoffset[1]:pauseoffset[0],  0, 0, pauseoffset[2], pauseoffset[3], Tr("End Game"));

        update(1, 0);

        newkeys = player[controlp].newkeys;

        if(newkeys & (FLAG_MOVEUP | FLAG_MOVEDOWN))
        {
            pauselector ^= 1;
            sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
        }
        if(newkeys & FLAG_START)
        {
            if(pauselector)
            {
                for(i = 0; i < MAX_PLAYERS; i++)
                {
                    player[i].lives = 0;
                }
                endgame = 2;
            }
            quit = 1;
            sound_pause_music(0);
            sound_pause_sample(0);
            sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            pauselector = 0;
        }
        if(newkeys & FLAG_ESC)
        {
            quit = 1;
            sound_pause_music(0);
            sound_pause_sample(0);
            sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            pauselector = 0;
        }
        if(newkeys & FLAG_SCREENSHOT)
        {
            _pause = 1;
            sound_pause_music(1);
            sound_pause_sample(1);
            sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            menu_options();
        }
    }
    _pause = 0;
    bothnewkeys = 0;
    spriteq_unlock();
    spriteq_clear();
    freescreen(&pausebuffer);
}

unsigned getFPS(void)
{
    static u64 lasttick = 0, framerate = 0;
    u64 curtick = timer_uticks();
    if(lasttick > curtick)
    {
        lasttick = curtick;
    }
    framerate = (framerate + (curtick - lasttick)) / 2;
    lasttick = curtick;
    if(!framerate)
    {
        // if the frame took 0 ms, act like it was 1 ms instead
        return 1000;
    }

    return round(1.0e6 / framerate);

}

void updatestatus()
{

    int dt;
    int i;
    s_model *model = NULL;
    s_set_entry *set = levelsets + current_set;

    for(i = 0; i < set->maxplayers; i++)
    {
        if(player[i].ent)
        {
            ;
        }
        else if(player[i].joining && player[i].name[0])
        {
            model = findmodel(player[i].name);
            if((player[i].playkeys & FLAG_ANYBUTTON || skipselect[i][0]) && !freezeall && !nojoin)    // Can't join while animations are frozen
            {
                player[i].lives = PLAYER_LIVES;            // to address new lives settings
                player[i].joining = 0;
                player[i].hasplayed = 1;
                player[i].spawnhealth = model->health;
                player[i].spawnmp = model->mp;

                spawnplayer(i);

                execute_join_script(i);

                player[i].disablekeys = player[i].playkeys = player[i].newkeys = player[i].releasekeys = 0;

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

                if(!level->noreset)
                {
                    timeleft = level->settime * COUNTER_SPEED;    // Feb 24, 2005 - This line moved here to set custom time
                }

            }
            else if(player[i].playkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT))
            {
                model = ((player[i].playkeys & FLAG_MOVELEFT) ? prevplayermodeln : nextplayermodeln)(model, i);
                strcpy(player[i].name, model->name);

                player[i].colourmap = (colourselect && (set->nosame & 2)) ? nextcolourmapn(model, -1, i) : 0;

                player[i].playkeys = 0;
            }
            // don't like a characters color try a new one!
            else if(player[i].playkeys & (FLAG_MOVEUP | FLAG_MOVEDOWN) && colourselect)
            {
                player[i].colourmap = ((player[i].playkeys & FLAG_MOVEUP) ? nextcolourmapn : prevcolourmapn)(model, player[i].colourmap, i);

                player[i].playkeys = 0;
            }
        }
        else if( !nojoin && (player[i].credits || credits || (!player[i].hasplayed && noshare)) )
        {
            if(player[i].playkeys & FLAG_START)
            {
                player[i].lives = 0;
                model = skipselect[i][0] ? findmodel(skipselect[i]) : nextplayermodeln(NULL, i);
                strncpy(player[i].name, model->name, MAX_NAME_LEN);

                player[i].colourmap = (colourselect && (set->nosame & 2)) ? nextcolourmapn(model, -1, i) : 0;

                player[i].joining = 1;
                player[i].disablekeys = player[i].playkeys = player[i].newkeys = player[i].releasekeys = 0;

                if(!level->noreset)
                {
                    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(!(global_config.cheats & CHEAT_OPTIONS_CREDITS_ACTIVE))
                {
                    if(noshare)
                    {
                        --player[i].credits;
                    }
                    else
                    {
                        --credits;
                    }
                    if(set->continuescore == 1)
                    {
                        player[i].score = 0;
                    }
                    if(set->continuescore == 2)
                    {
                        player[i].score = player[i].score + 1;
                    }
                }
            }
        }
    }// end of for

    dt = timeleft / COUNTER_SPEED;
    if(dt >= 99)
    {
        dt      = 99;
        oldtime = 99;
    }
    if(dt <= 0)
    {
        dt      = 0;
        oldtime = 99;
    }

    if (is_total_timeover) timetoshow = 0;
    else timetoshow = dt;

    if(timetoshow < oldtime || oldtime == 0)
    {
        execute_timetick_script(timetoshow, go_time);
        oldtime = timetoshow;
    }

    if(dt > 0 && !is_total_timeover)
    {
        showtimeover = 0;
    }

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

        if(dt < GAME_SPEED / 2)
        {
            global_config.showgo = 1;
            screen_status |= IN_SCREEN_SHOW_GO_ARROW; //Kratus (04-2022) Added the "showgo" event accessible by script
            
            if(gosound == 0 )
            {

                if(global_sample_list.go >= 0)
                {
                    sound_play_sample(global_sample_list.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
        {
            global_config.showgo = gosound = 0;    //26-12-2004 Resets go sample after loop so it can be played again next time
            screen_status &= ~IN_SCREEN_SHOW_GO_ARROW; //Kratus (04-2022) Added the "showgo" event accessible by script
        }
    }
    else
    {
        global_config.showgo = 0;
        screen_status &= ~IN_SCREEN_SHOW_GO_ARROW; //Kratus (04-2022) Added the "showgo" event accessible by script
    }

}


// Caskey, Damon V.
// 2016-11-16
//
// Draw dot onto screen to indicate actual entity position,
// with text readout of Base, X, Y, and Z coordinates directly below.
void draw_properties_entity(entity *entity, int offset_z, int color, s_drawmethod *drawmethod)
{
    #define FONT_LABEL          1
	#define FONT_VALUE          0
	#define TEXT_MARGIN_Y       1
    #define OFFSET_LAYER       -2

    // Array keys for the list of items 
	// we want to display
    enum
    {
		DRAW_PROPERTIES_KEY_NAME,
		DRAW_PROPERTIES_KEY_BASE,
		DRAW_PROPERTIES_KEY_POS,
		DRAW_PROPERTIES_KEY_STATUS,
		DRAW_PROPERTIES_ARRAY_SIZE	// Array size, so always last.
    };

    typedef struct
    {
        s_axis_principal_int        position;
        s_axis_plane_vertical_int   size;
    } draw_coords;

    s_axis_plane_vertical_int   screen_offset;  // Base location calculated from screen offsets.
    s_axis_plane_vertical_int   base_pos;       // Entity position with screen offsets applied.
    draw_coords                 box;            // On screen coords for display elements.

    int i;                              // Counter.
    int str_offset_x;                   // Calculated offset of text for centering.
	int label_width_max;
	int str_width_max;                  // largest string width.
    int str_height_max;                 // Largest string height.
    size_t str_size;                    // Memory size of string.

	char		*output_label[DRAW_PROPERTIES_ARRAY_SIZE];
	const char  *output_format[DRAW_PROPERTIES_ARRAY_SIZE]; // Format ("%d, %s ..").
    char        *output_value[DRAW_PROPERTIES_ARRAY_SIZE];  // Final string to display position.
	
    // Let's build the format for information
	// we want to display.
	output_format[DRAW_PROPERTIES_KEY_NAME]		= "%s";
	output_format[DRAW_PROPERTIES_KEY_BASE]		= "%d";
	output_format[DRAW_PROPERTIES_KEY_POS]		= "%d, %d, %d";
	output_format[DRAW_PROPERTIES_KEY_STATUS]	= "%d, %d";
	
	// Double pass method for unknown string size. 
	//
	// 1. Build the label.
	//
	// 2. Attempt to copy 0 chars to unallocated 
	// buffer and record how many characters
	// would be needed, plus 1 for the NULL terminator
	// and record as a string_size.
	// 
	// 3. Allocate memory using the string size.
	//
	// 4. Copy formatted string to allocated buffer
	// for real.
	//
	// Repeat for each line item we want to display.
	//
	// TO: Work this into a loop. Main obstacle is
	// the number of format string inputs vary depending
	// on type of property.

	// Name
	output_label[DRAW_PROPERTIES_KEY_NAME] = "Name: ";
	output_value[DRAW_PROPERTIES_KEY_NAME] = NULL;
	str_size = snprintf(output_value[DRAW_PROPERTIES_KEY_NAME], 0, output_format[DRAW_PROPERTIES_KEY_NAME], entity->model->name) + 1;
	output_value[DRAW_PROPERTIES_KEY_NAME] = malloc(str_size);
	snprintf(output_value[DRAW_PROPERTIES_KEY_NAME], str_size, output_format[DRAW_PROPERTIES_KEY_NAME], entity->model->name);

	// Base
	output_label[DRAW_PROPERTIES_KEY_BASE] = "Base: ";
	output_value[DRAW_PROPERTIES_KEY_BASE] = NULL;
	str_size = snprintf(output_value[DRAW_PROPERTIES_KEY_BASE], 0, output_format[DRAW_PROPERTIES_KEY_BASE], (int)entity->base) + 1;
	output_value[DRAW_PROPERTIES_KEY_BASE] = malloc(str_size);
	snprintf(output_value[DRAW_PROPERTIES_KEY_BASE], str_size, output_format[DRAW_PROPERTIES_KEY_BASE], (int)entity->base);

	// XYZ
	output_label[DRAW_PROPERTIES_KEY_POS] = "X,Y,Z: ";
	output_value[DRAW_PROPERTIES_KEY_POS] = NULL;
	str_size = snprintf(output_value[DRAW_PROPERTIES_KEY_POS], 0, output_format[DRAW_PROPERTIES_KEY_POS], (int)entity->position.x, (int)entity->position.y, (int)entity->position.z) + 1;
	output_value[DRAW_PROPERTIES_KEY_POS] = malloc(str_size);
	snprintf(output_value[DRAW_PROPERTIES_KEY_POS], str_size, output_format[DRAW_PROPERTIES_KEY_POS], (int)entity->position.x, (int)entity->position.y, (int)entity->position.z);

	// HP & MP
	output_label[DRAW_PROPERTIES_KEY_STATUS] = "HP, MP: ";
	output_value[DRAW_PROPERTIES_KEY_STATUS] = NULL;
	str_size = snprintf(output_value[DRAW_PROPERTIES_KEY_STATUS], 0, output_format[DRAW_PROPERTIES_KEY_STATUS], (int)entity->energy_state.health_current, (int)entity->energy_state.mp_current) + 1;
	output_value[DRAW_PROPERTIES_KEY_STATUS] = malloc(str_size);
	snprintf(output_value[DRAW_PROPERTIES_KEY_STATUS], str_size, output_format[DRAW_PROPERTIES_KEY_STATUS], (int)entity->energy_state.health_current, (int)entity->energy_state.mp_current);


	// Get the largest string X and Y space. For X find the largest
	// label and value, then add them. For Y, just get height of 
	// largest font.
    label_width_max = font_string_width_max(output_label, DRAW_PROPERTIES_ARRAY_SIZE, FONT_LABEL);
	str_width_max = label_width_max + font_string_width_max(output_value, DRAW_PROPERTIES_ARRAY_SIZE, FONT_VALUE);

	if (fontheight(FONT_LABEL) > fontheight(FONT_VALUE))
	{
		str_height_max = fontheight(FONT_LABEL);
	}
	else
	{
		str_height_max = fontheight(FONT_VALUE);
	}

    // Get our base offsets from screen vs. location.
    screen_offset.x = screenx - ((entity->modeldata.quake_config & QUAKE_CONFIG_DISABLE_SELF) ? 0 : gfx_x_offset);
    screen_offset.y = screeny - ((entity->modeldata.quake_config & QUAKE_CONFIG_DISABLE_SELF) ? 0 : gfx_y_offset);

    // Get entity position with screen offsets.
    base_pos.x = entity->position.x - screen_offset.x;
    base_pos.y = (entity->position.z - offset_z) - entity->position.y - screen_offset.y;

    // Get a value of half the text width.
    // We can use this to center our text
    // on the entity.
    str_offset_x = str_width_max / 2;

    // Apply text offset.
    box.position.x = base_pos.x - str_offset_x;

    box.position.y = base_pos.y;
    box.position.z = entity->position.z + offset_z;

    // Draw position dot.
    // The +1 to Z is a quick fix - offset_z
    // distorts the dot's vertical position
    // instead of just adjusting Z.
    spriteq_add_dot(base_pos.x, base_pos.y, box.position.z+1, color, drawmethod);

    // Print each item text.
    for(i = 0; i < DRAW_PROPERTIES_ARRAY_SIZE; i++)
    {
        // If the item string exists then
        // we can find a position, print it to
        // the screen, and free up allocated memory.
        if(output_value[i])
        {
           // Add font height and margin to Y position.
            base_pos.y += (str_height_max + TEXT_MARGIN_Y);

            // Print label to the screen. The value X
			// position adds maximum label width, plus
			// 25% the width a single value character.
            font_printf(box.position.x, base_pos.y, FONT_LABEL, OFFSET_LAYER, output_label[i]);
			font_printf(box.position.x + label_width_max + (fontmonowidth(FONT_VALUE) * 0.25), base_pos.y, FONT_VALUE, OFFSET_LAYER, output_value[i]);

            // Release memory allocated for the value strings.
            free(output_value[i]);
        }
    }

    return;

    // Remove local constants.
    #undef FONT
    #undef TEXT_MARGIN_Y
    #undef OFFSET_LAYER
}

// Caskey, Damon V.
// 2016-11-16
//
// Convert entity's world position to screen
// position and draw a box.
void draw_box_on_entity(entity *entity, int pos_x, int pos_y, int pos_z, int size_w, int size_h, int offset_z, int color, s_drawmethod *drawmethod)
{
    s_axis_plane_vertical_int   screen_offset;  // Base location calculated from screen offsets.
    s_axis_plane_vertical_int   base_pos;       // Entity position with screen offsets applied.

    typedef struct
    {
        s_axis_principal_int        position;
        s_axis_plane_vertical_int   size;
    } draw_coords;

    draw_coords box;
    int far_x = 0;
    int far_y = 0;

    // Get our base offsets from screen vs. location.
    screen_offset.x = screenx - ((entity->modeldata.quake_config & QUAKE_CONFIG_DISABLE_SELF) ? 0 : gfx_x_offset);
    screen_offset.y = screeny - ((entity->modeldata.quake_config & QUAKE_CONFIG_DISABLE_SELF) ? 0 : gfx_y_offset);

    // Get entity position with screen offsets.
    base_pos.x = entity->position.x - screen_offset.x;
    base_pos.y = (entity->position.z - offset_z) - entity->position.y - screen_offset.y;

    // Now apply drawing coords to position.
    box.size.x = size_w - pos_x;

    // Don't forget to accommodate for
    // entity direction.
    if(entity->direction == DIRECTION_RIGHT)
    {
        box.position.x = base_pos.x + pos_x;
    }
    else
    {
        box.position.x = base_pos.x - (box.size.x + pos_x);
    }

    box.position.y = base_pos.y + pos_y;
    box.size.y = (base_pos.y + size_h) - box.position.y;

    box.position.z = pos_z + offset_z;

    

    // Add box to que.
    spriteq_add_box(box.position.x, box.position.y, box.size.x, box.size.y, box.position.z, color, drawmethod);

    far_x = box.position.x + (box.size.x - 1);
    far_y = box.position.y + box.size.y;

    spriteq_add_line(box.position.x, box.position.y, far_x, box.position.y, box.position.z, color, NULL); // Top
    spriteq_add_line(box.position.x, far_y, far_x, far_y, box.position.z, color, NULL); // Bottom
    spriteq_add_line(box.position.x, box.position.y, box.position.x, far_y, box.position.z, color, NULL);
    spriteq_add_line(far_x, box.position.y, far_x, far_y, box.position.z, color, NULL);    
}

/*
* Caskey, Damon V.
* Unknown date ~2018
* 
* Draw collision on screen as visual boxes.
*/
void draw_visual_debug()
{
    #define LOCAL_COLOR_BLUE        _makecolour(0, 0, 255)
    #define LOCAL_COLOR_GREEN       _makecolour(0, 255, 0)
    #define LOCAL_COLOR_MAGENTA     _makecolour(255, 0, 255)
    #define LOCAL_COLOR_WHITE       _makecolour(255, 255, 255)

    int i;
    s_hitbox            *coords;
    s_drawmethod        drawmethod = plainmethod;
    entity              *entity;

    s_collision_attack* collision_attack_cursor;
    s_collision_body* collision_body_cursor;

	int range_y_min = 0;
	int range_y_max = 0;

    drawmethod.alpha = BLEND_MODE_ALPHA;

    for(i=0; i<ent_max; i++)
    {
        entity = ent_list[i];

        // Entity must exist.
        if(!entity)
        {
            continue;
        }

        // Entity must be alive.
        if(entity->death_state & DEATH_STATE_DEAD)
        {
            continue;
        }

        // Basic properties (Name, position, HP, etc.).
        if(savedata.debuginfo & DEBUG_DISPLAY_PROPERTIES)
        {
            draw_properties_entity(entity, 0, LOCAL_COLOR_WHITE, NULL);
        }

        // Range debug requested?
        if(savedata.debuginfo & DEBUG_DISPLAY_RANGE)
        {
			// Range is calculated a bit differently than body/attack 
			// boxes, which is what the draw_box_on_entity() funciton
			// is meant for. For Y axis, We need to invert the value, 
			// and place them in opposiing parameters (Max Y into 
			// function's min Y parameter, and and min Y into function's
			// max Y parameter).

			range_y_min =  -entity->animation->range.y.min;
			range_y_max =  -entity->animation->range.y.max;
			
            draw_box_on_entity(entity, entity->animation->range.x.min, range_y_max, entity->position.z+1, entity->animation->range.x.max, range_y_min, -1, LOCAL_COLOR_GREEN, &drawmethod);
        }        

        /* Collision attack debug requested? */
        if(savedata.debuginfo & DEBUG_DISPLAY_COLLISION_ATTACK)
        {
            /* Animation has collision? */
            if(entity->animation->collision_attack)
            {
                collision_attack_cursor = entity->animation->collision_attack[entity->animpos];

                while (collision_attack_cursor != NULL)
                {                    
                    coords = collision_attack_cursor->coords;
                    draw_box_on_entity(entity, coords->x, coords->y, entity->position.z + 1, coords->width, coords->height, 2, LOCAL_COLOR_MAGENTA, &drawmethod);                    

                    collision_attack_cursor = collision_attack_cursor->next;
                }

                collision_attack_cursor = NULL;
            }
        }

        /* Collision body debug requested? */
        if (savedata.debuginfo & DEBUG_DISPLAY_COLLISION_BODY)
        {
            /* Animation has collision? */
            if (entity->animation->collision_body)
            {
                collision_body_cursor = entity->animation->collision_body[entity->animpos];

                while (collision_body_cursor != NULL)
                {
                    coords = collision_body_cursor->coords;
                    draw_box_on_entity(entity, coords->x, coords->y, entity->position.z + 1, coords->width, coords->height, 2, LOCAL_COLOR_BLUE, &drawmethod);

                    collision_body_cursor = collision_body_cursor->next;
                }

                collision_body_cursor = NULL;
            }
        }
    }

    #undef LOCAL_COLOR_BLUE
    #undef LOCAL_COLOR_GREEN
    #undef LOCAL_COLOR_MAGENTA
    #undef LOCAL_COLOR_WHITE
}


void predrawstatus()
{

    int icon = 0;
    int i;
    unsigned long tmp;
    s_set_entry *set = levelsets + current_set;
    s_model *model = NULL;
    s_drawmethod drawmethod = plainmethod;

    if(bgicon >= 0)
    {
        spriteq_add_sprite(videomodes.hShift + bgicon_offsets[0], savedata.windowpos + bgicon_offsets[1], bgicon_offsets[2], bgicon, NULL, 0);
    }
    if(olicon >= 0)
    {
        spriteq_add_sprite(videomodes.hShift + olicon_offsets[0], savedata.windowpos + olicon_offsets[1], olicon_offsets[2], olicon, NULL, 0);
    }

    for(i = 0; i < set->maxplayers; i++)
    {
        if(player[i].ent)
        {
            tmp = player[i].score; //work around issue on 64bit where sizeof(long) != sizeof(int)
            if(!pscore[i][2] && !pscore[i][3] && !pscore[i][4] && !pscore[i][5])
            {
                font_printf(videomodes.shiftpos[i] + pscore[i][0], savedata.windowpos + pscore[i][1], pscore[i][6], 0, (scoreformat ? "%s - %09lu" : "%s - %lu"), (char *)(player[i].ent->name), tmp);
            }
            else
            {
                font_printf(videomodes.shiftpos[i] + pscore[i][0], savedata.windowpos + pscore[i][1], pscore[i][6], 0, "%s", player[i].ent->name);
                font_printf(videomodes.shiftpos[i] + pscore[i][2], savedata.windowpos + pscore[i][3], pscore[i][6], 0, "-");
                font_printf(videomodes.shiftpos[i] + pscore[i][4], savedata.windowpos + pscore[i][5], pscore[i][6], 0, (scoreformat ? "%09lu" : "%lu"), tmp);
            }

            if(player[i].ent->energy_state.health_current <= 0)
            {
                icon = player[i].ent->modeldata.icon.die;
            }
            else if(player[i].ent->inpain & IN_PAIN_HIT)
            {
                icon = player[i].ent->modeldata.icon.pain;
            }
            else if(player[i].ent->getting)
            {
                icon = player[i].ent->modeldata.icon.get;
            }
            else
            {
                icon = player[i].ent->modeldata.icon.def;
            }

            if(icon >= 0)
            {
                drawmethod.table = player[i].ent->modeldata.icon.usemap ? player[i].ent->colourmap : NULL;
                spriteq_add_sprite(videomodes.shiftpos[i] + picon[i][0], savedata.windowpos + picon[i][1], 10000, icon, &drawmethod, 0);
            }

            if(player[i].ent->weapent)
            {                
                if(player[i].ent->weapent->modeldata.icon.weapon >= 0)
                {
                    drawmethod.table = player[i].ent->weapent->modeldata.icon.usemap ? player[i].ent->weapent->colourmap : NULL;
                    spriteq_add_sprite(videomodes.shiftpos[i] + piconw[i][0], savedata.windowpos + piconw[i][1], 10000, player[i].ent->weapent->modeldata.icon.weapon, &drawmethod, 0);
                }

                if(player[i].ent->weapent->modeldata.weapon_properties.weapon_state & WEAPON_STATE_LIMITED_USE && player[i].ent->weapent->modeldata.weapon_properties.use_count)
                {
                    font_printf(videomodes.shiftpos[i] + pshoot[i][0], savedata.windowpos + pshoot[i][1], pshoot[i][2], 0, "%u", player[i].ent->weapent->modeldata.weapon_properties.use_count);
                }
            }

            if(player[i].ent->modeldata.mp)
            {
                drawmethod.table = player[i].ent->modeldata.icon.usemap ? player[i].ent->colourmap : NULL;
                if(player[i].ent->modeldata.icon.mphigh > 0 && (player[i].ent->energy_state.mp_old >= (player[i].ent->modeldata.mp * .66)))
                {
                    spriteq_add_sprite(videomodes.shiftpos[i] + mpicon[i][0], savedata.windowpos + mpicon[i][1], 10000, player[i].ent->modeldata.icon.mphigh, &drawmethod, 0);
                }
                else if(player[i].ent->modeldata.icon.mpmed > 0 && (player[i].ent->energy_state.mp_old >= (player[i].ent->modeldata.mp * .33) && player[i].ent->energy_state.mp_old < (player[i].ent->modeldata.mp * .66)))
                {
                    spriteq_add_sprite(videomodes.shiftpos[i] + mpicon[i][0], savedata.windowpos + mpicon[i][1], 10000, player[i].ent->modeldata.icon.mpmed, &drawmethod, 0);
                }
                else if(player[i].ent->modeldata.icon.mplow > 0 && (player[i].ent->energy_state.mp_old >= 0 && player[i].ent->energy_state.mp_old < (player[i].ent->modeldata.mp * .33)))
                {
                    spriteq_add_sprite(videomodes.shiftpos[i] + mpicon[i][0], savedata.windowpos + mpicon[i][1], 10000, player[i].ent->modeldata.icon.mplow, &drawmethod, 0);
                }
                else if(player[i].ent->modeldata.icon.mphigh > 0 && player[i].ent->modeldata.icon.mpmed == -1 && player[i].ent->modeldata.icon.mplow == -1)
                {
                    spriteq_add_sprite(videomodes.shiftpos[i] + mpicon[i][0], savedata.windowpos + mpicon[i][1], 10000, player[i].ent->modeldata.icon.mphigh, &drawmethod, 0);
                }
            }

            font_printf(videomodes.shiftpos[i] + plifeX[i][0], savedata.windowpos + plifeX[i][1], plifeX[i][2], 0, "x");
            font_printf(videomodes.shiftpos[i] + plifeN[i][0], savedata.windowpos + plifeN[i][1], plifeN[i][2], 0, "%i", player[i].lives);

            if(rush[0] && player[i].ent->rush.count > 1 && _time <= player[i].ent->rush.time)
            {
                font_printf(videomodes.shiftpos[i] + prush[i][0], prush[i][1], rush[2], 0, "%s", rush_names[0]);
                font_printf(videomodes.shiftpos[i] + prush[i][2], prush[i][3], rush[3], 0, "%i", player[i].ent->rush.count);

                if(rush[0] != 2)
                {
                    font_printf(videomodes.shiftpos[i] + prush[i][4], prush[i][5], rush[4], 0, "%s", rush_names[1]);
                    font_printf(videomodes.shiftpos[i] + prush[i][6], prush[i][7], rush[5], 0, "%i", player[i].ent->rush.max);
                }
            }

            if(rush[0] == 2)
            {
                font_printf(videomodes.shiftpos[i] + prush[i][4], prush[i][5], rush[4], 0, "%s", rush_names[1]);
                font_printf(videomodes.shiftpos[i] + prush[i][6], prush[i][7], rush[5], 0, "%i", player[i].ent->rush.max);
            }

            if(player[i].ent->opponent && !player[i].ent->opponent->modeldata.nolife)
            {
                // Displays life unless overridden by flag
                font_printf(videomodes.shiftpos[i] + ename[i][0], savedata.windowpos + ename[i][1], ename[i][2], 0, player[i].ent->opponent->name);
                if(player[i].ent->opponent->energy_state.health_current <= 0)
                {
                    icon = player[i].ent->opponent->modeldata.icon.die;
                }
                else if(player[i].ent->opponent->inpain & IN_PAIN_HIT)
                {
                    icon = player[i].ent->opponent->modeldata.icon.pain;
                }
                else if(player[i].ent->opponent->getting)
                {
                    icon = player[i].ent->opponent->modeldata.icon.get;
                }
                else
                {
                    icon = player[i].ent->opponent->modeldata.icon.def;
                }

                if(icon >= 0)
                {
                    drawmethod.table = player[i].ent->opponent->modeldata.icon.usemap ? player[i].ent->opponent->colourmap : NULL;
                    spriteq_add_sprite(videomodes.shiftpos[i] + eicon[i][0], savedata.windowpos + eicon[i][1], 10000, icon, &drawmethod, 0); // Feb 26, 2005 - Changed to opponent->map so icons don't pallete swap with die animation
                }
            }
        }
        else if(player[i].joining && player[i].name[0])
        {
            model = findmodel(player[i].name);
            font_printf(videomodes.shiftpos[i] + pnameJ[i][0], savedata.windowpos + pnameJ[i][1], pnameJ[i][6], 0, player[i].name);
            if(nojoin)
            {
                font_printf(videomodes.shiftpos[i] + pnameJ[i][2], savedata.windowpos + pnameJ[i][3], pnameJ[i][6], 0, Tr("Please Wait"));
            }
            else
            {
                font_printf(videomodes.shiftpos[i] + pnameJ[i][2], savedata.windowpos + pnameJ[i][3], pnameJ[i][6], 0, Tr("Select Hero"));
            }
            icon = model->icon.def;

            if(icon >= 0)
            {
                drawmethod.table = model->icon.usemap ? model_get_colourmap(model, player[i].colourmap) : NULL;
                spriteq_add_sprite(videomodes.shiftpos[i] + picon[i][0], picon[i][1], 10000, icon, &drawmethod, 0);
            }
        }
        else if(player[i].credits || credits || (!player[i].hasplayed && noshare))
        {
            if(player[i].credits && ((_time / (GAME_SPEED * 2)) & 1))
            {
                font_printf(videomodes.shiftpos[i] + pnameJ[i][4], savedata.windowpos + pnameJ[i][5], pnameJ[i][6], 0, Tr("Credit %i"), player[i].credits);
            }
            else if(credits && ((_time / (GAME_SPEED * 2)) & 1))
            {
                font_printf(videomodes.shiftpos[i] + pnameJ[i][4], savedata.windowpos + pnameJ[i][5], pnameJ[i][6], 0, Tr("Credit %i"), credits);
            }
            else if(!player[i].hasplayed  && ((_time / (GAME_SPEED * 2)) & 1))
            {
                int showcredits = (!noshare) ? credits : CONTINUES;

                font_printf(videomodes.shiftpos[i] + pnameJ[i][4], savedata.windowpos + pnameJ[i][5], pnameJ[i][6], 0, Tr("Credit %i"), showcredits);
            }
            else if(nojoin)
            {
                font_printf(videomodes.shiftpos[i] + pnameJ[i][4], savedata.windowpos + pnameJ[i][5], pnameJ[i][6], 0, Tr("Please Wait"));
            }
            else
            {
                font_printf(videomodes.shiftpos[i] + pnameJ[i][4], savedata.windowpos + pnameJ[i][5], pnameJ[i][6], 0, Tr("Press Start"));
            }
        }
        else
        {
            font_printf(videomodes.shiftpos[i] + pnameJ[i][4], savedata.windowpos + pnameJ[i][5], pnameJ[i][6], 0, Tr("GAME OVER"));
        }
    }// end of for

	// If any of the debug flags are enabled, let's
	// output debug data to end user.
    if(savedata.debuginfo)
    {		
        draw_visual_debug();
    }

    if(timeicon >= 0)
    {
        spriteq_add_sprite(videomodes.hShift + timeicon_offsets[0], savedata.windowpos + timeicon_offsets[1], 10000, timeicon, NULL, 0);
    }
    if(!level->notime)
    {
        font_printf(videomodes.hShift + timeloc[0] + 2, savedata.windowpos + timeloc[1] + 2, timeloc[5], 0, "%02i", timetoshow);
    }
    if(showtimeover)
    {
        font_printf(videomodes.hShift + 113, videomodes.vShift + savedata.windowpos + 110, timeloc[5], 0, Tr("TIME OVER"));
    }

    if(global_config.showgo)
    {
        if(level->scrolldir & SCROLL_LEFT) //TODO: upward and downward go
        {

            if(golsprite >= 0)
            {
                spriteq_add_sprite(40, 60 + videomodes.vShift, 10000, golsprite, NULL, 0);    // new sprite for left direction
            }
            else
            {
                drawmethod.table = 0;
                drawmethod.flipx = 1;
                spriteq_add_sprite(40, 60 + videomodes.vShift, 10000, gosprite, &drawmethod, 0);
            }
        }
        else if(level->scrolldir & SCROLL_RIGHT)
        {
            spriteq_add_sprite(videomodes.hRes - 40, 60 + videomodes.vShift, 10000, gosprite, NULL, 0);
        }
    }

    // Performance info.
    if(savedata.debuginfo & DEBUG_DISPLAY_PERFORMANCE)
    {
        spriteq_add_box(0, videomodes.dOffset - 12, videomodes.hRes, videomodes.dOffset + 12, LAYER_Z_LIMIT_BOX_MAX, 0, NULL);
        font_printf(2, videomodes.dOffset - 10, 0, LAYER_Z_LIMIT_MAX, Tr("FPS: %03d"), getFPS());
        font_printf(videomodes.hRes / 2, videomodes.dOffset - 10, 0, LAYER_Z_LIMIT_MAX, Tr("Free Ram: %s KB"), commaprint(freeram / KBYTES));
        font_printf(2, videomodes.dOffset, 0, LAYER_Z_LIMIT_MAX, Tr("Sprites: %d / %d"), spriteq_get_sprite_count(), spriteq_get_sprite_max());
        font_printf(videomodes.hRes / 2, videomodes.dOffset, 0, LAYER_Z_LIMIT_MAX, Tr("Used Ram: %s KB"), commaprint(usedram / KBYTES));
    }
}

/*
* Caskey, Damon V.
* 2023-03-08
* 
* Allocate an object.
*/
s_barstatus* bar_status_allocate_object()
{
    s_barstatus* result;

    /* Allocate memory with 0 values and get the pointer. */
    result = calloc(1, sizeof(*result));

    return result;
}

// draw boss status on screen
void drawenemystatus(entity *ent)
{
    s_drawmethod drawmethod;
    s_barstatus* status;
    int icon;

    status = ent->modeldata.hud_popup;

    if(status->name_position.x > -1000 && status->name_position.y > -1000)
    {
        font_printf(status->name_position.x, status->name_position.y, 0, 0, "%s", ent->name);
    }

    if(ent->modeldata.icon.position.x > -1000 &&  ent->modeldata.icon.position.y > -1000)
    {
        if(ent->energy_state.health_current <= 0)
        {
            icon = ent->modeldata.icon.die;
        }
        else if(ent->inpain & IN_PAIN_HIT)
        {
            icon = ent->modeldata.icon.pain;
        }
        else if(ent->getting)
        {
            icon = ent->modeldata.icon.get;
        }
        else
        {
            icon = ent->modeldata.icon.def;
        }

        if(icon >= 0)
        {
            drawmethod = plainmethod;
            drawmethod.table = ent->modeldata.icon.usemap ? ent->colourmap : NULL;
            spriteq_add_sprite(ent->modeldata.icon.position.x, ent->modeldata.icon.position.y, HUD_Z, icon, &drawmethod, 0);
        }
    }

    if(ent->modeldata.health && status->graph_position.x > -1000 && status->graph_position.y > -1000)
    {
        bar(status->graph_position.x, status->graph_position.y, ent->energy_state.health_old, ent->modeldata.health, status);
    }
}


void drawstatus()
{
    int i;

    for(i = 0; i < MAX_PLAYERS; i++)
    {
        if(player[i].ent)
        {
            // Health bars
            bar(videomodes.shiftpos[i] + plife[i][0], savedata.windowpos + plife[i][1], player[i].ent->energy_state.health_old, player[i].ent->modeldata.health, &lbarstatus);
            if(player[i].ent->opponent && !player[i].ent->opponent->modeldata.nolife && player[i].ent->opponent->modeldata.health)
            {
                bar(videomodes.shiftpos[i] + elife[i][0], savedata.windowpos + elife[i][1], player[i].ent->opponent->energy_state.health_old, player[i].ent->opponent->modeldata.health, &olbarstatus);    // Tied in with the nolife flag
            }
            // Draw mpbar
            if(player[i].ent->modeldata.mp)
            {
                bar(videomodes.shiftpos[i] + pmp[i][0], savedata.windowpos + pmp[i][1], player[i].ent->energy_state.mp_old, player[i].ent->modeldata.mp, &mpbarstatus);
            }
        }
    }

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

void update_loading(s_loadingbar *s,  int value, int max)
{
    static unsigned int lasttick = 0;
    static unsigned int soundtick = 0;
    static unsigned int keybtick = 0;
    int pos_x = s->bar_position.x + videomodes.hShift;
    int pos_y = s->bar_position.y + videomodes.vShift;
    int size_x = s->bsize;
    int text_x = s->text_position.x + videomodes.hShift;
    int text_y = s->text_position.y + videomodes.vShift;
    unsigned int ticks = timer_gettick();

    if(ticks - soundtick > 20)
    {
        sound_update_music();
        soundtick = ticks;
    }

    if(ticks - keybtick > 250)
    {
        control_update(playercontrolpointers, 1); // Respond to exit and/or fullscreen requests from user/OS
        keybtick = ticks;
    }


    if(ticks - lasttick > s->refreshMs || value < 0 || value == max)   // Negative value forces a repaint. used when only bg is drawn for the first time
    {
        spriteq_clear();
        execute_loading_script(value, max);
        if(s->set)
        {
            if (value < 0)
            {
                value = 0;
            }
            if(isLoadingScreenTypeBar(s->set))
            {
                loadingbarstatus.size.x = size_x;
                bar(pos_x, pos_y, value, max, &loadingbarstatus);
            }
            font_printf(text_x, text_y, s->tf, 0, Tr("Loading..."));
            if(isLoadingScreenTypeBg(s->set))
            {
                if(background)
                {
                    putscreen(vscreen, background, 0, 0, NULL);
                }
                else
                {
                    clearscreen(vscreen);
                }
            }
            spriteq_draw(vscreen, 0, MIN_INT, MAX_INT, 0, 0);
            video_copy_screen(vscreen);
            spriteq_clear();
        }
        else if(value < 0)   // Original BOR v1.0029 used this method.  Since loadingbg is optional, we should print this one again.
        {
            clearscreen(vscreen);
            font_printf(120 + videomodes.hShift, 110 + videomodes.vShift, 0, 0, Tr("Loading..."));
            spriteq_draw(vscreen, 0, MIN_INT, MAX_INT, 0, 0);
            video_copy_screen(vscreen);
            spriteq_clear();
        }
        lasttick = ticks;
    }
}

void addscore(int playerindex, int add)
{
    unsigned int s = 0;
    unsigned int next1up = 0;
    ScriptVariant var; // used for execute script
    Script *cs;

    if(playerindex < 0)
    {
        return;    //dont score if <0, e.g., npc damage enemy, enemy damage enemy
    }

    playerindex &= 3;

    s = player[playerindex].score;
    cs = score_script + playerindex;

    if (lifescore > 0) next1up = ((s / lifescore) + 1) * lifescore;
	else lifescore = 0;

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

    while(s > next1up)
    {

        if(global_sample_list.one_up >= 0)
        {
            sound_play_sample(global_sample_list.one_up, 0, savedata.effectvol, savedata.effectvol, 100);
        }

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

    player[playerindex].score = s;

    //execute a script then
    if(Script_IsInitialized(cs))
    {
        ScriptVariant_Init(&var);
        ScriptVariant_ChangeType(&var, VT_INTEGER);
        var.lVal = (LONG)add;
        Script_Set_Local_Variant(cs, "score", &var);
        Script_Execute(cs);
        ScriptVariant_Clear(&var);
        Script_Set_Local_Variant(cs, "score", &var);
    }
}




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

void free_ent(entity *e)
{
    if(!e)
    {
        return;
    }
    clear_all_scripts(e->scripts, 2);
    free_all_scripts(&e->scripts);

    // Item properties.
    if(e->item_properties)
    {
        free(e->item_properties);
        e->item_properties = NULL;
    }

	// Recursive damage (damage over time).
	if (e->recursive_damage)
	{
		recursive_damage_free_list(e->recursive_damage);
		e->recursive_damage = NULL;
	}

    if(e->waypoints)
    {
        free(e->waypoints);
        e->waypoints = NULL;
    }
    if(e->defense)
    {
        defense_free_object(e->defense);
        e->defense = NULL;
    }
    if(e->offense)
    {
        offense_free_object(e->offense);
        e->offense = NULL;
    }

	if (e->drawmethod)
	{
		free(e->drawmethod);
		e->drawmethod = NULL;
	}

    if(e->varlist)
    {
        // Although free_ent will be only called once when the engine is shutting down,
        // just clear those in case we forget something
        Varlist_Clear(e->varlist);
        free(e->varlist);
        e->varlist = NULL;
    }
    free(e);
    e = NULL;
}

void free_ents()
{
    int i;
    if(!ent_list)
    {
        return;
    }
    for(i = 0; i < ent_list_size; i++)
    {
        free_ent(ent_list[i]);
    }
    free(ent_list);
    free(ent_stack);
    ent_list = ent_stack = NULL;
    ent_list_size = ent_max = ent_count = ent_stack_size = 0;
}

entity *alloc_ent()
{
    entity *ent = malloc(sizeof(*ent));
    memset(ent, 0, sizeof(*ent));
    
    ent->object_type = OBJECT_TYPE_ENTITY;
    ent->defense = defense_allocate_object();    
    ent->offense = offense_allocate_object();
    
    ent->varlist = calloc(1, sizeof(*ent->varlist));
    // memset should be OK by know, because VT_EMPTY is zero by value, or else we should use ScriptVariant_Init
    Varlist_Init(ent->varlist, max_entity_vars);
    alloc_all_scripts(&ent->scripts);
    return ent;
}



int alloc_ents()
{
    int i;

    if(ent_list_size >= maxentities)
    {
        return 0;
    }

    ent_list_size += MAX_ENTS;

    ent_list = realloc(ent_list, sizeof(*ent_list) * ent_list_size);
    ent_stack = realloc(ent_stack, sizeof(*ent_list) * ent_list_size);

    for(i = ent_list_size - MAX_ENTS; i < ent_list_size; i++)
    {
        ent_list[i] = alloc_ent();
        ent_list[i]->sortid = i * 100;
        ent_stack[i] = NULL;
    }
    //ent_count = ent_max = 0;
    return 1;
}

int is_walking(int iAni)
{
    int i;

    for(i = 0; i < max_downs; i++)
    {
        if (iAni == animdowns[i]) return 1;
    }
    for(i = 0; i < max_ups; i++)
    {
        if (iAni == animups[i]) return 1;
    }
    for(i = 0; i < max_walks; i++)
    {
        if (iAni == animwalks[i]) return 1;
    }
    for(i = 0; i < max_backwalks; i++)
    {
        if (iAni == animbackwalks[i]) return 1;
    }

    return 0;
}

// UT: merged DC's common walk/idle functions
//
// Caskey, Damon V.
// 2019-02-09
//
// Rewritten for greater readability.
static bool common_anim_series(entity *ent, e_animations *alterates, int max_alternates, int force_mode, e_animations default_animation)
{
	int i;						// Loop cursor.
	int loop_min;							
	int loop_max;							
	e_animations animation_id;	// Animation to apply.
	
	// If we have a forced mode, we'll use it to constrict
	// loop options to just the forced mode.
	loop_min = force_mode ? force_mode - 1 : 0;
	loop_max = force_mode ? force_mode : max_alternates;

	// Loop through available types of series animations (max idles/walks/etc.).
    for (i = loop_min; i < loop_max; i++)
    {
		// Get animation from array of alternates.
		animation_id = alterates[i];

		// If we don't have the selected animation, just
		// get out of this loop iteration.
		if (!validanim(ent, animation_id))
		{
			continue;
		}

		// Can't be the default animation.
		if (animation_id == default_animation)
		{
			continue;
		}

		// If there's a target in range of this alternate animation, or 
		// we're forced to use it, switch animations.
		if (force_mode || normal_find_target(animation_id, 0))
		{
			ent->ducking = DUCK_NONE;
			ent_set_anim(ent, animation_id, 0);
			if (is_walking(animation_id)) ent->walking = 1;

			// Return true.
			return 1;
		}
    }

	// No alternates were set. Use default if we have it.
    if (validanim(ent, default_animation))
    {
		ent->ducking = DUCK_NONE;
		ent_set_anim(ent, default_animation, 0);
		if (is_walking(default_animation)) ent->walking = 1;

		// Return true.
		return 1;
    }

    return 0;
}

int common_idle_anim(entity *ent)
{
    /*
    common_idle_anim
    Damon Vaughn Caskey
    11012009
    Determine and set appropriate idle animation based on condition and range.
    Returns 1 if any animation is set.
    */
    entity *tempself = self;

    self = ent;

    self->ducking = DUCK_NONE;
    if(self->idling)
    {
        self->idling |= IDLING_ACTIVE;
    }

    if (ent->model->subtype != SUBTYPE_BIKER && ent->model->type != TYPE_NONE) // biker fix by Plombo // type none being "idle" prevented contra locked and loaded from working correctly. fixed by anallyst
    {
        ent->velocity.x = ent->velocity.z = 0;    //Stop movement.
    }

    if(validanim(ent, ANI_FAINT) && ent->energy_state.health_current <= ent->modeldata.health / 4)           //ANI_FAINT and health at/below 25%?
    {
        ent_set_anim(ent, ANI_FAINT, 0);                                                //Set ANI_FAINT.
        goto found;                                                                      //Return 1 and exit.
    }
    else if(validanim(ent, ANI_SLEEP) && _time > ent->sleeptime)    //ANI_SLEEP, sleeptime up
    {
        ent_set_anim(ent, ANI_SLEEP, 0);                                                //Set sleep anim.
        goto found;                                                                     //Return 1 and exit.
    }
    else if(validanim(ent, ANI_EDGE) && ent->edge && (ent->idling & IDLING_ACTIVE) && ent->ducking == DUCK_NONE)
    {
        if ( (ent->edge & EDGE_RIGHT) && (ent->edge & EDGE_LEFT) )
        {
            ent_set_anim(ent, ANI_EDGE, 0);
        }
        else if (ent->edge & EDGE_RIGHT)
        {
            if (ent->direction == DIRECTION_RIGHT)
            {
                ent_set_anim(ent, ANI_EDGE, 0);
            }
            else
            {
                if(validanim(ent, ANI_BACKEDGE)) ent_set_anim(ent, ANI_BACKEDGE, 0);
                else ent_set_anim(ent, ANI_EDGE, 0);
            }
        }
        else if (ent->edge & EDGE_LEFT)
        {
            if (ent->direction == DIRECTION_LEFT)
            {
                ent_set_anim(ent, ANI_EDGE, 0);
            }
            else
            {
                if(validanim(ent, ANI_BACKEDGE)) ent_set_anim(ent, ANI_BACKEDGE, 0);
                else ent_set_anim(ent, ANI_EDGE, 0);
            }
        }
        goto found;                                                                     //Return 1 and exit.
    }
    else if(common_anim_series(ent, animidles, max_idles, ent->idlemode, ANI_IDLE))
    {
        goto found;
    }

    self = tempself;
    return 0;
found:
    self = tempself;
    return 1;
}


#define common_walk_anim(ent) \
	common_anim_series(ent, animwalks, max_walks, ent->walkmode, ANI_WALK)

#define common_backwalk_anim(ent) \
	common_anim_series(ent, animbackwalks, max_backwalks, ent->walkmode, ANI_BACKWALK)

#define common_up_anim(ent) \
	common_anim_series(ent, animups, max_ups, ent->walkmode, ANI_UP)

#define common_down_anim(ent) \
	common_anim_series(ent, animdowns, max_downs, ent->walkmode, ANI_DOWN)


// This function find nearst x escape point from the given position
// It assumes the point is already inside the wall
static float find_nearest_wall_x(int wall, float x, float z)
{
    float x1, x2;

    x1 = level->walls[wall].x + level->walls[wall].lowerleft + (level->walls[wall].z - z) * ((level->walls[wall].upperleft - level->walls[wall].lowerleft) / level->walls[wall].depth);
    x2 = level->walls[wall].x + level->walls[wall].lowerright + (level->walls[wall].z - z) * ((level->walls[wall].upperright - level->walls[wall].lowerright) / level->walls[wall].depth);

    if(diff(x1, x) > diff(x2, x))
    {
        return x2;
    }
    else
    {
        return x1;
    }
}

// this method initialize an entity's A.I. behaviors
void ent_default_init(entity *e)
{
    int dodrop;
    int wall;
    entity *other;

    if(!e)
    {
        return;
    }

    if((!(screen_status & IN_SCREEN_SELECT) && !_time) || e->modeldata.type != TYPE_PLAYER )
    {
        if( validanim(e, ANI_SPAWN))
        {
            ent_set_anim(e, ANI_SPAWN, 0);    // use new playerselect spawn anim
        }
        else if( validanim(e, ANI_RESPAWN))
        {
            ent_set_anim(e, ANI_RESPAWN, 0);
        }
        //else set_idle(e);
    }
    else if(!(screen_status & IN_SCREEN_SELECT) && _time && e->modeldata.type == TYPE_PLAYER) // mid-level respawn
    {
        if( validanim(e, ANI_RESPAWN))
        {
            ent_set_anim(e, ANI_RESPAWN, 0);
        }
        else if( validanim(e, ANI_SPAWN))
        {
            ent_set_anim(e, ANI_SPAWN, 0);
        }
        //else set_idle(e);
    }
    else if(screen_status & IN_SCREEN_SELECT && validanim(e, ANI_SELECT))
    {
		// Play transition if we have one. Default Select otherwise.
		if (validanim(e, ANI_SELECTIN))
		{
			ent_set_anim(e, ANI_SELECTIN, 0);
		}
		else
		{	
			ent_set_anim(e, ANI_SELECT, 0);
		}
	}
    //else set_idle(e);

    if(!level)
    {
        if(!e->animation)
        {
            set_idle(e);
        }
        return;
    }

    e->nograb_default = 0; // init all entities to 0 by default

    switch(e->modeldata.type)
    {
    case TYPE_ANY:
    case TYPE_NO_COPY:
    case TYPE_RESERVED:
	case TYPE_UNDELCARED:
	case TYPE_UNKNOWN:
		//Do nothing.
        break;
    case TYPE_ENDLEVEL:
    case TYPE_ITEM:
        e->nograb = 1;
        e->nograb_default = e->nograb;
        break;

    case TYPE_PLAYER:
        //e->direction = (level->scrolldir != SCROLL_LEFT);
        e->takedamage = player_takedamage;
        e->think = player_think;
        e->trymove = player_trymove;

        if(validanim(e, ANI_SPAWN) || validanim(e, ANI_RESPAWN))
        {
            e->takeaction = common_spawn;
        }
        else if(!e->animation)
        {
            int player_index = (int)e->playerindex;

            if(player_index < levelsets[current_set].maxplayers && level->spawn && _time && level->spawn[player_index].y > e->position.y)
            {
                e->takeaction = common_drop;
                e->position.y = (float)level->spawn[player_index].y;
                if(validanim(e, ANI_JUMP))
                {
                    ent_set_anim(e, ANI_JUMP, 0);
                }
            }
        }
        if(_time && e->modeldata.makeinv)
        {
            // Spawn invincible code
            e->invincible |= INVINCIBLE_INTANGIBLE;
            e->blink = (e->modeldata.makeinv > 0);
            e->invinctime = _time + ABS(e->modeldata.makeinv);
            e->arrowon = 1;    // Display the image above the player
        }
        break;
    case TYPE_NPC: // use NPC(or A.I. player) instread of an enemy subtype or trap subtype, for further A.I. use
        if(e->modeldata.multiple == 0)
        {
            e->modeldata.multiple = -1;
        }

    case TYPE_ENEMY:
        e->think = common_think;
        if(e->modeldata.subtype == SUBTYPE_BIKER)
        {
            e->nograb = 1;
            e->nograb_default = e->nograb;
            e->attacking = ATTACKING_ACTIVE;
            //e->direction = (e->position.x<0);
            if(e->modeldata.speed.x)
            {
                e->velocity.x = (e->direction == DIRECTION_RIGHT) ? (e->modeldata.speed.x) : (-e->modeldata.speed.x);
            }
            else
            {
                e->velocity.x = (e->direction == DIRECTION_RIGHT) ? (1.7 + randf((float)0.6)) : (-(1.7 + randf((float)0.6)));
            }
            e->takedamage = biker_takedamage;
            e->speedmul = 2;
            break;
        }
        // define new subtypes
        else if(e->modeldata.subtype == SUBTYPE_ARROW)
        {
            e->energy_state.health_current = 1;
            if(!e->modeldata.speed.x && !(e->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE))
            {
                e->modeldata.speed.x = 2;    // Set default speed to 2 for arrows
            }
            else if(e->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
            {
                e->modeldata.speed.x = 0;
            }
            if(e->projectile_prime & PROJECTILE_PRIME_BASE_FLOOR)
            {
                e->base = 0;
            }
            else
            {
                e->base = e->position.y;
            }
            e->nograb = 1;
            e->nograb_default = e->nograb;
            e->attacking = ATTACKING_ACTIVE;
            e->takedamage = arrow_takedamage;
            e->speedmul = 2;
            break;
        }
        else
        {
            e->trymove = common_trymove;
            // Must just be a regular enemy, set defaults accordingly
            if(!e->modeldata.speed.x && !(e->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE))
            {
                e->modeldata.speed.x = 1;
            }
            else if(e->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
            {
                e->modeldata.speed.x = 0;
            }
            if(e->modeldata.multiple == 0)
            {
                e->modeldata.multiple = 5;
            }
            e->takedamage = common_takedamage;//enemy_takedamage;
        }

        if(e->modeldata.subtype == SUBTYPE_NOTGRAB)
        {
            e->nograb = 1;
            e->nograb_default = e->nograb;
        }

        if(validanim(e, ANI_SPAWN) || validanim(e, ANI_RESPAWN))
        {
            e->takeaction = common_spawn;
        }
        else
        {
            dodrop = (e->modeldata.subtype != SUBTYPE_ARROW && level && (level->scrolldir == SCROLL_UP || level->scrolldir == SCROLL_DOWN));

            if(!nodropspawn && (dodrop || (e->position.x > advancex - 30 && e->position.x < advancex + videomodes.hRes + 30 && e->position.y == 0)) )
            {
                e->position.y += videomodes.vRes + randf(40);
            }
            if(inair(e))
            {
                e->takeaction = common_drop;//enemy_drop;
                if(validanim(e, ANI_JUMP))
                {
                    ent_set_anim(e, ANI_JUMP, 0);
                }
            }
        }
        break;
        // define trap type
    case TYPE_TRAP:
        e->think = trap_think;
        e->takedamage =  common_takedamage;//enemy_takedamage;
        e->speedmul = 2;
        break;
    case TYPE_OBSTACLE:
        e->nograb = 1;
        e->nograb_default = e->nograb;
        if(e->energy_state.health_current <= 0)
        {
            e->death_state |= DEATH_STATE_DEAD;    // so it won't get hit
        }
        e->takedamage = obstacle_takedamage;//obstacle_takedamage;
        break;
    case TYPE_STEAMER:
        e->nograb = 1;
        e->nograb_default = e->nograb;
        e->think = steamer_think;
        e->base = e->position.y;
        break;
    case TYPE_TEXTBOX:    // New type for displaying text purposes
        e->nograb = 1;
        e->nograb_default = e->nograb;
        e->think = text_think;
        break;
	case TYPE_PROJECTILE:
        
        e->energy_state.health_current = 1;
        e->nograb = 1;
        e->nograb_default = e->nograb;
        e->think = common_think;
        e->takedamage = arrow_takedamage;
        e->attacking = ATTACKING_ACTIVE;
        
        if (e->projectile_prime & PROJECTILE_PRIME_BASE_FLOOR)
        {
            e->base = 0;
        }
        else
        {
            e->base = e->position.y;
        }
        e->speedmul = 2;
        e->trymove = common_trymove;
        break;

    case TYPE_SHOT:
        e->energy_state.health_current = 1;
        e->nograb = 1;
        e->nograb_default = e->nograb;
        e->think = common_think;
        e->takedamage = arrow_takedamage;
        e->attacking = ATTACKING_ACTIVE;
        if(!e->model->speed.x && !(e->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE))
        {
            e->modeldata.speed.x = 2;    // Set default speed to 2 for arrows
        }
        else if(e->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
        {
            e->modeldata.speed.x = 0;
        }
        if(e->projectile_prime & PROJECTILE_PRIME_BASE_FLOOR)
        {
            e->base = 0;
        }
        else
        {
            e->base = e->position.y;
        }
        e->speedmul = 2;
        break;
    case TYPE_NONE:
        e->nograb = 1;
        e->nograb_default = e->nograb;
        
        //e->base=e->position.y; //complained?
        e->modeldata.move_config_flags |= (MOVE_CONFIG_NO_ADJUST_BASE | MOVE_CONFIG_SUBJECT_TO_GRAVITY);

        if(validanim(e, ANI_WALK))
        {
            if(e->direction == DIRECTION_RIGHT)
            {
                e->velocity.x = e->modeldata.speed.x;
            }
            else
            {
                e->velocity.x = -(e->modeldata.speed.x);
            }
            e->think = anything_walk;

            common_walk_anim(e);
            //ent_set_anim(e, ANI_WALK, 0);
        }
        else
        {
            e->speedmul = 2;
        }
        e->trymove = common_trymove;
        break;
    case TYPE_PANEL:
        e->nograb = 1;
        e->nograb_default = e->nograb;
        break;
    }

    /* Faction data. */   
    faction_copy_data(&e->faction, &e->modeldata.faction);

    if(!e->animation)
    {
        set_idle(e);
    }

    if(e->modeldata.multiple < 0)
    {
        e->modeldata.multiple = 0;
    }

    if(e->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_PLATFORM && (other = check_platform_below(e->position.x, e->position.z, e->position.y, e)))
    {
        e->base = other->position.y + other->animation->platform[other->animpos][PLATFORM_HEIGHT];
    }
    else if(e->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_WALL && (wall = checkwall_below(e->position.x, e->position.z, T_MAX_CHECK_ALTITUDE)) >= 0)
    {
        if(level->walls[wall].height > MAX_WALL_HEIGHT)
        {
            e->position.x = find_nearest_wall_x(wall, e->position.x, e->position.z);
        }
        else
        {
            e->base = level->walls[wall].height;
        }
    }
}

void ent_spawn_ent(entity *ent)
{
    entity *s_ent = NULL;
	s_sub_entity *spawnframe = ent->animation->sub_entity_spawn;
    float dy = level ? 4.0 : 0.0;
    
	// spawn point relative to current entity
    if(spawnframe->placement == SUB_ENTITY_PLACEMENT_PARENT)
    {
        s_ent = spawn(ent->position.x + ((ent->direction == DIRECTION_RIGHT) ? spawnframe->position.x : -spawnframe->position.x), ent->position.z + spawnframe->position.z, ent->position.y + spawnframe->position.y, ent->direction, NULL, ent->animation->sub_entity_model_index, NULL);
    }
    //relative to screen position
    else if(spawnframe->placement == SUB_ENTITY_PLACEMENT_SCREEN)
    {
        if(level && !(level->scrolldir & SCROLL_UP) && !(level->scrolldir & SCROLL_DOWN))
        {
            s_ent = spawn(advancex + spawnframe->position.x, advancey + spawnframe->position.z + dy, spawnframe->position.y, 0, NULL, ent->animation->sub_entity_model_index, NULL);
        }
        else
        {
            s_ent = spawn(advancex + spawnframe->position.x, spawnframe->position.z + dy, spawnframe->position.y, 0, NULL, ent->animation->sub_entity_model_index, NULL);
        }
    }
    //absolute position in level
    else
    {
        s_ent = spawn(spawnframe->position.x, spawnframe->position.z, spawnframe->position.y + 0.001, 0, NULL, ent->animation->sub_entity_model_index, NULL);
    }

    if(s_ent)
    {
        //ent_default_init(s_ent);
        if(s_ent->modeldata.type & TYPE_SHOT)
        {
            s_ent->playerindex = ent->playerindex;
        }
        if(s_ent->modeldata.subtype == SUBTYPE_ARROW)
        {
            s_ent->owner = ent;
        }
        s_ent->spawntype = SPAWN_TYPE_CMD_SPAWN;
        s_ent->parent = ent;  //maybe used by A.I.
        execute_onspawn_script(s_ent);
    }

}

void ent_summon_ent(entity *ent)
{
    entity *s_ent = NULL;
	s_sub_entity *spawnframe = ent->animation->sub_entity_summon;
    float dy = level ? 4.0 : 0.0;
    // spawn point relative to current entity
    if(spawnframe->placement == SUB_ENTITY_PLACEMENT_PARENT)
    {
        s_ent = spawn(ent->position.x + ((ent->direction == DIRECTION_RIGHT) ? spawnframe->position.x : -spawnframe->position.x), ent->position.z + spawnframe->position.z,  ent->position.y + spawnframe->position.y, ent->direction, NULL, ent->animation->sub_entity_model_index, NULL);
    }
    //relative to screen position
    else if(spawnframe->placement == SUB_ENTITY_PLACEMENT_SCREEN)
    {
        if(level && !(level->scrolldir & SCROLL_UP) && !(level->scrolldir & SCROLL_DOWN))
        {
            s_ent = spawn(advancex + spawnframe->position.x, advancey + spawnframe->position.z + dy, spawnframe->position.y, 0, NULL, ent->animation->sub_entity_model_index, NULL);
        }
        else
        {
            s_ent = spawn(advancex + spawnframe->position.x, spawnframe->position.z + dy, spawnframe->position.y, 0, NULL, ent->animation->sub_entity_model_index, NULL);
        }
    }
    //absolute position in level
    else
    {
        s_ent = spawn(spawnframe->position.x, spawnframe->position.z, spawnframe->position.y, 0, NULL, ent->animation->sub_entity_model_index, NULL);
    }

    if(s_ent)
    {
        if(spawnframe->placement == SUB_ENTITY_PLACEMENT_PARENT)
        {
            s_ent->direction = ent->direction;
        }
        //ent_default_init(s_ent);
        if(s_ent->modeldata.type & TYPE_SHOT)
        {
            s_ent->playerindex = ent->playerindex;
        }
        if(s_ent->modeldata.subtype == SUBTYPE_ARROW)
        {
            s_ent->owner = ent;
        }
        //maybe used by A.I.
        s_ent->spawntype = SPAWN_TYPE_CMD_SUMMON;
        s_ent->parent = ent;
        ent->subentity = s_ent;
        execute_onspawn_script(s_ent);
    }
}

/*
* Caskey, Damon V.
* Unknown date (~2008)
* 
* Get final delay.
*/
int calculate_edelay(entity *ent, int frame)
{
    int result;
    int cap_min;
    int cap_max; 
    int range_min;
    int range_max;

    s_anim *anim = ent->animation;

    range_min = ent->modeldata.edelay.range.min;
    range_max = ent->modeldata.edelay.range.max;

    result = anim->delay[frame];

    if (result >= range_min && result <= range_max) //Regular delay within ignore ranges?
    {
        result = (int)(result * ent->modeldata.edelay.factor);
        result += ent->modeldata.edelay.modifier;            

        cap_min = ent->modeldata.edelay.cap.min;
        cap_max = ent->modeldata.edelay.cap.max;

        if (result < cap_min)
        {
            result = cap_min;
        }
        
        if (result > cap_max)
        {
            result = cap_max;
        }
    }

    return result;
}

/*
* Caskey, Damon V.
* 2018-04-20
*
* Set up jumping velocity for an animation
* that has jump frame defined and is at the
* designated jump frame. Also spawns effect
* entity if one is defined.
*/
bool check_jumpframe(entity * const acting_entity, const unsigned int frame)
{
    if (!acting_entity || !acting_entity->animation)
    {
        return 0;
    }

    const s_onframe_move* jump = &acting_entity->animation->jumpframe;

    // Must have jump frame allocated.
    if(jump->frame == FRAME_NONE)
    {
        return 0;
    }

    // Must be on assigned jump frame.
    if(jump->frame != frame)
    {
        return 0;
    }

    // Chuck entity into the air.
    toss(acting_entity, jump->velocity.y);

    // Set left or right horizontal velocity depending on
    // current direction.
    if(acting_entity->direction == DIRECTION_RIGHT)
    {
        acting_entity->velocity.x = jump->velocity.x;
    }
    else
    {
        acting_entity->velocity.x = -jump->velocity.x;
    }

    // Lateral velocity.
    acting_entity->velocity.z = jump->velocity.z;

    // Spawn an effect entity if defined.
    if(jump->model_index != MODEL_INDEX_NONE)
    {
        entity* const effect_entity = spawn(acting_entity->position.x, acting_entity->position.z, acting_entity->position.y, acting_entity->direction, NULL, jump->model_index, NULL);
        
        if(effect_entity)
        {
            effect_entity->spawntype = SPAWN_TYPE_DUST_JUMP;
            effect_entity->base = acting_entity->position.y;
            effect_entity->autokill |= AUTOKILL_ANIMATION_COMPLETE;
            execute_onspawn_script(effect_entity);
        }
    }

    return 1;

}

// move here to prevent some duplicated code in ent_sent_anim and update_ents
void update_frame(entity *ent, unsigned int f)
{
    entity *tempself;
    s_attack attack;
    s_defense* defense_object = NULL;
    s_axis_principal_float move;
    s_anim *anim = ent->animation;

    if(f >= anim->numframes) // prevent a crash with invalid frame index.
    {
        return;
    }

    //important!
    tempself = self;
    self = ent;

    self->animpos = f;
    //self->currentsprite = self->animation->sprite[f];

    if(self->animating)
    {
        if (self->nextanim != DELAY_INFINITE) { self->nextanim = _time + calculate_edelay(self, f); }

        self->pausetime = 0;
        execute_animation_script(self);
    }

    if(ent->animation != anim || ent->animpos != f)
    {
        goto uf_interrupted;
    }

    if(level && (anim->move[f]->axis.x || anim->move[f]->axis.z))
    {
        move.x = (float)(anim->move[f]->axis.x ? anim->move[f]->axis.x : 0);
        move.z = (float)(anim->move[f]->axis.z ? anim->move[f]->axis.z : 0);
        if(self->direction == DIRECTION_LEFT)
        {
            move.x = -move.x;
        }
        self->movex += move.x;
        self->movez += move.z;
    }

    if(anim->move[0]->base && anim->move[0]->base >= 0 && self->base <= 0)
    {
        ent->base = (float)anim->move[0]->base;
    }
    else if(!anim->move[0]->base || anim->move[0]->base < 0)
    {
        
        move.y = (float)(anim->move[f]->axis.y ? anim->move[f]->axis.y : 0);
                        
        self->base += move.y;
        
        if(move.y != 0)
        {
            self->altbase += move.y;
        }
        else
        {
            self->altbase = 0;
        }
    }

    if(anim->flipframe == f)
    {
        self->direction = !self->direction;
    }

    if(anim->weaponframe && anim->weaponframe[0] == f)
    {
        dropweapon(2);
        set_weapon(self, anim->weaponframe[1], 0);
        if(!anim->weaponframe[2])
        {
            set_idle(self);
        }
    }

    if(anim->quakeframe.framestart + anim->quakeframe.cnt == f)
    {
        if(level)
        {
            if(anim->quakeframe.cnt % 2 || anim->quakeframe.v > 0)
            {
                level->quake = anim->quakeframe.v;
            }
            else
            {
                level->quake = anim->quakeframe.v * -1;
            }
        }
        if((anim->quakeframe.repeat - anim->quakeframe.cnt) > 1)
        {
            anim->quakeframe.cnt++;
        }
        else
        {
            anim->quakeframe.cnt = 0;
        }
    }

    if(anim->sub_entity_unsummon == f)
    {
        if(self->subentity)
        {
            self = self->subentity;
            attack = emptyattack;
            attack.dropv = default_model_dropv;
            attack.attack_force = self->energy_state.health_current;
            attack.attack_type = ATK_SUB_ENTITY_UNSUMMON;
            if(self->takedamage)
            {
                defense_object = defense_find_current_object(self, NULL, attack.attack_type);
                self->takedamage(self, &attack, 0, defense_object);
            }
            else
            {
                kill_entity(self, KILL_ENTITY_TRIGGER_UNSUMMON);
            }
            self = ent; // lol ...
            self->subentity = NULL;
        }
    }

    //spawn / summon /unsummon features
    if(anim->sub_entity_spawn && anim->sub_entity_spawn->frame == f && anim->sub_entity_model_index >= 0)
    {
        ent_spawn_ent(self);
    }

    if(anim->sub_entity_summon && anim->sub_entity_summon->frame == f && anim->sub_entity_model_index >= 0)
    {
        //subentity is dead
        if(!self->subentity || self->subentity->death_state & DEATH_STATE_DEAD)
        {
            ent_summon_ent(self);
        }
    }

    /* Child spawn */
    if (anim->child_spawn && anim->child_spawn[f])
    {
        child_spawn_execute_list(anim->child_spawn[f], ent);
    }

    if(anim->soundtoplay && anim->soundtoplay[f] >= 0)
    {
        sound_play_sample(anim->soundtoplay[f], 0, savedata.effectvol, savedata.effectvol, 100);
    }

    // Perform jumping if on a jumpframe.
    check_jumpframe(self, f);

	// This animation have projectile settings?
	if (anim->projectile)
	{
		int position_x = anim->projectile->position.x;

		if (self->direction == DIRECTION_LEFT)
		{
			position_x = -position_x;
		}

		if (anim->projectile->throwframe == f)
		{
			// For backward compatible thing
			// throw stars in the air, hmm, strange
			// custstar custknife in animation should be checked first
			// then if the entity is jumping, check star first, if failed, try knife instead
			// well, try knife at last, if still failed, try star, or just let if shutdown?

#define __trystar star_spawn(self, anim->projectile)
#define __tryknife knife_spawn(self, anim->projectile)
			
			if (anim->projectile->knife >= 0 || anim->projectile->flash >= 0)
			{
				__tryknife;
			}
			else if (anim->projectile->star >= 0)
			{
				__trystar;
			}
			else if (self->jumping)
			{
				if (!__trystar)
				{
					__tryknife;
				}
			}
			else if (!__tryknife)
			{
				__trystar;
			}
			self->weapon_state |= WEAPON_STATE_DEDUCT_USE;
		}

		if (anim->projectile->shootframe == f)
		{
			knife_spawn(self, anim->projectile);
			self->weapon_state |= WEAPON_STATE_DEDUCT_USE;
		}

		if (anim->projectile->tossframe == f)
		{
			bomb_spawn(self, anim->projectile);
			self->weapon_state |= WEAPON_STATE_DEDUCT_USE;
		}
	}

uf_interrupted:

    //important!
    self = tempself;

    #undef __trystar
    #undef __tryknife
}


void ent_set_anim(entity *ent, int aninum, int resetable)
{
    s_anim *ani = NULL;
    int animpos;

    if(!ent)
    {
        //printf("FATAL: tried to set animation with invalid address (no such object)");
        return;
    }

    if(aninum < 0 || aninum >= max_animations)
    {
        //printf("FATAL: tried to set animation with invalid index (%s, %i)", ent->name, aninum);
        return;
    }

    if(!validanim(ent, aninum))
    {
        //printf("FATAL: tried to set animation with invalid address (%s, %i)", ent->name, aninum);
        return;
    }

    ani = ent->modeldata.animation[aninum];

    if(!resetable && ent->animation == ani)
    {
        return;
    }

    if(ani->numframes == 0)
    {
        return;
    }

    if(ent->animation && ((resetable & 2) || (ani->sync >= 0 && ent->animation->sync == ani->sync)))
    {
        animpos = ent->animpos;
        if(animpos >= ani->numframes)
        {
            animpos = 0;
        }
        ent->animnum_previous = ent->animnum;
        ent->animnum = aninum;
        ent->animation = ani;
        ent->animpos = animpos;
        ent->walking = 0;
    }
    else
    {
        ent->animnum_previous = ent->animnum;
        ent->animnum = aninum;    // Stored for nocost usage
        ent->animation = ani;
        ent->animation->hit_count = 0;

        ent->animating = ANIMATING_FORWARD;
        ent->lasthit = ent->grabbing;
        ent->altbase = 0;
        ent->walking = 0;

        update_frame(ent, 0);
    }
}

unsigned char *model_get_colourmap(s_model *model, unsigned which)
{
    if(which <= 0 || which > model->maps_loaded)
    {
        return model->palette;
    }
    else
    {
        return model->colourmap[which - 1];
    }
}

// 0 = none, 1+ = alternative
void ent_set_colourmap(entity *ent, unsigned int which)
{
    if(which > ent->modeldata.maps_loaded)
    {
        which = 0;
    }
    if(which <= 0)
    {
        ent->colourmap = ent->modeldata.palette;
    }
    else
    {
        ent->colourmap = ent->modeldata.colourmap[which - 1];
    }
    ent->map = which;
}

// used by ent_set_model
void ent_copy_uninit(entity *ent, s_model *oldmodel)
{
    if(ent->modeldata.multiple < 0)
    {
        ent->modeldata.multiple             = oldmodel->multiple;
    }

    ent->modeldata.move_config_flags = oldmodel->move_config_flags;
      
    if(ent->modeldata.aimove == AIMOVE1_NONE)
    {
        ent->modeldata.aimove               = oldmodel->aimove;
    }
    if (ent->modeldata.aiattack == -1)
    {
        ent->modeldata.aiattack = oldmodel->aiattack;
    }    

    faction_copy_data(&ent->modeldata.faction, &oldmodel->faction);
    faction_copy_data(&ent->faction, &oldmodel->faction);

    if(!ent->modeldata.health)
    {
        ent->modeldata.health               = oldmodel->health;
    }
    if(!ent->modeldata.mp)
    {
        ent->modeldata.mp                   = oldmodel->mp;
    }
    if(ent->modeldata.risetime.rise == -1)
    {
        ent->modeldata.risetime.rise          = oldmodel->risetime.rise;
    }
    /*
    if(!ent->modeldata.grab_resistance)
    	ent->modeldata.grab_resistance             = oldmodel->antigrab;
    if(!ent->modeldata.grab_force)
    	ent->modeldata.grab_force            = oldmodel->grabforce;
    if(!ent->modeldata.paingrab)
    	ent->modeldata.paingrab             = oldmodel->paingrab;*/

    if(ent->energy_state.health_current > ent->modeldata.health)
    {
        ent->energy_state.health_current = ent->modeldata.health;
    }
    if(ent->energy_state.mp_current > ent->modeldata.mp)
    {
        ent->energy_state.mp_current = ent->modeldata.mp;
    }

    ent->shadow_config_flags = ent->modeldata.shadow_config_flags;
}


//if syncAnim is set, only change animation reference
void ent_set_model(entity *ent, char *modelname, int syncAnim)
{
    s_model *m = NULL;
    s_model oldmodel;
    if(ent == NULL)
    {
        borShutdown(1, "FATAL: tried to change model of invalid object");
    }
    m = findmodel(modelname);
    if(m == NULL)
    {
        borShutdown(1, "Model not found: '%s'", modelname);
    }
    oldmodel = ent->modeldata;
    ent->model = m;
    ent->modeldata = *m;
    ent_copy_uninit(ent, &oldmodel);
    ent_set_colourmap(ent, ent->map);

    if(syncAnim && m->animation[ent->animnum])
    {
        ent->animation = m->animation[ent->animnum];
        if(ent->animpos >= ent->animation->numframes)
        {
            ent->animpos = ent->animation->numframes - 1;
        }

        if (ent->nextanim != DELAY_INFINITE) { ent->nextanim = _time + calculate_edelay(ent, ent->animpos); }

        
        //update_frame(ent, ent->animpos);
    }
    else
    {
        ent->attacking = ATTACKING_NONE;

        if((!(screen_status & IN_SCREEN_SELECT) && !_time) || !(ent->modeldata.type & TYPE_PLAYER))
        {
            // use new playerselect spawn anim
            if( validanim(ent, ANI_SPAWN))
            {
                ent_set_anim(ent, ANI_SPAWN, 0);
            }
            else
            {
                if( validanim(ent, ANI_IDLE)) ent_set_anim(ent, ANI_IDLE, 0);
            }
        }
        else if(!(screen_status & IN_SCREEN_SELECT) && _time && (ent->modeldata.type & TYPE_PLAYER))
        {
            // mid-level respawn
            if( validanim(ent, ANI_RESPAWN))
            {
                ent_set_anim(ent, ANI_RESPAWN, 0);
            }
            else if( validanim(ent, ANI_SPAWN))
            {
                ent_set_anim(ent, ANI_SPAWN, 0);
            }
            else
            {
                if( validanim(ent, ANI_IDLE)) ent_set_anim(ent, ANI_IDLE, 0);
            }
        }
        else if(screen_status & IN_SCREEN_SELECT && validanim(ent, ANI_SELECT))
        {
			// Play transition if we have one. Default Select otherwise.
			if (validanim(ent, ANI_SELECTIN))
			{
				ent_set_anim(ent, ANI_SELECTIN, 0);
			}
			else
			{
				ent_set_anim(ent, ANI_SELECT, 0);
			}
        }
        else
        {
            if( validanim(ent, ANI_IDLE)) ent_set_anim(ent, ANI_IDLE, 0);
        }
    }
}

// Caskey, Damon V.
// ~2018
//
// Allocate memory for a drawmethod and return pointer.
s_drawmethod *allocate_drawmethod()
{
	s_drawmethod *result;

	// Allocate memory and get the pointer.
	result = malloc(sizeof(*result));

	// Copy default values into new drawmethod.
	memcpy(result, &plainmethod, sizeof(*result));

	return result;
}

// Caskey, Damon V.
// 2019-12-13
//
// Allocate memory for a projectile animation setting and return pointer.
s_projectile* allocate_projectile()
{
	s_projectile* result;

	// Allocate memory and get the pointer.
	result = malloc(sizeof(*result));

	// Copy default values into new projectile setting.
	memcpy(result, &projectile_default_config, sizeof(*result));

	return result;
}

// Caskey, Damon V.
// 2019-12-11
//
// Allocate memory for a sub entity command and return pointer.
s_sub_entity *allocate_sub_entity()
{
	s_sub_entity *result;

	// Allocate memory and get the pointer.
	result = malloc(sizeof(*result));

	// Set any default values we need.
	result->frame = FRAME_NONE;

	return result;
}

entity *spawn(const float pos_x, const float pos_z, const float pos_y, const e_direction direction, char *model_name, const int model_index, s_model* model_pointer)
{
    entity *acting_entity = NULL;
    int i, id;
    s_defense *dfs;
    s_offense *ofs;
    e_object_type object_type;
    Varlist *vars;
    s_scripts *scripts;

    /*
    * Prioritize parameter we use to 
    * spawn: 
    * 
    * 1. model_pointer.
    * 2. model_index.
    * 3. model_name.
    */

    s_model* spawn_model = model_pointer;

    if(!spawn_model)
    {
        if(model_index >= 0)
        {
            spawn_model = model_cache[model_index].model;
        }
        else if(model_name)
        {
            spawn_model = findmodel(model_name);
        }
    }
        
    if(spawn_model == NULL)
    {
        return NULL;
    }

    if(ent_count >= ent_list_size && !alloc_ents())
    {
        return NULL;    //out of memory ?
    }

    for(i = 0; i < ent_list_size; i++)
    {
        if(!ent_list[i]->exists || (ent_count >= spawnoverride && ent_list[i]->modeldata.priority < 0 && ent_list[i]->modeldata.priority <= spawn_model->priority))
        {
            acting_entity = ent_list[i];
            if(acting_entity->exists)
            {
                kill_entity(acting_entity, KILL_ENTITY_TRIGGER_SPAWN_OVERRIDE);
            }
            // save these values, or they will loss when memset called
            object_type = acting_entity->object_type;
            id      = acting_entity->sortid;
            dfs     = acting_entity->defense;

            ofs     = acting_entity->offense;
            vars    = acting_entity->varlist;
            Varlist_Cleanup(vars);

            memcpy(dfs, spawn_model->defense, sizeof(*dfs)*max_attack_types);
            memcpy(ofs, spawn_model->offense, sizeof(*ofs)*max_attack_types);
                        
            // clear up
            clear_all_scripts(acting_entity->scripts, 1);
            if(acting_entity->waypoints)
            {
                free(acting_entity->waypoints);
            }

            scripts = acting_entity->scripts;
            memset(acting_entity, 0, sizeof(*acting_entity));
            
			// e->drawmethod = plainmethod;
            acting_entity->drawmethod = allocate_drawmethod();

            acting_entity->drawmethod->flag = 0;

            // add to list and count current entities
            acting_entity->exists = 1;
            ent_count++;

            acting_entity->modeldata = *spawn_model; // copy the entir model data here
            acting_entity->model = spawn_model;
            acting_entity->defaultmodel = spawn_model;

            acting_entity->scripts = scripts;
            // copy from model a fresh script

            copy_all_scripts(spawn_model->scripts, acting_entity->scripts, 1);

            if(ent_count > ent_max)
            {
                ent_max = ent_count;
            }
            acting_entity->timestamp = _time; // log time so update function will ignore it if it is new

            acting_entity->energy_state.health_current = acting_entity->modeldata.health;
            acting_entity->energy_state.mp_current = acting_entity->modeldata.mp;
            acting_entity->knockdowncount = acting_entity->modeldata.knockdowncount;
            acting_entity->position.x = pos_x;
            acting_entity->position.z = pos_z;
            acting_entity->position.y = pos_y;
            acting_entity->shadow_config_flags = acting_entity->modeldata.shadow_config_flags;
            acting_entity->direction = direction;
            acting_entity->nextthink = _time + 1;
            acting_entity->nextmove = _time + 1;
            acting_entity->speedmul = 1;
            ent_set_colourmap(acting_entity, 0);
            acting_entity->lifespancountdown = spawn_model->lifespan; // new life span countdown
            
            if((acting_entity->modeldata.type & TYPE_PLAYER) && ((level && level->nohit == DAMAGE_FROM_PLAYER_OFF) || savedata.mode))
            {
                acting_entity->faction.type_hostile &= ~TYPE_PLAYER;
                acting_entity->faction.type_damage_direct &= ~TYPE_PLAYER;
            }
            
            if(acting_entity->modeldata.type & TYPE_PLAYER)
            {
                acting_entity->playerindex = currentspawnplayer;
            }

            if(acting_entity->modeldata.type & TYPE_TEXTBOX)
            {
                textbox = acting_entity;
            }

            strncpy(acting_entity->name, acting_entity->modeldata.name, MAX_NAME_LEN - 1);
            // copy back the value
            acting_entity->object_type = object_type;
            acting_entity->sortid = id;
            acting_entity->defense = dfs;
            acting_entity->offense = ofs;
            acting_entity->varlist = vars;

            ent_default_init(acting_entity);
            return acting_entity;
        }
    }
    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->grabbing = NULL;
    }
    e->link = NULL;
    e->grabbing = NULL;
}



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



void kill_entity(entity *victim, e_kill_entity_trigger trigger)
{
    int i = 0;
    s_attack attack;
    s_defense* defense_object = NULL;
    entity *tempent = self;

    if(victim == NULL || !victim->exists)
    {
        return;
    }

    execute_onkill_script(victim, trigger);

    ent_unlink(victim);
    victim->weapent = NULL;
    victim->energy_state.health_current = 0;
    victim->exists = 0;
    ent_count--;

    //UT: caution, script function killentity calls this
    clear_all_scripts(victim->scripts, 1);

    if(victim->parent && victim->parent->subentity == victim)
    {
        victim->parent->subentity = NULL;
    }
    victim->parent = NULL;
    if(victim->modeldata.summonkill)
    {
        attack = emptyattack;
        attack.attack_type = ATK_SUB_ENTITY_PARENT_KILL;
        attack.dropv = default_model_dropv;
    }

    defense_object = defense_find_current_object(self, NULL, attack.attack_type);

    // kill minions
    if(victim->modeldata.summonkill == 1 && victim->subentity)
    {
        // kill only summoned one
        victim->subentity->parent = NULL;
        self = victim->subentity;
        attack.attack_force = self->energy_state.health_current;
        if(self->takedamage && !level_completed)
        {
            self->takedamage(self, &attack, 0, defense_object);
        }
        else
        {
            kill_entity(self, KILL_ENTITY_TRIGGER_PARENT_KILL_SUMMON);
        }
    }
    victim->subentity = NULL;

    for (i = 0; i < MAX_PLAYERS; i++) {
        if(victim == player[i].ent) {
            player[i].ent = NULL;
            break;
        }
    }

    if(victim == smartbomber)
    {
        smartbomber = NULL;
    }
    if(victim == textbox)
    {
        textbox = NULL;
    }

    for(i = 0; i < ent_max; i++)
    {
        if(ent_list[i]->exists)
        {
            // kill all minions
            self = ent_list[i];
            if(self->parent == victim)
            {
                self->parent = NULL;
                if(victim->modeldata.summonkill == 2)
                {
                    attack.attack_force = self->energy_state.health_current;
                    if(self->takedamage && !level_completed)
                    {
                        self->takedamage(self, &attack, 0, defense_object);
                    }
                    else
                    {
                        kill_entity(self, KILL_ENTITY_TRIGGER_PARENT_KILL_ALL);
                    }
                }
            }
            if(self->owner == victim)
            {
                self->owner = victim->owner;
            }
            if(self->opponent == victim)
            {
                self->opponent = NULL;
            }
            if(self->binding.target == victim)
            {
                self->binding.target = NULL;
            }
            if(self->landed_on_platform == victim)
            {
                self->landed_on_platform = NULL;
            }
            if(self->hithead == victim)
            {
                self->hithead = NULL;
            }
            if(self->lasthit == victim)
            {
                self->lasthit = NULL;
            }
            if(!textbox && (self->modeldata.type & TYPE_TEXTBOX))
            {
                textbox = self;
            }
        }
    }

    victim = NULL;

    //free_ent(victim);
    //victim = alloc_ent();

    self = tempent;
}


void kill_all()
{
    int i;
    entity *e = NULL;
    for(i = 0; i < ent_max; i++)
    {
        e = ent_list[i];
        if (e && e->exists)
        {
            execute_onkill_script(e, KILL_ENTITY_TRIGGER_ALL);
            clear_all_scripts(e->scripts, 1);
        }
        e->exists = 0; // well, no need to use kill function
    }
    textbox = smartbomber = NULL;
    _time = 0;
    ent_count = ent_max = ent_stack_size = 0;
    if(ent_list_size > MAX_ENTS) //shrinking...
    {
        free_ents();
        alloc_ents(); //this shouldn't return 0, because the list shrinks...
    }
}

/*
* Caskey, Damon V.
* 2022-04-20
* 
* Replacement for legacy canbegrabbed
* macro. Return true if target entity
* is eligible for grabbing.
*/
int check_canbegrabbed(entity* acting_entity, entity* target_entity)
{
    if (!target_entity->animation->vulnerable[target_entity->animpos])
    {
        return 0;
    }

    /*
    * Note move refers to "move<axis> animation 
    * commands, not if entity has velocity.
    */

    if (acting_entity->animation->move)
    {
        if (acting_entity->animation->move[acting_entity->animpos]->axis.x != 0)
        {
            return 0;
        }

        if (acting_entity->animation->move[acting_entity->animpos]->axis.z != 0)
        {
            return 0;
        }   
    }

    if (target_entity->nograb)
    {
        return 0;
    }

    if (target_entity->invincible & INVINCIBLE_INTANGIBLE)
    {
        return 0;
    }

    if (target_entity->link)
    {
        return 0;
    }

    if (target_entity->model->weapon_properties.weapon_state & WEAPON_STATE_ANIMAL)
    {
        return 0;
    }

    if (inair(target_entity))
    {
        return 0;
    }

    /*
    * DC 2022-04-20. 
    *  
    * This check seems very open-ended. I would
    * think savedata.mode should equal something
    * specific and not just "true". Will need to 
    * look over the menu options code to make 
    * sure this is behaving as intended.
    */

    if (acting_entity->modeldata.type & TYPE_PLAYER && target_entity->modeldata.type & TYPE_PLAYER)
    {   
        if (savedata.mode)
        {
            return 0;
        }   
    }

    return 1;
}

/*
* Caskey, Damon V.
* 2022-04-09
* 
* Replacement for legacy cangrab 
* macro. Return true if acting 
* entity can grab target entity.
*/
int check_cangrab(entity* acting_entity, entity* target_entity)
{
    int grab_resistance = 0;
    
    if (!check_canbegrabbed(acting_entity, target_entity))
    {
        return 0;
    }

    if (inair_range(acting_entity))
    {
        return 0;
    }

    if (diff(target_entity->position.y, acting_entity->position.y) > T_WALKOFF)
    {
        return 0;
    }    

    /*
    * Grab resistance. Acting entity's grab force
    * must exceed target's total grab resistance.
    *
    * If the target as paingrab property, we add
    * its value to grab resistance property if
    * the target's pain flag is not active.
    */
    
    grab_resistance = target_entity->modeldata.grab_resistance;

    if (target_entity->modeldata.paingrab)
    {
        if ((target_entity->inpain & IN_PAIN_HIT) == 0)
        {
            grab_resistance += target_entity->modeldata.paingrab;
        }
    }

    if (grab_resistance > acting_entity->modeldata.grab_force)
    {
        return 0;
    }

    return 1;    
}

/*
* Caskey, Damon V.
* 2020-02-04
*
* Test collision between two boxes. If any overlap is found,
* populates collision_check_data->return_overlap with overlap
* position and returns true.
*/
int check_collision(s_collision_check_data* collision_data)
{
	s_box seek_pos;
	s_box detect_pos;

    /*
	* X axis. 
	*
	* Before we can check X positions, we need to 
	* accomidate handle left/right flipping of
	* both the seeker and target.
    */

	if (collision_data->seeker_direction == DIRECTION_LEFT)
	{
		seek_pos.left = collision_data->seeker_pos->x - collision_data->seeker_coords->width;
		seek_pos.right = collision_data->seeker_pos->x - collision_data->seeker_coords->x;
	}
	else
	{
		seek_pos.left = collision_data->seeker_pos->x + collision_data->seeker_coords->x;
		seek_pos.right = collision_data->seeker_pos->x + collision_data->seeker_coords->width;
	}
	
	if (collision_data->target_direction == DIRECTION_LEFT)
	{
		detect_pos.left = collision_data->target_pos->x - collision_data->target_coords->width;
		detect_pos.right = collision_data->target_pos->x - collision_data->target_coords->x;
	}
	else
	{
		detect_pos.left = collision_data->target_pos->x + collision_data->target_coords->x;
		detect_pos.right = collision_data->target_pos->x + collision_data->target_coords->width;
	}

	// If we are out of bounds, there's no collision.
	if (seek_pos.left > detect_pos.right || seek_pos.right < detect_pos.left)
	{
		return 0;
	}

    /*
	* Y axis.
	*
	* This looks backwards, but we're not crazy.
	* 
	* The text input treats box as starting from
	* a Y position with a Y size that proceeds
	* downward, but when checking for collision
	* we do the opposite. So here the Y position
	* is our lower coordinate and Y size is the 
	* top coordinate.
    */

	seek_pos.bottom = collision_data->seeker_pos->y + -(collision_data->seeker_coords->height);
	seek_pos.top = collision_data->seeker_pos->y + - (collision_data->seeker_coords->y);

	detect_pos.bottom = collision_data->target_pos->y + -(collision_data->target_coords->height);
	detect_pos.top = collision_data->target_pos->y + - (collision_data->target_coords->y);

	// If we are out of bounds, there's no collision.
	if (seek_pos.bottom > detect_pos.top || seek_pos.top < detect_pos.bottom)
	{
		return 0;
	}
	
    /*
	* Z axis.
	*
	* Same principal as the left and right sides of X 
	* axis, except we don't have to worry about flipping 
	* direction.
	*/

	seek_pos.background = collision_data->seeker_pos->z - collision_data->seeker_coords->z_background;
	seek_pos.foreground = collision_data->seeker_pos->z + collision_data->seeker_coords->z_foreground;
	
	detect_pos.background = collision_data->target_pos->z - collision_data->target_coords->z_background;
	detect_pos.foreground = collision_data->target_pos->z + collision_data->target_coords->z_foreground;
	
    // If we are out of bounds, there's no collision.
    if (seek_pos.background > detect_pos.foreground || seek_pos.foreground < detect_pos.background)
	{
		return 0;
	}

    /*
	* If we got this far, find the collision area and apply
	* values to collision area box supplied by parent function.
	*
	* We do this by treating the collision area as a third 
	* box set of coordinates between the attack and detect
	* boxes. Then we find the center of our third box. This 
	* gives us a calculated center of the collision detection 
	* point.	
    */

	collision_data->return_overlap->left = seek_pos.left < detect_pos.left ? detect_pos.left : seek_pos.left;
	collision_data->return_overlap->right = seek_pos.right > detect_pos.right ? detect_pos.right : seek_pos.right;
    collision_data->return_overlap->center_x = (collision_data->return_overlap->left + collision_data->return_overlap->right) / 2;
    
    collision_data->return_overlap->bottom = seek_pos.bottom < detect_pos.bottom ? detect_pos.bottom : seek_pos.bottom;
	collision_data->return_overlap->top = seek_pos.top > detect_pos.top ? detect_pos.top : seek_pos.top;
    collision_data->return_overlap->center_y = (collision_data->return_overlap->top + collision_data->return_overlap->bottom) / 2;
        
    collision_data->return_overlap->background = seek_pos.background < detect_pos.background ? detect_pos.background : seek_pos.background;
    collision_data->return_overlap->foreground = seek_pos.foreground > detect_pos.foreground ? detect_pos.foreground : seek_pos.foreground;
    collision_data->return_overlap->center_z = (collision_data->return_overlap->background + collision_data->return_overlap->foreground) / 2;
    
	return 1;
}

/*
* Caskey, Damon V.
* 2020-02-04
*
* Send collision and entity data to last hit structure.
* This data is vital for checking hit reactions, spawning
* flash effects, performing hit overrides, populating 
* script variables and other post hit functionality.
*/
void populate_lasthit(s_collision_check_data* collision_data, s_collision_attack* collision_attack, s_collision_body* detect_collision_body, s_collision_attack* detect_collision_attack)
{
    /*
    * Why have both attack and collision_attack when
    * attack is a member of collision_attack?
    * 
    * There are times (like damage on landing) where
    * the lasthit structure is populated manually
    * and we don't have a collision container, only
    * the property object.
    */
    lasthit.collision_attack = collision_attack;
	lasthit.attack = collision_attack->attack;
    lasthit.detect_collision_body = detect_collision_body;
	lasthit.detect_body = detect_collision_body->body;
    lasthit.detect_collision_attack = detect_collision_attack;

	lasthit.position.x = collision_data->return_overlap->center_x;
	lasthit.position.y = collision_data->return_overlap->center_y;
	lasthit.position.z = collision_data->return_overlap->center_z;
	lasthit.target = collision_data->target_ent;
	lasthit.attacker = collision_data->seeker_ent;
	lasthit.confirm = 1;	
}

/*
* Caskey, Damon V.
* 2020-02-04
* 2021-08-23 - Reworked for linked list containers.
* 
* Check collisions of a seeking box vs. all of a target's
* current frame body boxes. Return pointer to 
* collision container detecting collision if found, NULL
* if no collision found.
*/
s_collision_body* check_collision_vs_body(s_collision_check_data* collision_check_data)
{
    s_anim* animation = collision_check_data->target_animation;
    int frame = collision_check_data->target_frame;    
    
    s_collision_body* detect_cursor;

    /* 
    * If there's no body allocated for target's
    * animation, we obviously can't have a collision
    * and going any further would throw a NULL pointer 
    * exception. Return FALSE now. 
    */

    if (!animation->collision_body)
    {
        return NULL;
    }

    /*
    * Starting from the head node, loop over all collision
    * nodes. At each node, populate collision_check_data 
    * structure with the collision object's coordinates 
    * pointer. Then we can run the check collision function.
    *
    * If the collision check function finds a collision we
    * return detect pointer and exit. If we pass over all 
    * collision objects without a collision, then we return 
    * a NULL.
    */

    detect_cursor = animation->collision_body[frame];

    while (detect_cursor != NULL && detect_cursor->coords != NULL)
    {
        collision_check_data->target_coords = detect_cursor->coords;

        if (check_collision(collision_check_data))
        {
            return detect_cursor;
        }

        detect_cursor = detect_cursor->next;
    }

    return NULL;
}

/*
* Caskey, Damon V.
* 2021-08-23
*
* Check collisions of a seeking box vs. all of a target's
* current frame attack boxes. Return pointer to
* collision container detecting collision if found, NULL
* if no collision found.
*/
s_collision_attack* check_collision_vs_attack(s_collision_check_data* collision_check_data)
{
    s_anim* animation = collision_check_data->target_animation;
    int frame = collision_check_data->target_frame;

    s_collision_attack* detect_cursor;

    /*
    * If there's no attack allocated for target's
    * animation, we obviously can't have a collision
    * and going any further would throw a NULL pointer
    * exception. Return FALSE now.
    */

    if (!animation->collision_attack)
    {
        return NULL;
    }

    /*
    * Starting from the head node, loop over all collision
    * nodes. At each node, populate collision_check_data
    * structure with the collision object's coordinates
    * pointer. Then we can run the check collision function.
    *
    * If the collision check function finds a collision we
    * return detect pointer and exit. If we pass over all
    * collision objects without a collision, then we return
    * a NULL.
    */

    detect_cursor = animation->collision_attack[frame];

    while (detect_cursor != NULL && detect_cursor->coords != NULL)
    {
        collision_check_data->target_coords = detect_cursor->coords;

        if (check_collision(collision_check_data))
        {
            return detect_cursor;
        }

        detect_cursor = detect_cursor->next;
    }

    return NULL;
}

int checkhit(entity *attacker, entity *target)
{
	/* 
    * Before we do anything else, let's make
	* make sure we aren't about to run collision
	* checks on ourself or a target with no
	* collision boxes active.
    */

    if(attacker == target
       || !target->animation->collision_body
       || !attacker->animation->collision_attack
       || !target->animation->vulnerable[target->animpos]
       )
    {
        return FALSE;
    }
    
    s_collision_attack* seek_cursor = NULL;
    
	s_collision_attack* detect_collision_attack = NULL;
	s_collision_body* detect_collision_body = NULL;
	s_collision_check_data collision_check_data;
    	
	/* 
    * We'll use these in collision check data
	* structure in lieu of memory allocations.
	*/
    s_axis_principal_int seeker_pos;
	s_axis_principal_int target_pos;
	s_box return_overlap;

	/* Get entity positions, cast as int. */
	seeker_pos.x = (int)attacker->position.x;
	seeker_pos.y = (int)attacker->position.y;
	seeker_pos.z = (int)attacker->position.z;
	target_pos.x = (int)target->position.x;
	target_pos.y = (int)target->position.y;
	target_pos.z = (int)target->position.z;

	/* 
    * Populate the collision data check structure pointers
	* with the address of local vars from this function.
	*/
    collision_check_data.seeker_pos = &seeker_pos;
	collision_check_data.target_pos = &target_pos;
	collision_check_data.return_overlap = &return_overlap;
    
    /* 
    * Populate collision check data with everything
    * we can before running loop checks. 
    */
    collision_check_data.seeker_ent = attacker;
    collision_check_data.target_ent = target;
    collision_check_data.target_animation = target->animation;
    collision_check_data.target_frame = target->animpos;
	
	collision_check_data.seeker_direction = attacker->direction;
	collision_check_data.target_direction = target->direction;

    /* New */
    if (!attacker->animation->collision_attack)
    {
        return FALSE;
    }

    /* Set seek cursor to seeker's collision list head. */
    seek_cursor = attacker->animation->collision_attack[attacker->animpos];
    
    /*
    * Iterate through seeker's collision list.
    * During iteration, we skip any collision node that 
    * does not have coordinates defined. This check might 
    * seem redundant because the model text read-in eliminates 
    * collision nodes without defined coordinates. However, 
    * it is possible for a creator to add collisions with 
    * script that bypass the model read-in criteria.
    */
    while (seek_cursor != NULL && seek_cursor->coords != NULL)
    {
        collision_check_data.seeker_coords = seek_cursor->coords;
        
        // TO DO: Don't use attack properties without verifying
        // attack is defined.
        
        /* Check against target body boxes. */
        detect_collision_body = check_collision_vs_body(&collision_check_data);

        if (detect_collision_body)
        {
            populate_lasthit(&collision_check_data, seek_cursor, detect_collision_body, detect_collision_attack);

            return TRUE;            
        }

        /*
        * If this is a counter attack let's check against the
        * target's attack boxes.
        */
        /*
        detect_collision_attack = check_collision_vs_attack(&collision_check_data);

        if (detect_collision_attack)
        {
            populate_lasthit(&collision_check_data, seek_cursor, detect_collision_body, detect_collision_attack);

            return TRUE;
        }
        */

        seek_cursor = seek_cursor->next;
    }
    
	/* 
    * If we made it here, then we were unable to find
	* any collisions, - return FALSE. 
    */

    return FALSE;
}


/*
* 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.
*/
int testhole(int hole, float x, float z)
{
    float coef1, coef2;
    if(z <= level->holes[hole].z && z >= level->holes[hole].z - level->holes[hole].depth)
    {
        coef1 = (level->holes[hole].z - z) * ((level->holes[hole].upperleft - level->holes[hole].lowerleft) / level->holes[hole].depth);
        coef2 = (level->holes[hole].z - z) * ((level->holes[hole].upperright - level->holes[hole].lowerright) / level->holes[hole].depth);
        if(x >= level->holes[hole].x + level->holes[hole].lowerleft + coef1 && x <= level->holes[hole].x + level->holes[hole].lowerright + coef2)
        {
            return 1;
        }
    }
    return 0;
}

// find all holes here and return the count
int checkholes(float x, float z)
{
    int i, c;

    for(i = 0, c = 0; i < level->numholes; i++)
    {
        c += testhole(i, x, z);
    }

    return c;
}

// find the 1st hole here
int checkhole(float x, float z)
{
    int i;

    if(level == NULL)
    {
        return 0;
    }

    for(i = 0; i < level->numholes; i++)
    {
        if(testhole(i, x, z))
        {
            holez = i;
            return 1;
        }
    }
    return 0;
}

// find all holes here within altitude1 and 2, return the count
int checkhole_between(float x, float z, float a1, float a2)
{
    int i, c;

    for(i = 0, c = 0; i < level->numholes; i++)
    {
        c += (testhole(i, x, z) && level->holes[i].height >= a1 && level->holes[i].height <= a2);
    }

    return c;
}

// get a highest hole below this altitude
int checkhole_in(float x, float z, float a)
{
    float maxa;
    int i, ind;

    if(level == NULL)
    {
        return 0;
    }

    maxa = -1;
    ind = -1;
    for(i = 0; i < level->numholes; i++)
    {
        if(testhole(i, x, z) && level->holes[i].height+T_WALKOFF >= a && level->holes[i].height > maxa) // && level->holes[i].height+T_WALKOFF >= a
        {
            maxa = level->holes[i].height;
            ind = i;
        }
    }

    if ( ind >= 0 ) {
        holez = ind;
        return 1;
    } else return 0;
}

// find the hole id for highest hole
int checkholeindex_in(float x, float z, float a)
{
    float maxa;
    int i, ind;

    if(level == NULL)
    {
        return -1;
    }

    maxa = -1;
    ind = -1;
    for(i = 0; i < level->numholes; i++)
    {
        if(testhole(i, x, z) && level->holes[i].height+T_WALKOFF >= a && level->holes[i].height > maxa) // && level->holes[i].height+T_WALKOFF >= a
        {
            maxa = level->holes[i].height;
            ind = i;
        }
    }

    return ind;
}

// find the 1st hole id here
int checkhole_index(float x, float z)
{
    int i;

    if(level == NULL)
    {
        return -1;
    }

    for(i = 0; i < level->numholes; i++)
    {
        if(testhole(i, x, z))
        {
            //holez = i;
            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.
*/
int testwall(int wall, float x, float z)
{
    float coef1, coef2;

    //if(wall >= level->numwalls || wall < 0) return 0;
    if(z <= level->walls[wall].z && z >= level->walls[wall].z - level->walls[wall].depth)
    {
        coef1 = (level->walls[wall].z - z) * ((level->walls[wall].upperleft - level->walls[wall].lowerleft) / level->walls[wall].depth);
        coef2 = (level->walls[wall].z - z) * ((level->walls[wall].upperright - level->walls[wall].lowerright) / level->walls[wall].depth);
        if(x >= level->walls[wall].x + level->walls[wall].lowerleft + coef1 && x <= level->walls[wall].x + level->walls[wall].lowerright + coef2)
        {
            return 1;
        }
    }

    return 0;
}

// find all walls here within altitude1 and 2, return the count
int checkwalls(float x, float z, float a1, float a2)
{
    int i, c;

    for(i = 0, c = 0; i < level->numwalls; i++)
    {
        c += (testwall(i, x, z) && level->walls[i].height >= a1 && level->walls[i].height <= a2);
    }

    return c;
}

// get a highest wall below this altitude
int checkwall_below(float x, float z, float a)
{
    float maxa;
    int i, ind;

    if(level == NULL)
    {
        return -1;
    }

    maxa = 0;
    ind = -1;
    for(i = 0; i < level->numwalls; i++)
    {
        if(testwall(i, x, z) && level->walls[i].height <= a && level->walls[i].height > maxa)
        {
            maxa = level->walls[i].height;
            ind = i;
        }
    }

    return ind;
}

// return the 1st wall found here
int checkwall_index(float x, float z)
{
    int i;
    if(level == NULL)
    {
        return WALL_INDEX_NONE;
    }

    for (i = 0; i < level->numwalls; i++)
    {
        if (testwall(i, x, z))
        {
            return i;
        }
    }

    return WALL_INDEX_NONE;
}

/*
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.
*/
int testplatform(entity *plat, float x, float z, entity *exclude)
{
    float coef1, coef2;
    float offz, offx;
    if(plat == exclude)
    {
        return 0;
    }
    if(!plat->animation || !plat->animation->platform || !plat->animation->platform[plat->animpos][PLATFORM_HEIGHT])
    {
        return 0;
    }
    offz = plat->position.z + plat->animation->platform[plat->animpos][PLATFORM_Z];
    offx = plat->position.x + plat->animation->platform[plat->animpos][PLATFORM_X];
    if(z <= offz && z >= offz - plat->animation->platform[plat->animpos][PLATFORM_DEPTH])
    {
        coef1 = (offz - z) * ((plat->animation->platform[plat->animpos][PLATFORM_UPPERLEFT] -
                               plat->animation->platform[plat->animpos][PLATFORM_LOWERLEFT]) / plat->animation->platform[plat->animpos][PLATFORM_DEPTH]);
        coef2 = (offz - z) * ((plat->animation->platform[plat->animpos][PLATFORM_UPPERRIGHT] -
                               plat->animation->platform[plat->animpos][PLATFORM_LOWERRIGHT]) / plat->animation->platform[plat->animpos][PLATFORM_DEPTH]);

        if(x >= offx + plat->animation->platform[plat->animpos][PLATFORM_LOWERLEFT] + coef1 &&
                x <= offx + plat->animation->platform[plat->animpos][PLATFORM_LOWERRIGHT] + coef2)
        {
            return 1;
        }
    }
    return 0;
}

//find the first platform between these 2 altitudes
entity *check_platform_between(float x, float z, float amin, float amax, entity *exclude)
{
    entity *plat = NULL;
    int i;

    if(level == NULL)
    {
        return NULL;
    }

    for(i = 0; i < ent_max; i++)
    {
        if(ent_list[i]->exists && testplatform(ent_list[i], x, z, exclude) )
        {
            plat = ent_list[i];
            if(plat->position.y <= amax && plat->position.y + plat->animation->platform[plat->animpos][PLATFORM_HEIGHT] > amin)
            {
                return plat;
            }
        }
    }
    return NULL;
}

//find a lowest platform above this altitude
entity *check_platform_above(float x, float z, float a, entity *exclude)
{
    float mina;
    entity *plat = NULL;
    int i, ind;

    if(level == NULL)
    {
        return NULL;
    }

    mina = 9999999;
    ind = -1;
    for(i = 0; i < ent_max; i++)
    {
        if(ent_list[i]->exists && testplatform(ent_list[i], x, z, exclude) )
        {
            plat = ent_list[i];
            if(plat->position.y >= a && plat->position.y < mina)
            {
                mina = plat->position.y;
                ind = i;
            }
        }
    }
    return (ind >= 0) ? ent_list[ind] : NULL;
}

//find a highest platform below this altitude
entity *check_platform_below(float x, float z, float a, entity *exclude)
{
    float maxa;
    entity *plat = NULL;
    int i, ind;

    if(level == NULL)
    {
        return NULL;
    }

    maxa = MIN_INT;
    ind = -1;
    for(i = 0; i < ent_max; i++)
    {
        if(ent_list[i]->exists && testplatform(ent_list[i], x, z, exclude) )
        {
            plat = ent_list[i];
            if(plat->position.y + plat->animation->platform[plat->animpos][PLATFORM_HEIGHT] <= a &&
                    plat->position.y + plat->animation->platform[plat->animpos][PLATFORM_HEIGHT] > maxa)
            {
                maxa = plat->position.y + plat->animation->platform[plat->animpos][PLATFORM_HEIGHT];
                ind = i;
            }
        }
    }
    return (ind >= 0) ? ent_list[ind] : NULL;
}

entity *check_platform_above_entity(entity *e)
{
    float mina;
    int heightvar;
    entity *plat = NULL;
    int i, ind;

    if(level == NULL)
    {
        return NULL;
    }
    if(e->animation->size.y)
    {
        heightvar = e->animation->size.y;
    }
    else
    {
        heightvar = e->modeldata.size.y;
    }

    mina = T_MAX_CHECK_ALTITUDE;
    ind = -1;
    for(i = 0; i < ent_max; i++)
    {
        if(ent_list[i]->exists && testplatform(ent_list[i], e->position.x, e->position.z, e) )
        {
            plat = ent_list[i];
            if(plat->position.y + plat->animation->platform[plat->animpos][PLATFORM_HEIGHT] > e->position.y + heightvar && plat->position.y < mina)
            {
                mina = plat->position.y;
                ind = i;
            }
        }
    }
    return (ind >= 0) ? ent_list[ind] : NULL;
}

entity *check_platform_below_entity(entity *e)
{
    float maxa, a;
    entity *plat = NULL;
    int i, ind;

    if(level == NULL)
    {
        return NULL;
    }

    maxa = MIN_INT;
    ind = -1;
    for(i = 0; i < ent_max; i++)
    {
        if(ent_list[i]->exists && testplatform(ent_list[i], e->position.x, e->position.z, e) )
        {
            plat = ent_list[i];
            a = plat->position.y + plat->animation->platform[plat->animpos][PLATFORM_HEIGHT];
            if(a < e->position.y + T_WALKOFF && plat->position.y < e->position.y && a > maxa)
            {
                maxa = a;
                ind = i;
            }
        }
    }
    return (ind >= 0) ? ent_list[ind] : NULL;
}

float checkbase(float x, float z, float y, entity *ent)
{
    float maxbase = -1.0f, base = -1.0f;
    int index = -1;
    entity *platform = NULL;

    base = (checkhole(self->position.x, self->position.z)) ? -1.0f : 0.0f;
    if (base > maxbase) maxbase = base;

    base = check_basemap(self->position.x, self->position.z);
    if (base > maxbase) maxbase = base;

    base = -1.0f;
    if((index = checkwall_below(x, z, T_MAX_CHECK_ALTITUDE)) >= 0)
    {
        base = level->walls[index].height;
        if (base > maxbase) maxbase = base;
    }

    base = -1.0f;
    platform = check_platform_below(x, z, y, ent);
    if (platform != NULL)
    {
        base = get_platform_base(platform);
        if (base > maxbase) maxbase = base;
    }

    return maxbase;
}

// find the 1st platform entity here
entity *check_platform(float x, float z, entity *exclude)
{
    int i;
    if(level == NULL)
    {
        return NULL;
    }

    for(i = 0; i < ent_max; i++)
    {
        if(ent_list[i]->exists && testplatform(ent_list[i], x, z, exclude))
        {
            return ent_list[i];
        }
    }
    return NULL;
}

float get_platform_base(entity *plat)
{
    float alt = 0;

    alt = plat->position.y;
    alt += plat->animation->platform[plat->animpos][PLATFORM_HEIGHT];

    return alt;
}

int is_on_platform(entity *ent)
{
    entity *plat = check_platform_below(ent->position.x,ent->position.z,ent->position.y+T_WALKOFF,ent);
    if (plat)
    {
        if ( diff(ent->base,get_platform_base(plat)) <= T_WALKOFF ) return 1;
    }

    return 0;
}

entity *get_platform_on(entity *ent)
{
    entity *plat = check_platform_below(ent->position.x,ent->position.z,ent->position.y+T_WALKOFF,ent);
    if (plat)
    {
        if ( diff(ent->base,get_platform_base(plat)) <= T_WALKOFF ) return plat;
    }

    return NULL;
}

// for adjust grab position function, test if an entity can move from a to b
// TODO: check points between the two pionts, if necessary
// special return values:
// -1: ent can jump over walls, for jump check
// -2: for hole ai reaction check
int testmove(entity *ent, float sx, float sz, float x, float z)
{
    entity *other = NULL, *platbelow = NULL;
    int wall, heightvar;
    float xdir, zdir;

    xdir = x - sx;
    zdir = z - sz;

    if(!xdir && !zdir)
    {
        return 1;
    }

    // -----------bounds checking---------------
    // Subjec to Z and out of bounds? Return to level!
    if (ent->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_MIN_Z)
    {
        if(zdir && z < PLAYER_MIN_Z)
        {
            return 0;
        }
    }

    if (ent->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_MAX_Z)
    {
        if(zdir && z > PLAYER_MAX_Z)
        {
            return 0;
        }
    }

    // screen checking
    // Kratus (29-04-21) Reduced the "screen checking" range from 10 to 5 to avoid the entities to stuck in the edge of the screen
    // This change was made because the "common_trymove" function also has another "screen checking" with a range of 10 too
    // If the "testmove" function has a equal or bigger range than the "common_trymove" function, sometimes the entities will stuck
    if(ent->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_SCREEN)
    {
        if(x < advancex + 5)
        {
            return 0;
        }
        else if(x > advancex + (videomodes.hRes - 5))
        {
            return 0;
        }
    }
    //-----------end of bounds checking-----------

    //-------------hole checking ---------------------
    if(ent->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_HOLE)
    {
        if(checkhole(x, z) && checkwall_index(x, z) < 0 && !check_platform_below(x, z, ent->position.y, ent))
        {
            return -2;
        }
    }
    //-----------end of hole checking---------------

    //--------------obstacle checking ------------------
    if(ent->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_OBSTACLE)
    {
        if((other = find_ent_here(ent, x, z, (TYPE_OBSTACLE | TYPE_TRAP), NULL)) &&
                (!other->animation->platform || !other->animation->platform[other->animpos][PLATFORM_HEIGHT]))
        {
            return 0;
        }
    }
    //-----------end of obstacle checking--------------

    // ---------------- platform checking----------------

    if(ent->animation->size.y)
    {
        heightvar = ent->animation->size.y;
    }
    else
    {
        heightvar = ent->modeldata.size.y;
    }

    // Check for obstacles with platform code and adjust base accordingly
    if(ent->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_PLATFORM && (other = check_platform_between(x, z, ent->position.y, ent->position.y + heightvar, ent)) )
    {
        platbelow = check_platform_below(x, z, ent->position.y+T_WALKOFF, ent);
        if ( !platbelow ) return 0;
        else
        {
            float palt = get_platform_base(platbelow);
            if ( other != platbelow && diff(ent->position.y,palt) > T_WALKOFF ) return 0;
        }
    }
    //-----------end of platform checking------------------

    // ------------------ wall checking ---------------------
    if(ent->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_WALL && (wall = checkwall_below(x, z, T_MAX_CHECK_ALTITUDE)) >= 0 && level->walls[wall].height > ent->position.y)
    {
        if(validanim(ent, ANI_JUMP) && sz < level->walls[wall].z && sz > level->walls[wall].z - level->walls[wall].depth) //Can jump?
        {
            //rmin = (float)ent->modeldata.animation[ANI_JUMP]->range.x.min;
            //rmax = (float)ent->modeldata.animation[ANI_JUMP]->range.x.max;
            if(level->walls[wall].height < ent->position.y + ent->modeldata.animation[ANI_JUMP]->range.x.max)
            {
                return -1;
            }
        }
        return 0;
    }
    //----------------end of wall checking--------------

    return 1;

}

// find real opponent
void set_opponent(entity *ent, entity *other)
{
    entity *realself, *realother;

    if(!ent)
    {
        return;
    }

    realself = ent;
    while(realself->owner)
    {
        realself = realself->owner;
    }

    realother = other;
    while(realother && realother->owner)
    {
        realother = realother->owner;
    }

    realself->opponent = ent->opponent = realother;
    if(realother)
    {
        realother->opponent = other->opponent = realself;
    }

}

/*
* Caskey, Damon V.
* 2023-04-10
*
* Accept string input and return
* matching constant.
*/
e_pain_config_flags pain_get_config_flag_from_string(const char* value)
{
    static const struct
    {
        const char* text_name;
        e_pain_config_flags flag;
    } item_lookup_table[] = {
        {"none", PAIN_CONFIG_NONE},
        {"back_pain", PAIN_CONFIG_BACK_PAIN},
        {"fall_disable", PAIN_CONFIG_FALL_DISABLE},
        {"fall_disable_air", PAIN_CONFIG_FALL_DISABLE_AIR},
        {"pain_disable", PAIN_CONFIG_PAIN_DISABLE}
    };

    const size_t list_count = sizeof(item_lookup_table) / sizeof(*item_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, item_lookup_table[i].text_name) == 0)
        {
            return item_lookup_table[i].flag;
        }
    }

    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * unknown flag.
    */

    printf("\n\n  Unknown pain config flag (%s). \n", value);
    return PAIN_CONFIG_NONE;    
}

/*
* Caskey, Damon V.
* 2023-04-10
*
* Get arguments and output final
* bitmask.
*/
e_pain_config_flags pain_get_config_flags_from_arguments(const ArgList* arglist)
{
    int i = 0;
    char* value = "";

    e_pain_config_flags result = PAIN_CONFIG_NONE;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= pain_get_config_flag_from_string(value);
    }

    return result;
}


/*
* Caskey, Damon V.
* 2023-04-05
*
* Accept string input and return
* matching constant.
*/
e_block_config_flags block_get_config_flag_from_string(const char* value)
{
    static const struct
    {
        const char* text_name;
        e_block_config_flags flag;
    } item_lookup_table[] = {
        {"none", BLOCK_CONFIG_NONE},
        {"active", BLOCK_CONFIG_ACTIVE},
        {"back", BLOCK_CONFIG_BACK},
        {"disabled", BLOCK_CONFIG_DISABLED},
        {"hold_impact", BLOCK_CONFIG_HOLD_IMPACT},
        {"hold_infinite", BLOCK_CONFIG_HOLD_INFINITE}
    };

    const size_t list_count = sizeof(item_lookup_table) / sizeof(*item_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, item_lookup_table[i].text_name) == 0)
        {
            return item_lookup_table[i].flag;
        }
    }

    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * unknown flag.
    */

    printf("\n\n  Unknown block config flag (%s). \n", value);
    return BLOCK_CONFIG_NONE;
}

/*
* Caskey, Damon V.
* 2023-04-05
*
* Get arguments and output final
* bitmask.
*/
e_block_config_flags block_get_config_flags_from_arguments(const ArgList * arglist)
{
    int i = 0;
    char* value = "";

    e_block_config_flags result = BLOCK_CONFIG_NONE;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= block_get_config_flag_from_string(value);
    }

    return result;
}

/* 
* Caskey, Damon V.
* 2018-12-31
* 
* Initialize appropriate block animation and flags. Called when 
* entity blocks actively (blocking before attack hits). Used 
* by all player controlled entities or AI controlled entities 
* with active block enabled. 
*/
void do_active_block(entity *ent)
{
	/* Run blocking action. */
	ent->takeaction = common_block;

	/* Stop movement. */
	ent->velocity.x = 0;
	ent->velocity.z = 0;

	/* Set flags. */
	set_blocking(ent);

	/* End combo. */
    ent->combostep[0] = 0;

	/* 
    * If we have a block tranisiton animation, use it. Otherwise
	* go right to block.
	*/
    if (validanim(ent, ANI_BLOCKSTART))
	{
		ent_set_anim(ent, ANI_BLOCKSTART, 0);
	}
	else
	{
		ent_set_anim(ent, ANI_BLOCK, 0);
	}
}

/*
* Caskey, Damon V.
* 2018-09-16
*
* Find out if attack can be blocked by entity.
* This function is concerned with the attack
* vs. entity in terms of game mechanics like
* guard break, attack type vs. defense, and
* so on. It does not handle rules for AI blocking.
*/
int check_blocking_eligible(entity *ent, entity *other, s_attack *attack, s_body *body) 
{
    s_defense* defense_object = NULL;
    int temp_block_threshold = 0;
     
	/* 
	* Kratus (10-2021) For safe, confirm if the entity's "BLOCKING" instance was gone or not
	* This is to avoid the entity to block while in other animations like RISE, PAIN or WALK.
	*/

	if (!ent->blocking)
	{
		return 0;
	}

	/* If guardpoints are set, then find out if they've been depleted. */
	
    if (ent->modeldata.guardpoints.max)
	{
		if (ent->modeldata.guardpoints.current <= 0)
		{
			return 0;
		}
	}
    
    /*
	* Attack from behind? Can't block that if
	* we don't have block back flag enabled.
	*/
    
    if (ent->direction == other->direction)
	{
		if (!(ent->modeldata.block_config_flags & BLOCK_CONFIG_BACK))
		{
			return 0;
		}
	}
        
    /* Need defense object for subsequent checks. */
    defense_object = defense_find_current_object(ent, body, attack->attack_type);

    /* Attack block breaking exceeds block power? */
    
    if (attack->no_block || defense_object->blockpower)
    {
        if (attack->no_block >= defense_object->blockpower)
        {
            return 0;
        }
    }

	/* 
    * Is there a blocking threshhold for the attack type?
	* Verify it vs. attack force.
	*/
    
    /* Is there a blocking threshold ? Verify it vs.attack force. */

    temp_block_threshold = ent->modeldata.thold + defense_object->blockthreshold;

    if (temp_block_threshold)
	{
		if (temp_block_threshold > attack->attack_force)
		{
			return 0;
		}
	}

    /*
	* If we made it through all that, then
	* attack can be blocked. Return true.
	*/

    return 1;
}

// Caskey, Damon V.
// 2018-09-19
//
// Mandatory conditions the AI must pass before it
// can decide to block. These are not rules for
// blocking in general.
int check_blocking_rules(entity *ent)
{
	// If already blocking we can
	// forget the rest and return
	// true right away.
	if (ent->blocking)
	{
		return 1;
	}

	// No blocking animation?
	if (!validanim(ent, ANI_BLOCK))
	{
		return 0;
	}

	// Have to be idle.
	if (!ent->idling)
	{
		return 0;
	}

	// AI can't be attacking.
	if (ent->attacking == ATTACKING_ACTIVE)
	{
		return 0;
	}

	// Grappling?
	if (ent->link)
	{
		return 0;
	}

	//  Airborne?
	if (inair(ent))
	{
		return 0;
	}

	// Frozen?
	if (ent->frozen)
	{
		return 0;
	}

	// Falling?
	if (ent->falling)
	{
		return 0;
	}

	return 1;
}

// Caskey, Damon V.
// 2018-09-17
//
// AI blocking decision. Handles AI's chances
// to block. Returns true if AI chooses to attempt 
// a block.
int check_blocking_decision(entity *ent)
{
	// If we have active block enabled and we're
	// already blocking, then we want the AI to
	// keep blocking (like most players would).
	if (ent->modeldata.block_config_flags & BLOCK_CONFIG_ACTIVE)
	{
		if (ent->blocking)
		{
			return 1;
		}
	}

	// Kratus (10-2021) Fixed the random blocking chance according with the "blockodds" value defined by the modder
	// Now it works as intended (1 = block all / 2147483647 = never block)
	// Run random chance against blockodds. If it
	// passes, AI will block.
	if ((rand32()&ent->modeldata.blockodds) == 0)
	{
		return 1;
	}

	// If we got this far, we never decided to
	// block, so return false.
	return 0;
}

// Caskey, Damon V.
// 2018-09-17
//
// Runs all blocking conditions and returns true
// if the attack should be blocked.
int check_blocking_master(entity *ent, entity *other, s_attack *attack, s_body *body)
{
	e_entity_type entity_type;

	entity_type = ent->modeldata.type;

    if (ent->modeldata.block_config_flags & BLOCK_CONFIG_DISABLED)
    {
        return 0;
    }

	// Check AI or player blocking rules.
	if (entity_type & TYPE_PLAYER)
	{
		// For players, all we need to know is if they
		// are in a blocking state. If not we exit.
		if (!ent->blocking)
		{
			return 0;
		}

		// Verify entity can block the attack at all.
		if (!check_blocking_eligible(ent, other, attack, body))
		{
			return 0;
		}
	}
	else
	{
		// AI must pass a series of conditions
		// before it may block attacks.
		if (!check_blocking_rules(ent))
		{
			return 0;
		}

		// Now that we know AI is allowed
		// to block let's find out if it
		// wants to.
		if (!check_blocking_decision(ent))
		{
			return 0;
		}

		// Verify entity can block the attack at all.
		if (!check_blocking_eligible(ent, other, attack, body))
		{
			return 0;
		}
	}

	// Looks like we made it through
	// all the verifications. Return true.
	return 1;
}

/* 
* Caskey, Damon V.
* 2018-09-18
*
* Apply primary block settings, animations,
* actions, and scripts.
*/
void set_blocking_action(entity *ent, entity *other, s_attack *attack)
{
	/* Execute the attacker's didhit script with blocked flag. */
	execute_didhit_script(other, ent, attack, 1);

	/* Set up blocking action and flag. */
	ent->takeaction = common_block;
	set_blocking(ent);	

	/* Stop ground movement. */
    ent->velocity.x = 0;
    ent->velocity.z = 0;

	/* If we have guardpoints, then reduce them here. */
	if (ent->modeldata.guardpoints.max > 0)
	{
		ent->modeldata.guardpoints.current -= attack->guardcost;
	}

	/* 
    * Blocked hit is still a hit, so
	* increment the attacker's hit counter.
	*/
    ++other->animation->hit_count;

	/* Execute our block script. */
	execute_didblock_script(ent, other, attack);
}

// Caskey, Damon V.
// 2018-09-18
//
// Verify entity has blockpain and that attack
// should trigger it.
int check_blocking_pain(entity *ent, s_attack *attack)
{
	// If blockpain is greater than attack
	// force, we don't apply it.
	if (attack->attack_force >= self->modeldata.blockpain)
	{
		return 0;
	}

	return 1;
}

// Caskey, Damon V.
// 2018-09-21
//
// Place entity into appropriate blocking animation.
void set_blocking_animation(entity *ent, s_attack *attack)
{
	// If we have an appropriate blockpain, lets
	// apply it here.
	if (check_blocking_pain(ent, attack))
	{
		set_blockpain(self, attack->attack_type, 1);
	}
	else
	{
		ent_set_anim(ent, ANI_BLOCK, 0);
	}
}

// Caskey, Damon V.
// 2018-09-21
//
// Perform a block.
void do_passive_block(entity *ent, entity *other, s_attack *attack)
{	
	// Place entity in blocking animation.
	set_blocking_animation(ent, attack);
	
	// Spawn the blocking flash.
	spawn_attack_flash(ent, attack, attack->blockflash, ent->modeldata.bflash);	

	// Run blocking actions and scripts.
	set_blocking_action(ent, other, attack);
}

/*
* Caskey, Damon V.
* 2018-09-18
*
* Handle flash spawning for hits. If conditions 
* are met, spawns and prepares an appropriate 
* flash effect entity at hit location.
*
* Returns pointer to flash entity if spawned, 
* or NULL if not.
*/
entity *spawn_attack_flash(entity *ent, s_attack *attack, int attack_flash, int model_flash)
{
    //printf("\n\n spawn_attack_flash(%p, %p, %d, %d)", ent, attack, attack_flash, model_flash);

	int model_index = MODEL_INDEX_NONE; // THe model we will spawn as flash entity.
	entity *flash = NULL;               // Flash entity pointer.
    float attacker_position_z = lasthit.position.z;
    float target_position_z = lasthit.position.z;
    int set_layer = 0;
    float position_z = 0.0;
    int flash_layer_source = 0;
    int flash_z_source = 0;

    /*
	* Flash disabled by attack?
	* We're done. Do nothing and exit.
	*/

    if (attack->no_flash)
	{
		return NULL;
	}

    /*
	* If the model doesn't allow incoming custom 
	* flash effects, default to the model's global 
    * flash.
	*
	* Otherwise we need to see if the custom
	* attack flash index is valid. If it is, then
	* we will use it to spawn a flash effect.
	*/

    if (!ent->modeldata.noatflash)
	{
		/* Valid custom flash index ? */
		if (attack_flash >= 0)
		{
            model_index = attack_flash;
		}
		else
		{
            model_index = model_flash;
		}
	}
	else
	{
        model_index = model_flash;
	}
        
    /*
    * Get the attacker and target Z position. 
    * We are using local variables here just 
    * in case the attacker or target are not
    * populated with valid entities.
    */

    if (lasthit.attacker)
    {
        attacker_position_z = lasthit.attacker->position.z;        
    }

    if (ent)
    {
        target_position_z = ent->position.z;
    }

    /*
    * Base Z position. This is the starting Z location
    * to spawn flash. Start with global config value
    * then apply adjustments (if any) from collison
    * objects. Then we determine source for base Z
    * using the total. 
    */

    flash_z_source = global_config.flash_z_source;
    flash_z_source += lasthit.attack ? lasthit.attack->flash_z_source : 0;
    flash_z_source += lasthit.detect_body ? lasthit.detect_body->flash_z_source : 0;

    if (!flash_z_source)
    {       
        position_z = lasthit.position.z;
    }
    else if (flash_z_source <= -255)
    { 
        position_z = attacker_position_z < target_position_z ? attacker_position_z : target_position_z;
    }
    else if (flash_z_source > -255 && flash_z_source < 0)
    {
        position_z = attacker_position_z;
    }
    else if (flash_z_source > 0 && flash_z_source < 255)
    {
        position_z = target_position_z;
    }
    else // >= 255
    {
        position_z = attacker_position_z > target_position_z ? attacker_position_z : target_position_z;
    }

	flash = spawn(lasthit.position.x, position_z, lasthit.position.y, DIRECTION_LEFT, NULL, model_index, NULL);

    if (!flash)
    {
        return NULL;
    }

    /*
    * Adjust the layer (sort id). Combine values
    * from attack, defense and global adjustment. 
    * 
    * Then we decide what to do from the total:
    * 
    * 0: Use highest Z (attacker vs. recipient).
    * <0: Use attacker.
    * >0: Use defender.
    * 
    * Then we combine all static adjustments and
    * apply to flash model's sort ID.
    */

    //printf("\n\t global_config.flash_layer_source: %d", global_config.flash_layer_source);
    
    flash_layer_source = global_config.flash_layer_source;
    flash_layer_source += lasthit.attack ? lasthit.attack->flash_layer_source : 0;
    flash_layer_source += lasthit.detect_body ? lasthit.detect_body->flash_layer_source : 0;

    //printf("\n\t flash_layer_source: %d", flash_layer_source);

    if (!flash_layer_source)
    {
        set_layer = position_z;
    }
    else if (flash_layer_source <= -255)
    {
        set_layer = attacker_position_z < target_position_z ? attacker_position_z : target_position_z;
    }
    else if (flash_layer_source > -255 && flash_layer_source < 0)
    {
        set_layer = attacker_position_z;
    }
    else if (flash_layer_source > 0 && flash_layer_source < 255)
    {
        set_layer = target_position_z;
    }
    else // >= 255
    {
        set_layer = attacker_position_z > target_position_z ? attacker_position_z : target_position_z;
    }

    //printf("\n\t set_layer: %d", set_layer);

    set_layer += global_config.flash_layer_adjust + lasthit.attack->flash_layer_adjust + lasthit.detect_body->flash_layer_adjust;
    
    //printf("\n\t set_layer (adjusted): %d", set_layer);

    flash->modeldata.setlayer = set_layer;

    //printf("\n\t flash->modeldata.setlayer: %d", flash->modeldata.setlayer);

    /* 
    * We have a valid flash entity, let's
    * set up its basic properties.
    */
    
    flash->spawntype = SPAWN_TYPE_FLASH;
    flash->base = lasthit.position.y;
    flash->autokill |= AUTOKILL_ANIMATION_COMPLETE;

    /*
    * If flipping enabled, flip the flash right 
    * if hit is to right of target entity's
    * position.
    */

    if (flash->modeldata.toflip)
    {
        if (lasthit.position.x > ent->position.x)
        {
            flash->direction = DIRECTION_RIGHT;
        }
    }
    else
    {
    /*
    * Kratus (10-2021) If the flag is 0, the flash will get the same direction as the defender
    * This change was made to avoid the "random" direction applied by "toflip 1", because it depends on the "lasthit" position
    * Without this line, the flash will never change the direction according to the defender's facing when "toflip" is 0
    */

        flash->direction = (ent->direction);
    }

    /* 
    * Run flash's spawn script and
    * return the flash pointer.
    */
    
    execute_onspawn_script(flash);

    return flash;
}

/*
* Caskey, Damon V. 
* 2019-11-24
*
* Check follow up conditions. Return true 
* if all conditions pass, false otherwise.
*/
int check_follow_up_condition(entity *ent, entity *target, s_anim *animation, int blocked)
{
	if (!animation->followup.animation)
	{
		return 0;
	}

	/* No follow up allowed. */
 	
    if (animation->followup.condition & FOLLOW_CONDITION_NONE)
	{
		return 0;
	}

	/* Always do follow up. */
	
    if (animation->followup.condition & FOLLOW_CONDITION_ANY)
	{
		return 1;
	}

	/* Block attack. */
	
    if (animation->followup.condition & FOLLOW_CONDITION_BLOCK_FALSE)
	{
		if (blocked)
		{
			return 0;
		}
	}
	
	if (animation->followup.condition & FOLLOW_CONDITION_BLOCK_TRUE)
	{
		if (!blocked)
		{
			return 0;
		}
	}

	/* Possible to grab target. */
	
    if (animation->followup.condition & FOLLOW_CONDITION_GRAB_FALSE)
	{
		if (check_cangrab(ent, target))
		{
			return 0;
		}
	}
	
	if (animation->followup.condition & FOLLOW_CONDITION_GRAB_TRUE)
	{
		if (!check_cangrab(ent, target))
		{
			return 0;
		}
	}

	/* We are hostile toward target. */
	
    if (animation->followup.condition & FOLLOW_CONDITION_HOSTILE_ATTACKER_FALSE)
	{
        if (faction_check_is_hostile(ent, target))
        {
            return 0;
        }
	}

	if (animation->followup.condition & FOLLOW_CONDITION_HOSTILE_ATTACKER_TRUE)
	{
        if (!faction_check_is_hostile(ent, target))
        {
            return 0;
        }
	}

	/* Target is hostile toward us. */
	
    if (animation->followup.condition & FOLLOW_CONDITION_HOSTILE_TARGET_FALSE)
	{
        if (faction_check_is_hostile(target, ent))
        {
            return 0;
        }
	}

	if (animation->followup.condition & FOLLOW_CONDITION_HOSTILE_TARGET_TRUE)
	{
        if (!faction_check_is_hostile(target, ent))
        {
            return 0;
        }
	}

	/* Lethal damage. */
	
    if (animation->followup.condition & FOLLOW_CONDITION_LETHAL_FALSE)
	{
		if (target->energy_state.health_current <= 0)
		{
			return 0;
		}
	}

	if (animation->followup.condition & FOLLOW_CONDITION_LETHAL_TRUE)
	{
		if (target->energy_state.health_current > 0)
		{
			return 0;
		}
	}

	/* If all checks passed, return true. */
	
    return 1;
}

/*
* Caskey, Damon  V.
* 2019-11-24
*
* Attempt to perform follow up animation. 
* If successful, sets entity animation to 
* appropriate follow up and returns true.
*/
int try_follow_up(entity *ent, entity *target, s_anim *animation, int didblock)
{
	e_animations animation_id = ANI_NONE;

	/* If we don't have a follow action, get out. */
	
    if (!animation->followup.animation)
	{
		return 0;
	}

	/* Must meet follow up conditions. */
	
    if (!check_follow_up_condition(ent, target, animation, didblock))
	{
		return 0;
	}
		
	/* If we have the animation, then execute it now. */
	
    animation_id = animfollows[animation->followup.animation - 1];
	
	if (validanim(ent, animation_id))
	{		
		ent_set_anim(ent, animation_id, 1); 

		return 1;
	}	

	return 0;
}

/*
* Caskey, Damon V.
* 2019-12-03
*
* Verify an attack meets conditions to trigger a counter action.
*/
int check_counter_condition(entity* target, entity* attacker, s_attack* attack_object, s_body* body_object)
{
	s_counter_action* counter = &target->animation->counter_action;
    s_defense* defense_object = NULL;
    int force = 0;

	/* If there's no condition, get out now. */
	if (!counter->condition)
	{
		return 0;
	}

	/* Verify in the frame range. */
	if (target->animpos < counter->frame.min || target->animpos > counter->frame.max)
	{
		return 0;
	}
	
	/* Now we verify condition flags. */

	/* Always is always... */
	if (counter->condition == COUNTER_ACTION_CONDITION_ANY)
	{
		return 1;
	}

	/* In the back ? */
	if (counter->condition & COUNTER_ACTION_CONDITION_BACK_FALSE)
	{
		if (target->direction == attacker->direction)
		{
			return 0;
		}
	}

	if (counter->condition & COUNTER_ACTION_CONDITION_BACK_TRUE)
	{
		if (target->direction != attacker->direction)
		{
			return 0;
		}
	}
	
    /* We need defense object for subsequent checks. */
    defense_object = defense_find_current_object(target, body_object, attack_object->attack_type);

	/* Blockable ? */
	if (counter->condition & COUNTER_ACTION_CONDITION_BLOCK_FALSE)
	{
		if (attack_object->no_block <= defense_object->blockpower)
		{
			return 0;
		}
	}

	if (counter->condition & COUNTER_ACTION_CONDITION_BLOCK_TRUE)
	{
		if (attack_object->no_block > defense_object->blockpower)
		{
			return 0;
		}
	}

	/* Vs.lethal / non - lethal damage. */
	force = calculate_force_damage(target, attacker, attack_object, defense_object);

	if (counter->condition & COUNTER_ACTION_CONDITION_DAMAGE_LETHAL_FALSE)
	{
		if (target->energy_state.health_current <= force)
		{
			return 0;
		}
	}

	if (counter->condition & COUNTER_ACTION_CONDITION_DAMAGE_LETHAL_TRUE)
	{
		if (target->energy_state.health_current > force)
		{
			return 0;
		}
	}

	/* Freeze attack ? */
	if (counter->condition & COUNTER_ACTION_CONDITION_FREEZE_FALSE)
	{
		if (attack_object->freeze)
		{
			return 0;
		}
	}

	if (counter->condition & COUNTER_ACTION_CONDITION_FREEZE_TRUE)
	{
		if (!attack_object->freeze)
		{
			return 0;
		}
	}

	/* Attacker hostile to us ? */
	if (counter->condition == COUNTER_ACTION_CONDITION_HOSTILE_ATTACKER_FALSE)
	{
        if (faction_check_is_hostile(attacker, target))
        {
            return 0;
        }
	}

	if (counter->condition == COUNTER_ACTION_CONDITION_HOSTILE_ATTACKER_TRUE)
	{
        if (!faction_check_is_hostile(attacker, target))
        {
            return 0;
        }
	}

	/* Hostile to attacker ? */
	if (counter->condition == COUNTER_ACTION_CONDITION_HOSTILE_TARGET_FALSE)
	{
        if (faction_check_is_hostile(target, attacker))
        {
            return 0;
        }
	}

	if (counter->condition == COUNTER_ACTION_CONDITION_HOSTILE_TARGET_TRUE)
	{
        if (!faction_check_is_hostile(target, attacker))
        {
            return 0;
        }
	}

	/* Passed all checks. We can return true. */
	return 1;
}

/*
* Caskey, Damon  V.
* 2019-12-04
*
* Attempt to perform counter action animation. 
* If successful, sets entity animation to
* appropriate counter and returns true.
*/
int try_counter_action(entity* target, entity* attacker, s_attack* attack_object, s_body* body_object)
{
	int force = 0;
	int current_follow_id = 0;
    s_defense* defense_object = NULL;

    /*
	* If we don't have a follow animation to use 
	* for counter, get out.
	*/
    
    if (!target->animation->followup.animation)
	{
		return 0;
	}

	/* Must meet counter action conditions. */
	
    if (!check_counter_condition(target, attacker, attack_object, body_object))
	{
		return 0;
	}	

	/* Take damage from attack ? */
	
    if (target->animation->counter_action.damaged == COUNTER_ACTION_TAKE_DAMAGE_NORMAL)
	{
		/* We need the real damage. */
        defense_object = defense_find_current_object(target, body_object, attack_object->attack_type);

		force = calculate_force_damage(target, attacker, attack_object, defense_object);

		/* Revert lethal damage to 1. */
		if (target->energy_state.health_current - force <= 0)
		{
			target->energy_state.health_current = 1;
		}
		else
		{
			target->energy_state.health_current -= force;
		}
	}

	/* Set counter animation if we can. */
	
    current_follow_id = animfollows[target->animation->followup.animation - 1];
	
    if (validanim(self, current_follow_id))
	{
		if (!target->modeldata.animation[current_follow_id]->attack_one)
		{
			target->modeldata.animation[current_follow_id]->attack_one = target->animation->attack_one;
		}
		ent_set_anim(target, current_follow_id, 0);
	}

	/* Flash spawn. */
	spawn_attack_flash(target, attack_object, attack_object->blockflash, target->modeldata.bflash);

	return 1;
}

/*
* Caskey, Damon V.
* 2021-09-04
* 
* Update attack IDs to avoid single attack 
* hitting on every update. Original concept
* of multiple attack IDs by Kratus. Migrated 
* to array and encapsulated into function
* by DC.
*/
void attack_update_id(entity* acting_entity, int attack_id)
{
    int i = 0;
    int i_source = 0;

    /* 
    * Loop backward from highest element to second lowest.
    * At each iteration, update the current attack ID
    * element in array with value from element one lower 
    * in order.
    * 
    * Ex: array[4] = array[3]
    */
    for(i = MAX_ATTACK_IDS-1; i > 0; i--)
    {
        i_source = i - 1;

        acting_entity->attack_id_incoming[i] = acting_entity->attack_id_incoming[i_source];
    }

    /* Update element 0 with supplied ID. */
    acting_entity->attack_id_incoming[0] = attack_id;
}

/*
* Caskey, Damon V.
* 2021-09-04
* 
* Compare supplied attack ID with existing 
* attack IDs. Returns true if any match or 
* if rule exceptions from attack or multihit 
* are enabled.
*/
int attack_id_check_match(entity* acting_entity, s_attack* attack_object, int attack_id, int multihit)
{
    int i = 0;
    int max_id = MAX_ATTACK_IDS;

    /* 
    * Attack is allowed to ignore ID checks, so 
    * just return true now.
    */
    if (attack_object->ignore_attack_id)
    {
        return 1;
    }

    /* 
    * If mutihit is enabled, we only want
    * check the first ID. Set our max to 1
    * so loop only runs for element 0.
    */
    if (multihit)
    {
        max_id = 1;
    }
   
    /* 
    * If any array element value matches supplied
    * attack ID, we return true.
    */
    for (i = 0; i < max_id; i++)
    {
        if (acting_entity->attack_id_incoming[i] == attack_id)
        {
            return 1;
        }
    }

    /* Couldn't find any match or exception. Return false. */
    return 0;
}

void do_attack(entity *attacking_entity)
{
    int indirect = 0;
    int i = 0;
    int force = 0;
    e_blocktype blocktype       = BLOCK_TYPE_MP_FIRST;
    entity* temp                = NULL;
    entity* def                 = NULL;
    entity* topowner            = NULL;
    entity* otherowner          = NULL;
    entity* target              = NULL;
    s_anim* current_anim        = NULL;
    s_attack* attack            = NULL;
    s_defense* defense_object = NULL;
    s_body* target_body_object  = NULL;
    int didhit              = 0;
    int didblock            = 0;    // So a different sound effect can be played when an attack is blocked
    int current_attack_id   = 0;
    //int hit_detected        = 0;    // Has a hit been detected?


#define followed (current_anim!=attacking_entity->animation)
    static unsigned int new_attack_id = 1;

    // Can't get hit after this
    if(level_completed)
    {
        return;
    }

    topowner = attacking_entity; // trace the top owner, for projectile combo checking :)
    while(topowner->owner)
    {
        topowner = topowner->owner;
    }

	// If any blast active, use indirect damage downstream.
    if(attacking_entity->projectile != BLAST_NONE)
    {
        indirect = 1;
    }

    // Every attack gets a unique ID to make sure no one
    // gets hit more than once by the same attack
    current_attack_id = attacking_entity->attack_id_outgoing;

    if(!current_attack_id)
    {
        ++new_attack_id;
        if(new_attack_id == 0)
        {
            new_attack_id = 1;
        }
        attacking_entity->attack_id_outgoing = current_attack_id = new_attack_id;
    }


    current_anim = attacking_entity->animation;

    for(i = 0; i < ent_max && !followed; i++)
    {
        target = ent_list[i];

        if(!target->exists)
        {
            continue;
        }

        // Check collision. If a collision
        // is found, the impacting
        // collision pointers are also
        // populated into lasthit, which
        // we will use below.
        if(!checkhit(attacking_entity, target))
        {
            continue;
        }
                
        attack = lasthit.attack;
        force = attack->attack_force;
        target_body_object = lasthit.detect_collision_body->body;
        defense_object = defense_find_current_object(target, target_body_object, attack->attack_type);

        // Verify target is alive.
        if(target->death_state & DEATH_STATE_DEAD)
        {
            continue;
        }

        // Verify target is invincible,
        // or attack type is an item.
        // This is to allow item collection
        // even while invincible.
        if(target->invincible & INVINCIBLE_INTANGIBLE)
        {
            if(attack->attack_type != ATK_ITEM)
            {
                continue;
            }
        }

        // If attack is set to only hit
        // one entity at a time (attack_one),
        // we verify last hit (lasthit) is
        // set. If last hit is set and
        // differs from current target,
        // then we are trying to hit
        // another entity and should exit.
        if(current_anim->attack_one)
        {
            if(attacking_entity->lasthit)
            {
                if(target != attacking_entity->lasthit)
                {
                    continue;
                }
            }
        }

        /*
        * Verify this is a faction we
        * can hit.
        */
        if (!faction_check_can_damage(attacking_entity, target, indirect))
        {
            continue;
        }        

        /*
        * Pain time must have expired.
        * This is to allow reasonable delay
        * between hits so engine will not
        * run hit on every update.
        */
        if(target->next_hit_time >= _time)
        {
            continue;
        }

        // Target takedamage flag
        // must be set.
        if(!target->takedamage)
        {
            continue;
        }
        
        /* 
        * Avoid mutiple hits per update for a single collision.
        * If any last incoming attack IDs match current attack ID
        * then we exit iteration.
        * 
        * Note that function includes exceptions for an attacks 
        * that ignore IDs and the global mutlihit cheat.
        */
        if (attack_id_check_match(target, attack, current_attack_id, (global_config.cheats & CHEAT_OPTIONS_MULTIHIT_ACTIVE)))
        {
            continue;
        }

		// Target laying down? Exit if
        // attack only hits standing targets.
		// Otherwise exit if attack only hits 
		// grounded targets.
        if(target->takeaction == common_lie)
        {
            if(attack->otg == OTG_NONE)
            {
                continue;
            }
        }
		else
		{
            if(attack->otg == OTG_GROUND_ONLY)
            {
                continue;
            }
        }

        // If in the air, then check the juggle cost.
        if(inair(target))
        {
            if(attack->jugglecost > target->modeldata.jugglepoints.current)
            {
                continue;
            }
        }

        temp = self;
        self = target;

        // Execute the doattack scripts so author can set take action
        // before the hit code below does.
        execute_ondoattack_script(self, attacking_entity, attack, EXCHANGE_RECIPIANT, current_attack_id);
        execute_ondoattack_script(attacking_entity, self, attack, EXCHANGE_CONFERRER, current_attack_id);

        // 2010-12-31
        // Damon V. Caskey

        // If lasthit.confirm is not true, it must have been turned off by the author; almost
        // certainly with the ondoattack event scripts above. Skip the engine's
        // default hit handling below. Useful for scripting parry systems, alternate blocking,
        // or other custom collision events.
        if(lasthit.confirm)
        {
            didhit = 1;
        }
        else
        {
            // By White Dragon
            // This line: self = temp; is the fix for
            // !lasthit.confirm bug. Without it when
            // active lasthitc 0 the damagetaker has
            // weird speedy effect.
            self = temp;
            continue;
        }

        otherowner = self; // trace top owner for opponent
        while(otherowner->owner)
        {
            otherowner = otherowner->owner;
        }

        // Ensure projectile can't hit its owner. Important since projectile attack
		// boxes usualy overlap owner's body boxes as the projectile is first thrown.
		// Note if an author wants to add projectile reflection feature, they need to
		// change the projectile's owner when reflect effect occurs, or the reflected 
		// projectile won't be able to hit its original owner. If they need to know the 
		// original owner after changing the owner property, they can check the parent 
		// property.
		if(topowner == otherowner)
        {
            didhit = 0;
        }

        //Ground missle checking, and bullets wont hit each other
        if( (attacking_entity->owner && self->owner) ||
                (attacking_entity->modeldata.ground && inair(attacking_entity)))
        {
            didhit = 0;
        }

        // Blocking code section.
        if(didhit)
        {
            if(attack->attack_type == ATK_ITEM)
            {
                do_item_script(self, attacking_entity);

                didfind_item(attacking_entity);
                return;
            }
            
			/* 
            * Set bomb projectile to detonate status if it
            * hits. Set to detonate if it takes a hit and
            * has the "take hit" detonate animation. 
            * 
            * Bombs that don't have detonate animation take
            * hits normally.
            */

            if(self->toexplode & EXPLODE_PREPARE_TOUCH)
            {
                if (validanim(self, ANI_ATTACK2))
                {
                    self->toexplode |= EXPLODE_DETONATE_DAMAGED;
                }
            }
           
            if(attacking_entity->toexplode & EXPLODE_PREPARE_TOUCH)
            {
                attacking_entity->toexplode |= EXPLODE_DETONATE_HIT;
            }

            if(inair(self))
            {
                self->modeldata.jugglepoints.current = self->modeldata.jugglepoints.current - attack->jugglecost;    //reduce available juggle points.
            }

            didblock = check_blocking_master(self, attacking_entity, attack, target_body_object);

            // Blocking the attack?
            if(didblock)
            {
                // Perform the blocking actions.
                do_passive_block(self, attacking_entity, attack);
            }
            // Counter the attack? 
           	else if(try_counter_action(self, attacking_entity, attack, target_body_object))
			{		
                /* Kratus(20 - 04 - 21) used by the multihit glitch memorization. */
                attack_update_id(self, current_attack_id);
            }
            else if(self->takedamage(attacking_entity, attack, 0, defense_object))
            {
                

                // This is the block for normal hits. The
                // hit was not blocked, countered, or
                // otherwise nullified, and this entity
                // has takedamage() function. Let's
                // process the hit.

                execute_didhit_script(attacking_entity, self, attack, 0);
                ++attacking_entity->animation->hit_count;

                attacking_entity->lasthit = self;

                // Flash spawn.
                spawn_attack_flash(self, attack, attack->hitflash, self->modeldata.flash);

				// Add to owner's combo time.
                topowner->combotime = _time + combodelay; 

				// If equalairpause is set, inair(attacking_entity) is nolonger a condition for extra pausetime.
                if(attacking_entity->pausetime < _time || (inair(attacking_entity) && !equalairpause))
                {
                    // Adds pause to the current animation
                    attacking_entity->toss_time += attack->pause_add;      // So jump height pauses in midair
                    attacking_entity->nextmove += attack->pause_add;      // xdir, zdir
                   
                    if (attacking_entity->nextanim != DELAY_INFINITE) { attacking_entity->nextanim += attack->pause_add; }

                    attacking_entity->nextthink += attack->pause_add;      // So anything that auto moves will pause
                    attacking_entity->pausetime = _time + attack->pause_add ; //UT: temporary solution
                }

                self->toss_time += attack->pause_add;       // So jump height pauses in midair
                self->nextmove += attack->pause_add;      // xdir, zdir
                self->nextanim += attack->pause_add;        //Pause animation for a bit
                self->nextthink += attack->pause_add;       // So anything that auto moves will pause

            }
            else
            {
                // If we made it to this block the hit was
                // not countered or blocked, but the entity
                // does not have a takedamage() function. It
                // therefore must be a type that is meant
                // to ignore hits.

                didhit = 0;
                continue;
            }

            // 2007 3 24, hmm, def should be like this
            if(didblock && !def)
            {
                def = self;
            }
            
			// Attacker executes a follow up animation if it can.
			try_follow_up(attacking_entity, self, attacking_entity->animation, didblock);

            /* Kratus(20 - 04 - 21) used by the multihit glitch memorization. */
            attack_update_id(self, current_attack_id);

			// If hit, stop blocking.
			if(self == def)
            {
                self->blocking = didblock;   
            }

            /*
			* Utunnels
            * 2011-11-24 UT
			*
			* Move the next_hit_time logic here, because block needs this 
			* as well. Otherwise, blockratio causes instant death
            */
            self->next_hit_time = _time + (attack->next_hit_time ? attack->next_hit_time : (GAME_SPEED / 5));
            self->nextattack = 0; // reset this, make it easier to fight back
        }
        self = temp;

    }


    // Did we get a hit? let's process it.
    if(didhit)
    {
		// Handle energy cost if attacking animation has any.

		// Caskey, Damon V.
		// 2020-01-22
		//
		// I'm honestly not sure how the legacy logic works. Will need to spend
		// some more time breaking it down.

        if(current_anim->energy_cost.cost > 0)
        {
            // well, dont check player or not - UTunnels. TODO: take care of that health cheat
            if(attacking_entity == topowner && nocost && !(global_config.cheats & CHEAT_OPTIONS_HEALTH_ACTIVE))
            {
                attacking_entity->tocost = 1;    // Set flag so life is subtracted when animation is finished
            }
            else if(attacking_entity != topowner && nocost && !(global_config.cheats & CHEAT_OPTIONS_HEALTH_ACTIVE) && !attacking_entity->tocost) // if it is not top, then must be a shot
            {
                if(current_anim->energy_cost.mponly != COST_TYPE_MP_THEN_HP && topowner->energy_state.mp_current > 0)
                {
                    topowner->energy_state.mp_current -= current_anim->energy_cost.cost;
                    if(topowner->energy_state.mp_current < 0)
                    {
                        topowner->energy_state.mp_current = 0;
                    }
                }
                else
                {
                    topowner->energy_state.health_current -= current_anim->energy_cost.cost;
                    if(topowner->energy_state.health_current <= 0)
                    {
                        topowner->energy_state.health_current = 1;
                    }
                }

				// Little backwards, but set to 1 so cost doesn't get subtracted multiple times.
                attacking_entity->tocost = 1;
            }
        }

        /*
		* Caskey, Damon V.
        * 2008-04-27 
		*
		* Added checks for defense property specific blockratio and type. 
		* Could probably use some cleaning.
        */
        if(didblock && level->nohurt == DAMAGE_FROM_ENEMY_ON)
        {
            /* Gets total force after defense adjustments. */
            force = defense_result_damage(defense_object, force, 1);            
            
            /*
            * Handle block type. Global blocktype is a 
            * legacy true/false set by "mpblock", so If 
            * our defense object defaults to global we
            * will need to get the correct constant 
            * before we run downstream switch statement.
            *
            * True = MP then HP.
            * False = HP.
            */

            if (defense_object->blocktype == BLOCK_TYPE_GLOBAL)
            {
                blocktype = global_config.block_type ? BLOCK_TYPE_MP_FIRST : BLOCK_TYPE_HP;
            }
            else
            {
                blocktype = defense_object->blocktype;
            }

            switch (blocktype)
            {
                case BLOCK_TYPE_GLOBAL:
                case BLOCK_TYPE_HP:
                    /* 
                    * Do nothing. This allows creators to overidde energy_cost 
                    * mponly 1 with health only. 
                    */
                    break;

                case BLOCK_TYPE_MP_ONLY:

                    def->energy_state.mp_current -= force;
                    force = 0;

                    if(def->energy_state.mp_current < 0)
                    {
                        def->energy_state.mp_current = 0;
                    }

                    break;

                case BLOCK_TYPE_MP_FIRST:

                    def->energy_state.mp_current -= force;

                    /*
                    * If there isn't enough MP to cover force, subtract remaining 
					* MP from force and set MP to 0.
                    */
                    if(def->energy_state.mp_current < 0)
                    {
                        force = -def->energy_state.mp_current;
                        def->energy_state.mp_current = 0;
                    }
                    else
                    {
                        force = 0;
                    }

                    break;

                case BLOCK_TYPE_BOTH:

                    def->energy_state.mp_current -= force;

                    if(def->energy_state.mp_current < 0)
                    {
                        def->energy_state.mp_current = 0;
                    }

                    break;
            }

			// Apply remaining damage force to HP after blocking calculations.
			// 
			// 1. Damage force < HP: Subtract directly - Don't use take 
			// damage because we don't want a visible reaction in game.
			//
			// 2. Damage force > HP, but nochipdeath is enabled (meaning
			// chip death is not allowed) - Set HP to 1. Again, we don't 
			// want a reaction.
			//
			// 3. Damage force > HP, chip death is allowed - Set take 
			// damage so engine will apply damage normally and KO the 
			// entity.
            if(force < def->energy_state.health_current)
            {
                def->energy_state.health_current -= force;
            }
            else if(nochipdeath)
            {
                def->energy_state.health_current = 1;
            }
            else
            {
                temp = self;
                self = def;
                self->takedamage(attacking_entity, attack, 0, defense_object);
                self = temp;
            }            
        }

		// If the attack was not blocked, let's increment the
		// attacker's combo counter and time.
        if(!didblock)
        {
            topowner->rush.time = _time + (GAME_SPEED * rush[1]);
            topowner->rush.count++;
            if(topowner->rush.count > topowner->rush.max && topowner->rush.count > 1)
            {
                topowner->rush.max = topowner->rush.count;
            }
        }

        /* Choose the sound and playback speed for hit. */
        play_hit_impact_sound(attack, attacking_entity, didblock);

		/* 
        * If the auto kill flag is set, attacker 
		* kills itself instantly. Used mainly for
		* projectiles.
        */
        if(attacking_entity->autokill & AUTOKILL_ATTACK_HIT)
        {
            kill_entity(attacking_entity, KILL_ENTITY_TRIGGER_AUTOKILL_ATTACK_HIT);
        }
    }
#undef followed
}

/*
* Caskey, Damon V
* 2021-11-16
* 
* Play appropriate hit impact sound 
* based on hit/block, attack force, 
* and defined samples.
*/
int play_hit_impact_sound(s_attack* attack_object, entity* attacking_entity, int attack_blocked)
{
#define PLAYBACK_PRIORITY 0
#define PLAYBACK_SPEED_MIN 60
#define PLAYBACK_SPEED_MAX 100
#define PLAYBACK_SPEED_MODIFIER 5

    int sound_index = SAMPLE_ID_NONE;
    int playback_speed = PLAYBACK_SPEED_MAX;
    
    /*
    * Play the impact sound effect.
    *
    * 1. Attack blocked.
    *	a) Attack has block sound - play attack block sound.
    *	b) Attack does not have block sound - play global block sound.
    *
    * 2. Attacker is actually a "projectile" (as in, thrown or knocked
    * through the air by a blasting attack) - play global indirect sound.
    *
    * 3. Typical attack hit.
    *	a) Attack has hit sound - play attack hit sound.
    *	b) Attack does not have hit sound - play global hit sound.
    */

    if (attack_blocked)
    {
        if (attack_object->blocksound >= 0)
        {
            sound_index = attack_object->blocksound;
        }
        else if (global_sample_list.block >= 0)
        {
            sound_index = global_sample_list.block;
        }
    }
    else if (attacking_entity->projectile != BLAST_NONE && global_sample_list.indirect >= 0)
    {
        sound_index = global_sample_list.indirect;
    }
    else if (attack_object->hitsound >= 0)
    {
        sound_index = attack_object->hitsound;

        /*
        * If noslofx is enabled, then just play sound at default speed.
        * Otherwise, subtract 5 from damage force, and then subtract
        * that result from default play speed.
        *
        * This has the effect of slowing down the sound playback as damage
        * increases to give it a slightly lower tone and more impact.
        */

        playback_speed = PLAYBACK_SPEED_MAX - (noslowfx ? 0 : (attack_object->attack_force - PLAYBACK_SPEED_MODIFIER));

        if (playback_speed > PLAYBACK_SPEED_MAX)
        {
            playback_speed = PLAYBACK_SPEED_MAX;
        }
        else if (playback_speed < PLAYBACK_SPEED_MIN)
        {
            playback_speed = PLAYBACK_SPEED_MIN;
        }        
    }

    if (sound_index != SAMPLE_ID_NONE)
    {
        sound_play_sample(sound_index, PLAYBACK_PRIORITY, savedata.effectvol, savedata.effectvol, playback_speed);
    }

    return sound_index;

#undef PLAYBACK_PRIORITY
#undef PLAYBACK_SPEED_MIN
#undef PLAYBACK_SPEED_MAX
#undef PLAYBACK_SPEED_MODIFIER
}

// it can be useful for next changes
/*static int is_obstacle_around(entity* ent, float threshold)
{
    int i, j;
    int heightvar;

    if(ent->animation->size.y)
    {
        heightvar = ent->animation->size.y;
    }
    else
    {
        heightvar = ent->modeldata.size.y;
    }

    for(i = -1; i <= 1; i++ )
    {
        for(j = -1; j <= 1; j++ )
        {
            int w;
            entity *hito = find_ent_here(ent, ent->position.x+(i*threshold), ent->position.z+(j*threshold), (TYPE_OBSTACLE | TYPE_TRAP), NULL);
            entity *hitp = check_platform_between(ent->position.x+(i*threshold), ent->position.z+(j*threshold), ent->position.y, ent->position.y + heightvar, ent);
            int     hitw = (int)( (w = checkwall_below(ent->position.x+(i*threshold), ent->position.z+(j*threshold), T_MAX_CHECK_ALTITUDE)) >= 0 && level->walls[w].height > ent->position.y );

            if ( hito || hitp || hitw ) return 1;
        }
    }

    return 0;
}*/

// Caskey, Damon V.
// 2018-04-20
//
// Go to landing frame if available. Also spawns an effect ("dust") entity if set.
bool check_landframe(entity *ent)
{
    entity *effect;

	s_onframe_set* land;

	land = &ent->animation->landframe;

    // Must have a landframe.
    if(land->frame == FRAME_NONE)
    {
        return 0;
    }

    // Can't be bound with a landframe override.
    if(check_bind_override(ent, BIND_CONFIG_OVERRIDE_LANDFRAME))
    {
        return 0;
    }

    // Can't be passed over current animation's frame count.
    if(land->frame > ent->animation->numframes)
    {
        return 0;
    }

    // Can't be already at or passed land frame.
    if(ent->animpos >= land->frame)
    {
        return 0;
    }

    // If a land frame dust effect entity is set, let's spawn it here.
    if(land->model_index >= 0)
    {
        effect = spawn(ent->position.x, ent->position.z, ent->position.y, ent->direction, NULL, land->model_index, NULL);

        if(effect)
        {
            effect->spawntype = SPAWN_TYPE_DUST_LAND;
            effect->base = ent->position.y;
            effect->autokill |= AUTOKILL_ANIMATION_COMPLETE;
            execute_onspawn_script(effect);
        }
    }

    update_frame(ent, land->frame);

    return 1;
}

// Caskey, Damon V.
// 2018-04-20
//
// Go to landing frame if available. Also spawns an effect ("dust") entity if set.
bool check_frame_set_drop(entity* ent)
{
	entity* effect;

	s_onframe_set* drop;

	drop = &ent->animation->dropframe;

	// Dropframe set?
	if (drop->frame == FRAME_NONE)
	{
		return 0;
	}
	
	// Falling?
	if (ent->velocity.y > 0)
	{
		return 0;
	}

	/* Can't be bound with a drop frame override. */
	if (check_bind_override(ent, BIND_CONFIG_OVERRIDE_DROPFRAME))
	{
		return 0;
	}

	// Can't be passed over current animation's frame count.
	if (drop->frame > ent->animation->numframes)
	{
		return 0;
	}

	// Can't be already at or passed drop frame.
	if (ent->animpos >= drop->frame)
	{
		return 0;
	}

	// Passed all checks. Let's update frame and spawn model (if available).

	update_frame(ent, drop->frame);

	// If a frame set effect entity is set, let's spawn it here.
	if (drop->model_index != MODEL_INDEX_NONE)
	{
		effect = spawn(ent->position.x, ent->position.z, ent->position.y, ent->direction, NULL, drop->model_index, NULL);

		if (effect)
		{
			effect->spawntype = SPAWN_TYPE_DUST_DROP;
			effect->base = ent->position.y;
			effect->autokill |= AUTOKILL_ANIMATION_COMPLETE;
			execute_onspawn_script(effect);
		}
	}

	return 1;
}


int check_edge(entity *ent)
{
    float x = ent->position.x;
    float z = ent->position.z;
    float y = ent->position.y;
    e_direction dir = ent->direction;
    float height = 0.0f;
    float t_alt = 1.0f, t_walkoff = 1.0f, t_edge_default = 8.0f;
    float t_edge_x = t_edge_default, t_edge_z = t_edge_default / 2;

    if (ent->modeldata.edgerange.x > t_edge_x) t_edge_x = ent->modeldata.edgerange.x;
    if (ent->modeldata.edgerange.z > t_edge_z) t_edge_z = ent->modeldata.edgerange.z;

	if(ent->animation->size.y) height = ent->animation->size.y;
    else height = ent->modeldata.size.y;

    //test base
    float base_left  = checkbase(x - t_edge_x, z, y + t_walkoff, ent);
    float base_right = checkbase(x + t_edge_x, z, y + t_walkoff, ent);
    float base_up    = checkbase(x, z - t_edge_z, y + t_walkoff, ent);
    float base_down  = checkbase(x, z + t_edge_z, y + t_walkoff, ent);

    entity *plat_left  = check_platform_between(x - t_edge_x, z, y + t_walkoff, y + height, ent);
    entity *plat_right = check_platform_between(x + t_edge_x, z, y + t_walkoff, y + height, ent);
    entity *plat_up    = check_platform_between(x, z - t_edge_z, y + t_walkoff, y + height, ent);
    entity *plat_down  = check_platform_between(x, z + t_edge_z, y + t_walkoff, y + height, ent);

    if ( (base_left < y - t_alt) && plat_left == NULL ) return EDGE_LEFT;
    else if ( (base_right < y - t_alt) && plat_right == NULL ) return EDGE_RIGHT;

    // priority based on direction check
    if (dir == DIRECTION_LEFT)
    {
        if ( (base_left < y - t_alt) && plat_left == NULL ) return EDGE_LEFT;
        else if ( (base_right < y - t_alt) && plat_right == NULL ) return EDGE_RIGHT;
    }
    else
    {
        if ( (base_right < y - t_alt) && plat_right == NULL ) return EDGE_RIGHT;
        else if ( (base_left < y - t_alt) && plat_left == NULL ) return EDGE_LEFT;
    }

    if (
         ((base_up   < y - t_alt) && plat_up == NULL) ||
         ((base_down < y - t_alt) && plat_down == NULL)
    ) return EDGE_LEFT + EDGE_RIGHT;

    return EDGE_NONE;
}

void check_gravity(entity *e)
{

    int heightvar;
    entity *other = NULL, *dust, *tempself, *plat = NULL;
    float gravity;
    float fmin, fmax;

    if(e->update_mark & UPDATE_MARK_CHECK_GRAVITY)
    {
        return;
    }

    tempself = self;
    self = e;

    adjust_base(self, &plat);
    if (self->position.y <= self->base) self->edge = check_edge(self); // && self->idling & IDLING_ACTIVE
    else self->edge = EDGE_NONE;

    if(!is_frozen(self) )// Incase an entity is in the air, don't update animations
    {
        if(self->animation->size.y)
        {
            heightvar = self->animation->size.y;
        }
        else
        {
            heightvar = self->modeldata.size.y;
        }

        // White Dragon: turn-off the hitwall flag if you're not near a obstacle. this help to avoid a hit loop
        /*if( (self->position.y <= self->base || !inair(self)) && self->velocity.y <= 0)
        {
            if ( self->hitwall && !is_obstacle_around(self,1.0) ) self->hitwall = 0;
        }*/

        if((self->falling || self->velocity.y || self->position.y != self->base) && self->toss_time <= _time)
        {
            if(heightvar && self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_PLATFORM && self->velocity.y > 0)
            {
                other = check_platform_above_entity(self);
            }
            else
            {
                other = NULL;
            }

            if( other && other->position.y <= self->position.y + heightvar && !(other->modeldata.move_config_flags & MOVE_CONFIG_NO_HIT_HEAD))
            {
                if(self->hithead == NULL) // bang! Hit the ceiling.
                {
                    self->velocity.y = 0;
                    self->hithead = other;
                    execute_onblocka_script(self, other);
                }
            }
            else
            {
                self->hithead = NULL;
            }
            // gravity, antigravity factors
            self->position.y += self->velocity.y * 100.0 / GAME_SPEED;
            if(!(self->animation->move_config_flags & MOVE_CONFIG_SUBJECT_TO_GRAVITY))
            {
                gravity = 0;
            }
            else
            {
                gravity = (level ? level->gravity : default_level_gravity) * (1.0 - self->modeldata.antigravity);
            }
            if(self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_GRAVITY)
            {
                self->velocity.y += gravity * 100.0 / GAME_SPEED;
            }

            fmin = (level ? level->maxfallspeed : default_level_maxfallspeed);
            fmax = (level ? level->maxtossspeed : default_level_maxtossspeed);

            if(self->velocity.y < fmin)
            {
                self->velocity.y = fmin;
            }
            else if(self->velocity.y > fmax)
            {
                self->velocity.y = fmax;
            }

			// Evaluate and apply drop frame settings if we have them.
			check_frame_set_drop(self);				

            if (self->velocity.y)
            {
                execute_onmovea_script(self);    //Move A event.
            }
                       
            if( self->idling && validanim(self, ANI_WALKOFF) && diff(self->position.y, self->base) > T_WALKOFF )
            {
                entity *cplat = check_platform_below(self->position.x,self->position.z-1.0,self->position.y,self);

                // White Dragon: fix for too low velocityz
                if ( !cplat || (cplat && diff(get_platform_base(cplat),self->position.y) > T_WALKOFF) )
                {
                    self->idling = IDLING_NONE;
                    self->ducking = DUCK_NONE;
                    self->takeaction = common_walkoff;
                    ent_set_anim(self, ANI_WALKOFF, 0);
                    self->landed_on_platform = plat = NULL;
                }
            }

            // UTunnels: tossv <= 0 means land, while >0 means still rising, so
            // you wont be stopped if you are passing the edge of a wall
            if( self->position.y <= self->base || !inair(self))
            {
                // 0+ means still rising. We need to be falling.
                if(self->velocity.y <=0)
                {
                    // No bind target, or binding set to ignore fall lands.
                    if(!check_bind_override(self, BIND_CONFIG_OVERRIDE_FALL_LAND))
                    {
                        self->position.y = self->base;
                        self->falling = 0;

                        if ( self->hitwall ) self->hitwall = 0;

                        // cust dust entity
                        if(self->modeldata.dust.fall_land >= 0 && self->velocity.y < -1 && self->drop)
                        {
                            dust = spawn(self->position.x, self->position.z, self->position.y, self->direction, NULL, self->modeldata.dust.fall_land, NULL);
                            if(dust)
                            {
                                dust->spawntype = SPAWN_TYPE_DUST_FALL;
                                dust->base = self->position.y;
                                dust->autokill |= AUTOKILL_ANIMATION_COMPLETE;
                                execute_onspawn_script(dust);
                            }
                        }

                        // bounce/quake
                        if(tobounce(self) && self->modeldata.bounce)
                        {
                            int i;
                            self->velocity.x /= self->animation->bounce_factor;
                            self->velocity.z /= self->animation->bounce_factor;
                            toss(self, (-self->velocity.y) / self->animation->bounce_factor);
                            if(level && !(self->modeldata.quake_config & QUAKE_CONFIG_DISABLE_SCREEN))
                            {
                                level->quake = 4;    // Don't shake if specified
                            }
                            if(global_sample_list.fall >= 0)
                            {
                                sound_play_sample(global_sample_list.fall, 0, savedata.effectvol, savedata.effectvol, 100);
                            }
                            if(self->modeldata.type & TYPE_PLAYER)
                            {
                                if (savedata.joyrumble[self->playerindex]) control_rumble(self->playerindex, 1, 100 * (int)self->velocity.y / 2);
                            }
                            for(i = 0; i < MAX_PLAYERS; i++)
                            {
                                if (savedata.joyrumble[i]) control_rumble(i, 1, 75 * (int)self->velocity.y / 2);
                            }
                        }
                        else if((!self->animation->move[self->animpos]->base || self->animation->move[self->animpos]->base < 0) &&
                                (!self->animation->move[self->animpos]->axis.y || self->animation->move[self->animpos]->axis.y <= 0))
                        {
                            self->velocity.x = 0;
                            self->velocity.z = 0;
                            self->velocity.y = 0;
                        }
                        else
                        {
                            self->velocity.y = 0;
                        }

                        if(plat && !self->landed_on_platform && self->position.y <= plat->position.y + plat->animation->platform[plat->animpos][PLATFORM_HEIGHT])
                        {
                            self->landed_on_platform = plat;
                        }

                        // Set landing frame if we have one.
                        check_landframe(self);

                        // Taking damage on a landing?
                        checkdamageonlanding(self);

                        // in case landing, set hithead to NULL
                        self->hithead = NULL;
                    }
                }
            }// end of if - land checking
        }// end of if  - in-air checking
        
		if(self->toss_time <= _time)
        {
            self->toss_time = _time + 1;
        }

    }//end of if

    self->update_mark |= UPDATE_MARK_CHECK_GRAVITY;

    self = tempself;
}

int check_lost()
{
    s_defense* defense_object = NULL;
    s_attack attack;
    int osk = self->modeldata.offscreenkill ? self->modeldata.offscreenkill : DEFAULT_OFFSCREEN_KILL;

    if((self->position.z != ITEM_HIDE_POSITION_Z && (advancex - self->position.x > osk || self->position.x - advancex - videomodes.hRes > osk ||
                              (level->scrolldir != SCROLL_UP && level->scrolldir != SCROLL_DOWN && (advancey - self->position.z + self->position.y > osk || self->position.z - self->position.y - advancey - videomodes.vRes > osk)) ||
                              ((level->scrolldir == SCROLL_UP || level->scrolldir == SCROLL_DOWN) && (self->position.z - self->position.y < -osk || self->position.z - self->position.y > videomodes.vRes + osk))		) )
            || self->position.y < 2 * PIT_DEPTH) //self->position.z<ITEM_HIDE_POSITION_Z, so weapon item won't be killed
    {
        if(self->modeldata.type & TYPE_PLAYER)
        {
            player_die();
        }
        else
        {
            kill_entity(self, KILL_ENTITY_TRIGGER_OUT_OF_BOUNDS);
        }
        return 1;
    }

    // fall into a pit
    if(self->position.y < PIT_DEPTH)
    {
        if(!self->takedamage)
        {
            kill_entity(self, KILL_ENTITY_TRIGGER_PIT);
        }
        else
        {
            attack          = emptyattack;
            attack.dropv	= default_model_dropv;
            attack.attack_force = self->energy_state.health_current;
            attack.attack_type  = ATK_PIT;
            defense_object = defense_find_current_object(self, NULL, attack.attack_type);
            self->takedamage(self, &attack, 0, defense_object);
        }
        return 1;
    }
    else if(self->lifespancountdown < 0) //Lifespan expired.
    {
        if(!self->takedamage)
        {
            kill_entity(self, KILL_ENTITY_TRIGGER_LIFESPAN);
        }
        else
        {
            attack          = emptyattack;
			attack.dropv	= default_model_dropv;
            attack.attack_force = self->energy_state.health_current;
            attack.attack_type  = ATK_LIFESPAN;
            defense_object = defense_find_current_object(self, NULL, attack.attack_type);
            self->takedamage(self, &attack, 0, defense_object);
        }
        return 1;
    }//else

    // Doom count down
    if(!is_frozen(self) && self->lifespancountdown != LIFESPAN_DEFAULT)
    {
        self->lifespancountdown--;
    }

    return 0;
}

// grab walk check
void check_link_move(float xdir, float zdir)
{
    float x, z, gx, gz;
    int tryresult;
    entity *tempself = self;

    gx = self->grabbing->position.x;
    gz = self->grabbing->position.z;
    x = self->position.x;
    z = self->position.z;
    self = self->grabbing;
    tryresult = self->trymove(xdir, zdir);
    self = tempself;

    if(tryresult != 1) // changed
    {
        xdir = self->grabbing->position.x - gx;
        zdir = self->grabbing->position.z - gz;
    }
    tryresult = self->trymove(xdir, zdir);
    if(tryresult != 1)
    {
        self->grabbing->position.x = self->position.x - x + gx;
        self->grabbing->position.z = self->position.z - z + gz;
    }
}

/*
* Caskey, Damon V.
* 2022-04-21
* 
* Return true if currently in any 
* active game or menu screen.
*/
int check_in_screen()
{
    /*
    * Check the screen status first. 
    * It should be faster.
    */

    if (screen_status & (IN_SCREEN_BUTTON_CONFIG_MENU | IN_SCREEN_SELECT | IN_SCREEN_TITLE | IN_SCREEN_HALL_OF_FAME | IN_SCREEN_GAME_OVER | IN_SCREEN_SHOW_COMPLETE | IN_SCREEN_SHOW_GO_ARROW | IN_SCREEN_ENGINE_CREDIT | IN_SCREEN_MENU | IN_SCREEN_GAME_START_MENU | IN_SCREEN_NEW_GAME_MENU | IN_SCREEN_LOAD_GAME_MENU | IN_SCREEN_OPTIONS_MENU | IN_SCREEN_CONTROL_OPTIONS_MENU | IN_SCREEN_SOUND_OPTIONS_MENU | IN_SCREEN_VIDEO_OPTIONS_MENU | IN_SCREEN_SYSTEM_OPTIONS_MENU))
    {
        return 1;
    }

    if (currentScene)
    {
        return 1;
    }

    return 0;
        
}

void check_ai()
{
    if(self->nextthink <= _time && !endgame)
    {
        self->update_mark |= UPDATE_MARK_CHECK_AI; //mark it
        
        // take actions
        if(self->takeaction)
        {
            self->takeaction();
        }

        // A.I. think
        if(self->think)
        {
            if(self->nextthink <= _time)
            {
                self->nextthink = _time + THINK_SPEED;
            }
            // use noaicontrol flag to turn of A.I. think
            if(!self->noaicontrol)
            {
                self->think();
            }
        }

        // Execute think script
        execute_think_script(self);

        // Used so all entities can have a spawn animation, and then just changes to the idle animation when done
        // move here to so players wont get stuck
        if((self->animation == self->modeldata.animation[ANI_SPAWN] || self->animation == self->modeldata.animation[ANI_RESPAWN]) && !self->animating /*&& (!inair(self)||!(self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_GRAVITY))*/)
        {
            set_idle(self);
        }
    }
}

float check_basemap(int x, int z)
{
    float maxbase = 0, base = T_MIN_BASEMAP;
    int i;

    if(!level)
    {
        return 0;
    }

    for(i = 0; i < level->numbasemaps; i++)
    {
        if(x >= level->basemaps[i].position.x && x < level->basemaps[i].position.x + level->basemaps[i].size.x &&
                z >= level->basemaps[i].position.z && z < level->basemaps[i].position.z + level->basemaps[i].size.z)
        {
            base = level->basemaps[i].map[x - level->basemaps[i].position.x + level->basemaps[i].size.x * (z - level->basemaps[i].position.z)];
            if(base > maxbase)
            {
                maxbase = base;
            }
        }
    }
    return base == T_MIN_BASEMAP ? base : maxbase;
}

int check_basemap_index(int x, int z)
{
    float maxbase = 0, base = T_MIN_BASEMAP;
    int i, index = -1;

    if(!level)
    {
        return -1;
    }

    for(i = 0; i < level->numbasemaps; i++)
    {
        if(x >= level->basemaps[i].position.x && x < level->basemaps[i].position.x + level->basemaps[i].size.x &&
                z >= level->basemaps[i].position.z && z < level->basemaps[i].position.z + level->basemaps[i].size.z)
        {
            base = level->basemaps[i].map[x - level->basemaps[i].position.x + level->basemaps[i].size.x * (z - level->basemaps[i].position.z)];
            if(base > maxbase)
            {
                maxbase = base;
                index = i;
            }
        }
    }
    return base == T_MIN_BASEMAP ? -1 : index;
}



void adjust_base(entity *acting_entity, entity **pla)
{
    int wall = WALL_INDEX_NONE; 
    int in_hole = 0;
    int hole_index = HOLE_INDEX_NONE;
    float seta = 0; 
    float maxbase = 0;
    float base_adjust = 0.0;
    entity* other = NULL; 
    entity* platform = NULL;    

    if(acting_entity->velocity.y > 0)
    {
        acting_entity->landed_on_platform = NULL;
    }
        
    /*
    * If no platform found below entity
    * then clear our local platform and
    * the platform argument. 
    * 
    * If entity isn't subject to platform
    * at all, then clear entity's last
    * landed platform, platform parameter,
    * and the local other entity.
    */
    if(acting_entity->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_PLATFORM)
    {
        other = check_platform_below_entity(acting_entity);
        if(!other && acting_entity->landed_on_platform)
        {
            *pla = NULL; 
            acting_entity->landed_on_platform = NULL;
        }
    }
    else
    {
        *pla = NULL;
        other = NULL; 
        acting_entity->landed_on_platform = NULL;
    }

    if(other && !(other->update_mark & UPDATE_MARK_CHECK_GRAVITY))
    {
        check_gravity(other);
    }

    //no longer underneath?
    if(acting_entity->landed_on_platform && !testplatform(acting_entity->landed_on_platform, acting_entity->position.x, acting_entity->position.z, NULL))
    {
        *pla = acting_entity->landed_on_platform = NULL;
    }

    if(other 
        && !acting_entity->landed_on_platform 
        && acting_entity->position.y <= other->position.y + other->animation->platform[other->animpos][PLATFORM_HEIGHT])
    {
        acting_entity->landed_on_platform = other;
    }

    if( (platform = acting_entity->landed_on_platform) )
    {
        if(!(platform->update_mark & UPDATE_MARK_CHECK_GRAVITY))
        {
            check_gravity(platform);
        }
        acting_entity->position.y = acting_entity->base = platform->position.y + platform->animation->platform[platform->animpos][PLATFORM_HEIGHT];
    }

    *pla = other;

    // adjust base
    if (acting_entity->modeldata.move_config_flags & MOVE_CONFIG_NO_ADJUST_BASE)
    {        
        return;
    }

    if (acting_entity->animation->move[acting_entity->animpos]->base)
    {
        seta = (float)acting_entity->animation->move[acting_entity->animpos]->base;
    }
    else
    {
        seta = -1;
    }
        
    // Checks to see if entity is over a wall and or obstacle, and adjusts the base accordingly
    //wall = checkwall_below(acting_entity->position.x, acting_entity->position.z);
    //find a wall below us
    if(acting_entity->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_WALL)
    {
        wall = checkwall_below(acting_entity->position.x, acting_entity->position.z, T_MAX_CHECK_ALTITUDE);
    }
    else
    {
        wall = WALL_INDEX_NONE;
    }
    /*
    * If we can't adjust base, nothing downstream 
    * matters. Just exit.
    */

    if (acting_entity->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_BASEMAP)
    {
        maxbase = check_basemap(acting_entity->position.x, acting_entity->position.z);
    }

    /* 
    * Fall into a hole?
    */

    if(acting_entity->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_HOLE &&
        ((acting_entity->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_BASEMAP && maxbase == T_MIN_BASEMAP) || !(acting_entity->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_BASEMAP))
        )
    {
        in_hole = (wall < 0 && !other) ? checkhole_in(acting_entity->position.x, acting_entity->position.z, acting_entity->position.y) : 0;
        
        if (in_hole)
        {
            hole_index = checkholeindex_in(acting_entity->position.x, acting_entity->position.z, acting_entity->position.y);
            
            if (hole_index >= 0)
            {
                execute_inhole_script(acting_entity, &level->holes[hole_index], hole_index);
            }
        }

        if(seta < 0 && in_hole)
        {
            acting_entity->base = T_MIN_BASEMAP;
            ent_unlink(acting_entity);
        }
        else if(!in_hole && acting_entity->base == T_MIN_BASEMAP)
        {
            if(acting_entity->position.y >= 0)
            {
                acting_entity->base = 0;
            }
            else
            {
                acting_entity->velocity.x = acting_entity->velocity.z = 0; // hit the hole border
            }
        }
    }

    /*
    * Apply platform, wall, and manual base
    * adjustments.
    */

    if(acting_entity->base != T_MIN_BASEMAP || wall >= 0)
    {       
        base_adjust = (seta + acting_entity->altbase >= 0) * (seta + acting_entity->altbase);
        
        /*
        * If (wall > position Y)
        *   Explode.
        * Else
        *   Adjust base.
        */       
        if(other != NULL && other != acting_entity)
        {
            acting_entity->base = base_adjust + other->position.y + other->animation->platform[other->animpos][PLATFORM_HEIGHT];            
        }
        else if(wall >= 0)
        {            
            /*
            * Bombs normally need to adjust base to control
            * detonation when landing, but this has a side 
            * effect: Bombs that hit the side of walls or 
            * platforms instantly warp to the top of wall
            * as they detonate. 
            * 
            * Legacy projectile wall bounce has a similar
            * issue.
            * 
            * To fix this, we check to see if wall is higher 
            * than current position of entities with their
            * detonation flags on. If so, then reset entity
            * base.
            */
                        
            if ((acting_entity->toexplode & (EXPLODE_PREPARE_GROUND | EXPLODE_DETONATE_HIT) || acting_entity->modeldata.move_config_flags & MOVE_CONFIG_PROJECTILE_WALL_BOUNCE) && level->walls[wall].height > acting_entity->position.y)
            {
                acting_entity->base = 0;
            }            
            else
            {
                acting_entity->base = base_adjust + level->walls[wall].height;
            }    
        }
        else if(seta >= 0)
        {
            acting_entity->base = base_adjust;
        }
        else if(!acting_entity->animation->move[acting_entity->animpos]->axis.y)
        {
            // No obstacle/wall or seta, so just set to 0
            acting_entity->base = 0;
        }
    }

    if(acting_entity->base != T_MIN_BASEMAP && maxbase > acting_entity->base)
    {
        acting_entity->base = maxbase;
        // White Dragon: fix bug for floating entity on basemaps using a threshold
        if (acting_entity->velocity.y <= 0 && acting_entity->position.y - acting_entity->base <= T_WALKOFF)
        {
            acting_entity->position.y = acting_entity->base;
        }
        //debug_printf("y:%f maxbase:%f",acting_entity->position.y,maxbase);
    }    
}

// Caskey Damon V.
// 2018-04-08
//
// If entity has a timed color set, check
// to see if colorset has expired. If so,
// revert to default colorset and clear
// the timer.
int colorset_timed_expire(entity *ent)
{
    // No color set time? Nothing to do.
    if(!ent->maptime)
    {
        return 0;
    }

    // If elapsed time has surpassed color
    // set time, then color set time is expired.
    // Revert entity back to default color set
    // and reset the color set timer.
    if(_time >= ent->maptime)
    {
        ent_set_colourmap(ent, ent->map);
        ent->maptime = 0;

        return 1;
    }

    // Color set _time was not expired.
    return 0;
}


void update_animation()
{
    int f;
    float scrollv = 0;

    if(level)
    {
        if(self->modeldata.facing == FACING_ADJUST_RIGHT || level->facing == FACING_ADJUST_RIGHT)
        {
            self->direction = DIRECTION_RIGHT;
        }
        else if(self->modeldata.facing == FACING_ADJUST_LEFT || level->facing == FACING_ADJUST_LEFT)
        {
            self->direction = DIRECTION_LEFT;
        }
        else if((self->modeldata.facing == FACING_ADJUST_LEVEL || level->facing == FACING_ADJUST_LEVEL) && (level->scrolldir & SCROLL_RIGHT))
        {
            self->direction = DIRECTION_RIGHT;
        }
        else if((self->modeldata.facing == FACING_ADJUST_LEVEL || level->facing == FACING_ADJUST_LEVEL) && (level->scrolldir & SCROLL_LEFT))
        {
            self->direction = DIRECTION_LEFT;
        }
        if(self->modeldata.type & TYPE_PANEL)
        {
            scrollv += self->modeldata.speed.x;
        }
        if(self->modeldata.scroll)
        {
            scrollv += self->modeldata.scroll;
        }
        if(scrollv)
        {
            self->position.x += scrolldx * scrollv;
            if(level->scrolldir == SCROLL_UP)
            {
                scrollv = -scrollv;
            }
            self->position.y -= scrolldy * scrollv;
            self->base = -99999; // temporary fix otherwise it won't go underground
        }
    }

    if(!(self->idling & IDLING_ACTIVE) || (self->animnum == ANI_SLEEP && !self->animating))
    {
        self->sleeptime = _time + self->modeldata.sleepwait;
    }

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

    if(self->freezetime && _time >= self->freezetime)
    {
        unfrozen(self);
    }

    // Check for forced color set expiring.
    colorset_timed_expire(self);

    if(self->sealtime && _time >= self->sealtime) //Remove seal, special moves are available again.
    {
        self->seal = 0;
    }
    // Reset their escapecount if they aren't being spammed anymore.
    if(self->modeldata.escapehits && self->inpain == IN_PAIN_NONE)
    {
        self->escapecount = 0;
    }

    if((self->nextanim == _time && self->nextanim != DELAY_INFINITE) ||
            ((self->modeldata.type & TYPE_TEXTBOX) && self->modeldata.subtype != SUBTYPE_NOSKIP &&
             (bothnewkeys & (FLAG_JUMP | FLAG_ATTACK | FLAG_ATTACK2 | FLAG_ATTACK3 | FLAG_ATTACK4 | FLAG_SPECIAL)))) // Textbox will autoupdate if a valid player presses an action button
    {
        // Now you can display text and cycle through with any jump/attack/special unless SUBTYPE_NOSKIP

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

        //Specified loop break frame.
        if(self->animation->loop.mode && self->animation->loop.frame.max)
        {
            if (f == self->animation->loop.frame.max)
            {
                if(f < 0)
                {
                    f = self->animation->numframes - 1;
                }
                else
                {
                    f = 0;
                }

                if (self->animation->loop.frame.min)
                {
                    f = self->animation->loop.frame.min;
                }
            }
            else if((unsigned)f >= (unsigned)self->animation->numframes)
            {
                self->animating = ANIMATING_NONE;

                if(self->autokill & AUTOKILL_ANIMATION_COMPLETE)
                {
                    kill_entity(self, KILL_ENTITY_TRIGGER_AUTOKILL_ANIMATION_COMPLETE_DEFINED_LOOP_MAX);
                    return;
                }
            }
        }
        else if((unsigned)f >= (unsigned)self->animation->numframes)
        {
            if(f < 0)
            {
                f = self->animation->numframes - 1;
            }
            else
            {
                f = 0;
            }

            if(!self->animation->loop.mode)
            {
                self->animating = ANIMATING_NONE;

                if(self->autokill & AUTOKILL_ANIMATION_COMPLETE)
                {
                    kill_entity(self, KILL_ENTITY_TRIGGER_AUTOKILL_ANIMATION_COMPLETE_UNDEFINED_LOOP_MAX);
                    return;
                }
            }
            else
            {
                if (self->animation->loop.frame.min)
                {
                    f = self->animation->loop.frame.min;
                }
            }
        }

        if(self->animating)
        {
            //self->nextanim = _time + (self->animation->delay[f]);
            self->update_mark |= UPDATE_MARK_UPDATE_ANIMATION; // frame updated, mark it
            // just switch frame to f, if frozen, expand_time will deal with it well
            update_frame(self, f);
			bothnewkeys = 0; //stop mutiple skips.
        }
    }

}

void check_attack()
{
    /* a normal fall */
    if(self->falling && self->projectile == BLAST_NONE)
    {
        self->attack_id_outgoing = 0;
        return;
    }

    /* on ground */
    if(self->drop && !self->falling)
    {
        self->attack_id_outgoing = 0;
        return;
    }

    /* Can't hit an opponent if you are frozen. */
    if(!is_frozen(self) && self->animation->collision_attack &&
            self->animation->collision_attack[self->animpos])
    {
                do_attack(self);
        return;
    }
    self->attack_id_outgoing = 0;
}

// Caskey, Damon V.
// 2018-04-08
//
// If actively charging, add energy to
// entity based on chargerate property
// and elapsed time. Returns 1 if
// energy was added, 0 otherwise.
int do_energy_charge(entity *ent)
{
	#define ENERGY_CHARGE_RATE 0.25

	// Must be charging.
	if (!ent->charging)
	{
		return 0;
	}

	// Must be time for a charge tick.
	if (_time < ent->mpchargetime)
	{
		return 0;
	}

	// Add charge rate to current mp.
	ent->energy_state.mp_current += ent->modeldata.chargerate;

	// Time for next charge tick.
	ent->mpchargetime = _time + (GAME_SPEED * ENERGY_CHARGE_RATE);

	return 1;

	#undef ENERGY_CHARGE_RATE
}


void update_health()
{
    //12/30/2008: Guardrate by OX. Guardpoints increase over time.
    if(self->modeldata.guardpoints.max > 0 && _time >= self->guardtime) // If this is > 0 then guardpoints are set..
    {
        if(self->blocking)
        {
            self->modeldata.guardpoints.current += (self->modeldata.guardrate / 2);
            if(self->modeldata.guardpoints.current > self->modeldata.guardpoints.max)
            {
                self->modeldata.guardpoints.current = self->modeldata.guardpoints.max;
            }
        }
        else
        {
            self->modeldata.guardpoints.current += self->modeldata.guardrate;
            if(self->modeldata.guardpoints.current > self->modeldata.guardpoints.max)
            {
                self->modeldata.guardpoints.current = self->modeldata.guardpoints.max;
            }
        }
        self->guardtime = _time + GAME_SPEED;    //Reset guardtime.
    }

    //Damage over time.
    recursive_damage_update(self);

    // this is for restoring mp by _time by tails
    // Cleaning and addition of mpstable by DC, 08172008.
    // stabletype 4 added by OX 12272008
    
    if(magic_type == 0 && !self->charging)
    {
        if(_time >= self->magictime)
        {

            // 1 Only recover MP > mpstableval.
            // 2 No recover. Drop MP if MP < mpstableval.
            // 3 Both: recover if MP if MP < mpstableval and drop if MP > mpstableval.
            // 4 Gain until stable, then fall to stable.
			// 0 Default. Recover MP at all times.

			if (self->modeldata.mpstable == 1)
            {
                if (self->energy_state.mp_current < self->modeldata.mpstableval)
                {
                    self->energy_state.mp_current += self->modeldata.mprate;
                }
            }
            else if(self->modeldata.mpstable == 2)
            {
                if (self->energy_state.mp_current > self->modeldata.mpstableval)
                {
                    self->energy_state.mp_current -= self->modeldata.mpdroprate;
                }
            }
            else if (self->modeldata.mpstable == 3)
            {
                if (self->energy_state.mp_current < self->modeldata.mpstableval)
                {

                    self->energy_state.mp_current += self->modeldata.mprate;
                }
                else if (self->energy_state.mp_current > self->modeldata.mpstableval)
                {
                    self->energy_state.mp_current -= self->modeldata.mpdroprate;
                }
            }

            // OX. Stabletype 4. Gain mp until it reaches max. Then it drops down to mpstableval.
            else if (self->modeldata.mpstable == 4)
            {
                if(self->energy_state.mp_current <= self->modeldata.mpstableval)
                {
                    self->modeldata.mpswitch = 0;
                }
                else if(self->energy_state.mp_current == self->modeldata.mp)
                {
                    self->modeldata.mpswitch = 1;
                }

                if(self->modeldata.mpswitch == 1)
                {
                    self->energy_state.mp_current -= self->modeldata.mpdroprate;
                }
                else if(self->modeldata.mpswitch == 0)
                {
                    self->energy_state.mp_current += self->modeldata.mprate;
                }
            }
            else
            {
                self->energy_state.mp_current += self->modeldata.mprate;
            }

            self->magictime = _time + GAME_SPEED;    //Reset magictime.
        }
    }

    // Active MP charging?
    do_energy_charge(self);

    /* Energy cheat keeps MP at maximum. */
    if (global_config.cheats & CHEAT_OPTIONS_ENERGY_ACTIVE && self->modeldata.type & TYPE_PLAYER)
    {
        self->energy_state.mp_current = self->modeldata.mp;
    }

    if(self->energy_state.mp_current > self->modeldata.mp)
    {
        self->energy_state.mp_current = self->modeldata.mp;    // Don't want to add more than the max
    }

    if(self->energy_state.health_old < self->energy_state.health_current)
    {
        self->energy_state.health_old++;
    }
    else if(self->energy_state.health_old > self->energy_state.health_current)
    {
        self->energy_state.health_old--;
    }

    if(self->energy_state.mp_old < self->energy_state.mp_current)
    {
        self->energy_state.mp_old++;
    }
    else if(self->energy_state.mp_old > self->energy_state.mp_current)
    {
        self->energy_state.mp_old--;
    }
}

/* ***** Binding ***** */

/*
* Caskey, Damon V.
* 2022-05-27
*
* Allocate a bind property structure and return pointer.
*/
s_bind* bind_allocate_object()
{
    s_bind* result;
    size_t alloc_size;

    /* Get amount of memory we'll need. */
    alloc_size = sizeof(*result);

    /* Allocate memory and get pointer. */
    result = malloc(alloc_size);

    /*
    * Make sure the data members are
    * zero'd out.
    */

    memset(result, 0, alloc_size);
          
    return result;
}

/*
* Caskey, Damon V.
* 2022-05-27
*
* Allocate new bind object with same values (but not 
* same pointers) as received object. Returns pointer 
* to new object.
*/
s_bind* bind_clone_object(s_bind* source)
{
    s_bind* result = NULL;

    if (!source)
    {
        return result;
    }

    result = bind_allocate_object();

    /*
    * Rather than do everything piecemeal, we'll memcopy
    * to get all the basic values, and then overwrite
    * members individually as needed.
    */

    memcpy(result, source, sizeof(*result));

    return result;
}

/*
* Caskey, Damon V
* 2022-05-27
*
* Send all bind data to log for debugging.
*/
void bind_dump_object(s_bind* object)
{
    printf("\n\n -- Bind (%p) dump --", object);

    if (object)
    {
        printf("\n\t ->animation: %d", object->animation);
        printf("\n\t ->config: %d", object->config);
        printf("\n\t ->direction_adjust: %d", object->direction_adjust);
        printf("\n\t ->frame: %d", object->frame);
        printf("\n\t ->meta_data: %p", object->meta_data);
        printf("\n\t ->meta_tag: %d", object->meta_tag);
        printf("\n\t ->offset.x: %d", object->offset.x);
        printf("\n\t ->offset.y: %d", object->offset.y);
        printf("\n\t ->offset.z: %d", object->offset.z);
        printf("\n\t ->sortid: %d", object->sortid);
        printf("\n\t ->target: %p", object->target);
    }

    printf("\n\n -- Bind (%p) dump complete... -- \n", object);
}

/*
* Caskey, Damon V.
* 2022-05-27
*
* Free bind properties from memory.
*/
void bind_free_object(s_bind* object)
{
    free(object);
}

/*
* Caskey, Damon V.
* 2022-05-06
* 
* Full rewrite of orginal function by uTunnels
* and its later modifications by me (Damon).
*
* Acting entity binds itself to a target
* depending on bind property settings. Also 
* executes bind scripts for acting and target.
*/
void adjust_bind(entity* acting_entity)
{
	#define ADJUST_BIND_SET_ANIM_RESETABLE 1
	#define ADJUST_BIND_NO_FRAME_MATCH -1   

    int				frame = 0;
    e_animations	animation = ANI_NONE;

	/* 
    * Exit if there is no bind 
    * or bind target. 
    */
	if (!acting_entity->binding.target)
	{
		return;
	}

	/* 
    * Run bind update scripts for target and
    * acting entity. 
    */

	execute_on_bind_update_other_to_self(acting_entity->binding.target, acting_entity, &acting_entity->binding); 
    execute_on_bind_update_self_to_other(acting_entity, acting_entity->binding.target, &acting_entity->binding);

    /*
    printf("\n\n adjust_bind(%p)", acting_entity);
    printf("\n\t Acting Name: %s", acting_entity->name);
    printf("\n\t acting_entity->binding: %p", &acting_entity->binding);
    printf("\n\t\t binding.animation: %d", acting_entity->binding.animation);
    printf("\n\t\t binding.config: %d", acting_entity->binding.config);
    printf("\n\t\t binding.direction_adjust: %d", acting_entity->binding.direction_adjust);
    printf("\n\t\t binding.frame: %d", acting_entity->binding.frame);
    printf("\n\t\t binding.meta_data: %p", &acting_entity->binding.meta_data);
    printf("\n\t\t binding.meta_tag: %d", acting_entity->binding.meta_tag);
    printf("\n\t\t binding.offset: %d, %d, %d", acting_entity->binding.offset.x, acting_entity->binding.offset.y, acting_entity->binding.offset.z);
    printf("\n\t\t binding.sortid: %d", acting_entity->binding.sortid);    
    
    if (acting_entity->binding.target)
    {
        printf("\n\t\t binding.target: %p (%s)", acting_entity->binding.target, acting_entity->binding.target->name);
    }
    else
    {
        printf("\n\t\t binding.target: %p (%s)", acting_entity->binding.target, "");
    }
    printf("\n\n");
    */

	if (acting_entity->binding.config & (BIND_CONFIG_ANIMATION_DEFINED | BIND_CONFIG_ANIMATION_TARGET | BIND_CONFIG_ANIMATION_FRAME_DEFINED | BIND_CONFIG_ANIMATION_FRAME_TARGET))
	{
		/* 
        * If a defined value is requested,
		* use the binding member value.
		* Otherwise use target's current value.
		*/
        if (acting_entity->binding.config & BIND_CONFIG_ANIMATION_DEFINED)
		{
			animation = acting_entity->binding.animation;
		}
		else
		{
			animation = acting_entity->binding.target->animnum;
		}

		/* Are we NOT currently playing the target animation? */
		if (acting_entity->animnum != animation)
		{
			/*
            * If we don't have the target animation
			* and animation kill flag is set, then
			* we kill ourselves and exit the function.
			*/
            if (!validanim(acting_entity, animation))
			{
				/* Don't have the animation? Kill self. */
				if (acting_entity->binding.config & BIND_CONFIG_ANIMATION_REMOVE)
				{
					kill_entity(acting_entity, KILL_ENTITY_TRIGGER_BIND_ANIMATION_MATCH);
				}

				/* Cancel the bind and exit. */
                acting_entity->binding.target = NULL;
				return;
			}

			/*
            * Made it this far, we must have the target
			* animation, so let's apply it.
			*/
            ent_set_anim(acting_entity, animation, ADJUST_BIND_SET_ANIM_RESETABLE);
		}

		
		/*
        * If a defined value is requested,
		* use the binding member value.
		* If target value is requested use
		* target's current value (duh).
		* if no frame match at all requested
		* then set ADJUST_BIND_NO_FRAME_MATCH
		* so frame matching logic is skipped.		
		*/

		if (acting_entity->binding.config & BIND_CONFIG_ANIMATION_FRAME_DEFINED)
		{
			frame = acting_entity->binding.frame;
		}
		else if (acting_entity->binding.config & BIND_CONFIG_ANIMATION_FRAME_TARGET)
		{
			frame = acting_entity->binding.target->animpos;
		}
		else
		{
			frame = ADJUST_BIND_NO_FRAME_MATCH;
		}

		/* 
        * Any frame match flag set?
		*/
        if (frame != ADJUST_BIND_NO_FRAME_MATCH)
		{
			/* Are we NOT currently playing the target frame ? */
			if (acting_entity->animpos != frame)
			{
				/*
                * If we don't have the frame and frame kill flag is
				* set, kill self.
				*/
                if ((acting_entity->animation->numframes -1) < frame)
				{
					if (acting_entity->binding.config & BIND_CONFIG_ANIMATION_FRAME_REMOVE)
					{
						kill_entity(acting_entity, KILL_ENTITY_TRIGGER_BIND_FRAME_MATCH);
                        						
						return;
					}					
				}

				/*
                * Made it this far, let's try to
				* apply the frame.
				*/
                update_frame(acting_entity, frame);
			}
		}
	}

	/* Apply sort ID adjustment. */
    acting_entity->sortid = acting_entity->binding.target->sortid + acting_entity->binding.sortid;

	/* Getand apply direction adjustment. */
    acting_entity->direction = direction_get_adjustment_result(acting_entity, acting_entity->binding.target, acting_entity->binding.direction_adjust);

    /*
	* Apply positioning based on config. For
    * the X axis, we invert adjustment when
    * target faces left.
	*/

    // X
    if (acting_entity->binding.config & BIND_CONFIG_AXIS_X_TARGET)
    {
        if (acting_entity->binding.target->direction == DIRECTION_LEFT)
        {
            acting_entity->position.x = acting_entity->binding.target->position.x - acting_entity->binding.offset.x;            
        }
        else
        {
            acting_entity->position.x = acting_entity->binding.target->position.x + acting_entity->binding.offset.x;
        }
    }
    else if (acting_entity->binding.config & BIND_CONFIG_AXIS_X_LEVEL)
    {
        acting_entity->position.x = acting_entity->binding.offset.x;
    }
    
    // Y
    if (acting_entity->binding.config & BIND_CONFIG_AXIS_Y_TARGET)
    {
        acting_entity->position.y = acting_entity->binding.target->position.y + acting_entity->binding.offset.y;
    }
    else if (acting_entity->binding.config & BIND_CONFIG_AXIS_Y_LEVEL)
    {
        acting_entity->position.y = acting_entity->binding.offset.y;
    }

    // Z
    if (acting_entity->binding.config & BIND_CONFIG_AXIS_Z_TARGET)
    {
        acting_entity->position.z = acting_entity->binding.target->position.z + acting_entity->binding.offset.z;
    }
    else if (acting_entity->binding.config & BIND_CONFIG_AXIS_Z_LEVEL)
    {
        acting_entity->position.z = acting_entity->binding.offset.z;
    }
    	
	#undef ADJUST_BIND_SET_ANIM_RESETABLE
	#undef ADJUST_BIND_NO_FRAME_MATCH
}

/*
* Caskey, Damon V.
* 2021-08-24
* 
* Read a text argument for direction and output
* appropriate direction adjustment constant. If
* input is legacy integer, we just pass it on.
*/
e_direction_adjust direction_get_adjustment_from_argument(char* filename, char* command, char* value)
{
    e_direction_adjust result = DIRECTION_ADJUST_NONE;
        
    if (stricmp(value, "left") == 0)
    {
        result = DIRECTION_ADJUST_LEFT;
    }
    else if (stricmp(value, "none") == 0)
    {
        result = DIRECTION_ADJUST_NONE;
    }
    else if (stricmp(value, "opposite") == 0)
    {
        result = DIRECTION_ADJUST_OPPOSITE;
    }
    else if (stricmp(value, "right") == 0)
    {
        result = DIRECTION_ADJUST_RIGHT;
    }
    else if (stricmp(value, "same") == 0)
    {
        result = DIRECTION_ADJUST_SAME;
    }
    else if (stricmp(value, "toward") == 0)
    {
        result = DIRECTION_ADJUST_TOWARD;
    }
    else if (stricmp(value, "away") == 0)
    {
        result = DIRECTION_ADJUST_AWAY;
    }
    else
    {
        result = getValidInt(value, filename, command);
    }

    return result;
}

// Caskey, Damon V.
// 2018-10-13
//
// Return an adjusted entity direction based 
// on original direction, target direction
// and direction adjust setting.
e_direction direction_get_adjustment_result(entity* acting_entity, const entity* target_entity, const e_direction_adjust adjustment)
{
    if (!acting_entity || !target_entity)
    {
        borShutdown(1, "direction_get_adjustment_result() was passed a NULL pointer.");
    }

    const e_direction acting_direction = acting_entity->direction;
    const e_direction target_direction = target_entity->direction;

    const float acting_position_x = acting_entity->position.x;
    const float target_position_x = target_entity->position.x;

	// Apply direction adjustment.
	switch (adjustment)
	{
		default:
		case DIRECTION_ADJUST_NONE:

			// Use original direction.
			return acting_direction;
			break;

		case DIRECTION_ADJUST_SAME:

			// Same as target direction.
			return target_direction;
			break;

		case DIRECTION_ADJUST_OPPOSITE:

			// Opposite of target direction.
			if (target_direction == DIRECTION_LEFT)
			{
				return DIRECTION_RIGHT;
			}
			else
			{
				return DIRECTION_LEFT;
			}

			break;

		case DIRECTION_ADJUST_RIGHT:

			return DIRECTION_RIGHT;
			break;

		case DIRECTION_ADJUST_LEFT:

			return DIRECTION_LEFT;
			break;

        case DIRECTION_ADJUST_TOWARD:

            if (acting_position_x < target_position_x)
            {
                return DIRECTION_RIGHT;
            }
            else if (acting_position_x > target_position_x)
            {
                return DIRECTION_LEFT;
            }
            else
            {
                return acting_direction;
            }

            break;

        case DIRECTION_ADJUST_AWAY:

            if (acting_position_x < target_position_x)
            {
                return DIRECTION_LEFT;
            }
            else if (acting_position_x > target_position_x)
            {
                return DIRECTION_RIGHT;
            }
            else
            {
                return acting_direction;
            }

            break;
	}
}

/*
* Caskey, Damon V.
* 2018-09-08
*
* Return true if the target entity has a valid
* bind target and match for the override argument.
*/
int check_bind_override(entity *ent, e_bind_config bind_config)
{
    if(ent->binding.target)
    {
        if(ent->binding.config & bind_config)
        {
            return TRUE;
        }
    }

    return FALSE;
}

void check_move(entity *e)
{
    float x, z;
    entity *plat, *tempself;

    if((e->update_mark & UPDATE_MARK_CHECK_MOVE))
    {
        return;
    }
    tempself = self;
    self = e;

    x = self->position.x;
    z = self->position.z;

    // check moving platform
    if((plat = self->landed_on_platform) ) // on the platform?
    {
        //update platform first to get actual movex and movez
        if(!(plat->update_mark & UPDATE_MARK_CHECK_MOVE))
        {
            check_move(plat);
        }
        if(plat->movex || plat->movez)
        {
            if(self->trymove)
            {
                if(self->grabbing)
                {
                    check_link_move(plat->movex, plat->movez);
                }
                else if(!self->link)
                {
                    self->trymove(plat->movex, plat->movez);
                }
            }
            else
            {
                self->position.x += plat->movex;
                self->position.z += plat->movez;
            }
        }
    }
    if(!is_frozen(self) )
    {
        if(self->nextmove <= _time && (self->movex || self->movez) )
        {
            if(self->trymove)
            {
                // only do linked move while grabwalking for now,
                // otherwise some grab moves that use move/movez command may act strangely
                if(self->grabbing && self->grabwalking)
                {
                    check_link_move(self->movex, self->movez);
                }
                else// if(!self->link || self->grabbing)
                {
                    if(1 != self->trymove(self->movex, self->movez) && self->idling)
                    {
                        self->pathblocked += _time % 2;
                    }
                    else
                    {
                        self->pathblocked = 0;
                    }
                }
            }
            else
            {
                self->position.x += self->movex;
                self->position.z += self->movez;
            }
        }
        if(self->nextmove <= _time)
        {
            self->nextmove = _time + 1;
        }

    }
    self->movex = self->position.x - x;
    self->movez = self->position.z - z;
    self->update_mark |= UPDATE_MARK_CHECK_MOVE;
    self = tempself;
}

void ent_post_update(entity *e)
{
    check_gravity(e);// check gravity
    check_entity_collision_for(e);
    check_move(e);

    adjust_bind(e);
}

// arrenge the list reduce its length
void arrange_ents()
{
    int i, ind = -1;
    entity *temp;
    if(ent_count == 0)
    {
        return;
    }
    if(ent_max == ent_count)
    {
        for(i = 0; i < ent_max; i++)
        {
            if(ent_list[i]->exists)
            {
                ent_post_update(ent_list[i]);
            }
        }
    }
    else
    {
        for(i = 0; i < ent_max; i++)
        {
            if(!ent_list[i]->exists && ind < 0)
            {
                ind = i;
            }
            else if(ent_list[i]->exists && ind >= 0)
            {
                temp = ent_list[i];
                ent_list[i] = ent_list[ind];
                ent_list[ind] = temp;
                ind++;
            }
            if(ent_list[i]->exists)
            {
                ent_post_update(ent_list[i]);
            }
        }
        ent_max = ent_count;
    }
    for(i = 0; i < ent_max; i++)
    {
        ent_list[i]->movex = ent_list[i]->movez = 0;
    }
}

// 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 i;
    for(i = 0; i < ent_max; i++)
    {
        if(ent_list[i]->exists && _time != ent_list[i]->timestamp)// dont update fresh entity
        {
            self = ent_list[i];
            self->update_mark = UPDATE_MARK_NONE;
            if(level)
            {
                check_lost();    // check lost caused by level scrolling or lifespan
            }
            if(!self->exists)
            {
                continue;
            }
            // expand _time incase being frozen
            if(is_frozen(self))
            {
                expand_time(self);
            }
            else
            {

                execute_updateentity_script(self);// execute a script
                if(!self->exists)
                {
                    continue;
                }
                check_ai();// check ai
                if(!self->exists)
                {
                    continue;
                }
                update_animation(); // if not frozen, update animation
                if(!self->exists)
                {
                    continue;
                }
                check_attack();// Collission detection
                if(!self->exists)
                {
                    continue;
                }
                update_health();// Update displayed health
                self->movex += self->velocity.x * self->speedmul * (100.0 / GAME_SPEED);
                self->movez += self->velocity.z * self->speedmul * (100.0 / GAME_SPEED);
            }
        }
    }//end of for
    arrange_ents();
    /*
    if(time>=nextplan){
    	plan();
    	nextplan = time+GAME_SPEED/2;
    }*/
}


void display_ents()
{
    unsigned f;
    int i, z, wall = 0, wall2;
    entity *e = NULL;
    entity *other = NULL;
    int qx; 
    int qy;
    int sy;
    int sz;
    int alty;
    int in_air;
    int sortid;
    float basemap;
    float temp1, temp2;
    int useshadow = 0;
    int can_mirror = 0;
    int shadowz;
    s_drawmethod *drawmethod = NULL;
    s_drawmethod commonmethod;
    s_drawmethod shadowmethod;
    int use_mirror = (level && level->mirror);

    int o_scrx = screenx, o_scry = screeny, scrx, scry;

    if(level)
    {
        shadowz = SHADOW_Z;
    }
    else
    {
        shadowz = MIN_INT + 100;
    }

    for(i = 0; i < ent_max; i++)
    {
        if(ent_list[i] && ent_list[i]->exists)
        {
            e = ent_list[i];
            // in a loop need to initialize to reset prev val. to avoid bugs.
            z = basemap = qx = qy = sy = sz = alty = sortid = temp1 = temp2 = 0;

            if(Script_IsInitialized(e->scripts->ondraw_script))
            {
                ent_stack[ent_stack_size++] = e;
            }
            //if(!e->exists) continue; // just in case kill is called in the script

            /*
            * If we have status, draw to screen (ex. boss life).
            */

            if(e->modeldata.hud_popup)
            {
                drawenemystatus(e);
            }
            
			sortid = e->sortid;
            scrx = o_scrx - ((e->modeldata.quake_config & QUAKE_CONFIG_DISABLE_SELF) ? 0 : gfx_x_offset);
            scry = o_scry - ((e->modeldata.quake_config & QUAKE_CONFIG_DISABLE_SELF) ? 0 : gfx_y_offset);
            
			if(freezeall || !(e->blink && (_time % (GAME_SPEED / 10)) < (GAME_SPEED / 20)))
            {
                float eheight = T_WALKOFF, eplatheight = 0;

                // get the height of the entity
                if ( e->animation->platform )
                {
                    s_anim *anim = e->animation;

                    /* if ( anim->platform[e->animpos] ) */ //pvmk - gcc says this is always true
                    {
                        if ( anim->platform[e->animpos][PLATFORM_HEIGHT] ) eplatheight += anim->platform[e->animpos][PLATFORM_HEIGHT];
                    }
                }
                if ( e->modeldata.size.y && eplatheight <= 0 ) eheight += e->modeldata.size.y;
                else eheight += eplatheight;

                // If special is being executed, display all entities regardless
                f = e->animation->sprite[e->animpos];

                //other = check_platform(e->position.x, e->position.z, e);
                other = check_platform_below(e->position.x, e->position.z, e->position.y+eheight, e);
                wall = checkwall_index(e->position.x, e->position.z);

                if (e->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_BASEMAP)
                {
                    basemap = check_basemap(e->position.x, e->position.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 = (int)e->position.z;    // Set the layer offset
					
                    if(other && e->position.y >= other->position.y + other->animation->platform[other->animpos][PLATFORM_HEIGHT] && !other->modeldata.setlayer)
                    {
                        float zdepth = (float)( (float)e->position.z - (float)other->position.z +
                                                (float)other->animation->platform[other->animpos][PLATFORM_DEPTH] -
                                                (float)other->animation->platform[other->animpos][PLATFORM_Z] );

                        if(
                            e->link														// Linked to entity.
							&& ((e->modeldata.grabback && !e->grabbing)						// Have grab back AND not grabbing.
								|| (e->link->modeldata.grabback && e->link->grabbing)		// Linked has grab back and is grabbing.
								|| e->grabbing)												// Grabbing.
                        )
                        {

                            // Make sure entities get displayed in front of obstacle and grabbee
                            sortid = other->sortid + zdepth + 2;
                            e->sortid = sortid;
                            z = (int)( other->position.z + 2 );
                        }

                        else
                        {
                            //if ( e->model->type == TYPE_PLAYER ) debug_printf("zdepth: %f",zdepth);

                            // Entity should always display in front of the obstacle
                            sortid = other->sortid + zdepth + 1;
                            e->sortid = sortid;
                            z = (int)( other->position.z + 1 );
                        }

                    }

                    // In most cases we want any spawned entity to
                    // default in front of owner.
                    if(e->owner)
                    {
                        // If this entity is not an exception to the rule,
                        // move its display order in front of owner.
                        if (!(self->modeldata.aimove & AIMOVE1_STAR))
                        {
                            sortid = e->owner->sortid + 1;
                        }
                    }

                    if(e->modeldata.setlayer)
                    {
                        z = HOLE_Z + e->modeldata.setlayer;    // Setlayer takes precedence
                    }

                    drawmethod = e->animation->drawmethods ? getDrawMethod(e->animation, e->animpos) : NULL;
                    
					if(e->drawmethod->flag)
                    {
                        drawmethod = (e->drawmethod);
                    }
                    if(!drawmethod)
                    {
                        commonmethod = plainmethod;
                    }
                    else
                    {
                        commonmethod = *drawmethod;
                    }
                    drawmethod = &commonmethod;

                    if(e->modeldata.alpha >= 1 && e->modeldata.alpha <= MAX_BLENDINGS)
                    {
                        if(drawmethod->alpha == BLEND_MODE_MODEL)
                        {
                            drawmethod->alpha = e->modeldata.alpha;
                        }
                    }

					// Color selection. If drawmethod does not yet have a color table pointer, then
					// we need to find one here.
                    if(!drawmethod->table)
                    {
						// Drawmethod remap by index. Is there a value?
                        if(drawmethod->remap >= 1)
                        {
							// Does the value fall within range of tables loaded? If so, use 
							// value to locate the color table by index, and then populate
							// drawmethod table value with the color table pointer.
							if (drawmethod->remap <= e->modeldata.maps_loaded)
							{
								drawmethod->table = model_get_colourmap(&(e->modeldata), drawmethod->remap);
							}                            
                        }

						// Color selection by entity property. Does it have a value? Note that script functions
						// and most text values in OpenBOR give the appearance this property is an integer index. 
						// In actuality those functions accept an index, but immediately use it to locate a color 
						// table pointer to populate this property. See ent_set_colourmap.
                        if(e->colourmap)
                        {
							// We don't want to override drawmethods, so first check drawmethod 
							// remap to make sure it is disabled (0 = force default, 1+ force alternates).
							// If it is (dsiabled) then use property value to populate drawmethod table.
                            if(drawmethod->remap < 0)
                            {
                                drawmethod->table = e->colourmap;
                            }
                        }

						// If we haven't populated the drawmethod table yet, use
						// model's default color table.
                        if(!drawmethod->table)
                        {
                            drawmethod->table = e->modeldata.palette;
                        }

						// If globalmap is true, then author wants entity to use the current
						// global color table.
                        if(e->modeldata.globalmap)
                        {
							// If we are in a level and the level has a palette index specified,
							// then use that index to find the color table pointer and populate
							// drawmethod table. Otherwise, just get the global color table pointer.
                            if(level && current_palette)
                            {
                                drawmethod->table = level->palettes[current_palette - 1];
                            }
                            else
                            {
                                drawmethod->table = pal;
                            }
                        }
                    }

					// If we have a dying remap, let's check for the dying flash effect.
                    if(e->dying)
                    {
						// This checks against both dying percentage thresholds and their associated 
						// timing. If any pass, then we can move on and apply a flash.
                        if((e->energy_state.health_current <= e->per1 && e->energy_state.health_current > e->per2 && (_time % (GAME_SPEED / 5)) < (GAME_SPEED / 10)) ||
                                (e->energy_state.health_current <= e->per2 && (_time % (GAME_SPEED / 10)) < (GAME_SPEED / 20)))
                        {
							// Have any HP left?
                            if(e->energy_state.health_current > 0 )
                            {
								// Do we have a second dying map? If not, we just use map 1.
                                if(e->dying2 > 0)
                                {
									// If health is between percentage 1 and 2, use map 1. Otherwise
									// use map 2.
                                    if(e->energy_state.health_current <= e->per1 && e->energy_state.health_current > e->per2)
                                    {
                                        drawmethod->table = model_get_colourmap(&(e->modeldata), e->dying);
                                    }
                                    else if(e->energy_state.health_current <= e->per2)
                                    {
                                        drawmethod->table = model_get_colourmap(&(e->modeldata), e->dying2);
                                    }
                                }
								else
								{
									drawmethod->table = model_get_colourmap(&(e->modeldata), e->dying);
								}
                            }
                        }
                    }

					// Draw the entity according to its facing.
                    if(e->direction == DIRECTION_LEFT)
                    {
						// Reverse the drawmethod flipx.
                        drawmethod->flipx = !drawmethod->flipx;
                        
						// If the flip rotate is enabled, reverse the
						// rotation setting.
						if(drawmethod->fliprotate && drawmethod->rotate)
                        {
                            drawmethod->rotate = 360 - drawmethod->rotate;
                        }
                    }

					// don't display if behind the mirror
                    if(!use_mirror || z > MIRROR_Z) 
                    {
                        //just a simple check, doesn't work with mirror nor gfxshadow
                        if(drawmethod->clipw)
                        {
                            drawmethod->clipx += (int)(e->position.x - scrx);
                            drawmethod->clipy += (int)(e->position.z - e->position.y - scry);
                        }
                        spriteq_add_sprite((int)(e->position.x - scrx), (int)(e->position.z - e->position.y - scry), z, f, drawmethod, sortid);
                    }

                    can_mirror = (use_mirror && self->position.z > MIRROR_Z);
                    if(can_mirror)
                    {
                        spriteq_add_sprite((int)(e->position.x - scrx), (int)((2 * MIRROR_Z - e->position.z) - e->position.y - scry), 2 * PANEL_Z - z , f, drawmethod, ent_list_size * 100 - sortid);
                    }
                }//end of if(f<sprites_loaded)                
                
                
                if (!(e->shadow_config_flags & SHADOW_CONFIG_DISABLED))
                {
                    if (e->shadow_config_flags & (SHADOW_CONFIG_GRAPHIC_REPLICA_AIR | SHADOW_CONFIG_GRAPHIC_REPLICA_GROUND) && f < sprites_loaded) //gfx shadow
                    {
                        useshadow = (e->animation->shadow ? e->animation->shadow[e->animpos] : 1) && shadowcolor && light.y;
                        //printf("\n %d, %d, %d\n", shadowcolor, light.x, light.y);

                        if (useshadow && e->position.y >= 0)
                        {
                            in_air = inair(e);

                            if ((e->shadow_config_flags & SHADOW_CONFIG_GRAPHIC_REPLICA_GROUND && !in_air) || (e->shadow_config_flags & SHADOW_CONFIG_GRAPHIC_REPLICA_AIR && in_air))
                            {
                                wall = checkwall_below(e->position.x, e->position.z, e->position.y);
                                if (wall < 0)
                                {
                                    alty = (int)e->position.y;
                                    temp1 = -1 * e->position.y * light.x / 256; // xshift
                                    temp2 = (float)(-alty * light.y / 256);               // zshift
                                    qx = (int)(e->position.x - scrx/* + temp1*/);
                                    qy = (int)(e->position.z - scry/* +  temp2*/);
                                }
                                else
                                {
                                    alty = (int)(e->position.y - level->walls[wall].height);
                                    temp1 = -1 * (e->position.y - level->walls[wall].height) * light.x / 256; // xshift
                                    temp2 = (float)(-alty * light.y / 256);               // zshift
                                    qx = (int)(e->position.x - scrx/* + temp1*/);
                                    qy = (int)(e->position.z - scry /*+  temp2*/ - level->walls[wall].height);
                                }

                                wall2 = checkwall_below(e->position.x + temp1, e->position.z + temp2, e->position.y); // check if the shadow drop into a hole or fall on another wall

                                if (other && other != e && e->position.y >= other->position.y + other->animation->platform[other->animpos][PLATFORM_HEIGHT] && !(e->shadow_config_flags & SHADOW_CONFIG_BASE_STATIC))
                                {
                                    alty = (int)(e->position.y - (other->position.y + other->animation->platform[other->animpos][PLATFORM_HEIGHT]));
                                    temp1 = -1 * (e->position.y - (other->position.y + other->animation->platform[other->animpos][PLATFORM_HEIGHT])) * light.x / 256; // xshift
                                    temp2 = (float)(-e->position.y * light.y / 256);

                                    qx = (int)(e->position.x - scrx);
                                    qy = (int)(e->position.z - scry - other->position.y - other->animation->platform[other->animpos][PLATFORM_HEIGHT]); // + (other->animation->platform[other->animpos][PLATFORM_DEPTH]/2)
                                    //qy = (int)( e->position.z - e->position.y - scry + (e->position.y-e->base) );
                                }

                                if (basemap > 0 && !(e->shadow_config_flags & SHADOW_CONFIG_BASE_STATIC))
                                {
                                    alty = (int)(e->position.y - basemap);
                                    temp1 = -1 * (e->position.y - basemap) * light.x / 256; // xshift
                                    temp2 = (float)(-alty * light.y / 256);               // zshift
                                    qx = (int)(e->position.x - scrx);
                                    qy = (int)(e->position.z - scry - basemap);
                                }

                                //TODO check platforms, don't want to go through the entity list again right now // && !other after wall2
                                if (!(checkhole(e->position.x + temp1, e->position.z + temp2) && wall2 < 0 && !other)) //&& !(wall>=0 && level->walls[wall].height>e->position.y))
                                {
                                    if (wall >= 0 && wall2 >= 0)
                                    {
                                        alty += (int)(level->walls[wall].height - level->walls[wall2].height);
                                        /*qx += -1*(level->walls[wall].height-level->walls[wall2].height)*light.x/256;
                                        qy += (level->walls[wall].height-level->walls[wall2].height) - (level->walls[wall].height-level->walls[wall2].height)*light.y/256;*/
                                    }
                                    else if (wall >= 0)
                                    {
                                        alty += (int)(level->walls[wall].height);
                                        /*qx += -1*level->walls[wall].height*light.x/256;
                                        qy += level->walls[wall].height - level->walls[wall].height*light.y/256;*/
                                    }
                                    else if (wall2 >= 0)
                                    {
                                        alty -= (int)(level->walls[wall2].height);
                                        /*qx -= -1*level->walls[wall2].height*light.x/256;
                                        qy -= level->walls[wall2].height - level->walls[wall2].height*light.y/256;*/
                                    }

                                    /*if (other)
                                    {
                                        alty += (int)(other->position.y + other->animation->platform[other->animpos][PLATFORM_HEIGHT]);
                                    }*/

                                    // set 2D-LIKE shadow
                                    if ((e->shadow_config_flags & SHADOW_CONFIG_BASE_PLATFORM)) alty = temp1 = temp2 = 0;

                                    sy = (2 * MIRROR_Z - qy) - 2 * scry;

                                    if (other && !(e->shadow_config_flags & SHADOW_CONFIG_BASE_STATIC)) z = other->position.z + 1;
                                    else z = shadowz;

                                    sz = PANEL_Z - HUD_Z;

                                    if (e->animation->shadow_coords)
                                    {
                                        if (e->direction == DIRECTION_RIGHT)
                                        {
                                            qx += e->animation->shadow_coords[e->animpos][0];
                                        }
                                        else
                                        {
                                            qx -= e->animation->shadow_coords[e->animpos][0];
                                        }
                                        qy += e->animation->shadow_coords[e->animpos][1];
                                        sy -= e->animation->shadow_coords[e->animpos][1];
                                    }
                                    shadowmethod = plainmethod;
                                    shadowmethod.fillcolor = (shadowcolor > 0 ? shadowcolor : 0);
                                    shadowmethod.alpha = shadowalpha;
                                    shadowmethod.channelb = shadowmethod.channelg = shadowmethod.channelr = shadowopacity;
                                    shadowmethod.table = drawmethod->table;
                                    shadowmethod.scalex = drawmethod->scalex;
                                    shadowmethod.flipx = drawmethod->flipx;
                                    shadowmethod.scaley = light.y * drawmethod->scaley / 256;
                                    shadowmethod.flipy = drawmethod->flipy;
                                    shadowmethod.centery += alty;
                                    if (shadowmethod.flipy)
                                    {
                                        shadowmethod.centery = -shadowmethod.centery;
                                    }
                                    if (shadowmethod.scaley < 0)
                                    {
                                        shadowmethod.scaley = -shadowmethod.scaley;
                                        shadowmethod.flipy = !shadowmethod.flipy;
                                    }
                                    shadowmethod.rotate = drawmethod->rotate;
                                    shadowmethod.shiftx = drawmethod->shiftx + light.x;

                                    spriteq_add_sprite(qx, qy, z, f, &shadowmethod, 0);
                                    if (use_mirror)
                                    {
                                        shadowmethod.flipy = !shadowmethod.flipy;
                                        shadowmethod.centery = -shadowmethod.centery;
                                        spriteq_add_sprite(qx, sy, sz, f, &shadowmethod, 0);
                                    }
                                }
                            }
                        }//end of gfxshadow
                    }

                    /* Plain (sprite) shadow. */
                    if (e->shadow_config_flags & (SHADOW_CONFIG_GRAPHIC_STATIC_AIR | SHADOW_CONFIG_GRAPHIC_STATIC_GROUND))
                    {
                        useshadow = e->animation->shadow ? e->animation->shadow[e->animpos] : e->modeldata.shadow;
                        if (useshadow < 0)
                        {
                            useshadow = e->modeldata.shadow;
                        }
                        if (useshadow && e->position.y >= 0 && !(checkhole(e->position.x, e->position.z) && checkwall_below(e->position.x, e->position.z, e->position.y) < 0))
                        {
                            in_air = inair(e);

                            if ((e->shadow_config_flags & SHADOW_CONFIG_GRAPHIC_STATIC_GROUND && !in_air) || (e->shadow_config_flags & SHADOW_CONFIG_GRAPHIC_STATIC_AIR && in_air))
                            {

                                if (other && other != e && e->position.y >= other->position.y + other->animation->platform[other->animpos][PLATFORM_HEIGHT] && !(e->shadow_config_flags & SHADOW_CONFIG_BASE_STATIC))
                                {
                                    qx = (int)(e->position.x - scrx);
                                    qy = (int)(e->position.z - other->position.y - other->animation->platform[other->animpos][PLATFORM_HEIGHT] - scry);
                                    sy = (int)((2 * MIRROR_Z - e->position.z) - other->position.y - other->animation->platform[other->animpos][PLATFORM_HEIGHT] - scry);

                                    z = (int)(other->position.z + 1);
                                    sz = 2 * PANEL_Z - z;
                                }
                                else if (level && wall >= 0)// && e->position.y >= level->walls[wall].height)
                                {
                                    qx = (int)(e->position.x - scrx);
                                    qy = (int)(e->position.z - level->walls[wall].height - scry);
                                    sy = (int)((2 * MIRROR_Z - e->position.z) - level->walls[wall].height - scry);
                                    z = shadowz;
                                    sz = PANEL_Z - HUD_Z;
                                }
                                else if (level && basemap > 0 && !(e->shadow_config_flags & SHADOW_CONFIG_BASE_STATIC))
                                {
                                    qx = (int)(e->position.x - scrx);
                                    qy = (int)(e->position.z - basemap - scry);
                                    sy = (int)((2 * MIRROR_Z - e->position.z) - basemap - scry);
                                    z = shadowz;
                                    sz = PANEL_Z - HUD_Z;
                                }
                                else
                                {
                                    qx = (int)(e->position.x - scrx);
                                    qy = (int)(e->position.z - scry);
                                    sy = (int)((2 * MIRROR_Z - e->position.z) - scry);
                                    z = shadowz;
                                    sz = PANEL_Z - HUD_Z;
                                }
                                if (e->animation->shadow_coords)
                                {
                                    if (e->direction == DIRECTION_RIGHT)
                                    {
                                        qx += e->animation->shadow_coords[e->animpos][0];
                                    }
                                    else
                                    {
                                        qx -= e->animation->shadow_coords[e->animpos][0];
                                    }
                                    qy += e->animation->shadow_coords[e->animpos][1];
                                    sy -= e->animation->shadow_coords[e->animpos][1];
                                }

                                shadowmethod = plainmethod;
                                shadowmethod.alpha = BLEND_MULTIPLY + 1;
                                shadowmethod.flipx = !e->direction;

                                spriteq_add_sprite(qx, qy, z, shadowsprites[useshadow - 1], &shadowmethod, 0);
                                if (use_mirror)
                                {
                                    spriteq_add_sprite(qx, sy, sz, shadowsprites[useshadow - 1], &shadowmethod, 0);
                                }
                            }
                        }//end of plan shadow
                    }
                }
                
            }// end of blink checking

            if(e->arrowon)    // Display the players image while invincible to indicate player number
            {
                if(e->modeldata.player_arrow[(int)e->playerindex].sprite && e->invincible & INVINCIBLE_INTANGIBLE)
                {
                    spriteq_add_sprite((int)(e->position.x - scrx + e->modeldata.player_arrow[(int)e->playerindex].position.x), (int)(e->position.z - e->position.y - scry + e->modeldata.player_arrow[(int)e->playerindex].position.y), (int)e->position.z, e->modeldata.player_arrow[(int)e->playerindex].sprite, NULL, sortid * 2);
                }
            }
        }// end of if(ent_list[i]->exists)

        // reset vars for next loop

    }// end of for

    //defer ondraw script so it can manage spriteq better
    for(i = 0; i < ent_stack_size; i++)
    {
        execute_ondraw_script(ent_stack[i]);
    }
    ent_stack_size = 0;
}



void toss(entity *ent, float lift)
{
    if(!lift)
    {
        return;    //zero?
    }
    ent->toss_time = _time + 1;
    ent->velocity.y = lift;
    ent->position.y += 0.5;        // Get some altitude (needed for checks)
}

entity *findent(int types)
{
    int i;
    for(i = 0; i < ent_max; i++)
    {        
        if(ent_list[i]->exists // Must exist.
            && (ent_list[i]->modeldata.type & types) // Be a type we are looking for.
            && !((ent_list[i]->death_state & (DEATH_STATE_DEAD | DEATH_STATE_CORPSE)) == (DEATH_STATE_DEAD | DEATH_STATE_CORPSE))) // Ignore dead corpses.
        {
            return ent_list[i];
        }
    }
    return NULL;
}



int count_ents(int types)
{
    int i;
    int count = 0;
    for(i = 0; i < ent_max; i++)
    {           

        count += (ent_list[i]->exists && (ent_list[i]->modeldata.type & types) && !(ent_list[i]->death_state & DEATH_STATE_CORPSE));
    }
    return count;
}

int isItem(entity *e)
{
    return e->modeldata.type & TYPE_ITEM;
}

int isSubtypeTouch(entity *e)
{
    return e->modeldata.subtype == SUBTYPE_TOUCH;
}

int isSubtypeWeapon(entity *e)
{
    return e->modeldata.subtype == SUBTYPE_WEAPON;
}

int isSubtypeProjectile(entity *e)
{
    return e->modeldata.subtype == SUBTYPE_PROJECTILE;
}

//check if an item is usable by the entity
int normal_test_item(entity *ent, entity *item)
{
    return (
               isItem(item) &&
               (item->modeldata.stealth.hide <= ent->modeldata.stealth.detect) &&
               diff(item->position.x, ent->position.x) + diff(item->position.z, ent->position.z) < videomodes.hRes / 2 &&
               item->animation->vulnerable[item->animpos] && !item->blink &&
               (validanim(ent, ANI_GET) || (isSubtypeTouch(item) && faction_check_can_damage(item, ent, 0))) &&
               (
                   (isSubtypeWeapon(item) && !ent->weapent && ent->modeldata.weapon_properties.weapon_list &&
                    ent->modeldata.weapon_properties.weapon_count >= item->modeldata.weapon_properties.weapon_index && ent->modeldata.weapon_properties.weapon_list[item->modeldata.weapon_properties.weapon_index - 1] >= 0)
                   || (isSubtypeProjectile(item) && !ent->weapent)
                   || (item->energy_state.health_current && (ent->energy_state.health_current < ent->modeldata.health) && ! isSubtypeProjectile(item) && ! isSubtypeWeapon(item))
               )
           );
}

int test_item(entity *ent, entity *item)
{
    if (!(
                isItem(item) &&
                item->animation->vulnerable[item->animpos] && !item->blink &&
                (validanim(ent, ANI_GET) || (isSubtypeTouch(item) && faction_check_can_damage(item, ent, 0)))
            ))
    {
        return 0;
    }
    if(isSubtypeProjectile(item) && ent->weapent)
    {
        return 0;
    }
    if(isSubtypeWeapon(item) &&
            (ent->weapent || !ent->modeldata.weapon_properties.weapon_list ||
             ent->modeldata.weapon_properties.weapon_count < item->modeldata.weapon_properties.weapon_index ||
             ent->modeldata.weapon_properties.weapon_list[item->modeldata.weapon_properties.weapon_index - 1] < 0)
      )
    {
        return 0;
    }
    return 1;
}

int player_test_pickable(entity *ent, entity *item)
{
    if(isSubtypeTouch(item))
    {
        return 0;
    }
    
    if(isSubtypeWeapon(item) && ent->modeldata.weapon_properties.weapon_state & WEAPON_STATE_ANIMAL)
    {
        return 0;
    }

    if(diff(ent->base , item->position.y) > 0.1)
    {
        return 0;
    }
    
    return test_item(ent, item);
}

int player_test_touch(entity *ent, entity *item)
{
    if(!isSubtypeTouch(item))
    {
        return 0;
    }

    if(isSubtypeWeapon(item) && ent->modeldata.weapon_properties.weapon_state & WEAPON_STATE_ANIMAL)
    {
        return 0;
    }

    if(diff(ent->base , item->position.y) > 1)
    {
        return 0;
    }
    return test_item(ent, item);
}

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

int set_idle(entity *ent)
{
    ent->idling = IDLING_PREPARED;
    ent->attacking = ATTACKING_NONE;
    ent->inpain = IN_PAIN_NONE;
    ent->rising = RISING_NONE;
    ent->ducking = DUCK_NONE;
    ent->inbackpain = 0;
    ent->falling = 0;
    ent->jumping = 0;
    ent->blocking = 0;
    common_idle_anim(ent);
    return 1;
}

int set_death(entity *iDie, int type, int reset)
{
    int die = 0;

    //iDie->velocity.x = iDie->velocity.z = iDie->velocity.y = 0; // stop the target
    if(iDie->blocking && validanim(iDie, ANI_CHIPDEATH))
    {
        ent_set_anim(iDie, ANI_CHIPDEATH, reset);
        iDie->idling = IDLING_NONE;
        iDie->getting = 0;
        iDie->jumping = 0;
        iDie->charging = 0;
        iDie->attacking = ATTACKING_NONE;
        iDie->blocking = 0;
        iDie->inpain = IN_PAIN_NONE;
        iDie->falling = 0;
        iDie->rising = RISING_NONE;
        iDie->ducking = DUCK_NONE;
        return 1;
    }

    if(type < 0 || type >= max_attack_types)
    {
        type = 0;
    }

    if ( iDie->inbackpain ) die = animbackdies[type];
    else die = animdies[type];

    if(validanim(iDie, die))
    {
        ent_set_anim(iDie, die, reset);
    }
    else if( iDie->inbackpain && validanim(iDie, animbackdies[0]) )
    {
        ent_set_anim(iDie, animbackdies[0], reset);
    }
    else if(validanim(iDie, animdies[type]))
    {
        if ( iDie->inbackpain ) reset_backpain(iDie);
        iDie->inbackpain = 0;
        ent_set_anim(iDie, animdies[type], reset);
    }
    else if(validanim(iDie, animdies[0]))
    {
        if ( iDie->inbackpain ) reset_backpain(iDie);
        iDie->inbackpain = 0;
        ent_set_anim(iDie, animdies[0], reset);
    }
    else
    {
        return 0;
    }

    iDie->idling = IDLING_NONE;
    iDie->getting = 0;
    iDie->jumping = 0;
    iDie->charging = 0;
    iDie->attacking = ATTACKING_NONE;
    iDie->blocking = 0;
    iDie->inpain = IN_PAIN_NONE;
    iDie->falling = 0;
    iDie->rising = RISING_NONE;
    iDie->ducking = DUCK_NONE;
    if(iDie->frozen)
    {
        unfrozen(iDie);
    }
    return 1;
}


int set_fall(entity *ent, entity *other, s_attack *attack, int reset)
{
    int fall = 0;

    if ( ent->inbackpain ) fall = animbackfalls[attack->attack_type];
    else fall = animfalls[attack->attack_type];
   
    if(validanim(ent, fall))
    {
        ent_set_anim(ent, fall, reset);
    }
    else if( ent->inbackpain && validanim(ent, animbackfalls[0]) )
    {       
        ent_set_anim(ent, animbackfalls[0], reset);
    }
    else if( validanim(ent, animfalls[attack->attack_type]) )
    {
        if ( ent->inbackpain ) reset_backpain(ent);
        ent->inbackpain = 0;
        ent_set_anim(ent, animfalls[attack->attack_type], reset);
    }
    else if(validanim(ent, animfalls[0]))
    {
        if ( ent->inbackpain ) reset_backpain(ent);
        ent->inbackpain = 0;
        ent_set_anim(ent, animfalls[0], reset);
    }
    else
    {
        return 0;
    }

    ent->drop = 1;
    ent->inpain = IN_PAIN_NONE;
    ent->rising = RISING_NONE;
    ent->idling = IDLING_NONE;
    ent->falling = 1;
    ent->jumping = 0;
    ent->ducking = DUCK_NONE;
    ent->getting = 0;
    ent->charging = 0;
    ent->attacking = ATTACKING_NONE;
    ent->blocking = 0;
    ent->nograb = 1;

    if(ent->frozen)
    {
        unfrozen(ent);
    }
    execute_onfall_script(ent, other, attack);

    return 1;
}

int set_rise(entity *iRise, int type, int reset)
{
    int rise = 0;

    if(type < 0 || type >= max_attack_types)
    {
        type = 0;
    }

    if ( iRise->inbackpain ) rise = animbackrises[type];
    else rise = animrises[type];

    if(validanim(iRise, rise))
    {
        ent_set_anim(iRise, rise, reset);
    }
    else if( iRise->inbackpain && validanim(iRise, animbackrises[0]) )
    {
        ent_set_anim(iRise, animbackrises[0], reset);
    }
    else if( validanim(iRise, animrises[type]) )
    {
        if ( iRise->inbackpain ) reset_backpain(iRise);
        iRise->inbackpain = 0;
        ent_set_anim(iRise, animrises[type], reset);
    }
    else if(validanim(iRise, animrises[0]))
    {
        if ( iRise->inbackpain ) reset_backpain(iRise);
        iRise->inbackpain = 0;
        ent_set_anim(iRise, animrises[0], reset);
    }
    else
    {
        return 0;
    }

    iRise->takeaction = common_rise;
    // Get up again
    iRise->drop = 0;
    iRise->falling = 0;
    iRise->rising |= RISING_RISE;
    iRise->rising &= ~RISING_ATTACK;
    iRise->projectile = BLAST_NONE;
    iRise->nograb = iRise->nograb_default; //iRise->nograb = 0;
    iRise->velocity.x = self->velocity.z = self->velocity.y = 0;
    iRise->modeldata.jugglepoints.current = iRise->modeldata.jugglepoints.max; //reset jugglepoints
    return 1;
}

int set_riseattack(entity *iRiseattack, int type, int reset)
{
    int riseattack = 0;

    if( (!validanim(iRiseattack, animriseattacks[type]) ||
        (iRiseattack->inbackpain && !validanim(iRiseattack, animbackriseattacks[type]) && !validanim(iRiseattack, animriseattacks[type]))) &&
       iRiseattack->modeldata.riseattacktype == 1 )
    {
        type = 0;
    }
    if(iRiseattack->modeldata.riseattacktype == 0 || type < 0 || type >= max_attack_types)
    {
        type = 0;
    }

    if ( iRiseattack->inbackpain ) riseattack = animbackriseattacks[type];
    else riseattack = animriseattacks[type];

    if(validanim(iRiseattack, riseattack))
    {
        ent_set_anim(iRiseattack, riseattack, reset);
    }
    else if( iRiseattack->inbackpain && validanim(iRiseattack, animbackriseattacks[0]) )
    {
        ent_set_anim(iRiseattack, animbackriseattacks[0], reset);
    }
    else if( validanim(iRiseattack, animriseattacks[type]) )
    {
        if ( iRiseattack->inbackpain ) reset_backpain(iRiseattack);
        iRiseattack->inbackpain = 0;
        ent_set_anim(iRiseattack, animriseattacks[type], reset);
    }
    else if(validanim(iRiseattack, animriseattacks[0]))
    {
        if ( iRiseattack->inbackpain ) reset_backpain(iRiseattack);
        iRiseattack->inbackpain = 0;
        ent_set_anim(iRiseattack, animriseattacks[0], reset);
    }
    else
    {
        return 0;
    }

    iRiseattack->takeaction = common_attack_proc;
    self->staydown.riseattack_stall = 0;			//Reset riseattack delay.
    set_attacking(iRiseattack);
    iRiseattack->inpain = IN_PAIN_NONE;
    iRiseattack->falling = 0;
    iRiseattack->ducking = DUCK_NONE;
    iRiseattack->rising &= ~RISING_RISE;
    iRiseattack->rising |= RISING_ATTACK;
    iRiseattack->drop = 0;
    iRiseattack->nograb = iRiseattack->nograb_default; //iRiseattack->nograb = 0;
    iRiseattack->modeldata.jugglepoints.current = iRiseattack->modeldata.jugglepoints.max; //reset jugglepoints
    return 1;
}

int set_blockpain(entity *ent, e_attack_types attack_type, int reset)
{
    e_animations animation;

    // If attack type is out of bounds we
    // just use normal.
    if(attack_type < ATK_NORMAL || attack_type >= max_attack_types)
    {
        attack_type = ATK_NORMAL;
    }

    // In front or back?
    if (ent->inbackpain)
    {
        animation = animbackblkpains[attack_type];
    }
    else
    {
        animation = animblkpains[attack_type];
    }

    if(validanim(ent, animation))
    {
        ent_set_anim(ent, animation, reset);
    }
    else if( ent->inbackpain && validanim(ent, animbackblkpains[ATK_NORMAL]) )
    {
        ent_set_anim(ent, animbackblkpains[ATK_NORMAL], reset);
    }
    else if(validanim(ent, animblkpains[attack_type]))
    {
        if (ent->inbackpain)
        {
            reset_backpain(ent);
        }

        ent->inbackpain = 0;
        ent_set_anim(ent, animblkpains[attack_type], reset);
    }
    else if(validanim(ent, animblkpains[ATK_NORMAL]))
    {
        if (ent->inbackpain)
        {
            reset_backpain(ent);
        }

        ent->inbackpain = 0;
        ent_set_anim(ent, animblkpains[ATK_NORMAL], reset);
    }
    else
    {
        return 0;
    }

    ent->takeaction = common_block;
    set_blocking(self);
    ent->inpain = IN_PAIN_BLOCK;
    ent->rising = RISING_NONE;
    ent->ducking = DUCK_NONE;
    ent_set_anim(ent, animblkpains[attack_type], reset);
    return 1;
}

int reset_backpain(entity *ent)
{
    if (ent->normaldamageflipdir >= 0)
    {
        if (ent->normaldamageflipdir == DIRECTION_RIGHT)
        {
            ent->direction = DIRECTION_RIGHT;
        }
        else 
        {
            ent->direction = DIRECTION_LEFT;
        }
        if(ent->direction == DIRECTION_RIGHT) ent->velocity.x = -1*fabsf(ent->velocity.x);
        else ent->velocity.x = fabsf(ent->velocity.x);

        return 1;
    }

    return 0;
}

int check_backpain(entity* attacker, entity* defender) {
    if ( !(defender->modeldata.pain_config_flags & PAIN_CONFIG_BACK_PAIN)) return 0;
    if ( defender->inpain & IN_PAIN_HIT) return 0;
    if ( defender->falling ) return 0;
    if ( defender->death_state & DEATH_STATE_DEAD) return 0;
    if ( ((!defender->direction && attacker->position.x > defender->position.x) || (defender->direction && attacker->position.x < defender->position.x)) )
    {
        defender->inbackpain = 1;
        return 1;
    } else if ( defender->inbackpain ) defender->inbackpain = 0;

    return 0;
}

int set_pain(entity *iPain, int type, int reset)
{
    int pain = 0;

    iPain->velocity.x = iPain->velocity.z = iPain->velocity.y = 0; // stop the target
    if(iPain->modeldata.guardpoints.max > 0 && iPain->modeldata.guardpoints.current <= 0)
    {
        pain = ANI_GUARDBREAK;
        iPain->modeldata.guardpoints.current = iPain->modeldata.guardpoints.max;
    }
    else if(type == -1 || type >= max_attack_types)
    {
        pain = ANI_GRABBED;
    }
    else
    {
        if ( iPain->inbackpain ) pain = animbackpains[type];
        else pain = animpains[type];
    }


    if(validanim(iPain, pain))
    {
        ent_set_anim(iPain, pain, reset);
    }
    else if( iPain->inbackpain && validanim(iPain, animbackpains[0]) )
    {
        ent_set_anim(iPain, animbackpains[0], reset);
    }
    else if( (type != -1 && type < max_attack_types) && validanim(iPain, animpains[type]) )
    {
        if ( iPain->inbackpain ) reset_backpain(iPain);
        iPain->inbackpain = 0;
        ent_set_anim(iPain, animpains[type], reset);
    }
    else if(validanim(iPain, animpains[0]))
    {
        if ( iPain->inbackpain ) reset_backpain(iPain);
        iPain->inbackpain = 0;
        ent_set_anim(iPain, animpains[0], reset);
    }
    else if(validanim(iPain, ANI_IDLE))
    {
        if ( iPain->inbackpain ) reset_backpain(iPain);
        iPain->inbackpain = 0;
        ent_set_anim(iPain, ANI_IDLE, reset);
    }
    else
    {
        return 0;
    }

	iPain->idling = IDLING_NONE;
	iPain->falling = 0;
	iPain->rising = RISING_NONE;
	iPain->ducking = DUCK_NONE;
	iPain->projectile = BLAST_NONE;
	iPain->drop = 0;
	iPain->attacking = ATTACKING_NONE;
	iPain->getting = 0;
	iPain->charging = 0;
	iPain->jumping = 0;
	iPain->blocking = 0;
	iPain->inpain = IN_PAIN_HIT;
	if(iPain->frozen) unfrozen(iPain);

    if(pain == ANI_GRABBED)
    {
        iPain->inpain = IN_PAIN_NONE;
        iPain->rising = RISING_NONE;
        iPain->ducking = DUCK_NONE;
        if ( iPain->inbackpain ) reset_backpain(iPain);
        iPain->inbackpain = 0;
    }

    execute_onpain_script(iPain, type, reset);
    return 1;
}

/*
 * Copy animations from newmodel to model. If newmodel has anim that is not in model,
 * then copy that anim (but no frames with alloc_anim()) from newmodel to model
 */
void normalize_anim_models(s_model *model, s_model *newmodel)
{
	int i = 0;

	for(i = 0; i < max_animations; i++)
	{
		if(!model->animation[i] && newmodel->animation[i])
		{
			model->animation[i] = alloc_anim();
		}
	}

	return;
}

//change model, anim_flag 1: reset animation 0: use original animation
void set_model_ex(entity *ent, char *modelname, int index, s_model *newmodel, int anim_flag)
{
    s_model *model = NULL;
    entity tempe;
    s_defense *dfs = NULL;
    s_offense *ofs = NULL;
    int   i;
    int   type = ent->modeldata.type;

    model = ent->model;
    tempe.exists = 0;

    if(!newmodel)
    {
        if(index >= 0)
        {
            newmodel = model_cache[index].model;
        }
        else
        {
            newmodel = findmodel(modelname);
        }
    }
    if(!newmodel)
    {
        borShutdown(1, "Can't set model for entity '%s', model not found.\n", ent->name);
    }
    if(newmodel == model)
    {
        return;
    }

    if(!(newmodel->model_flag & MODEL_COPY_FLAG_NO_BASIC))
    {
        newmodel->move_config_flags = model->move_config_flags;

        if(!newmodel->speed.x)
        {
            newmodel->speed.x = model->speed.x;
        }
        if(!newmodel->runspeed)
        {
            newmodel->runspeed = model->runspeed;
            newmodel->runjumpheight = model->runjumpheight;
            newmodel->runjumpdist = model->runjumpdist;
            newmodel->run_config_flags = model->run_config_flags;
        }
        if(newmodel->icon.def           <   0)
        {
            newmodel->icon.def          = model->icon.def;
        }
        if(newmodel->icon.pain       <   0)
        {
            newmodel->icon.pain      = model->icon.pain;
        }
        if(newmodel->icon.get        <   0)
        {
            newmodel->icon.get       = model->icon.get;
        }
        if(newmodel->icon.die        <   0)
        {
            newmodel->icon.die       = model->icon.die;
        }
        if(newmodel->shadow         <   0)
        {
            newmodel->shadow        = model->shadow;
        }
        if(newmodel->knife          <   0)
        {
            newmodel->knife         = model->knife;
        }
        if(newmodel->pshotno        <   0)
        {
            newmodel->pshotno       = model->pshotno;
        }
        if(newmodel->bomb           <   0)
        {
            newmodel->bomb          = model->bomb;
        }
        if(newmodel->star           <   0)
        {
            newmodel->star          = model->star;
        }
        if(newmodel->flash          <   0)
        {
            newmodel->flash         = model->flash;
        }
        if(newmodel->bflash         <   0)
        {
            newmodel->bflash        = model->bflash;
        }
        if(newmodel->dust.fall_land        <   0)
        {
            newmodel->dust.fall_land       = model->dust.fall_land;
        }
        if(newmodel->dust.jump_land  <   0)
        {
            newmodel->dust.jump_land       = model->dust.jump_land;
        }
        if(newmodel->diesound       <   0)
        {
            newmodel->diesound      = model->diesound;
        }

        for(i = 0; i < max_animations; i++)
        {
            if(!newmodel->animation[i] && model->animation[i] && model->animation[i]->numframes > 0)
            {
                newmodel->animation[i] = model->animation[i];
            }
        }
		//normalize_anim_models(model, newmodel);        
    }

    // copy the weapon list if model flag is not set to use its own weapon list
    if (!(newmodel->model_flag & MODEL_COPY_FLAG_NO_WEAPON))
    {
        newmodel->weapon_properties.weapon_index = model->weapon_properties.weapon_index;
        if (!newmodel->weapon_properties.weapon_list)
        {
            newmodel->weapon_properties.weapon_list = model->weapon_properties.weapon_list;
            newmodel->weapon_properties.weapon_count = model->weapon_properties.weapon_count;
        }
    }

    //Make a shallow copy of old entity values, not safe but easy.
    //Also copy offense and defense because they are more likely be used by weapons,
    //other references are left alone for now
    if(Script_IsInitialized(newmodel->scripts->onmodelcopy_script))
    {
        tempe = *ent;
        dfs = malloc(sizeof(*dfs) * max_attack_types);
        ofs = malloc(sizeof(*ofs) * max_attack_types);

        memcpy(dfs, ent->defense, sizeof(*dfs)*max_attack_types);
        memcpy(ofs, ent->offense, sizeof(*ofs)*max_attack_types);
        
        tempe.defense = dfs;
        tempe.offense = ofs;
    }

    ent_set_model(ent, newmodel->name, anim_flag);

    ent->modeldata.type = type;

    if((newmodel->model_flag & MODEL_COPY_FLAG_NO_SCRIPT))
    {
        clear_all_scripts(ent->scripts, 0);
    }

    copy_all_scripts(newmodel->scripts, ent->scripts, 0);

    memcpy(ent->defense, ent->modeldata.defense, sizeof(*ent->defense)*max_attack_types);
    memcpy(ent->offense, ent->modeldata.offense, sizeof(*ent->offense)*max_attack_types);

    ent_set_colourmap(ent, ent->map);
    if(Script_IsInitialized(ent->scripts->onmodelcopy_script))
    {
        execute_onmodelcopy_script(ent, &tempe);
        if(ofs)
        {
            free(ofs);
        }
        if(dfs)
        {
            free(dfs);
        }
    }
}

void set_weapon(entity *ent, int wpnum, int anim_flag) // anim_flag added for scripted midair weapon changing
{
    if(!ent)
    {
        return;
    }
//printf("setweapon: %d \n", wpnum);

    if(ent->modeldata.type & TYPE_PLAYER) // save current weapon for player's weaploss WEAPON_LOSS_CONDITION_STAGE
    {
        if(ent->modeldata.weapon_properties.loss_condition & WEAPON_LOSS_CONDITION_STAGE)
        {
            player[(int)ent->playerindex].weapnum = wpnum;
        }
        else
        {
            player[(int)ent->playerindex].weapnum = level->setweap;
        }
    }

    if(ent->modeldata.weapon_properties.weapon_list && wpnum > 0 && wpnum <= ent->modeldata.weapon_properties.weapon_count && ent->modeldata.weapon_properties.weapon_list[wpnum - 1])
    {
        set_model_ex(ent, NULL, ent->modeldata.weapon_properties.weapon_list[wpnum - 1], NULL, !anim_flag);
    }
    else
    {
        set_model_ex(ent, NULL, -1, ent->defaultmodel, 1);
    }
}

//////////////////////////////////////////////////////////////////////////
//                  common A.I. code for enemies & NPCs
//////////////////////////////////////////////////////////////////////////


entity *melee_find_target()
{
    return NULL;
}

entity *long_find_target()
{
    return NULL;
}

entity *block_find_target(int anim, int detect_adj)
{
    int i;
    int min;
    int max;
    int detect;
    int index = -1;
    min = 0;
    max = 9999;
    float diffx, diffz, diffd, diffo = 0;
    entity      *attacker;

    detect = detect_adj + self->modeldata.stealth.detect;

    //find the 'nearest' attacking one
    for(i = 0; i < ent_max; i++)
    {
        attacker = ent_list[i];

        if (attacker && attacker->exists && attacker != self // Can't target self
            && (faction_check_can_damage(attacker, self, 0)) // Type is something attacker can damage.
            && (anim < 0 || (anim >= 0 && check_range_target_all(self, attacker, anim, 0, 0))) // Valid animation ID and in range.
            && !(attacker->death_state & DEATH_STATE_DEAD) // Must be alive.
            && attacker->attacking != ATTACKING_NONE // Must be attacking.
            && collision_attack_find_no_block_on_frame(attacker->animation, attacker->animpos, 1) != NULL // Valid blockable attack.
            && (diffd = (diffx = diff(attacker->position.x, self->position.x)) + (diffz = diff(attacker->position.z, self->position.z))) >= min
            && diffd <= max
            && (attacker->modeldata.stealth.hide <= detect) // Stealth factor less then perception factor (allows invisibility).
            )
        {
            if (index < 0 || diffd < diffo)
            {
                index = i;
                diffo = diffd;

                continue;
            }
        }
    }
    if( index >= 0)
    {
        return ent_list[index];
    }
    return NULL;
}

entity *normal_find_target(int anim, int detect_adj)
{

    /*
    normal_find_target
    Author unknown
    Date unknown
    ~Damon Caskey, 2011_07_22: Add support for detect adjustment.

    int anim:           Animation find range will be calculated by. Default to current animation if not passed.
    int detect_adj:     Local detection adjustment. Allows lesser or greater penetration of target's stealth for location.
    */

    int i;
    int min;
    int max;
    int detect;
    int index = -1;
    float diffx = 0;
    float diffz = 0;
    float diffd = 0;
    float diffo = 0;

    min = 0;
    max = 9999;

    detect = detect_adj + self->modeldata.stealth.detect;

    //find the 'nearest' one
    for(i = 0; i < ent_max; i++)
    {
        // Must exist.
        if(!ent_list[i]->exists)
        {
            continue;
        }

        // Can't be self.
        if(ent_list[i] == self)
        {
            continue;
        }

        /* Must be hostile toward it. */
        if (!faction_check_is_hostile(self, ent_list[i]))
        {
            continue;
        }

        // If anim is defined, then then target must be
        // in range of animation.
        if(anim >= 0)
        {
            if(!check_range_target_all(self, ent_list[i], anim, 0, 0))
            {
                continue;
            }
        }

        // Can't be dead.
        if(ent_list[i]->death_state & DEATH_STATE_DEAD)
        {
            continue;
        }

        // Get X and Z differences between us and target. We then
        // add them up to get a total distance.
        diffx = diff(ent_list[i]->position.x, self->position.x);
        diffz = diff(ent_list[i]->position.z, self->position.z);
        diffd = diffx + diffz;

        // Distance must be within min and max.
        if(diffd <= min || diffd >= max)
        {
            continue;
        }

        // Stealth must not be greater than perception.
        if(ent_list[i]->modeldata.stealth.hide > detect)
        {
            continue;
        }


        if(index < 0
           || (index >= 0 && (!ent_list[index]->animation->vulnerable[ent_list[index]->animpos] || ent_list[index]->invincible & INVINCIBLE_INTANGIBLE))
           // don't turn to the one on the back
           || ((self->position.x < ent_list[i]->position.x) == (self->direction == DIRECTION_RIGHT) && diffd < diffo)
          )
        {
            index = i;
            diffo = diffd;
        }


    }

    if(index >= 0)
    {
        return ent_list[index];
    }
    return NULL;
}

//Used by default A.I. pattern
// A.I. characters try to find a pickable item
entity *normal_find_item()
{

    int i;
    int index = -1;
    entity *ce = NULL;
    //find the 'nearest' one
    for(i = 0; i < ent_max; i++)
    {
        ce = ent_list[i];

        if( ce->exists && normal_test_item(self, ce) )
        {
            if(index < 0 || diff(ce->position.x, self->position.x) + diff(ce->position.z, self->position.z) < diff(ent_list[index]->position.x, self->position.x) + diff(ent_list[index]->position.z, self->position.z))
            {
                index = i;
            }
        }
    }
    if( index >= 0)
    {
        return ent_list[index];
    }
    return NULL;
}

int long_attack()
{
    return 0;
}

int melee_attack()
{
    return 0;
}

// chose next attack in atchain, if succeeded, return 1, otherwise return 0.
int perform_atchain()
{
    int pickanim = 0;

    if(self->modeldata.chainlength <= 0)
    {
        return 0;
    }

    if(self->combotime > _time)
    {
        self->combostep[0]++;
    }
    else
    {
        self->combostep[0] = 1;
    }

    if(self->modeldata.atchain[self->combostep[0] - 1] == 0) // 0 means the chain ends
    {
        self->combostep[0] = 1;
    }

    if(validanim(self, animattacks[self->modeldata.atchain[self->combostep[0] - 1] - 1]) )
    {
        if(((self->combostep[0] == 1 || !(self->modeldata.combostyle & 1)) && (self->modeldata.type & TYPE_PLAYER)) || // player should use attack 1st step without checking range

                (!(self->modeldata.combostyle & 1) && normal_find_target(animattacks[self->modeldata.atchain[0] - 1], 0)) || // normal chain just checks the first attack in chain(guess no one like it)

                ((self->modeldata.combostyle & 1) && normal_find_target(animattacks[self->modeldata.atchain[self->combostep[0] - 1] - 1], 0))) // combostyle 1 checks all anyway
        {
            pickanim = 1;
        }
        else if((self->modeldata.combostyle & 1) && self->combostep[0] != 1) // ranged combo? search for a valid attack
        {

            while(++self->combostep[0] <= self->modeldata.chainlength)
            {
                if(self->modeldata.atchain[self->combostep[0] - 1] &&
                        validanim(self, animattacks[self->modeldata.atchain[self->combostep[0] - 1] - 1]) &&
                        (self->combostep[0] == self->modeldata.chainlength ||
                         normal_find_target(animattacks[self->modeldata.atchain[self->combostep[0] - 1] - 1], 0)))
                {
                    pickanim = 1;
                    break;
                }
            }
        }
    }
    else
    {
        self->combostep[0] = 0;
    }
    if(pickanim && validanim(self, animattacks[self->modeldata.atchain[self->combostep[0] - 1] - 1]))
    {
        self->takeaction = common_attack_proc;
        set_attacking(self);
        ent_set_anim(self, animattacks[self->modeldata.atchain[self->combostep[0] - 1] - 1], 1);
    }
    if(!pickanim || self->combostep[0] > self->modeldata.chainlength)
    {
        self->combostep[0] = 0;
    }
    if((self->modeldata.combostyle & 2))
    {
        self->combotime = _time + combodelay;
    }
    return pickanim;
}

void upper_prepare()
{
    int predir = self->direction;

    entity *target = normal_find_target(ANI_UPPER, 0);

    self->velocity.x = self->velocity.z = 0; //stop

    if(!target)
    {
        self->idling = IDLING_PREPARED;
        self->takeaction = NULL;
        return;
    }

    //check if target is behind, so we can perform a turn back animation
    if(!(self->modeldata.move_config_flags & MOVE_CONFIG_NO_FLIP))
    {
        self->direction = (self->position.x < target->position.x);
    }
    if(predir != self->direction && validanim(self, ANI_TURN))
    {
        self->takeaction = common_turn;
        self->direction = predir;
        set_turning(self);
        ent_set_anim(self, ANI_TURN, 0);
        return;
    }

    // Wait...
    if(_time < self->stalltime)
    {
        return;
    }

    // Target jumping? Try uppercut!
    if(target && target->jumping)
    {
        self->takeaction = common_attack_proc;
        set_attacking(self);
        self->velocity.z = self->velocity.x = 0;
        // Don't waste any time!
        ent_set_anim(self, ANI_UPPER, 0);
        return;
    }
}

void normal_prepare()
{
    int i, j;
    int found = 0, special = 0;
    int predir = self->direction;

    entity *target = normal_find_target(-1, 0);

    self->velocity.x = self->velocity.z = 0; //stop

    if(!target)
    {
        self->idling = IDLING_PREPARED;
        self->takeaction = NULL;
        return;
    }

    //check if target is behind, so we can perform a turn back animation
    if(!(self->modeldata.move_config_flags & MOVE_CONFIG_NO_FLIP))
    {
        self->direction = (self->position.x < target->position.x);
    }
    if(predir != self->direction && validanim(self, ANI_TURN))

    {
        self->takeaction = common_turn;
        self->direction = predir;
        set_turning(self);
        ent_set_anim(self, ANI_TURN, 0);
        return;
    }

    // Wait...
    if(_time < self->stalltime)
    {
        return;
    }
    // let go the projectile, well
    if( self->weapent && self->weapent->modeldata.subtype == SUBTYPE_PROJECTILE &&
            validanim(self, ANI_THROWATTACK) &&
            check_range_target_all(self, target, ANI_THROWATTACK, 0, 0))
    {
        self->takeaction = common_attack_proc;
        set_attacking(self);
        ent_set_anim(self, ANI_THROWATTACK, 0);
        return ;
    }

    // move freespecial check here

    for(i = 0; i < max_freespecials; i++)
    {
        if(validanim(self, animspecials[i]) &&
                (check_energy(ENERGY_TYPE_MP, animspecials[i]) ||
                 check_energy(ENERGY_TYPE_HP, animspecials[i])) &&
                check_range_target_all(self, target, animspecials[i], 0, 0))
        {
            atkchoices[found++] = animspecials[i];
        }
    }
    if((rand32() & 7) < 2)
    {
        if(found && check_costmove(atkchoices[(rand32() & 0xffff) % found], 1, 0) )
        {
            return;
        }
    }
    special = found;

    if(self->modeldata.chainlength > 1) // have a chain?
    {
        if(perform_atchain())
        {
            return;
        }
    }
    else if (self->ducking & DUCK_ACTIVE)
    {
        self->takeaction = common_attack_proc;
        set_attacking(self);
        ent_set_anim(self, ANI_DUCKATTACK, 0);
        return;
    }
    else // dont have a chain so just select an attack randomly
    {
        // Pick an attack
        for(i = 0; i < max_attacks; i++)
        {
            if( validanim(self, animattacks[i]) &&
                    check_range_target_all(self, target, animattacks[i], 0, 0))
            {
                // a trick to make attack 1 has a greater chance to be chosen
                // 6 5 4 3 2 1 1 1 1 1 ....
                for(j = ((5 - i) >= 0 ? (5 - i) : 0); j >= 0; j--)
                {
                    atkchoices[found++] = animattacks[i];
                }
            }
        }
        if(found > special)
        {
            self->takeaction = common_attack_proc;
            set_attacking(self);
            ent_set_anim(self, atkchoices[special + (rand32() & 0xffff) % (found - special)], 0);
            return;
        }
    }

    // if no attack was picked, just choose a random one from the valid list
    if(special && check_costmove(atkchoices[(rand32() & 0xffff) % special], 1, 0))
    {
        return;
    }

    // No attack to perform, return to A.I. root
    self->idling = IDLING_PREPARED;
    self->takeaction = NULL;
}

void common_jumpland()
{
    if(self->animating)
    {
        return;
    }
    self->takeaction = NULL;
    set_idle(self);
}

//A.I characters play the jump animation
void common_jump()
{
    entity *dust;

    if(inair(self))
    {
        //printf("%f %f %f %d\n", self->base, self->position.y, self->velocity.y, self->landed_on_platform);
        return;
    }

    if(self->velocity.y <= 0) // wait if it is still go up
    {
        self->velocity.y = 0;
        self->position.y = self->base;

        self->jumping = 0;
        self->ducking = DUCK_NONE;
        self->attacking = ATTACKING_NONE;

        if (!(self->modeldata.run_config_flags & RUN_CONFIG_LAND))
        {
            self->running = RUN_STATE_NONE;
        }


        self->velocity.z = self->velocity.x = 0;

        // check if jumpland animation exists and not using landframe
        if(validanim(self, ANI_JUMPLAND) && self->animation->landframe.frame == FRAME_NONE)
        {
            self->takeaction = common_jumpland;
            ent_set_anim(self, ANI_JUMPLAND, 0);
            if(self->modeldata.dust.jump_land >= 0)
            {
                dust = spawn(self->position.x, self->position.z, self->position.y, self->direction, NULL, self->modeldata.dust.jump_land, NULL);
                if(dust)
                {
                    dust->spawntype = SPAWN_TYPE_DUST_LAND;
                    dust->base = self->position.y;
                    dust->autokill |= AUTOKILL_ANIMATION_COMPLETE;
                    execute_onspawn_script(dust);
                }
            }
        }
        else
        {
            if(self->modeldata.dust.jump_land >= 0 && self->animation->landframe.frame == FRAME_NONE)
            {
                dust = spawn(self->position.x, self->position.z, self->position.y, self->direction, NULL, self->modeldata.dust.jump_land, NULL);
                if(dust)
                {
                    dust->spawntype = SPAWN_TYPE_DUST_LAND;
                    dust->base = self->position.y;
                    dust->autokill |= AUTOKILL_ANIMATION_COMPLETE;
                    execute_onspawn_script(dust);
                }
            }
            if(self->animation->landframe.frame != FRAME_NONE && self->animating)
            {
                return;
            }

            self->takeaction = NULL; // back to A.I. root
            set_idle(self);
        }
    }
}

//A.I. characters spawn
void common_spawn()
{
    self->idling = IDLING_NONE;
    if(self->animating)
    {
        return;
    }
    self->takeaction = NULL; // come to life
    set_idle(self);
}

//A.I. characters drop from the sky
void common_drop()
{
    if(inair(self))
    {
        return;
    }
    self->idling = IDLING_PREPARED;
    self->takeaction = NULL;
    if(self->energy_state.health_current <= 0)
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_DROP_NO_HEALTH);
    }
}

//Similar as above, walk off a wall/cliff
void common_walkoff()
{
    if(inair(self) || self->animating)
    {
        return;
    }
    self->takeaction = NULL;
    set_idle(self);
}

// play turn animation and then flip
void common_turn()
{
    if(!self->animating)
    {
        self->takeaction = NULL;
        self->velocity.x = self->velocity.z = 0;
        self->direction = !self->direction;
        set_idle(self);
    }
}

// switch to land animation, land safely
void doland()
{
    self->velocity.x = self->velocity.z = 0;
    self->drop = 0;
    self->projectile = BLAST_NONE;
    self->damage_on_landing.attack_force = 0;
    self->damage_on_landing.attack_type = ATK_NONE;
    if(validanim(self, ANI_LAND))
    {
        self->takeaction = common_land;
        self->direction = !self->direction;
        ent_set_anim(self, ANI_LAND, 0);
    }
    else
    {
        self->takeaction = NULL;
        set_idle(self);
    }
}

void common_fall()
{
    // Still falling?
    if(self->falling || inair(self) || self->velocity.y)
    {
        return;
    }
   
    // Landed. Let's see if we could land
	// safely.
    if(self->projectile != BLAST_NONE)
	{ 
		if (self->projectile & BLAST_TOSS)
		{
			// damage_on_landing.attack_force==-2 means a player has pressed up+jump and has a land animation
			if ((autoland == 1 && self->damage_on_landing.attack_force == ATTACK_FORCE_LAND_AUTO) || self->damage_on_landing.attack_force == ATTACK_FORCE_LAND_COMMAND)
			{
				// Added autoland option for landing
				doland();
				return;
			}

			self->falling = 0;
		}
    }

    // Drop Weapon due to Enemy Falling.
    //if(self->modeldata.weapon_properties.loss_condition & WEAPON_LOSS_CONDITION_FALL) dropweapon(1);

    if(self->boss && level_completed)
    {
        tospeedup = 1;
    }

    // Pause a bit...
    self->takeaction	= common_lie;
    self->stalltime		= _time + MAX(0, (int)(self->staydown.rise + GAME_SPEED - self->modeldata.risetime.rise));	//Set rise delay.
    self->staydown.riseattack_stall	= _time + MAX(0, (int)(self->staydown.riseattack - self->modeldata.risetime.riseattack));					//Set rise attack delay.
    self->staydown.rise = 0; //Reset staydown.
    self->staydown.riseattack = 0; //Reset staydown atk.
}

void common_try_riseattack()
{
    entity *target;
    if(!validanim(self, ANI_RISEATTACK))
    {
        return;
    }

    target = normal_find_target(ANI_RISEATTACK, 0);
    if(!target)
    {
        self->direction = !self->direction;
        target = normal_find_target(ANI_RISEATTACK, 0);
        self->direction = !self->direction;
    }

    if(target)
    {
        self->direction = (target->position.x > self->position.x);    // Stands up and swings in the right direction depending on chosen target
        set_riseattack(self, self->last_damage_type, 0);
    }
}

/*
* Caskey, Damon V.
* 2023-03-28
* 
* Run death sequece (if any) for entity.
*/
int death_try_sequence_damage(entity* acting_entity, e_death_config_flags death_sequence, e_death_sequence_acting_event acting_event)
{
    int result = 0;
    e_attack_types attack_type = acting_entity->last_damage_type;
    e_death_state death_state = acting_entity->death_state;
    
    if (death_state & DEATH_STATE_AIR)
    {
        /* 
        * Fall first? This works by just turning
        * control back over to the damage function 
        * and letting it handle fall routines.
        */
        if ((death_sequence & DEATH_CONFIG_FALL_LAND_AIR && acting_event != DEATH_TRY_SEQUENCE_ACTING_EVENT_LIE) || (death_sequence & DEATH_CONFIG_FALL_LIE_AIR && acting_entity->animating))
        {
            /* Turn on blinking? */
            if (death_sequence & DEATH_CONFIG_BLINK_FALL_AIR)
            {
                acting_entity->blink = 1;
            }

            result = 0;
            return result;
        }

        /* Play death animation? */
        if (death_sequence & DEATH_CONFIG_DEATH_AIR)
        {
            /* Turn on blinking? */
            if (death_sequence & DEATH_CONFIG_BLINK_DEATH_AIR)
            {
                acting_entity->blink = 1;
            }

            acting_entity->velocity.x = 0;
            acting_entity->velocity.y = 0;
            acting_entity->velocity.z = 0;

            set_death(acting_entity, attack_type, 0);

            result = 1;

            /* Allow death animation to finish. */
            if (acting_entity->animating)
            {
                return result;
            }
        } 

        /*
        * Remove entity from the screen?
        */
        if (death_sequence & DEATH_CONFIG_REMOVE_VANISH_AIR)
        {
            acting_entity->takeaction = (acting_entity->modeldata.type & TYPE_PLAYER) ? player_blink : suicide;

            /* 
            * If blink requested, we turn on blink
            * effect and set a delay before killing
            * self.
            */
            if (death_sequence & DEATH_CONFIG_BLINK_REMOVE_AIR)
            {                
                acting_entity->blink = 1;
                acting_entity->stalltime = _time + GAME_SPEED * 2;
            }
        }
        else if (death_sequence & DEATH_CONFIG_REMOVE_CORPSE_AIR)
        {
            /* Turn on blinking? */
            if (death_sequence & DEATH_CONFIG_BLINK_REMOVE_AIR)
            {
                acting_entity->blink = 1;
            }

            /* Set corpse flag and disable AI control. */
            if (acting_entity->modeldata.type & TYPE_PLAYER)
            {
                acting_entity->takeaction = player_die;
            }
            else
            {
                acting_entity->death_state |= DEATH_STATE_CORPSE;
                acting_entity->noaicontrol = 1;
            }
        }
    }
    else
    {
        if ((death_sequence & DEATH_CONFIG_FALL_LAND_GROUND && acting_event != DEATH_TRY_SEQUENCE_ACTING_EVENT_LIE) || (death_sequence & DEATH_CONFIG_FALL_LIE_GROUND && acting_entity->animating))
        {
            result = 0;
            return result;
        }

        /* Play death animation? */
        if (death_sequence & DEATH_CONFIG_DEATH_GROUND)
        {
            /* Turn on blinking? */
            if (death_sequence & DEATH_CONFIG_BLINK_DEATH_GROUND)
            {
                acting_entity->blink = 1;
            }

            acting_entity->velocity.x = 0;
            acting_entity->velocity.y = 0;
            acting_entity->velocity.z = 0;

            set_death(acting_entity, attack_type, 0);

            result = 1;

            /* Allow death animation to finish. */
            if (acting_entity->animating)
            {
                return result;
            }
        }

        /*
        * Remove entity from the screen?
        */
        if (death_sequence & DEATH_CONFIG_REMOVE_VANISH_GROUND)
        {
            acting_entity->takeaction = (acting_entity->modeldata.type & TYPE_PLAYER) ? player_blink : suicide;

            /*
            * If blink requested, we turn on blink
            * effect and set a delay before killing
            * self.
            */
            if (death_sequence & DEATH_CONFIG_BLINK_REMOVE_GROUND)
            {
                acting_entity->blink = 1;
                acting_entity->stalltime = _time + GAME_SPEED * 2;
            }
        }
        else if (death_sequence & DEATH_CONFIG_REMOVE_CORPSE_GROUND)
        {
            /* Turn on blinking? */
            if (death_sequence & DEATH_CONFIG_BLINK_REMOVE_GROUND)
            {
                acting_entity->blink = 1;
            }

            /* Set corpse flag and disable AI control. */
            if (acting_entity->modeldata.type & TYPE_PLAYER)
            {
                acting_entity->takeaction = player_die;
            }
            else
            {
                acting_entity->death_state |= DEATH_STATE_CORPSE;
                acting_entity->noaicontrol = 1;
            }
        }
    }    

    return result;
}

void common_lie()
{
    entity* acting_entity = self;
    e_death_config_flags death_config;
    s_defense* defense_object;

    // Died?
    if(acting_entity->energy_state.health_current <= 0)
    {        
        defense_object = defense_find_current_object(acting_entity, NULL, acting_entity->last_damage_type);
        
        death_config = defense_object->death_config_flags;

        if (death_config & DEATH_CONFIG_SOURCE_MODEL)
        {
            death_config = acting_entity->modeldata.death_config_flags;
        }

        death_try_sequence_damage(acting_entity, death_config, DEATH_TRY_SEQUENCE_ACTING_EVENT_LIE);

        /*
        * Apply KO (death) map if we have one.
        */
        if (acting_entity->modeldata.colorsets.ko != COLORSET_INDEX_NONE)
        {   
            /* 
            * Wait for animation to finish unless type is set to
            * apply map immediately.
            */
            
            if (acting_entity->modeldata.colorsets.kotype == KO_COLORSET_CONFIG_INSTANT || !acting_entity->animating)
            {
                acting_entity->colourmap = model_get_colourmap(&(acting_entity->modeldata), acting_entity->modeldata.colorsets.ko);
            }
        }

        return;
    }

    if(_time < acting_entity->stalltime || acting_entity->position.y != acting_entity->base || acting_entity->velocity.y)
    {
        return;
    }

    set_rise(acting_entity, acting_entity->last_damage_type, 0);
}

// rise proc
void common_rise()
{
    if(self->animating)
    {
        return;
    }
    self->takeaction = NULL;
    self->staydown.riseattack_stall = 0;	//Reset riseattack delay.
    if(self->modeldata.riseinv)
    {
        self->blink = self->modeldata.riseinv > 0;
        self->invinctime = _time + ABS(self->modeldata.riseinv);
        self->invincible |= INVINCIBLE_INTANGIBLE;
    }
    set_idle(self);
}

// pain proc
void common_pain()
{
    //self->velocity.x = self->velocity.z = 0; // complained

    if(self->animating || inair(self))
    {
        return;
    }

    self->inpain = IN_PAIN_HIT;
    self->rising = RISING_NONE;
    self->ducking = DUCK_NONE;
    self->inbackpain = 0;
    if(self->link)
    {
//        set_pain(self, -1, 0);
        self->takeaction = common_grabbed;
    }
    else if(self->blocking)
    {
        self->inpain = IN_PAIN_BLOCK;
        self->takeaction = common_block;
        ent_set_anim(self, ANI_BLOCK, 1);
    }
    else
    {
        self->takeaction = NULL;
        set_idle(self);
    }
}

void doprethrow()
{
    entity *other = self->link;
    other->takeaction = common_prethrow;
    self->takeaction = common_throw_wait;
    self->velocity.x = self->velocity.z = self->velocity.y = other->velocity.x = other->velocity.z = other->velocity.y = 0;
    ent_set_anim(self, ANI_THROW, 0);
}

// 1 grabattack 2 grabforward 3 grabup 4 grabdown 5 grabbackward
// other means grab finisher at once

// Unknown author (utunnels?). 
//
// Retooled by Caskey, Damon V. to use named constants
// for selecting which grab attack.
// 2019-05-31
//
// Perform a grab attack action depending on request and
// current number already performed for of a given
// grab attack.
void dograbattack(int which)
{
    entity *other = self->link;
    self->takeaction = common_grabattack;
    self->attacking = ATTACKING_ACTIVE;
    other->velocity.x = other->velocity.z = self->velocity.x = self->velocity.z = 0;
    
	// If we requested finish attack, do it now. Otherwise
	// we'll look at current combostep for the selected grab
	// attack. If we're at the combo limit, then we finish.
	// If not, do the requested attack.
	if (which == GRAB_ACTION_SELECT_FINISH)
	{
		do_grab_attack_finish(self, 0);
	}
	else
	{
		++self->combostep[which];
		if (self->combostep[which] < 3 && validanim(self, grab_attacks[which][0]))
		{
			ent_set_anim(self, grab_attacks[which][0], 0);
		}
		else
		{
			do_grab_attack_finish(self, which);
		}
	}
}

// Caskey, Damon V.
// 2018-04-11
//
// Choose appropriate grab finish animation
// or do nothing if we can't find one. Returns
// selected animation.
e_animations do_grab_attack_finish(entity *ent, int which)
{
    e_animations animation;

    // Clear out the combostep array since this is
    // the finishing attack.
    memset(ent->combostep, 0, sizeof(*ent->combostep) * 5);

    // Get the finisher animation.
    animation = grab_attacks[which][1];

    // If selected attack animation exists then
    // that's what we use. Otherwise default to
    // attack3. If THAT fails, we don't do anything.
    // The target entity is already unlinked from
    // grab before this function was called, so in
    // game they are let go with no finishing attack.
    if(validanim(ent, animation))
    {
        ent_set_anim(ent, animation, 0);
        return animation;
    }
    else if(validanim(ent, ANI_ATTACK3))
    {
        // Get the finisher animation.
        ent_set_anim(ent, ANI_ATTACK3, 0);
        return ANI_ATTACK3;
    }

    // Could not find a valid finisher. Return none.
    return ATK_NONE;
}

void common_grab_check()
{
    int rnum, which;
    entity *other = self->link;

    if(other == NULL || (self->modeldata.grabfinish && self->animating && !self->grabwalking))
    {
        return;
    }

    if(self->base != other->base)
    {
        // Change this from ->position.y to ->base
        self->takeaction = NULL;
        ent_unlink(self);
        set_idle(self);
        return;
    }

    if(!nolost && self->modeldata.weapon_properties.loss_condition & WEAPON_LOSS_CONDITION_GRABBING)
    {
        dropweapon(1);
    }

    self->attacking = ATTACKING_NONE; //for checking

    rnum = rand32() & 31;

    if(_time > self->releasetime)
    {
        if(rnum < 12)
        {
            // Release
            self->takeaction = NULL;
            ent_unlink(self);
            set_idle(self);
            return;
        }
        else
        {
            self->releasetime = _time + (GAME_SPEED / 2);
        }
    }

    if(validanim(self, ANI_THROW) && rnum < 7)
    {
        if(self->modeldata.throwframewait >= 0)
        {
            doprethrow();
        }
        else
        {
            dothrow();
        }
        return;
    }

    // Kratus (04-2023) Implemented vault animation to all A.I. controlled characters
    if(validanim(self, ANI_VAULT) && rnum < 6)
    {
        self->attacking = ATTACKING_ACTIVE;
        self->takeaction = common_grabattack;
        memset(self->combostep, 0, sizeof(*self->combostep) * 5);
        ent_set_anim(self, ANI_VAULT, 0);
        return;
    }

    //grab finisher
    if(rnum < 4)
    {
        dograbattack(GRAB_ACTION_SELECT_FINISH);
        return;
    }
    which = rnum % GRAB_ACTION_SELECT_MAX;
    // grab attacks
    if(rnum > 12 && validanim(self, grab_attacks[which][0]))
    {
        dograbattack(which);
        return;
    }
}

//grabbing someone
void common_grab()
{
    // if(self->link) return;
    if(self->link || (self->modeldata.grabfinish && self->animating && !self->grabwalking))
    {
        return;
    }

    self->takeaction = NULL;
    self->attacking = ATTACKING_NONE;
    memset(self->combostep, 0, sizeof(*self->combostep) * 5);
    set_idle(self);
}

// being grabbed
void common_grabbed()
{
    // Just check if we're still grabbed...
    if(self->link)
    {
        return;
    }

    self->stalltime = 0;
    self->takeaction = NULL;
    set_idle(self);
}

// picking up something
void common_get()
{
    if(self->animating)
    {
        return;
    }

    self->getting = 0;
    self->takeaction = NULL;
    set_idle(self);
}

// Continue or release block.
void common_block()
{
	// Player type with holdblock, also not in pain 
	// or has post blockpain holdblock ability.
    int player_hold_block_eligible = self->modeldata.block_config_flags & (BLOCK_CONFIG_HOLD_IMPACT | BLOCK_CONFIG_HOLD_INFINITE)
		&& (self->modeldata.type & TYPE_PLAYER) 
		&& (self->inpain == IN_PAIN_NONE || (self->modeldata.block_config_flags & BLOCK_CONFIG_HOLD_INFINITE));
    
	// Controlling player is holding special key.
	int player_holding_special = ((player + self->playerindex)->keys & FLAG_SPECIAL);

	// If we are in a block transition, let's see if it is finished.
	// If it is, apply block animation.
	if (self->animnum == ANI_BLOCKSTART && !self->animating)
	{
		ent_set_anim(self, ANI_BLOCK, 0);
	}
	
    /*
	* In "Blockstun", at last frame of animation, and 
    * have holdblock after blockpain ability? Then we 
    * return to block.
	*
	* Otherwise, entity is a player with various other 
    * flags (see player_hold_block_eligible) but not 
    * holding special key, or the entity has finihsed 
    * animation and  doesn't match any of the player
    * holdblock criteria. It could be another entity 
    * type, doesn't have holdblock ability, or the 
    * controlling player isn't holding special key. 
    *
    * In any of those cases, we disable blocking flag 
    * and return to idle.
    */

    if(self->inpain & IN_PAIN_BLOCK
		&& (self->modeldata.block_config_flags & BLOCK_CONFIG_HOLD_INFINITE)
		&& !self->animating 
		&& validanim(self, ANI_BLOCK))
    {
		self->inpain = IN_PAIN_NONE;
		self->rising = RISING_NONE;
		self->inbackpain = 0;
		ent_set_anim(self, ANI_BLOCK, 0);
    }
    else if((player_hold_block_eligible && !player_holding_special)
		|| (!self->animating && (!player_hold_block_eligible || !player_holding_special)))
    {
		
        /*
        * Is blockstun complete (no blockpain and
        * animation finished)? If yes, play the block
        * release animation. If we don't have a block 
        * release or the block release is finished, 
        * return to idle.
        */ 

		if (self->inpain & ~IN_PAIN_BLOCK || !self->animating)
		{
			if (self->animnum == ANI_BLOCKRELEASE && !self->animating)
			{
				self->blocking = 0;
				self->takeaction = NULL;
				set_idle(self);
			}
			else
			{
				if (validanim(self, ANI_BLOCKRELEASE))
				{
					ent_set_anim(self, ANI_BLOCKRELEASE, 0);
				}
				else
				{
					self->blocking = 0;
					self->takeaction = NULL;
					set_idle(self);
				}				
			}			
		}
    }
}


void common_charge()
{
    if(self->animating)
    {
        return;
    }

    self->charging = 0;
    self->takeaction = NULL;
    set_idle(self);
}

// common code for entities hold an item
entity *drop_item(entity *e)
{
    s_spawn_entry p;
    entity *item;
    memset(&p, 0, sizeof(p));

    // Just to make sure there is an item so
    // we don't look for data on a NULL pointer.
    if(!e->item_properties)
    {
        return NULL;
    }

    p.index         = e->item_properties->index;
    p.item_properties.index = p.weaponindex = -1;
    strcpy(p.alias, e->item_properties->alias);
    p.position.y    = e->position.y + 0.01; // For check, or an enemy "item" will drop from the sky
    p.health[0]     = e->item_properties->health;
    p.alpha         = e->item_properties->alpha;
    p.colourmap     = e->item_properties->colorset;
    p.flip          = e->direction;

    item = smartspawn(&p);

    if(item)
    {
        item->spawntype = SPAWN_TYPE_ITEM;

        item->position.x = e->position.x;
        item->position.z = e->position.z;
        if(item->position.x < advancex)
        {
            item->position.x = advancex + 10;
        }
        else if(item->position.x > advancex + videomodes.hRes)
        {
            item->position.x = advancex + videomodes.hRes - 10;
        }
        if(!(level->scrolldir & (SCROLL_UP | SCROLL_DOWN)))
        {
            if(item->position.z - item->position.y < advancey)
            {
                item->position.z = advancey + 10;
            }
            else if(item->position.z - item->position.y > advancey + videomodes.vRes)
            {
                item->position.z = advancey + videomodes.vRes - 10;
            }
        }
        if(e->boss && (item->modeldata.type & TYPE_ENEMY))
        {
            item->boss = 1;
        }
    }
    return item;
}

//drop the driver, just spawn, dont takedamage
// damage will adjust by the biker
entity *drop_driver(entity *e)
{
    int i;
    s_spawn_entry p;
    entity *driver;
    memset(&p, 0, sizeof(p));

    if(e->modeldata.rider >= 0)
    {
        p.index = e->modeldata.rider;
    }
    else
    {
        return NULL;    // should not happen, just in case
    }
    p.position.y = e->position.y + 10;
    p.weaponindex = -1;
    strcpy(p.alias, e->name);

    // Carrying an item, so let's transfer that to spawn
    // entry for the driver.
    if(e->item_properties)
    {
        strcpy(p.item_properties.alias, e->item_properties->alias);

        p.item_properties.index         = e->item_properties->index;
        p.item_properties.colorset      = e->item_properties->colorset;
        p.item_properties.alpha         = e->item_properties->alpha;
        p.item_properties.health        = e->item_properties->health;
        p.item_properties.player_count  = e->item_properties->player_count;
    }

    /* Match driver color to entity color. */
    p.colourmap = e->map;

    for(i = 0; i < MAX_PLAYERS; i++)
    {
        p.health[i] = e->modeldata.health;
    }
    p.boss = e->boss;

    driver = smartspawn(&p);
    if(driver)
    {
        driver->spawntype = SPAWN_TYPE_BIKER;
        driver->position.x = e->position.x;
        driver->position.z = e->position.z;
    }
    return driver;
}


void checkdeath()
{
    if(self->energy_state.health_current > 0)
    {
        return;
    }
    self->death_state |= DEATH_STATE_DEAD;
    
    /* Killed in the air? */
    if (inair(self))
    {
        self->death_state |= DEATH_STATE_AIR;
    }

    /* In the back? D*** move banner! */
    if (self->inbackpain)
    {
        self->death_state |= DEATH_STATE_BACK;
    }
    
    //be careful, since the opponent can be other types
    if(self->opponent && (self->opponent->modeldata.type & TYPE_PLAYER))
    {
        addscore(self->opponent->playerindex, self->modeldata.score);    // Add score to the player
    }
    self->nograb = 1;
    self->idling = IDLING_NONE;
    self->ducking = DUCK_NONE;

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

    // Drop an item if we have one.
    if(self->item_properties)
    {
        if(count_ents(TYPE_PLAYER) > self->item_properties->player_count)
        {
            drop_item(self);
        }
    }


    if(self->boss)
    {
        self->boss = 0;
        --level->bossescount;
        if(level->bossescount <= 0 && (self->modeldata.type & TYPE_ENEMY))
        {
            kill_all_enemies();
            level_completed = 1;
            level_completed_defeating_boss |= 1;
        }
    }
}

void checkdamageflip(entity* target_entity, entity *other, s_attack *attack_object, s_defense* defense_object)
{
    /* Debuging info */
    //attack_dump_object(attack_object);

    target_entity->normaldamageflipdir = DIRECTION_RIGHT;
    
    int pain_check = 0;

    /*
    * Check all the following conditions before we
    * take further actions.
    */

    /*
    * No damage entity source, or we are the 
    * damage source. 
    */
    if (other == NULL || other == target_entity)
    {
        return;
    }

    /*
    * Attack did not knock down and
    * won't put us into a pain reaction.
    */
    if(!target_entity->drop)
    {
        if (attack_object->no_pain)
        {
            return;
        }

        if (target_entity->modeldata.pain_config_flags & PAIN_CONFIG_PAIN_DISABLE)
        {
            return;
        }

        pain_check = defense_result_pain(attack_object, defense_object);

        if (!pain_check)
        {
            return;
        }        
    }

    /* We're frozen. */
    if (target_entity->frozen)
    {
        return;
    }

    /* We can't turn. */
    if (target_entity->modeldata.move_config_flags & MOVE_CONFIG_NO_FLIP)
    {
        return;
    }


    /* Apply appropriate direction switch (if any). */

    //printf("\n\t attack_object->force_direction: %d", attack_object->force_direction);

    switch(attack_object->force_direction)
    {
        case DIRECTION_ADJUST_TOWARD:

            if (other->position.x < target_entity->position.x)
            {
                target_entity->direction = DIRECTION_RIGHT;
            }
            else if (other->position.x > target_entity->position.x)
            {
                target_entity->direction = DIRECTION_LEFT;
            }
            else
            {
                // target_entity->direction = target_entity->direction;
            }

            break;

        case DIRECTION_ADJUST_AWAY:

            if (other->position.x < target_entity->position.x)
            {
                target_entity->direction = DIRECTION_LEFT;
            }
            else if (other->position.x > target_entity->position.x)
            {
                target_entity->direction = DIRECTION_RIGHT;
            }
            else
            {
                //target_entity->direction = target_entity->direction;
            }

            break;

        case DIRECTION_ADJUST_NONE:

            //printf("\n\t DIRECTION_ADJUST_NONE");

            if(!target_entity->inbackpain )
            {
                if(target_entity->position.x < other->position.x)
                {
                    target_entity->direction = DIRECTION_RIGHT;
                }
                else if(target_entity->position.x > other->position.x)
                {
                    target_entity->direction = DIRECTION_LEFT;
                }
            }
            else
            {
                if(target_entity->position.x < other->position.x)
                {
                    target_entity->normaldamageflipdir = DIRECTION_RIGHT;
                }
                else if(target_entity->position.x > other->position.x)
                {
                    target_entity->normaldamageflipdir = DIRECTION_LEFT;
                }
            }
            break;

        case DIRECTION_ADJUST_SAME:

            //printf("\n\t DIRECTION_ADJUST_SAME");

            target_entity->direction = other->direction;
            break;

        case DIRECTION_ADJUST_OPPOSITE:

            //printf("\n\t DIRECTION_ADJUST_OPPOSITE");

            target_entity->direction = !other->direction;
            break;

        case DIRECTION_ADJUST_RIGHT:

            //printf("\n\t DIRECTION_ADJUST_RIGHT");

            target_entity->direction = DIRECTION_RIGHT;
            break;

        case DIRECTION_ADJUST_LEFT:

            //printf("\n\t DIRECTION_ADJUST_LEFT");

            target_entity->direction = DIRECTION_LEFT;
            break;
    }
    
}

void checkdamageeffects(s_attack *attack)
{
#define _freeze         attack->freeze
#define _maptime        attack->maptime
#define _freezetime     attack->freezetime
#define _remap          attack->forcemap
#define _blast          attack->blast
#define _steal          attack->steal
#define _seal           attack->seal
#define _sealtime       attack->sealtime
#define _staydown_rise			attack->staydown.rise
#define _staydown_rise_attack	attack->staydown.riseattack

    entity *opp = self->opponent;

	// Steal. Take HP from the entity and add it to attacker.
    if(_steal && opp && opp != self)
    {
		// If we have enough HP to withstand the attack, give attacker
		// the same amount as attack force. Otherwise just give them 
		// whatever HP we have left.
		if(self->energy_state.health_current >= attack->attack_force)
        {
            opp->energy_state.health_current += attack->attack_force;
        }
        else
        {
            opp->energy_state.health_current += self->energy_state.health_current;
        }

		// Cap the effect so attacker doesn't go over their maximum HP.
        if(opp->energy_state.health_current > opp->modeldata.health)
        {
            opp->energy_state.health_current = opp->modeldata.health;
        }
    }

	// Freeze effect. If this is a freeze attack and we're
	// not already frozen, apply a freeze effect and possibly 
	// remap to freeze palette. If we ARE frozen, then
	// unfreeze and knock down instead.
    if(_freeze && !self->frozen)
    {
        
		// Set freeze status and expire time.
        self->frozen = 1;
        if(self->freezetime == 0)
        {
            self->freezetime = _time + _freezetime;
        }

		// 2007-12-14 
		// Caskey, Damon V.
		//
		// If opponents frozen map = -1 or only stun, then don't change the color map.

        if(_remap == -1 && self->modeldata.colorsets.frozen != -1)
        {
            self->colourmap = model_get_colourmap(&(self->modeldata), self->modeldata.colorsets.frozen);
        }

        self->drop = 0;
    }
    else if(self->frozen)
    {
        unfrozen(self);
        self->drop = 1;
    }

	// If we want to apply a remap without freezing (forcemap attack command) then
	// set the map and expire time here.
    if(_remap != 0 && !_freeze)
    {
        /*
        * Caskey, Damon V.
        * 2020-10-20
        * 
        * Apply named map if available 
        * or fall back to direct index. 
        *
        * TODO: Code is pretty messy and
        * repetitive. Needs some refinement 
        * and offloading to a function.
        */
        switch (_remap)
        {
            case MAP_TYPE_BURN:
                if (self->modeldata.colorsets.burn != COLORSET_INDEX_NONE)
                {
                    self->colourmap = model_get_colourmap(&(self->modeldata), self->modeldata.colorsets.burn);
                }
                break;
            case MAP_TYPE_FREEZE:
                if (self->modeldata.colorsets.frozen != COLORSET_INDEX_NONE)
                {
                    self->colourmap = model_get_colourmap(&(self->modeldata), self->modeldata.colorsets.frozen);
                }
                break;
            case MAP_TYPE_KO:
                if (self->modeldata.colorsets.ko != COLORSET_INDEX_NONE)
                {
                    self->colourmap = model_get_colourmap(&(self->modeldata), self->modeldata.colorsets.ko);
                }
                break;
            case MAP_TYPE_SHOCK:
                if (self->modeldata.colorsets.shock != COLORSET_INDEX_NONE)
                {
                    self->colourmap = model_get_colourmap(&(self->modeldata), self->modeldata.colorsets.shock);
                }
                break;
            default:
                self->colourmap = model_get_colourmap(&(self->modeldata), _remap);
                break;
        }

        self->maptime = _time + _maptime;        
    }

	// Disable specials. Apply seal (Any animation with 
	// energy_cost > seal) is disabled and time to expire.
    if(_seal)                                                                       
    {
        self->sealtime  = _time + _sealtime;
        self->seal      = _seal;
    }

	// Apply any recursive (damage over time) effects.
	recursive_damage_check_apply(self, opp, attack);

	// Static enemies/nodrop enemies cannot be knocked down
    if(self->modeldata.pain_config_flags & PAIN_CONFIG_FALL_DISABLE)
    {
        self->drop = 0;    
    }

	// Always knock airborne entities down unless we're freezeing them
	// or they are specfically immune to in air knockdowns.
    if(inair(self) && !self->frozen && !(self->modeldata.pain_config_flags & PAIN_CONFIG_FALL_DISABLE_AIR))
    {
        self->drop = 1;
    }

	// Immune to hit stun? No knockdown either.
    if(attack->no_pain)
    {
        self->drop = 0;
    }

	// If entity will be knocked down, let's apply knockdown specific effects here.
    if(self->drop)
    {
		self->projectile = _blast;

        self->staydown.rise	= _staydown_rise;                                            //Staydown: Add to risetime until next rise.
        self->staydown.riseattack   = _staydown_rise_attack;
    }

#undef _freeze
#undef _maptime
#undef _freezetime
#undef _remap
#undef _blast
#undef _steal
#undef _seal
#undef _sealtime
#undef _staydown_rise
#undef _staydown_rise_attack
}

/*
* Orginal author and date unknown.
* 
* Determines fall status after taking damage.
* 
* Caskey, Damon V.
* 2021-09-08
* 
* Slightly reworked to accept the target entity
* as parameter rather than referencing the
* global "self" variable.
*
* Accepts defense object to pass on into
* total damage calculation functions.
*/ 
void checkdamagedrop(entity* target_entity, s_attack* attack_object, s_defense* defense_object)
{
    int attack_drop = attack_object->attack_drop;
    float defense_knockdown = defense_object->knockdown;

    /* 
    * If already falling or attack is a "no reflect", then
    * do nothing and exit.
    */
    
    if (target_entity->drop || attack_object->no_pain)
    {
        return;
    }

    /*
    * Legacy behavior. Always fall if in "animal" mode.
    */
    
    if(target_entity->modeldata.weapon_properties.weapon_state & WEAPON_STATE_ANIMAL)
    {
        target_entity->drop = 1;
    }
    
    /* Make sure guard break doesn't knock target down. */
    
    if(target_entity->modeldata.guardpoints.max > 0 && target_entity->modeldata.guardpoints.current <= 0)
    {
        attack_drop = 0;
    } 
    
    /*
    * Reset knockdown count if the knockdowntime
    * is expired.
    */

    if(target_entity->knockdowntime && target_entity->knockdowntime < _time)
    {
        target_entity->knockdowncount = target_entity->modeldata.knockdowncount;
    }

    /* 
    * Defense knockdown works just like damage. It
    * is a mutiplier we apply to the attack's drop
    * power. We then subjtract the result from target's 
    * current knockdowncount.
    * 
    * We also update the expire time for resetting
    * target's current knockdown.
    * 
    * If target's knockdown count reaches 0, we
    * set target's drop status true, and target
    * falls in game.
    */

    target_entity->knockdowncount -= (attack_drop * defense_knockdown);    
    target_entity->knockdowntime = _time + GAME_SPEED;

    if (target_entity->knockdowncount < 0)
    {
        target_entity->drop = 1;
    }
}

void checkmpadd()
{
    entity *other = self->opponent;
    if(other == NULL || other == self)
    {
        return;
    }

    if(magic_type == 1 )
    {
        other->energy_state.mp_current += other->modeldata.mprate;

        if(other->energy_state.mp_current > other->modeldata.mp)
        {
            other->energy_state.mp_current = other->modeldata.mp;
        }
        else if(other->energy_state.mp_current < 0)
        {
            other->energy_state.mp_current = 0;
        }
    }
}

void checkhitscore(entity *other, s_attack *attack)
{
    entity *opp = self->opponent;
    if(!opp)
    {
        return;
    }
    if(opp && opp != self && (opp->modeldata.type & TYPE_PLAYER))
    {
        // Added obstacle so explosions can hurt enemies
        addscore(opp->playerindex, attack->attack_force * self->modeldata.multiple);  // New multiple variable
        if (savedata.joyrumble[opp->playerindex]) control_rumble(opp->playerindex, 1, attack->attack_force * 2);
    }
    // Don't animate or fall if hurt by self, since
    // it means self fell to the ground already. :)
    // Add throw score to the player
    else if(other == self && self->damage_on_landing.attack_force > 0)
    {
        addscore(opp->playerindex, attack->attack_force);
    }
}

/*
* Caskey, Damon V.
* 2023-04-03
*
* Accept string input and return
* matching constant.
*/
e_death_config_flags death_get_config_flag_from_string(const char* value)
{    
    static const struct 
    {
        const char* text_name;
        e_death_config_flags flag;
    } flag_lookup_table[] = {
        {"none", DEATH_CONFIG_NONE},
        {"default", DEATH_CONFIG_MACRO_DEFAULT},
        {"blink_death_air", DEATH_CONFIG_BLINK_DEATH_AIR},
        {"blink_death_ground", DEATH_CONFIG_BLINK_DEATH_GROUND},
        {"blink_fall_air", DEATH_CONFIG_BLINK_FALL_AIR},
        {"blink_fall_ground", DEATH_CONFIG_BLINK_FALL_GROUND},
        {"blink_remove_air", DEATH_CONFIG_BLINK_REMOVE_AIR},
        {"blink_remove_ground", DEATH_CONFIG_BLINK_REMOVE_GROUND},
        {"death_air", DEATH_CONFIG_DEATH_AIR},
        {"death_ground", DEATH_CONFIG_DEATH_GROUND},
        {"fall_land_air", DEATH_CONFIG_FALL_LAND_AIR},
        {"fall_land_ground", DEATH_CONFIG_FALL_LAND_GROUND},
        {"fall_lie_air", DEATH_CONFIG_FALL_LIE_AIR},
        {"fall_lie_ground", DEATH_CONFIG_FALL_LIE_GROUND},
        {"remove_corpse_air", DEATH_CONFIG_REMOVE_CORPSE_AIR},
        {"remove_corpse_ground", DEATH_CONFIG_REMOVE_CORPSE_GROUND},
        {"remove_vanish_air", DEATH_CONFIG_REMOVE_VANISH_AIR},
        {"remove_vanish_ground", DEATH_CONFIG_REMOVE_VANISH_GROUND},
        {"source_model", DEATH_CONFIG_SOURCE_MODEL},
    };

    const size_t list_count = sizeof(flag_lookup_table) / sizeof(*flag_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, flag_lookup_table[i].text_name) == 0)
        {
            return flag_lookup_table[i].flag;
        }
    }
       
    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * none flag.
    */

    printf("\n\n Unknown death config option (%s). \n", value);
    return DEATH_CONFIG_NONE;
}

/*
* Caskey, Damon V.
* 2023-03-20
*
* Get arguments to output final
* bitmask.
*/
e_death_config_flags death_get_config_flags_from_arguments(const ArgList* arglist, int start_position)
{
    int i = 0;
    char* value = "";

    e_death_config_flags result = DEATH_CONFIG_NONE;

    for (i = start_position; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= death_get_config_flag_from_string(value);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2023-03-20
* 
* Accept current value and a legacy falldie.
* Returns appropriate config flags to match
* expected legacy behavior.
*/
e_death_config_flags death_config_get_value_from_falldie(e_death_config_flags current_value, e_falldie_config acting_value)
{
    //printf("\n\n death_config_get_value_from_falldie(%d, %d)", current_value, acting_value);

    e_death_config_flags result = current_value & ~DEATH_CONFIG_MACRO_DEATH_FALL_ALL;

    switch (acting_value)
    {

    case FALLDIE_CONFIG_NONE:
        result |= DEATH_CONFIG_MACRO_DEFAULT;
        break;

    case FALLDIE_CONFIG_DEATH_INSTANT:
        result |= DEATH_CONFIG_MACRO_DEATH | DEATH_CONFIG_SOURCE_MODEL;
        break;

    case FALLDIE_CONFIG_DEATH_FALL:
        result |= DEATH_CONFIG_MACRO_DEATH_FALL_LAND | DEATH_CONFIG_SOURCE_MODEL;
        break;
    }

    //printf("\n\t result: %d", result);

    return result;
}

/*
* Caskey, Damon V.
* 2023-03-20
*
* Accept current value. Returns a legacy
* value based on active flags.
*/
e_falldie_config death_config_get_falldie_from_value(e_death_config_flags acting_value)
{
    //printf("\n\n death_config_get_falldie_from_value(%d)", acting_value);
    e_falldie_config result = FALLDIE_CONFIG_NONE;     

    if ((acting_value & DEATH_CONFIG_MACRO_DEATH_FALL_LAND) == DEATH_CONFIG_MACRO_DEATH_FALL_LAND)
    {
        result = FALLDIE_CONFIG_DEATH_FALL;
    }
    else if ((acting_value & DEATH_CONFIG_MACRO_DEATH) == DEATH_CONFIG_MACRO_DEATH)
    {
        result = FALLDIE_CONFIG_DEATH_INSTANT;
    }
    else if ((acting_value & (DEATH_CONFIG_FALL_LAND_AIR | DEATH_CONFIG_FALL_LAND_GROUND)) == (DEATH_CONFIG_FALL_LAND_AIR | DEATH_CONFIG_FALL_LAND_GROUND))
    {
        result = FALLDIE_CONFIG_NONE;
    }

    //printf("\n\t result: %d", result);
    
    return result;
}

/*
* Caskey, Damon V.
* 2023-03-20
*
* Accept current value and a legacy falldie.
* Returns appropriate config flags to match
* expected legacy behavior.
*/
e_death_config_flags death_config_get_value_from_nodieblink(e_death_config_flags current_value, e_nodieblink_config acting_value)
{
    //printf("\n\n death_config_get_value_from_nodieblink(%d, %d)", current_value, acting_value);

    e_death_config_flags result = (current_value &= ~(DEATH_CONFIG_MACRO_BLINK | DEATH_CONFIG_MACRO_FALL | DEATH_CONFIG_MACRO_REMOVE));

    switch (acting_value)
    {

    case NODIEBLINK_CONFIG_NONE:
        result |= DEATH_CONFIG_BLINK_REMOVE_AIR | DEATH_CONFIG_BLINK_REMOVE_GROUND | DEATH_CONFIG_FALL_LAND_AIR | DEATH_CONFIG_FALL_LAND_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND;
        break;

    case NODIEBLINK_CONFIG_FALL_LIE_BLINK:
        result |= DEATH_CONFIG_BLINK_REMOVE_AIR | DEATH_CONFIG_BLINK_REMOVE_GROUND | DEATH_CONFIG_FALL_LIE_AIR | DEATH_CONFIG_FALL_LIE_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND;
        break;

    case NODIEBLINK_CONFIG_FALL_LIE_CORPSE:
        result |= DEATH_CONFIG_FALL_LIE_AIR | DEATH_CONFIG_FALL_LIE_GROUND | DEATH_CONFIG_REMOVE_CORPSE_AIR | DEATH_CONFIG_REMOVE_CORPSE_GROUND;
        break;

    case NODIEBLINK_CONFIG_FALL_LIE_VANISH:
        result |= DEATH_CONFIG_FALL_LIE_AIR | DEATH_CONFIG_FALL_LIE_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND;
        break;
    }

    //printf("\n\t result: %d", result);

    return result;
}

/*
* Caskey, Damon V.
* 2023-03-20
*
* Accept current value. Returns a legacy
* value based on active flags.
*/
e_nodieblink_config death_config_get_nodieblink_from_value(e_death_config_flags acting_value)
{
    //printf("\n\n death_config_get_falldie_from_value(%d)", acting_value);
    e_nodieblink_config result = NODIEBLINK_CONFIG_NONE;

    if ((acting_value & (DEATH_CONFIG_FALL_LIE_AIR | DEATH_CONFIG_FALL_LIE_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND)) == (DEATH_CONFIG_FALL_LIE_AIR | DEATH_CONFIG_FALL_LIE_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND))
    {
        result = NODIEBLINK_CONFIG_FALL_LIE_VANISH;
    }
    else if ((acting_value & (DEATH_CONFIG_FALL_LIE_AIR | DEATH_CONFIG_FALL_LIE_GROUND | DEATH_CONFIG_REMOVE_CORPSE_AIR | DEATH_CONFIG_REMOVE_CORPSE_GROUND)) == (DEATH_CONFIG_FALL_LIE_AIR | DEATH_CONFIG_FALL_LIE_GROUND | DEATH_CONFIG_REMOVE_CORPSE_AIR | DEATH_CONFIG_REMOVE_CORPSE_GROUND))
    {
        result = NODIEBLINK_CONFIG_FALL_LIE_CORPSE;
    }
    else if ((acting_value & (DEATH_CONFIG_BLINK_REMOVE_AIR | DEATH_CONFIG_BLINK_REMOVE_GROUND | DEATH_CONFIG_FALL_LIE_AIR | DEATH_CONFIG_FALL_LIE_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND)) == (DEATH_CONFIG_BLINK_REMOVE_AIR | DEATH_CONFIG_BLINK_REMOVE_GROUND | DEATH_CONFIG_FALL_LIE_AIR | DEATH_CONFIG_FALL_LIE_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND))
    {
        result = NODIEBLINK_CONFIG_FALL_LIE_BLINK;
    }
    else if ((acting_value & (DEATH_CONFIG_BLINK_REMOVE_AIR | DEATH_CONFIG_BLINK_REMOVE_GROUND | DEATH_CONFIG_FALL_LAND_AIR | DEATH_CONFIG_FALL_LAND_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND)) == (DEATH_CONFIG_BLINK_REMOVE_AIR | DEATH_CONFIG_BLINK_REMOVE_GROUND | DEATH_CONFIG_FALL_LAND_AIR | DEATH_CONFIG_FALL_LAND_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND))
    {
        result = NODIEBLINK_CONFIG_NONE;
    }

    //printf("\n\t result: %d", result);

    return result;
}

/*
* Caskey, Damon V.
* 2023-04-03
*
* Accept string input and return
* matching constant.
*/
e_run_config_flags run_get_config_flag_from_string(const char* value)
{
    static const struct
    {
        const char* text_name;
        e_death_config_flags flag;
    } flag_lookup_table[] = {
        {"none", RUN_CONFIG_NONE},
        {"land", RUN_CONFIG_LAND},
        {"x_left_dash_command", RUN_CONFIG_X_LEFT_DASH_COMMAND},
        {"x_left_dash_fixed", RUN_CONFIG_X_LEFT_DASH_FIXED},
        {"x_left_enabled", RUN_CONFIG_X_LEFT_ENABLED},
        {"x_left_initial", RUN_CONFIG_X_LEFT_INITIAL},
        {"x_right_dash_command", RUN_CONFIG_X_RIGHT_DASH_COMMAND},
        {"x_right_dash_fixed", RUN_CONFIG_X_RIGHT_DASH_FIXED},
        {"x_right_enabled", RUN_CONFIG_X_RIGHT_ENABLED},
        {"x_right_initial", RUN_CONFIG_X_RIGHT_INITIAL},
        {"z_down_dash_command", RUN_CONFIG_Z_DOWN_DASH_COMMAND},
        {"z_down_dash_fixed", RUN_CONFIG_Z_DOWN_DASH_FIXED},
        {"z_down_enabled", RUN_CONFIG_Z_DOWN_ENABLED},
        {"z_down_initial", RUN_CONFIG_Z_DOWN_INITIAL},
        {"z_up_dash_command", RUN_CONFIG_Z_UP_DASH_COMMAND},
        {"z_up_dash_fixed", RUN_CONFIG_Z_UP_DASH_FIXED},
        {"z_up_enabled", RUN_CONFIG_Z_UP_ENABLED},
        {"z_up_initial", RUN_CONFIG_Z_UP_INITIAL},
    };

    const size_t list_count = sizeof(flag_lookup_table) / sizeof(*flag_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, flag_lookup_table[i].text_name) == 0)
        {
            return flag_lookup_table[i].flag;
        }
    }

    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * none flag.
    */

    printf("\n\n Unknown run config option (%s). \n", value);
    return RUN_CONFIG_NONE;
}

/*
* Caskey, Damon V.
* 2023-04-26
*
* Get arguments to output final
* bitmask.
*/
e_run_config_flags run_get_config_flags_from_arguments(const ArgList* arglist, const unsigned int start_position)
{
    int i = 0;
    char* value = "";

    e_run_config_flags result = RUN_CONFIG_NONE;

    for (i = start_position; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= run_get_config_flag_from_string(value);
    }

    return result;
}


/*
* Caskey, Damon V.
* 2023-03-20
*
* Accept string input and return
* matching constant.
*/
e_shadow_config_flags shadow_get_config_flag_from_string(const char* value)
{
    static const struct
    {
        const char* text_name;
        e_shadow_config_flags flag;
    } flag_lookup_table[] = {
        {"none", SHADOW_CONFIG_NONE},
        {"default", SHADOW_CONFIG_DEFAULT},
        {"base_platform", SHADOW_CONFIG_BASE_PLATFORM},
        {"base_static", SHADOW_CONFIG_BASE_STATIC},
        {"disabled", SHADOW_CONFIG_DISABLED},
        {"replica_air", SHADOW_CONFIG_GRAPHIC_REPLICA_AIR},
        {"replica_ground", SHADOW_CONFIG_GRAPHIC_REPLICA_GROUND},
        {"static_air", SHADOW_CONFIG_GRAPHIC_STATIC_AIR},
        {"static_ground", SHADOW_CONFIG_GRAPHIC_STATIC_GROUND}
    };

    const size_t list_count = sizeof(flag_lookup_table) / sizeof(*flag_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, flag_lookup_table[i].text_name) == 0)
        {
            return flag_lookup_table[i].flag;
        }
    }

    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * none flag.
    */

    printf("\n\n Unknown death config option (%s). \n", value);
    return SHADOW_CONFIG_NONE;
}

/*
* Caskey, Damon V.
* 2023-03-20
*
* Get arguments foroutput final
* bitmask.
*/
e_shadow_config_flags shadow_get_config_flags_from_arguments(const ArgList* arglist)
{
    int i = 0;
    char* value = "";

    e_shadow_config_flags result = SHADOW_CONFIG_NONE;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= shadow_get_config_flag_from_string(value);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2023-03-19
* 
* Accept current shadow config
* state and a legacy air only
* property. Returns appropriate
* shadow config value.
*/
e_shadow_config_flags shadow_get_config_from_legacy_aironly(e_shadow_config_flags shadow_config_flags, int legacy_value)
{
    e_shadow_config_flags result = shadow_config_flags;    

    if (!legacy_value)
    {
        /* 
        * Don't do anything. Text "aironly 0" doesn't
        * mean no air shadows, and fortunatly there
        * is no legacy aironly script access, so
        * we don't need to worry about that.
        */

        return result;
    }

    /*
    * If either type of ground shadow is turned on,
    * then disable it here.
    */

    if (shadow_config_flags & SHADOW_CONFIG_GRAPHIC_REPLICA_GROUND)
    {
        result &= ~SHADOW_CONFIG_GRAPHIC_REPLICA_GROUND;
        result |= SHADOW_CONFIG_GRAPHIC_REPLICA_AIR;
    }

    if (shadow_config_flags & SHADOW_CONFIG_GRAPHIC_STATIC_GROUND)
    {
        result &= ~SHADOW_CONFIG_GRAPHIC_STATIC_GROUND;
        result |= SHADOW_CONFIG_GRAPHIC_STATIC_AIR;
    }

    return result;
}

/*
* Caskey, Damon V.
* 2023-03-19
*
* Accept current shadow config
* state and a legacy gfxshadow
* property. Returns appropriate
* shadow config value.
*/
e_shadow_config_flags shadow_get_config_from_legacy_gfxshadow(e_shadow_config_flags shadow_config_flags, int legacy_value)
{
    e_shadow_config_flags result = shadow_config_flags;

    /*
    * Creator wants to turn off "gfx", so swap any
    * existing replica flags for static.
    */

    if (!legacy_value)
    {  
        if (shadow_config_flags & SHADOW_CONFIG_GRAPHIC_REPLICA_AIR)
        {
            result |= SHADOW_CONFIG_GRAPHIC_STATIC_AIR;
            result &= ~SHADOW_CONFIG_GRAPHIC_REPLICA_AIR;
        }

        if (shadow_config_flags & SHADOW_CONFIG_GRAPHIC_REPLICA_GROUND)
        {
            result |= SHADOW_CONFIG_GRAPHIC_STATIC_GROUND;
            result &= ~SHADOW_CONFIG_GRAPHIC_REPLICA_GROUND;
        }       

        /*
        * If no shadow at all set, creator expects
        * gfxshadow 0 to set up static shadow. Let's
        * initialize the flags here.
        */

        if (!(shadow_config_flags & SHADOW_CONFIG_GRAPHIC_ALL))
        {
            result |= (SHADOW_CONFIG_GRAPHIC_STATIC_GROUND | SHADOW_CONFIG_GRAPHIC_STATIC_AIR);
        }

        return result;
    }
        

    /*
    * Creator wants to turn on "gfx", so swap any
    * existing static flags for replica.
    */

    if (shadow_config_flags & SHADOW_CONFIG_GRAPHIC_STATIC_GROUND)
    {
        result &= ~SHADOW_CONFIG_GRAPHIC_STATIC_GROUND;
        result |= SHADOW_CONFIG_GRAPHIC_REPLICA_GROUND;
    }

    if (shadow_config_flags & SHADOW_CONFIG_GRAPHIC_STATIC_AIR)
    {
        result &= ~SHADOW_CONFIG_GRAPHIC_STATIC_AIR;
        result |= SHADOW_CONFIG_GRAPHIC_REPLICA_AIR;
    }

    /*
    * If no shadow at all set, creator expects
    * gfxshadow to set up replica shadow. Let's
    * initialize the flags here.
    */
    if (!(shadow_config_flags & SHADOW_CONFIG_GRAPHIC_ALL))
    {
        result |= (SHADOW_CONFIG_GRAPHIC_REPLICA_GROUND | SHADOW_CONFIG_GRAPHIC_REPLICA_AIR);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2023-03-19
*
* Accept current shadow config
* state and a legacy property. 
* Returns appropriate shadow 
* config value.
*/
e_shadow_config_flags shadow_get_config_from_legacy_shadowbase(e_shadow_config_flags shadow_config_flags, e_shadowbase_config legacy_value)
{
    e_shadow_config_flags result = shadow_config_flags;
        
    /* Remove all existing base flags.*/
    result &= ~SHADOW_CONFIG_BASE_ALL;

    switch (legacy_value)
    {
    default:
    case SHADOWBASE_CONFIG_NONE:

        /* Do nothing. */
        break;

    case SHADOWBASE_CONFIG_NO_CHANGE:

        result |= SHADOW_CONFIG_BASE_STATIC;
        break;

    case SHADOWBASE_CONFIG_PLATFORM:

        result |= SHADOW_CONFIG_BASE_PLATFORM;
        break;

    case SHADOWBASE_COMBINE:

        result |= (SHADOW_CONFIG_BASE_STATIC | SHADOW_CONFIG_BASE_PLATFORM);
        break;
    }

    return result;
}


/*
* Caskey, Damon V.
* 2021-08-30
*
* Allocate a defense object and return pointer.
*/
s_defense* defense_allocate_object()
{
    int i = 0;
    s_defense* result;

    /* Allocate memory with 0 values and get the pointer. */
    result = calloc(max_attack_types + 1, sizeof(*result));

    /*
    * Default values.
    *
    * -- Copy the global default to each attack type.
    */
    
    for (i = 0; i < max_attack_types; i++)
    {
        result[i] = default_defense;
    }

    return result;
}

/*
* Caskey, Damon V.
* 2021-08-30
* 
* Applies value to an attack type element
* of defense.
*/
void defense_apply_setup_to_property(char* filename, char* command, s_defense* defense, ArgList* arglist, e_defense_parameters target_parameter)
{
    //printf("\n\n defense_apply_setup_to_property(%s, %s, %p, %p, %d)", filename, command, defense, arglist, target_parameter);
    //printf("\n\t GET_FLOAT_ARGP(2): %f", GET_FLOAT_ARGP(2));

    /* 
    * If a NULL pointer gets through, lets
    * get out before we cause a NULL pointer 
    * error
    */
    if (!defense)
    {
        return;
    }
    
    /*
    * As of 2021-08-30, the up to date method
    * sets one parameter at a time. This is more
    * readable in the model text files. It also 
    * allows easier debug and expansion in the future.
    * 
    * Defense is a bit clunky since we have to send
    * a constant to tell us which parameter to update
    * but it still beats the legacy method.
    */

    switch (target_parameter)
    {
    case DEFENSE_PARAMETER_BLOCK_DAMAGE_ADJUST:
        defense->block_damage_adjust = GET_INT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_BLOCK_DAMAGE_MAX:
        defense->block_damage_max = GET_INT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_BLOCK_DAMAGE_MIN:
        defense->block_damage_min = GET_INT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_BLOCK_POWER:
        defense->blockpower = GET_FLOAT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_BLOCK_RATIO:
        defense->blockratio = GET_FLOAT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_BLOCK_THRESHOLD:
        defense->blockthreshold = GET_FLOAT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_BLOCK_TYPE:
        defense->blocktype = GET_INT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_DAMAGE_ADJUST:
        defense->damage_adjust = GET_INT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_DAMAGE_MAX:
        defense->damage_max = GET_INT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_DAMAGE_MIN:
        defense->damage_min = GET_INT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_DEATH_CONFIG:
        defense->death_config_flags = death_get_config_flags_from_arguments(arglist, 2);
        break;

    case DEFENSE_PARAMETER_FACTOR:
        defense->factor = GET_FLOAT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_KNOCKDOWN:
        defense->knockdown = GET_FLOAT_ARGP(2);
        break;

    case DEFENSE_PARAMETER_LEGACY:
            
        /*
        * Legacy read. Populate values from single
        * line muti-parameter command.
        * 
        * Please do not expand the legacy read. We 
        * want to encourage creators to use up to 
        * date methods.
        */

        if (arglist->count >= 2)
        {
            defense->factor = GET_FLOAT_ARGP(2);
        }
        if (arglist->count >= 3)
        {
            defense->pain = GET_FLOAT_ARGP(3);
        }
        if (arglist->count >= 4)
        {
            defense->knockdown = GET_FLOAT_ARGP(4);
        }
        if (arglist->count >= 5)
        {
            defense->blockpower = GET_FLOAT_ARGP(5);
        }
        if (arglist->count >= 6)
        {
            defense->blockthreshold = GET_FLOAT_ARGP(6);
        }
        if (arglist->count >= 7)
        {
            defense->blockratio = GET_FLOAT_ARGP(7);
        }
        if (arglist->count >= 8)
        {
            defense->blocktype = GET_FLOAT_ARGP(8);
        }
        break;

    case DEFENSE_PARAMETER_PAIN:
        defense->pain = GET_FLOAT_ARGP(2);
        break;
    }
    
}

/*
* Caskey, Damon V.
* 2021-08-30
* 
* Free defense from memory.
*/
void defense_free_object(s_defense* target)
{
    if (!target)
    {
        return;
    }

    free(target);
}

/*
* Caskey, Damon V.
* 2021-09-08
* 
* Accept animation object and frame. Returns 
* the defense object from animation frame's
* collision, or NULL if not available.
*/
//s_defense* defense_get_current_body_defense_from_animation(s_anim* animation_object, int animation_frame)
//{
//    s_collision_body* collision_body_object = NULL;
//    s_body* body_object = NULL;
//
//    /* Animation frame has a collision body allocated? */
//
//    collision_body_object = animation_object->collision_body[animation_frame];
//    
//    if (!collision_body_object)
//    {
//        return NULL;
//    }
//
//    /* 
//    * Collision body should always have body 
//    * allocated, but let's make certain.
//    */
//
//    body_object = collision_body_object->body;
//
//    if (!body_object)
//    {
//        return NULL;
//    }
//
//    /* 
//    * Return body's defense property. In most
//    * cases there won't be a defense allocated
//    * for individual bodies, so this is likely 
//    * a NULL pointer value. Be very careful
//    * when referencing downstream.
//    */
//
//    return body_object->defense;    
//}
//
///*
//* Caskey, Damon V.
//* 2021-09-08
//*
//* Accept entity pointer. Returns defense object 
//* from current animation frame's collision, or 
//* NULL if not available.
//*/
//s_defense* defense_get_current_body_defense_object_from_entity(entity* target_entity)
//{
//    s_anim* animation_object = NULL;
//    
//    /* 
//    * Every entity should have an animation
//    * allocated, but let's make certain.
//    */
//
//    animation_object = target_entity->animation;
//
//    if (!animation_object)
//    {
//        return NULL;
//    }
//
//    /* 
//    * Send animation pointer to the defense get 
//    * function for animations and return its
//    * result. Most animation frames won't have
//    * a defense allocated, so this is likely a
//    * NULL pointer value. Be very careful
//    * when referencing downstream.
//    */
//    
//    return defense_get_current_body_defense_from_animation(animation_object, target_entity->animpos);
//}


/*
* Caskey, Damon V.
* 2023-02-09
* 
* Return final damage output from
* after offense adjustments.
*/
int offense_result_damage(s_offense* offense_object, int attack_force)
{
    int result = attack_force;

    if (!offense_object)
    {
        return result;
    }
    
    result = (int)(result * offense_object->factor);

    result += offense_object->damage_adjust;

    if (result < offense_object->damage_min)
    {
        result = offense_object->damage_min;
    }

    if (result > offense_object->damage_max)
    {
        result = offense_object->damage_max;
    }

    return result;
}

/*
* Caskey, Damon V.
* 2021-09-08
* 
* Get damage after applying defense adjustments.
* 
* Note: We send damage separately from attack object
* as we usually want to get a calculated value
* without modifying the original property.
*/
int defense_result_damage(s_defense* defense_object, int attack_force, int blocked)
{   
#define DEFENSE_GLOBAL_BLOCK_RATIO 0.25

    //printf("\n\n defense_result_damage(%p, %p, %d, %d)", defense_object, attack_force, blocked);
    
    int result = attack_force;
    float ratio = 0.0;
    int damage_adjust = 0;
    int damage_min = MIN_INT;
    int damage_max = MAX_INT;

    /* 
    * If there's no defense object, then
    * fall back to default defense.
    */

    if (!defense_object)
    {
        defense_object = (s_defense*)&default_defense;
    }

    /*
    * We have a defense object, so get the
    * ratio and adjustments for hit or block.
    */

    if (blocked)
    {
        /*
        * We want blockratio to default as 0.0 and 
        * also override the legacy global blockratio 
        * setting with local defense values. However,
        * a value of 0.0 is too ambiguous. We can't 
        * tell if it was intentional from the creator 
        * or they just left defense blank. 
        * 
        * We could dynamically allocate defense and 
        * then check for valid pointers, but that would 
        * add several failure points and increase the
        * complexity for creators.
        * 
        * Instead, we use an impractical default value. 
        * If that value is still in place here, we know 
        * the creator did not define defense and we can 
        * apply a real default of 0.0, or 
        when the
        * legacy global blockratio is enabled.
        */

        if (defense_object->blockratio == DEFENSE_BLOCKRATIO_COMPATABILITY_DEFAULT)
        {
            if (global_config.block_ratio)
            {
                ratio = DEFENSE_GLOBAL_BLOCK_RATIO;
            }
            else
            {
                ratio = 0.0;
            }
        }
        else
        {
            ratio = defense_object->blockratio;
        }
               
        damage_adjust = defense_object->block_damage_adjust;
        damage_min = defense_object->block_damage_min;
        damage_max = defense_object->block_damage_max;
    }
    else
    {
        ratio = defense_object->factor;
        damage_adjust = defense_object->damage_adjust;
        damage_min = defense_object->damage_min;
        damage_max = defense_object->damage_max;
    }

    /*
    * Apply ratio, then adjustment, and
    * cap the result to min/max.
    */

    result = (int)(result * ratio);

    result += damage_adjust;

    if (result < damage_min)
    {
        result = damage_min;
    }

    if (result > damage_max)
    {
        result = damage_max;
    }

    return result;

#undef DEFENSE_GLOBAL_BLOCK_RATIO
}

/*
* Caskey, Damon V.
* 2021-09-08
*
* Return true if entity should enter pain reaction
* after applying defense pain property.
*/
int defense_result_pain(s_attack* attack_object, s_defense* defense_object)
{
    int attack_force = attack_object->attack_force;
    e_attack_types attack_type = attack_object->attack_type;

    /*
    * Make sure attack types are in bounds and defense
    * object is valid.
    */

    if (!defense_object)
    {
        return 1;
    }

    if (attack_type < 0 || attack_type > max_attack_types)
    {
        return 1;
    }

    /*
    * Defense pain 0 is default, so we don't have any
    * resistance to pain.
    */

    if (!defense_object->pain)
    {
        return 1;
    }

    /*
    * Attack force meets or beats pain? Then return true
    * and downstream functions will set up pain reactions.
    */
    if (attack_force >= defense_object->pain)
    {
        return 1;
    }

    /* 
    * Nothing passed, so defense is available and sufficient
    * to resist attack force. Return false. Downstream functions
    * can skip pain reaction setup.
    */

    return 0;
}

/*
* Caskey, Damon V.
* 2021-09-01
* 
* Locate the appropriate pointer for dereferencing
* an "at the moment" defense property. Meant for 
* hit logic, so we know which defense value to use. 
* Returns one of the following, first to last.
* 
* 1. Supplied body object's defense member.
* Presumably the body object that detected
* incoming hit. Return if defense pointer
* is valid.
*  
* 2. Target entity's model level defense member.
* Return if pointer is valid.
* 
* 3. Global defense_default constant.
*/
s_defense* defense_find_current_object(entity* ent, s_body* body_object, e_attack_types attack_type)
{    
    //printf("\n\n defense_find_current_object(%p, %p, %d)", ent, body_object, attack_type);
    
     /* Supplied body. */

    if (body_object && body_object->defense)
    {   
        //printf("\n\t &body_object->defense[%d]: %p", attack_type, &body_object->defense[attack_type]);

        return &body_object->defense[attack_type];
    }

    /* Entity defense */

    if (ent->defense)
    {
        //printf("\n\t ent->defense: %p", &ent->defense[attack_type]);

        return &ent->defense[attack_type];
    }

    /* 
    * Global default. We're recasting a
    * a constant to return its pointer, so 
    * we need to be careful and avoid mutating 
    * any values downstream.
    */

    //printf("\n\t &default_defense: %p", (s_defense*)&default_defense);

    return (s_defense *)&default_defense;
}

/*
* Caskey, Damon V.
* 2021-08-30
* 
* Read first argument in supplied argument list
* for type, and determines which attack type or
* attack types to assign a defense value. 
*/ 
void defense_setup_from_arg(char* filename, char* command, s_defense* target_defense, ArgList* arglist, e_defense_parameters target_parameter)
{
    //printf("\n\n defense_setup_from_arg(%s, %s, %p, %p, %d)", filename, command, target_defense, arglist, target_parameter);

    int tempInt = 0;
    int i = 0;
    char* value = GET_ARGP(1);
    
    /*
    * Now we need to figure out which attack
    * type this defense entry applies to.
    */

    //printf("\n\t value: %s", value);

#define tempdef(x, y) \
					    x(stricmp(value, #y)==0) \
					    { \
                            defense_apply_setup_to_property(filename, command, &target_defense[ATK_##y], arglist, target_parameter); \
					    }

    tempdef(if, NORMAL)
        tempdef(else if, NORMAL2)
        tempdef(else if, NORMAL3)
        tempdef(else if, NORMAL4)
        tempdef(else if, NORMAL5)
        tempdef(else if, NORMAL6)
        tempdef(else if, NORMAL7)
        tempdef(else if, NORMAL8)
        tempdef(else if, NORMAL9)
        tempdef(else if, NORMAL10)
        tempdef(else if, BLAST)
        tempdef(else if, STEAL)
        tempdef(else if, BURN)
        tempdef(else if, SHOCK)
        tempdef(else if, FREEZE)

        tempdef(else if, BOSS_DEATH)
        tempdef(else if, ITEM)
        tempdef(else if, LAND)
        tempdef(else if, LIFESPAN)
        tempdef(else if, LOSE)
        tempdef(else if, PIT)
        tempdef(else if, SUB_ENTITY_PARENT_KILL)
        tempdef(else if, SUB_ENTITY_UNSUMMON)
        tempdef(else if, TIMEOVER)

        else if (starts_with(value, "normal"))
        {
            get_tail_number(tempInt, value, "normal");

            defense_apply_setup_to_property(filename, command, &target_defense[tempInt + STA_ATKS - 1], arglist, target_parameter);
        }
        else if (stricmp(value, "ALL") == 0)
        {
            /*
            * "All" is a convenience feature so the creator
            * doesn't need a defense entry for every type
            * when they want to set up a generic defense
            * across all attack types.
            *
            * To handle this we want to apply defense on
            * all the attack types other than special types
            * not normally used by creator. They may say
            * �all� but they probably don�t mean get stuck
            * in a pit forever because they're immune to
            * pit damage! Loop through all types and type
            * check function. If the type is special, we
            * skip to the next. Otherwise apply the temporary
            * values to that attack type to defense.
            */

            for (i = 0; i < max_attack_types; i++)
            {
                if (is_attack_type_special(i))
                {
                    continue;
                }

                defense_apply_setup_to_property(filename, command, &target_defense[i], arglist, target_parameter);
            }
        }

#undef tempdef

}

/*
* Caskey, Damon V.
* 2023-02-07
* 
* Dump object data to log.
*/
void defense_dump_object(s_defense* object)
{
    const int space_label = 20;

    printf("\n\n -- Defense dump object (%p) Dump --\n", object);

    if (object)
    {
        printf("\n\t %-*s %d", space_label, "->block_damage_adjust", object->block_damage_adjust);
        printf("\n\t %-*s %d", space_label, "->block_damage_max", object->block_damage_max);
        printf("\n\t %-*s %d", space_label, "->block_damage_min", object->block_damage_min);
        printf("\n\t %-*s %d", space_label, "->blockpower", object->blockpower);
        printf("\n\t %-*s %f", space_label, "->blockratio", object->blockratio);
        printf("\n\t %-*s %d", space_label, "->blockthreshold", object->blockthreshold);
        printf("\n\t %-*s %d", space_label, "->blocktype", object->blocktype);
        printf("\n\t %-*s %d", space_label, "->damage_adjust", object->damage_adjust);
        printf("\n\t %-*s %d", space_label, "->damage_max", object->damage_max);
        printf("\n\t %-*s %d", space_label, "->damage_min", object->damage_min);
        printf("\n\t %-*s %d", space_label, "->death_config_flags", object->death_config_flags);
        printf("\n\t %-*s %f", space_label, "->factor", object->factor);
        printf("\n\t %-*s %f", space_label, "->knockdown", object->knockdown);
        printf("\n\t %-*s %p", space_label, "->meta_data", object->meta_data);
        printf("\n\t %-*s %d", space_label, "->meta_tag", object->meta_tag);
        printf("\n\t %-*s %d", space_label, "->pain", object->pain);
    }

    printf("\n\n -- Defense dump object (%p) dump complete! -- \n", object);
}

/*
* Caskey, Damon V.
* 2023-02-07
*
* Allocate an offense object and return pointer.
*/
s_offense* offense_allocate_object()
{
    int i = 0;
    s_offense* result;

    /* Allocate memory with 0 values and get the pointer. */
    result = calloc(max_attack_types + 1, sizeof(*result));

    /*
    * Default values.
    *
    * -- Copy the global default to each attack type.
    */

    for (i = 0; i < max_attack_types; i++)
    {
        result[i] = default_offense;
    }

    return result;
}

/*
* Caskey, Damon V.
* 2023-02-07
*
* Free offense from memory.
*/
void offense_free_object(s_offense* target)
{
    if (!target)
    {
        return;
    }

    free(target);
}

/*
* Caskey, Damon V.
* 2023-02-07
*
* Read first argument in supplied argument list
* for type, and determines which attack type or
* attack types to assign an offense value.
*/
void offense_setup_from_arg(char* filename, char* command, s_offense* target_offense, ArgList* arglist, e_offense_parameters target_parameter)
{
    int tempInt = 0;
    int i = 0;
    char* value = GET_ARGP(1);
    
    /*
    * Now we need to figure out which attack
    * type this offense entry applies to.
    */

#define tempoff(x, y) \
					    x(stricmp(value, #y)==0)\
					    {\
                            offense_apply_setup_to_property(filename, command, &target_offense[ATK_##y], arglist, target_parameter);\
					    }

    tempoff(if, NORMAL)
        tempoff(else if, NORMAL2)
        tempoff(else if, NORMAL3)
        tempoff(else if, NORMAL4)
        tempoff(else if, NORMAL5)
        tempoff(else if, NORMAL6)
        tempoff(else if, NORMAL7)
        tempoff(else if, NORMAL8)
        tempoff(else if, NORMAL9)
        tempoff(else if, NORMAL10)
        tempoff(else if, BLAST)
        tempoff(else if, STEAL)
        tempoff(else if, BURN)
        tempoff(else if, SHOCK)
        tempoff(else if, FREEZE)

        tempoff(else if, BOSS_DEATH)
        tempoff(else if, ITEM)
        tempoff(else if, LAND)
        tempoff(else if, LIFESPAN)
        tempoff(else if, LOSE)
        tempoff(else if, PIT)
        tempoff(else if, SUB_ENTITY_PARENT_KILL)
        tempoff(else if, SUB_ENTITY_UNSUMMON)
        tempoff(else if, TIMEOVER)

        else if (starts_with(value, "normal"))
        {
            get_tail_number(tempInt, value, "normal");

            offense_apply_setup_to_property(filename, command, &target_offense[tempInt + STA_ATKS - 1], arglist, target_parameter);
        }
        else if (stricmp(value, "ALL") == 0)
        {
            /*
            * "All" is a convenience feature so the creator
            * doesn't need a defense entry for every type
            * when they want to set up a generic defense
            * across all attack types.
            *
            * To handle this we want to apply offense on
            * all the attack types other than special types
            * not normally used by creator. 
            */

            for (i = 0; i < max_attack_types; i++)
            {
                if (is_attack_type_special(i))
                {
                    continue;
                }

                offense_apply_setup_to_property(filename, command, &target_offense[i], arglist, target_parameter);
            }
        }

#undef tempoff

}

/*
* Caskey, Damon V.
* 2023-02-07
*
* Applies value to an attack type element
* of defense.
*/
void offense_apply_setup_to_property(char* filename, char* command, s_offense* offense, ArgList* arglist, e_offense_parameters target_parameter)
{
    /*
    * If a NULL pointer gets through, lets
    * get out before we cause a NULL pointer
    * error
    */
    if (!offense)
    {
        return;
    }

    /*
    * As of 2021-08-30, the up to date method
    * sets one parameter at a time. This is more
    * readable in the model text files. It also
    * allows easier debug and expansion in the future.
    *
    * Offense is a bit clunky since we have to send
    * a constant to tell us which parameter to update
    * but it still beats the legacy method.
    */

    switch (target_parameter)
    {
    case OFFENSE_PARAMETER_DAMAGE_ADJUST:
        offense->damage_adjust = GET_FLOAT_ARGP(2);
        break;

    case OFFENSE_PARAMETER_DAMAGE_MAX:
        offense->damage_max = GET_FLOAT_ARGP(2);
        break;

    case OFFENSE_PARAMETER_DAMAGE_MIN:
        offense->damage_min = GET_FLOAT_ARGP(2);
        break;

    case OFFENSE_PARAMETER_FACTOR:
    case OFFENSE_PARAMETER_LEGACY:
        offense->factor = GET_INT_ARGP(2);
        break;
    }
}

int calculate_force_damage(entity *target, entity *attacker, s_attack *attack_object, s_defense* defense_object)
{
    //printf("\n\n calculate_force_damage(%p, %p, %p, %p)", target, attacker, attack_object, defense_object);

    int force = attack_object->attack_force;
    int type = attack_object->attack_type;
   
    /*
    * If touch of death cheat is active, boost player damage
    * to equal the target's hp.
    * 
    * This is upstream of damage mitigation by design. Some
    * games use defense to make entities immortal for special
    * uses and we don't want to break that.
    */
    if (global_config.cheats & CHEAT_OPTIONS_TOD_ACTIVE && attacker->modeldata.type & TYPE_PLAYER)
    {
        if (force < target->energy_state.health_current)
        {
            force = target->energy_state.health_current;
        }
    }

    if(target->modeldata.guardpoints.max > 0 && target->modeldata.guardpoints.current <= 0)
    {
        return 0;    //guardbreak does not deal damage.
    }

    //printf("\n\t force: %d", force);

    if(type >= 0 && type < max_attack_types && attacker->offense)
    {
        force = offense_result_damage(&attacker->offense[type], force);
    }

    //printf("\n\t force: %d", force);
    //printf("\n\t defense_object: %p", defense_object);
    //printf("\n\t defense_object->factor: %f", defense_object->factor);

    if (!defense_object)
    {
        defense_object = target->defense;
    }

    force = defense_result_damage(defense_object, force, 0);

    //printf("\n\t force: %d", force);

    return force;
}

void checkdamageonlanding(entity* acting_entity)
{
    //printf("\n\n checkdamageonlanding(%p)", acting_entity);

    s_defense* defense_object = NULL;
    s_attack attack = emptyattack;
    entity* other = NULL;
    int didhit = 0;

    if (acting_entity->energy_state.health_current <= 0)
    {
        return;
    }

    if((acting_entity->damage_on_landing.attack_force > 0 && !(acting_entity->death_state & DEATH_STATE_DEAD)))
    {    
        //##################
        attack              = emptyattack;
        attack.attack_force = acting_entity->damage_on_landing.attack_force;
        
        /* 
        * Apply user defined type for damage on landing
        * if populated. Usually there won't be one and we 
        * fall back to ATK_LAND.
        */

        if (attack.damage_on_landing.attack_type >= 0)
        {
            attack.attack_type = acting_entity->damage_on_landing.attack_type;
        }
        else
        {
            attack.attack_type = ATK_LAND;
        }

        /*
        * If we have a valid living opponent, that is 
        * the entity we take consider the attacker. If 
        * not, we use ourselves.
        */

        if (acting_entity->opponent && acting_entity->opponent->exists && !(acting_entity->opponent->death_state & DEATH_STATE_DEAD) && acting_entity->opponent->energy_state.health_current > 0)
        {
            other = acting_entity->opponent;
        }
        else
        {
            other = acting_entity;
        }

        lasthit.confirm = 1;
        lasthit.attack = &attack;
        lasthit.position.x = acting_entity->position.x;
        lasthit.position.y = acting_entity->position.y;
        lasthit.position.z = acting_entity->position.z;

        defense_object = defense_find_current_object(acting_entity, NULL, attack.attack_type);

        //defense_dump_object(defense_object);

        // Execute the doattack functions before damage is
        // processed.
        execute_ondoattack_script(acting_entity, other, &attack, EXCHANGE_RECIPIANT, other->attack_id_outgoing);
        execute_ondoattack_script(other, acting_entity, &attack, EXCHANGE_CONFERRER, other->attack_id_outgoing);

        //printf("\n\tother: %p", other);
        //printf("\n\tattack.attack_type: %d", attack.attack_type);
        //printf("\n\tattack.attack_force: %d", attack.attack_force);
        
        if(lasthit.confirm)
        {
            didhit = 1;
        }

        /*
        * Can't take damage if we're dead.
        */

        if(acting_entity->death_state & DEATH_STATE_DEAD)
        {
            return;
        }

        /*
        * Bombs detonante with their own logic when
        * landing. We don't want them to take damage.
        */

        if(acting_entity->toexplode & (EXPLODE_DETONATE_HIT | EXPLODE_DETONATE_DAMAGED))
        {
            return;
        }

        // fake 'grab', if failed, return as the attack hit nothing
        if(!checkgrab(other, &attack))
        {
            return;    // try to grab but failed, so return 0 means attack missed
        }

        if(acting_entity != other)
        {
            set_opponent(acting_entity, other);
        }
        
        //##################

        if (didhit)
        {           
            // pre-check drop
            checkdamagedrop(acting_entity, &attack, defense_object);

            // Drop Weapon due to being hit.
            if(acting_entity->modeldata.weapon_properties.loss_condition & WEAPON_LOSS_CONDITION_LAND_DAMAGE)
            {
                dropweapon(1);
            }
            // check effects, e.g., frozen, blast, steal
            if(!(acting_entity->modeldata.guardpoints.max > 0 && acting_entity->modeldata.guardpoints.current <= 0))
            {
                checkdamageeffects(&attack);
            }

            // mprate can also control the MP recovered per hit.
            checkmpadd();
            //damage score
            checkhitscore(other, &attack);

            /*
            * Applies the damage. Send NULL as body object so
            * we always use model defense or global default 
            * when calculating final damage.
            */
            checkdamage(acting_entity, other, &attack, defense_object);            

            // is it dead now?
            checkdeath();

            execute_didhit_script(other, acting_entity, &attack, 0);
        }

        if (acting_entity->energy_state.health_current <= 0)
        {
            acting_entity->die_on_landing = 1;
        }

        acting_entity->damage_on_landing.attack_force = 0;
    }

    // takedamage if thrown or basted
    //if( (acting_entity->damage_on_landing.attack_force > 0 && !acting_entity->death_state) &&
    if( (acting_entity->die_on_landing && !(acting_entity->death_state & DEATH_STATE_DEAD)) &&
        ((!tobounce(acting_entity) && acting_entity->modeldata.bounce) || !acting_entity->modeldata.bounce) &&
        (acting_entity->velocity.x == 0 && acting_entity->velocity.z == 0 && acting_entity->velocity.y == 0)
      )
    {
        if(acting_entity->takedamage)
        {
            //##################
            s_attack attack;
            entity *other;

            attack              = emptyattack;
            attack.attack_force = acting_entity->damage_on_landing.attack_force;
            if (attack.damage_on_landing.attack_type >= 0) attack.attack_type  = acting_entity->damage_on_landing.attack_type;
            else attack.attack_type  = ATK_LAND;

            if (acting_entity->opponent && acting_entity->opponent->exists && !(acting_entity->opponent->death_state & DEATH_STATE_DEAD) && acting_entity->opponent->energy_state.health_current > 0)
            {
                other = acting_entity->opponent;
            }
            else
            {
                other = acting_entity;
            }
            //##################

            defense_object = defense_find_current_object(other, NULL, attack.attack_type);
            
            acting_entity->takedamage(other, &attack, 1, defense_object);
        }
        else
        {
            kill_entity(acting_entity, KILL_ENTITY_TRIGGER_DAMAGE_ON_LANDING);
        }
        
        if (acting_entity)
        {
            acting_entity->damage_on_landing.attack_force = 0;
            acting_entity->damage_on_landing.attack_type = ATK_NONE;
            acting_entity->die_on_landing = 0;
        }
    }

    return;
}

/*
* Caskey, Damon V.
* 2019-12-26
*
* Return true if attack type is one of the 
* types not included in normal use by creators.
*/
int is_attack_type_special(e_attack_types type)
{
	switch (type)
	{
	default:
		return 0;
        break;

	case ATK_BOSS_DEATH:
	case ATK_ITEM:
	case ATK_LIFESPAN:
	case ATK_LOSE:
	case ATK_SUB_ENTITY_PARENT_KILL:
	case ATK_SUB_ENTITY_UNSUMMON:
	case ATK_TIMEOVER:
	case ATK_PIT:
		return 1;
        break;
	}
}

/*
* Original by unknown author, unknown date.
*
* Applies HP damage to target entity. Executes
* appropriate damage and death scripts as needed.
*
* Caskey, Damon V.
* 2021-09-07
*
* Slightly reworked to accept the target entity
* as parameter rather than referencing the
* global "self" variable.
*
* Accepts defense object to pass on into
* total damage calculation functions.
*/
void checkdamage(entity* target_entity, entity* attacking_entity, s_attack* attack_object, s_defense* defense_object)
{ 
    //printf("\n\n checkdamage(%p, %p, %p, %p)", target_entity, attacking_entity, attack_object, defense_object);

	int	force = 0;
	int	normal_damage = 0;    

	/* Get attack damage force after defense is applied. */
    force = calculate_force_damage(target_entity, attacking_entity, attack_object, defense_object);

    /*
	* Damage does not return HP and comes from
	* a normal source?
	*/
    normal_damage = (!is_attack_type_special(attack_object->attack_type) && force >= 0);

	/* 
    * If we're invincible to normal damage 
    * sources, laugh it off.
	*/
    if (target_entity->invincible & INVINCIBLE_HP_NULLIFY && normal_damage)
	{
		force = 0;
	}
	
	/* 
    * Apply the damage. In the case of negative
    * damage (IOW, giving back HP), cap at
    * maximum health.
    * 
    * If attack has no kill flag enable, don't
    * fall below 1 hit point.
    */

    target_entity->energy_state.health_current -= force;

	if (target_entity->energy_state.health_current > target_entity->modeldata.health)
    {
        target_entity->energy_state.health_current = target_entity->modeldata.health;
    }

    if(attack_object->no_kill && target_entity->energy_state.health_current <= 0)
    {
        target_entity->energy_state.health_current = 1;
    }

    /* Execute the take damage script. */
    execute_takedamage_script(target_entity, attacking_entity, attack_object);

	/* 
    * Attack meant to put health at 0? We execute
    * death script, but may reset HP according
    * to invincibility flags.
    */
    if (target_entity->energy_state.health_current <= 0)
    {
		if(normal_damage)
        {
			if (target_entity->invincible & INVINCIBLE_HP_MINIMUM)
            {
                target_entity->energy_state.health_current = 1;
            }
            
			if(target_entity->invincible & INVINCIBLE_HP_RESET)
            {
                target_entity->energy_state.health_current = target_entity->modeldata.health;
            }
        }
        
        execute_ondeath_script(target_entity, attacking_entity, attack_object);
    }
}

int checkgrab(entity *other, s_attack *attack)
{
    //if(attack->no_pain) return  0; //no effect, let modders to deside, don't bother check it here
    if(self != other && attack->grab && check_cangrab(other, self))
    {
        if(adjust_grabposition(other, self, attack->grab_distance, attack->grab))
        {
            ents_link(other, self);
            self->position.y = other->position.y;
        }
        else
        {
            return 0;
        }
    }
    return 1;
}

int arrow_takedamage(entity *other, s_attack *attack, int fall_flag, s_defense* defense_object)
{
    self->modeldata.move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;
    self->modeldata.move_config_flags |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);

    if( common_takedamage(other, attack, 0, defense_object) && self->death_state & DEATH_STATE_DEAD)
    {
        return 1;
    }
    return 0;
}

int common_takedamage(entity *other, s_attack *attack, int fall_flag, s_defense* defense_object)
{   
    entity* acting_entity = self;

    int pain_check = 0; // React with pain animations (1) or ignore (0);
    e_death_config_flags death_config;
    
    if(acting_entity->death_state & DEATH_STATE_DEAD)
    {
        return 0;
    }

    if(acting_entity->toexplode & (EXPLODE_DETONATE_HIT | EXPLODE_DETONATE_DAMAGED))
    {
        return 0;
    }    
    
    // fake 'grab', if failed, return as the attack hit nothing
    if(!checkgrab(other, attack))
    {
        return 0;    // try to grab but failed, so return 0 means attack missed
    }
   
    // set oppoent
    if(acting_entity != other)
    {
        set_opponent(acting_entity, other);
    }
    
    // adjust type
    if(attack->attack_type >= 0 && attack->attack_type < max_attack_types)
    {
        acting_entity->last_damage_type = attack->attack_type;
    }
    else
    {
        acting_entity->last_damage_type = ATK_NORMAL;
    }

    if (!acting_entity->die_on_landing)
    {        
        // pre-check drop
        checkdamagedrop(acting_entity, attack, defense_object);

        // Drop Weapon due to being hit.
        if(acting_entity->modeldata.weapon_properties.loss_condition & WEAPON_LOSS_CONDITION_DAMAGE)
        {
            dropweapon(1);
        }
        // check effects, e.g., frozen, blast, steal
        if(!(acting_entity->modeldata.guardpoints.max > 0 && acting_entity->modeldata.guardpoints.current <= 0))
        {
            checkdamageeffects(attack);
        }
    }

    // check backpain
    check_backpain(other,acting_entity);
    
    /* Check and apply direction flip. */
    checkdamageflip(acting_entity, other, attack, defense_object);

    if (!acting_entity->die_on_landing)
    {
        // mprate can also control the MP recovered per hit.
        checkmpadd();
        //damage score
        checkhitscore(other, attack);        
        
        // check damage, cost hp.
        checkdamage(acting_entity, other, attack, defense_object);

        // is it dead now?
        checkdeath();
    }

    if(acting_entity->modeldata.type & TYPE_PLAYER)
    {
        if (savedata.joyrumble[acting_entity->playerindex]) control_rumble(acting_entity->playerindex, 1, attack->attack_force * 3);
    }
    if(acting_entity->position.y <= PIT_DEPTH && acting_entity->death_state & DEATH_STATE_DEAD)
    {
        if(acting_entity->modeldata.type & TYPE_PLAYER)
        {
            player_die();
        }
        else
        {
            kill_entity(acting_entity, KILL_ENTITY_TRIGGER_TAKE_DAMAGE_COMMON_PIT);
        }
        return 1;
    }

    // fall to the ground so don't fall again
    /*if(acting_entity->damage_on_landing.attack_force)
    {
        acting_entity->damage_on_landing.attack_force = 0;
        return 1;
    }*/
    // reset damageonlanding
    acting_entity->damage_on_landing.attack_force = 0;
    acting_entity->damage_on_landing.attack_type = ATK_NONE;

	// White Dragon: fix damage_on_landing bug
	if(acting_entity->die_on_landing && acting_entity->energy_state.health_current <= 0)
	{
		acting_entity->modeldata.death_config_flags |= DEATH_CONFIG_MACRO_DEATH_FALL_LAND;
	}

    // unlink due to being hit
    if((acting_entity->opponent && acting_entity->opponent->grabbing != acting_entity) // Have an opponent, but opponent is not grabbing me. 
		|| acting_entity->death_state & DEATH_STATE_DEAD				// Dead.
		|| acting_entity->frozen										// Frozen. 
		|| acting_entity->drop)										// Knocked down.
    {
        ent_unlink(acting_entity);
    }
    // Enemies can now use SPECIAL2 to escape cheap attack strings!
    if(acting_entity->modeldata.escapehits)
    {
        if(acting_entity->drop)
        {
            acting_entity->escapecount = 0;
        }
        else
        {
            acting_entity->escapecount++;
        }
    }

    /* 
    * Handle Pain, fall, and death animations.
    * May also not react at all if attack doesn't 
    * cause pain or defense is enough to ignore it.
    */
    pain_check = defense_result_pain(attack, defense_object);
    
    if(acting_entity->drop || acting_entity->energy_state.health_current <= 0)
    {
        acting_entity->takeaction = common_fall;
        
        // Drop Weapon due to death.
        if(acting_entity->modeldata.weapon_properties.loss_condition & WEAPON_LOSS_CONDITION_DEATH && acting_entity->energy_state.health_current <= 0)
        {
            dropweapon(1);
        }
        else if(acting_entity->modeldata.weapon_properties.loss_condition & WEAPON_LOSS_CONDITION_FALL)
        {
            dropweapon(1);
        }

        death_config = defense_object->death_config_flags;

        if (death_config & DEATH_CONFIG_SOURCE_MODEL)
        {
            death_config = acting_entity->modeldata.death_config_flags;
        }

        /* We're alive, or death sequence wants us to handle falling. */
        if(acting_entity->energy_state.health_current > 0 || !death_try_sequence_damage(acting_entity, death_config, DEATH_TRY_SEQUENCE_ACTING_EVENT_DAMAGE))
        {
            if (fall_flag >= 1) return 1;
            acting_entity->velocity.x = attack->dropv.x;
            acting_entity->velocity.z = attack->dropv.z;
            if(acting_entity->direction == DIRECTION_RIGHT)
            {
                acting_entity->velocity.x = -acting_entity->velocity.x;
            }
            if(acting_entity->inbackpain) acting_entity->velocity.x *= -1;
            toss(acting_entity, attack->dropv.y);
            acting_entity->damage_on_landing.attack_force = attack->damage_on_landing.attack_force;
            acting_entity->damage_on_landing.attack_type = attack->damage_on_landing.attack_type;
            acting_entity->knockdowncount = acting_entity->modeldata.knockdowncount; // reset the knockdowncount
            acting_entity->knockdowntime = 0;

            // If no fall/die animations exist, entity simply disappears.
            if(!set_fall(acting_entity, other, attack, 1))
            {
                if(acting_entity->modeldata.type & TYPE_PLAYER)
                {
                    player_die();
                }
                else
                {
                    kill_entity(acting_entity, KILL_ENTITY_TRIGGER_TAKE_DAMAGE_COMMON_FALL);
                }
                return 1;
            }
        }

        if(acting_entity->modeldata.type & TYPE_PLAYER)
        {
            if (savedata.joyrumble[acting_entity->playerindex]) control_rumble(acting_entity->playerindex, 1, attack->attack_force * 3);
        }
    }
    else if(attack->grab && !attack->no_pain)
    {
        acting_entity->takeaction = common_pain;
        other->takeaction = common_grabattack;
        other->stalltime = _time + GRAB_STALL;
        acting_entity->releasetime = _time + (GAME_SPEED / 2);
        set_pain(acting_entity, acting_entity->last_damage_type, 0);
    }
    // Don't change to pain animation if frozen
    else if(!acting_entity->frozen && !(acting_entity->modeldata.pain_config_flags & PAIN_CONFIG_PAIN_DISABLE) && !attack->no_pain && pain_check)
    {
        acting_entity->takeaction = common_pain;
        set_pain(acting_entity, acting_entity->last_damage_type, 1);
    }

    return 1;
}

int common_try_runattack(entity *target)
{
    if(!self->running || !validanim(self, ANI_RUNATTACK))
    {
        return 0;
    }


    if(!target)
    {
        target = normal_find_target(ANI_RUNATTACK, 0);
    }

    if(target)
    {
        if(!target->animation->vulnerable[target->animpos] && (target->drop || target->attacking != ATTACKING_NONE))
        {
            return 0;
        }
        self->takeaction = common_attack_proc;
        self->velocity.z = self->velocity.x = 0;
        set_attacking(self);
        ent_set_anim(self, ANI_RUNATTACK, 0);
        return 1;
    }
    return 0;
}

// Active blocking enabled. 
//
// AI can behave more like players when blocking. Normally AI
// blocking is passive. IOW, it can only choose to block attacks
// as they hit. This function allows the AI to initiate blocking 
// preemptively the way players have to.
// 
// AI blocks if following conditions are met:
//
// 1. Entity has active block enabled.
// 2. Target is within range of BLOCK animation.
// 3. Target is actively attacking.
// 4. Blocking chance passes (same rules as active blocking).
int common_try_block(entity *target)
{
	// Must have block animation.
	if (!validanim(self, ANI_BLOCK))
	{
		return 0;
	}

	// Exit if we choose not to block. This function includes 
	// the check for active blocking flag.
	if (!check_blocking_decision(self))
	{	
		return 0;
	}

	// If no current target, use block range.
    if(!target)
    {
        target = block_find_target(ANI_BLOCK, 0);
    }

	// Still no target? Nothing to do, so exit.
	if (!target)
	{
		return 0;
	}

    // If target is attacking, let's block and return true.
    if(target->attacking != ATTACKING_NONE)
    {
		// Set up flags, action, and blocking animations.
		do_active_block(self);

        return 1;
    }

    return 0;
}

// this logic could be used for multiple times, so make a function
// pick a random attack or return the first attacks if testonly is set
// when testonly is set, the function will not check special attacks (upper, jumpattack)
// if target is NULL, ranges are not checked
int pick_random_attack(entity *target, int testonly)
{
    int found = 0, i, j;

    for(i = 0; i < max_attacks; i++) // TODO: recheck range for attacks chains
    {
        if(validanim(self, animattacks[i]) &&
                (!target || check_range_target_all(self, target, animattacks[i], 0, 0)))
        {
            for(j = ((5 - i) >= 0 ? (5 - i) : 0) * 3; j >= 0; j--)
            {
                atkchoices[found++] = animattacks[i];
            }
        }
    }
    for(i = 0; i < max_freespecials; i++)
    {
        if(validanim(self, animspecials[i]) &&
                (check_energy(ENERGY_TYPE_MP, animspecials[i]) ||
                 check_energy(ENERGY_TYPE_HP, animspecials[i])) &&
                (!target || check_range_target_all(self, target, animspecials[i], 0, 0)))
        {
            atkchoices[found++] = animspecials[i];
        }
    }
    if( validanim(self, ANI_THROWATTACK) &&
            self->weapent && self->weapent->modeldata.subtype == SUBTYPE_PROJECTILE &&
            (!target || check_range_target_all(self, target, ANI_THROWATTACK, 0, 0) ))
    {
        atkchoices[found++] = ANI_THROWATTACK;
    }

    if(testonly)
    {
        if(found)
        {
            return atkchoices[(rand32() & 0xffff) % found];
        }
        return -1;
    }

    if( validanim(self, ANI_JUMPATTACK) &&
            (!target || check_range_target_all(self, target, ANI_JUMPATTACK, 0, 0)) )
    {
        if(testonly)
        {
            return ANI_JUMPATTACK;
        }
        atkchoices[found++] = ANI_JUMPATTACK;
    }
    if( validanim(self, ANI_UPPER) &&
            (!target || check_range_target_all(self, target, ANI_UPPER, 0, 0)) )
    {
        if(testonly)
        {
            return ANI_UPPER;
        }
        atkchoices[found++] = ANI_UPPER;
    }

    if(found)
    {
        return atkchoices[(rand32() & 0xffff) % found];
    }

    return -1;
}


// code to lower the chance of attacks, may change while testing old mods
// min - min attack chance
// max - max attack chance
int check_attack_chance(entity *target, float min, float max)
{

    float chance, chance1, chance2;//, aggfix;

    if(self->modeldata.aiattack & AIATTACK1_ALWAYS)
    {
        return 1;
    }

    chance1 = MIN(1.0f, (diff(self->position.x, self->destx) + diff(self->position.z, self->destz)) / (videomodes.hRes + videomodes.vRes) * move_noatk_factor);
    chance2 = MIN(1.0f, (count_ents(self->modeldata.type) - 1) * group_noatk_factor);

    chance = (1.0f - chance1) * (1.0f - chance2);

    if(chance > max)
    {
        chance = max;
    }
    else if(chance < min)
    {
        chance = min;
    }

    chance *= (1.0 - self->modeldata.attackthrottle);

    if(self->position.x < screenx - 10 || self->position.x > screenx + videomodes.hRes + 10)
    {
        if (self->modeldata.offscreen_noatk_factor != 0) chance *= (1.0 - self->modeldata.offscreen_noatk_factor);
        else chance *= (1.0 - offscreen_noatk_factor);
    }

    return (randf(1) <= chance);
}

//make a function, mostly for debug purpose
//give it a chance to reset current noattack timer
u32 recheck_nextattack(entity *target)
{
    if(target->blocking)
    {
        self->nextattack = 0;
    }
    else if(target->attacking != ATTACKING_NONE && self->nextattack > 4)
    {
        self->nextattack -= 4;
    }
    else if(target->jumping && self->nextattack > 16)
    {
        self->nextattack -= 16;
    }

    return self->nextattack;
}

int common_try_normalattack(entity *target)
{
    target = normal_find_target(-1, 0);

    if(!target)
    {
        return 0;
    }

    recheck_nextattack(target);

    if(recheck_nextattack(target) > _time)
    {
        return 0;
    }

    if(!target->animation->vulnerable[target->animpos] && (target->drop || target->attacking != ATTACKING_NONE || target->takeaction == common_rise))
    {
        return 0;
    }

    if(pick_random_attack(target, 1) >= 0)
    {
        if(self->combostep[0] && self->combotime > _time)
        {
            self->stalltime = _time + 1;
        }
        else
        {
            if(!check_attack_chance(target, 1.0f - min_noatk_chance, 1.0f - min_noatk_chance))
            {
                self->nextattack = _time + randf(self->modeldata.attackthrottletime);
                return 0;
            }
            else
            {
                self->stalltime = _time + (int)randf((float)MAX(1, GAME_SPEED * 3 / 4 - self->modeldata.aggression));
            }
        }

        self->takeaction = normal_prepare;
        self->velocity.z = self->velocity.x = 0;
        set_idle(self);
        self->idling = IDLING_NONE; // not really idle, in fact it is thinking
        self->attacking = ATTACKING_PREPARED; // pre-attack, for AI-block check
        return 1;
    }

    return 0;
}

int common_try_jumpattack(entity *target)
{
    entity *dust;
    int rnum, ani = 0;
    if((validanim(self, ANI_JUMPATTACK) || validanim(self, ANI_JUMPATTACK2)))
    {
        if(!validanim(self, ANI_JUMPATTACK))
        {
            rnum = 1;
        }
        else if(validanim(self, ANI_JUMPATTACK2) && (rand32() & 1))
        {
            rnum = 1;
        }
        else
        {
            rnum = 0;
        }

        if(rnum == 0 &&
                // do a jumpattack
                (target || (target = normal_find_target(ANI_JUMPATTACK, 0))) )
        {
            if(recheck_nextattack(target) > _time)
            {
                return 0;
            }

            if(!target->animation->vulnerable[target->animpos] && (target->drop || target->attacking != ATTACKING_NONE))
            {
                rnum = -1;
            }
            else
            {
                if(!check_attack_chance(target, 0.05f, 0.4f))
                {
                    self->nextattack = _time + randf(self->modeldata.attackthrottletime);
                    return 0;
                }
                //ent_set_anim(self, ANI_JUMPATTACK, 0);
                ani = ANI_JUMPATTACK;
                if(self->direction == DIRECTION_RIGHT)
                {
                    self->velocity.x = (float)1.3;
                }
                else
                {
                    self->velocity.x = (float) - 1.3;
                }
                self->velocity.z = 0;
            }
        }
        else if(rnum == 1 &&
                // do a jumpattack2
                (target || (target = normal_find_target(ANI_JUMPATTACK2, 0))) )
        {
            if(recheck_nextattack(target) > _time)
            {
                return 0;
            }

            if(!target->animation->vulnerable[target->animpos] && (target->drop || target->attacking != ATTACKING_NONE))
            {
                rnum = -1;
            }
            else
            {
                if(!check_attack_chance(target, 0.05f, 0.5f))
                {
                    self->nextattack = _time + randf(self->modeldata.attackthrottletime);
                    return 0;
                }
                //ent_set_anim(self, ANI_JUMPATTACK2, 0);
                ani = ANI_JUMPATTACK2;
                self->velocity.x = self->velocity.z = 0;
            }
        }
        else
        {
            rnum = -1;
        }

        if(rnum >= 0)
        {

            self->takeaction = common_jump;
            set_attacking(self);
            self->jumping = 1;
            toss(self, self->modeldata.jumpheight);

            if(self->modeldata.dust.jump_start >= 0)
            {
                dust = spawn(self->position.x, self->position.z, self->position.y, self->direction, NULL, self->modeldata.dust.jump_start, NULL);
                if(dust)
                {
                    dust->spawntype = SPAWN_TYPE_DUST_JUMP;
                    dust->base = self->position.y;
                    dust->autokill |= AUTOKILL_ANIMATION_COMPLETE;
                    execute_onspawn_script(dust);
                }
            }

            ent_set_anim(self, ani, 0);

            return 1;
        }
    }

    return 0;
}

int common_try_grab(entity *other)
{
    //printf("\n\t common_try_grab(%p)", other);

    int trygrab(entity * t);

    /*
    * Note order does matter in the 
    * checks below. usually we don't 
    * have an "other" until it is
    * populated by find_ent_here.
    */

    // old rand()
    if( (rand32() & 7) == 0 &&
            (validanim(self, ANI_THROW) || validanim(self, ANI_GRAB)) && 
            self->idling &&            
            (other || (other = find_ent_here(self, self->position.x, self->position.z, TYPE_ANY, NULL))) &&
            faction_check_is_hostile(self, other) &&
            trygrab(other))
    {
        return 1;
    }

    return 0;
}

// A.I. try upper cut
int common_try_upper(entity *target)
{
    if(!validanim(self, ANI_UPPER))
    {
        return 0;
    }

    if(!target)
    {
        target = normal_find_target(ANI_UPPER, 0);
    }

    // Target jumping? Try uppercut!
    if(target && target->jumping)
    {
        /*self->takeaction = common_attack_proc;
        set_attacking(self);
        self->velocity.z = self->velocity.x = 0;
        // Don't waste any time!
        ent_set_anim(self, ANI_UPPER, 0);*/

        if(!check_attack_chance(target, 1.0f - min_noatk_chance, 1.0f - min_noatk_chance))
        {
            self->nextattack = _time + randf(self->modeldata.attackthrottletime);
            return 0;
        }
        else
        {
            self->stalltime = _time + (int)randf((float)MAX(1, GAME_SPEED * 3 / 4 - self->modeldata.aggression));
        }

        self->takeaction = upper_prepare;
        self->velocity.z = self->velocity.x = 0;
        set_idle(self);
        self->idling = IDLING_NONE; // not really idle, in fact it is thinking
        self->attacking = ATTACKING_PREPARED; // pre-attack, for AI-block check
        return 1;
    }

    return 0;
}

int common_try_duckattack(entity *other)
{
    int indirect;
    entity *target = NULL;

    if(!validanim(self, ANI_DUCKATTACK) || !(self->ducking & DUCK_ACTIVE))
    {
        return 0;
    }

    if(_time / THINK_SPEED % 4 == 0)
    {
        return 0;
    }

    if(self->projectile != BLAST_NONE)
    {
        indirect = 1;
    }

    if(self->custom_target == NULL || !self->custom_target->exists ) target = normal_find_target(-1, 0);
    else target = self->custom_target;

    if(target && !(faction_check_can_damage(self, target, indirect)))
    {
        return 0;
    }

    if(recheck_nextattack(target) > _time)
    {
        return 0;
    }

    if(!target || !(target->ducking & DUCK_ACTIVE))
    {
        return 0;
    }

    if(!check_attack_chance(target, 1.0f - min_noatk_chance, 1.0f - min_noatk_chance))
    {
        self->nextattack = _time + randf(self->modeldata.attackthrottletime);
        return 0;
    }
    else
    {
        self->stalltime = _time + (int)randf((float)MAX(1, GAME_SPEED * 3 / 4 - self->modeldata.aggression));
    }

    // finally attack!
    self->takeaction = common_attack_proc;
    set_attacking(self);
    self->velocity.z = self->velocity.x = 0;
    // Don't waste any time!
    ent_set_anim(self, ANI_DUCKATTACK, 0);
   
    return 1;
}

// Normal attack style
// Used by root A.I., what to do if a target is found.
// return 0 if no action is token
// return 1 if an action is token
int normal_attack()
{
    //int rnum;

    //rnum = rand32()&7;
    if( common_try_duckattack(NULL) ||
        common_try_grab(NULL) ||
        common_try_upper(NULL) ||
        common_try_block(NULL) ||
        common_try_runattack(NULL) ||
        //(rnum < 2 && common_try_freespecial(NULL)) ||
        common_try_normalattack(NULL) ||
        common_try_jumpattack(NULL) )
    {
        self->running = RUN_STATE_NONE;
        return 1;
    }

    return 0;// nothing to do? so go to next think step
}

// A.I. characters do a throw
void common_throw()
{
    if(self->animating)
    {
        return;    // just play the throw animation
    }

    // we have done the throw, return to A.I. root
    self->takeaction = NULL;

    set_idle(self);
}

// toss the grabbed one
void dothrow()
{
    s_attack attack;
    entity *other;
    self->velocity.x = self->velocity.z = 0;
    other = self->link;
    if(other == NULL) //change back to idle, or we will get stuck here
    {
        self->takeaction = NULL;// A.I. root again
        set_idle(self);
        return;
    }

    if(other->modeldata.throwheight)
    {
        toss(other, other->modeldata.throwheight);
    }
    else
    {
        toss(other, other->modeldata.jumpheight);
    }

    other->direction = self->direction;
	other->projectile |= BLAST_TOSS;
	other->projectile |= BLAST_ATTACK;
    other->velocity.x = (other->direction == DIRECTION_RIGHT) ? (-other->modeldata.throwdist) : (other->modeldata.throwdist);

    if(autoland == 1 && validanim(other, ANI_LAND))
    {
        other->damage_on_landing.attack_force = ATTACK_FORCE_LAND_AUTO;
    }
    else
    {
        other->damage_on_landing.attack_force = self->modeldata.throwdamage;
    }

    ent_unlink(other);

    other->takeaction = common_fall;
    self->takeaction = common_throw;

    // Use default attack values.
    attack = emptyattack;
    set_fall(other, self, &attack, 0);
    ent_set_anim(self, ANI_THROW, 0);
}


// Waiting until throw frame reached
void common_throw_wait()
{
    if(!self->link)
    {
        self->takeaction = NULL;// A.I. root again
        set_idle(self);
        return;
    }

    self->releasetime += THINK_SPEED; //extend release time

    if(self->animpos != self->modeldata.throwframewait)
    {
        return;
    }

    dothrow();
}


void common_prethrow()
{
    self->running = RUN_STATE_NONE;    // Quits running if grabbed by opponent

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

    self->takeaction = NULL;// A.I. root again

    set_idle(self);
}

/*
* Caskey, Damon V.
* 2023-05-24 (rewrite of orginal function by Utunnels, unknown date)
* 
* Reposition entity to its parent's location
* and play appropriate animation.
*/
void npc_recall()
{
    entity* const acting_entity = self;

    const s_child_follow* const child_follow = acting_entity->modeldata.child_follow;

    if(!child_follow || !acting_entity->parent)
    {
        return;
    }

    acting_entity->base = acting_entity->parent->base;
    acting_entity->position.x = acting_entity->parent->position.x + (acting_entity->parent->direction == DIRECTION_RIGHT ? child_follow->follow_offset.x : -child_follow->follow_offset.x);
    acting_entity->position.y = acting_entity->parent->position.y + child_follow->recall_offset.y;
    acting_entity->position.z = acting_entity->parent->position.z + child_follow->recall_offset.z;
    
    acting_entity->velocity.x = 0;
    acting_entity->velocity.y = 0;
    acting_entity->velocity.z = 0;

    if (child_follow->recall_animation != ANI_NONE)
    {
        if (validanim(acting_entity, child_follow->recall_animation))
        {
            acting_entity->takeaction = common_spawn;
            ent_set_anim(acting_entity, child_follow->recall_animation, 0);
        }
        else if (validanim(acting_entity, ANI_SPAWN))
        {
            acting_entity->takeaction = common_spawn;
            ent_set_anim(acting_entity, ANI_SPAWN, 0);
        }
    }
}

int adjust_grabposition(entity *ent, entity *other, float dist, int grabin)
{
	float x1;
	float z1;
	float x2;
	float z2;
	float x;

    if(diff(ent->position.y,other->position.y) > T_WALKOFF)
    {
        return 0;
    }
   
	if(diff(ent->base,other->base) > T_WALKOFF)
    {
        return 0;
    }

    if(grabin == 1)
    {
        x1 = ent->position.x;
        z1 = z2 = ent->position.z;
        x2 = ent->position.x + ((other->position.x > ent->position.x) ? dist : -dist);
    }
    else
    {
        x = (ent->position.x + other->position.x) / 2;
        x1 = x + ((ent->position.x >= other->position.x) ? (dist / 2) : (-dist / 2));
        x2 = x + ((other->position.x > ent->position.x) ? (dist / 2) : (-dist / 2));
        z1 = z2 = (ent->position.z + other->position.z) / 2;
    }

    if(0 >= testmove(ent, ent->position.x, ent->position.z, x1, z1) || 0 >= testmove(other, other->position.x, other->position.z, x2, z2))
    {
        return 0;
    }

    ent->position.x = x1;
    ent->position.z = z1;
    other->position.x = x2;
    other->position.z = z2;
    //other->position.y = ent->position.y;
    //other->base = ent->base;

	// Sort order control.
	//
	// If grabback is set (grabback = 1) the grabbed entity's 
	// sort order is forced 1 greater than grappler so grappler 
	// appears behind. Otherwise grabbed is moved one lower, 
	// forcing grappler to appear in front.
	if (ent->modeldata.grabback)
	{
		other->sortid = ent->sortid + 1;
	}
	else if (!ent->modeldata.grabback)
	{
		other->sortid = ent->sortid - 1;
	}

	
    return 1;
}

int dograb(entity *attacker, entity *target, e_dograb_adjustcheck adjustcheck)
{
    /*
    Execute grab action. Added by splitting off trygrab
    so entities can be forced to perform grab by script.
    Damon V. Caskey
    2013-12-30

    Attacker: Entity performing grab.
    target, entity being grabbed.
    */

    int result  = 0; //Output value.
    int pass    = 1; //Adjust pass/fail.

    /* If an adjust check is needed, make sure adjusting did not fail. */
    if(adjustcheck == DOGRAB_ADJUSTCHECK_TRUE)
    {
        pass = adjust_grabposition(attacker, target, attacker->modeldata.grabdistance, 0);
    }

    /* If adjust_grabposition passed (or wasn't needed) perform grab actions. */
    if(pass)
    {
        if(attacker->modeldata.grabflip & 1) // Kratus (10-2021) Make grabflip property accessible by script
        {
            attacker->direction = (attacker->position.x < target->position.x);
        }

        /* Set flags. */
        set_opponent(target, attacker);
        ents_link(attacker, target);
        target->attacking = ATTACKING_NONE;
        attacker->idling = IDLING_NONE;
        attacker->running = RUN_STATE_NONE;
        attacker->ducking = DUCK_NONE;
        attacker->inbackpain = 0;

        /* Stop all movement. */
        attacker->velocity.x = 0;
        attacker->velocity.z = 0;
        target->velocity.x = 0;
        target->velocity.z = 0;

        /* Check for grab animation, otherwise use original throwing system. */
        if(validanim(self, ANI_GRAB))
        {
            if(attacker->modeldata.grabflip & 2) // Kratus (10-2021) Make grabflip property accessible by script
            {
                target->direction = !attacker->direction;
            }
            attacker->attacking = ATTACKING_NONE;
            memset(attacker->combostep, 0, 5 * sizeof(*attacker->combostep));
            target->stalltime = _time + GRAB_STALL;
            attacker->releasetime = _time + (GAME_SPEED / 2);
            target->takeaction = common_grabbed;
            attacker->takeaction = common_grab;
            ent_set_anim(attacker, ANI_GRAB, 0);
            set_pain(target, -1, 0); //set grabbed animation
        }
        else
        {
            /*
            If no throwframewait use original throw code immediately.
            Otherwise use throwframewait.
            */
            if(attacker->modeldata.throwframewait == -1)
            {
                dothrow();
            }
            else
            {
                if(attacker->modeldata.grabflip & 2) // Kratus (10-2021) Make grabflip property accessible by script
                {
                    target->direction = !attacker->direction;
                }

                target->takeaction = common_prethrow;
                attacker->takeaction = common_throw_wait;
                ent_set_anim(attacker, ANI_THROW, 0);
                set_pain(target, -1, 0); // set grabbed animation
            }
        }

        result = 1;
    }

    return result;
}

int trygrab(entity *other)
{
    /*
    Actions after grab possibility check moved to dograb.
    Damon V. Caskey
    2013-12-30

    other: Grab target.
    */

    int result = 0; //return value.

    if(check_cangrab(self, other))
    {
        result = dograb(self, other, DOGRAB_ADJUSTCHECK_TRUE);
    }

    return result;
}

int check_entity_collision(entity *ent, entity *target)
{
    s_hitbox *coords_col_entity_ent;
    s_hitbox *coords_col_entity_target;
    s_collision_entity  *col_entity_ent = NULL;
    s_collision_entity  *col_entity_target = NULL;
    int     x1,
            x2,
            y1,
            y2,
            z1,
            z2;
    int col_entity_ent_instance;
    int     col_entity_ent_pos_x        = 0,
            col_entity_ent_pos_y        = 0,
            col_entity_ent_size_x       = 0,
            col_entity_ent_size_y       = 0,
            col_entity_target_pos_x     = 0,
            col_entity_target_pos_y     = 0,
            col_entity_target_size_x    = 0,
            col_entity_target_size_y    = 0;
    int     zdist = 0;
    int     zdepth1 = 0, zdepth2 = 0;
    int     entity_pushing = ent->modeldata.entitypushing;
    float   PUSH_FACTOR = ent->modeldata.pushingfactor;

    if(ent == target
       || !target->animation->collision_entity
       || !ent->animation->collision_entity
       )
    {
        return 0;
    }

    if(ent->link || target->link)
    {
        return 0;
    }

    int col_entity_target_instance = 0;
    int collision_found = 0;

    if (entity_pushing && !PUSH_FACTOR) PUSH_FACTOR = 1.0f;

    for(col_entity_ent_instance = 0; col_entity_ent_instance < max_collisons; col_entity_ent_instance++)
    {
        col_entity_ent  = ent->animation->collision_entity[ent->animpos]->instance[col_entity_ent_instance];
        coords_col_entity_ent   = col_entity_ent->coords;

        for(col_entity_target_instance = 0; col_entity_target_instance < max_collisons; col_entity_target_instance++)
        {
            col_entity_target          = target->animation->collision_entity[target->animpos]->instance[col_entity_target_instance];
            coords_col_entity_target   = col_entity_target->coords;

            z1      = ent->position.z + ent->movez;
            z2      = target->position.z + target->movez;
            zdist   = 0;

            if(coords_col_entity_ent->z_foreground > coords_col_entity_ent->z_background)
            {
                zdepth1 = (coords_col_entity_ent->z_foreground - coords_col_entity_ent->z_background) / 2;
                z1 += coords_col_entity_ent->z_background + zdepth1;
                zdist += zdepth1;
            }
            else if(coords_col_entity_ent->z_background)
            {
                zdepth1 = coords_col_entity_ent->z_background;
                zdist += coords_col_entity_ent->z_background;
            }

            if(coords_col_entity_target->z_foreground > coords_col_entity_target->z_background)
            {
                zdepth2 = (coords_col_entity_target->z_foreground - coords_col_entity_target->z_background) / 2;
                z2 += coords_col_entity_target->z_background + zdepth2;
                zdist += zdepth2;
            }
            else if(coords_col_entity_target->z_background)
            {
                zdepth2 = coords_col_entity_target->z_background;
                zdist += coords_col_entity_target->z_background;
            }

            if(diff(z1, z2) > zdist)
            {
                continue;
            }

            x1 = (int)ent->position.x + ent->movex;
            z1 = (int)ent->position.z + ent->movez;
            y1 = (int)z1 - ent->position.y;
            x2 = (int)target->position.x + target->movex;
            z2 = (int)target->position.z + target->movez;
            y2 = (int)z2 - target->position.y;

            if(ent->direction == DIRECTION_LEFT)
            {
                col_entity_ent_pos_x   = x1 - coords_col_entity_ent->width;
                col_entity_ent_size_x  = x1 - coords_col_entity_ent->x;
            }
            else
            {
                col_entity_ent_pos_x    = x1 + coords_col_entity_ent->x;
                col_entity_ent_size_x   = x1 + coords_col_entity_ent->width;
            }
            col_entity_ent_pos_y    = y1 + coords_col_entity_ent->y;
            col_entity_ent_size_y   = y1 + coords_col_entity_ent->height;

            if(target->direction == DIRECTION_LEFT)
            {
                col_entity_target_pos_x    = x2 - coords_col_entity_target->width;
                col_entity_target_size_x   = x2 - coords_col_entity_target->x;
            }
            else
            {
                col_entity_target_pos_x    = x2 + coords_col_entity_target->x;
                col_entity_target_size_x   = x2 + coords_col_entity_target->width;
            }
            col_entity_target_pos_y    = y2 + coords_col_entity_target->y;
            col_entity_target_size_y   = y2 + coords_col_entity_target->height;

            if(col_entity_ent_pos_x > col_entity_target_size_x)
            {
                continue;
            }
            if(col_entity_target_pos_x > col_entity_ent_size_x)
            {
                continue;
            }
            if(col_entity_ent_pos_y > col_entity_target_size_y)
            {
                continue;
            }
            if(col_entity_target_pos_y > col_entity_ent_size_y)
            {
                continue;
            }

            // If we got this far, set collision flag
            // and break this loop.
            collision_found = 1;
            break;
        }

        // If a collision was found
        // break out of loop.
        if(collision_found)
        {
            break;
        }
    }

    if(!collision_found)
    {
        return 0;
    }

    // check on axis x
    if(col_entity_ent_pos_x <= col_entity_target_pos_x)
    {
        if (!entity_pushing)
        {
            if (ent->movex > 0) ent->movex = 0;
        }
        else
        {
            // let to escape
            if (ent->collided_entity && ent->collided_entity == target)
            {
                if (ent->movex > 0) ent->movex = 0;
            }
            if (ent->movex != -1 * target->movex || (!target->movex)) ent->movex -= PUSH_FACTOR;
        }
    }
    else
    {
        if (!entity_pushing)
        {
            if (ent->movex < 0) ent->movex = 0;
        }
        else
        {
            // let to escape
            if (ent->collided_entity && ent->collided_entity == target)
            {
                if (ent->movex < 0) ent->movex = 0;
            }
            if (ent->movex != -1 * target->movex || (!target->movex)) ent->movex += PUSH_FACTOR;
        }
    }

    // check on axis z
    if(z1 - zdepth1 <= z2 + zdepth2 &&
       z1 - zdepth1 >= z2)
    {
        if (!entity_pushing)
        {
            if (ent->movez < 0) ent->movez = 0;
        }
        else
        {
            // let to escape
            if (ent->collided_entity && ent->collided_entity == target)
            {
                if (ent->movez < 0) ent->movez = 0;
            }
            if (ent->movez != -1 * target->movez || (!target->movez)) ent->movez += PUSH_FACTOR;
        }
    }
    else if(z1 + zdepth1 >= z2 - zdepth2 &&
            z1 + zdepth1 <= z2)
    {
        if (!entity_pushing)
        {
            if (ent->movez > 0) ent->movez = 0;
        }
        else
        {
            // let to escape
            if (ent->collided_entity && ent->collided_entity == target)
            {
                if (ent->movez > 0) ent->movez = 0;
            }
            if (ent->movez != -1 * target->movez || (!target->movez)) ent->movez -= PUSH_FACTOR;
        }
    }

    // execute event
    execute_onentitycollision_script(ent, target, col_entity_ent, col_entity_target);

    return 1;
}

void check_entity_collision_for(entity* ent)
{
    // Animation has collision?
    if (ent && ent->animation && ent->animation->collision_entity)
    {
        int i;
        for(i = 0; i < ent_max; i++)
        {
            //s_anim *a = ent->animation[ent->animnum];
            entity* target = ent_list[i];
            if(target->exists && target != ent)
            {
                if (check_entity_collision(ent, target))
                {
                    ent->collided_entity = target;
                    target->collided_entity = ent;
                    return;
                }
            }
        }
    }

    ent->collided_entity = NULL;
    return;
}

int common_trymove(float xdir, float zdir)
{
    entity *other = NULL, *te = NULL;
    int wall, heightvar, t, needcheckhole = 0;
    float x, z, oxdir, ozdir;

    if(!xdir && !zdir)
    {
        return 0;
    }

    // UT: move player grab logic here to bypass some extra checks
    // it used to be at the very end of this function
    //------------------ grab/throw checking ------------------

    //printf("rand(): %d rand32(): %d\n",rand(),rand32());
    // old rand()
    if((self->modeldata.type & TYPE_PLAYER) &&
            (rand32() & 7) == 0 &&
            (validanim(self, ANI_THROW) ||
             validanim(self, ANI_GRAB)) && self->idling &&
            (other = find_ent_here(self, self->position.x, self->position.z, self->faction.type_hostile, NULL)))
    {
        if(trygrab(other))
        {
            return 0;
        }
    }
    // ---------------  end of grab/throw checking ------------------------


    oxdir = xdir;
    ozdir = zdir;
    /*
    // entity is grabbed by other
    if(self->link && self->link->grabbing==self && self->link->grabwalking)
    {
    	return 1; // just return so we don't have to check twice
    }*/

    x = self->position.x + xdir;
    z = self->position.z + zdir;
    // -----------bounds checking---------------
    // Subjec to Z and out of bounds? Return to level!
    if (self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_MIN_Z)
    {
        if(z < PLAYER_MIN_Z)
        {
            zdir = PLAYER_MIN_Z - self->position.z;
            execute_onblockz_script(self);
        }
    }

    if (self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_MAX_Z)
    {
        if(z > PLAYER_MAX_Z)
        {
            zdir = PLAYER_MAX_Z - self->position.z;
            execute_onblockz_script(self);
        }
    }

    // screen checking
    if(self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_SCREEN)
    {
        if(x < advancex + 10)
        {
            xdir = advancex + 10 - self->position.x;
            execute_onblocks_script(self);  //Screen block event.
        }
        else if(x > advancex + (videomodes.hRes - 10))
        {
            xdir = advancex + (videomodes.hRes - 10) - self->position.x;
            execute_onblocks_script(self);  //Screen block event.
        }
    }

    if(!xdir && !zdir)
    {
        return 0;
    }
    x = self->position.x + xdir;
    z = self->position.z + zdir;

    //-----------end of bounds checking-----------

    //-------------hole checking ---------------------
    // Don't walk into a hole or walk off platforms into holes
    if( self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_HOLE && !inair(self) && !(self->modeldata.type & TYPE_PLAYER) && self->idling &&
            (!self->animation->move[self->animpos]->base || self->animation->move[self->animpos]->base < 0) &&
            !(self->modeldata.aimove & AIMOVE2_IGNOREHOLES))
    {

        needcheckhole = 1;
        if(zdir && checkhole(self->position.x, z) && checkwall_index(self->position.x, z) < 0 && !check_platform_below(self->position.x, z, self->position.y, self))
        {
            //int holeind = checkholeindex_in(self->position.x, z, self->position.y);

            zdir = 0;
            //execute_inhole_script(self, 2, (double)level->holes[holeind].height, holeind);
        }
        if(xdir && checkhole(x, self->position.z) && checkwall_index(x, self->position.z) < 0 && !check_platform_below(x, self->position.z, self->position.y, self))
        {
            //int holeind = checkholeindex_in(x, self->position.z, self->position.y);

            xdir = 0;
            //execute_inhole_script(self, 1, (double)level->holes[holeind].height, holeind);
        }
    }

    if(!xdir && !zdir)
    {
        return 0;
    }
    x = self->position.x + xdir;
    z = self->position.z + zdir;
    //-----------end of hole checking---------------

    //--------------obstacle checking ------------------
    if(self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_OBSTACLE /*&& !inair(self)*/)
    {
        int hit = 0;

        //TODO, check once instead of twice
        if((other = find_ent_here(self, x, self->position.z, (TYPE_OBSTACLE | TYPE_TRAP), NULL)) &&
                (xdir > 0 ? other->position.x >= self->position.x : other->position.x <= self->position.x) &&
                (!other->animation->platform || !other->animation->platform[other->animpos][PLATFORM_HEIGHT]))
        {
            xdir    = 0;
            if ( self->falling ) hit |= 1;
            te = other;
            execute_onblocko_script(self, PLANE_X, other);
        }
        if((other = find_ent_here(self, self->position.x, z, (TYPE_OBSTACLE | TYPE_TRAP), NULL)) &&
                (zdir > 0 ? other->position.z >= self->position.z : other->position.z <= self->position.z) &&
                (!other->animation->platform || !other->animation->platform[other->animpos][PLATFORM_HEIGHT]))
        {
            zdir    = 0;
            if ( self->falling ) hit |= 1;
            if(te != other) //just in case they are the same obstacle
            {
                execute_onblocko_script(self, PLANE_Z, other);
            }
        }

        if ( hit && !self->hitwall && validanim(self, ANI_HITOBSTACLE) ) ent_set_anim(self, ANI_HITOBSTACLE, 0);
        if ( hit && !self->hitwall ) self->hitwall = 1;
    }

    if(!xdir && !zdir)
    {
        return 0;
    }
    x = self->position.x + xdir;
    z = self->position.z + zdir;

    //-----------end of obstacle checking--------------

    // ---------------- platform checking----------------

    if(self->animation->size.y)
    {
        heightvar = self->animation->size.y;
    }
    else
    {
        heightvar = self->modeldata.size.y;
    }

    // Check for obstacles with platform code and adjust base accordingly
    if(self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_PLATFORM)
    {
        int hit = 0;

        //if(xdir>0 ? other->position.x>self->position.x : other->position.x<self->position.x) {xdir = 0; }
        //if(zdir>0 ? other->position.z>self->position.z : other->position.z<self->position.z) {zdir = 0; }
        //temporary fix for thin platforms (i.e, offset is not between left and right side)
        // TODO: find the collision position, merge with wall code
        if(xdir && (other = check_platform_between(x, self->position.z, self->position.y, self->position.y + heightvar, self))  )
        {
            xdir = 0;
            if ( self->falling ) hit |= 1;
            execute_onblockp_script(self, PLANE_X, other);
        }
        if(zdir && (other = check_platform_between(self->position.x, z, self->position.y, self->position.y + heightvar, self))  )
        {
            zdir = 0;
            if ( self->falling ) hit |= 1;
            execute_onblockp_script(self, PLANE_Z, other);
        }

        if ( hit && !self->hitwall && validanim(self, ANI_HITPLATFORM) ) ent_set_anim(self, ANI_HITPLATFORM, 0);
        if ( hit && !self->hitwall ) self->hitwall = 1;
    }

    if(!xdir && !zdir)
    {
        return 0;
    }
    x = self->position.x + xdir;
    z = self->position.z + zdir;

    //-----------end of platform checking------------------

    // ------------------ wall checking ---------------------
    if(self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_WALL)
    {
        int hit = 0;

        if(xdir && (wall = checkwall_below(x, self->position.z, T_MAX_CHECK_ALTITUDE)) >= 0 && level->walls[wall].height > self->position.y)
        {
            xdir = 0;
            if ( self->falling && (self->modeldata.hitwalltype < 0 || (self->modeldata.hitwalltype >= 0 && level->walls[wall].type == self->modeldata.hitwalltype)) ) hit |= 1;
            execute_onblockw_script(self, &level->walls[wall], PLANE_X, wall);
        }
        if(zdir && (wall = checkwall_below(self->position.x, z, T_MAX_CHECK_ALTITUDE)) >= 0 && level->walls[wall].height > self->position.y)
        {
            zdir = 0;
            if ( self->falling && (self->modeldata.hitwalltype < 0 || (self->modeldata.hitwalltype >= 0 && level->walls[wall].type == self->modeldata.hitwalltype)) ) hit |= 1;
            execute_onblockw_script(self, &level->walls[wall], PLANE_Z, wall);
        }

        if ( hit && !self->hitwall && validanim(self, ANI_HITWALL) ) ent_set_anim(self, ANI_HITWALL, 0);
        if ( hit && !self->hitwall ) self->hitwall = 1;
    }

    if(!xdir && !zdir)
    {
        return 0;
    }
    x = self->position.x + xdir;
    z = self->position.z + zdir;
    //----------------end of wall checking--------------

    /*
    // Final check to ensure we don't move into other obstacles.
    // The old logic allows xdir if zdir is block and vice versa,
    // but has a risk that the new destination is actually blocked
    // because of multiple obstacle types.
    //
    // block  old
    //    |  /
    //    | /
    //    |/__ new
    */
    //xdir = zdir = 0;
    // TODO: should we add some checks in testmove to execute those onblockwhatever scripts?
    t = testmove(self, self->position.x, self->position.z, x, z);
    // extra hole check, only avoid hole while idling
    if(t <= 0 && (t != -2 || needcheckhole))
    {
        return 0;
    }

    // do move and return
    self->position.x += xdir;
    self->position.z += zdir;

    if(xdir)
    {
        execute_onmovex_script(self);    //X move event.
    }
    if(zdir)
    {
        execute_onmovez_script(self);    //Z move event.
    }
    return 2 - (xdir == oxdir && zdir == ozdir); // return 2 for some checks
}

// enemies run off after attack
void common_runoff()
{
    entity *target = normal_find_target(-1, 0);

    if(target == NULL)   //sealth checking
    {
        self->velocity.z = self->velocity.x = 0;
        self->takeaction = NULL; // OK, back to A.I. root
        set_idle(self);
        return;
    }

    if(!(self->modeldata.move_config_flags & MOVE_CONFIG_NO_FLIP))
    {
        self->direction = (self->position.x < target->position.x);
    }
    if(self->direction == DIRECTION_RIGHT)
    {
        self->velocity.x = -self->modeldata.speed.x / 2;
    }
    else
    {
        self->velocity.x = self->modeldata.speed.x / 2;
    }

    self->velocity.z = 0;

    if(_time > self->stalltime)
    {
        self->takeaction = NULL;    // OK, back to A.I. root
    }

    adjust_walk_animation(target);
}


/*void common_stuck_underneath()
{
    int heightvar = self->animation->size.y ? self->animation->size.y : self->modeldata.size.y;
    if(!check_platform_between(self->position.x, self->position.z, self->position.y, self->position.y + heightvar, self) )
    {
        self->takeaction = NULL;
        set_idle(self);
        return;
    }
    if(player[self->playerindex].keys & FLAG_MOVELEFT)
    {
        self->direction = DIRECTION_LEFT;
    }
    else if(player[self->playerindex].keys & FLAG_MOVERIGHT)
    {
        self->direction = DIRECTION_RIGHT;
    }
    if(player[self->playerindex].playkeys & FLAG_ATTACK && validanim(self, ANI_DUCKATTACK))
    {
        player[self->playerindex].playkeys &= ~FLAG_ATTACK;
        self->takeaction = common_attack_proc;
        set_attacking(self);
        self->velocity.x = self->velocity.z = 0;
        self->combostep[0] = 0;
        self->running = RUN_STATE_NONE;
        ent_set_anim(self, ANI_DUCKATTACK, 0);
        return;
    }
    if((player[self->playerindex].keys & FLAG_MOVEDOWN) && (player[self->playerindex].playkeys & FLAG_JUMP) && validanim(self, ANI_SLIDE))
    {
        player[self->playerindex].playkeys &= ~FLAG_JUMP;
        self->takeaction = common_attack_proc;
        set_attacking(self);
        self->velocity.x = self->velocity.z = 0;
        self->combostep[0] = 0;
        self->running = RUN_STATE_NONE;
        ent_set_anim(self, ANI_SLIDE, 0);
        return;
    }
}*/


// finish attacking, do something
void common_attack_finish()
{
    entity *target;
    int stall;

    self->velocity.x = self->velocity.z = 0;

    if(self->modeldata.type & TYPE_PLAYER)
    {
        if (self->ducking & DUCK_ACTIVE)
        {
            doduck(self);
        }
        else
        {
            self->takeaction = NULL;
            set_idle(self);
        }
        return;
    }

    target = self->opponent;

    if(target && !(self->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE) && self->ducking == DUCK_NONE && diff(self->position.x, target->position.x) < 80 && (rand32() & 3))
    {
        self->takeaction = NULL;//common_runoff;
        self->destx = self->position.x > target->position.x ? MIN(self->position.x + 40, target->position.x + 80) : MAX(self->position.x - 40, target->position.x - 80);
        self->destz = self->position.z;
        self->velocity.x = self->position.x > target->position.x ? self->modeldata.speed.x : -self->modeldata.speed.x;
        self->velocity.z = 0;
        adjust_walk_animation(target);
        self->idling = IDLING_PREPARED;
    }
    else
    {
        if (self->ducking & DUCK_ACTIVE)
        {
            doduck(self);
        }
        else
        {
            self->takeaction = NULL;
            set_idle(self);
        }
    }

    stall = GAME_SPEED - self->modeldata.aggression;
    if (stall < GAME_SPEED / 2)
    {
        stall = GAME_SPEED / 2;
    }
    self->stalltime = _time + MAX(0, stall);
}

//while playing any simple animation
void common_animation_normal()
{
    if(self->animating)
    {
        return;
    }

    self->takeaction = NULL;
    set_idle(self);

    return;
}

//while playing attack animation
void common_attack_proc()
{
    if(self->animating || diff(self->position.y, self->base) >= 4)
    {
        return;
    }

    if(self->tocost)
    {
       
		// Enemy was hit with a special so go ahead and subtract life
        if(check_energy(ENERGY_TYPE_MP, self->animnum))
        {
            self->energy_state.mp_current -= self->animation->energy_cost.cost;
        }
        else
        {
            self->energy_state.health_current -= self->animation->energy_cost.cost;
        }
        
        self->tocost = 0;    // Life is subtracted, so go ahead and reset the flag
    }

    if(self == smartbomber)
    {
        // Player is done with the special animation, so unfreeze and execute a smart bomb
        smart_bomb(self, self->modeldata.smartbomb);
        smartbomber = NULL;
    }
    if(self->weapon_state & WEAPON_STATE_DEDUCT_USE)
    {
        subtract_shot();
        self->weapon_state &= ~WEAPON_STATE_DEDUCT_USE;
    }
    self->attacking = ATTACKING_NONE;
    // end of attack proc
    common_attack_finish();
}


// dispatch A.I. attack
int common_attack()
{
    int aiattack;

    //if(stalker==self) return 0;

    if(_time / THINK_SPEED % 4 == 0)
    {
        return 0;
    }

    if(self->modeldata.aiattack == -1)
    {
        return 0;
    }

    aiattack = self->modeldata.aiattack & MASK_AIATTACK1;

    switch(aiattack)
    {
    case AIATTACK1_LONG:
    case AIATTACK1_MELEE:
    case AIATTACK1_NOATTACK:
        return 0;
    default:                    // this is the only available attack style by now
        return inair(self) ? 0 : normal_attack();
    }
}

//maybe used many times, so make a function
// A.I. characters will check if there's a wall infront, and jump onto it if possible
// return 1 if jump
int common_try_jump()
{
#define COMMON_TRY_JUMP_DEFAULT 1
#define COMMON_TRY_JUMP_RUN 2

    float xdir = 0.0;
    float zdir = 0.0;
    int wall = 0;
    int to_jump = 0;
    float rmin = 0.0;
    float rmax = 0.0;
    float initial_z_velocity = 0.0;

    e_animations jump_animation = ANI_JUMP;

    /*
    * If we can't jump at all, return false now.
    */

    if (self->modeldata.air_control & AIR_CONTROL_JUMP_DISABLE)
    {
        return 0;
    }

    if(validanim(self, ANI_JUMP))
    {        
        xdir = 0;
        wall = -1;
        rmin = (float)self->modeldata.animation[ANI_JUMP]->range.x.min;
        rmax = (float)self->modeldata.animation[ANI_JUMP]->range.x.max;
        
        if(self->direction == DIRECTION_RIGHT)
        {
            xdir = self->position.x + rmin;
        }
        else
        {
            xdir = self->position.x - rmin;
        }

        /*
        * If we can Z jump, apply velocity to ZDir.
        * This means our checks below will have a
        * bit of lateral (z) range.
        */

        if(self->modeldata.air_control & AIR_CONTROL_JUMP_Z_INITIAL)
        {
            zdir = self->position.z + self->velocity.z;
        }
        else
        {
            zdir = self->position.z;
        }

        /* Check for obstruction in range of JUMP. */

        if( (wall = checkwall_below(xdir, zdir, T_MAX_CHECK_ALTITUDE)) >= 0 &&
                level->walls[wall].height <= self->position.y + rmax &&
                !inair(self) && self->position.y < level->walls[wall].height  )
        {
            to_jump = COMMON_TRY_JUMP_DEFAULT;
        }
        else if(checkhole(self->position.x + (self->direction == DIRECTION_RIGHT ? 2 : -2), zdir) &&
                checkwall_index(self->position.x + (self->direction == DIRECTION_RIGHT ? 2 : -2), zdir) < 0 &&
                check_platform (self->position.x + (self->direction == DIRECTION_RIGHT ? 2 : -2), zdir, self) == NULL &&
                !checkhole(self->position.x + (self->direction == DIRECTION_RIGHT ? rmax : -rmax), zdir))
        {
            to_jump = COMMON_TRY_JUMP_DEFAULT;
        }
    }

    /*
    * Caskey, Damon V.
    * 2010-03-29 
    * 
    * AI will check its RUNJUMP range if JUMP 
    * check failed. Code is pretty redundant, 
    * should probably move to a function later.
    */

    if(!to_jump && validanim(self, ANI_RUNJUMP))														
    {
        xdir = 0;
        wall = -1;
        rmin = (float)self->modeldata.animation[ANI_RUNJUMP]->range.x.min;
        rmax = (float)self->modeldata.animation[ANI_RUNJUMP]->range.x.max;
        
        if(self->direction == DIRECTION_RIGHT)
        {
            xdir = self->position.x + rmin;
        }
        else
        {
            xdir = self->position.x - rmin;
        }
        
        /* 
        * If we can Z jump, apply velocity to ZDir.
        * This means our checks below will have a
        * bit of lateral (z) range.
        */ 

        if(self->modeldata.air_control & AIR_CONTROL_JUMP_Z_INITIAL)
        {
            zdir = self->position.z + self->velocity.z;
        }
        else
        {
            zdir = self->position.z;
        }

        /* Check for obstruction in range of RUNJUMP. */
        if( (wall = checkwall_below(xdir, zdir, T_MAX_CHECK_ALTITUDE)) >= 0 &&
                level->walls[wall].height <= self->position.y + rmax &&
                !inair(self) && self->position.y < level->walls[wall].height  )
        {
            to_jump = COMMON_TRY_JUMP_RUN;
        }        
        else if(checkhole(self->position.x + (self->direction == DIRECTION_RIGHT ? 2 : -2), zdir) &&
                checkwall_index(self->position.x + (self->direction == DIRECTION_RIGHT ? 2 : -2), zdir) < 0 &&
                check_platform (self->position.x + (self->direction == DIRECTION_RIGHT ? 2 : -2), zdir, self) == NULL &&
                !checkhole(self->position.x + (self->direction == DIRECTION_RIGHT ? rmax : -rmax), zdir))
        {
            to_jump = COMMON_TRY_JUMP_RUN;																				
        }
    }

    /*
    * Now select the appropriate animation
    * and send data to tryjump.
    */

    if(to_jump)
    {
        if(self->running || to_jump == COMMON_TRY_JUMP_RUN)
        {
            if(validanim(self, ANI_RUNJUMP))														
            {
                jump_animation = ANI_RUNJUMP;
            }
            else if(validanim(self, ANI_FORWARDJUMP))
            {
                jump_animation = ANI_FORWARDJUMP;
            }
            else
            {
                jump_animation = ANI_JUMP;
            }
        }
        else
        {
            if(validanim(self, ANI_FORWARDJUMP))
            {
                jump_animation = ANI_FORWARDJUMP;
            }
            else
            {
                jump_animation = ANI_JUMP;
            }
        }

        if (self->modeldata.air_control & AIR_CONTROL_JUMP_Z_INITIAL)
        {
            initial_z_velocity = self->velocity.z;
        }
        else
        {
            initial_z_velocity = 0.0;
        }

        tryjump(self->modeldata.runjumpheight, self->modeldata.jumpspeed* self->modeldata.runjumpdist, initial_z_velocity, jump_animation);

        return 1;
    }
    return 0;

#undef COMMON_TRY_JUMP_DEFAULT
#undef COMMON_TRY_JUMP_RUN
}

//test if direction is available for anim_up
static int testup(float xd, float zd)
{
    float f;
    if(zd < 0)
    {
        if(!xd)
        {
            return 1;
        }
        f = zd / xd;
        return (f > 0.5 || f < -0.5);
    }
    return 0;
}

//test if direction is available for anim_down
static int testdown(float xd, float zd)
{
    float f;
    if(zd > 0)
    {
        if(!xd)
        {
            return 1;
        }
        f = zd / xd;
        return (f > 0.5 || f < -0.5);
    }
    return 0;
}

void adjust_walk_animation(entity *other)
{
    int dir = 0;
    if(self->running)
    {
        if (validanim(self, ANI_BACKRUN))
        {
            if(is_in_backrun(self)) ent_set_anim(self, ANI_BACKRUN, 0);
            else ent_set_anim(self, ANI_RUN, 0);
        }
        else ent_set_anim(self, ANI_RUN, 0); // Set to run animation if exists
        return;
    }

    //reset the walk animation
    if(validanim(self, ANI_UP) && ((!other && testup(self->velocity.x, self->velocity.z)) || (other && testup(other->position.x - self->position.x, other->position.z - self->position.z))))
    {
        common_up_anim(self); //ent_set_anim(self, ANI_UP, 0);
        dir = 2;
    }
    else if(validanim(self, ANI_DOWN) && ((!other && testdown(self->velocity.x, self->velocity.z)) || (other && testdown(other->position.x - self->position.x, other->position.z - self->position.z))))
    {
        common_down_anim(self); //ent_set_anim(self, ANI_DOWN, 0);
        dir = 3;
    }
    else if((self->direction == DIRECTION_RIGHT ? self->velocity.x < 0 : self->velocity.x > 0) && validanim(self, ANI_BACKWALK))
    {
        common_backwalk_anim(self);    //ent_set_anim(self, ANI_BACKWALK, 0);
    }
    else
    {
        common_walk_anim(self); //ent_set_anim(self, ANI_WALK, 0);
        dir = 1;
    }

    if(((self->direction == DIRECTION_RIGHT ? self->velocity.x < 0 : self->velocity.x > 0) && dir == 1) ||
            (dir == 2 && self->velocity.z > 0) || (dir == 3 && self->velocity.z < 0) )
    {
        self->animating = ANIMATING_REVERSE;
    }
    else
    {
        self->animating = ANIMATING_FORWARD;
    }
}


// ai character try to move towards the item
// TODO, check path or entity might get stuck under a wall
int common_try_pick(entity *other)
{
    // if there's an item to pick up, move towards it.
    float maxspeed = self->modeldata.speed.x * 1.5;
    float dx = diff(self->position.x, other->position.x);
    float dz = diff(self->position.z, other->position.z);

    if(other == NULL || self->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
    {
        return 0;
    }

    if(!dz && !dx)
    {
        self->velocity.x = self->velocity.z = 0;
        self->destz = self->position.z;
        self->destx = self->position.x;
    }
    else
    {
        self->velocity.x = maxspeed * dx / (dx + dz);
        self->velocity.z = maxspeed * dz / (dx + dz);
        self->destx = other->position.x;
        self->destz = other->position.z;
    }
    if(self->position.x > other->position.x)
    {
        self->velocity.x = -self->velocity.x;
    }
    if(self->position.z > other->position.z)
    {
        self->velocity.z = -self->velocity.z;
    }

    self->running = RUN_STATE_NONE;

    adjust_walk_animation(other);

    return 1;
}

#define astarw 640
#define astarh 360
#define starts (astarw*astarh)

// not so completed pathfinding logic based on a*
// it should be fairly slow due to the complicacy of terrain checking
// and it doesn't always work since walking from wall to wall
// requires jump.
int astar(entity *ent, float destx, float destz, float step, s_axis_plane_lateral_float **wp)
{
    int (*came_from)[astarw][astarh][2] = malloc(sizeof(*came_from));
    unsigned char (*closed)[astarw][astarh] = malloc(sizeof(*closed));
    int (*openset)[starts][2] = malloc(sizeof(*openset));
    float (*gscore)[astarw][astarh] = malloc(sizeof(*gscore));
    float (*hscore)[astarw][astarh] = malloc(sizeof(*hscore));
    float (*fscore)[astarw][astarh] = malloc(sizeof(*fscore));
    int opensize = 0;
    int result = 0, mi = 0;
    float tg, minf;
    int x, z, i, j, tx, tz, better;
    static int vx[8] = {0, 1, 1, 1, 0, -1, -1, -1}, vz[8] = { -1, -1, 0, 1, 1, 1, 0, -1};
    static float score[8] = {1.0, 1.4, 1.0, 1.4, 1.0, 1.4, 1.0, 1.4};

    int sx = astarw / 2, sz = astarh / 2;
    int dx = sx + (destx - ent->position.x) / step, dz = sz + (destz - ent->position.z) / step;

    *wp = NULL;
    if(dx < 0 || dx >= astarw || dz < 0 || dz >= astarh)
    {
        goto pfclearup;
    }
    memset(closed, 0, sizeof(*closed));
    (*openset)[opensize][0] = sx;
    (*openset)[opensize][1] = sz;
    opensize++;
    memset(came_from, 0, sizeof(*came_from));

    (*gscore)[sx][sz] = 0;
    (*hscore)[sx][sz] = diff(dx, sx) + diff(dz, sz);
    (*fscore)[sx][sz] = (*gscore)[sx][sz] + (*hscore)[sx][sz];
    (*came_from)[sx][sz][0] = -1;

    while(opensize > 0)
    {
        minf = 9999999;
        for(j = 0; j < opensize; j++)
        {
            x = (*openset)[j][0];
            z = (*openset)[j][1];
            if((*fscore)[x][z] < minf)
            {
                minf = (*fscore)[x][z];
                mi = j;
            }
        }

        x = (*openset)[mi][0];
        z = (*openset)[mi][1];
        if(x == dx && z == dz)
        {
            do
            {
                tx = (*came_from)[x][z][0];
                tz = (*came_from)[x][z][1];
                result++;
                x = tx;
                z = tz;
            }
            while(x >= 0);
            *wp = malloc(sizeof(*wp) * result);
            tx = (*came_from)[dx][dz][0];
            tz = (*came_from)[dx][dz][1];
            j = 0;
            while(tx >= 0)
            {
                (*wp)[j].x = (tx - sx) * step + ent->position.x;
                (*wp)[j].z = (tz - sz) * step + ent->position.z;
                x = (*came_from)[tx][tz][0];
                z = (*came_from)[tx][tz][1];
                tx = x;
                tz = z;
                j++;
            }
            goto pfclearup;
        }

        (*openset)[mi][0] = (*openset)[opensize - 1][0];
        (*openset)[mi][1] = (*openset)[opensize - 1][1];

        opensize--;
        (*closed)[x][z] = 1;

        for(i = 0; i < 8; i++)
        {
            tx = x + vx[i];
            tz = z + vz[i];

            if(tx < 0 || tx >= astarw || tz < 0 || tz >= astarh)
            {
                continue;
            }
            if((*closed)[tx][tz])
            {
                continue;
            }

            if(!testmove(ent, (x - sx)*step + ent->position.x, (z - sz)*step + ent->position.z,  (tx - sx)*step + ent->position.x, (tz - sz)*step + ent->position.z))
            {
                // (*closed)[tx][tz] = 1; // don't add that to close list just in case the entity can jump
                continue;
            }

            tg = (*gscore)[x][z] + score[i];

            for(j = 0; j < opensize; j++)
            {
                if((*openset)[j][0] == tx && (*openset)[j][1] == tz)
                {
                    break;
                }
            }

            if(j == opensize)
            {
                (*openset)[opensize][0] = tx;
                (*openset)[opensize][1] = tz;
                opensize++;
                better = 1;
            }
            else if (tg < (*gscore)[tx][tz])
            {
                better = 1;
            }
            else
            {
                better = 0;
            }

            if(better)
            {
                (*came_from)[tx][tz][0] = x;
                (*came_from)[tx][tz][1] = z;
                (*gscore)[tx][tz] = tg;
                (*hscore)[tx][tz] = diff(tx, dx) + diff(tz, dz);
                (*fscore)[tx][tz] = (*gscore)[tx][tz] + (*hscore)[tx][tz];
            }
        }
    }

pfclearup:
    if(came_from)
    {
        free(came_from);
    }
    came_from = NULL;
    if(closed)
    {
        free(closed);
    }
    closed = NULL;
    if(openset)
    {
        free(openset);
    }
    openset = NULL;
    if(gscore)
    {
        free(gscore);
    }
    gscore = NULL;
    if(hscore)
    {
        free(hscore);
    }
    hscore = NULL;
    if(fscore)
    {
        free(fscore);
    }
    fscore = NULL;

    return result;
}


// use this after a wall checking meets
// wall sliding code
// whichside:
//      0
//  1       3
//      2
int adjustdirection(float coords[], float offx, float offz, float ox, float oz, float xdir, float zdir, float *cxdir, float *czdir)
{
    float x[4], z[4];
    int whichside, i;
    float a;

    for(i = 0; i < 4; i++)
    {
        x[i] = coords[2 + i] + coords[0] + offx;
    }
    z[1] = z[3] = coords[1] + offz;
    z[0] = z[2] = z[1] - coords[6];

    if(oz <= z[0])
    {
        whichside = 0;
    }
    else if(oz >= z[1])
    {
        whichside = 2;
    }
    else if(ox < x[2])
    {
        whichside = 1;
    }
    else
    {
        whichside = 3;
    }

    if(whichside == 0 || whichside == 2)
    {
        *cxdir = xdir;
        *czdir = 0;
    }
    else
    {
        if((x[0] == x[1] && whichside == 1) || (x[2] == x[3] && whichside == 3))
        {
            *cxdir = 0;
            *czdir = zdir;
        }
        else
        {
            if(whichside == 1)
            {
                a = (z[1] - z[0]) / (x[1] - x[0]);
            }
            else
            {
                a = (z[3] - z[2]) / (x[3] - x[2]);
            }

            *cxdir = xdir + zdir / a;
            *czdir = a * xdir + zdir;

            a = (ABS(xdir) + ABS(zdir)) / (ABS(*cxdir) + ABS(*czdir)) ;
            *cxdir *= a;
            *czdir *= a;
        }
    }
    //printf("%f, %f, %f, %f, %d\n", xdir, zdir, *cxdir, *czdir, whichside);
    return whichside;
}

// adjust walk speed for entity assuming it walks straight forward
// x, z - current position
// tx, tz - target position
// speed - max speed
// xdir, zdir - return values
void adjustspeed(float speed, float x, float z, float tx, float tz, float *xdir, float *zdir)
{
    float xd, zd;
    float dx = diff(tx, x);
    float dz = diff(tz, z) * 2;

    if(dx > dz)
    {
        xd = speed;
        zd = xd / dx * dz;
    }
    else if(dz > dx)
    {
        zd = speed;
        xd = zd / dz * dx;
    }
    else if(dx)
    {
        xd = zd = speed;
    }
    else
    {
        xd = zd = 0;
    }

    if(tx < x)
    {
        xd = -xd;
    }
    if(tz < z)
    {
        zd = -zd;
    }

    zd /= 2;

    *xdir = xd;
    *zdir = zd;

}

int checkpathblocked()
{
    float x, z, r;
    int aitype, wpc;
    entity *target;
	s_axis_plane_lateral_float *wp;
    if(self->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
    {
        return 0;
    }
    if(self->stalltime >= _time)
    {
        aitype = self->modeldata.aimove;
        if(self->modeldata.subtype == SUBTYPE_CHASE)
        {
            aitype |= AIMOVE1_CHASE;
        }

        //be moo tolerable to PLAYER_MAX_Z and PLAYER_MIN_Z
        if((self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_MAX_Z && self->velocity.z > 0 && !self->velocity.x && self->velocity.z + self->position.z > PLAYER_MAX_Z) ||
                (self->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_MIN_Z && self->velocity.z < 0 && !self->velocity.x && self->velocity.z + self->position.z < PLAYER_MIN_Z) )
        {
            self->velocity.z = -self->velocity.z;
            self->pathblocked = 0;
            self->destz = self->velocity.z > 0 ? (PLAYER_MIN_Z + videomodes.vRes / 10) : (PLAYER_MAX_Z - videomodes.vRes / 10);
            adjust_walk_animation(NULL);
            return 1;
        }

        if(self->pathblocked > 40 || (self->pathblocked > 20 && (aitype & (AIMOVE1_CHASEX | AIMOVE1_CHASEZ | AIMOVE1_CHASE))))
        {
            if(self->modeldata.pathfindstep > 0)
            {
                target = normal_find_target(-1, 0);

                if(target)
                {
                    //printf("pathfind: (%f %f)-(%f %f) %d steps\n", self->position.x, self->position.z, self->destx, self->destz, pathfind(self, self->destx, self->destz));
                    if((wpc = astar(self, target->position.x, target->position.z, self->modeldata.pathfindstep, &wp)) > 0)
                    {
                        //printf("wp %d\n", wp);
                        self->numwaypoints = wpc;
                        if(self->waypoints)
                        {
                            free(self->waypoints);
                        }
                        self->waypoints = wp;
                        self->destx = self->waypoints[self->numwaypoints - 1].x;
                        self->destz = self->waypoints[self->numwaypoints - 1].z;
                        self->numwaypoints--;
                        self->pathblocked = 0;
                        return 1;
                    }
                }
            }

            x = self->velocity.x;
            z = self->velocity.z;
            if(x > 0)
            {
                x = self->modeldata.speed.x;
            }
            else if(x < 0)
            {
                x = -self->modeldata.speed.x;
            }
            if(z > 0)
            {
                z = self->modeldata.speed.x / 2;
            }
            else if(z < 0)
            {
                z = -self->modeldata.speed.x / 2;
            }
            r = randf(1);
            if(r > 0.6f)
            {
                self->velocity.z = x;
                self->velocity.x = -z;
            }
            else if(r > 0.2f)
            {
                self->velocity.z = -x;
                self->velocity.x = z;
            }
            else
            {
                self->velocity.z = (1.0f - randf(2)) * self->modeldata.speed.x / 2;
                self->velocity.x = (1.0f - randf(2)) * self->modeldata.speed.x;
            }
            self->running = RUN_STATE_NONE; // TODO: re-adjust walk speed
            self->stalltime = _time + GAME_SPEED / 2;
            adjust_walk_animation(NULL);
            self->pathblocked = 0;

            if(self->velocity.z > 0)
            {
                self->destz = self->position.z + 40;
            }
            else if(self->velocity.z < 0)
            {
                self->destz = self->position.z - 40;
            }
            else
            {
                self->destz = self->position.z;
            }
            if(self->velocity.x > 0)
            {
                self->destx = self->position.x + 40;
            }
            else if(self->velocity.x < 0)
            {
                self->destx = self->position.x - 40;
            }
            else
            {
                self->destx = self->position.x;
            }

            return 1;

        }
    }
    return 0;
}


// this is the most aggressive aimove pattern
// the entity will try get in and attack at anytime
// though the range depends on what attack you setup
int common_try_chase(entity* acting_entity, const entity *target, const bool axis_x, const bool axis_z)
{
    // start chasing the target
    float dx, dz, range;
    int randomatk;

    acting_entity->running = RUN_STATE_NONE;

    //adjustspeed(acting_entity->modeldata.speed.x, acting_entity->position.x, acting_entity->position.z, acting_entity->position.x + acting_entity->velocity.x, acting_entity->position.z + acting_entity->velocity.z, &acting_entity->velocity.x, &acting_entity->velocity.z);

    if(target == NULL || acting_entity->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
    {
        return 0;
    }

    randomatk = pick_random_attack(NULL, 0);

    if(randomatk >= 0)
    {
        range = (acting_entity->modeldata.animation[randomatk]->range.x.min + acting_entity->modeldata.animation[randomatk]->range.x.max) / 2;
        //printf("range picked: ani %d, range %f\n", randomatk, range);
        if(range < 0)
        {
            range = acting_entity->modeldata.grabdistance;
        }
        else if(range > videomodes.hRes / 4)
        {
            range = videomodes.hRes / 4;
        }
    }
    else
    {
        range = acting_entity->modeldata.grabdistance;
    }

    if(axis_x)
    {
        if(acting_entity->position.x > target->position.x)
        {
            acting_entity->destx = target->position.x + range - 1;
        }
        else
        {
            acting_entity->destx = target->position.x - range + 1;
        }
        dx = diff(acting_entity->position.x, acting_entity->destx);

        if(dx > 150 && validanim(acting_entity, ANI_RUN))
        {
            acting_entity->velocity.x = acting_entity->modeldata.runspeed;
            acting_entity->running &= ~RUN_STATE_START_Z;
            acting_entity->running |= RUN_STATE_START_X;
        }
        else
        {
            acting_entity->velocity.x = acting_entity->modeldata.speed.x;
        }
        if(acting_entity->destx < acting_entity->position.x)
        {
            acting_entity->velocity.x = -acting_entity->velocity.x;
        }

        ////////////
    }

    if(axis_z)
    {
        acting_entity->destz = target->position.z ;
        dz = diff(acting_entity->position.z, acting_entity->destz);

        if(dz > 100 && acting_entity->modeldata.runupdown && validanim(acting_entity, ANI_RUN))
        {
            acting_entity->velocity.z = acting_entity->modeldata.runspeed / 2;
            acting_entity->running &= ~RUN_STATE_START_X;
            acting_entity->running |= RUN_STATE_START_Z;
        }
        else
        {
            acting_entity->velocity.z = acting_entity->modeldata.speed.x / 2;
        }

        if(acting_entity->destz < acting_entity->position.z)
        {
            acting_entity->velocity.z = -acting_entity->velocity.z;
        }
    }

    return 1;
}

/*
* Caskey, Damon V.
* 2023-05-24 - Rewrite of orginal function by uTunnels, unknown date.
* 
* Acting entity attempts to follow
* target entity allong allowed axis.
*/
int common_try_follow(entity* const acting_entity, const entity *target, const bool axis_x, const bool axis_z)
{   
    //printf("\n\n common_try_follow(%p, %p, %d, %d)", acting_entity, target, axis_x, axis_z);

    const s_child_follow* const child_follow = acting_entity->modeldata.child_follow;

    if(acting_entity == NULL || target == NULL || !child_follow || acting_entity->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
    {
        return 0;
    }

    enum e_follow_state
    {
        FOLLOW_STATE_NONE = 0,
        FOLLOW_STATE_RUN_X = (1 << 0),
        FOLLOW_STATE_RUN_Z = (1 << 1),
        FOLLOW_STATE_X = (1 << 2),
        FOLLOW_STATE_Z = (1 << 3)
    } follow_state = FOLLOW_STATE_NONE;

    float follow_speed = 0;
    e_run_state run_state = RUN_STATE_NONE;
    
    /*
    * Target positon, distance to target 
    * entity, and if acting entity is 
    * facing the target.
    */

    const s_axis_principal_float target_pos = {
        .x = target->position.x + (target->direction == DIRECTION_RIGHT ? child_follow->follow_offset.x : -child_follow->follow_offset.x),
        .y = target->position.y + child_follow->follow_offset.y,
        .z = target->position.z + child_follow->follow_offset.z
    };
    
    const float distance_x = diff(acting_entity->position.x, target_pos.x);
    const float distance_z = diff(acting_entity->position.z, target_pos.z);

    const int facing_target = (acting_entity->direction == DIRECTION_RIGHT ? acting_entity->position.x < target_pos.x : acting_entity->position.x > target_pos.x);

    /*
    * Set up follow/run ranges. The use of 
    * idle range min value for min/max range 
    * defaults is for legacy compatability.
    */

    const int valid_idle = validanim(acting_entity, ANI_IDLE);
    const int follow_range_default_x = (valid_idle) ? acting_entity->modeldata.animation[ANI_IDLE]->range.x.min : 100;
    const int follow_range_default_z = (int)follow_range_default_x * 0.5;
    const int follow_range_default_run = follow_range_default_x + 100;

    const s_range follow_range = {
        .x = {
            .max = (child_follow->follow_range.x.max == MAX_INT && valid_idle) ? follow_range_default_x : child_follow->follow_range.x.max,
            .min = (child_follow->follow_range.x.min == MIN_INT && valid_idle) ? -follow_range_default_x : child_follow->follow_range.x.min
        },
        .z = {
            .max = (child_follow->follow_range.z.max == MAX_INT && valid_idle) ? follow_range_default_z : child_follow->follow_range.z.max,
            .min = (child_follow->follow_range.z.min == MIN_INT && valid_idle) ? -follow_range_default_z : child_follow->follow_range.z.min
        }
    };

    const s_range follow_run_range = {
        .x = {
            .max = child_follow->follow_run_range.x.max == MAX_INT ? follow_range_default_run : child_follow->follow_run_range.x.max,
            .min = child_follow->follow_run_range.x.min == MIN_INT ? -follow_range_default_run : child_follow->follow_run_range.x.min
        },        

        //.y = 
        //    .max = child_follow->follow_run_range.y.max == MAX_INT ? (int)videomodes.vRes * 0.90 : child_follow->follow_run_range.y.max,
        //    .min = child_follow->follow_run_range.y.min == MIN_INT ? (int)videomodes.vRes * -0.90 : child_follow->follow_run_range.y.min
        //},

        .z = {
            .max = child_follow->follow_run_range.z.max == MAX_INT ? follow_range_default_run : child_follow->follow_run_range.z.max,
            .min = child_follow->follow_run_range.z.min == MIN_INT ? -follow_range_default_run : child_follow->follow_run_range.z.min
        }
    };

    //printf("\n\t follow_range.x.min: %d", follow_range.x.min);
    //printf("\n\t follow_range.x.max: %d", follow_range.x.max);
    //printf("\n\t follow_range.z.min: %d", follow_range.z.min);
    //printf("\n\t follow_range.z.max: %d", follow_range.z.max);
    

    /*
    * Let's try to follow on each axis. If ranges
    * are not reversed, then we check to see if
    * target is OUTSIDE the range, and if so, then
    * set a flag to run downstream movement logic
    * to pursue target. We do this for both walking 
    * and running.
    */

    if (axis_x)
    {
        if ((follow_range.x.min <= follow_range.x.max)
            && !check_range_target_x(acting_entity, target, NULL, follow_range.x.min, follow_range.x.max)) {

            follow_state |= FOLLOW_STATE_X;
        }

        /* Following and able to run? */
        if (follow_state & FOLLOW_STATE_X && validanim(acting_entity, ANI_RUN)
            && facing_target
            && (follow_run_range.x.min <= follow_run_range.x.max)
            && !check_range_target_x(acting_entity, target, NULL, follow_run_range.x.min, follow_run_range.x.max)) {

            follow_state |= FOLLOW_STATE_RUN_X;
        }        
    }

    if (axis_z)
    {
        if ((follow_range.z.min <= follow_range.z.max)
            && !check_range_target_z(acting_entity, target, NULL, follow_range.z.min, follow_range.z.max)) {

            follow_state |= FOLLOW_STATE_Z;
        }

        /* Following and able to run? */
        if (follow_state & FOLLOW_STATE_Z && validanim(acting_entity, ANI_RUN)
            && facing_target
            && (follow_run_range.z.min <= follow_run_range.z.max)
            && !check_range_target_z(acting_entity, target, NULL, follow_run_range.z.min, follow_run_range.z.max)) {

            follow_state |= FOLLOW_STATE_RUN_Z;
        }
    }

    //printf("\n\n acting_entity: %p", acting_entity);
    //printf("\n target_pos.x: %f", target_pos.x);
    //printf("\n target_pos.z: %f", target_pos.z);
    //printf("\n distance_x: %f", distance_x);
    //printf("\n distance_max: %f", distance_max);
    //printf("\n distance_x: %f", distance_x);
    //printf("\n distance_z: %f", distance_z);      
    
    //printf("\n\t follow_state: %d", follow_state);
    //printf("\n\t axis_x: %d", axis_x);
    //printf("\n\t axis_z: %d", axis_z);

    /*
    * If we are farther from target on the 
    * X axis than the Z axis, we will try 
    * to initialize running toward target 
    * on X and veer toward target on Z. If 
    * we are farther on Z axis, then we'll 
    * do the opposite.
    */

    if (distance_x > distance_z) {

        if (follow_state & FOLLOW_STATE_X) {

            if (follow_state & FOLLOW_STATE_RUN_X) {

                if (acting_entity->direction == DIRECTION_LEFT                    
                    && (acting_entity->modeldata.run_config_flags & (RUN_CONFIG_X_LEFT_INITIAL | RUN_CONFIG_X_LEFT_ENABLED)) == (RUN_CONFIG_X_LEFT_INITIAL | RUN_CONFIG_X_LEFT_ENABLED)) {
                    follow_speed = -acting_entity->modeldata.runspeed;
                    run_state |= RUN_STATE_START_X;
                }
                else if (acting_entity->direction == DIRECTION_RIGHT
                    && (acting_entity->modeldata.run_config_flags & (RUN_CONFIG_X_RIGHT_INITIAL | RUN_CONFIG_X_RIGHT_ENABLED)) == (RUN_CONFIG_X_RIGHT_INITIAL | RUN_CONFIG_X_RIGHT_ENABLED)) {
                    follow_speed = acting_entity->modeldata.runspeed;
                    run_state |= RUN_STATE_START_X;
                }
            }
            else {
                follow_speed = acting_entity->modeldata.speed.x;
            }

            /* Reverse speed if target is to our left. */
            if (acting_entity->position.x > target_pos.x)
            {
                follow_speed = -follow_speed;
            }

            acting_entity->velocity.x = follow_speed;
            acting_entity->running = run_state;
            acting_entity->destx = target_pos.x;
        }

        follow_speed = 0;
        
        if (follow_state & FOLLOW_STATE_Z) {
            /*
            * If we're running and have Z running enabled, let's
            * veer ourselves toward the target.
            */

            if (follow_state & FOLLOW_STATE_RUN_Z) {

                if (acting_entity->modeldata.run_config_flags & RUN_CONFIG_Z_DOWN_ENABLED) {
                    follow_speed = acting_entity->modeldata.runspeed / 2;                    
                }
                else if (acting_entity->modeldata.run_config_flags & RUN_CONFIG_Z_UP_ENABLED) {
                    follow_speed = -acting_entity->modeldata.runspeed / 2;
                }
            }
            else {
                follow_speed = acting_entity->modeldata.speed.x / 2;                
            }
            
            /* Reverse speed if target is behind our Z position. */
            if (acting_entity->position.z > target_pos.z) {
                follow_speed = -follow_speed;
            }

            acting_entity->velocity.z = follow_speed;
            acting_entity->running = run_state;
            acting_entity->destz = target_pos.z;
        }
    }
    else {

        if (follow_state & FOLLOW_STATE_Z)
        {
            if (follow_state & FOLLOW_STATE_RUN_Z)
            {
                if (acting_entity->position.z < target_pos.z
                    && (acting_entity->modeldata.run_config_flags & (RUN_CONFIG_Z_DOWN_INITIAL | RUN_CONFIG_Z_DOWN_ENABLED)) == (RUN_CONFIG_Z_DOWN_INITIAL | RUN_CONFIG_Z_DOWN_ENABLED)) {

                    follow_speed = acting_entity->modeldata.runspeed;
                    run_state = RUN_STATE_START_Z;
                }
                else if (acting_entity->position.z > target_pos.z
                    && (acting_entity->modeldata.run_config_flags & (RUN_CONFIG_Z_UP_INITIAL | RUN_CONFIG_Z_UP_ENABLED)) == (RUN_CONFIG_Z_UP_INITIAL | RUN_CONFIG_Z_UP_ENABLED)) {

                    follow_speed = -acting_entity->modeldata.runspeed;
                    run_state = RUN_STATE_START_Z;
                }                
            }
            else {
                follow_speed = acting_entity->modeldata.speed.x;
            }

            acting_entity->velocity.z = follow_speed;
            acting_entity->running = run_state;
            acting_entity->destz = target_pos.z;
        }

        follow_speed = 0;

        if (follow_state & FOLLOW_STATE_X) {
            /*
            * If we're running and have Z running enabled, let's
            * veer ourselves toward the target.
            */

            if (follow_state & FOLLOW_STATE_RUN_X) {

                if (acting_entity->direction == DIRECTION_LEFT
                    && acting_entity->modeldata.run_config_flags & RUN_CONFIG_X_LEFT_ENABLED) {
                    follow_speed = -acting_entity->modeldata.runspeed / 2;
                }
                else if (acting_entity->direction == DIRECTION_RIGHT
                    && acting_entity->modeldata.run_config_flags & RUN_CONFIG_X_RIGHT_ENABLED) {
                    follow_speed = acting_entity->modeldata.runspeed / 2;
                }
            }
            else {
                follow_speed = acting_entity->modeldata.speed.x / 2;
            }

            /* Reverse speed if target is behind our Z position. */
            if (acting_entity->position.z > target_pos.z) {
                follow_speed = -follow_speed;
            }

            acting_entity->velocity.z = follow_speed;
            acting_entity->running = run_state;
            acting_entity->destz = target_pos.z;
        }
    }

    //printf("\n\t acting_entity->running : %d", acting_entity->running);
    //printf("\n\t acting_entity->destx: %f", acting_entity->destx);
    //printf("\n\t acting_entity->destz: %f", acting_entity->destz);
    //printf("\n\t acting_entity->velocity.x: %f", acting_entity->velocity.x);
    //printf("\n\t acting_entity->velocity.z: %f", acting_entity->velocity.z);

    return 1;
}

// try to avoid the target
// used by 'avoid avoidz avoidx
// Basic logic: the entity walk within a min distance and a max distance from the target
int common_try_avoid(entity *target, int dox, int doz)
{
    float dx, dz;
    float maxdz, mindz, maxdx, mindx;
    int randomatk;

    if(target == NULL || self->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
    {
        return 0;
    }

    dx = diff(self->position.x, target->position.x);
    dz = diff(self->position.z, target->position.z);

    randomatk = pick_random_attack(NULL, 0);

    if((rand32() & 15) < 8 && randomatk >= 0)
    {
        maxdx = self->modeldata.animation[randomatk]->range.x.max - self->modeldata.speed.x;
        if(maxdx < videomodes.hRes / 5)
        {
            maxdx = videomodes.hRes / 5;
        }
        mindx = maxdx - 10;
        maxdz = self->modeldata.animation[randomatk]->range.z.max - self->modeldata.speed.x;
        if(maxdz < videomodes.vRes / 5)
        {
            maxdz = videomodes.vRes / 5;
        }
        mindz = maxdz - 10;
    }
    else
    {
        mindx = videomodes.hRes / 3;
        maxdx = videomodes.hRes / 2;
        mindz = videomodes.vRes / 3;
        maxdz = videomodes.vRes / 2;
    }

    if(dox)
    {
        if(self->position.x < screenx)
        {
            self->velocity.x = self->modeldata.speed.x;
            self->destx = screenx + videomodes.hRes / 12.0;
        }
        else if(self->position.x > screenx + videomodes.hRes)
        {
            self->velocity.x = -self->modeldata.speed.x;
            self->destx = screenx + videomodes.hRes * 11.0 / 12.0;
        }
        else if(dx < mindx)
        {
            self->velocity.x = (self->position.x < target->position.x) ? (-self->modeldata.speed.x) : self->modeldata.speed.x;
            self->destx = (self->position.x < target->position.x) ? (target->position.x - maxdx) : (target->position.x + maxdx);
        }
        else if (dx > maxdx)
        {
            self->velocity.x = (self->position.x < target->position.x) ? self->modeldata.speed.x : (-self->modeldata.speed.x);
            self->destx = (self->position.x < target->position.x) ? (target->position.x - mindx) : (target->position.x + mindx);
        }
        else
        {
            self->velocity.x = 0;
            self->destx = self->position.x;
        }
    }

    if(doz)
    {
        if(self->position.z < screeny)
        {
            self->velocity.z = self->modeldata.speed.x / 2;
            self->destz = screeny +  videomodes.vRes / 12.0;
        }
        else if(self->position.z > screeny + videomodes.vRes)
        {
            self->velocity.z = -self->modeldata.speed.x / 2;
            self->destz = screeny +  videomodes.vRes * 11.0 / 12.0;
        }
        else if(dz < mindz)
        {
            self->velocity.z = (self->position.z < target->position.z) ? (-self->modeldata.speed.x / 2) : (self->modeldata.speed.x / 2);
            self->destz = (self->position.z < target->position.z) ? (target->position.z - maxdz) : (target->position.z + maxdz);
        }
        else if(dz > maxdz)
        {
            self->velocity.z = (self->position.z < target->position.z) ? (self->modeldata.speed.x / 2) : (-self->modeldata.speed.x / 2);
            self->destz = (self->position.z < target->position.z) ? (target->position.z - mindz) : (target->position.z + mindz);
        }
        else
        {
            self->velocity.z = 0;
            self->destz = self->position.z;
        }
    }

    return 1;
}

//  wander completely and ignore the target
// this ai pattern only works when you use aimove wander,
// if you mix wander with other patterns like chase or avoid
// this pattern is not triggered
int common_try_wandercompletely(int dox, int doz)
{
    int rnum;

    if(self->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
    {
        return 0;
    }

    if(dox)
    {
        rnum = rand32() & 15;
        if(rnum < 4)
        {
            self->velocity.x = -self->modeldata.speed.x;
        }
        else if(rnum > 11)
        {
            self->velocity.x = self->modeldata.speed.x;
        }
        else
        {
            self->velocity.x = 0;
        }
        if( self->position.x < screenx - 10)
        {
            self->velocity.x = self->modeldata.speed.x;
        }
        else if(self->position.x > screenx + videomodes.hRes + 10)
        {
            self->velocity.x = -self->modeldata.speed.x;
        }

        if(self->velocity.x > 0)
        {
            self->destx = self->position.x + videomodes.hRes / 5;
        }
        else if(self->velocity.x < 0)
        {
            self->destx = self->position.x - videomodes.hRes / 5;
        }
        else
        {
            self->destx = self->position.x;
        }

    }
    if(doz)
    {
        rnum = rand32() & 15;
        if(rnum < 4)
        {
            self->velocity.z = -self->modeldata.speed.x / 2;
        }
        else if(rnum > 11)
        {
            self->velocity.z = self->modeldata.speed.x / 2;
        }
        else
        {
            self->velocity.z = 0;
        }
        if(self->position.z < screeny - 10)
        {
            self->velocity.z = self->modeldata.speed.x / 2;
        }
        else if(self->position.z > screeny + videomodes.vRes + 10)
        {
            self->velocity.z = -self->modeldata.speed.x / 2;
        }

        if(self->velocity.z > 0)
        {
            self->destz = self->position.z + videomodes.vRes / 5;
        }
        else if(self->velocity.z < 0)
        {
            self->destz = self->position.z - videomodes.vRes / 5;
        }
        else
        {
            self->destz = self->position.z;
        }

    }

    return 1;

}
/*
int assume_safe_distance(entity* target, int ani, int* minx, int* maxx, int* minz, int* maxz)
{
	int f, set = 0;
	short tminx, tmaxx, tminz, tmaxz;
	s_anim* ta;
	short* coords;
	if(validanim(target, ani)){
		ta = target->modeldata.animation[ani];
		*minx = *minz = 9999;
		*maxx = *maxz = -9999;
		if(ta->collision_attack){
			for(f=0; f<ta->numframes; f++){
				if(!ta->collision_attack[f]) continue;
				coords = ta->collision_attack[f]->coords;
				if(target->direction == DIRECTION_RIGHT) {
					tminx = coords[0];
					tmaxx = coords[2];
				}else{
					tminx = -coords[2];
					tmaxx = -coords[0];
				}
				tminz = -coords[4];
				tmaxz = coords[4];
				if(tminx<*minx)
					*minx = tminx;
				if(tminz<*minz)
					*minz = tminz;
				if(tmaxx>*maxx)
					*maxx = tmaxx;
				if(tmaxz>*maxz)
					*maxz = tmaxz;

				set = 1;
			}

			if(set && self->animation->collision_body_legacy->coords && self->animation->collision_body_legacy[self->animpos]->coords){
				coords = self->animation->collision_body_legacy[self->animpos]->coords;
				*minx -= coords[2] - coords[0];
				*minz -= coords[4];
				*maxx += coords[2] - coords[0];
				*maxz += coords[4];
			}

			return set;
		}
	}

	return 0;

}
*/
// for normal and default ai patttern
// the entity is not actually wandering
// they just go around the target and get close
// occasionally to attack
int common_try_wander(entity *target, int dox, int doz)
{
    int walk = 0, behind, grabd, mod;

    float diffx, diffz, //distance from target
          returndx, returndz, //how far should keep from target
          borderdx, borderdz, //how far should keep offscreen
          mindx, mindz;// don't walk into the target
    int rnum = rand32() & 15, t, randomatk;

    if(!target || (self->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE))
    {
        return 0;
    }

    diffx = diff(self->position.x, target->position.x);
    diffz = diff(self->position.z, target->position.z);
    behind = ((self->position.x < target->position.x) == target->direction);
    grabd = self->modeldata.grabdistance;
    //when entity is behind the target, it has a greater chance to go after the target
    if(behind && diffx < grabd * 4 && diffz < grabd) //right behind, go for it
    {
        t = 13;
    }
    else if(behind)   // only behind, half chance
    {
        t = 7;
    }
    else    // otherwise, 1/8 chance
    {
        t = 2;
    }

    if(behind && target->attacking != ATTACKING_NONE)
    {
        t += 5;
    }

    // could use this to replace the completely wander ai
    if(dox != doz)
    {
        t = 0;
    }

    if(rnum < t) //chase target
    {
        returndx = videomodes.hRes / 4;
        returndz = videomodes.vRes / 8;
    }
    else   // only chase when too far away
    {
        returndx = videomodes.hRes * 0.6;
        returndz = videomodes.vRes * 0.4;
    }
    if(rnum > 7)
    {
        borderdx = videomodes.hRes / 8;
        borderdz = videomodes.vRes / 8;
    }
    else
    {
        borderdx = borderdz = 0;
    }

    randomatk = pick_random_attack(NULL, 0);

    if(randomatk >= 0)
    {
        mindx = self->modeldata.animation[randomatk]->range.x.max - (self->modeldata.animation[randomatk]->range.x.max - self->modeldata.animation[randomatk]->range.x.min) / 4 - 1;
    }
    else
    {
        mindx = (!behind && target->attacking != ATTACKING_NONE) ? grabd * 3 : grabd * 1.2;
    }
    mindz = grabd / 4;

    mod = ((int)(_time / (videomodes.hRes / self->modeldata.speed.x)) + 1000 + self->energy_state.health_current / 3 + self->pathblocked + self->modeldata.aggression / 10) % 4;
    if(mod < 0)
    {
        mod = -mod;
    }
    //if ((self->sortid / 100) % 2)
    if (rand32() % 2)
    {
        mod = 3 - mod;
    }

    if(dox)
    {
        if(self->position.x < screenx - borderdx)
        {
            self->velocity.x = self->modeldata.speed.x;
            self->destx = screenx + videomodes.hRes / 8.0;
            walk = 1;
        }
        else if (self->position.x > screenx + videomodes.hRes + borderdx)
        {
            self->velocity.x = -self->modeldata.speed.x;
            self->destx = screenx + videomodes.hRes * 7.0 / 8.0;
            walk = 1;
        }
        else if(diffx > returndx)
        {
            self->velocity.x = (self->position.x > target->position.x) ? -self->modeldata.speed.x : self->modeldata.speed.x;
            self->destx = (self->position.x > target->position.x) ? (target->position.x + mindx) : (target->position.x - mindx);
            walk = 1;
        }
        else
        {
            switch(mod)
            {
            case 0:
                self->destx = target->position.x + grabd;
                break;
            case 2:
                self->destx = target->position.x - grabd;
                break;
            case 1:
                self->destx = target->position.x + videomodes.hRes * 0.4 + (self->energy_state.health_current / 3 % 20);
                break;
            case 3:
                self->destx = target->position.x - videomodes.hRes * 0.4 - (self->energy_state.health_current / 3 % 20);
                break;
            }
            self->velocity.x = self->position.x > self->destx ? -self->modeldata.speed.x : self->modeldata.speed.x;
            walk = 1;
            //printf("mod x %d\n", mod);
        }
    }

    if(doz)
    {
        if(self->position.z < screeny - borderdz)
        {
            self->velocity.z = self->modeldata.speed.x / 2;
            self->destz = screeny + videomodes.vRes / 12.0;
            walk |= 1;
        }
        else if (self->position.z > screeny + videomodes.vRes + borderdz)
        {
            self->velocity.z = -self->modeldata.speed.x / 2;
            self->destz = screeny + videomodes.vRes * 11.0 / 12.0;
            walk |= 1;
        }
        else if(diffz > returndz)
        {
            self->velocity.z = (self->position.z > target->position.z) ? -self->modeldata.speed.x / 2 : self->modeldata.speed.x / 2;
            self->destz = (self->position.z > target->position.z) ? (target->position.z + mindz) : (target->position.z - mindz);
            walk |= 1;
        }
        else
        {
            switch(mod)
            {
            case 1:
                self->destz = target->position.z + grabd / 2;
                break;
            case 3:
                self->destz = target->position.z - grabd / 2;
                break;
            case 2:
                self->destz = target->position.z + MIN((PLAYER_MAX_Z - PLAYER_MIN_Z), videomodes.vRes) * 0.25 + (self->energy_state.health_current / 3 % 5);
                break;
            case 0:
                self->destz = target->position.z - MIN((PLAYER_MAX_Z - PLAYER_MIN_Z), videomodes.vRes) * 0.25 - (self->energy_state.health_current / 3 % 5);
                break;
            }
            self->velocity.z = self->position.z > self->destz ? -self->modeldata.speed.x / 2 : self->modeldata.speed.x / 2;
            walk |= 1;
            //printf("mod z %d\n", mod);
        }
    }

    return walk;
}

// Caskey, Damon V.
// 208-04-09
//
// Set up attack by item and execute didhit script as if item "hit" collector
// to allow easy item scripting.
void do_item_script(entity *ent, entity *item)
{
    s_attack attack;
    attack = emptyattack;
    attack.attack_type = ATK_ITEM;

    execute_didhit_script(item, ent, &attack, 0);
}

//A.I chracter pickup an item
void common_pickupitem(entity *other)
{
    int pickup = 0;
    //weapons
    if(self->weapent == NULL && isSubtypeWeapon(other) && validanim(self, ANI_GET))
    {
        self->takeaction = common_get;
        dropweapon(0);  //don't bother dropping the previous one though, scine it won't pickup another
        self->weapent = other;
        
        set_weapon(self, other->modeldata.weapon_properties.weapon_index, 0);
        
        set_getting(self);
        self->velocity.x = self->velocity.z = 0; //stop moving

        /* Move to weapon location if it's an "animal". */
        if(self->modeldata.weapon_properties.weapon_state & WEAPON_STATE_ANIMAL)
        {
            self->direction = other->direction;
            self->position.x = other->position.x;
            self->position.z = other->position.z;
        }

        if (other->nextanim != DELAY_INFINITE)
        {
            other->nextanim = _time + GAME_SPEED * 999999;
        }
            
        other->nextthink = _time + GAME_SPEED * 999999;
        ent_set_anim(self, ANI_GET, 0);
        pickup = 1;
    }
    // projectiles
    else if(self->weapent == NULL && isSubtypeProjectile(other) && validanim(self, ANI_GET))
    {
        self->takeaction = common_get;
        dropweapon(0);
        self->weapent = other;
        set_getting(self);
        self->velocity.x = self->velocity.z = 0; //stop moving
        if (other->nextanim != DELAY_INFINITE) { other->nextanim = _time + GAME_SPEED * 999999; }
            
            
        other->nextthink = _time + GAME_SPEED * 999999;
        ent_set_anim(self, ANI_GET, 0);
        pickup = 1;
    }
    // other items
    else if(! isSubtypeWeapon(other) && ! isSubtypeProjectile(other) )
    {
        if(validanim(self, ANI_GET) && !isSubtypeTouch(other))
        {
            self->takeaction = common_get;
            set_getting(self);
            self->velocity.x = self->velocity.z = 0; //stop moving
            ent_set_anim(self, ANI_GET, 0);
        }
        if(other->energy_state.health_current)
        {
            self->energy_state.health_current += other->energy_state.health_current;
            if(self->energy_state.health_current > self->modeldata.health)
            {
                self->energy_state.health_current = self->modeldata.health;
            }
            other->energy_state.health_current = 0;
            //if(global_sample_list.get >= 0) sound_play_sample(global_sample_list.get, 0, savedata.effectvol,savedata.effectvol, 100);
        }
        // else if, TODO: other effects
        // kill that item
        other->takeaction = suicide;
        other->nextthink = _time + GAME_SPEED * 3;
        pickup = 1;
    }
    // hide it
    if(pickup)
    {
        do_item_script(self, other);

        other->position.z = ITEM_HIDE_POSITION_Z;
    }
}

// for old bikers
int biker_move()
{

    if((self->direction == DIRECTION_RIGHT) ? (self->position.x > advancex + (videomodes.hRes + 200)) : (self->position.x < advancex - 200))
    {
        self->direction = !self->direction;
        self->attack_id_outgoing = 0;
        self->position.z = (float)(PLAYER_MIN_Z + randf((float)(PLAYER_MAX_Z - PLAYER_MIN_Z)));
        
        if(global_sample_list.bike >= 0)
        {
            sound_play_sample(global_sample_list.bike, 0, savedata.effectvol, savedata.effectvol, 100);
        }

        if(self->modeldata.speed.x)
        {
            self->velocity.x = (self->direction == DIRECTION_RIGHT) ? (self->modeldata.speed.x) : (-self->modeldata.speed.x);
        }
        else
        {
            self->velocity.x = (self->direction == DIRECTION_RIGHT) ? ((float)1.7 + randf((float)0.6)) : (-((float)1.7 + randf((float)0.6)));
        }
    }

    return 1;
}

// for common arrow types
int arrow_move(entity* acting_entity)
{
    float dx;
    float dz;
    float maxspeed;
    entity *target = NULL;
    
    // new subtype chase
    if(acting_entity->modeldata.subtype == SUBTYPE_CHASE || acting_entity->modeldata.aimove & AIMOVE1_CHASE)
    {
        target = homing_find_target(acting_entity);

        if(target)
        {
            if(!(acting_entity->modeldata.move_config_flags & MOVE_CONFIG_NO_FLIP))
            {
                acting_entity->direction = (target->position.x > acting_entity->position.x);
            }
            // start chasing the target
            dx = diff(acting_entity->position.x, target->position.x);
            dz = diff(acting_entity->position.z, target->position.z);
            maxspeed = acting_entity->modeldata.speed.x * 1.5;

            if(!dz && !dx)
            {
                acting_entity->velocity.x = acting_entity->velocity.z = 0;
            }
            else
            {
                acting_entity->velocity.x = maxspeed * dx / (dx + dz);
                acting_entity->velocity.z = maxspeed * dz / (dx + dz);
            }
            if(acting_entity->direction == DIRECTION_LEFT)
            {
                acting_entity->velocity.x = -acting_entity->velocity.x;
            }
            if(acting_entity->position.z > target->position.z)
            {
                acting_entity->velocity.z = -acting_entity->velocity.z;
            }
        }
        else
        {
            if(!acting_entity->velocity.x && !acting_entity->velocity.z)
            {
                if(acting_entity->direction == DIRECTION_LEFT)
                {
                    acting_entity->velocity.x = -acting_entity->modeldata.speed.x;
                }
                else if(acting_entity->direction == DIRECTION_RIGHT)
                {
                    acting_entity->velocity.x = acting_entity->modeldata.speed.x;
                }
            }
        }
        
        if(!(acting_entity->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE))
        {
            if(target && acting_entity->position.z > target->position.z && validanim(acting_entity, ANI_UP))
            {
                common_up_anim(acting_entity);    //ent_set_anim(self, ANI_UP, 0);
            }
            else if(target && target->position.z > acting_entity->position.z && validanim(acting_entity, ANI_DOWN))
            {
                common_down_anim(acting_entity);    //ent_set_anim(self, ANI_DOWN, 0);
            }
            else if(validanim(acting_entity, ANI_WALK))
            {
                common_walk_anim(acting_entity);    //ent_set_anim(self, ANI_WALK, 0);
            }
            else
            {
                if(validanim(acting_entity, ANI_IDLE)) ent_set_anim(acting_entity, ANI_IDLE, 0);
            }
        }
    }
    else
    {
        /*
        * Apply speed to velocity. If spawned by legacy 
        * functions, only apply X axis for backward
        * compatability.
        */

        if(acting_entity->direction == DIRECTION_LEFT)
        {
            acting_entity->velocity.x = -acting_entity->modeldata.speed.x;
        }
        else if(acting_entity->direction == DIRECTION_RIGHT)
        {
            acting_entity->velocity.x = acting_entity->modeldata.speed.x;
        }
                
        
        if (!(acting_entity->projectile_prime & PROJECTILE_PRIME_INITIALIZE_LEGACY_PROJECTILE_FUNCTION))
        {
            acting_entity->velocity.y = acting_entity->modeldata.speed.y;
            acting_entity->velocity.z = acting_entity->modeldata.speed.z;
        }
    }

    if(level)
    {
        // Bounce off walls or platforms.
        projectile_wall_deflect(acting_entity);
        
        if (acting_entity->modeldata.move_config_flags & MOVE_CONFIG_PROJECTILE_BASE_DIE)
        {
            if (acting_entity->landed_on_platform || acting_entity->position.y <= acting_entity->base)
            {
                acting_entity->takeaction = common_fall;
                acting_entity->attacking = ATTACKING_NONE;
                acting_entity->energy_state.health_current = 0;
                acting_entity->projectile = BLAST_NONE;
                acting_entity->damage_on_landing.attack_force = 0;
                acting_entity->damage_on_landing.attack_type = ATK_NONE;

                /* Use default attack values. */
                s_attack attack = emptyattack;
                attack.attack_drop = 10000;
                set_fall(acting_entity, acting_entity, &attack, 0);
            }
        }
    }

    if(acting_entity->projectile_prime & PROJECTILE_PRIME_LAUNCH_STATIONARY)// PROJECTILE_PRIME_BASE_FLOOR)
    {
        acting_entity->autokill |= AUTOKILL_ANIMATION_COMPLETE;
    }

    return 1;
}

/*
* Caskey, Damon V.
* 2022-06-28
* 
* Explode (play attack animation and stop) if detonate
* flag is set and entity meets conditions.
* 
* Return 0 if conditions fail. Return 1 if conditions
* pass or detonation in progress.
*/
int bomb_try_detonate(entity* acting_entity)
{
    //printf("\n %s:", "bomb_try_detonate");

    /*
    * If not already set to detonate, check if
    * we are preared for detonation and in air.
    */

    //printf("\n\t %s: %d", "acting_entity->toexplode", acting_entity->toexplode);

    if (!(acting_entity->toexplode & (EXPLODE_DETONATE_HIT | EXPLODE_DETONATE_DAMAGED)))
    {
        if (!(acting_entity->toexplode & (EXPLODE_PREPARE_TOUCH | EXPLODE_PREPARE_GROUND)))
        {
            return 0;
        }

        //printf("\n\t %s: %d", "inair(acting_entity)", inair(acting_entity));

        /*
        * - Touches a wall.
        * - Touches a platform.
        * - Touches base 0 and isn't in a hole.
        */            
        
        //printf("\n\t %s: %d", "check_block_wall(acting_entity)", check_block_wall(acting_entity));

        if (check_block_wall(acting_entity) == WALL_INDEX_NONE && 
            !check_block_obstacle(acting_entity)
            && inair(acting_entity))
        {
            return 0;
        }
    }

    //printf("\n\t %s: %p", "acting_entity->takeaction", acting_entity->takeaction);
    //printf("\n\t %s: %p", "bomb_explode", bomb_explode);
    //printf("\n\t %s: %f, %f, %f, %f", "Position (x,y,z,b):", acting_entity->position.x, acting_entity->position.y, acting_entity->position.z, acting_entity->base);

    /* Already detonated? */
    if (acting_entity->takeaction == bomb_explode)
    {
        return 1;
    }
    
    /*
    * Set explode action. Entity stays on screen
    * until explode animation finishes, then kills 
    * itself.
    */
    acting_entity->takeaction = bomb_explode;    

    /*
    * Stop movement.
    */
    if (!checkhole(acting_entity->position.x, acting_entity->position.z)) 
    {
        acting_entity->base = acting_entity->position.y;
        acting_entity->velocity.y = 0;
        acting_entity->velocity.x = 0;
        acting_entity->velocity.z = 0;
    }

    /*
    * Play die sound if we have it. This can act as 
    * the explosion sound.
    */ 
    if (acting_entity->modeldata.diesound >= 0)
    {
        sound_play_sample(acting_entity->modeldata.diesound, 0, savedata.effectvol, savedata.effectvol, 100);
    }

    /*
    * If we hit or got hit, then play ATTACK2. 
    * If we landed first, play ATTACK1.
    */ 
    if (acting_entity->toexplode & (EXPLODE_DETONATE_HIT | EXPLODE_DETONATE_DAMAGED))
    {
        /*
        * Play explode animation if we have it.
        * 
        * If we do not have explode animation and 
        * the explode was triggered by hitting, then 
        * let's behave like a typical projectile and
        * check the remove flag.
        */

        if (validanim(acting_entity, ANI_ATTACK2))
        {
            ent_set_anim(acting_entity, ANI_ATTACK2, 0);
            acting_entity->animation->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_GRAVITY;
        }
        else if(acting_entity->modeldata.remove && acting_entity->toexplode & EXPLODE_DETONATE_HIT)
        {
            kill_entity(acting_entity, KILL_ENTITY_TRIGGER_BOMB_EXPLODE_ANIMATION_UNAVAILABLE);
        }
    }
    else if (validanim(acting_entity, ANI_ATTACK1))
    {
        ent_set_anim(acting_entity, ANI_ATTACK1, 0);
        acting_entity->animation->move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_GRAVITY;    
    }

    return 1;
}

/*
* Caskey, Damon V.
* 2019-12-22
*
* Original author unknown. Refactored to stop using global self,
* and fix bomb falling after detonation.
*/
int bomb_move(entity* acting_entity)
{
    /*
    * If in air, and prepared to explode (meaning will detonate on contact), 
    * but NOT yet set to detonate, then we move using velocity (presumably 
    * we've been tossed and so any Z and Y momentum is already handled). 
    *
    * EXPLODE_DETONATE_HIT status is applied by do_attack() if we touch
    * ground or hit another entity.
    *
    * EXPLODE_DETONATE_DAMAGE set when hit by another entity attack.
    * 
    * In either case case, we "explode" by playing an appropriate animation.
    */

    /* 
    * Bounce off walls or platforms if there 
    * is a fall animation.
    */

    if (projectile_wall_deflect(acting_entity))
    {
        return 1;
    }

    /*
    * Check detonation conditions and
    * apply accordingly.
    */

    if (bomb_try_detonate(acting_entity))
    {
        return 1;
    }
    
    if (acting_entity->direction == DIRECTION_LEFT)
    {
        acting_entity->velocity.x = -acting_entity->modeldata.speed.x;
    }
    else if (acting_entity->direction == DIRECTION_RIGHT)
    {
        acting_entity->velocity.x = acting_entity->modeldata.speed.x;
    }

    return 1;
}

/*
* Caskey, Damon V.
* 2018-04-07
*
* Find out if there is a wall blocking target entity, and
* if so return its array key. Returns WALL_INDEX_NONE if 
* no blocking wall found.
*/
int check_block_wall(entity *entity)
{
    //printf("\n\n check_block_wall(%p)", entity);
    //printf("\n entity->modeldata.move_config_flags: %d", entity->modeldata.move_config_flags);

    int wall = WALL_INDEX_NONE;

    /* Target entity affected by walls? */
    if(entity->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_WALL)
    {
        //printf("\n\t Has constraint.");

        /* Get wall number at our Xand Z axis(if any). */
        wall = checkwall_index(entity->position.x, entity->position.z);

        //printf("\n\t wall: %d", wall);

        /* Did we find a wall? */
        if (wall >= 0)
        {
            /*
            * Compare wall height to our current
            * Y axis position. If the wall is
            * higher, then it's blocking our way.
            * We can return the wall number.
            */
           
            if(entity->position.y < level->walls[wall].height)
            {
                return wall;
            }
        }
    }

    /*
    * Got this far? Then there's no wall blocking our way.
    */
    return WALL_INDEX_NONE;
}

// Caskey, Damon V.
// 2018-04-07
//
// Return obstacle entity that is blocking target
// entity (if any). Returns NULL if not blocking
// obstacle found.
entity *check_block_obstacle(entity *ent)
{
    entity *obstacle = NULL;
    int height;

    // Target entity affected by obstacles?
    if(ent->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_PLATFORM)
    {
        // Get the height. if entity does not have an
        // animation height defined, then use its
        // the entity height instead.
        if(ent->animation->size.y)
        {
            height = ent->animation->size.y;
        }
        else
        {
            height = ent->modeldata.size.y;
        }

        // Add Y position to get the exact Y axis
        // height of the entity's top edge.
        height += ent->position.y;

        // Find obstacle at entitiy's position (if any).
        obstacle = check_platform_between(ent->position.x, ent->position.z, ent->position.y, height, ent);
    }

    return obstacle;
}

/*
* Caskey, Damon V
* 2018-04-06
*
* Ricochet a projectile off of walls and platforms.
* Returns 1 on successful ricochet. 0 otherwise.
*/
int projectile_wall_deflect(entity *acting_entity)
{
    #define RICHOCHET_FALL_FORCE        10000 
    #define RICHOCHET_VELOCITY_X_FACTOR 0.25    // This value is multiplied by current velocity to get an X velocity value to bounce off wall..
    #define RICHOCHET_VELOCITY_Y        2.5     // Base Y velocity applied when projectile bounces off wall.
    #define RICHOCHET_VELOCITY_Y_RAND   1       // Random seed for Y variance added to base Y velocity when bouncing off wall.

    float richochet_velocity_x;
    s_attack attack;

    //printf("\n\n projectile_wall_deflect(%p)", acting_entity);
    //printf("\n\t model(%s)", acting_entity->model->name);
    //printf("\n\t acting_entity->drop: ", acting_entity->drop);

    if(validanim(acting_entity, ANI_FALL) && acting_entity->modeldata.move_config_flags & MOVE_CONFIG_PROJECTILE_WALL_BOUNCE)
    {
        //printf("\n\t Has Fall");
        //printf("\n\t position: %f, %f, %f", acting_entity->position.x, acting_entity->position.y, acting_entity->position.z);
        //printf("\n\t velocity: %f, %f, %f", acting_entity->velocity.x, acting_entity->velocity.y, acting_entity->velocity.z);

        int blocking_wall;
        entity *blocking_obstacle = NULL;

        /*
        * Blocked by wall or obstacle? Note these
        * functions return false if the entity
        * is not subject to wall/obstacle.
        */

        blocking_wall = check_block_wall(acting_entity);
        blocking_obstacle = check_block_obstacle(acting_entity);

        //printf("\n\t wall(%d), obstacle(%p)", blocking_wall, blocking_obstacle);

        if(blocking_wall >= 0 || blocking_obstacle) 
        {
            //printf("\n\t Is blocked.");

            /*
            * Use the projectile's speed and our factor to see 
            * how hard it will bounce off wall.
            */
            richochet_velocity_x = -acting_entity->velocity.x * RICHOCHET_VELOCITY_X_FACTOR;
            
            //printf("\n\t richochet_velocity_x: %f", richochet_velocity_x);

            acting_entity->takeaction = common_fall;
            acting_entity->attacking = ATTACKING_NONE;
            acting_entity->energy_state.health_current = 0;
            acting_entity->projectile = BLAST_NONE;
                              
            acting_entity->velocity.x = richochet_velocity_x;
            
            acting_entity->velocity.z = 0.0;
            acting_entity->damage_on_landing.attack_force = 0;
            acting_entity->damage_on_landing.attack_type = ATK_NONE;
            toss(acting_entity, RICHOCHET_VELOCITY_Y + randf(RICHOCHET_VELOCITY_Y_RAND));
                        
            /* Reset base detection */
            acting_entity->modeldata.move_config_flags |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);
            acting_entity->modeldata.move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;
            
            acting_entity->base = 0;

            /* Use default attack values. */
            attack = emptyattack;
            attack.attack_drop = RICHOCHET_FALL_FORCE;
            set_fall(acting_entity, acting_entity, &attack, 0);

            //printf("\n\t velocity: %f, %f, %f", acting_entity->velocity.x, acting_entity->velocity.y, acting_entity->velocity.z);

            return 1;
        }

        //printf("\n\t Not blocked.");
    }

    /* Did not ricochet, so return false. */
    return 0;

    #undef RICHOCHET_FALL_FORCE
    #undef RICHOCHET_VELOCITY_X_FACTOR
    #undef RICHOCHET_VELOCITY_Y
    #undef RICHOCHET_VELOCITY_Y_RAND
}

// Caskey, Damon V.
// 2018-04-06
//
// Invert current sorting position vs. parent.
void sort_invert_by_parent(entity *ent, entity *parent)
{
    if(ent->sortid <= parent->sortid)
    {
        ent->sortid = parent->sortid + 1;
    }
    else
    {
        ent->sortid = parent->sortid - 1;
    }
}

int star_move()
{
    if(self->position.x < advancex - 80 || self->position.x > advancex + (videomodes.hRes + 80) || (self->position.y <= self->base && (self->modeldata.death_config_flags & DEATH_CONFIG_MACRO_DEATH)))
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_STAR_OUT_OF_BOUNDS);
        return 0;
    }

    //self->base -= 4;
    //self->position.y = self->base;

    // Bounce off walls or platforms.
    projectile_wall_deflect(self);

    if(self->landed_on_platform || self->position.y <= self->base)
    {
        self->takeaction = common_lie;
        self->death_state |= DEATH_STATE_DEAD;
        self->energy_state.health_current = 0;
        if((self->modeldata.death_config_flags & (DEATH_CONFIG_FALL_LAND_AIR | DEATH_CONFIG_FALL_LAND_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND)) == (DEATH_CONFIG_FALL_LAND_AIR | DEATH_CONFIG_FALL_LAND_GROUND | DEATH_CONFIG_REMOVE_VANISH_AIR | DEATH_CONFIG_REMOVE_VANISH_GROUND))
        {
            self->animating = ANIMATING_NONE;
        }
    }

    return 1;
}


//dispatch move patterns
//root function for aimove
int common_move()
{
    entity* acting_entity = self;
    e_aimove aimove;
    int makestop = 0;
    int reachx;
    int reachz;
    int air = inair(acting_entity);
    entity *other = NULL; //item
    entity *target = NULL;//hostile target
    entity *owner = NULL;
    entity *ent = NULL;
    e_direction predir; 
    long stall;
    int patx[5];
    int pxc;
    int px;
    int patz[5];
    int pzc;
    int pz;
    int fz;

    if(acting_entity->modeldata.aimove == -1)
    {
        return 0;    // invalid value
    }

    // get move pattern
    aimove = acting_entity->modeldata.aimove & MASK_AIMOVE1;

//if(stricmp(acting_entity->name, "os")==0) printf("%d\n", aimove);
    // old and outdated patterns, but MUST be kept anyway
    if(aimove & AIMOVE1_BIKER)
    {
        // for biker subtype
        return biker_move();
    }
    else if(aimove & AIMOVE1_ARROW)
    {
        // for common straight-flying arrow
        return arrow_move(acting_entity);
    }
    else if(aimove & AIMOVE1_STAR)
    {
        // for a star, disappear when hit ground
        return star_move();
    }
    else if(aimove & AIMOVE1_BOMB)
    {
        // for a bomb, travel in a arc
        return bomb_move(acting_entity);
    }
    else if(aimove & AIMOVE1_NOMOVE)
    {
        // no move, just return
        return 0;
    }
    else
    {
        // all above are special move patterns, real AI goes here:

        // skip if the entity is in air,
        // removing this and entity might be spawned walking in air
        if (air)
        {
            return 0;
        }

        // store this for turning checking
        predir = acting_entity->direction;

        // find all possible entities for target
        // bad for optimization, but makes better sense
        if (acting_entity->custom_target == NULL || !acting_entity->custom_target->exists) target = normal_find_target(-1, 0); // confirm the target again
        else target = acting_entity->custom_target;

        other = ((_time / GAME_SPEED + acting_entity->energy_state.health_current / 3 + 1000) % 15 < 10) ? normal_find_item() : NULL; // find an item
        owner = acting_entity->parent;

        // temporary solution to turn off running if xdir is not set
        // unless one day vertical running logic is written
        if (!acting_entity->velocity.x)
        {
            acting_entity->running = RUN_STATE_NONE;
        }

        // change direction unless the ai pattern ignores target or model has noflip
        if (!(acting_entity->modeldata.move_config_flags & MOVE_CONFIG_NO_FLIP) && !acting_entity->running && aimove != AIMOVE1_WANDER)
        {
            if (other)   //try to pick up an item, if possible
            {
                acting_entity->direction = (acting_entity->position.x < other->position.x) ? DIRECTION_RIGHT : DIRECTION_LEFT;
            }
            else if (target)
            {
                acting_entity->direction = (acting_entity->position.x < target->position.x) ? DIRECTION_RIGHT : DIRECTION_LEFT;;
            }
            else if (owner)
            {
                /*
                * If we're following a parent, then check to see
                * if we are in direction adjust range and if so
                * we'll apply the direction adjustment. Otherwise
                * we just face parent.
                * 
                * Any range with min > max is true.
                */                

                const s_child_follow* const child_follow = acting_entity->modeldata.child_follow;

                if (child_follow  
                    && (child_follow->direction_adjust_range.x.min <= child_follow->direction_adjust_range.x.max && check_range_target_x(acting_entity, owner, NULL, child_follow->direction_adjust_range.x.min, child_follow->direction_adjust_range.x.max))
                    && (child_follow->direction_adjust_range.z.min <= child_follow->direction_adjust_range.z.max && check_range_target_z(acting_entity, owner, NULL, child_follow->direction_adjust_range.z.min, child_follow->direction_adjust_range.z.max))
                    && (child_follow->direction_adjust_range.y.min <= child_follow->direction_adjust_range.y.max && check_range_target_y(acting_entity, owner, NULL, child_follow->direction_adjust_range.y.min, child_follow->direction_adjust_range.y.max))) {
                    
                    acting_entity->direction = direction_get_adjustment_result(acting_entity, owner, child_follow->direction_adjust_config);
                }
            }
        }
        else if (aimove == AIMOVE1_WANDER)
        {
            if (acting_entity->velocity.x)
            {
                acting_entity->direction = (acting_entity->velocity.x > 0) ? DIRECTION_RIGHT : DIRECTION_LEFT;
            }
        }

        //turn back if we have a turn animation
        // TODO, make a function for ai script
        if (acting_entity->direction != predir && validanim(acting_entity, ANI_TURN) && acting_entity->ducking == DUCK_NONE)
        {
            acting_entity->takeaction = common_turn;
            acting_entity->direction = (acting_entity->direction == DIRECTION_RIGHT) ? DIRECTION_LEFT : DIRECTION_RIGHT;
            acting_entity->velocity.x = 0;
            acting_entity->velocity.z = 0;
            ent_set_anim(acting_entity, ANI_TURN, 0);
            return 1;
        }

        //pick up the item if possible
        if (other && diff(other->position.x, acting_entity->position.x) < (acting_entity->modeldata.grabdistance * 0.83333)
            && diff(other->position.z, acting_entity->position.z) < (acting_entity->modeldata.grabdistance / 3) &&
            other->animation->vulnerable[other->animpos])//won't pickup an item that is not previous one
        {
            if (diff(acting_entity->base, other->position.y) < 0.1)
            {
                common_pickupitem(other);
                return 1;
            }
        }

        if (acting_entity->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
        {
            acting_entity->idling = IDLING_PREPARED;
            return 1;
        }

        if (common_try_jump())
        {
            acting_entity->numwaypoints = 0;
            return 1;  //need to jump? so quit
        }

        if (checkpathblocked())
        {
            return 1;    // handle path blocked logic
        }

        // judge next move if stalltime is expired
        // skip if waypoints presents (passive move)
        if (acting_entity->stalltime < _time && !acting_entity->waypoints)
        {
            if (other)
            {
                // try walking to the item
                common_try_pick(other);
                ent = other;
            }
            else
            {
                if (target && (acting_entity->modeldata.subtype == SUBTYPE_CHASE ||
                    ((acting_entity->modeldata.type & TYPE_NPC) && acting_entity->parent)))
                    // try chase a target
                {
                    aimove |= AIMOVE1_CHASE;
                }

                if (aimove & AIMOVE1_CHASE)
                {
                    aimove |= AIMOVE1_CHASEX | AIMOVE1_CHASEZ;
                }
                if (aimove & AIMOVE1_AVOID)
                {
                    aimove |= AIMOVE1_AVOIDX | AIMOVE1_AVOIDZ;
                }

                if (other != ent)
                {
                    acting_entity->velocity.x = acting_entity->velocity.z = 0;
                }

                if (!aimove && target)
                {
                    common_try_wander(target, 1, 1);
                    ent = target;
                }
                else if (target && (!(acting_entity->modeldata.aimove & AIMOVE2_NOTARGETIDLE) || ((acting_entity->modeldata.aimove & AIMOVE2_NOTARGETIDLE) && target->animnum != ANI_IDLE)))
                {
                    ent = target;
                    pxc = pzc = 0;

                    if (aimove & AIMOVE1_WANDER)
                    {
                        patx[pxc++] = AIMOVE1_WANDER;
                        patx[pxc++] = AIMOVE1_WANDER;
                        patz[pzc++] = AIMOVE1_WANDER;
                        patz[pzc++] = AIMOVE1_WANDER;
                    }
                    if (aimove & AIMOVE1_CHASEX)
                    {
                        patx[pxc++] = AIMOVE1_CHASEX;
                    }
                    if (aimove & AIMOVE1_AVOIDX)
                    {
                        patx[pxc++] = AIMOVE1_AVOIDX;
                    }
                    if (aimove & AIMOVE1_CHASEZ)
                    {
                        patz[pzc++] = AIMOVE1_CHASEZ;
                    }
                    if (aimove & AIMOVE1_AVOIDZ)
                    {
                        patz[pzc++] = AIMOVE1_AVOIDZ;
                    }
                    if (!pxc)
                    {
                        patx[pxc++] = AIMOVE1_WANDER;
                    }
                    if (!pzc)
                    {
                        patz[pzc++] = AIMOVE1_WANDER;
                    }
                    px = patx[(rand32() & 0xff) % pxc];
                    pz = patz[(rand32() & 0xff) % pzc];

                    fz = 0;

                    aimove = (acting_entity->modeldata.aimove & MASK_AIMOVE1);

                    //valid types: avoidx, aviodz, chasex, chasez, wander
                    if (px == AIMOVE1_WANDER)
                    {
                        if (pz == AIMOVE1_WANDER && aimove == AIMOVE1_WANDER)
                        {
                            common_try_wandercompletely(1, 1);
                            fz = 1;
                        }
                        else
                        {
                            common_try_wander(target, 1, 0);
                        }

                    }
                    else if (px == AIMOVE1_CHASEX)
                    {
                        common_try_chase(acting_entity, target, 1, (pz == AIMOVE1_CHASEZ));
                        fz = (pz == AIMOVE1_CHASEZ);
                    }
                    else if (px == AIMOVE1_AVOIDX)
                    {
                        common_try_avoid(target, 1, (pz == AIMOVE1_AVOIDZ));
                        fz = (pz == AIMOVE1_AVOIDZ);
                    }
                    if (!fz)
                    {
                        if (pz == AIMOVE1_WANDER)
                        {
                            common_try_wander(target, 0, 1);
                        }
                        else if (pz == AIMOVE1_CHASEZ)
                        {
                            common_try_chase(acting_entity, target, 0, 1);
                        }
                        else if (pz == AIMOVE1_AVOIDZ)
                        {
                            common_try_avoid(target, 0, 1);
                        }
                    }

                }
                else if (!common_try_follow(acting_entity, owner, 1, 1) && !(acting_entity->modeldata.aimove & AIMOVE2_NOTARGETIDLE))
                {
                    common_try_wandercompletely(1, 1);
                    ent = NULL;
                }
                else
                {
                    ent = owner;
                }
            }
            //end of if

        }//if(acting_entity->stalltime < _time )
        else
        {
            ent = other;
            if (!ent)
            {
                ent = target;
            }
            if (!ent)
            {
                ent = owner;
            }
        }
        if (acting_entity->numwaypoints == 0 && acting_entity->waypoints)
        {
            free(acting_entity->waypoints);
            acting_entity->waypoints = NULL;
        }

        //fix 2d level panic, or should this be moved to the very beginning?
        if (acting_entity->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_MIN_Z && acting_entity->destz < PLAYER_MIN_Z)
        {
            acting_entity->destz = PLAYER_MIN_Z;
        }
        if (acting_entity->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_MAX_Z && acting_entity->destz > PLAYER_MAX_Z)
        {
            acting_entity->destz = PLAYER_MAX_Z;
        }

        // don't run in passive move mode. The path could be complex and running may look bad.
        if (acting_entity->waypoints)
        {
            acting_entity->running = RUN_STATE_NONE;
        }

        if (acting_entity->direction == (acting_entity->destx < acting_entity->position.x))
        {
            acting_entity->running = RUN_STATE_NONE;
        }

        // make the entity walks in a straight path instead of flickering here and there
        // acceleration can be added easily based on this logic, if necessary
        adjustspeed(acting_entity->running ? acting_entity->modeldata.runspeed : acting_entity->modeldata.speed.x,
            acting_entity->position.x, acting_entity->position.z,
            acting_entity->destx, acting_entity->destz,
            &acting_entity->velocity.x,
            &acting_entity->velocity.z);

        /*
        * If we can't run on a different axis
        * then stay on the rails.
        */

        if (acting_entity->running & RUN_STATE_START_X) {
            if ((acting_entity->velocity.z < 0 && !(acting_entity->modeldata.run_config_flags & RUN_CONFIG_Z_UP_ENABLED))
                || (acting_entity->velocity.z > 0 && !(acting_entity->modeldata.run_config_flags & RUN_CONFIG_Z_DOWN_ENABLED))
                || (acting_entity->velocity.x < 0 && acting_entity->modeldata.run_config_flags & RUN_CONFIG_X_LEFT_DASH_FIXED)
                || (acting_entity->velocity.x > 0 && acting_entity->modeldata.run_config_flags & RUN_CONFIG_X_RIGHT_DASH_FIXED)) {
                acting_entity->velocity.z = 0;
                acting_entity->destz = acting_entity->position.z;
            }
        }
        else if (acting_entity->running & RUN_STATE_START_Z) {
            if ((acting_entity->velocity.x < 0 && !(acting_entity->modeldata.run_config_flags & RUN_CONFIG_X_LEFT_ENABLED))
                || (acting_entity->velocity.x > 0 && !(acting_entity->modeldata.run_config_flags & RUN_CONFIG_X_RIGHT_ENABLED))
                || (acting_entity->velocity.z < 0 && acting_entity->modeldata.run_config_flags & RUN_CONFIG_Z_UP_DASH_FIXED)
                || (acting_entity->velocity.z > 0 && acting_entity->modeldata.run_config_flags & RUN_CONFIG_Z_DOWN_DASH_FIXED)) {
                acting_entity->velocity.x = 0;
                acting_entity->destz = acting_entity->position.x;
            }
        }

        // check destination point to make a stop or pop a waypoint from the stack
        reachx = (diff(acting_entity->position.x, acting_entity->destx) < MAX(1, ABS(acting_entity->velocity.x)));
        reachz = (diff(acting_entity->position.z, acting_entity->destz) < MAX(1, ABS(acting_entity->velocity.z)));

        // check destination point to make a stop or pop a waypoint from the stack
        if(reachx && reachz)
        {
            if(acting_entity->waypoints && acting_entity->numwaypoints)
            {
                acting_entity->destx = acting_entity->waypoints[acting_entity->numwaypoints - 1].x;
                acting_entity->destz = acting_entity->waypoints[acting_entity->numwaypoints - 1].z;
                acting_entity->numwaypoints--;
            }
            else if(acting_entity->velocity.x || acting_entity->velocity.z)
            {
                makestop = 1;
            }
        }

        if(!acting_entity->waypoints || !acting_entity->numwaypoints)
        {
            if(reachx)
            {
                acting_entity->velocity.x = 0;
                acting_entity->destx = acting_entity->position.x;
            }
            if(reachz)
            {
                acting_entity->velocity.z = 0;
                acting_entity->destz = acting_entity->position.z;
            }
        }

        // IMPORTANT: stoped so play idle, preventinng funny stepping bug, but may cause flickering
        if(!acting_entity->velocity.x && !acting_entity->velocity.z && !acting_entity->waypoints)
        {
            set_idle(acting_entity);
            if(makestop)
            {
                stall = (GAME_SPEED - acting_entity->modeldata.aggression) / 2;
                if(stall < GAME_SPEED / 5)
                {
                    stall = GAME_SPEED / 5;
                }
                acting_entity->stalltime = _time + MAX(0, stall);
            }
        }
        else
        {
            // readjust walk animation
            adjust_walk_animation(ent);
            // give proper stalltime if destination point is not reached
            // if the destination point is not reachable,
            // it should be already handled in checkpathblocked
            if(_time > acting_entity->stalltime)
            {
                if(ABS(acting_entity->velocity.x) > ABS(acting_entity->velocity.z))
                {
                    stall = diff(acting_entity->destx, acting_entity->position.x) / ABS(acting_entity->velocity.x) * 2;
                }
                else if(acting_entity->velocity.z)
                {
                    stall = diff(acting_entity->destz, acting_entity->position.z) / ABS(acting_entity->velocity.z) * 2;
                }
                else
                {
                    stall = GAME_SPEED / 2;
                }
                acting_entity->stalltime = _time + MAX(0, stall);
            }
        }

        //target is moving?  readjust destination sooner
        if(aimove != AIMOVE1_WANDER && !acting_entity->waypoints && ent && (acting_entity->velocity.x || acting_entity->velocity.z) && (ent->velocity.x || ent->velocity.z))
        {
            if(acting_entity->running && acting_entity->stalltime > _time + GAME_SPEED / 2)
            {
                acting_entity->stalltime = _time + GAME_SPEED / 2;
            }
            else if(!acting_entity->running && acting_entity->stalltime > _time + GAME_SPEED / 5)
            {
                acting_entity->stalltime = _time + GAME_SPEED / 5;
            }
        }

        return 1;

    }

    return 1;
}


void decide_stalker()
{
    entity *ent, *furthest = NULL;
    int i;
    int l = 0, r = 0;
    float maxz = 0.0f, z;

    if(stalker && stalking)
    {
        return;
    }

    firstplayer = NULL;

    for(i = 0; i < MAX_PLAYERS; i++)
    {
        if(player[i].ent)
        {
            firstplayer = player[i].ent;
            break;
        }
    }

    if(!firstplayer)
    {
        return;
    }

    for(i = 0; i < ent_max; i++)
    {
        ent = ent_list[i];

        if(ent->exists && !(ent->death_state & DEATH_STATE_DEAD) && (ent->modeldata.type & TYPE_ENEMY))
        {
            if(ent->position.x > firstplayer->position.x)
            {
                r++;
            }
            else
            {
                l++;
            }

            if((z = diff(ent->position.z, firstplayer->position.z)) >= maxz &&
                    (ent->modeldata.aimove == 0 || (ent->modeldata.aimove & AIMOVE1_CHASE))) // 2 mostly used type
            {
                maxz = z;
                furthest = ent;
            }
        }
    }

    if((l > 1 && !r) || (r > 1 && !l))
    {
        stalker = furthest;
        //printf("** stalker decided: %s @ _time %d\n", stalker->name, _time);
    }
}


void plan()
{
    decide_stalker();
}

void checkstalker()
{
    float maxspeed;
    e_run_state running = RUN_STATE_NONE;

    if(self != stalker)
    {
        return;
    }

    if(!firstplayer)
    {
        stalker = NULL;
        return;
    }

    if(stalking)
    {
        if(self->stalltime <= _time)
        {
            //printf("** stalk _time expired: %s @ _time %d\n", stalker->name, _time);
            stalker = NULL;
        }
        return;
    }

    if (validanim(self, ANI_RUN))
    {
        running |= RUN_STATE_START_X;
    }

    maxspeed = (running & RUN_STATE_START_X) ? self->modeldata.runspeed : self->modeldata.speed.x;

    self->velocity.x = maxspeed;
    self->velocity.z = 0;

    if(self->position.x > firstplayer->position.x)
    {
        self->velocity.x = -self->velocity.x;
    }

    self->running = running;


    self->stalltime = _time + (diff(self->position.x, firstplayer->position.x) + 150) / maxspeed * THINK_SPEED;

    adjust_walk_animation(firstplayer);

    stalking = 1;
    //printf("**start stalking: %s @ _time %d till @%d\n", stalker->name, time, self->stalltime);
}

int checkplanned()
{
    return 0;
}

/*
* Caskey, Damon V.
* 2023-05-24 (rewrite of orginal function by Utunnels, unknown date)
*
* Check if entity is outside of recall
* location, and if so, trigger the
* recall function.
*/
int ai_check_recall()
{
    entity* const acting_entity = self;
    const s_child_follow* const child_follow = acting_entity->modeldata.child_follow;

    if(!child_follow || acting_entity->link)
    {
        return 0;
    }

    /*
    * Set up recall range. X and Z using the
    * idle range x max when supplied values
    * are MIN/MAX is for legacy compatability.
    */

    const int valid_idle = validanim(acting_entity, ANI_IDLE);
    const int default_range = (valid_idle) ? acting_entity->modeldata.animation[ANI_IDLE]->range.x.max : MAX_INT;

    const s_range recall_range = {
        .x = {
            .max = (child_follow->recall_range.x.max == MAX_INT && valid_idle) ? default_range : child_follow->recall_range.x.max,
            .min = (child_follow->recall_range.x.min == MIN_INT && valid_idle) ? -default_range : child_follow->recall_range.x.min
        },

        .y = {
            .max = child_follow->recall_range.y.max,
            .min = child_follow->recall_range.y.min
        },

        .z = {
            .max = (child_follow->recall_range.z.max == MAX_INT && valid_idle) ? default_range : child_follow->recall_range.z.max,
            .min = (child_follow->recall_range.z.min == MIN_INT && valid_idle) ? -default_range : child_follow->recall_range.z.min
        },

        .base = {
            .max = child_follow->recall_range.base.max,
            .min = child_follow->recall_range.base.min
        }
    };    
    
    if(acting_entity->modeldata.subtype == SUBTYPE_FOLLOW && acting_entity->parent &&
            (!check_range_target_x(acting_entity, acting_entity->parent, NULL, recall_range.x.min, recall_range.x.max) ||
                !check_range_target_z(acting_entity, acting_entity->parent, NULL, recall_range.z.min, recall_range.z.max) ||
                !check_range_target_y(acting_entity, acting_entity->parent, NULL, recall_range.y.min, recall_range.y.max) ||
                !check_range_target_base(acting_entity, acting_entity->parent, NULL, recall_range.base.min, recall_range.base.max)))
    {
        acting_entity->takeaction = npc_recall;
        return 1;
    }
    return 0;
}

int ai_check_lie()
{
    if(self->drop 
		&& self->position.y == self->base 
		&& !self->velocity.y 
		&& validanim(self, ANI_RISEATTACK) 
		&& ((rand32() % (self->stalltime - _time + 1)) < 3) 
		&& (self->energy_state.health_current > 0 
		&& _time > self->staydown.riseattack_stall))
    {
        common_try_riseattack();
        return 1;
    }
    return 0;
}

int ai_check_grabbed()
{
    if (!self->link)
    {
        return 0;
    }

    if (self->grabbing)
    {
        return 0;
    }

    if (self->inpain & ~IN_PAIN_NONE)
    {
        return 0;
    }

    if (self->takeaction == common_prethrow)
    {
        return 0;
    }

    if (inair(self))
    {
        return 0;
    }

    if (_time < self->stalltime)
    {
        return 0;
    }

    if (!validanim(self, ANI_SPECIAL))
    {
        return 0;
    }

    check_special();

    return 1;
}

int ai_check_grab()
{
    if(self->grabbing && self->attacking == ATTACKING_NONE)
    {
        common_grab_check();
        return 1;
    }
    return 0;
}

int ai_check_escape()
{
    if((self->escapecount > self->modeldata.escapehits) && !inair(self) && validanim(self, ANI_SPECIAL2))
    {
        // Counter the player!
        check_costmove(ANI_SPECIAL2, 0, 0);
        return 1;
    }
    return 0;
}

int ai_check_busy()
{
    return self->link || !self->idling;
}

int ai_check_ducking()
{
    int indirect;
    entity *target = NULL;
    float t_rangex = 60.0f;
    float t_rangez = 30.0f;

    if ((self->ducking & DUCK_PREPARED) || (self->ducking & DUCK_RISE))
    {
        return 1;
    }

    if(self->link || inair(self))
    {
        return 0;
    }

    if( !validanim(self, ANI_DUCK) || !validanim(self, ANI_DUCKATTACK) )
    {
        return 0;
    }

    if(self->projectile & BLAST_ATTACK)
    {
        indirect = 1;
    }

    if(self->custom_target == NULL || !self->custom_target->exists ) target = normal_find_target(-1, 0);
    else target = self->custom_target;

    if(target && !(faction_check_can_damage(self, target, indirect)))
    {
        return 0;
    }

    if ((self->ducking & DUCK_ACTIVE) && self->animnum == ANI_DUCK)
    {
        if ( !target || inair(target) || !(target->ducking & DUCK_ACTIVE) ||
             (!check_range_target_all(self,target,ANI_DUCK, 0, 0) &&
                diff(self->position.x, target->position.x) > t_rangex && diff(self->position.z, target->position.z) > t_rangez) )
        {
            self->velocity.x = self->velocity.z = 0;
            tryduckrise(self);
            return 1;
        }
    }

    if(!target || !(target->ducking & DUCK_ACTIVE))
    {
        return 0;
    }
    else
    {
        if (self->ducking & DUCK_ACTIVE)
        {
            return 1;
        }
        else
        {
            int range_flag = check_range_target_all(self,target,ANI_DUCK, 0, 0);
            if (!range_flag)
            {
                if ( diff(self->position.x, target->position.x) <= t_rangex &&
                     diff(self->position.z, target->position.z) <= t_rangez) range_flag = 1;
            }
            if (!range_flag) return 0;

            if(self->ducking == DUCK_NONE)
            {
                tryduck(self);
            }
            return 1;
        }
    }

    return 0;
}


// A.I root
void common_think()
{

    if(self->death_state & DEATH_STATE_DEAD)
    {
        return;
    }    

    //if(checkplanned()) return;

    // too far away , do a warp
    if(ai_check_recall())
    {
        return;
    }

    // rise? try rise attack
    if(ai_check_lie())
    {
        return;
    }

    // Escape?
    if(ai_check_grabbed())
    {
        return;
    }

    //grabbing something
    if(ai_check_grab())
    {
        return;
    }

    // Enemies can now escape non-knockdown spammage (What a weird phrase)!
    if(ai_check_escape())
    {
        return;
    }

    // busy right now?
    if(ai_check_busy())
    {
        return;
    }

    // idle, so try to attack or judge next move
    // dont move if fall into a hole or off a wall
    if(common_attack())
    {
        return;
    }

    // target is ducking? try to ducking..
    if(ai_check_ducking())
    {
        return;
    }

    common_move();
}

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

void suicide()
{
   
    if(_time < self->stalltime)
    {
        return;
    }
    level_completed |= self->boss;
    level_completed_defeating_boss |= self->boss;

    kill_entity(self, KILL_ENTITY_TRIGGER_SUICIDE);
}



// Re-enter playfield
// Used by player_fall and player_takedamage
void player_die()
{
    int playerindex = self->playerindex;
    int i = 0;

    if(!(global_config.cheats & CHEAT_OPTIONS_LIVES_ACTIVE))
    {
        --player[playerindex].lives;
    }

    if(firstplayer == self)
    {
        firstplayer = NULL;
    }

    execute_pdie_script(playerindex);

    if(nomaxrushreset[4] >= 1)
    {
        nomaxrushreset[playerindex] = player[playerindex].ent->rush.max;
    }
    player[playerindex].ent = NULL;
    player[playerindex].spawnhealth = self->modeldata.health;
    player[playerindex].spawnmp = self->modeldata.mp;

    if(self->modeldata.death_config_flags & ~(DEATH_CONFIG_REMOVE_CORPSE_AIR | DEATH_CONFIG_REMOVE_CORPSE_GROUND))
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_PLAYER_DEATH);
    }
    else
    {
        self->think = NULL;
        self->takeaction = NULL;
        self->death_state |= DEATH_STATE_CORPSE;
    }

    if(player[playerindex].lives <= 0)
    {
        int all_p_ko = 0;

		// Count number of KO'd (dead) players, by looping player
		// indexes and incrementing when player does not have
		// an entity.
        for(i = 0; i < MAX_PLAYERS; i++)
        {
			if (!player[i].ent)
			{
				++all_p_ko;
			}
        }
        
		// If all players are KO'd, then KO count = 1.
		// Otherwise, set it to 0.
		all_p_ko = (all_p_ko >= MAX_PLAYERS) ? 1 : 0;

		// All players KO'd?
        if(all_p_ko)
        {
			int all_p_nojoin = 0;
			int all_p_nocredits = 0;

			// All players NOT joining?
			// Same logic as all player KO. 
            for(i = 0; i < MAX_PLAYERS; i++)
            {
				if (!player[i].joining)
				{
					++all_p_nojoin;
				}
            }

            all_p_nojoin = (all_p_nojoin >= MAX_PLAYERS) ? 1 : 0;

			// All players out of credits?
			// Same logic as all player KO.
            for(i = 0; i < MAX_PLAYERS; i++)
            {
				if (player[i].credits < 1)
				{
					++all_p_nocredits;
				}
            }			

            all_p_nocredits = (all_p_nocredits >= MAX_PLAYERS) ? 1 : 0;

			// Set the timer to a 10 second count down.
            timeleft = 10 * COUNTER_SPEED;

			// No one joining in?
            if(all_p_nojoin)
            {
				// If the player can't continue, let's set the time over
				// to end almost instantly so they won't have to wait.

				// If noshare is enabled, credit shares are not allowed. Verify all 
				// player individual credit supplies are empty. Otherwise credit
				// shares are allowed, so verify pool of credits is empty.
				if (noshare)
				{
					if (all_p_nocredits)
					{
						timeleft = COUNTER_SPEED / 2;
					}					
				}
				else
				{
					if (credits < 1)
					{
						timeleft = COUNTER_SPEED / 2;
					}
				}
            }
        }

        if(self->modeldata.weapon_properties.loss_condition & WEAPON_LOSS_CONDITION_STAGE)
        {
            player[playerindex].weapnum = level->setweap;
        }

        if(nomaxrushreset[4] != 2)
        {
            nomaxrushreset[playerindex] = 0;
        }
        
		return;
    }
    else
    {
        spawnplayer(playerindex);
        execute_respawn_script(playerindex);
        if(!nodropen)
        {
            if (savedata.joyrumble[playerindex]) control_rumble(playerindex, 1, 125);
            drop_all_enemies();
        }
    }

    if(!level->noreset)
    {
        timeleft = level->settime * COUNTER_SPEED;    // Feb 24, 2005 - This line moved here to set custom time
    }

}



int player_trymove(float xdir, float zdir)
{
    return common_trymove(xdir, zdir);
}

int check_energy(e_cost_check which, int ani)
{
    int result = FALSE;
    e_entity_type type;        //Entity type.
    s_energy_cost energy_cost;
	   
    // Get animation's energy_cost data.
    energy_cost.cost = self->modeldata.animation[ani]->energy_cost.cost;
	energy_cost.disable = self->modeldata.animation[ani]->energy_cost.disable;
	energy_cost.mponly = self->modeldata.animation[ani]->energy_cost.mponly;

    // Get entity type.
    type	   = self->modeldata.type;

    // If we're bind and special is overridden, then
    // return false.
    if(type & (TYPE_ENEMY  | TYPE_NPC))
    {
        if(check_bind_override(self, BIND_CONFIG_OVERRIDE_SPECIAL_AI))
        {
            return FALSE;
        }
    }
    else if(type & TYPE_PLAYER)
    {
        if(check_bind_override(self, BIND_CONFIG_OVERRIDE_SPECIAL_PLAYER))
        {
            return FALSE;
        }
    }

    if(validanim(self, ani))
    {
        // Caskey, Damon V.
        // 2010-05-08
        //
        // It is now possible to individually disable specials. In
        // many cases (weapons in particular) this can	help cut down the need for
        // superfluous models when differing abilities are desired for players,
        // enemies, or npcs.
        // Kratus (10-2021) Fixed the new broken code for "disable" flag check, back to the previous code
        if(!(energy_cost.disable == type													// Disabled by type?
                || (energy_cost.disable == -1)											    // Disabled for all?
                || (energy_cost.disable == -2 && (type & (TYPE_ENEMY  | TYPE_NPC)))		    // Disabled for all AI?
                || (energy_cost.disable == -3 && (type & (TYPE_PLAYER | TYPE_NPC)))	        // Disabled for players & NPCs?
                || (energy_cost.disable == -4 && (type & (TYPE_PLAYER | TYPE_ENEMY)))))     // Disabled for all AI?
        {
            // No seal or seal is less/same as energy cost?
            if(!self->seal || self->seal >= energy_cost.cost)
            {
                if((which == ENERGY_TYPE_MP && (energy_cost.mponly != COST_TYPE_HP_ONLY) && (self->energy_state.mp_current >= energy_cost.cost)) 
					||
                   (which == ENERGY_TYPE_HP && (energy_cost.mponly != COST_TYPE_MP_ONLY) &&  (self->energy_state.health_current > energy_cost.cost)))
                {
                    result = TRUE;
                }
                else
                {
                    //DC 2009-01-23
                    //Tried putting the CANT animation here to keep code compacted, but won't work. I'll come back to this.
                    //if (validanim(self,ani)){
                    //    ent_set_anim(self, ANI_CANT, 0);
                    //    self->takeaction = common_attack_proc;
                    //    player[self->playerindex].playkeys = 0;
                    //}
                }
            }
        }
    }

    return result;
}

// Caskey Damon V.
// 2018-05-10
//
// Replaces unreadable check_range() macro. Runs individual
// check range functions for each axis and returns true
// if target is within range of ALL.
int check_range_target_all(const entity *ent, const entity *target, const e_animations animation_id, const int range_min, const int range_max)
{
    // Must have a valid target entity.
    if(!target)
    {
        return 0;
    }

    // Get pointer to animation.
    const s_anim *animation = ent->modeldata.animation[animation_id];

    if (!check_range_target_x(ent, target, animation, range_min, range_max))
    {
        return 0;
    }

    if (!check_range_target_y(ent, target, animation, range_min, range_max))
    {
        return 0;
    }

    if (!check_range_target_z(ent, target, animation, range_min, range_max))
    {
        return 0;
    }

    if (!check_range_target_base(ent, target, animation, range_min, range_max))
    {
        return 0;
    }

    return 1;
}

// Caskey, Damon V.
// 2018-05-12
//
// Return true if target is within Base range
// of entity's animation.
int check_range_target_base(const entity *acting_entity, const entity *target, const s_anim *animation, const int range_min, const int range_max)
{
    int ent_base;
    int target_base;

    s_metric_range range;

    // Must have a target.
    if(!acting_entity || !target)
    {
        return 0;
    }

    range.max = range_max;
    range.min = range_min;

    if (animation)
    {
        range.max += animation->range.base.max;
        range.min += animation->range.base.min;
    }

    // Get positions cast as integers.
    ent_base       = (int)acting_entity->base;
    target_base    = (int)target->base;

    // Subtract entity Base position from target position.
    target_base -= ent_base;

    // Return true if final target location is
    // within range min and max.
    return (target_base >= range.min
            && target_base <= range.max);
}

// Caskey, Damon V.
// 2018-05-10
//
// Return true if target is within X range
// of entity's animation.
int check_range_target_x(const entity *acting_entity, const entity *target, const s_anim *animation, const int range_min, const int range_max)
{
    // Must have entities.
    if(!acting_entity || !target)
    {
        return 0;
    }

    // Get positions cast as integers.
    const int ent_x       = (int)acting_entity->position.x;
    const int target_x    = (int)target->position.x;

    s_metric_range range;

    range.max = range_max;
    range.min = range_min;

    if (animation)
    {
        range.max += animation->range.x.max;
        range.min += animation->range.x.min;
    }

    // Return true if final target location is
    // within range X min and max. Range comparison
    // is reversed when entity faces left.
    if(acting_entity->direction == DIRECTION_RIGHT)
    {
        // Add range to entity X position
        // for final X range coordinates.
        range.min = ent_x + range.min;
        range.max = ent_x + range.max;

        return (target_x >= range.min
                && target_x <= range.max);
    }
    else
    {
        // Subtract range from entity X
        // position for final X range coordinates.
        range.min = ent_x - range.min;
        range.max = ent_x - range.max;

        return (target_x <= range.min
                && target_x >= range.max);
    }
}

// Caskey, Damon V.
// 2018-05-10
//
// Return true if target is within Y range
// of entity's animation.
int check_range_target_y(const entity *acting_entity, const entity *target, const s_anim *animation, const int range_min, const int range_max)
{
    // Must have a target.
    if(!acting_entity || !target)
    {
        return 0;
    }

    s_metric_range range;

    range.max = range_max;
    range.min = range_min;

    if (animation)
    {
        range.max += animation->range.y.max;
        range.min += animation->range.y.min;
    }

    // Get positions cast as integers.
    const int ent_y = (int)acting_entity->position.y;
    const int target_y = (int)target->position.y - ent_y;

    // Return true if final target location is
    // within range min and max.
    return (target_y >= range.min
            && target_y <= range.max);
}

// Caskey, Damon V.
// 2018-05-12
//
// Return true if target is within Z range
// of entity's animation.
int check_range_target_z(const entity * acting_entity, const entity *target, const s_anim *animation, const int range_min, const int range_max)
{
    // Must have a target.
    if(!acting_entity || !target)
    {
        return 0;
    }

    s_metric_range range;

    range.min = range_min;
    range.max = range_max;

    if (animation)
    {
        range.max += animation->range.z.max;
        range.min += animation->range.z.min;
    }

    // Get positions cast as integers.
    const int ent_z = (int)acting_entity->position.z;
    const int target_z = (int)target->position.z - ent_z;

    // Return true if final target location is
    // within range min and max.
    return (target_z >= range.min
            && target_z <= range.max);
}

int check_special()
{
    entity *e;
    if((!level->nospecial || level->nospecial == 3) && validanim(self, ANI_SPECIAL) &&
            (check_energy(ENERGY_TYPE_HP, ANI_SPECIAL) || check_energy(ENERGY_TYPE_MP, ANI_SPECIAL))
       )
    {
        self->takeaction = common_attack_proc;
        set_attacking(self);
        memset(self->combostep, 0, sizeof(*self->combostep) * 5);

        e = self->link;
        if(e)
        {
            e->takeaction = NULL;
            ent_unlink(self);
            set_idle(e);
        }

        if(self->modeldata.smartbomb && !self->modeldata.dofreeze)
        {
            smart_bomb(self, self->modeldata.smartbomb); // do smartbomb immediately if it doesn't freeze screen
        }

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

        if(self->modeldata.dofreeze)
        {
            smartbomber = self;    // Freezes the animations of all enemies/players while special is executed
        }

        // Kratus (10-2021) Now the "infinite health cheat" affects players only, not enemies or npc
        // And now the "infinite health cheat" will only work when the cost is HEALTH, will not work when the cost is MP anymore
        if(self->modeldata.type & TYPE_PLAYER)
        {
            if(!nocost)
            {
                if(!(global_config.cheats & CHEAT_OPTIONS_HEALTH_ACTIVE))
                {
                    if(self->modeldata.animation[ANI_SPECIAL]->energy_cost.cost)
                    {
                        if(check_energy(ENERGY_TYPE_MP, ANI_SPECIAL))
                        {
                            self->energy_state.mp_current -= self->modeldata.animation[ANI_SPECIAL]->energy_cost.cost;
                        }
                        else
                        {
                            self->energy_state.health_current -= self->modeldata.animation[ANI_SPECIAL]->energy_cost.cost;
                        }
                    }
                }
                else
                {
                    if(self->modeldata.animation[ANI_SPECIAL]->energy_cost.cost)
                    {
                        if(check_energy(ENERGY_TYPE_MP, ANI_SPECIAL))
                        {
                            self->energy_state.mp_current -= self->modeldata.animation[ANI_SPECIAL]->energy_cost.cost;
                        }
                    }
                }
            }
        }
        else
        {
            if(!nocost)
            {
                if(self->modeldata.animation[ANI_SPECIAL]->energy_cost.cost)
                {
                    if(check_energy(ENERGY_TYPE_MP, ANI_SPECIAL))
                    {
                        self->energy_state.mp_current -= self->modeldata.animation[ANI_SPECIAL]->energy_cost.cost;
                    }
                    else
                    {
                        self->energy_state.health_current -= self->modeldata.animation[ANI_SPECIAL]->energy_cost.cost;
                    }
                }
            }
        }
        return 1;
    }
    return 0;
}

// Check keys for special move. Used several times, so I func'd it.
// Kratus (10-2021) Added new flags to use with another ATTACK# keys as an new alternative
int player_check_special()
{
    u64 thekey = 0;
    e_key_def player_keys = player[self->playerindex].playkeys;

    switch (global_config.ajspecial)
    {
    case AJSPECIAL_KEY_SPECIAL:
        
        if (player_keys & FLAG_SPECIAL)
        {
            thekey = FLAG_SPECIAL;
        }        

        break;

    case AJSPECIAL_KEY_DOUBLE:

        if (!validanim(self, ANI_BLOCK) && player_keys & FLAG_SPECIAL)
        {
            thekey = FLAG_SPECIAL;
        }
        else if (player_keys & FLAG_JUMP && player_keys & FLAG_ATTACK)
        {
            thekey = FLAG_SPECIAL;
        }
        break;

    case AJSPECIAL_KEY_ATTACK2:

        if (player_keys & FLAG_ATTACK2)
        {
            thekey = FLAG_SPECIAL;
        }
        break;

    case AJSPECIAL_KEY_ATTACK3:

        if (player_keys & FLAG_ATTACK3)
        {
            thekey = FLAG_SPECIAL;
        }
        break;

    case AJSPECIAL_KEY_ATTACK4:

        if (player_keys & FLAG_ATTACK4)
        {
            thekey = FLAG_SPECIAL;
        }
        break;
    
    default:
        thekey = 0;
    }

    if (!thekey)
    {
        return 0;
    }

    if(check_special())
    {
        self->stalltime = 0;
        player[self->playerindex].playkeys &= ~thekey;
        return 1;
    }
    else
    {
        return 0;
    }
}


void common_land()
{
    self->velocity.x = self->velocity.z = 0;
    if(self->animating)
    {
        return;
    }

    self->takeaction = NULL;
    set_idle(self);
}


//animal run when you lost it 3 times by tails
void runanimal()
{
    common_walk_anim(self);
    //ent_set_anim(self, ANI_WALK, 0);

    if(self->position.x < advancex - 80 || self->position.x > advancex + (videomodes.hRes + 80))
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_ANIMAL_RUN_OUT_OF_BOUNDS);
        return;
    }

    if(self->direction == DIRECTION_RIGHT)
    {
        self->position.x += self->modeldata.speed.x;
    }
    else
    {
        self->position.x -= self->modeldata.speed.x;
    }
}


void player_blink()
{
    self->blink = 1;
    if(_time >= self->stalltime)
    {
        player_die();
    }
}


void common_grabattack()
{
    if(self->animating)
    {
        return;
    }

    self->attacking = ATTACKING_NONE;

    if(!(self->combostep[0] || self->combostep[1] ||
            self->combostep[2] || self->combostep[3] ||
            self->combostep[4]))
    {
        ent_unlink(self);
    }

    if(self->link)
    {
        self->takeaction = common_grab;
        self->link->takeaction = common_grabbed;
        self->attacking = ATTACKING_NONE;
        ent_set_anim(self, ANI_GRAB, 0);
        set_pain(self->link, -1, 0);
        update_frame(self, self->animation->numframes - 1);
        update_frame(self->link, self->link->animation->numframes - 1);
    }
    else
    {
        self->takeaction = NULL;
        memset(self->combostep, 0, sizeof(*self->combostep) * 5);
        set_idle(self);
    }
}

// Function that causes the player to continue to move up or down until the animation has finished playing
void common_dodge()    // New function so players can dodge with up up or down down
{
    if(self->animating)    // Continues to move as long as the player is animating
    {
        return;
    }
    else    // Once done animating, returns to thinking
    {
        self->takeaction = NULL;
        self->velocity.x = self->velocity.z = 0;
        set_idle(self);
    }
}


void common_preduck()
{
    if(self->animating)
    {
        return;
    }
    doduck(self);
}


void common_idle()
{
    if(self->animating)
    {
        return;
    }
    self->takeaction = NULL;
    self->attacking = ATTACKING_NONE;
    self->idling = IDLING_PREPARED;
    common_idle_anim(self);
}


void tryduck(entity *ent)
{
    ent->running = RUN_STATE_NONE;
    if(validanim(ent, ANI_DUCKING))
    {
        ent->takeaction = common_preduck;
        ent->velocity.x = ent->velocity.z = 0;
        ent->ducking = DUCK_PREPARED;
        ent->idling = IDLING_NONE;
        ent_set_anim(ent, ANI_DUCKING, 0);
    }
    else
    {
        doduck(ent);
    }
}


void tryduckrise(entity *ent)
{
    ent->running = RUN_STATE_NONE;
    if(validanim(ent, ANI_DUCKRISE))
    {
        ent->takeaction = common_idle;
        ent->velocity.x = ent->velocity.z = 0;
        ent->ducking = DUCK_RISE;
        ent->idling = IDLING_NONE;
        ent_set_anim(ent, ANI_DUCKRISE, 0);
    }
    else
    {
        ent->takeaction = NULL;
        ent->idling = IDLING_PREPARED;
        common_idle_anim(self);
    }
}


void doduck(entity *ent)
{
    ent->takeaction = NULL;
    ent->velocity.x = ent->velocity.z = 0;
    ent->ducking = DUCK_ACTIVE;
    ent->idling |= IDLING_PREPARED;
    ent_set_anim(ent, ANI_DUCK, 0);
}


void common_prejump()
{
    if(self->animating)
    {
        return;
    }
    dojump(self->jump.velocity.y, self->jump.velocity.x, self->jump.velocity.z, self->jump.animation_id);
}


void tryjump(float jumpv, float jumpx, float jumpz, e_animations animation_id)
{
    self->jump.velocity.y = jumpv;
    self->jump.velocity.x = jumpx;
    self->jump.velocity.z = jumpz;
    self->jump.animation_id = animation_id;

    self->ducking = DUCK_NONE;
    if(validanim(self, ANI_JUMPDELAY))
    {
        self->takeaction = common_prejump;
        self->velocity.x = self->velocity.z = 0;

        self->idling = IDLING_NONE;
        ent_set_anim(self, ANI_JUMPDELAY, 0);
    }
    else
    {
        dojump(jumpv, jumpx, jumpz, animation_id);
    }
}


void dojump(float jumpv, float jumpx, float jumpz, e_animations animation_id)
{
    entity *dust;

    self->takeaction = common_jump;

    if(global_sample_list.jump >= 0)
    {
        sound_play_sample(global_sample_list.jump, 0, savedata.effectvol, savedata.effectvol, 100);
    }

    //Spawn jumpstart dust.
    if(self->modeldata.dust.jump_start >= 0)
    {
        dust = spawn(self->position.x, self->position.z, self->position.y, self->direction, NULL, self->modeldata.dust.jump_start, NULL);
        if(dust)
        {
            dust->spawntype = SPAWN_TYPE_DUST_JUMP;
            dust->base = self->position.y;
            dust->autokill |= AUTOKILL_ANIMATION_COMPLETE;
            execute_onspawn_script(dust);
        }
    }

    set_jumping(self);

    toss(self, jumpv);

    if(self->direction == DIRECTION_LEFT)
    {
        self->velocity.x = -jumpx;
    }
    else
    {
        self->velocity.x = jumpx;
    }

    self->velocity.z = jumpz;
    ent_set_anim(self, animation_id, 0);
}

// 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 *other)
{
    // Function that takes care of items when picked up
    set_opponent(self, other);

    /*
    * Legacy limited use replenish.
    * 
    * If item has a use_add value and collecting
    * entity has a limited use weapon, we add
    * use_add to uses remaining.
    */

    if(other->modeldata.weapon_properties.use_add)
    {
        if(self->weapent && self->weapent->modeldata.weapon_properties.weapon_state & WEAPON_STATE_LIMITED_USE)
        {
            self->weapent->modeldata.weapon_properties.use_count += other->modeldata.weapon_properties.use_add;

            if(global_sample_list.get >= 0)
            {
                sound_play_sample(global_sample_list.get, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        else
        {
            addscore(self->playerindex, other->modeldata.score);
            if(global_sample_list.get_2 >= 0)
            {
                sound_play_sample(global_sample_list.get_2, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
    }

    //end of weapons items section
    else if(other->modeldata.score)
    {
        addscore(self->playerindex, other->modeldata.score);
        if(global_sample_list.get_2 >= 0)
        {
            sound_play_sample(global_sample_list.get_2, 0, savedata.effectvol, savedata.effectvol, 100);
        }
    }
    else if(other->energy_state.health_current)
    {
        self->energy_state.health_current += other->energy_state.health_current;

        if(self->energy_state.health_current > self->modeldata.health)
        {
            self->energy_state.health_current = self->modeldata.health;
        }

        other->energy_state.health_current = 0;

        if(global_sample_list.get >= 0)
        {
            sound_play_sample(global_sample_list.get, 0, savedata.effectvol, savedata.effectvol, 100);
        }
    }
    else if(other->modeldata.mp)
    {
        self->energy_state.mp_current += other->modeldata.mp;

        if(self->energy_state.mp_current > self->modeldata.mp)
        {
            self->energy_state.mp_current = self->modeldata.mp;
        }

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

        if(global_sample_list.get_2 >= 0)
        {
            sound_play_sample(global_sample_list.get_2, 0, savedata.effectvol, savedata.effectvol, 100);
        }
    }
    else if(other->modeldata.makeinv)
    {
        // Mar 2, 2005 - New item makes player invincible
        self->invincible |= INVINCIBLE_INTANGIBLE;
        self->invinctime = _time + ABS(other->modeldata.makeinv);
        self->blink = (other->modeldata.makeinv > 0);

        if(global_sample_list.get_2 >= 0)
        {
            sound_play_sample(global_sample_list.get_2, 0, savedata.effectvol, savedata.effectvol, 100);
        }
    }
    else if(other->modeldata.smartbomb)
    {
        // Damages everything on the screen
        smart_bomb(self, other->modeldata.smartbomb);

        if(global_sample_list.get_2 >= 0)
        {
            sound_play_sample(global_sample_list.get_2, 0, savedata.effectvol, savedata.effectvol, 100);
        }
    }
    else if(other->modeldata.subtype == SUBTYPE_WEAPON)
    {
        dropweapon(0);
        self->weapent = other;
        set_weapon(self, other->modeldata.weapon_properties.weapon_index, 0);

        /* Move to weapon location if it's an "animal". */
        if(self->modeldata.weapon_properties.weapon_state & WEAPON_STATE_ANIMAL)
        {
            self->direction = other->direction;
            self->position.x = other->position.x;
            self->position.z = other->position.z;
        }

        if(!(other->modeldata.weapon_properties.weapon_state & WEAPON_STATE_LIMITED_USE) && self->modeldata.weapon_properties.weapon_state & WEAPON_STATE_LIMITED_USE)
        {
            other->modeldata.weapon_properties.weapon_state |= WEAPON_STATE_LIMITED_USE;
        }

        if(global_sample_list.get >= 0)
        {
            sound_play_sample(global_sample_list.get, 0, savedata.effectvol, savedata.effectvol, 100);
        }
    }
    else if(other->modeldata.subtype == SUBTYPE_PROJECTILE)
    {
        dropweapon(0);
        self->weapent = other;

        if(global_sample_list.get >= 0)
        {
            sound_play_sample(global_sample_list.get, 0, savedata.effectvol, savedata.effectvol, 100);
        }
    }
    else if(other->modeldata.credit)
    {
        if(!noshare)
        {
            credits++;
        }
        else
        {
            player[self->playerindex].credits++;
        }

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

        if(global_sample_list.one_up >= 0)
        {
            sound_play_sample(global_sample_list.one_up, 0, savedata.effectvol, savedata.effectvol, 100);
        }
    }

    if(other->modeldata.subtype != SUBTYPE_WEAPON && other->modeldata.subtype != SUBTYPE_PROJECTILE)
    {
        other->takeaction = suicide;
        if(!other->modeldata.instantitemdeath)
        {
            other->nextthink = _time + GAME_SPEED * 3;
        }
    }
    else
    {
        if (other->nextanim != DELAY_INFINITE) { other->nextanim = _time + GAME_SPEED * 999999; }
        other->nextthink = _time + GAME_SPEED * 999999;
    }
    other->position.z = ITEM_HIDE_POSITION_Z;
}

void player_fall_check()
{
    if(autoland != 2 && (player[self->playerindex].keys & (FLAG_MOVEUP | FLAG_JUMP)) == (FLAG_MOVEUP | FLAG_JUMP))
    {
		// mark it, so we will play land animation when hit the ground.
        self->damage_on_landing.attack_force = ATTACK_FORCE_LAND_COMMAND; 
    }
}

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

    if(other == NULL || (self->modeldata.grabfinish && self->animating && !self->grabwalking))
    {
        return;
    }

    if(self->base != other->base)
    {
        // Change this from ->position.y to ->base
        self->takeaction = NULL;
        ent_unlink(self);
        set_idle(self);
        return;
    }

    if(player_check_special())
    {
        return;
    }

    if(!nolost && self->modeldata.weapon_properties.loss_condition & WEAPON_LOSS_CONDITION_GRABBING)
    {
        dropweapon(1);
    }

    // grabturn code
    if(self->animation == self->modeldata.animation[ANI_GRABTURN])
    {
        // still turning? don't bother with anything else
        if(self->animating)
        {
            return;
        }

        // done turning? switch directions and return to grab animation
        else
        {
            if(self->direction == DIRECTION_RIGHT)
            {
                self->direction = DIRECTION_LEFT;
                other->direction = DIRECTION_RIGHT;
            }
            else
            {
                self->direction = DIRECTION_RIGHT;
                other->direction = DIRECTION_LEFT;
            }
            other->position.x = self->position.x + (((self->direction * 2) - 1) * self->modeldata.grabdistance);
            ent_set_anim(self, ANI_GRAB, 0);
            set_pain(other, -1, 0);
            update_frame(self, self->animation->numframes - 1);
            update_frame(other, other->animation->numframes - 1);
        }
    }

    self->attacking = ATTACKING_NONE; //for checking
    self->grabwalking = 0;

	// Move key opposite vs. dicretion?
    if(self->direction == DIRECTION_RIGHT ? (player[self->playerindex].keys & FLAG_MOVELEFT) : (player[self->playerindex].keys & FLAG_MOVERIGHT))
    {
        // initiating grabturn
        if(self->modeldata.grabturn)
        {
            // start animation if it exists...
            if(validanim(self, ANI_GRABTURN))
            {
                ent_set_anim(self, ANI_GRABTURN, 0);
                if(validanim(other, ANI_GRABBEDTURN))
                {
                    ent_set_anim(other, ANI_GRABBEDTURN, 0);
                }
                else if(validanim(other, ANI_GRABBED))
                {
                    ent_set_anim(other, ANI_GRABBED, 0);
                }
                else
                {
                    if ( ((!other->direction && self->position.x > other->position.x) || (other->direction && self->position.x < other->position.x)) && validanim(other, ANI_BACKPAIN) ) ent_set_anim(other, ANI_BACKPAIN, 0);
                    else ent_set_anim(other, ANI_PAIN, 0);
                }
                other->velocity.x = other->velocity.z = self->velocity.x = self->velocity.z = 0;
                other->position.x = self->position.x;
                return;
            }

            // otherwise, just turn around
            else
            {
                if(self->direction == DIRECTION_RIGHT)
                {
                    self->direction = DIRECTION_LEFT;
                    other->direction = DIRECTION_RIGHT;
                }
                else
                {
                    self->direction = DIRECTION_RIGHT;
                    other->direction = DIRECTION_LEFT;
                }
                ent_set_anim(self, ANI_GRAB, 0);
                set_pain(other, -1, 0);
                update_frame(self, self->animation->numframes - 1);
                update_frame(other, other->animation->numframes - 1);
                other->position.x = self->position.x + (((self->direction * 2) - 1) * self->modeldata.grabdistance);
            }
        }
        else if(!validanim(self, ANI_GRABWALK) && _time > self->releasetime)
        {
            // Release
            self->takeaction = NULL;
            ent_unlink(self);
            set_idle(self);
            return;
        }
    }
    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(validanim(self, ANI_GRABBACKWARD))
        {
            dograbattack(GRAB_ACTION_SELECT_BACKWARD);
        }
        else if(validanim(self, ANI_THROW))
        {
            if(self->modeldata.throwframewait >= 0)
            {
                doprethrow();
            }
            else
            {
                dothrow();
            }
        }
        else
        {
            dograbattack(GRAB_ACTION_SELECT_ATTACK);
        }
    }
    // grab forward
    else if((player[self->playerindex].playkeys & FLAG_ATTACK) &&
            validanim(self, ANI_GRABFORWARD) &&
            (!self->direction ?
             (player[self->playerindex].keys & FLAG_MOVELEFT) :
             (player[self->playerindex].keys & FLAG_MOVERIGHT)))
    {
        player[self->playerindex].playkeys &= ~FLAG_ATTACK;
        dograbattack(GRAB_ACTION_SELECT_FORWARD);
    }
    // grab up
    else if((player[self->playerindex].playkeys & FLAG_ATTACK) &&
            validanim(self, ANI_GRABUP) && (player[self->playerindex].keys & FLAG_MOVEUP))
    {
        player[self->playerindex].playkeys &= ~FLAG_ATTACK;
        dograbattack(GRAB_ACTION_SELECT_UP);
    }
    // grab down
    else if((player[self->playerindex].playkeys & FLAG_ATTACK) &&
            validanim(self, ANI_GRABDOWN) && (player[self->playerindex].keys & FLAG_MOVEDOWN))
    {
        player[self->playerindex].playkeys &= ~FLAG_ATTACK;
        dograbattack(GRAB_ACTION_SELECT_DOWN);
    }
    // normal grab attack
    else if((player[self->playerindex].playkeys & FLAG_ATTACK) && validanim(self, ANI_GRABATTACK))
    {
        player[self->playerindex].playkeys &= ~FLAG_ATTACK;
        dograbattack(GRAB_ACTION_SELECT_ATTACK);
    }
	// Kratus (10-2021) Added the vault animation
    else if(player[self->playerindex].playkeys & FLAG_JUMP && validanim(self, ANI_VAULT))
    {
        // Kratus (07-2022) Fixed the "3-vaults" loop bug, now will execute once as intended
        // Kratus (01-2023) Fixed an issue where grabattack2 animation nullifies vault if both are declared
        // Kratus (01-2023) Added some native behaviours
        player[self->playerindex].playkeys &= ~FLAG_JUMP;
        self->attacking = ATTACKING_ACTIVE;
        self->takeaction = common_grabattack;
        memset(self->combostep, 0, sizeof(*self->combostep) * 5);
        ent_set_anim(self, ANI_VAULT, 0);
    }
    // grab attack finisher
    else if(player[self->playerindex].playkeys & (FLAG_ATTACK | FLAG_JUMP))
    {
        player[self->playerindex].playkeys &= ~(FLAG_ATTACK | FLAG_JUMP);

        // Perform final blow
        if(validanim(self, ANI_GRABATTACK2) || validanim(self, ANI_ATTACK3))
        {
            dograbattack(GRAB_ACTION_SELECT_FINISH);
        }
        else
        {
            self->attacking = ATTACKING_ACTIVE;
            memset(self->combostep, 0, sizeof(*self->combostep) * 5);
            self->takeaction = common_grabattack;
            tryjump(self->modeldata.jumpheight, self->modeldata.jumpspeed, 0, ANI_JUMP);
        }
    }

    // grab walk code
    else if(validanim(self, ANI_GRABWALK) // check if grabwalk animation exists

            // if entity is still animating anything besides a grabwalk variant, don't let them move
            && (!self->animating || self->animation == self->modeldata.animation[ANI_GRABWALK]
                || self->animation == self->modeldata.animation[ANI_GRABWALKUP]
                || self->animation == self->modeldata.animation[ANI_GRABWALKDOWN]
                || self->animation == self->modeldata.animation[ANI_GRABBACKWALK]))
    {

        // z axis movement
        if(PLAYER_MIN_Z != PLAYER_MAX_Z)
        {
            if(player[self->playerindex].keys & FLAG_MOVEUP)
            {
                if(self->modeldata.grabwalkspeed)
                {
                    self->velocity.z = -self->modeldata.grabwalkspeed / 2;
                }
                else
                {
                    self->velocity.z = -self->modeldata.speed.x / 2;
                }
            }
            else if(player[self->playerindex].keys & FLAG_MOVEDOWN)
            {
                if(self->modeldata.grabwalkspeed)
                {
                    self->velocity.z = self->modeldata.grabwalkspeed / 2;
                }
                else
                {
                    self->velocity.z = self->modeldata.speed.x / 2;
                }
            }
            else if(!(player[self->playerindex].keys & (FLAG_MOVEUP | FLAG_MOVEDOWN)))
            {
                self->velocity.z = 0;
            }
        }

        // x axis movement
        if(player[self->playerindex].keys & FLAG_MOVELEFT)
        {
            if(self->modeldata.grabwalkspeed)
            {
                self->velocity.x = -self->modeldata.grabwalkspeed;
            }
            else
            {
                self->velocity.x = -self->modeldata.speed.x;
            }
        }

        else if(player[self->playerindex].keys & FLAG_MOVERIGHT)
        {
            if(self->modeldata.grabwalkspeed)
            {
                self->velocity.x = self->modeldata.grabwalkspeed;
            }
            else
            {
                self->velocity.x = self->modeldata.speed.x;
            }
        }
        else if(!((player[self->playerindex].keys & FLAG_MOVELEFT) || (player[self->playerindex].keys & FLAG_MOVERIGHT)) )
        {
            self->velocity.x = 0;
        }

        // setting the animations based on the velocity set above
        if(self->velocity.x || self->velocity.z)
        {
            if(((self->velocity.x > 0 && self->direction == DIRECTION_LEFT) || (self->velocity.x < 0 && self->direction == DIRECTION_RIGHT)) && validanim(self, ANI_GRABBACKWALK))
            {
                ent_set_anim(self, ANI_GRABBACKWALK, 0);
            }
            else if(self->velocity.z < 0 && validanim(self, ANI_GRABWALKUP))
            {
                ent_set_anim(self, ANI_GRABWALKUP, 0);
            }
            else if(self->velocity.z > 0 && validanim(self, ANI_GRABWALKDOWN))
            {
                ent_set_anim(self, ANI_GRABWALKDOWN, 0);
            }
            else
            {
                ent_set_anim(self, ANI_GRABWALK, 0);
            }
            if(self->animation == self->modeldata.animation[ANI_GRABWALKUP] && validanim(other, ANI_GRABBEDWALKUP))
            {
                ent_set_anim(other, ANI_GRABBEDWALKUP, 0);
            }
            else if(self->animation == self->modeldata.animation[ANI_GRABWALKDOWN] && validanim(other, ANI_GRABBEDWALKDOWN))
            {
                ent_set_anim(other, ANI_GRABBEDWALKDOWN, 0);
            }
            else if(self->animation == self->modeldata.animation[ANI_GRABBACKWALK] && validanim(other, ANI_GRABBEDBACKWALK))
            {
                ent_set_anim(other, ANI_GRABBEDBACKWALK, 0);
            }
            else if(validanim(other, ANI_GRABBEDWALK))
            {
                ent_set_anim(other, ANI_GRABBEDWALK, 0);
            }
            else if (validanim(other, ANI_GRABBED))
            {
                ent_set_anim(other, ANI_GRABBED, 0);
            }
            else
            {
                ent_set_anim(other, ANI_PAIN, 0);
            }
        }
        else
        {
            ent_set_anim(self, ANI_GRAB, 0);
            if (validanim(other, ANI_GRABBED))
            {
                ent_set_anim(other, ANI_GRABBED, 0);
            }
            else
            {
                ent_set_anim(other, ANI_PAIN, 0);
            }
        }
        // use check_link_move to set velocity, don't change it here
        other->velocity.z = other->velocity.x = 0;
        self->grabwalking = 1;
    }

    if(self->attacking != ATTACKING_NONE)
    {
        self->releasetime = _time + (GAME_SPEED / 2);    // reset releasetime when do collision
    }
}

void player_walkoff_check()
{
    /*
    * Turn. 
    */
    if(self->modeldata.air_control & AIR_CONTROL_WALKOFF_TURN)
    {
        if((player[self->playerindex].keys & FLAG_MOVELEFT))
        {
            self->direction = DIRECTION_LEFT;
        }
        else if((player[self->playerindex].keys & FLAG_MOVERIGHT))
        {
            self->direction = DIRECTION_RIGHT;
        }
    }

    /*
    * X stop. If not holding a Left or
    * Right key, kill horizontal momentum.
    */
    if (self->modeldata.air_control & AIR_CONTROL_WALKOFF_X_STOP)
    {
        if (!(player[self->playerindex].keys & (FLAG_MOVELEFT | FLAG_MOVERIGHT)))
        {
            if (self->velocity.x != 0)
            {
                self->velocity.x *= AIR_CONTROL_STOP_FACTOR;
            }
        }
    }

    /*
    * Z stop. If not holding an Up or
    * Down key, kill lateral momentum.
    */
    if (self->modeldata.air_control & AIR_CONTROL_WALKOFF_Z_STOP)
    {
        if (!(player[self->playerindex].keys & (FLAG_MOVEUP | FLAG_MOVEDOWN)))
        {
            if (self->velocity.z != 0)
            {
                self->velocity.z *= AIR_CONTROL_STOP_FACTOR;
            }
        }
    }

    /* Horizontal move control (if already moving). */
    if(self->modeldata.air_control & AIR_CONTROL_WALKOFF_X_ADJUST) //move?
    {
        if(((player[self->playerindex].keys & FLAG_MOVELEFT) && self->velocity.x > 0) ||
                ((player[self->playerindex].keys & FLAG_MOVERIGHT) && self->velocity.x < 0))
        {
            self->velocity.x = -self->velocity.x;
        }
    }

    /* Horizontal move control (any walkoff). */
    if(self->modeldata.air_control & AIR_CONTROL_WALKOFF_X_MOVE)
    {
        if(((player[self->playerindex].keys & FLAG_MOVELEFT) && self->velocity.x > 0) ||
                ((player[self->playerindex].keys & FLAG_MOVERIGHT) && self->velocity.x < 0))
        {
            self->velocity.x = -self->velocity.x;
        }

        if((player[self->playerindex].keys & FLAG_MOVELEFT) && (!self->velocity.x))
        {
            self->velocity.x -= self->modeldata.speed.x;
        }
        else if((player[self->playerindex].keys & FLAG_MOVERIGHT) && (!self->velocity.x))
        {
            self->velocity.x = self->modeldata.speed.x;
        }
    }

    /* Z move control (if already moving). */
    if(self->modeldata.air_control & AIR_CONTROL_WALKOFF_Z_ADJUST)
    {
        if(((player[self->playerindex].keys & FLAG_MOVEUP) && self->velocity.z > 0) ||
                ((player[self->playerindex].keys & FLAG_MOVEDOWN) && self->velocity.z < 0))
        {
            self->velocity.z = -self->velocity.z;
        }
    }

    /* Z move control (any walkoff). */
    if(self->modeldata.air_control & AIR_CONTROL_WALKOFF_Z_MOVE)
    {        
        if((player[self->playerindex].keys & FLAG_MOVEUP) && (!self->velocity.z))
        {
            self->velocity.z -= (0.5 * self->modeldata.speed.x);
        }
        else if((player[self->playerindex].keys & FLAG_MOVEDOWN) && (!self->velocity.z))
        {
            self->velocity.z = (0.5 * self->modeldata.speed.x);
        }
    }

    return;
}

void player_jump_check()
{
    int candospecial = 0;

    // Kratus (10-2021) Fixed the noaircancel function
    // Now the flag "2" works as intended and completely disables the cancelation between all jumping attacks
    // In the previous code, both flags 1 and 2 have the same effect and allow the cancelation after last jumpattack is finished
    if((!noaircancel) || (noaircancel&1 && !self->animating) || (noaircancel&3 && (self->animnum == self->jump.animation_id)))
    {
        //air special, copied and changed from Fugue's code
        if((!level->nospecial || level->nospecial == 3) && player[self->playerindex].playkeys & FLAG_SPECIAL)
        {
            if(validanim(self, ANI_JUMPSPECIAL))
            {
                // Kratus (10-2021) For safe, added an new step to check if the entity is not in the "jumpspecial" animation
                // Fixes the bug that constantly consumes health or mp if the special button is constantly pressed, even if
                // the current "jumpspecial" animation cycle is not finished yet
                if(self->animnum != ANI_JUMPSPECIAL)
                {
                    if(self->modeldata.animation[ANI_JUMPSPECIAL]->energy_cost.cost && check_energy(ENERGY_TYPE_MP, ANI_JUMPSPECIAL))
                    {
                        // Kratus (10-2021) Now the "infinite health cheat" will only work when the cost is HEALTH, will not work when the cost is MP anymore
                        self->energy_state.mp_current -= self->modeldata.animation[ANI_JUMPSPECIAL]->energy_cost.cost;
                        candospecial = 1;
                    }
                    else if(self->modeldata.animation[ANI_JUMPSPECIAL]->energy_cost.cost && check_energy(ENERGY_TYPE_HP, ANI_JUMPSPECIAL))
                    {
                        if(!(global_config.cheats & CHEAT_OPTIONS_HEALTH_ACTIVE))
                        {
                            self->energy_state.health_current -= self->modeldata.animation[ANI_JUMPSPECIAL]->energy_cost.cost;
                        }
                        candospecial = 1;
                    }
                    else if(validanim(self, ANI_JUMPCANT))
                    {
                        ent_set_anim(self, ANI_JUMPCANT, 0);
                        self->velocity.y = 0;
                    }
                }

                if(candospecial)
                {
                    player[self->playerindex].playkeys &= ~FLAG_SPECIAL;
                    self->attacking = ATTACKING_ACTIVE;

                    // Kratus (10-2021) Add a option to kill or not the xyz movement
                    // Kratus (04-2022) Minor fix on the jumpspecial code
                    if(!(self->modeldata.jumpspecial & 1))
                    {
                        self->velocity.x = self->velocity.z = 0; // Kill movement when the special starts
                        self->velocity.y = 0;
                    }

                    ent_set_anim(self, ANI_JUMPSPECIAL, 0);
                }
            }
        }//end of jumpspecial

        //jumpattacks, up down forward normal....we don't check energy cost
        else if(player[self->playerindex].playkeys & FLAG_ATTACK)
        {
            player[self->playerindex].playkeys &= ~FLAG_ATTACK;
            self->attacking = ATTACKING_ACTIVE;

            if((player[self->playerindex].keys & FLAG_MOVEDOWN) && validanim(self, ANI_JUMPATTACK2))
            {
                ent_set_anim(self, ANI_JUMPATTACK2, 0);
            }
            else if((player[self->playerindex].keys & FLAG_MOVEUP) && validanim(self, ANI_JUMPATTACK3))
            {
                ent_set_anim(self, ANI_JUMPATTACK3, 0);
            }
            else if(self->running && validanim(self, ANI_RUNJUMPATTACK))
            {
                ent_set_anim(self, ANI_RUNJUMPATTACK, 0);    // Added so an extra strong jump attack can be executed
            }
            else if(self->velocity.x != 0 && validanim(self, ANI_JUMPFORWARD))
            {
                ent_set_anim(self, ANI_JUMPFORWARD, 0);    // If moving and set, do this attack
            }
            else if(validanim(self, ANI_JUMPATTACK))
            {
                ent_set_anim(self, ANI_JUMPATTACK, 0);
            }
        }//end of jumpattack
    }

    /* 
    * Jump height control. Stop rising if jump
    * key is inactive.
    */
    if (self->modeldata.air_control & AIR_CONTROL_JUMP_Y_STOP)
    {
        if (!(player[self->playerindex].keys & FLAG_JUMP))
        {
            if (self->velocity.y > 0)
            {
                self->velocity.y *= AIR_CONTROL_STOP_FACTOR;
            }
        }
    }

    /* 
    * Jump X stop. If not holding a Left or
    * Right key, kill horizontal momentum.
    */
    if (self->modeldata.air_control & AIR_CONTROL_JUMP_X_STOP)
    {
        if (!(player[self->playerindex].keys & (FLAG_MOVELEFT | FLAG_MOVERIGHT)))
        {
            if (self->velocity.x != 0)
            {
                self->velocity.x *= AIR_CONTROL_STOP_FACTOR;
            }
        }
    }

    /*
    * Jump Z stop. If not holding an Up or
    * Down key, kill lateral momentum.
    */
    if (self->modeldata.air_control & AIR_CONTROL_JUMP_Z_STOP)
    {
        if (!(player[self->playerindex].keys & (FLAG_MOVEUP | FLAG_MOVEDOWN)))
        {
            if (self->velocity.z != 0)
            {
                self->velocity.z *= AIR_CONTROL_STOP_FACTOR;
            }
        }
    }

    /* Jump turn control. */
    if(self->modeldata.air_control & AIR_CONTROL_JUMP_TURN)
    {
        if((player[self->playerindex].keys & FLAG_MOVELEFT))
        {
            self->direction = DIRECTION_LEFT;
        }
        else if((player[self->playerindex].keys & FLAG_MOVERIGHT))
        {
            self->direction = DIRECTION_RIGHT;
        }
    }

    /* Jump horizontal move control (if already moving). */
    if(self->modeldata.air_control & AIR_CONTROL_JUMP_X_ADJUST)
    {
        if(((player[self->playerindex].keys & FLAG_MOVELEFT) && self->velocity.x > 0 ) ||
                ((player[self->playerindex].keys & FLAG_MOVERIGHT) && self->velocity.x < 0))
        {
            self->velocity.x = -self->velocity.x;
        }
    }
    
    /* Jump horizontal move control (any jump). */
    if(self->modeldata.air_control & AIR_CONTROL_JUMP_X_MOVE)
    {        
        if(((player[self->playerindex].keys & FLAG_MOVELEFT) && self->velocity.x > 0) ||
                ((player[self->playerindex].keys & FLAG_MOVERIGHT) && self->velocity.x < 0))
        {
            self->velocity.x = -self->velocity.x;
        }

        if((player[self->playerindex].keys & FLAG_MOVELEFT) && (!self->velocity.x))
        {
            self->velocity.x -= self->modeldata.speed.x;
        }
        else if((player[self->playerindex].keys & FLAG_MOVERIGHT) && (!self->velocity.x))
        {
            self->velocity.x = self->modeldata.speed.x;
        }
    }
        
    /* Jump Z move control (if already moving). */
    if(self->modeldata.air_control & AIR_CONTROL_JUMP_Z_ADJUST)
    {
        if(((player[self->playerindex].keys & FLAG_MOVEUP) && self->velocity.z > 0) ||
                ((player[self->playerindex].keys & FLAG_MOVEDOWN) && self->velocity.z < 0))
        {
            self->velocity.z = -self->velocity.z;
        }
    }

    /* Jump Z move control (any jump). */
    if(self->modeldata.air_control & AIR_CONTROL_JUMP_Z_MOVE)
    {
        if((player[self->playerindex].keys & FLAG_MOVELEFT))
        {
            self->direction = DIRECTION_LEFT;
        }
        else if((player[self->playerindex].keys & FLAG_MOVERIGHT))
        {
            self->direction = DIRECTION_RIGHT;
        }

        if(((player[self->playerindex].keys & FLAG_MOVEUP) && self->velocity.z > 0) ||
                ((player[self->playerindex].keys & FLAG_MOVEDOWN) && self->velocity.z < 0))
        {
            self->velocity.z = -self->velocity.z;
        }

        if((player[self->playerindex].keys & FLAG_MOVEUP) && (!self->velocity.z))
        {
            self->velocity.z -= (0.5 * self->modeldata.speed.x);
        }
        else if((player[self->playerindex].keys & FLAG_MOVEDOWN) && (!self->velocity.z))
        {
            self->velocity.z = (0.5 * self->modeldata.speed.x);
        }
    }

    return;
}

void player_pain_check()
{
    if(player_check_special())
    {
        self->inpain = IN_PAIN_NONE;
        self->rising = RISING_NONE;
        self->ducking = DUCK_NONE;
        self->inbackpain = 0;
    }
}

// check riseattack input up+attack
void player_lie_check()
{
    if(validanim(self, ANI_RISEATTACK) &&
            (player[self->playerindex].playkeys & FLAG_ATTACK) &&
            (player[self->playerindex].keys & FLAG_MOVEUP) &&
            (self->energy_state.health_current > 0 && _time > self->staydown.riseattack_stall))
    {
        player[self->playerindex].playkeys &= ~FLAG_ATTACK;
        if((player[self->playerindex].keys & FLAG_MOVELEFT))
        {
            self->direction = DIRECTION_LEFT;
        }
        if((player[self->playerindex].keys & FLAG_MOVERIGHT))
        {
            self->direction = DIRECTION_RIGHT;
        }
        self->stalltime = 0;
        set_riseattack(self, self->last_damage_type, 0);
    }
}

void player_charge_check()
{
    if(!((player[self->playerindex].keys & FLAG_JUMP) &&
            (player[self->playerindex].keys & FLAG_SPECIAL)))
    {
        self->takeaction = NULL;
        self->charging = 0;
        set_idle(self);
    }
}

// make a function so enemies can use
// UT: jumphack is a temporary fix for jump cancel
int check_costmove(int s, int fs, int jumphack)
{
    if(((fs == 1 && level->nospecial < 2) || (fs == 0 && level->nospecial == 0) || (fs == 0 && level->nospecial == 3)) &&
            (check_energy(ENERGY_TYPE_HP, s) ||
             check_energy(ENERGY_TYPE_MP, s))  )
    {
        if(!jumphack)
        {
            self->takeaction = common_attack_proc;
        }
        // Kratus (10-2021) Now the "infinite health cheat" affects players only, not enemies or npc
        // And now the "infinite health cheat" will only work when the cost is HEALTH, will not work when the cost is MP anymore
        if(self->modeldata.type & TYPE_PLAYER)
        {
            if(!nocost)
            {
                if(!(global_config.cheats & CHEAT_OPTIONS_HEALTH_ACTIVE))
                {
                    if(self->modeldata.animation[s]->energy_cost.cost)
                    {
                        if(check_energy(ENERGY_TYPE_MP, s))
                        {
                            self->energy_state.mp_current -= self->modeldata.animation[s]->energy_cost.cost;
                        }
                        else
                        {
                            self->energy_state.health_current -= self->modeldata.animation[s]->energy_cost.cost;
                        }
                    }
                }
                else
                {
                    if(self->modeldata.animation[s]->energy_cost.cost)
                    {
                        if(check_energy(ENERGY_TYPE_MP, s))
                        {
                            self->energy_state.mp_current -= self->modeldata.animation[s]->energy_cost.cost;
                        }
                    }
                }
            }
        }
        else
        {
            if(!nocost)
            {
                if(self->modeldata.animation[s]->energy_cost.cost)
                {
                    if(check_energy(ENERGY_TYPE_MP, s))
                    {
                        self->energy_state.mp_current -= self->modeldata.animation[s]->energy_cost.cost;
                    }
                    else
                    {
                        self->energy_state.health_current -= self->modeldata.animation[s]->energy_cost.cost;
                    }
                }
            }
        }

		self->running = RUN_STATE_NONE;
        self->velocity.x = self->velocity.z = 0;
        set_attacking(self);
        self->inpain = IN_PAIN_NONE;
        self->rising = RISING_NONE;
        self->inbackpain = 0;
        memset(self->combostep, 0, sizeof(*self->combostep) * 5);
        ent_unlink(self);
        ent_set_anim(self, s, 0);
        return 1;
    }
    return 0;
}

int match_combo(const e_key_def sequence[], s_player *p, const int l)
{
    int j, step;

    for(j = 0; j < l; j++)
    {
        step = p->combostep - 1 - j;
        step = (step + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;

        // old: !(a[l - 1 - j]&p->combokey[step])
        if( ((sequence[l - 1 - j]&p->combokey[step]) ^ sequence[l - 1 - j]) ) // if input&combokey == 0 then not good btn
        {
            return 0;
        }
    }

    return 1;
}


int check_combo()
{
    int i, maxstep = -1, maxkeys = -1, valid = -1;
    s_com *com;
    s_player *p;

    p = player + self->playerindex;

    for(i = 0; i < self->modeldata.specials_loaded; i++)
    {
        com = self->modeldata.special + i;

        if(self->animation->cancel != ANIMATION_CANCEL_DISABLED &&
                (self->animnum != com->cancel ||
                 com->frame.min > self->animpos ||
                 com->frame.max < self->animpos ||
                 self->animation->hit_count < com->hits))
        {
            continue;
        }
        else if(self->animation->cancel == ANIMATION_CANCEL_DISABLED &&
                (com->cancel || !self->idling || diff(self->position.y, self->base) > 1) )
        {
            continue;
        }

        // find the longest possible combo with more keys pressed concurrently
        if( com->steps >= maxstep && com->numkeys > maxkeys &&
                validanim(self, com->anim) &&
                (check_energy(ENERGY_TYPE_MP, com->anim) || check_energy(ENERGY_TYPE_HP, com->anim)) &&
                match_combo(com->input, p, com->steps))
        {
            // combo valid! but which better? The longest combo that has with more keys pressed concurrently
            valid = com->anim;
            maxstep = com->steps;
            maxkeys = com->numkeys;
        }
    }//end of for

    if(valid >= 0 && check_costmove(valid, 1, self->jumping))
    {
        return 1;
    }

    return 0;
}

int player_preinput()
{
    if(player[self->playerindex].playkeys)
    {
        if(check_combo())
        {
            player[self->playerindex].playkeys &= ~FLAG_CONTROLKEYS;
            return 1;
        }
    }
    return 0;
}


/*
* Caskey, Damon V.
* 2023-12-29    
* 
* Stop running if player
* no longer inputs direction
* that started the run.
*/
void run_try_runstop_check(entity* acting_entity, const e_RunXDirection movex, const e_RunZDirection movez, const e_RunXDirection running_x, const e_RunZDirection running_z, const int runConfigFlags, const int dashCommandFlag, const int dashFixedFlag, const int enabledFlag, const int stopStateFlag) {
    if (!(runConfigFlags & enabledFlag)        
        || (dashFixedFlag && !acting_entity->animating)
        || ((dashCommandFlag && !acting_entity->animating)
            || (diff(movex, running_x) && (movez == RUN_DIR_Z_NONE || acting_entity->running & RUN_STATE_START_X))
            || (diff(movez, running_z) && (movex == RUN_DIR_X_NONE || acting_entity->running & RUN_STATE_START_Z)))) {
        acting_entity->running = stopStateFlag;
    }
}

/*
* Caskey, Damon V.
* 2023-12-29
* 
* Check player input and run stop
* function if needed.
*/
void run_try_runstop_player(entity* acting_entity, const s_player* acting_player)
{   
    const e_run_state run_state = acting_entity->running;

    if (run_state == RUN_STATE_NONE)
    {
        return;
    }

    /*
    * Get player directional input
    * from key status.
    */

    const e_RunZDirection movez = (acting_player->keys & FLAG_MOVEUP) ? RUN_DIR_Z_UP : (acting_player->keys & FLAG_MOVEDOWN) ? RUN_DIR_Z_DOWN : RUN_DIR_Z_NONE;
    const e_RunXDirection movex = (acting_player->keys & FLAG_MOVELEFT) ? RUN_DIR_X_LEFT : (acting_player->keys & FLAG_MOVERIGHT) ? RUN_DIR_X_RIGHT : RUN_DIR_X_NONE;

    /*
    * Get a running direction from
    * velocity.
    */

    const e_RunZDirection running_z = run_state ? ((acting_entity->velocity.z < 0) ? RUN_DIR_Z_UP : (acting_entity->velocity.z > 0) ? RUN_DIR_Z_DOWN : RUN_DIR_Z_NONE) : RUN_DIR_Z_NONE;
    const e_RunXDirection running_x = run_state ? ((acting_entity->velocity.x < 0) ? RUN_DIR_X_LEFT : (acting_entity->velocity.x > 0) ? RUN_DIR_X_RIGHT : RUN_DIR_X_NONE) : RUN_DIR_X_NONE;

    /* Just for readability below. */
    const e_run_config_flags acting_run_config_flags = acting_entity->modeldata.run_config_flags;

    /*
    * Stop running?
    *
    * 1. X axis direction.
    * 2. Able to run in X direction?
    * 3. Fixed dash? Fixed dashes stop
    * when the animation completes. They
    * ignore releasing the key and do not
    * stop on a perpendicular axis command
    * even if we can't move on the other
    * axis. Note the other axis is ignored
    * by logic downstream.
    *
    * 4. Stop on following:
    * - No direction command.
    * - Direction command opposes current direction.
    * - Perpendicular axis command when we don't have the flag enabled.
    *
    * Repeat check for opposite direction
    * and for Z axis.
    */

    switch (running_x) {
    case RUN_DIR_X_LEFT:
        run_try_runstop_check(acting_entity, movex, movez, running_x, running_z, acting_run_config_flags, RUN_CONFIG_X_LEFT_DASH_COMMAND, RUN_CONFIG_X_LEFT_DASH_FIXED, RUN_CONFIG_X_LEFT_ENABLED, RUN_STATE_NONE);
        break;

    case RUN_DIR_X_RIGHT:
        run_try_runstop_check(acting_entity, movex, movez, running_x, running_z, acting_run_config_flags, RUN_CONFIG_X_RIGHT_DASH_COMMAND, RUN_CONFIG_X_RIGHT_DASH_FIXED, RUN_CONFIG_X_RIGHT_ENABLED, RUN_STATE_NONE);
        break;

    case RUN_DIR_X_NONE:
        break;
    }

    switch (running_z) {
    case RUN_DIR_Z_UP:
        run_try_runstop_check(acting_entity, movex, movez, running_x, running_z, acting_run_config_flags, RUN_CONFIG_Z_UP_DASH_COMMAND, RUN_CONFIG_Z_UP_DASH_FIXED, RUN_CONFIG_Z_UP_ENABLED, RUN_STATE_NONE);
        break;

    case RUN_DIR_Z_DOWN:
        run_try_runstop_check(acting_entity, movex, movez, running_x, running_z, acting_run_config_flags, RUN_CONFIG_Z_DOWN_DASH_COMMAND, RUN_CONFIG_Z_DOWN_DASH_FIXED, RUN_CONFIG_Z_DOWN_ENABLED, RUN_STATE_NONE);
        break;

    case RUN_DIR_Z_NONE:
        break;
    }
}



void player_think()
{
    typedef enum e_local_action_flags
    {
        ACTION_NONE,
        ACTION_WALK,
        ACTION_UP,
        ACTION_DOWN,
        ACTION_RUN
    } e_local_action_flags;

    e_local_action_flags action = 0;
    int bkwalk = 0;   //backwalk
    int t = 0;
    int t2 = 0;    
    entity *other = NULL;
    float altdiff;
    int notinair;
    float initial_jump_velocity_z = 0.0;

    entity* acting_entity = self;

    static const e_key_def sequence_left_left[] = {FLAG_MOVELEFT, FLAG_MOVELEFT};
    static const e_key_def sequence_right_right[] = {FLAG_MOVERIGHT, FLAG_MOVERIGHT};
    static const e_key_def sequence_up_up[] = {FLAG_MOVEUP, FLAG_MOVEUP};
    static const e_key_def sequence_down_down[] = {FLAG_MOVEDOWN, FLAG_MOVEDOWN};
    static const e_key_def sequence_back_attack[] = {FLAG_BACKWARD, FLAG_ATTACK};

    
    int pli = acting_entity->playerindex;
    s_player *acting_player = player + pli;

    if(acting_player->ent != acting_entity || acting_entity->death_state & DEATH_STATE_DEAD)
    {
        return;
    }

    // check endlevel item
    if((other = find_ent_here(acting_entity, acting_entity->position.x, acting_entity->position.z, TYPE_ENDLEVEL, NULL)) && diff(acting_entity->position.y, other->position.y) <= 0.1)
    {
        int no_reached_flag = 0, sum_reached = 0;;
        int i;

        for (i = 0; i < MAX_PLAYERS; i++)
        {
            if (!reached[i]) ++no_reached_flag;
        }
        no_reached_flag = (no_reached_flag >= MAX_PLAYERS) ? 1 : 0;

        if(no_reached_flag)
        {
            addscore(pli, other->modeldata.score);
        }
        reached[pli] = 1;

        for (i = 0; i < MAX_PLAYERS; i++)
        {
            sum_reached += reached[i];
        }

        if (!other->modeldata.subtype || (other->modeldata.subtype == SUBTYPE_BOTH && sum_reached >= (count_ents(TYPE_PLAYER))))
        {
            level_completed = 1;

            if(other->modeldata.branch)
            {
                strncpy( branch_name, other->modeldata.branch, MAX_NAME_LEN);    //now, you can branch to another level
            }
            return;
        }
    }

    if(_time > acting_entity->rush.time)
    {
        acting_entity->rush.count = 0;
        acting_entity->rush.time = 0;
    }

    if(player_preinput())
    {
        goto endthinkcheck;
    }

    if(acting_entity->charging)
    {
        player_charge_check();
        goto endthinkcheck;
    }

    if(acting_entity->inpain & ~IN_PAIN_NONE || (acting_entity->link && !acting_entity->grabbing))
    {
        player_pain_check();
        goto endthinkcheck;
    }

    // falling? check for landing
    if(acting_entity->projectile & BLAST_TOSS)
    {
        player_fall_check();
        goto endthinkcheck;
    }

    // grab section, dont move if still animating
    if(acting_entity->grabbing && acting_entity->attacking == ATTACKING_NONE && acting_entity->takeaction != common_throw_wait)
    {
        player_grab_check();
        goto endthinkcheck;
    }

    // jump section
    if(acting_entity->jumping)
    {
        player_jump_check();
        goto endthinkcheck;
    }

    if(acting_entity->animnum == ANI_WALKOFF)
    {
        player_walkoff_check();
        goto endthinkcheck;
    }

    if(acting_entity->drop && acting_entity->position.y == acting_entity->base && !acting_entity->velocity.y)
    {
        player_lie_check();
        goto endthinkcheck;
    }


    // cant do anything if busy
    if(!acting_entity->idling && !(acting_entity->animation->idle && acting_entity->animation->idle[acting_entity->animpos]))
    {
        goto endthinkcheck;
    }


    // Check if entity is under a platform
    /*if(acting_entity->modeldata.move_config_flags & MOVE_CONFIG_SUBJECT_TO_PLATFORM && (heightvar = acting_entity->animation->size.y ? acting_entity->animation->size.y : acting_entity->modeldata.size.y) &&
            validanim(acting_entity, ANI_DUCK) && check_platform_between(acting_entity->position.x, acting_entity->position.z, acting_entity->position.y, acting_entity->position.y + heightvar, acting_entity))
    {
        acting_entity->idling = IDLING_NONE;
        acting_entity->ducking = DUCK_ACTIVE;
        acting_entity->takeaction = common_stuck_underneath;
        ent_set_anim(acting_entity, ANI_DUCK, 0);
        goto endthinkcheck;
    }*/

    altdiff = diff(acting_entity->position.y, acting_entity->base);
    notinair = (acting_entity->landed_on_platform ? altdiff < 5 : altdiff < 2);

    if(acting_player->playkeys & FLAG_MOVEUP)
    {
        t = (notinair && match_combo(sequence_up_up, acting_player, 2));
        if(t && (acting_entity->modeldata.run_config_flags & (RUN_CONFIG_Z_UP_ENABLED | RUN_CONFIG_Z_UP_INITIAL)) == (RUN_CONFIG_Z_UP_ENABLED | RUN_CONFIG_Z_UP_INITIAL) && validanim(acting_entity, ANI_RUN))
        {
            acting_player->playkeys &= ~FLAG_MOVEUP;
            acting_player->combostep = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            acting_entity->running |= RUN_STATE_START_X;    // Player begins to run
        }
        else if(t && validanim(acting_entity, ANI_ATTACKUP))
        {
            // New u u combo attack
            acting_player->playkeys &= ~FLAG_MOVEUP;
            acting_entity->takeaction = common_attack_proc;
            set_attacking(acting_entity);
            acting_entity->combostep[0] = 0;
            acting_entity->velocity.x = acting_entity->velocity.z = 0;
            ent_set_anim(acting_entity, ANI_ATTACKUP, 0);
            acting_player->combostep = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS; // this workaround deals default freespecial2
            goto endthinkcheck;
        }
        else if(t && validanim(acting_entity, ANI_DODGE))
        {
            // New dodge move like on SOR3
            acting_player->playkeys &= ~FLAG_MOVEUP;
            acting_entity->takeaction = common_dodge;
            acting_entity->combostep[0] = 0;
            acting_entity->idling = IDLING_NONE;
            acting_entity->velocity.z = -acting_entity->modeldata.speed.x * 1.75;
            acting_entity->velocity.x = 0;// OK you can use jumpframe to modify this anyway
            ent_set_anim(acting_entity, ANI_DODGE, 0);
            acting_player->combostep = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            goto endthinkcheck;
        }
    }

    if(acting_player->playkeys & FLAG_MOVEDOWN)
    {
        t = (notinair && match_combo(sequence_down_down, acting_player, 2));
        if(t && (acting_entity->modeldata.run_config_flags & (RUN_CONFIG_Z_DOWN_ENABLED | RUN_CONFIG_Z_DOWN_INITIAL)) == (RUN_CONFIG_Z_DOWN_ENABLED | RUN_CONFIG_Z_DOWN_INITIAL) && validanim(acting_entity, ANI_RUN))
        {
            acting_player->playkeys &= ~FLAG_MOVEDOWN;
            acting_player->combostep = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            acting_entity->running |= RUN_STATE_START_Z;    // Player begins to run
        }
        else if(t && validanim(acting_entity, ANI_ATTACKDOWN))
        {
            // New d d combo attack
            acting_player->playkeys &= ~FLAG_MOVEDOWN;
            acting_entity->takeaction = common_attack_proc;
            set_attacking(acting_entity);
            acting_entity->velocity.x = acting_entity->velocity.z = 0;
            acting_entity->combostep[0] = 0;
            ent_set_anim(acting_entity, ANI_ATTACKDOWN, 0);
            acting_player->combostep = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            goto endthinkcheck;
        }
        else if(t && validanim(acting_entity, ANI_DODGE))
        {
            // New dodge move like on SOR3
            acting_player->playkeys &= ~FLAG_MOVEDOWN;
            acting_entity->takeaction = common_dodge;
            acting_entity->combostep[0] = 0;
            acting_entity->idling = IDLING_NONE;
            acting_entity->velocity.z = acting_entity->modeldata.speed.x * 1.75;
            acting_entity->velocity.x = 0;
            ent_set_anim(acting_entity, ANI_DODGE, 0);
            acting_player->combostep = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            goto endthinkcheck;
        }
    }

    if((acting_player->playkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT)))
    {
        int t3;
        const unsigned int command_match_left = (notinair && (acting_entity->direction == DIRECTION_LEFT && match_combo(sequence_left_left, acting_player, 2)));
        const unsigned int command_match_right = (notinair && (acting_entity->direction == DIRECTION_RIGHT && match_combo(sequence_right_right, acting_player, 2)));

        t = (notinair && ((acting_entity->direction == DIRECTION_RIGHT && match_combo(sequence_right_right, acting_player, 2)) || (acting_entity->direction == DIRECTION_LEFT && match_combo(sequence_left_left, acting_player, 2))));
        t3 = (notinair && acting_entity->modeldata.facing && ((acting_entity->direction == DIRECTION_RIGHT && match_combo(sequence_left_left, acting_player, 2)) || (acting_entity->direction == DIRECTION_LEFT && match_combo(sequence_right_right, acting_player, 2))));

        if (command_match_left && (acting_entity->modeldata.run_config_flags & (RUN_CONFIG_X_LEFT_ENABLED | RUN_CONFIG_X_LEFT_INITIAL)) == (RUN_CONFIG_X_LEFT_ENABLED | RUN_CONFIG_X_LEFT_INITIAL) && validanim(acting_entity, ANI_RUN)) {

            acting_player->playkeys &= ~(FLAG_MOVELEFT | FLAG_MOVERIGHT); // usually left + right is not acceptable, so it is OK to null both
            acting_player->combostep = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            acting_entity->running |= RUN_STATE_START_X;    // Player begins to run
        }
        else if(command_match_right && (acting_entity->modeldata.run_config_flags & (RUN_CONFIG_X_RIGHT_ENABLED | RUN_CONFIG_X_RIGHT_INITIAL)) == (RUN_CONFIG_X_RIGHT_ENABLED | RUN_CONFIG_X_RIGHT_INITIAL) && validanim(acting_entity, ANI_RUN)) {
            
            acting_player->playkeys &= ~(FLAG_MOVELEFT | FLAG_MOVERIGHT); // usually left + right is not acceptable, so it is OK to null both
            acting_player->combostep = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            acting_entity->running |= RUN_STATE_START_X;    // Player begins to run
        }
        else if(t3 && validanim(acting_entity, ANI_BACKRUN))
        {
            acting_player->playkeys &= ~(FLAG_MOVELEFT | FLAG_MOVERIGHT); // usually left + right is not acceptable, so it is OK to null both
            acting_player->combostep = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            acting_entity->running |= RUN_STATE_START_X;    // Player begins to run
        }
        else if(t && validanim(acting_entity, ANI_ATTACKFORWARD))
        {
            acting_player->playkeys &= ~(FLAG_MOVELEFT | FLAG_MOVERIGHT);
            acting_entity->takeaction = common_attack_proc;
            set_attacking(acting_entity);
            acting_entity->velocity.x = acting_entity->velocity.z = 0;
            acting_entity->combostep[0] = 0;
            ent_set_anim(acting_entity, ANI_ATTACKFORWARD, 0);
            acting_player->combostep = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            goto endthinkcheck;
        }
    }

    /*
    * Attack both. Attack + Key mapped
    * by ajspecial.
    */

    if( (global_config.ajspecial == AJSPECIAL_KEY_SPECIAL && (acting_player->playkeys & FLAG_JUMP) && validanim(acting_entity, ANI_ATTACKBOTH))||
        (global_config.ajspecial == AJSPECIAL_KEY_ATTACK2 && (acting_player->playkeys & FLAG_JUMP) && validanim(acting_entity, ANI_ATTACKBOTH))||
        (global_config.ajspecial == AJSPECIAL_KEY_ATTACK3 && (acting_player->playkeys & FLAG_JUMP) && validanim(acting_entity, ANI_ATTACKBOTH))||
        (global_config.ajspecial == AJSPECIAL_KEY_ATTACK4 && (acting_player->playkeys & FLAG_JUMP) && validanim(acting_entity, ANI_ATTACKBOTH)))
    {
        if((acting_player->keys & FLAG_ATTACK) && notinair)
        {
            acting_player->playkeys &= ~FLAG_JUMP;
            acting_entity->takeaction = common_attack_proc;
            set_attacking(acting_entity);
            acting_entity->velocity.x = acting_entity->velocity.z = 0;
            acting_entity->combostep[0] = 0;
            acting_entity->stalltime = 0;    // If attack is pressed, holding down attack to execute attack3 is no longer valid
            ent_set_anim(acting_entity, ANI_ATTACKBOTH, 0);
            goto endthinkcheck;
        }
    }

    /*
    * Command MP Charge.
    */

    if((acting_player->playkeys & FLAG_JUMP) &&  validanim(acting_entity, ANI_CHARGE))
    {
        if((acting_player->keys & FLAG_SPECIAL) && notinair)
        {
            acting_player->playkeys &= ~FLAG_JUMP;
            acting_entity->takeaction = common_charge;
            acting_entity->combostep[0] = 0;
            acting_entity->velocity.x = acting_entity->velocity.z = 0;
            acting_entity->stalltime = 0;
            set_charging(acting_entity);
            ent_set_anim(acting_entity, ANI_CHARGE, 0);
            goto endthinkcheck;
        }
    }

    /*
    * Handle special key. Check for part of a 
    * freespecial command, and then blocking.
    */

    if(acting_player->playkeys & FLAG_SPECIAL)    //    The special button can now be used for freespecials
    {
        if( validanim(acting_entity, ANI_SPECIAL2) && notinair &&
                (acting_entity->direction == DIRECTION_LEFT ?
                 (acting_player->keys & FLAG_MOVELEFT) :
                 (acting_player->keys & FLAG_MOVERIGHT))  )
        {
            if(check_costmove(ANI_SPECIAL2, 0, 0))
            {
                acting_player->playkeys &= ~FLAG_SPECIAL;
                goto endthinkcheck;
            }
        }

		// Blocking.
        if(validanim(acting_entity, ANI_BLOCK) && notinair && !(acting_entity->modeldata.block_config_flags & BLOCK_CONFIG_DISABLED))
        {
            acting_player->playkeys &= ~FLAG_SPECIAL;

			// Set up flags, action, and block animations.
			do_active_block(acting_entity);

			goto endthinkcheck;
        }
    }

    /*
    * Breakout special.
    */

    if(notinair && player_check_special())
    {
        goto endthinkcheck;    // So you don't perform specials falling off the edge
    }

    /*
    * Charge attack (hold Attack & release).
    */

    if((acting_player->releasekeys & FLAG_ATTACK))
    {
        if(acting_entity->stalltime && notinair &&
                ((validanim(acting_entity, ANI_CHARGEATTACK) && acting_entity->stalltime + (GAME_SPEED * acting_entity->modeldata.animation[ANI_CHARGEATTACK]->charge_time) < _time) ||
                 (!validanim(acting_entity, ANI_CHARGEATTACK) && validanim(acting_entity, animattacks[acting_entity->modeldata.atchain[acting_entity->modeldata.chainlength - 1] - 1])
                  && acting_entity->modeldata.chainlength > 0 && acting_entity->stalltime + (GAME_SPEED * acting_entity->modeldata.animation[animattacks[acting_entity->modeldata.atchain[acting_entity->modeldata.chainlength - 1] - 1]]->charge_time) < _time)))
        {
            acting_entity->takeaction = common_attack_proc;
            set_attacking(acting_entity);
            acting_entity->velocity.x = acting_entity->velocity.z = 0;

            acting_entity->stalltime = 0;
            acting_entity->combostep[0] = 0;

            if(global_sample_list.punch >= 0)
            {
                sound_play_sample(global_sample_list.punch, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            if(validanim(acting_entity, ANI_CHARGEATTACK))
            {
                ent_set_anim(acting_entity, ANI_CHARGEATTACK, 0);
            }
            else if(validanim(acting_entity, animattacks[acting_entity->modeldata.atchain[acting_entity->modeldata.chainlength - 1] - 1]))
            {
                ent_set_anim(acting_entity, animattacks[acting_entity->modeldata.atchain[acting_entity->modeldata.chainlength - 1] - 1], 0);
            }
            goto endthinkcheck;
        }
        acting_entity->stalltime = 0;
    }

    /*
    * Attack button. Handle basic attack 
    * button actions (attack, get, etc.).
    */

    if((acting_player->playkeys & FLAG_ATTACK) && notinair)
    {
        acting_player->playkeys &= ~FLAG_ATTACK;
        acting_entity->stalltime = 0;    // Disable the attack3 stalltime

        if((acting_entity->ducking & DUCK_ACTIVE) && validanim(acting_entity, ANI_DUCKATTACK) && PLAYER_MIN_Z == PLAYER_MAX_Z) //acting_player->keys & FLAG_MOVEDOWN
        {
            acting_entity->takeaction = common_attack_proc;
            set_attacking(acting_entity);
            acting_entity->velocity.x = acting_entity->velocity.z = 0;
            acting_entity->combostep[0] = 0;
            ent_set_anim(acting_entity, ANI_DUCKATTACK, 0);
            goto endthinkcheck;
        }

        if(acting_entity->running && validanim(acting_entity, ANI_RUNATTACK))   // New run attack code section
        {
            acting_entity->takeaction = common_attack_proc;
            set_attacking(acting_entity);
            acting_entity->velocity.x = acting_entity->velocity.z = 0;
            acting_entity->combostep[0] = 0;
            acting_entity->running = RUN_STATE_NONE;
            ent_set_anim(acting_entity, ANI_RUNATTACK, 0);
            goto endthinkcheck;
        }

        if(validanim(acting_entity, ANI_ATTACKBACKWARD) && match_combo(sequence_back_attack, acting_player, 2))
        {
            t = (acting_player->combostep - 1 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            t2 = (acting_player->combostep - 2 + MAX_SPECIAL_INPUTS) % MAX_SPECIAL_INPUTS;
            if(acting_player->inputtime[t] - acting_player->inputtime[t2] < GAME_SPEED / 10)
            {
                acting_entity->takeaction = common_attack_proc;
                set_attacking(acting_entity);
                acting_entity->velocity.x = acting_entity->velocity.z = 0;
                if(acting_entity->direction == DIRECTION_LEFT && (acting_player->combokey[t2]&FLAG_MOVELEFT))
                {
                    acting_entity->direction = DIRECTION_RIGHT;
                }
                else if(acting_entity->direction == DIRECTION_RIGHT && (acting_player->combokey[t2]&FLAG_MOVERIGHT))
                {
                    acting_entity->direction = DIRECTION_LEFT;
                }
                acting_entity->combostep[0] = 0;
                ent_set_anim(acting_entity, ANI_ATTACKBACKWARD, 0);
                goto endthinkcheck;
            }
        }

        if( validanim(acting_entity, ANI_GET) && (other = find_ent_here(acting_entity, acting_entity->position.x, acting_entity->position.z, TYPE_ITEM, player_test_pickable)) )
        {
            acting_entity->velocity.x = acting_entity->velocity.z = 0;
            set_getting(acting_entity);
            acting_entity->takeaction = common_get;
            ent_set_anim(acting_entity, ANI_GET, 0);

            // Item "attacks" collector to make it
            // easy to script actions on item pick up.
            do_item_script(acting_entity, other);

            didfind_item(other);
            goto endthinkcheck;
        }

        // Use stalltime to charge end-move
        acting_entity->stalltime = _time;
        acting_entity->velocity.x = acting_entity->velocity.z = 0;

        if( acting_entity->weapent &&
                acting_entity->weapent->modeldata.subtype == SUBTYPE_PROJECTILE &&
                validanim(acting_entity, ANI_THROWATTACK)  )
        {
            acting_entity->takeaction = common_attack_proc;
            set_attacking(acting_entity);
            ent_set_anim(acting_entity, ANI_THROWATTACK, 0);
            goto endthinkcheck;
        }
        else if(perform_atchain())
        {
            if(global_sample_list.punch >= 0 && acting_entity->attacking != ATTACKING_NONE)
            {
                sound_play_sample(global_sample_list.punch, 0, savedata.effectvol, savedata.effectvol, 100);
            }
            goto endthinkcheck;
        }

    }
    
    /*
    * Jumping contorl.
    */

    if(acting_player->playkeys & FLAG_JUMP && notinair)
    {
        // Added !inair(acting_entity) so players can't jump when falling into holes
        acting_player->playkeys &= ~FLAG_JUMP;

        /*
        * Z axis control.
        *
        * If player can initialize a Z jump
        * and Up or Down keys are active we'll
        * get a portion of their jump speed as
        * a Z velocity. We'll use this value as
        * the Z parameter for all downstream
        * jump function calls.
        */

        if (acting_entity->modeldata.air_control & AIR_CONTROL_JUMP_Z_INITIAL)
        {
            if (acting_player->keys & FLAG_MOVEUP)
            {
                initial_jump_velocity_z = -acting_entity->modeldata.jumpspeed * 0.5;
            }
            else if (acting_player->keys & FLAG_MOVEDOWN)
            {
                initial_jump_velocity_z = acting_entity->modeldata.jumpspeed * 0.5;
            }
        }
        else
        {
            initial_jump_velocity_z = 0.0;
        }

        if(acting_entity->running)
        {
            //Slide
            if((acting_player->keys & FLAG_MOVEDOWN) && validanim(acting_entity, ANI_RUNSLIDE))
            {
                acting_entity->takeaction = common_attack_proc;
                set_attacking(acting_entity);
                acting_entity->velocity.x = acting_entity->velocity.z = 0;
                acting_entity->combostep[0] = 0;
                acting_entity->running = RUN_STATE_NONE;
                ent_set_anim(acting_entity, ANI_RUNSLIDE, 0);
                goto endthinkcheck;
            }           

            /* Jumping allowed? */
            if (!(acting_entity->modeldata.air_control & AIR_CONTROL_JUMP_DISABLE))
            {
                if (validanim(acting_entity, ANI_RUNJUMP))
                {
                    tryjump(acting_entity->modeldata.runjumpheight, acting_entity->modeldata.jumpspeed * acting_entity->modeldata.runjumpdist, initial_jump_velocity_z, ANI_RUNJUMP);
                }
                else if (validanim(acting_entity, ANI_FORWARDJUMP))
                {
                    tryjump(acting_entity->modeldata.runjumpheight, acting_entity->modeldata.jumpspeed * acting_entity->modeldata.runjumpdist, initial_jump_velocity_z, ANI_FORWARDJUMP);
                }
                else if (validanim(acting_entity, ANI_JUMP))
                {
                    tryjump(acting_entity->modeldata.runjumpheight, acting_entity->modeldata.jumpspeed * acting_entity->modeldata.runjumpdist, initial_jump_velocity_z, ANI_JUMP);
                }
            }
        }
        else
        {
            //Slide
            if((acting_player->keys & FLAG_MOVEDOWN) && validanim(acting_entity, ANI_SLIDE))
            {
                acting_entity->takeaction = common_attack_proc;
                set_attacking(acting_entity);
                acting_entity->velocity.x = acting_entity->velocity.z = 0;
                acting_entity->combostep[0] = 0;
                acting_entity->running = RUN_STATE_NONE;
                ent_set_anim(acting_entity, ANI_SLIDE, 0);
                goto endthinkcheck;
            }

            /* Jumping allowed? */
            if (!(acting_entity->modeldata.air_control & AIR_CONTROL_JUMP_DISABLE))
            {

                /*
                * Handle left/right direction command.
                *
                * If left or right key is active,
                * then switch facing accordingly and
                * continue on to moving jump logic.
                *
                * Otherwise perform a jump with no
                * horizontal velocity.
                */

                if (!(acting_player->keys & (FLAG_MOVELEFT | FLAG_MOVERIGHT)) && validanim(acting_entity, ANI_JUMP))
                {
                    tryjump(acting_entity->modeldata.jumpheight, 0, initial_jump_velocity_z, ANI_JUMP);
                    goto endthinkcheck;
                }
                else if ((acting_player->keys & FLAG_MOVELEFT))
                {
                    acting_entity->direction = DIRECTION_LEFT;
                }
                else if ((acting_player->keys & FLAG_MOVERIGHT))
                {
                    acting_entity->direction = DIRECTION_RIGHT;
                }

                /*
                * Horizontal moving jump.
                */

                if (validanim(acting_entity, ANI_FORWARDJUMP))
                {
                    tryjump(acting_entity->modeldata.jumpheight, acting_entity->modeldata.jumpspeed, initial_jump_velocity_z, ANI_FORWARDJUMP);
                }
                else if (validanim(acting_entity, ANI_JUMP))
                {
                    tryjump(acting_entity->modeldata.jumpheight, acting_entity->modeldata.jumpspeed, initial_jump_velocity_z, ANI_JUMP);
                }
            }
        }
        return;
    }

    /*
    * Run stop.
    */
    run_try_runstop_player(acting_entity, acting_player);

    /*
    */

    if(PLAYER_MIN_Z != PLAYER_MAX_Z && acting_entity->ducking == DUCK_NONE)
    {
        // More of a platform feel
        if(acting_player->keys & FLAG_MOVEUP)
        {            
            if(validanim(acting_entity, ANI_UP) && acting_entity->running == RUN_STATE_NONE)
            {
                action = ACTION_UP;
                acting_entity->velocity.z = -acting_entity->modeldata.speed.x / 2;  // Used for up animation
            }
            else if(acting_entity->running & RUN_STATE_START_X)
            {
                action = ACTION_RUN;

                if (acting_entity->modeldata.run_config_flags & RUN_CONFIG_X_RIGHT_DASH_FIXED 
                    && !(acting_entity->modeldata.run_config_flags & RUN_CONFIG_Z_UP_ENABLED)) {
                    acting_entity->velocity.z = 0.0;
                }
                else
                {
                    acting_entity->velocity.z = -acting_entity->modeldata.runspeed / 2;  // Moves up at a faster rate running
                }
            }
            else
            {
                action = ACTION_WALK;
                acting_entity->velocity.z = -acting_entity->modeldata.speed.x / 2;
            }
        }
        else if(acting_player->keys & FLAG_MOVEDOWN)
        {            
            if(validanim(acting_entity, ANI_DOWN) && acting_entity->running == RUN_STATE_NONE)
            {
                action = ACTION_DOWN;
                acting_entity->velocity.z = acting_entity->modeldata.speed.x / 2;  // Used for down animation
            }
            else if(acting_entity->running & RUN_STATE_START_X)
            {
                action = ACTION_RUN;

                if (acting_entity->modeldata.run_config_flags & RUN_CONFIG_X_RIGHT_DASH_FIXED
                    && !(acting_entity->modeldata.run_config_flags & RUN_CONFIG_Z_DOWN_ENABLED)) {
                    acting_entity->velocity.z = 0.0;
                }
                else
                {
                    acting_entity->velocity.z = acting_entity->modeldata.runspeed / 2;  // Moves up at a faster rate running
                }
            }
            else
            {
                action = ACTION_WALK;
                acting_entity->velocity.z = acting_entity->modeldata.speed.x / 2;
            }
        }
        else if(!(acting_player->keys & (FLAG_MOVEUP | FLAG_MOVEDOWN)))
        {
            acting_entity->velocity.z = 0;
        }
    }
    else if(acting_entity->ducking == DUCK_NONE && validanim(acting_entity, ANI_DUCK) && acting_player->keys & FLAG_MOVEDOWN && notinair)
    {
        tryduck(acting_entity);
        goto endthinkcheck;
    }

    if(acting_player->keys & FLAG_MOVELEFT && acting_entity->ducking == DUCK_NONE)
    {
        if(acting_entity->direction == DIRECTION_RIGHT)
        {
            //acting_entity->running = RUN_STATE_NONE;    // Quits running if player changes direction
            if(acting_entity->modeldata.turndelay && !acting_entity->turntime)
            {
                acting_entity->turntime = _time + acting_entity->modeldata.turndelay;
            }
            else if(acting_entity->turntime && _time >= acting_entity->turntime)
            {
                acting_entity->turntime = 0;
                if(validanim(acting_entity, ANI_TURN))
                {
                    acting_entity->takeaction = common_turn;
                    set_turning(acting_entity);
                    acting_entity->velocity.x = acting_entity->velocity.z = 0;
                    ent_set_anim(acting_entity, ANI_TURN, 0);
                    goto endthinkcheck;
                }
                acting_entity->direction = DIRECTION_LEFT;
            }
            else if(!acting_entity->modeldata.turndelay && validanim(acting_entity, ANI_TURN))
            {
                acting_entity->takeaction = common_turn;
                set_turning(acting_entity);
                acting_entity->velocity.x = acting_entity->velocity.z = 0;
                ent_set_anim(acting_entity, ANI_TURN, 0);
                goto endthinkcheck;
            }
            else if(!acting_entity->turntime)
            {
                acting_entity->direction = DIRECTION_LEFT;
            }
        }
        else
        {
            acting_entity->turntime = 0;
        }

        if(acting_entity->running)
        {
            action = ACTION_RUN;
            acting_entity->velocity.x = -acting_entity->modeldata.runspeed;    // If running, player moves at a faster rate
        }
        else if(action != ACTION_UP && action != ACTION_DOWN)
        {
            action = ACTION_WALK;
            acting_entity->velocity.x = -acting_entity->modeldata.speed.x;
        }
        else
        {
            acting_entity->velocity.x = -acting_entity->modeldata.speed.x;
        }
    }
    else if(acting_player->keys & FLAG_MOVERIGHT && acting_entity->ducking == DUCK_NONE)
    {
        if(acting_entity->direction == DIRECTION_LEFT)
        {
            //acting_entity->running = RUN_STATE_NONE;    // Quits running if player changes direction
            if(acting_entity->modeldata.turndelay && !acting_entity->turntime)
            {
                acting_entity->turntime = _time + acting_entity->modeldata.turndelay;
            }
            else if(acting_entity->turntime && _time >= acting_entity->turntime)
            {
                acting_entity->turntime = 0;
                if(validanim(acting_entity, ANI_TURN))
                {
                    acting_entity->takeaction = common_turn;
                    set_turning(acting_entity);
                    acting_entity->velocity.x = acting_entity->velocity.z = 0;
                    ent_set_anim(acting_entity, ANI_TURN, 0);
                    goto endthinkcheck;
                }
                acting_entity->direction = DIRECTION_RIGHT;
            }
            else if(!acting_entity->modeldata.turndelay && validanim(acting_entity, ANI_TURN))
            {
                acting_entity->takeaction = common_turn;
                set_turning(acting_entity);
                acting_entity->velocity.x = acting_entity->velocity.z = 0;
                ent_set_anim(acting_entity, ANI_TURN, 0);
                goto endthinkcheck;
            }
            else if(!acting_entity->turntime)
            {
                acting_entity->direction = DIRECTION_RIGHT;
            }
        }
        else
        {
            acting_entity->turntime = 0;
        }

        if(acting_entity->running)
        {
            action = ACTION_RUN;
            acting_entity->velocity.x = acting_entity->modeldata.runspeed;    // If running, player moves at a faster rate
        }
        else if(action != ACTION_UP && action != ACTION_DOWN)
        {
            action = ACTION_WALK;
            acting_entity->velocity.x = acting_entity->modeldata.speed.x;
        }
        else
        {
            acting_entity->velocity.x = acting_entity->modeldata.speed.x;
        }
    }
    else if(!((acting_player->keys & FLAG_MOVELEFT) ||
              (acting_player->keys & FLAG_MOVERIGHT)) )
    {
        //acting_entity->running = RUN_STATE_NONE;    // Player let go of left/right and so quits running
        acting_entity->velocity.x = 0;
        acting_entity->turntime = 0;
    }

    if((other = find_ent_here(acting_entity, acting_entity->position.x, acting_entity->position.z, TYPE_ITEM, player_test_touch))  )
    {
        do_item_script(acting_entity, other);
        didfind_item(other);    // Added function to clean code up a bit
    }

    if (acting_entity->ducking & DUCK_ACTIVE)
    {
        if (!(acting_player->keys & FLAG_MOVEDOWN))
        {
            tryduckrise(acting_entity);
            goto endthinkcheck;
        }
        if(acting_player->keys & FLAG_MOVELEFT)
        {
            if(acting_entity->direction == DIRECTION_RIGHT)
            {
                acting_entity->direction = DIRECTION_LEFT;
            }
        }
        else if(acting_player->keys & FLAG_MOVERIGHT)
        {
            if(acting_entity->direction == DIRECTION_LEFT)
            {
                acting_entity->direction = DIRECTION_RIGHT;
            }
        }
    }
    if (acting_entity->ducking)
    {
        goto endthinkcheck;
    }


    //White Dragon: prepare for idling animations...
    if(action)
    {
        acting_entity->takeaction = NULL;
        acting_entity->idling = IDLING_PREPARED;
    }

    switch(action)
    {
    case ACTION_WALK:
        // back walk feature
        if(level && validanim(acting_entity, ANI_BACKWALK))
        {
            if(acting_entity->modeldata.facing == FACING_ADJUST_RIGHT || level->facing == FACING_ADJUST_RIGHT)
            {
                bkwalk = !acting_entity->direction;
            }
            else if(acting_entity->modeldata.facing == FACING_ADJUST_LEFT || level->facing == FACING_ADJUST_RIGHT)
            {
                bkwalk = acting_entity->direction;
            }
            else if((acting_entity->modeldata.facing == FACING_ADJUST_LEVEL || level->facing == FACING_ADJUST_LEVEL) && (level->scrolldir & SCROLL_LEFT) && acting_entity->direction == DIRECTION_LEFT)
            {
                bkwalk = 1;
            }
            else if((acting_entity->modeldata.facing == FACING_ADJUST_LEVEL || level->facing == FACING_ADJUST_LEVEL) && (level->scrolldir & SCROLL_RIGHT) && acting_entity->direction == DIRECTION_RIGHT)
            {
                bkwalk = 1;
            }
            else if(acting_entity->turntime && acting_entity->modeldata.turndelay)
            {
                bkwalk = 1;
            }
            if(bkwalk)
            {
                common_backwalk_anim(acting_entity);    //ent_set_anim(acting_entity, ANI_BACKWALK, 0);
            }
            else
            {
                common_walk_anim(acting_entity);    //ent_set_anim(acting_entity, ANI_WALK, 0);    // If neither up nor down exist, set to walk
            }
        }
        else
        {
            common_walk_anim(acting_entity);    //ent_set_anim(acting_entity, ANI_WALK, 0);    // If neither up nor down exist, set to walk
        }
        break;
    case ACTION_UP:
        common_up_anim(acting_entity); //ent_set_anim(acting_entity, ANI_UP, 0);    // Set to up animation if exists
        break;
    case ACTION_DOWN:
        common_down_anim(acting_entity); //ent_set_anim(acting_entity, ANI_DOWN, 0);    // Set to down animation if exists
        break;
    case ACTION_RUN:
        if (validanim(acting_entity, ANI_BACKRUN))
        {
            if (is_in_backrun(acting_entity)) ent_set_anim(acting_entity, ANI_BACKRUN, 0);
            else ent_set_anim(acting_entity, ANI_RUN, 0);
        }
        else ent_set_anim(acting_entity, ANI_RUN, 0); // Set to run animation if exists

        break;
    default:

        if(acting_entity->idling)
        {
            common_idle_anim(acting_entity);
        }
        break;
    }


endthinkcheck:
    //insert check here
    return;
}

int is_in_backrun(entity* self)
{
    if ( ((self->modeldata.facing == FACING_ADJUST_RIGHT || level->facing == FACING_ADJUST_RIGHT) && self->velocity.x < 0) ||
         ((self->modeldata.facing == FACING_ADJUST_LEFT  || level->facing == FACING_ADJUST_LEFT)  && self->velocity.x > 0) ||
         (((self->modeldata.facing == FACING_ADJUST_LEVEL || level->facing == FACING_ADJUST_LEVEL) && (level->scrolldir & SCROLL_RIGHT)) && self->velocity.x > 0) ||
         (((self->modeldata.facing == FACING_ADJUST_LEVEL || level->facing == FACING_ADJUST_LEVEL) && (level->scrolldir & SCROLL_LEFT))  && self->velocity.x > 0)
       ) return 1;
    else return 0;
}

//ammo count goes down
void subtract_shot()
{
    if(self->weapent && self->weapent->modeldata.weapon_properties.use_count)
    {
        self->weapent->modeldata.weapon_properties.use_count--;

        /*
        * Out of uses? Drop the weapon.
        */

        if(!self->weapent->modeldata.weapon_properties.use_count)
        {
            self->weapent->modeldata.weapon_properties.loss_count = 0;
            dropweapon(0);
        }
    }
}


void dropweapon(int flag)
{
    int wall = 0;
    entity *other = NULL;
    entity* weapon_entity = NULL;
    s_weapon* weapon_properties = NULL;

	// If we already have a weapon, we'll need to discard it.
    if(self->weapent)
    {
        /*
        * Dump pointers to self's weapon entity and the 
        * weapon entity's modeldata weapon properties 
        * into local variables. This is just for eaiser 
        * reading downstream.
        */

        weapon_entity = *&self->weapent;
        weapon_properties = &self->weapent->modeldata.weapon_properties;

		// 2019-09-29 - Not sure about this logic. It appears that only type shot
		// weapons or weapons with shot ammo are dropped.  Anything else is simply discarded.
		// Need to evaluate all weapon logic to get the workflow.
        if(weapon_properties->weapon_state & WEAPON_STATE_LIMITED_USE || (!(weapon_properties->weapon_state & WEAPON_STATE_LIMITED_USE) && weapon_properties->use_count))
        {            
			// If the flag is 2 or below, we subtract the flag's
			// value from weapon counter.
			if(flag < 2)
            {
                weapon_properties->loss_count -= flag;
            }
            
			// We're going to use our own position for the weapon.
            weapon_entity->direction = self->direction;
            weapon_entity->position.z = self->position.z;
            weapon_entity->position.x = self->position.x;
            weapon_entity->position.y = self->position.y;

			// Get any walls and platforms.
            other = check_platform(weapon_entity->position.x, weapon_entity->position.z, self);
            wall = checkwall_index(weapon_entity->position.x, weapon_entity->position.z);

			// Place onto wall or platform.
            if(other && other != weapon_entity)
            {
                weapon_entity->base += other->position.y + other->animation->platform[other->animpos][PLATFORM_HEIGHT];
            }
            else if(wall >= 0)
            {
                weapon_entity->base += level->walls[wall].height;
            }

			// Use the weapon's RESPAWN or SPAWN animations if available, otherwise
			// go right to idle.
            if(validanim(weapon_entity, ANI_RESPAWN))
            {
                ent_set_anim(weapon_entity, ANI_RESPAWN, 1);
            }
            else if(validanim(weapon_entity, ANI_SPAWN))
            {
                ent_set_anim(weapon_entity, ANI_SPAWN, 1);
            }
            else
            {
                if(validanim(weapon_entity, ANI_IDLE)) ent_set_anim(weapon_entity, ANI_IDLE, 1);
            }

			// If the weapon's counter is depleted, then weapon is lost for good.
			// If it is an "animal", then we apply the animal running away logic.
			// Otherwise the weapon blinks out.
            if(!weapon_properties->loss_count)
            {
                if(!(self->modeldata.weapon_properties.weapon_state & WEAPON_STATE_ANIMAL))
                {
                    weapon_entity->blink = 1;
                    weapon_entity->takeaction = common_lie;
                }
                else
                {
                    weapon_entity->modeldata.type = TYPE_NONE;
                    weapon_entity->think = runanimal;
                }
            }
            weapon_entity->nextthink = _time + 1;
        }

		// Clear our tracking variable that keeps the weapon entity pointer.
        self->weapent = NULL;
    }

	// Flag 2 means we're probably setting the weapon directly (ex: setweapon command). 
	// In that case we don't worry about a weapon entity. Just switch ourselves over
	// to the weapon model.
    if(flag < 2)
    {
        if(self->modeldata.type & TYPE_PLAYER)
        {
            if(player[self->playerindex].weapnum)
            {
                set_weapon(self, player[self->playerindex].weapnum, 0);
            }
            else
            {
                set_weapon(self, level->setweap, 0);
            }
        }
        else
        {
            set_weapon(self, 0, 0);
        }
    }

	// Model override. If this is populated, we use its value
	// to locate a model by index and revert to that instead 
	// of the default model when a weapon is lost.
    if(self->modeldata.weapon_properties.loss_index != MODEL_INDEX_NONE)
    {
        set_weapon(self, self->modeldata.weapon_properties.loss_index, 0);
    }
}


int player_takedamage(entity *other, s_attack *attack, int fall_flag, s_defense* defense_object)
{
    s_attack atk = *attack;
    //printf("damaged by: '%s' %d\n", other->name, attack->attack_force);

	// Kratus (10-2021) Now the "infinite health cheat" will check the damage source, it will avoid some "special" damage sources
	bool normal_damage;

	// Damage comes from a normal source?
	normal_damage = (!is_attack_type_special(atk.attack_type));

    if((global_config.cheats & CHEAT_OPTIONS_HEALTH_ACTIVE && normal_damage) || (level->nohurt == DAMAGE_FROM_ENEMY_OFF && (other->modeldata.type & TYPE_ENEMY)))
    {
        atk.attack_force = 0;
    }
    
    return common_takedamage(other, &atk, fall_flag, defense_object);
}

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

// Called when player re-enters the game.
// Drop all enemies EXCEPT for the linked/frozen ones.
void drop_all_enemies()
{
    int i;
    entity *weapself = self;
    s_attack attack;

    for(i = 0; i < ent_max; i++)
    {
        if(ent_list[i]->exists &&
                ent_list[i]->energy_state.health_current > 0 &&
                (ent_list[i]->modeldata.type & TYPE_ENEMY) &&
                !ent_list[i]->owner &&    // Don't want to knock down a projectile
                !ent_list[i]->frozen &&    // Don't want to unfreeze a frozen enemy
                !(ent_list[i]->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE) &&
                !(ent_list[i]->modeldata.pain_config_flags & PAIN_CONFIG_FALL_DISABLE) &&
                validanim(ent_list[i], ANI_FALL) )
        {
            ent_list[i]->attacking = ATTACKING_NONE;
            ent_list[i]->projectile = BLAST_NONE;
            ent_list[i]->takeaction = common_fall;//enemy_fall;
            ent_list[i]->damage_on_landing.attack_force = 0;
            ent_list[i]->damage_on_landing.attack_type = ATK_NONE;
            self = ent_list[i];
            ent_unlink(self);
            ent_list[i]->velocity.x = (self->direction == DIRECTION_RIGHT) ? (-1.2) : 1.2;
            
            if(ent_list[i]->modeldata.weapon_properties.loss_condition & WEAPON_LOSS_CONDITION_STAGE)
            {
                dropweapon(1);
            }

            toss(ent_list[i], 2.5 + randf(1));
            ent_list[i]->knockdowncount = ent_list[i]->modeldata.knockdowncount;

            ent_list[i]->knockdowntime = 0;

            // Use default attack values.
            attack = emptyattack;
            set_fall(ent_list[i], self, &attack, 1);
        }
    }
    self = weapself;
}



/* Called when boss dies. */
void kill_all_enemies()
{
    int i;
    s_attack attack;
    entity *tmpself = NULL;

    attack = emptyattack;
	attack.attack_type = ATK_BOSS_DEATH;
	attack.dropv = default_model_dropv;

    /* 
    * Downstream damage functions use 
    * self global, so we populate it with
    * entity cursor in each iteration of 
    * loop below. Keep current value here 
    * so we can restore when loop is finished. 
    */
    tmpself = self;

    /* 
    * Every valid enemy type with a takedamage
    * function takes its current health in damage.
    */
    for(i = 0; i < ent_max; i++)
    {
        if(  ent_list[i]->exists
                && ent_list[i]->energy_state.health_current > 0
                && (ent_list[i]->modeldata.type & TYPE_ENEMY)
                && ent_list[i]->takedamage)
        {
            self = ent_list[i];

            attack.attack_force = self->energy_state.health_current;
            self->takedamage(self, &attack, 0, self->defense);           
        }
    }

    self = tmpself;    
}



void smart_bomb(entity *e, s_attack *attack)    // New method for smartbombs
{
    int i, hostile, hit = 0;
    entity *tmpself = NULL;
    s_defense* defense_object = NULL;

    hostile = e->faction.type_hostile;
    if(e->modeldata.type & TYPE_PLAYER)
    {
        hostile &= ~(TYPE_PLAYER);
    }

    tmpself = self;
    for(i = 0; i < ent_max; i++)
    {
        if( ent_list[i]->exists
                && ent_list[i] != e
                && ent_list[i]->energy_state.health_current > 0
                && faction_check_is_hostile(e, ent_list[i]))
        {
            self = ent_list[i];
            hit = 1; // for nocost, if the bomb doesn't hit, it won't cost energy

            lasthit.attack = attack;
            lasthit.attacker = e;
            lasthit.collision_attack = NULL;
            lasthit.confirm = 1;
            lasthit.detect_body = NULL;
            lasthit.detect_collision_attack = NULL;
            lasthit.detect_collision_body = NULL;
            lasthit.position.x = self->position.x;
            lasthit.position.y = self->position.y;
            lasthit.position.z = self->position.z;
            lasthit.target = self;

            if(self->takedamage)
            {

                defense_object = defense_find_current_object(self, NULL, attack->attack_type);
                
                //attack.attack_drop = self->modeldata.knockdowncount+1;
                self->takedamage(e, attack, 0, defense_object);
            }
            else
            {
                self->energy_state.health_current -= attack->attack_force;
                if(self->energy_state.health_current <= 0)
                {
                    kill_entity(self, KILL_ENTITY_TRIGGER_SMARTBOMB);
                }
            }

            spawn_attack_flash(self, attack, attack->hitflash, self->modeldata.flash);
            
        }
    }
    if(nocost && hit && smartbomber) // don't use e, because this can be an item-bomb
    {
        self = smartbomber;
        
        if(check_energy(ENERGY_TYPE_MP, ANI_SPECIAL))
        {
            self->energy_state.mp_current -= self->modeldata.animation[ANI_SPECIAL]->energy_cost.cost;
        }
        else
        {
            self->energy_state.health_current -= self->modeldata.animation[ANI_SPECIAL]->energy_cost.cost;
        }
        
    }
    self = tmpself;

}


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

void anything_walk()
{
    if(self->position.x < advancex - 80 || self->position.x > advancex + (videomodes.hRes + 80))
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_WALK_OUT_OF_BOUNDS);
        return;
    }
    //self->position.x += self->velocity.x;
}

// Caskey, Damon V.
// 2019-12-18
//
// Apply color set adjustment to entity, possibly
// based on a parent/owner depending on color set 
// adjustment setting.
void apply_color_set_adjust(entity* ent, entity* parent, e_color_adjust adjustment)
{
	int i = 0; // Loop cursor.

	// Apply color setting.
	switch (adjustment)
	{
	default:
	
		// Use adjustment value as color set index.
		ent_set_colourmap(ent, adjustment);
		break;
	
	case COLORSET_ADJUST_NONE:
		
		// Do nothing.		
		break;
	
	case COLORSET_ADJUST_PARENT_INDEX:

		// Locate parent's current color set index. Then
		// set our color set by that index.

		for (i = 0; i < parent->modeldata.maps_loaded; i++)
		{
			if (parent->colourmap == parent->modeldata.colourmap[i])
			{
				ent_set_colourmap(ent, i);
				break;
			}
		}
		break;

	case COLORSET_ADJUST_PARENT_TABLE:
		
		// Use parent's color table.

		ent->colourmap = parent->colourmap;
		break;

	}
}

/*
* Caskey, Damon V.
*
* 2019-12-22
* Copy the faction settings from a 
* source entity.
*/ 
void faction_copy_all(entity* dest, entity* source)
{
    faction_copy_data(&dest->modeldata.faction, &source->faction);
    faction_copy_data(&dest->faction, &source->faction);
}

/*
* Caskey, Damon V.
* 2023-02-28
* 
* Copy faction properties if 
* conditions pass.
*/
void faction_copy_data(s_faction* dest, s_faction* source)
{
    
    /*
    * Faction groups. Only copy if source or 
    * destination has a value and does not
    * have the no copy flag.
    * 
    * I'd rather be able to copy empty values 
    * as well, but that requires inserting 
    * default values in several differnt 
    * locations around the code. Better to
    * keep it here and maintainable. 
    */

    if (source->damage_direct != FACTION_GROUP_NONE && !(source->damage_direct & FACTION_GROUP_NO_COPY) && !(dest->damage_direct & FACTION_GROUP_NO_COPY))
    { 
        dest->damage_direct = source->damage_direct; 
    }

    if (source->damage_indirect != FACTION_GROUP_NONE && !(source->damage_indirect & FACTION_GROUP_NO_COPY) && !(dest->damage_indirect & FACTION_GROUP_NO_COPY))
    { 
        dest->damage_indirect = source->damage_indirect; 
    }
    
    if (source->hostile != FACTION_GROUP_NONE && !(source->hostile & FACTION_GROUP_NO_COPY) && !(dest->hostile & FACTION_GROUP_NO_COPY))
    { 
        dest->hostile = source->hostile; 
    }
    
    if (source->member != FACTION_GROUP_NONE && !(source->member & FACTION_GROUP_NO_COPY) && !(dest->member & FACTION_GROUP_NO_COPY))
    { 
        dest->member = source->member; 
    }

    /*
    * Types. Same rule as faction groups.
    */

    if (source->type_damage_direct != TYPE_UNDELCARED && !(source->type_damage_direct & TYPE_NO_COPY) && !(dest->type_damage_direct & TYPE_NO_COPY))
    { 
        dest->type_damage_direct = source->type_damage_direct; 
    }

    if (source->type_damage_indirect != TYPE_UNDELCARED && !(source->type_damage_indirect & TYPE_NO_COPY) && !(dest->type_damage_indirect & TYPE_NO_COPY))
    { 
        dest->type_damage_indirect = source->type_damage_indirect; 
    }

    if (source->type_hostile != TYPE_UNDELCARED && !(source->type_hostile & TYPE_NO_COPY) && !(dest->type_hostile & TYPE_NO_COPY))
    { 
        dest->type_hostile = source->type_hostile; 
    }
}

/*
* Caskey, Damon V.
* 2022-05-24
*
* Read a text argument for model copy flag
* and output appropriate constant. If input
* is legacy integer, we just pass it on.
*/
e_faction_group faction_get_flag_from_string(const char* value)
{   
    static const struct 
    {
        const char* text_name;
        e_faction_group flag;

    } flag_lookup_table[] = {
        { "none", FACTION_GROUP_NONE },
        { "all", FACTION_GROUP_ALL_NORMAL },
        { "neutral", FACTION_GROUP_NEUTRAL },
        { "no_copy", FACTION_GROUP_NO_COPY },
        { "player_verses", FACTION_GROUP_PLAYER_VERSES },
        { "type_exclusive", FACTION_GROUP_TYPE_EXCLUSIVE },
        { "type_inclusive", FACTION_GROUP_TYPE_INCLUSIVE },
        { "a", FACTION_GROUP_A },
        { "b", FACTION_GROUP_B },
        { "c", FACTION_GROUP_C },
        { "d", FACTION_GROUP_D },
        { "e", FACTION_GROUP_E },
        { "f", FACTION_GROUP_F },
        { "g", FACTION_GROUP_G },
        { "h", FACTION_GROUP_H },
        { "i", FACTION_GROUP_I },
        { "j", FACTION_GROUP_J },
        { "k", FACTION_GROUP_K },
        { "l", FACTION_GROUP_L },
        { "m", FACTION_GROUP_M },
        { "n", FACTION_GROUP_N },
        { "o", FACTION_GROUP_O },
        { "p", FACTION_GROUP_P },
        { "q", FACTION_GROUP_Q },
        { "r", FACTION_GROUP_R },
        { "s", FACTION_GROUP_S },
        { "t", FACTION_GROUP_T },
        { "u", FACTION_GROUP_U },
        { "v", FACTION_GROUP_V },
        { "w", FACTION_GROUP_W },
        { "x", FACTION_GROUP_X },
        { "y", FACTION_GROUP_Y },
        { "z", FACTION_GROUP_Z }
    };

    const size_t list_count = sizeof(flag_lookup_table) / sizeof(*flag_lookup_table);

    for (size_t i = 0; i < list_count; i++)
    {
        if (stricmp(value, flag_lookup_table[i].text_name) == 0)
        {
            return flag_lookup_table[i].flag;
        }
    }
    
    /*
    * Couldn't find a match in the lookup
    * table. Send alert to log and return
    * none flag.
    */

    printf("\n\n Unknown faction (%s). \n", value);    
    return FACTION_GROUP_NONE;
}

/*
* Caskey, Damon V.
* 2022-05-24
*
* Populate faction property from
* text arguments.
*/
e_faction_group faction_get_flags_from_arglist(const ArgList* arglist)
{
    int i = 0;
    char* value = "";

    e_faction_group result = FACTION_GROUP_NONE;

    for (i = 1; (value = GET_ARGP(i)) && value[0]; i++)
    {
        result |= faction_get_flag_from_string(value);
    }

    return result;
}

/*
* Caskey, Damon V.
* 2023-02-26
*
* Return true if acting entity can 
* hit target entity with attacks.
*/
int faction_check_can_damage(entity* acting_entity, entity* target_entity, int indirect)
{
    e_entity_type acting_type;
    e_entity_type target_type;
    e_faction_group acting_faction;
    e_faction_group acting_faction_filtered;
    e_faction_group target_faction;

    if (!acting_entity || !target_entity)
    {
        return 0;
    }

    /*
    * We will use different acting faction property
    * and type if the indirect flag is set.
    */

    if (indirect)
    {
        acting_faction = acting_entity->faction.damage_indirect;
        acting_type = acting_entity->faction.type_damage_indirect;
    }
    else
    {
        acting_faction = acting_entity->faction.damage_direct;
        acting_type = acting_entity->faction.type_damage_direct;
    }

    acting_faction_filtered = acting_faction;

    /*
    * Remove special types.
    */

    acting_faction_filtered &= ~FACTION_GROUP_NO_CHECK;
    acting_type &= ~TYPE_NO_CHECK;

    /*
    * Check player interaction.
    */

    if (faction_check_player_verses(acting_entity, target_entity, acting_faction))
    {
        return 0;
    }

    target_type = target_entity->modeldata.type;

    /*
    * If one of the acting factions is the
    * type exclusive group, then we only
    * check acting types vs. target type
    * and ignore other factions.
    */

    if (acting_faction & FACTION_GROUP_TYPE_EXCLUSIVE)
    {
        if (acting_type & target_type)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    /*
    * Now we compare our acting faction(s) to the
    * faction(s) target is a member of for any match.
    */

    target_faction = target_entity->faction.member;

    if (acting_faction_filtered & target_faction)
    {
        /*
        * If one of the acting factions is
        * the tye inclusing group, then we
        * also check acting types vs the
        * target's type.
        */

        if (acting_faction & FACTION_GROUP_TYPE_INCLUSIVE)
        {
            if (acting_type & target_type)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }

        return 1;
    }

    return 0;
}

/*
* Caskey, Damon V.
* 2023-02-26
* 
* Return true if acting entity is
* hostile toward target entity.
*/
int faction_check_is_hostile(entity* acting_entity, entity* target_entity)
{
    //printf("\n\n faction_check_is_hostile(%p, %p)", acting_entity, target_entity);

    e_entity_type acting_type;
    e_entity_type target_type;
    e_faction_group acting_faction;
    e_faction_group filtered_faction;
    e_faction_group target_faction;

    if (!acting_entity || !target_entity)
    {
        return 0;
    }

    //printf("\n\t acting_entity->name: %s, target_entity->name: %s", acting_entity->name, target_entity->name);

    acting_faction = acting_entity->faction.hostile;
    filtered_faction = acting_faction;
    acting_type = acting_entity->faction.type_hostile;
    target_type = target_entity->modeldata.type;

    //printf("\n\t acting_faction: %d", acting_faction);
    //printf("\n\t acting_type: %d", acting_type);
    //printf("\n\t target_type: %d", target_type);

    /*
    * Remove special types.
    */

    filtered_faction &= ~FACTION_GROUP_NO_CHECK;
    acting_type &= ~TYPE_NO_CHECK;

    //printf("\n\t acting_faction: %d", acting_faction);
    //printf("\n\t acting_type: %d", acting_type);

    /*
    * Check player interaction. 
    */

    if (faction_check_player_verses(acting_entity, target_entity, acting_faction))
    {
        //printf("\n\t PLayer Vs. Return 0");

        return 0;
    }

    /*
    * If one of the acting factions is the
    * type exclusive group, then we only
    * check acting types vs. target type
    * and ignore other factions.
    */

    if (acting_faction & FACTION_GROUP_TYPE_EXCLUSIVE)
    {
        //printf("\n\t\t acting_faction & FACTION_GROUP_TYPE_EXCLUSIVE: %d", acting_faction & FACTION_GROUP_TYPE_EXCLUSIVE);

        if (acting_type & target_type)
        {
            //printf("\n\t\t acting_type & target_type: %d (yes)", acting_type & target_type);

            return 1;
        }
        else
        {
            printf("\n\t\t acting_type & target_type: %d (no)", acting_type & target_type);

            return 0;
        }
    }

    /*
    * Now we compare our acting faction(s) to the
    * faction(s) target is a member of for any match.
    */

    target_faction = target_entity->faction.member;

    if (filtered_faction & target_faction)
    {
        /*
        * If one of the acting factions is
        * the tye inclusing group, then we
        * also check acting types vs the
        * target's type.
        */

        if (acting_faction & FACTION_GROUP_TYPE_INCLUSIVE)
        {
            //printf("\n\t\t acting_faction & FACTION_GROUP_TYPE_INCLUSIVE: %d", acting_faction & FACTION_GROUP_TYPE_INCLUSIVE);

            if (acting_type & target_type)
            {
                //printf("\n\t\t acting_type & target_type: %d (yes)", acting_type & target_type);

                return 1;
            }
            else
            {
                //printf("\n\t\t acting_type & target_type: %d (no)", acting_type & target_type);

                return 0;
            }
        }

        return 1;
    }

    //printf("\n\t No match.");

    return 0;
}

/*
* Caskey, Damon V.
* 2023-02-28
* 
* Returs 1 if players are not allowed 
* vs. interaction on a given faction 
* property. Ex. Hostile, direct damage, 
* indirect damage.
*/
int faction_check_player_verses(entity* acting_entity, entity* target_entity, e_faction_group faction_property)
{
    if (!acting_entity || !target_entity)
    {
        return 0;
    }

    /*
    * Both entites must be players.
    */

    if (!(acting_entity->modeldata.type & TYPE_PLAYER))
    {
        return 0;
    }

    if (!(target_entity->modeldata.type & TYPE_PLAYER))
    {
        return 0;
    }

    /*
    * In faction group to override friendly
    * fire settings?
    */

    if (faction_property & FACTION_GROUP_PLAYER_VERSES)
    {
        return 0;
    }

    /*
    * If VS. mode turned off in options
    * or level nohit is enabled, then
    * we are "friendly" toward target.
    */

    if (savedata.mode)
    {
        return 1;           
    }

    if (level && level->nohit == DAMAGE_FROM_PLAYER_OFF)
    {
        return 1;
    }

    return 0;
}

/*
* Caskey, Damon  V.
* 2019-12-18 (refactor)
*
* Original author unknown (Tails?). Refactored to remove the ever-growing parameter list
* and consolidate projectile spawn logic. Spawns an entity and fires it as a projectile. 
* Model used for spawn is determined by a hierarchy of legacy parameters (see detailed 
* comments in function).
*
* Returns pointer of spawned projectile, or NULL on fail.
*/
entity *knife_spawn(entity *parent, s_projectile *projectile)
{
    entity *projectile_entity = NULL;

	s_axis_principal_float position;
	e_direction direction = DIRECTION_RIGHT;
	e_projectile_prime projectile_prime = PROJECTILE_PRIME_NONE;
	
	/* If there's no projectile or parent setting, exit now. */
	if (!projectile || !parent)
	{
		return NULL;
	}

    position.x = parent->position.x;
	position.y = parent->position.y + projectile->position.y;
	position.z = parent->position.z + projectile->position.z;

    /*
	* Now we need to spawn the projectile entity. There are many haphazard legacy 
	* additions to sift through, so we need to prioritize which model to spawn. 
	* In general, we work back from most granular to most global.
	*
	* From highest to lowest priority:
	* 
	* 1. Projectile Knife property.
	* 2. Projectile Flash property.
	* 3. Using weapon with model Project property.
	* 4. Model Knife property.
	* 5. Model Pshotno property.
	* 6. Global hardcode model name, "Knife".
	* 7. Global hardcode model name, "Shot".
	*/
    if (projectile->knife >= 0)
	{
        projectile_entity = spawn(position.x, position.z, position.y, direction, NULL, projectile->knife, NULL);
		
		projectile_prime |= PROJECTILE_PRIME_BASE_Y;
		projectile_prime |= PROJECTILE_PRIME_LAUNCH_MOVING;
		projectile_prime |= PROJECTILE_PRIME_SOURCE_PROJ_KNIFE;
	}
	else if (projectile->flash >= 0)
	{
        projectile_entity = spawn(position.x, position.z, position.y, direction, NULL, projectile->flash, NULL);
		
		projectile_prime |= PROJECTILE_PRIME_BASE_FLOOR;
		projectile_prime |= PROJECTILE_PRIME_LAUNCH_STATIONARY;
		projectile_prime |= PROJECTILE_PRIME_SOURCE_PROJ_FLASH;
	}
	else if (parent->weapent && parent->weapent->modeldata.project >= 0)
	{
        projectile_entity = spawn(position.x, position.z, position.y, direction, NULL, parent->weapent->modeldata.project, NULL);
		
		projectile_prime |= PROJECTILE_PRIME_BASE_Y;
		projectile_prime |= PROJECTILE_PRIME_LAUNCH_MOVING;
		projectile_prime |= PROJECTILE_PRIME_SOURCE_WEAPON_PROJECTILE;
	}
	else if (parent->modeldata.knife >= 0)
	{
        projectile_entity = spawn(position.x, position.z, position.y, direction, NULL, parent->modeldata.knife, NULL);
		
		projectile_prime |= PROJECTILE_PRIME_BASE_Y;
		projectile_prime |= PROJECTILE_PRIME_LAUNCH_MOVING;
		projectile_prime |= PROJECTILE_PRIME_SOURCE_MODEL_KNIFE;
	}
	else if (parent->modeldata.pshotno >= 0)
	{
        projectile_entity = spawn(position.x, position.z, position.y, direction, NULL, parent->modeldata.pshotno, NULL);
		
		projectile_prime |= PROJECTILE_PRIME_BASE_FLOOR;
		projectile_prime |= PROJECTILE_PRIME_LAUNCH_STATIONARY;
		projectile_prime |= PROJECTILE_PRIME_SOURCE_MODEL_PSHOTNO;
	}
	else
	{
        /*
		* No model indexes set, so let's fall back to
		* the legacy hardcode model names.
        *
		* Try hardcode "knife" first. If that fails, we'll try
		* "shot" next.
        */
        projectile_entity = spawn(position.x, position.z, position.y, direction, "Knife", MODEL_INDEX_NONE, NULL);
		
		if (projectile_entity)
		{
            /*
			* Hardcode knife spawn successful. Mark as legacy knife
			* and continue.
            */ 
			projectile_prime |= PROJECTILE_PRIME_SOURCE_GLOBAL_KNIFE;
		}
		else 
		{
			/* Try "shot". */
            projectile_entity = spawn(position.x, position.z, position.y, direction, "Shot", MODEL_INDEX_NONE, NULL);

			projectile_prime |= PROJECTILE_PRIME_SOURCE_GLOBAL_SHOT;
		}

		projectile_prime |= PROJECTILE_PRIME_BASE_Y;
		projectile_prime |= PROJECTILE_PRIME_LAUNCH_MOVING;
	}

	/* If we never successfully spawned a projectile entity, exit. */
    if(!projectile_entity)
    {
        return NULL;
    }

    /*
    * Get result of direction adjustment. We need this before we can handle
    * positioning on X axis.
    */
    direction = direction_get_adjustment_result(projectile_entity, parent, projectile->direction_adjust);

    /*
    * Let's set up the spawn position. Reverse X when
    * parent faces left.
    *
    * Apply default X position if creator did not give
    * us a value.
    */

    if (projectile->position.x == PROJECTILE_LEGACY_COMPATABILITY_POSITION_X)
    {
        projectile->position.x = PROJECTILE_DEFAULT_POSITION_X;
    }

    if (direction == DIRECTION_RIGHT)
    {
        position.x = parent->position.x + projectile->position.x;
    }
    else
    {
        position.x = parent->position.x - projectile->position.x;
    }

    projectile_entity->direction = direction;
    projectile_entity->position.x = position.x;
        
    /* 
    * Player projectiles are always type "shot", unless 
    * using the current PROJECTILE type.
    */
    if (!(projectile_entity->modeldata.type & TYPE_PROJECTILE))
    {
        if (parent->modeldata.type & TYPE_PLAYER)
        {
            projectile_entity->modeldata.type = TYPE_SHOT;
        }
        else
        {
            projectile_entity->modeldata.type = parent->modeldata.type;
        }
    }

	/* Apply projectile prime flags. */
    projectile_entity->projectile_prime = projectile_prime;

    /*
	* Copy offense values from parent offense settings 
	* to projectile enity if requested.
	*/
    if (projectile->offense == PROJECTILE_OFFENSE_PARENT)
	{
		memcpy(projectile_entity->offense, parent->offense, sizeof(*projectile_entity->offense) * max_attack_types);
	}

	/* Apply color adjustment. */
	apply_color_set_adjust(projectile_entity, parent, projectile->color_set_adjust);
		

    /*
	* If no move, then all speed is 0. Otherwise check for use of
	* projectile velocity. If player supplied any value other 
	* than MODEL_SPEED_NONE, we use player's value. If not, fall
	* back to default values. This is a bit overcomplicated, but
	* allows players to supply a 0 velocity value on any axis.
	*/
    if (projectile_entity->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
	{
        projectile_entity->modeldata.speed.x = 0;
        projectile_entity->modeldata.speed.y = 0;
        projectile_entity->modeldata.speed.z = 0;
	}
	else
	{	
		/* Copy speed values from animation projectile settings to model. */
        projectile_entity->modeldata.speed = projectile->velocity;
	}

	/* Set up behavior flags. */
    projectile_entity->spawntype = SPAWN_TYPE_PROJECTILE_NORMAL;
    projectile_entity->owner = parent;
    projectile_entity->nograb = 1;
    projectile_entity->attacking = ATTACKING_ACTIVE;
    projectile_entity->think = common_think;
    projectile_entity->nextthink = _time + 1;
    projectile_entity->trymove = NULL;
    projectile_entity->takedamage = arrow_takedamage;
    projectile_entity->takeaction = NULL;
    projectile_entity->modeldata.aimove = AIMOVE1_ARROW;
    projectile_entity->speedmul = 2;
    projectile_entity->modeldata.aiattack = AIATTACK1_NOATTACK;
    
    if(!projectile_entity->modeldata.offscreenkill)
    {
        projectile_entity->modeldata.offscreenkill = 200;    //default value
    }	
    
	/* Kill self when we hit. */
	if (projectile_entity->modeldata.remove)
	{
        projectile_entity->autokill |= AUTOKILL_ATTACK_HIT;
	}
	
    /* Kill self when we finish animation. */
	if (projectile_entity->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
	{
        projectile_entity->autokill |= AUTOKILL_ANIMATION_COMPLETE;
	}
	
	/* Is this a floor or flying projectile ? Set base accordingly. */
    if(projectile_entity->projectile_prime & PROJECTILE_PRIME_BASE_FLOOR)
    {
        projectile_entity->base = 0;
    }
    else
    {
        projectile_entity->base = position.y;
    }

    /*
	* Copy faction data from parent.
	*/

    faction_copy_all(projectile_entity, parent);

    /*
	* If player damage turned off, remove player type from
	* hostile (so homing projectiles leave players alone) and 
	* from candamage.
    */
    if((parent->modeldata.type & TYPE_PLAYER) && ((level && level->nohit == DAMAGE_FROM_PLAYER_OFF) || savedata.mode))
    {
        projectile_entity->faction.type_hostile &= ~TYPE_PLAYER;
        projectile_entity->faction.type_damage_direct &= ~TYPE_PLAYER;
    }

	/*
    * Set terrain and movement behavior flags. Legacy 
    * behavior wouldn't allow movement along Y axis
    * without an arc, but we can't fix this without
    * breaking compatability, so we'll need a different
    * setup depending on what function creator used.
    */
    
    projectile_entity->modeldata.move_config_flags |= (MOVE_CONFIG_PROJECTILE_BASE_DIE | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL | MOVE_CONFIG_PROJECTILE_WALL_BOUNCE);
    projectile_entity->modeldata.move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;

    if (projectile_entity->projectile_prime & PROJECTILE_PRIME_INITIALIZE_LEGACY_PROJECTILE_FUNCTION)
    {
        projectile_entity->modeldata.move_config_flags |= MOVE_CONFIG_SUBJECT_TO_GRAVITY;
    }
    else
    {
        projectile_entity->modeldata.move_config_flags &= ~MOVE_CONFIG_SUBJECT_TO_GRAVITY;
    }
        
	/* Execute the projectile's on spawn event. */
	execute_onspawn_script(projectile_entity);
	
	return projectile_entity;
}

void bomb_explode()
{
    if(self->animating)
    {
        return;
    }
    kill_entity(self, KILL_ENTITY_TRIGGER_BOMB_EXPLODE_ANIMATION_COMPLETE);
}

/*
* Caskey, Damon  V.
* 2019-12-22 (refactor)
*
* Original author unknown (Tails?). Refactored to remove the ever-growing parameter list
* and consolidate projectile spawn logic. Spawns an entity and fires it as a bomb projectile. 
* Model used for spawn is determined by a hierarchy of legacy parameters (see detailed 
* comments in function).
*
* Returns pointer of spawned projectile, or NULL on fail.
*/
entity *bomb_spawn(entity *parent, s_projectile *projectile)
{
	entity* ent = NULL;
	s_axis_principal_float position;
	e_direction direction = DIRECTION_RIGHT;
	e_projectile_prime projectile_prime = PROJECTILE_PRIME_NONE;

	// If there's no projectile or parent setting, exit now.
	if (!projectile || !parent)
	{
		return NULL;
	}
    	
    position.x = parent->position.x;
	position.y = parent->position.y + projectile->position.y;
	position.z = parent->position.z + projectile->position.z;

    /*
	* Now we need to spawn the projectile entity. There are many haphazard legacy 
	* additions to sift through, so we need to prioritize which model to spawn. 
	* In general, we work back from most granular to most global.
	*
	* From highest to lowest priority:
	* 
	* 1. Projectile Bomb property.
	* 2. Using weapon with model bomb property.
	* 3. Model bomb property.
	*/

    if(projectile->bomb >= 0)
    {
        ent = spawn(position.x, position.z, position.y, direction, NULL, projectile->bomb, NULL);

		projectile_prime |= PROJECTILE_PRIME_SOURCE_PROJ_BOMB;
	}
	else if (self->weapent && self->weapent->modeldata.subtype == SUBTYPE_PROJECTILE && self->weapent->modeldata.project >= 0)
	{
		ent = spawn(position.x, position.z, position.y, direction, NULL, self->weapent->modeldata.project, NULL);
		
		projectile_prime |= PROJECTILE_PRIME_SOURCE_WEAPON_PROJECTILE;
	}
    else if(self->modeldata.bomb >= 0)
    {
        ent = spawn(position.x, position.z, position.y, direction, NULL, self->modeldata.bomb, NULL);

		projectile_prime |= PROJECTILE_PRIME_SOURCE_MODEL_BOMB;
	}
	
	projectile_prime |= PROJECTILE_PRIME_BASE_FLOOR;
	projectile_prime |= PROJECTILE_PRIME_LAUNCH_MOVING;
	
	// If we never successfully spawned a projectile entity, exit.
    if(!ent)
    {
        return NULL;
    }

    /*
    * Get result of direction adjustment. We need this before we can handle
    * positioning on X axis.
    */
    direction = direction_get_adjustment_result(ent, parent, projectile->direction_adjust);

    /*
    * Let's set up the spawn position. Reverse X when
    * parent faces left.
    *
    * Apply default X position if creator did not give
    * us a value.
    */

    if (projectile->position.x == PROJECTILE_LEGACY_COMPATABILITY_POSITION_X)
    {
        projectile->position.x = PROJECTILE_DEFAULT_POSITION_X;
    }

    if (direction == DIRECTION_RIGHT)
    {
        position.x = parent->position.x + projectile->position.x;
    }
    else
    {
        position.x = parent->position.x - projectile->position.x;
    }

    ent->direction = direction;
    ent->position.x = position.x;


	ent->projectile_prime = projectile_prime;

    /*
    * If no move, then all speed is 0. Otherwise check for use of
	* projectile velocity. If player supplied any value other 
	* than MODEL_SPEED_NONE, we use player's value. If not, fall
	* back to default values. This is a bit overcomplicated, but
	* allows players to supply a 0 velocity value on any axis.
    */

	if (ent->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
	{
		ent->modeldata.speed.x = 0;
		ent->modeldata.speed.y = 0;
		ent->modeldata.speed.z = 0;
	}
	else
	{		
		// Copy speed values from animation projectile settings to model.
		ent->modeldata.speed = projectile->velocity;
	}

    /*
	* Toss the bomb entity in an arc.
	*
	* We want to handle legacy behavior (use projectile jumpheight 
	* for Y toss velocity), allow author to apply a 0 value for Y 
	* velocity, and use the same velocity structure members for bomb 
	* and knife projectiles. The last part is an extra challenge because 
	* the default velocity needs for knife and bomb are not compatible. 
	* To handle all of this this we will set the velocity.y member to 
	* MODEL_SPEED_NONE specfically when a bomb projectile is requested 
	* by the Throwframe command or "bomb" type from legacy script function 
	* projectile(). If the author does not modify this value, we know 
	* to fall back to legacy behavior and use the projectile entity's 
	* model jumpheight.  
	*
	* If the value is anything other than MODEL_SPEED_NONE, then we know 
	* the author requested a specific velocity (including 0) and will 
	* use author value instead of model jumpheight.
	*/

	if (projectile->velocity.y == MODEL_SPEED_NONE)
	{
		toss(ent, ent->modeldata.jumpheight);
	}
	else
	{
		toss(ent, projectile->velocity.y);
	}
	
	/* Apply color adjustment. */
	apply_color_set_adjust(ent, parent, projectile->color_set_adjust);

    ent->spawntype = SPAWN_TYPE_PROJECTILE_BOMB;
    ent->attacking = ATTACKING_ACTIVE;
    ent->owner = parent;                                                     
    ent->nograb = 1;                                                       
    ent->toexplode |= (EXPLODE_PREPARE_TOUCH | EXPLODE_PREPARE_GROUND);    
        
    ent->think = common_think;
    ent->nextthink = _time + 1;
    ent->trymove = NULL;
    ent->takeaction = NULL;
    ent->modeldata.aimove = AIMOVE1_BOMB;
    ent->modeldata.aiattack = AIATTACK1_NOATTACK;
    ent->takedamage = common_takedamage;
	ent->autokill &= ~AUTOKILL_ATTACK_HIT;    

	if (ent->modeldata.move_config_flags & MOVE_CONFIG_NO_MOVE)
	{
		ent->autokill |= AUTOKILL_ANIMATION_COMPLETE;
	}
	
    ent->speedmul = 2;

	/*
    * Copy faction data.
    */

	faction_copy_all(ent, parent);
    
	ent->modeldata.move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;
	ent->modeldata.move_config_flags |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_PROJECTILE_BASE_DIE | MOVE_CONFIG_PROJECTILE_WALL_BOUNCE | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);
	
	// Execute the projectile's on spawn event.
	execute_onspawn_script(ent);
	
	return ent;
}

// Spawn 3 stars
//
// Caskey, Damon V.
// 2019-12-17
//
// Spawn three �star� projectiles. Meant for Eiji enemies in 
// original Beats of Rage, who would jump and throw three star 
// shuriken diagonally downward at players. Original author 
// Roel, but modified several times by unknown parties. Refactored 
// by DC 2019-12-17 to work with current projectile system. 
// Obviously, we need this for backward compatibility with legacy 
// modules, but otherwise script is the best choice to handle 
// multiple projectiles or any other sort of specialized 
// projectile spawns.
//
// Return TRUE if stars spawned, FALSE on fail.
int star_spawn(entity *parent, s_projectile *projectile)
{
#define MAX_STARS 3

    entity *ent = NULL;
	int i = 0;
	int index = MODEL_INDEX_NONE;
    int first_sortid = 0;

	s_axis_principal_float position;
	e_direction direction = DIRECTION_RIGHT;
	e_projectile_prime projectile_prime = PROJECTILE_PRIME_NONE;

	// If there's no projectile or parent setting, exit now.
	if (!projectile || !parent)
	{
		return FALSE;
	}
	
    position.x = parent->position.x;
	position.y = parent->position.y + projectile->position.y;
	position.z = parent->position.z + projectile->position.z;

    // Same concept as knife spawn. Look for model to spawn.
	// 1. Animation projectile.
	// 2. Weapon model projectile.
	// 3. Base model projectile.
	// 4. Legacy default.
    if(projectile->star >= 0)
    {
        index = projectile->star;

		projectile_prime |= PROJECTILE_PRIME_SOURCE_PROJ_STAR;
    }
	else if (parent->weapent && parent->weapent->modeldata.subtype == SUBTYPE_PROJECTILE && parent->weapent->modeldata.project >= 0)
	{
		index = parent->weapent->modeldata.project;

		projectile_prime |= PROJECTILE_PRIME_SOURCE_WEAPON_PROJECTILE;
	}
    else if(parent->modeldata.star >= 0)
    {
        index = parent->modeldata.star;

		projectile_prime |= PROJECTILE_PRIME_SOURCE_MODEL_STAR;
    }
    else
    {
        index = get_cached_model_index("Star"); 

		projectile_prime |= PROJECTILE_PRIME_SOURCE_GLOBAL_STAR;
    }

	projectile_prime |= PROJECTILE_PRIME_BASE_Y;
	projectile_prime |= PROJECTILE_PRIME_LAUNCH_MOVING;

	// Loop to max star count.
    for(i = 0; i < MAX_STARS; i++)
    {
		// Spawn the star entity. If we fail, exit and return false.
        ent = spawn(position.x, position.z, position.y, direction, NULL, index, NULL);
        if(ent == NULL)
        {
            return 0;
        }

        // Get result of direction adjustment. We need this before we can handle
        // positioning on X axis.
        direction = direction_get_adjustment_result(ent, parent, projectile->direction_adjust);

        /*
        * Apply default X position if
        * creator did not give us a value.
        */

        if (projectile->position.x == PROJECTILE_LEGACY_COMPATABILITY_POSITION_X)
        {
            projectile->position.x = PROJECTILE_DEFAULT_STAR_POSITION_X;
        }

        // Reverse X when parent faces left.
        if (direction == DIRECTION_RIGHT)
        {
            position.x = parent->position.x + projectile->position.x;
        }
        else
        {
            position.x = parent->position.x - projectile->position.x;
        }

        ent->position.x = position.x;
        ent->direction = direction;

		// 2019-12-17 DC - Not sure why we set attacking off, but
		// leaving it here for legacy behavior.
        parent->attacking = ATTACKING_NONE;

		// First star spawned sort id serves as a base for sorting. 
		// Then the next star's sort is the base - loop index. Each 
		// subsequent star appears one step further behind in
		// sorting order. 
		//
		// Ex: Base (first star) = 20, 20 - 1 = 19, 20 - 2 = 18.
		if (i <= 0)
		{
			first_sortid = ent->sortid;
		}

        ent->sortid = first_sortid - i;

        ent->takedamage = arrow_takedamage;
        ent->owner = parent;
        ent->attacking = ATTACKING_ACTIVE;
        ent->nograb = 1;
        
		// Get the star velocity setting from animation.
		ent->velocity.x = projectile->star_velocity[i];

		// Reverse X velocity if direction is left.
		if (direction == DIRECTION_LEFT)
		{
			ent->velocity.x = -ent->velocity.x;
		}
		
		ent->think = common_think;
        ent->nextthink = _time + 1;
        ent->trymove = NULL;
        ent->takeaction = NULL;
        ent->modeldata.aimove = AIMOVE1_STAR;
        ent->modeldata.aiattack = AIATTACK1_NOATTACK;
        
		// Remove star on contact.
		if (ent->modeldata.remove)
		{
			ent->autokill |= AUTOKILL_ATTACK_HIT;
		}
				
		ent->position.y = position.y;
		ent->base = position.y;
        ent->speedmul = 2;
        
        /*
        * Copy faction data.
        */
        
        faction_copy_all(ent, parent);

		// Basic terrian property setup.
		ent->modeldata.move_config_flags |= (MOVE_CONFIG_SUBJECT_TO_BASEMAP | MOVE_CONFIG_SUBJECT_TO_GRAVITY | MOVE_CONFIG_SUBJECT_TO_HOLE | MOVE_CONFIG_SUBJECT_TO_PLATFORM | MOVE_CONFIG_SUBJECT_TO_WALL);
		ent->modeldata.move_config_flags &= ~MOVE_CONFIG_NO_ADJUST_BASE;

        ent->spawntype = SPAWN_TYPE_PROJECTILE_STAR;
		ent->projectile_prime = projectile_prime;

		// Execute the projectile's on spawn event.
		execute_onspawn_script(ent);
    }
    return TRUE;

#undef MAX_STARS
}



void steam_think()
{
    if(!self->animating)
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_STEAM_ANIMATION_COMPLETE);
        return;
    }

    self->base += 1;
    self->position.y = self->base;
}



// for the "trap" type   7-1-2005  trap start
void trap_think()
{
    self->attacking = ATTACKING_ACTIVE;
}
//    7-1-2005  trap end




void steam_spawn(float x, float z, float a)
{
    entity *e = NULL;

    e = spawn(x, z, a, 0, "Steam", -1, NULL);

    if(e == NULL)
    {
        return;
    }

    e->spawntype = SPAWN_TYPE_STEAM;
    e->base = a;
    e->modeldata.move_config_flags |= MOVE_CONFIG_NO_ADJUST_BASE;
    e->think = steam_think;

	// Execute the steams's on spawn event.
	execute_onspawn_script(e);
}



void steamer_think()
{
    steam_spawn(self->position.x, self->position.z, self->position.y);
    self->nextthink = _time + (GAME_SPEED / 10) + (rand32() & 31);
}



void text_think()     // New function so text can be displayed
{
    // wait to suicide
    if(!self->animating)
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_TEXT_ANIMATION_COMPLETE);
    }
}

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

//homing arrow find its target
// type : target type
entity *homing_find_target(entity* acting_entity)
{
    int i;
    int min;
    int max;
        
    entity* target_entity = NULL;
    entity* cursor_entity;

    //use the walk animation's range
    if(validanim(acting_entity, ANI_WALK))
    {
        min = acting_entity->modeldata.animation[ANI_WALK]->range.x.min;
        max = acting_entity->modeldata.animation[ANI_WALK]->range.x.max;
    }
    else
    {
        min = 0;
        max = 999;
    }

    //find the 'nearest' one
    for(i = 0; i < ent_max; i++)
    {
        cursor_entity = ent_list[i];

        if(cursor_entity->exists && cursor_entity != acting_entity  // cant target self
                && cursor_entity != acting_entity->owner // Don't go after owner.
                && faction_check_is_hostile(acting_entity, cursor_entity)
                && diff(cursor_entity->position.x, acting_entity->position.x) + diff(cursor_entity->position.z, acting_entity->position.z) >= min
                && diff(cursor_entity->position.x, acting_entity->position.x) + diff(cursor_entity->position.z, acting_entity->position.z) <= max
                && cursor_entity->animation->vulnerable[cursor_entity->animpos]  )
        {
            if(!target_entity || diff(cursor_entity->position.x, acting_entity->position.x) + diff(cursor_entity->position.z, acting_entity->position.z) < diff(target_entity->position.x, acting_entity->position.x) + diff(target_entity->position.z, acting_entity->position.z))
            {
                target_entity = cursor_entity;
            }
        }
    }
    
    return target_entity;
}


void bike_crash()
{
    int i;
    if(self->direction == DIRECTION_RIGHT)
    {
        self->velocity.x = 2;
    }
    else
    {
        self->velocity.x = -2;
    }
    for(i = 0; i < levelsets[current_set].maxplayers; i++)
    {
        if (savedata.joyrumble[i]) control_rumble(i, 1, 100);
    }
    //if(self->position.x < advancex-100 || self->position.x > advancex+(videomodes.hRes+100)) kill_entity(self);
}



int biker_takedamage(entity *other, s_attack *attack, int fall_flag, s_defense* defense_object)
{
    entity *driver = NULL;
    entity *tempself = NULL;

    if(self->death_state & DEATH_STATE_DEAD)
    {
        return 0;
    }
    // Fell in a hole
    if(self->position.y < PIT_DEPTH)
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_TAKE_DAMAGE_BIKER_PIT);
        return 0;
    }
    if(other != self)
    {
        set_opponent(other, self);
    }

    if(attack->no_pain) // don't drop driver until it is dead, because the attack has no pain effect
    {
        checkdamage(self, other, attack, defense_object);
        if(self->energy_state.health_current > 0)
        {
            return 1;    // not dead yet
        }
    }

    check_backpain(other,self);
    set_pain(self,  self->last_damage_type, 1);
    self->attacking = ATTACKING_ACTIVE;
    if(!self->modeldata.offscreenkill)
    {
        self->modeldata.offscreenkill = 100;
    }
    self->think = bike_crash;
    // well, this is the real entity, the driver who take the damage
    if((driver = drop_driver(self)))
    {
        driver->position.y = self->position.y;
        tempself = self;
        self = driver;
        self->drop = 1;
        self->direction = tempself->direction;
        if(self->takedamage)
        {
            self->takedamage(other, attack, fall_flag, defense_object);
        }
        else
        {
            self->energy_state.health_current -= attack->attack_force;
        }
        self = tempself;

    }
    self->energy_state.health_current = 0;
    checkdeath();
    return 1;
}



void obstacle_fall()
{
    if(inair(self))
    {
        return;
    }

    self->velocity.x = self->velocity.z = 0;
    if((!self->animating && validanim(self, ANI_DIE)) || !validanim(self, ANI_DIE))
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_OBSTACLE_FALL_NO_DEATH_ANIMATION);    // Fixed so ANI_DIE can be used
    }
}



void obstacle_fly()    // Now obstacles can fly when hit like on Simpsons/TMNT
{
    //self->position.x += self->velocity.x * 4;    // Equivelant of speed 40
    if(self->position.x > advancex + (videomodes.hRes + 200) || self->position.x < advancex - 200)
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_OBSTACLE_FLY_OUT_OF_BOUNDS);
    }
}



int obstacle_takedamage(entity *other, s_attack *attack, int fall_flag, s_defense* defense_object)
{
    if(self->position.y <= PIT_DEPTH)
    {
        kill_entity(self, KILL_ENTITY_TRIGGER_TAKE_DAMAGE_OBSTACLE_PIT);
        return 0;
    }

    set_opponent(other, self);
    if(self->opponent && (self->opponent->modeldata.type & TYPE_PLAYER))
    {
        if (savedata.joyrumble[self->opponent->playerindex]) control_rumble(self->opponent->playerindex, 1, 75);
    }
    
    /* Calculate and apply HP damage. */
    checkdamage(self, other, attack, defense_object);

    self->playerindex = other->playerindex;    // Added so points go to the correct player
    addscore(other->playerindex, attack->attack_force * self->modeldata.multiple);  // Points can now be given for hitting an obstacle

    if(self->energy_state.health_current <= 0)
    {

        checkdeath();

        if(other->position.x < self->position.x)
        {
            self->velocity.x = 1;
        }
        else
        {
            self->velocity.x = -1;
        }

        self->attacking = ATTACKING_ACTIVE;    // So obstacles can explode and hurt players/enemies

        if(self->modeldata.subtype == SUBTYPE_FLYDIE)     // Now obstacles can fly like on Simpsons/TMNT
        {
            self->velocity.x *= 4;
            self->think = obstacle_fly;
            ent_set_anim(self, ANI_FALL, 0);
        }
        else
        {
            self->think = obstacle_fall;

            if(validanim(self, ANI_DIE))
            {
                ent_set_anim(self, ANI_DIE, 0);    //  LTB 1-13-05  Die before toss
            }
            else
            {
                toss(self, self->modeldata.jumpheight / 1.333);
                ent_set_anim(self, ANI_FALL, 0);
            }

            if(self->modeldata.death_config_flags & DEATH_CONFIG_MACRO_BLINK)
            {
                self->blink = 1;
            }
        }
    }

    self->nextthink = _time + 1;
    return 1;
}

// Caskey, Damon V.
// 2018-04-27
//
// Allocate memory and set entity properties that will be transfered
// to a dropped item.
void initialize_item_carry(entity *ent, s_spawn_entry *spawn_entry)
{
    // It's possible to call this from script, so if
    // if there is already memory for an item allocated
    // here, clear it out to make sure we don't end up
    // with any memory leaks.
    if(ent->item_properties)
    {
       free(ent->item_properties);
       ent->item_properties = NULL;
    }

    // Allocate memory for the item.
    ent->item_properties = malloc(sizeof(*ent->item_properties));
    memset(ent->item_properties, 0, sizeof(*ent->item_properties));

    if(spawn_entry)
    {
        ent->item_properties->index = spawn_entry->item_properties.index;

        if(spawn_entry->item_properties.alias[0])
        {
            strcpy(ent->item_properties->alias, spawn_entry->item_properties.alias);
        }

        if(spawn_entry->item_properties.colorset)
        {
            ent->item_properties->colorset = spawn_entry->item_properties.colorset;
        }

        if(spawn_entry->item_properties.health)
        {
            ent->item_properties->health = spawn_entry->item_properties.health;
        }
        ent->item_properties->player_count = spawn_entry->item_properties.player_count;
    }
}

entity *smartspawn(s_spawn_entry *props)      // 7-1-2005 Entire section replaced with lord balls code
{
    entity *e = NULL;
    entity *wp = NULL;
    int playercount;

    if( props == NULL /* ||
        (level == NULL &&
         !(screen_status & (IN_SCREEN_SELECT | IN_SCREEN_TITLE | IN_SCREEN_HALL_OF_FAME | IN_SCREEN_GAME_OVER | IN_SCREEN_SHOW_COMPLETE | IN_SCREEN_ENGINE_CREDIT | IN_SCREEN_MENU | IN_SCREEN_GAME_START_MENU | IN_SCREEN_NEW_GAME_MENU | IN_SCREEN_LOAD_GAME_MENU | IN_SCREEN_OPTIONS_MENU | IN_SCREEN_CONTROL_OPTIONS_MENU | IN_SCREEN_SOUND_OPTIONS_MENU | IN_SCREEN_VIDEO_OPTIONS_MENU | IN_SCREEN_SYSTEM_OPTIONS_MENU)) &&
         currentScene)*/
      )
    {
        return NULL;
    }

    /* Spawn item based on number of active players. */
    if(props->spawnplayer_count >= (playercount = MAX(1, count_ents(TYPE_PLAYER))))
    {
        if(props->boss && level != NULL)
        {
            --level->bossescount;
        }
        return NULL;
    }

    if( level != NULL && ((level->scrolldir & SCROLL_INWARD) || (level->scrolldir & SCROLL_OUTWARD)) )
    {
        e = spawn(props->position.x, props->position.z + advancey, props->position.y, props->flip, props->name, props->index, props->model);
    }
    else
    {
        e = spawn(props->position.x + advancex, props->position.z, props->position.y, props->flip, props->name, props->index, props->model);
    }

    if(e == NULL)
    {
        return NULL;
    }

    //printf("%s, (%f, %f, %f) - (%f, %f, %f)", props->name, props->position.x, props->position.z, props->position.y, e->position.x, e->position.z, e->position.y);

    // Alias?
    if(props->alias[0])
    {
        memset(e->name, 0, sizeof(e->name));
        strcpy(e->name, props->alias);
    }

    // If we have item properties in spawn entry, then prepare a set of
    // properties to pass on to the item when it is dropped.
    if(props->item)
    {
        initialize_item_carry(e, props);
    }
    
    faction_copy_data(&e->modeldata.faction, &props->faction);
    faction_copy_data(&e->faction, &props->faction);

    if(props->spawntype)
    {
        e->spawntype = props->spawntype;    //2011_03_23, Pass spawntype.
    }

    if(props->health[playercount - 1] != 0)
    {
        e->energy_state.health_current = e->modeldata.health = props->health[playercount - 1];
    }

    if(props->mp != 0)
    {
        e->energy_state.mp_current = e->modeldata.mp = props->mp;
    }

    if(props->score != 0)
    {
        e->modeldata.score = props->score;    // Overwrite score if exists in the level's. txt file
    }
    if(props->multiple != 0)
    {
        e->modeldata.multiple = props->multiple;    // Overwrite multiple if exists in the level's .txt file
    }

    if(!e->map && props->colourmap)
    {
        ent_set_colourmap(e, props->colourmap);
    }

    if(props->aggression)
    {
        e->modeldata.aggression = props->aggression;    // Aggression can be changed with spawn points now
    }
    if(props->item_properties.alpha)
    {
        e->item_properties->alpha = props->item_properties.alpha;
    }
    if(props->alpha)
    {
        e->modeldata.alpha = props->alpha;
    }

    // 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
        e->dying2 = props->dying2;    // Dec 15, 2016 - Used to define which colourmap is used for the dying flash for per2 By White Dragon
    }

    if(props->nolife)
    {
        e->modeldata.nolife = props->nolife;    // Overwrite whether live is visible or not
    }
    e->boss = props->boss;

    if(props->boss && level != NULL && level->bossmusic[0])
    {
        music(level->bossmusic, 1, level->bossmusic_offset);
    }

    // give the entity a weapon item
    if(props->weapon)
    {
        wp = spawn(e->position.x, ITEM_HIDE_POSITION_Z, 0, 0, props->weapon, props->weaponindex, props->weaponmodel);
        if(wp)
        {
            //ent_default_init(wp);
            set_weapon(e, wp->modeldata.weapon_properties.weapon_index, 0);
            e->weapent = wp;

            e->weapent->spawntype = SPAWN_TYPE_WEAPON;
        }
    }

    // set entity type: player, enemy, npc...
    if(props->entitytype)
    {
        e->modeldata.type = props->entitytype;
    }

    // set a parent
    if(props->parent) //->varlist->vars->vt != VT_EMPTY
    {
        e->parent = (entity *)props->parent;
    }

    //ent_default_init(e);
    execute_onspawn_script(e);
    execute_spawn_script(props, e);
    return e;
}   // 7-1-2005 replaced section ends here

int is_incam(float x, float z, float a, float threshold)
{
    if (level)
    {
        if ( x >= advancex+threshold && x <= advancex+videomodes.hRes-threshold && z-a >= advancey+threshold && z-a <= advancey+videomodes.vRes-4 ) {
            if ( x >= scrollminx && x <= scrollmaxx+videomodes.hRes && z >= PLAYER_MIN_Z && z <= PLAYER_MAX_Z ) {
                return 1;
            }
        }
    }

    return 0;
}

void spawnplayer(int index)
{
    s_spawn_entry p;
    //s_model * model = NULL;
    int wall;
    int xc, zc, find = 0;
    index &= 3;

//    model = find_model(player[index].name);
//    if(model == NULL) return;

    memset(&p, 0, sizeof(p));
    p.name = player[index].name;
    p.index = -1;
    p.item_properties.index = -1;
    p.weaponindex = -1;
    p.colourmap = player[index].colourmap;
    p.spawnplayer_count = -1;
    p.spawntype = SPAWN_TYPE_PLAYER_MAIN;

    if(level->scrolldir & SCROLL_LEFT)
    {
        if(level->spawn && level->spawn[index].x)
        {
            p.position.x = (float)(videomodes.hRes - level->spawn[index].x);
        }
        else
        {
            p.position.x = (float)((videomodes.hRes - 20) - 30 * index);
        }
    }
    else
    {
        if(level->spawn && level->spawn[index].x)
        {
            p.position.x = (float)(level->spawn[index].x);
        }
        else
        {
            p.position.x = (float)(20 + 30 * index);
        }
        p.flip = 1;
    }

    if(level->spawn && level->spawn[index].z)
    {
        if(level->scrolldir & (SCROLL_INWARD | SCROLL_OUTWARD))
        {
            p.position.z = (float)(level->spawn[index].z);
        }
        else
        {
            p.position.z = (float)(PLAYER_MIN_Z + level->spawn[index].z);
        }
    }
    else if(PLAYER_MAX_Z - PLAYER_MIN_Z > 5)
    {
        p.position.z = (float)(PLAYER_MIN_Z + 5);
    }
    else
    {
        p.position.z = (float)PLAYER_MIN_Z;
    }

    if(p.position.z < PLAYER_MIN_Z)
    {
        p.position.z = PLAYER_MIN_Z;
    }
    else if(p.position.z > PLAYER_MAX_Z)
    {
        p.position.z = PLAYER_MAX_Z;
    }

    //////////////////checking holes/ walls///////////////////////////////////
    for(xc = 0; xc < videomodes.hRes / 4; xc++)
    {
        if(p.position.x > videomodes.hRes)
        {
            p.position.x -= videomodes.hRes;
        }
        if(p.position.x < 0)
        {
            p.position.x += videomodes.hRes;
        }
        if(PLAYER_MIN_Z == PLAYER_MAX_Z)
        {
            wall = checkwall_index(advancex + p.position.x, p.position.z);
            if(wall >= 0 && level->walls[wall].height < MAX_WALL_HEIGHT)
            {
                break;    //found
            }
            if(checkhole_in(advancex + p.position.x, p.position.z, p.position.y) || (wall >= 0 && level->walls[wall].height >= MAX_WALL_HEIGHT))
            {
                find = 0;
            }
            else
            {
                break;    // found
            }
        }
        else for(zc = 0; zc < (PLAYER_MAX_Z - PLAYER_MIN_Z) / 3; zc++, p.position.z += 3)
            {
                if(p.position.z > PLAYER_MAX_Z)
                {
                    p.position.z -= PLAYER_MAX_Z - PLAYER_MIN_Z;
                }
                if(p.position.z < PLAYER_MIN_Z)
                {
                    p.position.z += PLAYER_MAX_Z - PLAYER_MIN_Z;
                }
                wall = checkwall_index(advancex + p.position.x, p.position.z);
                if(wall >= 0 && level->walls[wall].height < MAX_WALL_HEIGHT)
                {
                    find = 1;
                    break;
                }
                else if(wall >= 0 && level->walls[wall].height >= MAX_WALL_HEIGHT)
                {
                    continue;
                }
                if(checkhole_in(advancex + p.position.x, p.position.z, p.position.y))
                {
                    continue;
                }
                find = 1;
                break;
            }
        if(find)
        {
            break;
        }
        p.position.x += (level->scrolldir & SCROLL_LEFT) ? -4 : 4;
    }
    ///////////////////////////////////////////////////////////////////////
    currentspawnplayer = index;
    player[index].ent = smartspawn(&p);

    if(player[index].ent == NULL)
    {
        borShutdown(1, "Fatal: unable to spawn player from '%s'\n", p.name);
    }

    player[index].ent->playerindex = index;

    if(nomaxrushreset[4] >= 1)
    {
        player[index].ent->rush.max = nomaxrushreset[index];
    }
    else
    {
        player[index].ent->rush.max = 0;
    }

    memset(player[index].combokey, 0, sizeof(*player[index].combokey)*MAX_SPECIAL_INPUTS);
    memset(player[index].inputtime, 0, sizeof(*player[index].inputtime)*MAX_SPECIAL_INPUTS);
    player[index].combostep = 0;

    if(player[index].spawnhealth)
    {
        player[index].ent->energy_state.health_current = player[index].spawnhealth + 5;
    }
    if(player[index].ent->energy_state.health_current > player[index].ent->modeldata.health)
    {
        player[index].ent->energy_state.health_current = player[index].ent->modeldata.health;
    }

    //mp little recorver after a level by tails
    if(player[index].spawnmp)
    {
        player[index].ent->energy_state.mp_current = player[index].spawnmp + 2;
    }
    if(player[index].ent->energy_state.mp_current > player[index].ent->modeldata.mp)
    {
        player[index].ent->energy_state.mp_current = player[index].ent->modeldata.mp;
    }

    if(player[index].weapnum)
    {
        set_weapon(player[index].ent, player[index].weapnum, 0);
    }
    else
    {
        set_weapon(player[index].ent, level->setweap, 0);
    }
}

int no_player_alive_to_join()
{
    int no_alive_players = 0;
    int i;
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        if( ((!player[i].ent || player[i].lives <= 0 || (player[i].lives <= 1 && player[i].ent->energy_state.health_current <= 0)) &&
            ((noshare && player[i].credits <= 0) || (!noshare && credits <= 0)))
        )
        {
            ++no_alive_players;
        }
    }
    no_alive_players = (no_alive_players >= MAX_PLAYERS) ? 1 : 0;

    return no_alive_players;
}

void kill_all_players_by_timeover()
{
    int i;
    s_attack attack_timeover, attack_lose;
    s_defense* defense_object = NULL;

    attack_timeover = emptyattack;
    attack_timeover.attack_type = ATK_TIMEOVER;
    attack_timeover.dropv.y = default_model_dropv.y;
    attack_timeover.dropv.x = default_model_dropv.x;
    attack_timeover.dropv.z = default_model_dropv.z;

    attack_lose = emptyattack;
    attack_lose.attack_type = ATK_LOSE;
    

    endgame = 1;
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        entity* tmp = self;
        self = player[i].ent;
        if(self && !validanim(self, ANI_LOSE))
        {
            endgame = 0;
            attack_timeover.attack_force = self->energy_state.health_current;

            defense_object = defense_find_current_object(self, NULL, attack_timeover.attack_type);
            
            self->takedamage(self, &attack_timeover, 0, defense_object);
        }
        else if(self)
        {
            endgame = 0;

            attack_lose.attack_force = self->energy_state.health_current;
            if (inair(self) && validanim(self, ANI_FALLLOSE))
            {
                attack_lose.dropv.y = default_model_dropv.y;
                attack_lose.dropv.x = default_model_dropv.x;
                attack_lose.dropv.z = default_model_dropv.z;
                self->modeldata.death_config_flags |= DEATH_CONFIG_MACRO_DEATH_FALL_LAND;
            }
            else
            {
                self->modeldata.death_config_flags |= DEATH_CONFIG_MACRO_DEATH;
            }

            defense_object = defense_find_current_object(self, NULL, attack_lose.attack_type);
           
            self->takedamage(self, &attack_lose, 0, defense_object);
        }
        self = tmp;
    }
}

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(!level_completed)
    {
        kill_all_players_by_timeover();

        if (!is_total_timeover)
        {
            if(global_sample_list.time_over >= 0)
            {
                sound_play_sample(global_sample_list.time_over, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            timeleft = level->settime * COUNTER_SPEED;    // Feb 24, 2005 - This line moved here to set custom time
            if(!endgame)
            {
                showtimeover = 1;
            }
        }
        if (!is_total_timeover && no_player_alive_to_join())
        {
            is_total_timeover = 1;
            nojoin = 1;
        }
    }
}


// ----------------------- Update functions ------------------------------

void update_scroller()
{
    float to = 0;
    int i, againstend = 0;
    int numplay = 0; //4player
    float tx = advancex, ty = advancey;
    float rm = -9999, lm = 999999, bm = -9999, tm = 999999; //player boundary box
    static int scrolladd = 0;
    scrolldx = scrolldy = 0;
    int p_alive = 0;

    if(_time < level->advancetime || freezeall)
    {
        return;    // Added freezeall so backgrounds/scrolling don't update if animations are frozen
    }

    /*
    	//level->advancetime = _time + (GAME_SPEED/100);    // Changed so scrolling speeds up for faster players
    	level->advancetime = _time  -
    		((player[0].ent && (player[0].ent->modeldata.speed.x >= 12 || player[0].ent->modeldata.runspeed >= 12)) ||
    		 (player[1].ent && (player[1].ent->modeldata.speed.x >= 12 || player[1].ent->modeldata.runspeed >= 12)) ||
    		 (player[2].ent && (player[2].ent->modeldata.speed.x >= 12 || player[2].ent->modeldata.runspeed >= 12)) ||
    		 (player[3].ent && (player[3].ent->modeldata.speed.x >= 12 || player[3].ent->modeldata.runspeed >= 12)) );    // Changed so if your player is faster the backgrounds scroll faster*/

    level->advancetime = _time;

    if(level_completed)
    {
        return;
    }

    for(i = 0; i < MAX_PLAYERS; i++)
    {
        if (player[i].ent && !(player[i].ent->death_state & DEATH_STATE_DEAD))
        {
            p_alive = 1;
            break;
        }
    }

    //White Dragon: No more enemies!
    if(current_spawn >= level->numspawns && !findent(TYPE_ENEMY) && p_alive)
    {
        if(!findent(TYPE_ENDLEVEL) && ((!findent(TYPE_ITEM | TYPE_OBSTACLE) && level->type == 1) || level->type == 0)) // 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 &&
                level->pos >= level->spawnpoints[current_spawn].at
             )
        {
            if(level->spawnpoints[current_spawn].musicfade)
            {
                musicfade[0] = (float)level->spawnpoints[current_spawn].musicfade;
                musicfade[1] = (float)savedata.musicvol;
            }
            else if(level->spawnpoints[current_spawn].music[0])
            {
                strncpy(musicname, level->spawnpoints[current_spawn].music, MAX_BUFFER_LEN);
                musicoffset = level->spawnpoints[current_spawn].musicoffset;
                musicloop = 1;
            }
            else if(level->spawnpoints[current_spawn].wait)
            {
                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 if(level->spawnpoints[current_spawn].nojoin != 0)
            {
                nojoin = (level->spawnpoints[current_spawn].nojoin == 1);
            }
            else if(level->spawnpoints[current_spawn].scrollminz & 0x80000000)
            {
                scrollminz = (float)(level->spawnpoints[current_spawn].scrollminz & 0x7fffffff);
                scrollmaxz = (float)level->spawnpoints[current_spawn].scrollmaxz;
                if(!_time)
                {
                    advancey = scrollminz;    // reset y if spawn at very beginning
                }
            }
            else if(level->spawnpoints[current_spawn].scrollminx & 0x80000000)
            {
                scrollminx = (float)(level->spawnpoints[current_spawn].scrollminx & 0x7fffffff);
                scrollmaxx = (float)level->spawnpoints[current_spawn].scrollmaxx;
            }
            else if(level->spawnpoints[current_spawn].blockade)
            {
                // assume level spawn entry will not roll back, so just change it to 0 here
                if(level->spawnpoints[current_spawn].blockade < 0)
                {
                    level->spawnpoints[current_spawn].blockade = 0;
                }
                blockade = (float)level->spawnpoints[current_spawn].blockade;
            }
            else if(level->spawnpoints[current_spawn].palette != 0)
            {
                // assume level spawn entry will not roll back, so just change it to 0 here
                if(level->spawnpoints[current_spawn].palette < 0)
                {
                    level->spawnpoints[current_spawn].palette = 0;
                }
                change_system_palette(level->spawnpoints[current_spawn].palette);
            }
            else if(level->spawnpoints[current_spawn].light.y)  // change light direction for gfxshadow
            {
                light.x = level->spawnpoints[current_spawn].light.x;
                light.y = level->spawnpoints[current_spawn].light.y;
            }
            else if(level->spawnpoints[current_spawn].shadowcolor)  // change color for gfxshadow
            {
                shadowcolor = level->spawnpoints[current_spawn].shadowcolor;
                if(shadowcolor == -1)
                {
                    shadowcolor = 0;
                }
                else if(shadowcolor == -2)
                {
                    shadowcolor = -1;
                }
            }
            else if(level->spawnpoints[current_spawn].shadowalpha)  // change color for gfxshadow
            {
                shadowalpha = level->spawnpoints[current_spawn].shadowalpha;
                if(shadowalpha == BLEND_MODE_MODEL)
                {
                    shadowalpha = BLEND_MODE_NONE;
                }
            }
            else if(level->spawnpoints[current_spawn].shadowopacity)  // change color for gfxshadow
            {
                shadowopacity = level->spawnpoints[current_spawn].shadowopacity;
                if(shadowopacity == -1)
                {
                    shadowopacity = 0;
                }
                if(shadowopacity == -2)
                {
                    shadowopacity = -1;
                }
            }
            else
            {
                smartspawn(&level->spawnpoints[current_spawn]);
            }
            ++current_spawn;
        }
    }

    for(i = 0; i < levelsets[current_set].maxplayers; i++)
    {
        if(player[i].ent)
        {
            if(player[i].ent->position.x > rm)
            {
                rm = player[i].ent->position.x;
            }
            if(player[i].ent->position.x < lm)
            {
                lm = player[i].ent->position.x;
            }
            if(player[i].ent->position.z > bm)
            {
                bm = player[i].ent->position.z;
            }
            if(player[i].ent->position.z < tm)
            {
                tm = player[i].ent->position.z;
            }
            numplay++;
        }
    }

    if(level->waiting)
    {
        // Wait for all enemies to be defeated
        if(!findent(TYPE_ENEMY))
        {
            level->waiting = 0;
            if(level->noreset <= 1)
            {
                timeleft = level->settime * COUNTER_SPEED;    // Feb 24, 2005 - This line moved here to set custom time
            }
            go_time = _time + 3 * GAME_SPEED;
        }
    }

    if(numplay == 0)
    {
        return;
    }



    if(!level->waiting || global_config.cheats & CHEAT_OPTIONS_IMPLACABLE_ACTIVE)
    {
        if(level->scrolldir & SCROLL_RIGHT)
        {

            againstend = (level->width <= videomodes.hRes);

            if(rm - lm > videomodes.hRes)
            {
                to = advancex;
            }
            else
            {
                to = (lm + rm) / 2 - videomodes.hRes / 2 + level->cameraxoffset;
            }

            if(to < scrollminx)
            {
                to = scrollminx;
            }
            else if(to > scrollmaxx)
            {
                to = scrollmaxx;
            }

            if((level->scrolldir & SCROLL_BACK) && to < blockade)
            {
                to = blockade;
            }

            if(to > advancex)
            {
                if(to > advancex + level->scrollspeed)
                {
                    to = advancex + level->scrollspeed;
                }
                advancex = to;
            }
            else if((level->scrolldir & SCROLL_BACK) && to < advancex)
            {
                if(to < advancex - level->scrollspeed)
                {
                    to = advancex - level->scrollspeed;
                }
                advancex = to;
            }

            if(advancex < 0)
            {
                advancex = 0;
            }
            if(advancex >= level->width - videomodes.hRes)
            {
                advancex = (float)level->width - videomodes.hRes;
                againstend = 1;
            }

            if(againstend)
            {
                level->pos++;
            }
            else
            {
                level->pos = (int)advancex;
            }


        }
        else if(level->scrolldir & SCROLL_LEFT)
        {

            againstend = (level->width <= videomodes.hRes);

            if(rm - lm > videomodes.hRes)
            {
                to = advancex;
            }
            else
            {
                to = (lm + rm) / 2 - videomodes.hRes / 2 + level->cameraxoffset;
            }

            if(to < scrollminx)
            {
                to = scrollminx;
            }
            else if(to > scrollmaxx)
            {
                to = scrollmaxx;
            }

            if((level->scrolldir & SCROLL_BACK) && level->width - videomodes.hRes - to < blockade)
            {
                to = level->width - videomodes.hRes - blockade;
            }

            if(to < advancex)
            {
                if(to < advancex - level->scrollspeed)
                {
                    to = advancex - level->scrollspeed;
                }
                advancex = to;
            }
            else if((level->scrolldir & SCROLL_BACK) && to > advancex)
            {
                if(to > advancex + level->scrollspeed)
                {
                    to = advancex + level->scrollspeed;
                }
                advancex = to;
            }

            if(advancex > level->width - videomodes.hRes)
            {
                advancex = (float)level->width - videomodes.hRes;
            }
            if(advancex <= 0)
            {
                advancex = 0;
                againstend = 1;
            }

            if(againstend)
            {
                level->pos++;
            }
            else
            {
                level->pos = (int)((level->width - videomodes.hRes) - advancex);
            }
        }
        else if(level->scrolldir & SCROLL_OUTWARD) // z scroll only
        {

            if(bm - tm > videomodes.vRes)
            {
                to = advancey;
            }
            else
            {
                to = (bm + tm) / 2 - videomodes.vRes / 2 + level->camerazoffset;
            }

            if(to < scrollminz)
            {
                to = scrollminz;
            }
            else if(to > scrollmaxz)
            {
                to = scrollmaxz;
            }

            if((level->scrolldir & SCROLL_BACK) && to < blockade)
            {
                to = blockade;
            }

            if(to > advancey)
            {
                if(to > advancey + level->scrollspeed)
                {
                    to = advancey + level->scrollspeed;
                }
                advancey = to;
            }
            else if((level->scrolldir & SCROLL_BACK) && to < advancey)
            {
                if(to < advancey - level->scrollspeed)
                {
                    to = advancey - level->scrollspeed;
                }
                advancey = to;
            }

            if(advancey > panel_height - videomodes.vRes)
            {
                advancey = (float)panel_height - videomodes.vRes;
                againstend = 1;
            }
            if(advancey < 0)
            {
                advancey = 0;
            }

            if(againstend)
            {
                level->pos++;
            }
            else
            {
                level->pos = (int)advancey;
            }
        }
        else if(level->scrolldir & SCROLL_INWARD)
        {
            if(bm - tm > videomodes.vRes)
            {
                to = advancey;
            }
            else
            {
                to = (bm + tm) / 2 - videomodes.vRes / 2 + level->camerazoffset;
            }

            if(to < scrollminz)
            {
                to = scrollminz;
            }
            else if(to > scrollmaxz)
            {
                to = scrollmaxz;
            }

            if((level->scrolldir & SCROLL_BACK) && panel_height - videomodes.vRes - to < blockade)
            {
                to = panel_height - videomodes.vRes - blockade;
            }

            if(to < advancey)
            {
                if(to < advancey - level->scrollspeed)
                {
                    to = advancey - level->scrollspeed;
                }
                advancey = to;
            }
            else if((level->scrolldir & SCROLL_BACK) && to > advancey)
            {
                if(to > advancey + level->scrollspeed)
                {
                    to = advancey + level->scrollspeed;
                }
                advancey = to;
            }

            if(advancey > panel_height - videomodes.vRes)
            {
                advancey = (float)panel_height - videomodes.vRes;
            }
            if(advancey <= 0)
            {
                advancey = 0;
                againstend = 1;
            }

            if(againstend)
            {
                level->pos++;
            }
            else
            {
                level->pos = (int)((panel_height - videomodes.vRes) - advancey);
            }
        }
        //up down, elevator stage
        else if(level->scrolldir & (SCROLL_UP | SCROLL_DOWN))
        {
            //advancey += 0.5;
            if(scrolladd == 1)
            {
                scrolladd = 0;
                advancey++;
            }
            else
            {
                scrolladd++;
            }
            level->pos = (int)advancey;
        }
    }//if(!level->waiting)

    // z auto-scroll
    if((level->scrolldir & SCROLL_LEFT) || (level->scrolldir & SCROLL_RIGHT)) // added scroll type both; weird things can happen, but only if the modder is lazy in using blockades, lol
    {

        if(cameratype == 1)
        {
            bm = -9999;
            tm = 999999; //recalculate
            for(i = 0; i < levelsets[current_set].maxplayers; i++)
            {
                if(player[i].ent)
                {
                    if(player[i].ent->position.z - player[i].ent->position.y > bm)
                    {
                        bm = player[i].ent->position.z - player[i].ent->position.y;
                    }
                    if(player[i].ent->position.z - player[i].ent->position.y < tm)
                    {
                        tm = player[i].ent->position.z - player[i].ent->position.y;
                    }
                }
            }
        }
        if(bm - tm > videomodes.vRes)
        {
            to = advancey;
        }
        else
        {
            to = (bm + tm) / 2 - videomodes.vRes / 2 + level->camerazoffset;
        }

        // new scroll limit
        if(to > scrollmaxz)
        {
            to = scrollmaxz;
        }
        else if(to < scrollminz)
        {
            to = scrollminz;
        }

        if(to != advancey)
        {
            if(to > advancey + level->scrollspeed)
            {
                to = advancey + level->scrollspeed;
            }
            else if(to < advancey - level->scrollspeed)
            {
                to = advancey - level->scrollspeed;
            }
            advancey = (float)to;
        }

        if(advancey > panel_height - (level->rocking ? 16 : 12) - videomodes.vRes)
        {
            advancey = (float)(panel_height - (level->rocking ? 16 : 12) - videomodes.vRes);
        }
        if(advancey < 0)
        {
            advancey = 0;
        }
    }
    // now x auto scroll
    else if((level->scrolldir & SCROLL_INWARD) || (level->scrolldir & SCROLL_OUTWARD))
    {
        if(rm - lm > videomodes.hRes)
        {
            to = advancex;
        }
        else
        {
            to = (lm + rm) / 2 - videomodes.hRes / 2 + level->cameraxoffset;
        }

        // new scroll limit
        if(to > scrollmaxx)
        {
            to = scrollmaxx;
        }
        else if(to < scrollminx)
        {
            to = scrollminx;
        }

        if(to != advancex)
        {
            if(to > advancex + level->scrollspeed)
            {
                to = advancex + level->scrollspeed;
            }
            else if(to < advancex - level->scrollspeed)
            {
                to = advancex - level->scrollspeed;
            }
            advancex = (float)to;
        }

        if(advancex > level->width - videomodes.hRes)
        {
            advancex = (float)(level->width - videomodes.hRes);
        }
        if(advancex < 0)
        {
            advancex = 0;
        }
    }
    //end of z auto-scroll
    // global value for type_panel
    scrolldx = advancex - tx;
    scrolldy = advancey - ty;
}


void update_scrolled_bg()
{
    float rocktravel;
    unsigned char neonp[32];//3*8
    static int neon_count = 0;
    static int rockpos = 0;
    static int rockoffssine[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
    };   // normal rock
    static int rockoffsshake[32] =
    {
        2, 2, 2, 2, 2, 2, 2, 2,
        2, 2, 0, 4, 2, 0, 4, 2,
        2, 2, 2, 2, 2, 2, 2, 2,
        2, 2, 0, 4, 2, 0, 4, 2,
    };   // slow, constant jarring rock, like on a train
    static int rockoffsrumble[32] =
    {
        2, 2, 3, 3, 2, 2, 3, 3,
        2, 2, 3, 3, 2, 3, 2, 3,
        2, 2, 3, 3, 2, 2, 3, 3,
        2, 2, 3, 3, 2, 3, 2, 3,
    };   // fast, constant rumbling, like in/on a van or trailer
    int pb = pixelbytes[(int)PIXEL_32];

    // Time to update neon and screen all flag false?
    if(_time >= neon_time && !freezeall)
    {
        memcpy(neonp, neontable + 128 * pb, 8 * pb);
        memcpy(neontable + 128 * pb, neonp + 2 * pb, 6 * pb);
        memcpy(neontable + (128 + 6)*pb, neonp, 2 * pb);

        neon_time = _time + (GAME_SPEED / 3);
        neon_count += 2;
    }

    if(!freezeall)
    {
        rocktravel = (level->rocking) ? ((_time - traveltime) / ((float)GAME_SPEED / 30)) : 0; // no like in real life, maybe
        if(level->bgspeed < 0)
        {
            rocktravel = -rocktravel;
        }
        bgtravelled  += (_time - traveltime) * level->bgspeed  / 30 * 4 + rocktravel;
        vbgtravelled += (_time - traveltime) * level->vbgspeed / 30 * 4;
    }
    else
    {
        texttime += _time - traveltime;
    }

    timevar = _time - texttime;

    if(level->rocking)
    {
        rockpos = (timevar / (GAME_SPEED / 8)) & 31;
        if(level->rocking == 1)
        {
            gfx_y_offset = level->quake - 4 - rockoffssine[rockpos];
        }
        else if(level->rocking == 2)
        {
            gfx_y_offset = level->quake - 4 - rockoffsshake[rockpos];
        }
        else if(level->rocking == 3)
        {
            gfx_y_offset = level->quake - 4 - rockoffsrumble[rockpos];
        }
    }
    else
    {
        if(level->quake >= 0)
        {
            gfx_y_offset = level->quake - 4;
        }
        else
        {
            gfx_y_offset = level->quake + 4;
        }
    }

    //if(level->scrolldir!=SCROLL_UP && level->scrolldir!=SCROLL_DOWN) gfx_y_offset -= advancey;
    gfx_y_offset += gfx_y_offset_adj;   //2011_04_03, DC: Apply modder adjustment.

    traveltime = _time;

    if(_time >= level->quaketime)
    {
        level->quake /= 2;
        level->quaketime = _time + (GAME_SPEED / 25);
    }
}

void draw_scrolled_bg()
{
    int index = 0, x, z, i = 0, j, l, m;
    s_layer *layer;
    int width, height;

    int vpx, vpy, vpw, vph;

    if(viewportw > 0)
    {
        vpx = viewportx;
        vpy = viewporty;
        vpw = viewportw;
        vph = viewporth;
    }
    else
    {
        vpx = vpy = 0;
        vpw = videomodes.hRes;
        vph = videomodes.vRes;
    }

    //if(level) printf("%d %d %d %d\n", vpx, vpy, vpw, vph);

    s_drawmethod screenmethod = plainmethod;

    /*s_drawmethod *pscreenmethod = &screenmethod;
    for(i = 0; i < level->numholes; i++)
    {
        spriteq_add_sprite((int)(level->holes[i].x - screenx + gfx_x_offset), (int)(level->holes[i].z - level->holes[i].depth - screeny + gfx_y_offset), HOLE_Z, holesprite, pscreenmethod, 0);
    }*/

    for(index = 0; index < level->numlayersref; index++)
    {
        layer = level->layersref + index;

        screenmethod = layer->drawmethod;

        //printf("layer %d, handle:%u, z:%d\n", index, layer->gfx.handle, layer->position.z);

		// Layer must be enabled and have at least one instace, or we don't draw it.
        if(!screenmethod.xrepeat || !screenmethod.yrepeat || !layer->enabled)
        {
            continue;
        }

        width = screenmethod.xspan = layer->size.x + layer->spacing.x;
        height = screenmethod.yspan = layer->size.y + layer->spacing.z;

        x = (int)(layer->offset.x - (advancex +  bgtravelled * layer->bgspeedratio) * (1.0 - layer->ratio.x) ) ;

        //printf("layerxratio %f  %d %f\n ", layer->xratio, x, layer->bgspeedratio);

        if((level->scrolldir & SCROLL_UP))
        {
            //z = (int)(layer->offset.z + advancey * (1.0 - layer->ratio.z) ) ;
            z = (int)(layer->offset.z - (advancey + vbgtravelled * layer->bgspeedratio) * (1.0 - layer->ratio.z) ) ;
        }
        else
        {
            //z = (int)(layer->offset.z - advancey * (1.0 - layer->ratio.z) ) ;
            z = (int)(layer->offset.z - (advancey + vbgtravelled * layer->bgspeedratio) * (1.0 - layer->ratio.z) ) ;
        }

        if(layer->quake)
        {
            x += gfx_x_offset;
            z += gfx_y_offset;
            //printf("%d y %d %d\n", index, gfx_y_offset, z);
        }

        x -= vpx;
        z -= vpy;


        if(x < 0)
        {
            i = (-x) / width;
            x %= width;
        }
        else
        {
            i = 0;
        }

        if(i > 0 && screenmethod.water.watermode != WATER_MODE_SHEAR && screenmethod.water.amplitude)
        {
            i--;
            x -= width;
        }

        if(z < 0)
        {
            j = (-z) / height;
            z %= height;
        }
        else
        {
            j = 0;
        }
        
		if(layer->neon)
        {
            screenmethod.table = neontable;
        }
        else
        {
            if(current_palette > 0)
            {
                screenmethod.table = level->palettes[current_palette - 1];
            }
            else
            {
                screenmethod.table = NULL;
            }
        }

        screenmethod.water.wavetime =  (int)(timevar * screenmethod.water.wavespeed);
        screenmethod.xrepeat = screenmethod.yrepeat = 0;
        for(m = z; j < layer->drawmethod.yrepeat && m < vph; m += height, j++, screenmethod.yrepeat++);
        for(l = x; i < layer->drawmethod.xrepeat && l < vpw + (screenmethod.water.watermode == WATER_MODE_SHEAR ? 0 : screenmethod.water.amplitude * 2); l += width, i++, screenmethod.xrepeat++);

        if(layer->gfx.screen->magic == screen_magic)
        {
            spriteq_add_screen(x + vpx, z + vpy, layer->z, layer->gfx.screen, &screenmethod, index);
        }
        else if(layer->gfx.sprite->magic == sprite_magic)
        {
            spriteq_add_frame(x + vpx, z + vpy, layer->z, layer->gfx.sprite, &screenmethod, index);
        }

        //printf("******%d\t%d\t%d\t%d\t%d*****\n", x+vpx, z+vpy, layer->.z, screenmethod.xrepeat, screenmethod.yrepeat);
    }


}

u32 getinterval()
{
    interval = timer_getinterval(GAME_SPEED); // so interval can be logged into movie
    if(interval > GAME_SPEED)
    {
        interval = 1;
    }
    if(interval > GAME_SPEED / 4)
    {
        interval = GAME_SPEED / 4;
    }
    return interval;
}

// Caskey, Damon V.
// 2019-04-22
// 
// Run input scripts. Similar to keys, but
// execute before processing any command functions.
void execute_input_scripts(int player_index)
{
	s_player *player_obj = NULL;
		
	player_obj = player + player_index;

	if (!player_obj)
	{
		return;
	}
	
	if (player_obj->newkeys || (keyscriptrate && player->keys) || player->releasekeys)
	{
		// 2019-04-22 Don't exist yet
		//if (level)
		//{

			//execute_level_key_script(player_index, player);
			//execute_entity_key_script(player.ent);
		//}
		//execute_key_script(player_index, player);

		execute_input_script_all(player_index);
	}
}

void inputrefresh(int playrecmode)
{
    int p;
    s_player *pl;
    u64 k;

    control_update(playercontrolpointers, MAX_PLAYERS);

    bothkeys = 0;
    bothnewkeys = 0;

    for(p = 0; p < MAX_PLAYERS; p++)
    {
        pl = player + p;

        if ( playrecmode != A_REC_PLAY )
        {

            pl->releasekeys = (playercontrolpointers[p]->keyflags | pl->keys) - playercontrolpointers[p]->keyflags;
            pl->releasekeys &= ~pl->disablekeys;
            pl->keys = playercontrolpointers[p]->keyflags & ~pl->disablekeys;
            pl->newkeys = playercontrolpointers[p]->newkeyflags & ~pl->disablekeys;
            pl->playkeys |= pl->newkeys;
            pl->playkeys &= pl->keys;
            pl->playkeys &= ~pl->disablekeys;
        }
        else
        {
            // in play mode: add pressed keys to rec keys
            pl->releasekeys |= (playercontrolpointers[p]->keyflags | pl->prevkeys) - playercontrolpointers[p]->keyflags;
            pl->releasekeys &= ~pl->disablekeys;
            pl->keys |= playercontrolpointers[p]->keyflags & ~pl->disablekeys;
            pl->newkeys |= playercontrolpointers[p]->newkeyflags & ~pl->disablekeys;
            pl->playkeys |= pl->newkeys;
            pl->playkeys &= pl->keys;
            pl->playkeys &= ~pl->disablekeys;
        }
				
		execute_input_scripts(p);		

        if(pl->ent && pl->ent->movetime < _time)
        {
            memset(pl->combokey, 0, sizeof(*pl->combokey)*MAX_SPECIAL_INPUTS);
            memset(pl->inputtime, 0, sizeof(*pl->inputtime)*MAX_SPECIAL_INPUTS);
            pl->combostep = 0;
        }
        if(pl->newkeys)
        {			
            k = pl->newkeys;
            if(pl->ent)
            {
                pl->ent->movetime = _time + GAME_SPEED / 4;
                if(k & FLAG_MOVELEFT)
                {
                    k |= pl->ent->direction ? FLAG_BACKWARD : FLAG_FORWARD;
                }
                else if(k & FLAG_MOVERIGHT)
                {
                    k |= pl->ent->direction ? FLAG_FORWARD : FLAG_BACKWARD;
                }
            }
            pl->inputtime[pl->combostep] = _time;
            pl->combokey[pl->combostep] = k;
            pl->combostep++;
            pl->combostep %= MAX_SPECIAL_INPUTS;
        }

        bothkeys |= player[p].keys;
        bothnewkeys |= player[p].newkeys;
    }

}

void execute_keyscripts()
{
    int p;
    for(p = 0; p < levelsets[current_set].maxplayers; p++)
    {
        if(!_pause && (level || check_in_screen()) && (player[p].newkeys || (keyscriptrate && player[p].keys) || player[p].releasekeys))
        {
            if(level)
            {
                execute_level_key_script(p);
                execute_entity_key_script(player[p].ent);
            }
            execute_key_script(p);
            execute_key_script_all(p);
        }
    }
}

void execute_updatescripts()
{
    if(Script_IsInitialized(&update_script))
    {
        Script_Execute(&(update_script));
    }
    if(level && Script_IsInitialized(&(level->update_script)))
    {
        Script_Execute(&(level->update_script));
    }
}

void execute_updatedscripts()
{
    if(Script_IsInitialized(&updated_script))
    {
        Script_Execute(&(updated_script));
    }
    if(level && Script_IsInitialized(&(level->updated_script)))
    {
        Script_Execute(&(level->updated_script));
    }
}

void draw_textobjs()
{
    int i;
    s_textobj *textobj;
    if(!level)
    {
        return;
    }
    for(i = 0; i < level->numtextobjs ; i++)
    {
        textobj = level->textobjs + i;

        if(textobj->time && textobj->time <= _time)		//If a time was set and passed, remove the text object.
        {
            level->textobjs[i].time	= 0;
            level->textobjs[i].position.x = 0;
            level->textobjs[i].position.y = 0;
            level->textobjs[i].font = 0;
            level->textobjs[i].position.z = 0;
            if(level->textobjs[i].text)
            {
                free(level->textobjs[i].text);
                level->textobjs[i].text = NULL;
            }
        }
        else
        {
            if(textobj->text)
            {
                font_printf(textobj->position.x, textobj->position.y, textobj->font, textobj->position.z, "%s", textobj->text);
            }
        }
    }
}

int recordInputs()
{
    int p = 0;
    RecKeys reckey;
    unsigned int window = 4096;
    u32 max_rec_time = GAME_SPEED*60*10; // protection

    if(playrecstatus->status != A_REC_REC) return 0;
    if ( !playrecstatus->begin )
    {
        playrecstatus->starttime = _time;
        playrecstatus->synctime = 0;
        if (playrecstatus->buffer)
        {
            free(playrecstatus->buffer);
            playrecstatus->buffer = NULL;
        }
        playrecstatus->buffer = (RecKeys*)calloc(window+playrecstatus->synctime*sizeof(RecKeys),sizeof(RecKeys));
        if (playrecstatus->buffer == NULL)
        {
            printf("Error to allocate buffer in record inputs mode.\n");
            return 0;
        }
        playrecstatus->begin = 1;
        playrecstatus->seed = getseed();
        playrecstatus->cseed = _time;
        srand(_time);
        playrecstatus->ticks = timer_gettick();
    }
    else
    {
        if ( playrecstatus->synctime%window >= window-2 ) // last is NULL bytes
        {
            playrecstatus->buffer = (RecKeys*)realloc(playrecstatus->buffer,sizeof(RecKeys)*((int)(trunc(playrecstatus->synctime/window)+1)*window+window));
            if (playrecstatus->buffer == NULL)
            {
                printf("Error to allocate buffer in record inputs mode.\n");
                return 0;
            } else memset(playrecstatus->buffer+(playrecstatus->synctime+1),0,(int)(trunc(playrecstatus->synctime/window)+1)*window+window-(playrecstatus->synctime+1)-1); // -2 becouse -1 is to 0 to size-1
        }
    }

    // now rec!
    if(playrecstatus->buffer && playrecstatus->begin)
    {
        for ( p = 0; p < MAX_PLAYERS; p++ )
        {
            reckey.keys[p]        = player[p].keys;
            reckey.newkeys[p]     = player[p].newkeys;
            reckey.releasekeys[p] = player[p].releasekeys;
            reckey.playkeys[p]    = player[p].playkeys;
        }
        reckey.time     = _time;
        reckey.interval = interval;
        reckey.synctime = playrecstatus->synctime;
        //reckey.seed     = getseed();
        memcpy( &playrecstatus->buffer[playrecstatus->synctime], &reckey, sizeof(reckey) );
    }

    if ( _time >= max_rec_time ) stopRecordInputs(); // safe
    if(playrecstatus->status == A_REC_REC) ++playrecstatus->synctime;

    //debug_printf("time: %d sync: %d",(u32)time,(u32)playrecstatus->synctime);
    //debug_printf("keys: %d",player[0].releasekeys&FLAG_ATTACK);

    return 1;
}

int playRecordedInputs()
{
    int p = 0;
    RecKeys reckey;
    char path[MAX_ARG_LEN + 1];
    size_t filesize = 0;
    char header[6];

    if(playrecstatus->status != A_REC_PLAY) return 0;
    if ( !playrecstatus->begin ) //time >= playrecstatus->starttime &&
    {
        if ( strlen(playrecstatus->path) <= 0 ) getBasePath(path, "Saves", 0);
        else strcpy(path,playrecstatus->path);
        if ( path[strlen(path)-1] != '/' ) strcpy(path,"/");

        if (playrecstatus->buffer)
        {
            free(playrecstatus->buffer);
            playrecstatus->buffer = NULL;
        }
        if(playrecstatus->handle)
        {
            fclose(playrecstatus->handle);
        }

        playrecstatus->handle = fopen(strcat(path,playrecstatus->filename), "rb+");
        if(playrecstatus->handle)
        {
            fseek(playrecstatus->handle, 0L, SEEK_END);
            filesize = ftell(playrecstatus->handle);
            fseek(playrecstatus->handle, 0L, SEEK_SET); // or rewind(playrecstatus->handle)

            if (filesize <= 0)
            {
                printf("Empty recorded inputs file.\n");
                return 0;
            }
            playrecstatus->buffer = (RecKeys*)calloc(1,filesize+1);
            if(!playrecstatus->buffer)
            {
                printf("Error to allocate buffer for recorded inputs.\n");
                return 0;
            }
        } else
        {
            printf("Error to open recorded inputs file.\n");
            return 0;
        }
        fread(&header, 6, 1, playrecstatus->handle);
        fread(&playrecstatus->starttime, sizeof(u32), 1, playrecstatus->handle);
        fread(&playrecstatus->endtime, sizeof(u32), 1, playrecstatus->handle);
        fread(&playrecstatus->totsynctime, sizeof(u32), 1, playrecstatus->handle);
        fread(&playrecstatus->cseed, sizeof(u32), 1, playrecstatus->handle);
        fread(&playrecstatus->seed, sizeof(unsigned long), 1, playrecstatus->handle);
        fread(&playrecstatus->ticks, sizeof(unsigned), 1, playrecstatus->handle);
        fread(playrecstatus->buffer, sizeof(RecKeys)*(playrecstatus->endtime+1), 1, playrecstatus->handle);

        // sync at start time
        _time = playrecstatus->starttime;
        playrecstatus->synctime = 0;
        playrecstatus->begin = 1;
        srand(playrecstatus->cseed);
        srand32(playrecstatus->seed);
        //set_ticks(timer_gettick()-playrecstatus->ticks);
    }

    // now play!
    if(playrecstatus->buffer && playrecstatus->begin && playrecstatus->synctime < playrecstatus->totsynctime)
    {
        memcpy( &reckey, &playrecstatus->buffer[playrecstatus->synctime], sizeof(reckey) );

        if( _time != reckey.time )
        {
            u32 nextsynctime = reckey.synctime;

            //time = (u32)reckey.time;
            printf("Play recorded inputs: Out of sync! Time: %d, RecTime: %d\n",time,reckey.time);
            /*if ( interval != reckey.interval )
            {
                //interval = (u32)reckey.interval;
                printf("Play recorded inputs: Out of sync! Interval: %d, RecInterval: %d\n",interval,reckey.interval);
            }*/

            while( _time > reckey.time && nextsynctime > 0 ) {
                memcpy( &reckey, &playrecstatus->buffer[--nextsynctime], sizeof(reckey) );
            }
            _time = reckey.time;

            while( _time < reckey.time && nextsynctime < playrecstatus->totsynctime ) {
                memcpy( &reckey, &playrecstatus->buffer[++nextsynctime], sizeof(reckey) );
            }
            _time = reckey.time;
        }

        if ( _time == reckey.time )
        {
            if ( playrecstatus->synctime != reckey.synctime )
            {
                u32 nextsynctime = reckey.synctime;

                printf("Play recorded inputs: Out of sync! SyncTime: %d, RecSyncTime: %d\n",playrecstatus->synctime,reckey.synctime);

                while( playrecstatus->synctime > reckey.synctime && nextsynctime > 0 ) {
                    memcpy( &reckey, &playrecstatus->buffer[--nextsynctime], sizeof(reckey) );
                }
                playrecstatus->synctime = reckey.synctime;

                while( playrecstatus->synctime < reckey.synctime && nextsynctime < playrecstatus->totsynctime ) {
                    memcpy( &reckey, &playrecstatus->buffer[++nextsynctime], sizeof(reckey) );
                }
                playrecstatus->synctime = reckey.synctime;
            }

            if ( interval != reckey.interval ) interval = reckey.interval;
            //srand32(reckey.seed);
            for ( p = 0; p < MAX_PLAYERS; p++ )
            {
                player[p].keys        = reckey.keys[p];
                player[p].newkeys     = reckey.newkeys[p];
                player[p].releasekeys = reckey.releasekeys[p];
                player[p].playkeys    = reckey.playkeys[p];
            }
            //inputrefresh(playrecstatus->status);
        }
    }

    //debug_printf("synctim: %d totsync: %d status:%d",playrecstatus->synctime,playrecstatus->totsynctime,playrecstatus->status);
    if(playrecstatus->status == A_REC_PLAY) ++playrecstatus->synctime;
    if ( playrecstatus->synctime >= playrecstatus->totsynctime || _time >= playrecstatus->endtime ) stopRecordInputs();

    //debug_printf("time: %d sync: %d",(u32)time,(u32)playrecstatus->synctime);
    //debug_printf("keys: %d",player[0].releasekeys&FLAG_ATTACK);

    return 1;
}

int stopRecordInputs()
{
    if(playrecstatus)
    {
        switch(playrecstatus->status)
        {
            case A_REC_REC:
            {
                char path[MAX_ARG_LEN + 1];
                char header[6] = "INP10";

                if ( strlen(playrecstatus->path) <= 0 ) getBasePath(path, "Saves", 0);
                else strcpy(path,playrecstatus->path);
                if ( path[strlen(path)-1] != '/' ) strcpy(path,"/");

                if (playrecstatus->buffer)
                {
                    playrecstatus->handle = fopen(strcat(path,playrecstatus->filename), "wb+");
                    if(playrecstatus->handle)
                    {
                        playrecstatus->endtime = _time;
                        if (playrecstatus->synctime < 2) playrecstatus->synctime = 2;
                        else playrecstatus->synctime -= 2;

                        fwrite(header, 6, 1, playrecstatus->handle);
                        fwrite(&playrecstatus->starttime, sizeof(u32), 1, playrecstatus->handle);
                        fwrite(&playrecstatus->endtime, sizeof(u32), 1, playrecstatus->handle);
                        fwrite(&playrecstatus->synctime, sizeof(u32), 1, playrecstatus->handle);
                        fwrite(&playrecstatus->cseed, sizeof(u32), 1, playrecstatus->handle);
                        fwrite(&playrecstatus->seed, sizeof(unsigned long), 1, playrecstatus->handle);
                        fwrite(&playrecstatus->ticks, sizeof(unsigned), 1, playrecstatus->handle);
                        fwrite(playrecstatus->buffer, sizeof(RecKeys)*(playrecstatus->synctime+1), 1, playrecstatus->handle);
                        fflush(playrecstatus->handle); // safe
                        fclose(playrecstatus->handle);
                    } else return 0;

                    free(playrecstatus->buffer);
                    playrecstatus->buffer = NULL;
                } else return 0;
                break;
            }
            case A_REC_PLAY:
            {
                if(playrecstatus->handle)
                {
                    if (playrecstatus->handle) fclose(playrecstatus->handle);
                    else return 0;
                }
                break;
            }
            case A_REC_STOP:
            {
                if(playrecstatus->handle)
                {
                    if (playrecstatus->handle) fclose(playrecstatus->handle);
                    else return 0;
                }
                break;
            }
        }

        playrecstatus->status = A_REC_STOP;
        playrecstatus->begin = 0;
        playrecstatus->synctime = 0;
        freeRecordedInputs();
    } return 0;

    return 1;
}

int freeRecordedInputs()
{
    playrecstatus->status = A_REC_STOP;
    if(playrecstatus->handle) fclose(playrecstatus->handle);
    if(playrecstatus->buffer)
    {
        free(playrecstatus->buffer);
        playrecstatus->buffer = NULL;
        return 1;
    }

    return 0;
}

a_playrecstatus* init_input_recorder()
{
    playrecstatus = (a_playrecstatus*)calloc(1,sizeof(*playrecstatus));

    return playrecstatus;
}

void free_input_recorder()
{
    if(playrecstatus)
    {
        if(playrecstatus->buffer)
        {
            if(playrecstatus->handle) fclose(playrecstatus->handle);
            free(playrecstatus->buffer);
            playrecstatus->buffer = NULL;
        }
        free(playrecstatus);
        playrecstatus = NULL;
    }
}

void update(int ingame, int usevwait)
{
    int i = 0;
    int p_keys = 0;

    getinterval();
    if(playrecstatus->status == A_REC_PLAY && !_pause && level) if ( !playRecordedInputs() ) stopRecordInputs();
    inputrefresh(playrecstatus->status);
    if(playrecstatus->status == A_REC_REC && !_pause && level) if ( !recordInputs() ) stopRecordInputs();

    if ((!_pause && ingame == 1) || alwaysupdate)
    {
        execute_updatescripts();
    }

    newtime = 0;
    if(!_pause)
    {
        if(ingame == 1 || check_in_screen())
        {
            execute_keyscripts();
        }

        if((level_completed && level->boss_slow == BOSS_SLOW_ON && !tospeedup) || slowmotion.toggle > SLOW_MOTION_OFF)
        {
            if(slowmotion.duration == slowmotion.counter)
            {
                newtime = _time + interval;
            }
        }
        else
        {
            newtime = _time + interval;
        }

        slowmotion.counter++;
        if(slowmotion.counter == (slowmotion.duration + 1))
        {
            slowmotion.counter = 0;
            if(slowmotion.toggle > SLOW_MOTION_ON)
            {
                slowmotion.duration = slowmotion.toggle;
            }
        }
        if(newtime > _time + 100)
        {
            newtime = _time + 100;
        }

        while(_time < newtime)
        {
            if(ingame == 1)
            {
                update_scroller();
                if(!freezeall)
                {
                    int all_p_alive = 0;

                    for(i = 0; i < MAX_PLAYERS; i++)
                    {
                        if (!player[i].ent) ++all_p_alive;
                    }
                    all_p_alive = (all_p_alive >= MAX_PLAYERS) ? 1 : 0;

                    if(level->settime > 0 || (level->type != 2 && all_p_alive))
                    //if(level->settime > 0 || (level->type != 2 && !player[0].ent && !player[1].ent && !player[2].ent && !player[3].ent))
                    {
                        int all_p_nojoin = 0, all_p_nocredits = 0;

                        for(i = 0; i < MAX_PLAYERS; i++)
                        {
                            if (!player[i].joining) ++all_p_nojoin;
                        }
                        all_p_nojoin = (all_p_nojoin >= MAX_PLAYERS) ? 1 : 0;

                        for(i = 0; i < MAX_PLAYERS; i++)
                        {
                            if (player[i].credits < 1) ++all_p_nocredits;
                        }
                        all_p_nocredits = (all_p_nocredits >= MAX_PLAYERS) ? 1 : 0;

                        if(timeleft > 0)
                        {
                            --timeleft;
                        }
                        else if((level->settime > 0 && all_p_nojoin) ||
                                ( ((!noshare && credits < 1) || (noshare && all_p_nocredits))
                                 && all_p_nojoin )
                               )
                        {
                            time_over();
                        }
                    }
                }
                update_scrolled_bg();
                if(level->type != 2)
                {
                    updatestatus();
                }
            }
            if(ingame == 1 || check_in_screen())
            {
                update_ents();
            }
            ++_time;
        }

    }

    /************ gfx queueing ************/

    //clearscreen(vscreen); //betopp - not needed?

    if(ingame == 1 && !_pause)
    {
        draw_scrolled_bg();
        if(level->type != 2)
        {
            predrawstatus();
        }
        if(level->type != 2)
        {
            drawstatus();
        }
        draw_textobjs();
    }

    if(!ingame)
    {
        if(background)
        {
            spriteq_add_screen(0, 0, MIN_INT, background, NULL, 0);
        }
    }

    // entity sprites queueing
    if(ingame == 1 || check_in_screen())
        if(!_pause)
        {
            display_ents();
        }

    /************ updated script  ************/
    if(ingame == 1 || alwaysupdate)
    {
        execute_updatedscripts();
    }

    for(i = 0; i < MAX_PLAYERS; i++)
    {
        if (player[i].ent && (player[i].newkeys & FLAG_START))
        {
            p_keys = 1;
            break;
        }
    }

    // 2011/10/22 UT: move pause menu logic here
    /*if(ingame == 1 && !_pause && !nopause &&
            ((player[0].ent && (player[0].newkeys & FLAG_START)) ||
             (player[1].ent && (player[1].newkeys & FLAG_START)) ||
             (player[2].ent && (player[2].newkeys & FLAG_START)) ||
             (player[3].ent && (player[3].newkeys & FLAG_START)))
      )*/
    if(ingame == 1 && !_pause && !nopause && p_keys)
    {
        if ( !(goto_mainmenu_flag&1) )
        {
            sound_pause_music(1);
            sound_pause_sample(1);
            sound_play_sample(global_sample_list.pause, 0, savedata.effectvol, savedata.effectvol, 100);
            pausemenu();
            return;
        }
    }
    if( ingame == 1 && (goto_mainmenu_flag&1) )
    {
        backto_mainmenu();
        return;
    }

    /********** update screen **************/

    spriteq_draw(vscreen, 0, MIN_INT, MAX_INT, 0, 0); // notice, always draw sprites at the very end of other methods

    if(_pause != 2 && !noscreenshot && (bothnewkeys & FLAG_SCREENSHOT))
    {
        screenshot(vscreen, getpal, 1);
    }

    // Debug stuff, should not appear on screenshot
    if(debug_time == 0xFFFFFFFF)
    {
        debug_time = _time + GAME_SPEED * 5;
    }
    if(_time < debug_time && debug_msg[0])
    {
        if( debug_xy_msg.x >= 0 && debug_xy_msg.y >= 0 )
        {
            if ( debug_xy_msg.font_index < 0 ) debug_xy_msg.font_index = 0;
            screen_printf(vscreen, debug_xy_msg.x, debug_xy_msg.y, debug_xy_msg.font_index, debug_msg);
        } else screen_printf(vscreen, 0, videomodes.vRes-fontheight(0), 0, debug_msg);
    }
    else
    {
        debug_msg[0] = 0;
        debug_xy_msg.x = -1;
        debug_xy_msg.y = -1;
#ifdef DEBUG_MODE
        if(level->pos)
        {
            debug_printf("Position: %i, width: %i, spawn: %i, offsets: %i/%i", level->pos, level->width, current_spawn, level->quake, gfx_y_offset);
        }
#endif
    }

    if(usevwait)
    {
        vga_vwait();
    }
    video_copy_screen(vscreen);
    spriteq_clear();

    check_music();
    sound_update_music();
}




// ----------------------------------------------------------------------
/* Plombo 9/4/2010: New function that can use brightness/gamma correction
 * independent from the global palette on platforms where it's available.
 * Hardware accelerated brightness/gamma correction is available on Wii and
 * OpenGL platforms using TEV and GLSL, respectively. Returns 1 on success, 0 on
 * error. */
int set_color_correction(int gm, int br)
{
#if WII || SDL || VITA
    video_set_color_correction(gm, br);
    return 1;
#else
    return 0;
#endif
}

// Simple palette fade / vscreen fade
void fade_out(int type, int speed)
{
    int i, j = 0;
    int b, g = 0;
    u32 interval = 0;
    int current = speed ? speed : fade;
    s_screen *fbuffer = NULL;
    s_drawmethod dm = plainmethod;
    dm.alpha = BLEND_MODE_AVERAGE;

    for(i = 0, j = 0; j < 64; )
    {
        while(j <= i && j < 64)
        {
            if(!type || type == 1)
            {
                b = ((savedata.brightness + 256) * (64 - j) / 64) - 256;
                g = 256 - ((savedata.gamma + 256) * (64 - j) / 64);
                vga_vwait();
                if(!set_color_correction(g, b))
                {
                    if(!fbuffer)
                    {
                        fbuffer = allocscreen(vscreen->width, vscreen->height, vscreen->pixelformat);
                        copyscreen(fbuffer, vscreen);
                    }
                    //255 + alpha BLEND_MODE_AVERAGE is actually half blend, so use 254 instead
                    dm.channelr = dm.channelg = dm.channelb = 254 * (64 - j) / 64;
                    clearscreen(vscreen);
                    putscreen(vscreen, fbuffer, 0, 0, &dm);
                }
            }
            j++;
            if(!type || type == 1)
            {
                video_copy_screen(vscreen);
            }
        }
        if(!type || type == 2)
        {
            sound_update_music();
            if(!musicoverlap)
            {
                sound_volume_music(savedata.musicvol * (64 - j) / 64, savedata.musicvol * (64 - j) / 64);
            }
        }
        interval = timer_getinterval(current);
        if(interval > current)
        {
            interval = current / 60;
        }
        if(interval > current / 4)
        {
            interval = current / 4;
        }
        i += interval;
    }

    if(!type || type == 2)
    {
        if(!musicoverlap)
        {
            sound_close_music();
        }
    }

    if(!type || type == 1)
    {
        clearscreen(vscreen);
        video_copy_screen(vscreen);
        vga_vwait();
        //the black screen, so we return to normal palette
        set_color_correction(savedata.gamma, savedata.brightness);
    }

    if(fbuffer)
    {
        freescreen(&fbuffer);
    }
}



void apply_controls()
{
    int p;

    for(p = 0; p < MAX_PLAYERS; 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_ATTACK,     savedata.keys[p][SDID_ATTACK]);
        control_setkey(playercontrolpointers[p], FLAG_ATTACK2,    savedata.keys[p][SDID_ATTACK2]);
        control_setkey(playercontrolpointers[p], FLAG_ATTACK3,    savedata.keys[p][SDID_ATTACK3]);
        control_setkey(playercontrolpointers[p], FLAG_ATTACK4,    savedata.keys[p][SDID_ATTACK4]);
        control_setkey(playercontrolpointers[p], FLAG_JUMP,       savedata.keys[p][SDID_JUMP]);
        control_setkey(playercontrolpointers[p], FLAG_SPECIAL,    savedata.keys[p][SDID_SPECIAL]);
        control_setkey(playercontrolpointers[p], FLAG_START,      savedata.keys[p][SDID_START]);
        control_setkey(playercontrolpointers[p], FLAG_SCREENSHOT, savedata.keys[p][SDID_SCREENSHOT]);
    }
}



// ----------------------------------------------------------------------

void display_credits()
{
    u32 finishtime = _time + 10 * GAME_SPEED;
    int done = 0;
    int s = videomodes.vShift / 2 + 3;
    int v = (videomodes.vRes - videomodes.vShift) / 24;
    int m = 0;
    int h = videomodes.hRes / 2;
    int col1 = h - fontmonowidth(0) * 16;
    int col2 = h + fontmonowidth(0) * 4;

    if(savedata.logo != 1)
    {
        return;
    }
    fade_out(0, 0);

    unload_level();

    bothnewkeys = 0;

    while(!done)
    {
        m = 2;

        font_printf(_strmidx(2, "Credits"), s,  2, 0, "Credits");

        font_printf(_strmidx(1, "OpenBOR"), s + v * m,  1, 0, "OpenBOR"); ++m;

        font_printf(col1, s + v * m,  0, 0, "Caskey, Damon V.");
        font_printf(col2, s + v * m,  0, 0, "Project Lead"); ++m;

        font_printf(col1, s + v * m,  0, 0, "Msmalik681");
        font_printf(col2, s + v * m,  0, 0, "Developer"); ++m;

        font_printf(col1, s + v * m,  0, 0, "Plombo");
        font_printf(col2, s + v * m,  0, 0, "Developer"); ++m;

        font_printf(_strmidx(1, "Former Staff"), s + v * m,  1, 0, "Former Staff"); ++m;

        font_printf(col1, s + v * m, 0, 0, "Fightn Words");
        font_printf(col2, s + v * m,  0, 0, "Fugue"); ++m;
        font_printf(col1, s + v * m, 0, 0, "KBAndressen");
        font_printf(col2, s + v * m,  0, 0, "Kirby"); ++m;
        font_printf(col1, s + v * m,  0, 0, "LordBall");
        font_printf(col2, s + v * m, 0, 0, "Orochi_X");  ++m;
        font_printf(col1, s + v * m, 0, 0, "SX");
        font_printf(col2, s + v * m,  0, 0, "Tails"); ++m;
        font_printf(col1, s + v * m,  0, 0, "uTunnels");
		font_printf(col2, s + v * m,  0, 0, "White Dragon"); ++m;

        font_printf(_strmidx(1, "Ports"), s + v * m,  1, 0, "Ports"); ++m;
        font_printf(col1, s + v * m, 0, 0, "PSP/Linux/OSX");
        font_printf(col2, s + v * m, 0, 0, "SX"); ++m;
		/*
        font_printf(col1, s + v * m, 0, 0, "OpenDingux");
        font_printf(col2, s + v * m, 0, 0, "Shin-NiL"); ++m;
        
        font_printf(col1, s + v * m, 0, 0, "DreamCast");
        font_printf(col2, s + v * m, 0, 0, "Neill Corlett, SX"); ++m;
		*/
        font_printf(col1, s + v * m, 0, 0, "Wii");
        font_printf(col2, s + v * m, 0, 0, "Plombo, SX, Msmalik681"); ++m;

        font_printf(col1, s + v * m, 0, 0, "Android");
        font_printf(col2, s + v * m, 0, 0, "CRxTRDude, Plombo,"); ++m;
        font_printf(col2, s + v * m, 0, 0, "uTunnels, Msmalik681"); ++m;
        font_printf(col2, s + v * m, 0, 0, "White Dragon"); ++m;

        font_printf(col1,  s + v * m, 0, 0, "PS Vita");
        font_printf(col2, s + v * m, 0, 0, "Plombo"); ++m;

        update(2, 0);

        done |= (_time > finishtime);
        done |= (bothnewkeys & (FLAG_START + FLAG_ESC));
    }
    fade = 75;
    fade_out(0, 0);
}


void borShutdown(int status, char *msg, ...)
{
    char buf[1024] = "";
    va_list arglist;
    int i;

    static int shuttingdown = 0;

    if(shuttingdown)
    {
        return;
    }

    shuttingdown = 1;

    //printf("savedata.logo %d\n", savedata.logo);

    va_start(arglist, msg);
    vsprintf(buf, msg, arglist);
    va_end(arglist);

    if(!disablelog)
    {
        switch(status)
        {
        case 0:
            printf("\n************ Shutting Down ************\n\n");
            break;
        default:
            printf("\n********** An Error Occurred **********"
                   "\n*            Shutting Down            *\n\n");
            break;
        }
    }

    if(!disablelog)
    {
        printf("%s", buf);
    }


    getRamStatus(BYTES);
    savesettings();

    /* entry point for the engine credits screen. */
    //screen_status |= IN_SCREEN_ENGINE_CREDIT;		

    // if(status != 2)
    // {
    //     display_credits();
    // }

    if(startup_done)
    {        
        screen_status &= ~IN_SCREEN_ENGINE_CREDIT;
        term_videomodes();
    }

    if(!disablelog)
    {
        printf("Release level data");
    }
    if (startup_done)
    {
        unload_levelorder();
    }
    if(!disablelog)
    {
        printf("...........\n");
    }
    if(startup_done)
    {
        unload_level();
    }
    if(!disablelog)
    {
        printf("Done!\n\n");
    }

    if(!disablelog)
    {
        printf("Release graphics data");
    }
    if(!disablelog)
    {
        printf("..");
    }
    if(startup_done)
    {
        freescreen(&vscreen);    // allocated by init_videomodes
    }
    if(startup_done && pixelformat == PIXEL_x8) for(i = 0; i < MAX_BLENDINGS; i++)
        {
            free(blendtables[i]);
        }
    if(!disablelog)
    {
        printf("..");
    }
    if(startup_done)
    {
        freescreen(&background);
    }
    if(!disablelog)
    {
        printf("..");
    }
#ifdef CACHE_BACKGROUNDS
    if(startup_done) for(i = 0; i < MAX_CACHED_BACKGROUNDS; i++)
        {
            freescreen(&bg_cache[i]);
        }
    if(!disablelog)
    {
        printf("..");
    }
#endif
    if(startup_done)
    {
        freesprites();
    }
    if(!disablelog)
    {
        printf("..");
    }
    if(startup_done)
    {
        unload_all_fonts();
    }
    if(!disablelog)
    {
        printf("\tDone!\n");
    }


    if(!disablelog)
    {
        printf("Release game data............\n\n");
    }

    if(startup_done)
    {
        free_ents();
    }
    if(startup_done)
    {
        free_models();
    }
    if(startup_done)
    {
        free_modelcache();
    }
    if(startup_done)
    {
        clear_scripts();
    }
    if(!disablelog)
    {
        printf("\nRelease game data............\tDone!\n");
    }

    if(!disablelog)
    {
        printf("Release timer................");
    }
    if(startup_done)
    {
        borTimerExit();
    }
    if(!disablelog)
    {
        printf("\tDone!\n");
    }

    if(!disablelog)
    {
        printf("Release input hardware.......");
    }
    if(startup_done)
    {
        control_exit();
    }
    if(!disablelog)
    {
        printf("\tDone!\n");
    }

    if(!disablelog)
    {
        printf("Release sound system.........");
    }
    if(startup_done)
    {
        sound_exit();
    }
    if(!disablelog)
    {
        printf("\tDone!\n");
    }

    if(!disablelog)
    {
        printf("Release FileCaching System...");
    }
    if(startup_done)
    {
        pak_term();
    }
    if(!disablelog)
    {
        printf("\tDone!\n");
    }

    if(modelcmdlist)
    {
        freeCommandList(modelcmdlist);    // moved here because list is not initialized if shutdown is initiated from inside the menu
    }
    if(modelstxtcmdlist)
    {
        freeCommandList(modelstxtcmdlist);
    }
    if(levelcmdlist)
    {
        freeCommandList(levelcmdlist);
    }
    if(levelordercmdlist)
    {
        freeCommandList(levelordercmdlist);
    }

    freeModelList();
    if(savelevel)
    {
        free(savelevel);
    }
    freefilenamecache();
    ob_termtrans();

    // free input recorder
    free_input_recorder();

    if(!disablelog)
    {
        printf("\n**************** Done *****************\n\n");
    }

    if(!disablelog)
    {
        printf("%s", buf);
    }

    shuttingdown = 0;
    borExit(status);
}


#if DC
void guistartup()
{
    int i;

    if(!font_load(0, "menu/font1", packfile, 0))
    {
        borShutdown(1, "Unable to load font #1!\n");
    }
    if(!font_load(1, "menu/font2", packfile, 0))
    {
        borShutdown(1, "Unable to load font #2!\n");
    }
    if(!font_load(2, "menu/font3", packfile, 0))
    {
        borShutdown(1, "Unable to load font #3!\n");
    }


    borTimerInit();

    control_init(2);
    apply_controls();

    init_videomodes(0);
    if(!video_set_mode(videomodes))
    {
        borShutdown(1, "Unable to set video mode: %d x %d!\n", videomodes.hRes, videomodes.vRes);
    }

    for(i = 0; i < 256; i++)
    {
        neontable[i] = i;
    }
}
#endif

void startup()
{
    int i;

    printf("FileCaching System Init.......\t");
    if(pak_init())
    {
        printf("Enabled\n");
    }
    else
    {
        printf("Disabled\n");
    }

    loadHighScoreFile();
    clearSavedGame();

    init_videomodes(1);
    if(!video_set_mode(videomodes))
    {
        borShutdown(1, "Unable to set video mode: %d x %d!\n", videomodes.hRes, videomodes.vRes);
    }

    printf("Timer init...................\t");
    borTimerInit();
    printf("Done!\n");

    printf("Initialize Sound.............\t");
    if(sound_init(12))
    {
        if(load_special_sounds())
        {
            printf("Done!\n");
        }
        else
        {
            printf("\n");
        }
        if(!sound_start_playback())
        {
            printf("Warning: can't play sound!\n");
        }
        SB_setvolume(SB_MASTERVOL, savedata.soundvol);
        SB_setvolume(SB_VOICEVOL, savedata.soundvol);
    }
    else
    {
        borShutdown(1, "Unable to Initialize Sound.\n");
    }

    // init. input recorder
    init_input_recorder();

    printf("Loading sprites..............\t");
    load_special_sprites();
    printf("Done!\n");

    printf("Loading level order..........\t");
    load_levelorder();
    printf("Done!\n");

    printf("Loading model constants......\t");
    load_model_constants();
    printf("Done!\n");

    printf("Loading script settings......\t");
    load_script_setting();
    printf("Done!\n");

    printf("Loading scripts..............\t");
    load_scripts();
    printf("Done!\n");

    printf("Loading models...............\n\n");
    load_models();

    printf("Object engine init...........\t");
    if(!alloc_ents())
    {
        borShutdown(1, "Not enough memory for game objects!\n");
    }
    printf("Done!\n");

    printf("Loading menu.txt.............\t");
    load_menu_txt();
    printf("Done!\n");

    printf("Loading fonts................\t");
    load_all_fonts();
    printf("Done!\n");

    /*
        Kratus (10-2021) Moved the translation, menu and font functions to the end of the engine "startup" function,
        but before the "control init" function
    */
    printf("Loading translation..........\t");
    ob_inittrans();
    printf("Done!\n");

    printf("Input init...................\t");
    control_init(savedata.usejoy);
    apply_controls();
    printf("Done!\n");

#ifdef CACHE_BACKGROUNDS
    printf("Caching backgrounds..........\t");
    cache_all_backgrounds();
    printf("Done!\n");
#endif

    printf("Create blending tables.......\t");
    if(pixelformat == PIXEL_x8)
    {
        create_blend_tables_x8(blendtables);
    }

    for(i = 0; i < MAX_PAL_SIZE / 4; i++)
    {
        neontable[i] = i;
    }
    printf("Done!\n");

    if(savedata.logo++ > 10)
    {
        savedata.logo = 0;
    }

    printf("Save settings so far.........\t");
    savesettings();
    printf("Done!\n");

    startup_done = 1;

    printf("\n\n");

}



// ----------------------------------------------------------------------------


// Returns 0 on error, -1 on escape
int playgif(char *filename, int x, int y, int noskip)
{
    anigif_info *info = calloc(1, sizeof(*info));
    s_screen *backbuffer = NULL;
    s_screen *tempbg = background;
    int result = 1;
    u32 synctosound;
    u32 lasttime;
    u32 milliseconds;
    u32 temptime, tempnewtime; // temporary patch for ingame gif play

    synctosound = (sound_getinterval() != 0xFFFFFFFF);
    temptime = _time;
    tempnewtime = newtime;
    _time = 0;
    milliseconds = 0;
    lasttime = 0;
    background = NULL;

    if(!(result = anigif_open(filename, packfile, info)))
    {
        goto playgif_end;
    }

    while(!info->done)
    {
        if(milliseconds >= info->info[0].nextframe)
        {
            anigif_decode_frame(info);
        }
        if(!(backbuffer = anigif_getbuffer(info)))
        {
            break;
        }
        spriteq_add_screen(x, y, 0, backbuffer, NULL, 0);
        if(info->frame == 0)
        {
            vga_vwait();
            update(0, 0);
        }
        else
        {
            update(0, 1);
        }

        if(synctosound)
        {
            milliseconds += sound_getinterval();
        }
        else
        {
            milliseconds += (_time - lasttime) * 1000 / GAME_SPEED;
        }

        lasttime = _time;

        if(!noskip && (bothnewkeys & (FLAG_ESC | FLAG_ANYBUTTON)))
        {
            result = -1;
            break;
        }
    }


playgif_end:
    anigif_close(info);
    free(info);
    _time = temptime;
    newtime = tempnewtime;
    background = tempbg;
    standard_palette(1);

    if(0 == result)
    {
        printf("\nWarning, an error occurred while playing animated gif file '%s'.\n", filename);
    }
    return result;

}


#ifdef WEBM
// Returns 0 on error, -1 on escape
int playwebm(const char *path, int noskip)
{
    int retval = 1;
    webm_context *ctx = NULL;
    yuv_video_mode info;
    s_screen *rgb_frame = NULL;

    ctx = webm_start_playback(path, savedata.musicvol);
    if(ctx == NULL) {retval=0; goto quit;}

    // set video output to YUV mode
    webm_get_video_info(ctx, &info);
    int status = video_setup_yuv_overlay(&info);
    if(!status) {retval=0; goto quit;}

    // allocate s_screen for screenshot capture
    yuv_init(2);
    rgb_frame = allocscreen(info.width, info.height, PIXEL_16);
    if(!rgb_frame) {retval=0; goto quit;}

    u64 start_time = timer_uticks();
    u64 next_frame_time = 0;
    yuv_frame *frame = NULL;

    while(1)
    {
        inputrefresh(playrecstatus->status);
        if(!noskip && (bothnewkeys & (FLAG_ESC | FLAG_ANYBUTTON)))
        {
            retval = -1;
            yuv_frame_destroy(frame);
            break;
        }
        else if(frame && !noscreenshot && (bothnewkeys & FLAG_SCREENSHOT))
        {
            yuv_to_rgb(frame, rgb_frame);
            screenshot(rgb_frame, NULL, 0);
        }

        u64 time_passed = timer_uticks() - start_time;

        if(next_frame_time <= time_passed)
        {
            // display the current frame
            if(frame)
            {
                video_display_yuv_frame();
                yuv_frame_destroy(frame);
            }

            // prepare the next frame for display
            frame = webm_get_next_frame(ctx);
            if(frame == NULL) break;
            video_prepare_yuv_frame(frame);
            next_frame_time = frame->timestamp / 1000;
        }
        else usleep(next_frame_time - time_passed);
    }

quit:
    if(ctx) webm_close(ctx);
    if(rgb_frame) freescreen(&rgb_frame);
    yuv_clear();
    video_set_mode(videomodes);
    return retval;
}
#endif



void playscene(char *filename)
{
    char *buf;
    size_t size;
    int pos;
    char *command = NULL;
    char videofile[MAX_BUFFER_LEN];
    int x = 0, y = 0, skipone = 0, noskip = 0, i;
    int closing = 0, status;

    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";

    // Read file
    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        return;
    }

    currentScene = filename;

    // Now interpret the contents of buf line by line
    pos = 0;
    while(buf[pos])
    {
        ParseArgs(&arglist, buf + pos, argbuf);
        command = GET_ARG(0);
        if(command[0])
        {
            if(!closing && stricmp(command, "music") == 0)
            {
                music(GET_ARG(1), GET_INT_ARG(2), atol(GET_ARG(3)));
            }
            else if(!closing && stricmp(command, "animation") == 0)
            {
                strcpy(videofile, GET_ARG(1));
                x = GET_INT_ARG(2);
                y = GET_INT_ARG(3);
                skipone = GET_INT_ARG(4);
                noskip = GET_INT_ARG(5);
                status = playgif(videofile, x, y, noskip);
                if(status == -1 && !skipone)
                {
                    closing = 1;
                }
            }
            else if(!closing && stricmp(command, "video") == 0)
            {
#ifdef WEBM
                strcpy(videofile, GET_ARG(1));
                skipone = GET_INT_ARG(2);
                noskip = GET_INT_ARG(3);
                status = playwebm(videofile, noskip);
                if(status == -1 && !skipone)
                {
                    closing = 1;
                }
                else if(status == 0)
                {
                    printf("An error occurred when trying to play the video %s\n", videofile);
                }
#else
                printf("Skipping video %s; WebM playback not supported on this platform\n");
#endif
            }
            else if(stricmp(command, "silence") == 0)
            {
                sound_close_music();
            }
        }
        // Go to next non-blank line
        pos += getNewLineStart(buf + pos);
    }
    if(buf != NULL)
    {
        free(buf);
        buf = NULL;
    }
    currentScene = NULL;
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        player[i].disablekeys = player[i].newkeys = player[i].playkeys = 0;
    }
}




// ----------------------------------------------------------------------------




void gameover()
{
    int done = 0;
    char tmpBuff[MAX_BUFFER_LEN] = {""};

    music("data/music/gameover", 0, 0);

    _time = 0;
    screen_status |= IN_SCREEN_GAME_OVER;

    if(custScenes != NULL)
    {
        strcpy(tmpBuff, custScenes);
        strcat(tmpBuff, "gameover.txt");
        if(testpackfile(tmpBuff, packfile) >= 0)
        {
            playscene(tmpBuff);
            done = 1;
        }
    }
    else
    {
        if(testpackfile("data/scenes/gameover.txt", packfile) >= 0)
        {
            playscene("data/scenes/gameover.txt");
            done = 1;
        }

    }

    while(!done)
    {
        font_printf(_strmidx(3, Tr("GAME OVER")), 110 + videomodes.vShift, 3, 0, Tr("GAME OVER"));
        done |= (_time > GAME_SPEED * 8 && !sound_query_music(NULL, NULL));
        done |= (bothnewkeys & (FLAG_ESC | FLAG_ANYBUTTON));
        update(0, 0);
    }

    screen_status &= ~IN_SCREEN_GAME_OVER;
}




void hallfame(int addtoscore)
{
    int done = 0;
    int topten[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    u32 score;
    char name[MAX_NAME_LEN + 1];
    int i, p, y;
    char tmpBuff[MAX_BUFFER_LEN] = {""};
    int col1 = -8;
    int col2 = 6;

    screen_status |= IN_SCREEN_HALL_OF_FAME;

    if(hiscorebg)
    {
        // New alternative background path for PSP
        if(custBkgrds != NULL)
        {
            strcpy(tmpBuff, custBkgrds);
            strcat(tmpBuff, "hiscore");
            load_background(tmpBuff);
        }
        else
        {
            load_cached_background("data/bgs/hiscore");
        }
    }

    if(addtoscore)
    {
        for(p = 0; p < levelsets[current_set].maxplayers; p++)
        {
            if(player[p].score > savescore.highsc[9])
            {
                savescore.highsc[9] = player[p].score;
                strcpy(savescore.hscoren[9], player[p].name);
                topten[9] = 1;

                for(i = 8; i >= 0 && player[p].score > savescore.highsc[i]; i--)
                {
                    score = savescore.highsc[i];
                    strcpy(name, savescore.hscoren[i]);
                    savescore.highsc[i] = player[p].score;
                    strcpy(savescore.hscoren[i], player[p].name);
                    topten[i] = 1;
                    savescore.highsc[i + 1] = score;
                    strcpy(savescore.hscoren[i + 1], name);
                    topten[i + 1] = 0;
                }
            }
        }
        saveHighScoreFile();
    }

    _time = 0;

    while(!done)
    {
        y = 56;
        if(!hiscorebg)
        {
            font_printf(_strmidx(3, Tr("Hall Of Fame")), y - fontheight(3) - 10 + videomodes.vShift, 3, 0, Tr("Hall Of Fame"));
        }

        for(i = 0; i < 10; i++)
        {
            font_printf(_colx(topten[i], col1), y + videomodes.vShift, topten[i], 0, "%2i.  %s", i + 1, savescore.hscoren[i]);
            font_printf(_colx(topten[i], col2), y + videomodes.vShift, topten[i], 0, (scoreformat ? "%09lu" : "%u"), savescore.highsc[i]);
            y += (videomodes.vRes - videomodes.vShift - 56 - 32) / 10; //font_heights[topten[i]] + 6;
        }

        // Kratus (01-2023) Added the "FLAG_ANYBUTTON" to exit the Hall of Fame screen
        update(0, 0);
        done |= (_time > GAME_SPEED * 8);
        done |= (bothnewkeys & (FLAG_START | FLAG_ANYBUTTON | FLAG_ESC));
    }
    unload_background();
    screen_status &= ~IN_SCREEN_HALL_OF_FAME;
}




// Level completed, show bonus stuff
void showcomplete(int num)
{
    int done = 0;
    int i, j, k;
    u32 clearbonus[4] = { 10000, 10000, 10000, 10000 };
    u32 lifebonus[4] = { 10000, 10000, 10000, 10000 };
    u32 rushbonus[4] = { 10000, 10000, 10000, 10000 };
    u32 nexttime = 0;
    u32 finishtime = 0;
    int chan = 0;
    char tmpBuff[MAX_BUFFER_LEN] = {""};

    screen_status |= IN_SCREEN_SHOW_COMPLETE;

    if(completebg)
    {
        // New alternative background path for PSP
        if(custBkgrds != NULL)
        {
            strcpy(tmpBuff, custBkgrds);
            strcat(tmpBuff, "complete");
            load_background(tmpBuff);
        }
        else
        {
            load_cached_background("data/bgs/complete");
        }
    }

    music("data/music/complete", 0, 0);

    for(i = 0; i < levelsets[current_set].maxplayers; i++)
    {
        if(rush[0] >= 1 && showrushbonus == 1)
        {
            rushbonus[i] = nomaxrushreset[i] * scbonuses[2];
        }
        if(scbonuses[3] == 1)
        {
            clearbonus[i] = num * scbonuses[0];
        }
        else
        {
            clearbonus[i] = scbonuses[0];
        }
        lifebonus[i] = player[i].lives * scbonuses[1];
    }

    update(0, 0);

    _time = 0;
    while(!done)
    {
        if(!scomplete[5])
        {
            font_printf(videomodes.hShift + scomplete[0], videomodes.vShift + scomplete[1], 3, 0, Tr("Stage %i Complete!"), num);
        }
        else
        {
            font_printf(videomodes.hShift + scomplete[0], videomodes.vShift + scomplete[1], 3, 0, Tr("Stage"));
            font_printf(videomodes.hShift + scomplete[2], videomodes.vShift + scomplete[3], 3, 0, "%i", num);
            font_printf(videomodes.hShift + scomplete[4], videomodes.vShift + scomplete[5], 3, 0, Tr("Complete"));
        }

        font_printf(videomodes.hShift + cbonus[0], videomodes.vShift + cbonus[1], 0, 0, Tr("Clear Bonus"));
        for(i = 0, j = 2, k = 3; i < levelsets[current_set].maxplayers; i++, j = j + 2, k = k + 2) if(player[i].lives > 0)
            {
                font_printf(videomodes.hShift + cbonus[j], videomodes.vShift + cbonus[k], 0, 0, (scoreformat ? "%09lu" : "%lu"), clearbonus[i]);
            }
        font_printf(videomodes.hShift + lbonus[0], videomodes.vShift + lbonus[1], 0, 0, Tr("Life bonus"));
        for(i = 0, j = 2, k = 3; i < levelsets[current_set].maxplayers; i++, j = j + 2, k = k + 2) if(player[i].lives > 0)
            {
                font_printf(videomodes.hShift + lbonus[j], videomodes.vShift + lbonus[k], 0, 0, (scoreformat ? "%09lu" : "%lu"), lifebonus[i]);
            }
        if(rush[0] >= 1 && showrushbonus == 1)
        {
            font_printf(videomodes.hShift + rbonus[0], videomodes.vShift + rbonus[1], 0, 0, Tr("Rush Bonus"));
            for(i = 0, j = 2, k = 3; i < levelsets[current_set].maxplayers; i++, j = j + 2, k = k + 2) if(player[i].lives > 0)
                {
                    font_printf(videomodes.hShift + rbonus[j], videomodes.vShift + rbonus[k], 0, 0, (scoreformat ? "%09lu" : "%lu"), rushbonus[i]);
                }
        }
        font_printf(videomodes.hShift + tscore[0], videomodes.vShift + tscore[1], 0, 0, Tr("Total Score"));
        for(i = 0, j = 2, k = 3; i < levelsets[current_set].maxplayers; i++, j = j + 2, k = k + 2) if(player[i].lives > 0)
            {
                font_printf(videomodes.hShift + tscore[j], videomodes.vShift + tscore[k], 0, 0, (scoreformat ? "%09lu" : "%lu"), player[i].score);
            }

        while(_time > nexttime)
        {
            if(!finishtime)
            {
                finishtime = _time + 4 * GAME_SPEED;
            }

            for(i = 0; i < levelsets[current_set].maxplayers; 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;
                    }
                    else if(rush[0] >= 1 && showrushbonus == 1 && (rushbonus[i] > 0))
                    {
                        addscore(i, 10);
                        rushbonus[i] -= 10;
                        finishtime = 0;
                    }
                }
            }

            if(!finishtime && !(nexttime & 15))
            {
                sound_stop_sample(chan);
                chan = sound_play_sample(global_sample_list.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 < levelsets[current_set].maxplayers; i++)
    {
        if(player[i].lives > 0)
        {
            if(rush[0] >= 1 && showrushbonus == 1)
            {
                addscore(i, rushbonus[i]);
            }
            addscore(i, clearbonus[i]);
            addscore(i, lifebonus[i]);
        }
    }
    unload_background();

    screen_status &= ~IN_SCREEN_SHOW_COMPLETE;
}

void savelevelinfo()
{
    int i;
    s_set_entry *set = levelsets + current_set;
    s_savelevel *save = savelevel + current_set;

    save->flag = set->saveflag;
    // don't check flag here save all info, for simple logic
    for(i = 0; i < set->maxplayers; i++)
    {
        save->pLives[i] = player[i].lives;
        save->pCredits[i] = player[i].credits;
        save->pScores[i] = player[i].score;
        save->pSpawnhealth[i] = player[i].spawnhealth;
        save->pSpawnmp[i] = player[i].spawnmp;
        save->pWeapnum[i] = player[i].weapnum;
        save->pColourmap[i] = player[i].colourmap;
        strncpy(save->pName[i], player[i].name, MAX_NAME_LEN);
    }
    save->credits = credits;
    save->level = current_level;
    save->stage = current_stage;
    save->which_set = current_set;
    strncpy(save->dName, set->name, MAX_NAME_LEN - 1);
    for(i = 0; i < sizeof(allowselect_args); i++) save->allowSelectArgs[i] = '\0'; // clear
    for(i = 0; i < sizeof(allowselect_args); i++) save->allowSelectArgs[i] = allowselect_args[i];
}

void tryvictorypose(entity *ent)
{
    if( ent &&
       ent->inpain & ~IN_PAIN_NONE &&
       !ent->falling &&
       !(ent->death_state & DEATH_STATE_DEAD) &&
       !ent->rising &&
       (ent->idling & IDLING_ACTIVE) &&
       ent->position.y <= ent->base )
    {
        ent->takeaction = NULL;
        ent->velocity.x = ent->velocity.z = 0;
        ent->idling = IDLING_NONE;
        ent_set_anim(ent, ANI_VICTORY, 0);
    }
}

static void check_victory_pose()
{
    if ( (endgame & 1) && level_completed_defeating_boss )
    {
        int i;
        for(i = 0; i < MAX_PLAYERS; i++)
        {
            entity* tmp = self;
            self = player[i].ent;
            if(self && validanim(self, ANI_VICTORY) && self->animnum != ANI_VICTORY)
            {
                tryvictorypose(self);
                endgame = 0;
            }
            else if (self && self->animnum == ANI_VICTORY && self->animating)
            {
                endgame = 0;
            }

            self = tmp;
        }
    }
}

int playlevel(char *filename)
{
    int i, type, p_alive = 0;

    kill_all();

    savelevelinfo(); // just in case we lose them after level is freed

    load_level(filename);

    if(!nosave)
    {
        saveGameFile();
        saveHighScoreFile();
        saveScriptFile();
    }
    nosave = 0;

    _time = 0;
    nextplan = 0;
    stalker = NULL;
    firstplayer = NULL;
    type = level->type;

    // Fixes the start level executing last button bug
    for(i = 0; i < levelsets[current_set].maxplayers; i++)
    {
        if(player[i].lives > 0)
        {
            player[i].disablekeys = player[i].newkeys = player[i].playkeys = 0;
            player[i].weapnum = level->setweap;
            player[i].joining = 0;
            player[i].hasplayed = 1;
            spawnplayer(i);
            player[i].ent->rush.max = 0;
        }
    }

    //execute a script when level started
    if(Script_IsInitialized(&level_script))
    {
        Script_Execute(&level_script);
    }
    if(Script_IsInitialized(&(level->level_script)))
    {
        Script_Execute(&(level->level_script));
    }

    while(!endgame)
    {
        update(1, 0);

        if (level->force_finishlevel)
        {
            level_completed = 1;
            endgame |= 1;
            level->force_finishlevel = 0;
        }
        if (level->force_gameover)
        {
            //entity* temp = self;
            for(i = 0; i < MAX_PLAYERS; i++) //levelsets[current_set].maxplayers
            {
                player[i].lives = 0;
                if(noshare)
                {
                    player[i].credits = 0;
                }
                else
                {
                    credits = 0;
                }
                if (player[i].ent)
				{
					kill_entity(player[i].ent, KILL_ENTITY_TRIGGER_LEVEL_GAME_OVER);
					player[i].ent = NULL;
				}
                //self = player[i].ent;
                //player_die();
            }
            //self = temp;
            //kill_all();
            endgame |= 1;
            level->force_gameover = 0;
        }
        if(level_completed)
        {
            endgame |= (!findent(TYPE_ENEMY) || level->type || findent(TYPE_ENDLEVEL));    // Ends when all enemies die or a bonus level
            check_victory_pose();
        }
    }
    //execute a script when level finished
    if(Script_IsInitialized(&endlevel_script))
    {
        Script_Execute(&endlevel_script);
    }
    if(Script_IsInitialized(&(level->endlevel_script)))
    {
        Script_Execute(&(level->endlevel_script));
    }
    if(!nofadeout)
    {
        fade_out(0, 0);
    }

    for(i = 0; i < levelsets[current_set].maxplayers; i++)
    {
        if(player[i].ent)
        {
            nomaxrushreset[i] = player[i].ent->rush.max;
            player[i].spawnhealth = player[i].ent->energy_state.health_current;
            player[i].spawnmp = player[i].ent->energy_state.mp_current;
        }
        // reset
        player[i].weapnum = 0;
    }

    if(!musicoverlap)
    {
        sound_close_music();
    }
    sound_stopall_sample();

    unload_level();

    // Are any players alive?
	for(i = 0; i < MAX_PLAYERS; i++)
    {
        if (player[i].lives > 0)
        {
            p_alive = 1;
            break;
        }
    }

    return ((type == 2 && endgame != 2) || p_alive);
}

// Caskey, Damon V (retool, OA unknown)
// 2019-01-03
//
// For select screen. Spawn sample entity for player_index.
static entity *spawnexample(int player_index)
{
	#define SPAWN_MODEL_NAME	NULL
	#define SPAWN_MODEL_INDEX	MODEL_INDEX_NONE

    entity	*example;
	s_model *model;
	s_set_entry *set;

	float pos_x;
	float pos_y;
	float pos_z;
	int direction;
		
	set = levelsets + current_set;

	// Get spawn attributes and spawn entity.
	pos_x = (float)psmenu[player_index][0];
	pos_y = 0;
	pos_z = (float)psmenu[player_index][1];
	direction = spdirection[player_index];

	// Next selectable model in cycle. We'll use this
	// to decide what we spawn.
	model = nextplayermodeln(NULL, player_index);

	example = spawn(pos_x, pos_z, pos_y, direction, SPAWN_MODEL_NAME, SPAWN_MODEL_INDEX, model);
    
	// Copy model's name to player property.
	strcpy(player[player_index].name, model->name);

	// If color selection is allowed and we want
	// players with same models to use different
	// maps, then use next map in cycle. Otherwise
	// just go with default map.
	if (colourselect && (set->nosame & 2))
	{
		player[player_index].colourmap = nextcolourmapn(model, -1, player_index);
	}
	else
	{
		player[player_index].colourmap = 0;
	}

	// Apply map to spawned entity.
    ent_set_colourmap(example, player[player_index].colourmap);
    
	// So the entity knows how it came to be.
	example->spawntype = SPAWN_TYPE_PLAYER_SELECT;
    
	return example;

	#undef SPAWN_MODEL_NAME
	#undef SPAWN_MODEL_INDEX
}

// load saved select screen
static void load_select_screen_info(s_savelevel *save)
{
    int i = 0;
    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";
    char *filename = ""; // not used, just fused into GET_INT_ARG()
    char *command = ""; // not used, just fused into GET_INT_ARG()

    // load model_loads
    for(i = 0; i < save->selectLoadCount; i++)
    {
        s_model *tempmodel;
        command = GET_ARG(0);

        if(!command || !command[0]) continue;
        ParseArgs(&arglist, save->selectLoad[i], argbuf);

        tempmodel = findmodel(GET_ARG(1));
        if (tempmodel)
        {
            update_model_loadflag(tempmodel, GET_INT_ARG(2));
        }
    }

    ParseArgs(&arglist, save->selectMusic, argbuf);
    command = GET_ARG(0);
    if(command && command[0]) music(GET_ARG(1), GET_INT_ARG(2), atol(GET_ARG(3)));

    ParseArgs(&arglist, save->selectBackground, argbuf);
    command = GET_ARG(0);
    if(command && command[0]) load_background(GET_ARG(1));

    return;
}

int selectplayer(int *players, char *filename, int useSavedGame)
{
	s_model *tempmodel;
	s_model *model_old = NULL;
	s_model *model_new = NULL;
	int i;
	int exit = 0;
	int escape = 0;
	int defaultselect = 0;
	unsigned exitdelay = 0;
	int players_busy = 0;
	int players_ready = 0;
	char string[MAX_BUFFER_LEN] = { "" };
	char *buf, *command;
	size_t size = 0;
	ptrdiff_t pos = 0;
	ArgList arglist;
	char argbuf[MAX_ARG_LEN + 1] = "";
	s_set_entry *set = levelsets + current_set;
	s_savelevel *save = savelevel + current_set;
	int load_count = 0;
	int saved_select_screen = 0;
	int is_first_select = 1;

	savelevelinfo();

    screen_status |= IN_SCREEN_SELECT;

    kill_all();

	// Initialize player sized arrays.
	entity *example[set->maxplayers];
	int ready[set->maxplayers];

	// Initialize 
	for (i = 0; i < set->maxplayers; i++)
	{
		example[i] = NULL;
		ready[i] = 0;
	}

	// Allow select? 'a' is the first char of allowselect,
	// if there's 'a' then there is allowselect.
	if (allowselect_args[0] != 'a'
		&& allowselect_args[0] != 'A')
	{
		reset_playable_list(1);
	}

	// Reset memory for player array.
	memset(player, 0, sizeof(*player) * MAX_PLAYERS);

	// Load game selected and a save game available?
	if (useSavedGame && save)
	{
		if (save->selectFlag)
		{
			load_select_screen_info(save);
			load_playable_list(save->allowSelectArgs);
			saved_select_screen = 1;
		}
	}

	// Mark "hasplayed" for all players.
	for (i = 0; i < set->maxplayers; i++)
	{
		player[i].hasplayed = players[i];
	}

	for (i = 0; i < set->maxplayers; i++)
	{
		if (savelevel[current_set].pLives[i] > 0)
		{
			is_first_select = 0;
			break;
		}
	}

	// Load and apply selection text file.
	if (filename && filename[0])
	{
		if (buffer_pakfile(filename, &buf, &size) != 1)
		{
			borShutdown(1, "Failed to load player select file '%s'", filename);
		}
		while (pos < size)
		{
			ParseArgs(&arglist, buf + pos, argbuf);
			command = GET_ARG(0);
			if (command && command[0])
			{
				if (stricmp(command, "music") == 0)
				{
					music(GET_ARG(1), GET_INT_ARG(2), atol(GET_ARG(3)));
					// SAVE
					multistrcatsp(save->selectMusic, command, GET_ARG(1), GET_ARG(2), GET_ARG(3), NULL);
				}
				else if (stricmp(command, "allowselect") == 0)
				{
					load_playable_list(buf + pos);
					memcpy(&save->allowSelectArgs, &allowselect_args, sizeof(allowselect_args)); // SAVE
				}
				else if (stricmp(command, "background") == 0)
				{
					load_background(GET_ARG(1));
					// SAVE
					multistrcatsp(save->selectBackground, command, GET_ARG(1), NULL);
				}
				else if (stricmp(command, "load") == 0)
				{
					tempmodel = findmodel(GET_ARG(1));
					if (!tempmodel)
					{
						load_cached_model(GET_ARG(1), filename, GET_INT_ARG(2));
					}
					else
					{
						update_model_loadflag(tempmodel, GET_INT_ARG(2));
					}
					// SAVE
					if (load_count < MAX_SELECT_LOADS)
					{
						multistrcatsp(save->selectLoad[load_count], command, GET_ARG(1), GET_ARG(2), NULL);
						load_count++;
					}
				}
				else if (command && command[0])
				{
					printf("Command '%s' is not understood in file '%s'\n", command, filename);
				}
			}

			pos += getNewLineStart(buf + pos);
		}
		save->selectLoadCount = load_count; // SAVE number of LOAD command
		save->selectFlag = 1;

		if (buf != NULL)
		{
			free(buf);
			buf = NULL;
		}
	}
	else // without select.txt
	{
		if (is_first_select || (!skipselect[0][0] && !set->noselect)) // no select is skipselect without names
		{
			defaultselect = 1; // normal select or skipselect/noselect? 1 == normal select
		}

		if (!noshare)
		{
			credits = CONTINUES;
		}
		else for (i = 0; i < set->maxplayers; i++)
		{
			if (players[i])
			{
				player[i].credits = CONTINUES;
			}
		}

		if (skipselect[0][0] || set->noselect)
		{
			for (i = 0; i < set->maxplayers; i++)
			{
				if (!players[i])
				{
					continue;
				}
				strncpy(player[i].name, skipselect[i], MAX_NAME_LEN);

				if (defaultselect)
				{
					player[i].lives = PLAYER_LIVES;
					if (!(global_config.cheats & CHEAT_OPTIONS_CREDITS_ACTIVE))
					{
						if (noshare)
						{
							--player[i].credits;
						}
						else
						{
							--credits;
						}
					}
				}
				else
				{
					player[i].lives = savelevel[current_set].pLives[i];
					player[i].score = savelevel[current_set].pScores[i];
					if (noshare) player[i].credits = savelevel[current_set].pCredits[i];
					else credits = savelevel[current_set].credits;
				}
			}

			screen_status &= ~IN_SCREEN_SELECT;

			return 1;
		}

		if (!saved_select_screen)
		{
			if (unlockbg && bonus)
			{
				// New alternative background path for PSP
				if (custBkgrds != NULL)
				{
					strcpy(string, custBkgrds);
					strcat(string, "unlockbg");
					load_background(string);
				}
				else
				{
					load_cached_background("data/bgs/unlockbg");
				}
			}
			else
			{
				// New alternative background path for PSP
				if (custBkgrds != NULL)
				{
					strcpy(string, custBkgrds);
					strcat(string, "select");
					load_background(string);
				}
				else
				{
					load_cached_background("data/bgs/select");
				}
			}
			if (!music("data/music/menu", 1, 0))
			{
				music("data/music/remix", 1, 0);
			}
		}
	}

	for (i = 0; i < set->maxplayers; i++)
	{
		if (players[i])
		{
			example[i] = spawnexample(i);
			player[i].playkeys = 0;
			if (defaultselect)
			{
				player[i].lives = PLAYER_LIVES;
				if (!(global_config.cheats & CHEAT_OPTIONS_CREDITS_ACTIVE))
				{
					if (noshare)
					{
						--player[i].credits;
					}
					else
					{
						--credits;
					}
				}
			}
			else
			{
				player[i].lives = savelevel[current_set].pLives[i];
				player[i].score = savelevel[current_set].pScores[i];
				if (noshare) player[i].credits = savelevel[current_set].pCredits[i];
				else credits = savelevel[current_set].credits;
			}
		}
	}

	_time = 0;

	// Stay in selection until escape or exit.
	// 
	// exit = all players ready (selected) and exit delay expired.
	// escape = Escape key pressed.
	while (!(exit || escape))
	{
		players_busy = 0;
		players_ready = 0;

		// Loop through players.
		for (i = 0; i < set->maxplayers; i++)
		{
			// Current player index not yet selected?
			if (!ready[i])
			{
				
				// This is where we present player selections. The logic is long
				// and a little messy, so buckle up! Basically, we want to spawn
				// an example entity to get started, and that example entity
				// is what player sees on the selection screen. Then we switch
				// its model/color/animation based on the situation and player
				// input.
				//
				// 1. If an example entity exists and is playing a transition 
				//  then we...
				//
				// a) do nothing if the animation isn't finished.
				//
				// b) If it IS finished...
				//
				// -- 1. If the animation is ANI_SELECTIN, then play ANI_SELECT.
				// 
				// -- 2. If aniamton is ANI_SELECTOUT, then we switch to new 
				// model (if available).
				//
				// 2. If player hasn't played yet, has some credits or 
				// can draw from credit pool and pressed any action button,
				// then we'll deal with their credit pool and spawn the
				// first example (selectable model preview). Having an 
				// example spawned also tells us the player has completed 
				// this step, and so it's OK to run actions from any of 
				// the others.
				//
				// 3. If the player pressed Left or Right instead and there's
				// an example spawned, then we find the previous/next 
				// character in line, and record it to a variable. Then we
				// see if example has ANI_SELECTIN. if it doesn't we switch
				// to new model. If it does, play ANI_SELECT.
				//
				// 4. If the player presses Up or Down, we have an example 
				// spawned and colourselect is enabled, then cycle to the
				// model's previous/next color set choice.
				//
				// 5. If the player presses any action button and we have
				// an example spawned, then we mark the player's ready delay 
				// flag, and stalltime. See the parent logic block for 
				// selection delay & exit details. This is the player
				// making their selection choice.

				// Example exists and select transition animation?
				if (example[i] 
					&& (example[i]->animnum == ANI_SELECTIN || example[i]->animnum == ANI_SELECTOUT))
				{
					// If still animating than do nothing. Let the transition finish.
					if (example[i]->animating)
					{
					}
					else
					{
						// Transition to select animation.
						if (example[i]->animnum == ANI_SELECTIN)
						{
							ent_set_anim(example[i], ANI_SELECT, 0);
						}

						// Transition from select (player selected another model, and the
						// select out transition is now finished). Repeat of left/right key 
						// logic below and probably needs consolidation.
						if (example[i]->animnum == ANI_SELECTOUT && model_new)
						{
							// Apply new model.
							ent_set_model(example[i], model_new->name, 0);

							// Copy example model name to player name variable.
							strcpy(player[i].name, example[i]->model->name);

							// If colorselect is enabled and nosame 2 is enabled, skip to
							// start at next avaialble color cycle. Otherwise just start 
							// with default color set (0).
							if (colourselect && (set->nosame & 2))
							{
								player[i].colourmap = nextcolourmapn(example[i]->model, -1, i);
							}
							else
							{
								player[i].colourmap = 0;
							}

							//  Apply color set.
							ent_set_colourmap(example[i], player[i].colourmap);
						}
					}
				}
				else if (!player[i].hasplayed
					&& (noshare || credits > 0)
					&& (player[i].newkeys & FLAG_ANYBUTTON))
				{

					//  Now this player has played.
					players[i] = player[i].hasplayed = 1;
					//printf("%d %d %d\n", i, player[i].lives, immediate[i]);

					// Noshare means each player has their own credit pool.
					if (noshare)
					{
						player[i].credits = CONTINUES;
					}

					// Credits cheat = infinite credits. If that's not enabled,
					// then deduct a credit.
					if (!(global_config.cheats & CHEAT_OPTIONS_CREDITS_ACTIVE))
					{
						if (noshare)
						{
							--player[i].credits;
						}
						else
						{
							--credits;
						}
					}

					// Give player default number of lives, spawn
					// example model and cancel the key flag.
					player[i].lives = PLAYER_LIVES;
					example[i] = spawnexample(i);
					player[i].playkeys = 0;

					// Play sound effect.
					if (global_sample_list.beep >= 0)
					{
						sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
					}
				}
				else if ((player[i].newkeys & FLAG_ANYBUTTON) && example[i]) //Kratus (01-05-21) Moved the "anybutton" code to before of the "left/right" code to fix a bug that makes no character chosen when both are pressed together
				{
					if (global_sample_list.beep_2 >= 0)
					{
						sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
					}
					// yay you picked me!
					if (validanim(example[i], ANI_PICK))
					{
						ent_set_anim(example[i], ANI_PICK, 0);
					}
					example[i]->stalltime = _time + GAME_SPEED * 2;
					ready[i] = 1;
				}
				else if (player[i].newkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT) && example[i])
				{
					// Give player a feedback sound.
					if (global_sample_list.beep >= 0)
					{
						sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
					}

					// Get model in use right now.
					model_old = example[i]->model;

					// Let's get the new model. Left key = previous model 
					// in cycle. Right key = next.
					if ((player[i].newkeys & FLAG_MOVELEFT))
					{
						model_new = prevplayermodeln(model_old, i);
					}
					else
					{
						model_new = nextplayermodeln(model_old, i);
					}

					// Do we have a select out transition? If so play it here. 
					// Otherwise switch to new model. 
					if (validanim(example[i], ANI_SELECTOUT))
					{						
						ent_set_anim(example[i], ANI_SELECTOUT, 0);				
					}
					else
					{
						// Apply new model.
						ent_set_model(example[i], model_new->name, 0);

						// Copy example model name to player name variable.
						strcpy(player[i].name, example[i]->model->name);

						// If colorselect is enabled and nosame 2 is enabled, skip to
						// start at next avaialble color cycle. Otherwise just start 
						// with default color set (0).
						if (colourselect && (set->nosame & 2))
						{
							player[i].colourmap = nextcolourmapn(example[i]->model, -1, i);
						}
						else
						{
							player[i].colourmap = 0;
						}

						//  Apply color set.
						ent_set_colourmap(example[i], player[i].colourmap);
					}					
				}
				else if (player[i].newkeys & (FLAG_MOVEUP | FLAG_MOVEDOWN) && colourselect && example[i])
				{
					player[i].colourmap = ((player[i].newkeys & FLAG_MOVEUP) ? nextcolourmapn : prevcolourmapn)(example[i]->model, player[i].colourmap, i);
					ent_set_colourmap(example[i], player[i].colourmap);
				}
			}
			else if (ready[i] == 1)
			{
				if (((!validanim(example[i], ANI_PICK) || example[i]->modeldata.animation[ANI_PICK]->loop.mode) && _time > example[i]->stalltime) || !example[i]->animating)
				{
					ready[i] = 2;
					exitdelay = _time + GAME_SPEED;
				}
			}
			else if (ready[i] == 2)
			{
				font_printf(psmenu[i][2], psmenu[i][3], 0, 0, Tr("Ready!"));
			}

			if (example[i] != NULL)
			{
				players_busy++;
			}
			if (ready[i] == 2)
			{
				players_ready++;
			}
		}

		if (players_busy && players_busy == players_ready && exitdelay && _time > exitdelay)
		{
			exit = 1;
		}
		update(0, 0);

		if (bothnewkeys & FLAG_ESC || escape_flag == 11) //Kratus (20-04-21) Added the new "escape" flag in the select screen by using the "gotomainmenu" function and the flag "11"
		{
			escape = 1;
            escape_flag = 0;
		}
	}

	// No longer at the select screen
	kill_all();
	sound_close_music();
	screen_status &= ~IN_SCREEN_SELECT;

	return (!escape);
}

void playgame(int *players,  unsigned which_set, int useSavedGame)
{
    int i;
    current_level = 0;
    current_stage = 1;
    current_set = which_set;
    s_set_entry *set = levelsets + current_set;
    s_savelevel *save = savelevel + current_set;
    s_level_entry *le;

    useSave = 0;
    useSet = -1;

    if(which_set >= num_difficulties)
    {
        return;
    }
    // borShutdown(1, "Illegal set chosen: index %i (there are only %i sets)!", which_set, num_difficulties);

    allow_secret_chars = set->ifcomplete;
    PLAYER_LIVES = set->lives;
    musicoverlap = set->musicoverlap;
    fade = set->custfade;
    CONTINUES = set->credits;
    magic_type = set->typemp;
    if(PLAYER_LIVES == 0)
    {
        PLAYER_LIVES = 3; // default
    }
    if(CONTINUES == 0)
    {
        CONTINUES = 5; // default
    }
    if(fade == 0)
    {
        fade = 24; // default
    }
    sameplayer = set->nosame;

    if(useSavedGame == 1 && save->flag)
    {
        memset(player, 0, sizeof(*player) * 4);
        if(!loadScriptFile())
        {
            printf("Warning, failed to load script save!\n");
        }
        current_level = save->level;
        current_stage = save->stage;
        if(save->flag == 2) // don't check 1 or 0 becuase if we use saved game the flag must be >0
        {
            for(i = 0; i < set->maxplayers; i++)
            {
                player[i].lives = save->pLives[i];
                player[i].credits = save->pCredits[i];
                player[i].score = save->pScores[i];
                player[i].colourmap = save->pColourmap[i];
                player[i].weapnum = save->pWeapnum[i];
                player[i].spawnhealth = save->pSpawnhealth[i];
                player[i].spawnmp = save->pSpawnmp[i];
                strncpy(player[i].name, save->pName[i], MAX_NAME_LEN);
            }
            credits = save->credits;
        }
        load_playable_list(save->allowSelectArgs); //TODO: change sav format to support dynamic allowselect list.
        //reset_playable_list(1); // add this because there's no select screen, temporary solution
    }

    nosave = 1;

    // fix save an inexistent level
    if ( current_level >= set->numlevels )
    {
        current_level = set->numlevels-1;
    }

    le = set->levelorder + current_level;
    set->noselect = le->noselect;
    if (save->selectSkipSelect[0]) le = load_skipselect(save->selectSkipSelect, set); // LOAD skipselect
    for(i = 0; i < MAX_PLAYERS; i++)
    {
        if(le->skipselect[i])
        {
            strcpy(skipselect[i], le->skipselect[i]);
        }
        else
        {
            skipselect[i][0] = 0;
        }
    }

    if((useSavedGame == 1 && save->flag == 2) || useSavedGame == 2 || selectplayer(players, NULL, useSavedGame)) // if save flag is 2 don't select player
    {
        while(current_level < set->numlevels)
        {
            if(branch_name[0])  // branch checking
            {
                //current_stage = 1; //jump, jump... perhaps we don't need to reset it, modders should take care of it.
                for(i = 0; i < set->numlevels; i++)
                {
                    if(set->levelorder[i].branchname && stricmp(set->levelorder[i].branchname, branch_name) == 0)
                    {
                        current_level = i;
                        break;
                    }
                    //if(levelorder[which_set][i]->gonext==1) ++current_stage; OX. Commented this line out. Seems to be cause of inacurate stage # complete message.
                }
                branch_name[0] = 0;// clear up so we won't stuck here
            }
            le = set->levelorder + current_level;
            PLAYER_MIN_Z = le->z_coords[0];
            PLAYER_MAX_Z = le->z_coords[1];
            BGHEIGHT = le->z_coords[2];

            if(le->type == LE_TYPE_CUT_SCENE)
            {
                playscene(le->filename);
            }
            else if(le->type == LE_TYPE_SELECT_SCREEN)
            {
                memset(save->selectSkipSelect,0,sizeof(save->selectSkipSelect)); // RESET skipselect
                set->noselect = 0;
                for(i = 0; i < set->maxplayers; i++) // reset skipselect
                {
                    if(le->skipselect[i])
                    {
                        skipselect[i][0] = 0;
                    }
                }
                for(i = 0; i < set->maxplayers ; i++)
                {
                    players[i] = (player[i].lives > 0);
                }
                if(selectplayer(players, le->filename, useSavedGame) == 0)
                {
                    break;
                }
            }
            else if(le->type == LE_TYPE_SKIP_SELECT)
            {
                set->noselect = le->noselect;
                for(i = 0; i < MAX_PLAYERS; i++)
                {
                    if(le->skipselect[i])
                    {
                        strcpy(skipselect[i], le->skipselect[i]);
                    }
                    else
                    {
                        skipselect[i][0] = 0;
                    }
                }
                save_skipselect(save->selectSkipSelect, skipselect); // TO SAVE FILE
                selectplayer(players, NULL, useSavedGame); // re-select a player
            }
            else if(!playlevel(le->filename))
            {
                int all_p_lives_zero = 0;
                for(i = 0; i < MAX_PLAYERS; i++)
                {
                    if (player[i].lives <= 0) ++all_p_lives_zero;
                }
                all_p_lives_zero = (all_p_lives_zero >= MAX_PLAYERS) ? 1 : 0;

                //if( (player[0].lives <= 0 && player[1].lives <= 0 && player[2].lives <= 0 && player[3].lives <= 0) )
                if(all_p_lives_zero)
                {
                    if( (!set->noshowgameover && !(goto_mainmenu_flag&2)) )
                    {
                        gameover();
                    }
                    if(!set->noshowhof && !(goto_mainmenu_flag&4))
                    {
                        hallfame(1);
                    }
                    for(i = 0; i < set->maxplayers; i++)
                    {
                        player[i].hasplayed = 0;
                        player[i].weapnum = 0;
                    }
                }
                break;
            }
            if(le->gonext == 1)
            {
                if (!set->noshowcomplete) showcomplete(current_stage);
                for(i = 0; i < set->maxplayers; i++)
                {
                    player[i].spawnhealth = 0;
                    player[i].spawnmp = 0;
                }
                ++current_stage;
                save->stage = current_stage;
            }
            current_level++;
            le = set->levelorder + current_level;
            save->level = current_level;
            //2007-2-24, gonext = 2, end game
            if((le - 1)->gonext == 2)
            {
                current_level = set->numlevels;
            }
            if(useSave)
            {
                goto endgame;    //quick exit without saving, for script load game logic
            }
        }//while

        if(current_level >= set->numlevels)
        {
            bonus += save->times_completed++;
            saveGameFile();
            if(!nofadeout)
            {
                fade_out(0, 0);
            }
            if(!set->noshowhof)
            {
                hallfame(1);
            }
        }
    }

endgame:
    // clear global script variant list
    branch_name[0] = 0;
    sound_close_music();
}

int menu_difficulty()
{
    int quit = 0;
    int selector = 0;
    int maxdisplay = 5;
    int i, j, t;
    //float slider = 0;
    int barx, bary, barw, barh;
    s_drawmethod drawmethod = plainmethod;
    drawmethod.alpha = BLEND_MODE_ALPHA;

    barx = videomodes.hRes / 5;
    bary = _liney(0, 0) - 2;
    barw = videomodes.hRes * 3 / 5;
    barh = 5 * (fontheight(0) + 1) + 4;
    screen_status |= IN_SCREEN_NEW_GAME_MENU;
    bothnewkeys = 0;

    loadGameFile();

    while(!quit)
    {
        if(num_difficulties > 1)
        {
            _menutextm(2, -2, 0, Tr("Game Mode"));
            t = (selector - (selector == num_difficulties)) / maxdisplay * maxdisplay;
            for(j = 0, i = t; i < maxdisplay + t && i < num_difficulties; j++, i++)
            {
                if(j < maxdisplay)
                {
                    // Kratus (10-2021) Added translation feature to the "set" name
                    // It will get the "set" name in the "levels.txt" file and translate it
                    // You need to add a new "msgid/msgstr" instance in your "translation.txt" file and put
                    // the same names as used by all level sets
                    if(bonus >= levelsets[i].ifcomplete)
                    {
                        _menutextm((selector == i), j, 0, "%s", Tr(levelsets[i].name));
                    }
                    else
                    {
                        if(levelsets[i].ifcomplete > 1)
                        {
                            _menutextm((selector == i), j, 0, Tr("%s - Finish Game %i Times To UnLock"), Tr(levelsets[i].name), levelsets[i].ifcomplete);
                        }
                        else
                        {
                            _menutextm((selector == i), j, 0, Tr("%s - Finish Game To UnLock"), Tr(levelsets[i].name));
                        }
                    }
                }
                else
                {
                    break;
                }
            }
            _menutextm((selector == i), 6, 0, Tr("Back"));

            //draw the scroll bar
            if(num_difficulties > maxdisplay)
            {
                spriteq_add_box(barx,  bary,        barw,     barh,   0, color_black, &drawmethod); //outerbox
                spriteq_add_line(barx, bary,  barx + 8, bary, 1, color_white, NULL);
                spriteq_add_line(barx, bary, barx, bary + barh, 1, color_white, NULL);
                spriteq_add_line(barx + 8, bary, barx + 8, bary + barh,  1, color_white, NULL);
                spriteq_add_line(barx, bary + barh, barx + 8, bary + barh,  1, color_white, NULL);
                spriteq_add_box(barx + 1,  bary + selector * (barh - 3) / num_difficulties, 7,             3,            2, color_white, NULL); //slider
            }
        }

        update(0, 0);

        if(num_difficulties == 1) // OX. Mods with only one set will auto load that difficulty.
        {
            if(selector == num_difficulties)
            {
                quit = 1;
            }
            else if(bonus >= levelsets[selector].ifcomplete)
            {
                saveslot = selector;
                strncpy(savelevel[saveslot].dName, levelsets[saveslot].name, MAX_NAME_LEN - 1);
                screen_status &= ~IN_SCREEN_NEW_GAME_MENU;
                return saveslot;
            }
        }

        if(bothnewkeys & FLAG_ESC)
        {
            quit = 1;
        }
        if(bothnewkeys & FLAG_MOVEUP)
        {
            --selector;
            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(bothnewkeys & FLAG_MOVEDOWN)
        {
            ++selector;
            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(selector < 0)
        {
            selector = num_difficulties;
        }
        if(selector > num_difficulties)
        {
            selector = 0;
        }
        //if(selector<num_difficulties) slider = selector * 4.5;

        if(bothnewkeys & FLAG_ANYBUTTON)
        {

            if(global_sample_list.beep_2 >= 0)
            {
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            if(selector == num_difficulties)
            {
                quit = 1;
            }
            else if(bonus >= levelsets[selector].ifcomplete)
            {
                saveslot = selector;
                strncpy(savelevel[saveslot].dName, levelsets[saveslot].name, MAX_NAME_LEN - 1);
                screen_status &= ~IN_SCREEN_NEW_GAME_MENU;
                return saveslot;
            }
        }
    }
    bothnewkeys = 0;
    screen_status &= ~IN_SCREEN_NEW_GAME_MENU;
    return -1;
}

int load_saved_game()
{
    int quit = 0;
    int selector = 0;
    int savedStatus = 0;
    char name[MAX_BUFFER_LEN] = {""};
    int col1 = -8, col2 = 6;

    screen_status |= IN_SCREEN_LOAD_GAME_MENU;
    bothnewkeys = 0;

    if((savedStatus = loadGameFile()))
    {
        getPakName(name, 0);
    }
    for(saveslot = 0; saveslot < num_difficulties; saveslot++) if(savelevel[saveslot].flag && savelevel[saveslot].level)
        {
            break;
        }

    while(!quit)
    {
        if(saveslot >= num_difficulties) // not found
        {
            _menutextm(2, -4, 0, Tr("Load Game"));
            _menutext(0, col1, -2, Tr("Saved File:"));
            _menutext(0, col2, -2, Tr("Not Found!"));
            _menutextm(1, 6, 0, Tr("Back"));

            selector = 2;
        }
        else
        {
            _menutextm(2, -4, 0, Tr("Load Game"));
            _menutext(0, col1, -2, Tr("Saved File:"));
            if(savedStatus)
            {
                _menutext(0, col2, -2, "%s", name);
            }
            else
            {
                _menutext(0, col2, -2, Tr("Not Found!"));
            }

            if(savedStatus)
            {
                _menutext((selector == 0), col1, -1, Tr("Mode:"));
                _menutext((selector == 0), col2, -1, "%s", savelevel[saveslot].dName);
                _menutext(0, col1, 0, Tr("Stage:"));
                _menutext(0, col2, 0, "%d", savelevel[saveslot].stage);
                _menutext(0, col1, 1, Tr("Level:"));
                _menutext(0, col2, 1, "%d", savelevel[saveslot].level);
                _menutext(0, col1, 2, Tr("Credits:"));

                if(noshare){
                    _menutext(0, col2, 2, "%d/%d/%d/%d",
                                  savelevel[saveslot].pCredits[0],
                                  savelevel[saveslot].pCredits[1], savelevel[saveslot].pCredits[2],
                                  savelevel[saveslot].pCredits[3]);
                } else {
                    _menutext(0, col2, 2, "%d", savelevel[saveslot].credits);
                }

                _menutext(0, col1, 3, Tr("Player Lives:"));
                _menutext(0, col2, 3, "%d/%d/%d/%d",
                          savelevel[saveslot].pLives[0],
                          savelevel[saveslot].pLives[1], savelevel[saveslot].pLives[2],
                          savelevel[saveslot].pLives[3]);
            }
            _menutextm((selector == 1), 6, 0, Tr("Back"));
        }
        update(0, 0);

        if(bothnewkeys & FLAG_ESC)
        {
            quit = 1;
        }
        if(selector == 0 && (bothnewkeys & FLAG_MOVELEFT))
        {
            while(1)
            {
                --saveslot;
                if(saveslot < 0)
                {
                    saveslot = num_difficulties - 1;
                }
                if(savelevel[saveslot].flag && savelevel[saveslot].level)
                {
                    break;
                }
            }
            sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
        }
        if(selector == 0 && (bothnewkeys & FLAG_MOVERIGHT))
        {
            while(1)
            {
                ++saveslot;
                if(saveslot > num_difficulties - 1)
                {
                    saveslot = 0;
                }
                if(savelevel[saveslot].flag && savelevel[saveslot].level)
                {
                    break;
                }
            }
            sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
        }
        if(bothnewkeys & FLAG_MOVEUP)
        {
            --selector;
            sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
        }
        if(bothnewkeys & FLAG_MOVEDOWN)
        {
            ++selector;
            sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
        }
        if(savedStatus)
        {
            if(selector < 0)
            {
                selector = 1;
            }
            if(selector > 1)
            {
                selector = 0;
            }
        }
        else
        {
            selector = 1;
        }

        if((bothnewkeys & FLAG_ANYBUTTON))
        {
            sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            switch(selector)
            {
            case 0:
                return saveslot;
                break;
            case 1:
                quit = 1;
                break;
            }
        }
    }
    bothnewkeys = 0;
    screen_status &= ~IN_SCREEN_LOAD_GAME_MENU;
    return -1;
}

int choose_mode(int *players)
{
    int quit = 0;
    int relback = 0;
    int selector = 0;
    int status = 0;

    screen_status |= IN_SCREEN_GAME_START_MENU;
    bothnewkeys = 0;

    while(!quit)
    {
        _menutextm(2, 1, 0, Tr("Choose Mode"));
        _menutextm((selector == 0), 3, 0, Tr("New Game"));
        _menutextm((selector == 1), 4, 0, Tr("Load Game"));
        _menutextm((selector == 2), 6, 0, Tr("Back"));

        update(0, 0);

        if(bothnewkeys & FLAG_ESC)
        {
            quit = 1;
        }
        if(bothnewkeys & FLAG_MOVEUP)
        {
            --selector;
            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(bothnewkeys & FLAG_MOVEDOWN)
        {
            ++selector;
            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(selector < 0)
        {
            selector = 2;
        }
        if(selector > 2)
        {
            selector = 0;
        }

        if(bothnewkeys & FLAG_ANYBUTTON)
        {
            if(global_sample_list.beep_2 >= 0)
            {
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            }
            switch(selector)
            {
            case 0:
                status = menu_difficulty();
                if(status != -1)
                {
                    playgame(players, status, 0);
                    relback = 1;
                    quit = 1;
                }
                break;
            case 1:
                status = load_saved_game();
                if(status != -1)
                {
                    playgame(players, status, 1);
                    relback = 1;
                    quit = 1;
                }
                break;
            default:
                quit = 1;
                break;
            }
        }
    }
    bothnewkeys = 0;
    screen_status &= ~IN_SCREEN_GAME_START_MENU;
    return relback;
}

void term_videomodes()
{
    videomodes.hRes = 0;
    videomodes.vRes = 0;
    video_set_mode(videomodes);
    if(custScenes != NULL)
    {
        free(custScenes);
    }
    custScenes = NULL;
    if(custBkgrds != NULL)
    {
        free(custBkgrds);
    }
    custBkgrds = NULL;
    if(custLevels != NULL)
    {
        free(custLevels);
    }
    custLevels = NULL;
    if(custModels != NULL)
    {
        free(custModels);
    }
    custModels = NULL;
}

// Load Video Mode from file
void init_videomodes(int log)
{
    char *filename = "data/video.txt";
    int tmp;
    ptrdiff_t pos, len;
    size_t size;
    char *buf = NULL;
    char *command = NULL;
    char *value = NULL, *value2;
    ArgList arglist;
    char argbuf[MAX_ARG_LEN + 1] = "";

    //

    if(log)
    {
        printf("Initializing video............\n");
    }

    // Use an alternative video.txt if there is one.  Some of these are long filenames; create your PAKs with borpak and you'll be fine.
#define tryfile(X) if((tmp=openpackfile(X,packfile))!=-1) { closepackfile(tmp); filename=X; goto readfile; }
#if WIN || LINUX
    tryfile("data/videopc.txt");
#elif WII
    tryfile("data/videowii.txt");
    if(CONF_GetAspectRatio() == CONF_ASPECT_16_9)
    {
        tryfile("data/video169.txt")
    }
    else
    {
        tryfile("data/video43.txt");
    }
#elif WIZ
    tryfile("data/videowiz.txt");
    tryfile("data/video169.txt");
#elif GP2X
    tryfile("data/videogp2x.txt");
    tryfile("data/video43.txt");
#elif OPENDINGUX
    tryfile("data/videoopendingux.txt");
    tryfile("data/video43.txt");
#elif SYMBIAN
    tryfile("data/videosymbian.txt");
#elif VITA
    tryfile("data/videovita.txt");
    tryfile("data/video169.txt");
#endif
#undef tryfile

readfile:
    // Read file
    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        videoMode = 0;
        printf("'%s' not found.\n", filename);
        goto VIDEOMODES;
    }

    printf("Reading video settings from '%s'.\n", filename);

    // Now interpret the contents of buf line by line
    pos = 0;
    while(pos < size)
    {
        ParseArgs(&arglist, buf + pos, argbuf);
        command = GET_ARG(0);

        if(command && command[0])
        {
            if(stricmp(command, "video") == 0)
            {
                value = GET_ARG(1);
                if((value2 = strchr(value, 'x')))
                {
                    videomodes.hRes = atoi(value);
                    videomodes.vRes = atoi(value2 + 1);
                    videoMode = 255;
                }
                else
                {
                    videoMode = GET_INT_ARG(1);
                }
            }
            else if(stricmp(command, "scenes") == 0)
            {
                len = strlen(GET_ARG(1));
                custScenes = malloc(len + 1);
                strcpy(custScenes, GET_ARG(1));
                custScenes[len] = 0;
            }
            else if(stricmp(command, "backgrounds") == 0)
            {
                len = strlen(GET_ARG(1));
                custBkgrds = malloc(len + 1);
                strcpy(custBkgrds, GET_ARG(1));
                custBkgrds[len] = 0;
            }
            else if(stricmp(command, "levels") == 0)
            {
                len = strlen(GET_ARG(1));
                custLevels = malloc(len + 1);
                strcpy(custLevels, GET_ARG(1));
                custLevels[len] = 0;
            }
            else if(stricmp(command, "models") == 0)
            {
                len = strlen(GET_ARG(1));
                custModels = malloc(len + 1);
                strcpy(custModels, GET_ARG(1));
                custModels[len] = 0;
            }
            else if(stricmp(command, "colourdepth") == 0)
            {
                printf("\nColordepth is depreciated. All modules are displayed with a 32bit color screen.\n\n");
            }
            else if(stricmp(command, "forcemode") == 0) {}
            else if(command && command[0])
            {
                printf("Command '%s' not understood in file '%s'!\n", command, filename);
            }
        }
        // Go to next line
        pos += getNewLineStart(buf + pos);
    }

    if(buf != NULL)
    {
        free(buf);
        buf = NULL;
    }

#if OPENDINGUX || GP2X
    videoMode = 0;
#endif

#if SYMBIAN
    if(videoMode != 0 && videoMode != 2)
    {
        videoMode = 0;
    }
#endif
    
    videoMode = 0;

VIDEOMODES:
    videomodes.mode    = videoMode;
    videomodes.filter  = savedata.swfilter;
    switch (videoMode)
    {
        // 320x240 - All Platforms
    case 0:
        videomodes.hRes    = 320;
        videomodes.vRes    = 240;
        videomodes.hScale  = 1;
        videomodes.vScale  = 1;
        videomodes.hShift  = 0;
        videomodes.vShift  = 0;
        videomodes.dOffset = 231;
        PLAYER_MIN_Z       = 160;
        PLAYER_MAX_Z       = 232;
        BGHEIGHT           = 160;
        break;

        // 480x272 - All Platforms
    case 1:
        videomodes.hRes    = 480;
        videomodes.vRes    = 272;
        videomodes.hScale  = (float)1.5;
        videomodes.vScale  = (float)1.13;
        videomodes.hShift  = 80;
        videomodes.vShift  = 20;
        videomodes.dOffset = 263;
        PLAYER_MIN_Z       = 182;
        PLAYER_MAX_Z       = 264;
        BGHEIGHT           = 182;
        break;

        // 640x480 - PC, Dreamcast, Wii
    case 2:
        videomodes.hRes    = 640;
        videomodes.vRes    = 480;
        videomodes.hScale  = 2;
        videomodes.vScale  = 2;
        videomodes.hShift  = 160;
        videomodes.vShift  = 35;
        videomodes.dOffset = 464;
        PLAYER_MIN_Z       = 321;
        PLAYER_MAX_Z       = 465;
        BGHEIGHT           = 321;
        break;

        // 720x480 - PC, Wii
    case 3:
        videomodes.hRes    = 720;
        videomodes.vRes    = 480;
        videomodes.hScale  = 2.25;
        videomodes.vScale  = 2;
        videomodes.hShift  = 200;
        videomodes.vShift  = 35;
        videomodes.dOffset = 464;
        PLAYER_MIN_Z       = 321;
        PLAYER_MAX_Z       = 465;
        BGHEIGHT           = 321;
        break;

        // 800x480 - PC, Wii, Pandora
    case 4:
        videomodes.hRes    = 800;
        videomodes.vRes    = 480;
        videomodes.hScale  = 2.5;
        videomodes.vScale  = 2;
        videomodes.hShift  = 240;
        videomodes.vShift  = 35;
        videomodes.dOffset = 464;
        PLAYER_MIN_Z       = 321;
        PLAYER_MAX_Z       = 465;
        BGHEIGHT           = 321;
        break;

        // 800x600 - PC, Dreamcast, Wii
    case 5:
        videomodes.hRes    = 800;
        videomodes.vRes    = 600;
        videomodes.hScale  = 2.5;
        videomodes.vScale  = 2.5;
        videomodes.hShift  = 240;
        videomodes.vShift  = 44;
        videomodes.dOffset = 580;
        PLAYER_MIN_Z       = 401;
        PLAYER_MAX_Z       = 582;
        BGHEIGHT           = 401;
        break;

        // 960x540 - PC, Wii
    case 6:
        videomodes.hRes    = 960;
        videomodes.vRes    = 540;
        videomodes.hScale  = 3;
        videomodes.vScale  = 2.25;
        videomodes.hShift  = 320;
        videomodes.vShift  = 40;
        videomodes.dOffset = 522;
        PLAYER_MIN_Z       = 362;
        PLAYER_MAX_Z       = 524;
        BGHEIGHT           = 362;
        break;

    case 255:
        videomodes.dOffset = videomodes.vRes * 0.9625;
        printf("\nUsing debug video mode: %d x %d\n", videomodes.hRes, videomodes.vRes);
        break;
    default:
        borShutdown(1, "Invalid video mode: %d in 'data/video.txt', supported modes:\n"
                 "0 - 320x240\n"
                 "1 - 480x272\n"
                 "2 - 640x480\n"
                 "3 - 720x480\n"
                 "4 - 800x480\n"
                 "5 - 800x600\n"
                 "6 - 960x540\n\n", videoMode);
        break;
    }

#if SDL || WII
    video_stretch(savedata.stretch);
#endif

    if((vscreen = allocscreen(videomodes.hRes, videomodes.vRes, PIXEL_32)) == NULL)
    {
        borShutdown(1, "Not enough memory!\n");
    }
    videomodes.pixel = pixelbytes[(int)vscreen->pixelformat];
    //video_set_mode(videomodes);
    clearscreen(vscreen);

    if(log)
    {
        printf("Initialized video............\t%dx%d (Mode: %d)\n\n", videomodes.hRes, videomodes.vRes, videoMode);
    }
}



// ----------------------------------------------------------------------------


// Set key or button safely (with switching)
void safe_set(int *arr, int index, int newkey, int oldkey)
{
    int i;
    for(i = 0; i < 12; i++)
    {
        if(arr[i] == newkey)
        {
            arr[i] = oldkey;
        }
    }
    arr[index] = newkey;
}

void keyboard_setup(int player)
{
    const int btnnum = MAX_BTN_NUM;

    int quit = 0; 
    int sdid = 0;
    int selector = 0;
    int setting = -1;
    int i = 0;
    int k = 0;
    int ok = 0;
    int disabledkey[MAX_BTN_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
    int col1 = -8;
    int col2 = 6;

    ptrdiff_t voffset; 
    ptrdiff_t pos;

    char* buf;
    char* command;
    char* filename = "translation/menu.txt";
    
    char  buttonnames[btnnum][32];
    
    size_t size;
    ArgList arglist;
    
    char argbuf[MAX_ARG_LEN + 1] = "";
    #if SDL || WII || DC
    int OPTIONS_NUM = btnnum + 3;
    #else
    int OPTIONS_NUM = btnnum + 2;
    #endif

    screen_status |= IN_SCREEN_BUTTON_CONFIG_MENU;

    printf("Loading control settings.......\t");

    strcpy(buttonnames[SDID_MOVEUP], "Move Up");
    strcpy(buttonnames[SDID_MOVEDOWN], "Move Down");
    strcpy(buttonnames[SDID_MOVELEFT], "Move Left");
    strcpy(buttonnames[SDID_MOVERIGHT], "Move Right");
    strcpy(buttonnames[SDID_ATTACK], "Attack 1");
    strcpy(buttonnames[SDID_ATTACK2], "Attack 2");
    strcpy(buttonnames[SDID_ATTACK3], "Attack 3");
    strcpy(buttonnames[SDID_ATTACK4], "Attack 4");
    strcpy(buttonnames[SDID_JUMP], "Jump");
    strcpy(buttonnames[SDID_SPECIAL], "Special");
    strcpy(buttonnames[SDID_START], "Start");
    strcpy(buttonnames[SDID_SCREENSHOT], "Screenshot");
    strcpy(buttonnames[SDID_ESC], "Exit");

    savesettings();
    bothnewkeys = 0;

    /*
        Kratus (10-2021) Added an alternative location for the translation file, now it's possible to use in an external folder
        Now the modder can load exported translation files by using "filestream" script functions
        Useful for creating custom translations without unpack the game
        The default engine translation location will be maintained for backward compatibility

        Kratus (11-2021) Inverted the path priority, now the external file will override the internal file
        Useful to maintain the english translation intact inside the pak file if no other language file is found in the external path
        Otherwise you will need to rollback the english file every time another language is used and then removed
        This operation is needed only if the english translation file uses some custom menu texts for english language too
    */
    if(buffer_pakfile(filename, &buf, &size) != 1)
    {
        goto default_file;
    }
    else
    {
        goto proceed;
    }

default_file:

    if(buffer_pakfile("data/menu.txt", &buf, &size) != 1)
    {
        goto finish;
    }
    else
    {
        goto proceed;
    }

proceed:

    // Read file
    // Now interpret the contents of buf line by line
    pos = 0;
    while(pos < size)
    {
        ParseArgs(&arglist, buf + pos, argbuf);
        command = GET_ARG(0);
        if(command[0])
        {
            if(stricmp(command, "disablekey") == 0)
            {
                sdid = translate_SDID(GET_ARG(1));
                if(sdid >= 0)
                {
                    disabledkey[sdid] = 1;
                }
            }
            else if(stricmp(command, "renamekey") == 0)
            {
                sdid = translate_SDID(GET_ARG(1));
                if(sdid >= 0)
                {
                    strcpy(buttonnames[sdid], GET_ARG(2));
                }
            }

        }
        // Go to next line
        pos += getNewLineStart(buf + pos);
    }
    if(buf != NULL)
    {
        free(buf);
        buf = NULL;
    }

finish:

    while(disabledkey[selector]) if(++selector > btnnum - 1) break;

    while(!quit)
    {
        voffset = -6;
        _menutextm(2, -8, 0, Tr("Player %i"), player + 1);
        for(i = 0; i < btnnum; i++)
        {
            if(!disabledkey[i])
            {
                _menutext((selector == i), col1, voffset, "%s", buttonnames[i]);
                _menutext((selector == i), col2, voffset, "%s", control_getkeyname(savedata.keys[player][i]));
                voffset++;
            }
        }
        #if SDL || WII || DC
        ++voffset;
        if(savedata.joyrumble[player])
        {
            _menutext((selector == OPTIONS_NUM - 3), col1, voffset++, Tr("Rumble Enabled"));
        }
        else
        {
            _menutext((selector == OPTIONS_NUM - 3), col1, voffset++, Tr("Rumble Disabled"));
        }
        #endif

        _menutextm((selector == OPTIONS_NUM - 2), ++voffset, 0, Tr("OK"));
        _menutextm((selector == OPTIONS_NUM - 1), ++voffset, 0, Tr("Cancel"));
        _menutextm((selector == OPTIONS_NUM), ++voffset, 0, Tr("Default"));
        update((level != NULL), 0);

        if(setting > -1)
        {
            if(bothnewkeys & FLAG_ESC)
            {
                savedata.keys[player][setting] = ok;
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 50);
                setting = -1;
            }
            if(setting > -1)
            {
                k = control_scankey();
                if(k)
                {
                    safe_set(savedata.keys[player], setting, k, ok);
                    sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
                    setting = -1;
                    // Prevent accidental screenshot
                    bothnewkeys = 0;
                }
            }
        }
        else
        {
            if(bothnewkeys & FLAG_ESC)
            {
                quit = 1;
            }
            if(bothnewkeys & FLAG_MOVEUP)
            {
                do
                {
                    if(--selector < 0)
                    {
                        break;
                    }
                }
                while(selector < btnnum && disabledkey[selector]);
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
            if(bothnewkeys & FLAG_MOVEDOWN)
            {
                do
                {
                    if(++selector > btnnum - 1) break;
                }
                while(disabledkey[selector]);
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
            if(selector < 0)
            {
                selector = OPTIONS_NUM;
            }
            if(selector > OPTIONS_NUM)
            {
                selector = 0;
                while(disabledkey[selector]) if(++selector > btnnum - 1) break;
            }

            #if SDL || WII || DC
            if(bothnewkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT | FLAG_ANYBUTTON))
            #else
            if(bothnewkeys & (FLAG_ANYBUTTON))
            #endif
            if(bothnewkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT | FLAG_ANYBUTTON))
            {
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);

#if SDL || WII || DC
                if (selector != OPTIONS_NUM - 3 &&
                    bothnewkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT)) continue;

                if(selector == OPTIONS_NUM - 3)
                {
                    savedata.joyrumble[player] ^= 1;
                }
                else if(selector == OPTIONS_NUM - 2) // OK
                #else
                if(selector == OPTIONS_NUM - 2) // OK
#endif
                {
                    quit = 2;
                }
                else if(selector == OPTIONS_NUM - 1) // cancel
                {
                    quit = 1;
                }
                else if(selector == OPTIONS_NUM) // default
                {
                    clearbuttons(player);
                }
                else
                {
                    setting = selector;
                    ok = savedata.keys[player][setting];
                    savedata.keys[player][setting] = 0;
#ifndef DC
                    keyboard_getlastkey();
#endif
                }
            }
        }
    }

    if(quit == 2)
    {
        apply_controls();
        savesettings();
    }
    else
    {
        loadsettings();
    }

    screen_status &= ~IN_SCREEN_BUTTON_CONFIG_MENU;

    update(0, 0);
    bothnewkeys = 0;
    printf("Done!\n");
}

void menu_options_input()
{
    int quit = 0;
    int selector = 1; // 0
    int x_pos = -6;
    #if ANDROID
    int OPTIONS_NUM = 6;
    #else
    int OPTIONS_NUM = 5;
    #endif

    screen_status |= IN_SCREEN_CONTROL_OPTIONS_MENU;
    bothnewkeys = 0;

    // Kratus (10-2021) Added a second instance of the "control_init" function while in the Control Options
    // Useful to refresh some text translation if the language is changed "on-the-fly" and re-detect all active controls
    control_init(savedata.usejoy);

    while(!quit)
    {
        _menutextm(2, x_pos-1, 0, Tr("Control Options"));
                
        #if WII
        if(savedata.usejoy)
        {
            _menutext((selector == 0), -4, -2, Tr("Nunchuk Analog Enabled"));
        }
        else
        {
            _menutext((selector == 0), -4, -2, Tr("Nunchuk Analog Disabled"));
        }
        #else
        if(savedata.usejoy)
        {
            _menutext((selector == 0), x_pos, -2, Tr("GamePads Enabled"));
            if(!control_getjoyenabled())
            {
                _menutext((selector == 0), x_pos+11, -2, Tr(" - Device Not Ready"));
            }
        }
        else
        {
            _menutext((selector == 0), x_pos, -2, Tr("GamePads Disabled"));
        }
        #endif

        _menutext((selector == 1), x_pos,-1, Tr("Setup Player 1..."));
        _menutext((selector == 2), x_pos, 0, Tr("Setup Player 2..."));
        _menutext((selector == 3), x_pos, 1, Tr("Setup Player 3..."));
        _menutext((selector == 4), x_pos, 2, Tr("Setup Player 4..."));
        #if ANDROID
        if(savedata.is_touchpad_vibration_enabled)
        {
            _menutextm((selector == 5), 4, 0, Tr("Touchpad Vibration Enabled"));
        }
        else
        {
            _menutextm((selector == 5), 4, 0, Tr("Touchpad Vibration Disabled"));
        }
        _menutextm((selector == 6), 6, 0, Tr("Back"));
        #else
        _menutextm((selector == 5), 5, 0, Tr("Back"));
        #endif

        update((level != NULL), 0);

        if(bothnewkeys & FLAG_ESC)
        {
            quit = 1;
        }
        if(bothnewkeys & FLAG_MOVEUP)
        {
            --selector;
            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(bothnewkeys & FLAG_MOVEDOWN)
        {
            ++selector;
            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(selector < 0)
        {
            selector = OPTIONS_NUM;
        }
        if(selector > OPTIONS_NUM)
        {
            selector = 0;
        }
        if(bothnewkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT | FLAG_ANYBUTTON))
        {

            if(global_sample_list.beep_2 >= 0)
            {
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            switch(selector)
            {
            case 0:
                control_usejoy((savedata.usejoy ^= 1));
                break;
            case 1:
                keyboard_setup(0);
                break;
            case 2:
                keyboard_setup(1);
                break;
            case 3:
                keyboard_setup(2);
                break;
            case 4:
                keyboard_setup(3);
                break;
            #if ANDROID
            case 5:
                savedata.is_touchpad_vibration_enabled ^= 1;
                break;
            #endif
            default:
                quit = (bothnewkeys & FLAG_ANYBUTTON);
            }
        }
    }
    savesettings();
    bothnewkeys = 0;
    screen_status &= ~IN_SCREEN_CONTROL_OPTIONS_MENU;
}


/*
    Kratus (02-2023) Readjusted the master / effectvol / musicvol range and increment
    Round values like 100 by default make more sense than some like 70/120
    So, I transferred the hardcoded volume adjustment to the "soundmix" code instead of changing the default volumes
    Master now goes from 0 to 100 and default to 100
    Effects (samples) now goes from 0 to 100 and default to 100
    Music now goes from 0 to 200 and default to 100
*/
void menu_options_sound()
{
    int quit = 0;
    int selector = 0;
    int dir;
    int col1 = -8;
    int col2 = 6;

    screen_status |= IN_SCREEN_SOUND_OPTIONS_MENU;
    bothnewkeys = 0;

    while(!quit)
    {
        _menutextm(2, -5, 0, Tr("Sound Options"));
        _menutext((selector == 0), col1, -2, Tr("Master Volume:")); // Kratus (02-2023) Renamed from Sound to Master volume
        _menutext((selector == 0), col2, -2, "%i", savedata.soundvol);
        _menutext((selector == 1), col1, -1, Tr("Effects Volume:")); // Kratus (02-2023) Renamed from SFX to Effects volume
        _menutext((selector == 1), col2, -1, "%i", savedata.effectvol);
        _menutext((selector == 2), col1, 0, Tr("Music Volume:"));
        _menutext((selector == 2), col2, 0, "%i", savedata.musicvol);
        _menutext((selector == 3), col1, 1, Tr("BGM:"));
        _menutext((selector == 3), col2, 1, (savedata.usemusic ? Tr("Enabled") : Tr("Disabled")));
        _menutext((selector == 4), col1, 2, Tr("Show Titles:"));
        _menutext((selector == 4), col2, 2, (savedata.showtitles ? Tr("Yes") : Tr("No")));
        _menutextm((selector == 5), 5, 0, Tr("Back"));

        update((level != NULL), 0);

        if(bothnewkeys & FLAG_ESC)
        {
            quit = 1;
        }
        if(bothnewkeys & FLAG_MOVEUP)
        {
            --selector;

            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(bothnewkeys & FLAG_MOVEDOWN)
        {
            ++selector;

            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.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(global_sample_list.beep_2 >= 0)
            {
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            // Kratus (02-2023) Reworked the old and unuseful "soundvol" to act as the Master Volume for both sound/music
            switch(selector)
            {
            case 0:
                savedata.soundvol += 5 * dir;
                if(savedata.soundvol < 0)
                {
                    savedata.soundvol = 0;
                }
                if(savedata.soundvol > 100)
                {
                    savedata.soundvol = 100;
                }
                SB_setvolume(SB_VOICEVOL, savedata.soundvol);

                // Kratus (02-2023) Synchronize sound/music volumes to master
                savedata.effectvol = savedata.soundvol;
                savedata.musicvol = savedata.soundvol;
                break;
            case 1:
                savedata.effectvol += 5 * dir;
                if(savedata.effectvol < 0)
                {
                    savedata.effectvol = 0;
                }
                if(savedata.effectvol > 100)
                {
                    savedata.effectvol = 100;
                }
                break;
            case 3:
                if(!dir)
                {
                    break;
                }
                if(!savedata.usemusic)
                {
                    savedata.usemusic = 1;
                    music("data/music/remix", 1, 0);
                }
                else
                {
                    savedata.usemusic = 0;
                    sound_close_music();
                }
                break;
            case 2:
                savedata.musicvol += 5 * dir;
                if(savedata.musicvol < 0)
                {
                    savedata.musicvol = 0;
                }
                if(savedata.musicvol > 200)
                {
                    savedata.musicvol = 200;
                }
                sound_volume_music(savedata.musicvol, savedata.musicvol);
                break;
            case 4:
                savedata.showtitles = !savedata.showtitles;
                break;
            default:
                quit = 1;
            }
        }
    }
    savesettings();
    bothnewkeys = 0;
    screen_status &= ~IN_SCREEN_SOUND_OPTIONS_MENU;
}

void menu_options_config()     //  OX. Load from / save to default.cfg. Restore OpenBoR "factory" settings.
{
    int quit = 0;
    int selector = 0;
    int saved = 0;
    int loaded = 0;
    int restored = 0;

    bothnewkeys = 0;

    // Kratus (11-2021) Removed spaces on the " Done!" text to fix translation
    while(!quit)
    {
        _menutextm(2, -5, 0, Tr("Configuration Settings"));

        if(saved == 1)
        {
            _menutextm((selector == 0), -3, 0, Tr("Save Settings To Default.cfg%s"), Tr(" Done!"));
        }
        else
        {
            _menutextm((selector == 0), -3, 0, Tr("Save Settings To Default.cfg%s"), "");
        }

        if(loaded == 1)
        {
            _menutextm((selector == 1), -2, 0, Tr("Load Settings From Default.cfg%s"), Tr(" Done!"));
        }
        else
        {
            _menutextm((selector == 1), -2, 0, Tr("Load Settings From Default.cfg%s"), "");
        }

        if(restored == 1)
        {
            _menutextm((selector == 2), -1, 0, Tr("Restore OpenBoR Defaults%s"), Tr(" Done!"));
        }
        else
        {
            _menutextm((selector == 2), -1, 0, Tr("Restore OpenBoR Defaults%s"), "");
        }

        _menutextm((selector == 3), 6, 0, Tr("Back"));

        update((level != NULL), 0);

        if(bothnewkeys & FLAG_ESC)
        {
            quit = 1;
        }
        if(bothnewkeys & FLAG_MOVEUP)
        {
            --selector;

            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(bothnewkeys & FLAG_MOVEDOWN)
        {
            ++selector;

            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.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(global_sample_list.beep_2 >= 0)
            {
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            switch(selector)
            {
            case 0:
                saveasdefault();
                saved = 1;
                break;

            case 1:
                loadfromdefault();
                //borShutdown(2, "\nSettings Loaded From Default.cfg. Restart Required.\n\n");
                init_videomodes(0);
                if(!video_set_mode(videomodes))
                {
                    borShutdown(1, "Unable to set video mode: %d x %d!\n", videomodes.hRes, videomodes.vRes);
                }
                SB_setvolume(SB_VOICEVOL, savedata.soundvol);
                sound_volume_music(savedata.musicvol, savedata.musicvol);
                loaded = 1;
                break;
            case 2:
                clearsettings();
                //borShutdown(2, "\nSettings Loaded From Default.cfg. Restart Required.\n\n");
                init_videomodes(0);
                if(!video_set_mode(videomodes))
                {
                    borShutdown(1, "Unable to set video mode: %d x %d!\n", videomodes.hRes, videomodes.vRes);
                }
                SB_setvolume(SB_VOICEVOL, savedata.soundvol);
                sound_volume_music(savedata.musicvol, savedata.musicvol);
                restored = 1;
                break;
            default:
                quit = 1;
            }
        }
    }
    savesettings();
    bothnewkeys = 0;
}

void menu_options_debug()
{
    #define MENU_POS_Y              -4
    #define MENU_ITEMS_MARGIN_Y     2
    #define COLUMN_1_POS_X          -11
    #define COLUMN_2_POS_X          COLUMN_1_POS_X + 14
    #define MENU_ITEM_FIRST_INDEX   0

    // Selections enumerator. All
    // selection items should be placed
    // here first.
    typedef enum
    {
        // First item can be
        // whatever we like,
        // but it must be set
        // to the MENU_ITEM_FIRST_INDEX
        // constant.
        ITEM_PERFORMANCE = MENU_ITEM_FIRST_INDEX,

        // Items between the first
        // and last can go in any
        // order.
        ITEM_POSITION,
        ITEM_COL_ATTACK,
        ITEM_COL_BODY,
        ITEM_COL_RANGE,

        // This is the "Back"
        // selection and should
        // always be last.
        ITEM_EXIT
    } e_selections;

    int pos_y;
    int quit                = 0;
    e_selections selector   = 0;
    bothnewkeys             = 0;

    while(!quit)
    {
        // Display menu title.
        _menutextm(2, MENU_POS_Y, 0, Tr("Debug Settings"));

        // Menu items.
        // Y position is controlled by a incremented integer.
        // Integer has a base value of the Menu title Y
        // plus static offset. Below each item, this Y value
        // increments by one. We can then add or remove
        // menu items here and the on screen position will
        // take care of itself without us needing to mess
        // with a bunch of hard constants.

        // Reset menu item position Y.
        pos_y = MENU_POS_Y + MENU_ITEMS_MARGIN_Y;

        _menutext((selector == ITEM_PERFORMANCE),    COLUMN_1_POS_X, pos_y, Tr("Performance:"));
        _menutext((selector == ITEM_PERFORMANCE),    COLUMN_2_POS_X, pos_y, (savedata.debuginfo & DEBUG_DISPLAY_PERFORMANCE ? Tr("Enabled") : Tr("Disabled")));
        pos_y++;

        _menutext((selector == ITEM_POSITION),       COLUMN_1_POS_X, pos_y, Tr("Basic Properties:"));
        _menutext((selector == ITEM_POSITION),       COLUMN_2_POS_X, pos_y, (savedata.debuginfo & DEBUG_DISPLAY_PROPERTIES ? Tr("Enabled") : Tr("Disabled")));
        pos_y++;

        _menutext((selector == ITEM_COL_ATTACK),     COLUMN_1_POS_X, pos_y, Tr("Collision Attack:"));
        _menutext((selector == ITEM_COL_ATTACK),     COLUMN_2_POS_X, pos_y, (savedata.debuginfo & DEBUG_DISPLAY_COLLISION_ATTACK ? Tr("Enabled") : Tr("Disabled")));
        pos_y++;

        _menutext((selector == ITEM_COL_BODY),       COLUMN_1_POS_X, pos_y, Tr("Collision Body:"));
        _menutext((selector == ITEM_COL_BODY),       COLUMN_2_POS_X, pos_y, (savedata.debuginfo & DEBUG_DISPLAY_COLLISION_BODY ? Tr("Enabled") : Tr("Disabled")));
        pos_y++;

        _menutext((selector == ITEM_COL_RANGE),      COLUMN_1_POS_X, pos_y, Tr("Range:"));
        _menutext((selector == ITEM_COL_RANGE),      COLUMN_2_POS_X, pos_y, (savedata.debuginfo & DEBUG_DISPLAY_RANGE ? Tr("Enabled") : Tr("Disabled")));
        pos_y++;

        // Display exit title
        _menutextm((selector == ITEM_EXIT), pos_y + (MENU_ITEMS_MARGIN_Y), 0, Tr("Back"));

        // Run an engine update.
        update((level != NULL), 0);

        // If user presses up/down or esc, let's act accordingly.
        if(bothnewkeys & (FLAG_MOVEUP | FLAG_MOVEDOWN | FLAG_ESC))
        {
            // If user presses escape, then set quit
            // flag immediately. Else wise, increment
            // or decrement selector as needed.
            if(bothnewkeys & FLAG_ESC)
            {
                quit = 1;
            }
            else if(bothnewkeys & FLAG_MOVEUP)
            {
                // Play beep if available.
                // Kratus (04-2022) Moved the BEEP sound to work only with UP/DOWN keys
                if(global_sample_list.beep >= 0)
                {
                    sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
                }

                // If we are at the top item, loop
                // to last. Otherwise, move one up.
                if(selector <= MENU_ITEM_FIRST_INDEX)
                {
                    selector = ITEM_EXIT;
                }
                else
                {
                    --selector;
                }
            }
            else if(bothnewkeys & FLAG_MOVEDOWN)
            {
                // Play beep if available.
                // Kratus (04-2022) Moved the BEEP sound to work only with UP/DOWN keys
                if(global_sample_list.beep >= 0)
                {
                    sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
                }

                // If we are at the last item
                // (which should be "back"), then
                // loop back to first. Otherwise
                // move one down.
                if(selector >= ITEM_EXIT)
                {
                    selector = MENU_ITEM_FIRST_INDEX;
                }
                else
                {
                    ++selector;
                }
            }
        }


        // Toggle selection value on left/right or
        // trigger button press.
        if(bothnewkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT | FLAG_ANYBUTTON))
        {
            if(global_sample_list.beep_2 >= 0)
            {
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            // This is where menu items are executed.
            switch(selector)
            {
                case ITEM_PERFORMANCE:
                    savedata.debuginfo ^= DEBUG_DISPLAY_PERFORMANCE;
                    break;
                case ITEM_POSITION:
                    savedata.debuginfo ^= DEBUG_DISPLAY_PROPERTIES;
                    break;                
                case ITEM_COL_ATTACK:
                    savedata.debuginfo ^= DEBUG_DISPLAY_COLLISION_ATTACK;
                    break;
                case ITEM_COL_BODY:
                    savedata.debuginfo ^= DEBUG_DISPLAY_COLLISION_BODY;
                    break;
                case ITEM_COL_RANGE:
                    savedata.debuginfo ^= DEBUG_DISPLAY_RANGE;
                    break;
                case ITEM_EXIT:
                    quit = 1;
            }
        }
    }
    savesettings();
    bothnewkeys = 0;

    #undef MENU_POS_Y
    #undef MENU_ITEMS_MARGIN_Y
    #undef COLUMN_1_POS_X
    #undef COLUMN_2_POS_X
    #undef MENU_ITEM_FIRST_INDEX
}

/*
* Caskey, Damon V.
* 2022-05-05
* 
* Display cheat options menu and
* apply player selections.
*/
void menu_options_cheats()
{
#define MENU_POS_Y              -4
#define MENU_ITEMS_MARGIN_Y     2
#define COLUMN_1_POS_X          -11
#define COLUMN_2_POS_X          COLUMN_1_POS_X + 14

    typedef struct s_option_list
    {
        e_cheat_options active;
        e_cheat_options menu;
        char label[20];
    } s_option_list;

    int option_list_count = 7;
    s_option_list option_list[] = {
            {.active = CHEAT_OPTIONS_IMPLACABLE_ACTIVE,     .menu = CHEAT_OPTIONS_IMPLACABLE_MENU,      .label = "Implacable March:" },
            {.active = CHEAT_OPTIONS_CREDITS_ACTIVE,        .menu = CHEAT_OPTIONS_CREDITS_MENU,         .label = "Infinite Credits:" },
            {.active = CHEAT_OPTIONS_ENERGY_ACTIVE,         .menu = CHEAT_OPTIONS_ENERGY_MENU,          .label = "Infinite Energy:" },
            {.active = CHEAT_OPTIONS_HEALTH_ACTIVE,         .menu = CHEAT_OPTIONS_HEALTH_MENU,          .label = "Infinite Health:" },
            {.active = CHEAT_OPTIONS_LIVES_ACTIVE,          .menu = CHEAT_OPTIONS_LIVES_MENU,           .label = "Infinite Lives:" },
            {.active = CHEAT_OPTIONS_MULTIHIT_ACTIVE,       .menu = CHEAT_OPTIONS_MULTIHIT_MENU,        .label = "Multihit Glitch:" },
            {.active = CHEAT_OPTIONS_TOD_ACTIVE,            .menu = CHEAT_OPTIONS_TOD_MENU,             .label = "Touch of Death:" }
    };
    
    int option_list_cursor = 0;
    int option_available_count = 0;
    int option_available_first = 0;
    int pos_y = 0;
    int quit = 0;
    int selector = 0;
    
    bothnewkeys = 0;

    screen_status |= IN_SCREEN_CHEAT_OPTIONS_MENU;    

    //printf("\n\n menu_options_cheats()");

    while (!quit)
    {
        /* Display menu title. */
        _menutextm(2, MENU_POS_Y, 0, Tr("Cheat Options"));
                
        /*
        * Reset menu item position Y and
        * valid menu item count.
        */
        option_available_count = 0;
        option_available_first = 0;
        pos_y = MENU_POS_Y + MENU_ITEMS_MARGIN_Y;

        /*
        * Iterate through the option list
        * array and display available items.
        * 
        * At each valid option, increment
        * option count and Y position.
        */

        for (option_list_cursor = 0; option_list_cursor < option_list_count; option_list_cursor++)
        {            
            if (global_config.cheats & option_list[option_list_cursor].menu)
            {   
                /* 
                * If option available count is still 0, this
                * is the first available option. Record the
                * index for use downstream.
                */

                if (!option_available_count)
                {
                    option_available_first = option_list_cursor;
                }
                
                /* Print the menu text and on/off status. */
                _menutext((selector == option_list_cursor), COLUMN_1_POS_X, pos_y, Tr(option_list[option_list_cursor].label));
                _menutext((selector == option_list_cursor), COLUMN_2_POS_X, pos_y, (global_config.cheats & option_list[option_list_cursor].active ? Tr("On") : Tr("Off")));
            
                option_available_count++;
                pos_y++;
            }            
        }

        /* No items avaialble. Alert player.  */
        if (!option_available_count)
        {
            _menutextm(0, pos_y, 0, Tr("No cheats available."));
        }        

        /*
        * Add the exit option. We can use the array size 
        * for a selector index due to 0 indexing.
        */
                
        pos_y++;        
        _menutextm((selector == option_list_count), pos_y, 0, Tr("Back"));

        /* Run an engine update. */
        update((level != NULL), 0);
                
        /* 
        * Handle user input and populate selector to
        * appropriate value. 
        * 
        * First we set selector to first item in case 
        * the first available's index is not 0.
        * 
        * Next, handle buttons. ESC quits the menu 
        * instantly. Up/Down select target item.
        * Left/Right/Action buttons toggle value.
        */

        if (!selector && option_available_first)
        {
            selector = option_available_first;
        }

        if (bothnewkeys & FLAG_ESC)
        {
            quit = 1;
        }

        if (bothnewkeys & FLAG_MOVEUP)
        {
            /* Play beep if available. */
            if (global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            /* Wrap around if needed. */
            if (selector <= option_available_first)
            {
                selector = option_list_count;
            }
            else
            {
                selector--;
            }

            /* Skip disabled items. */
            while (!(global_config.cheats & option_list[selector].menu) && selector > option_available_first)
            {               
                selector--;
            }            
        }
            
        if (bothnewkeys & FLAG_MOVEDOWN)
        {
            /* Play beep if available. */
            if (global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            /* Wrap around if needed. */
            if (selector >= option_list_count)
            {
                selector = option_available_first;
            }
            else
            {
                selector++;
            }

            /* Skip disabled items. */
            while (!(global_config.cheats & option_list[selector].menu) && selector < option_list_count)
            {
                selector++;
            }
        }        

        /* 
        * Toggle selection value on left/right or
        * trigger button press.
        */
        if (bothnewkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT | FLAG_ANYBUTTON))
        {
            if (global_sample_list.beep_2 >= 0)
            {
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            /*
            * Take action based on selector.
            * 
            * The last option is the exit item, so if that's
            * our selector value we exit this menu. 
            *
            * Otherwise use selector as array key to get the 
            * bit we want to toggle in our target value.
            */

            if (selector < option_list_count)
            {
                global_config.cheats ^= option_list[selector].active;
            }
            else
            {
                quit = 1;
            }        
        }
    }
    
    //savesettings();
    
    bothnewkeys = 0;
    screen_status &= ~IN_SCREEN_CHEAT_OPTIONS_MENU;

#undef MENU_POS_Y
#undef MENU_ITEMS_MARGIN_Y
#undef COLUMN_1_POS_X
#undef COLUMN_2_POS_X
}

void menu_options_system()
{
    #define SYS_OPT_Y_POS -4

    enum {
        SYS_OPT_LOG,
        SYS_OPT_VSDAMAGE,
        SYS_OPT_CHEATS,
        SYS_OPT_DEBUG,
        SYS_OPT_CONFIG,
        SYS_OPT_BACK
    };

    int quit = 0;
    int selector = 0;
    int col1 = -11;
    int col2 = col1+14;
    int ex_labels = 0;
    int RET = SYS_OPT_BACK;
    int line = 0;

    screen_status |= IN_SCREEN_SYSTEM_OPTIONS_MENU;
    bothnewkeys = 0;
    if (nodebugoptions) ex_labels = 1;
    RET -= ex_labels;
       
    while(!quit)
    {
        line = 0;

        _menutextm(2, SYS_OPT_Y_POS-2, 0, Tr("System Options"));

        _menutext(0, col1, SYS_OPT_Y_POS + line, Tr("Total RAM:"));
        _menutext(0, col2, SYS_OPT_Y_POS + line, Tr("%s KB"), commaprint(getSystemRam(KBYTES)));
        line++;
        
        _menutext(0, col1, SYS_OPT_Y_POS + line, Tr("Used RAM:"));
        _menutext(0, col2, SYS_OPT_Y_POS + line, Tr("%s KB"), commaprint(getUsedRam(KBYTES)));
        line++;

        _menutext(0, col1, SYS_OPT_Y_POS + line, Tr("Max Players:"));
        _menutext(0, col2, SYS_OPT_Y_POS + line, Tr("%i"), levelsets[current_set].maxplayers);
        line++;

        _menutext((selector == SYS_OPT_LOG), col1, SYS_OPT_Y_POS + line, Tr("File Logging:"));
        _menutext((selector == SYS_OPT_LOG), col2, SYS_OPT_Y_POS + line, (savedata.uselog ? Tr("Enabled") : Tr("Disabled")));
        line++;

        _menutext((selector == SYS_OPT_VSDAMAGE), col1, SYS_OPT_Y_POS + line, Tr("Versus Damage:"), 0);
        if(versusdamage == 0)
        {
            _menutext((selector == SYS_OPT_VSDAMAGE), col2, SYS_OPT_Y_POS + line, Tr("Disabled by Module"));
        }
        else if(versusdamage == 1)
        {
            _menutext((selector == SYS_OPT_VSDAMAGE), col2, SYS_OPT_Y_POS + line, Tr("Enabled by Module"));
        }
        else
        {
            if(savedata.mode)
            {
                _menutext((selector == SYS_OPT_VSDAMAGE), col2, SYS_OPT_Y_POS + line, Tr("Disabled"));    //Mode 1 - Players CAN'T attack each other
            }
            else
            {
                _menutext((selector == SYS_OPT_VSDAMAGE), col2, SYS_OPT_Y_POS + line, Tr("Enabled"));    //Mode 2 - Players CAN attack each other
            }
        }
        line++;
                    
        if (global_config.cheats & CHEAT_OPTIONS_MASTER_MENU)
        {
            _menutext((selector == SYS_OPT_CHEATS), col1, SYS_OPT_Y_POS + line, Tr("Cheat Options..."));
            line++;
        }

        if (!nodebugoptions)
        {
            _menutext((selector == SYS_OPT_DEBUG), col1, SYS_OPT_Y_POS + line, Tr("Debug Settings..."));
            line++;
        }

        _menutext((selector == SYS_OPT_CONFIG), col1, SYS_OPT_Y_POS + line, Tr("Config Settings..."));

        /* Extra lines for spacing. */
        line += 2;

        _menutextm((selector == RET), SYS_OPT_Y_POS + line, 0, Tr("Back"));

        update((level != NULL), 0);

        if(bothnewkeys & FLAG_ESC)
        {
            quit = 1;
        }

        if(bothnewkeys & FLAG_MOVEUP)
        {
            --selector;

            /* Skip disabled items. */

            if (selector == SYS_OPT_CHEATS && !(global_config.cheats & CHEAT_OPTIONS_MASTER_MENU))
            {
                --selector;
            }

            if (selector == SYS_OPT_DEBUG && nodebugoptions)
            {
                --selector;
            }

            sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
        }

        if(bothnewkeys & FLAG_MOVEDOWN)
        {
            ++selector;

            /* Skip disabled items. */

            if (selector == SYS_OPT_CHEATS && !(global_config.cheats & CHEAT_OPTIONS_MASTER_MENU))
            {
                ++selector;
            }

            if (selector == SYS_OPT_DEBUG && nodebugoptions)
            {
                ++selector;
            }                        

            sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
        }

        if(selector < 0)
        {
            selector = RET;
        }
        if(selector > RET)
        {
            selector = 0;
        }

        if(bothnewkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT | FLAG_ANYBUTTON))
        {
            sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);

            if (selector == RET) quit = 1;
            else if (selector == SYS_OPT_LOG) savedata.uselog = !savedata.uselog;
            else if (selector == SYS_OPT_VSDAMAGE)
            {
                if (versusdamage > 1)
                {
                    if (savedata.mode)
                    {
                        savedata.mode = 0;
                    }
                    else
                    {
                        savedata.mode = 1;
                    }
                }
            }
            else if (selector == SYS_OPT_CHEATS)
            {
                menu_options_cheats();
            }
            else if (selector == SYS_OPT_DEBUG && !nodebugoptions)
            {
                menu_options_debug();
            }
            else if (selector == SYS_OPT_CHEATS)
            {
                menu_options_cheats();
            }
            else if (selector == SYS_OPT_DEBUG && !nodebugoptions) menu_options_debug();
            else if (selector==SYS_OPT_CONFIG) menu_options_config();
            else quit = 1;
        }
    }
    savesettings();
    bothnewkeys = 0;
    screen_status &= ~IN_SCREEN_SYSTEM_OPTIONS_MENU;

    #undef SYS_OPT_Y_POS
}


void menu_options_video()
{
    int quit = 0;
    int selector = 0;
    int dir;
    int col1 = -15, col2 = 1;

    screen_status |= IN_SCREEN_VIDEO_OPTIONS_MENU;
    bothnewkeys = 0;

    while(!quit)
    {
        _menutextm(2, -5, 0, Tr("Video Options"));
        _menutext((selector == 0), col1, -3, Tr("Brightness:"));
        _menutext((selector == 0), col2, -3, "%i", savedata.brightness);
        _menutext((selector == 1), col1, -2, Tr("Gamma:"));
        _menutext((selector == 1), col2, -2, "%i", savedata.gamma);
        _menutext((selector == 2), col1, -1, Tr("Window Offset:"));
        _menutext((selector == 2), col2, -1, "%i", savedata.windowpos);

#if OPENDINGUX
        _menutext((selector == 3), col1, 0, Tr("Display Mode:"));
        _menutext((selector == 3), col2, 0, savedata.fullscreen ? Tr("Full") : Tr("Window"));
        _menutextm((selector == 4), 6, 0, Tr("Back"));
        if(selector < 0)
        {
            selector = 4;
        }
        if(selector > 4)
        {
            selector = 0;
        }
#endif

#if DC || GP2X || VITA
        _menutextm((selector == 3), 6, 0, Tr("Back"));
        if(selector < 0)
        {
            selector = 3;
        }
        if(selector > 3)
        {
            selector = 0;
        }
#endif

#if WII
        _menutext((selector == 3), col1, 0, Tr("Display Mode:"));
        _menutext((selector == 3), col2, 0, (savedata.stretch ? Tr("Stretch to Screen") : Tr("Preserve Aspect Ratio")));
        _menutextm((selector == 4), 6, 0, Tr("Back"));
        if(selector < 0)
        {
            selector = 4;
        }
        if(selector > 4)
        {
            selector = 0;
        }
#endif

#if SDL
#if !defined(GP2X) && !defined(OPENDINGUX)
        _menutext((selector == 3), col1, 0, Tr("Display Mode:"));
        _menutext((selector == 3), col2, 0, savedata.fullscreen ? Tr("Full") : Tr("Window"));

        _menutext((selector == 4), col1, 1, Tr("Video Backend:"));
        _menutext((selector == 4), col2, 1, (opengl ? Tr("OpenGL") : Tr("SDL")));

        _menutext((selector == 5), col1, 2, Tr("Scale:"));
#ifdef ANDROID
        if(savedata.hwscale == 0)
#else
		if(savedata.fullscreen)
#endif
        {
            _menutext((selector == 5), col2, 2, Tr("Automatic"));
        }
        else
        {
            _menutext((selector == 5), col2, 2, "%4.2fx - %ix%i", savedata.hwscale, (int)(videomodes.hRes * savedata.hwscale), (int)(videomodes.vRes * savedata.hwscale));
        }

        _menutext((selector == 6), col1, 3, Tr("Hardware Filter:"));
        {
            char *filterName;
            if (savedata.hwscale == 1.0 && !savedata.fullscreen)
                filterName = "Disabled";
            else if (opengl)
                filterName = "High Quality";
            else if (savedata.hwfilter)
                filterName = "Simple";
            else
                filterName = "Bilinear";
            _menutext((selector == 6), col2, 3, Tr(filterName));
        }

        _menutext((selector == 7), col1, 4, Tr("Software Filter:"));
        _menutext((selector == 7), col2, 4, ((savedata.hwscale >= 2.0 || savedata.fullscreen) ? Tr(GfxBlitterNames[savedata.swfilter]) : Tr("Disabled")));

        _menutext((selector == 8), col1, 5, Tr("VSync:"));
        _menutext((selector == 8), col2, 5, savedata.vsync ? Tr("Enabled") : Tr("Disabled")); // Kratus (10-2021) Added "Tr" for translation purpose 

        // Kratus (01-2023) Added a FPS limit option in the video settings
        _menutext((selector == 9), col1, 6, Tr("FPS Limit:"));
        _menutext((selector == 9), col2, 6, savedata.fpslimit ? Tr("Enabled") : Tr("Disabled"));

        if(savedata.fullscreen)
        {
            _menutext((selector == 10), col1, 7, Tr("Fullscreen Type:"));
            _menutext((selector == 10), col2, 7, (savedata.stretch ? Tr("Stretch to Screen") : Tr("Preserve Aspect Ratio")));
        }
        else if(selector == 10)
        {
            selector = (bothnewkeys & FLAG_MOVEUP) ? 9 : 11;
        }

        _menutextm((selector == 11), 9, 0, Tr("Back"));
        if(selector < 0)
        {
            selector = 11;
        }
        if(selector > 11)
        {
            selector = 0;
        }
#endif
#endif


        update((level != NULL), 0);

        if(bothnewkeys & FLAG_ESC)
        {
            quit = 1;
        }
        if(bothnewkeys & FLAG_MOVEUP)
        {
            --selector;
            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(bothnewkeys & FLAG_MOVEDOWN)
        {
            ++selector;
            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(bothnewkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT | FLAG_ANYBUTTON))
        {
            dir = 0;

            if(bothnewkeys & FLAG_MOVELEFT)
            {
                dir = -1;
            }
            else if(bothnewkeys & FLAG_MOVERIGHT)
            {
                dir = 1;
            }

            if(global_sample_list.beep_2 >= 0)
            {
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            }

            switch(selector)
            {
            case 0:
                savedata.brightness += 8 * dir;
                if(savedata.brightness < -256)
                {
                    savedata.brightness = -256;
                }
                if(savedata.brightness > 256)
                {
                    savedata.brightness = 256;
                }
                vga_vwait();
                set_color_correction(savedata.gamma, savedata.brightness);
                break;
            case 1:
                savedata.gamma += 8 * dir;
                if(savedata.gamma < -256)
                {
                    savedata.gamma = -256;
                }
                if(savedata.gamma > 256)
                {
                    savedata.gamma = 256;
                }
                vga_vwait();
                set_color_correction(savedata.gamma, savedata.brightness);
                break;
            case 2:
                savedata.windowpos += dir;
                if(savedata.windowpos < -2)
                {
                    savedata.windowpos = -2;
                }
                if(savedata.windowpos > 20)
                {
                    savedata.windowpos = 20;
                }
                break;
#if SDL || WII
            case 3:
#if OPENDINGUX
                video_fullscreen_flip();
                break;
#endif

#if WII
                //video_fullscreen_flip();
                video_stretch((savedata.stretch ^= 1));
                break;
#endif

#endif


#if SDL
#if !defined(GP2X) && !defined(OPENDINGUX)
                video_fullscreen_flip();
                break;
            case 4:
                savedata.usegl = !savedata.usegl;
                video_set_mode(videomodes);
                set_color_correction(savedata.gamma, savedata.brightness);
                break;
            case 5:
#ifndef ANDROID
                if(savedata.fullscreen)
                {
                    break;
                }
                savedata.hwscale += dir * 0.25;
                if(savedata.hwscale < 0.25)
                {
                    savedata.hwscale = 0.25;
                }
                if(savedata.hwscale > 4.00)
                {
                    savedata.hwscale = 4.00;
                }
                video_set_mode(videomodes);
#else
                savedata.hwscale += dir * 0.25;
                if(savedata.hwscale < 0.0)
                {
                    savedata.hwscale = 0.0;
                }
                if(savedata.hwscale > 4.00)
                {
                    savedata.hwscale = 4.00;
                }
#endif
                break;
            case 6:
#ifndef ANDROID
                if (opengl || (!savedata.fullscreen && savedata.hwscale == 1.0))
                {
                    break;
                }
#endif
                savedata.hwfilter += dir;
                if(savedata.hwfilter < 0)
                {
                    savedata.hwfilter = 1;
                }
                if(savedata.hwfilter > 1)
                {
                    savedata.hwfilter = 0;
                }
                video_set_mode(videomodes);
				break;
            case 7:
                if(!savedata.fullscreen && savedata.hwscale < 2.0)
                {
                    break;
                }
                videomodes.filter += dir;
                if(videomodes.filter > BLITTER_MAX - 1)
                {
                    videomodes.filter = 0;
                }
                if(videomodes.filter < 0)
                {
                    videomodes.filter = BLITTER_MAX - 1;
                }
                savedata.swfilter = videomodes.filter;
                memset(pDeltaBuffer, 0x00, 1244160);
				video_set_mode(videomodes);
                break;
            case 8:
                savedata.vsync = !savedata.vsync;
                video_set_mode(videomodes);
                break;
            case 9:
                savedata.fpslimit = !savedata.fpslimit; // Kratus (01-2023) Added a FPS limit option in the video settings
                break;
            case 10:
                video_stretch((savedata.stretch ^= 1));
                break;
#endif
#endif
            default:
                quit = 1;
            }
        }
    }
    savesettings();
    bothnewkeys = 0;
    screen_status &= ~IN_SCREEN_VIDEO_OPTIONS_MENU;
}


void menu_options()
{
    #define OPT_Y_POS          -1
    #define OPT_X_POS          -7
    
    typedef enum {
        VIDEO_OPTION,
        SOUND_OPTION,
        CONTROL_OPTION,
        SYSTEM_OPTION,

        END_OPTION
    } e_selector;

    int quit = 0;
    int y_offset = OPT_Y_POS;
    int BACK_OPTION = END_OPTION;
    e_selector selector = VIDEO_OPTION;

    screen_status |= IN_SCREEN_OPTIONS_MENU;
    bothnewkeys = 0;    
    
    while(!quit)
    {        
        _menutextm((selector == VIDEO_OPTION), y_offset+VIDEO_OPTION, 0, Tr("Video Options..."));
        _menutextm((selector == SOUND_OPTION), y_offset+SOUND_OPTION, 0, Tr("Sound Options..."));
        _menutextm((selector == CONTROL_OPTION), y_offset+CONTROL_OPTION, 0, Tr("Control Options..."));
        _menutextm((selector == SYSTEM_OPTION), y_offset+SYSTEM_OPTION, 0, Tr("System Options..."));
               
        _menutextm((selector == BACK_OPTION), y_offset+BACK_OPTION+2, 0, Tr("Back"));

        update((level != NULL), 0);

        if(bothnewkeys & FLAG_ESC)
        {
            quit = 1;
        }
        if(bothnewkeys & FLAG_MOVEUP)
        {
            if(selector <= VIDEO_OPTION)
            {
                selector = BACK_OPTION;
            }
            else --selector;

            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(bothnewkeys & FLAG_MOVEDOWN)
        {
            ++selector;
            if(selector > BACK_OPTION)
            {
                selector = VIDEO_OPTION;
            }

            if(global_sample_list.beep >= 0)
            {
                sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
            }
        }
        if(bothnewkeys & (FLAG_MOVELEFT | FLAG_MOVERIGHT | FLAG_ANYBUTTON))
        {

            if(global_sample_list.beep_2 >= 0)
            {
                sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
            }

                if(selector==BACK_OPTION) quit = 1;
            else if(selector==VIDEO_OPTION) menu_options_video();
            else if(selector==SOUND_OPTION) menu_options_sound();
            else if(selector==CONTROL_OPTION) menu_options_input();
            else if(selector==SYSTEM_OPTION)
            {
                menu_options_system();                
            }
            
            else quit = 1;
        }
    }
    savesettings();
    if(_pause == 1)
    {
        _pause = 2;
    }
    bothnewkeys = 0;
    screen_status &= ~IN_SCREEN_OPTIONS_MENU;

    #undef TOT_CHEATS
    #undef OPT_Y_POS
    #undef OPT_X_POS
    #undef CHEAT_PAUSE_POSY
}

// ----------------------------------------------------------------------------


void openborMain(int argc, char **argv)
{
    sprite_map = NULL;
    int quit = 0;
    int relback = 1;
    int selector = 0;
    u32 introtime = 0;
    int started = 0;
    char tmpBuff[MAX_BUFFER_LEN] = {""};
    int players[MAX_PLAYERS] = {0, 0, 0, 0};
    int i;
    int argl;

    printf("OpenBoR %s, Compile Date: " __DATE__ "\n\n", VERSION);

    if(argc > 1)
    {
        argl = strlen(argv[1]);
        if(argl > 14 && !memcmp(argv[1], "offscreenkill=", 14))
        {
            DEFAULT_OFFSCREEN_KILL = getValidInt((char *)argv[1] + 14, "", "");
        }
        if(argl > 14 && !memcmp(argv[1], "showfilesused=", 14))
        {
            printFileUsageStatistics = getValidInt((char *)argv[1] + 14, "", "");
        }
    }


    modelcmdlist = createModelCommandList();
    modelstxtcmdlist = createModelstxtCommandList();
    levelcmdlist = createLevelCommandList();
    levelordercmdlist = createLevelOrderCommandList();
    createModelList();

    // Load necessary components.
    printf("Game Selected: %s\n\n", packfile);
    loadsettings();
    startup();
	bothnewkeys = 0;

    if(skiptoset < 0)
    {

        // New alternative background path for PSP
        if(custBkgrds != NULL)
        {
            strcpy(tmpBuff, custBkgrds);
            strcat(tmpBuff, "logo");
            load_background(tmpBuff);
        }
        else
        {
            load_cached_background("data/bgs/logo");
        }

        while(_time < GAME_SPEED * 6 && !(bothnewkeys & (FLAG_ANYBUTTON | FLAG_ESC)))
        {
            update(0, 0);
        }

        music("data/music/remix", 1, 0);

        // New alternative scene path for PSP
        if(custScenes != NULL)
        {
            strcpy(tmpBuff, custScenes);
            strcat(tmpBuff, "logo.txt");
            playscene(tmpBuff);
        }
        else
        {
            playscene("data/scenes/logo.txt");
        }
    }
    clearscreen(background);

    while(!quit)
    {
        if(skiptoset < 0 && !(goto_mainmenu_flag&8))
        {
            if(_time >= introtime)
            {
                // New alternative scene path for PSP
                if(custScenes != NULL)
                {
                    strcpy(tmpBuff, custScenes);
                    strcat(tmpBuff, "intro.txt");
                    playscene(tmpBuff);
                }
                else
                {
                    playscene("data/scenes/intro.txt");
                }
                update(0, 0);
                introtime = _time + GAME_SPEED * 20;
                relback = 1;
                started = 0;
            }

            if(bothnewkeys & FLAG_ESC)
            {
                quit = 1;
            }
        }
        else
        {
            started = 1;
            relback = 0;
        }

        if (goto_mainmenu_flag != 0) goto_mainmenu_flag = 0;
        if(!started)
        {
            if((_time % GAME_SPEED) < (GAME_SPEED / 2))
            {
                _menutextm(0, 0, 0, Tr("PRESS START"));
            }
            if(bothnewkeys & (FLAG_ANYBUTTON))
            {
                started = 1;
                relback = 1;
            }
        }
        else if(skiptoset >= 0)
        {
            loadGameFile();
            playgame(players, useSet >= 0 ? useSet : skiptoset, useSave);
        }
        else
        {
            _menutextm((selector == 0), 2, 0, Tr("Start Game"));
            _menutextm((selector == 1), 3, 0, Tr("Options"));
            _menutextm((selector == 2), 4, 0, Tr("How To Play"));
            _menutextm((selector == 3), 5, 0, Tr("Hall Of Fame"));
            _menutextm((selector == 4), 6, 0, Tr("Quit"));
            if(selector < 0)
            {
                selector = 4;
            }
            if(selector > 4)
            {
                selector = 0;
            }

            if(bothnewkeys)
            {
                introtime = _time + GAME_SPEED * 20;
            }

            if(bothnewkeys & FLAG_MOVEUP)
            {
                --selector;
                if(global_sample_list.beep >= 0)
                {
                    sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
                }
            }
            if(bothnewkeys & FLAG_MOVEDOWN)
            {
                ++selector;
                if(global_sample_list.beep >= 0)
                {
                    sound_play_sample(global_sample_list.beep, 0, savedata.effectvol, savedata.effectvol, 100);
                }
            }
            if(bothnewkeys & (FLAG_ANYBUTTON))
            {
                if(global_sample_list.beep_2 >= 0)
                {
                    sound_play_sample(global_sample_list.beep_2, 0, savedata.effectvol, savedata.effectvol, 100);
                }
                switch(selector)
                {
                case 0:
                    for(i = 0; i < MAX_PLAYERS; i++)
                    {
                        players[i] = player[i].newkeys & (FLAG_ANYBUTTON);
                    }
                    relback = choose_mode(players);
                    if(relback)
                    {
                        started = 0;
                    }
                    break;
                case 1:
                    menu_options();
                    break;
                case 2:
                {
                    int previousLoop = musicloop;
                    char previousMusic[sizeof(currentmusic)];

                    memset(previousMusic, 0, sizeof(previousMusic));
                    strcpy(previousMusic, currentmusic);

                    if(custScenes != NULL)
                    {
                        strcpy(tmpBuff, custScenes);
                        strcat(tmpBuff, "howto.txt");
                        playscene(tmpBuff);
                    }
                    else
                    {
                        playscene("data/scenes/howto.txt");
                    }
                    if(stricmp(previousMusic, currentmusic) != 0)
                    {
                        music(previousMusic, previousLoop, 0);
                    }
                    relback = 1;
                    break;
                }
                case 3:
                    hallfame(0);
                    relback = 1;
                    break;
                default:
                    quit = 1;
                    break;
                }
                introtime = _time + GAME_SPEED * 20;
            }
        }
        if(relback)
        {
            if(started)
            {
                /* 
                    Kratus (10-2021) Added an additional instance of the translation function at menu screen
                    Used to refresh all text without close and reopen the engine

                    Kratus (02-2023) Added an additional instance of the video_mode function at menu screen
                    Used to refresh the touchscreen properties on Android without restarting
                */
                video_set_mode(videomodes);
                ob_inittrans();
                
                screen_status |= IN_SCREEN_MENU;
                screen_status &= ~IN_SCREEN_TITLE;

                if(custBkgrds != NULL)
                {
                    strcpy(tmpBuff, custBkgrds);
                    strcat(tmpBuff, "titleb");
                    load_background(tmpBuff);
                }
                else
                {
                    load_cached_background("data/bgs/titleb");
                }
            }
            else
            {
                screen_status &= ~IN_SCREEN_MENU;
                screen_status |= IN_SCREEN_TITLE;

                if(custBkgrds != NULL)
                {
                    strcpy(tmpBuff, custBkgrds);
                    strcat(tmpBuff, "title");
                    load_background(tmpBuff);
                }
                else
                {
                    load_cached_background("data/bgs/title");
                }
            }

            if(!sound_query_music(NULL, NULL))
            {
                music("data/music/remix", 1, 0);
            }
            relback = 0;
        }
        update(0, 0);
    }
    borShutdown(0, DEFAULT_SHUTDOWN_MESSAGE);
}

#undef GET_ARG
#undef GET_ARG_LEN
#undef GET_ARGP
#undef GET_ARGP_LEN
#undef GET_INT_ARG
#undef GET_FLOAT_ARG
#undef GET_INT_ARGP
#undef GET_FLOAT_ARGP
