// functions:
entity get_weaponinfo(float id);
string W_FixWeaponOrder(string order, float complete);
++string W_UndeprecateName(string s);
string W_NameWeaponOrder(string order);
string W_NumberWeaponOrder(string order);
string W_FixWeaponOrder_BuildImpulseList(string o);
--- /dev/null
+#ifndef PROGSDEFS_H
+#define PROGSDEFS_H
+
+/*
+==============================================================================
+
+ SOURCE FOR GLOBALVARS_T C STRUCTURE
+ MUST NOT BE MODIFIED, OR CRC ERRORS WILL APPEAR
+
+==============================================================================
+*/
+
+//
+// system globals
+//
+entity self;
+entity other;
+entity world;
+float time;
+float frametime;
+
+float force_retouch; // force all entities to touch triggers
+ // next frame. this is needed because
+ // non-moving things don't normally scan
+ // for triggers, and when a trigger is
+ // created (like a teleport trigger), it
+ // needs to catch everything.
+ // decremented each frame, so set to 2
+ // to guarantee everything is touched
+string mapname;
+
+float deathmatch;
+float coop;
+float teamplay;
+
+int serverflags; // propagated from level to level, used to
+ // keep track of completed episodes
+
+float total_secrets;
+float total_monsters;
+
+float found_secrets; // number of secrets found
+float killed_monsters; // number of monsters killed
+
+
+// spawnparms are used to encode information about clients across server
+// level changes
+float parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16;
+
+//
+// global variables set by built in functions
+//
+vector v_forward, v_up, v_right; // set by makevectors()
+
+// set by traceline / tracebox
+float trace_allsolid;
+float trace_startsolid;
+float trace_fraction;
+vector trace_endpos;
+vector trace_plane_normal;
+float trace_plane_dist;
+entity trace_ent;
+float trace_inopen;
+float trace_inwater;
+
+entity msg_entity; // destination of single entity writes
+
+//
+// required prog functions
+//
+void() main; // only for testing
+
+void() StartFrame;
+
+void() PlayerPreThink;
+void() PlayerPostThink;
+
+void() ClientKill;
++#ifdef DP_EXT_PRECONNECT
++void() ClientPreConnect;
++#endif
+void() ClientConnect;
+void() PutClientInServer; // call after setting the parm1... parms
+void() ClientDisconnect;
+
+void() SetNewParms; // called when a client first connects to
+ // a server. sets parms so they can be
+ // saved off for restarts
+
+void() SetChangeParms; // call to set parms for self so they can
+ // be saved for a level transition
+
+
+//================================================
+void end_sys_globals; // flag for structure dumping
+//================================================
+
+/*
+==============================================================================
+
+ SOURCE FOR ENTVARS_T C STRUCTURE
+ MUST NOT BE MODIFIED, OR CRC ERRORS WILL APPEAR
+
+==============================================================================
+*/
+
+//
+// system fields (*** = do not set in prog code, maintained by C code)
+//
+.int modelindex; // *** model index in the precached list
+.vector absmin, absmax; // *** origin + mins / maxs
+
+.float ltime; // local time for entity
+.float movetype;
+.float solid;
+
+.vector origin; // ***
+.vector oldorigin; // ***
+.vector velocity;
+.vector angles;
+.vector avelocity;
+
+.vector punchangle; // temp angle adjust from damage or recoil
+
+.string classname; // spawn function
+.string model;
+.int frame;
+.int skin;
+.int effects;
+
+.vector mins, maxs; // bounding box extents reletive to origin
+.vector size; // maxs - mins
+
+.void() touch;
+.void() use;
+.void() think;
+.void() blocked; // for doors or plats, called when can't push other
+
+.float nextthink;
+.entity groundentity;
+
+// stats
+.float health;
+.float frags;
+.int weapon; // one of the IT_SHOTGUN, etc flags
+.string weaponmodel;
+.float weaponframe;
+.float currentammo;
+.float ammo_shells, ammo_nails, ammo_rockets, ammo_cells;
+
+.int items; // bit flags
+
+.float takedamage;
+.entity chain;
+.float deadflag;
+
+.vector view_ofs; // add to origin to get eye point
+
+
+.float button0; // fire
+.float button1; // use
+.float button2; // jump
+
+.float impulse; // weapon changes
+
+.float fixangle;
+.vector v_angle; // view / targeting angle for players
+.float idealpitch; // calculated pitch angle for lookup up slopes
+
+
+.string netname;
+
+.entity enemy;
+
+.int flags;
+
+.int colormap;
+.float team;
+
+.float max_health; // players maximum health is stored here
+
+.float teleport_time; // don't back up
+
+.float armortype; // save this fraction of incoming damage
+.float armorvalue;
+
+.float waterlevel; // 0 = not in, 1 = feet, 2 = wast, 3 = eyes
+.float watertype; // a contents value
+
+.float ideal_yaw;
+.float yaw_speed;
+
+.entity aiment;
+
+.entity goalentity; // a movetarget or an enemy
+
+.int spawnflags;
+
+.string target;
+.string targetname;
+
+// damage is accumulated through a frame. and sent as one single
+// message, so the super shotgun doesn't generate huge messages
+.float dmg_take;
+.float dmg_save;
+.entity dmg_inflictor;
+
+.entity owner; // who launched a missile
+.vector movedir; // mostly for doors, but also used for waterjump
+
+.string message; // trigger messages
+
+.float sounds; // either a cd track number or sound number
+
+.string noise, noise1, noise2, noise3; // contains names of wavs to play
+
+//================================================
+void end_sys_fields; // flag for structure dumping
+//================================================
+
+/*
+==============================================================================
+
+ CONSTANT DEFINITIONS
+
+==============================================================================
+*/
+
+
+//
+// constants
+//
+
+// edict.flags
+const int FL_FLY = 1;
+const int FL_SWIM = 2;
+const int FL_CLIENT = 8; // set for all client edicts
+const int FL_INWATER = 16; // for enter / leave water splash
+const int FL_MONSTER = 32;
+const int FL_GODMODE = 64; // player cheat
+const int FL_NOTARGET = 128; // player cheat
+const int FL_ITEM = 256; // extra wide size for bonus items
+const int FL_ONGROUND = 512; // standing on something
+const int FL_PARTIALGROUND = 1024; // not all corners are valid
+const int FL_WATERJUMP = 2048; // player jumping out of water
+const int FL_JUMPRELEASED = 4096; // for jump debouncing
+
+// edict.movetype values
+const int MOVETYPE_NONE = 0; // never moves
+//const int MOVETYPE_ANGLENOCLIP= 1;
+//const int MOVETYPE_ANGLECLIP = 2;
+const int MOVETYPE_WALK = 3; // players only
+const int MOVETYPE_STEP = 4; // discrete, not real time unless fall
+const int MOVETYPE_FLY = 5;
+const int MOVETYPE_TOSS = 6; // gravity
+const int MOVETYPE_PUSH = 7; // no clip to world, push and crush
+const int MOVETYPE_NOCLIP = 8;
+const int MOVETYPE_FLYMISSILE = 9; // fly with extra size against monsters
+const int MOVETYPE_BOUNCE = 10;
+const int MOVETYPE_BOUNCEMISSILE= 11; // bounce with extra size
+
+// edict.solid values
+const int SOLID_NOT = 0; // no interaction with other objects
+const int SOLID_TRIGGER = 1; // touch on edge, but not blocking
+const int SOLID_BBOX = 2; // touch on edge, block
+const int SOLID_SLIDEBOX = 3; // touch on edge, but not an onground
+const int SOLID_BSP = 4; // bsp clip, touch on edge, block
+
+// range values
+const int RANGE_MELEE = 0;
+const int RANGE_NEAR = 1;
+const int RANGE_MID = 2;
+const int RANGE_FAR = 3;
+
+// deadflag values
+
+const int DEAD_NO = 0;
+const int DEAD_DYING = 1;
+const int DEAD_DEAD = 2;
+const int DEAD_RESPAWNABLE = 3;
+const int DEAD_RESPAWNING = 4; // dead, waiting for buttons to be released
+
+// takedamage values
+
+const int DAMAGE_NO = 0;
+const int DAMAGE_YES = 1;
+const int DAMAGE_AIM = 2;
+
+// items
+const int IT_AXE = 4096;
+const int IT_SHOTGUN = 1;
+const int IT_SUPER_SHOTGUN = 2;
+const int IT_NAILGUN = 4;
+const int IT_SUPER_NAILGUN = 8;
+const int IT_GRENADE_LAUNCHER = 16;
+const int IT_ROCKET_LAUNCHER = 32;
+const int IT_LIGHTNING = 64;
+const int IT_EXTRA_WEAPON = 128;
+
+//const int IT_SHELLS = 256;
+//const int IT_NAILS = 512;
+//const int IT_ROCKETS = 1024;
+//const int IT_CELLS = 2048;
+
+const int IT_ARMOR1 = 8192;
+const int IT_ARMOR2 = 16384;
+const int IT_ARMOR3 = 32768;
+const int IT_SUPERHEALTH = 65536;
+
+//const int IT_KEY1 = 131072;
+//const int IT_KEY2 = 262144;
+
+const int IT_INVISIBILITY = 524288;
+const int IT_INVULNERABILITY = 1048576;
+const int IT_SUIT = 2097152;
+const int IT_QUAD = 4194304;
+
+// point content values
+
+const int CONTENT_EMPTY = -1;
+const int CONTENT_SOLID = -2;
+const int CONTENT_WATER = -3;
+const int CONTENT_SLIME = -4;
+const int CONTENT_LAVA = -5;
+const int CONTENT_SKY = -6;
+
+const int STATE_TOP = 0;
+const int STATE_BOTTOM = 1;
+const int STATE_UP = 2;
+const int STATE_DOWN = 3;
+
+const vector VEC_ORIGIN = '0 0 0';
+const vector VEC_HULL_MIN = '-16 -16 -24';
+const vector VEC_HULL_MAX = '16 16 32';
+
+const vector VEC_HULL2_MIN = '-32 -32 -24';
+const vector VEC_HULL2_MAX = '32 32 64';
+
+// protocol bytes
+const int SVC_TEMPENTITY = 23;
+const int SVC_KILLEDMONSTER = 27;
+const int SVC_FOUNDSECRET = 28;
+const int SVC_INTERMISSION = 30;
+const int SVC_FINALE = 31;
+const int SVC_CDTRACK = 32;
+const int SVC_SELLSCREEN = 33;
+
+
+const int TE_SPIKE = 0;
+const int TE_SUPERSPIKE = 1;
+const int TE_GUNSHOT = 2;
+const int TE_EXPLOSION = 3;
+const int TE_TAREXPLOSION = 4;
+const int TE_LIGHTNING1 = 5;
+const int TE_LIGHTNING2 = 6;
+const int TE_WIZSPIKE = 7;
+const int TE_KNIGHTSPIKE = 8;
+const int TE_LIGHTNING3 = 9;
+const int TE_LAVASPLASH = 10;
+const int TE_TELEPORT = 11;
+
+// sound channels
+// channel 0 never willingly overrides
+// other channels (1-7) allways override a playing sound on that channel
+const int CHAN_AUTO = 0;
+const int CHAN_WEAPON = 1;
+const int CHAN_VOICE = 2;
+const int CHAN_ITEM = 3;
+const int CHAN_BODY = 4;
+
+const int ATTN_NONE = 0;
+const int ATTN_NORM = 1;
+const int ATTN_IDLE = 2;
+const int ATTN_STATIC = 3;
+
+// update types
+
+const int UPDATE_GENERAL = 0;
+const int UPDATE_STATIC = 1;
+const int UPDATE_BINARY = 2;
+const int UPDATE_TEMP = 3;
+
+// entity effects
+
+const int EF_BRIGHTFIELD = 1;
+const int EF_MUZZLEFLASH = 2;
+const int EF_BRIGHTLIGHT = 4;
+const int EF_DIMLIGHT = 8;
+
+
+// messages
+const int MSG_BROADCAST = 0; // unreliable to all
+const int MSG_ONE = 1; // reliable to one (msg_entity)
+const int MSG_ALL = 2; // reliable to all
+const int MSG_INIT = 3; // write to the init string
+
+//===========================================================================
+
+//
+// builtin functions
+//
+
+void(vector ang) makevectors = #1; // sets v_forward, etc globals
+void(entity e, vector o) setorigin = #2;
+void(entity e, string m) setmodel = #3; // set movetype and solid first
+void(entity e, vector min, vector max) setsize = #4;
+// #5 was removed
+void() break_to_debugger = #6;
+float() random = #7; // returns 0 - 1
+void(entity e, float chan, string samp, float vol, float atten) sound = #8;
+vector(vector v) normalize = #9;
+void(string e, ...) error = #10;
+void(string e, ...) objerror = #11;
+float(vector v) vlen = #12;
+float(vector v) vectoyaw = #13;
+entity() spawn = #14;
+void(entity e) remove = #15;
+
+// sets trace_* globals
+// nomonsters can be:
+// An entity will also be ignored for testing if forent == test,
+// forent->owner == test, or test->owner == forent
+// a forent of world is ignored
+void(vector v1, vector v2, float nomonsters, entity forent) traceline = #16;
+
+entity() checkclient = #17; // returns a client to look for
+entity(entity start, .string fld, string match) find = #18;
+string(string s) precache_sound = #19;
+string(string s) precache_model = #20;
+void(entity client, string s, ...)stuffcmd = #21;
+entity(vector org, float rad) findradius = #22;
+void(string s, ...) bprint = #23;
+void(entity client, string s, ...) sprint = #24;
+void(string s, ...) dprint = #25;
+string(float f) ftos = #26;
+string(vector v) vtos = #27;
+void() coredump = #28; // prints all edicts
+void() traceon = #29; // turns statment trace on
+void() traceoff = #30;
+void(entity e) eprint = #31; // prints an entire edict
+float(float yaw, float dist) walkmove = #32; // returns true or false
+// #33 was removed
+float() droptofloor= #34; // true if landed on floor
+void(float style, string value) lightstyle = #35;
+float(float v) rint = #36; // round to nearest int
+float(float v) floor = #37; // largest integer <= v
+float(float v) ceil = #38; // smallest integer >= v
+// #39 was removed
+float(entity e) checkbottom = #40; // true if self is on ground
+float(vector v) pointcontents = #41; // returns a CONTENT_*
+// #42 was removed
+float(float f) fabs = #43;
+vector(entity e, float speed) aim = #44; // returns the shooting vector
+float(string s) cvar = #45; // return cvar.value
+void(string s, ...) localcmd = #46; // put string into local que
+entity(entity e) nextent = #47; // for looping through all ents
+void(vector o, vector d, float color, float count) particle = #48;// start a particle effect
+void() ChangeYaw = #49; // turn towards self.ideal_yaw
+ // at self.yaw_speed
+// #50 was removed
+vector(vector v) vectoangles = #51;
+
+//
+// direct client message generation
+//
+void(float to, float f) WriteByte = #52;
+void(float to, float f) WriteChar = #53;
+void(float to, float f) WriteShort = #54;
+void(float to, float f) WriteLong = #55;
+void(float to, float f) WriteCoord = #56;
+void(float to, float f) WriteAngle = #57;
+void(float to, string s, ...) WriteString = #58;
+void(float to, entity s) WriteEntity = #59;
+
+//
+// broadcast client message generation
+//
+
+// void(float f) bWriteByte = #59;
+// void(float f) bWriteChar = #60;
+// void(float f) bWriteShort = #61;
+// void(float f) bWriteLong = #62;
+// void(float f) bWriteCoord = #63;
+// void(float f) bWriteAngle = #64;
+// void(string s) bWriteString = #65;
+// void(entity e) bWriteEntity = #66;
+
+void(float step) movetogoal = #67;
+
+string(string s) precache_file = #68; // no effect except for -copy
+void(entity e) makestatic = #69;
+void(string s) changelevel = #70;
+
+//#71 was removed
+
+void(string name, string value) cvar_set = #72; // sets cvar.value
+
+void(entity client, string s, ...) centerprint = #73; // sprint, but in middle
+
+void(vector pos, string samp, float vol, float atten) ambientsound = #74;
+
+string(string s) precache_model2 = #75; // registered version only
+string(string s) precache_sound2 = #76; // registered version only
+string(string s) precache_file2 = #77; // registered version only
+
+void(entity e) setspawnparms = #78; // set parm1... to the
+ // values at level start
+ // for coop respawn
+
+//============================================================================
+#endif
--- /dev/null
+#ifdef INTERFACE
+CLASS(Item) EXTENDS(Object)
+ METHOD(Item, draw, void(entity))
+ METHOD(Item, keyDown, float(entity, float, float, float))
+ METHOD(Item, keyUp, float(entity, float, float, float))
+ METHOD(Item, mouseMove, float(entity, vector))
+ METHOD(Item, mousePress, float(entity, vector))
+ METHOD(Item, mouseDrag, float(entity, vector))
+ METHOD(Item, mouseRelease, float(entity, vector))
+ METHOD(Item, focusEnter, void(entity))
+ METHOD(Item, focusLeave, void(entity))
+ METHOD(Item, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(Item, relinquishFocus, void(entity))
+ METHOD(Item, showNotify, void(entity))
+ METHOD(Item, hideNotify, void(entity))
+ METHOD(Item, toString, string(entity))
+ METHOD(Item, destroy, void(entity))
+ ATTRIB(Item, focused, float, 0)
+ ATTRIB(Item, focusable, float, 0)
++ ATTRIB(Item, allowFocusSound, float, 0)
+ ATTRIB(Item, parent, entity, NULL)
+ ATTRIB(Item, preferredFocusPriority, float, 0)
+ ATTRIB(Item, origin, vector, '0 0 0')
+ ATTRIB(Item, size, vector, '0 0 0')
+ ATTRIB(Item, tooltip, string, string_null)
+ENDCLASS(Item)
+#endif
+
+#ifdef IMPLEMENTATION
+void Item_destroy(entity me)
+{
+ // free memory associated with me
+}
+
+void Item_relinquishFocus(entity me)
+{
+ if(me.parent)
+ if(me.parent.instanceOfContainer)
+ me.parent.setFocus(me.parent, NULL);
+}
+
+void Item_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ me.origin = absOrigin;
+ me.size = absSize;
+}
+
+float autocvar_menu_showboxes;
+void Item_draw(entity me)
+{
+ if(autocvar_menu_showboxes)
+ {
+ vector rgb = '1 0 1';
+ float a = fabs(autocvar_menu_showboxes);
+
+ // don't draw containers and border images
+ if(me.instanceOfContainer || me.instanceOfBorderImage)
+ {
+ rgb = '0 0 0';
+ a = 0;
+ }
+
+#if 0
+ // hack to detect multi drawing
+ float r = random() * 3;
+ if(r >= 2)
+ rgb = '1 0 0';
+ else if(r >= 1)
+ rgb = '0 1 0';
+ else
+ rgb = '0 0 1';
+#endif
+ if(autocvar_menu_showboxes < 0)
+ {
+ draw_Fill('0 0 0', '0.5 0.5 0', rgb, a);
+ draw_Fill('0.5 0.5 0', '0.5 0.5 0', rgb, a);
+ }
+ if(autocvar_menu_showboxes > 0)
+ {
+ draw_Fill('0 0 0', '1 1 0', rgb, a);
+ }
+ }
+}
+
+void Item_showNotify(entity me)
+{
+}
+
+void Item_hideNotify(entity me)
+{
+}
+
+float Item_keyDown(entity me, float scan, float ascii, float shift)
+{
+ return 0; // unhandled
+}
+
+float Item_keyUp(entity me, float scan, float ascii, float shift)
+{
+ return 0; // unhandled
+}
+
+float Item_mouseMove(entity me, vector pos)
+{
+ return 0; // unhandled
+}
+
+float Item_mousePress(entity me, vector pos)
+{
+ return 0; // unhandled
+}
+
+float Item_mouseDrag(entity me, vector pos)
+{
+ return 0; // unhandled
+}
+
+float Item_mouseRelease(entity me, vector pos)
+{
+ return 0; // unhandled
+}
+
+void Item_focusEnter(entity me)
+{
++ if(me.allowFocusSound)
++ m_play_focus_sound();
+}
+
+void Item_focusLeave(entity me)
+{
+}
+
+string Item_toString(entity me)
+{
+ return string_null;
+}
+#endif
--- /dev/null
- METHOD(Button, focusEnter, void(entity))
+#ifdef INTERFACE
+CLASS(Button) EXTENDS(Label)
+ METHOD(Button, configureButton, void(entity, string, float, string))
+ METHOD(Button, draw, void(entity))
+ METHOD(Button, showNotify, void(entity))
+ METHOD(Button, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(Button, keyDown, float(entity, float, float, float))
+ METHOD(Button, mousePress, float(entity, vector))
+ METHOD(Button, mouseDrag, float(entity, vector))
+ METHOD(Button, mouseRelease, float(entity, vector))
- if(cvar("menu_sounds"))
- localsound("sound/misc/menu2.wav");
++ METHOD(Button, playClickSound, void(entity))
+ ATTRIB(Button, onClick, void(entity, entity), func_null)
+ ATTRIB(Button, onClickEntity, entity, NULL)
+ ATTRIB(Button, src, string, string_null)
+ ATTRIB(Button, srcSuffix, string, string_null)
+ ATTRIB(Button, src2, string, string_null) // is centered, same aspect, and stretched to label size
+ ATTRIB(Button, src2scale, float, 1)
+ ATTRIB(Button, srcMulti, float, 1) // 0: button square left, text right; 1: button stretched, text over it
+ ATTRIB(Button, buttonLeftOfText, float, 0)
+ ATTRIB(Button, focusable, float, 1)
++ ATTRIB(Button, allowFocusSound, float, 1)
+ ATTRIB(Button, pressed, float, 0)
+ ATTRIB(Button, clickTime, float, 0)
+ ATTRIB(Button, disabled, float, 0)
+ ATTRIB(Button, disabledAlpha, float, 0.3)
+ ATTRIB(Button, forcePressed, float, 0)
+ ATTRIB(Button, color, vector, '1 1 1')
+ ATTRIB(Button, colorC, vector, '1 1 1')
+ ATTRIB(Button, colorF, vector, '1 1 1')
+ ATTRIB(Button, colorD, vector, '1 1 1')
+ ATTRIB(Button, color2, vector, '1 1 1')
+ ATTRIB(Button, alpha2, float, 1)
+
+ ATTRIB(Button, origin, vector, '0 0 0')
+ ATTRIB(Button, size, vector, '0 0 0')
+ENDCLASS(Button)
+#endif
+
+#ifdef IMPLEMENTATION
+void Button_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ if(me.srcMulti)
+ me.keepspaceLeft = 0;
+ else
+ me.keepspaceLeft = min(0.8, absSize.y / absSize.x);
+ SUPER(Button).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+}
+void Button_configureButton(entity me, string txt, float sz, string gfx)
+{
+ SUPER(Button).configureLabel(me, txt, sz, me.srcMulti ? 0.5 : 0);
+ me.src = gfx;
+}
+float Button_keyDown(entity me, float key, float ascii, float shift)
+{
+ if(key == K_ENTER || key == K_SPACE || key == K_KP_ENTER)
+ {
++ me.playClickSound(me);
+ me.clickTime = 0.1; // delayed for effect
+ return 1;
+ }
+ return 0;
+}
+float Button_mouseDrag(entity me, vector pos)
+{
+ me.pressed = 1;
+ if(pos.x < 0) me.pressed = 0;
+ if(pos.y < 0) me.pressed = 0;
+ if(pos.x >= 1) me.pressed = 0;
+ if(pos.y >= 1) me.pressed = 0;
+ return 1;
+}
+float Button_mousePress(entity me, vector pos)
+{
+ me.mouseDrag(me, pos); // verify coordinates
+ return 1;
+}
+float Button_mouseRelease(entity me, vector pos)
+{
+ me.mouseDrag(me, pos); // verify coordinates
+ if(me.pressed)
+ {
+ if (!me.disabled)
+ {
- void Button_focusEnter(entity me)
- {
- if(cvar("menu_sounds") > 1)
- localsound("sound/misc/menu1.wav");
- SUPER(Button).focusEnter(me);
- }
++ me.playClickSound(me);
+ if(me.onClick)
+ me.onClick(me, me.onClickEntity);
+ }
+ me.pressed = 0;
+ }
+ return 1;
+}
+void Button_showNotify(entity me)
+{
+ me.focusable = !me.disabled;
+}
+void Button_draw(entity me)
+{
+ vector bOrigin, bSize;
+ float save;
+
+ me.focusable = !me.disabled;
+
+ save = draw_alpha;
+ if(me.disabled)
+ draw_alpha *= me.disabledAlpha;
+
+ if(me.src)
+ {
+ if(me.srcMulti)
+ {
+ bOrigin = '0 0 0';
+ bSize = '1 1 0';
+ if(me.disabled)
+ draw_ButtonPicture(bOrigin, strcat(me.src, "_d", me.srcSuffix), bSize, me.colorD, 1);
+ else if(me.forcePressed || me.pressed || me.clickTime > 0)
+ draw_ButtonPicture(bOrigin, strcat(me.src, "_c", me.srcSuffix), bSize, me.colorC, 1);
+ else if(me.focused)
+ draw_ButtonPicture(bOrigin, strcat(me.src, "_f", me.srcSuffix), bSize, me.colorF, 1);
+ else
+ draw_ButtonPicture(bOrigin, strcat(me.src, "_n", me.srcSuffix), bSize, me.color, 1);
+ }
+ else
+ {
+ if(me.realFontSize_y == 0)
+ {
+ bOrigin = '0 0 0';
+ bSize = '1 1 0';
+ }
+ else
+ {
+ bOrigin = eY * (0.5 * (1 - me.realFontSize.y)) + eX * (0.5 * (me.keepspaceLeft - me.realFontSize.x));
+ bSize = me.realFontSize;
+ }
+ if(me.disabled)
+ draw_Picture(bOrigin, strcat(me.src, "_d", me.srcSuffix), bSize, me.colorD, 1);
+ else if(me.forcePressed || me.pressed || me.clickTime > 0)
+ draw_Picture(bOrigin, strcat(me.src, "_c", me.srcSuffix), bSize, me.colorC, 1);
+ else if(me.focused)
+ draw_Picture(bOrigin, strcat(me.src, "_f", me.srcSuffix), bSize, me.colorF, 1);
+ else
+ draw_Picture(bOrigin, strcat(me.src, "_n", me.srcSuffix), bSize, me.color, 1);
+ }
+ }
+ if(me.src2)
+ {
+ bOrigin = me.keepspaceLeft * eX;
+ bSize = eY + eX * (1 - me.keepspaceLeft);
+
+ bOrigin += bSize * (0.5 - 0.5 * me.src2scale);
+ bSize = bSize * me.src2scale;
+
+ draw_Picture(bOrigin, me.src2, bSize, me.color2, me.alpha2);
+ }
+
+ draw_alpha = save;
+
+ if(me.clickTime > 0 && me.clickTime <= frametime)
+ {
+ // keyboard click timer expired? Fire the event then.
+ if (!me.disabled)
+ if(me.onClick)
+ me.onClick(me, me.onClickEntity);
+ }
+ me.clickTime -= frametime;
+
+ SUPER(Button).draw(me);
+}
++void Dialog_Close(entity button, entity me);
++void Button_playClickSound(entity me)
++{
++ if(me.onClick == DialogOpenButton_Click)
++ m_play_click_sound(MENU_SOUND_OPEN);
++ else if(me.onClick == Dialog_Close)
++ m_play_click_sound(MENU_SOUND_CLOSE);
++ else
++ m_play_click_sound(MENU_SOUND_EXECUTE);
++}
+#endif
--- /dev/null
+#ifdef INTERFACE
+void CheckBox_Click(entity me, entity other);
+CLASS(CheckBox) EXTENDS(Button)
+ METHOD(CheckBox, configureCheckBox, void(entity, string, float, string))
+ METHOD(CheckBox, draw, void(entity))
++ METHOD(CheckBox, playClickSound, void(entity))
+ METHOD(CheckBox, toString, string(entity))
+ METHOD(CheckBox, setChecked, void(entity, float))
+ ATTRIB(CheckBox, useDownAsChecked, float, 0)
+ ATTRIB(CheckBox, checked, float, 0)
+ ATTRIB(CheckBox, onClick, void(entity, entity), CheckBox_Click)
+ ATTRIB(CheckBox, srcMulti, float, 0)
+ ATTRIB(CheckBox, disabled, float, 0)
+ENDCLASS(CheckBox)
+#endif
+
+#ifdef IMPLEMENTATION
+void CheckBox_setChecked(entity me, float val)
+{
+ me.checked = val;
+}
+void CheckBox_Click(entity me, entity other)
+{
+ me.setChecked(me, !me.checked);
+}
+string CheckBox_toString(entity me)
+{
+ return strcat(SUPER(CheckBox).toString(me), ", ", me.checked ? "checked" : "unchecked");
+}
+void CheckBox_configureCheckBox(entity me, string txt, float sz, string gfx)
+{
+ me.configureButton(me, txt, sz, gfx);
+ me.align = 0;
+}
+void CheckBox_draw(entity me)
+{
+ float s;
+ s = me.pressed;
+ if(me.useDownAsChecked)
+ {
+ me.srcSuffix = string_null;
+ me.forcePressed = me.checked;
+ }
+ else
+ me.srcSuffix = (me.checked ? "1" : "0");
+ me.pressed = s;
+ SUPER(CheckBox).draw(me);
+}
++void CheckBox_playClickSound(entity me)
++{
++ m_play_click_sound(MENU_SOUND_SELECT);
++}
+#endif
--- /dev/null
+// Note: this class is called Dialog, but it can also handle a tab under the following conditions:
+// - isTabRoot is 0
+// - backgroundImage is the tab's background
+// - closable is 0
+// - rootDialog is 0
+// - title is ""
+// - marginTop is
+// - intendedHeight ends up to be the tab's actual height, or at least close
+// - titleFontSize is 0
+// - marginTop cancels out as much of titleHeight as needed (that is, it should be actualMarginTop - titleHeight)
+// To ensure the latter, you best create all tabs FIRST and insert the tabbed
+// control to your dialog THEN - with the right height
+//
+// a subclass may help with using this as a tab
+
+#ifdef INTERFACE
+CLASS(Dialog) EXTENDS(InputContainer)
+ METHOD(Dialog, configureDialog, void(entity)) // no runtime configuration, all parameters are given in the code!
+ METHOD(Dialog, fill, void(entity)) // to be overridden by user to fill the dialog with controls
+ METHOD(Dialog, keyDown, float(entity, float, float, float))
+ METHOD(Dialog, close, void(entity))
+ METHOD(Dialog, addItemSimple, void(entity, float, float, float, float, entity, vector))
+
+ METHOD(Dialog, TD, void(entity, float, float, entity))
+ METHOD(Dialog, TDNoMargin, void(entity, float, float, entity, vector))
+ METHOD(Dialog, TDempty, void(entity, float))
+ METHOD(Dialog, setFirstColumn, void(entity, float))
+ METHOD(Dialog, TR, void(entity))
+ METHOD(Dialog, gotoRC, void(entity, float, float))
+
+ ATTRIB(Dialog, isTabRoot, float, 1)
+ ATTRIB(Dialog, closeButton, entity, NULL)
+ ATTRIB(Dialog, intendedHeight, float, 0)
+ ATTRIB(Dialog, itemOrigin, vector, '0 0 0')
+ ATTRIB(Dialog, itemSize, vector, '0 0 0')
+ ATTRIB(Dialog, itemSpacing, vector, '0 0 0')
+ ATTRIB(Dialog, currentRow, float, 0)
+ ATTRIB(Dialog, currentColumn, float, 0)
+ ATTRIB(Dialog, firstColumn, float, 0)
+
+ // to be customized
+ ATTRIB(Dialog, closable, float, 1)
+ ATTRIB(Dialog, title, string, "Form1") // ;)
+ ATTRIB(Dialog, color, vector, '1 0.5 1')
+ ATTRIB(Dialog, intendedWidth, float, 0)
+ ATTRIB(Dialog, rows, float, 3)
+ ATTRIB(Dialog, columns, float, 2)
+
+ ATTRIB(Dialog, marginTop, float, 0) // pixels
+ ATTRIB(Dialog, marginBottom, float, 0) // pixels
+ ATTRIB(Dialog, marginLeft, float, 0) // pixels
+ ATTRIB(Dialog, marginRight, float, 0) // pixels
+ ATTRIB(Dialog, columnSpacing, float, 0) // pixels
+ ATTRIB(Dialog, rowSpacing, float, 0) // pixels
+ ATTRIB(Dialog, rowHeight, float, 0) // pixels
+ ATTRIB(Dialog, titleHeight, float, 0) // pixels
+ ATTRIB(Dialog, titleFontSize, float, 0) // pixels; if 0, title causes no margin
+ ATTRIB(Dialog, zoomedOutTitleBarPosition, float, 0)
+ ATTRIB(Dialog, zoomedOutTitleBar, float, 0)
+
+ ATTRIB(Dialog, requiresConnection, float, 0) // set to true if the dialog requires a connection to be opened
+
+ ATTRIB(Dialog, backgroundImage, string, string_null)
+ ATTRIB(Dialog, borderLines, float, 1)
+ ATTRIB(Dialog, closeButtonImage, string, string_null)
+
+ ATTRIB(Dialog, frame, entity, NULL)
+ENDCLASS(Dialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void Dialog_Close(entity button, entity me)
+{
+ me.close(me);
+}
+
+void Dialog_fill(entity me)
+{
+}
+
+void Dialog_addItemSimple(entity me, float row, float col, float rowspan, float colspan, entity e, vector v)
+{
+ vector o, s;
+ o = me.itemOrigin + eX * ( col * me.itemSpacing.x) + eY * ( row * me.itemSpacing.y);
+ s = me.itemSize + eX * ((colspan - 1) * me.itemSpacing.x) + eY * ((rowspan - 1) * me.itemSpacing.y);
+ o.x -= 0.5 * (me.itemSpacing.x - me.itemSize.x) * v.x;
+ s.x += (me.itemSpacing.x - me.itemSize.x) * v.x;
+ o.y -= 0.5 * (me.itemSpacing.y - me.itemSize.y) * v.y;
+ s.y += (me.itemSpacing.y - me.itemSize.y) * v.y;
+ me.addItem(me, e, o, s, 1);
+}
+
+void Dialog_gotoRC(entity me, float row, float col)
+{
+ me.currentRow = row;
+ me.currentColumn = col;
+}
+
+void Dialog_TR(entity me)
+{
+ me.currentRow += 1;
+ me.currentColumn = me.firstColumn;
+}
+
+void Dialog_TD(entity me, float rowspan, float colspan, entity e)
+{
+ me.addItemSimple(me, me.currentRow, me.currentColumn, rowspan, colspan, e, '0 0 0');
+ me.currentColumn += colspan;
+}
+
+void Dialog_TDNoMargin(entity me, float rowspan, float colspan, entity e, vector v)
+{
+ me.addItemSimple(me, me.currentRow, me.currentColumn, rowspan, colspan, e, v);
+ me.currentColumn += colspan;
+}
+
+void Dialog_setFirstColumn(entity me, float col)
+{
+ me.firstColumn = col;
+}
+
+void Dialog_TDempty(entity me, float colspan)
+{
+ me.currentColumn += colspan;
+}
+
+void Dialog_configureDialog(entity me)
+{
+ float absWidth, absHeight;
+
+ me.frame = spawnBorderImage();
+ me.frame.configureBorderImage(me.frame, me.title, me.titleFontSize, me.color, me.backgroundImage, me.borderLines * me.titleHeight);
+ me.frame.zoomedOutTitleBarPosition = me.zoomedOutTitleBarPosition;
+ me.frame.zoomedOutTitleBar = me.zoomedOutTitleBar;
+ me.frame.alpha = me.alpha;
+ me.addItem(me, me.frame, '0 0 0', '1 1 0', 1);
+
+ if (!me.titleFontSize)
+ me.titleHeight = 0; // no title bar
+
+ absWidth = me.intendedWidth * conwidth;
+ absHeight = me.borderLines * me.titleHeight + me.marginTop + me.rows * me.rowHeight + (me.rows - 1) * me.rowSpacing + me.marginBottom;
+ me.itemOrigin = eX * (me.marginLeft / absWidth)
+ + eY * ((me.borderLines * me.titleHeight + me.marginTop) / absHeight);
+ me.itemSize = eX * ((1 - (me.marginLeft + me.marginRight + me.columnSpacing * (me.columns - 1)) / absWidth) / me.columns)
+ + eY * (me.rowHeight / absHeight);
+ me.itemSpacing = me.itemSize
+ + eX * (me.columnSpacing / absWidth)
+ + eY * (me.rowSpacing / absHeight);
+ me.intendedHeight = absHeight / conheight;
+ me.currentRow = -1;
+ me.currentColumn = -1;
+
+ me.fill(me);
+
+ if(me.closable && me.borderLines > 0)
+ {
+ entity closebutton;
+ closebutton = me.closeButton = me.frame.closeButton = spawnButton();
+ closebutton.configureButton(closebutton, "", 0, me.closeButtonImage);
+ closebutton.onClick = Dialog_Close; closebutton.onClickEntity = me;
+ closebutton.srcMulti = 0;
+ me.addItem(me, closebutton, '0 0 0', '1 1 0', 1); // put it as LAST
+ }
+}
+
+void Dialog_close(entity me)
+{
+ if(me.parent.instanceOfNexposee)
+ {
+ ExposeeCloseButton_Click(me, me.parent);
+ }
+ else if(me.parent.instanceOfModalController)
+ {
+ DialogCloseButton_Click(me, me);
+ }
+}
+
+float Dialog_keyDown(entity me, float key, float ascii, float shift)
+{
+ if(me.closable)
+ {
+ if(key == K_ESCAPE)
+ {
++ m_play_click_sound(MENU_SOUND_CLOSE);
+ me.close(me);
+ return 1;
+ }
+ }
+ return SUPER(Dialog).keyDown(me, key, ascii, shift);
+}
+#endif
--- /dev/null
- void InputBox_Clear_Click(entity btn, entity me);
+#ifdef INTERFACE
+CLASS(InputBox) EXTENDS(Label)
+ METHOD(InputBox, configureInputBox, void(entity, string, float, float, string))
+ METHOD(InputBox, draw, void(entity))
+ METHOD(InputBox, setText, void(entity, string))
+ METHOD(InputBox, enterText, void(entity, string))
+ METHOD(InputBox, keyDown, float(entity, float, float, float))
+ METHOD(InputBox, mouseMove, float(entity, vector))
+ METHOD(InputBox, mouseRelease, float(entity, vector))
+ METHOD(InputBox, mousePress, float(entity, vector))
+ METHOD(InputBox, mouseDrag, float(entity, vector))
+ METHOD(InputBox, showNotify, void(entity))
+ METHOD(InputBox, resizeNotify, void(entity, vector, vector, vector, vector))
+
+ ATTRIB(InputBox, src, string, string_null)
+
+ ATTRIB(InputBox, cursorPos, float, 0) // characters
+ ATTRIB(InputBox, scrollPos, float, 0) // widths
+
+ ATTRIB(InputBox, focusable, float, 1)
++ ATTRIB(InputBox, allowFocusSound, float, 1)
+ ATTRIB(InputBox, disabled, float, 0)
+ ATTRIB(InputBox, lastChangeTime, float, 0)
+ ATTRIB(InputBox, dragScrollTimer, float, 0)
+ ATTRIB(InputBox, dragScrollPos, vector, '0 0 0')
+ ATTRIB(InputBox, pressed, float, 0)
+ ATTRIB(InputBox, editColorCodes, float, 1)
+ ATTRIB(InputBox, forbiddenCharacters, string, "")
+ ATTRIB(InputBox, color, vector, '1 1 1')
+ ATTRIB(InputBox, colorF, vector, '1 1 1')
+ ATTRIB(InputBox, maxLength, float, 255) // if negative, it counts bytes, not chars
+
+ ATTRIB(InputBox, enableClearButton, float, 1)
+ ATTRIB(InputBox, clearButton, entity, NULL)
+ ATTRIB(InputBox, cb_width, float, 0)
+ ATTRIB(InputBox, cb_pressed, float, 0)
+ ATTRIB(InputBox, cb_focused, float, 0)
+ ATTRIB(InputBox, cb_color, vector, '1 1 1')
+ ATTRIB(InputBox, cb_colorF, vector, '1 1 1')
+ ATTRIB(InputBox, cb_colorC, vector, '1 1 1')
+ENDCLASS(InputBox)
- void InputBox_Clear_Click(entity btn, entity me)
- {
- me.setText(me, "");
- }
-
+#endif
+
+#ifdef IMPLEMENTATION
+void InputBox_configureInputBox(entity me, string theText, float theCursorPos, float theFontSize, string gfx)
+{
+ SUPER(InputBox).configureLabel(me, theText, theFontSize, 0.0);
+ me.src = gfx;
+ me.cursorPos = theCursorPos;
+}
+void InputBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ SUPER(InputBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+ if (me.enableClearButton)
+ {
+ me.cb_width = absSize.y / absSize.x;
+ me.cb_offset = bound(-1, me.cb_offset, 0) * me.cb_width; // bound to range -1, 0
+ me.keepspaceRight = me.keepspaceRight - me.cb_offset + me.cb_width;
+ }
+}
+
+void InputBox_setText(entity me, string txt)
+{
+ if(me.text)
+ strunzone(me.text);
+ SUPER(InputBox).setText(me, strzone(txt));
+}
+
- InputBox_Clear_Click(world, me);
+float over_ClearButton(entity me, vector pos)
+{
+ if (pos.x >= 1 + me.cb_offset - me.cb_width)
+ if (pos.x < 1 + me.cb_offset)
+ if (pos.y >= 0)
+ if (pos.y < 1)
+ return 1;
+ return 0;
+}
+
+float InputBox_mouseMove(entity me, vector pos)
+{
+ if (me.enableClearButton)
+ {
+ if (over_ClearButton(me, pos))
+ {
+ me.cb_focused = 1;
+ return 1;
+ }
+ me.cb_focused = 0;
+ }
+ return 1;
+}
+
+float InputBox_mouseDrag(entity me, vector pos)
+{
+ float p;
+ if(me.pressed)
+ {
+ me.dragScrollPos = pos;
+ p = me.scrollPos + pos.x - me.keepspaceLeft;
+ me.cursorPos = draw_TextLengthUpToWidth(me.text, p, 0, me.realFontSize);
+ me.lastChangeTime = time;
+ }
+ else if (me.enableClearButton)
+ {
+ if (over_ClearButton(me, pos))
+ {
+ me.cb_pressed = 1;
+ return 1;
+ }
+ }
+ me.cb_pressed = 0;
+ return 1;
+}
+
+float InputBox_mousePress(entity me, vector pos)
+{
+ if (me.enableClearButton)
+ if (over_ClearButton(me, pos))
+ {
+ me.cb_pressed = 1;
+ return 1;
+ }
+ me.dragScrollTimer = time;
+ me.pressed = 1;
+ return InputBox_mouseDrag(me, pos);
+}
+
+float InputBox_mouseRelease(entity me, vector pos)
+{
+ if(me.cb_pressed)
+ if (over_ClearButton(me, pos))
+ {
++ m_play_click_sound(MENU_SOUND_CLEAR);
++ me.setText(me, "");
+ me.cb_pressed = 0;
+ return 1;
+ }
+ float r = InputBox_mouseDrag(me, pos);
+ //reset cb_pressed after mouseDrag, mouseDrag could set cb_pressed in this case:
+ //mouse press out of the clear button, drag and then mouse release over the clear button
+ me.cb_pressed = 0;
+ me.pressed = 0;
+ return r;
+}
+
+void InputBox_enterText(entity me, string ch)
+{
+ float i;
+ for(i = 0; i < strlen(ch); ++i)
+ if(strstrofs(me.forbiddenCharacters, substring(ch, i, 1), 0) > -1)
+ return;
+ if(me.maxLength > 0)
+ {
+ if(strlen(ch) + strlen(me.text) > me.maxLength)
+ return;
+ }
+ else if(me.maxLength < 0)
+ {
+ if(u8_strsize(ch) + u8_strsize(me.text) > -me.maxLength)
+ return;
+ }
+ me.setText(me, strcat(substring(me.text, 0, me.cursorPos), ch, substring(me.text, me.cursorPos, strlen(me.text) - me.cursorPos)));
+ me.cursorPos += strlen(ch);
+}
+
+float InputBox_keyDown(entity me, float key, float ascii, float shift)
+{
+ me.lastChangeTime = time;
+ me.dragScrollTimer = time;
+ if(ascii >= 32 && ascii != 127)
+ {
+ me.enterText(me, chr(ascii));
+ return 1;
+ }
+ switch(key)
+ {
+ case K_KP_LEFTARROW:
+ case K_LEFTARROW:
+ me.cursorPos -= 1;
+ return 1;
+ case K_KP_RIGHTARROW:
+ case K_RIGHTARROW:
+ me.cursorPos += 1;
+ return 1;
+ case K_KP_HOME:
+ case K_HOME:
+ me.cursorPos = 0;
+ return 1;
+ case K_KP_END:
+ case K_END:
+ me.cursorPos = strlen(me.text);
+ return 1;
+ case K_BACKSPACE:
+ if(me.cursorPos > 0)
+ {
+ me.cursorPos -= 1;
+ me.setText(me, strcat(substring(me.text, 0, me.cursorPos), substring(me.text, me.cursorPos + 1, strlen(me.text) - me.cursorPos - 1)));
+ }
+ return 1;
+ case K_KP_DEL:
+ case K_DEL:
+ if(shift & S_CTRL)
++ {
++ m_play_click_sound(MENU_SOUND_CLEAR);
+ me.setText(me, "");
++ }
+ else
+ me.setText(me, strcat(substring(me.text, 0, me.cursorPos), substring(me.text, me.cursorPos + 1, strlen(me.text) - me.cursorPos - 1)));
+ return 1;
+ }
+ return 0;
+}
+
+void InputBox_draw(entity me)
+{
+ string CURSOR = "_";
+ float cursorPosInWidths, totalSizeInWidths;
+
+ if(me.pressed)
+ me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
+
+ if(me.recalcPos)
+ me.recalcPositionWithText(me, me.text);
+
+ me.focusable = !me.disabled;
+ if(me.disabled)
+ draw_alpha *= me.disabledAlpha;
+
+ if(me.src)
+ {
+ if(me.focused && !me.disabled)
+ draw_ButtonPicture('0 0 0', strcat(me.src, "_f"), '1 1 0', me.colorF, 1);
+ else
+ draw_ButtonPicture('0 0 0', strcat(me.src, "_n"), '1 1 0', me.color, 1);
+ }
+
+ me.cursorPos = bound(0, me.cursorPos, strlen(me.text));
+ cursorPosInWidths = draw_TextWidth(substring(me.text, 0, me.cursorPos), 0, me.realFontSize);
+ totalSizeInWidths = draw_TextWidth(strcat(me.text, CURSOR), 0, me.realFontSize);
+
+ if(me.dragScrollTimer < time)
+ {
+ float save;
+ save = me.scrollPos;
+ me.scrollPos = bound(cursorPosInWidths - (0.875 - me.keepspaceLeft - me.keepspaceRight), me.scrollPos, cursorPosInWidths - 0.125);
+ if(me.scrollPos != save)
+ me.dragScrollTimer = time + 0.2;
+ }
+ me.scrollPos = min(me.scrollPos, totalSizeInWidths - (1 - me.keepspaceRight - me.keepspaceLeft));
+ me.scrollPos = max(0, me.scrollPos);
+
+ draw_SetClipRect(eX * me.keepspaceLeft, eX * (1 - me.keepspaceLeft - me.keepspaceRight) + eY);
+ if(me.editColorCodes)
+ {
+ string ch, ch2;
+ float i, n;
+ vector theColor;
+ float theAlpha; //float theVariableAlpha;
+ vector p;
+ vector theTempColor;
+ float component;
+
+ p = me.realOrigin - eX * me.scrollPos;
+ theColor = '1 1 1';
+ theAlpha = 1; //theVariableAlpha = 1; // changes when ^ax found
+
+ n = strlen(me.text);
+ for(i = 0; i < n; ++i)
+ {
+ ch = substring(me.text, i, 1);
+ if(ch == "^")
+ {
+ float w;
+ ch2 = substring(me.text, i+1, 1);
+ w = draw_TextWidth(strcat(ch, ch2), 0, me.realFontSize);
+ if(ch2 == "^")
+ {
+ draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
+ draw_Text(p + eX * 0.25 * w, "^", me.realFontSize, theColor, theAlpha, 0);
+ }
+ else if(ch2 == "0" || stof(ch2)) // digit?
+ {
+ switch(stof(ch2))
+ {
+ case 0: theColor = '0 0 0'; theAlpha = 1; break;
+ case 1: theColor = '1 0 0'; theAlpha = 1; break;
+ case 2: theColor = '0 1 0'; theAlpha = 1; break;
+ case 3: theColor = '1 1 0'; theAlpha = 1; break;
+ case 4: theColor = '0 0 1'; theAlpha = 1; break;
+ case 5: theColor = '0 1 1'; theAlpha = 1; break;
+ case 6: theColor = '1 0 1'; theAlpha = 1; break;
+ case 7: theColor = '1 1 1'; theAlpha = 1; break;
+ case 8: theColor = '1 1 1'; theAlpha = 0.5; break;
+ case 9: theColor = '0.5 0.5 0.5'; theAlpha = 1; break;
+ }
+ draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
+ draw_Text(p, strcat(ch, ch2), me.realFontSize, theColor, theAlpha, 0);
+ }
+ else if(ch2 == "x") // ^x found
+ {
+ theColor = '1 1 1';
+
+ component = HEXDIGIT_TO_DEC(substring(me.text, i+2, 1));
+ if (component >= 0) // ^xr found
+ {
+ theTempColor.x = component/15;
+
+ component = HEXDIGIT_TO_DEC(substring(me.text, i+3, 1));
+ if (component >= 0) // ^xrg found
+ {
+ theTempColor.y = component/15;
+
+ component = HEXDIGIT_TO_DEC(substring(me.text, i+4, 1));
+ if (component >= 0) // ^xrgb found
+ {
+ theTempColor.z = component/15;
+ theColor = theTempColor;
+ w = draw_TextWidth(substring(me.text, i, 5), 0, me.realFontSize);
+
+ draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
+ draw_Text(p, substring(me.text, i, 5), me.realFontSize, theColor, 1, 0); // theVariableAlpha instead of 1 using alpha tags ^ax
+ i += 3;
+ }
+ else
+ {
+ // blue missing
+ w = draw_TextWidth(substring(me.text, i, 4), 0, me.realFontSize);
+ draw_Fill(p, eX * w + eY * me.realFontSize.y, eZ, 0.5);
+ draw_Text(p, substring(me.text, i, 4), me.realFontSize, '1 1 1', theAlpha, 0);
+ i += 2;
+ }
+ }
+ else
+ {
+ // green missing
+ w = draw_TextWidth(substring(me.text, i, 3), 0, me.realFontSize);
+ draw_Fill(p, eX * w + eY * me.realFontSize.y, eY, 0.5);
+ draw_Text(p, substring(me.text, i, 3), me.realFontSize, '1 1 1', theAlpha, 0);
+ i += 1;
+ }
+ }
+ else
+ {
+ // red missing
+ //w = draw_TextWidth(substring(me.text, i, 2), 0) * me.realFontSize_x;
+ draw_Fill(p, eX * w + eY * me.realFontSize.y, eX, 0.5);
+ draw_Text(p, substring(me.text, i, 2), me.realFontSize, '1 1 1', theAlpha, 0);
+ }
+ }
+ else
+ {
+ draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
+ draw_Text(p, strcat(ch, ch2), me.realFontSize, theColor, theAlpha, 0);
+ }
+ p += w * eX;
+ ++i;
+ continue;
+ }
+ draw_Text(p, ch, me.realFontSize, theColor, theAlpha, 0); // TODO theVariableAlpha
+ p += eX * draw_TextWidth(ch, 0, me.realFontSize);
+ }
+ }
+ else
+ draw_Text(me.realOrigin - eX * me.scrollPos, me.text, me.realFontSize, '1 1 1', 1, 0);
+
+ if(!me.focused || (time - me.lastChangeTime) < floor(time - me.lastChangeTime) + 0.5)
+ draw_Text(me.realOrigin + eX * (cursorPosInWidths - me.scrollPos), CURSOR, me.realFontSize, '1 1 1', 1, 0);
+
+ draw_ClearClip();
+
+ if (me.enableClearButton)
+ if (me.text != "")
+ {
+ if(me.focused && me.cb_pressed)
+ draw_Picture(eX * (1 + me.cb_offset - me.cb_width), strcat(me.cb_src, "_c"), eX * me.cb_width + eY, me.cb_colorC, 1);
+ else if(me.focused && me.cb_focused)
+ draw_Picture(eX * (1 + me.cb_offset - me.cb_width), strcat(me.cb_src, "_f"), eX * me.cb_width + eY, me.cb_colorF, 1);
+ else
+ draw_Picture(eX * (1 + me.cb_offset - me.cb_width), strcat(me.cb_src, "_n"), eX * me.cb_width + eY, me.cb_color, 1);
+ }
+
+ // skipping SUPER(InputBox).draw(me);
+ Item_draw(me);
+}
+
+void InputBox_showNotify(entity me)
+{
+ me.focusable = !me.disabled;
+}
+#endif
--- /dev/null
+#ifdef INTERFACE
+CLASS(ListBox) EXTENDS(Item)
+ METHOD(ListBox, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(ListBox, configureListBox, void(entity, float, float))
+ METHOD(ListBox, draw, void(entity))
+ METHOD(ListBox, keyDown, float(entity, float, float, float))
+ METHOD(ListBox, mousePress, float(entity, vector))
+ METHOD(ListBox, mouseDrag, float(entity, vector))
+ METHOD(ListBox, mouseRelease, float(entity, vector))
+ METHOD(ListBox, focusLeave, void(entity))
+ ATTRIB(ListBox, focusable, float, 1)
++ ATTRIB(ListBox, allowFocusSound, float, 1)
+ ATTRIB(ListBox, selectedItem, float, 0)
+ ATTRIB(ListBox, size, vector, '0 0 0')
+ ATTRIB(ListBox, origin, vector, '0 0 0')
+ ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
+ ATTRIB(ListBox, previousValue, float, 0)
+ ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
+ ATTRIB(ListBox, pressOffset, float, 0)
+
+ METHOD(ListBox, updateControlTopBottom, void(entity))
+ ATTRIB(ListBox, controlTop, float, 0)
+ ATTRIB(ListBox, controlBottom, float, 0)
+ ATTRIB(ListBox, controlWidth, float, 0)
+ ATTRIB(ListBox, dragScrollTimer, float, 0)
+ ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
+
+ ATTRIB(ListBox, src, string, string_null) // scrollbar
+ ATTRIB(ListBox, color, vector, '1 1 1')
+ ATTRIB(ListBox, color2, vector, '1 1 1')
+ ATTRIB(ListBox, colorC, vector, '1 1 1')
+ ATTRIB(ListBox, colorF, vector, '1 1 1')
+ ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
+ ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
+ ATTRIB(ListBox, nItems, float, 42)
+ ATTRIB(ListBox, itemHeight, float, 0)
+ ATTRIB(ListBox, colorBG, vector, '0 0 0')
+ ATTRIB(ListBox, alphaBG, float, 0)
+
+ ATTRIB(ListBox, lastClickedItem, float, -1)
+ ATTRIB(ListBox, lastClickedTime, float, 0)
+
+ METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
+ METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
+ METHOD(ListBox, doubleClickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
+ METHOD(ListBox, setSelected, void(entity, float))
+
+ METHOD(ListBox, getLastFullyVisibleItemAtScrollPos, float(entity, float))
+ METHOD(ListBox, getFirstFullyVisibleItemAtScrollPos, float(entity, float))
+
+ // NOTE: override these four methods if you want variable sized list items
+ METHOD(ListBox, getTotalHeight, float(entity))
+ METHOD(ListBox, getItemAtPos, float(entity, float))
+ METHOD(ListBox, getItemStart, float(entity, float))
+ METHOD(ListBox, getItemHeight, float(entity, float))
+ // NOTE: if getItemAt* are overridden, it may make sense to cache the
+ // start and height of the last item returned by getItemAtPos and fast
+ // track returning their properties for getItemStart and getItemHeight.
+ // The "hot" code path calls getItemAtPos first, then will query
+ // getItemStart and getItemHeight on it soon.
+ // When overriding, the following consistency rules must hold:
+ // getTotalHeight() == SUM(getItemHeight(i), i, 0, me.nItems-1)
+ // getItemStart(i+1) == getItemStart(i) + getItemHeight(i)
+ // for 0 <= i < me.nItems-1
+ // getItemStart(0) == 0
+ // getItemStart(getItemAtPos(p)) <= p
+ // if p >= 0
+ // getItemAtPos(p) == 0
+ // if p < 0
+ // getItemStart(getItemAtPos(p)) + getItemHeight(getItemAtPos(p)) > p
+ // if p < getTotalHeigt()
+ // getItemAtPos(p) == me.nItems - 1
+ // if p >= getTotalHeight()
+ENDCLASS(ListBox)
+#endif
+
+#ifdef IMPLEMENTATION
+void ListBox_setSelected(entity me, float i)
+{
+ me.selectedItem = bound(0, i, me.nItems - 1);
+}
+void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+ me.controlWidth = me.scrollbarWidth / absSize.x;
+}
+void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
+{
+ me.scrollbarWidth = theScrollbarWidth;
+ me.itemHeight = theItemHeight;
+}
+
+float ListBox_getTotalHeight(entity me)
+{
+ return me.nItems * me.itemHeight;
+}
+float ListBox_getItemAtPos(entity me, float pos)
+{
+ return floor(pos / me.itemHeight);
+}
+float ListBox_getItemStart(entity me, float i)
+{
+ return me.itemHeight * i;
+}
+float ListBox_getItemHeight(entity me, float i)
+{
+ return me.itemHeight;
+}
+
+float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
+{
+ return me.getItemAtPos(me, pos + 1.001) - 1;
+}
+float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
+{
+ return me.getItemAtPos(me, pos - 0.001) + 1;
+}
+float ListBox_keyDown(entity me, float key, float ascii, float shift)
+{
+ me.dragScrollTimer = time;
+ if(key == K_MWHEELUP)
+ {
+ me.scrollPos = max(me.scrollPos - 0.5, 0);
+ me.setSelected(me, min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
+ }
+ else if(key == K_MWHEELDOWN)
+ {
+ me.scrollPos = min(me.scrollPos + 0.5, me.getTotalHeight(me) - 1);
+ me.setSelected(me, max(me.selectedItem, me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
+ }
+ else if(key == K_PGUP || key == K_KP_PGUP)
+ {
+ float i = me.selectedItem;
+ float a = me.getItemHeight(me, i);
+ for (;;)
+ {
+ --i;
+ if (i < 0)
+ break;
+ a += me.getItemHeight(me, i);
+ if (a >= 1)
+ break;
+ }
+ me.setSelected(me, i + 1);
+ }
+ else if(key == K_PGDN || key == K_KP_PGDN)
+ {
+ float i = me.selectedItem;
+ float a = me.getItemHeight(me, i);
+ for (;;)
+ {
+ ++i;
+ if (i >= me.nItems)
+ break;
+ a += me.getItemHeight(me, i);
+ if (a >= 1)
+ break;
+ }
+ me.setSelected(me, i - 1);
+ }
+ else if(key == K_UPARROW || key == K_KP_UPARROW)
+ me.setSelected(me, me.selectedItem - 1);
+ else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
+ me.setSelected(me, me.selectedItem + 1);
+ else if(key == K_HOME || key == K_KP_HOME)
+ {
+ me.scrollPos = 0;
+ me.setSelected(me, 0);
+ }
+ else if(key == K_END || key == K_KP_END)
+ {
+ me.scrollPos = max(0, me.getTotalHeight(me) - 1);
+ me.setSelected(me, me.nItems - 1);
+ }
+ else
+ return 0;
+ return 1;
+}
+float ListBox_mouseDrag(entity me, vector pos)
+{
+ float hit;
+ float i;
+ me.updateControlTopBottom(me);
+ me.dragScrollPos = pos;
+ if(me.pressed == 1)
+ {
+ hit = 1;
+ if(pos.x < 1 - me.controlWidth - me.tolerance.y * me.controlWidth) hit = 0;
+ if(pos.y < 0 - me.tolerance.x) hit = 0;
+ if(pos.x >= 1 + me.tolerance.y * me.controlWidth) hit = 0;
+ if(pos.y >= 1 + me.tolerance.x) hit = 0;
+ if(hit)
+ {
+ // calculate new pos to v
+ float d;
+ d = (pos.y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
+ me.scrollPos = me.previousValue + d;
+ }
+ else
+ me.scrollPos = me.previousValue;
+ me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
+ me.scrollPos = max(me.scrollPos, 0);
+ i = min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos));
+ i = max(i, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos));
+ me.setSelected(me, i);
+ }
+ else if(me.pressed == 2)
+ {
+ me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
+ }
+ return 1;
+}
+float ListBox_mousePress(entity me, vector pos)
+{
+ if(pos.x < 0) return 0;
+ if(pos.y < 0) return 0;
+ if(pos.x >= 1) return 0;
+ if(pos.y >= 1) return 0;
+ me.dragScrollPos = pos;
+ me.updateControlTopBottom(me);
+ me.dragScrollTimer = time;
+ if(pos.x >= 1 - me.controlWidth)
+ {
+ // if hit, set me.pressed, otherwise scroll by one page
+ if(pos.y < me.controlTop)
+ {
+ // page up
+ me.scrollPos = max(me.scrollPos - 1, 0);
+ me.setSelected(me, min(me.selectedItem, ListBox_getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
+ }
+ else if(pos.y > me.controlBottom)
+ {
+ // page down
+ me.scrollPos = min(me.scrollPos + 1, me.getTotalHeight(me) - 1);
+ me.setSelected(me, max(me.selectedItem, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
+ }
+ else
+ {
+ me.pressed = 1;
+ me.pressOffset = pos.y;
+ me.previousValue = me.scrollPos;
+ }
+ }
+ else
+ {
+ // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
+ me.pressed = 2;
+ // an item has been clicked. Select it, ...
+ me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
+ }
+ return 1;
+}
+float ListBox_mouseRelease(entity me, vector pos)
+{
+ if(me.pressed == 1)
+ {
+ // slider dragging mode
+ // in that case, nothing happens on releasing
+ }
+ else if(me.pressed == 2)
+ {
+ me.pressed = 3; // do that here, so setSelected can know the mouse has been released
+ // item dragging mode
+ // select current one one last time...
+ me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
+ // and give it a nice click event
+ if(me.nItems > 0)
+ {
+ vector where = globalToBox(pos, eY * (me.getItemStart(me, me.selectedItem) - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, me.selectedItem));
+
+ if((me.selectedItem == me.lastClickedItem) && (time < me.lastClickedTime + 0.3))
+ me.doubleClickListBoxItem(me, me.selectedItem, where);
+ else
+ me.clickListBoxItem(me, me.selectedItem, where);
+
+ me.lastClickedItem = me.selectedItem;
+ me.lastClickedTime = time;
+ }
+ }
+ me.pressed = 0;
+ return 1;
+}
+void ListBox_focusLeave(entity me)
+{
+ // Reset the var pressed in case listbox loses focus
+ // by a mouse click on an item of the list
+ // for example showing a dialog on right click
+ me.pressed = 0;
+}
+void ListBox_updateControlTopBottom(entity me)
+{
+ float f;
+ // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
+ if(me.getTotalHeight(me) <= 1)
+ {
+ // we don't need no stinkin' scrollbar, we don't need no view control...
+ me.controlTop = 0;
+ me.controlBottom = 1;
+ me.scrollPos = 0;
+ }
+ else
+ {
+ if(frametime) // only do this in draw frames
+ {
+ if(me.dragScrollTimer < time)
+ {
+ float save;
+ save = me.scrollPos;
+ // if selected item is below listbox, increase scrollpos so it is in
+ me.scrollPos = max(me.scrollPos, me.getItemStart(me, me.selectedItem) + me.getItemHeight(me, me.selectedItem) - 1);
+ // if selected item is above listbox, decrease scrollpos so it is in
+ me.scrollPos = min(me.scrollPos, me.getItemStart(me, me.selectedItem));
+ if(me.scrollPos != save)
+ me.dragScrollTimer = time + 0.2;
+ }
+ }
+ // if scroll pos is below end of list, fix it
+ me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
+ // if scroll pos is above beginning of list, fix it
+ me.scrollPos = max(me.scrollPos, 0);
+ // now that we know where the list is scrolled to, find out where to draw the control
+ me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
+ me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
+
+ float minfactor;
+ minfactor = 2 * me.controlWidth / me.size.y * me.size.x;
+ f = me.controlBottom - me.controlTop;
+ if(f < minfactor) // FIXME good default?
+ {
+ // f * X + 1 * (1-X) = minfactor
+ // (f - 1) * X + 1 = minfactor
+ // (f - 1) * X = minfactor - 1
+ // X = (minfactor - 1) / (f - 1)
+ f = (minfactor - 1) / (f - 1);
+ me.controlTop = me.controlTop * f + 0 * (1 - f);
+ me.controlBottom = me.controlBottom * f + 1 * (1 - f);
+ }
+ }
+}
+void ListBox_draw(entity me)
+{
+ float i;
+ vector absSize, fillSize = '0 0 0';
+ vector oldshift, oldscale;
+ if(me.pressed == 2)
+ me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
+ me.updateControlTopBottom(me);
+ fillSize.x = (1 - me.controlWidth);
+ if(me.alphaBG)
+ draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
+ if(me.controlWidth)
+ {
+ draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
+ if(me.getTotalHeight(me) > 1)
+ {
+ vector o, s;
+ o = eX * (1 - me.controlWidth) + eY * me.controlTop;
+ s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
+ if(me.pressed == 1)
+ draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
+ else if(me.focused)
+ draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
+ else
+ draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
+ }
+ }
+ draw_SetClip();
+ oldshift = draw_shift;
+ oldscale = draw_scale;
+ float y;
+ i = me.getItemAtPos(me, me.scrollPos);
+ y = me.getItemStart(me, i) - me.scrollPos;
+ for (; i < me.nItems && y < 1; ++i)
+ {
+ draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
+ vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
+ absSize = boxToGlobalSize(relSize, me.size);
+ draw_scale = boxToGlobalSize(relSize, oldscale);
+ me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
+ y += relSize.y;
+ }
+ draw_ClearClip();
+
+ draw_shift = oldshift;
+ draw_scale = oldscale;
+ SUPER(ListBox).draw(me);
+}
+
+void ListBox_clickListBoxItem(entity me, float i, vector where)
+{
+ // template method
+}
+
+void ListBox_doubleClickListBoxItem(entity me, float i, vector where)
+{
+ // template method
+}
+
+void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
+{
+ draw_Text('0 0 0', sprintf(_("Item %d"), i), eX * (8 / absSize.x) + eY * (8 / absSize.y), (selected ? '0 1 0' : '1 1 1'), 1, 0);
+}
+#endif
--- /dev/null
- f = (e.ModalController_factor = min(1, e.ModalController_factor + df));
- if(e.ModalController_state)
- if(f < 1)
- animating = 1;
-
- if(f < 1)
- {
- prevFactor = (1 - f) / (1 - f + df);
- targetFactor = df / (1 - f + df);
- }
- else
- {
- prevFactor = 0;
- targetFactor = 1;
- }
-
+#ifdef INTERFACE
+CLASS(ModalController) EXTENDS(Container)
+ METHOD(ModalController, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(ModalController, draw, void(entity))
+ METHOD(ModalController, showChild, void(entity, entity, vector, vector, float))
+ METHOD(ModalController, hideChild, void(entity, entity, float))
+ METHOD(ModalController, hideAll, void(entity, float))
+ METHOD(ModalController, addItem, void(entity, entity, vector, vector, float))
+ METHOD(ModalController, addTab, void(entity, entity, entity))
+
+ METHOD(ModalController, initializeDialog, void(entity, entity))
+
+ METHOD(ModalController, switchState, void(entity, entity, float, float))
+ ATTRIB(ModalController, origin, vector, '0 0 0')
+ ATTRIB(ModalController, size, vector, '0 0 0')
+ ATTRIB(ModalController, previousButton, entity, NULL)
+ ATTRIB(ModalController, fadedAlpha, float, 0.3)
+ENDCLASS(ModalController)
+
+.entity tabSelectingButton;
+.vector origin;
+.vector size;
+void TabButton_Click(entity button, entity tab); // assumes a button has set the above fields to its own absolute origin, its size, and the tab to activate
+void DialogOpenButton_Click(entity button, entity tab); // assumes a button has set the above fields to its own absolute origin, its size, and the tab to activate
+void DialogOpenButton_Click_withCoords(entity button, entity tab, vector theOrigin, vector theSize);
+void DialogCloseButton_Click(entity button, entity tab); // assumes a button has set the above fields to the tab to close
+#endif
+
+#ifdef IMPLEMENTATION
+
+// modal dialog controller
+// handles a stack of dialog elements
+// each element can have one of the following states:
+// 0: hidden (fading out)
+// 1: visible (zooming in)
+// 2: greyed out (inactive)
+// While an animation is running, no item has focus. When an animation is done,
+// the topmost item gets focus.
+// The items are assumed to be added in overlapping order, that is, the lowest
+// window must get added first.
+//
+// Possible uses:
+// - to control a modal dialog:
+// - show modal dialog: me.showChild(me, childItem, buttonAbsOrigin, buttonAbsSize, 0) // childItem also gets focus
+// - dismiss modal dialog: me.hideChild(me, childItem, 0) // childItem fades out and relinquishes focus
+// - show first screen in m_show: me.hideAll(me, 1); me.showChild(me, me.firstChild, '0 0 0', '0 0 0', 1);
+// - to show a temporary dialog instead of the menu (teamselect): me.hideAll(me, 1); me.showChild(me, teamSelectDialog, '0 0 0', '0 0 0', 1);
+// - as a tabbed dialog control:
+// - to initialize: me.hideAll(me, 1); me.showChild(me, me.firstChild, '0 0 0', '0 0 0', 1);
+// - to show a tab: me.hideChild(me, currentTab, 0); me.showChild(me, newTab, buttonAbsOrigin, buttonAbsSize, 0);
+
+.vector ModalController_initialSize;
+.vector ModalController_initialOrigin;
+.vector ModalController_initialFontScale;
+.float ModalController_initialAlpha;
+.vector ModalController_buttonSize;
+.vector ModalController_buttonOrigin;
+.float ModalController_state;
+.float ModalController_factor;
+.entity ModalController_controllingButton;
+
+void ModalController_initializeDialog(entity me, entity root)
+{
+ me.hideAll(me, 1);
+ me.showChild(me, root, '0 0 0', '0 0 0', 1); // someone else animates for us
+}
+
+void TabButton_Click(entity button, entity tab)
+{
+ if(tab.ModalController_state == 1)
+ return;
+ tab.parent.hideAll(tab.parent, 0);
+ button.forcePressed = 1;
+ tab.ModalController_controllingButton = button;
+ tab.parent.showChild(tab.parent, tab, button.origin, button.size, 0);
+}
+
+void DialogOpenButton_Click(entity button, entity tab)
+{
+ DialogOpenButton_Click_withCoords(button, tab, button.origin, button.size);
+}
+
+void DialogOpenButton_Click_withCoords(entity button, entity tab, vector theOrigin, vector theSize)
+{
+ if(tab.ModalController_state)
+ return;
+ if(button)
+ button.forcePressed = 1;
+ if(tab.parent.focusedChild)
+ tab.parent.focusedChild.saveFocus(tab.parent.focusedChild);
+ tab.ModalController_controllingButton = button;
+ tab.parent.showChild(tab.parent, tab, theOrigin, theSize, 0);
+}
+
+void DialogCloseButton_Click(entity button, entity tab)
+{
+ tab.parent.hideChild(tab.parent, tab, 0);
+}
+
+void ModalController_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ me.resizeNotifyLie(me, relOrigin, relSize, absOrigin, absSize, ModalController_initialOrigin, ModalController_initialSize, ModalController_initialFontScale);
+}
+
+void ModalController_switchState(entity me, entity other, float state, float skipAnimation)
+{
+ float previousState;
+ previousState = other.ModalController_state;
+ if(state == previousState && !skipAnimation)
+ return;
+ other.ModalController_state = state;
+ switch(state)
+ {
+ case 0:
+ other.ModalController_factor = 1 - other.Container_alpha / other.ModalController_initialAlpha;
+ // fading out
+ break;
+ case 1:
+ other.ModalController_factor = other.Container_alpha / other.ModalController_initialAlpha;
+ if(previousState == 0 && !skipAnimation)
+ {
+ other.Container_origin = other.ModalController_buttonOrigin;
+ other.Container_size = other.ModalController_buttonSize;
+ }
+ // zooming in
+ break;
+ case 2:
+ other.ModalController_factor = bound(0, (1 - other.Container_alpha / other.ModalController_initialAlpha) / me.fadedAlpha, 1);
+ // fading out halfway
+ break;
+ }
+ if(skipAnimation)
+ other.ModalController_factor = 1;
+}
+
+void ModalController_draw(entity me)
+{
+ entity e;
+ entity front;
+ float animating;
+ float f; // animation factor
+ float df; // animation step size
+ float prevFactor, targetFactor;
+ vector targetOrigin, targetSize; float targetAlpha;
+ vector fs;
+ animating = 0;
+
+ front = world;
+ for(e = me.firstChild; e; e = e.nextSibling)
+ if(e.ModalController_state)
+ {
+ if(front)
++ {
+ me.switchState(me, front, 2, 0);
++ if(front.ModalController_factor < 1)
++ animating = 1;
++ }
+ front = e;
+ }
+ if(front)
++ {
+ me.switchState(me, front, 1, 0);
++ if(front.ModalController_factor < 1)
++ animating = 1;
++ }
++
++ if(front && front.Container_alpha == front.ModalController_initialAlpha)
++ goto update_done; // update isn't needed, everything stay as is
+
+ df = frametime * 3; // animation speed
+
+ for(e = me.firstChild; e; e = e.nextSibling)
+ {
- if(f < 1)
- animating = 1;
+ if(e.ModalController_state == 2)
+ {
+ // fading out partially
+ targetOrigin = e.Container_origin; // stay as is
+ targetSize = e.Container_size; // stay as is
+ targetAlpha = me.fadedAlpha * e.ModalController_initialAlpha;
+ }
+ else if(e.ModalController_state == 1)
+ {
+ // zooming in
+ targetOrigin = e.ModalController_initialOrigin;
+ targetSize = e.ModalController_initialSize;
+ targetAlpha = e.ModalController_initialAlpha;
+ }
+ else
+ {
+ // fading out
- e.Container_origin = e.Container_origin * prevFactor + targetOrigin * targetFactor;
- e.Container_size = e.Container_size * prevFactor + targetSize * targetFactor;
- me.setAlphaOf(me, e, e.Container_alpha * prevFactor + targetAlpha * targetFactor);
+ targetOrigin = e.Container_origin; // stay as is
+ targetSize = e.Container_size; // stay as is
+ targetAlpha = 0;
+ }
+
++ f = (e.ModalController_factor = min(1, e.ModalController_factor + df));
+ if(f == 1)
+ {
++ prevFactor = 0;
++ targetFactor = 1;
+ e.Container_origin = targetOrigin;
+ e.Container_size = targetSize;
+ me.setAlphaOf(me, e, targetAlpha);
+ }
+ else
+ {
- fs = globalToBoxSize(e.Container_size, e.ModalController_initialSize);
- e.Container_fontscale_x = fs.x * e.ModalController_initialFontScale.x;
- e.Container_fontscale_y = fs.y * e.ModalController_initialFontScale.y;
++ prevFactor = (1 - f) / (1 - f + df);
++ if(!e.ModalController_state) // optimize code and avoid precision errors
++ me.setAlphaOf(me, e, e.Container_alpha * prevFactor);
++ else
++ {
++ targetFactor = df / (1 - f + df);
++
++ if(e.ModalController_state == 1)
++ {
++ e.Container_origin = e.Container_origin * prevFactor + targetOrigin * targetFactor;
++ e.Container_size = e.Container_size * prevFactor + targetSize * targetFactor;
++ }
++ me.setAlphaOf(me, e, e.Container_alpha * prevFactor + targetAlpha * targetFactor);
++ }
+ }
+ // assume: o == to * f_prev + X * (1 - f_prev)
+ // make: o' = to * f + X * (1 - f)
+ // -->
+ // X == (o - to * f_prev) / (1 - f_prev)
+ // o' = to * f + (o - to * f_prev) / (1 - f_prev) * (1 - f)
+ // --> (maxima)
+ // o' = (to * (f - f_prev) + o * (1 - f)) / (1 - f_prev)
+
++ if(e.ModalController_state == 1)
++ {
++ fs = globalToBoxSize(e.Container_size, e.ModalController_initialSize);
++ e.Container_fontscale_x = fs.x * e.ModalController_initialFontScale.x;
++ e.Container_fontscale_y = fs.y * e.ModalController_initialFontScale.y;
++ }
+ }
++ :update_done
++
+ if(animating || !me.focused)
+ me.setFocus(me, NULL);
+ else
+ me.setFocus(me, front);
+ SUPER(ModalController).draw(me);
+}
+
+void ModalController_addTab(entity me, entity other, entity tabButton)
+{
+ me.addItem(me, other, '0 0 0', '1 1 1', 1);
+ tabButton.onClick = TabButton_Click;
+ tabButton.onClickEntity = other;
+ other.tabSelectingButton = tabButton;
+ if(other == me.firstChild)
+ {
+ tabButton.forcePressed = 1;
+ other.ModalController_controllingButton = tabButton;
+ me.showChild(me, other, '0 0 0', '0 0 0', 1);
+ }
+}
+
+void ModalController_addItem(entity me, entity other, vector theOrigin, vector theSize, float theAlpha)
+{
+ SUPER(ModalController).addItem(me, other, theOrigin, theSize, (other == me.firstChild) ? theAlpha : 0);
+ other.ModalController_initialFontScale = other.Container_fontscale;
+ other.ModalController_initialSize = other.Container_size;
+ other.ModalController_initialOrigin = other.Container_origin;
+ other.ModalController_initialAlpha = theAlpha; // hope Container never modifies this
+ if(other.ModalController_initialFontScale == '0 0 0')
+ other.ModalController_initialFontScale = '1 1 0';
+}
+
+void ModalController_showChild(entity me, entity other, vector theOrigin, vector theSize, float skipAnimation)
+{
+ if(other.ModalController_state == 0 || skipAnimation)
+ {
+ me.setFocus(me, NULL);
+ if(!skipAnimation)
+ {
+ other.ModalController_buttonOrigin = globalToBox(theOrigin, me.origin, me.size);
+ other.ModalController_buttonSize = globalToBoxSize(theSize, me.size);
+ }
+ me.switchState(me, other, 1, skipAnimation);
+ } // zoom in from button (factor increases)
+}
+
+void ModalController_hideAll(entity me, float skipAnimation)
+{
+ entity e;
+ for(e = me.firstChild; e; e = e.nextSibling)
+ me.hideChild(me, e, skipAnimation);
+}
+
+void ModalController_hideChild(entity me, entity other, float skipAnimation)
+{
+ if(other.ModalController_state || skipAnimation)
+ {
+ me.setFocus(me, NULL);
+ me.switchState(me, other, 0, skipAnimation);
+ if(other.ModalController_controllingButton)
+ {
+ other.ModalController_controllingButton.forcePressed = 0;
+ other.ModalController_controllingButton = NULL;
+ }
+ } // just alpha fade out (factor increases and decreases alpha)
+}
+#endif
--- /dev/null
+#ifdef INTERFACE
+CLASS(Nexposee) EXTENDS(Container)
+ METHOD(Nexposee, draw, void(entity))
+ METHOD(Nexposee, keyDown, float(entity, float, float, float))
+ METHOD(Nexposee, keyUp, float(entity, float, float, float))
+ METHOD(Nexposee, mousePress, float(entity, vector))
+ METHOD(Nexposee, mouseMove, float(entity, vector))
+ METHOD(Nexposee, mouseRelease, float(entity, vector))
+ METHOD(Nexposee, mouseDrag, float(entity, vector))
+ METHOD(Nexposee, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(Nexposee, focusEnter, void(entity))
+ METHOD(Nexposee, close, void(entity))
+
+ ATTRIB(Nexposee, animationState, float, -1)
+ ATTRIB(Nexposee, animationFactor, float, 0)
+ ATTRIB(Nexposee, selectedChild, entity, NULL)
+ ATTRIB(Nexposee, mouseFocusedChild, entity, NULL)
+ METHOD(Nexposee, addItem, void(entity, entity, vector, vector, float))
+ METHOD(Nexposee, calc, void(entity))
+ METHOD(Nexposee, setNexposee, void(entity, entity, vector, float, float))
+ ATTRIB(Nexposee, mousePosition, vector, '0 0 0')
+ METHOD(Nexposee, pullNexposee, void(entity, entity, vector))
+ENDCLASS(Nexposee)
+
+void ExposeeCloseButton_Click(entity button, entity other); // un-exposees the current state
+#endif
+
+// animation states:
+// 0 = thumbnails seen
+// 1 = zooming in
+// 2 = zoomed in
+// 3 = zooming out
+// animation factor: 0 = minimum theSize, 1 = maximum theSize
+
+#ifdef IMPLEMENTATION
+
+.vector Nexposee_initialSize;
+.vector Nexposee_initialFontScale;
+.vector Nexposee_initialOrigin;
+.float Nexposee_initialAlpha;
+
+.vector Nexposee_smallSize;
+.vector Nexposee_smallOrigin;
+.float Nexposee_smallAlpha;
+.float Nexposee_mediumAlpha;
+.vector Nexposee_scaleCenter;
+.vector Nexposee_align;
+.float Nexposee_animationFactor;
+
+void Nexposee_close(entity me)
+{
+ // user must override this
+}
+
+void ExposeeCloseButton_Click(entity button, entity other)
+{
+ other.selectedChild = other.focusedChild;
+ other.setFocus(other, NULL);
+ other.animationState = 3;
+}
+
+void Nexposee_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ me.calc(me);
+ me.resizeNotifyLie(me, relOrigin, relSize, absOrigin, absSize, Nexposee_initialOrigin, Nexposee_initialSize, Nexposee_initialFontScale);
+}
+
+void Nexposee_Calc_Scale(entity me, float scale)
+{
+ entity e;
+ for(e = me.firstChild; e; e = e.nextSibling)
+ {
+ e.Nexposee_smallOrigin = (e.Nexposee_initialOrigin - e.Nexposee_scaleCenter) * scale + e.Nexposee_scaleCenter;
+ e.Nexposee_smallSize = e.Nexposee_initialSize * scale;
+ if(e.Nexposee_align.x > 0)
+ e.Nexposee_smallOrigin_x = 1 - e.Nexposee_align.x * scale;
+ if(e.Nexposee_align.x < 0)
+ e.Nexposee_smallOrigin_x = -e.Nexposee_smallSize.x + e.Nexposee_align.x * scale;
+ if(e.Nexposee_align.y > 0)
+ e.Nexposee_smallOrigin_y = 1 - e.Nexposee_align.y * scale;
+ if(e.Nexposee_align.y < 0)
+ e.Nexposee_smallOrigin_y = -e.Nexposee_smallSize.y + e.Nexposee_align.y * scale;
+ }
+}
+
+void Nexposee_calc(entity me)
+{
+ /*
+ * patented by Apple
+ * can't put that here ;)
+ */
+ float scale;
+ entity e, e2;
+ vector emins, emaxs, e2mins, e2maxs;
+
+ for(scale = 0.7;; scale *= 0.99)
+ {
+ Nexposee_Calc_Scale(me, scale);
+
+ for(e = me.firstChild; e; e = e.nextSibling)
+ {
+ emins = e.Nexposee_smallOrigin;
+ emaxs = emins + e.Nexposee_smallSize;
+ for(e2 = e.nextSibling; e2; e2 = e2.nextSibling)
+ {
+ e2mins = e2.Nexposee_smallOrigin;
+ e2maxs = e2mins + e2.Nexposee_smallSize;
+
+ // two intervals [amins, amaxs] and [bmins, bmaxs] overlap if:
+ // amins < bmins < amaxs < bmaxs
+ // for which suffices
+ // bmins < amaxs
+ // amins < bmaxs
+ if((e2mins.x - emaxs.x) * (emins.x - e2maxs.x) > 0) // x overlap
+ if((e2mins.y - emaxs.y) * (emins.y - e2maxs.y) > 0) // y overlap
+ {
+ goto have_overlap;
+ }
+ }
+ }
+
+ break;
+:have_overlap
+ }
+
+ scale *= 0.95;
+
+ Nexposee_Calc_Scale(me, scale);
+}
+
+void Nexposee_setNexposee(entity me, entity other, vector scalecenter, float a0, float a1)
+{
+ other.Nexposee_scaleCenter = scalecenter;
+ other.Nexposee_smallAlpha = a0;
+ me.setAlphaOf(me, other, a0);
+ other.Nexposee_mediumAlpha = a1;
+}
+
+void Nexposee_draw(entity me)
+{
+ float a;
+ float a0;
+ entity e;
+ float f;
+ vector fs;
+
+ if(me.animationState == -1)
+ {
+ me.animationState = 0;
+ }
+
+ f = min(1, frametime * 5);
+ switch(me.animationState)
+ {
+ case 0:
+ me.animationFactor = 0;
+ break;
+ case 1:
+ me.animationFactor += f;
+ if(me.animationFactor >= 1)
+ {
+ me.animationFactor = 1;
+ me.animationState = 2;
+ SUPER(Nexposee).setFocus(me, me.selectedChild);
+ }
+ break;
+ case 2:
+ me.animationFactor = 1;
+ break;
+ case 3:
+ me.animationFactor -= f;
+ me.mouseFocusedChild = me.itemFromPoint(me, me.mousePosition);
+ if(me.animationFactor <= 0)
+ {
+ me.animationFactor = 0;
+ me.animationState = 0;
+ me.selectedChild = me.mouseFocusedChild;
+ }
+ break;
+ }
+
+ f = min(1, frametime * 10);
+ for(e = me.firstChild; e; e = e.nextSibling)
+ {
+ if(e == me.selectedChild)
+ {
+ e.Container_origin = e.Nexposee_smallOrigin * (1 - me.animationFactor) + e.Nexposee_initialOrigin * me.animationFactor;
+ e.Container_size = e.Nexposee_smallSize * (1 - me.animationFactor) + e.Nexposee_initialSize * me.animationFactor;
+ e.Nexposee_animationFactor = me.animationFactor;
+ a0 = e.Nexposee_mediumAlpha;
+ if(me.animationState == 3)
+ if(e != me.mouseFocusedChild)
+ a0 = e.Nexposee_smallAlpha;
+ a = a0 * (1 - me.animationFactor) + me.animationFactor;
+ }
+ else
+ {
+ // minimum theSize counts
+ e.Container_origin = e.Nexposee_smallOrigin;
+ e.Container_size = e.Nexposee_smallSize;
+ e.Nexposee_animationFactor = 0;
+ a = e.Nexposee_smallAlpha * (1 - me.animationFactor);
+ }
+ me.setAlphaOf(me, e, e.Container_alpha * (1 - f) + a * f);
+
+ fs = globalToBoxSize(e.Container_size, e.Nexposee_initialSize);
+ e.Container_fontscale_x = fs.x * e.Nexposee_initialFontScale.x;
+ e.Container_fontscale_y = fs.y * e.Nexposee_initialFontScale.y;
+ }
+
+ SUPER(Nexposee).draw(me);
+}
+
+float Nexposee_mousePress(entity me, vector pos)
+{
+ if(me.animationState == 0)
+ {
+ me.mouseFocusedChild = NULL;
+ Nexposee_mouseMove(me, pos);
+ if(me.mouseFocusedChild)
+ {
++ m_play_click_sound(MENU_SOUND_OPEN);
+ me.animationState = 1;
+ SUPER(Nexposee).setFocus(me, NULL);
+ }
+ else
+ me.close(me);
+ return 1;
+ }
+ else if(me.animationState == 2)
+ {
+ if (!(SUPER(Nexposee).mousePress(me, pos)))
+ {
++ m_play_click_sound(MENU_SOUND_CLOSE);
+ me.animationState = 3;
+ SUPER(Nexposee).setFocus(me, NULL);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+float Nexposee_mouseRelease(entity me, vector pos)
+{
+ if(me.animationState == 2)
+ return SUPER(Nexposee).mouseRelease(me, pos);
+ return 0;
+}
+
+float Nexposee_mouseDrag(entity me, vector pos)
+{
+ if(me.animationState == 2)
+ return SUPER(Nexposee).mouseDrag(me, pos);
+ return 0;
+}
+
+float Nexposee_mouseMove(entity me, vector pos)
+{
+ entity e;
+ me.mousePosition = pos;
+ e = me.mouseFocusedChild;
+ me.mouseFocusedChild = me.itemFromPoint(me, pos);
+ if(me.animationState == 2)
+ return SUPER(Nexposee).mouseMove(me, pos);
+ if(me.animationState == 0)
+ {
+ if(me.mouseFocusedChild)
+ if(me.mouseFocusedChild != e || me.mouseFocusedChild != me.selectedChild)
+ me.selectedChild = me.mouseFocusedChild;
+ return 1;
+ }
+ return 0;
+}
+
+float Nexposee_keyUp(entity me, float scan, float ascii, float shift)
+{
+ if(me.animationState == 2)
+ return SUPER(Nexposee).keyUp(me, scan, ascii, shift);
+ return 0;
+}
+
+float Nexposee_keyDown(entity me, float scan, float ascii, float shift)
+{
+ float nexposeeKey = 0;
+ if(me.animationState == 2)
+ if(SUPER(Nexposee).keyDown(me, scan, ascii, shift))
+ return 1;
+ if(scan == K_TAB)
+ {
+ if(me.animationState == 0)
+ {
+ if(shift & S_SHIFT)
+ {
+ if(me.selectedChild)
+ me.selectedChild = me.selectedChild.prevSibling;
+ if (!me.selectedChild)
+ me.selectedChild = me.lastChild;
+ }
+ else
+ {
+ if(me.selectedChild)
+ me.selectedChild = me.selectedChild.nextSibling;
+ if (!me.selectedChild)
+ me.selectedChild = me.firstChild;
+ }
+ }
+ }
+ switch(me.animationState)
+ {
+ default:
+ case 0:
+ case 3:
+ nexposeeKey = ((scan == K_SPACE) || (scan == K_ENTER) || (scan == K_KP_ENTER));
+ break;
+ case 1:
+ case 2:
+ nexposeeKey = (scan == K_ESCAPE);
+ break;
+ }
+ if(nexposeeKey)
+ {
+ switch(me.animationState)
+ {
+ default:
+ case 0:
+ case 3:
++ m_play_click_sound(MENU_SOUND_OPEN);
+ me.animationState = 1;
+ break;
+ case 1:
+ case 2:
++ m_play_click_sound(MENU_SOUND_CLOSE);
+ me.animationState = 3;
+ break;
+ }
+ if(me.focusedChild)
+ me.selectedChild = me.focusedChild;
+ if (!me.selectedChild)
+ me.animationState = 0;
+ SUPER(Nexposee).setFocus(me, NULL);
+ return 1;
+ }
+ return 0;
+}
+
+void Nexposee_addItem(entity me, entity other, vector theOrigin, vector theSize, float theAlpha)
+{
+ SUPER(Nexposee).addItem(me, other, theOrigin, theSize, theAlpha);
+ other.Nexposee_initialFontScale = other.Container_fontscale;
+ other.Nexposee_initialSize = other.Container_size;
+ other.Nexposee_initialOrigin = other.Container_origin;
+ other.Nexposee_initialAlpha = other.Container_alpha;
+ if(other.Nexposee_initialFontScale == '0 0 0')
+ other.Nexposee_initialFontScale = '1 1 0';
+}
+
+void Nexposee_focusEnter(entity me)
+{
+ if(me.animationState == 2)
+ SUPER(Nexposee).setFocus(me, me.selectedChild);
+}
+
+void Nexposee_pullNexposee(entity me, entity other, vector theAlign)
+{
+ other.Nexposee_align = theAlign;
+}
+#endif
--- /dev/null
- METHOD(Slider, focusEnter, void(entity))
+// Note:
+// to use this, you FIRST call configureSliderVisuals, then configureSliderValues
+#ifdef INTERFACE
+CLASS(Slider) EXTENDS(Label)
+ METHOD(Slider, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(Slider, configureSliderVisuals, void(entity, float, float, float, string))
+ METHOD(Slider, configureSliderValues, void(entity, float, float, float, float, float, float))
+ METHOD(Slider, draw, void(entity))
+ METHOD(Slider, keyDown, float(entity, float, float, float))
++ METHOD(Slider, keyUp, float(entity, float, float, float))
+ METHOD(Slider, mousePress, float(entity, vector))
+ METHOD(Slider, mouseDrag, float(entity, vector))
+ METHOD(Slider, mouseRelease, float(entity, vector))
- // TODO more keys
+ METHOD(Slider, valueToText, string(entity, float))
+ METHOD(Slider, toString, string(entity))
+ METHOD(Slider, setValue, void(entity, float))
+ METHOD(Slider, setSliderValue, void(entity, float))
+ METHOD(Slider, showNotify, void(entity))
+ ATTRIB(Slider, src, string, string_null)
+ ATTRIB(Slider, focusable, float, 1)
++ ATTRIB(Slider, allowFocusSound, float, 1)
+ ATTRIB(Slider, value, float, 0)
+ ATTRIB(Slider, animated, float, 1)
+ ATTRIB(Slider, sliderValue, float, 0)
+ ATTRIB(Slider, valueMin, float, 0)
+ ATTRIB(Slider, valueMax, float, 0)
+ ATTRIB(Slider, valueStep, float, 0)
+ ATTRIB(Slider, valueDigits, float, 0)
+ ATTRIB(Slider, valueKeyStep, float, 0)
+ ATTRIB(Slider, valuePageStep, float, 0)
+ ATTRIB(Slider, valueDisplayMultiplier, float, 1.0)
+ ATTRIB(Slider, textSpace, float, 0)
+ ATTRIB(Slider, controlWidth, float, 0)
+ ATTRIB(Slider, pressed, float, 0)
+ ATTRIB(Slider, pressOffset, float, 0)
+ ATTRIB(Slider, previousValue, float, 0)
+ ATTRIB(Slider, tolerance, vector, '0 0 0')
+ ATTRIB(Slider, disabled, float, 0)
+ ATTRIB(Slider, color, vector, '1 1 1')
+ ATTRIB(Slider, color2, vector, '1 1 1')
+ ATTRIB(Slider, colorD, vector, '1 1 1')
+ ATTRIB(Slider, colorC, vector, '1 1 1')
+ ATTRIB(Slider, colorF, vector, '1 1 1')
+ ATTRIB(Slider, disabledAlpha, float, 0.3)
+ENDCLASS(Slider)
+#endif
+
+#ifdef IMPLEMENTATION
+void Slider_setValue(entity me, float val)
+{
+ if (me.animated) {
+ anim.removeObjAnim(anim, me);
+ makeHostedEasing(me, Slider_setSliderValue, easingQuadInOut, 1, me.sliderValue, val);
+ } else {
+ me.setSliderValue(me, val);
+ }
+ me.value = val;
+}
+void Slider_setSliderValue(entity me, float val)
+{
+ me.sliderValue = val;
+}
+string Slider_toString(entity me)
+{
+ return sprintf("%d (%s)", me.value, me.valueToText(me, me.value));
+}
+void Slider_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ SUPER(Slider).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+ me.controlWidth = absSize.y / absSize.x;
+}
+string Slider_valueToText(entity me, float val)
+{
+ if(almost_in_bounds(me.valueMin, val, me.valueMax))
+ return ftos_decimals(val * me.valueDisplayMultiplier, me.valueDigits);
+ return "";
+}
+void Slider_configureSliderVisuals(entity me, float sz, float theAlign, float theTextSpace, string gfx)
+{
+ SUPER(Slider).configureLabel(me, string_null, sz, theAlign);
+ me.textSpace = theTextSpace;
+ me.keepspaceLeft = (theTextSpace == 0) ? 0 : (1 - theTextSpace);
+ me.src = gfx;
+}
+void Slider_configureSliderValues(entity me, float theValueMin, float theValue, float theValueMax, float theValueStep, float theValueKeyStep, float theValuePageStep)
+{
+ me.value = theValue;
+ me.sliderValue = theValue;
+ me.valueStep = theValueStep;
+ me.valueMin = theValueMin;
+ me.valueMax = theValueMax;
+ me.valueKeyStep = theValueKeyStep;
+ me.valuePageStep = theValuePageStep;
+ me.valueDigits = 3;
+ if(fabs(floor(me.valueStep * 100 + 0.5) - (me.valueStep * 100)) < 0.01) // about a whole number of 100ths
+ me.valueDigits = 2;
+ if(fabs(floor(me.valueStep * 10 + 0.5) - (me.valueStep * 10)) < 0.01) // about a whole number of 10ths
+ me.valueDigits = 1;
+ if(fabs(floor(me.valueStep * 1 + 0.5) - (me.valueStep * 1)) < 0.01) // about a whole number
+ me.valueDigits = 0;
+}
+float Slider_keyDown(entity me, float key, float ascii, float shift)
+{
+ float inRange;
+ if(me.disabled)
+ return 0;
+ inRange = (almost_in_bounds(me.valueMin, me.value, me.valueMax));
+ if(key == K_LEFTARROW || key == K_KP_LEFTARROW || key == K_MWHEELDOWN)
+ {
+ if(inRange)
+ me.setValue(me, median(me.valueMin, me.value - me.valueKeyStep, me.valueMax));
+ else
+ me.setValue(me, me.valueMax);
+ return 1;
+ }
+ if(key == K_RIGHTARROW || key == K_KP_RIGHTARROW || key == K_MWHEELUP)
+ {
+ if(inRange)
+ me.setValue(me, median(me.valueMin, me.value + me.valueKeyStep, me.valueMax));
+ else
+ me.setValue(me, me.valueMin);
+ return 1;
+ }
+ if(key == K_PGDN || key == K_KP_PGDN)
+ {
+ if(inRange)
+ me.setValue(me, median(me.valueMin, me.value - me.valuePageStep, me.valueMax));
+ else
+ me.setValue(me, me.valueMax);
+ return 1;
+ }
+ if(key == K_PGUP || key == K_KP_PGUP)
+ {
+ if(inRange)
+ me.setValue(me, median(me.valueMin, me.value + me.valuePageStep, me.valueMax));
+ else
+ me.setValue(me, me.valueMin);
+ return 1;
+ }
+ if(key == K_HOME || key == K_KP_HOME)
+ {
+ me.setValue(me, me.valueMin);
+ return 1;
+ }
+ if(key == K_END || key == K_KP_END)
+ {
+ me.setValue(me, me.valueMax);
+ return 1;
+ }
- if(cvar("menu_sounds"))
- localsound("sound/misc/menu2.wav");
++ // TODO more keys (NOTE also add them to Slider_keyUp)
++ return 0;
++}
++float Slider_keyUp(entity me, float key, float ascii, float shift)
++{
++ if(me.disabled)
++ return 0;
++ switch(key)
++ {
++ case K_LEFTARROW:
++ case K_KP_LEFTARROW:
++ case K_RIGHTARROW:
++ case K_KP_RIGHTARROW:
++ case K_PGUP:
++ case K_KP_PGUP:
++ case K_PGDN:
++ case K_KP_PGDN:
++ case K_HOME:
++ case K_KP_HOME:
++ case K_END:
++ case K_KP_END:
++ m_play_click_sound(MENU_SOUND_SLIDE);
++ }
+ return 0;
+}
+float Slider_mouseDrag(entity me, vector pos)
+{
+ float hit;
+ float v, animed;
+ if(me.disabled)
+ return 0;
+
+ anim.removeObjAnim(anim, me);
+ animed = me.animated;
+ me.animated = false;
+
+ if(me.pressed)
+ {
+ hit = 1;
+ if(pos.x < 0 - me.tolerance.x) hit = 0;
+ if(pos.y < 0 - me.tolerance.y) hit = 0;
+ if(pos.x >= 1 - me.textSpace + me.tolerance.x) hit = 0;
+ if(pos.y >= 1 + me.tolerance.y) hit = 0;
+ if(hit)
+ {
+ v = median(0, (pos.x - me.pressOffset - 0.5 * me.controlWidth) / (1 - me.textSpace - me.controlWidth), 1) * (me.valueMax - me.valueMin) + me.valueMin;
+ if(me.valueStep)
+ v = floor(0.5 + v / me.valueStep) * me.valueStep;
+ me.setValue(me, v);
+ }
+ else
+ me.setValue(me, me.previousValue);
+ }
+
+ me.animated = animed;
+
+ return 1;
+}
+float Slider_mousePress(entity me, vector pos)
+{
+ float controlCenter;
+ if(me.disabled)
+ return 0;
+ if(pos.x < 0) return 0;
+ if(pos.y < 0) return 0;
+ if(pos.x >= 1 - me.textSpace) return 0;
+ if(pos.y >= 1) return 0;
+ controlCenter = (me.value - me.valueMin) / (me.valueMax - me.valueMin) * (1 - me.textSpace - me.controlWidth) + 0.5 * me.controlWidth;
+ if(fabs(pos.x - controlCenter) <= 0.5 * me.controlWidth)
+ {
+ me.pressed = 1;
+ me.pressOffset = pos.x - controlCenter;
+ me.previousValue = me.value;
+ //me.mouseDrag(me, pos);
+ }
+ else
+ {
+ float clickValue, pageValue, inRange;
+ clickValue = median(0, (pos.x - me.pressOffset - 0.5 * me.controlWidth) / (1 - me.textSpace - me.controlWidth), 1) * (me.valueMax - me.valueMin) + me.valueMin;
+ inRange = (almost_in_bounds(me.valueMin, me.value, me.valueMax));
+ if(pos.x < controlCenter)
+ {
+ pageValue = me.value - me.valuePageStep;
+ if(me.valueStep)
+ clickValue = floor(clickValue / me.valueStep) * me.valueStep;
+ pageValue = max(pageValue, clickValue);
+ if(inRange)
+ me.setValue(me, median(me.valueMin, pageValue, me.valueMax));
+ else
+ me.setValue(me, me.valueMax);
+ }
+ else
+ {
+ pageValue = me.value + me.valuePageStep;
+ if(me.valueStep)
+ clickValue = ceil(clickValue / me.valueStep) * me.valueStep;
+ pageValue = min(pageValue, clickValue);
+ if(inRange)
+ me.setValue(me, median(me.valueMin, pageValue, me.valueMax));
+ else
+ me.setValue(me, me.valueMax);
+ }
+ if(pageValue == clickValue)
+ {
+ controlCenter = (me.value - me.valueMin) / (me.valueMax - me.valueMin) * (1 - me.textSpace - me.controlWidth) + 0.5 * me.controlWidth;
+ me.pressed = 1;
+ me.pressOffset = pos.x - controlCenter;
+ me.previousValue = me.value;
+ //me.mouseDrag(me, pos);
+ }
+ }
+ return 1;
+}
+float Slider_mouseRelease(entity me, vector pos)
+{
+ me.pressed = 0;
+ if(me.disabled)
+ return 0;
- void Slider_focusEnter(entity me)
- {
- if(cvar("menu_sounds") > 1)
- localsound("sound/misc/menu1.wav");
- SUPER(Slider).focusEnter(me);
- }
++ m_play_click_sound(MENU_SOUND_SLIDE);
+ return 1;
+}
+void Slider_showNotify(entity me)
+{
+ me.focusable = !me.disabled;
+}
+void Slider_draw(entity me)
+{
+ float controlLeft;
+ float save;
+ me.focusable = !me.disabled;
+ save = draw_alpha;
+ if(me.disabled)
+ draw_alpha *= me.disabledAlpha;
+ draw_ButtonPicture('0 0 0', strcat(me.src, "_s"), eX * (1 - me.textSpace) + eY, me.color2, 1);
+ if(almost_in_bounds(me.valueMin, me.sliderValue, me.valueMax))
+ {
+ controlLeft = (me.sliderValue - me.valueMin) / (me.valueMax - me.valueMin) * (1 - me.textSpace - me.controlWidth);
+ if(me.disabled)
+ draw_Picture(eX * controlLeft, strcat(me.src, "_d"), eX * me.controlWidth + eY, me.colorD, 1);
+ else if(me.pressed)
+ draw_Picture(eX * controlLeft, strcat(me.src, "_c"), eX * me.controlWidth + eY, me.colorC, 1);
+ else if(me.focused)
+ draw_Picture(eX * controlLeft, strcat(me.src, "_f"), eX * me.controlWidth + eY, me.colorF, 1);
+ else
+ draw_Picture(eX * controlLeft, strcat(me.src, "_n"), eX * me.controlWidth + eY, me.color, 1);
+ }
+ me.setText(me, me.valueToText(me, me.value));
+ draw_alpha = save;
+ SUPER(Slider).draw(me);
+ me.text = string_null; // TEMPSTRING!
+}
+#endif
void postMenuDraw(); // this is run just after the menu is drawn (or not). Useful to draw something over everything else.
void m_sync();
+
+ // sounds
+
+ const string MENU_SOUND_CLEAR = "sound/menu/clear.wav";
+ const string MENU_SOUND_CLOSE = "sound/menu/close.wav";
+ const string MENU_SOUND_EXECUTE = "sound/menu/execute.wav";
+ const string MENU_SOUND_FOCUS = "sound/menu/focus.wav";
+ const string MENU_SOUND_OPEN = "sound/menu/open.wav";
+ const string MENU_SOUND_SELECT = "sound/menu/select.wav";
+ const string MENU_SOUND_SLIDE = "sound/menu/slide.wav";
+ const string MENU_SOUND_WINNER = "sound/menu/winner.wav";
+
+ void m_play_focus_sound();
+ void m_play_click_sound(string soundfile);
+#endif
--- /dev/null
+#ifdef INTERFACE
+CLASS(XonoticColorpicker) EXTENDS(Image)
+ METHOD(XonoticColorpicker, configureXonoticColorpicker, void(entity, entity))
+ METHOD(XonoticColorpicker, mousePress, float(entity, vector))
+ METHOD(XonoticColorpicker, mouseRelease, float(entity, vector))
+ METHOD(XonoticColorpicker, mouseDrag, float(entity, vector))
+ ATTRIB(XonoticColorpicker, controlledTextbox, entity, NULL)
+ ATTRIB(XonoticColorpicker, image, string, SKINGFX_COLORPICKER)
+ ATTRIB(XonoticColorpicker, imagemargin, vector, SKINMARGIN_COLORPICKER)
+ ATTRIB(XonoticColorpicker, focusable, float, 1)
+ METHOD(XonoticColorpicker, focusLeave, void(entity))
+ METHOD(XonoticColorpicker, keyDown, float(entity, float, float, float))
+ METHOD(XonoticColorpicker, draw, void(entity))
+ENDCLASS(XonoticColorpicker)
+entity makeXonoticColorpicker(entity theTextbox);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeXonoticColorpicker(entity theTextbox)
+{
+ entity me;
+ me = spawnXonoticColorpicker();
+ me.configureXonoticColorpicker(me, theTextbox);
+ return me;
+}
+
+void XonoticColorpicker_configureXonoticColorpicker(entity me, entity theTextbox)
+{
+ me.controlledTextbox = theTextbox;
+ me.configureImage(me, me.image);
+}
+
+float XonoticColorpicker_mousePress(entity me, vector coords)
+{
+ me.mouseDrag(me, coords);
+ return 1;
+}
+
+// must match hslimage.c
+vector hslimage_color(vector v, vector margin)
+{
+ v_x = (v.x - margin.x) / (1 - 2 * margin.x);
+ v_y = (v.y - margin.y) / (1 - 2 * margin.y);
+ if(v.x < 0) v_x = 0;
+ if(v.y < 0) v_y = 0;
+ if(v.x > 1) v_x = 1;
+ if(v.y > 1) v_y = 1;
+ if(v.y > 0.875) // grey bar
+ return hsl_to_rgb(eZ * v.x);
+ else
+ return hsl_to_rgb(v.x * 6 * eX + eY + v.y / 0.875 * eZ);
+}
+
+vector color_hslimage(vector v, vector margin)
+{
+ vector pos = '0 0 0';
+ v = rgb_to_hsl(v);
+ if (v.y)
+ {
+ pos_x = v.x / 6;
+ pos_y = v.z * 0.875;
+ }
+ else // grey scale
+ {
+ pos_x = v.z;
+ pos_y = 0.875 + 0.07;
+ }
+ pos_x = margin.x + pos.x * (1 - 2 * margin.x);
+ pos_y = margin.y + pos.y * (1 - 2 * margin.y);
+ return pos;
+}
+
+float XonoticColorpicker_mouseDrag(entity me, vector coords)
+{
+ float i, carets;
+ for (;;)
+ {
+ i = me.controlledTextbox.cursorPos;
+ if(i >= 2)
+ {
+ if(substring(me.controlledTextbox.text, i-2, 1) == "^")
+ {
+ carets = 1;
+ while (i - 2 - carets >= 0 && substring(me.controlledTextbox.text, i - 2 - carets, 1) == "^")
+ ++carets;
+ if (carets & 1)
+ if(strstrofs("0123456789", substring(me.controlledTextbox.text, i-1, 1), 0) >= 0)
+ {
+ me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
+ me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
+ continue;
+ }
+ }
+ }
+
+ if(i >= 5)
+ {
+ if(substring(me.controlledTextbox.text, i-5, 2) == "^x")
+ {
+ carets = 1;
+ while (i - 5 - carets >= 0 && substring(me.controlledTextbox.text, i - 5 - carets, 1) == "^")
+ ++carets;
+ if (carets & 1)
+ if(strstrofs("0123456789abcdefABCDEF", substring(me.controlledTextbox.text, i-3, 1), 0) >= 0)
+ if(strstrofs("0123456789abcdefABCDEF", substring(me.controlledTextbox.text, i-2, 1), 0) >= 0)
+ if(strstrofs("0123456789abcdefABCDEF", substring(me.controlledTextbox.text, i-1, 1), 0) >= 0)
+ {
+ me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
+ me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
+ me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
+ me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
+ me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
+ continue;
+ }
+ }
+ }
+ break;
+ }
+
+ if(substring(me.controlledTextbox.text, i-1, 1) == "^")
+ {
+ carets = 1;
+ while (i - 1 - carets >= 0 && substring(me.controlledTextbox.text, i - 1 - carets, 1) == "^")
+ ++carets;
+ if (carets & 1)
+ me.controlledTextbox.enterText(me.controlledTextbox, "^"); // escape previous caret
+ }
+
+ vector margin;
+ margin = me.imagemargin;
+ if(coords.x >= margin.x)
+ if(coords.y >= margin.y)
+ if(coords.x <= 1 - margin.x)
+ if(coords.y <= 1 - margin.y)
+ me.controlledTextbox.enterText(me.controlledTextbox, rgb_to_hexcolor(hslimage_color(coords, margin)));
+
+ return 1;
+}
+
+float XonoticColorpicker_mouseRelease(entity me, vector coords)
+{
++ m_play_click_sound(MENU_SOUND_SLIDE);
+ me.mouseDrag(me, coords);
+ return 1;
+}
+
+void XonoticColorpicker_focusLeave(entity me)
+{
+ me.controlledTextbox.saveCvars(me.controlledTextbox);
+}
+float XonoticColorpicker_keyDown(entity me, float key, float ascii, float shift)
+{
+ return me.controlledTextbox.keyDown(me.controlledTextbox, key, ascii, shift);
+}
+void XonoticColorpicker_draw(entity me)
+{
+ SUPER(XonoticColorpicker).draw(me);
+
+ float B, C, aC;
+ C = cvar("r_textcontrast");
+ B = cvar("r_textbrightness");
+
+ // for this to work, C/(1-B) must be in 0..1
+ // B must be < 1
+ // C must be < 1-B
+
+ B = bound(0, B, 1);
+ C = bound(0, C, 1-B);
+
+ aC = 1 - C / (1 - B);
+
+ draw_Picture(me.imgOrigin, strcat(me.src, "_m"), me.imgSize, '0 0 0', aC);
+ draw_Picture(me.imgOrigin, strcat(me.src, "_m"), me.imgSize, me.color, B);
+}
+#endif
--- /dev/null
+#ifdef INTERFACE
+CLASS(XonoticColorpickerString) EXTENDS(Image)
+ METHOD(XonoticColorpickerString, configureXonoticColorpickerString, void(entity, string, string))
+ METHOD(XonoticColorpickerString, mousePress, float(entity, vector))
+ METHOD(XonoticColorpickerString, mouseRelease, float(entity, vector))
+ METHOD(XonoticColorpickerString, mouseDrag, float(entity, vector))
+ ATTRIB(XonoticColorpickerString, cvarName, string, string_null)
+ METHOD(XonoticColorPickerString, loadCvars, void(entity))
+ METHOD(XonoticColorPickerString, saveCvars, void(entity))
+ ATTRIB(XonoticColorpickerString, prevcoords, vector, '0 0 0')
+ ATTRIB(XonoticColorpickerString, image, string, SKINGFX_COLORPICKER)
+ ATTRIB(XonoticColorpickerString, imagemargin, vector, SKINMARGIN_COLORPICKER)
+ ATTRIB(XonoticColorpickerString, focusable, float, 1)
+ METHOD(XonoticColorpickerString, draw, void(entity))
+ ATTRIB(XonoticColorpickerString, disabledAlpha, float, 0.3)
+ENDCLASS(XonoticColorpickerString)
+entity makeXonoticColorpickerString(string theCvar, string theDefaultCvar);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeXonoticColorpickerString(string theCvar, string theDefaultCvar)
+{
+ entity me;
+ me = spawnXonoticColorpickerString();
+ me.configureXonoticColorpickerString(me, theCvar, theDefaultCvar);
+ return me;
+}
+
+void XonoticColorpickerString_configureXonoticColorpickerString(entity me, string theCvar, string theDefaultCvar)
+{
+ me.cvarName = theCvar;
+ me.configureImage(me, me.image);
+ if(theCvar)
+ {
+ me.cvarName = theCvar;
+ me.tooltip = getZonedTooltipForIdentifier(theCvar);
+ me.loadCvars(me);
+ }
+}
+
+void XonoticColorPickerString_loadCvars(entity me)
+{
+ if (!me.cvarName)
+ return;
+
+ if(substring(me.cvarName, -1, 1) == "_")
+ {
+ me.prevcoords = color_hslimage(
+ eX * cvar(strcat(me.cvarName, "red")) +
+ eY * cvar(strcat(me.cvarName, "green")) +
+ eZ * cvar(strcat(me.cvarName, "blue")),
+ me.imagemargin);
+ }
+ else
+ me.prevcoords = color_hslimage(stov(cvar_string(me.cvarName)), me.imagemargin);
+}
+
+void XonoticColorPickerString_saveCvars(entity me)
+{
+ if (!me.cvarName)
+ return;
+
+ if(substring(me.cvarName, -1, 1) == "_")
+ {
+ vector v = hslimage_color(me.prevcoords, me.imagemargin);
+ cvar_set(strcat(me.cvarName, "red"), ftos(v.x));
+ cvar_set(strcat(me.cvarName, "green"), ftos(v.y));
+ cvar_set(strcat(me.cvarName, "blue"), ftos(v.z));
+ }
+ else
+ cvar_set(me.cvarName, sprintf("%v", hslimage_color(me.prevcoords, me.imagemargin)));
+}
+
+float XonoticColorpickerString_mousePress(entity me, vector coords)
+{
+ me.mouseDrag(me, coords);
+ return 1;
+}
+
+float XonoticColorpickerString_mouseDrag(entity me, vector coords)
+{
+ if(me.disabled)
+ return 0;
+ vector margin;
+ margin = me.imagemargin;
+ if(coords.x >= margin.x)
+ if(coords.y >= margin.y)
+ if(coords.x <= 1 - margin.x)
+ if(coords.y <= 1 - margin.y)
+ {
+ me.prevcoords = coords;
+ me.saveCvars(me);
+ }
+
+ return 1;
+}
+
+float XonoticColorpickerString_mouseRelease(entity me, vector coords)
+{
++ m_play_click_sound(MENU_SOUND_SLIDE);
+ me.mouseDrag(me, coords);
+ return 1;
+}
+
+void XonoticColorpickerString_draw(entity me)
+{
+ float save;
+ save = draw_alpha;
+ if(me.disabled)
+ draw_alpha *= me.disabledAlpha;
+
+ SUPER(XonoticColorpickerString).draw(me);
+
+ vector sz;
+ sz = draw_PictureSize(strcat(me.src, "_selected"));
+ sz = globalToBoxSize(sz, draw_scale);
+
+ if(!me.disabled)
+ draw_Picture(me.imgOrigin + me.prevcoords - 0.5 * sz, strcat(me.src, "_selected"), sz, '1 1 1', 1);
+
+ draw_alpha = save;
+}
+#endif
--- /dev/null
- me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_gender"));
+#ifdef INTERFACE
+CLASS(XonoticProfileTab) EXTENDS(XonoticTab)
+ METHOD(XonoticProfileTab, fill, void(entity))
+ METHOD(XonoticProfileTab, draw, void(entity))
+ ATTRIB(XonoticProfileTab, title, string, _("Profile"))
+ ATTRIB(XonoticProfileTab, intendedWidth, float, 0.9)
+ ATTRIB(XonoticProfileTab, rows, float, 23)
+ ATTRIB(XonoticProfileTab, columns, float, 6.1) // added extra .2 for center space
+ ATTRIB(XonoticProfileTab, playerNameLabel, entity, NULL)
+ ATTRIB(XonoticProfileTab, playerNameLabelAlpha, float, SKINALPHA_HEADER)
+ENDCLASS(XonoticProfileTab)
+entity makeXonoticProfileTab();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeXonoticProfileTab()
+{
+ entity me;
+ me = spawnXonoticProfileTab();
+ me.configureDialog(me);
+ return me;
+}
+void XonoticProfileTab_draw(entity me)
+{
+ if(cvar_string("_cl_name") == "Player")
+ me.playerNameLabel.alpha = ((mod(time * 2, 2) < 1) ? 1 : 0);
+ else
+ me.playerNameLabel.alpha = me.playerNameLabelAlpha;
+ SUPER(XonoticProfileTab).draw(me);
+}
+void XonoticProfileTab_fill(entity me)
+{
+ entity e, pms, label, box;
+ float i;
+
+ // ==============
+ // NAME SECTION
+ // ==============
+ me.gotoRC(me, 0.5, 0);
+ me.TD(me, 1, 3, me.playerNameLabel = makeXonoticHeaderLabel(_("Name")));
+
+ me.gotoRC(me, 1.5, 0);
+ me.TD(me, 1, 3, label = makeXonoticTextLabel(0.5, string_null));
+ label.allowCut = 1;
+ label.allowColors = 1;
+ label.alpha = 1;
+ label.isBold = true;
+ label.fontSize = SKINFONTSIZE_TITLE;
+
+ me.gotoRC(me, 2.5, 0);
+ me.TD(me, 1, 3.0, box = makeXonoticInputBox(1, "_cl_name"));
+ box.forbiddenCharacters = "\r\n\\\"$"; // don't care, isn't getting saved
+ box.maxLength = -127; // negative means encoded length in bytes
+ box.saveImmediately = 1;
+ box.enableClearButton = 0;
+ label.textEntity = box;
+ me.TR(me);
+ me.TD(me, 5, 1, e = makeXonoticColorpicker(box));
+ me.TD(me, 5, 2, e = makeXonoticCharmap(box));
+
+ // ===============
+ // MODEL SECTION
+ // ===============
+ //me.gotoRC(me, 0.5, 3.1); me.setFirstColumn(me, me.currentColumn); // TOP RIGHT
+ //me.gotoRC(me, 9, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM RIGHT
+ me.gotoRC(me, 9, 0); me.setFirstColumn(me, me.currentColumn); // BOTTOM LEFT
+ me.TD(me, 1, 3, e = makeXonoticHeaderLabel(_("Model")));
+
+ me.TR(me);
+ //me.TDempty(me, 0); // MODEL LEFT, COLOR RIGHT
+ me.TDempty(me, 1); // MODEL RIGHT, COLOR LEFT
+ pms = makeXonoticPlayerModelSelector();
+ me.TD(me, 1, 0.3, e = makeXonoticButton("<<", '0 0 0'));
+ e.onClick = PlayerModelSelector_Prev_Click;
+ e.onClickEntity = pms;
+ me.TD(me, 11.5, 1.4, pms);
+ me.TD(me, 1, 0.3, e = makeXonoticButton(">>", '0 0 0'));
+ e.onClick = PlayerModelSelector_Next_Click;
+ e.onClickEntity = pms;
+
+ //me.setFirstColumn(me, me.currentColumn + 2); // MODEL LEFT, COLOR RIGHT
+ me.gotoRC(me, me.currentRow, 0); me.setFirstColumn(me, me.currentColumn); // MODEL RIGHT, COLOR LEFT
+ me.TR(me);
+ me.TD(me, 1, 1, e = makeXonoticHeaderLabel(_("Glowing color")));
+ for(i = 0; i < 15; ++i)
+ {
+ if(mod(i, 5) == 0)
+ me.TR(me);
+ me.TDNoMargin(me, 1, 0.2, e = makeXonoticColorButton(1, 0, i), '0 1 0');
+ }
+ me.TR(me);
+ me.TR(me);
+ me.TD(me, 1, 1, e = makeXonoticHeaderLabel(_("Detail color")));
+ for(i = 0; i < 15; ++i)
+ {
+ if(mod(i, 5) == 0)
+ me.TR(me);
+ me.TDNoMargin(me, 1, 0.2, e = makeXonoticColorButton(2, 1, i), '0 1 0');
+ }
+
+ // ====================
+ // STATISTICS SECTION
+ // ====================
+ me.gotoRC(me, 0.5, 3.1); me.setFirstColumn(me, me.currentColumn); // TOP RIGHT
+ //me.gotoRC(me, 9, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM RIGHT
+ //me.gotoRC(me, 9, 0); me.setFirstColumn(me, me.currentColumn); // BOTTOM LEFT
+ me.TD(me, 1, 3, e = makeXonoticHeaderLabel(_("Statistics")));
+
+ me.TR(me);
+ me.TDempty(me, 0.25);
+ me.TD(me, 1, 2.5, e = makeXonoticCheckBox(0, "cl_allow_uidtracking", _("Allow player statistics to track your client")));
+ me.TR(me);
+ me.TDempty(me, 0.25);
+ me.TD(me, 1, 2.5, e = makeXonoticCheckBox(0, "cl_allow_uid2name", _("Allow player statistics to use your nickname")));
+ setDependent(e, "cl_allow_uidtracking", 1, 1);
+ me.gotoRC(me, 4, 3.1); // TOP RIGHT
+ //me.gotoRC(me, 12.5, 3.1); // BOTTOM RIGHT
+ //me.gotoRC(me, 12.5, 0); // BOTTOM LEFT
+ me.TDempty(me, 0.25);
+ me.TD(me, 9, 2.5, statslist = makeXonoticStatsList());
+ //setDependent(statslist, "cl_allow_uidtracking", 1, 1);
+
+ // =================
+ // COUNTRY SECTION
+ // =================
+ me.gotoRC(me, 16, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM SECTION, TOP POS
+ //me.gotoRC(me, 13.5, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM SECTION, TOP POS
+ //me.gotoRC(me, 0.5, 3.1); me.setFirstColumn(me, me.currentColumn); // TOP SECTION, TOP POS
+ me.TD(me, 1, 3, e = makeXonoticHeaderLabel(_("Country")));
+
+ me.TR(me);
+ me.TDempty(me, 0.5);
+ me.TD(me, 4.5, 2, e = makeXonoticLanguageList()); // todo: cl_country: create proper country list
+
+
+ // ================
+ // GENDER SECTION
+ // ================
+ me.gotoRC(me, 13.5, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM SECTION, TOP POS
+ //me.gotoRC(me, 19.5, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM SECTION, BOTTOM POS
+ //me.gotoRC(me, 6.5, 3.1); me.setFirstColumn(me, me.currentColumn); // TOP SECTION, BOTTOM POS
+ #if 0
+ me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Gender:")));
- me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "cl_gender", "2", _("Female")));
- me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "cl_gender", "1", _("Male")));
- me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "cl_gender", "0", _("Undisclosed")));
++ me.TD(me, 1, 2, e = makeXonoticTextSlider("_cl_gender"));
+ e.addValue(e, ZCTX(_("GENDER^Undisclosed")), "0");
+ e.addValue(e, ZCTX(_("GENDER^Female")), "1");
+ e.addValue(e, ZCTX(_("GENDER^Male")), "2");
+ e.configureXonoticTextSliderValues(e);
+ #else
+ me.TD(me, 1, 3, e = makeXonoticHeaderLabel(_("Gender")));
+ me.TR(me);
+ #define GENDERWIDTH_OFFSET 0.25
+ #define GENDERWIDTH_LENGTH 2.5
+ #define GENDERWIDTH_ITEM (GENDERWIDTH_LENGTH / 3)
+ me.TDempty(me, GENDERWIDTH_OFFSET);
++ me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "_cl_gender", "2", _("Female")));
++ me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "_cl_gender", "1", _("Male")));
++ me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "_cl_gender", "0", _("Undisclosed")));
+ #endif
+
+ me.gotoRC(me, me.rows - 1, 0);
+ me.TD(me, 1, me.columns, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "color -1 -1;name \"$_cl_name\";sendcvar cl_weaponpriority;sendcvar cl_autoswitch;sendcvar cl_forceplayermodels;sendcvar cl_forceplayermodelsfromxonotic;playermodel $_cl_playermodel;playerskin $_cl_playerskin", COMMANDBUTTON_APPLY));
+}
+#endif
--- /dev/null
- me.TD(me, 1, 3, makeXonoticCheckBoxEx(2, 0, "menu_sounds", _("Menu sounds")));
+#ifdef INTERFACE
+CLASS(XonoticAudioSettingsTab) EXTENDS(XonoticTab)
+ METHOD(XonoticAudioSettingsTab, fill, void(entity))
+ ATTRIB(XonoticAudioSettingsTab, title, string, _("Audio"))
+ ATTRIB(XonoticAudioSettingsTab, intendedWidth, float, 0.9)
+ ATTRIB(XonoticAudioSettingsTab, rows, float, 15.5)
+ ATTRIB(XonoticAudioSettingsTab, columns, float, 6.2) // added extra .2 for center space
++ ATTRIB(XonoticAudioSettingsTab, hiddenMenuSoundsSlider, entity, NULL)
+ENDCLASS(XonoticAudioSettingsTab)
+entity makeXonoticAudioSettingsTab();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeXonoticAudioSettingsTab()
+{
+ entity me;
+ me = spawnXonoticAudioSettingsTab();
+ me.configureDialog(me);
+ return me;
+}
+
+void XonoticAudioSettingsTab_fill(entity me)
+{
+ entity e, s;
+
+ me.TR(me);
+ s = makeXonoticDecibelsSlider(-40, 0, 0.4, "mastervolume");
+ me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Master:")));
+ me.TD(me, 1, 2, s);
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ s = makeXonoticDecibelsSlider(-40, 0, 0.4, "bgmvolume");
+ makeMulti(s, "snd_channel8volume");
+ me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Music:")));
+ me.TD(me, 1, 2, s);
+ setDependentStringNotEqual(e, "mastervolume", "0");
+ setDependentStringNotEqual(s, "mastervolume", "0");
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_staticvolume");
+ makeMulti(s, "snd_channel9volume");
+ me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("VOL^Ambient:"))));
+ me.TD(me, 1, 2, s);
+ setDependentStringNotEqual(e, "mastervolume", "0");
+ setDependentStringNotEqual(s, "mastervolume", "0");
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel0volume");
+ me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Info:")));
+ me.TD(me, 1, 2, s);
+ setDependentStringNotEqual(e, "mastervolume", "0");
+ setDependentStringNotEqual(s, "mastervolume", "0");
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel3volume");
+ me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Items:")));
+ me.TD(me, 1, 2, s);
+ setDependentStringNotEqual(e, "mastervolume", "0");
+ setDependentStringNotEqual(s, "mastervolume", "0");
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel6volume");
+ me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Pain:")));
+ me.TD(me, 1, 2, s);
+ setDependentStringNotEqual(e, "mastervolume", "0");
+ setDependentStringNotEqual(s, "mastervolume", "0");
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel7volume");
+ me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Player:")));
+ me.TD(me, 1, 2, s);
+ setDependentStringNotEqual(e, "mastervolume", "0");
+ setDependentStringNotEqual(s, "mastervolume", "0");
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel4volume");
+ me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Shots:")));
+ me.TD(me, 1, 2, s);
+ setDependentStringNotEqual(e, "mastervolume", "0");
+ setDependentStringNotEqual(s, "mastervolume", "0");
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel2volume");
+ me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Voice:")));
+ me.TD(me, 1, 2, s);
+ setDependentStringNotEqual(e, "mastervolume", "0");
+ setDependentStringNotEqual(s, "mastervolume", "0");
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel1volume");
+ makeMulti(s, "snd_channel5volume"); // @!#%'n Tuba
+ me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Weapons:")));
+ me.TD(me, 1, 2, s);
+ setDependentStringNotEqual(e, "mastervolume", "0");
+ setDependentStringNotEqual(s, "mastervolume", "0");
+ me.TR(me);
+ me.TR(me);
+ me.TD(me, 1, 3, makeXonoticCheckBox(0, "menu_snd_attenuation_method", _("New style sound attenuation")));
+ me.TR(me);
+ me.TD(me, 1, 3, makeXonoticCheckBox(0, "snd_mutewhenidle", _("Mute sounds when not active")));
+
+ me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn);
+ me.TD(me, 1, 1, makeXonoticTextLabel(0, _("Frequency:")));
+ me.TD(me, 1, 2, e = makeXonoticTextSlider("snd_speed"));
+ e.addValue(e, _("8 kHz"), "8000");
+ e.addValue(e, _("11.025 kHz"), "11025");
+ e.addValue(e, _("16 kHz"), "16000");
+ e.addValue(e, _("22.05 kHz"), "22050");
+ e.addValue(e, _("24 kHz"), "24000");
+ e.addValue(e, _("32 kHz"), "32000");
+ e.addValue(e, _("44.1 kHz"), "44100");
+ e.addValue(e, _("48 kHz"), "48000");
+ e.configureXonoticTextSliderValues(e);
+ me.TR(me);
+ me.TD(me, 1, 1, makeXonoticTextLabel(0, _("Channels:")));
+ me.TD(me, 1, 2, e = makeXonoticTextSlider("snd_channels"));
+ e.addValue(e, _("Mono"), "1");
+ e.addValue(e, _("Stereo"), "2");
+ e.addValue(e, _("2.1"), "3");
+ e.addValue(e, _("4"), "4");
+ e.addValue(e, _("5"), "5");
+ e.addValue(e, _("5.1"), "6");
+ e.addValue(e, _("6.1"), "7");
+ e.addValue(e, _("7.1"), "8");
+ e.configureXonoticTextSliderValues(e);
+ me.TR(me);
+ me.TR(me);
+ me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "snd_swapstereo", _("Swap stereo output channels")));
+ setDependent(e, "snd_channels", 1.5, 0.5);
+ me.TR(me);
+ me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "snd_spatialization_control", _("Headphone friendly mode")));
+ setDependent(e, "snd_channels", 1.5, 0.5);
+ me.TR(me);
+ me.TR(me);
+ me.TD(me, 1, 3, makeXonoticCheckBox(0, "cl_hitsound", _("Hit indication sound")));
+ e.sendCvars = true;
+ me.TR(me);
+ me.TD(me, 1, 3, makeXonoticCheckBox(0, "con_chatsound", _("Chat message sound")));
+ me.TR(me);
++ me.hiddenMenuSoundsSlider = makeXonoticSlider(1, 1, 1, "menu_sounds");
++ me.TD(me, 1, 1.2, makeXonoticSliderCheckBox(0, 1, me.hiddenMenuSoundsSlider, _("Menu sounds")));
++ me.TD(me, 1, 1.8, e = makeXonoticSliderCheckBox(2, 0, me.hiddenMenuSoundsSlider, _("Focus sounds")));
++ setDependent(e, "menu_sounds", 1, 2);
+ me.TR(me);
+ me.TR(me);
+ me.TD(me, 1, 1, makeXonoticTextLabel(0, _("Time announcer:")));
+ me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_announcer_maptime"));
+ e.addValue(e, ZCTX(_("WRN^Disabled")), "0");
+ e.addValue(e, _("1 minute"), "1");
+ e.addValue(e, _("5 minutes"), "2");
+ e.addValue(e, ZCTX(_("WRN^Both")), "3");
+ e.configureXonoticTextSliderValues(e);
+ me.TR(me);
+ me.TD(me, 1, 1, makeXonoticTextLabel(0, _("Automatic taunts:")));
+ me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_autotaunt"));
+ e.addValue(e, _("Never"), "0");
+ e.addValue(e, _("Sometimes"), "0.35");
+ e.addValue(e, _("Often"), "0.65");
+ e.addValue(e, _("Always"), "1");
+ e.configureXonoticTextSliderValues(e);
+ e.sendCvars = true;
+ me.TR(me);
+ me.TR(me);
+ if(cvar("developer"))
+ me.TD(me, 1, 3, makeXonoticCheckBox(0, "showsound", _("Debug info about sounds")));
+
+ me.gotoRC(me, me.rows - 1, 0);
+ me.TD(me, 1, me.columns, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "snd_restart; snd_attenuation_method_${menu_snd_attenuation_method}", COMMANDBUTTON_APPLY));
+}
+#endif
--- /dev/null
+#ifdef INTERFACE
+CLASS(XonoticGameCrosshairSettingsTab) EXTENDS(XonoticTab)
+ //METHOD(XonoticGameCrosshairSettingsTab, toString, string(entity))
+ METHOD(XonoticGameCrosshairSettingsTab, fill, void(entity))
+ METHOD(XonoticGameCrosshairSettingsTab, showNotify, void(entity))
+ ATTRIB(XonoticGameCrosshairSettingsTab, title, string, _("Crosshair"))
+ ATTRIB(XonoticGameCrosshairSettingsTab, intendedWidth, float, 0.9)
+ ATTRIB(XonoticGameCrosshairSettingsTab, rows, float, 13)
+ ATTRIB(XonoticGameCrosshairSettingsTab, columns, float, 6.2)
+ENDCLASS(XonoticGameCrosshairSettingsTab)
+entity makeXonoticGameCrosshairSettingsTab();
+#endif
+
+#ifdef IMPLEMENTATION
+void XonoticGameCrosshairSettingsTab_showNotify(entity me)
+{
+ loadAllCvars(me);
+}
+entity makeXonoticGameCrosshairSettingsTab()
+{
+ entity me;
+ me = spawnXonoticGameCrosshairSettingsTab();
+ me.configureDialog(me);
+ return me;
+}
+
+void XonoticGameCrosshairSettingsTab_fill(entity me)
+{
+ entity e;
+ float i;
+
+ // crosshair_enabled: 0 = no crosshair options, 1 = no crosshair selection, but everything else enabled, 2 = all crosshair options enabled
+ // FIXME: In the future, perhaps make one global crosshair_type cvar which has 0 for disabled, 1 for custom, 2 for per weapon, etc?
+ me.TR(me); //me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn);
+ me.TD(me, 1, 1, e = makeXonoticRadioButton(3, "crosshair_enabled", "0", _("No crosshair")));
+ //me.TR(me);
+ me.TD(me, 1, 1, e = makeXonoticRadioButton(3, "crosshair_per_weapon", string_null, _("Per weapon")));
+ makeMulti(e, "crosshair_enabled");
+ //me.TR(me);
+ me.TD(me, 1, 1, e = makeXonoticRadioButton(3, "crosshair_enabled", "2", _("Custom")));
+ me.TR(me);
+ me.TDempty(me, 0.1);
+ for(i = 1; i <= 14; ++i) {
+ me.TDNoMargin(me, 1, 2 / 14, e = makeXonoticCrosshairButton(4, i), '1 1 0');
+ setDependentAND(e, "crosshair_per_weapon", 0, 0, "crosshair_enabled", 1, 2);
+ }
+ // show a larger preview of the selected crosshair
+ me.TDempty(me, 0.1);
+ me.TDNoMargin(me, 3, 0.8, e = makeXonoticCrosshairButton(7, -1), '1 1 0'); // crosshair -1 makes this a preview
+ setDependentAND(e, "crosshair_per_weapon", 0, 0, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TDempty(me, 0.1);
+ for(i = 15; i <= 28; ++i) {
+ me.TDNoMargin(me, 1, 2 / 14, e = makeXonoticCrosshairButton(4, i), '1 1 0');
+ setDependentAND(e, "crosshair_per_weapon", 0, 0, "crosshair_enabled", 1, 2);
+ }
+ me.TR(me);
++ me.TDempty(me, 0.1);
++ for(i = 29; i <= 42; ++i) {
++ me.TDNoMargin(me, 1, 2 / 14, e = makeXonoticCrosshairButton(4, i), '1 1 0');
++ setDependentAND(e, "crosshair_per_weapon", 0, 0, "crosshair_enabled", 1, 2);
++ }
+ me.TR(me);
+ me.TDempty(me, 0.1);
+ me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair size:")));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TD(me, 1, 1.9, e = makeXonoticSlider(0.1, 1.0, 0.01, "crosshair_size"));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TDempty(me, 0.1);
+ me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair alpha:")));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TD(me, 1, 1.9, e = makeXonoticSlider(0, 1, 0.1, "crosshair_alpha"));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TDempty(me, 0.1);
+ me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair color:")));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TD(me, 1, 0.9, e = makeXonoticRadioButton(5, "crosshair_color_special", "1", _("Per weapon")));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TD(me, 1, 1, e = makeXonoticRadioButton(5, "crosshair_color_special", "2", _("By health")));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ me.TD(me, 1, 0.8, e = makeXonoticRadioButton(5, "crosshair_color_special", "0", _("Custom")));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TD(me, 2, 2, e = makeXonoticColorpickerString("crosshair_color", "crosshair_color"));
+ setDependentAND(e, "crosshair_color_special", 0, 0, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TR(me);
+ me.TR(me);
+ me.TDempty(me, 0.1);
+ me.TD(me, 1, 2.9, e = makeXonoticCheckBox(0, "crosshair_ring", _("Use rings to indicate weapon status")));
+ makeMulti(e, "crosshair_ring_reload");
+ setDependent(e, "crosshair_enabled", 1, 2);
+ //me.TR(me);
+ // me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Ring size:")));
+ // setDependentAND(e, "crosshair_ring", 1, 1, "crosshair_enabled", 1, 2);
+ // me.TD(me, 1, 2, e = makeXonoticSlider(2, 4, 0.1, "crosshair_ring_size"));
+ // setDependentAND(e, "crosshair_ring", 1, 1, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TDempty(me, 0.3);
+ me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Ring alpha:")));
+ setDependentAND(e, "crosshair_ring", 1, 1, "crosshair_enabled", 1, 2);
+ me.TD(me, 1, 1.8, e = makeXonoticSlider(0.1, 1, 0.1, "crosshair_ring_alpha"));
+ setDependentAND(e, "crosshair_ring", 1, 1, "crosshair_enabled", 1, 2);
+
+ me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn);
+ me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "crosshair_dot", _("Enable center crosshair dot")));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TDempty(me, 0.1);
+ me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Dot size:")));
+ setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
+ me.TD(me, 1, 2, e = makeXonoticSlider(0.2, 2, 0.1, "crosshair_dot_size"));
+ setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TDempty(me, 0.1);
+ me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Dot alpha:")));
+ setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
+ me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 1, 0.1, "crosshair_dot_alpha"));
+ setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TDempty(me, 0.1);
+ me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Dot color:")));
+ setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
+ me.TD(me, 1, 2, e = makeXonoticRadioButton(1, "crosshair_dot_color_custom", "0", _("Use normal crosshair color")));
+ setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TDempty(me, 0.2);
+ me.TD(me, 1, 0.8, e = makeXonoticRadioButton(1, "crosshair_dot_color_custom", "1", _("Custom")));
+ setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
+ me.TD(me, 2, 2, e = makeXonoticColorpickerString("crosshair_dot_color", "crosshair_dot_color"));
+ setDependentAND3(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2, "crosshair_dot_color_custom", 1, 1);
+ me.TR(me);
+ me.TR(me);
+ me.TR(me);
+ me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "crosshair_effect_scalefade", _("Smooth effects of crosshairs")));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "crosshair_hittest_blur", _("Blur crosshair if the shot is obstructed")));
+ setDependentAND(e, "crosshair_hittest", 1, 100, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TD(me, 1, 3, e = makeXonoticCheckBoxEx(1.25, 0, "crosshair_hittest_scale", _("Enlarge crosshair if targeting an enemy")));
+ setDependentAND(e, "crosshair_hittest", 1, 100, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TD(me, 1, 3, e = makeXonoticCheckBoxEx(0.5, 0, "crosshair_hitindication", _("Animate crosshair when hitting an enemy")));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ me.TR(me);
+ me.TD(me, 1, 3, e = makeXonoticCheckBoxEx(0.25, 0, "crosshair_pickup", _("Animate crosshair when picking up an item")));
+ setDependent(e, "crosshair_enabled", 1, 2);
+ /*me.TR(me);
+ me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Hit testing:")));
+ me.TD(me, 1, 2, e = makeXonoticTextSlider("crosshair_hittest"));
+ e.addValue(e, ZCTX(_("HTTST^Disabled")), "0");
+ e.addValue(e, ZCTX(_("HTTST^TrueAim")), "1");
+ e.addValue(e, ZCTX(_("HTTST^Enemies")), "1.25");
+ e.configureXonoticTextSliderValues(e);
+ setDependent(e, "crosshair_enabled", 1, 2);*/
+
+ /*me.TR(me);
+
+ me.gotoRC(me, me.rows - 1, 0);
+ me.TD(me, 1, me.columns, e = makeXonoticButton(_("OK"), '0 0 0'));
+ e.onClick = Dialog_Close;
+ e.onClickEntity = me;*/
+}
+#endif
--- /dev/null
+#ifdef INTERFACE
+CLASS(XonoticWinnerDialog) EXTENDS(XonoticDialog)
+ METHOD(XonoticWinnerDialog, fill, void(entity))
++ METHOD(XonoticWinnerDialog, focusEnter, void(entity))
+ ATTRIB(XonoticWinnerDialog, title, string, _("Winner"))
+ ATTRIB(XonoticWinnerDialog, color, vector, SKINCOLOR_DIALOG_SINGLEPLAYER)
+ ATTRIB(XonoticWinnerDialog, intendedWidth, float, 0.32)
+ ATTRIB(XonoticWinnerDialog, rows, float, 12)
+ ATTRIB(XonoticWinnerDialog, columns, float, 3)
+ENDCLASS(XonoticWinnerDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void XonoticWinnerDialog_fill(entity me)
+{
+ entity e;
+
+ me.TR(me);
+ me.TD(me, me.rows - 2, me.columns, e = makeXonoticImage("/gfx/winner", -1));
+
+ me.gotoRC(me, me.rows - 1, 0);
+ me.TD(me, 1, me.columns, e = makeXonoticButton(_("OK"), '0 0 0'));
+ e.onClick = Dialog_Close;
+ e.onClickEntity = me;
+}
++void XonoticWinnerDialog_focusEnter(entity me)
++{
++ m_play_click_sound(MENU_SOUND_WINNER);
++}
+#endif
--- /dev/null
-
+#ifdef INTERFACE
+CLASS(XonoticGametypeList) EXTENDS(XonoticListBox)
+ METHOD(XonoticGametypeList, configureXonoticGametypeList, void(entity))
+ ATTRIB(XonoticGametypeList, rowsPerItem, float, 2)
+ METHOD(XonoticGametypeList, drawListBoxItem, void(entity, float, vector, float))
+ METHOD(XonoticGametypeList, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(XonoticGametypeList, setSelected, void(entity, float))
+ METHOD(XonoticGametypeList, loadCvars, void(entity))
+ METHOD(XonoticGametypeList, saveCvars, void(entity))
+ METHOD(XonoticGametypeList, keyDown, float(entity, float, float, float))
++ METHOD(XonoticGametypeList, clickListBoxItem, void(entity, float, vector))
+
+ ATTRIB(XonoticGametypeList, realFontSize, vector, '0 0 0')
+ ATTRIB(XonoticGametypeList, realUpperMargin, float, 0)
+ ATTRIB(XonoticGametypeList, columnIconOrigin, float, 0)
+ ATTRIB(XonoticGametypeList, columnIconSize, float, 0)
+ ATTRIB(XonoticGametypeList, columnNameOrigin, float, 0)
+ ATTRIB(XonoticGametypeList, columnNameSize, float, 0)
+ENDCLASS(XonoticGametypeList)
+entity makeXonoticGametypeList();
+#endif
+
+#ifdef IMPLEMENTATION
+
+entity makeXonoticGametypeList(void)
+{
+ entity me;
+ me = spawnXonoticGametypeList();
+ me.configureXonoticGametypeList(me);
+ return me;
+}
+void XonoticGametypeList_configureXonoticGametypeList(entity me)
+{
+ float i;
+ me.configureXonoticListBox(me);
+ me.nItems = GameType_GetCount();
+
+ // we want the pics mipmapped
+ for(i = 0; i < GameType_GetCount(); ++i)
+ draw_PreloadPictureWithFlags(GameType_GetIcon(i), PRECACHE_PIC_MIPMAP);
+
+ me.loadCvars(me);
+}
+void XonoticGametypeList_setSelected(entity me, float i)
+{
+ SUPER(XonoticGametypeList).setSelected(me, i);
+ me.saveCvars(me);
+}
-
+void XonoticGametypeList_loadCvars(entity me)
+{
+ float t;
+ t = MapInfo_CurrentGametype();
+ float i;
+ for(i = 0; i < GameType_GetCount(); ++i)
+ if(t == GameType_GetID(i))
+ break;
+ if(i >= GameType_GetCount())
+ {
+ for(i = 0; i < GameType_GetCount(); ++i)
+ if(t == MAPINFO_TYPE_DEATHMATCH)
+ break;
+ if(i >= GameType_GetCount())
+ i = 0;
+ }
+ me.setSelected(me, i);
+ // do we need this: me.parent.gameTypeChangeNotify(me.parent); // to make sure
+}
+void XonoticGametypeList_saveCvars(entity me)
+{
+ float t;
+ t = GameType_GetID(me.selectedItem);
+ if(t == MapInfo_CurrentGametype())
+ return;
+ MapInfo_SwitchGameType(t);
+ me.parent.gameTypeChangeNotify(me.parent);
+}
+void XonoticGametypeList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
+{
+ string s1, s2;
+
+ if(isSelected)
+ draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
+
+ draw_Picture(me.columnIconOrigin * eX, GameType_GetIcon(i), me.columnIconSize * eX + eY, '1 1 1', SKINALPHA_LISTBOX_SELECTED);
+ s1 = GameType_GetName(i);
+
+ if(_MapInfo_GetTeamPlayBool(GameType_GetID(i)))
+ s2 = _("teamplay");
+ else
+ s2 = _("free for all");
+
+ vector save_fontscale = draw_fontscale;
+ float f = draw_CondensedFontFactor(strcat(s1, " ", s2), false, me.realFontSize, 1);
+ draw_fontscale.x *= f;
+ vector fs = me.realFontSize;
+ fs.x *= f;
+ draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s1, fs, '1 1 1', SKINALPHA_TEXT, 0);
+ draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + 1.0 * (me.columnNameSize - draw_TextWidth(s2, 0, fs))) * eX, s2, fs, SKINCOLOR_TEXT, SKINALPHA_TEXT, 0);
+ draw_fontscale = save_fontscale;
+}
+void XonoticGametypeList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ me.itemAbsSize = '0 0 0';
+ SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+
+ me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
+ me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
+ me.columnIconOrigin = 0;
+ me.columnIconSize = me.itemAbsSize.y / me.itemAbsSize.x;
+ me.columnNameOrigin = me.columnIconOrigin + me.columnIconSize + (0.5 * me.realFontSize.x);
+ me.columnNameSize = 1 - me.columnIconSize - (1.5 * me.realFontSize.x);
+}
+float XonoticGametypeList_keyDown(entity me, float scan, float ascii, float shift)
+{
+ if(scan == K_ENTER || scan == K_KP_ENTER)
+ {
++ m_play_click_sound(MENU_SOUND_EXECUTE);
+ me.parent.gameTypeSelectNotify(me.parent);
+ return 1;
+ }
+
+ return SUPER(XonoticGametypeList).keyDown(me, scan, ascii, shift);
+}
++void XonoticGametypeList_clickListBoxItem(entity me, float i, vector where)
++{
++ m_play_click_sound(MENU_SOUND_SELECT);
++}
+#endif
--- /dev/null
- localcmd("exec binds-default.cfg\n");
+#ifdef INTERFACE
+CLASS(XonoticKeyBinder) EXTENDS(XonoticListBox)
+ METHOD(XonoticKeyBinder, configureXonoticKeyBinder, void(entity))
+ ATTRIB(XonoticKeyBinder, rowsPerItem, float, 1)
+ METHOD(XonoticKeyBinder, drawListBoxItem, void(entity, float, vector, float))
+ METHOD(XonoticKeyBinder, doubleClickListBoxItem, void(entity, float, vector))
+ METHOD(XonoticKeyBinder, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(XonoticKeyBinder, setSelected, void(entity, float))
+ METHOD(XonoticKeyBinder, keyDown, float(entity, float, float, float))
+ METHOD(XonoticKeyBinder, keyGrabbed, void(entity, float, float))
+
+ ATTRIB(XonoticKeyBinder, realFontSize, vector, '0 0 0')
+ ATTRIB(XonoticKeyBinder, realUpperMargin, float, 0)
+ ATTRIB(XonoticKeyBinder, columnFunctionOrigin, float, 0)
+ ATTRIB(XonoticKeyBinder, columnFunctionSize, float, 0)
+ ATTRIB(XonoticKeyBinder, columnKeysOrigin, float, 0)
+ ATTRIB(XonoticKeyBinder, columnKeysSize, float, 0)
+
+ ATTRIB(XonoticKeyBinder, previouslySelected, float, -1)
+ ATTRIB(XonoticKeyBinder, inMouseHandler, float, 0)
+ ATTRIB(XonoticKeyBinder, userbindEditButton, entity, NULL)
+ ATTRIB(XonoticKeyBinder, keyGrabButton, entity, NULL)
+ ATTRIB(XonoticKeyBinder, clearButton, entity, NULL)
+ ATTRIB(XonoticKeyBinder, userbindEditDialog, entity, NULL)
+ METHOD(XonoticKeyBinder, editUserbind, void(entity, string, string, string))
+ENDCLASS(XonoticKeyBinder)
+entity makeXonoticKeyBinder();
+void KeyBinder_Bind_Change(entity btn, entity me);
+void KeyBinder_Bind_Clear(entity btn, entity me);
+void KeyBinder_Bind_Edit(entity btn, entity me);
+#endif
+
+#ifdef IMPLEMENTATION
+
+const string KEY_NOT_BOUND_CMD = "// not bound";
+
+const float MAX_KEYS_PER_FUNCTION = 2;
+const float MAX_KEYBINDS = 256;
+string Xonotic_KeyBinds_Functions[MAX_KEYBINDS];
+string Xonotic_KeyBinds_Descriptions[MAX_KEYBINDS];
+float Xonotic_KeyBinds_Count = -1;
+
+void Xonotic_KeyBinds_Read()
+{
+ float fh;
+ string s;
+
+ Xonotic_KeyBinds_Count = 0;
+ fh = fopen(language_filename("keybinds.txt"), FILE_READ);
+ if(fh < 0)
+ return;
+ while((s = fgets(fh)))
+ {
+ if(tokenize_console(s) != 2)
+ continue;
+ Xonotic_KeyBinds_Functions[Xonotic_KeyBinds_Count] = strzone(argv(0));
+ Xonotic_KeyBinds_Descriptions[Xonotic_KeyBinds_Count] = strzone(argv(1));
+ ++Xonotic_KeyBinds_Count;
+ if(Xonotic_KeyBinds_Count >= MAX_KEYBINDS)
+ break;
+ }
+ fclose(fh);
+}
+
+entity makeXonoticKeyBinder()
+{
+ entity me;
+ me = spawnXonoticKeyBinder();
+ me.configureXonoticKeyBinder(me);
+ return me;
+}
+void replace_bind(string from, string to)
+{
+ float n, j, k;
+ n = tokenize(findkeysforcommand(from, 0)); // uses '...' strings
+ for(j = 0; j < n; ++j)
+ {
+ k = stof(argv(j));
+ if(k != -1)
+ localcmd("\nbind \"", keynumtostring(k), "\" \"", to, "\"\n");
+ }
+ if(n)
+ cvar_set("_hud_showbinds_reload", "1");
+}
+void XonoticKeyBinder_configureXonoticKeyBinder(entity me)
+{
+ me.configureXonoticListBox(me);
+ if(Xonotic_KeyBinds_Count < 0)
+ Xonotic_KeyBinds_Read();
+ me.nItems = Xonotic_KeyBinds_Count;
+ me.setSelected(me, 0);
+
+ // TEMP: Xonotic 0.1 to later
+ replace_bind("impulse 1", "weapon_group_1");
+ replace_bind("impulse 2", "weapon_group_2");
+ replace_bind("impulse 3", "weapon_group_3");
+ replace_bind("impulse 4", "weapon_group_4");
+ replace_bind("impulse 5", "weapon_group_5");
+ replace_bind("impulse 6", "weapon_group_6");
+ replace_bind("impulse 7", "weapon_group_7");
+ replace_bind("impulse 8", "weapon_group_8");
+ replace_bind("impulse 9", "weapon_group_9");
+ replace_bind("impulse 14", "weapon_group_0");
+}
+void XonoticKeyBinder_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ SUPER(XonoticKeyBinder).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+
+ me.realFontSize_y = me.fontSize / (absSize.y * me.itemHeight);
+ me.realFontSize_x = me.fontSize / (absSize.x * (1 - me.controlWidth));
+ me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
+
+ me.columnFunctionOrigin = 0;
+ me.columnKeysSize = me.realFontSize.x * 12;
+ me.columnFunctionSize = 1 - me.columnKeysSize - 2 * me.realFontSize.x;
+ me.columnKeysOrigin = me.columnFunctionOrigin + me.columnFunctionSize + me.realFontSize.x;
+
+ if(me.userbindEditButton)
+ me.userbindEditButton.disabled = (substring(Xonotic_KeyBinds_Descriptions[me.selectedItem], 0, 1) != "$");
+}
+void KeyBinder_Bind_Change(entity btn, entity me)
+{
+ string func;
+
+ func = Xonotic_KeyBinds_Functions[me.selectedItem];
+ if(func == "")
+ return;
+
+ me.keyGrabButton.forcePressed = 1;
+ me.clearButton.disabled = 1;
+ keyGrabber = me;
+}
+void XonoticKeyBinder_keyGrabbed(entity me, float key, float ascii)
+{
+ float n, j, k, nvalid;
+ string func;
+
+ me.keyGrabButton.forcePressed = 0;
+ me.clearButton.disabled = 0;
+
+ if(key == K_ESCAPE)
+ return;
+
+ // forbid these keys from being bound in the menu
+ if(key == K_CAPSLOCK || key == K_NUMLOCK)
+ {
+ KeyBinder_Bind_Change(me, me);
+ return;
+ }
+
+ func = Xonotic_KeyBinds_Functions[me.selectedItem];
+ if(func == "")
+ return;
+
+ n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings
+ nvalid = 0;
+ for(j = 0; j < n; ++j)
+ {
+ k = stof(argv(j));
+ if(k != -1)
+ ++nvalid;
+ }
+ if(nvalid >= MAX_KEYS_PER_FUNCTION)
+ {
+ for(j = 0; j < n; ++j)
+ {
+ k = stof(argv(j));
+ if(k != -1)
+ //localcmd("\nunbind \"", keynumtostring(k), "\"\n");
+ localcmd("\nbind \"", keynumtostring(k), "\" \"", KEY_NOT_BOUND_CMD, "\"\n");
+ }
+ }
++ m_play_click_sound(MENU_SOUND_SELECT);
+ localcmd("\nbind \"", keynumtostring(key), "\" \"", func, "\"\n");
+ localcmd("-zoom\n"); // to make sure we aren't in togglezoom'd state
+ cvar_set("_hud_showbinds_reload", "1");
+}
+void XonoticKeyBinder_editUserbind(entity me, string theName, string theCommandPress, string theCommandRelease)
+{
+ string func, descr;
+
+ if(!me.userbindEditDialog)
+ return;
+
+ func = Xonotic_KeyBinds_Functions[me.selectedItem];
+ if(func == "")
+ return;
+
+ descr = Xonotic_KeyBinds_Descriptions[me.selectedItem];
+ if(substring(descr, 0, 1) != "$")
+ return;
+ descr = substring(descr, 1, strlen(descr) - 1);
+
+ // Hooray! It IS a user bind!
+ cvar_set(strcat(descr, "_description"), theName);
+ cvar_set(strcat(descr, "_press"), theCommandPress);
+ cvar_set(strcat(descr, "_release"), theCommandRelease);
+}
+void KeyBinder_Bind_Edit(entity btn, entity me)
+{
+ string func, descr;
+
+ if(!me.userbindEditDialog)
+ return;
+
+ func = Xonotic_KeyBinds_Functions[me.selectedItem];
+ if(func == "")
+ return;
+
+ descr = Xonotic_KeyBinds_Descriptions[me.selectedItem];
+ if(substring(descr, 0, 1) != "$")
+ return;
+ descr = substring(descr, 1, strlen(descr) - 1);
+
+ // Hooray! It IS a user bind!
+ me.userbindEditDialog.loadUserBind(me.userbindEditDialog, cvar_string(strcat(descr, "_description")), cvar_string(strcat(descr, "_press")), cvar_string(strcat(descr, "_release")));
+
+ DialogOpenButton_Click(btn, me.userbindEditDialog);
+}
+void KeyBinder_Bind_Clear(entity btn, entity me)
+{
+ float n, j, k;
+ string func;
+
+ func = Xonotic_KeyBinds_Functions[me.selectedItem];
+ if(func == "")
+ return;
+
+ n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings
+ for(j = 0; j < n; ++j)
+ {
+ k = stof(argv(j));
+ if(k != -1)
+ //localcmd("\nunbind \"", keynumtostring(k), "\"\n");
+ localcmd("\nbind \"", keynumtostring(k), "\" \"", KEY_NOT_BOUND_CMD, "\"\n");
+ }
++ m_play_click_sound(MENU_SOUND_CLEAR);
+ localcmd("-zoom\n"); // to make sure we aren't in togglezoom'd state
+ cvar_set("_hud_showbinds_reload", "1");
+}
+void KeyBinder_Bind_Reset_All(entity btn, entity me)
+{
+ localcmd("unbindall\n");
++ localcmd("exec binds-xonotic.cfg\n");
+ localcmd("-zoom\n"); // to make sure we aren't in togglezoom'd state
+ cvar_set("_hud_showbinds_reload", "1");
+}
+void XonoticKeyBinder_doubleClickListBoxItem(entity me, float i, vector where)
+{
+ KeyBinder_Bind_Change(NULL, me);
+}
+void XonoticKeyBinder_setSelected(entity me, float i)
+{
+ // handling of "unselectable" items
+ i = floor(0.5 + bound(0, i, me.nItems - 1));
+ if(me.pressed == 0 || me.pressed == 1) // keyboard or scrolling - skip unselectable items
+ {
+ if(i > me.previouslySelected)
+ {
+ while((i < me.nItems - 1) && (Xonotic_KeyBinds_Functions[i] == ""))
+ ++i;
+ }
+ while((i > 0) && (Xonotic_KeyBinds_Functions[i] == ""))
+ --i;
+ while((i < me.nItems - 1) && (Xonotic_KeyBinds_Functions[i] == ""))
+ ++i;
+ }
+ if(me.pressed == 3) // released the mouse - fall back to last valid item
+ {
+ if(Xonotic_KeyBinds_Functions[i] == "")
+ i = me.previouslySelected;
+ }
+ if(Xonotic_KeyBinds_Functions[i] != "")
+ me.previouslySelected = i;
+ if(me.userbindEditButton)
+ me.userbindEditButton.disabled = (substring(Xonotic_KeyBinds_Descriptions[i], 0, 1) != "$");
+ SUPER(XonoticKeyBinder).setSelected(me, i);
+}
+float XonoticKeyBinder_keyDown(entity me, float key, float ascii, float shift)
+{
+ float r;
+ r = 1;
+ switch(key)
+ {
+ case K_ENTER:
+ case K_KP_ENTER:
+ case K_SPACE:
+ KeyBinder_Bind_Change(me, me);
+ break;
+ case K_DEL:
+ case K_KP_DEL:
+ case K_BACKSPACE:
+ KeyBinder_Bind_Clear(me, me);
+ break;
+ default:
+ r = SUPER(XonoticKeyBinder).keyDown(me, key, ascii, shift);
+ break;
+ }
+ return r;
+}
+void XonoticKeyBinder_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
+{
+ string s;
+ float j, k, n;
+ vector theColor;
+ float theAlpha;
+ string func, descr;
+ float extraMargin;
+
+ descr = Xonotic_KeyBinds_Descriptions[i];
+ func = Xonotic_KeyBinds_Functions[i];
+
+ if(func == "")
+ {
+ theAlpha = 1;
+ theColor = SKINCOLOR_KEYGRABBER_TITLES;
+ theAlpha = SKINALPHA_KEYGRABBER_TITLES;
+ extraMargin = 0;
+ }
+ else
+ {
+ if(isSelected)
+ {
+ if(keyGrabber == me)
+ draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_WAITING, SKINALPHA_LISTBOX_WAITING);
+ else
+ draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
+ }
+ theAlpha = SKINALPHA_KEYGRABBER_KEYS;
+ theColor = SKINCOLOR_KEYGRABBER_KEYS;
+ extraMargin = me.realFontSize.x * 0.5;
+ }
+
+ if(substring(descr, 0, 1) == "$")
+ {
+ s = substring(descr, 1, strlen(descr) - 1);
+ descr = cvar_string(strcat(s, "_description"));
+ if(descr == "")
+ descr = s;
+ if(cvar_string(strcat(s, "_press")) == "")
+ if(cvar_string(strcat(s, "_release")) == "")
+ theAlpha *= SKINALPHA_DISABLED;
+ }
+
+ s = draw_TextShortenToWidth(descr, me.columnFunctionSize, 0, me.realFontSize);
+ draw_Text(me.realUpperMargin * eY + extraMargin * eX, s, me.realFontSize, theColor, theAlpha, 0);
+ if(func != "")
+ {
+ n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings
+ s = "";
+ for(j = 0; j < n; ++j)
+ {
+ k = stof(argv(j));
+ if(k != -1)
+ {
+ if(s != "")
+ s = strcat(s, ", ");
+ s = strcat(s, keynumtostring(k));
+ }
+ }
+ s = draw_TextShortenToWidth(s, me.columnKeysSize, 0, me.realFontSize);
+ draw_CenterText(me.realUpperMargin * eY + (me.columnKeysOrigin + 0.5 * me.columnKeysSize) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+ }
+}
+#endif
--- /dev/null
- if(scan == K_ENTER || scan == K_KP_ENTER) {
+#ifdef INTERFACE
+CLASS(XonoticLanguageList) EXTENDS(XonoticListBox)
+ METHOD(XonoticLanguageList, configureXonoticLanguageList, void(entity))
+ ATTRIB(XonoticLanguageList, rowsPerItem, float, 1)
+ METHOD(XonoticLanguageList, drawListBoxItem, void(entity, float, vector, float))
+ METHOD(XonoticLanguageList, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(XonoticLanguageList, setSelected, void(entity, float))
+ METHOD(XonoticLanguageList, loadCvars, void(entity))
+ METHOD(XonoticLanguageList, saveCvars, void(entity))
+
+ ATTRIB(XonoticLanguageList, realFontSize, vector, '0 0 0')
+ ATTRIB(XonoticLanguageList, realUpperMargin, float, 0)
+ ATTRIB(XonoticLanguageList, columnNameOrigin, float, 0)
+ ATTRIB(XonoticLanguageList, columnNameSize, float, 0)
+ ATTRIB(XonoticLanguageList, columnPercentageOrigin, float, 0)
+ ATTRIB(XonoticLanguageList, columnPercentageSize, float, 0)
+
+ METHOD(XonoticLanguageList, doubleClickListBoxItem, void(entity, float, vector))
+ METHOD(XonoticLanguageList, keyDown, float(entity, float, float, float)) // enter handling
+
+ METHOD(XonoticLanguageList, destroy, void(entity))
+
+ ATTRIB(XonoticLanguageList, languagelist, float, -1)
+ METHOD(XonoticLanguageList, getLanguages, void(entity))
+ METHOD(XonoticLanguageList, setLanguage, void(entity))
+ METHOD(XonoticLanguageList, languageParameter, string(entity, float, float))
+
+ ATTRIB(XonoticLanguageList, name, string, "languageselector") // change this to make it noninteractive (for first run dialog)
+ENDCLASS(XonoticLanguageList)
+
+entity makeXonoticLanguageList();
+void SetLanguage_Click(entity btn, entity me);
+#endif
+
+#ifdef IMPLEMENTATION
+
+const float LANGPARM_ID = 0;
+const float LANGPARM_NAME = 1;
+const float LANGPARM_NAME_LOCALIZED = 2;
+const float LANGPARM_PERCENTAGE = 3;
+const float LANGPARM_COUNT = 4;
+
+entity makeXonoticLanguageList()
+{
+ entity me;
+ me = spawnXonoticLanguageList();
+ me.configureXonoticLanguageList(me);
+ return me;
+}
+
+void XonoticLanguageList_configureXonoticLanguageList(entity me)
+{
+ me.configureXonoticListBox(me);
+ me.getLanguages(me);
+ me.loadCvars(me);
+}
+
+void XonoticLanguageList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
+{
+ string s, p;
+ if(isSelected)
+ draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
+
+ s = me.languageParameter(me, i, LANGPARM_NAME_LOCALIZED);
+
+ vector save_fontscale = draw_fontscale;
+ float f = draw_CondensedFontFactor(s, false, me.realFontSize, 1);
+ draw_fontscale.x *= f;
+ vector fs = me.realFontSize;
+ fs.x *= f;
+ draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, fs, SKINCOLOR_TEXT, SKINALPHA_TEXT, 0);
+ draw_fontscale = save_fontscale;
+
+ p = me.languageParameter(me, i, LANGPARM_PERCENTAGE);
+ if(p != "")
+ {
+ vector save_fontscale = draw_fontscale;
+ float f = draw_CondensedFontFactor(p, false, me.realFontSize, 1);
+ draw_fontscale.x *= f;
+ vector fs = me.realFontSize;
+ fs.x *= f;
+ draw_Text(me.realUpperMargin * eY + (me.columnPercentageOrigin + (me.columnPercentageSize - draw_TextWidth(p, 0, fs))) * eX, p, fs, SKINCOLOR_TEXT, SKINALPHA_TEXT, 0);
+ draw_fontscale = save_fontscale;
+ }
+}
+
+void XonoticLanguageList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ SUPER(XonoticLanguageList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+ me.realFontSize_y = me.fontSize / (absSize.y * me.itemHeight);
+ me.realFontSize_x = me.fontSize / (absSize.x * (1 - me.controlWidth));
+ me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
+ me.columnPercentageSize = me.realFontSize.x * 3;
+ me.columnPercentageOrigin = 1 - me.columnPercentageSize;
+ me.columnNameOrigin = 0;
+ me.columnNameSize = me.columnPercentageOrigin;
+}
+
+void XonoticLanguageList_setSelected(entity me, float i)
+{
+ SUPER(XonoticLanguageList).setSelected(me, i);
+ me.saveCvars(me);
+}
+
+void XonoticLanguageList_loadCvars(entity me)
+{
+ string s;
+ float i, n;
+ s = cvar_string("_menu_prvm_language");
+ n = me.nItems;
+
+ // default to English
+ for(i = 0; i < n; ++i)
+ {
+ if(me.languageParameter(me, i, LANGPARM_ID) == "en")
+ {
+ me.selectedItem = i;
+ break;
+ }
+ }
+
+ // otherwise, find the language
+ for(i = 0; i < n; ++i)
+ {
+ if(me.languageParameter(me, i, LANGPARM_ID) == s)
+ {
+ me.selectedItem = i;
+ break;
+ }
+ }
+
+ // save it off (turning anything unknown into "en")
+ me.saveCvars(me);
+}
+
+void XonoticLanguageList_saveCvars(entity me)
+{
+ cvar_set("_menu_prvm_language", me.languageParameter(me, me.selectedItem, LANGPARM_ID));
+}
+
+void XonoticLanguageList_doubleClickListBoxItem(entity me, float i, vector where)
+{
++ m_play_click_sound(MENU_SOUND_EXECUTE);
+ me.setLanguage(me);
+}
+
+float XonoticLanguageList_keyDown(entity me, float scan, float ascii, float shift)
+{
++ if(scan == K_ENTER || scan == K_KP_ENTER)
++ {
++ m_play_click_sound(MENU_SOUND_EXECUTE);
+ me.setLanguage(me);
+ return 1;
+ }
+ else
+ return SUPER(XonoticLanguageList).keyDown(me, scan, ascii, shift);
+}
+
+void XonoticLanguageList_destroy(entity me)
+{
+ buf_del(me.languagelist);
+}
+
+void XonoticLanguageList_getLanguages(entity me)
+{
+ float buf, i, n, fh;
+ string s;
+
+ buf = buf_create();
+
+ fh = fopen("languages.txt", FILE_READ);
+ i = 0;
+ while((s = fgets(fh)))
+ {
+ n = tokenize_console(s);
+ if(n < 3)
+ continue;
+ bufstr_set(buf, i * LANGPARM_COUNT + LANGPARM_ID, argv(0));
+ bufstr_set(buf, i * LANGPARM_COUNT + LANGPARM_NAME, argv(1));
+ float k = strstrofs(argv(2), "(", 0);
+ if(k > 0)
+ if(substring(argv(2), strlen(argv(2)) - 1, 1) == ")")
+ {
+ string percent = substring(argv(2), k + 1, -2);
+ if(percent != "100%")
+ bufstr_set(buf, i * LANGPARM_COUNT + LANGPARM_PERCENTAGE, percent);
+ }
+ bufstr_set(buf, i * LANGPARM_COUNT + LANGPARM_NAME_LOCALIZED, (k < 0) ? argv(2) : substring(argv(2), 0, k - 1));
+ ++i;
+ }
+ fclose(fh);
+
+ me.languagelist = buf;
+ me.nItems = i;
+}
+
+void XonoticLanguageList_setLanguage(entity me)
+{
+ if(prvm_language != cvar_string("_menu_prvm_language"))
+ {
+ if(!(gamestatus & GAME_CONNECTED))
+ localcmd("\nprvm_language \"$_menu_prvm_language\"; menu_restart; menu_cmd languageselect\n");
+ else
+ DialogOpenButton_Click(me, main.languageWarningDialog);
+ }
+}
+
+string XonoticLanguageList_languageParameter(entity me, float i, float key)
+{
+ return bufstr_get(me.languagelist, i * LANGPARM_COUNT + key);
+}
+
+void SetLanguage_Click(entity btn, entity me)
+{
+ me.setLanguage(me);
+}
+
+#endif
--- /dev/null
+#ifdef INTERFACE
+CLASS(XonoticMapList) EXTENDS(XonoticListBox)
+ METHOD(XonoticMapList, configureXonoticMapList, void(entity))
+ ATTRIB(XonoticMapList, rowsPerItem, float, 4)
+ METHOD(XonoticMapList, draw, void(entity))
+ METHOD(XonoticMapList, drawListBoxItem, void(entity, float, vector, float))
+ METHOD(XonoticMapList, clickListBoxItem, void(entity, float, vector))
+ METHOD(XonoticMapList, doubleClickListBoxItem, void(entity, float, vector))
+ METHOD(XonoticMapList, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(XonoticMapList, refilter, void(entity))
+ METHOD(XonoticMapList, refilterCallback, void(entity, entity))
+ METHOD(XonoticMapList, keyDown, float(entity, float, float, float))
+
+ ATTRIB(XonoticMapList, realFontSize, vector, '0 0 0')
+ ATTRIB(XonoticMapList, columnPreviewOrigin, float, 0)
+ ATTRIB(XonoticMapList, columnPreviewSize, float, 0)
+ ATTRIB(XonoticMapList, columnNameOrigin, float, 0)
+ ATTRIB(XonoticMapList, columnNameSize, float, 0)
+ ATTRIB(XonoticMapList, checkMarkOrigin, vector, '0 0 0')
+ ATTRIB(XonoticMapList, checkMarkSize, vector, '0 0 0')
+ ATTRIB(XonoticMapList, realUpperMargin1, float, 0)
+ ATTRIB(XonoticMapList, realUpperMargin2, float, 0)
+
+ ATTRIB(XonoticMapList, lastGametype, float, 0)
+ ATTRIB(XonoticMapList, lastFeatures, float, 0)
+
+ ATTRIB(XonoticMapList, origin, vector, '0 0 0')
+ ATTRIB(XonoticMapList, itemAbsSize, vector, '0 0 0')
+
+ ATTRIB(XonoticMapList, g_maplistCache, string, string_null)
+ METHOD(XonoticMapList, g_maplistCacheToggle, void(entity, float))
+ METHOD(XonoticMapList, g_maplistCacheQuery, float(entity, float))
+
+ ATTRIB(XonoticMapList, startButton, entity, NULL)
+
+ METHOD(XonoticMapList, loadCvars, void(entity))
+
+ ATTRIB(XonoticMapList, typeToSearchString, string, string_null)
+ ATTRIB(XonoticMapList, typeToSearchTime, float, 0)
+
+ METHOD(XonoticMapList, destroy, void(entity))
+
+ ATTRIB(XonoticListBox, alphaBG, float, 0)
+ENDCLASS(XonoticMapList)
+entity makeXonoticMapList();
+void MapList_All(entity btn, entity me);
+void MapList_None(entity btn, entity me);
+void MapList_LoadMap(entity btn, entity me);
+#endif
+
+#ifdef IMPLEMENTATION
+void XonoticMapList_destroy(entity me)
+{
+ MapInfo_Shutdown();
+}
+
+entity makeXonoticMapList()
+{
+ entity me;
+ me = spawnXonoticMapList();
+ me.configureXonoticMapList(me);
+ return me;
+}
+
+void XonoticMapList_configureXonoticMapList(entity me)
+{
+ me.configureXonoticListBox(me);
+ me.refilter(me);
+}
+
+void XonoticMapList_loadCvars(entity me)
+{
+ me.refilter(me);
+}
+
+float XonoticMapList_g_maplistCacheQuery(entity me, float i)
+{
+ return stof(substring(me.g_maplistCache, i, 1));
+}
+void XonoticMapList_g_maplistCacheToggle(entity me, float i)
+{
+ string a, b, c, s, bspname;
+ float n;
+ s = me.g_maplistCache;
+ if (!s)
+ return;
+ b = substring(s, i, 1);
+ if(b == "0")
+ b = "1";
+ else if(b == "1")
+ b = "0";
+ else
+ return; // nothing happens
+ a = substring(s, 0, i);
+ c = substring(s, i+1, strlen(s) - (i+1));
+ strunzone(s);
+ me.g_maplistCache = strzone(strcat(a, b, c));
+ // TODO also update the actual cvar
+ if (!((bspname = MapInfo_BSPName_ByID(i))))
+ return;
+ if(b == "1")
+ cvar_set("g_maplist", strcat(bspname, " ", cvar_string("g_maplist")));
+ else
+ {
+ s = "";
+ n = tokenize_console(cvar_string("g_maplist"));
+ for(i = 0; i < n; ++i)
+ if(argv(i) != bspname)
+ s = strcat(s, " ", argv(i));
+ cvar_set("g_maplist", substring(s, 1, strlen(s) - 1));
+ }
+}
+
+void XonoticMapList_draw(entity me)
+{
+ if(me.startButton)
+ me.startButton.disabled = ((me.selectedItem < 0) || (me.selectedItem >= me.nItems));
+ SUPER(XonoticMapList).draw(me);
+}
+
+void XonoticMapList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ me.itemAbsSize = '0 0 0';
+ SUPER(XonoticMapList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+
+ me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
+ me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.realUpperMargin1 = 0.5 * (1 - 2.5 * me.realFontSize.y);
+ me.realUpperMargin2 = me.realUpperMargin1 + 1.5 * me.realFontSize.y;
+
+ me.columnPreviewOrigin = 0;
+ me.columnPreviewSize = me.itemAbsSize.y / me.itemAbsSize.x * 4 / 3;
+ me.columnNameOrigin = me.columnPreviewOrigin + me.columnPreviewSize + me.realFontSize.x;
+ me.columnNameSize = 1 - me.columnPreviewSize - 2 * me.realFontSize.x;
+
+ me.checkMarkSize = (eX * (me.itemAbsSize.y / me.itemAbsSize.x) + eY) * 0.5;
+ me.checkMarkOrigin = eY + eX * (me.columnPreviewOrigin + me.columnPreviewSize) - me.checkMarkSize;
+}
+
+void XonoticMapList_clickListBoxItem(entity me, float i, vector where)
+{
+ if(where.x <= me.columnPreviewOrigin + me.columnPreviewSize)
+ if(where.x >= 0)
++ {
++ m_play_click_sound(MENU_SOUND_SELECT);
+ me.g_maplistCacheToggle(me, i);
++ }
+}
+
+void XonoticMapList_doubleClickListBoxItem(entity me, float i, vector where)
+{
+ if(where.x >= me.columnNameOrigin)
+ if(where.x <= 1)
+ {
+ // pop up map info screen
++ m_play_click_sound(MENU_SOUND_OPEN);
+ main.mapInfoDialog.loadMapInfo(main.mapInfoDialog, i, me);
+ DialogOpenButton_Click_withCoords(NULL, main.mapInfoDialog, me.origin + eX * (me.columnNameOrigin * me.size.x) + eY * ((me.itemHeight * i - me.scrollPos) * me.size.y), eY * me.itemAbsSize.y + eX * (me.itemAbsSize.x * me.columnNameSize));
+ }
+}
+
+void XonoticMapList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
+{
+ // layout: Ping, Map name, Map name, NP, TP, MP
+ string s;
+ float theAlpha;
+ float included;
+
+ if(!MapInfo_Get_ByID(i))
+ return;
+
+ included = me.g_maplistCacheQuery(me, i);
+ if(included || isSelected)
+ theAlpha = SKINALPHA_MAPLIST_INCLUDEDFG;
+ else
+ theAlpha = SKINALPHA_MAPLIST_NOTINCLUDEDFG;
+
+ if(isSelected)
+ draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
+ else if(included)
+ draw_Fill('0 0 0', '1 1 0', SKINCOLOR_MAPLIST_INCLUDEDBG, SKINALPHA_MAPLIST_INCLUDEDBG);
+
+ if(draw_PictureSize(strcat("/maps/", MapInfo_Map_bspname)) == '0 0 0')
+ draw_Picture(me.columnPreviewOrigin * eX, "nopreview_map", me.columnPreviewSize * eX + eY, '1 1 1', theAlpha);
+ else
+ draw_Picture(me.columnPreviewOrigin * eX, strcat("/maps/", MapInfo_Map_bspname), me.columnPreviewSize * eX + eY, '1 1 1', theAlpha);
+
+ if(included)
+ draw_Picture(me.checkMarkOrigin, "checkmark", me.checkMarkSize, '1 1 1', 1);
+ s = draw_TextShortenToWidth(strdecolorize(MapInfo_Map_titlestring), me.columnNameSize, 0, me.realFontSize);
+ draw_Text(me.realUpperMargin1 * eY + (me.columnNameOrigin + 0.00 * (me.columnNameSize - draw_TextWidth(s, 0, me.realFontSize))) * eX, s, me.realFontSize, SKINCOLOR_MAPLIST_TITLE, theAlpha, 0);
+ s = draw_TextShortenToWidth(strdecolorize(MapInfo_Map_author), me.columnNameSize, 0, me.realFontSize);
+ draw_Text(me.realUpperMargin2 * eY + (me.columnNameOrigin + 1.00 * (me.columnNameSize - draw_TextWidth(s, 0, me.realFontSize))) * eX, s, me.realFontSize, SKINCOLOR_MAPLIST_AUTHOR, theAlpha, 0);
+
+ MapInfo_ClearTemps();
+}
+
+void XonoticMapList_refilter(entity me)
+{
+ float i, j, n;
+ string s;
+ float gt, f;
+ gt = MapInfo_CurrentGametype();
+ f = MapInfo_CurrentFeatures();
+ MapInfo_FilterGametype(gt, f, MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
+ me.nItems = MapInfo_count;
+ for(i = 0; i < MapInfo_count; ++i)
+ draw_PreloadPicture(strcat("/maps/", MapInfo_BSPName_ByID(i)));
+ if(me.g_maplistCache)
+ strunzone(me.g_maplistCache);
+ s = "0";
+ for(i = 1; i < MapInfo_count; i *= 2)
+ s = strcat(s, s);
+ n = tokenize_console(cvar_string("g_maplist"));
+ for(i = 0; i < n; ++i)
+ {
+ j = MapInfo_FindName(argv(i));
+ if(j >= 0)
+ s = strcat(
+ substring(s, 0, j),
+ "1",
+ substring(s, j+1, MapInfo_count - (j+1))
+ );
+ }
+ me.g_maplistCache = strzone(s);
+ if(gt != me.lastGametype || f != me.lastFeatures)
+ {
+ me.lastGametype = gt;
+ me.lastFeatures = f;
+ me.setSelected(me, 0);
+ }
+}
+
+void XonoticMapList_refilterCallback(entity me, entity cb)
+{
+ me.refilter(me);
+}
+
+void MapList_All(entity btn, entity me)
+{
+ float i;
+ string s;
+ MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, 0, MapInfo_ForbiddenFlags(), 0); // all
+ s = "";
+ for(i = 0; i < MapInfo_count; ++i)
+ s = strcat(s, " ", MapInfo_BSPName_ByID(i));
+ cvar_set("g_maplist", substring(s, 1, strlen(s) - 1));
+ me.refilter(me);
+}
+
+void MapList_None(entity btn, entity me)
+{
+ cvar_set("g_maplist", "");
+ me.refilter(me);
+}
+
+void MapList_LoadMap(entity btn, entity me)
+{
+ string m;
+ float i;
+
+ i = me.selectedItem;
+
+ if(btn.parent.instanceOfXonoticMapInfoDialog)
+ {
+ i = btn.parent.currentMapIndex;
+ Dialog_Close(btn, btn.parent);
+ }
+
+ if(i >= me.nItems || i < 0)
+ return;
+
+ m = MapInfo_BSPName_ByID(i);
+ if (!m)
+ {
+ print(_("Huh? Can't play this (m is NULL). Refiltering so this won't happen again.\n"));
+ me.refilter(me);
+ return;
+ }
+ if(MapInfo_CheckMap(m))
+ {
+ localcmd("\nmenu_loadmap_prepare\n");
+ if(cvar("menu_use_default_hostname"))
+ localcmd("hostname \"", sprintf(_("%s's Xonotic Server"), strdecolorize(cvar_string("_cl_name"))), "\"\n");
+ MapInfo_LoadMap(m, 1);
+ }
+ else
+ {
+ print(_("Huh? Can't play this (invalid game type). Refiltering so this won't happen again.\n"));
+ me.refilter(me);
+ return;
+ }
+}
+
+float XonoticMapList_keyDown(entity me, float scan, float ascii, float shift)
+{
+ string ch, save;
+ if(me.nItems <= 0)
+ return SUPER(XonoticMapList).keyDown(me, scan, ascii, shift);
+ if(scan == K_MOUSE2 || scan == K_SPACE || scan == K_ENTER || scan == K_KP_ENTER)
+ {
+ // pop up map info screen
++ m_play_click_sound(MENU_SOUND_OPEN);
+ main.mapInfoDialog.loadMapInfo(main.mapInfoDialog, me.selectedItem, me);
+ DialogOpenButton_Click_withCoords(NULL, main.mapInfoDialog, me.origin + eX * (me.columnNameOrigin * me.size.x) + eY * ((me.itemHeight * me.selectedItem - me.scrollPos) * me.size.y), eY * me.itemAbsSize.y + eX * (me.itemAbsSize.x * me.columnNameSize));
+ }
+ else if(scan == K_MOUSE3 || scan == K_INS || scan == K_KP_INS)
+ {
++ m_play_click_sound(MENU_SOUND_SELECT);
+ me.g_maplistCacheToggle(me, me.selectedItem);
+ }
+ else if(ascii == 43) // +
+ {
+ if (!me.g_maplistCacheQuery(me, me.selectedItem))
++ {
++ m_play_click_sound(MENU_SOUND_SELECT);
+ me.g_maplistCacheToggle(me, me.selectedItem);
++ }
+ }
+ else if(ascii == 45) // -
+ {
+ if(me.g_maplistCacheQuery(me, me.selectedItem))
++ {
++ m_play_click_sound(MENU_SOUND_SELECT);
+ me.g_maplistCacheToggle(me, me.selectedItem);
++ }
+ }
+ else if(scan == K_BACKSPACE)
+ {
+ if(time < me.typeToSearchTime)
+ {
+ save = substring(me.typeToSearchString, 0, strlen(me.typeToSearchString) - 1);
+ if(me.typeToSearchString)
+ strunzone(me.typeToSearchString);
+ me.typeToSearchString = strzone(save);
+ me.typeToSearchTime = time + 0.5;
+ if(strlen(me.typeToSearchString))
+ {
+ MapInfo_FindName(me.typeToSearchString);
+ if(MapInfo_FindName_firstResult >= 0)
+ me.setSelected(me, MapInfo_FindName_firstResult);
+ }
+ }
+ }
+ else if(ascii >= 32 && ascii != 127)
+ {
+ ch = chr(ascii);
+ if(time > me.typeToSearchTime)
+ save = ch;
+ else
+ save = strcat(me.typeToSearchString, ch);
+ if(me.typeToSearchString)
+ strunzone(me.typeToSearchString);
+ me.typeToSearchString = strzone(save);
+ me.typeToSearchTime = time + 0.5;
+ MapInfo_FindName(me.typeToSearchString);
+ if(MapInfo_FindName_firstResult >= 0)
+ me.setSelected(me, MapInfo_FindName_firstResult);
+ }
+ else
+ return SUPER(XonoticMapList).keyDown(me, scan, ascii, shift);
+ return 1;
+}
+
+#endif
--- /dev/null
+#ifdef INTERFACE
+CLASS(XonoticPlayerList) EXTENDS(XonoticListBox)
+ ATTRIB(XonoticPlayerList, rowsPerItem, float, 1)
+ METHOD(XonoticPlayerList, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(XonoticPlayerList, drawListBoxItem, void(entity, float, vector, float))
++ ATTRIB(XonoticPlayerList, allowFocusSound, float, 0)
+ ATTRIB(XonoticPlayerList, realFontSize, vector, '0 0 0')
+ ATTRIB(XonoticPlayerList, columnNameOrigin, float, 0)
+ ATTRIB(XonoticPlayerList, columnNameSize, float, 0)
+ ATTRIB(XonoticPlayerList, columnScoreOrigin, float, 0)
+ ATTRIB(XonoticPlayerList, columnScoreSize, float, 0)
+ ATTRIB(XonoticPlayerList, realUpperMargin, float, 0)
+ ATTRIB(XonoticPlayerList, origin, vector, '0 0 0')
+ ATTRIB(XonoticPlayerList, itemAbsSize, vector, '0 0 0')
+ METHOD(XonoticPlayerList, setPlayerList, void(entity, string))
+ METHOD(XonoticPlayerList, getPlayerList, string(entity, float, float))
+ ATTRIB(XonoticPlayerList, playerList, float, -1)
+ENDCLASS(XonoticPlayerList)
+entity makeXonoticPlayerList();
+#endif
+
+#ifdef IMPLEMENTATION
+
+const float PLAYERPARM_SCORE = 0;
+const float PLAYERPARM_PING = 1;
+const float PLAYERPARM_TEAM = 2;
+const float PLAYERPARM_NAME = 3;
+const float PLAYERPARM_COUNT = 4;
+
+entity makeXonoticPlayerList()
+{
+ entity me;
+ me = spawnXonoticPlayerList();
+ me.configureXonoticListBox(me);
+ return me;
+}
+
+void XonoticPlayerList_setPlayerList(entity me, string plist)
+{
+ int buf,i,n;
+ string s;
+
+ buf = buf_create();
+ me.nItems = tokenizebyseparator(plist, "\n");
+ for(i = 0; i < me.nItems; ++i)
+ {
+ bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_NAME, argv(i)); // -666 100 "^4Nex ^2Player"
+ }
+
+ for(i = 0; i < me.nItems; ++i)
+ {
+ s = bufstr_get(buf, i * PLAYERPARM_COUNT + PLAYERPARM_NAME);
+ n = tokenize_console(s);
+
+ if(n == 4)
+ {
+ bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_SCORE, argv(0)); // -666
+ bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_PING, argv(1)); // 100
+ bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_TEAM, argv(2)); // 0 for spec, else 1, 2, 3, 4
+ bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_NAME, argv(3)); // ^4Nex ^2Player
+ }
+ else
+ {
+ bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_SCORE, argv(0)); // -666
+ bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_PING, argv(1)); // 100
+ bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_TEAM, "-1");
+ bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_NAME, argv(2)); // ^4Nex ^2Player
+ }
+ }
+ me.playerList = buf;
+}
+
+string XonoticPlayerList_getPlayerList(entity me, float i, float key)
+{
+ return bufstr_get(me.playerList, i * PLAYERPARM_COUNT + key);
+}
+
+void XonoticPlayerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ me.itemAbsSize = '0 0 0';
+ SUPER(XonoticPlayerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+
+ me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
+ me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
+
+ // this list does 1 char left and right margin
+ me.columnScoreSize = 5 * me.realFontSize.x;
+ me.columnNameSize = 1 - 3 * me.realFontSize.x - me.columnScoreSize;
+
+ me.columnNameOrigin = me.realFontSize.x;
+ me.columnScoreOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize.x;
+}
+
+void XonoticPlayerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
+{
+ string s;
+ string score;
+ float t;
+ vector rgb;
+
+ t = stof(me.getPlayerList(me, i, PLAYERPARM_TEAM));
+ if(t == 1)
+ rgb = colormapPaletteColor(4, 0);
+ else if(t == 2)
+ rgb = colormapPaletteColor(13, 0);
+ else if(t == 3)
+ rgb = colormapPaletteColor(12, 0);
+ else if(t == 4)
+ rgb = colormapPaletteColor(9, 0);
+ else
+ rgb = SKINCOLOR_TEXT;
+
+ s = me.getPlayerList(me, i, PLAYERPARM_NAME);
+ score = me.getPlayerList(me, i, PLAYERPARM_SCORE);
+
+ if(substring(score, strlen(score) - 10, 10) == ":spectator")
+ {
+ score = _("spectator");
+ }
+ else
+ {
+ if((t = strstrofs(score, ":", 0)) >= 0)
+ score = substring(score, 0, t);
+ if((t = strstrofs(score, ",", 0)) >= 0)
+ score = substring(score, 0, t);
+
+ if(stof(score) == -666)
+ score = _("spectator");
+ }
+
+ s = draw_TextShortenToWidth(s, me.columnNameSize, 1, me.realFontSize);
+ draw_Text(me.realUpperMargin2 * eY + (me.columnNameOrigin + 0.00 * (me.columnNameSize - draw_TextWidth(s, 1, me.realFontSize))) * eX, s, me.realFontSize, '1 1 1', 1, 1);
+
+ score = draw_TextShortenToWidth(score, me.columnScoreSize, 0, me.realFontSize);
+ draw_Text(me.realUpperMargin2 * eY + (me.columnScoreOrigin + 1.00 * (me.columnScoreSize - draw_TextWidth(score, 1, me.realFontSize))) * eX, score, me.realFontSize, rgb, 1, 0);
+}
+
+#endif
--- /dev/null
+#ifdef INTERFACE
+CLASS(XonoticServerList) EXTENDS(XonoticListBox)
+ METHOD(XonoticServerList, configureXonoticServerList, void(entity))
+ ATTRIB(XonoticServerList, rowsPerItem, float, 1)
+ METHOD(XonoticServerList, draw, void(entity))
+ METHOD(XonoticServerList, drawListBoxItem, void(entity, float, vector, float))
+ METHOD(XonoticServerList, doubleClickListBoxItem, void(entity, float, vector))
+ METHOD(XonoticServerList, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(XonoticServerList, keyDown, float(entity, float, float, float))
+ METHOD(XonoticServerList, toggleFavorite, void(entity, string))
+
+ ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
+
+ ATTRIB(XonoticServerList, realFontSize, vector, '0 0 0')
+ ATTRIB(XonoticServerList, realUpperMargin, float, 0)
+ ATTRIB(XonoticServerList, columnIconsOrigin, float, 0)
+ ATTRIB(XonoticServerList, columnIconsSize, float, 0)
+ ATTRIB(XonoticServerList, columnPingOrigin, float, 0)
+ ATTRIB(XonoticServerList, columnPingSize, float, 0)
+ ATTRIB(XonoticServerList, columnNameOrigin, float, 0)
+ ATTRIB(XonoticServerList, columnNameSize, float, 0)
+ ATTRIB(XonoticServerList, columnMapOrigin, float, 0)
+ ATTRIB(XonoticServerList, columnMapSize, float, 0)
+ ATTRIB(XonoticServerList, columnTypeOrigin, float, 0)
+ ATTRIB(XonoticServerList, columnTypeSize, float, 0)
+ ATTRIB(XonoticServerList, columnPlayersOrigin, float, 0)
+ ATTRIB(XonoticServerList, columnPlayersSize, float, 0)
+
+ ATTRIB(XonoticServerList, selectedServer, string, string_null) // to restore selected server when needed
+ METHOD(XonoticServerList, setSelected, void(entity, float))
+ METHOD(XonoticServerList, setSortOrder, void(entity, float, float))
+ ATTRIB(XonoticServerList, filterShowEmpty, float, 1)
+ ATTRIB(XonoticServerList, filterShowFull, float, 1)
+ ATTRIB(XonoticServerList, filterString, string, string_null)
+ ATTRIB(XonoticServerList, controlledTextbox, entity, NULL)
+ ATTRIB(XonoticServerList, ipAddressBox, entity, NULL)
+ ATTRIB(XonoticServerList, favoriteButton, entity, NULL)
+ ATTRIB(XonoticServerList, nextRefreshTime, float, 0)
+ METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: REFRESHSERVERLIST_*
+ ATTRIB(XonoticServerList, needsRefresh, float, 1)
+ METHOD(XonoticServerList, focusEnter, void(entity))
+ METHOD(XonoticServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
+ ATTRIB(XonoticServerList, sortButton1, entity, NULL)
+ ATTRIB(XonoticServerList, sortButton2, entity, NULL)
+ ATTRIB(XonoticServerList, sortButton3, entity, NULL)
+ ATTRIB(XonoticServerList, sortButton4, entity, NULL)
+ ATTRIB(XonoticServerList, sortButton5, entity, NULL)
+ ATTRIB(XonoticServerList, connectButton, entity, NULL)
+ ATTRIB(XonoticServerList, infoButton, entity, NULL)
+ ATTRIB(XonoticServerList, currentSortOrder, float, 0)
+ ATTRIB(XonoticServerList, currentSortField, float, -1)
+
+ ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
+
+ ATTRIB(XonoticServerList, seenIPv4, float, 0)
+ ATTRIB(XonoticServerList, seenIPv6, float, 0)
+ ATTRIB(XonoticServerList, categoriesHeight, float, 1.25)
+
+ METHOD(XonoticServerList, getTotalHeight, float(entity))
+ METHOD(XonoticServerList, getItemAtPos, float(entity, float))
+ METHOD(XonoticServerList, getItemStart, float(entity, float))
+ METHOD(XonoticServerList, getItemHeight, float(entity, float))
+ENDCLASS(XonoticServerList)
+entity makeXonoticServerList();
+
+#ifndef IMPLEMENTATION
+float autocvar_menu_slist_categories;
+float autocvar_menu_slist_categories_onlyifmultiple;
+float autocvar_menu_slist_purethreshold;
+float autocvar_menu_slist_modimpurity;
+float autocvar_menu_slist_recommendations;
+float autocvar_menu_slist_recommendations_maxping;
+float autocvar_menu_slist_recommendations_minfreeslots;
+float autocvar_menu_slist_recommendations_minhumans;
+float autocvar_menu_slist_recommendations_purethreshold;
+
+// server cache fields
+#define SLIST_FIELDS \
+ SLIST_FIELD(CNAME, "cname") \
+ SLIST_FIELD(PING, "ping") \
+ SLIST_FIELD(GAME, "game") \
+ SLIST_FIELD(MOD, "mod") \
+ SLIST_FIELD(MAP, "map") \
+ SLIST_FIELD(NAME, "name") \
+ SLIST_FIELD(MAXPLAYERS, "maxplayers") \
+ SLIST_FIELD(NUMPLAYERS, "numplayers") \
+ SLIST_FIELD(NUMHUMANS, "numhumans") \
+ SLIST_FIELD(NUMBOTS, "numbots") \
+ SLIST_FIELD(PROTOCOL, "protocol") \
+ SLIST_FIELD(FREESLOTS, "freeslots") \
+ SLIST_FIELD(PLAYERS, "players") \
+ SLIST_FIELD(QCSTATUS, "qcstatus") \
+ SLIST_FIELD(CATEGORY, "category") \
+ SLIST_FIELD(ISFAVORITE, "isfavorite")
+
+#define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
+SLIST_FIELDS
+#undef SLIST_FIELD
+
+const float REFRESHSERVERLIST_RESORT = 0; // sort the server list again to update for changes to e.g. favorite status, categories
+const float REFRESHSERVERLIST_REFILTER = 1; // ..., also update filter and sort criteria
+const float REFRESHSERVERLIST_ASK = 2; // ..., also suggest querying servers now
+const float REFRESHSERVERLIST_RESET = 3; // ..., also clear the list first
+
+// function declarations
+float IsServerInList(string list, string srv);
+#define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
+#define IsPromoted(srv) IsServerInList(_Nex_ExtResponseSystem_PromotedServers, srv)
+#define IsRecommended(srv) IsServerInList(_Nex_ExtResponseSystem_RecommendedServers, srv)
+
+entity RetrieveCategoryEnt(float catnum);
+
+float CheckCategoryOverride(float cat);
+float CheckCategoryForEntry(float entry);
+float m_gethostcachecategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
+
+void RegisterSLCategories();
+
+void ServerList_Connect_Click(entity btn, entity me);
+void ServerList_Categories_Click(entity box, entity me);
+void ServerList_ShowEmpty_Click(entity box, entity me);
+void ServerList_ShowFull_Click(entity box, entity me);
+void ServerList_Filter_Change(entity box, entity me);
+void ServerList_Favorite_Click(entity btn, entity me);
+void ServerList_Info_Click(entity btn, entity me);
+void ServerList_Update_favoriteButton(entity btn, entity me);
+
+// fields for category entities
+const float MAX_CATEGORIES = 9;
+const float CATEGORY_FIRST = 1;
+entity categories[MAX_CATEGORIES];
+float category_ent_count;
+.string cat_name;
+.string cat_string;
+.string cat_enoverride_string;
+.string cat_dioverride_string;
+.float cat_enoverride;
+.float cat_dioverride;
+
+// fields for drawing categories
+float category_name[MAX_CATEGORIES];
+float category_item[MAX_CATEGORIES];
+float category_draw_count;
+
+#define SLIST_CATEGORIES \
+ SLIST_CATEGORY(CAT_FAVORITED, "", "", ZCTX(_("SLCAT^Favorites"))) \
+ SLIST_CATEGORY(CAT_RECOMMENDED, "", "", ZCTX(_("SLCAT^Recommended"))) \
+ SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Normal Servers"))) \
+ SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Servers"))) \
+ SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Competitive Mode"))) \
+ SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Modified Servers"))) \
+ SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Overkill Mode"))) \
+ SLIST_CATEGORY(CAT_INSTAGIB, "", "CAT_SERVERS", ZCTX(_("SLCAT^InstaGib Mode"))) \
+ SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", ZCTX(_("SLCAT^Defrag Mode")))
+
+#define SLIST_CATEGORY_AUTOCVAR(name) autocvar_menu_slist_categories_##name##_override
+#define SLIST_CATEGORY(name,enoverride,dioverride,str) \
+ float name; \
+ string SLIST_CATEGORY_AUTOCVAR(name) = enoverride;
+SLIST_CATEGORIES
+#undef SLIST_CATEGORY
+
+#endif
+#endif
+#ifdef IMPLEMENTATION
+
+void RegisterSLCategories()
+{
+ entity cat;
+ #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
+ SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
+ CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
+ cat = spawn(); \
+ categories[name - 1] = cat; \
+ cat.classname = "slist_category"; \
+ cat.cat_name = strzone(#name); \
+ cat.cat_enoverride_string = strzone(SLIST_CATEGORY_AUTOCVAR(name)); \
+ cat.cat_dioverride_string = strzone(dioverride); \
+ cat.cat_string = strzone(str);
+ SLIST_CATEGORIES
+ #undef SLIST_CATEGORY
+
+ float i, x, catnum;
+ string s;
+
+ #define PROCESS_OVERRIDE(override_string,override_field) \
+ for(i = 0; i < category_ent_count; ++i) \
+ { \
+ s = categories[i].override_string; \
+ if((s != "") && (s != categories[i].cat_name)) \
+ { \
+ catnum = 0; \
+ for(x = 0; x < category_ent_count; ++x) \
+ { if(categories[x].cat_name == s) { \
+ catnum = (x+1); \
+ break; \
+ } } \
+ if(catnum) \
+ { \
+ strunzone(categories[i].override_string); \
+ categories[i].override_field = catnum; \
+ continue; \
+ } \
+ else \
+ { \
+ printf( \
+ "RegisterSLCategories(): Improper override '%s' for category '%s'!\n", \
+ s, \
+ categories[i].cat_name \
+ ); \
+ } \
+ } \
+ strunzone(categories[i].override_string); \
+ categories[i].override_field = 0; \
+ }
+ PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
+ PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
+ #undef PROCESS_OVERRIDE
+}
+
+// Supporting Functions
+entity RetrieveCategoryEnt(float catnum)
+{
+ if((catnum > 0) && (catnum <= category_ent_count))
+ {
+ return categories[catnum - 1];
+ }
+ else
+ {
+ error(sprintf("RetrieveCategoryEnt(%d): Improper category number!\n", catnum));
+ return world;
+ }
+}
+
+float IsServerInList(string list, string srv)
+{
+ string p;
+ float i, n;
+ if(srv == "")
+ return false;
+ srv = netaddress_resolve(srv, 26000);
+ if(srv == "")
+ return false;
+ p = crypto_getidfp(srv);
+ n = tokenize_console(list);
+ for(i = 0; i < n; ++i)
+ {
+ if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
+ {
+ if(p)
+ if(argv(i) == p)
+ return true;
+ }
+ else
+ {
+ if(srv == netaddress_resolve(argv(i), 26000))
+ return true;
+ }
+ }
+ return false;
+}
+
+float CheckCategoryOverride(float cat)
+{
+ entity catent = RetrieveCategoryEnt(cat);
+ if(catent)
+ {
+ float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride);
+ if(override) { return override; }
+ else { return cat; }
+ }
+ else
+ {
+ error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
+ return cat;
+ }
+}
+
+float CheckCategoryForEntry(float entry)
+{
+ string s, k, v, modtype = "";
+ float j, m, impure = 0, freeslots = 0, sflags = 0;
+ s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
+ m = tokenizebyseparator(s, ":");
+
+ for(j = 2; j < m; ++j)
+ {
+ if(argv(j) == "") { break; }
+ k = substring(argv(j), 0, 1);
+ v = substring(argv(j), 1, -1);
+ switch(k)
+ {
+ case "P": { impure = stof(v); break; }
+ case "S": { freeslots = stof(v); break; }
+ case "F": { sflags = stof(v); break; }
+ case "M": { modtype = strtolower(v); break; }
+ }
+ }
+
+ if(modtype != "xonotic") { impure += autocvar_menu_slist_modimpurity; }
+
+ // check if this server is favorited
+ if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
+
+ // now check if it's recommended
+ if(autocvar_menu_slist_recommendations)
+ {
+ string cname = gethostcachestring(SLIST_FIELD_CNAME, entry);
+
+ if(IsPromoted(cname)) { return CAT_RECOMMENDED; }
+ else
+ {
+ float recommended = 0;
+ if(autocvar_menu_slist_recommendations & 1)
+ {
+ if(IsRecommended(cname)) { ++recommended; }
+ else { --recommended; }
+ }
+ if(autocvar_menu_slist_recommendations & 2)
+ {
+ if(
+ ///// check for minimum free slots
+ (freeslots >= autocvar_menu_slist_recommendations_minfreeslots)
+
+ && // check for purity requirement
+ (
+ (autocvar_menu_slist_recommendations_purethreshold < 0)
+ ||
+ (impure <= autocvar_menu_slist_recommendations_purethreshold)
+ )
+
+ && // check for minimum amount of humans
+ (
+ gethostcachenumber(SLIST_FIELD_NUMHUMANS, entry)
+ >=
+ autocvar_menu_slist_recommendations_minhumans
+ )
+
+ && // check for maximum latency
+ (
+ gethostcachenumber(SLIST_FIELD_PING, entry)
+ <=
+ autocvar_menu_slist_recommendations_maxping
+ )
+ )
+ { ++recommended; }
+ else
+ { --recommended; }
+ }
+ if(recommended > 0) { return CAT_RECOMMENDED; }
+ }
+ }
+
+ // if not favorited or recommended, check modname
+ if(modtype != "xonotic")
+ {
+ switch(modtype)
+ {
+ // old servers which don't report their mod name are considered modified now
+ case "": { return CAT_MODIFIED; }
+
+ case "xpm": { return CAT_XPM; }
+ case "minstagib":
+ case "instagib": { return CAT_INSTAGIB; }
+ case "overkill": { return CAT_OVERKILL; }
+ //case "nix": { return CAT_NIX; }
+ //case "newtoys": { return CAT_NEWTOYS; }
+
+ // "cts" is allowed as compat, xdf is replacement
+ case "cts":
+ case "xdf": { return CAT_DEFRAG; }
+
+ default: { dprintf("Found strange mod type: %s\n", modtype); return CAT_MODIFIED; }
+ }
+ }
+
+ // must be normal or impure server
+ return ((impure > autocvar_menu_slist_purethreshold) ? CAT_MODIFIED : CAT_NORMAL);
+}
+
+void XonoticServerList_toggleFavorite(entity me, string srv)
+{
+ string s, s0, s1, s2, srv_resolved, p;
+ float i, n, f;
+ srv_resolved = netaddress_resolve(srv, 26000);
+ p = crypto_getidfp(srv_resolved);
+ s = cvar_string("net_slist_favorites");
+ n = tokenize_console(s);
+ f = 0;
+ for(i = 0; i < n; ++i)
+ {
+ if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
+ {
+ if(p)
+ if(argv(i) != p)
+ continue;
+ }
+ else
+ {
+ if(srv_resolved != netaddress_resolve(argv(i), 26000))
+ continue;
+ }
+ s0 = s1 = s2 = "";
+ if(i > 0)
+ s0 = substring(s, 0, argv_end_index(i - 1));
+ if(i < n-1)
+ s2 = substring(s, argv_start_index(i + 1), -1);
+ if(s0 != "" && s2 != "")
+ s1 = " ";
+ cvar_set("net_slist_favorites", strcat(s0, s1, s2));
+ s = cvar_string("net_slist_favorites");
+ n = tokenize_console(s);
+ f = 1;
+ --i;
+ }
+
+ if(!f)
+ {
+ s1 = "";
+ if(s != "")
+ s1 = " ";
+ if(p)
+ cvar_set("net_slist_favorites", strcat(s, s1, p));
+ else
+ cvar_set("net_slist_favorites", strcat(s, s1, srv));
+ }
+
+ me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
+}
+
+void ServerList_Update_favoriteButton(entity btn, entity me)
+{
+ me.favoriteButton.setText(me.favoriteButton,
+ (IsFavorite(me.ipAddressBox.text) ?
+ _("Remove") : _("Favorite")
+ )
+ );
+}
+
+entity makeXonoticServerList()
+{
+ entity me;
+ me = spawnXonoticServerList();
+ me.configureXonoticServerList(me);
+ return me;
+}
+void XonoticServerList_configureXonoticServerList(entity me)
+{
+ me.configureXonoticListBox(me);
+
+ // update field ID's
+ #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
+ SLIST_FIELDS
+ #undef SLIST_FIELD
+
+ // clear list
+ me.nItems = 0;
+}
+void XonoticServerList_setSelected(entity me, float i)
+{
+ float save;
+ save = me.selectedItem;
+ SUPER(XonoticServerList).setSelected(me, i);
+ /*
+ if(me.selectedItem == save)
+ return;
+ */
+ if(me.nItems == 0)
+ return;
+ if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != me.nItems)
+ return; // sorry, it would be wrong
+
+ if(me.selectedServer)
+ strunzone(me.selectedServer);
+ me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
+
+ me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
+ me.ipAddressBox.cursorPos = strlen(me.selectedServer);
+ me.ipAddressBoxFocused = -1;
+}
+void XonoticServerList_refreshServerList(entity me, float mode)
+{
+ //print("refresh of type ", ftos(mode), "\n");
+
+ if(mode >= REFRESHSERVERLIST_REFILTER)
+ {
+ float m, i, n;
+ float listflags = 0;
+ string s, typestr, modstr;
+
+ s = me.filterString;
+
+ m = strstrofs(s, ":", 0);
+ if(m >= 0)
+ {
+ typestr = substring(s, 0, m);
+ s = substring(s, m + 1, strlen(s) - m - 1);
+ while(substring(s, 0, 1) == " ")
+ s = substring(s, 1, strlen(s) - 1);
+ }
+ else
+ typestr = "";
+
+ modstr = cvar_string("menu_slist_modfilter");
+
+ m = SLIST_MASK_AND - 1;
+ resethostcachemasks();
+
+ // ping: reject negative ping (no idea why this happens in the first place, engine bug)
+ sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
+
+ // show full button
+ if(!me.filterShowFull)
+ {
+ sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
+ sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
+ }
+
+ // show empty button
+ if(!me.filterShowEmpty)
+ sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
+
+ // gametype filtering
+ if(typestr != "")
+ sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
+
+ // mod filtering
+ if(modstr != "")
+ {
+ if(substring(modstr, 0, 1) == "!")
+ sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
+ else
+ sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
+ }
+
+ // server banning
+ n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
+ for(i = 0; i < n; ++i)
+ if(argv(i) != "")
+ sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
+
+ m = SLIST_MASK_OR - 1;
+ if(s != "")
+ {
+ sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
+ sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
+ sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
+ sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
+ }
+
+ // sorting flags
+ //listflags |= SLSF_FAVORITES;
+ listflags |= SLSF_CATEGORIES;
+ if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
+ sethostcachesort(me.currentSortField, listflags);
+ }
+
+ resorthostcache();
+ if(mode >= REFRESHSERVERLIST_ASK)
+ refreshhostcache(mode >= REFRESHSERVERLIST_RESET);
+}
+void XonoticServerList_focusEnter(entity me)
+{
++ SUPER(XonoticServerList).focusEnter(me);
+ if(time < me.nextRefreshTime)
+ {
+ //print("sorry, no refresh yet\n");
+ return;
+ }
+ me.nextRefreshTime = time + 10;
+ me.refreshServerList(me, REFRESHSERVERLIST_ASK);
+}
+
+void XonoticServerList_draw(entity me)
+{
+ float i, found, owned;
+
+ if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
+ {
+ if(!me.needsRefresh)
+ me.needsRefresh = 2;
+ _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
+ }
+
+ if(_Nex_ExtResponseSystem_PromotedServersNeedsRefresh)
+ {
+ if(!me.needsRefresh)
+ me.needsRefresh = 3;
+ _Nex_ExtResponseSystem_PromotedServersNeedsRefresh = 0;
+ }
+
+ if(_Nex_ExtResponseSystem_RecommendedServersNeedsRefresh)
+ {
+ if(!me.needsRefresh)
+ me.needsRefresh = 3;
+ _Nex_ExtResponseSystem_RecommendedServersNeedsRefresh = 0;
+ }
+
+ if(me.currentSortField == -1)
+ {
+ me.setSortOrder(me, SLIST_FIELD_PING, +1);
+ me.refreshServerList(me, REFRESHSERVERLIST_RESET);
+ }
+ else if(me.needsRefresh == 1)
+ {
+ me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
+ }
+ else if(me.needsRefresh == 2)
+ {
+ me.needsRefresh = 0;
+ me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
+ }
+ else if(me.needsRefresh == 3)
+ {
+ me.needsRefresh = 0;
+ me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
+ }
+
+ owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
+
+ for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
+ category_draw_count = 0;
+
+ if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
+ {
+ float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
+ me.nItems = itemcount;
+
+ //float visible = floor(me.scrollPos / me.itemHeight);
+ // ^ unfortunately no such optimization can be made-- we must process through the
+ // entire list, otherwise there is no way to know which item is first in its category.
+
+ // binary search method suggested by div
+ float x;
+ float begin = 0;
+ for(x = 1; x <= category_ent_count; ++x) {
+ float first = begin;
+ float last = (itemcount - 1);
+ if (first > last) {
+ // List is empty.
+ break;
+ }
+ float catf = gethostcachenumber(SLIST_FIELD_CATEGORY, first);
+ float catl = gethostcachenumber(SLIST_FIELD_CATEGORY, last);
+ if (catf > x) {
+ // The first one is already > x.
+ // Therefore, category x does not exist.
+ // Higher numbered categories do exist though.
+ } else if (catl < x) {
+ // The last one is < x.
+ // Thus this category - and any following -
+ // don't exist.
+ break;
+ } else if (catf == x) {
+ // Starts at first. This breaks the loop
+ // invariant in the binary search and thus has
+ // to be handled separately.
+ if(gethostcachenumber(SLIST_FIELD_CATEGORY, first) != x)
+ error("Category mismatch I");
+ if(first > 0)
+ if(gethostcachenumber(SLIST_FIELD_CATEGORY, first - 1) == x)
+ error("Category mismatch II");
+ category_name[category_draw_count] = x;
+ category_item[category_draw_count] = first;
+ ++category_draw_count;
+ begin = first + 1;
+ } else {
+ // At this point, catf <= x < catl, thus
+ // catf < catl, thus first < last.
+ // INVARIANTS:
+ // last - first >= 1
+ // catf == gethostcachenumber(SLIST_FIELD_CATEGORY(first)
+ // catl == gethostcachenumber(SLIST_FIELD_CATEGORY(last)
+ // catf < x
+ // catl >= x
+ while (last - first > 1) {
+ float middle = floor((first + last) / 2);
+ // By loop condition, middle != first && middle != last.
+ float cat = gethostcachenumber(SLIST_FIELD_CATEGORY, middle);
+ if (cat >= x) {
+ last = middle;
+ catl = cat;
+ } else {
+ first = middle;
+ catf = cat;
+ }
+ }
+ if (catl == x) {
+ if(gethostcachenumber(SLIST_FIELD_CATEGORY, last) != x)
+ error("Category mismatch III");
+ if(last > 0)
+ if(gethostcachenumber(SLIST_FIELD_CATEGORY, last - 1) == x)
+ error("Category mismatch IV");
+ category_name[category_draw_count] = x;
+ category_item[category_draw_count] = last;
+ ++category_draw_count;
+ begin = last + 1; // already scanned through these, skip 'em
+ }
+ else
+ begin = last; // already scanned through these, skip 'em
+ }
+ }
+ if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
+ {
+ category_name[0] = -1;
+ category_item[0] = -1;
+ category_draw_count = 0;
+ me.nItems = itemcount;
+ }
+ }
+ else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
+
+ me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
+ me.infoButton.disabled = ((me.nItems == 0) || !owned);
+ me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
+
+ found = 0;
+ if(me.selectedServer)
+ {
+ for(i = 0; i < me.nItems; ++i)
+ {
+ if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
+ {
+ me.selectedItem = i;
+ found = 1;
+ break;
+ }
+ }
+ }
+ if(!found)
+ {
+ if(me.nItems > 0)
+ {
+ if(me.selectedItem >= me.nItems)
+ me.selectedItem = me.nItems - 1;
+ if(me.selectedServer)
+ strunzone(me.selectedServer);
+ me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
+ }
+ }
+
+ if(owned)
+ {
+ if(me.selectedServer != me.ipAddressBox.text)
+ {
+ me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
+ me.ipAddressBox.cursorPos = strlen(me.selectedServer);
+ me.ipAddressBoxFocused = -1;
+ }
+ }
+
+ if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
+ {
+ if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
+ ServerList_Update_favoriteButton(NULL, me);
+ me.ipAddressBoxFocused = me.ipAddressBox.focused;
+ }
+
+ SUPER(XonoticServerList).draw(me);
+}
+void ServerList_PingSort_Click(entity btn, entity me)
+{
+ me.setSortOrder(me, SLIST_FIELD_PING, +1);
+}
+void ServerList_NameSort_Click(entity btn, entity me)
+{
+ me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
+}
+void ServerList_MapSort_Click(entity btn, entity me)
+{
+ me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
+}
+void ServerList_PlayerSort_Click(entity btn, entity me)
+{
+ me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
+}
+void ServerList_TypeSort_Click(entity btn, entity me)
+{
+ string s, t;
+ float i, m;
+ s = me.filterString;
+ m = strstrofs(s, ":", 0);
+ if(m >= 0)
+ {
+ s = substring(s, 0, m);
+ while(substring(s, m+1, 1) == " ") // skip spaces
+ ++m;
+ }
+ else
+ s = "";
+
+ for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
+ {
+ t = MapInfo_Type_ToString(i);
+ if(i > 1)
+ if(t == "") // it repeats (default case)
+ {
+ // no type was found
+ // choose the first one
+ s = MapInfo_Type_ToString(1);
+ break;
+ }
+ if(s == t)
+ {
+ // the type was found
+ // choose the next one
+ s = MapInfo_Type_ToString(i * 2);
+ if(s == "")
+ s = MapInfo_Type_ToString(1);
+ break;
+ }
+ }
+
+ if(s != "")
+ s = strcat(s, ":");
+ s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
+
+ me.controlledTextbox.setText(me.controlledTextbox, s);
+ me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
+ me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
+ //ServerList_Filter_Change(me.controlledTextbox, me);
+}
+void ServerList_Filter_Change(entity box, entity me)
+{
+ if(me.filterString)
+ strunzone(me.filterString);
+ if(box.text != "")
+ me.filterString = strzone(box.text);
+ else
+ me.filterString = string_null;
+ me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
+
+ me.ipAddressBox.setText(me.ipAddressBox, "");
+ me.ipAddressBox.cursorPos = 0;
+ me.ipAddressBoxFocused = -1;
+}
+void ServerList_Categories_Click(entity box, entity me)
+{
+ box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
+ me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
+
+ me.ipAddressBox.setText(me.ipAddressBox, "");
+ me.ipAddressBox.cursorPos = 0;
+ me.ipAddressBoxFocused = -1;
+}
+void ServerList_ShowEmpty_Click(entity box, entity me)
+{
+ box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
+ me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
+
+ me.ipAddressBox.setText(me.ipAddressBox, "");
+ me.ipAddressBox.cursorPos = 0;
+ me.ipAddressBoxFocused = -1;
+}
+void ServerList_ShowFull_Click(entity box, entity me)
+{
+ box.setChecked(box, me.filterShowFull = !me.filterShowFull);
+ me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
+
+ me.ipAddressBox.setText(me.ipAddressBox, "");
+ me.ipAddressBox.cursorPos = 0;
+ me.ipAddressBoxFocused = -1;
+}
+void XonoticServerList_setSortOrder(entity me, float fld, float direction)
+{
+ if(me.currentSortField == fld)
+ direction = -me.currentSortOrder;
+ me.currentSortOrder = direction;
+ me.currentSortField = fld;
+ me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
+ me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
+ me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
+ me.sortButton4.forcePressed = 0;
+ me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
+ me.selectedItem = 0;
+ if(me.selectedServer)
+ strunzone(me.selectedServer);
+ me.selectedServer = string_null;
+ me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
+}
+void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
+{
+ vector originInLBSpace, sizeInLBSpace;
+ originInLBSpace = eY * (-me.itemHeight);
+ sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
+
+ vector originInDialogSpace, sizeInDialogSpace;
+ originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
+ sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
+
+ btn.Container_origin_x = originInDialogSpace.x + sizeInDialogSpace.x * theOrigin;
+ btn.Container_size_x = sizeInDialogSpace.x * theSize;
+ btn.setText(btn, theTitle);
+ btn.onClick = theFunc;
+ btn.onClickEntity = me;
+ btn.resized = 1;
+}
+void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+
+ me.realFontSize_y = me.fontSize / (absSize.y * me.itemHeight);
+ me.realFontSize_x = me.fontSize / (absSize.x * (1 - me.controlWidth));
+ me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
+
+ me.columnIconsOrigin = 0;
+ me.columnIconsSize = me.realFontSize.x * 4 * me.iconsSizeFactor;
+ me.columnPingSize = me.realFontSize.x * 3;
+ me.columnMapSize = me.realFontSize.x * 10;
+ me.columnTypeSize = me.realFontSize.x * 4;
+ me.columnPlayersSize = me.realFontSize.x * 5;
+ me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize.x;
+ me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize.x;
+ me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize.x;
+ me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize.x;
+ me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize.x;
+ me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize.x;
+
+ me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
+ me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
+ me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
+ me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
+ me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
+
+ float f;
+ f = me.currentSortField;
+ if(f >= 0)
+ {
+ me.currentSortField = -1;
+ me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
+ }
+}
+void ServerList_Connect_Click(entity btn, entity me)
+{
+ localcmd(sprintf("connect %s\n",
+ ((me.ipAddressBox.text != "") ?
+ me.ipAddressBox.text : me.selectedServer
+ )
+ ));
+}
+void ServerList_Favorite_Click(entity btn, entity me)
+{
+ string ipstr;
+ ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
+ if(ipstr != "")
+ {
++ m_play_click_sound(MENU_SOUND_SELECT);
+ me.toggleFavorite(me, me.ipAddressBox.text);
+ me.ipAddressBoxFocused = -1;
+ }
+}
+void ServerList_Info_Click(entity btn, entity me)
+{
+ if (me.nItems != 0)
+ main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
+
+ vector org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
+ vector sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
+ DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
+}
+void XonoticServerList_doubleClickListBoxItem(entity me, float i, vector where)
+{
+ ServerList_Connect_Click(NULL, me);
+}
+void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
+{
+ // layout: Ping, Server name, Map name, NP, TP, MP
+ float p, q;
+ float isv4, isv6;
+ vector theColor;
+ float theAlpha;
+ float m, pure, freeslots, j, sflags;
+ string s, typestr, versionstr, k, v, modname;
+
+ //printf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems);
+
+ vector oldscale = draw_scale;
+ vector oldshift = draw_shift;
+#define SET_YRANGE(start,end) \
+ draw_scale = boxToGlobalSize(eX * 1 + eY * (end - start), oldscale); \
+ draw_shift = boxToGlobal(eY * start, oldshift, oldscale);
+
+ for (j = 0; j < category_draw_count; ++j) {
+ // Matches exactly the headings with increased height.
+ if (i == category_item[j])
+ break;
+ }
+
+ if (j < category_draw_count)
+ {
+ entity catent = RetrieveCategoryEnt(category_name[j]);
+ if(catent)
+ {
+ SET_YRANGE(
+ (me.categoriesHeight - 1) / (me.categoriesHeight + 1),
+ me.categoriesHeight / (me.categoriesHeight + 1)
+ );
+ draw_Text(
+ eY * me.realUpperMargin
+ +
+#if 0
+ eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
+ catent.cat_string,
+#else
+ eX * (me.columnNameOrigin),
+ strcat(catent.cat_string, ":"),
+#endif
+ me.realFontSize,
+ SKINCOLOR_SERVERLIST_CATEGORY,
+ SKINALPHA_SERVERLIST_CATEGORY,
+ 0
+ );
+ SET_YRANGE(me.categoriesHeight / (me.categoriesHeight + 1), 1);
+ }
+ }
+
+ if(isSelected)
+ draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
+
+ s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
+ m = tokenizebyseparator(s, ":");
+ typestr = "";
+ if(m >= 2)
+ {
+ typestr = argv(0);
+ versionstr = argv(1);
+ }
+ freeslots = -1;
+ sflags = -1;
+ modname = "";
+ pure = 0;
+ for(j = 2; j < m; ++j)
+ {
+ if(argv(j) == "")
+ break;
+ k = substring(argv(j), 0, 1);
+ v = substring(argv(j), 1, -1);
+ if(k == "P")
+ pure = stof(v);
+ else if(k == "S")
+ freeslots = stof(v);
+ else if(k == "F")
+ sflags = stof(v);
+ else if(k == "M")
+ modname = v;
+ }
+
+#ifdef COMPAT_NO_MOD_IS_XONOTIC
+ if(modname == "")
+ modname = "Xonotic";
+#endif
+
+ /*
+ SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
+ s = gethostcachestring(SLIST_FIELD_MOD, i);
+ if(s != "data")
+ if(modname == "Xonotic")
+ modname = s;
+ */
+
+ // list the mods here on which the pure server check actually works
+ if(modname != "Xonotic")
+ if(modname != "InstaGib" || modname != "MinstaGib")
+ if(modname != "CTS")
+ if(modname != "NIX")
+ if(modname != "NewToys")
+ pure = 0;
+
+ if(gethostcachenumber(SLIST_FIELD_FREESLOTS, i) <= 0)
+ theAlpha = SKINALPHA_SERVERLIST_FULL;
+ else if(freeslots == 0)
+ theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
+ else if (!gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
+ theAlpha = SKINALPHA_SERVERLIST_EMPTY;
+ else
+ theAlpha = 1;
+
+ p = gethostcachenumber(SLIST_FIELD_PING, i);
+ const float PING_LOW = 75;
+ const float PING_MED = 200;
+ const float PING_HIGH = 500;
+ if(p < PING_LOW)
+ theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
+ else if(p < PING_MED)
+ theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
+ else if(p < PING_HIGH)
+ {
+ theColor = SKINCOLOR_SERVERLIST_HIGHPING;
+ theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
+ }
+ else
+ {
+ theColor = eX;
+ theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
+ }
+
+ if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, i))
+ {
+ theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
+ theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
+ }
+
+ s = gethostcachestring(SLIST_FIELD_CNAME, i);
+
+ isv4 = isv6 = 0;
+ if(substring(s, 0, 1) == "[")
+ {
+ isv6 = 1;
+ me.seenIPv6 += 1;
+ }
+ else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
+ {
+ isv4 = 1;
+ me.seenIPv4 += 1;
+ }
+
+ q = stof(substring(crypto_getencryptlevel(s), 0, 1));
+ if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
+ {
+ theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
+ theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
+ }
+
+ if(q == 1)
+ {
+ if(cvar("crypto_aeslevel") >= 2)
+ q |= 4;
+ }
+ if(q == 2)
+ {
+ if(cvar("crypto_aeslevel") >= 1)
+ q |= 4;
+ }
+ if(q == 3)
+ q = 5;
+ else if(q >= 3)
+ q -= 2;
+ // possible status:
+ // 0: crypto off
+ // 1: AES possible
+ // 2: AES recommended but not available
+ // 3: AES possible and will be used
+ // 4: AES recommended and will be used
+ // 5: AES required
+
+ // --------------
+ // RENDER ICONS
+ // --------------
+ vector iconSize = '0 0 0';
+ iconSize_y = me.realFontSize.y * me.iconsSizeFactor;
+ iconSize_x = me.realFontSize.x * me.iconsSizeFactor;
+
+ vector iconPos = '0 0 0';
+ iconPos_x = (me.columnIconsSize - 3 * iconSize.x) * 0.5;
+ iconPos_y = (1 - iconSize.y) * 0.5;
+
+ string n;
+
+ if (!(me.seenIPv4 && me.seenIPv6))
+ {
+ iconPos.x += iconSize.x * 0.5;
+ }
+ else if(me.seenIPv4 && me.seenIPv6)
+ {
+ n = string_null;
+ if(isv6)
+ draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
+ else if(isv4)
+ draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
+ if(n)
+ draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
+ iconPos.x += iconSize.x;
+ }
+
+ if(q > 0)
+ {
+ draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
+ draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
+ }
+ iconPos.x += iconSize.x;
+
+ if(modname == "Xonotic")
+ {
+ if(pure == 0)
+ {
+ draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
+ draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
+ }
+ }
+ else
+ {
+ draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
+ if(draw_PictureSize(n) == '0 0 0')
+ draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
+ if(pure == 0)
+ draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
+ else
+ draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
+ }
+ iconPos.x += iconSize.x;
+
+ if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
+ {
+ draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
+ draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
+ }
+ iconPos.x += iconSize.x;
+
+ // --------------
+ // RENDER TEXT
+ // --------------
+
+ // ping
+ s = ftos(p);
+ draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+
+ // server name
+ s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize, 0, me.realFontSize);
+ draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
+
+ // server map
+ s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), me.columnMapSize, 0, me.realFontSize);
+ draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+
+ // server gametype
+ s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
+ draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+
+ // server playercount
+ s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
+ draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+}
+
+float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
+{
+ vector org, sz;
+
+ org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
+ sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
+
+ if(scan == K_ENTER || scan == K_KP_ENTER)
+ {
+ ServerList_Connect_Click(NULL, me);
+ return 1;
+ }
+ else if(scan == K_MOUSE2 || scan == K_SPACE)
+ {
+ if(me.nItems != 0)
+ {
++ m_play_click_sound(MENU_SOUND_OPEN);
+ main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
+ DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
+ return 1;
+ }
+ return 0;
+ }
+ else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
+ {
+ if(me.nItems != 0)
+ {
+ me.toggleFavorite(me, me.selectedServer);
+ me.ipAddressBoxFocused = -1;
+ return 1;
+ }
+ return 0;
+ }
+ else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
+ return 1;
+ else if(!me.controlledTextbox)
+ return 0;
+ else
+ return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
+}
+
+float XonoticServerList_getTotalHeight(entity me) {
+ float num_normal_rows = me.nItems;
+ float num_headers = category_draw_count;
+ return me.itemHeight * (num_normal_rows + me.categoriesHeight * num_headers);
+}
+float XonoticServerList_getItemAtPos(entity me, float pos) {
+ pos = pos / me.itemHeight;
+ float i;
+ for (i = category_draw_count - 1; i >= 0; --i) {
+ float itemidx = category_item[i];
+ float itempos = i * me.categoriesHeight + category_item[i];
+ if (pos >= itempos + me.categoriesHeight + 1)
+ return itemidx + 1 + floor(pos - (itempos + me.categoriesHeight + 1));
+ if (pos >= itempos)
+ return itemidx;
+ }
+ // No category matches? Note that category 0 is... 0. Therefore no headings exist at all.
+ return floor(pos);
+}
+float XonoticServerList_getItemStart(entity me, float item) {
+ float i;
+ for (i = category_draw_count - 1; i >= 0; --i) {
+ float itemidx = category_item[i];
+ float itempos = i * me.categoriesHeight + category_item[i];
+ if (item >= itemidx + 1)
+ return (itempos + me.categoriesHeight + 1 + item - (itemidx + 1)) * me.itemHeight;
+ if (item >= itemidx)
+ return itempos * me.itemHeight;
+ }
+ // No category matches? Note that category 0 is... 0. Therefore no headings exist at all.
+ return item * me.itemHeight;
+}
+float XonoticServerList_getItemHeight(entity me, float item) {
+ float i;
+ for (i = 0; i < category_draw_count; ++i) {
+ // Matches exactly the headings with increased height.
+ if (item == category_item[i])
+ return me.itemHeight * (me.categoriesHeight + 1);
+ }
+ return me.itemHeight;
+}
+
+#endif
--- /dev/null
- if(scan == K_ENTER || scan == K_KP_ENTER) {
+#ifdef INTERFACE
+CLASS(XonoticSkinList) EXTENDS(XonoticListBox)
+ METHOD(XonoticSkinList, configureXonoticSkinList, void(entity))
+ ATTRIB(XonoticSkinList, rowsPerItem, float, 4)
+ METHOD(XonoticSkinList, resizeNotify, void(entity, vector, vector, vector, vector))
+ METHOD(XonoticSkinList, drawListBoxItem, void(entity, float, vector, float))
+ METHOD(XonoticSkinList, getSkins, void(entity))
+ METHOD(XonoticSkinList, setSkin, void(entity))
+ METHOD(XonoticSkinList, loadCvars, void(entity))
+ METHOD(XonoticSkinList, saveCvars, void(entity))
+ METHOD(XonoticSkinList, skinParameter, string(entity, float, float))
+ METHOD(XonoticSkinList, doubleClickListBoxItem, void(entity, float, vector))
+ METHOD(XonoticSkinList, keyDown, float(entity, float, float, float))
+ METHOD(XonoticSkinList, destroy, void(entity))
+
+ ATTRIB(XonoticSkinList, skinlist, float, -1)
+ ATTRIB(XonoticSkinList, realFontSize, vector, '0 0 0')
+ ATTRIB(XonoticSkinList, columnPreviewOrigin, float, 0)
+ ATTRIB(XonoticSkinList, columnPreviewSize, float, 0)
+ ATTRIB(XonoticSkinList, columnNameOrigin, float, 0)
+ ATTRIB(XonoticSkinList, columnNameSize, float, 0)
+ ATTRIB(XonoticSkinList, realUpperMargin1, float, 0)
+ ATTRIB(XonoticSkinList, realUpperMargin2, float, 0)
+ ATTRIB(XonoticSkinList, origin, vector, '0 0 0')
+ ATTRIB(XonoticSkinList, itemAbsSize, vector, '0 0 0')
+
+ ATTRIB(XonoticSkinList, name, string, "skinselector")
+ENDCLASS(XonoticSkinList)
+
+entity makeXonoticSkinList();
+void SetSkin_Click(entity btn, entity me);
+#endif
+
+#ifdef IMPLEMENTATION
+
+const float SKINPARM_NAME = 0;
+const float SKINPARM_TITLE = 1;
+const float SKINPARM_AUTHOR = 2;
+const float SKINPARM_PREVIEW = 3;
+const float SKINPARM_COUNT = 4;
+
+entity makeXonoticSkinList()
+{
+ entity me;
+ me = spawnXonoticSkinList();
+ me.configureXonoticSkinList(me);
+ return me;
+}
+
+void XonoticSkinList_configureXonoticSkinList(entity me)
+{
+ me.configureXonoticListBox(me);
+ me.getSkins(me);
+ me.loadCvars(me);
+}
+
+void XonoticSkinList_loadCvars(entity me)
+{
+ string s;
+ float i, n;
+ s = cvar_string("menu_skin");
+ n = me.nItems;
+ for(i = 0; i < n; ++i)
+ {
+ if(me.skinParameter(me, i, SKINPARM_NAME) == s)
+ {
+ me.selectedItem = i;
+ break;
+ }
+ }
+}
+
+void XonoticSkinList_saveCvars(entity me)
+{
+ cvar_set("menu_skin", me.skinParameter(me, me.selectedItem, SKINPARM_NAME));
+}
+
+string XonoticSkinList_skinParameter(entity me, float i, float key)
+{
+ return bufstr_get(me.skinlist, i * SKINPARM_COUNT + key);
+}
+
+void XonoticSkinList_getSkins(entity me)
+{
+ float glob, buf, i, n, fh;
+ string s;
+
+ buf = buf_create();
+ glob = search_begin("gfx/menu/*/skinvalues.txt", true, true);
+ if(glob < 0)
+ {
+ me.skinlist = buf;
+ me.nItems = 0;
+ return;
+ }
+
+ n = search_getsize(glob);
+ for(i = 0; i < n; ++i)
+ {
+ s = search_getfilename(glob, i);
+ bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_NAME, substring(s, 9, strlen(s) - 24)); // the * part
+ bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_TITLE, _("<TITLE>"));
+ bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_AUTHOR, _("<AUTHOR>"));
+ if(draw_PictureSize(strcat("/gfx/menu/", substring(s, 9, strlen(s) - 24), "/skinpreview")) == '0 0 0')
+ bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_PREVIEW, "nopreview_menuskin");
+ else
+ bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_PREVIEW, strcat("/gfx/menu/", substring(s, 9, strlen(s) - 24), "/skinpreview"));
+ fh = fopen(language_filename(s), FILE_READ);
+ if(fh < 0)
+ {
+ print("Warning: can't open skinvalues.txt file\n");
+ continue;
+ }
+ while((s = fgets(fh)))
+ {
+ // these two are handled by skinlist.qc
+ if(substring(s, 0, 6) == "title ")
+ bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_TITLE, substring(s, 6, strlen(s) - 6));
+ else if(substring(s, 0, 7) == "author ")
+ bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_AUTHOR, substring(s, 7, strlen(s) - 7));
+ }
+ fclose(fh);
+ }
+
+ search_end(glob);
+
+ me.skinlist = buf;
+ me.nItems = n;
+}
+
+void XonoticSkinList_destroy(entity me)
+{
+ buf_del(me.skinlist);
+}
+
+void XonoticSkinList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+ me.itemAbsSize = '0 0 0';
+ SUPER(XonoticSkinList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
+
+ me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
+ me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
+ me.realUpperMargin1 = 0.5 * (1 - 2.5 * me.realFontSize.y);
+ me.realUpperMargin2 = me.realUpperMargin1 + 1.5 * me.realFontSize.y;
+
+ me.columnPreviewOrigin = 0;
+ me.columnPreviewSize = me.itemAbsSize.y / me.itemAbsSize.x * 4 / 3;
+ me.columnNameOrigin = me.columnPreviewOrigin + me.columnPreviewSize + me.realFontSize.x;
+ me.columnNameSize = 1 - me.columnPreviewSize - 2 * me.realFontSize.x;
+}
+
+void XonoticSkinList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
+{
+ string s;
+
+ if(isSelected)
+ draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
+
+ s = me.skinParameter(me, i, SKINPARM_PREVIEW);
+ draw_Picture(me.columnPreviewOrigin * eX, s, me.columnPreviewSize * eX + eY, '1 1 1', 1);
+
+ s = me.skinParameter(me, i, SKINPARM_TITLE);
+ s = draw_TextShortenToWidth(s, me.columnNameSize, 0, me.realFontSize);
+ draw_Text(me.realUpperMargin1 * eY + (me.columnNameOrigin + 0.00 * (me.columnNameSize - draw_TextWidth(s, 0, me.realFontSize))) * eX, s, me.realFontSize, SKINCOLOR_SKINLIST_TITLE, SKINALPHA_TEXT, 0);
+
+ s = me.skinParameter(me, i, SKINPARM_AUTHOR);
+ s = draw_TextShortenToWidth(s, me.columnNameSize, 0, me.realFontSize);
+ draw_Text(me.realUpperMargin2 * eY + (me.columnNameOrigin + 1.00 * (me.columnNameSize - draw_TextWidth(s, 0, me.realFontSize))) * eX, s, me.realFontSize, SKINCOLOR_SKINLIST_AUTHOR, SKINALPHA_TEXT, 0);
+}
+
+void XonoticSkinList_setSkin(entity me)
+{
+ me.saveCvars(me);
+ localcmd("\nmenu_restart\nmenu_cmd skinselect\n");
+}
+
+void SetSkin_Click(entity btn, entity me)
+{
+ me.setSkin(me);
+}
+
+void XonoticSkinList_doubleClickListBoxItem(entity me, float i, vector where)
+{
++ m_play_click_sound(MENU_SOUND_EXECUTE);
+ me.setSkin(me);
+}
+
+float XonoticSkinList_keyDown(entity me, float scan, float ascii, float shift)
+{
++ if(scan == K_ENTER || scan == K_KP_ENTER)
++ {
++ m_play_click_sound(MENU_SOUND_EXECUTE);
+ me.setSkin(me);
+ return 1;
+ }
+ else
+ return SUPER(XonoticSkinList).keyDown(me, scan, ascii, shift);
+}
+#endif
void CommonCommand_macro_write_aliases(float fh);
// keep track of the next token to use for argc
-float next_token;
+float next_token;
+
+// select the proper prefix for usage and other messages
+string GetCommandPrefix(entity caller);
+
+// if client return player nickname, or if server return admin nickname
+string GetCallerName(entity caller);
+
++// verify that the client provided is acceptable for kicking
++float VerifyKickableEntity(entity client);
++
+// verify that the client provided is acceptable for use
+float VerifyClientEntity(entity client, float must_be_real, float must_be_bots);
+
+// if the client is not acceptable, return a string to be used for error messages
+string GetClientErrorString(float clienterror, string original_input);
+
+// is this entity number even in the possible range of entities?
+float VerifyClientNumber(float tmp_number);
+
+entity GetIndexedEntity(float argc, float start_index);
+
+// find a player which matches the input string, and return their entity
+entity GetFilteredEntity(string input);
+
+// same thing, but instead return their edict number
+float GetFilteredNumber(string input);
+
+// switch between sprint and print depending on whether the receiver is the server or a player
+void print_to(entity to, string input);
+
+// ==========================================
+// Supporting functions for common commands
+// ==========================================
+
+// used by CommonCommand_timeout() and CommonCommand_timein() to handle game pausing and messaging and such.
+void timeout_handler_reset();
+
+void timeout_handler_think();
+
+// ===================================================
+// Common commands used in both sv_cmd.qc and cmd.qc
+// ===================================================
+
+void CommonCommand_cvar_changes(float request, entity caller);
+
+void CommonCommand_cvar_purechanges(float request, entity caller);
+
+void CommonCommand_info(float request, entity caller, float argc);
+
+void CommonCommand_ladder(float request, entity caller);
+
+void CommonCommand_lsmaps(float request, entity caller);
+
+void CommonCommand_printmaplist(float request, entity caller);
+
+void CommonCommand_rankings(float request, entity caller);
+
+void CommonCommand_records(float request, entity caller);
+
+void CommonCommand_teamstatus(float request, entity caller);
+
+void CommonCommand_time(float request, entity caller);
+
+void CommonCommand_timein(float request, entity caller);
+
+void CommonCommand_timeout(float request, entity caller);
+
+void CommonCommand_who(float request, entity caller, float argc);
+
+
+// ==================================
+// Macro system for common commands
+// ==================================
+
+// Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
+#define COMMON_COMMANDS(request,caller,arguments,command) \
+ COMMON_COMMAND("cvar_changes", CommonCommand_cvar_changes(request, caller), "Prints a list of all changed server cvars") \
+ COMMON_COMMAND("cvar_purechanges", CommonCommand_cvar_purechanges(request, caller), "Prints a list of all changed gameplay cvars") \
+ COMMON_COMMAND("info", CommonCommand_info(request, caller, arguments), "Request for unique server information set up by admin") \
+ COMMON_COMMAND("ladder", CommonCommand_ladder(request, caller), "Get information about top players if supported") \
+ COMMON_COMMAND("lsmaps", CommonCommand_lsmaps(request, caller), "List maps which can be used with the current game mode") \
+ COMMON_COMMAND("printmaplist", CommonCommand_printmaplist(request, caller), "Display full server maplist reply") \
+ COMMON_COMMAND("rankings", CommonCommand_rankings(request, caller), "Print information about rankings") \
+ COMMON_COMMAND("records", CommonCommand_records(request, caller), "List top 10 records for the current map") \
+ COMMON_COMMAND("teamstatus", CommonCommand_teamstatus(request, caller), "Show information about player and team scores") \
+ COMMON_COMMAND("time", CommonCommand_time(request, caller), "Print different formats/readouts of time") \
+ COMMON_COMMAND("timein", CommonCommand_timein(request, caller), "Resume the game from being paused with a timeout") \
+ COMMON_COMMAND("timeout", CommonCommand_timeout(request, caller), "Call a timeout which pauses the game for certain amount of time unless unpaused") \
+ COMMON_COMMAND("vote", VoteCommand(request, caller, arguments, command), "Request an action to be voted upon by players") \
+ COMMON_COMMAND("who", CommonCommand_who(request, caller, arguments), "Display detailed client information about all players") \
+ /* nothing */
+
+void CommonCommand_macro_help(entity caller)
+{
+ #define COMMON_COMMAND(name,function,description) \
+ { print_to(caller, strcat(" ^2", name, "^7: ", description)); }
+
+ COMMON_COMMANDS(0, caller, 0, "");
+ #undef COMMON_COMMAND
+
+ return;
+}
+
+float CommonCommand_macro_command(float argc, entity caller, string command)
+{
+ #define COMMON_COMMAND(name,function,description) \
+ { if(name == strtolower(argv(0))) { function; return true; } }
+
+ COMMON_COMMANDS(CMD_REQUEST_COMMAND, caller, argc, command);
+ #undef COMMON_COMMAND
+
+ return false;
+}
+
+float CommonCommand_macro_usage(float argc, entity caller)
+{
+ #define COMMON_COMMAND(name,function,description) \
+ { if(name == strtolower(argv(1))) { function; return true; } }
+
+ COMMON_COMMANDS(CMD_REQUEST_USAGE, caller, argc, "");
+ #undef COMMON_COMMAND
+
+ return false;
+}
+
+void CommonCommand_macro_write_aliases(float fh)
+{
+ #define COMMON_COMMAND(name,function,description) \
+ { CMD_Write_Alias("qc_cmd_svcmd", name, description); }
+
+ COMMON_COMMANDS(0, world, 0, "");
+ #undef COMMON_COMMAND
+
+ return;
+}
+
+
+#endif
float Ban_MaybeEnforceBanOnce(entity client)
{
if(client.ban_checked)
- return FALSE;
- client.ban_checked = TRUE;
+ return false;
+ client.ban_checked = true;
- return Ban_MaybeEnforceBan(self);
+ return Ban_MaybeEnforceBan(client);
}
string Ban_Enforce(float i, string reason)
--- /dev/null
+#ifndef MISCFUNCTIONS_H
+#define MISCFUNCTIONS_H
+
+#include "t_items.qh"
+
+#include "mutators/base.qh"
+#include "mutators/gamemode_race.qh"
+
+#include "../common/constants.qh"
+#include "../common/mapinfo.qh"
+
+#ifdef RELEASE
+#define cvar_string_normal builtin_cvar_string
+#define cvar_normal builtin_cvar
+#else
+string cvar_string_normal(string n)
+{
+ if (!(cvar_type(n) & 1))
+ backtrace(strcat("Attempt to access undefined cvar: ", n));
+ return builtin_cvar_string(n);
+}
+
+float cvar_normal(string n)
+{
+ return stof(cvar_string_normal(n));
+}
+#endif
+#define cvar_set_normal builtin_cvar_set
+
+.vector dropped_origin;
+.void(void) uncustomizeentityforclient;
+.float uncustomizeentityforclient_set;
+.float nottargeted;
+
+
+float DistributeEvenly_amount;
+float DistributeEvenly_totalweight;
+var void remove(entity e);
+void objerror(string s);
+void droptofloor();
+void() spawnfunc_info_player_deathmatch; // needed for the other spawnpoints
+void() spawnpoint_use;
+void() SUB_Remove;
+
+void attach_sameorigin(entity e, entity to, string tag);
+
+void crosshair_trace(entity pl);
+
+void crosshair_trace_plusvisibletriggers(entity pl);
+
+void detach_sameorigin(entity e);
+
+void follow_sameorigin(entity e, entity to);
+
+string formatmessage(string msg);
+
+void GameLogEcho(string s);
+
+void GameLogInit();
+
+void GameLogClose();
+
+void GetCvars(float f);
+
+string GetMapname();
+
+float isPushable(entity e);
+
+float LostMovetypeFollow(entity ent);
+
+float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance);
+
+string NearestLocation(vector p);
+
+void play2(entity e, string filename);
+
+string playername(entity p);
+
+void precache();
+
+void remove_safely(entity e);
+
+void remove_unsafely(entity e);
+
+void SetMovetypeFollow(entity ent, entity e);
+
+vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn);
+
+void soundto(float dest, entity e, float chan, string samp, float vol, float atten);
+
+void stopsound(entity e, float chan);
+
+float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma);
+
+void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
+
+void WarpZone_crosshair_trace(entity pl);
+
+void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
+
+
+#define IFTARGETED if(!self.nottargeted && self.targetname != "")
+
+#define ITEM_TOUCH_NEEDKILL() (((trace_dpstartcontents | trace_dphitcontents) & DPCONTENTS_NODROP) || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY))
+#define ITEM_DAMAGE_NEEDKILL(dt) (((dt) == DEATH_HURTTRIGGER) || ((dt) == DEATH_SLIME) || ((dt) == DEATH_LAVA) || ((dt) == DEATH_SWAMP))
+
+#define PROJECTILE_TOUCH if(WarpZone_Projectile_Touch()) return
+
+const string STR_PLAYER = "player";
+const string STR_SPECTATOR = "spectator";
+const string STR_OBSERVER = "observer";
+
+#define IS_PLAYER(v) (v.classname == STR_PLAYER)
+#define IS_SPEC(v) (v.classname == STR_SPECTATOR)
+#define IS_OBSERVER(v) (v.classname == STR_OBSERVER)
+#define IS_CLIENT(v) (v.flags & FL_CLIENT)
+#define IS_BOT_CLIENT(v) (clienttype(v) == CLIENTTYPE_BOT)
+#define IS_REAL_CLIENT(v) (clienttype(v) == CLIENTTYPE_REAL)
+#define IS_NOT_A_CLIENT(v) (clienttype(v) == CLIENTTYPE_NOTACLIENT)
+
+#define FOR_EACH_CLIENTSLOT(v) for(v = world; (v = nextent(v)) && (num_for_edict(v) <= maxclients); )
+#define FOR_EACH_CLIENT(v) FOR_EACH_CLIENTSLOT(v) if(IS_CLIENT(v))
+#define FOR_EACH_REALCLIENT(v) FOR_EACH_CLIENT(v) if(IS_REAL_CLIENT(v))
+
+#define FOR_EACH_PLAYER(v) FOR_EACH_CLIENT(v) if(IS_PLAYER(v))
+#define FOR_EACH_SPEC(v) FOR_EACH_CLIENT(v) if (!IS_PLAYER(v)) // Samual: shouldn't this be IS_SPEC(v)? and rather create a separate macro to include observers too
+#define FOR_EACH_REALPLAYER(v) FOR_EACH_REALCLIENT(v) if(IS_PLAYER(v))
+
+#define FOR_EACH_MONSTER(v) for(v = world; (v = findflags(v, flags, FL_MONSTER)) != world; )
+
+#define CENTER_OR_VIEWOFS(ent) (ent.origin + (IS_PLAYER(ent) ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5)))
+
+// copies a string to a tempstring (so one can strunzone it)
+string strcat1(string s) = #115; // FRIK_FILE
+
+float logfile_open;
+float logfile;
+
+#define strstr strstrofs
+/*
+// NOTE: DO NOT USE THIS FUNCTION TOO OFTEN.
+// IT WILL MOST PROBABLY DESTROY _ALL_ OTHER TEMP
+// STRINGS AND TAKE QUITE LONG. haystack and needle MUST
+// BE CONSTANT OR strzoneD!
+float strstr(string haystack, string needle, float offset)
+{
+ float len, endpos;
+ string found;
+ len = strlen(needle);
+ endpos = strlen(haystack) - len;
+ while(offset <= endpos)
+ {
+ found = substring(haystack, offset, len);
+ if(found == needle)
+ return offset;
+ offset = offset + 1;
+ }
+ return -1;
+}
+*/
+
+const float NUM_NEAREST_ENTITIES = 4;
+entity nearest_entity[NUM_NEAREST_ENTITIES];
+float nearest_length[NUM_NEAREST_ENTITIES];
+
+
+//#NO AUTOCVARS START
+
+float g_pickup_shells;
+float g_pickup_shells_max;
+float g_pickup_nails;
+float g_pickup_nails_max;
+float g_pickup_rockets;
+float g_pickup_rockets_max;
+float g_pickup_cells;
+float g_pickup_cells_max;
+float g_pickup_plasma;
+float g_pickup_plasma_max;
+float g_pickup_fuel;
+float g_pickup_fuel_jetpack;
+float g_pickup_fuel_max;
+float g_pickup_armorsmall;
+float g_pickup_armorsmall_max;
+float g_pickup_armorsmall_anyway;
+float g_pickup_armormedium;
+float g_pickup_armormedium_max;
+float g_pickup_armormedium_anyway;
+float g_pickup_armorbig;
+float g_pickup_armorbig_max;
+float g_pickup_armorbig_anyway;
+float g_pickup_armorlarge;
+float g_pickup_armorlarge_max;
+float g_pickup_armorlarge_anyway;
+float g_pickup_healthsmall;
+float g_pickup_healthsmall_max;
+float g_pickup_healthsmall_anyway;
+float g_pickup_healthmedium;
+float g_pickup_healthmedium_max;
+float g_pickup_healthmedium_anyway;
+float g_pickup_healthlarge;
+float g_pickup_healthlarge_max;
+float g_pickup_healthlarge_anyway;
+float g_pickup_healthmega;
+float g_pickup_healthmega_max;
+float g_pickup_healthmega_anyway;
+float g_pickup_ammo_anyway;
+float g_pickup_weapons_anyway;
+float g_weaponarena;
+WepSet g_weaponarena_weapons;
+float g_weaponarena_random;
+float g_weaponarena_random_with_blaster;
+string g_weaponarena_list;
+float g_weaponspeedfactor;
+float g_weaponratefactor;
+float g_weapondamagefactor;
+float g_weaponforcefactor;
+float g_weaponspreadfactor;
+
+WepSet start_weapons;
+WepSet start_weapons_default;
+WepSet start_weapons_defaultmask;
+int start_items;
+float start_ammo_shells;
+float start_ammo_nails;
+float start_ammo_rockets;
+float start_ammo_cells;
+float start_ammo_plasma;
+float start_ammo_fuel;
+float start_health;
+float start_armorvalue;
+WepSet warmup_start_weapons;
+WepSet warmup_start_weapons_default;
+WepSet warmup_start_weapons_defaultmask;
+#define WARMUP_START_WEAPONS ((g_warmup_allguns == 1) ? (warmup_start_weapons & (weaponsInMap | start_weapons)) : warmup_start_weapons)
+float warmup_start_ammo_shells;
+float warmup_start_ammo_nails;
+float warmup_start_ammo_rockets;
+float warmup_start_ammo_cells;
+float warmup_start_ammo_plasma;
+float warmup_start_ammo_fuel;
+float warmup_start_health;
+float warmup_start_armorvalue;
+float g_weapon_stay;
+
+float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
+{
+ int i = weaponinfo.weapon;
+ int d = 0;
+
+ if (!i)
+ return 0;
+
+ if (g_lms || g_ca || allguns)
+ {
+ if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
+ d = true;
+ else
+ d = false;
+ }
+ else if (g_cts)
+ d = (i == WEP_SHOTGUN);
+ else if (g_nexball)
+ d = 0; // weapon is set a few lines later
+ else
+ d = !(!weaponinfo.weaponstart);
+
+ if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
+ d |= (i == WEP_HOOK);
+ if(!g_cts && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
+ d = 0;
+
+ float t = weaponinfo.weaponstartoverride;
+
+ //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
+
+ // bit order in t:
+ // 1: want or not
+ // 2: is default?
+ // 4: is set by default?
+ if(t < 0)
+ t = 4 | (3 * d);
+ else
+ t |= (2 * d);
+
+ return t;
+}
+
+void readplayerstartcvars()
+{
+ entity e;
+ float i, j, t;
+ string s;
+
+ // initialize starting values for players
+ start_weapons = '0 0 0';
+ start_weapons_default = '0 0 0';
+ start_weapons_defaultmask = '0 0 0';
+ start_items = 0;
+ start_ammo_shells = 0;
+ start_ammo_nails = 0;
+ start_ammo_rockets = 0;
+ start_ammo_cells = 0;
+ start_ammo_plasma = 0;
+ start_health = cvar("g_balance_health_start");
+ start_armorvalue = cvar("g_balance_armor_start");
+
+ g_weaponarena = 0;
+ g_weaponarena_weapons = '0 0 0';
+
+ s = cvar_string("g_weaponarena");
+ if (s == "0" || s == "")
+ {
+ if(g_ca)
+ s = "most";
+ }
+
+ if (s == "0" || s == "")
+ {
+ // no arena
+ }
+ else if (s == "off")
+ {
+ // forcibly turn off weaponarena
+ }
+ else if (s == "all" || s == "1")
+ {
+ g_weaponarena = 1;
+ g_weaponarena_list = "All Weapons";
+ for (j = WEP_FIRST; j <= WEP_LAST; ++j)
+ {
+ e = get_weaponinfo(j);
+ if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
+ g_weaponarena_weapons |= WepSet_FromWeapon(j);
+ }
+ }
+ else if (s == "most")
+ {
+ g_weaponarena = 1;
+ g_weaponarena_list = "Most Weapons";
+ for (j = WEP_FIRST; j <= WEP_LAST; ++j)
+ {
+ e = get_weaponinfo(j);
+ if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
+ if (e.spawnflags & WEP_FLAG_NORMAL)
+ g_weaponarena_weapons |= WepSet_FromWeapon(j);
+ }
+ }
+ else if (s == "none")
+ {
+ g_weaponarena = 1;
+ g_weaponarena_list = "No Weapons";
+ }
+ else
+ {
+ g_weaponarena = 1;
+ t = tokenize_console(s);
+ g_weaponarena_list = "";
+ for (i = 0; i < t; ++i)
+ {
+ s = argv(i);
+ for (j = WEP_FIRST; j <= WEP_LAST; ++j)
+ {
+ e = get_weaponinfo(j);
+ if (e.netname == s)
+ {
+ g_weaponarena_weapons |= WepSet_FromWeapon(j);
+ g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
+ break;
+ }
+ }
+ if (j > WEP_LAST)
+ {
+ print("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
+ }
+ }
+ g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
+ }
+
+ if(g_weaponarena)
+ g_weaponarena_random = cvar("g_weaponarena_random");
+ else
+ g_weaponarena_random = 0;
+ g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster");
+
+ if (g_weaponarena)
+ {
+ g_weapon_stay = 0; // incompatible
+ start_weapons = g_weaponarena_weapons;
+ start_items |= IT_UNLIMITED_AMMO;
+ }
+ else
+ {
+ for (i = WEP_FIRST; i <= WEP_LAST; ++i)
+ {
+ e = get_weaponinfo(i);
+ int w = want_weapon(e, false);
+ if(w & 1)
+ start_weapons |= WepSet_FromWeapon(i);
+ if(w & 2)
+ start_weapons_default |= WepSet_FromWeapon(i);
+ if(w & 4)
+ start_weapons_defaultmask |= WepSet_FromWeapon(i);
+ }
+ }
+
+ if(!cvar("g_use_ammunition"))
+ start_items |= IT_UNLIMITED_AMMO;
+
+ if(start_items & IT_UNLIMITED_WEAPON_AMMO)
+ {
+ start_ammo_shells = 999;
+ start_ammo_nails = 999;
+ start_ammo_rockets = 999;
+ start_ammo_cells = 999;
+ start_ammo_plasma = 999;
+ start_ammo_fuel = 999;
+ }
+ else
+ {
+ start_ammo_shells = cvar("g_start_ammo_shells");
+ start_ammo_nails = cvar("g_start_ammo_nails");
+ start_ammo_rockets = cvar("g_start_ammo_rockets");
+ start_ammo_cells = cvar("g_start_ammo_cells");
+ start_ammo_plasma = cvar("g_start_ammo_plasma");
+ start_ammo_fuel = cvar("g_start_ammo_fuel");
+ }
+
+ if (warmup_stage)
+ {
+ warmup_start_ammo_shells = start_ammo_shells;
+ warmup_start_ammo_nails = start_ammo_nails;
+ warmup_start_ammo_rockets = start_ammo_rockets;
+ warmup_start_ammo_cells = start_ammo_cells;
+ warmup_start_ammo_plasma = start_ammo_plasma;
+ warmup_start_ammo_fuel = start_ammo_fuel;
+ warmup_start_health = start_health;
+ warmup_start_armorvalue = start_armorvalue;
+ warmup_start_weapons = start_weapons;
+ warmup_start_weapons_default = start_weapons_default;
+ warmup_start_weapons_defaultmask = start_weapons_defaultmask;
+
+ if (!g_weaponarena && !g_ca)
+ {
+ warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
+ warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
+ warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
+ warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
+ warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
+ warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
+ warmup_start_health = cvar("g_warmup_start_health");
+ warmup_start_armorvalue = cvar("g_warmup_start_armor");
+ warmup_start_weapons = '0 0 0';
+ warmup_start_weapons_default = '0 0 0';
+ warmup_start_weapons_defaultmask = '0 0 0';
+ for (i = WEP_FIRST; i <= WEP_LAST; ++i)
+ {
+ e = get_weaponinfo(i);
+ int w = want_weapon(e, g_warmup_allguns);
+ if(w & 1)
+ warmup_start_weapons |= WepSet_FromWeapon(i);
+ if(w & 2)
+ warmup_start_weapons_default |= WepSet_FromWeapon(i);
+ if(w & 4)
+ warmup_start_weapons_defaultmask |= WepSet_FromWeapon(i);
+ }
+ }
+ }
+
+ if (g_jetpack)
+ start_items |= IT_JETPACK;
+
+ MUTATOR_CALLHOOK(SetStartItems);
+
+ if ((start_items & IT_JETPACK) || (g_grappling_hook && (start_weapons & WEPSET_HOOK)))
+ {
+ start_items |= IT_FUEL_REGEN;
+ start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
+ warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
+ }
+
+ WepSet precache_weapons = start_weapons;
+ if (g_warmup_allguns != 1)
+ precache_weapons |= warmup_start_weapons;
+ for (i = WEP_FIRST; i <= WEP_LAST; ++i)
+ {
+ e = get_weaponinfo(i);
+ if(precache_weapons & WepSet_FromWeapon(i))
+ WEP_ACTION(i, WR_INIT);
+ }
+
+ start_ammo_shells = max(0, start_ammo_shells);
+ start_ammo_nails = max(0, start_ammo_nails);
+ start_ammo_rockets = max(0, start_ammo_rockets);
+ start_ammo_cells = max(0, start_ammo_cells);
+ start_ammo_plasma = max(0, start_ammo_plasma);
+ start_ammo_fuel = max(0, start_ammo_fuel);
+
+ warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
+ warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
+ warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
+ warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
+ warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
+ warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
+}
+
+float g_bugrigs;
+float g_bugrigs_planar_movement;
+float g_bugrigs_planar_movement_car_jumping;
+float g_bugrigs_reverse_spinning;
+float g_bugrigs_reverse_speeding;
+float g_bugrigs_reverse_stopping;
+float g_bugrigs_air_steering;
+float g_bugrigs_angle_smoothing;
+float g_bugrigs_friction_floor;
+float g_bugrigs_friction_brake;
+float g_bugrigs_friction_air;
+float g_bugrigs_accel;
+float g_bugrigs_speed_ref;
+float g_bugrigs_speed_pow;
+float g_bugrigs_steer;
+
+float sv_autotaunt;
+float sv_taunt;
+
+string GetGametype(); // g_world.qc
+void mutators_add(); // mutators.qc
+void readlevelcvars(void)
+{
+ // load mutators
+ mutators_add();
+
+ if(cvar("sv_allow_fullbright"))
+ serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT;
+
+ g_bugrigs = cvar("g_bugrigs");
+ g_bugrigs_planar_movement = cvar("g_bugrigs_planar_movement");
+ g_bugrigs_planar_movement_car_jumping = cvar("g_bugrigs_planar_movement_car_jumping");
+ g_bugrigs_reverse_spinning = cvar("g_bugrigs_reverse_spinning");
+ g_bugrigs_reverse_speeding = cvar("g_bugrigs_reverse_speeding");
+ g_bugrigs_reverse_stopping = cvar("g_bugrigs_reverse_stopping");
+ g_bugrigs_air_steering = cvar("g_bugrigs_air_steering");
+ g_bugrigs_angle_smoothing = cvar("g_bugrigs_angle_smoothing");
+ g_bugrigs_friction_floor = cvar("g_bugrigs_friction_floor");
+ g_bugrigs_friction_brake = cvar("g_bugrigs_friction_brake");
+ g_bugrigs_friction_air = cvar("g_bugrigs_friction_air");
+ g_bugrigs_accel = cvar("g_bugrigs_accel");
+ g_bugrigs_speed_ref = cvar("g_bugrigs_speed_ref");
+ g_bugrigs_speed_pow = cvar("g_bugrigs_speed_pow");
+ g_bugrigs_steer = cvar("g_bugrigs_steer");
+
+ g_instagib = cvar("g_instagib");
+
+ sv_clones = cvar("sv_clones");
+ sv_foginterval = cvar("sv_foginterval");
+ g_cloaked = cvar("g_cloaked");
+ g_footsteps = cvar("g_footsteps");
+ g_grappling_hook = cvar("g_grappling_hook");
+ g_jetpack = cvar("g_jetpack");
+ sv_maxidle = cvar("sv_maxidle");
+ sv_maxidle_spectatorsareidle = cvar("sv_maxidle_spectatorsareidle");
+ sv_autotaunt = cvar("sv_autotaunt");
+ sv_taunt = cvar("sv_taunt");
+
+ warmup_stage = cvar("g_warmup");
+ g_warmup_limit = cvar("g_warmup_limit");
+ g_warmup_allguns = cvar("g_warmup_allguns");
+ g_warmup_allow_timeout = cvar("g_warmup_allow_timeout");
+
+ if ((g_race && g_race_qualifying == 2) || g_assault || cvar("g_campaign"))
+ warmup_stage = 0; // these modes cannot work together, sorry
+
+ g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon");
+ g_pickup_respawntime_superweapon = cvar("g_pickup_respawntime_superweapon");
+ g_pickup_respawntime_ammo = cvar("g_pickup_respawntime_ammo");
+ g_pickup_respawntime_short = cvar("g_pickup_respawntime_short");
+ g_pickup_respawntime_medium = cvar("g_pickup_respawntime_medium");
+ g_pickup_respawntime_long = cvar("g_pickup_respawntime_long");
+ g_pickup_respawntime_powerup = cvar("g_pickup_respawntime_powerup");
+ g_pickup_respawntimejitter_weapon = cvar("g_pickup_respawntimejitter_weapon");
+ g_pickup_respawntimejitter_superweapon = cvar("g_pickup_respawntimejitter_superweapon");
+ g_pickup_respawntimejitter_ammo = cvar("g_pickup_respawntimejitter_ammo");
+ g_pickup_respawntimejitter_short = cvar("g_pickup_respawntimejitter_short");
+ g_pickup_respawntimejitter_medium = cvar("g_pickup_respawntimejitter_medium");
+ g_pickup_respawntimejitter_long = cvar("g_pickup_respawntimejitter_long");
+ g_pickup_respawntimejitter_powerup = cvar("g_pickup_respawntimejitter_powerup");
+
+ g_weaponspeedfactor = cvar("g_weaponspeedfactor");
+ g_weaponratefactor = cvar("g_weaponratefactor");
+ g_weapondamagefactor = cvar("g_weapondamagefactor");
+ g_weaponforcefactor = cvar("g_weaponforcefactor");
+ g_weaponspreadfactor = cvar("g_weaponspreadfactor");
+
+ g_pickup_shells = cvar("g_pickup_shells");
+ g_pickup_shells_max = cvar("g_pickup_shells_max");
+ g_pickup_nails = cvar("g_pickup_nails");
+ g_pickup_nails_max = cvar("g_pickup_nails_max");
+ g_pickup_rockets = cvar("g_pickup_rockets");
+ g_pickup_rockets_max = cvar("g_pickup_rockets_max");
+ g_pickup_cells = cvar("g_pickup_cells");
+ g_pickup_cells_max = cvar("g_pickup_cells_max");
+ g_pickup_plasma = cvar("g_pickup_plasma");
+ g_pickup_plasma_max = cvar("g_pickup_plasma_max");
+ g_pickup_fuel = cvar("g_pickup_fuel");
+ g_pickup_fuel_jetpack = cvar("g_pickup_fuel_jetpack");
+ g_pickup_fuel_max = cvar("g_pickup_fuel_max");
+ g_pickup_armorsmall = cvar("g_pickup_armorsmall");
+ g_pickup_armorsmall_max = cvar("g_pickup_armorsmall_max");
+ g_pickup_armorsmall_anyway = cvar("g_pickup_armorsmall_anyway");
+ g_pickup_armormedium = cvar("g_pickup_armormedium");
+ g_pickup_armormedium_max = cvar("g_pickup_armormedium_max");
+ g_pickup_armormedium_anyway = cvar("g_pickup_armormedium_anyway");
+ g_pickup_armorbig = cvar("g_pickup_armorbig");
+ g_pickup_armorbig_max = cvar("g_pickup_armorbig_max");
+ g_pickup_armorbig_anyway = cvar("g_pickup_armorbig_anyway");
+ g_pickup_armorlarge = cvar("g_pickup_armorlarge");
+ g_pickup_armorlarge_max = cvar("g_pickup_armorlarge_max");
+ g_pickup_armorlarge_anyway = cvar("g_pickup_armorlarge_anyway");
+ g_pickup_healthsmall = cvar("g_pickup_healthsmall");
+ g_pickup_healthsmall_max = cvar("g_pickup_healthsmall_max");
+ g_pickup_healthsmall_anyway = cvar("g_pickup_healthsmall_anyway");
+ g_pickup_healthmedium = cvar("g_pickup_healthmedium");
+ g_pickup_healthmedium_max = cvar("g_pickup_healthmedium_max");
+ g_pickup_healthmedium_anyway = cvar("g_pickup_healthmedium_anyway");
+ g_pickup_healthlarge = cvar("g_pickup_healthlarge");
+ g_pickup_healthlarge_max = cvar("g_pickup_healthlarge_max");
+ g_pickup_healthlarge_anyway = cvar("g_pickup_healthlarge_anyway");
+ g_pickup_healthmega = cvar("g_pickup_healthmega");
+ g_pickup_healthmega_max = cvar("g_pickup_healthmega_max");
+ g_pickup_healthmega_anyway = cvar("g_pickup_healthmega_anyway");
+
+ g_pickup_ammo_anyway = cvar("g_pickup_ammo_anyway");
+ g_pickup_weapons_anyway = cvar("g_pickup_weapons_anyway");
+
+ g_weapon_stay = cvar(strcat("g_", GetGametype(), "_weapon_stay"));
+ if(!g_weapon_stay)
+ g_weapon_stay = cvar("g_weapon_stay");
+
+ if (!warmup_stage)
+ game_starttime = time + cvar("g_start_delay");
+
++ for(int i = WEP_FIRST; i <= WEP_LAST; ++i)
++ WEP_ACTION(i, WR_INIT);
++
+ readplayerstartcvars();
+}
+
+//#NO AUTOCVARS END
+
+
+// Sound functions
+//string precache_sound (string s) = #19;
+// hack
+float precache_sound_index (string s) = #19;
+
+const float SND_VOLUME = 1;
+const float SND_ATTENUATION = 2;
+const float SND_LARGEENTITY = 8;
+const float SND_LARGESOUND = 16;
+
+// WARNING: this kills the trace globals
+#define EXACTTRIGGER_TOUCH if(WarpZoneLib_ExactTrigger_Touch()) return
+#define EXACTTRIGGER_INIT WarpZoneLib_ExactTrigger_Init()
+
+const float INITPRIO_FIRST = 0;
+const float INITPRIO_GAMETYPE = 0;
+const float INITPRIO_GAMETYPE_FALLBACK = 1;
+const float INITPRIO_FINDTARGET = 10;
+const float INITPRIO_DROPTOFLOOR = 20;
+const float INITPRIO_SETLOCATION = 90;
+const float INITPRIO_LINKDOORS = 91;
+const float INITPRIO_LAST = 99;
+
+.void(void) initialize_entity;
+.float initialize_entity_order;
+.entity initialize_entity_next;
+entity initialize_entity_first;
+
+
+
+
+
+float sound_allowed(float dest, entity e);
+void InitializeEntity(entity e, void(void) func, float order);
+void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer);
+void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc);
+
+#endif