#pragma once
enum UITYPE:float {
	EMPTY,
	LIST,
	TEXT,
	PICTURE,
	AVATAR,
};

enumflags UIFLAG:float {
	STRETCH,
	HIDE,
	CLICKABLE,
	SELECTABLE,
	KEEPSELECT,
	HIGHLIGHT,
	FORCECURSOR,
	CLIP,
	INIT,
	SETALPHA,
	MULTALPHA,
	IGNORECONSTRUCTOR,
	IGNORELINKING,
};

class uielement_c;

// FIXME: move this someplace else
union cm2_argument {
	int i;
	float f;
	string s;
	vector v;
	void *ptr;
};
#define CM2_COMMAND_MAX_ARGS 8
typedef struct cm2_commandcache_s {
	uielement_c subject;
	void(uielement_c, cm2_argument*) func;
	cm2_argument arguments[CM2_COMMAND_MAX_ARGS];
	cm2_commandcache_s *next;
} cm2_commandcache_t;
void CM2_BuildCommandChain(uielement_c this, __inout cm2_commandcache_t *cache, string cmdstr);
void CM2_ExecuteCommandChain(cm2_commandcache_t *cmd);
void CM2_FlushCommandChain(cm2_commandcache_t *cache);
void CM2_ExecuteString(uielement_c this, string cmd);
void CM2_Initialize(void);
hashtable cm2_globalnames;
hashtable cm2_localnames; // global that points to current local board to populate
#define CM2_FONTNAME_PREFIX "__font_"
#define CM2_FONTPATH_PREFIX "__fontpath_"
#define CM2_FONTNAME(n) (strcat(CM2_FONTNAME_PREFIX, n))
#define CM2_FONTPATH(n) (strcat(CM2_FONTPATH_PREFIX, n))
#define CM2_UNFONTNAME(n) (substring(n, strlen(CM2_FONTNAME_PREFIX), -1))
#define CM2_UNFONTPATH(n) (substring(n, strlen(CM2_FONTPATH_PREFIX), -1))

typedef struct cm2_stat_string_s {
	cm2_argument dat;
	int is_token;
	int version;
	cm2_stat_string_s *next;
} cm2_stat_string_t;

typedef struct {
	string value;
	int version;
} cm2_stat_cache_t;
#ifdef CSQC
hashtable cm2_hudstats_lookup;
void strstats_clear_buf(__inout cm2_stat_string_t *buf);
void strstats_to_buf(string strbody, __inout cm2_stat_string_t *buf)
string strstats_buf_to_str(__inout cm2_stat_string_t* buf);
int strstats_needchange(__in cm2_stat_string_t* lst);
float cm2_frametime;
#endif
#ifdef MENU
#define cltime time
#endif

// partnerlist define is a hack to avoid the compiler exploding :/
#define PARTNERLIST(x) local .uielement_c x[] = {uielement_c::partner, uielement_c::partner2, uielement_c::partner3, uielement_c::partner4, uielement_c::partner5, uielement_c::partner6, uielement_c::partner7, uielement_c::partner8, uielement_c::partner9};
#define INBOUNDS(point, mins, maxs)		((point[0] >= mins[0] && point[0] <= maxs[0]) && (point[1] >= mins[1] && point[1] <= maxs[1]))
vector ui_clipstart;
vector ui_clipsize;
vector ui_mouseposition;
vector ui_position;
vector ui_scale;
float ui_alpha;
uielement_c ui_selected;
uielement_c ui_hover;
uielement_c ui_oldhover;
uielement_c ui_screen_queue; // ui elements without an owner, aka parented to the screen overall
uielement_c ui_mouse_hover;
float ui_usemouse;
float ui_oldusemouse;
float ui_renderframe;

int autocvar_mg_debug_textbox;

#define ALIAS_CONSTRUCTOR(x) nonvirtual void() x { if (this.flags & UIFLAG::IGNORECONSTRUCTOR) return; if (this.classname != #x) return; this._init(); }

#ifdef CSQC
uielement_c ui_hud;
vector autocvar_scr_hudborder;
float autocvar_scr_hudscale;
#endif

#ifdef CSQC
#define frametime clframetime
#endif

//////// UI Element classes //////////////////////////////////
// this replaces the original hardcoded ui system.          //
// each element is an entity, and should be initialized     //
// at load time as this system is not optimized for adding  //
// or removing elements on the fly                          //
//////////////////////////////////////////////////////////////

class uielement_c
{
	// First we need the QuakeC specific part of the class
	float type;
	UIFLAG flags;

	uielement_c _list; // elements that I "own"
	uielement_c owner; // element that owns me, if any
	uielement_c _next; // the element after me in my owner's list

	uielement_c partner; // elements that I want to reference
	uielement_c partner2;
	uielement_c partner3;
	uielement_c partner4;
	uielement_c partner5;
	uielement_c partner6;
	uielement_c partner7;
	uielement_c partner8;
	uielement_c partner9;

	float cm2_namespace; // uh... this crashes the compiler if I make it type "hashtable" even though that's just a float under a macro...

	// Now, generate our shared data
	/*[[[cog
	import os
	import sys
	sys.path.append(os.getcwd())
	import element_utilities

	element_utilities.generate_element('../schemas/uielement_schema.json')
	]]]*/
	/* #region GENERATED SECTION, DO NOT HAND EDIT */

	// [ Variable Declarations ]
	string hoverText; // string
	#ifdef CSQC
	cm2_stat_string_t* hoverText_strlist; // pointer
	#endif // CSQC
	string name; // string
	#ifdef CSQC
	cm2_stat_string_t* name_strlist; // pointer
	#endif // CSQC
	int isGlobal; // bool
	#ifdef CSQC
	cm2_stat_cache_t* isGlobal_stat; // string pointer
	int isGlobal_stat_ver; // string pointer version
	#endif // CSQC
	cm2_stat_cache_t* isVisible_stat; // string pointer
	int isVisible_stat_ver; // string pointer version
	cm2_stat_cache_t* isStretched_stat; // string pointer
	int isStretched_stat_ver; // string pointer version
	vector origin; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* origin_stat; // string pointer
	int origin_stat_ver; // string pointer version
	#endif // CSQC
	vector size; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* size_stat; // string pointer
	int size_stat_ver; // string pointer version
	#endif // CSQC
	vector justify; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* justify_stat; // string pointer
	int justify_stat_ver; // string pointer version
	#endif // CSQC
	vector anchor; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* anchor_stat; // string pointer
	int anchor_stat_ver; // string pointer version
	#endif // CSQC
	vector color; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* color_stat; // string pointer
	int color_stat_ver; // string pointer version
	#endif // CSQC
	float alpha; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* alpha_stat; // string pointer
	int alpha_stat_ver; // string pointer version
	#endif // CSQC
	float mod_col; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* mod_col_stat; // string pointer
	int mod_col_stat_ver; // string pointer version
	#endif // CSQC
	float mod_alp; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* mod_alp_stat; // string pointer
	int mod_alp_stat_ver; // string pointer version
	#endif // CSQC
	string onClick; // event
	string onClick_old; // event
	cm2_commandcache_t *onClick_cmd; // event
	string onUnclick; // event
	string onUnclick_old; // event
	cm2_commandcache_t *onUnclick_cmd; // event
	string onHover; // event
	string onHover_old; // event
	cm2_commandcache_t *onHover_cmd; // event
	string onUnhover; // event
	string onUnhover_old; // event
	cm2_commandcache_t *onUnhover_cmd; // event
	string onTick; // event
	string onTick_old; // event
	cm2_commandcache_t *onTick_cmd; // event

