From: Samual Lenks Date: Tue, 15 Oct 2013 19:34:09 +0000 (-0400) Subject: Merge remote-tracking branch 'origin/master' into samual/combined_updates X-Git-Tag: xonotic-v0.8.0~139^2~1^2~134 X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=f8d89c06e0b40ce3d7ec536868d24059f0505096;hp=6042228a26fd6fe48bf7ee6cb7c284257bb498d9 Merge remote-tracking branch 'origin/master' into samual/combined_updates --- diff --git a/crosshairs.cfg b/crosshairs.cfg index c6a9e837b..365c22af2 100644 --- a/crosshairs.cfg +++ b/crosshairs.cfg @@ -32,8 +32,9 @@ seta crosshair_hitindication_speed 5 // hit testing/tracing for special effects for the crosshair set g_trueaim_minrange 44 "TrueAim minimum range (TrueAim adjusts shots so they hit the crosshair point even though the gun is not at the screen center)" -seta crosshair_hittest 1 "do a crosshair hit evaluation; also, the crosshair is scaled by the given number when aiming at an enemy, and blurred when aiming at a team mate" +seta crosshair_hittest 1 "do a crosshair hit evaluation, applying effects from the _blur, _scale, and _showipact cvars" seta crosshair_hittest_blur 1 "blur the crosshair if the shot is obstructed" +seta crosshair_hittest_scale 1.25 "enlarge crosshair if aiming at an enemy, shrink crosshair if shot is obstructed or aiming at a teammate" seta crosshair_hittest_showimpact 0 "move the crosshair to the actual impact location if obstructed" // change color based on special case diff --git a/defaultXonotic.cfg b/defaultXonotic.cfg index d967d53d5..e6cf3e1dc 100644 --- a/defaultXonotic.cfg +++ b/defaultXonotic.cfg @@ -978,6 +978,28 @@ seta menu_slist_showfull 1 "show servers even if they are full and have no slots seta menu_slist_showempty 1 "show servers even if they are no empty and have no opponents to play against" seta menu_slist_modfilter "" // set to either: !modname or modname. modname of = means "same as we are running now". +// other serverlist cvars +seta menu_slist_categories 0 +seta menu_slist_categories_onlyifmultiple 1 +seta menu_slist_purethreshold 0 +seta menu_slist_modimpurity 0 +seta menu_slist_recommendations 3 +seta menu_slist_recommendations_maxping 150 +seta menu_slist_recommendations_minfreeslots 1 +seta menu_slist_recommendations_minhumans 0 +seta menu_slist_recommendations_purethreshold -1 + +// serverlist category override cvars +seta menu_slist_categories_CAT_FAVORITED_override "" +seta menu_slist_categories_CAT_RECOMMENDED_override "" +seta menu_slist_categories_CAT_NORMAL_override "" +seta menu_slist_categories_CAT_SERVERS_override "CAT_NORMAL" +seta menu_slist_categories_CAT_XPM_override "CAT_NORMAL" +seta menu_slist_categories_CAT_MODIFIED_override "" +seta menu_slist_categories_CAT_OVERKILL_override "" +seta menu_slist_categories_CAT_MINSTAGIB_override "" +seta menu_slist_categories_CAT_DEFRAG_override "" + seta menu_weaponarena "" seta menu_maxplayers 16 "maxplayers value when the menu starts a game" diff --git a/effects-high.cfg b/effects-high.cfg index 979c8b174..b0178fa8f 100644 --- a/effects-high.cfg +++ b/effects-high.cfg @@ -1,7 +1,8 @@ cl_decals 1 cl_decals_models 0 cl_decals_time 4 -cl_particles_quality 1 +cl_particles 1 +cl_particles_quality 1.0 cl_damageeffect 1 cl_spawn_point_particles 1 cl_playerdetailreduction 0.5 @@ -15,7 +16,7 @@ hud_postprocessing_maxbluralpha 0.5 hud_powerup 0 r_depthfirst 2 r_drawdecals_drawdistance 500 -r_drawparticles_drawdistance 2000 +r_drawparticles_drawdistance 1500 r_glsl_deluxemapping 1 r_glsl_offsetmapping 0 r_glsl_offsetmapping_reliefmapping 0 diff --git a/effects-low.cfg b/effects-low.cfg index 8d6e72a4d..dd0b60709 100644 --- a/effects-low.cfg +++ b/effects-low.cfg @@ -1,6 +1,7 @@ cl_decals 1 cl_decals_models 0 cl_decals_time 2 +cl_particles 1 cl_particles_quality 0.4 cl_damageeffect 0 cl_spawn_point_particles 0 diff --git a/effects-med.cfg b/effects-med.cfg index 1611943c6..bc08dadc1 100644 --- a/effects-med.cfg +++ b/effects-med.cfg @@ -1,7 +1,8 @@ cl_decals 1 cl_decals_models 0 cl_decals_time 2 -cl_particles_quality 1 +cl_particles 1 +cl_particles_quality 0.8 cl_damageeffect 0 cl_spawn_point_particles 0 cl_playerdetailreduction 1 @@ -15,7 +16,7 @@ hud_postprocessing_maxbluralpha 0 hud_powerup 0 r_depthfirst 0 r_drawdecals_drawdistance 300 -r_drawparticles_drawdistance 1000 +r_drawparticles_drawdistance 750 r_glsl_deluxemapping 0 r_glsl_offsetmapping 0 r_glsl_offsetmapping_reliefmapping 0 diff --git a/effects-normal.cfg b/effects-normal.cfg index 064af6550..06b62edf2 100644 --- a/effects-normal.cfg +++ b/effects-normal.cfg @@ -1,7 +1,8 @@ cl_decals 1 cl_decals_models 0 cl_decals_time 2 -cl_particles_quality 1 +cl_particles 1 +cl_particles_quality 1.0 cl_damageeffect 1 cl_spawn_point_particles 1 cl_playerdetailreduction 1 diff --git a/effects-omg.cfg b/effects-omg.cfg index 9ede9c7d7..eb1ae56f9 100644 --- a/effects-omg.cfg +++ b/effects-omg.cfg @@ -1,6 +1,7 @@ cl_decals 0 cl_decals_models 0 cl_decals_time 2 +cl_particles 1 cl_particles_quality 0.4 cl_damageeffect 0 cl_spawn_point_particles 0 diff --git a/effects-ultimate.cfg b/effects-ultimate.cfg index 92539b9b7..d92cefe32 100644 --- a/effects-ultimate.cfg +++ b/effects-ultimate.cfg @@ -1,7 +1,8 @@ cl_decals 1 cl_decals_models 1 cl_decals_time 10 -cl_particles_quality 1 +cl_particles 1 +cl_particles_quality 1.0 cl_damageeffect 2 cl_spawn_point_particles 1 cl_playerdetailreduction 0 @@ -15,7 +16,7 @@ hud_postprocessing_maxbluralpha 0.5 hud_powerup 0.5 r_depthfirst 2 r_drawdecals_drawdistance 500 -r_drawparticles_drawdistance 2000 +r_drawparticles_drawdistance 3000 r_glsl_deluxemapping 1 r_glsl_offsetmapping 1 r_glsl_offsetmapping_reliefmapping 1 diff --git a/effects-ultra.cfg b/effects-ultra.cfg index 63cfc63ea..8da513bd0 100644 --- a/effects-ultra.cfg +++ b/effects-ultra.cfg @@ -1,8 +1,9 @@ cl_decals 1 cl_decals_models 0 cl_decals_time 10 -cl_particles_quality 1 -cl_damageeffect 1 +cl_particles 1 +cl_particles_quality 1.0 +cl_damageeffect 2 cl_spawn_point_particles 1 cl_playerdetailreduction 0 gl_flashblend 0 diff --git a/gfx/menu/luminos/skinvalues.txt b/gfx/menu/luminos/skinvalues.txt index 5e311c909..315d44f20 100755 --- a/gfx/menu/luminos/skinvalues.txt +++ b/gfx/menu/luminos/skinvalues.txt @@ -180,6 +180,7 @@ COLOR_DIALOG_MODEL '1 1 1' COLOR_DIALOG_CROSSHAIR '1 1 1' COLOR_DIALOG_HUD '1 1 1' COLOR_DIALOG_SERVERINFO '1 1 1' +COLOR_DIALOG_SCREENSHOTVIEWER '1 1 1' COLOR_DIALOG_CVARS '1 0 0' COLOR_DIALOG_HUDCONFIRM '1 0 0' diff --git a/gfx/menu/wickedx/skinvalues.txt b/gfx/menu/wickedx/skinvalues.txt old mode 100755 new mode 100644 index 156051285..be0ee5b0e --- a/gfx/menu/wickedx/skinvalues.txt +++ b/gfx/menu/wickedx/skinvalues.txt @@ -180,6 +180,7 @@ COLOR_DIALOG_MODEL '1 1 1' COLOR_DIALOG_CROSSHAIR '1 1 1' COLOR_DIALOG_HUD '1 1 1' COLOR_DIALOG_SERVERINFO '1 1 1' +COLOR_DIALOG_SCREENSHOTVIEWER '1 1 1' COLOR_DIALOG_CVARS '1 0 0' COLOR_DIALOG_HUDCONFIRM '1 0 0' diff --git a/gfx/menu/xaw/skinvalues.txt b/gfx/menu/xaw/skinvalues.txt index 3bd555f03..d401de3e5 100644 --- a/gfx/menu/xaw/skinvalues.txt +++ b/gfx/menu/xaw/skinvalues.txt @@ -35,6 +35,7 @@ COLOR_DIALOG_MODEL '1 1 1' COLOR_DIALOG_CROSSHAIR '1 1 1' COLOR_DIALOG_HUD '1 1 1' COLOR_DIALOG_SERVERINFO '1 1 1' +COLOR_DIALOG_SCREENSHOTVIEWER '1 1 1' COLOR_DIALOG_CVARS '1 0 0' COLOR_DIALOG_HUDCONFIRM '1 0 0' diff --git a/qcsrc/client/Main.qc b/qcsrc/client/Main.qc index 8e36cc9b5..4a6bd9847 100644 --- a/qcsrc/client/Main.qc +++ b/qcsrc/client/Main.qc @@ -81,7 +81,7 @@ void CSQC_Init(void) //registercommand("hud_configure"); //registercommand("hud_save"); //registercommand("menu_action"); - + ConsoleCommand_macro_init(); registercvar("hud_usecsqc", "1"); @@ -125,13 +125,13 @@ void CSQC_Init(void) turrets_precache(); Tuba_Precache(); CSQCPlayer_Precache(); - + if(autocvar_cl_reticle) { if(autocvar_cl_reticle_item_normal) { precache_pic("gfx/reticle_normal"); } if(autocvar_cl_reticle_item_nex) { precache_pic("gfx/reticle_nex"); } } - + get_mi_min_max_texcoords(1); // try the CLEVER way first minimapname = strcat("gfx/", mi_shortname, "_radar.tga"); shortmapname = mi_shortname; @@ -651,7 +651,7 @@ void Ent_ReadSpawnPoint(float is_new) // entity for spawnpoint spn_origin_x = ReadShort(); spn_origin_y = ReadShort(); spn_origin_z = ReadShort(); - + if(is_new) { self.origin = spn_origin; @@ -682,7 +682,7 @@ void Ent_ReadSpawnPoint(float is_new) // entity for spawnpoint } } else { self.cnt = particleeffectnum("spawn_point_neutral"); } - + self.draw = Spawn_Draw; } } @@ -696,7 +696,7 @@ void Ent_ReadSpawnEvent(float is_new) // this way the server can disable the sending of // spawn origin or such to clients if wanted. float entnum = ReadByte(); - + if(entnum) { self.origin_x = ReadShort(); @@ -724,7 +724,7 @@ void Ent_ReadSpawnEvent(float is_new) } } } - + // local spawn actions if(is_new && (!entnum || (entnum == player_localentnum))) { @@ -737,7 +737,7 @@ void Ent_ReadSpawnEvent(float is_new) button_zoom = FALSE; } } - + //print(sprintf("Ent_ReadSpawnEvent(is_new = %d); origin = %s, entnum = %d, localentnum = %d\n", is_new, vtos(self.origin), entnum, player_localentnum)); } @@ -822,9 +822,9 @@ void CSQC_Ent_Update(float bIsNewEntity) case ENT_CLIENT_GAUNTLET: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_GAUNTLET); break; case ENT_CLIENT_ACCURACY: Ent_ReadAccuracy(); break; case ENT_CLIENT_AUXILIARYXHAIR: Net_AuXair2(bIsNewEntity); break; - case ENT_CLIENT_TURRET: ent_turret(); break; + case ENT_CLIENT_TURRET: ent_turret(); break; case ENT_CLIENT_MODEL: CSQCModel_Read(bIsNewEntity); break; - case ENT_CLIENT_ITEM: ItemRead(bIsNewEntity); break; + case ENT_CLIENT_ITEM: ItemRead(bIsNewEntity); break; case ENT_CLIENT_BUMBLE_RAYGUN: bumble_raygun_read(bIsNewEntity); break; case ENT_CLIENT_SPAWNPOINT: Ent_ReadSpawnPoint(bIsNewEntity); break; case ENT_CLIENT_SPAWNEVENT: Ent_ReadSpawnEvent(bIsNewEntity); break; diff --git a/qcsrc/client/View.qc b/qcsrc/client/View.qc index 46920a417..36b49bda9 100644 --- a/qcsrc/client/View.qc +++ b/qcsrc/client/View.qc @@ -1125,7 +1125,18 @@ void CSQC_UpdateView(float w, float h) // wcross_origin = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight; wcross_origin = project_3d_to_2d(view_origin + MAX_SHOT_DISTANCE * view_forward); wcross_origin_z = 0; - if(autocvar_crosshair_hittest) + + if( + autocvar_crosshair_hittest + && + ( + autocvar_crosshair_hittest_blur + || + autocvar_crosshair_hittest_scale + || + autocvar_crosshair_hittest_showimpact + ) + ) { vector wcross_oldorigin; wcross_oldorigin = wcross_origin; @@ -1141,8 +1152,7 @@ void CSQC_UpdateView(float w, float h) if(!autocvar_crosshair_hittest_showimpact) wcross_origin = wcross_oldorigin; } - else - shottype = SHOTTYPE_HITWORLD; + else { shottype = SHOTTYPE_HITWORLD; } vector wcross_color = '0 0 0', wcross_size = '0 0 0'; string wcross_wep = "", wcross_name; @@ -1300,9 +1310,9 @@ void CSQC_UpdateView(float w, float h) } if(shottype == SHOTTYPE_HITENEMY) - wcross_scale *= autocvar_crosshair_hittest; // is not queried if hittest is 0 + wcross_scale *= autocvar_crosshair_hittest_scale; // is not queried if hittest is 0 if(shottype == SHOTTYPE_HITTEAM) - wcross_scale /= autocvar_crosshair_hittest; // is not queried if hittest is 0 + wcross_scale /= autocvar_crosshair_hittest_scale; // is not queried if hittest is 0 f = fabs(autocvar_crosshair_effect_time); if(wcross_scale != wcross_scale_goal_prev || wcross_alpha != wcross_alpha_goal_prev || wcross_color != wcross_color_goal_prev) diff --git a/qcsrc/client/autocvars.qh b/qcsrc/client/autocvars.qh index 8175695ab..2012228f4 100644 --- a/qcsrc/client/autocvars.qh +++ b/qcsrc/client/autocvars.qh @@ -113,6 +113,7 @@ string autocvar_crosshair_hitindication_per_weapon_color; float autocvar_crosshair_hitindication_speed; float autocvar_crosshair_hittest; float autocvar_crosshair_hittest_blur; +var float autocvar_crosshair_hittest_scale = 1.25; float autocvar_crosshair_hittest_showimpact; float autocvar_crosshair_per_weapon; float autocvar_crosshair_pickup; @@ -337,7 +338,7 @@ float autocvar_hud_panel_weapons_timeout; float autocvar_hud_panel_weapons_timeout_effect; float autocvar_hud_panel_weapons_timeout_fadebgmin; float autocvar_hud_panel_weapons_timeout_fadefgmin; -var float autocvar_hud_panel_weapons_timeout_speed_in = 0.25; +var float autocvar_hud_panel_weapons_timeout_speed_in = 0.25; var float autocvar_hud_panel_weapons_timeout_speed_out = 0.75; float autocvar_hud_progressbar_alpha; float autocvar_hud_showbinds; diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src index 114f0a5b5..1bae6f980 100644 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@ -14,6 +14,7 @@ Defs.qc ../warpzonelib/common.qh ../warpzonelib/client.qh +../common/playerstats.qh ../common/teams.qh ../common/util.qh ../common/test.qh @@ -102,6 +103,7 @@ noise.qc ../common/test.qc ../common/util.qc +../common/playerstats.qc ../common/notifications.qc ../common/command/markup.qc ../common/command/rpn.qc diff --git a/qcsrc/common/playerstats.qc b/qcsrc/common/playerstats.qc new file mode 100644 index 000000000..4f4a76cd1 --- /dev/null +++ b/qcsrc/common/playerstats.qc @@ -0,0 +1,693 @@ +#ifdef SVQC + +float playerstats_db; +string teamstats_last; +string playerstats_last; +string events_last; +.float playerstats_addedglobalinfo; +.string playerstats_id; + +void PlayerStats_Init() // initiated before InitGameplayMode so that scores are added properly +{ + string uri; + playerstats_db = -1; + playerstats_waitforme = TRUE; + uri = autocvar_g_playerstats_uri; + if(uri == "") + return; + playerstats_db = db_create(); + if(playerstats_db >= 0) + playerstats_waitforme = FALSE; // must wait for it at match end + + serverflags |= SERVERFLAG_PLAYERSTATS; + + PlayerStats_AddEvent(PLAYERSTATS_ALIVETIME); + PlayerStats_AddEvent(PLAYERSTATS_AVGLATENCY); + PlayerStats_AddEvent(PLAYERSTATS_WINS); + PlayerStats_AddEvent(PLAYERSTATS_MATCHES); + PlayerStats_AddEvent(PLAYERSTATS_JOINS); + PlayerStats_AddEvent(PLAYERSTATS_SCOREBOARD_VALID); + PlayerStats_AddEvent(PLAYERSTATS_SCOREBOARD_POS); + PlayerStats_AddEvent(PLAYERSTATS_RANK); + + // accuracy stats + entity w; + float i; + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + { + w = get_weaponinfo(i); + + PlayerStats_AddEvent(strcat("acc-", w.netname, "-hit")); + PlayerStats_AddEvent(strcat("acc-", w.netname, "-fired")); + + PlayerStats_AddEvent(strcat("acc-", w.netname, "-cnt-hit")); + PlayerStats_AddEvent(strcat("acc-", w.netname, "-cnt-fired")); + + PlayerStats_AddEvent(strcat("acc-", w.netname, "-frags")); + } + + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3); + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5); + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10); + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15); + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20); + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25); + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30); + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_BOTLIKE); + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD); + PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM); +} + +void PlayerStats_AddPlayer(entity e) +{ + string s; + + if(playerstats_db < 0) + return; + if(e.playerstats_id) + return; + + s = string_null; + if(e.crypto_idfp != "" && e.cvar_cl_allow_uidtracking == 1) + s = e.crypto_idfp; + else if(IS_BOT_CLIENT(e)) + s = sprintf("bot#%g#%s", skill, e.cleanname); + + if((s == "") || find(world, playerstats_id, s)) // already have one of the ID - next one can't be tracked then! + { + if(IS_BOT_CLIENT(e)) + s = sprintf("bot#%d", e.playerid); + else + s = sprintf("player#%d", e.playerid); + } + + e.playerstats_id = strzone(s); + + string key; + key = sprintf("%s:*", e.playerstats_id); + + string p; + p = db_get(playerstats_db, key); + if(p == "") + { + if(playerstats_last) + { + db_put(playerstats_db, key, playerstats_last); + strunzone(playerstats_last); + } + else + db_put(playerstats_db, key, "#"); + playerstats_last = strzone(e.playerstats_id); + } +} + +void PlayerStats_AddTeam(float t) +{ + if(playerstats_db < 0) + return; + + string key; + key = sprintf("%d", t); + + string p; + p = db_get(playerstats_db, key); + if(p == "") + { + if(teamstats_last) + { + db_put(playerstats_db, key, teamstats_last); + strunzone(teamstats_last); + } + else + db_put(playerstats_db, key, "#"); + teamstats_last = strzone(key); + } +} + +void PlayerStats_AddEvent(string event_id) +{ + if(playerstats_db < 0) + return; + + string key; + key = sprintf("*:%s", event_id); + + string p; + p = db_get(playerstats_db, key); + if(p == "") + { + if(events_last) + { + db_put(playerstats_db, key, events_last); + strunzone(events_last); + } + else + db_put(playerstats_db, key, "#"); + events_last = strzone(event_id); + } +} + +float PlayerStats_Event(entity e, string event_id, float value) +{ + if((e.playerstats_id == "") || playerstats_db < 0) + return 0; + + string key; + float val; + key = sprintf("%s:%s", e.playerstats_id, event_id); + val = stof(db_get(playerstats_db, key)); + val += value; + db_put(playerstats_db, key, ftos(val)); + return val; +} + +float PlayerStats_TeamScore(float t, string event_id, float value) +{ + if(playerstats_db < 0) + return 0; + + string key; + float val; + key = sprintf("team#%d:%s", t, event_id); + val = stof(db_get(playerstats_db, key)); + val += value; + db_put(playerstats_db, key, ftos(val)); + return val; +} + +/* + format spec: + + A collection of lines of the format SPACE NEWLINE, where + is always a single character. + + The following keys are defined: + + V: format version (always a fixed number) - this MUST be the first line! + #: comment (MUST be ignored by any parser) + R: release information on the server + G: game type + O: mod name (icon request) as in server browser + M: map name + I: match ID (see "matchid" in g_world.qc + S: "hostname" of the server + C: number of "unpure" cvar changes + U: UDP port number of the server + D: duration of the match + P: player ID of an existing player; this also sets the owner for all following "n", "e" and "t" lines (lower case!) + Q: team number of an existing team (format: team#NN); this also sets the owner for all following "e" lines (lower case!) + n: nickname of the player (optional) + t: team ID + i: player index + e: followed by an event name, a space, and the event count/score + event names can be: + alivetime: total playing time of the player + avglatency: average network latency compounded throughout the match + wins: number of games won (can only be set if matches is set) + matches: number of matches played to the end (not aborted by map switch) + joins: number of matches joined (always 1 unless player never played during the match) + scoreboardvalid: set to 1 if the player was there at the end of the match + total-: total score of that scoreboard item + scoreboard-: end-of-game score of that scoreboard item (can differ in non-team games) + achievement-: achievement counters (their "count" is usually 1 if nonzero at all) + kills-: number of kills against the indexed player + rank : rank of player + acc--hit: total damage dealt + acc--fired: total damage that all fired projectiles *could* have dealt + acc--cnt-hit: amount of shots that actually hit + acc--cnt-fired: amount of fired shots + acc--frags: amount of frags dealt by weapon + + Response format (not used yet): see https://gist.github.com/4284222 +*/ + +void PlayerStats_ready(entity fh, entity pass, float status) +{ + string t, tn; + string p, pn; + string e, en; + string nn, tt; + string s; + + switch(status) + { + case URL_READY_CANWRITE: + url_fputs(fh, "V 8\n"); +#ifdef WATERMARK + url_fputs(fh, sprintf("R %s\n", WATERMARK)); +#endif + url_fputs(fh, sprintf("G %s\n", GetGametype())); + url_fputs(fh, sprintf("O %s\n", modname)); + url_fputs(fh, sprintf("M %s\n", GetMapname())); + url_fputs(fh, sprintf("I %s\n", matchid)); + url_fputs(fh, sprintf("S %s\n", cvar_string("hostname"))); + url_fputs(fh, sprintf("C %d\n", cvar_purechanges_count)); + url_fputs(fh, sprintf("U %d\n", cvar("port"))); + url_fputs(fh, sprintf("D %f\n", max(0, time - game_starttime))); + if(teamplay) + { + for(t = teamstats_last; (tn = db_get(playerstats_db, sprintf("%d", stof(t)))) != ""; t = tn) + { + url_fputs(fh, sprintf("Q team#%s\n", t)); + for(e = events_last; (en = db_get(playerstats_db, sprintf("*:%s", e))) != ""; e = en) + { + float v; + v = stof(db_get(playerstats_db, sprintf("team#%d:%s", stof(t), e))); + if(v != 0) + url_fputs(fh, sprintf("e %s %g\n", e, v)); + } + } + } + for(p = playerstats_last; (pn = db_get(playerstats_db, sprintf("%s:*", p))) != ""; p = pn) + { + url_fputs(fh, sprintf("P %s\n", p)); + nn = db_get(playerstats_db, sprintf("%s:_playerid", p)); + if(nn != "") + url_fputs(fh, sprintf("i %s\n", nn)); + nn = db_get(playerstats_db, sprintf("%s:_netname", p)); + if(nn != "") + url_fputs(fh, sprintf("n %s\n", nn)); + if(teamplay) + { + tt = db_get(playerstats_db, sprintf("%s:_team", p)); + url_fputs(fh, sprintf("t %s\n", tt)); + } + for(e = events_last; (en = db_get(playerstats_db, sprintf("*:%s", e))) != ""; e = en) + { + float v; + v = stof(db_get(playerstats_db, sprintf("%s:%s", p, e))); + if(v != 0) + url_fputs(fh, sprintf("e %s %g\n", e, v)); + } + } + url_fputs(fh, "\n"); + url_fclose(fh); + break; + case URL_READY_CANREAD: + // url_fclose is processing, we got a response for writing the data + // this must come from HTTP + print("Got response from player stats server:\n"); + while((s = url_fgets(fh))) + print(" ", s, "\n"); + print("End of response.\n"); + url_fclose(fh); + break; + case URL_READY_CLOSED: + // url_fclose has finished + print("Player stats written\n"); + playerstats_waitforme = TRUE; + db_close(playerstats_db); + playerstats_db = -1; + break; + case URL_READY_ERROR: + default: + print("Player stats writing failed: ", ftos(status), "\n"); + playerstats_waitforme = TRUE; + if(playerstats_db >= 0) + { + db_close(playerstats_db); + playerstats_db = -1; + } + break; + } +} + +//#NO AUTOCVARS START +void PlayerStats_Shutdown() +{ + string uri; + + if(playerstats_db < 0) + return; + + uri = autocvar_g_playerstats_uri; + if(uri != "") + { + playerstats_waitforme = FALSE; + url_multi_fopen(uri, FILE_APPEND, PlayerStats_ready, world); + } + else + { + playerstats_waitforme = TRUE; + db_close(playerstats_db); + playerstats_db = -1; + } +} +//#NO AUTOCVARS END + +void PlayerStats_Accuracy(entity p) +{ + entity a, w; + a = p.accuracy; + float i; + + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + { + w = get_weaponinfo(i); + + PlayerStats_Event(p, strcat("acc-", w.netname, "-hit"), a.(accuracy_hit[i-1])); + PlayerStats_Event(p, strcat("acc-", w.netname, "-fired"), a.(accuracy_fired[i-1])); + + PlayerStats_Event(p, strcat("acc-", w.netname, "-cnt-hit"), a.(accuracy_cnt_hit[i-1])); + PlayerStats_Event(p, strcat("acc-", w.netname, "-cnt-fired"), a.(accuracy_cnt_fired[i-1])); + + PlayerStats_Event(p, strcat("acc-", w.netname, "-frags"), a.(accuracy_frags[i-1])); + } + //backtrace(strcat("adding player stat accuracy for ", p.netname, ".\n")); +} + +void PlayerStats_AddGlobalInfo(entity p) +{ + if(playerstats_db < 0) + return; + if((p.playerstats_id == "") || playerstats_db < 0) + return; + p.playerstats_addedglobalinfo = TRUE; + + // add global info! + if(p.alivetime) + { + PlayerStats_Event(p, PLAYERSTATS_ALIVETIME, time - p.alivetime); + p.alivetime = 0; + } + + db_put(playerstats_db, sprintf("%s:_playerid", p.playerstats_id), ftos(p.playerid)); + + if(p.cvar_cl_allow_uid2name == 1 || IS_BOT_CLIENT(p)) + db_put(playerstats_db, sprintf("%s:_netname", p.playerstats_id), p.netname); + + if(teamplay) + db_put(playerstats_db, sprintf("%s:_team", p.playerstats_id), ftos(p.team)); + + if(stof(db_get(playerstats_db, sprintf("%d:%s", p.playerstats_id, PLAYERSTATS_ALIVETIME))) > 0) + PlayerStats_Event(p, PLAYERSTATS_JOINS, 1); + + PlayerStats_Accuracy(p); + + if(IS_REAL_CLIENT(p)) + { + if(p.latency_cnt) + { + float latency = (p.latency_sum / p.latency_cnt); + if(latency) { PlayerStats_Event(p, PLAYERSTATS_AVGLATENCY, latency); } + } + } + + strunzone(p.playerstats_id); + p.playerstats_id = string_null; +} + +.float scoreboard_pos; +void PlayerStats_EndMatch(float finished) +{ + entity p; + PlayerScore_Sort(score_dummyfield, 0, 0, 0); + PlayerScore_Sort(scoreboard_pos, 1, 1, 1); + if(teamplay) + PlayerScore_TeamStats(); + FOR_EACH_CLIENT(p) + { + // add personal score rank + PlayerStats_Event(p, PLAYERSTATS_RANK, p.score_dummyfield); + + if(!p.scoreboard_pos) + continue; + + // scoreboard is valid! + PlayerStats_Event(p, PLAYERSTATS_SCOREBOARD_VALID, 1); + + // add scoreboard position + PlayerStats_Event(p, PLAYERSTATS_SCOREBOARD_POS, p.scoreboard_pos); + + // add scoreboard data + PlayerScore_PlayerStats(p); + + // if the match ended normally, add winning info + if(finished) + { + PlayerStats_Event(p, PLAYERSTATS_WINS, p.winning); + PlayerStats_Event(p, PLAYERSTATS_MATCHES, 1); + } + } +} + +#endif // SVQC + + + + +//// WIP -zykure ///////////////////////////////////////////////////// + + + + +float playerinfo_db; +string playerinfo_last; +string playerinfo_events_last; +.float playerid; + +void PlayerInfo_AddPlayer(entity e) +{ + if(playerinfo_db < 0) + return; + + string key; + key = sprintf("#%d:*", e.playerid); // TODO: use hashkey instead? + + string p; + p = db_get(playerinfo_db, key); + if(p == "") + { + if(playerinfo_last) + { + db_put(playerinfo_db, key, playerinfo_last); + strunzone(playerinfo_last); + } + else + db_put(playerinfo_db, key, "#"); + playerinfo_last = strzone(ftos(e.playerid)); + print(" Added player ", ftos(e.playerid), " to playerinfo_db\n");//DEBUG// + } +} + +void PlayerInfo_AddItem(entity e, string item_id, string val) +{ + if(playerinfo_db < 0) + return; + + string key; + key = sprintf("*:%s", item_id); + + string p; + p = db_get(playerinfo_db, key); + if(p == "") + { + if(playerinfo_events_last) + { + db_put(playerinfo_db, key, playerinfo_events_last); + strunzone(playerinfo_events_last); + } + else + db_put(playerinfo_db, key, "#"); + playerinfo_events_last = strzone(item_id); + } + + key = sprintf("#%d:%s", e.playerid, item_id); + db_put(playerinfo_db, key, val); + print(" Added item ", key, "=", val, " to playerinfo_db\n");//DEBUG// +} + +string PlayerInfo_GetItem(entity e, string item_id) +{ + if(playerinfo_db < 0) + return ""; + + string key; + key = sprintf("#%d:%s", e.playerid, item_id); + return db_get(playerinfo_db, key); +} + +string PlayerInfo_GetItemLocal(string item_id) +{ + entity p = spawn(); + p.playerid = 0; + return PlayerInfo_GetItem(p, item_id); +} + +void PlayerInfo_ready(entity fh, entity p, float status) +{ + float n; + string s; + + PlayerInfo_AddPlayer(p); + + switch(status) + { + case URL_READY_CANWRITE: + print("-- Sending data to player stats server\n"); + url_fputs(fh, "V 1\n"); +#ifdef WATERMARK + url_fputs(fh, sprintf("R %s\n", WATERMARK)); +#endif +#ifdef MENUQC + url_fputs(fh, sprintf("l %s\n", cvar_string("_menu_prvm_language"))); // language + url_fputs(fh, sprintf("c %s\n", cvar_string("_menu_prvm_country"))); // country + url_fputs(fh, sprintf("g %s\n", cvar_string("_menu_prvm_gender"))); // gender + url_fputs(fh, sprintf("n %s\n", cvar_string("_cl_name"))); // name + url_fputs(fh, sprintf("m %s %s\n", cvar_string("_cl_playermodel"), cvar_string("_cl_playerskin"))); // model/skin +#endif + url_fputs(fh, "\n"); + url_fclose(fh); + break; + case URL_READY_CANREAD: + print("-- Got response from player stats server:\n"); + string gametype = string_null; + while((s = url_fgets(fh))) + { + print(" ", s, "\n"); + + string key = string_null, value = string_null, data = string_null; + + n = tokenizebyseparator(s, " "); // key (value) data + if (n == 1) + continue; + else if (n == 2) + { + key = argv(0); + data = argv(1); + } + else if (n == 3) + { + key = argv(0); + value = argv(1); + data = argv(2); + } + else if (n == 4) + { + key = argv(0); + value = argv(1); + data = argv(2); + } + else + continue; + + if (data == "") + continue; + + if (key == "#") + // comment + continue; + else if (key == "V") + // version + PlayerInfo_AddItem(p, "_version", data); + else if (key == "R") + // watermark + PlayerInfo_AddItem(p, "_release", data); + else if (key == "T") + // timestamp + PlayerInfo_AddItem(p, "_time", data); + else if (key == "S") + // stats page URL + PlayerInfo_AddItem(p, "_statsurl", data); + else if (key == "P") + // player hashkey + PlayerInfo_AddItem(p, "_hashkey", data); + else if (key == "n") + // player nick + PlayerInfo_AddItem(p, "_playernick", data); + else if (key == "i") + // xonstats id + PlayerInfo_AddItem(p, "_playerid", data); + else if (key == "G") + gametype = data; + else if (key == "e" && value != "") + { + if (gametype == "") + PlayerInfo_AddItem(p, value, data); + else + PlayerInfo_AddItem(p, sprintf("%s/%s", gametype, value), data); + } + else + continue; + } + print("-- End of response.\n"); + url_fclose(fh); + break; + case URL_READY_CLOSED: + // url_fclose has finished + print("Player stats synchronized with server\n"); + break; + case URL_READY_ERROR: + default: + print("Receiving player stats failed: ", ftos(status), "\n"); + break; + } +} + +void PlayerInfo_Init() +{ + playerinfo_db = -1; + playerinfo_db = db_create(); +} + +#ifdef SVQC +void PlayerInfo_Basic(entity p) +{ + print("-- Getting basic PlayerInfo for player ",ftos(p.playerid)," (SVQC)\n"); + + if(playerinfo_db < 0) + return; + + string uri; + uri = playerinfo_uri; // FIXME + if(uri != "" && p.crypto_idfp != "") + { + uri = strcat(uri, "/elo/", uri_escape(p.crypto_idfp)); + print("Retrieving playerstats from URL: ", uri, "\n"); + url_single_fopen(uri, FILE_READ, PlayerInfo_ready, p); + } +} +#endif + +#ifdef MENUQC +void PlayerInfo_Details() +{ + print("-- Getting detailed PlayerInfo for local player (MENUQC)\n"); + + if(playerinfo_db < 0) + return; + + string uri; + uri = playerinfo_uri; // FIXME + if(uri != "" && crypto_getmyidstatus(0) > 0) + { + uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0))); + print("Retrieving playerstats from URL: ", uri, "\n"); + url_single_fopen(uri, FILE_APPEND, PlayerInfo_ready, world); + } +} +#endif + +#ifdef CSQC +/* + * FIXME - crypto_* builtin functions missing in CSQC (csprogsdefs.qc:885) +void PlayerInfo_Details() +{ + print("-- Getting detailed PlayerInfo for local player (CSQC)\n"); + + if(playerinfo_db < 0) + return; + + string uri; + uri = playerinfo_uri; // FIXME + if(uri != "" && crypto_getmyidstatus(0) > 0) + { + entity p = spawn(); + p.playerid = 0; // TODO: okay to use -1 for local player? or does local player already has an entity in MENUQC? + uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0))); + print("Retrieving playerstats from URL: ", uri, "\n"); + url_single_fopen(uri, FILE_READ, PlayerInfo_ready, p); + } +} +*/ +#endif diff --git a/qcsrc/common/playerstats.qh b/qcsrc/common/playerstats.qh new file mode 100644 index 000000000..bf3d4f2d7 --- /dev/null +++ b/qcsrc/common/playerstats.qh @@ -0,0 +1,81 @@ +#ifdef SVQC + +// time the player was alive and kicking +const string PLAYERSTATS_ALIVETIME = "alivetime"; +const string PLAYERSTATS_AVGLATENCY = "avglatency"; +const string PLAYERSTATS_WINS = "wins"; +const string PLAYERSTATS_MATCHES = "matches"; +const string PLAYERSTATS_JOINS = "joins"; +const string PLAYERSTATS_SCOREBOARD_VALID = "scoreboardvalid"; +const string PLAYERSTATS_RANK = "rank"; +const string PLAYERSTATS_SCOREBOARD_POS = "scoreboardpos"; + +const string PLAYERSTATS_TOTAL = "total-"; +const string PLAYERSTATS_SCOREBOARD = "scoreboard-"; + +const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3 = "achievement-kill-spree-3"; +const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5 = "achievement-kill-spree-5"; +const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10 = "achievement-kill-spree-10"; +const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15 = "achievement-kill-spree-15"; +const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20 = "achievement-kill-spree-20"; +const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25 = "achievement-kill-spree-25"; +const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30 = "achievement-kill-spree-30"; +const string PLAYERSTATS_ACHIEVEMENT_BOTLIKE = "achievement-botlike"; +const string PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD = "achievement-firstblood"; +const string PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM = "achievement-firstvictim"; + +// delay map switch until this is set +float playerstats_waitforme; + +// call at initialization +void PlayerStats_Init(); + +// add a new player +void PlayerStats_AddPlayer(entity e); + +// add a new team +void PlayerStats_AddTeam(float t); + +// add a new event +void PlayerStats_AddEvent(string event_id); + +// call on each event to track, or at player disconnect OR match end for "global stuff" +float PlayerStats_Event(entity e, string event_id, float value); + +// add a team score +float PlayerStats_TeamScore(float t, string event_id, float value); + +// call at game over +void PlayerStats_Shutdown(); // send stats to the server + +void PlayerStats_Accuracy(entity p); + +// call this whenever a player leaves +void PlayerStats_AddGlobalInfo(entity p); + +// call this at the end of the match +void PlayerStats_EndMatch(float finished); + +#endif //SVQC + + + + +//// WIP -zykure ///////////////////////////////////////////////////// + + +const string playerinfo_uri = "http://localhost:6543"; // FIXME + +string PlayerInfo_GetItem(entity e, string item_id); +string PlayerInfo_GetItemLocal(string item_id); + +void PlayerInfo_Init(); +#ifdef SVQC +void PlayerInfo_Basic(entity p); +#endif +#ifdef MENUQC +void PlayerInfo_Details(); +#endif +#ifdef CSQC +//void PlayerInfo_Details(); +#endif diff --git a/qcsrc/common/util.qc b/qcsrc/common/util.qc index 85efe4144..f9b96031a 100644 --- a/qcsrc/common/util.qc +++ b/qcsrc/common/util.qc @@ -148,7 +148,7 @@ void wordwrap_cb(string s, float l, void(string) callback) float dist_point_line(vector p, vector l0, vector ldir) { ldir = normalize(ldir); - + // remove the component in line direction p = p - (p * ldir) * ldir; @@ -264,7 +264,7 @@ void db_save(float db, string pFilename) { float fh, i, n; fh = fopen(pFilename, FILE_WRITE); - if(fh < 0) + if(fh < 0) { print(strcat("^1Can't write DB to ", pFilename)); return; @@ -408,6 +408,22 @@ void buf_save(float buf, string pFilename) fclose(fh); } +string format_time(float seconds) +{ + float days, hours, minutes; + seconds = floor(seconds + 0.5); + days = floor(seconds / 864000); + seconds -= days * 864000; + hours = floor(seconds / 36000); + seconds -= hours * 36000; + minutes = floor(seconds / 600); + seconds -= minutes * 600; + if (days > 0) + return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds); + else + return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds); +} + string mmsss(float tenths) { float minutes; @@ -458,7 +474,7 @@ string ScoreString(float pFlags, float pValue) valstr = TIME_ENCODED_TOSTRING(pValue); else valstr = ftos(pValue); - + return valstr; } @@ -664,7 +680,7 @@ string fixPriorityList(string order, float from, float to, float subtract, float neworder = strcat(neworder, ftos(w), " "); } } - + return substring(neworder, 0, strlen(neworder) - 1); } @@ -677,7 +693,7 @@ string mapPriorityList(string order, string(string) mapfunc) neworder = ""; for(i = 0; i < n; ++i) neworder = strcat(neworder, mapfunc(argv(i)), " "); - + return substring(neworder, 0, strlen(neworder) - 1); } @@ -702,7 +718,7 @@ string swapInPriorityList(string order, float i, float j) } return substring(s, 0, strlen(s) - 1); } - + return order; } @@ -1100,7 +1116,7 @@ vector rgb_to_hsv(vector rgb) hsv_y = 0; else hsv_y = 1 - mi/ma; - + return hsv; } @@ -1118,7 +1134,7 @@ vector rgb_to_hsl(vector rgb) ma = max(rgb_x, rgb_y, rgb_z); hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma); - + hsl_z = 0.5 * (mi + ma); if(mi == ma) hsl_y = 0; @@ -1126,7 +1142,7 @@ vector rgb_to_hsl(vector rgb) hsl_y = (ma - mi) / (2*hsl_z); else // if(hsl_z > 0.5) hsl_y = (ma - mi) / (2 - 2*hsl_z); - + return hsl; } @@ -1138,7 +1154,7 @@ vector hsl_to_rgb(vector hsl) maminusmi = hsl_y * 2 * hsl_z; else maminusmi = hsl_y * (2 - 2 * hsl_z); - + // hsl_z = 0.5 * mi + 0.5 * ma // maminusmi = - mi + ma mi = hsl_z - 0.5 * maminusmi; @@ -1199,7 +1215,7 @@ float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLe // terminate, as the range still halves each time - but nevertheless, it is // guaranteed that it finds ONE valid cutoff place (where "left" is in // range, and "right" is outside). - + // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4) // and decrease left on the basis of the chars detected of the truncated tag // Even if the ^xrgb tag is not complete/correct, left is decreased @@ -1227,7 +1243,7 @@ float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLe } } } - + return left; } @@ -1263,7 +1279,7 @@ float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_ // terminate, as the range still halves each time - but nevertheless, it is // guaranteed that it finds ONE valid cutoff place (where "left" is in // range, and "right" is outside). - + // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4) // and decrease left on the basis of the chars detected of the truncated tag // Even if the ^xrgb tag is not complete/correct, left is decreased @@ -1291,7 +1307,7 @@ float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_ } } } - + return left; } @@ -1338,7 +1354,7 @@ string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunc string s; s = getWrappedLine_remaining; - + if(w <= 0) { getWrappedLine_remaining = string_null; @@ -1384,7 +1400,7 @@ string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw) string s; s = getWrappedLine_remaining; - + if(w <= 0) { getWrappedLine_remaining = string_null; @@ -2323,12 +2339,12 @@ float InterpretBoolean(string input) case "true": case "on": return TRUE; - + case "no": case "false": case "off": return FALSE; - + default: return stof(input); } } @@ -2459,7 +2475,7 @@ float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor) /* // if this is the case, the possible zeros of the first derivative are outside // 0..1 - We can calculate this condition as condition + We can calculate this condition as condition if(se <= 3) return TRUE; */ @@ -2587,12 +2603,12 @@ void backtrace(string msg) string CCR(string input) { // See the autocvar declarations in util.qh for default values - + // foreground/normal colors - input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input); - input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input); - input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input); - input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input); + input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input); + input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input); + input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input); + input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input); // "kill" colors input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input); diff --git a/qcsrc/common/util.qh b/qcsrc/common/util.qh index d575bbfdd..f61df67a1 100644 --- a/qcsrc/common/util.qh +++ b/qcsrc/common/util.qh @@ -45,7 +45,7 @@ void ACCUMULATE_call(string func) // used for simplifying ACCUMULATE_FUNCTIONs #define SET_FIRST_OR_LAST(input,first,count) if(!input) { input = (first + count); } #define SET_FIELD_COUNT(field,first,count) if(!field) { field = (first + count); ++count; } -#define CHECK_MAX_COUNT(name,max,count,type) if(count == max) { error(strcat("Maximum ", type, " hit: ", #name, ": ", ftos(count), ".\n")); } +#define CHECK_MAX_COUNT(name,max,count,type) if(count > max) { error(strcat("Maximum ", type, " hit: ", #name, ": ", ftos(count), ".\n")); } // this returns a tempstring containing a copy of s with additional \n newlines added, it also replaces \n in the text with a real newline // NOTE: s IS allowed to be a tempstring @@ -94,10 +94,11 @@ void buf_save(float buf, string filename); // modulo function #ifndef MENUQC -float mod(float a, float b) { return a - (floor(a / b) * b); } +float mod(float a, float b) { return a - (floor(a / b) * b); } #endif #define TIME_TO_NTHS(t,n) floor((t) * (n) + 0.4) +string format_time(float seconds); string mmsss(float t); string mmssss(float t); diff --git a/qcsrc/dpdefs/menudefs.qc b/qcsrc/dpdefs/menudefs.qc index 2c21cd181..d97697325 100644 --- a/qcsrc/dpdefs/menudefs.qc +++ b/qcsrc/dpdefs/menudefs.qc @@ -18,6 +18,7 @@ void(float keynr, float ascii) m_keydown; void(float width, float height) m_draw; void(float mode) m_toggle; void() m_shutdown; +// optional: float(float) m_gethostcachecategory; ///////////////////////////////////////////////////////// // sys constants @@ -562,8 +563,11 @@ void resethostcachemasks(void) = #615; void sethostcachemaskstring(float mask, float fld, string str, float op) = #616; void sethostcachemasknumber(float mask, float fld, float num, float op) = #617; void resorthostcache(void) = #618; -void sethostcachesort(float fld, float descending) = #619; -void refreshhostcache(void) = #620; +float SLSF_DESCENDING = 1; +float SLSF_FAVORITES = 2; +float SLSF_CATEGORIES = 4; +void sethostcachesort(float fld, float slsf) = #619; +void refreshhostcache(...) = #620; // optional boolean argument "clear_list" float gethostcachenumber(float fld, float hostnr) = #621; float gethostcacheindexforkey(string key) = #622; void addwantedhostcachekey(string key) = #623; diff --git a/qcsrc/menu/classes.c b/qcsrc/menu/classes.c index f00971674..6bfdfb1c4 100644 --- a/qcsrc/menu/classes.c +++ b/qcsrc/menu/classes.c @@ -33,10 +33,11 @@ #include "xonotic/dialog_settings_video.c" #include "xonotic/dialog_settings_effects.c" #include "xonotic/dialog_settings_audio.c" +#include "xonotic/dialog_settings_game.c" #include "xonotic/dialog_settings_user.c" #include "xonotic/dialog_settings_misc.c" #include "xonotic/dialog_multiplayer.c" -#include "xonotic/dialog_multiplayer_playersetup.c" +#include "xonotic/dialog_multiplayer_profile.c" #include "xonotic/tabcontroller.c" #include "xonotic/textlabel.c" #include "xonotic/slider.c" @@ -78,15 +79,20 @@ #include "xonotic/dialog_singleplayer_winner.c" #include "xonotic/dialog_credits.c" #include "xonotic/credits.c" -#include "xonotic/dialog_multiplayer_playersetup_crosshair.c" -#include "xonotic/dialog_multiplayer_playersetup_hud.c" -#include "xonotic/dialog_multiplayer_playersetup_hudconfirm.c" -#include "xonotic/dialog_multiplayer_playersetup_model.c" -#include "xonotic/dialog_multiplayer_playersetup_view.c" -#include "xonotic/dialog_multiplayer_playersetup_weapons.c" +#include "xonotic/dialog_settings_game_crosshair.c" +#include "xonotic/dialog_settings_game_hud.c" +#include "xonotic/dialog_settings_game_hudconfirm.c" +#include "xonotic/dialog_settings_game_model.c" +#include "xonotic/dialog_settings_game_view.c" +#include "xonotic/dialog_settings_game_weapons.c" #include "xonotic/weaponslist.c" -#include "xonotic/dialog_multiplayer_demo.c" +#include "xonotic/dialog_multiplayer_media.c" +#include "xonotic/dialog_multiplayer_media_demo.c" #include "xonotic/demolist.c" +#include "xonotic/screenshotimage.c" +#include "xonotic/dialog_multiplayer_media_screenshot.c" +#include "xonotic/dialog_multiplayer_media_screenshot_viewer.c" +#include "xonotic/screenshotlist.c" #include "xonotic/colorpicker.c" #include "xonotic/colorpicker_string.c" #include "xonotic/cvarlist.c" @@ -110,3 +116,6 @@ #include "xonotic/dialog_hudpanel_physics.c" #include "xonotic/dialog_hudpanel_centerprint.c" #include "xonotic/slider_picmip.c" +#include "xonotic/dialog_settings_game_notification.c" +#include "xonotic/dialog_multiplayer_media_demo_democonfirm.c" +#include "xonotic/slider_particles.c" diff --git a/qcsrc/menu/command/menu_cmd.qc b/qcsrc/menu/command/menu_cmd.qc index c7499e58e..e89761cc3 100644 --- a/qcsrc/menu/command/menu_cmd.qc +++ b/qcsrc/menu/command/menu_cmd.qc @@ -116,5 +116,12 @@ void GameCommand(string theCommand) return; } + if(argv(0) == "debugstats") + { + PlayerInfo_Init(); + PlayerInfo_Details(); + return; + } + print(_("Invalid command. For a list of supported commands, try menu_cmd help.\n")); } diff --git a/qcsrc/menu/item/image.c b/qcsrc/menu/item/image.c index a7e63b0e1..8ae17dbcc 100644 --- a/qcsrc/menu/item/image.c +++ b/qcsrc/menu/item/image.c @@ -5,9 +5,22 @@ CLASS(Image) EXTENDS(Item) METHOD(Image, toString, string(entity)) METHOD(Image, resizeNotify, void(entity, vector, vector, vector, vector)) METHOD(Image, updateAspect, void(entity)) + METHOD(Image, initZoom, void(entity)) + METHOD(Image, setZoom, void(entity, float, float)) + METHOD(Image, drag_setStartPos, float(entity, vector)) + METHOD(Image, drag, float(entity, vector)) ATTRIB(Image, src, string, string_null) ATTRIB(Image, color, vector, '1 1 1') - ATTRIB(Image, forcedAspect, float, 0) + ATTRIB(Image, forcedAspect, float, 0) // special values: -1 keep image aspect ratio, -2 keep image size but bound to the containing box, -3 always keep image size + ATTRIB(Image, zoomBox, float, 0) // used by forcedAspect -2 when the image is larger than the containing box + ATTRIB(Image, zoomFactor, float, 1) + ATTRIB(Image, zoomOffset, vector, '0.5 0.5 0') + ATTRIB(Image, zoomSnapToTheBox, float, 1) // snap the zoomed in image to the box borders when zooming/dragging it + ATTRIB(Image, zoomTime, float, 0) + ATTRIB(Image, zoomLimitedByTheBox, float, 0) // forbids zoom if image would be larger than the containing box + ATTRIB(Image, zoomMax, float, 0) + ATTRIB(Image, start_zoomOffset, vector, '0 0 0') + ATTRIB(Image, start_coords, vector, '0 0 0') ATTRIB(Image, imgOrigin, vector, '0 0 0') ATTRIB(Image, imgSize, vector, '0 0 0') ENDCLASS(Image) @@ -22,14 +35,28 @@ void Image_configureImage(entity me, string path) { me.src = path; } +void Image_initZoom(entity me) +{ + me.zoomOffset = '0.5 0.5 0'; + me.zoomFactor = 1; + if (me.forcedAspect == -2) + me.zoomBox = -1; // calculate zoomBox at the first updateAspect call + if (me.zoomLimitedByTheBox) + me.zoomMax = -1; // calculate zoomMax at the first updateAspect call +} + void Image_draw(entity me) { + if(me.imgSize_x > 1 || me.imgSize_y > 1) + draw_SetClip(); draw_Picture(me.imgOrigin, me.src, me.imgSize, me.color, 1); + if(me.imgSize_x > 1 || me.imgSize_y > 1) + draw_ClearClip(); SUPER(Image).draw(me); } void Image_updateAspect(entity me) { - float asp; + float asp = 0; if(me.size_x <= 0 || me.size_y <= 0) return; if(me.forcedAspect == 0) @@ -39,26 +66,155 @@ void Image_updateAspect(entity me) } else { + vector sz = '0 0 0'; if(me.forcedAspect < 0) { - vector sz; sz = draw_PictureSize(me.src); asp = sz_x / sz_y; } else asp = me.forcedAspect; - if(me.size_x > asp * me.size_y) + + if(me.forcedAspect <= -2) + { + me.imgSize_x = sz_x / me.size_x; + me.imgSize_y = sz_y / me.size_y; + if(me.zoomBox < 0 && (me.imgSize_x > 1 || me.imgSize_y > 1)) + { + // image larger than the containing box, zoom it out to fit into the box + if(me.size_x > asp * me.size_y) + me.zoomBox = (me.size_y * asp / me.size_x) / me.imgSize_x; + else + me.zoomBox = (me.size_x / (asp * me.size_y)) / me.imgSize_y; + me.zoomFactor = me.zoomBox; + } + } + else + { + if(me.size_x > asp * me.size_y) + { + // x too large, so center x-wise + me.imgSize = eY + eX * (me.size_y * asp / me.size_x); + } + else + { + // y too large, so center y-wise + me.imgSize = eX + eY * (me.size_x / (asp * me.size_y)); + } + } + } + + if (me.zoomMax < 0) + { + if(me.zoomBox > 0) + me.zoomMax = me.zoomBox; + else + { + if(me.size_x > asp * me.size_y) + me.zoomMax = (me.size_y * asp / me.size_x) / me.imgSize_x; + else + me.zoomMax = (me.size_x / (asp * me.size_y)) / me.imgSize_y; + } + } + + if (me.zoomMax > 0 && me.zoomFactor > me.zoomMax) + me.zoomFactor = me.zoomMax; + if (me.zoomFactor) + me.imgSize = me.imgSize * me.zoomFactor; + + if(me.imgSize_x > 1 || me.imgSize_y > 1) + { + if(me.zoomSnapToTheBox) + { + if(me.imgSize_x > 1) + me.zoomOffset_x = bound(0.5/me.imgSize_x, me.zoomOffset_x, 1 - 0.5/me.imgSize_x); + else + me.zoomOffset_x = bound(1 - 0.5/me.imgSize_x, me.zoomOffset_x, 0.5/me.imgSize_x); + + if(me.imgSize_y > 1) + me.zoomOffset_y = bound(0.5/me.imgSize_y, me.zoomOffset_y, 1 - 0.5/me.imgSize_y); + else + me.zoomOffset_y = bound(1 - 0.5/me.imgSize_y, me.zoomOffset_y, 0.5/me.imgSize_y); + } + else + { + me.zoomOffset_x = bound(0, me.zoomOffset_x, 1); + me.zoomOffset_y = bound(0, me.zoomOffset_y, 1); + } + } + else + me.zoomOffset = '0.5 0.5 0'; + + me.imgOrigin_x = 0.5 - me.zoomOffset_x * me.imgSize_x; + me.imgOrigin_y = 0.5 - me.zoomOffset_y * me.imgSize_y; +} +float Image_drag_setStartPos(entity me, vector coords) +{ + //if(me.imgSize_x > 1 || me.imgSize_y > 1) // check disabled: mousewheel zoom may start from a non-zoomed-in image + { + me.start_zoomOffset = me.zoomOffset; + me.start_coords = coords; + } + return 1; +} +float Image_drag(entity me, vector coords) +{ + if(me.imgSize_x > 1 || me.imgSize_y > 1) + { + me.zoomOffset_x = me.start_zoomOffset_x + (me.start_coords_x - coords_x) / me.imgSize_x; + me.zoomOffset_y = me.start_zoomOffset_y + (me.start_coords_y - coords_y) / me.imgSize_y; + me.updateAspect(me); + } + return 1; +} +void Image_setZoom(entity me, float z, float atMousePosition) +{ + float prev_zoomFactor; + prev_zoomFactor = me.zoomFactor; + if (z < 0) // multiply by the current zoomFactor (but can also snap to real dimensions or to box) + { + me.zoomFactor *= -z; + float realSize_in_the_middle, boxSize_in_the_middle; + realSize_in_the_middle = ((prev_zoomFactor - 1) * (me.zoomFactor - 1) < 0); + boxSize_in_the_middle = (me.zoomBox > 0 && (prev_zoomFactor - me.zoomBox) * (me.zoomFactor - me.zoomBox) < 0); + if (realSize_in_the_middle && boxSize_in_the_middle) { - // x too large, so center x-wise - me.imgSize = eY + eX * (me.size_y * asp / me.size_x); + // snap to real dimensions or to box + if (prev_zoomFactor < me.zoomFactor) + me.zoomFactor = min(1, me.zoomBox); + else + me.zoomFactor = max(1, me.zoomBox); } + else if (realSize_in_the_middle) + me.zoomFactor = 1; // snap to real dimensions + else if (boxSize_in_the_middle) + me.zoomFactor = me.zoomBox; // snap to box + } + else if (z == 0) // reset (no zoom) + { + if (me.zoomBox > 0) + me.zoomFactor = me.zoomBox; else + me.zoomFactor = 1; + } + else // directly set + me.zoomFactor = z; + me.zoomFactor = bound(1/16, me.zoomFactor, 16); + if (me.zoomMax > 0 && me.zoomFactor > me.zoomMax) + me.zoomFactor = me.zoomMax; + if (prev_zoomFactor != me.zoomFactor) + { + me.zoomTime = time; + if (atMousePosition) { - // y too large, so center y-wise - me.imgSize = eX + eY * (me.size_x / (asp * me.size_y)); + me.zoomOffset_x = me.start_zoomOffset_x + (me.start_coords_x - 0.5) / me.imgSize_x; + me.zoomOffset_y = me.start_zoomOffset_y + (me.start_coords_y - 0.5) / me.imgSize_y; + // updateAspect will reset zoomOffset to '0.5 0.5 0' if + // with this zoomFactor the image will not be zoomed in + // (updateAspect will check the new values of imgSize). } - me.imgOrigin = '0.5 0.5 0' - 0.5 * me.imgSize; } + me.updateAspect(me); } void Image_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize) { diff --git a/qcsrc/menu/menu.qc b/qcsrc/menu/menu.qc index 0f2a525aa..99e8ea7d6 100644 --- a/qcsrc/menu/menu.qc +++ b/qcsrc/menu/menu.qc @@ -77,6 +77,8 @@ void m_init() CALL_ACCUMULATED_FUNCTION(RegisterWeapons); CALL_ACCUMULATED_FUNCTION(RegisterGametypes); + RegisterSLCategories(); + float ddsload = cvar("r_texture_dds_load"); float texcomp = cvar("gl_texturecompression"); updateCompression(); @@ -91,6 +93,8 @@ void m_init() m_hide(); cvar_set("_menu_initialized", "1"); } + + PlayerInfo_Details(); } const float MENU_ASPECT = 1.25; // 1280x1024 @@ -441,7 +445,7 @@ float m_allocatetooltipbox(vector pos) v = pos + avoidplus; if(m_testtooltipbox(v)) return TRUE; - + // bottom center v_x = pos_x - menuTooltipSize_x * 0.5; if(m_testtooltipbox(v)) @@ -461,12 +465,12 @@ float m_allocatetooltipbox(vector pos) v_x = pos_x - menuTooltipSize_x * 0.5; if(m_testtooltipbox(v)) return TRUE; - + // top right v_x = pos_x + avoidplus_x; if(m_testtooltipbox(v)) return TRUE; - + return FALSE; } entity m_findtooltipitem(entity root, vector pos) diff --git a/qcsrc/menu/progs.src b/qcsrc/menu/progs.src index 3036278c1..f0e0bd748 100644 --- a/qcsrc/menu/progs.src +++ b/qcsrc/menu/progs.src @@ -14,6 +14,7 @@ config.qh oo/base.h +../common/playerstats.qh ../common/teams.qh ../common/constants.qh ../common/mapinfo.qh @@ -39,6 +40,7 @@ oo/implementation.h ../common/util.qc ../common/test.qc +../common/playerstats.qc ../common/command/markup.qc ../common/command/rpn.qc ../common/command/generic.qc diff --git a/qcsrc/menu/skin-customizables.inc b/qcsrc/menu/skin-customizables.inc index 6b5cac042..4c0a08a8c 100644 --- a/qcsrc/menu/skin-customizables.inc +++ b/qcsrc/menu/skin-customizables.inc @@ -70,6 +70,7 @@ SKINBEGIN SKINVECTOR(COLOR_DIALOG_HUD, '1 0.7 0.7'); SKINVECTOR(COLOR_DIALOG_SERVERINFO, '0.7 0.7 1'); SKINVECTOR(COLOR_DIALOG_CVARS, '1 0 0'); + SKINVECTOR(COLOR_DIALOG_SCREENSHOTVIEWER, '0.7 0.7 1'); SKINVECTOR(COLOR_DIALOG_HUDCONFIRM, '1 0 0'); // nexposee positions of windows (they are the scale transformation diff --git a/qcsrc/menu/xonotic/dialog_multiplayer.c b/qcsrc/menu/xonotic/dialog_multiplayer.c index 2c86f79c3..7d234c7fe 100644 --- a/qcsrc/menu/xonotic/dialog_multiplayer.c +++ b/qcsrc/menu/xonotic/dialog_multiplayer.c @@ -13,15 +13,17 @@ ENDCLASS(XonoticMultiplayerDialog) void XonoticMultiplayerDialog_fill(entity me) { entity mc, e; - mc = makeXonoticTabController(me.rows - 2); + mc = makeXonoticTabController(me.rows - 1); me.TR(me); me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Servers"), makeXonoticServerListTab())); me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Create"), makeXonoticServerCreateTab())); - me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Demos"), makeXonoticDemoBrowserTab())); - me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Player Setup"), makeXonoticPlayerSettingsTab())); + //me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Demos"), makeXonoticDemoBrowserTab())); + //me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Screenshots"), makeXonoticScreenshotBrowserTab())); + //me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Players"), makeXonoticDemoBrowserTab())); + me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Media"), makeXonoticMediaTab())); + me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Profile"), makeXonoticProfileTab())); me.TR(me); - me.TR(me); - me.TD(me, me.rows - 2, me.columns, mc); + me.TD(me, me.rows - 1, me.columns, mc); } #endif diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_create.c b/qcsrc/menu/xonotic/dialog_multiplayer_create.c index 3fc6f31fe..6d0bd9062 100644 --- a/qcsrc/menu/xonotic/dialog_multiplayer_create.c +++ b/qcsrc/menu/xonotic/dialog_multiplayer_create.c @@ -4,7 +4,7 @@ CLASS(XonoticServerCreateTab) EXTENDS(XonoticTab) METHOD(XonoticServerCreateTab, gameTypeChangeNotify, void(entity)) ATTRIB(XonoticServerCreateTab, title, string, _("Create")) ATTRIB(XonoticServerCreateTab, intendedWidth, float, 0.9) - ATTRIB(XonoticServerCreateTab, rows, float, 22) + ATTRIB(XonoticServerCreateTab, rows, float, 23) ATTRIB(XonoticServerCreateTab, columns, float, 6.2) // added extra .2 for center space ATTRIB(XonoticServerCreateTab, mapListBox, entity, NULL) @@ -30,6 +30,7 @@ void XonoticServerCreateTab_fill(entity me) { entity e, e0; + me.TR(me); me.TR(me); me.TD(me, 1, 3, e = makeXonoticTextLabel(0, _("Game type:"))); me.TR(me); @@ -98,12 +99,12 @@ void XonoticServerCreateTab_fill(entity me) e.onClickEntity = main.advancedDialog; main.advancedDialog.refilterEntity = me.mapListBox; - me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn); + me.gotoRC(me, 1, 3.2); me.setFirstColumn(me, me.currentColumn); me.mapListBox = makeXonoticMapList(); me.TD(me, 1, 3, e = makeXonoticTextLabel(0, _("Map list:"))); makeCallback(e, me.mapListBox, me.mapListBox.refilterCallback); me.TR(me); - me.TD(me, me.rows - 4, 3, me.mapListBox); + me.TD(me, me.rows - 5, 3, me.mapListBox); me.gotoRC(me, me.rows - 3, 3.5); me.TDempty(me, 0.25); me.TD(me, 1, 1.125, e = makeXonoticButton(_("Select all"), '0 0 0')); diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_demo.c b/qcsrc/menu/xonotic/dialog_multiplayer_demo.c deleted file mode 100644 index 84115f1ed..000000000 --- a/qcsrc/menu/xonotic/dialog_multiplayer_demo.c +++ /dev/null @@ -1,47 +0,0 @@ -#ifdef INTERFACE -CLASS(XonoticDemoBrowserTab) EXTENDS(XonoticTab) - METHOD(XonoticDemoBrowserTab, fill, void(entity)) - ATTRIB(XonoticDemoBrowserTab, title, string, _("Demo")) - ATTRIB(XonoticDemoBrowserTab, intendedWidth, float, 0.9) - ATTRIB(XonoticDemoBrowserTab, rows, float, 22) - ATTRIB(XonoticDemoBrowserTab, columns, float, 4) - ATTRIB(XonoticDemoBrowserTab, name, string, "DemoBrowser") -ENDCLASS(XonoticDemoBrowserTab) -entity makeXonoticDemoBrowserTab(); -#endif - -#ifdef IMPLEMENTATION -entity makeXonoticDemoBrowserTab() -{ - entity me; - me = spawnXonoticDemoBrowserTab(); - me.configureDialog(me); - return me; -} -void XonoticDemoBrowserTab_fill(entity me) -{ - entity e, dlist; - - me.TR(me); - me.TD(me, 1, 4, e = makeXonoticCheckBox(0, "cl_autodemo", _("Automatically record demos while playing"))); - me.TR(me); - me.TR(me); - me.TD(me, 1, 0.5, e = makeXonoticTextLabel(0, _("Filter:"))); - me.TD(me, 1, 3.5, e = makeXonoticInputBox(0, string_null)); - dlist = makeXonoticDemoList(); - e.onChange = DemoList_Filter_Change; - e.onChangeEntity = dlist; - dlist.controlledTextbox = e; - - me.TR(me); - me.TD(me, me.rows - 4, me.columns, dlist); - - me.gotoRC(me, me.rows - 1, 0); - me.TD(me, 1, me.columns / 2, e = makeXonoticButton(_("Timedemo"), '0 0 0')); - e.onClick = TimeDemo_Click; - e.onClickEntity = dlist; - me.TD(me, 1, me.columns / 2, e = makeXonoticButton(ZCTX(_("DEMO^Play")), '0 0 0')); - e.onClick = StartDemo_Click; - e.onClickEntity = dlist; -} -#endif diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_join.c b/qcsrc/menu/xonotic/dialog_multiplayer_join.c index 02d3b4102..4636ebbdb 100644 --- a/qcsrc/menu/xonotic/dialog_multiplayer_join.c +++ b/qcsrc/menu/xonotic/dialog_multiplayer_join.c @@ -3,7 +3,7 @@ CLASS(XonoticServerListTab) EXTENDS(XonoticTab) METHOD(XonoticServerListTab, fill, void(entity)) ATTRIB(XonoticServerListTab, title, string, _("Join")) ATTRIB(XonoticServerListTab, intendedWidth, float, 0.9) - ATTRIB(XonoticServerListTab, rows, float, 22) + ATTRIB(XonoticServerListTab, rows, float, 23) ATTRIB(XonoticServerListTab, columns, float, 6.5) ENDCLASS(XonoticServerListTab) entity makeXonoticServerListTab(); @@ -24,12 +24,17 @@ void XonoticServerListTab_fill(entity me) slist = makeXonoticServerList(); - me.TR(me); - me.TD(me, 1, 0.4, e = makeXonoticTextLabel(0, _("Filter:"))); - me.TD(me, 1, me.columns - 0.6 * 3 - 0.4, e = makeXonoticInputBox(0, string_null)); + me.gotoRC(me, 0.5, 0); + me.TD(me, 1, 0.6, e = makeXonoticTextLabel(1, _("Filter:"))); + me.TD(me, 1, 2.8, e = makeXonoticInputBox(0, string_null)); e.onChange = ServerList_Filter_Change; e.onChangeEntity = slist; slist.controlledTextbox = e; + + me.gotoRC(me, 0.5, 3.6); + me.TD(me, 1, 0.9, e = makeXonoticCheckBox(0, "menu_slist_categories", ZCTX(_("SRVS^Categories")))); + e.onClickEntity = slist; + e.onClick = ServerList_Categories_Click; me.TD(me, 1, 0.6, e = makeXonoticCheckBox(0, "menu_slist_showempty", ZCTX(_("SRVS^Empty")))); slist.filterShowEmpty = e.checked; e.onClickEntity = slist; @@ -40,14 +45,14 @@ void XonoticServerListTab_fill(entity me) e.onClick = ServerList_ShowFull_Click; me.TD(me, 1, 0.6, e = makeXonoticCheckBox(0, "net_slist_pause", _("Pause"))); - me.TR(me); + me.gotoRC(me, 2, 0); me.TD(me, 1, 1, slist.sortButton1 = makeXonoticButton(string_null, '0 0 0')); me.TD(me, 1, 1, slist.sortButton2 = makeXonoticButton(string_null, '0 0 0')); me.TD(me, 1, 1, slist.sortButton3 = makeXonoticButton(string_null, '0 0 0')); me.TD(me, 1, 1, slist.sortButton4 = makeXonoticButton(string_null, '0 0 0')); me.TD(me, 1, 1, slist.sortButton5 = makeXonoticButton(string_null, '0 0 0')); me.TR(me); - me.TD(me, me.rows - 4, me.columns, slist); + me.TD(me, me.rows - 5, me.columns, slist); me.gotoRC(me, me.rows - 2, 0); me.TD(me, 1, 0.6, e = makeXonoticTextLabel(0, _("Address:"))); diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_media.c b/qcsrc/menu/xonotic/dialog_multiplayer_media.c new file mode 100644 index 000000000..9c47ef58d --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_multiplayer_media.c @@ -0,0 +1,33 @@ +#ifdef INTERFACE +CLASS(XonoticMediaTab) EXTENDS(XonoticTab) + METHOD(XonoticMediaTab, fill, void(entity)) + ATTRIB(XonoticMediaTab, title, string, _("Demo")) + ATTRIB(XonoticMediaTab, intendedWidth, float, 0.9) + ATTRIB(XonoticMediaTab, rows, float, 23) + ATTRIB(XonoticMediaTab, columns, float, 2) + ATTRIB(XonoticMediaTab, name, string, "Media") +ENDCLASS(XonoticMediaTab) +entity makeXonoticMediaTab(); +#endif + +#ifdef IMPLEMENTATION +entity makeXonoticMediaTab() +{ + entity me; + me = spawnXonoticMediaTab(); + me.configureDialog(me); + return me; +} +void XonoticMediaTab_fill(entity me) +{ + entity mc, e; + mc = makeXonoticTabController(me.rows - 2); + + me.gotoRC(me, 0.5, 0); + me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Demos"), makeXonoticDemoBrowserTab())); + me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Screenshots"), makeXonoticScreenshotBrowserTab())); + + me.gotoRC(me, 3, 0); + me.TD(me, me.rows - 2, me.columns, mc); +} +#endif diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_media_demo.c b/qcsrc/menu/xonotic/dialog_multiplayer_media_demo.c new file mode 100644 index 000000000..0004ce477 --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_multiplayer_media_demo.c @@ -0,0 +1,62 @@ +#ifdef INTERFACE +CLASS(XonoticDemoBrowserTab) EXTENDS(XonoticTab) + METHOD(XonoticDemoBrowserTab, fill, void(entity)) + ATTRIB(XonoticDemoBrowserTab, title, string, _("Demo")) + ATTRIB(XonoticDemoBrowserTab, intendedWidth, float, 0.9) + ATTRIB(XonoticDemoBrowserTab, rows, float, 21) + ATTRIB(XonoticDemoBrowserTab, columns, float, 6.5) + ATTRIB(XonoticDemoBrowserTab, name, string, "DemoBrowser") +ENDCLASS(XonoticDemoBrowserTab) +entity makeXonoticDemoBrowserTab(); +void Demo_Confirm(entity me, entity btn); +#endif + +#ifdef IMPLEMENTATION +void DemoConfirm_Check_Gamestatus(entity me, entity btn) +{ + if not(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)) // we're not in a match, lets watch the demo + { + //TimeDemo_Click; + //StartDemo_Click; + return; + } + else // already in a match, player has to confirm + { + Demo_Confirm(me, btn); + } +} + +entity makeXonoticDemoBrowserTab() +{ + entity me; + me = spawnXonoticDemoBrowserTab(); + me.configureDialog(me); + return me; +} +void XonoticDemoBrowserTab_fill(entity me) +{ + entity e, dlist; + + me.TR(me); + me.TD(me, 1, 4, e = makeXonoticCheckBox(0, "cl_autodemo", _("Automatically record demos while playing"))); + me.TR(me); + me.TR(me); + me.TD(me, 1, 0.5, e = makeXonoticTextLabel(0, _("Filter:"))); + me.TD(me, 1, 6, e = makeXonoticInputBox(0, string_null)); + dlist = makeXonoticDemoList(); + e.onChange = DemoList_Filter_Change; + e.onChangeEntity = dlist; + dlist.controlledTextbox = e; + + me.TR(me); + me.TD(me, me.rows - 4, me.columns, dlist); + + me.gotoRC(me, me.rows - 1, 0); + me.TD(me, 1, me.columns / 2, e = makeXonoticButton(_("Timedemo"), '0 0 0')); + e.onClick = DemoConfirm_Check_Gamestatus; + e.onClickEntity = dlist; + me.TD(me, 1, me.columns / 2, e = makeXonoticButton(ZCTX(_("DEMO^Play")), '0 0 0')); + e.onClick = DemoConfirm_Check_Gamestatus; + e.onClickEntity = dlist; +} +#endif diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_media_demo_democonfirm.c b/qcsrc/menu/xonotic/dialog_multiplayer_media_demo_democonfirm.c new file mode 100644 index 000000000..c51e065bb --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_multiplayer_media_demo_democonfirm.c @@ -0,0 +1,39 @@ +#ifdef INTERFACE +CLASS(XonoticDemoConfirmDialog) EXTENDS(XonoticDialog) + METHOD(XonoticDemoConfirmDialog, fill, void(entity)) + ATTRIB(XonoticDemoConfirmDialog, title, string, _("Disconnect")) + ATTRIB(XonoticDemoConfirmDialog, color, vector, SKINCOLOR_DIALOG_HUDCONFIRM) + ATTRIB(XonoticDemoConfirmDialog, intendedWidth, float, 0.5) + ATTRIB(XonoticDemoConfirmDialog, rows, float, 4) + ATTRIB(XonoticDemoConfirmDialog, columns, float, 2) +ENDCLASS(XonoticDemoConfirmDialog) +#endif + +#ifdef IMPLEMENTATION +void Demo_Confirm(entity me, entity btn) +{ + if not(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)) + //me.onClick = TimeDemo_Click; + me.onClick = StartDemo_Click; + else + localcmd("togglemenu 0\n"); +} + +void XonoticDemoConfirmDialog_fill(entity me) +{ + entity e; + + me.TR(me); + me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("Playing a demo will disconnect you from the current match."))); + me.TR(me); + me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("Do you really wish to disconnect now?"))); + me.TR(me); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticButton(ZCTX(_("DMCNFRM^Yes")), '1 0 0')); + e.onClick = Demo_Confirm; + e.onClickEntity = me; + me.TD(me, 1, 1, e = makeXonoticButton(ZCTX(_("DMCNFRM^No")), '0 1 0')); + e.onClick = Dialog_Close; + e.onClickEntity = me; +} +#endif diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_media_screenshot.c b/qcsrc/menu/xonotic/dialog_multiplayer_media_screenshot.c new file mode 100644 index 000000000..d4626d4c4 --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_multiplayer_media_screenshot.c @@ -0,0 +1,65 @@ +#ifdef INTERFACE +CLASS(XonoticScreenshotBrowserTab) EXTENDS(XonoticTab) + METHOD(XonoticScreenshotBrowserTab, fill, void(entity)) + ATTRIB(XonoticScreenshotBrowserTab, title, string, "Screenshot") + ATTRIB(XonoticScreenshotBrowserTab, intendedWidth, float, 1) + ATTRIB(XonoticScreenshotBrowserTab, rows, float, 21) + ATTRIB(XonoticScreenshotBrowserTab, columns, float, 6.5) + ATTRIB(XonoticScreenshotBrowserTab, name, string, "ScreenshotBrowser") + + METHOD(XonoticScreenshotBrowserTab, loadPreviewScreenshot, void(entity, string)) + ATTRIB(XonoticScreenshotBrowserTab, screenshotImage, entity, NULL) + ATTRIB(XonoticScreenshotBrowserTab, currentScrPath, string, string_null) +ENDCLASS(XonoticScreenshotBrowserTab) +entity makeXonoticScreenshotBrowserTab(); +#endif + +#ifdef IMPLEMENTATION +entity makeXonoticScreenshotBrowserTab() +{ + entity me; + me = spawnXonoticScreenshotBrowserTab(); + me.configureDialog(me); + return me; +} +void XonoticScreenshotBrowserTab_loadPreviewScreenshot(entity me, string scrImage) +{ + if (me.currentScrPath == scrImage) + return; + if (me.currentScrPath) + strunzone(me.currentScrPath); + me.currentScrPath = strzone(scrImage); + me.screenshotImage.load(me.screenshotImage, me.currentScrPath); +} +void XonoticScreenshotBrowserTab_fill(entity me) +{ + entity e, slist; + slist = makeXonoticScreenshotList(); + float slist_height = me.rows - 2; + me.TR(me); + me.TD(me, 1, 0.5, e = makeXonoticTextLabel(0, "Filter:")); + me.TD(me, 1, me.columns - 1.5, e = makeXonoticInputBox(0, string_null)); + e.onChange = ScreenshotList_Filter_Would_Change; + e.onChangeEntity = slist; + slist.screenshotViewerDialog = main.screenshotViewerDialog; + main.screenshotViewerDialog.scrList = slist; + me.TD(me, 1, 1, e = makeXonoticButton(_("Refresh"), '0 0 0')); + e.onClick = ScreenshotList_Refresh_Click; + e.onClickEntity = slist; + me.TR(me); + me.TD(me, slist_height, me.columns, slist); + + me.gotoRC(me, slist_height + 1, 0); + me.TD(me, 1, me.columns, e = makeXonoticButton(_("Open in the viewer"), '0 0 0')); + e.onClick = StartScreenshot_Click; + e.onClickEntity = slist; +/* + me.TR(me); + me.TD(me, me.rows - me.currentRow, me.columns, e = makeXonoticScreenshotImage()); + e.showTitle = 0; + me.screenshotImage = e; + slist.screenshotPreview = e; + slist.screenshotBrowserDialog = me; +*/ +} +#endif diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_media_screenshot_viewer.c b/qcsrc/menu/xonotic/dialog_multiplayer_media_screenshot_viewer.c new file mode 100644 index 000000000..146f496be --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_multiplayer_media_screenshot_viewer.c @@ -0,0 +1,171 @@ +#ifdef INTERFACE +CLASS(XonoticScreenshotViewerDialog) EXTENDS(XonoticDialog) + METHOD(XonoticScreenshotViewerDialog, fill, void(entity)) + METHOD(XonoticScreenshotViewerDialog, keyDown, float(entity, float, float, float)) + METHOD(XonoticScreenshotViewerDialog, loadScreenshot, void(entity, string)) + METHOD(XonoticScreenshotViewerDialog, close, void(entity)) + ATTRIB(XonoticScreenshotViewerDialog, title, string, "Screenshot Viewer") + ATTRIB(XonoticScreenshotViewerDialog, name, string, "ScreenshotViewer") + ATTRIB(XonoticScreenshotViewerDialog, intendedWidth, float, 1) + ATTRIB(XonoticScreenshotViewerDialog, rows, float, 25) + ATTRIB(XonoticScreenshotViewerDialog, columns, float, 4) + ATTRIB(XonoticScreenshotViewerDialog, color, vector, SKINCOLOR_DIALOG_SCREENSHOTVIEWER) + ATTRIB(XonoticScreenshotViewerDialog, scrList, entity, NULL) + ATTRIB(XonoticScreenshotViewerDialog, screenshotImage, entity, NULL) + ATTRIB(XonoticScreenshotViewerDialog, slideShowButton, entity, NULL) + ATTRIB(XonoticScreenshotViewerDialog, currentScrPath, string, string_null) +ENDCLASS(XonoticScreenshotViewerDialog) +#endif + +#ifdef IMPLEMENTATION +float music_playlist_index_backup; +void XonoticScreenshotViewerDialog_loadScreenshot(entity me, string scrImage) +{ + // disable music as it can lag depending on image loading time + if(!cvar("menu_screenshotviewer_enablemusic")) + if(cvar("music_playlist_index") != 999) // if the playlist isn't paused + { + // pause music + if(cvar("music_playlist_index") != -1) + { + music_playlist_index_backup = cvar("music_playlist_index"); + cvar_set("music_playlist_sampleposition0", "0"); + cvar_set("music_playlist_index", "999"); + } + else + localcmd("\ncd pause\n"); + } + + if (me.currentScrPath == scrImage) + return; + if (me.currentScrPath) + strunzone(me.currentScrPath); + me.currentScrPath = strzone(scrImage); + me.screenshotImage.load(me.screenshotImage, me.currentScrPath); + me.frame.setText(me.frame, me.screenshotImage.screenshotTitle); +} +void prevScreenshot_Click(entity btn, entity me) +{ + me.scrList.goScreenshot(me.scrList, -1); +} +void nextScreenshot_Click(entity btn, entity me) +{ + me.scrList.goScreenshot(me.scrList, +1); +} +void increaseZoom_Click(entity btn, entity me) +{ + me.screenshotImage.setZoom(me.screenshotImage, -2, FALSE); +} +void decreaseZoom_Click(entity btn, entity me) +{ + me.screenshotImage.setZoom(me.screenshotImage, -1/2, FALSE); +} +void resetZoom_Click(entity btn, entity me) +{ + me.screenshotImage.setZoom(me.screenshotImage, 0, FALSE); +} +void toggleSlideShow_Click(entity btn, entity me) +{ + if (me.slideShowButton.forcePressed) + { + me.scrList.stopSlideShow(me.scrList); + me.slideShowButton.forcePressed = 0; + } + else + { + me.scrList.startSlideShow(me.scrList); + me.slideShowButton.forcePressed = 1; + } +} +float XonoticScreenshotViewerDialog_keyDown(entity me, float key, float ascii, float shift) +{ + switch(key) + { + case K_KP_LEFTARROW: + case K_LEFTARROW: + me.scrList.goScreenshot(me.scrList, -1); + return 1; + case K_KP_RIGHTARROW: + case K_RIGHTARROW: + me.scrList.goScreenshot(me.scrList, +1); + return 1; + case K_KP_ENTER: + case K_ENTER: + case K_SPACE: + // we cannot use SPACE/ENTER directly, as in a dialog they are needed + // to press buttons while browsing with only the keyboard + if (shift & S_CTRL) + { + toggleSlideShow_Click(world, me); + return 1; + } + return SUPER(XonoticScreenshotViewerDialog).keyDown(me, key, ascii, shift); + default: + if (key == K_MWHEELUP || ascii == '+') + { + me.screenshotImage.setZoom(me.screenshotImage, -2, (key == K_MWHEELUP)); + return 1; + } + else if (key == K_MWHEELDOWN || ascii == '-') + { + me.screenshotImage.setZoom(me.screenshotImage, -1/2, (key == K_MWHEELDOWN)); + return 1; + } + if (me.scrList.keyDown(me.scrList, key, ascii, shift)) + { + // keyDown has already changed the selected item + me.scrList.goScreenshot(me.scrList, 0); + return 1; + } + return SUPER(XonoticScreenshotViewerDialog).keyDown(me, key, ascii, shift); + } +} +void XonoticScreenshotViewerDialog_close(entity me) +{ + // resume music + if(!cvar("menu_screenshotviewer_enablemusic")) + if(cvar("music_playlist_index") == 999) + { + cvar_set("music_playlist_index", ftos(music_playlist_index_backup)); + } + else + localcmd("\ncd resume\n"); + + me.scrList.stopSlideShow(me.scrList); + me.slideShowButton.forcePressed = 0; + SUPER(XonoticScreenshotViewerDialog).close(me); +} +void XonoticScreenshotViewerDialog_fill(entity me) +{ + entity e; + me.TR(me); + me.TD(me, me.rows - 1, me.columns, e = makeXonoticScreenshotImage()); + e.showTitle = 0; // dialog title is enough + me.screenshotImage = e; + me.gotoRC(me, me.rows - 1, 0); + me.TDempty(me, 1/20 * me.columns); + me.TD(me, 1, 1/20 * me.columns, e = makeXonoticButton("-", '0 0 0')); + e.onClick = decreaseZoom_Click; + e.onClickEntity = me; + me.TD(me, 1, 1/20 * me.columns, e = makeXonoticButton("+", '0 0 0')); + e.onClick = increaseZoom_Click; + e.onClickEntity = me; + me.TD(me, 1, 2/20 * me.columns, e = makeXonoticButton(_("Reset"), '0 0 0')); + e.onClick = resetZoom_Click; + e.onClickEntity = me; + + me.TDempty(me, 2/20 * me.columns); + me.TD(me, 1, 3/20 * me.columns, e = makeXonoticButton(_("Previous"), '0 0 0')); + e.onClick = prevScreenshot_Click; + e.onClickEntity = me; + me.TD(me, 1, 3/20 * me.columns, e = makeXonoticButton(_("Next"), '0 0 0')); + e.onClick = nextScreenshot_Click; + e.onClickEntity = me; + + me.TDempty(me, 2/20 * me.columns); + me.TD(me, 1, 4/20 * me.columns, e = makeXonoticButton(_("Slide show"), '0 0 0')); + e.onClick = toggleSlideShow_Click; + e.onClickEntity = me; + me.slideShowButton = e; +} +#endif diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup.c b/qcsrc/menu/xonotic/dialog_multiplayer_playersetup.c deleted file mode 100644 index 482a60510..000000000 --- a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup.c +++ /dev/null @@ -1,182 +0,0 @@ -#ifdef INTERFACE -CLASS(XonoticPlayerSettingsTab) EXTENDS(XonoticTab) - METHOD(XonoticPlayerSettingsTab, fill, void(entity)) - METHOD(XonoticPlayerSettingsTab, draw, void(entity)) - ATTRIB(XonoticPlayerSettingsTab, title, string, _("Player Setup")) - ATTRIB(XonoticPlayerSettingsTab, intendedWidth, float, 0.9) - ATTRIB(XonoticPlayerSettingsTab, rows, float, 22) - ATTRIB(XonoticPlayerSettingsTab, columns, float, 6.2) // added extra .2 for center space - ATTRIB(XonoticPlayerSettingsTab, playerNameLabel, entity, NULL) - ATTRIB(XonoticPlayerSettingsTab, playerNameLabelAlpha, float, 0) -ENDCLASS(XonoticPlayerSettingsTab) -entity makeXonoticPlayerSettingsTab(); -#endif - -#ifdef IMPLEMENTATION -entity makeXonoticPlayerSettingsTab() -{ - entity me; - me = spawnXonoticPlayerSettingsTab(); - me.configureDialog(me); - return me; -} -void XonoticPlayerSettingsTab_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(XonoticPlayerSettingsTab).draw(me); -} -void XonoticPlayerSettingsTab_fill(entity me) -{ - entity e, pms, label, box; - float i; - - me.TR(me); - me.TD(me, 1, 0.5, me.playerNameLabel = makeXonoticTextLabel(0, _("Name:"))); - me.playerNameLabelAlpha = me.playerNameLabel.alpha; - me.TD(me, 1, 2.5, label = makeXonoticTextLabel(0, string_null)); - label.allowCut = 1; - label.allowColors = 1; - label.alpha = 1; - me.TR(me); - 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)); - me.TR(me); - me.TR(me); - me.TR(me); - me.TR(me); - me.TR(me); - - me.TR(me); - me.TDempty(me, 1); - me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("Model:"))); - me.TR(me); - me.TDempty(me, 1); - pms = makeXonoticPlayerModelSelector(); - me.TD(me, 1, 0.3, e = makeXonoticButton("<<", '0 0 0')); - e.onClick = PlayerModelSelector_Prev_Click; - e.onClickEntity = pms; - me.TD(me, me.rows - (me.currentRow + 2), 1.4, pms); - me.TD(me, 1, 0.3, e = makeXonoticButton(">>", '0 0 0')); - e.onClick = PlayerModelSelector_Next_Click; - e.onClickEntity = pms; - me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0.5, _("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 = makeXonoticTextLabel(0.5, _("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'); - } - - // 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.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn); - me.TD(me, 1, 3, e = makeXonoticRadioButton(3, "crosshair_enabled", "0", _("No crosshair"))); - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticRadioButton(3, "crosshair_per_weapon", string_null, _("Per weapon crosshair"))); - makeMulti(e, "crosshair_enabled"); - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticRadioButton(3, "crosshair_enabled", "2", _("Custom crosshair"))); - 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.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair size:"))); - setDependent(e, "crosshair_enabled", 1, 2); - me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 1.0, 0.01, "crosshair_size")); - setDependent(e, "crosshair_enabled", 1, 2); - me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair alpha:"))); - setDependent(e, "crosshair_enabled", 1, 2); - me.TD(me, 1, 2, e = makeXonoticSlider(0, 1, 0.1, "crosshair_alpha")); - setDependent(e, "crosshair_enabled", 1, 2); - me.TR(me); - me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair color:"))); - setDependent(e, "crosshair_enabled", 1, 2); - me.TD(me, 1, 1, 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.1); - me.TD(me, 1, 0.9, 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.5); - me.TD(me, 1, 2, e = makeXonoticButton(_("Other crosshair settings"), '0 0 0')); - e.onClick = DialogOpenButton_Click; - e.onClickEntity = main.crosshairDialog; - setDependent(e, "crosshair_enabled", 1, 2); - // TODO: show status of crosshair dot and hittest and pickups and such here with text - me.TR(me); - me.TR(me); - me.TDempty(me, 0.5); - me.TD(me, 1, 2, e = makeXonoticButton(_("Model settings"), '0 0 0')); - e.onClick = DialogOpenButton_Click; - e.onClickEntity = main.modelDialog; - // TODO: show csqc model settings like forcemyplayer and deglowing/ghosting bodies with text here - me.TR(me); - me.TDempty(me, 0.5); - me.TD(me, 1, 2, e = makeXonoticButton(_("View settings"), '0 0 0')); - e.onClick = DialogOpenButton_Click; - e.onClickEntity = main.viewDialog; - // TODO: show fov and other settings with text here - me.TR(me); - me.TDempty(me, 0.5); - me.TD(me, 1, 2, e = makeXonoticButton(_("Weapon settings"), '0 0 0')); - e.onClick = DialogOpenButton_Click; - e.onClickEntity = main.weaponsDialog; - // I don't really think this is useful as is, and especially it doesn't look very clean... - // In the future, if ALL of these buttons had some information, then it would be justified/clean - //me.TD(me, 1, 1, e0 = makeXonoticTextLabel(0, string_null)); - // e0.textEntity = main.weaponsDialog; - // e0.allowCut = 1; - me.TR(me); - me.TDempty(me, 0.5); - me.TD(me, 1, 2, e = makeXonoticButton(_("HUD settings"), '0 0 0')); - e.onClick = DialogOpenButton_Click; - e.onClickEntity = main.hudDialog; - // TODO: show hud config name with text here - - 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 --git a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_crosshair.c b/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_crosshair.c deleted file mode 100644 index a18fec1c4..000000000 --- a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_crosshair.c +++ /dev/null @@ -1,95 +0,0 @@ -#ifdef INTERFACE -CLASS(XonoticCrosshairDialog) EXTENDS(XonoticDialog) - METHOD(XonoticCrosshairDialog, toString, string(entity)) - METHOD(XonoticCrosshairDialog, fill, void(entity)) - METHOD(XonoticCrosshairDialog, showNotify, void(entity)) - ATTRIB(XonoticCrosshairDialog, title, string, _("Crosshair settings")) - ATTRIB(XonoticCrosshairDialog, color, vector, SKINCOLOR_DIALOG_CROSSHAIR) - ATTRIB(XonoticCrosshairDialog, intendedWidth, float, 0.5) - ATTRIB(XonoticCrosshairDialog, rows, float, 18) - ATTRIB(XonoticCrosshairDialog, columns, float, 3) -ENDCLASS(XonoticCrosshairDialog) -#endif - -#ifdef IMPLEMENTATION -void XonoticCrosshairDialog_showNotify(entity me) -{ - loadAllCvars(me); -} -string XonoticCrosshairDialog_toString(entity me) -{ - return "hi"; // TODO: show status of crosshair dot and hittest and pickups and such here with text -} -void XonoticCrosshairDialog_fill(entity me) -{ - entity e; - - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "crosshair_dot", _("Enable center crosshair dot"))); - setDependent(e, "crosshair_enabled", 1, 2); - me.TR(me); - me.TD(me, 1, 1, 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.TD(me, 1, 1, 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.TD(me, 1, 1, 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.1); - 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 = makeXonoticTextLabel(0, _("Crosshair animations:"))); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "crosshair_effect_scalefade", _("Smooth effects of crosshairs"))); - setDependent(e, "crosshair_enabled", 1, 2); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, 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.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, 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.TDempty(me, 0.2); - me.TD(me, 1, 2.8, 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.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.5, 0, "crosshair_hitindication", _("Animate when hitting an enemy"))); - setDependent(e, "crosshair_enabled", 1, 2); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.25, 0, "crosshair_pickup", _("Animate when picking up an item"))); - 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 --git a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_hud.c b/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_hud.c deleted file mode 100644 index e53f99bc1..000000000 --- a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_hud.c +++ /dev/null @@ -1,103 +0,0 @@ -#ifdef INTERFACE -CLASS(XonoticHUDDialog) EXTENDS(XonoticDialog) - METHOD(XonoticHUDDialog, toString, string(entity)) - METHOD(XonoticHUDDialog, fill, void(entity)) - METHOD(XonoticHUDDialog, showNotify, void(entity)) - ATTRIB(XonoticHUDDialog, title, string, _("HUD settings")) - ATTRIB(XonoticHUDDialog, color, vector, SKINCOLOR_DIALOG_HUD) - ATTRIB(XonoticHUDDialog, intendedWidth, float, 0.5) - ATTRIB(XonoticHUDDialog, rows, float, 18) - ATTRIB(XonoticHUDDialog, columns, float, 3) -ENDCLASS(XonoticHUDDialog) -void HUDSetup_Start(entity me, entity btn); -#endif - -#ifdef IMPLEMENTATION -void HUDSetup_Check_Gamestatus(entity me, entity btn) -{ - if not(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)) // we're not in a match, ask the player if they want to start one anyway - { - DialogOpenButton_Click(me, main.hudconfirmDialog); - } - else // already in a match, lets just cut to the point and open up the hud editor directly - { - HUDSetup_Start(me, btn); - } -} -void XonoticHUDDialog_showNotify(entity me) -{ - loadAllCvars(me); -} -string XonoticHUDDialog_toString(entity me) -{ - return "hi"; // TODO: show hud config name with text here -} -void XonoticHUDDialog_fill(entity me) -{ - entity e; - - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticTextLabel(0, _("Damage:"))); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Overlay:"))); - me.TD(me, 1, 2, e = makeXonoticSlider(0, 1, 0.05, "hud_damage")); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Factor:"))); - setDependent(e, "hud_damage", 0.001, 100); - me.TD(me, 1, 2, e = makeXonoticSlider(0.025, 0.1, 0.025, "hud_damage_factor")); - setDependent(e, "hud_damage", 0.001, 100); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Fade rate:"))); - setDependent(e, "hud_damage", 0.001, 100); - me.TD(me, 1, 2, e = makeXonoticSlider(0.25, 1, 0.05, "hud_damage_fade_rate")); - setDependent(e, "hud_damage", 0.001, 100); - me.TR(me); - - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticCheckBox(1, "cl_hidewaypoints", _("Waypoints"))); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Scale:"))); - setDependent(e, "cl_hidewaypoints", 0, 0); - me.TD(me, 1, 2, e = makeXonoticSlider(0.5, 1.5, 0.05, "g_waypointsprite_scale")); - setDependent(e, "cl_hidewaypoints", 0, 0); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Alpha:"))); - setDependent(e, "cl_hidewaypoints", 0, 0); - me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 1, 0.05, "g_waypointsprite_alpha")); - setDependent(e, "cl_hidewaypoints", 0, 0); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Edge offset:"))); - setDependent(e, "cl_hidewaypoints", 0, 0); - me.TD(me, 1, 2, e = makeXonoticSlider(0, 0.3, 0.01, "g_waypointsprite_edgeoffset_bottom")); - makeMulti(e, "g_waypointsprite_edgeoffset_top g_waypointsprite_edgeoffset_left g_waypointsprite_edgeoffset_right"); - setDependent(e, "cl_hidewaypoints", 0, 0); - me.TR(me); - - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "hud_shownames", _("Show names above players"))); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(25, 0, "hud_shownames_crosshairdistance", _("Only when near crosshair"))); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "hud_shownames_status", _("Display health and armor"))); - me.TR(me); - me.TR(me); - me.TDempty(me, 0.5); - me.TD(me, 1, 2, e = makeXonoticButton(_("Enter HUD editor"), '0 0 0')); - e.onClick = HUDSetup_Check_Gamestatus; - e.onClickEntity = me; - // TODO: show hud config name with text here - - 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 \ No newline at end of file diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_hudconfirm.c b/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_hudconfirm.c deleted file mode 100644 index b06dc9207..000000000 --- a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_hudconfirm.c +++ /dev/null @@ -1,40 +0,0 @@ -#ifdef INTERFACE -CLASS(XonoticHUDConfirmDialog) EXTENDS(XonoticDialog) - METHOD(XonoticHUDConfirmDialog, fill, void(entity)) - ATTRIB(XonoticHUDConfirmDialog, title, string, _("Enter HUD editor")) - ATTRIB(XonoticHUDConfirmDialog, color, vector, SKINCOLOR_DIALOG_HUDCONFIRM) - ATTRIB(XonoticHUDConfirmDialog, intendedWidth, float, 0.5) - ATTRIB(XonoticHUDConfirmDialog, rows, float, 4) - ATTRIB(XonoticHUDConfirmDialog, columns, float, 2) -ENDCLASS(XonoticHUDConfirmDialog) -#endif - -#ifdef IMPLEMENTATION -void HUDSetup_Start(entity me, entity btn) -{ - if not(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)) - localcmd("map hudsetup/hudsetup", "\n"); - else - localcmd("togglemenu 0\n"); - - localcmd("_hud_configure 1", "\n"); -} - -void XonoticHUDConfirmDialog_fill(entity me) -{ - entity e; - - me.TR(me); - me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("In order for the HUD editor to show, you must first be in game."))); - me.TR(me); - me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("Do you wish to start a local game to set up the HUD?"))); - me.TR(me); - me.TR(me); - me.TD(me, 1, 1, e = makeXonoticButton(ZCTX(_("HDCNFRM^Yes")), '1 0 0')); - e.onClick = HUDSetup_Start; - e.onClickEntity = me; - me.TD(me, 1, 1, e = makeXonoticButton(ZCTX(_("HDCNFRM^No")), '0 1 0')); - e.onClick = Dialog_Close; - e.onClickEntity = me; -} -#endif diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_model.c b/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_model.c deleted file mode 100644 index c55d4d488..000000000 --- a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_model.c +++ /dev/null @@ -1,51 +0,0 @@ -#ifdef INTERFACE -CLASS(XonoticModelDialog) EXTENDS(XonoticDialog) - METHOD(XonoticModelDialog, toString, string(entity)) - METHOD(XonoticModelDialog, fill, void(entity)) - METHOD(XonoticModelDialog, showNotify, void(entity)) - ATTRIB(XonoticModelDialog, title, string, _("Model settings")) - ATTRIB(XonoticModelDialog, color, vector, SKINCOLOR_DIALOG_MODEL) - ATTRIB(XonoticModelDialog, intendedWidth, float, 0.5) - ATTRIB(XonoticModelDialog, rows, float, 7) - ATTRIB(XonoticModelDialog, columns, float, 3) -ENDCLASS(XonoticModelDialog) -#endif - -#ifdef IMPLEMENTATION -void XonoticModelDialog_showNotify(entity me) -{ - loadAllCvars(me); -} -string XonoticModelDialog_toString(entity me) -{ - return "hi"; // TODO: show csqc model settings like forcemyplayer and deglowing/ghosting bodies with text here -} -void XonoticModelDialog_fill(entity me) -{ - entity e; - - me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Body fading:"))); - me.TD(me, 1, 2, e = makeXonoticSlider(0, 2, 0.2, "cl_deathglow")); - me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Gibs:"))); - me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_nogibs")); - e.addValue(e, ZCTX(_("GIBS^None")), "1"); - e.addValue(e, ZCTX(_("GIBS^Few")), "0.75"); - e.addValue(e, ZCTX(_("GIBS^Many")), "0.5"); - e.addValue(e, ZCTX(_("GIBS^Lots")), "0"); - e.configureXonoticTextSliderValues(e); - setDependent(e, "cl_gentle", 0, 0); - me.TR(me); - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_forceplayermodels", _("Force player models to mine"))); - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_forceplayercolors", _("Force player colors to mine"))); - 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 --git a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_view.c b/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_view.c deleted file mode 100644 index 59a7e6e79..000000000 --- a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_view.c +++ /dev/null @@ -1,117 +0,0 @@ -#ifdef INTERFACE -CLASS(XonoticViewDialog) EXTENDS(XonoticDialog) - METHOD(XonoticViewDialog, toString, string(entity)) - METHOD(XonoticViewDialog, fill, void(entity)) - METHOD(XonoticViewDialog, showNotify, void(entity)) - ATTRIB(XonoticViewDialog, title, string, _("View settings")) - ATTRIB(XonoticViewDialog, color, vector, SKINCOLOR_DIALOG_VIEW) - ATTRIB(XonoticViewDialog, intendedWidth, float, 0.9) - ATTRIB(XonoticViewDialog, rows, float, 11) - ATTRIB(XonoticViewDialog, columns, float, 6.2) // added extra .2 for center space -ENDCLASS(XonoticViewDialog) -#endif - -#ifdef IMPLEMENTATION -void XonoticViewDialog_showNotify(entity me) -{ - loadAllCvars(me); -} -string XonoticViewDialog_toString(entity me) -{ - return "hi"; // TODO: show fov and other settings with text here -} -void XonoticViewDialog_fill(entity me) -{ - entity e; - - me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Field of view:"))); - me.TD(me, 1, 2, e = makeXonoticSlider(60, 130, 5, "fov")); - me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Zoom:"))); - me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_reticle")); - e.addValue(e, ZCTX(_("RETICLE^Fullscreen")), "0"); - e.addValue(e, ZCTX(_("RETICLE^With reticle")), "1"); - e.configureXonoticTextSliderValues(e); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("ZOOM^Factor:")))); - me.TD(me, 1, 2, e = makeXonoticSlider(2, 16, 0.5, "cl_zoomfactor")); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("ZOOM^Speed:")))); - me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_zoomspeed")); - e.addValue(e, "1", "1"); // Samual: for() loop doesn't work here, even though it would make sense. - e.addValue(e, "2", "2"); - e.addValue(e, "3", "3"); - e.addValue(e, "4", "4"); - e.addValue(e, "5", "5"); - e.addValue(e, "6", "6"); - e.addValue(e, "7", "7"); - e.addValue(e, "8", "8"); - e.addValue(e, ZCTX(_("ZOOM^Instant")), "-1"); - e.configureXonoticTextSliderValues(e); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("ZOOM^Sensitivity:")))); - me.TD(me, 1, 2, e = makeXonoticSlider(0, 1, 0.1, "cl_zoomsensitivity")); - me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Velocity zoom:"))); - me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_velocityzoom_type")); - e.addValue(e, ZCTX(_("VZOOM^Disabled")), "0"); - e.addValue(e, ZCTX(_("VZOOM^Forward only")), "2"); - e.addValue(e, ZCTX(_("VZOOM^All directions")), "1"); - e.configureXonoticTextSliderValues(e); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("VZOOM^Speed")))); - me.TD(me, 1, 2, e = makeXonoticSlider(-1, 1, 0.2, "cl_velocityzoom")); - setDependent(e, "cl_velocityzoom_type", 1, 3); - me.TR(me); - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticCheckBox(1, "cl_clippedspectating", _("Allow passing through walls while spectating"))); - - me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn); - me.TD(me, 1, 3, e = makeXonoticRadioButton(1, "chase_active", "0", _("1st person perspective"))); - makeMulti(e, "crosshair_hittest_showimpact"); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.05, 0, "cl_bobfall", _("Smooth the view when landing from a jump"))); - setDependent(e, "chase_active", -1, 0); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.05, 0, "cl_smoothviewheight", _("Smooth the view while crouching"))); - setDependent(e, "chase_active", -1, 0); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(1, 0, "v_idlescale", _("View waving while idle"))); - setDependent(e, "chase_active", -1, 0); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.01, 0, "cl_bob", _("View bobbing while walking around"))); - makeMulti(e, "cl_bob2"); - setDependent(e, "chase_active", -1, 0); - me.TR(me); - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticRadioButton(1, "chase_active", "1", _("3rd person perspective"))); - makeMulti(e, "crosshair_hittest_showimpact"); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Back distance"))); - setDependent(e, "chase_active", 1, 1); - me.TD(me, 1, 2, e = makeXonoticSlider(10, 100, 1, "chase_back")); - setDependent(e, "chase_active", 1, 1); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Up distance"))); - setDependent(e, "chase_active", 1, 1); - me.TD(me, 1, 2, e = makeXonoticSlider(10, 50, 1, "chase_up")); - setDependent(e, "chase_active", 1, 1); - 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 --git a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_weapons.c b/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_weapons.c deleted file mode 100644 index fa3549154..000000000 --- a/qcsrc/menu/xonotic/dialog_multiplayer_playersetup_weapons.c +++ /dev/null @@ -1,78 +0,0 @@ -#ifdef INTERFACE -CLASS(XonoticWeaponsDialog) EXTENDS(XonoticDialog) - METHOD(XonoticWeaponsDialog, toString, string(entity)) - METHOD(XonoticWeaponsDialog, fill, void(entity)) - METHOD(XonoticWeaponsDialog, showNotify, void(entity)) - ATTRIB(XonoticWeaponsDialog, title, string, _("Weapon settings")) - ATTRIB(XonoticWeaponsDialog, color, vector, SKINCOLOR_DIALOG_WEAPONS) - ATTRIB(XonoticWeaponsDialog, intendedWidth, float, 0.7) - ATTRIB(XonoticWeaponsDialog, rows, float, 12) - ATTRIB(XonoticWeaponsDialog, columns, float, 5.2) - ATTRIB(XonoticWeaponsDialog, weaponsList, entity, NULL) -ENDCLASS(XonoticWeaponsDialog) -#endif - -#ifdef IMPLEMENTATION -void XonoticWeaponsDialog_showNotify(entity me) -{ - loadAllCvars(me); -} -string XonoticWeaponsDialog_toString(entity me) -{ - return me.weaponsList.toString(me.weaponsList); -} -void XonoticWeaponsDialog_fill(entity me) -{ - entity e; - - me.TR(me); - me.TD(me, 1, 2, makeXonoticTextLabel(0, _("Weapon priority list:"))); - me.TR(me); - me.TD(me, 8, 2, e = me.weaponsList = makeXonoticWeaponsList()); - me.gotoRC(me, 9, 0); - me.TD(me, 1, 1, e = makeXonoticButton(_("Up"), '0 0 0')); - e.onClick = WeaponsList_MoveUp_Click; - e.onClickEntity = me.weaponsList; - me.TD(me, 1, 1, e = makeXonoticButton(_("Down"), '0 0 0')); - e.onClick = WeaponsList_MoveDown_Click; - e.onClickEntity = me.weaponsList; - - me.gotoRC(me, 0, 2.2); me.setFirstColumn(me, me.currentColumn); - me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_weaponpriority_useforcycling", _("Use priority list for weapon cycling"))); - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_autoswitch", _("Auto switch weapons on pickup"))); - me.TR(me); - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "r_drawviewmodel", _("Draw 1st person weapon model"))); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.9, e = makeXonoticRadioButton(1, "cl_gunalign", "4", _("Left align"))); - setDependent(e, "r_drawviewmodel", 1, 1); - me.TD(me, 1, 0.9, e = makeXonoticRadioButton(1, "cl_gunalign", "1", _("Center"))); - setDependent(e, "r_drawviewmodel", 1, 1); - me.TD(me, 1, 1.0, e = makeXonoticRadioButton(1, "cl_gunalign", "3", _("Right align"))); - setDependent(e, "r_drawviewmodel", 1, 1); - me.TR(me); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_followmodel", _("Gun model swaying"))); - makeMulti(e, "cl_leanmodel"); - setDependent(e, "r_drawviewmodel", 1, 1); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_bobmodel", _("Gun model bobbing"))); - setDependent(e, "r_drawviewmodel", 1, 1); - //me.TR(me); - //me.TR(me); - // me.TDempty(me, 0.2); - // me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("VWMDL^Scale")))); - // setDependent(e, "r_drawviewmodel", 1, 1); - // me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 2, 0.1, "cl_viewmodel_scale")); - // setDependent(e, "r_drawviewmodel", 1, 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; -} -#endif diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_profile.c b/qcsrc/menu/xonotic/dialog_multiplayer_profile.c new file mode 100644 index 000000000..668d808bf --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_multiplayer_profile.c @@ -0,0 +1,214 @@ +#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.2) // added extra .2 for center space + ATTRIB(XonoticProfileTab, playerNameLabel, entity, NULL) + ATTRIB(XonoticProfileTab, playerNameLabelAlpha, float, 0) +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; + + me.TR(me); + me.TR(me); + me.TD(me, 1, 0.5, me.playerNameLabel = makeXonoticTextLabel(0, _("Name:"))); + me.playerNameLabelAlpha = me.playerNameLabel.alpha; + me.TD(me, 1, 2.5, label = makeXonoticTextLabel(0, string_null)); + label.allowCut = 1; + label.allowColors = 1; + label.alpha = 1; + me.TR(me); + 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)); + me.TR(me); + me.TR(me); + me.TR(me); + me.TR(me); + me.TR(me); + + // Statistic Stuff -Debugger + me.TR(me); + me.TDempty(me, 0.5); + me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("Player Statistics:"))); + me.TR(me); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Join time:"))); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Total playing time:"))); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Last played:"))); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Games played:"))); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Win / Losses:"))); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Kills / Deaths:"))); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("CTF elo:"))); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("DM elo:"))); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("TDM elo:"))); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("DUEL elo:"))); + me.TR(me); + + + me.gotoRC(me, 1, 3.2); me.setFirstColumn(me, me.currentColumn); + me.TDempty(me, 1); + me.TD(me, 1, 3, e = makeXonoticTextLabel(0.5, _("Model:"))); + me.TR(me); + pms = makeXonoticPlayerModelSelector(); + me.TD(me, 1, 0.3, e = makeXonoticButton("<<", '0 0 0')); + e.onClick = PlayerModelSelector_Prev_Click; + e.onClickEntity = pms; + me.TD(me, 13, 2.4, pms); + me.TD(me, 1, 0.3, e = makeXonoticButton(">>", '0 0 0')); + e.onClick = PlayerModelSelector_Next_Click; + e.onClickEntity = pms; + + me.gotoRC(me, 15, 3.533); me.setFirstColumn(me, me.currentColumn); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0.5, _("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.gotoRC(me, 15, 4.866); me.setFirstColumn(me, me.currentColumn); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0.5, _("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'); + } + + /* + // 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.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn); + me.TD(me, 1, 3, e = makeXonoticRadioButton(3, "crosshair_enabled", "0", _("No crosshair"))); + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticRadioButton(3, "crosshair_per_weapon", string_null, _("Per weapon crosshair"))); + makeMulti(e, "crosshair_enabled"); + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticRadioButton(3, "crosshair_enabled", "2", _("Custom crosshair"))); + 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.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair size:"))); + setDependent(e, "crosshair_enabled", 1, 2); + me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 1.0, 0.01, "crosshair_size")); + setDependent(e, "crosshair_enabled", 1, 2); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair alpha:"))); + setDependent(e, "crosshair_enabled", 1, 2); + me.TD(me, 1, 2, e = makeXonoticSlider(0, 1, 0.1, "crosshair_alpha")); + setDependent(e, "crosshair_enabled", 1, 2); + me.TR(me); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair color:"))); + setDependent(e, "crosshair_enabled", 1, 2); + me.TD(me, 1, 1, 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.1); + me.TD(me, 1, 0.9, 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.5); + me.TD(me, 1, 2, e = makeXonoticButton(_("Other crosshair settings"), '0 0 0')); + e.onClick = DialogOpenButton_Click; + e.onClickEntity = main.crosshairDialog; + setDependent(e, "crosshair_enabled", 1, 2); + // TODO: show status of crosshair dot and hittest and pickups and such here with text + me.TR(me); + me.TR(me); + me.TDempty(me, 0.5); + me.TD(me, 1, 2, e = makeXonoticButton(_("Model settings"), '0 0 0')); + e.onClick = DialogOpenButton_Click; + e.onClickEntity = main.modelDialog; + // TODO: show csqc model settings like forcemyplayer and deglowing/ghosting bodies with text here + me.TR(me); + me.TDempty(me, 0.5); + me.TD(me, 1, 2, e = makeXonoticButton(_("View settings"), '0 0 0')); + e.onClick = DialogOpenButton_Click; + e.onClickEntity = main.viewDialog; + // TODO: show fov and other settings with text here + me.TR(me); + me.TDempty(me, 0.5); + me.TD(me, 1, 2, e = makeXonoticButton(_("Weapon settings"), '0 0 0')); + e.onClick = DialogOpenButton_Click; + e.onClickEntity = main.weaponsDialog; + // I don't really think this is useful as is, and especially it doesn't look very clean... + // In the future, if ALL of these buttons had some information, then it would be justified/clean + //me.TD(me, 1, 1, e0 = makeXonoticTextLabel(0, string_null)); + // e0.textEntity = main.weaponsDialog; + // e0.allowCut = 1; + me.TR(me); + me.TDempty(me, 0.5); + me.TD(me, 1, 2, e = makeXonoticButton(_("HUD settings"), '0 0 0')); + e.onClick = DialogOpenButton_Click; + e.onClickEntity = main.hudDialog; + // TODO: show hud config name with text here + + */ + 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 --git a/qcsrc/menu/xonotic/dialog_settings.c b/qcsrc/menu/xonotic/dialog_settings.c index 930fa7df9..7280c9719 100644 --- a/qcsrc/menu/xonotic/dialog_settings.c +++ b/qcsrc/menu/xonotic/dialog_settings.c @@ -15,13 +15,14 @@ void XonoticSettingsDialog_fill(entity me) entity mc; mc = makeXonoticTabController(me.rows - 2); me.TR(me); - me.TD(me, 1, 1, mc.makeTabButton(mc, _("Input"), makeXonoticInputSettingsTab())); - me.TD(me, 1, 1, mc.makeTabButton(mc, _("Video"), makeXonoticVideoSettingsTab())); - me.TD(me, 1, 1, mc.makeTabButton(mc, _("Effects"), makeXonoticEffectsSettingsTab())); - me.TD(me, 1, 1, mc.makeTabButton(mc, _("Audio"), makeXonoticAudioSettingsTab())); - me.TD(me, 1, 1, mc.makeTabButton(mc, _("User"), makeXonoticUserSettingsTab())); - me.TD(me, 1, 1, mc.makeTabButton(mc, _("Misc"), makeXonoticMiscSettingsTab())); + me.TD(me, 1, 2, mc.makeTabButton(mc, _("Video"), makeXonoticVideoSettingsTab())); + me.TD(me, 1, 2, mc.makeTabButton(mc, _("Effects"), makeXonoticEffectsSettingsTab())); + me.TD(me, 1, 2, mc.makeTabButton(mc, _("Audio"), makeXonoticAudioSettingsTab())); me.TR(me); + me.TD(me, 1, 1.5, mc.makeTabButton(mc, _("Game"), makeXonoticGameSettingsTab())); + me.TD(me, 1, 1.5, mc.makeTabButton(mc, _("Input"), makeXonoticInputSettingsTab())); + me.TD(me, 1, 1.5, mc.makeTabButton(mc, _("User"), makeXonoticUserSettingsTab())); + me.TD(me, 1, 1.5, mc.makeTabButton(mc, _("Misc"), makeXonoticMiscSettingsTab())); me.TR(me); me.TD(me, me.rows - 2, me.columns, mc); } diff --git a/qcsrc/menu/xonotic/dialog_settings_effects.c b/qcsrc/menu/xonotic/dialog_settings_effects.c index bea650eba..0d2c21913 100644 --- a/qcsrc/menu/xonotic/dialog_settings_effects.c +++ b/qcsrc/menu/xonotic/dialog_settings_effects.c @@ -126,21 +126,21 @@ void XonoticEffectsSettingsTab_fill(entity me) setDependentAND(e, "vid_gl20", 1, 1, "r_water", 1, 1); me.TR(me); me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Particles quality:"))); - me.TD(me, 1, 2, e = makeXonoticSlider(0.2, 1.0, 0.1, "cl_particles_quality")); - me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Particles distance:"))); - me.TD(me, 1, 2, e = makeXonoticSlider(500, 2000, 100, "r_drawparticles_drawdistance")); + me.TD(me, 1, 1, e = makeXonoticCheckBox(0, "cl_decals", _("Decals"))); + me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "cl_decals_models", _("Decals on models"))); + setDependent(e, "cl_decals", 1, 1); me.TR(me); - me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Damage effects:"))); - me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_damageeffect")); - e.addValue(e, ZCTX(_("DMGPRTCLS^Disabled")), "0"); - e.addValue(e, ZCTX(_("DMGPRTCLS^Skeletal")), "1"); - e.addValue(e, ZCTX(_("DMGPRTCLS^All")), "2"); - e.configureXonoticTextSliderValues(e); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Distance:"))); + setDependent(e, "cl_decals", 1, 1); + me.TD(me, 1, 2, e = makeXonoticSlider(200, 500, 20, "r_drawdecals_drawdistance")); + setDependent(e, "cl_decals", 1, 1); me.TR(me); - me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_spawn_point_particles", _("Particle effects for spawnpoints"))); - makeMulti(e, "cl_spawn_event_particles"); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Time:"))); + setDependent(e, "cl_decals", 1, 1); + me.TD(me, 1, 2, e = makeXonoticSlider(1, 20, 1, "cl_decals_time")); + setDependent(e, "cl_decals", 1, 1); me.gotoRC(me, 2, 3.2); me.setFirstColumn(me, me.currentColumn); me.TD(me, 1, 3, e = makeXonoticRadioButton(1, "r_coronas", "0", _("No dynamic lighting"))); @@ -180,22 +180,17 @@ void XonoticEffectsSettingsTab_fill(entity me) me.TD(me, 1, 2, s); me.TR(me); me.TR(me); - me.TD(me, 1, 1, e = makeXonoticCheckBox(0, "cl_decals", _("Decals"))); - me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "cl_decals_models", _("Decals on models"))); - setDependent(e, "cl_decals", 1, 1); - me.TR(me); - me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Distance:"))); - setDependent(e, "cl_decals", 1, 1); - me.TD(me, 1, 2, e = makeXonoticSlider(200, 500, 20, "r_drawdecals_drawdistance")); - setDependent(e, "cl_decals", 1, 1); + me.TD(me, 1, 1, e = makeXonoticCheckBox(0, "cl_particles", _("Particles"))); + me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "cl_spawn_point_particles", _("Spawnpoint effects"))); + makeMulti(e, "cl_spawn_event_particles"); + setDependent(e, "cl_particles", 1, 1); me.TR(me); me.TDempty(me, 0.2); - me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Time:"))); - setDependent(e, "cl_decals", 1, 1); - me.TD(me, 1, 2, e = makeXonoticSlider(1, 20, 1, "cl_decals_time")); - setDependent(e, "cl_decals", 1, 1); - + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Quality:"))); + setDependent(e, "cl_particles", 1, 1); + me.TD(me, 1, 2, e = makeXonoticParticlesSlider()); + setDependent(e, "cl_particles", 1, 1); + me.gotoRC(me, me.rows - 1, 0); me.TD(me, 1, me.columns, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "vid_restart", COMMANDBUTTON_APPLY)); } diff --git a/qcsrc/menu/xonotic/dialog_settings_game.c b/qcsrc/menu/xonotic/dialog_settings_game.c new file mode 100644 index 000000000..d38a87fb4 --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_settings_game.c @@ -0,0 +1,54 @@ +#ifdef INTERFACE +CLASS(XonoticGameSettingsTab) EXTENDS(XonoticTab) + METHOD(XonoticGameSettingsTab, fill, void(entity)) + ATTRIB(XonoticGameSettingsTab, title, string, _("Game")) + ATTRIB(XonoticGameSettingsTab, intendedWidth, float, 0.9) + ATTRIB(XonoticGameSettingsTab, rows, float, 17) + ATTRIB(XonoticGameSettingsTab, columns, float, 6.5) +ENDCLASS(XonoticGameSettingsTab) +entity makeXonoticGameSettingsTab(); +#endif + +#ifdef IMPLEMENTATION +entity makeXonoticGameSettingsTab() +{ + entity me; + me = spawnXonoticGameSettingsTab(); + me.configureDialog(me); + return me; +} + +void XonoticGameSettingsTab_fill(entity me) +{ + entity mc; + mc = makeXonoticTabController(me.rows - 2); + + me.gotoRC(me, 0.5, 0.25); + //me.TD(me, 1, 1, mc.makeTabButton(mc, _("General"), makeXonoticGameCrosshairSettingsTab())); + me.TD(me, 1, 1, mc.makeTabButton(mc, _("View"), makeXonoticGameViewSettingsTab())); + me.TD(me, 1, 1, mc.makeTabButton(mc, _("Crosshair"), makeXonoticGameCrosshairSettingsTab())); + me.TD(me, 1, 1, mc.makeTabButton(mc, _("HUD"), makeXonoticGameCrosshairSettingsTab())); + me.TD(me, 1, 1, mc.makeTabButton(mc, _("Messages"), makeXonoticGameCrosshairSettingsTab())); + me.TD(me, 1, 1, mc.makeTabButton(mc, _("Weapons"), makeXonoticGameCrosshairSettingsTab())); + me.TD(me, 1, 1, mc.makeTabButton(mc, _("Playermodels"), makeXonoticGameCrosshairSettingsTab())); + + me.gotoRC(me, 2, 0); + me.TD(me, me.rows - 2, me.columns, mc); + + /* + + makeXonoticGameViewSettingsTab())); + makeXonoticGameGeneralSettingsTab())); + makeXonoticGameCrosshairSettingsTab())); + + makeXonoticGameWeaponSettingsTab())); + l"), makeXonoticGamePlayermodelSettingsTab())); + makeXonoticGameHUDSettingsTab())); + on"), makeXonoticGameNotificationSettingsTab())); + + + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_gentle", _("Disable gore effects and harsh language"))); // also set sv_gentle + */ +} +#endif diff --git a/qcsrc/menu/xonotic/dialog_settings_game_crosshair.c b/qcsrc/menu/xonotic/dialog_settings_game_crosshair.c new file mode 100644 index 000000000..fa5a2b82b --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_settings_game_crosshair.c @@ -0,0 +1,94 @@ +#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, 15) + 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; + + 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.TD(me, 1, 1, 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.TD(me, 1, 1, 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.TD(me, 1, 1, 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.1); + 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_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, 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, 3, e = makeXonoticCheckBox(0, "crosshair_effect_scalefade", _("Smooth effects of crosshairs"))); + setDependent(e, "crosshair_enabled", 1, 2); + me.TR(me); + /*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.TD(me, 1, 2.8, 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, 2.8, 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.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 --git a/qcsrc/menu/xonotic/dialog_settings_game_hud.c b/qcsrc/menu/xonotic/dialog_settings_game_hud.c new file mode 100644 index 000000000..e53f99bc1 --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_settings_game_hud.c @@ -0,0 +1,103 @@ +#ifdef INTERFACE +CLASS(XonoticHUDDialog) EXTENDS(XonoticDialog) + METHOD(XonoticHUDDialog, toString, string(entity)) + METHOD(XonoticHUDDialog, fill, void(entity)) + METHOD(XonoticHUDDialog, showNotify, void(entity)) + ATTRIB(XonoticHUDDialog, title, string, _("HUD settings")) + ATTRIB(XonoticHUDDialog, color, vector, SKINCOLOR_DIALOG_HUD) + ATTRIB(XonoticHUDDialog, intendedWidth, float, 0.5) + ATTRIB(XonoticHUDDialog, rows, float, 18) + ATTRIB(XonoticHUDDialog, columns, float, 3) +ENDCLASS(XonoticHUDDialog) +void HUDSetup_Start(entity me, entity btn); +#endif + +#ifdef IMPLEMENTATION +void HUDSetup_Check_Gamestatus(entity me, entity btn) +{ + if not(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)) // we're not in a match, ask the player if they want to start one anyway + { + DialogOpenButton_Click(me, main.hudconfirmDialog); + } + else // already in a match, lets just cut to the point and open up the hud editor directly + { + HUDSetup_Start(me, btn); + } +} +void XonoticHUDDialog_showNotify(entity me) +{ + loadAllCvars(me); +} +string XonoticHUDDialog_toString(entity me) +{ + return "hi"; // TODO: show hud config name with text here +} +void XonoticHUDDialog_fill(entity me) +{ + entity e; + + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticTextLabel(0, _("Damage:"))); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Overlay:"))); + me.TD(me, 1, 2, e = makeXonoticSlider(0, 1, 0.05, "hud_damage")); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Factor:"))); + setDependent(e, "hud_damage", 0.001, 100); + me.TD(me, 1, 2, e = makeXonoticSlider(0.025, 0.1, 0.025, "hud_damage_factor")); + setDependent(e, "hud_damage", 0.001, 100); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Fade rate:"))); + setDependent(e, "hud_damage", 0.001, 100); + me.TD(me, 1, 2, e = makeXonoticSlider(0.25, 1, 0.05, "hud_damage_fade_rate")); + setDependent(e, "hud_damage", 0.001, 100); + me.TR(me); + + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticCheckBox(1, "cl_hidewaypoints", _("Waypoints"))); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Scale:"))); + setDependent(e, "cl_hidewaypoints", 0, 0); + me.TD(me, 1, 2, e = makeXonoticSlider(0.5, 1.5, 0.05, "g_waypointsprite_scale")); + setDependent(e, "cl_hidewaypoints", 0, 0); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Alpha:"))); + setDependent(e, "cl_hidewaypoints", 0, 0); + me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 1, 0.05, "g_waypointsprite_alpha")); + setDependent(e, "cl_hidewaypoints", 0, 0); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Edge offset:"))); + setDependent(e, "cl_hidewaypoints", 0, 0); + me.TD(me, 1, 2, e = makeXonoticSlider(0, 0.3, 0.01, "g_waypointsprite_edgeoffset_bottom")); + makeMulti(e, "g_waypointsprite_edgeoffset_top g_waypointsprite_edgeoffset_left g_waypointsprite_edgeoffset_right"); + setDependent(e, "cl_hidewaypoints", 0, 0); + me.TR(me); + + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "hud_shownames", _("Show names above players"))); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(25, 0, "hud_shownames_crosshairdistance", _("Only when near crosshair"))); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "hud_shownames_status", _("Display health and armor"))); + me.TR(me); + me.TR(me); + me.TDempty(me, 0.5); + me.TD(me, 1, 2, e = makeXonoticButton(_("Enter HUD editor"), '0 0 0')); + e.onClick = HUDSetup_Check_Gamestatus; + e.onClickEntity = me; + // TODO: show hud config name with text here + + 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 \ No newline at end of file diff --git a/qcsrc/menu/xonotic/dialog_settings_game_hudconfirm.c b/qcsrc/menu/xonotic/dialog_settings_game_hudconfirm.c new file mode 100644 index 000000000..b06dc9207 --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_settings_game_hudconfirm.c @@ -0,0 +1,40 @@ +#ifdef INTERFACE +CLASS(XonoticHUDConfirmDialog) EXTENDS(XonoticDialog) + METHOD(XonoticHUDConfirmDialog, fill, void(entity)) + ATTRIB(XonoticHUDConfirmDialog, title, string, _("Enter HUD editor")) + ATTRIB(XonoticHUDConfirmDialog, color, vector, SKINCOLOR_DIALOG_HUDCONFIRM) + ATTRIB(XonoticHUDConfirmDialog, intendedWidth, float, 0.5) + ATTRIB(XonoticHUDConfirmDialog, rows, float, 4) + ATTRIB(XonoticHUDConfirmDialog, columns, float, 2) +ENDCLASS(XonoticHUDConfirmDialog) +#endif + +#ifdef IMPLEMENTATION +void HUDSetup_Start(entity me, entity btn) +{ + if not(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)) + localcmd("map hudsetup/hudsetup", "\n"); + else + localcmd("togglemenu 0\n"); + + localcmd("_hud_configure 1", "\n"); +} + +void XonoticHUDConfirmDialog_fill(entity me) +{ + entity e; + + me.TR(me); + me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("In order for the HUD editor to show, you must first be in game."))); + me.TR(me); + me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("Do you wish to start a local game to set up the HUD?"))); + me.TR(me); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticButton(ZCTX(_("HDCNFRM^Yes")), '1 0 0')); + e.onClick = HUDSetup_Start; + e.onClickEntity = me; + me.TD(me, 1, 1, e = makeXonoticButton(ZCTX(_("HDCNFRM^No")), '0 1 0')); + e.onClick = Dialog_Close; + e.onClickEntity = me; +} +#endif diff --git a/qcsrc/menu/xonotic/dialog_settings_game_model.c b/qcsrc/menu/xonotic/dialog_settings_game_model.c new file mode 100644 index 000000000..c55d4d488 --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_settings_game_model.c @@ -0,0 +1,51 @@ +#ifdef INTERFACE +CLASS(XonoticModelDialog) EXTENDS(XonoticDialog) + METHOD(XonoticModelDialog, toString, string(entity)) + METHOD(XonoticModelDialog, fill, void(entity)) + METHOD(XonoticModelDialog, showNotify, void(entity)) + ATTRIB(XonoticModelDialog, title, string, _("Model settings")) + ATTRIB(XonoticModelDialog, color, vector, SKINCOLOR_DIALOG_MODEL) + ATTRIB(XonoticModelDialog, intendedWidth, float, 0.5) + ATTRIB(XonoticModelDialog, rows, float, 7) + ATTRIB(XonoticModelDialog, columns, float, 3) +ENDCLASS(XonoticModelDialog) +#endif + +#ifdef IMPLEMENTATION +void XonoticModelDialog_showNotify(entity me) +{ + loadAllCvars(me); +} +string XonoticModelDialog_toString(entity me) +{ + return "hi"; // TODO: show csqc model settings like forcemyplayer and deglowing/ghosting bodies with text here +} +void XonoticModelDialog_fill(entity me) +{ + entity e; + + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Body fading:"))); + me.TD(me, 1, 2, e = makeXonoticSlider(0, 2, 0.2, "cl_deathglow")); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Gibs:"))); + me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_nogibs")); + e.addValue(e, ZCTX(_("GIBS^None")), "1"); + e.addValue(e, ZCTX(_("GIBS^Few")), "0.75"); + e.addValue(e, ZCTX(_("GIBS^Many")), "0.5"); + e.addValue(e, ZCTX(_("GIBS^Lots")), "0"); + e.configureXonoticTextSliderValues(e); + setDependent(e, "cl_gentle", 0, 0); + me.TR(me); + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_forceplayermodels", _("Force player models to mine"))); + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_forceplayercolors", _("Force player colors to mine"))); + 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 --git a/qcsrc/menu/xonotic/dialog_settings_game_notification.c b/qcsrc/menu/xonotic/dialog_settings_game_notification.c new file mode 100644 index 000000000..7d4b50bc6 --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_settings_game_notification.c @@ -0,0 +1,96 @@ +#ifdef INTERFACE +CLASS(XonoticNotificationDialog) EXTENDS(XonoticDialog) + METHOD(XonoticNotificationDialog, toString, string(entity)) + METHOD(XonoticNotificationDialog, fill, void(entity)) + METHOD(XonoticNotificationDialog, showNotify, void(entity)) + ATTRIB(XonoticNotificationDialog, title, string, _("Notification settings")) + ATTRIB(XonoticNotificationDialog, color, vector, SKINCOLOR_DIALOG_MODEL) + ATTRIB(XonoticNotificationDialog, intendedWidth, float, 0.6) + ATTRIB(XonoticNotificationDialog, rows, float, 19) + ATTRIB(XonoticNotificationDialog, columns, float, 3) +ENDCLASS(XonoticNotificationDialog) +#endif + +#ifdef IMPLEMENTATION +void XonoticNotificationDialog_showNotify(entity me) +{ + loadAllCvars(me); +} +string XonoticNotificationDialog_toString(entity me) +{ + return "hi"; // TODO: show csqc model settings like forcemyplayer and deglowing/ghosting bodies with text here +} +void XonoticNotificationDialog_fill(entity me) +{ + entity e; + + // General settings for the player + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("General Settings:"))); + + me.TR(me); + me.TDempty(me, 0.1); + me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "notification_allow_chatboxprint", _("Print all notifications into the chatbox"))); + me.TR(me); + me.TDempty(me, 0.1); + me.TD(me, 1, 2, e = makeXonoticTextLabel(0, _("Notice connecting players:"))); + me.TR(me); + me.TDempty(me, 0.3); + me.TD(me, 1, 3, e = makeXonoticTextSlider("notification_INFO_QUIT_DISCONNECT")); + //makeMulti(me, "notification_INFO_QUIT_KICK_IDLING notification_INFO_QUIT_KICK_SPECTATING notification_INFO_JOIN_PLAY notification_INFO_JOIN_CONNECT_TEAM_BLUE notification_INFO_JOIN_CONNECT_TEAM_PINK notification_INFO_JOIN_CONNECT_TEAM_RED notification_INFO_JOIN_CONNECT_TEAM_YELLOW"); + e.addValue(e, ZCTX(_("off")), "0"); + e.addValue(e, ZCTX(_("only console")), "1"); + e.addValue(e, ZCTX(_("console & chat")), "2"); + e.configureXonoticTextSliderValues(e); + me.TR(me); + me.TDempty(me, 0.1); + me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "notification_ITEM_WEAPON_DONTHAVE", _("Item notifications"))); + me.TR(me); + me.TDempty(me, 0.1); + me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "notification_CENTER_POWERUP_INVISIBILITY", _("Powerup notifications"))); + //makeMulti(e, "notification_CENTER_POWERUP_SHIELD notification_CENTER_POWERUP_SPEED notification_CENTER_POWERUP_STRENGTH notification_CENTER_POWERDOWN_INVISIBILITY notification_CENTER_POWERDOWN_SHIELD notification_CENTER_POWERDOWN_SPEED notification_CENTER_POWERDOWN_STRENGTH notification_CENTER_SUPERWEAPON_BROKEN notification_CENTER_SUPERWEAPON_LOST notification_CENTER_SUPERWEAPON_PICKUP"); + me.TR(me); + me.TDempty(me, 0.1); + me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "notification_frag_verbose", _("Additional information on frags"))); + me.TR(me); + me.TR(me); + + // Gamemode specific notifications + me.TD(me, 1, 2, e = makeXonoticTextLabel(0, _("Gamemode notifications:"))); + me.TR(me); + me.TDempty(me, 0.1); + me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "notification_CENTER_ARENA_BEGIN", _("Centerprint for gamemode notifications"))); + //makeMulti(me, "notification_CENTER_ARENA_NEEDPLAYER notification_CENTER_ARENA_ROUNDSTART notification_CENTER_ASSAULT_ATTACKING notification_CENTER_ASSAULT_DEFENDING notification_CENTER_CTF_PICKUP_BLUE notification_CENTER_CTF_PICKUP_ENEMY notification_CENTER_CTF_PICKUP_RED notification_CENTER_CTF_PICKUP_TEAM notification_CENTER_FREEZETAG_FREEZE notification_CENTER_FREEZETAG_FROZEN notification_CENTER_FREEZETAG_REVIVE notification_CENTER_FREEZETAG_REVIVED notification_CENTER_FREEZETAG_ROUND_WIN_BLUE notification_CENTER_FREEZETAG_ROUND_WIN_PINK notification_CENTER_FREEZETAG_ROUND_WIN_RED notification_CENTER_FREEZETAG_ROUND_WIN_YELLOW notification_CENTER_FREEZETAG_SELF notification_CENTER_FREEZETAG_SPAWN_LATE notification_CENTER_KEEPAWAY_DROPPED notification_CENTER_KEEPAWAY_PICKUP notification_CENTER_KEEPAWAY_WARN notification_CENTER_KEYHUNT_HELP notification_CENTER_KEYHUNT_INTERFERE_BLUE notification_CENTER_KEYHUNT_INTERFERE_PINK notification_CENTER_KEYHUNT_INTERFERE_RED notification_CENTER_KEYHUNT_INTERFERE_YELLOW notification_CENTER_KEYHUNT_MEET notification_CENTER_KEYHUNT_SCAN notification_CENTER_KEYHUNT_START_BLUE notification_CENTER_KEYHUNT_START_PINK notification_CENTER_KEYHUNT_START_RED notification_CENTER_KEYHUNT_START_YELLOW notification_CENTER_KEYHUNT_WAIT notification_CENTER_LMS_CAMPCHECK"); + me.TR(me); + me.TDempty(me, 0.1); + me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "notification_ctf_capture_verbose", _("Additional information on flag captures"))); + me.TR(me); + + // Information on killingsprees + me.TR(me); + me.TD(me, 1, 2, e = makeXonoticTextLabel(0, _("Information on killingsprees:"))); + me.TR(me); + me.TDempty(me, 0.1); + me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "notification_show_sprees", _("Print information on sprees"))); + me.TR(me); + me.TDempty(me, 0.1); + me.TD(me, 1, 2, e = makeXonoticTextLabel(0, _("Show spree information:"))); + me.TR(me); + me.TDempty(me, 0.3); + me.TD(me, 1, 3, e = makeXonoticTextSlider("notification_show_sprees_info")); + e.addValue(e, ZCTX(_("off")), "0"); + e.addValue(e, ZCTX(_("target")), "1"); + e.addValue(e, ZCTX(_("attacker")), "2"); + e.addValue(e, ZCTX(_("target & attacker")), "3"); + e.configureXonoticTextSliderValues(e); + me.TR(me); + me.TDempty(me, 0.1); + me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "notification_show_sprees_info_newline", _("Print spree information in a new line"))); + + // Close this dialog + 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 \ No newline at end of file diff --git a/qcsrc/menu/xonotic/dialog_settings_game_view.c b/qcsrc/menu/xonotic/dialog_settings_game_view.c new file mode 100644 index 000000000..ef598e016 --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_settings_game_view.c @@ -0,0 +1,117 @@ +#ifdef INTERFACE +CLASS(XonoticGameViewSettingsTab) EXTENDS(XonoticTab) + //METHOD(XonoticGameCrosshairSettingsTab, toString, string(entity)) + METHOD(XonoticGameViewSettingsTab, fill, void(entity)) + METHOD(XonoticGameViewSettingsTab, showNotify, void(entity)) + ATTRIB(XonoticGameViewSettingsTab, title, string, _("View")) + ATTRIB(XonoticGameViewSettingsTab, intendedWidth, float, 0.9) + ATTRIB(XonoticGameViewSettingsTab, rows, float, 15) + ATTRIB(XonoticGameViewSettingsTab, columns, float, 6.2) +ENDCLASS(XonoticGameViewSettingsTab) +entity makeXonoticGameViewSettingsTab(); +#endif + +#ifdef IMPLEMENTATION +void XonoticGameViewSettingsTab_showNotify(entity me) +{ + loadAllCvars(me); +} +entity makeXonoticGameViewSettingsTab() +{ + entity me; + me = spawnXonoticGameViewSettingsTab(); + me.configureDialog(me); + return me; +} + +void XonoticGameViewSettingsTab_fill(entity me) +{ + entity e; + + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Field of view:"))); + me.TD(me, 1, 2, e = makeXonoticSlider(60, 130, 5, "fov")); + me.TR(me); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Zoom:"))); + me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_reticle")); + e.addValue(e, ZCTX(_("RETICLE^Fullscreen")), "0"); + e.addValue(e, ZCTX(_("RETICLE^With reticle")), "1"); + e.configureXonoticTextSliderValues(e); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("ZOOM^Factor:")))); + me.TD(me, 1, 2, e = makeXonoticSlider(2, 16, 0.5, "cl_zoomfactor")); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("ZOOM^Speed:")))); + me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_zoomspeed")); + e.addValue(e, "1", "1"); // Samual: for() loop doesn't work here, even though it would make sense. + e.addValue(e, "2", "2"); + e.addValue(e, "3", "3"); + e.addValue(e, "4", "4"); + e.addValue(e, "5", "5"); + e.addValue(e, "6", "6"); + e.addValue(e, "7", "7"); + e.addValue(e, "8", "8"); + e.addValue(e, ZCTX(_("ZOOM^Instant")), "-1"); + e.configureXonoticTextSliderValues(e); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("ZOOM^Sensitivity:")))); + me.TD(me, 1, 2, e = makeXonoticSlider(0, 1, 0.1, "cl_zoomsensitivity")); + me.TR(me); + me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Velocity zoom:"))); + me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_velocityzoom_type")); + e.addValue(e, ZCTX(_("VZOOM^Disabled")), "0"); + e.addValue(e, ZCTX(_("VZOOM^Forward only")), "2"); + e.addValue(e, ZCTX(_("VZOOM^All directions")), "1"); + e.configureXonoticTextSliderValues(e); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("VZOOM^Speed")))); + me.TD(me, 1, 2, e = makeXonoticSlider(-1, 1, 0.2, "cl_velocityzoom")); + setDependent(e, "cl_velocityzoom_type", 1, 3); + + me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn); + me.TD(me, 1, 3, e = makeXonoticRadioButton(1, "chase_active", "0", _("1st person perspective"))); + makeMulti(e, "crosshair_hittest_showimpact"); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.05, 0, "cl_bobfall", _("Smooth the view when landing from a jump"))); + setDependent(e, "chase_active", -1, 0); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.05, 0, "cl_smoothviewheight", _("Smooth the view while crouching"))); + setDependent(e, "chase_active", -1, 0); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(1, 0, "v_idlescale", _("View waving while idle"))); + setDependent(e, "chase_active", -1, 0); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.01, 0, "cl_bob", _("View bobbing while walking around"))); + makeMulti(e, "cl_bob2"); + setDependent(e, "chase_active", -1, 0); + me.TR(me); + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticRadioButton(1, "chase_active", "1", _("3rd person perspective"))); + makeMulti(e, "crosshair_hittest_showimpact"); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Back distance"))); + setDependent(e, "chase_active", 1, 1); + me.TD(me, 1, 2, e = makeXonoticSlider(10, 100, 1, "chase_back")); + setDependent(e, "chase_active", 1, 1); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Up distance"))); + setDependent(e, "chase_active", 1, 1); + me.TD(me, 1, 2, e = makeXonoticSlider(10, 50, 1, "chase_up")); + setDependent(e, "chase_active", 1, 1); + me.TR(me); + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticCheckBox(1, "cl_clippedspectating", _("Allow passing through walls while spectating"))); + // todo: onclick, do sendcvar if connected +} +#endif diff --git a/qcsrc/menu/xonotic/dialog_settings_game_weapons.c b/qcsrc/menu/xonotic/dialog_settings_game_weapons.c new file mode 100644 index 000000000..fa3549154 --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_settings_game_weapons.c @@ -0,0 +1,78 @@ +#ifdef INTERFACE +CLASS(XonoticWeaponsDialog) EXTENDS(XonoticDialog) + METHOD(XonoticWeaponsDialog, toString, string(entity)) + METHOD(XonoticWeaponsDialog, fill, void(entity)) + METHOD(XonoticWeaponsDialog, showNotify, void(entity)) + ATTRIB(XonoticWeaponsDialog, title, string, _("Weapon settings")) + ATTRIB(XonoticWeaponsDialog, color, vector, SKINCOLOR_DIALOG_WEAPONS) + ATTRIB(XonoticWeaponsDialog, intendedWidth, float, 0.7) + ATTRIB(XonoticWeaponsDialog, rows, float, 12) + ATTRIB(XonoticWeaponsDialog, columns, float, 5.2) + ATTRIB(XonoticWeaponsDialog, weaponsList, entity, NULL) +ENDCLASS(XonoticWeaponsDialog) +#endif + +#ifdef IMPLEMENTATION +void XonoticWeaponsDialog_showNotify(entity me) +{ + loadAllCvars(me); +} +string XonoticWeaponsDialog_toString(entity me) +{ + return me.weaponsList.toString(me.weaponsList); +} +void XonoticWeaponsDialog_fill(entity me) +{ + entity e; + + me.TR(me); + me.TD(me, 1, 2, makeXonoticTextLabel(0, _("Weapon priority list:"))); + me.TR(me); + me.TD(me, 8, 2, e = me.weaponsList = makeXonoticWeaponsList()); + me.gotoRC(me, 9, 0); + me.TD(me, 1, 1, e = makeXonoticButton(_("Up"), '0 0 0')); + e.onClick = WeaponsList_MoveUp_Click; + e.onClickEntity = me.weaponsList; + me.TD(me, 1, 1, e = makeXonoticButton(_("Down"), '0 0 0')); + e.onClick = WeaponsList_MoveDown_Click; + e.onClickEntity = me.weaponsList; + + me.gotoRC(me, 0, 2.2); me.setFirstColumn(me, me.currentColumn); + me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_weaponpriority_useforcycling", _("Use priority list for weapon cycling"))); + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_autoswitch", _("Auto switch weapons on pickup"))); + me.TR(me); + me.TR(me); + me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "r_drawviewmodel", _("Draw 1st person weapon model"))); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 0.9, e = makeXonoticRadioButton(1, "cl_gunalign", "4", _("Left align"))); + setDependent(e, "r_drawviewmodel", 1, 1); + me.TD(me, 1, 0.9, e = makeXonoticRadioButton(1, "cl_gunalign", "1", _("Center"))); + setDependent(e, "r_drawviewmodel", 1, 1); + me.TD(me, 1, 1.0, e = makeXonoticRadioButton(1, "cl_gunalign", "3", _("Right align"))); + setDependent(e, "r_drawviewmodel", 1, 1); + me.TR(me); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_followmodel", _("Gun model swaying"))); + makeMulti(e, "cl_leanmodel"); + setDependent(e, "r_drawviewmodel", 1, 1); + me.TR(me); + me.TDempty(me, 0.2); + me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_bobmodel", _("Gun model bobbing"))); + setDependent(e, "r_drawviewmodel", 1, 1); + //me.TR(me); + //me.TR(me); + // me.TDempty(me, 0.2); + // me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("VWMDL^Scale")))); + // setDependent(e, "r_drawviewmodel", 1, 1); + // me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 2, 0.1, "cl_viewmodel_scale")); + // setDependent(e, "r_drawviewmodel", 1, 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; +} +#endif diff --git a/qcsrc/menu/xonotic/dialog_settings_user.c b/qcsrc/menu/xonotic/dialog_settings_user.c index a3b89744b..fbd9ecf4a 100644 --- a/qcsrc/menu/xonotic/dialog_settings_user.c +++ b/qcsrc/menu/xonotic/dialog_settings_user.c @@ -84,7 +84,7 @@ void XonoticUserSettingsTab_fill(entity me) me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_allow_uid2name", _("Allow player statistics to use your nickname"))); setDependent(e, "cl_allow_uidtracking", 1, 1); - me.gotoRC(me, me.rows - 3, 2.6); + me.gotoRC(me, me.rows - 2, 2.6); me.TD(me, 1, 2, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "sendcvar cl_gentle; sendcvar cl_allow_uidtracking; sendcvar cl_allow_uid2name;", COMMANDBUTTON_APPLY)); } diff --git a/qcsrc/menu/xonotic/dialog_settings_video.c b/qcsrc/menu/xonotic/dialog_settings_video.c index 076dd2337..1258648a4 100644 --- a/qcsrc/menu/xonotic/dialog_settings_video.c +++ b/qcsrc/menu/xonotic/dialog_settings_video.c @@ -49,6 +49,8 @@ void XonoticVideoSettingsTab_fill(entity me) me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "vid_vsync", _("Vertical Synchronization"))); me.TR(me); + if(cvar("developer")) + { me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "v_flipped", _("Flip view horizontally"))); } me.TR(me); me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Anisotropy:"))); me.TD(me, 1, 2, e = makeXonoticTextSlider("gl_texture_anisotropy")); @@ -135,8 +137,6 @@ void XonoticVideoSettingsTab_fill(entity me) me.TR(me); me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "r_trippy", _("Trippy vertices (easter egg)"))); setDependent(e, "vid_gl20", 1, 1); - me.TR(me); - me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "v_flipped", _("Flip view horizontally"))); } me.gotoRC(me, me.rows - 1, 0); diff --git a/qcsrc/menu/xonotic/mainwindow.c b/qcsrc/menu/xonotic/mainwindow.c index f9d86c2dd..7983e0bef 100644 --- a/qcsrc/menu/xonotic/mainwindow.c +++ b/qcsrc/menu/xonotic/mainwindow.c @@ -11,14 +11,16 @@ CLASS(MainWindow) EXTENDS(ModalController) ATTRIB(MainWindow, winnerDialog, entity, NULL) ATTRIB(MainWindow, serverInfoDialog, entity, NULL) ATTRIB(MainWindow, cvarsDialog, entity, NULL) + ATTRIB(MainWindow, screenshotViewerDialog, entity, NULL) ATTRIB(MainWindow, viewDialog, entity, NULL) ATTRIB(MainWindow, modelDialog, entity, NULL) - ATTRIB(MainWindow, crosshairDialog, entity, NULL) ATTRIB(MainWindow, hudDialog, entity, NULL) ATTRIB(MainWindow, hudconfirmDialog, entity, NULL) ATTRIB(MainWindow, mainNexposee, entity, NULL) ATTRIB(MainWindow, fadedAlpha, float, SKINALPHA_BEHIND) ATTRIB(MainWindow, dialogToShow, entity, NULL) + ATTRIB(MainWindow, notificationDialog, entity, NULL) + ATTRIB(MainWindow, democonfirmDialog, entity, NULL) ENDCLASS(MainWindow) #endif @@ -116,10 +118,14 @@ void MainWindow_configureMainWindow(entity me) i = spawnXonoticHUDInfoMessagesDialog(); i.configureDialog(i); me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); - + i = spawnXonoticHUDPhysicsDialog(); i.configureDialog(i); me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); + + me.screenshotViewerDialog = i = spawnXonoticScreenshotViewerDialog(); + i.configureDialog(i); + me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); i = spawnXonoticHUDCenterprintDialog(); i.configureDialog(i); @@ -147,6 +153,10 @@ void MainWindow_configureMainWindow(entity me) i.configureDialog(i); me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); + me.democonfirmDialog = i = spawnXonoticDemoConfirmDialog(); + i.configureDialog(i); + me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); + // dialogs used by multiplayer/create me.mapInfoDialog = i = spawnXonoticMapInfoDialog(); @@ -162,11 +172,7 @@ void MainWindow_configureMainWindow(entity me) me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); - // dialogs used by multiplayer/player setup - me.crosshairDialog = i = spawnXonoticCrosshairDialog(); - i.configureDialog(i); - me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); - + // dialogs used by multiplayer/player setup me.hudDialog = i = spawnXonoticHUDDialog(); i.configureDialog(i); me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); @@ -179,14 +185,13 @@ void MainWindow_configureMainWindow(entity me) i.configureDialog(i); me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); - me.viewDialog = i = spawnXonoticViewDialog(); + me.weaponsDialog = i = spawnXonoticWeaponsDialog(); i.configureDialog(i); me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); - me.weaponsDialog = i = spawnXonoticWeaponsDialog(); + me.notificationDialog = i = spawnXonoticNotificationDialog(); i.configureDialog(i); me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); - // mutator dialogs i = spawnXonoticSandboxToolsDialog(); diff --git a/qcsrc/menu/xonotic/screenshotimage.c b/qcsrc/menu/xonotic/screenshotimage.c new file mode 100644 index 000000000..469f177fc --- /dev/null +++ b/qcsrc/menu/xonotic/screenshotimage.c @@ -0,0 +1,95 @@ +#ifdef INTERFACE +CLASS(XonoticScreenshotImage) EXTENDS(XonoticImage) + METHOD(XonoticScreenshotImage, configureXonoticScreenshotImage, void(entity)) + METHOD(XonoticScreenshotImage, load, void(entity, string)) + METHOD(XonoticScreenshotImage, draw, void(entity)) + ATTRIB(XonoticScreenshotImage, focusable, float, 1) // mousePress and mouseDrag work only if focusable is set + METHOD(XonoticScreenshotImage, mousePress, float(entity, vector)) + METHOD(XonoticScreenshotImage, mouseDrag, float(entity, vector)) + METHOD(XonoticScreenshotImage, mouseMove, float(entity, vector)) + METHOD(XonoticScreenshotImage, resizeNotify, void(entity, vector, vector, vector, vector)) + ATTRIB(XonoticScreenshotImage, realFontSize, vector, '0 0 0') + ATTRIB(XonoticScreenshotImage, fontSize, float, SKINFONTSIZE_NORMAL) + ATTRIB(XonoticScreenshotImage, showTitle, float, 1) + ATTRIB(XonoticScreenshotImage, screenshotTime, float, 0) + ATTRIB(XonoticScreenshotImage, screenshotTitle, string, string_null) +ENDCLASS(XonoticScreenshotImage) +entity makeXonoticScreenshotImage(); +#endif + +#ifdef IMPLEMENTATION +entity makeXonoticScreenshotImage() +{ + entity me; + me = spawnXonoticScreenshotImage(); + me.configureXonoticScreenshotImage(me); + return me; +} + +void XonoticScreenshotImage_configureXonoticScreenshotImage(entity me) +{ + me.configureXonoticImage(me, string_null, -2); + me.zoomLimitedByTheBox = FALSE; // enable this to forbid enlarging the image more than the containing box (if making use of draw_SetClip is a too bad thing) + me.zoomSnapToTheBox = FALSE; // disabled: it's cooler +} + +void XonoticScreenshotImage_load(entity me, string theImage) +{ + me.screenshotTime = time; + me.src = theImage; + if (me.screenshotTitle) + strunzone(me.screenshotTitle); + me.screenshotTitle = strzone(substring(me.src, 13, strlen(theImage) - 13)); //strip "/screenshots/" + + me.initZoom(me); // this image may have a different size + me.setZoom(me, 0, 0); +} + +float XonoticScreenshotImage_mousePress(entity me, vector coords) +{ + return me.drag_setStartPos(me, coords); +} + +float XonoticScreenshotImage_mouseDrag(entity me, vector coords) +{ + return me.drag(me, coords); +} + +float XonoticScreenshotImage_mouseMove(entity me, vector coords) +{ + return me.drag_setStartPos(me, coords); +} + +void XonoticScreenshotImage_draw(entity me) +{ + if (me.src != "") + { + float theAlpha; + SUPER(XonoticScreenshotImage).draw(me); + if (me.showTitle && time < me.screenshotTime + 4) // 3 seconds at full alpha, 1 second fading out + { + theAlpha = (4 - (time - me.screenshotTime)); + draw_CenterText('0.5 0 0', me.screenshotTitle, me.realFontSize, '1 1 1', theAlpha, FALSE); + } + if (time < me.zoomTime + 2) // 1 seconds at full alpha, 1 second fading out + { + string zoomString; + float z; + z = me.zoomFactor * 100; + if (z - floor(z) == 0) + zoomString = sprintf("%d%%", z); + else + zoomString = sprintf("%.2f%%", z); + theAlpha = (2 - (time - me.zoomTime)); + draw_Text('0.05 0.95 0', zoomString, me.realFontSize, '1 1 1', theAlpha, FALSE); + } + } +} + +void XonoticScreenshotImage_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize) +{ + SUPER(XonoticScreenshotImage).resizeNotify(me, relOrigin, relSize, absOrigin, absSize); + me.realFontSize_y = me.fontSize / absSize_y; + me.realFontSize_x = me.fontSize / absSize_x; +} +#endif diff --git a/qcsrc/menu/xonotic/screenshotlist.c b/qcsrc/menu/xonotic/screenshotlist.c new file mode 100644 index 000000000..49582e8cc --- /dev/null +++ b/qcsrc/menu/xonotic/screenshotlist.c @@ -0,0 +1,293 @@ +#ifdef INTERFACE +CLASS(XonoticScreenshotList) EXTENDS(XonoticListBox) + METHOD(XonoticScreenshotList, configureXonoticScreenshotList, void(entity)) + ATTRIB(XonoticScreenshotList, rowsPerItem, float, 1) + METHOD(XonoticScreenshotList, resizeNotify, void(entity, vector, vector, vector, vector)) + METHOD(XonoticScreenshotList, setSelected, void(entity, float)) + METHOD(XonoticScreenshotList, draw, void(entity)) + METHOD(XonoticScreenshotList, drawListBoxItem, void(entity, float, vector, float)) + METHOD(XonoticScreenshotList, getScreenshots, void(entity)) + METHOD(XonoticScreenshotList, previewScreenshot, void(entity)) + METHOD(XonoticScreenshotList, startScreenshot, void(entity)) + METHOD(XonoticScreenshotList, screenshotName, string(entity, float)) + METHOD(XonoticScreenshotList, clickListBoxItem, void(entity, float, vector)) + METHOD(XonoticScreenshotList, keyDown, float(entity, float, float, float)) + METHOD(XonoticScreenshotList, destroy, void(entity)) + METHOD(XonoticScreenshotList, showNotify, void(entity)) + ATTRIB(XonoticScreenshotList, listScreenshot, float, -1) + ATTRIB(XonoticScreenshotList, realFontSize, vector, '0 0 0') + ATTRIB(XonoticScreenshotList, columnNameOrigin, float, 0) + ATTRIB(XonoticScreenshotList, columnNameSize, float, 0) + ATTRIB(XonoticScreenshotList, realUpperMargin, float, 0) + ATTRIB(XonoticScreenshotList, origin, vector, '0 0 0') + ATTRIB(XonoticScreenshotList, itemAbsSize, vector, '0 0 0') + ATTRIB(XonoticScreenshotList, lastClickedScreenshot, float, -1) + ATTRIB(XonoticScreenshotList, lastClickedTime, float, 0) + ATTRIB(XonoticScreenshotList, filterString, string, string_null) + ATTRIB(XonoticScreenshotList, filterBox, entity, NULL) + ATTRIB(XonoticScreenshotList, filterTime, float, 0) + + ATTRIB(XonoticScreenshotList, newScreenshotTime, float, 0) + ATTRIB(XonoticScreenshotList, newSlideShowScreenshotTime, float, 0) + ATTRIB(XonoticScreenshotList, prevSelectedItem, float, 0) + + ATTRIB(XonoticScreenshotList, screenshotBrowserDialog, entity, NULL) + ATTRIB(XonoticScreenshotList, screenshotPreview, entity, NULL) + ATTRIB(XonoticScreenshotList, screenshotViewerDialog, entity, NULL) + METHOD(XonoticScreenshotList, goScreenshot, void(entity, float)) + METHOD(XonoticScreenshotList, startSlideShow, void(entity)) + METHOD(XonoticScreenshotList, stopSlideShow, void(entity)) +ENDCLASS(XonoticScreenshotList) + +entity makeXonoticScreenshotList(); +void StartScreenshot_Click(entity btn, entity me); +void ScreenshotList_Refresh_Click(entity btn, entity me); +void ScreenshotList_Filter_Would_Change(entity box, entity me); +void ScreenshotList_Filter_Change(entity box, entity me); +#endif + +#ifdef IMPLEMENTATION + +entity makeXonoticScreenshotList() +{ + entity me; + me = spawnXonoticScreenshotList(); + me.configureXonoticScreenshotList(me); + return me; +} + +void XonoticScreenshotList_configureXonoticScreenshotList(entity me) +{ + me.configureXonoticListBox(me); + me.getScreenshots(me); +} + +string XonoticScreenshotList_screenshotName(entity me, float i ) +{ + string s; + s = bufstr_get(me.listScreenshot, i); + s = substring(s, 12, strlen(s) - 12 - 4); // screenshots/, . + return s; +} + +// if subdir is TRUE look in subdirectories too (1 level) +void getScreenshots_for_ext(entity me, string ext, float subdir) +{ + string s; + if (subdir) + s="screenshots/*/"; + else + s="screenshots/"; + if(me.filterString) + s=strcat(s, me.filterString, ext); + else + s=strcat(s, "*", ext); + + float list, i, n; + list = search_begin(s, FALSE, TRUE); + if(list >= 0) + { + n = search_getsize(list); + for(i = 0; i < n; ++i) + bufstr_add(me.listScreenshot, search_getfilename(list, i), TRUE); + search_end(list); + } + + if (subdir) + getScreenshots_for_ext(me, ext, FALSE); +} + +void XonoticScreenshotList_getScreenshots(entity me) +{ + if (me.listScreenshot >= 0) + buf_del(me.listScreenshot); + me.listScreenshot = buf_create(); + if (me.listScreenshot < 0) + { + me.nItems = 0; + return; + } + getScreenshots_for_ext(me, ".jpg", TRUE); + getScreenshots_for_ext(me, ".tga", TRUE); + getScreenshots_for_ext(me, ".png", TRUE); + me.nItems = buf_getsize(me.listScreenshot); + if(me.nItems > 0) + buf_sort(me.listScreenshot, 128, FALSE); +} + +void XonoticScreenshotList_destroy(entity me) +{ + if(me.nItems > 0) + buf_del(me.listScreenshot); +} + +void XonoticScreenshotList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize) +{ + me.itemAbsSize = '0 0 0'; + SUPER(XonoticScreenshotList).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.columnNameOrigin = me.realFontSize_x; + me.columnNameSize = 1 - 2 * me.realFontSize_x; +} + +void XonoticScreenshotList_setSelected(entity me, float i) +{ + if (me.newSlideShowScreenshotTime) + me.startSlideShow(me); + me.prevSelectedItem = me.selectedItem; + SUPER(XonoticScreenshotList).setSelected(me, i); + if (me.pressed && me.selectedItem != me.prevSelectedItem) + { + // while dragging the scrollbar (or an item) + // for a smooth mouse movement do not load immediately the new selected images + me.newScreenshotTime = time + 0.22; // dragging an item we need a delay > 0.2 (from listbox: me.dragScrollTimer = time + 0.2;) + } + else if (time > me.newScreenshotTime) + { + me.newScreenshotTime = 0; + me.previewScreenshot(me); // load the preview on selection change + } +} + +void XonoticScreenshotList_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.screenshotName(me,i); + s = draw_TextShortenToWidth(s, me.columnNameSize, 0, me.realFontSize); + draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + 0.00 * (me.columnNameSize - draw_TextWidth(s, 0, me.realFontSize))) * eX, s, me.realFontSize, '1 1 1', SKINALPHA_TEXT, 0); +} + +void XonoticScreenshotList_showNotify(entity me) +{ + me.getScreenshots(me); + me.previewScreenshot(me); +} + +void ScreenshotList_Refresh_Click(entity btn, entity me) +{ + me.getScreenshots(me); + me.setSelected(me, 0); //always select the first element after a list update +} + +void ScreenshotList_Filter_Change(entity box, entity me) +{ + if(me.filterString) + strunzone(me.filterString); + + if(box.text != "") + { + if (strstrofs(box.text, "*", 0) >= 0 || strstrofs(box.text, "?", 0) >= 0) + me.filterString = strzone(box.text); + else + me.filterString = strzone(strcat("*", box.text, "*")); + } + else + me.filterString = string_null; + + ScreenshotList_Refresh_Click(world, me); +} + +void ScreenshotList_Filter_Would_Change(entity box, entity me) +{ + me.filterBox = box; + me.filterTime = time + 0.5; +} + +void XonoticScreenshotList_draw(entity me) +{ + if (me.filterTime && time > me.filterTime) + { + ScreenshotList_Filter_Change(me.filterBox, me); + me.filterTime = 0; + } + if (me.newScreenshotTime && time > me.newScreenshotTime) + { + me.previewScreenshot(me); + me.newScreenshotTime = 0; + } + else if (me.newSlideShowScreenshotTime && time > me.newSlideShowScreenshotTime) + { + if (me.selectedItem == me.nItems - 1) //last screenshot? + { + // restart from the first screenshot + me.setSelected(me, 0); + me.goScreenshot(me, +0); + } + else + me.goScreenshot(me, +1); + } + SUPER(XonoticScreenshotList).draw(me); +} + +void XonoticScreenshotList_startSlideShow(entity me) +{ + me.newSlideShowScreenshotTime = time + 3; +} + +void XonoticScreenshotList_stopSlideShow(entity me) +{ + me.newSlideShowScreenshotTime = 0; +} + +void XonoticScreenshotList_goScreenshot(entity me, float d) +{ + if(!me.screenshotViewerDialog) + return; + me.setSelected(me, me.selectedItem + d); + me.screenshotViewerDialog.loadScreenshot(me.screenshotViewerDialog, strcat("/screenshots/", me.screenshotName(me,me.selectedItem))); +} + +void XonoticScreenshotList_startScreenshot(entity me) +{ + me.screenshotViewerDialog.loadScreenshot(me.screenshotViewerDialog, strcat("/screenshots/", me.screenshotName(me,me.selectedItem))); + // pop up screenshot + DialogOpenButton_Click_withCoords(NULL, me.screenshotViewerDialog, 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)); +} + +void XonoticScreenshotList_previewScreenshot(entity me) +{ + if(!me.screenshotBrowserDialog) + return; + if (me.nItems <= 0) + me.screenshotBrowserDialog.loadPreviewScreenshot(me.screenshotBrowserDialog, ""); + else + me.screenshotBrowserDialog.loadPreviewScreenshot(me.screenshotBrowserDialog, strcat("/screenshots/", me.screenshotName(me,me.selectedItem))); +} + +void StartScreenshot_Click(entity btn, entity me) +{ + me.startScreenshot(me); +} + +void XonoticScreenshotList_clickListBoxItem(entity me, float i, vector where) +{ + if(i == me.lastClickedScreenshot) + if(time < me.lastClickedTime + 0.3) + { + // DOUBLE CLICK! + // pop up screenshot + me.setSelected(me, i); + me.startScreenshot(me); + } + me.lastClickedScreenshot = i; + me.lastClickedTime = time; +} + +float XonoticScreenshotList_keyDown(entity me, float scan, float ascii, float shift) +{ + if(scan == K_ENTER || scan == K_KP_ENTER) { + me.startScreenshot(me); + return 1; + } + if(scan == K_MWHEELUP || scan == K_MWHEELDOWN) + me.newScreenshotTime = time + 0.2; + return SUPER(XonoticScreenshotList).keyDown(me, scan, ascii, shift); +} +#endif diff --git a/qcsrc/menu/xonotic/serverlist.c b/qcsrc/menu/xonotic/serverlist.c index 86fdf1c63..69f5d4737 100644 --- a/qcsrc/menu/xonotic/serverlist.c +++ b/qcsrc/menu/xonotic/serverlist.c @@ -7,6 +7,7 @@ CLASS(XonoticServerList) EXTENDS(XonoticListBox) METHOD(XonoticServerList, clickListBoxItem, 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) @@ -35,7 +36,7 @@ CLASS(XonoticServerList) EXTENDS(XonoticListBox) ATTRIB(XonoticServerList, ipAddressBox, entity, NULL) ATTRIB(XonoticServerList, favoriteButton, entity, NULL) ATTRIB(XonoticServerList, nextRefreshTime, float, 0) - METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: 0 = just reparametrize, 1 = send new requests, 2 = clear + 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))) @@ -55,10 +56,70 @@ CLASS(XonoticServerList) EXTENDS(XonoticListBox) 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); @@ -66,47 +127,114 @@ void ServerList_Favorite_Click(entity btn, entity me); void ServerList_Info_Click(entity btn, entity me); void ServerList_Update_favoriteButton(entity btn, entity me); -#ifndef IMPLEMENTATION -float SLIST_FIELD_CNAME; -float SLIST_FIELD_PING; -float SLIST_FIELD_GAME; -float SLIST_FIELD_MOD; -float SLIST_FIELD_MAP; -float SLIST_FIELD_NAME; -float SLIST_FIELD_MAXPLAYERS; -float SLIST_FIELD_NUMPLAYERS; -float SLIST_FIELD_NUMHUMANS; -float SLIST_FIELD_NUMBOTS; -float SLIST_FIELD_PROTOCOL; -float SLIST_FIELD_FREESLOTS; -float SLIST_FIELD_PLAYERS; -float SLIST_FIELD_QCSTATUS; -float SLIST_FIELD_ISFAVORITE; -#endif +// fields for category entities +#define MAX_CATEGORIES 9 +#define 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, "", "CAT_SERVERS", 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_MINSTAGIB, "", "CAT_SERVERS", ZCTX(_("SLCAT^MinstaGib 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; \ + var string SLIST_CATEGORY_AUTOCVAR(name) = enoverride; +SLIST_CATEGORIES +#undef SLIST_CATEGORY #endif - +#endif #ifdef IMPLEMENTATION -void ServerList_UpdateFieldIDs() + +void RegisterSLCategories() { - SLIST_FIELD_CNAME = gethostcacheindexforkey( "cname" ); - SLIST_FIELD_PING = gethostcacheindexforkey( "ping" ); - SLIST_FIELD_GAME = gethostcacheindexforkey( "game" ); - SLIST_FIELD_MOD = gethostcacheindexforkey( "mod" ); - SLIST_FIELD_MAP = gethostcacheindexforkey( "map" ); - SLIST_FIELD_NAME = gethostcacheindexforkey( "name" ); - SLIST_FIELD_MAXPLAYERS = gethostcacheindexforkey( "maxplayers" ); - SLIST_FIELD_NUMPLAYERS = gethostcacheindexforkey( "numplayers" ); - SLIST_FIELD_NUMHUMANS = gethostcacheindexforkey( "numhumans" ); - SLIST_FIELD_NUMBOTS = gethostcacheindexforkey( "numbots" ); - SLIST_FIELD_PROTOCOL = gethostcacheindexforkey( "protocol" ); - SLIST_FIELD_FREESLOTS = gethostcacheindexforkey( "freeslots" ); - SLIST_FIELD_PLAYERS = gethostcacheindexforkey( "players" ); - SLIST_FIELD_QCSTATUS = gethostcacheindexforkey( "qcstatus" ); - SLIST_FIELD_ISFAVORITE = gethostcacheindexforkey( "isfavorite" ); -} - -float IsFavorite(string srv) + 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 \ + { \ + print(sprintf( \ + "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; @@ -116,7 +244,7 @@ float IsFavorite(string srv) if(srv == "") return FALSE; p = crypto_getidfp(srv); - n = tokenize_console(cvar_string("net_slist_favorites")); + 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) @@ -134,7 +262,124 @@ float IsFavorite(string srv) return FALSE; } -void ToggleFavorite(string srv) +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": { return CAT_MINSTAGIB; } + 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: { dprint(sprintf("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; @@ -181,15 +426,16 @@ void ToggleFavorite(string srv) cvar_set("net_slist_favorites", strcat(s, s1, srv)); } - resorthostcache(); + me.refreshServerList(me, REFRESHSERVERLIST_RESORT); } void ServerList_Update_favoriteButton(entity btn, entity me) { - if(IsFavorite(me.ipAddressBox.text)) - me.favoriteButton.setText(me.favoriteButton, _("Remove")); - else - me.favoriteButton.setText(me.favoriteButton, _("Bookmark")); + me.favoriteButton.setText(me.favoriteButton, + (IsFavorite(me.ipAddressBox.text) ? + _("Remove") : _("Favorite") + ) + ); } entity makeXonoticServerList() @@ -203,8 +449,12 @@ void XonoticServerList_configureXonoticServerList(entity me) { me.configureXonoticListBox(me); - ServerList_UpdateFieldIDs(); + // 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) @@ -231,20 +481,14 @@ void XonoticServerList_setSelected(entity me, float i) } void XonoticServerList_refreshServerList(entity me, float mode) { - // 0: just reparametrize - // 1: also ask for new servers - // 2: clear //print("refresh of type ", ftos(mode), "\n"); - /* if(mode == 2) // borken - { - // clear list - localcmd("net_slist\n"); - me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later - } - else */ + + if(mode >= REFRESHSERVERLIST_REFILTER) { - float m, o, i, n; // moin moin + float m, i, n; + float listflags = 0; string s, typestr, modstr; + s = me.filterString; m = strstrofs(s, ":", 0); @@ -304,14 +548,17 @@ void XonoticServerList_refreshServerList(entity me, float mode) sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS); sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH); } - o = 2; // favorites first - if(me.currentSortOrder < 0) - o |= 1; // descending - sethostcachesort(me.currentSortField, o); - resorthostcache(); - if(mode >= 1) - refreshhostcache(); + + // 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) { @@ -321,8 +568,9 @@ void XonoticServerList_focusEnter(entity me) return; } me.nextRefreshTime = time + 10; - me.refreshServerList(me, 1); + me.refreshServerList(me, REFRESHSERVERLIST_ASK); } + void XonoticServerList_draw(entity me) { float i, found, owned; @@ -334,10 +582,24 @@ void XonoticServerList_draw(entity me) _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, 2); + me.refreshServerList(me, REFRESHSERVERLIST_RESET); } else if(me.needsRefresh == 1) { @@ -346,12 +608,107 @@ void XonoticServerList_draw(entity me) else if(me.needsRefresh == 2) { me.needsRefresh = 0; - me.refreshServerList(me, 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 != "")); - me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); + 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); @@ -361,6 +718,7 @@ void XonoticServerList_draw(entity me) if(me.selectedServer) { for(i = 0; i < me.nItems; ++i) + { if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer) { if(i != me.selectedItem) @@ -371,8 +729,10 @@ void XonoticServerList_draw(entity me) found = 1; break; } + } } if(!found) + { if(me.nItems > 0) { if(me.selectedItem >= me.nItems) @@ -381,7 +741,8 @@ void XonoticServerList_draw(entity me) strunzone(me.selectedServer); me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem)); } - + } + if(owned) { if(me.selectedServer != me.ipAddressBox.text) @@ -471,7 +832,16 @@ void ServerList_Filter_Change(entity box, entity me) me.filterString = strzone(box.text); else me.filterString = string_null; - me.refreshServerList(me, 0); + 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; @@ -480,7 +850,7 @@ void ServerList_Filter_Change(entity box, entity me) void ServerList_ShowEmpty_Click(entity box, entity me) { box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty); - me.refreshServerList(me, 0); + me.refreshServerList(me, REFRESHSERVERLIST_REFILTER); me.ipAddressBox.setText(me.ipAddressBox, ""); me.ipAddressBox.cursorPos = 0; @@ -489,7 +859,7 @@ void ServerList_ShowEmpty_Click(entity box, entity me) void ServerList_ShowFull_Click(entity box, entity me) { box.setChecked(box, me.filterShowFull = !me.filterShowFull); - me.refreshServerList(me, 0); + me.refreshServerList(me, REFRESHSERVERLIST_REFILTER); me.ipAddressBox.setText(me.ipAddressBox, ""); me.ipAddressBox.cursorPos = 0; @@ -510,7 +880,7 @@ void XonoticServerList_setSortOrder(entity me, float fld, float direction) if(me.selectedServer) strunzone(me.selectedServer); me.selectedServer = string_null; - me.refreshServerList(me, 0); + me.refreshServerList(me, REFRESHSERVERLIST_REFILTER); } void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc) { @@ -566,10 +936,11 @@ void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, } void ServerList_Connect_Click(entity btn, entity me) { - if(me.ipAddressBox.text == "") - localcmd("connect ", me.selectedServer, "\n"); - else - localcmd("connect ", me.ipAddressBox.text, "\n"); + localcmd(sprintf("connect %s\n", + ((me.ipAddressBox.text != "") ? + me.ipAddressBox.text : me.selectedServer + ) + )); } void ServerList_Favorite_Click(entity btn, entity me) { @@ -577,13 +948,14 @@ void ServerList_Favorite_Click(entity btn, entity me) ipstr = netaddress_resolve(me.ipAddressBox.text, 26000); if(ipstr != "") { - ToggleFavorite(me.ipAddressBox.text); + me.toggleFavorite(me, me.ipAddressBox.text); me.ipAddressBoxFocused = -1; } } void ServerList_Info_Click(entity btn, entity me) { - main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem); + if (me.nItems != 0) + main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem); DialogOpenButton_Click(me, main.serverInfoDialog); } void XonoticServerList_clickListBoxItem(entity me, float i, vector where) @@ -607,6 +979,48 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float float m, pure, freeslots, j, sflags; string s, typestr, versionstr, k, v, modname; + //print(sprintf("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, + '1 1 1', + SKINALPHA_TEXT, + 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); @@ -736,83 +1150,96 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float // 4: AES recommended and will be used // 5: AES required - { - vector iconSize = '0 0 0'; - iconSize_y = me.realFontSize_y * me.iconsSizeFactor; - iconSize_x = me.realFontSize_x * me.iconsSizeFactor; + // -------------- + // 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; + vector iconPos = '0 0 0'; + iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5; + iconPos_y = (1 - iconSize_y) * 0.5; - string n; + string n; - if not(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 + if not(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(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(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(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS)) + if(modname == "Xonotic") + { + if(pure == 0) { - draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP + draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP); draw_Picture(iconPos, n, iconSize, '1 1 1', 1); } - iconPos_x += iconSize_x; } + 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) { - float i; vector org, sz; org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size); @@ -835,10 +1262,9 @@ float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift) } else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS) { - i = me.selectedItem; - if(i < me.nItems) + if(me.nItems != 0) { - ToggleFavorite(me.selectedServer); + me.toggleFavorite(me, me.selectedServer); me.ipAddressBoxFocused = -1; return 1; } @@ -851,4 +1277,47 @@ float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift) 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 --git a/qcsrc/menu/xonotic/slider_particles.c b/qcsrc/menu/xonotic/slider_particles.c new file mode 100644 index 000000000..db29f556d --- /dev/null +++ b/qcsrc/menu/xonotic/slider_particles.c @@ -0,0 +1,48 @@ +#ifdef INTERFACE +CLASS(XonoticParticlesSlider) EXTENDS(XonoticTextSlider) + METHOD(XonoticParticlesSlider, configureXonoticParticlesSlider, void(entity)) + METHOD(XonoticParticlesSlider, loadCvars, void(entity)) + METHOD(XonoticParticlesSlider, saveCvars, void(entity)) +ENDCLASS(XonoticParticlesSlider) +entity makeXonoticParticlesSlider(); +#endif + +#ifdef IMPLEMENTATION +entity makeXonoticParticlesSlider() +{ + entity me; + me = spawnXonoticParticlesSlider(); + me.configureXonoticParticlesSlider(me); + return me; +} +void XonoticParticlesSlider_configureXonoticParticlesSlider(entity me) +{ + me.configureXonoticTextSlider(me, "cl_particles_quality"); + if(cvar("developer")) { me.addValue(me, ZCTX(_("PART^OMG")), "0.4 250 0"); } + me.addValue(me, ZCTX(_("PART^Low")), "0.4 500 0"); + me.addValue(me, ZCTX(_("PART^Medium")), "0.8 750 0"); + me.addValue(me, ZCTX(_("PART^Normal")), "1.0 1000 1"); + me.addValue(me, ZCTX(_("PART^High")), "1.0 1500 1"); + me.addValue(me, ZCTX(_("PART^Ultra")), "1.0 2000 2"); + if(cvar("developer")) { me.addValue(me, ZCTX(_("PART^Ultimate")), "1.0 3000 2"); } + me.configureXonoticTextSliderValues(me); +} +void XonoticParticlesSlider_loadCvars(entity me) +{ + me.setValueFromIdentifier(me, sprintf("%s %s %s", + cvar_string("cl_particles_quality"), + cvar_string("r_drawparticles_drawdistance"), + cvar_string("cl_damageeffect") + )); +} +void XonoticParticlesSlider_saveCvars(entity me) +{ + if(me.value >= 0 || me.value < me.nValues) + { + tokenize_console(me.getIdentifier(me)); + cvar_set("cl_particles_quality", argv(0)); + cvar_set("r_drawparticles_drawdistance", argv(1)); + cvar_set("cl_damageeffect", argv(2)); + } +} +#endif diff --git a/qcsrc/menu/xonotic/util.qc b/qcsrc/menu/xonotic/util.qc index e5259cc8e..3f651727c 100644 --- a/qcsrc/menu/xonotic/util.qc +++ b/qcsrc/menu/xonotic/util.qc @@ -313,41 +313,94 @@ void UpdateNotification_URI_Get_Callback(float id, float status, string data) n = 0; else n = tokenizebyseparator(data, "\n"); + + float i; + string s; + + string un_version = ""; + string un_download = ""; + string un_url = ""; + string un_bannedservers = ""; + string un_emergency_pk3s = ""; + string un_promoted = ""; + string un_recommended = ""; - if(n >= 1) + for(i = 0; i < n; ++i) { - _Nex_ExtResponseSystem_UpdateTo = argv(0); - - if(vercmp(cvar_string("g_xonoticversion"), _Nex_ExtResponseSystem_UpdateTo) >= 0) + s = substring(argv(i), 2, -1); + if(s == "") { continue; } // ignore empty lines + + switch(substring(argv(i), 0, 1)) { - _Nex_ExtResponseSystem_UpdateTo = ""; // no update needed + #define APPEND_TO_STRING(list,sep,add) ((list) = (((list) != "") ? strcat(list, sep, add) : (add))) + case "V": + { + un_version = s; + break; + } + case "D": + { + un_download = s; + break; + } + case "U": + { + un_url = s; + break; + } + case "B": + { + APPEND_TO_STRING(un_bannedservers, " ", s); + break; + } + case "E": + { + if(cvar("menu_updatecheck_getpacks")) + APPEND_TO_STRING(un_emergency_pk3s, " ", s); + break; + } + case "P": + { + APPEND_TO_STRING(un_promoted, " ", s); + break; + } + case "R": + { + APPEND_TO_STRING(un_recommended, " ", s); + break; + } } - else + } + + if(un_version != "") + { + if(vercmp(cvar_string("g_xonoticversion"), un_version) < 0) { // update needed - if(n >= 2) - print(sprintf(_("Update can be downloaded at:\n%s\n"), argv(1))); - if(n >= 3) - _Nex_ExtResponseSystem_UpdateToURL = strzone(argv(2)); + _Nex_ExtResponseSystem_UpdateTo = strzone(un_version); + if(un_download) { print(sprintf(_("Update can be downloaded at:\n%s\n"), un_download)); } + if(un_url) { _Nex_ExtResponseSystem_UpdateToURL = strzone(un_url); } } + } + + if(un_emergency_pk3s != "") + { + _Nex_ExtResponseSystem_Packs = strzone(argv(4)); + _Nex_ExtResponseSystem_PacksStep = 1; + } - _Nex_ExtResponseSystem_UpdateTo = strzone(_Nex_ExtResponseSystem_UpdateTo); - - if(n >= 4) - { - _Nex_ExtResponseSystem_BannedServers = strzone(argv(3)); - _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 1; - } + if(un_promoted != "") + { + _Nex_ExtResponseSystem_PromotedServers = strzone(un_promoted); + _Nex_ExtResponseSystem_PromotedServersNeedsRefresh = 1; + } - if(n >= 5) - { - if(cvar("menu_updatecheck_getpacks")) - { - _Nex_ExtResponseSystem_Packs = strzone(argv(4)); - _Nex_ExtResponseSystem_PacksStep = 1; - } - } + if(un_recommended != "") + { + _Nex_ExtResponseSystem_RecommendedServers = strzone(un_recommended); + _Nex_ExtResponseSystem_RecommendedServersNeedsRefresh = 1; } + } // END OF URI SYSTEM //////////////////////////////////////////////////////// @@ -366,7 +419,7 @@ void updateCheck() // for privacy, munge the start count a little startcnt = floor((floor(startcnt / 10) + random()) * 10); - uri = sprintf("http://www.xonotic.org/dl/checkupdate.txt?version=%s&cnt=%d", uri_escape(cvar_string("g_xonoticversion")), startcnt); + uri = sprintf("http://www.xonotic.org/dl/checkupdate.txt?format=keys&version=%s&cnt=%d", uri_escape(cvar_string("g_xonoticversion")), startcnt); #ifdef CVAR_POPCON float cvar_handle, popcon_handle; diff --git a/qcsrc/menu/xonotic/util.qh b/qcsrc/menu/xonotic/util.qh index be13ee289..3371dd811 100644 --- a/qcsrc/menu/xonotic/util.qh +++ b/qcsrc/menu/xonotic/util.qh @@ -45,3 +45,7 @@ void dialog_hudpanel_common_notoggle(entity me, string panelname); string _Nex_ExtResponseSystem_BannedServers; float _Nex_ExtResponseSystem_BannedServersNeedsRefresh; +string _Nex_ExtResponseSystem_PromotedServers; +float _Nex_ExtResponseSystem_PromotedServersNeedsRefresh; +string _Nex_ExtResponseSystem_RecommendedServers; +float _Nex_ExtResponseSystem_RecommendedServersNeedsRefresh; diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc index fac4314ca..cdd3592a7 100644 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@ -167,7 +167,7 @@ void PutObserverInServer (void) MUTATOR_CALLHOOK(MakePlayerObserver); Portal_ClearAll(self); - + if(self.alivetime) { if(!warmup_stage) @@ -176,7 +176,7 @@ void PutObserverInServer (void) } if(self.vehicle) - vehicles_exit(VHEF_RELESE); + vehicles_exit(VHEF_RELESE); WaypointSprite_PlayerDead(); @@ -198,7 +198,7 @@ void PutObserverInServer (void) accuracy_resend(self); self.spectatortime = time; - + self.classname = "observer"; self.iscreature = FALSE; self.teleportable = TELEPORT_SIMPLE; @@ -558,7 +558,7 @@ void PutClientInServer (void) //stuffcmd(self, "chase_active 0"); //stuffcmd(self, "set viewsize $tmpviewsize \n"); - + target_voicescript_clear(self); // reset fields the weapons may use @@ -766,7 +766,7 @@ void ClientKill_Now() if(!self.killindicator_teamchange) { self.vehicle_health = -1; - Damage(self, self, self, 1 , DEATH_KILL, self.origin, '0 0 0'); + Damage(self, self, self, 1 , DEATH_KILL, self.origin, '0 0 0'); } } @@ -934,7 +934,7 @@ void ClientKill (void) if(gameover) return; if(self.player_blocked) return; if(self.freezetag_frozen) return; - + ClientKill_TeamChange(0); } @@ -1039,6 +1039,7 @@ void ClientConnect (void) player_count = 0; } + PlayerInfo_Basic(self); PlayerScore_Attach(self); ClientData_Attach(); accuracy_init(self); @@ -1277,7 +1278,7 @@ void ClientDisconnect (void) if(autocvar_sv_eventlog) GameLogEcho(strcat(":part:", ftos(self.playerid))); - + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_QUIT_DISCONNECT, self.netname); MUTATOR_CALLHOOK(ClientDisconnect); @@ -1516,7 +1517,7 @@ void player_powerups (void) self.superweapons_finished = 0; } } - + if(autocvar_g_nodepthtestplayers) self.effects = self.effects | EF_NODEPTHTEST; @@ -1730,7 +1731,7 @@ void SpectateCopy(entity spectatee) { setorigin(self, spectatee.origin); setsize(self, spectatee.mins, spectatee.maxs); SetZoomState(spectatee.zoomstate); - + anticheat_spectatecopy(spectatee); self.hud = spectatee.hud; if(spectatee.vehicle) @@ -1746,22 +1747,22 @@ void SpectateCopy(entity spectatee) { self.vehicle_reload2 = spectatee.vehicle_reload2; msg_entity = self; - + WriteByte (MSG_ONE, SVC_SETVIEWANGLES); WriteAngle(MSG_ONE, spectatee.v_angle_x); WriteAngle(MSG_ONE, spectatee.v_angle_y); WriteAngle(MSG_ONE, spectatee.v_angle_z); //WriteByte (MSG_ONE, SVC_SETVIEW); - // WriteEntity(MSG_ONE, self); + // WriteEntity(MSG_ONE, self); //makevectors(spectatee.v_angle); - //setorigin(self, spectatee.origin - v_forward * 400 + v_up * 300);*/ + //setorigin(self, spectatee.origin - v_forward * 400 + v_up * 300);*/ } } float SpectateUpdate() { if(!self.enemy) - return 0; + return 0; if (self == self.enemy) return 0; @@ -1820,13 +1821,13 @@ entity CA_SpectateNext(entity start) { if (start.team == self.team) { return start; } - + other = start; // continue from current player while(other && other.team != self.team) { other = find(other, classname, "player"); } - + if (!other) { // restart from begining other = find(other, classname, "player"); @@ -1834,7 +1835,7 @@ entity CA_SpectateNext(entity start) { other = find(other, classname, "player"); } } - + return other; } @@ -2143,7 +2144,7 @@ void PlayerUseKey() vehicles_exit(VHEF_NORMAL); return; } - + // a use key was pressed; call handlers MUTATOR_CALLHOOK(PlayerUseKey); } @@ -2305,7 +2306,7 @@ void PlayerPreThink (void) if(frametime) player_anim(); button_pressed = (self.BUTTON_ATCK || self.BUTTON_JUMP || self.BUTTON_ATCK2 || self.BUTTON_HOOK || self.BUTTON_USE); - + if (self.deadflag == DEAD_DYING) { if(self.respawn_flags & RESPAWN_FORCE) @@ -2410,10 +2411,10 @@ void PlayerPreThink (void) if(frametime) player_anim(); - + // secret status secrets_setstatus(); - + self.dmg_team = max(0, self.dmg_team - autocvar_g_teamdamage_resetspeed * frametime); //self.angles_y=self.v_angle_y + 90; // temp @@ -2573,7 +2574,7 @@ void PlayerPostThink (void) return; // intermission or finale GetPressedKeys(); } - + #ifdef TETRIS } #endif diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index fe41a23fa..93eff518f 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -55,7 +55,7 @@ float team1_score, team2_score, team3_score, team4_score; float maxclients; // flag set on worldspawn so that the code knows if it is dedicated or not -float server_is_dedicated; +float server_is_dedicated; // Fields @@ -475,7 +475,7 @@ void target_voicescript_clear(entity pl); .float target_random; .float trigger_reverse; -// Nexball +// Nexball .entity ballcarried; // Also used for keepaway .float metertime; float g_nexball_meter_period; @@ -516,8 +516,8 @@ string matchid; .float last_pickup; -.float hit_time; -.float typehit_time; +.float hit_time; +.float typehit_time; .float stat_leadlimit; @@ -615,3 +615,12 @@ string modname; #define MISSILE_IS_CONFUSABLE(m) ((m.missile_flags & MIF_GUIDED_CONFUSABLE) ? TRUE : FALSE) #define MISSILE_IS_GUIDED(m) ((m.missile_flags & MIF_GUIDED_ALL) ? TRUE : FALSE) #define MISSILE_IS_TRACKING(m) ((m.missile_flags & MIF_GUIDED_TRACKING) ? TRUE : FALSE) + + +//// + +.entity player_stats; +//.float playerid; +.string playernick; +.float elos; +.float ranks; diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index 0b5b42781..d6c1cf1d6 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -647,16 +647,16 @@ float want_weapon(string cvarprefix, entity weaponinfo, float allguns) d = 0; // weapon is set a few lines later else d = (i == WEP_LASER || i == WEP_SHOTGUN); - + if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook d |= (i == WEP_HOOK); if(weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED) // never default mutator blocked guns d = 0; var float t = cvar(strcat(cvarprefix, weaponinfo.netname)); - + //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n")); - + // bit order in t: // 1: want or not // 2: is default? @@ -903,7 +903,7 @@ void readlevelcvars(void) // load mutators #define CHECK_MUTATOR_ADD(mut_cvar,mut_name,dependence) \ { if(cvar(mut_cvar) && dependence) { MUTATOR_ADD(mut_name); } } - + CHECK_MUTATOR_ADD("g_dodging", mutator_dodging, 1); CHECK_MUTATOR_ADD("g_spawn_near_teammate", mutator_spawn_near_teammate, 1); CHECK_MUTATOR_ADD("g_physical_items", mutator_physical_items, 1); @@ -924,9 +924,9 @@ void readlevelcvars(void) CHECK_MUTATOR_ADD("g_nades", mutator_nades, 1); CHECK_MUTATOR_ADD("g_sandbox", sandbox, 1); CHECK_MUTATOR_ADD("g_campcheck", mutator_campcheck, 1); - + #undef CHECK_MUTATOR_ADD - + if(cvar("sv_allow_fullbright")) serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT; @@ -945,7 +945,7 @@ void readlevelcvars(void) 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_minstagib = cvar("g_minstagib"); sv_clones = cvar("sv_clones"); @@ -1814,7 +1814,7 @@ string uid2name(string myuid) { db_put(ServerProgsDB, strcat("uid2name", myuid), ""); } } - + if(s == "") s = "^1Unregistered Player"; return s; diff --git a/qcsrc/server/playerstats.qc b/qcsrc/server/playerstats.qc deleted file mode 100644 index 354b521e8..000000000 --- a/qcsrc/server/playerstats.qc +++ /dev/null @@ -1,432 +0,0 @@ -float playerstats_db; -string teamstats_last; -string playerstats_last; -string events_last; -.float playerstats_addedglobalinfo; -.string playerstats_id; - -void PlayerStats_Init() // initiated before InitGameplayMode so that scores are added properly -{ - string uri; - playerstats_db = -1; - playerstats_waitforme = TRUE; - uri = autocvar_g_playerstats_uri; - if(uri == "") - return; - playerstats_db = db_create(); - if(playerstats_db >= 0) - playerstats_waitforme = FALSE; // must wait for it at match end - - serverflags |= SERVERFLAG_PLAYERSTATS; - - PlayerStats_AddEvent(PLAYERSTATS_ALIVETIME); - PlayerStats_AddEvent(PLAYERSTATS_AVGLATENCY); - PlayerStats_AddEvent(PLAYERSTATS_WINS); - PlayerStats_AddEvent(PLAYERSTATS_MATCHES); - PlayerStats_AddEvent(PLAYERSTATS_JOINS); - PlayerStats_AddEvent(PLAYERSTATS_SCOREBOARD_VALID); - PlayerStats_AddEvent(PLAYERSTATS_SCOREBOARD_POS); - PlayerStats_AddEvent(PLAYERSTATS_RANK); - - // accuracy stats - entity w; - float i; - for(i = WEP_FIRST; i <= WEP_LAST; ++i) - { - w = get_weaponinfo(i); - - PlayerStats_AddEvent(strcat("acc-", w.netname, "-hit")); - PlayerStats_AddEvent(strcat("acc-", w.netname, "-fired")); - - PlayerStats_AddEvent(strcat("acc-", w.netname, "-cnt-hit")); - PlayerStats_AddEvent(strcat("acc-", w.netname, "-cnt-fired")); - - PlayerStats_AddEvent(strcat("acc-", w.netname, "-frags")); - } - - PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3); - PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5); - PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10); - PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15); - PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20); - PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25); - PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30); - PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_BOTLIKE); - PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD); - PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM); -} - -void PlayerStats_AddPlayer(entity e) -{ - string s; - - if(playerstats_db < 0) - return; - if(e.playerstats_id) - return; - - s = string_null; - if(e.crypto_idfp != "" && e.cvar_cl_allow_uidtracking == 1) - s = e.crypto_idfp; - else if(IS_BOT_CLIENT(e)) - s = sprintf("bot#%g#%s", skill, e.cleanname); - - if((s == "") || find(world, playerstats_id, s)) // already have one of the ID - next one can't be tracked then! - { - if(IS_BOT_CLIENT(e)) - s = sprintf("bot#%d", e.playerid); - else - s = sprintf("player#%d", e.playerid); - } - - e.playerstats_id = strzone(s); - - string key; - key = sprintf("%s:*", e.playerstats_id); - - string p; - p = db_get(playerstats_db, key); - if(p == "") - { - if(playerstats_last) - { - db_put(playerstats_db, key, playerstats_last); - strunzone(playerstats_last); - } - else - db_put(playerstats_db, key, "#"); - playerstats_last = strzone(e.playerstats_id); - } -} - -void PlayerStats_AddTeam(float t) -{ - if(playerstats_db < 0) - return; - - string key; - key = sprintf("%d", t); - - string p; - p = db_get(playerstats_db, key); - if(p == "") - { - if(teamstats_last) - { - db_put(playerstats_db, key, teamstats_last); - strunzone(teamstats_last); - } - else - db_put(playerstats_db, key, "#"); - teamstats_last = strzone(key); - } -} - -void PlayerStats_AddEvent(string event_id) -{ - if(playerstats_db < 0) - return; - - string key; - key = sprintf("*:%s", event_id); - - string p; - p = db_get(playerstats_db, key); - if(p == "") - { - if(events_last) - { - db_put(playerstats_db, key, events_last); - strunzone(events_last); - } - else - db_put(playerstats_db, key, "#"); - events_last = strzone(event_id); - } -} - -float PlayerStats_Event(entity e, string event_id, float value) -{ - if((e.playerstats_id == "") || playerstats_db < 0) - return 0; - - string key; - float val; - key = sprintf("%s:%s", e.playerstats_id, event_id); - val = stof(db_get(playerstats_db, key)); - val += value; - db_put(playerstats_db, key, ftos(val)); - return val; -} - -float PlayerStats_TeamScore(float t, string event_id, float value) -{ - if(playerstats_db < 0) - return 0; - - string key; - float val; - key = sprintf("team#%d:%s", t, event_id); - val = stof(db_get(playerstats_db, key)); - val += value; - db_put(playerstats_db, key, ftos(val)); - return val; -} - -/* - format spec: - - A collection of lines of the format SPACE NEWLINE, where - is always a single character. - - The following keys are defined: - - V: format version (always a fixed number) - this MUST be the first line! - #: comment (MUST be ignored by any parser) - R: release information on the server - T: time at which the game ended - G: game type - O: mod name (icon request) as in server browser - M: map name - I: match ID (see "matchid" in g_world.qc - S: "hostname" of the server - C: number of "unpure" cvar changes - U: UDP port number of the server - D: duration of the match - P: player ID of an existing player; this also sets the owner for all following "n", "e" and "t" lines (lower case!) - Q: team number of an existing team (format: team#NN); this also sets the owner for all following "e" lines (lower case!) - n: nickname of the player (optional) - t: team ID - i: player index - e: followed by an event name, a space, and the event count/score - event names can be: - alivetime: total playing time of the player - avglatency: average network latency compounded throughout the match - wins: number of games won (can only be set if matches is set) - matches: number of matches played to the end (not aborted by map switch) - joins: number of matches joined (always 1 unless player never played during the match) - scoreboardvalid: set to 1 if the player was there at the end of the match - total-: total score of that scoreboard item - scoreboard-: end-of-game score of that scoreboard item (can differ in non-team games) - achievement-: achievement counters (their "count" is usually 1 if nonzero at all) - kills-: number of kills against the indexed player - rank : rank of player - acc--hit: total damage dealt - acc--fired: total damage that all fired projectiles *could* have dealt - acc--cnt-hit: amount of shots that actually hit - acc--cnt-fired: amount of fired shots - acc--frags: amount of frags dealt by weapon - - Response format (not used yet): see https://gist.github.com/4284222 -*/ - -void PlayerStats_ready(entity fh, entity pass, float status) -{ - string t, tn; - string p, pn; - string e, en; - string nn, tt; - string s; - - switch(status) - { - case URL_READY_CANWRITE: - url_fputs(fh, "V 7\n"); -#ifdef WATERMARK - url_fputs(fh, sprintf("R %s\n", WATERMARK)); -#endif - url_fputs(fh, sprintf("T %s.%06d\n", strftime(FALSE, "%s"), floor(random() * 1000000))); - url_fputs(fh, sprintf("G %s\n", GetGametype())); - url_fputs(fh, sprintf("O %s\n", modname)); - url_fputs(fh, sprintf("M %s\n", GetMapname())); - url_fputs(fh, sprintf("I %s\n", matchid)); - url_fputs(fh, sprintf("S %s\n", cvar_string("hostname"))); - url_fputs(fh, sprintf("C %d\n", cvar_purechanges_count)); - url_fputs(fh, sprintf("U %d\n", cvar("port"))); - url_fputs(fh, sprintf("D %f\n", max(0, time - game_starttime))); - if(teamplay) - { - for(t = teamstats_last; (tn = db_get(playerstats_db, sprintf("%d", stof(t)))) != ""; t = tn) - { - url_fputs(fh, sprintf("Q team#%s\n", t)); - for(e = events_last; (en = db_get(playerstats_db, sprintf("*:%s", e))) != ""; e = en) - { - float v; - v = stof(db_get(playerstats_db, sprintf("team#%d:%s", stof(t), e))); - if(v != 0) - url_fputs(fh, sprintf("e %s %g\n", e, v)); - } - } - } - for(p = playerstats_last; (pn = db_get(playerstats_db, sprintf("%s:*", p))) != ""; p = pn) - { - url_fputs(fh, sprintf("P %s\n", p)); - nn = db_get(playerstats_db, sprintf("%s:_playerid", p)); - if(nn != "") - url_fputs(fh, sprintf("i %s\n", nn)); - nn = db_get(playerstats_db, sprintf("%s:_netname", p)); - if(nn != "") - url_fputs(fh, sprintf("n %s\n", nn)); - if(teamplay) - { - tt = db_get(playerstats_db, sprintf("%s:_team", p)); - url_fputs(fh, sprintf("t %s\n", tt)); - } - for(e = events_last; (en = db_get(playerstats_db, sprintf("*:%s", e))) != ""; e = en) - { - float v; - v = stof(db_get(playerstats_db, sprintf("%s:%s", p, e))); - if(v != 0) - url_fputs(fh, sprintf("e %s %g\n", e, v)); - } - } - url_fputs(fh, "\n"); - url_fclose(fh); - break; - case URL_READY_CANREAD: - // url_fclose is processing, we got a response for writing the data - // this must come from HTTP - print("Got response from player stats server:\n"); - while((s = url_fgets(fh))) - print(" ", s, "\n"); - print("End of response.\n"); - url_fclose(fh); - break; - case URL_READY_CLOSED: - // url_fclose has finished - print("Player stats written\n"); - playerstats_waitforme = TRUE; - db_close(playerstats_db); - playerstats_db = -1; - break; - case URL_READY_ERROR: - default: - print("Player stats writing failed: ", ftos(status), "\n"); - playerstats_waitforme = TRUE; - if(playerstats_db >= 0) - { - db_close(playerstats_db); - playerstats_db = -1; - } - break; - } -} - -//#NO AUTOCVARS START -void PlayerStats_Shutdown() -{ - string uri; - - if(playerstats_db < 0) - return; - - uri = autocvar_g_playerstats_uri; - if(uri != "") - { - playerstats_waitforme = FALSE; - url_multi_fopen(uri, FILE_APPEND, PlayerStats_ready, world); - } - else - { - playerstats_waitforme = TRUE; - db_close(playerstats_db); - playerstats_db = -1; - } -} -//#NO AUTOCVARS END - -void PlayerStats_Accuracy(entity p) -{ - entity a, w; - a = p.accuracy; - float i; - - for(i = WEP_FIRST; i <= WEP_LAST; ++i) - { - w = get_weaponinfo(i); - - PlayerStats_Event(p, strcat("acc-", w.netname, "-hit"), a.(accuracy_hit[i-1])); - PlayerStats_Event(p, strcat("acc-", w.netname, "-fired"), a.(accuracy_fired[i-1])); - - PlayerStats_Event(p, strcat("acc-", w.netname, "-cnt-hit"), a.(accuracy_cnt_hit[i-1])); - PlayerStats_Event(p, strcat("acc-", w.netname, "-cnt-fired"), a.(accuracy_cnt_fired[i-1])); - - PlayerStats_Event(p, strcat("acc-", w.netname, "-frags"), a.(accuracy_frags[i-1])); - } - //backtrace(strcat("adding player stat accuracy for ", p.netname, ".\n")); -} - -void PlayerStats_AddGlobalInfo(entity p) -{ - if(playerstats_db < 0) - return; - if((p.playerstats_id == "") || playerstats_db < 0) - return; - p.playerstats_addedglobalinfo = TRUE; - - // add global info! - if(p.alivetime) - { - PlayerStats_Event(p, PLAYERSTATS_ALIVETIME, time - p.alivetime); - p.alivetime = 0; - } - - db_put(playerstats_db, sprintf("%s:_playerid", p.playerstats_id), ftos(p.playerid)); - - if(p.cvar_cl_allow_uid2name == 1 || IS_BOT_CLIENT(p)) - db_put(playerstats_db, sprintf("%s:_netname", p.playerstats_id), p.netname); - - if(teamplay) - db_put(playerstats_db, sprintf("%s:_team", p.playerstats_id), ftos(p.team)); - - if(stof(db_get(playerstats_db, sprintf("%d:%s", p.playerstats_id, PLAYERSTATS_ALIVETIME))) > 0) - PlayerStats_Event(p, PLAYERSTATS_JOINS, 1); - - PlayerStats_Accuracy(p); - - if(IS_REAL_CLIENT(p)) - { - if(p.latency_cnt) - { - float latency = (p.latency_sum / p.latency_cnt); - if(latency) { PlayerStats_Event(p, PLAYERSTATS_AVGLATENCY, latency); } - } - } - - strunzone(p.playerstats_id); - p.playerstats_id = string_null; -} - -.float scoreboard_pos; -void PlayerStats_EndMatch(float finished) -{ - entity p; - PlayerScore_Sort(score_dummyfield, 0, 0, 0); - PlayerScore_Sort(scoreboard_pos, 1, 1, 1); - if(teamplay) - PlayerScore_TeamStats(); - FOR_EACH_CLIENT(p) - { - // add personal score rank - PlayerStats_Event(p, PLAYERSTATS_RANK, p.score_dummyfield); - - if(!p.scoreboard_pos) - continue; - - // scoreboard is valid! - PlayerStats_Event(p, PLAYERSTATS_SCOREBOARD_VALID, 1); - - // add scoreboard position - PlayerStats_Event(p, PLAYERSTATS_SCOREBOARD_POS, p.scoreboard_pos); - - // add scoreboard data - PlayerScore_PlayerStats(p); - - // if the match ended normally, add winning info - if(finished) - { - PlayerStats_Event(p, PLAYERSTATS_WINS, p.winning); - PlayerStats_Event(p, PLAYERSTATS_MATCHES, 1); - } - } -} diff --git a/qcsrc/server/playerstats.qh b/qcsrc/server/playerstats.qh deleted file mode 100644 index b95037747..000000000 --- a/qcsrc/server/playerstats.qh +++ /dev/null @@ -1,53 +0,0 @@ -// time the player was alive and kicking -const string PLAYERSTATS_ALIVETIME = "alivetime"; -const string PLAYERSTATS_AVGLATENCY = "avglatency"; -const string PLAYERSTATS_WINS = "wins"; -const string PLAYERSTATS_MATCHES = "matches"; -const string PLAYERSTATS_JOINS = "joins"; -const string PLAYERSTATS_SCOREBOARD_VALID = "scoreboardvalid"; -const string PLAYERSTATS_RANK = "rank"; -const string PLAYERSTATS_SCOREBOARD_POS = "scoreboardpos"; - -const string PLAYERSTATS_TOTAL = "total-"; -const string PLAYERSTATS_SCOREBOARD = "scoreboard-"; - -const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3 = "achievement-kill-spree-3"; -const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5 = "achievement-kill-spree-5"; -const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10 = "achievement-kill-spree-10"; -const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15 = "achievement-kill-spree-15"; -const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20 = "achievement-kill-spree-20"; -const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25 = "achievement-kill-spree-25"; -const string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30 = "achievement-kill-spree-30"; -const string PLAYERSTATS_ACHIEVEMENT_BOTLIKE = "achievement-botlike"; -const string PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD = "achievement-firstblood"; -const string PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM = "achievement-firstvictim"; - -// delay map switch until this is set -float playerstats_waitforme; - -// call at initialization -void PlayerStats_Init(); - -// add a new player -void PlayerStats_AddPlayer(entity e); - -// add a new team -void PlayerStats_AddTeam(float t); - -// add a new event -void PlayerStats_AddEvent(string event_id); - -// call on each event to track, or at player disconnect OR match end for "global stuff" -float PlayerStats_Event(entity e, string event_id, float value); - -// add a team score -float PlayerStats_TeamScore(float t, string event_id, float value); - -// call at game over -void PlayerStats_Shutdown(); // send stats to the server - -// call this whenever a player leaves -void PlayerStats_AddGlobalInfo(entity p); - -// call this at the end of the match -void PlayerStats_EndMatch(float finished); diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index e9e9a4b8c..2e42a3c4b 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -43,7 +43,7 @@ mutators/gamemode_ctf.qh mutators/gamemode_domination.qh mutators/gamemode_keyhunt.qh // TODO fix this mutators/gamemode_keepaway.qh -mutators/gamemode_nexball.qh +mutators/gamemode_nexball.qh mutators/gamemode_lms.qh mutators/mutator_dodging.qh mutators/mutator_nades.qh @@ -73,7 +73,7 @@ csqceffects.qc anticheat.qh cheats.qh -playerstats.qh +../common/playerstats.qh portals.qh @@ -220,7 +220,7 @@ playerdemo.qc anticheat.qc cheats.qc -playerstats.qc +../common/playerstats.qc round_handler.qc