From: TimePath Date: Mon, 2 Feb 2015 08:53:04 +0000 (+1100) Subject: Merge branch 'master' into TimePath/experiments/csqc_prediction X-Git-Tag: xonotic-v0.8.1~38^2~37^2~1 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=cd109cf922bc405155c680582745d645bd057ded;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into TimePath/experiments/csqc_prediction Conflicts: qcsrc/menu/classes.c qcsrc/menu/item/modalcontroller.qc qcsrc/menu/menu.qh qcsrc/menu/xonotic/maplist.qc qcsrc/server/command/banning.qc qcsrc/server/ipban.qc qcsrc/server/miscfunctions.qc --- cd109cf922bc405155c680582745d645bd057ded diff --cc qcsrc/common/weapons/weapons.qh index 2835c47d9,dca226f42..7ac8a9e8d --- a/qcsrc/common/weapons/weapons.qh +++ b/qcsrc/common/weapons/weapons.qh @@@ -69,6 -66,6 +69,7 @@@ WepSet WEPSET_SUPERWEAPONS // 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); diff --cc qcsrc/dpdefs/progsdefs.qh index 30ebe1bdf,000000000..fe632ce8d mode 100644,000000..100644 --- a/qcsrc/dpdefs/progsdefs.qh +++ b/qcsrc/dpdefs/progsdefs.qh @@@ -1,508 -1,0 +1,511 @@@ +#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 diff --cc qcsrc/menu/item.qc index d055b1a05,000000000..d0bd40b03 mode 100644,000000..100644 --- a/qcsrc/menu/item.qc +++ b/qcsrc/menu/item.qc @@@ -1,134 -1,0 +1,137 @@@ +#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 diff --cc qcsrc/menu/item/button.qc index f6ba208f5,000000000..52e58238c mode 100644,000000..100644 --- a/qcsrc/menu/item/button.qc +++ b/qcsrc/menu/item/button.qc @@@ -1,173 -1,0 +1,178 @@@ +#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)) - METHOD(Button, focusEnter, void(entity)) ++ 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) + { - if(cvar("menu_sounds")) - localsound("sound/misc/menu2.wav"); ++ 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_focusEnter(entity me) - { - if(cvar("menu_sounds") > 1) - localsound("sound/misc/menu1.wav"); - SUPER(Button).focusEnter(me); - } +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 diff --cc qcsrc/menu/item/checkbox.qc index 94f67ba70,000000000..2540cc846 mode 100644,000000..100644 --- a/qcsrc/menu/item/checkbox.qc +++ b/qcsrc/menu/item/checkbox.qc @@@ -1,48 -1,0 +1,53 @@@ +#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 diff --cc qcsrc/menu/item/dialog.qc index 62c74406e,000000000..1723f27cb mode 100644,000000..100644 --- a/qcsrc/menu/item/dialog.qc +++ b/qcsrc/menu/item/dialog.qc @@@ -1,191 -1,0 +1,192 @@@ +// 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 diff --cc qcsrc/menu/item/inputbox.qc index d3f68152e,000000000..275b20046 mode 100644,000000..100644 --- a/qcsrc/menu/item/inputbox.qc +++ b/qcsrc/menu/item/inputbox.qc @@@ -1,391 -1,0 +1,390 @@@ +#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); +#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)); +} + - void InputBox_Clear_Click(entity btn, entity me) - { - me.setText(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; - InputBox_Clear_Click(world, me); + 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 diff --cc qcsrc/menu/item/listbox.qc index 2f2979509,000000000..178b12b9a mode 100644,000000..100644 --- a/qcsrc/menu/item/listbox.qc +++ b/qcsrc/menu/item/listbox.qc @@@ -1,402 -1,0 +1,403 @@@ +#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 diff --cc qcsrc/menu/item/modalcontroller.qc index 38332fd74,000000000..bff217079 mode 100644,000000..100644 --- a/qcsrc/menu/item/modalcontroller.qc +++ b/qcsrc/menu/item/modalcontroller.qc @@@ -1,293 -1,0 +1,305 @@@ +#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) + { - 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; - } - + 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 - if(f < 1) - animating = 1; + 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 + { - 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); ++ 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) + - 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; ++ 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 diff --cc qcsrc/menu/item/nexposee.qc index 79a294a3c,000000000..141303985 mode 100644,000000..100644 --- a/qcsrc/menu/item/nexposee.qc +++ b/qcsrc/menu/item/nexposee.qc @@@ -1,363 -1,0 +1,367 @@@ +#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 diff --cc qcsrc/menu/item/slider.qc index f97871b22,000000000..2c74f6131 mode 100644,000000..100644 --- a/qcsrc/menu/item/slider.qc +++ b/qcsrc/menu/item/slider.qc @@@ -1,285 -1,0 +1,301 @@@ +// 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)) - METHOD(Slider, focusEnter, void(entity)) + 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; + } - // TODO more keys ++ // 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; - if(cvar("menu_sounds")) - localsound("sound/misc/menu2.wav"); ++ m_play_click_sound(MENU_SOUND_SLIDE); + return 1; +} +void Slider_showNotify(entity me) +{ + me.focusable = !me.disabled; +} - void Slider_focusEnter(entity me) - { - if(cvar("menu_sounds") > 1) - localsound("sound/misc/menu1.wav"); - SUPER(Slider).focusEnter(me); - } +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 diff --cc qcsrc/menu/menu.qh index 2aa2b5d6c,6f36a074e..00f6017aa --- a/qcsrc/menu/menu.qh +++ b/qcsrc/menu/menu.qh @@@ -52,4 -38,17 +52,18 @@@ void preMenuDraw(); // this is run befo 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 diff --cc qcsrc/menu/xonotic/colorpicker.qc index 6634728f3,000000000..f16ab0ebd mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/colorpicker.qc +++ b/qcsrc/menu/xonotic/colorpicker.qc @@@ -1,174 -1,0 +1,175 @@@ +#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 diff --cc qcsrc/menu/xonotic/colorpicker_string.qc index f0e3e6da0,000000000..458a72c01 mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/colorpicker_string.qc +++ b/qcsrc/menu/xonotic/colorpicker_string.qc @@@ -1,122 -1,0 +1,123 @@@ +#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 diff --cc qcsrc/menu/xonotic/dialog_multiplayer_profile.qc index bf891ac9f,000000000..1adfb015e mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/dialog_multiplayer_profile.qc +++ b/qcsrc/menu/xonotic/dialog_multiplayer_profile.qc @@@ -1,164 -1,0 +1,164 @@@ +#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, 2, e = makeXonoticTextSlider("cl_gender")); ++ 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"))); ++ 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 diff --cc qcsrc/menu/xonotic/dialog_settings_audio.qc index 81624fdef,000000000..cccaa2652 mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/dialog_settings_audio.qc +++ b/qcsrc/menu/xonotic/dialog_settings_audio.qc @@@ -1,166 -1,0 +1,170 @@@ +#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.TD(me, 1, 3, makeXonoticCheckBoxEx(2, 0, "menu_sounds", _("Menu sounds"))); ++ 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 diff --cc qcsrc/menu/xonotic/dialog_settings_game_crosshair.qc index 7e3415141,000000000..0aa38e85a mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/dialog_settings_game_crosshair.qc +++ b/qcsrc/menu/xonotic/dialog_settings_game_crosshair.qc @@@ -1,163 -1,0 +1,168 @@@ +#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 diff --cc qcsrc/menu/xonotic/dialog_singleplayer_winner.qc index 8e584b8a8,000000000..0d1c05af4 mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/dialog_singleplayer_winner.qc +++ b/qcsrc/menu/xonotic/dialog_singleplayer_winner.qc @@@ -1,25 -1,0 +1,30 @@@ +#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 diff --cc qcsrc/menu/xonotic/gametypelist.qc index 45f493bb9,000000000..feb1d89b1 mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/gametypelist.qc +++ b/qcsrc/menu/xonotic/gametypelist.qc @@@ -1,125 -1,0 +1,129 @@@ +#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 diff --cc qcsrc/menu/xonotic/keybinder.qc index 73042024b,000000000..a08320771 mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/keybinder.qc +++ b/qcsrc/menu/xonotic/keybinder.qc @@@ -1,363 -1,0 +1,365 @@@ +#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-default.cfg\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 diff --cc qcsrc/menu/xonotic/languagelist.qc index 2d43a47e3,000000000..8aa0d2c07 mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/languagelist.qc +++ b/qcsrc/menu/xonotic/languagelist.qc @@@ -1,215 -1,0 +1,218 @@@ +#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) { ++ 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 diff --cc qcsrc/menu/xonotic/maplist.qc index 60720bd54,000000000..d88ad0e8e mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/maplist.qc +++ b/qcsrc/menu/xonotic/maplist.qc @@@ -1,353 -1,0 +1,365 @@@ +#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 diff --cc qcsrc/menu/xonotic/playerlist.qc index 8daf7302f,000000000..d4dc5af05 mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/playerlist.qc +++ b/qcsrc/menu/xonotic/playerlist.qc @@@ -1,138 -1,0 +1,139 @@@ +#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 diff --cc qcsrc/menu/xonotic/serverlist.qc index db0fbfe41,000000000..c6c53cc8e mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/serverlist.qc +++ b/qcsrc/menu/xonotic/serverlist.qc @@@ -1,1314 -1,0 +1,1317 @@@ +#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 diff --cc qcsrc/menu/xonotic/skinlist.qc index c0c05a805,000000000..9990c83fe mode 100644,000000..100644 --- a/qcsrc/menu/xonotic/skinlist.qc +++ b/qcsrc/menu/xonotic/skinlist.qc @@@ -1,196 -1,0 +1,199 @@@ +#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, _("")); + 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) { ++ 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 diff --cc qcsrc/server/command/common.qh index e31a42884,589388bba..2a03041d9 --- a/qcsrc/server/command/common.qh +++ b/qcsrc/server/command/common.qh @@@ -43,139 -35,4 +43,142 @@@ void timeout_handler_think() 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 diff --cc qcsrc/server/ipban.qc index 81ca387cd,d8a70fe65..bd9880839 --- a/qcsrc/server/ipban.qc +++ b/qcsrc/server/ipban.qc @@@ -470,9 -457,9 +470,9 @@@ float Ban_MaybeEnforceBan(entity client 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) diff --cc qcsrc/server/miscfunctions.qh index 0c8c105d5,000000000..7cebe9c5d mode 100644,000000..100644 --- a/qcsrc/server/miscfunctions.qh +++ b/qcsrc/server/miscfunctions.qh @@@ -1,684 -1,0 +1,687 @@@ +#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