	// [ Data Getter / Setter ]
	nonvirtual void(string val, int usestats=1) _set_hoverText // string
	{
		this.hoverText = val;
		
	#ifdef CSQC
		strstats_clear_buf(this.hoverText_strlist);
		strstats_to_buf(val, this.hoverText_strlist);
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_hoverText // string
	{
		val.s = this.hoverText;
		type = EV_STRING;
	};

	nonvirtual void(string val, int usestats=1) _set_name // string
	{
		this.name = val;
		
	#ifdef CSQC
		strstats_clear_buf(this.name_strlist);
		strstats_to_buf(val, this.name_strlist);
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_name // string
	{
		val.s = this.name;
		type = EV_STRING;
	};

	nonvirtual void(string val, int usestats=1) _set_isGlobal // bool
	{
		this.isGlobal = stoi(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.isGlobal_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.isGlobal_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_isGlobal // bool
	{
		val.i = this.isGlobal;
		type = EV_INTEGER;
	};

	nonvirtual void(string val, int usestats=1) _set_isVisible // bool (bitflag)
	{
		int istrue = stoi(val);
		if (!istrue)
			this.flags |= UIFLAG::HIDE;
		else
			this.flags &= ~UIFLAG::HIDE;
	#ifdef CSQC
		if (usestats)
		{
			this.isVisible_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.isVisible_stat_ver = -1;
		}
	#endif
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_isVisible // bool (bitflag)
	{
		val.i = (this.flags & UIFLAG::HIDE) ? 0 : 1;
		type = EV_INTEGER;
	};

	nonvirtual void(string val, int usestats=1) _set_isStretched // bool (bitflag)
	{
		int istrue = stoi(val);
		if (istrue)
			this.flags |= UIFLAG::STRETCH;
		else
			this.flags &= ~UIFLAG::STRETCH;
	#ifdef CSQC
		if (usestats)
		{
			this.isStretched_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.isStretched_stat_ver = -1;
		}
	#endif
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_isStretched // bool (bitflag)
	{
		val.i = (this.flags & UIFLAG::STRETCH) ? 1 : 0;
		type = EV_INTEGER;
	};

	nonvirtual void(string val, int usestats=1) _set_origin // array
	{
		this.origin = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.origin_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.origin_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_origin_index // array (individual index)
	{
		this.origin[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_origin // array
	{
		val.v = this.origin;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_size // array
	{
		this.size = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.size_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.size_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_size_index // array (individual index)
	{
		this.size[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_size // array
	{
		val.v = this.size;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_justify // array
	{
		this.justify = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.justify_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.justify_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_justify_index // array (individual index)
	{
		this.justify[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_justify // array
	{
		val.v = this.justify;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_anchor // array
	{
		this.anchor = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.anchor_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.anchor_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_anchor_index // array (individual index)
	{
		this.anchor[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_anchor // array
	{
		val.v = this.anchor;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_color // array
	{
		this.color = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.color_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.color_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_color_index // array (individual index)
	{
		this.color[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_color // array
	{
		val.v = this.color;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_alpha // number
	{
		this.alpha = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.alpha_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.alpha_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_alpha // number
	{
		val.f = this.alpha;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_mod_col // number
	{
		this.mod_col = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.mod_col_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.mod_col_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_mod_col // number
	{
		val.f = this.mod_col;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_mod_alp // number
	{
		this.mod_alp = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.mod_alp_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.mod_alp_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_mod_alp // number
	{
		val.f = this.mod_alp;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_onClick // event
	{
		this.onClick = val;
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_onClick // event
	{
		val.s = this.onClick;
		type = EV_STRING;
	};

	nonvirtual void(uielement_c this) cmd_onClick_cm2 // event
	{
		dprint(__func__, ": ", this.onClick, "\n");
		if (!this.onClick)
			return;
		
		if (this.onClick != this.onClick_old)
		{
			dprint("     building new command queue\n");
			CM2_BuildCommandChain(this, this.onClick_cmd, this.onClick);
			this.onClick_old = this.onClick;
		}
		
		if (this.onClick_cmd != __NULL__) // execute the string
		{
			dprint("     executing command\n");
			CM2_ExecuteCommandChain(this.onClick_cmd);
		}
	};

	nonvirtual void(string val, int usestats=1) _set_onUnclick // event
	{
		this.onUnclick = val;
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_onUnclick // event
	{
		val.s = this.onUnclick;
		type = EV_STRING;
	};

	nonvirtual void(uielement_c this) cmd_onUnclick_cm2 // event
	{
		dprint(__func__, ": ", this.onUnclick, "\n");
		if (!this.onUnclick)
			return;
		
		if (this.onUnclick != this.onUnclick_old)
		{
			dprint("     building new command queue\n");
			CM2_BuildCommandChain(this, this.onUnclick_cmd, this.onUnclick);
			this.onUnclick_old = this.onUnclick;
		}
		
		if (this.onUnclick_cmd != __NULL__) // execute the string
		{
			dprint("     executing command\n");
			CM2_ExecuteCommandChain(this.onUnclick_cmd);
		}
	};

	nonvirtual void(string val, int usestats=1) _set_onHover // event
	{
		this.onHover = val;
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_onHover // event
	{
		val.s = this.onHover;
		type = EV_STRING;
	};

	nonvirtual void(uielement_c this) cmd_onHover_cm2 // event
	{
		dprint(__func__, ": ", this.onHover, "\n");
		if (!this.onHover)
			return;
		
		if (this.onHover != this.onHover_old)
		{
			dprint("     building new command queue\n");
			CM2_BuildCommandChain(this, this.onHover_cmd, this.onHover);
			this.onHover_old = this.onHover;
		}
		
		if (this.onHover_cmd != __NULL__) // execute the string
		{
			dprint("     executing command\n");
			CM2_ExecuteCommandChain(this.onHover_cmd);
		}
	};

	nonvirtual void(string val, int usestats=1) _set_onUnhover // event
	{
		this.onUnhover = val;
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_onUnhover // event
	{
		val.s = this.onUnhover;
		type = EV_STRING;
	};

	nonvirtual void(uielement_c this) cmd_onUnhover_cm2 // event
	{
		dprint(__func__, ": ", this.onUnhover, "\n");
		if (!this.onUnhover)
			return;
		
		if (this.onUnhover != this.onUnhover_old)
		{
			dprint("     building new command queue\n");
			CM2_BuildCommandChain(this, this.onUnhover_cmd, this.onUnhover);
			this.onUnhover_old = this.onUnhover;
		}
		
		if (this.onUnhover_cmd != __NULL__) // execute the string
		{
			dprint("     executing command\n");
			CM2_ExecuteCommandChain(this.onUnhover_cmd);
		}
	};

	nonvirtual void(string val, int usestats=1) _set_onTick // event
	{
		this.onTick = val;
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_onTick // event
	{
		val.s = this.onTick;
		type = EV_STRING;
	};

	nonvirtual void(uielement_c this) cmd_onTick_cm2 // event
	{
		dprint(__func__, ": ", this.onTick, "\n");
		if (!this.onTick)
			return;
		
		if (this.onTick != this.onTick_old)
		{
			dprint("     building new command queue\n");
			CM2_BuildCommandChain(this, this.onTick_cmd, this.onTick);
			this.onTick_old = this.onTick;
		}
		
		if (this.onTick_cmd != __NULL__) // execute the string
		{
			dprint("     executing command\n");
			CM2_ExecuteCommandChain(this.onTick_cmd);
		}
	};

	virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty
	{
		switch(_key)
		{
			case "hoverText": _get_hoverText(_val, _type); return TRUE;
			case "name": _get_name(_val, _type); return TRUE;
			case "isGlobal": _get_isGlobal(_val, _type); return TRUE;
			case "isVisible": _get_isVisible(_val, _type); return TRUE;
			case "isStretched": _get_isStretched(_val, _type); return TRUE;
			case "origin": _get_origin(_val, _type); return TRUE;
			case "size": _get_size(_val, _type); return TRUE;
			case "justify": _get_justify(_val, _type); return TRUE;
			case "anchor": _get_anchor(_val, _type); return TRUE;
			case "color": _get_color(_val, _type); return TRUE;
			case "alpha": _get_alpha(_val, _type); return TRUE;
			case "mod_col": _get_mod_col(_val, _type); return TRUE;
			case "mod_alp": _get_mod_alp(_val, _type); return TRUE;
			case "onClick": _get_onClick(_val, _type); return TRUE;
			case "onUnclick": _get_onUnclick(_val, _type); return TRUE;
			case "onHover": _get_onHover(_val, _type); return TRUE;
			case "onUnhover": _get_onUnhover(_val, _type); return TRUE;
			case "onTick": _get_onTick(_val, _type); return TRUE;
			default: break;
		}
		return FALSE;
	};

	virtual int(string _key, string _value) _setproperty
	{
		switch(_key)
		{
			// hoverText (string)
			case "hoverText": _set_hoverText(_value); return TRUE;
			// name (string)
			case "name": _set_name(_value); return TRUE;
			// isGlobal (boolean)
			case "isGlobal": _set_isGlobal(_value); return TRUE;
			// isVisible (boolean)
			case "isVisible": _set_isVisible(_value); return TRUE;
			// isStretched (boolean)
			case "isStretched": _set_isStretched(_value); return TRUE;
			// origin (array)
			case "origin": _set_origin(_value); return TRUE;
			case "origin[0]": _set_origin_index(_value, 0); return TRUE;
			case "origin[1]": _set_origin_index(_value, 1); return TRUE;
			case "origin_x": _set_origin_index(_value, 0); return TRUE;
			case "origin_y": _set_origin_index(_value, 1); return TRUE;
			// size (array)
			case "size": _set_size(_value); return TRUE;
			case "size[0]": _set_size_index(_value, 0); return TRUE;
			case "size[1]": _set_size_index(_value, 1); return TRUE;
			case "size_x": _set_size_index(_value, 0); return TRUE;
			case "size_y": _set_size_index(_value, 1); return TRUE;
			// justify (array)
			case "justify": _set_justify(_value); return TRUE;
			case "justify[0]": _set_justify_index(_value, 0); return TRUE;
			case "justify[1]": _set_justify_index(_value, 1); return TRUE;
			case "justify_x": _set_justify_index(_value, 0); return TRUE;
			case "justify_y": _set_justify_index(_value, 1); return TRUE;
			// anchor (array)
			case "anchor": _set_anchor(_value); return TRUE;
			case "anchor[0]": _set_anchor_index(_value, 0); return TRUE;
			case "anchor[1]": _set_anchor_index(_value, 1); return TRUE;
			case "anchor_x": _set_anchor_index(_value, 0); return TRUE;
			case "anchor_y": _set_anchor_index(_value, 1); return TRUE;
			// color (array)
			case "color": _set_color(_value); return TRUE;
			case "color[0]": _set_color_index(_value, 0); return TRUE;
			case "color[1]": _set_color_index(_value, 1); return TRUE;
			case "color[2]": _set_color_index(_value, 2); return TRUE;
			case "color_x": _set_color_index(_value, 0); return TRUE;
			case "color_y": _set_color_index(_value, 1); return TRUE;
			case "color_z": _set_color_index(_value, 2); return TRUE;
			// alpha (number)
			case "alpha": _set_alpha(_value); return TRUE;
			// mod_col (number)
			case "mod_col": _set_mod_col(_value); return TRUE;
			// mod_alp (number)
			case "mod_alp": _set_mod_alp(_value); return TRUE;
			// onClick (event)
			case "onClick": _set_onClick(_value); return TRUE;
			// onUnclick (event)
			case "onUnclick": _set_onUnclick(_value); return TRUE;
			// onHover (event)
			case "onHover": _set_onHover(_value); return TRUE;
			// onUnhover (event)
			case "onUnhover": _set_onUnhover(_value); return TRUE;
			// onTick (event)
			case "onTick": _set_onTick(_value); return TRUE;
			default: break;
		}
		return FALSE;
	};

	#ifdef CSQC
	virtual void() _preprocess
	{
		if (this.hoverText_strlist) { if (strstats_needchange(this.hoverText_strlist)) { this.hoverText = strstats_buf_to_str(this.hoverText_strlist); } }
		if (this.name_strlist) { if (strstats_needchange(this.name_strlist)) { this.name = strstats_buf_to_str(this.name_strlist); } }
		if (this.isGlobal_stat)
		{
			if (this.isGlobal_stat_ver != this.isGlobal_stat->version) { _set_isGlobal(this.isGlobal_stat->value, 0); this.isGlobal_stat_ver = this.isGlobal_stat->version; }
		}
		if (this.isVisible_stat)
		{
			if (this.isVisible_stat_ver != this.isVisible_stat->version) { _set_isVisible(this.isVisible_stat->value, 0); this.isVisible_stat_ver = this.isVisible_stat->version; }
		}
		if (this.isStretched_stat)
		{
			if (this.isStretched_stat_ver != this.isStretched_stat->version) { _set_isStretched(this.isStretched_stat->value, 0); this.isStretched_stat_ver = this.isStretched_stat->version; }
		}
		if (this.origin_stat)
		{
			if (this.origin_stat_ver != this.origin_stat->version) { _set_origin(this.origin_stat->value, 0); this.origin_stat_ver = this.origin_stat->version; }
		}
		if (this.size_stat)
		{
			if (this.size_stat_ver != this.size_stat->version) { _set_size(this.size_stat->value, 0); this.size_stat_ver = this.size_stat->version; }
		}
		if (this.justify_stat)
		{
			if (this.justify_stat_ver != this.justify_stat->version) { _set_justify(this.justify_stat->value, 0); this.justify_stat_ver = this.justify_stat->version; }
		}
		if (this.anchor_stat)
		{
			if (this.anchor_stat_ver != this.anchor_stat->version) { _set_anchor(this.anchor_stat->value, 0); this.anchor_stat_ver = this.anchor_stat->version; }
		}
		if (this.color_stat)
		{
			if (this.color_stat_ver != this.color_stat->version) { _set_color(this.color_stat->value, 0); this.color_stat_ver = this.color_stat->version; }
		}
		if (this.alpha_stat)
		{
			if (this.alpha_stat_ver != this.alpha_stat->version) { _set_alpha(this.alpha_stat->value, 0); this.alpha_stat_ver = this.alpha_stat->version; }
		}
		if (this.mod_col_stat)
		{
			if (this.mod_col_stat_ver != this.mod_col_stat->version) { _set_mod_col(this.mod_col_stat->value, 0); this.mod_col_stat_ver = this.mod_col_stat->version; }
		}
		if (this.mod_alp_stat)
		{
			if (this.mod_alp_stat_ver != this.mod_alp_stat->version) { _set_mod_alp(this.mod_alp_stat->value, 0); this.mod_alp_stat_ver = this.mod_alp_stat->version; }
		}
	};
	#endif // CSQC
	nonvirtual void(uielement_c, cm2_argument*) cmd_setProperty;
	nonvirtual void(uielement_c, cm2_argument*) cmd_toggleVisible;
	nonvirtual void(uielement_c, cm2_argument*) cmd_highlight;
	nonvirtual void(uielement_c, cm2_argument*) cmd_unhighlight;
	nonvirtual void(uielement_c, cm2_argument*) cmd_executeCommand;
	nonvirtual void(uielement_c, cm2_argument*) cmd_playSound;
	virtual int(cm2_commandcache_t *_cmd, string _cmdstr) _string_to_commandfunc
	{
		switch(_cmdstr)
		{
			case "setProperty":
				_cmd->func = cmd_setProperty;
				_cmd->arguments[0].s = (argv(1));
				_cmd->arguments[1].s = (argv(2));
				return TRUE;
			case "toggleVisible":
				_cmd->func = cmd_toggleVisible;
				return TRUE;
			case "highlight":
				_cmd->func = cmd_highlight;
				return TRUE;
			case "unhighlight":
				_cmd->func = cmd_unhighlight;
				return TRUE;
			case "executeCommand":
				_cmd->func = cmd_executeCommand;
				_cmd->arguments[0].s = (argv(1));
				return TRUE;
			case "playSound":
				_cmd->func = cmd_playSound;
				_cmd->arguments[0].s = (argv(1));
				_cmd->arguments[1].f = stof(argv(2));
				_cmd->arguments[2].f = stof(argv(3));
				return TRUE;
		}
		return FALSE;
	};
	/* #endregion END GENERATED SECTION */
	//[[[end]]]

	// generic linked list
	nonvirtual void() link_to_owner {
		if (!owner) // link to screen render queue
		{
			if (!ui_screen_queue)
			{
				ui_screen_queue = this;
				return;
			}
			
			uielement_c h, e;
			for(e = ui_screen_queue; e; h = e, e = e._next); // find end of list
			h._next = this; // link into list at end
			this._next = __NULL__;
			return;
		}

		if (!owner._list) // if owner list is __NULL__, we're the head of the list
		{
			owner._list = this;
			return;
		}
		
		uielement_c h, e;
		for(e = owner._list; e; h = e, e = e._next); // find end of list
		h._next = this; // link into list at end
		this._next = __NULL__;
	};

	// totalsize
	virtual vector() totalsize {
		return [size[0] * ui_scale[0], size[1] * ui_scale[1]];
	};

	var virtual void(uielement_c this, __inout vector position) preloop; // called before looping
	var virtual void(uielement_c this, __inout vector position) loopthrough; // called when looping through children for rendering

	// if present, returns true if we should draw
	var virtual float(uielement_c this) active_condition;

	// mouse is over button
	var virtual void(uielement_c this) m_over {
		if !(flags & UIFLAG::CLICKABLE)
			return;
		
		if (flags & UIFLAG::HIGHLIGHT)
		{
			mod_col = 1.2;
			mod_alp = 1.1;
		}
	};

	// mouse was over button, but now has moved
	var virtual void(uielement_c this) m_leave { 
		if (flags & UIFLAG::HIGHLIGHT)
		{
			mod_col = 1;
			mod_alp = 1;
		}
	};

	// mouse clicked on the button
	var virtual void(uielement_c this) m_click;
	var virtual void(uielement_c this) m_unclick;

	// general input entrypoint
	var virtual int(uielement_c this, float nkey, float ascii, float released) input;

	// renderer restarted
	var virtual float(uielement_c this) renderer_restarted;

	// render our element
	virtual void() render {
		return; // stub
	};

	// constructor
	virtual void() _init {
		if (flags & UIFLAG::INIT)
			return;
		
		flags |= UIFLAG::INIT;
		if (!color)
			color = '1 1 1';
		if (!alpha)
			alpha = 1.0;
		if (onClick && !m_click)
		{
			m_click = cmd_onClick_cm2;
			flags |= UIFLAG::CLICKABLE;
		}
		if (onUnclick && !m_unclick)
		{
			m_unclick = cmd_onUnclick_cm2;
			flags |= UIFLAG::CLICKABLE;
		}
		if (onHover && !m_over)
		{
			m_over = cmd_onHover_cm2;
			flags |= UIFLAG::CLICKABLE;
		}
		if (onUnhover && !m_leave)
		{
			m_leave = cmd_onUnhover_cm2;
			flags |= UIFLAG::CLICKABLE;
		}

		mod_col = 1;
		mod_alp = 1;
		if !(this.flags & UIFLAG::IGNORELINKING)
			link_to_owner();
	};
	ALIAS_CONSTRUCTOR(uielement_c);
};

////////////////
//  List Element
////////////////
class uilist_c : uielement_c
{
	float elements;
	float elements_temp;

	// Now, generate our shared data
	/*[[[cog
	import os
	import sys
	sys.path.append(os.getcwd())
	import element_utilities

	element_utilities.generate_element('../schemas/uilist_schema.json')
	]]]*/
	/* #region GENERATED SECTION, DO NOT HAND EDIT */

	// [ Variable Declarations ]
	float scroll_count; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* scroll_count_stat; // string pointer
	int scroll_count_stat_ver; // string pointer version
	#endif // CSQC
	float scroll; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* scroll_stat; // string pointer
	int scroll_stat_ver; // string pointer version
	#endif // CSQC
	vector separation; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* separation_stat; // string pointer
	int separation_stat_ver; // string pointer version
	#endif // CSQC

	// [ Data Getter / Setter ]
	nonvirtual void(string val, int usestats=1) _set_scroll_count // number
	{
		this.scroll_count = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.scroll_count_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.scroll_count_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_scroll_count // number
	{
		val.f = this.scroll_count;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_scroll // number
	{
		this.scroll = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.scroll_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.scroll_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_scroll // number
	{
		val.f = this.scroll;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_separation // array
	{
		this.separation = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.separation_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.separation_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_separation_index // array (individual index)
	{
		this.separation[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_separation // array
	{
		val.v = this.separation;
		type = EV_VECTOR;
	};

	virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty
	{
		switch(_key)
		{
			case "scroll_count": _get_scroll_count(_val, _type); return TRUE;
			case "scroll": _get_scroll(_val, _type); return TRUE;
			case "separation": _get_separation(_val, _type); return TRUE;
			default: break;
		}
		if (super::_getproperty(_key, _val, _type))
			return TRUE;
		return FALSE;
	};

	virtual int(string _key, string _value) _setproperty
	{
		switch(_key)
		{
			// scroll_count (number)
			case "scroll_count": _set_scroll_count(_value); return TRUE;
			// scroll (number)
			case "scroll": _set_scroll(_value); return TRUE;
			// separation (array)
			case "separation": _set_separation(_value); return TRUE;
			case "separation[0]": _set_separation_index(_value, 0); return TRUE;
			case "separation[1]": _set_separation_index(_value, 1); return TRUE;
			case "separation_x": _set_separation_index(_value, 0); return TRUE;
			case "separation_y": _set_separation_index(_value, 1); return TRUE;
			default: break;
		}
		if (super::_setproperty(_key, _value))
			return TRUE;
		return FALSE;
	};

	#ifdef CSQC
	virtual void() _preprocess
	{
		super::_preprocess();
		if (this.scroll_count_stat)
		{
			if (this.scroll_count_stat_ver != this.scroll_count_stat->version) { _set_scroll_count(this.scroll_count_stat->value, 0); this.scroll_count_stat_ver = this.scroll_count_stat->version; }
		}
		if (this.scroll_stat)
		{
			if (this.scroll_stat_ver != this.scroll_stat->version) { _set_scroll(this.scroll_stat->value, 0); this.scroll_stat_ver = this.scroll_stat->version; }
		}
		if (this.separation_stat)
		{
			if (this.separation_stat_ver != this.separation_stat->version) { _set_separation(this.separation_stat->value, 0); this.separation_stat_ver = this.separation_stat->version; }
		}
	};
	#endif // CSQC
	/* #endregion END GENERATED SECTION */
	//[[[end]]]
	
	// preloop
	nonvirtual void(uielement_c this, __inout vector position) list_preloop {
		elements = elements_temp;
		elements_temp = 0;
		vector scaled_sep = [separation[0] * ui_scale[0], separation[1] * ui_scale[1]];

		if (separation[0])
			scroll_count = floor(size[0] / separation[0]);
		else if (separation[1])
			scroll_count = floor(size[1] / separation[1]);

		if (elements > scroll_count)
		{
			scroll = bound(0, scroll, elements - scroll_count);
			position += scaled_sep * -scroll;
		}
		else
		{
			scroll = 0;
		}
	};

	// loopthrough
	nonvirtual void(uielement_c this, __inout vector position) list_loopthrough {
		vector scaled_sep = [separation[0] * ui_scale[0], separation[1] * ui_scale[1]];
		position += scaled_sep;
		elements_temp++;
	};

	// constructor
	virtual void() _init {
		super::_init();

		type = UITYPE::LIST;
		if (preloop == __NULL__)
			preloop = list_preloop;
		if (loopthrough == __NULL__)
			loopthrough = list_loopthrough;
	};
	ALIAS_CONSTRUCTOR(uilist_c);
};

class uismoothlist_c : uilist_c
{
	// Now, generate our shared data
	/*[[[cog
	import os
	import sys
	sys.path.append(os.getcwd())
	import element_utilities

	element_utilities.generate_element('../schemas/uismoothlist_schema.json')
	]]]*/
	/* #region GENERATED SECTION, DO NOT HAND EDIT */

	// [ Variable Declarations ]
	float smooth_speed; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* smooth_speed_stat; // string pointer
	int smooth_speed_stat_ver; // string pointer version
	#endif // CSQC
	float scroll_smooth; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* scroll_smooth_stat; // string pointer
	int scroll_smooth_stat_ver; // string pointer version
	#endif // CSQC

	// [ Data Getter / Setter ]
	nonvirtual void(string val, int usestats=1) _set_smooth_speed // number
	{
		this.smooth_speed = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.smooth_speed_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.smooth_speed_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_smooth_speed // number
	{
		val.f = this.smooth_speed;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_scroll_smooth // number
	{
		this.scroll_smooth = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.scroll_smooth_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.scroll_smooth_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_scroll_smooth // number
	{
		val.f = this.scroll_smooth;
		type = EV_FLOAT;
	};

	virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty
	{
		switch(_key)
		{
			case "smooth_speed": _get_smooth_speed(_val, _type); return TRUE;
			case "scroll_smooth": _get_scroll_smooth(_val, _type); return TRUE;
			default: break;
		}
		if (super::_getproperty(_key, _val, _type))
			return TRUE;
		return FALSE;
	};

	virtual int(string _key, string _value) _setproperty
	{
		switch(_key)
		{
			// smooth_speed (number)
			case "smooth_speed": _set_smooth_speed(_value); return TRUE;
			// scroll_smooth (number)
			case "scroll_smooth": _set_scroll_smooth(_value); return TRUE;
			default: break;
		}
		if (super::_setproperty(_key, _value))
			return TRUE;
		return FALSE;
	};

	#ifdef CSQC
	virtual void() _preprocess
	{
		super::_preprocess();
		if (this.smooth_speed_stat)
		{
			if (this.smooth_speed_stat_ver != this.smooth_speed_stat->version) { _set_smooth_speed(this.smooth_speed_stat->value, 0); this.smooth_speed_stat_ver = this.smooth_speed_stat->version; }
		}
		if (this.scroll_smooth_stat)
		{
			if (this.scroll_smooth_stat_ver != this.scroll_smooth_stat->version) { _set_scroll_smooth(this.scroll_smooth_stat->value, 0); this.scroll_smooth_stat_ver = this.scroll_smooth_stat->version; }
		}
	};
	#endif // CSQC
	/* #endregion END GENERATED SECTION */
	//[[[end]]]

	// preloop
	nonvirtual void(uielement_c this, __inout vector position) list_preloop_smooth {
		elements = elements_temp;
		elements_temp = 0;
		vector scaled_sep = [separation[0] * ui_scale[0], separation[1] * ui_scale[1]];
		
		if (separation[0])
			scroll_count = floor(size[0] / separation[0]);
		else if (separation[1])
			scroll_count = floor(size[1] / separation[1]);

		if (elements > scroll_count)
		{
			scroll = bound(0, scroll, elements - scroll_count);
		}
		else
		{
			scroll = 0;
		}

		position += scaled_sep * -scroll_smooth;
	};

	virtual void() render {
		if (scroll_smooth > scroll)
			scroll_smooth = max(scroll, scroll_smooth - (smooth_speed * frametime));
		else if (scroll_smooth < scroll)
			scroll_smooth = min(scroll, scroll_smooth + (smooth_speed * frametime));
	};

	// constructor
	virtual void() _init {
		super::_init();

		type = UITYPE::LIST;
		if (!smooth_speed)
			smooth_speed = 20;
		preloop = list_preloop_smooth;
	};
	ALIAS_CONSTRUCTOR(uismoothlist_c);
};

////////////////
// Image Element
////////////////
class uipicture_c : uielement_c
{
	// Now, generate our shared data
	/*[[[cog
	import os
	import sys
	sys.path.append(os.getcwd())
	import element_utilities

	element_utilities.generate_element('../schemas/uipicture_schema.json')
	]]]*/
	/* #region GENERATED SECTION, DO NOT HAND EDIT */

	// [ Variable Declarations ]
	string image; // string
	#ifdef CSQC
	cm2_stat_string_t* image_strlist; // pointer
	#endif // CSQC
	string image2; // string
	#ifdef CSQC
	cm2_stat_string_t* image2_strlist; // pointer
	#endif // CSQC
	float drawflag; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* drawflag_stat; // string pointer
	int drawflag_stat_ver; // string pointer version
	#endif // CSQC
	float angle; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* angle_stat; // string pointer
	int angle_stat_ver; // string pointer version
	#endif // CSQC

	// [ Data Getter / Setter ]
	nonvirtual void(string val, int usestats=1) _set_image // string
	{
		this.image = val;
		
	#ifdef CSQC
		strstats_clear_buf(this.image_strlist);
		strstats_to_buf(val, this.image_strlist);
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_image // string
	{
		val.s = this.image;
		type = EV_STRING;
	};

	nonvirtual void(string val, int usestats=1) _set_image2 // string
	{
		this.image2 = val;
		
	#ifdef CSQC
		strstats_clear_buf(this.image2_strlist);
		strstats_to_buf(val, this.image2_strlist);
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_image2 // string
	{
		val.s = this.image2;
		type = EV_STRING;
	};

	nonvirtual void(string val, int usestats=1) _set_drawflag // number
	{
		this.drawflag = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.drawflag_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.drawflag_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_drawflag // number
	{
		val.f = this.drawflag;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_angle // number
	{
		this.angle = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.angle_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.angle_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_angle // number
	{
		val.f = this.angle;
		type = EV_FLOAT;
	};

	virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty
	{
		switch(_key)
		{
			case "image": _get_image(_val, _type); return TRUE;
			case "image2": _get_image2(_val, _type); return TRUE;
			case "drawflag": _get_drawflag(_val, _type); return TRUE;
			case "angle": _get_angle(_val, _type); return TRUE;
			default: break;
		}
		if (super::_getproperty(_key, _val, _type))
			return TRUE;
		return FALSE;
	};

	virtual int(string _key, string _value) _setproperty
	{
		switch(_key)
		{
			// image (string)
			case "image": _set_image(_value); return TRUE;
			// image2 (string)
			case "image2": _set_image2(_value); return TRUE;
			// drawflag (number)
			case "drawflag": _set_drawflag(_value); return TRUE;
			// angle (number)
			case "angle": _set_angle(_value); return TRUE;
			default: break;
		}
		if (super::_setproperty(_key, _value))
			return TRUE;
		return FALSE;
	};

	#ifdef CSQC
	virtual void() _preprocess
	{
		super::_preprocess();
		if (this.image_strlist) { if (strstats_needchange(this.image_strlist)) { this.image = strstats_buf_to_str(this.image_strlist); } }
		if (this.image2_strlist) { if (strstats_needchange(this.image2_strlist)) { this.image2 = strstats_buf_to_str(this.image2_strlist); } }
		if (this.drawflag_stat)
		{
			if (this.drawflag_stat_ver != this.drawflag_stat->version) { _set_drawflag(this.drawflag_stat->value, 0); this.drawflag_stat_ver = this.drawflag_stat->version; }
		}
		if (this.angle_stat)
		{
			if (this.angle_stat_ver != this.angle_stat->version) { _set_angle(this.angle_stat->value, 0); this.angle_stat_ver = this.angle_stat->version; }
		}
	};
	#endif // CSQC
	/* #endregion END GENERATED SECTION */
	//[[[end]]]

	// constructor
	virtual void() _init {
		super::_init();

		type = UITYPE::PICTURE;
		if (image)
			precache_pic(image);
		if (image2)
			precache_pic(image2);
	};
	ALIAS_CONSTRUCTOR(uipicture_c);

	var virtual void(uipicture_c this, __inout string img_to_render) prerender;

	// rendering for fill or pictures
	virtual void() render {
		vector final_color = color * mod_col; 				// 
		float final_alpha = alpha * mod_alp * ui_alpha; 	// handle hightlighting
		vector final_origin = ui_position;
		vector final_size = [size[0] * ui_scale[0], size[1] * ui_scale[1]];
		
		string final_img = image;
		if (prerender)
			prerender(this, final_img);
		if (final_img != __NULL__ && final_alpha > 0)
		{
			#ifdef CSQC
			if (!angle)
			{
			#endif
				drawpic(final_origin, final_img, final_size, final_color, final_alpha, drawflag);
			#ifdef CSQC
			}
			else
			{
				vector slength = final_size / 2;
				vector tl = final_origin + slength;
				vector tr = final_origin + slength;
				vector br = final_origin + slength;
				vector bl = final_origin + slength;
				
				float a = (angle + 180) * DEG2RAD;
				float a2 = (angle + 270) * DEG2RAD;
				tl += [(cos(a) * slength[0]) + (cos(a2) * slength[1]), (sin(a) * slength[0]) + (sin(a2) * slength[1])];
				
				float a = (angle) * DEG2RAD;
				float a2 = (angle + 270) * DEG2RAD;
				tr += [(cos(a) * slength[0]) + (cos(a2) * slength[1]), (sin(a) * slength[0]) + (sin(a2) * slength[1])];

				float a = (angle + 180) * DEG2RAD;
				float a2 = (angle + 90) * DEG2RAD;
				bl += [(cos(a) * slength[0]) + (cos(a2) * slength[1]), (sin(a) * slength[0]) + (sin(a2) * slength[1])];
				
				float a = (angle) * DEG2RAD;
				float a2 = (angle + 90) * DEG2RAD;
				br += [(cos(a) * slength[0]) + (cos(a2) * slength[1]), (sin(a) * slength[0]) + (sin(a2) * slength[1])];



				R_BeginPolygon(final_img, DRAWFLAG_NORMAL, 1);
				R_PolygonVertex(tl, '0 0', final_color, final_alpha);
				R_PolygonVertex(tr, '1 0', final_color, final_alpha);
				R_PolygonVertex(br, '1 1', final_color, final_alpha);
				R_PolygonVertex(bl, '0 1', final_color, final_alpha);
				R_EndPolygon();
			}
			#endif
		}
	};
};

class uispritesheet_c : uielement_c
{
	// Now, generate our shared data
	/*[[[cog
	import os
	import sys
	sys.path.append(os.getcwd())
	import element_utilities

	element_utilities.generate_element('../schemas/uispritesheet_schema.json')
	]]]*/
	/* #region GENERATED SECTION, DO NOT HAND EDIT */

	// [ Variable Declarations ]
	string image; // string
	#ifdef CSQC
	cm2_stat_string_t* image_strlist; // pointer
	#endif // CSQC
	string image2; // string
	#ifdef CSQC
	cm2_stat_string_t* image2_strlist; // pointer
	#endif // CSQC
	vector sprite; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* sprite_stat; // string pointer
	int sprite_stat_ver; // string pointer version
	#endif // CSQC
	vector sheet_size; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* sheet_size_stat; // string pointer
	int sheet_size_stat_ver; // string pointer version
	#endif // CSQC
	vector gap_size; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* gap_size_stat; // string pointer
	int gap_size_stat_ver; // string pointer version
	#endif // CSQC

	// [ Data Getter / Setter ]
	nonvirtual void(string val, int usestats=1) _set_image // string
	{
		this.image = val;
		
	#ifdef CSQC
		strstats_clear_buf(this.image_strlist);
		strstats_to_buf(val, this.image_strlist);
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_image // string
	{
		val.s = this.image;
		type = EV_STRING;
	};

	nonvirtual void(string val, int usestats=1) _set_image2 // string
	{
		this.image2 = val;
		
	#ifdef CSQC
		strstats_clear_buf(this.image2_strlist);
		strstats_to_buf(val, this.image2_strlist);
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_image2 // string
	{
		val.s = this.image2;
		type = EV_STRING;
	};

	nonvirtual void(string val, int usestats=1) _set_sprite // array
	{
		this.sprite = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.sprite_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.sprite_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_sprite_index // array (individual index)
	{
		this.sprite[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_sprite // array
	{
		val.v = this.sprite;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_sheet_size // array
	{
		this.sheet_size = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.sheet_size_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.sheet_size_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_sheet_size_index // array (individual index)
	{
		this.sheet_size[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_sheet_size // array
	{
		val.v = this.sheet_size;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_gap_size // array
	{
		this.gap_size = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.gap_size_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.gap_size_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_gap_size_index // array (individual index)
	{
		this.gap_size[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_gap_size // array
	{
		val.v = this.gap_size;
		type = EV_VECTOR;
	};

	virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty
	{
		switch(_key)
		{
			case "image": _get_image(_val, _type); return TRUE;
			case "image2": _get_image2(_val, _type); return TRUE;
			case "sprite": _get_sprite(_val, _type); return TRUE;
			case "sheet_size": _get_sheet_size(_val, _type); return TRUE;
			case "gap_size": _get_gap_size(_val, _type); return TRUE;
			default: break;
		}
		if (super::_getproperty(_key, _val, _type))
			return TRUE;
		return FALSE;
	};

	virtual int(string _key, string _value) _setproperty
	{
		switch(_key)
		{
			// image (string)
			case "image": _set_image(_value); return TRUE;
			// image2 (string)
			case "image2": _set_image2(_value); return TRUE;
			// sprite (array)
			case "sprite": _set_sprite(_value); return TRUE;
			case "sprite[0]": _set_sprite_index(_value, 0); return TRUE;
			case "sprite[1]": _set_sprite_index(_value, 1); return TRUE;
			case "sprite_x": _set_sprite_index(_value, 0); return TRUE;
			case "sprite_y": _set_sprite_index(_value, 1); return TRUE;
			// sheet_size (array)
			case "sheet_size": _set_sheet_size(_value); return TRUE;
			case "sheet_size[0]": _set_sheet_size_index(_value, 0); return TRUE;
			case "sheet_size[1]": _set_sheet_size_index(_value, 1); return TRUE;
			case "sheet_size_x": _set_sheet_size_index(_value, 0); return TRUE;
			case "sheet_size_y": _set_sheet_size_index(_value, 1); return TRUE;
			// gap_size (array)
			case "gap_size": _set_gap_size(_value); return TRUE;
			case "gap_size[0]": _set_gap_size_index(_value, 0); return TRUE;
			case "gap_size[1]": _set_gap_size_index(_value, 1); return TRUE;
			case "gap_size_x": _set_gap_size_index(_value, 0); return TRUE;
			case "gap_size_y": _set_gap_size_index(_value, 1); return TRUE;
			default: break;
		}
		if (super::_setproperty(_key, _value))
			return TRUE;
		return FALSE;
	};

	#ifdef CSQC
	virtual void() _preprocess
	{
		super::_preprocess();
		if (this.image_strlist) { if (strstats_needchange(this.image_strlist)) { this.image = strstats_buf_to_str(this.image_strlist); } }
		if (this.image2_strlist) { if (strstats_needchange(this.image2_strlist)) { this.image2 = strstats_buf_to_str(this.image2_strlist); } }
		if (this.sprite_stat)
		{
			if (this.sprite_stat_ver != this.sprite_stat->version) { _set_sprite(this.sprite_stat->value, 0); this.sprite_stat_ver = this.sprite_stat->version; }
		}
		if (this.sheet_size_stat)
		{
			if (this.sheet_size_stat_ver != this.sheet_size_stat->version) { _set_sheet_size(this.sheet_size_stat->value, 0); this.sheet_size_stat_ver = this.sheet_size_stat->version; }
		}
		if (this.gap_size_stat)
		{
			if (this.gap_size_stat_ver != this.gap_size_stat->version) { _set_gap_size(this.gap_size_stat->value, 0); this.gap_size_stat_ver = this.gap_size_stat->version; }
		}
	};
	#endif // CSQC
	/* #endregion END GENERATED SECTION */
	//[[[end]]]
	#ifdef MENU
	int drawflags;
	#endif

	// constructor
	virtual void() _init {
		super::_init();

		type = UITYPE::PICTURE;
		if (image)
			precache_pic(image);
		if (image2)
			precache_pic(image2);
	};
	ALIAS_CONSTRUCTOR(uispritesheet_c);

	var virtual void(uispritesheet_c this, __inout string img_to_render, __inout vector sprite_to_render) prerender;

	virtual void() render {
		vector final_color = color * mod_col; 				// 
		float final_alpha = alpha * mod_alp * ui_alpha; 	// handle hightlighting
		vector final_origin = ui_position;
		vector final_size = [size[0] * ui_scale[0], size[1] * ui_scale[1]];

		string final_img = image;
		vector final_sprite = sprite;
		if (prerender)
			prerender(this, final_img, final_sprite);
		if (final_img && final_alpha > 0)
		{
			//#if defined(CSQC) || defined(FTEDEP)
			final_sprite[0] = floor(final_sprite[0]);
			final_sprite[1] = floor(final_sprite[1]);

			vector tc_u = '0 0';
			vector tc_v = '1 1';
			vector s_size = sheet_size;
			s_size += [gap_size[0] * (sheet_size[0] - 1), gap_size[1] * (sheet_size[1] - 1)];

			tc_v[0] /= s_size[0];//sheet_size[0];
			tc_v[1] /= s_size[1];//sheet_size[1];

			tc_u[0] += tc_v[0] * final_sprite[0];
			tc_u[1] += tc_v[1] * final_sprite[1];

			tc_v[0] += tc_v[0] * final_sprite[0];
			tc_v[1] += tc_v[1] * final_sprite[1];

			R_BeginPolygon(final_img, drawflags, 1);
			R_PolygonVertex(final_origin, 						[tc_u[0], tc_u[1]], final_color, final_alpha);
			R_PolygonVertex(final_origin + [final_size[0], 0], 	[tc_v[0], tc_u[1]], final_color, final_alpha);
			R_PolygonVertex(final_origin + final_size, 			[tc_v[0], tc_v[1]], final_color, final_alpha);
			R_PolygonVertex(final_origin + [0, final_size[1]], 	[tc_u[0], tc_v[1]], final_color, final_alpha);
			R_EndPolygon();
			//#endif
		}
	};
};

class uiavatar_c : uipicture_c
{
	// Now, generate our shared data
	/*[[[cog
	import os
	import sys
	sys.path.append(os.getcwd())
	import element_utilities

	element_utilities.generate_element('../schemas/uiavatar_schema.json')
	]]]*/
	/* #region GENERATED SECTION, DO NOT HAND EDIT */

	// [ Variable Declarations ]
	string steamid; // string
	#ifdef CSQC
	cm2_stat_string_t* steamid_strlist; // pointer
	#endif // CSQC

	// [ Data Getter / Setter ]
	nonvirtual void(string val, int usestats=1) _set_steamid // string
	{
		this.steamid = val;
		
	#ifdef CSQC
		strstats_clear_buf(this.steamid_strlist);
		strstats_to_buf(val, this.steamid_strlist);
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_steamid // string
	{
		val.s = this.steamid;
		type = EV_STRING;
	};

	virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty
	{
		switch(_key)
		{
			case "steamid": _get_steamid(_val, _type); return TRUE;
			default: break;
		}
		if (super::_getproperty(_key, _val, _type))
			return TRUE;
		return FALSE;
	};

	virtual int(string _key, string _value) _setproperty
	{
		switch(_key)
		{
			// steamid (string)
			case "steamid": _set_steamid(_value); return TRUE;
			default: break;
		}
		if (super::_setproperty(_key, _value))
			return TRUE;
		return FALSE;
	};

	#ifdef CSQC
	virtual void() _preprocess
	{
		super::_preprocess();
		if (this.steamid_strlist) { if (strstats_needchange(this.steamid_strlist)) { this.steamid = strstats_buf_to_str(this.steamid_strlist); } }
	};
	#endif // CSQC
	/* #endregion END GENERATED SECTION */
	//[[[end]]]

	string steamid_old;
	virtual void() _init {
		super::_init();

		type = UITYPE::AVATAR;
		if (image)
			precache_pic(image);
	};
	ALIAS_CONSTRUCTOR(uiavatar_c);

	virtual void() render {
		vector final_color = color * mod_col; 				// 
		float final_alpha = alpha * mod_alp * ui_alpha; 	// handle hightlighting
		vector final_origin = ui_position;
		vector final_size = [size[0] * ui_scale[0], size[1] * ui_scale[1]];

		string final_img = this.image;
		#ifdef CSQC
		if (this.steamid && this.steamid != this.steamid_old)
		{
			this.steamid_old = this.steamid;
			this.image2 = 0;

			// search through players to see if we have the avatar already
			int asize = 2;
			if (this.size[0] < 16)
				asize = 0;
			else if (this.size[0] < 32)
				asize = 1;
			
			playerinfo_t *el = scoreboard_playerlist;
			for (el; el != __NULL__; el = el->next)
			{
				if (el->steamid != this.steamid)
					continue;
				
				string av_path = __NULL__;
				switch(asize)
				{
					case 0: av_path = el->avatarpath_small; break;
					case 1: av_path = el->avatarpath_medium; break;
					default: av_path = el->avatarpath_large; break;
				}
				
				if !(av_path)
					continue;
				
				this.image2 = av_path;
			}
		}
		#endif
		
		if (this.image2)
			final_img = this.image2;

		if (prerender)
			prerender(this, final_img);
		if (final_img != __NULL__ && final_alpha > 0)
		{
			#ifdef CSQC
			if (!angle)
			{
			#endif
				drawpic(final_origin, final_img, final_size, final_color, final_alpha, drawflag);
			#ifdef CSQC
			}
			else
			{
				vector slength = final_size / 2;
				vector tl = final_origin + slength;
				vector tr = final_origin + slength;
				vector br = final_origin + slength;
				vector bl = final_origin + slength;
				
				float a = (angle + 180) * DEG2RAD;
				float a2 = (angle + 270) * DEG2RAD;
				tl += [(cos(a) * slength[0]) + (cos(a2) * slength[1]), (sin(a) * slength[0]) + (sin(a2) * slength[1])];
				
				float a = (angle) * DEG2RAD;
				float a2 = (angle + 270) * DEG2RAD;
				tr += [(cos(a) * slength[0]) + (cos(a2) * slength[1]), (sin(a) * slength[0]) + (sin(a2) * slength[1])];

				float a = (angle + 180) * DEG2RAD;
				float a2 = (angle + 90) * DEG2RAD;
				bl += [(cos(a) * slength[0]) + (cos(a2) * slength[1]), (sin(a) * slength[0]) + (sin(a2) * slength[1])];
				
				float a = (angle) * DEG2RAD;
				float a2 = (angle + 90) * DEG2RAD;
				br += [(cos(a) * slength[0]) + (cos(a2) * slength[1]), (sin(a) * slength[0]) + (sin(a2) * slength[1])];



				R_BeginPolygon(final_img, DRAWFLAG_NORMAL, 1);
				R_PolygonVertex(tl, '0 0', final_color, final_alpha);
				R_PolygonVertex(tr, '1 0', final_color, final_alpha);
				R_PolygonVertex(br, '1 1', final_color, final_alpha);
				R_PolygonVertex(bl, '0 1', final_color, final_alpha);
				R_EndPolygon();
			}
			#endif
		}
	};
};

class uifill_c : uielement_c
{
	// Now, generate our shared data
	/*[[[cog
	import os
	import sys
	sys.path.append(os.getcwd())
	import element_utilities

	element_utilities.generate_element('../schemas/uifill_schema.json')
	]]]*/
	/* #region GENERATED SECTION, DO NOT HAND EDIT */

	// [ Variable Declarations ]

	// [ Data Getter / Setter ]
	virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty
	{
		switch(_key)
		{
			default: break;
		}
		if (super::_getproperty(_key, _val, _type))
			return TRUE;
		return FALSE;
	};

	virtual int(string _key, string _value) _setproperty
	{
		switch(_key)
		{
			default: break;
		}
		if (super::_setproperty(_key, _value))
			return TRUE;
		return FALSE;
	};

	#ifdef CSQC
	virtual void() _preprocess
	{
		super::_preprocess();
	};
	#endif // CSQC
	/* #endregion END GENERATED SECTION */
	//[[[end]]]

	var virtual void(uifill_c this, __inout vector color_to_render, __inout float alpha_to_render) prerender;

	virtual void() _init {
		super::_init();

		type = UITYPE::PICTURE;
	};
	ALIAS_CONSTRUCTOR(uifill_c);

	// rendering for fill or pictures
	virtual void() render {
		vector final_color = color * mod_col; 				// 
		float final_alpha = alpha * mod_alp * ui_alpha; 	// handle hightlighting
		vector final_origin = ui_position;
		vector final_size = [size[0] * ui_scale[0], size[1] * ui_scale[1]];

		if (prerender)
			prerender(this, final_color, final_alpha);
		if (final_alpha > 0)
		{
			drawfill(final_origin, final_size, final_color, final_alpha, DRAWFLAG_NORMAL);
		}
	};
};

class uibar_c : uifill_c
{
	// Now, generate our shared data
	/*[[[cog
	import os
	import sys
	sys.path.append(os.getcwd())
	import element_utilities

	element_utilities.generate_element('../schemas/uibar_schema.json')
	]]]*/
	/* #region GENERATED SECTION, DO NOT HAND EDIT */

	// [ Variable Declarations ]
	float value; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* value_stat; // string pointer
	int value_stat_ver; // string pointer version
	#endif // CSQC
	float value2; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* value2_stat; // string pointer
	int value2_stat_ver; // string pointer version
	#endif // CSQC
	float value_max; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* value_max_stat; // string pointer
	int value_max_stat_ver; // string pointer version
	#endif // CSQC
	float value_min; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* value_min_stat; // string pointer
	int value_min_stat_ver; // string pointer version
	#endif // CSQC
	string shader; // string
	#ifdef CSQC
	cm2_stat_string_t* shader_strlist; // pointer
	#endif // CSQC

	// [ Data Getter / Setter ]
	nonvirtual void(string val, int usestats=1) _set_value // number
	{
		this.value = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.value_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.value_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_value // number
	{
		val.f = this.value;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_value2 // number
	{
		this.value2 = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.value2_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.value2_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_value2 // number
	{
		val.f = this.value2;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_value_max // number
	{
		this.value_max = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.value_max_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.value_max_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_value_max // number
	{
		val.f = this.value_max;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_value_min // number
	{
		this.value_min = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.value_min_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.value_min_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_value_min // number
	{
		val.f = this.value_min;
		type = EV_FLOAT;
	};

	nonvirtual void(string val, int usestats=1) _set_shader // string
	{
		this.shader = val;
		
	#ifdef CSQC
		strstats_clear_buf(this.shader_strlist);
		strstats_to_buf(val, this.shader_strlist);
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_shader // string
	{
		val.s = this.shader;
		type = EV_STRING;
	};

	virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty
	{
		switch(_key)
		{
			case "value": _get_value(_val, _type); return TRUE;
			case "value2": _get_value2(_val, _type); return TRUE;
			case "value_max": _get_value_max(_val, _type); return TRUE;
			case "value_min": _get_value_min(_val, _type); return TRUE;
			case "shader": _get_shader(_val, _type); return TRUE;
			default: break;
		}
		if (super::_getproperty(_key, _val, _type))
			return TRUE;
		return FALSE;
	};

	virtual int(string _key, string _value) _setproperty
	{
		switch(_key)
		{
			// value (number)
			case "value": _set_value(_value); return TRUE;
			// value2 (number)
			case "value2": _set_value2(_value); return TRUE;
			// value_max (number)
			case "value_max": _set_value_max(_value); return TRUE;
			// value_min (number)
			case "value_min": _set_value_min(_value); return TRUE;
			// shader (string)
			case "shader": _set_shader(_value); return TRUE;
			default: break;
		}
		if (super::_setproperty(_key, _value))
			return TRUE;
		return FALSE;
	};

	#ifdef CSQC
	virtual void() _preprocess
	{
		super::_preprocess();
		if (this.value_stat)
		{
			if (this.value_stat_ver != this.value_stat->version) { _set_value(this.value_stat->value, 0); this.value_stat_ver = this.value_stat->version; }
		}
		if (this.value2_stat)
		{
			if (this.value2_stat_ver != this.value2_stat->version) { _set_value2(this.value2_stat->value, 0); this.value2_stat_ver = this.value2_stat->version; }
		}
		if (this.value_max_stat)
		{
			if (this.value_max_stat_ver != this.value_max_stat->version) { _set_value_max(this.value_max_stat->value, 0); this.value_max_stat_ver = this.value_max_stat->version; }
		}
		if (this.value_min_stat)
		{
			if (this.value_min_stat_ver != this.value_min_stat->version) { _set_value_min(this.value_min_stat->value, 0); this.value_min_stat_ver = this.value_min_stat->version; }
		}
		if (this.shader_strlist) { if (strstats_needchange(this.shader_strlist)) { this.shader = strstats_buf_to_str(this.shader_strlist); } }
	};
	#endif // CSQC
	/* #endregion END GENERATED SECTION */
	//[[[end]]]

	virtual void() _init {
		super::_init();

		type = UITYPE::PICTURE;
	};
	ALIAS_CONSTRUCTOR(uibar_c);

	// rendering for fill or pictures
	virtual void() render {
		float final_alpha = alpha * mod_alp * ui_alpha; 	// handle hightlighting
		vector final_origin = ui_position;
		vector final_size = [size[0] * ui_scale[0], size[1] * ui_scale[1]];

		vector packed_color = '0 0 0';
		packed_color[0] = ((this.value - this.value_min) / (this.value_max - this.value_min));
		if (this.value2)
			packed_color[1] = ((this.value2 - this.value_min) / (this.value_max - this.value_min));
		else
			packed_color[1] = -1.0;

		if (final_alpha > 0)
		{
			drawpic(final_origin, this.shader, final_size, packed_color, final_alpha, DRAWFLAG_NORMAL);
		}
	};
};

class uivideo_c : uifill_c
{
	imagevideo_t *video;
	int drawflag;

	virtual void() _init {
		super::_init();

		type = UITYPE::PICTURE;
		if (!this.drawflag)
			this.drawflag = DRAWFLAG_NORMAL;
	};
	ALIAS_CONSTRUCTOR(uivideo_c);

	virtual void() render {
		imagevideo_t *vid = this.video;
		if (cltime >= vid->timeover)
		{
			this.flags |= UIFLAG::HIDE;
			return;
		}

		vector final_origin = ui_position;
		vector final_size = [size[0] * ui_scale[0], size[1] * ui_scale[1]];
		
		drawpic(final_origin, vid->shader, final_size, this.color, this.alpha, this.drawflag);
	};
};

////////////////
//  Text Element
////////////////
class uitext_c : uielement_c
{
	// Now, generate our shared data
	/*[[[cog
	import os
	import sys
	sys.path.append(os.getcwd())
	import element_utilities

	element_utilities.generate_element('../schemas/uitext_schema.json')
	]]]*/
	/* #region GENERATED SECTION, DO NOT HAND EDIT */

	// [ Variable Declarations ]
	vector font_size; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* font_size_stat; // string pointer
	int font_size_stat_ver; // string pointer version
	#endif // CSQC
	vector padding; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* padding_stat; // string pointer
	int padding_stat_ver; // string pointer version
	#endif // CSQC
	string text; // string
	#ifdef CSQC
	cm2_stat_string_t* text_strlist; // pointer
	#endif // CSQC
	int font; // font
	string font_str; // font

	// [ Data Getter / Setter ]
	nonvirtual void(string val, int usestats=1) _set_font_size // array
	{
		this.font_size = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.font_size_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.font_size_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_font_size_index // array (individual index)
	{
		this.font_size[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_font_size // array
	{
		val.v = this.font_size;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_padding // array
	{
		this.padding = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.padding_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.padding_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_padding_index // array (individual index)
	{
		this.padding[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_padding // array
	{
		val.v = this.padding;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_text // string
	{
		this.text = val;
		
	#ifdef CSQC
		strstats_clear_buf(this.text_strlist);
		strstats_to_buf(val, this.text_strlist);
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_text // string
	{
		val.s = this.text;
		type = EV_STRING;
	};

	nonvirtual void(string val, int usestats=1) _set_font // font
	{
		this.font_str = val;
		this.font = hash_get(cm2_localnames, CM2_FONTNAME(font_str), -1);
		if (this.font < 0)
		{
			this.font = hash_get(cm2_localnames, CM2_FONTNAME("default"), 0);
		}
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_font // font
	{
		val.s = this.font_str;
		type = EV_STRING;
	};

	virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty
	{
		switch(_key)
		{
			case "font_size": _get_font_size(_val, _type); return TRUE;
			case "padding": _get_padding(_val, _type); return TRUE;
			case "text": _get_text(_val, _type); return TRUE;
			case "font": _get_font(_val, _type); return TRUE;
			default: break;
		}
		if (super::_getproperty(_key, _val, _type))
			return TRUE;
		return FALSE;
	};

	virtual int(string _key, string _value) _setproperty
	{
		switch(_key)
		{
			// font_size (array)
			case "font_size": _set_font_size(_value); return TRUE;
			case "font_size[0]": _set_font_size_index(_value, 0); return TRUE;
			case "font_size[1]": _set_font_size_index(_value, 1); return TRUE;
			case "font_size_x": _set_font_size_index(_value, 0); return TRUE;
			case "font_size_y": _set_font_size_index(_value, 1); return TRUE;
			// padding (array)
			case "padding": _set_padding(_value); return TRUE;
			case "padding[0]": _set_padding_index(_value, 0); return TRUE;
			case "padding[1]": _set_padding_index(_value, 1); return TRUE;
			case "padding_x": _set_padding_index(_value, 0); return TRUE;
			case "padding_y": _set_padding_index(_value, 1); return TRUE;
			// text (string)
			case "text": _set_text(_value); return TRUE;
			// font (font)
			case "font": _set_font(_value); return TRUE;
			default: break;
		}
		if (super::_setproperty(_key, _value))
			return TRUE;
		return FALSE;
	};

	#ifdef CSQC
	virtual void() _preprocess
	{
		super::_preprocess();
		if (this.font_size_stat)
		{
			if (this.font_size_stat_ver != this.font_size_stat->version) { _set_font_size(this.font_size_stat->value, 0); this.font_size_stat_ver = this.font_size_stat->version; }
		}
		if (this.padding_stat)
		{
			if (this.padding_stat_ver != this.padding_stat->version) { _set_padding(this.padding_stat->value, 0); this.padding_stat_ver = this.padding_stat->version; }
		}
		if (this.text_strlist) { if (strstats_needchange(this.text_strlist)) { this.text = strstats_buf_to_str(this.text_strlist); } }
	};
	#endif // CSQC
	/* #endregion END GENERATED SECTION */
	//[[[end]]]

	// constructor
	virtual void() _init {
		super::_init();
		
		if (!this.font_str && cm2_localnames)
		{
			this.font = hash_get(cm2_localnames, CM2_FONTNAME("default"), 0);
		}
		
		if (!text)
			text = "NULL TEXT";
		#ifdef MENU
		if (!font)
			font = FONT_MENU;
		#else
		if (!font)
			font = FONT_HUD;
		#endif
		if (!font_size)
			font_size = '10 10';
		type = UITYPE::TEXT;
	};
	ALIAS_CONSTRUCTOR(uitext_c);

	// totalsize
	virtual vector() totalsize {
		float o_drawfont = drawfont;
		drawfont = font;
		if (!text)
			return '0 0';
		float txt_width = stringwidth(text, TRUE, font_size);
		drawfont = o_drawfont;

		if (this.size)
		{
			if (txt_width > this.size[0])
				txt_width = this.size[0];
		}
		txt_width *= ui_scale[0]; 

		vector sz = [txt_width, font_size[1] * ui_scale[1]];
		sz += [padding[0] * ui_scale[0], padding[1] * ui_scale[1]];
		return sz;
	};

	var virtual void(uitext_c this, __inout string txt_to_render, __inout vector color_to_render, __inout float alpha_to_render) prerender;

	// rendering our text
	virtual void() render {
		float o_drawfont = drawfont;
		drawfont = font;

		vector final_color = color * mod_col; 				// 
		float final_alpha = alpha * mod_alp * ui_alpha; 	// handle hightlighting
		vector final_origin = ui_position;
		final_origin += [padding[0] * ui_scale[0], padding[1] * ui_scale[1]] * 0.5;

		vector final_size = font_size;
		string final_text = text;

		if (prerender)
			prerender(this, final_text, final_color, final_alpha);

		// squish if we have a size
		if (this.size)
		{
			float txt_width = stringwidth(final_text, TRUE, final_size);
			if (txt_width > this.size[0])
				final_size[0] *= (this.size[0] / txt_width);
		}

		final_size[0] *= ui_scale[0];
		final_size[1] *= ui_scale[1];

		if (final_size[0] <= 0 || final_size[1] <= 0 || final_alpha <= 0 || !text || !strcmp(text, ""))
			return;
		
		drawstring(final_origin, final_text, final_size, final_color, final_alpha, DRAWFLAG_NORMAL);

		if (autocvar_mg_debug_textbox)
			drawfill(final_origin, this.totalsize(), '0 1.0 0', 0.2, 0);

		drawfont = o_drawfont;
	};
};

class uitextshadow_c : uitext_c
{
	// Now, generate our shared data
	/*[[[cog
	import os
	import sys
	sys.path.append(os.getcwd())
	import element_utilities

	element_utilities.generate_element('../schemas/uitextshadow_schema.json')
	]]]*/
	/* #region GENERATED SECTION, DO NOT HAND EDIT */

	// [ Variable Declarations ]
	vector shadow_font_size; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* shadow_font_size_stat; // string pointer
	int shadow_font_size_stat_ver; // string pointer version
	#endif // CSQC
	int shadow_font; // font
	string shadow_font_str; // font
	vector shadow_offset; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* shadow_offset_stat; // string pointer
	int shadow_offset_stat_ver; // string pointer version
	#endif // CSQC
	vector shadow_color; // array (vector)
	#ifdef CSQC
	cm2_stat_cache_t* shadow_color_stat; // string pointer
	int shadow_color_stat_ver; // string pointer version
	#endif // CSQC
	float shadow_alpha; // number (float)
	#ifdef CSQC
	cm2_stat_cache_t* shadow_alpha_stat; // string pointer
	int shadow_alpha_stat_ver; // string pointer version
	#endif // CSQC

	// [ Data Getter / Setter ]
	nonvirtual void(string val, int usestats=1) _set_shadow_font_size // array
	{
		this.shadow_font_size = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.shadow_font_size_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.shadow_font_size_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_shadow_font_size_index // array (individual index)
	{
		this.shadow_font_size[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_shadow_font_size // array
	{
		val.v = this.shadow_font_size;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_shadow_font // font
	{
		this.shadow_font_str = val;
		this.shadow_font = hash_get(cm2_localnames, CM2_FONTNAME(shadow_font_str), -1);
		if (this.shadow_font < 0)
		{
			this.shadow_font = hash_get(cm2_localnames, CM2_FONTNAME("default"), 0);
		}
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_shadow_font // font
	{
		val.s = this.shadow_font_str;
		type = EV_STRING;
	};

	nonvirtual void(string val, int usestats=1) _set_shadow_offset // array
	{
		this.shadow_offset = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.shadow_offset_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.shadow_offset_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_shadow_offset_index // array (individual index)
	{
		this.shadow_offset[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_shadow_offset // array
	{
		val.v = this.shadow_offset;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_shadow_color // array
	{
		this.shadow_color = stov(val);
	#ifdef CSQC
		if (usestats)
		{
			this.shadow_color_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.shadow_color_stat_ver = -1;
		}
	#endif // CSQC
	};
	nonvirtual void(string val, int i) _set_shadow_color_index // array (individual index)
	{
		this.shadow_color[i] = stof(val);
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_shadow_color // array
	{
		val.v = this.shadow_color;
		type = EV_VECTOR;
	};

	nonvirtual void(string val, int usestats=1) _set_shadow_alpha // number
	{
		this.shadow_alpha = stof(val);
		
	#ifdef CSQC
		if (usestats)
		{
			this.shadow_alpha_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);
			this.shadow_alpha_stat_ver = -1;
		}
	#endif // CSQC
	};

	nonvirtual void(__out cm2_argument val, __out int type) _get_shadow_alpha // number
	{
		val.f = this.shadow_alpha;
		type = EV_FLOAT;
	};

	virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty
	{
		switch(_key)
		{
			case "shadow_font_size": _get_shadow_font_size(_val, _type); return TRUE;
			case "shadow_font": _get_shadow_font(_val, _type); return TRUE;
			case "shadow_offset": _get_shadow_offset(_val, _type); return TRUE;
			case "shadow_color": _get_shadow_color(_val, _type); return TRUE;
			case "shadow_alpha": _get_shadow_alpha(_val, _type); return TRUE;
			default: break;
		}
		if (super::_getproperty(_key, _val, _type))
			return TRUE;
		return FALSE;
	};

	virtual int(string _key, string _value) _setproperty
	{
		switch(_key)
		{
			// shadow_font_size (array)
			case "shadow_font_size": _set_shadow_font_size(_value); return TRUE;
			case "shadow_font_size[0]": _set_shadow_font_size_index(_value, 0); return TRUE;
			case "shadow_font_size[1]": _set_shadow_font_size_index(_value, 1); return TRUE;
			case "shadow_font_size_x": _set_shadow_font_size_index(_value, 0); return TRUE;
			case "shadow_font_size_y": _set_shadow_font_size_index(_value, 1); return TRUE;
			// shadow_font (font)
			case "shadow_font": _set_shadow_font(_value); return TRUE;
			// shadow_offset (array)
			case "shadow_offset": _set_shadow_offset(_value); return TRUE;
			case "shadow_offset[0]": _set_shadow_offset_index(_value, 0); return TRUE;
			case "shadow_offset[1]": _set_shadow_offset_index(_value, 1); return TRUE;
			case "shadow_offset_x": _set_shadow_offset_index(_value, 0); return TRUE;
			case "shadow_offset_y": _set_shadow_offset_index(_value, 1); return TRUE;
			// shadow_color (array)
			case "shadow_color": _set_shadow_color(_value); return TRUE;
			case "shadow_color[0]": _set_shadow_color_index(_value, 0); return TRUE;
			case "shadow_color[1]": _set_shadow_color_index(_value, 1); return TRUE;
			case "shadow_color[2]": _set_shadow_color_index(_value, 2); return TRUE;
			case "shadow_color_x": _set_shadow_color_index(_value, 0); return TRUE;
			case "shadow_color_y": _set_shadow_color_index(_value, 1); return TRUE;
			case "shadow_color_z": _set_shadow_color_index(_value, 2); return TRUE;
			// shadow_alpha (number)
			case "shadow_alpha": _set_shadow_alpha(_value); return TRUE;
			default: break;
		}
		if (super::_setproperty(_key, _value))
			return TRUE;
		return FALSE;
	};

	#ifdef CSQC
	virtual void() _preprocess
	{
		super::_preprocess();
		if (this.shadow_font_size_stat)
		{
			if (this.shadow_font_size_stat_ver != this.shadow_font_size_stat->version) { _set_shadow_font_size(this.shadow_font_size_stat->value, 0); this.shadow_font_size_stat_ver = this.shadow_font_size_stat->version; }
		}
		if (this.shadow_offset_stat)
		{
			if (this.shadow_offset_stat_ver != this.shadow_offset_stat->version) { _set_shadow_offset(this.shadow_offset_stat->value, 0); this.shadow_offset_stat_ver = this.shadow_offset_stat->version; }
		}
		if (this.shadow_color_stat)
		{
			if (this.shadow_color_stat_ver != this.shadow_color_stat->version) { _set_shadow_color(this.shadow_color_stat->value, 0); this.shadow_color_stat_ver = this.shadow_color_stat->version; }
		}
		if (this.shadow_alpha_stat)
		{
			if (this.shadow_alpha_stat_ver != this.shadow_alpha_stat->version) { _set_shadow_alpha(this.shadow_alpha_stat->value, 0); this.shadow_alpha_stat_ver = this.shadow_alpha_stat->version; }
		}
	};
	#endif // CSQC
	/* #endregion END GENERATED SECTION */
	//[[[end]]]

	// constructor
	virtual void() _init {
		super::_init();
		
		if (!this.shadow_font_str && cm2_localnames)
			this.shadow_font = this.font;
		else if (!this.shadow_font)
			this.shadow_font = this.font;

		if (!this.shadow_font_size)
			this.shadow_font_size = this.font_size;

		if (!this.shadow_alpha)
			this.shadow_alpha = 1.0;
	};
	ALIAS_CONSTRUCTOR(uitextshadow_c);

	// rendering our text
	virtual void() render {
		float o_drawfont = drawfont;
		drawfont = this.font;

		vector final_color = this.color * this.mod_col; 				// 
		float final_alpha = this.alpha * this.mod_alp * ui_alpha; 	// handle hightlighting
		vector final_origin = ui_position;
		final_origin += [padding[0] * ui_scale[0], padding[1] * ui_scale[1]] * 0.5;

		float size_scalar = 1;
		vector final_size = this.font_size;
		string final_text = this.text;

		vector final_color_shadow = this.shadow_color * this.mod_col;
		float final_alpha_shadow = this.shadow_alpha * this.mod_col;
		vector final_size_shadow = this.shadow_font_size;
		vector final_origin_shadow;

		if (prerender)
			prerender(this, final_text, final_color, final_alpha);
		
		// squish if we have a size
		if (this.size)
		{
			float txt_width = stringwidth(final_text, TRUE, final_size);
			if (txt_width > this.size[0])
				size_scalar = (this.size[0] / txt_width);
		}

		// draw shadow first
		drawfont = this.shadow_font;
		final_size_shadow[0] *= size_scalar; // scale the shadow same as the main
		final_size_shadow[0] *= ui_scale[0];
		final_size_shadow[1] *= ui_scale[1];

		final_origin_shadow = final_origin + this.shadow_offset;

		drawstring(final_origin_shadow, final_text, final_size_shadow, final_color_shadow, final_alpha_shadow, DRAWFLAG_NORMAL);
		//
		

		drawfont = this.font;
		final_size[0] *= size_scalar;
		final_size[0] *= ui_scale[0];
		final_size[1] *= ui_scale[1];

		if (final_size[0] <= 0 || final_size[1] <= 0 || final_alpha <= 0 || !text || !strcmp(text, ""))
			return;
		
		drawstring(final_origin, final_text, final_size, final_color, final_alpha, DRAWFLAG_NORMAL);

		if (autocvar_mg_debug_textbox)
			drawfill(final_origin, this.totalsize(), '0 1.0 0', 0.2, 0);

		drawfont = o_drawfont;
	};
};

void UI_RenderElements(uielement_c chain);
void UI_CheckClick(uielement_c chain);
int UI_CheckInput(uielement_c chain, float nkey, float ascii, float released);
#ifdef CSQC
void UI_PreProcess(uielement_c chain);
#endif

float lastsoundjournal;
void Journal_Sound(void)
{
	float r = floor(random() *7) + 1;
	if (r == lastsoundjournal)
		r += 1;
	if (r > 7)
		r = 1;
	lastsoundjournal = r;
	string rsound = ftos(r);
	#ifdef MENU
	localsound(strcat("sound/ui/journal/journal_page", rsound, ".ogg"));
	#else
	localcmd(strcat("play2 sound/ui/journal/journal_page", rsound, ".ogg\n"));
	#endif
}

#undef frametime