cg_screen.cpp 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. #include "cg_local.h"
  4. constexpr int32_t STAT_MINUS = 10; // num frame for '-' stats digit
  5. constexpr const char *sb_nums[2][11] =
  6. {
  7. { "num_0", "num_1", "num_2", "num_3", "num_4", "num_5",
  8. "num_6", "num_7", "num_8", "num_9", "num_minus"
  9. },
  10. { "anum_0", "anum_1", "anum_2", "anum_3", "anum_4", "anum_5",
  11. "anum_6", "anum_7", "anum_8", "anum_9", "anum_minus"
  12. }
  13. };
  14. constexpr int32_t CHAR_WIDTH = 16;
  15. constexpr int32_t CONCHAR_WIDTH = 8;
  16. static int32_t font_y_offset;
  17. constexpr rgba_t alt_color { 112, 255, 52, 255 };
  18. static cvar_t *scr_usekfont;
  19. static cvar_t *scr_centertime;
  20. static cvar_t *scr_printspeed;
  21. static cvar_t *cl_notifytime;
  22. static cvar_t *scr_maxlines;
  23. static cvar_t *ui_acc_contrast;
  24. static cvar_t* ui_acc_alttypeface;
  25. // static temp data used for hud
  26. static struct
  27. {
  28. struct {
  29. struct {
  30. char text[24];
  31. } table_cells[6];
  32. } table_rows[11]; // just enough to store 8 levels + header + total (+ one slack)
  33. size_t column_widths[6];
  34. int32_t num_rows = 0;
  35. int32_t num_columns = 0;
  36. } hud_temp;
  37. #include <vector>
  38. // max number of centerprints in the rotating buffer
  39. constexpr size_t MAX_CENTER_PRINTS = 4;
  40. struct cl_bind_t {
  41. std::string bind;
  42. std::string purpose;
  43. };
  44. struct cl_centerprint_t {
  45. std::vector<cl_bind_t> binds; // binds
  46. std::vector<std::string> lines;
  47. bool instant; // don't type out
  48. size_t current_line; // current line we're typing out
  49. size_t line_count; // byte count to draw on current line
  50. bool finished; // done typing it out
  51. uint64_t time_tick, time_off; // time to remove at
  52. };
  53. inline bool CG_ViewingLayout(const player_state_t *ps)
  54. {
  55. return ps->stats[STAT_LAYOUTS] & (LAYOUTS_LAYOUT | LAYOUTS_INVENTORY);
  56. }
  57. inline bool CG_InIntermission(const player_state_t *ps)
  58. {
  59. return ps->stats[STAT_LAYOUTS] & LAYOUTS_INTERMISSION;
  60. }
  61. inline bool CG_HudHidden(const player_state_t *ps)
  62. {
  63. return ps->stats[STAT_LAYOUTS] & LAYOUTS_HIDE_HUD;
  64. }
  65. layout_flags_t CG_LayoutFlags(const player_state_t *ps)
  66. {
  67. return (layout_flags_t) ps->stats[STAT_LAYOUTS];
  68. }
  69. #include <optional>
  70. #include <array>
  71. constexpr size_t MAX_NOTIFY = 8;
  72. struct cl_notify_t {
  73. std::string message; // utf8 message
  74. bool is_active; // filled or not
  75. bool is_chat; // green or not
  76. uint64_t time; // rotate us when < CL_Time()
  77. };
  78. // per-splitscreen client hud storage
  79. struct hud_data_t {
  80. std::array<cl_centerprint_t, MAX_CENTER_PRINTS> centers; // list of centers
  81. std::optional<size_t> center_index; // current index we're drawing, or unset if none left
  82. std::array<cl_notify_t, MAX_NOTIFY> notify; // list of notifies
  83. };
  84. static std::array<hud_data_t, MAX_SPLIT_PLAYERS> hud_data;
  85. void CG_ClearCenterprint(int32_t isplit)
  86. {
  87. hud_data[isplit].center_index = {};
  88. }
  89. void CG_ClearNotify(int32_t isplit)
  90. {
  91. for (auto &msg : hud_data[isplit].notify)
  92. msg.is_active = false;
  93. }
  94. // if the top one is expired, cycle the ones ahead backwards (since
  95. // the times are always increasing)
  96. static void CG_Notify_CheckExpire(hud_data_t &data)
  97. {
  98. while (data.notify[0].is_active && data.notify[0].time < cgi.CL_ClientTime())
  99. {
  100. data.notify[0].is_active = false;
  101. for (size_t i = 1; i < MAX_NOTIFY; i++)
  102. if (data.notify[i].is_active)
  103. std::swap(data.notify[i], data.notify[i - 1]);
  104. }
  105. }
  106. // add notify to list
  107. static void CG_AddNotify(hud_data_t &data, const char *msg, bool is_chat)
  108. {
  109. size_t i = 0;
  110. if (scr_maxlines->integer <= 0)
  111. return;
  112. const int max = min(MAX_NOTIFY, (size_t)scr_maxlines->integer);
  113. for (; i < max; i++)
  114. if (!data.notify[i].is_active)
  115. break;
  116. // none left, so expire the topmost one
  117. if (i == max)
  118. {
  119. data.notify[0].time = 0;
  120. CG_Notify_CheckExpire(data);
  121. i = max - 1;
  122. }
  123. data.notify[i].message.assign(msg);
  124. data.notify[i].is_active = true;
  125. data.notify[i].is_chat = is_chat;
  126. data.notify[i].time = cgi.CL_ClientTime() + (cl_notifytime->value * 1000);
  127. }
  128. // draw notifies
  129. static void CG_DrawNotify(int32_t isplit, vrect_t hud_vrect, vrect_t hud_safe, int32_t scale)
  130. {
  131. auto &data = hud_data[isplit];
  132. CG_Notify_CheckExpire(data);
  133. int y;
  134. y = (hud_vrect.y * scale) + hud_safe.y;
  135. cgi.SCR_SetAltTypeface(ui_acc_alttypeface->integer && true);
  136. if (ui_acc_contrast->integer)
  137. {
  138. for (auto& msg : data.notify)
  139. {
  140. if (!msg.is_active || !msg.message.length())
  141. break;
  142. vec2_t sz = cgi.SCR_MeasureFontString(msg.message.c_str(), scale);
  143. sz.x += 10; // extra padding for black bars
  144. cgi.SCR_DrawColorPic((hud_vrect.x * scale) + hud_safe.x - 5, y, sz.x, 15 * scale, "_white", rgba_black);
  145. y += 10 * scale;
  146. }
  147. }
  148. y = (hud_vrect.y * scale) + hud_safe.y;
  149. for (auto &msg : data.notify)
  150. {
  151. if (!msg.is_active)
  152. break;
  153. cgi.SCR_DrawFontString(msg.message.c_str(), (hud_vrect.x * scale) + hud_safe.x, y, scale, msg.is_chat ? alt_color : rgba_white, true, text_align_t::LEFT);
  154. y += 10 * scale;
  155. }
  156. cgi.SCR_SetAltTypeface(false);
  157. // draw text input (only the main player can really chat anyways...)
  158. if (isplit == 0)
  159. {
  160. const char *input_msg;
  161. bool input_team;
  162. if (cgi.CL_GetTextInput(&input_msg, &input_team))
  163. cgi.SCR_DrawFontString(G_Fmt("{}: {}", input_team ? "say_team" : "say", input_msg).data(), (hud_vrect.x * scale) + hud_safe.x, y, scale, rgba_white, true, text_align_t::LEFT);
  164. }
  165. }
  166. /*
  167. ==============
  168. CG_DrawHUDString
  169. ==============
  170. */
  171. static int CG_DrawHUDString (const char *string, int x, int y, int centerwidth, int _xor, int scale, bool shadow = true)
  172. {
  173. int margin;
  174. char line[1024];
  175. int width;
  176. int i;
  177. margin = x;
  178. while (*string)
  179. {
  180. // scan out one line of text from the string
  181. width = 0;
  182. while (*string && *string != '\n')
  183. line[width++] = *string++;
  184. line[width] = 0;
  185. vec2_t size;
  186. if (scr_usekfont->integer)
  187. size = cgi.SCR_MeasureFontString(line, scale);
  188. if (centerwidth)
  189. {
  190. if (!scr_usekfont->integer)
  191. x = margin + ((centerwidth - width*CONCHAR_WIDTH*scale))/2;
  192. else
  193. x = margin + ((centerwidth - size.x))/2;
  194. }
  195. else
  196. x = margin;
  197. if (!scr_usekfont->integer)
  198. {
  199. for (i=0 ; i<width ; i++)
  200. {
  201. cgi.SCR_DrawChar (x, y, scale, line[i]^_xor, shadow);
  202. x += CONCHAR_WIDTH * scale;
  203. }
  204. }
  205. else
  206. {
  207. cgi.SCR_DrawFontString(line, x, y - (font_y_offset * scale), scale, _xor ? alt_color : rgba_white, true, text_align_t::LEFT);
  208. x += size.x;
  209. }
  210. if (*string)
  211. {
  212. string++; // skip the \n
  213. x = margin;
  214. if (!scr_usekfont->integer)
  215. y += CONCHAR_WIDTH * scale;
  216. else
  217. // TODO
  218. y += 10 * scale;//size.y;
  219. }
  220. }
  221. return x;
  222. }
  223. // Shamefully stolen from Kex
  224. size_t FindStartOfUTF8Codepoint(const std::string &str, size_t pos)
  225. {
  226. if(pos >= str.size())
  227. {
  228. return std::string::npos;
  229. }
  230. for(ptrdiff_t i = pos; i >= 0; i--)
  231. {
  232. const char &ch = str[i];
  233. if((ch & 0x80) == 0)
  234. {
  235. // character is one byte
  236. return i;
  237. }
  238. else if((ch & 0xC0) == 0x80)
  239. {
  240. // character is part of a multi-byte sequence, keep going
  241. continue;
  242. }
  243. else
  244. {
  245. // character is the start of a multi-byte sequence, so stop now
  246. return i;
  247. }
  248. }
  249. return std::string::npos;
  250. }
  251. size_t FindEndOfUTF8Codepoint(const std::string &str, size_t pos)
  252. {
  253. if(pos >= str.size())
  254. {
  255. return std::string::npos;
  256. }
  257. for(size_t i = pos; i < str.size(); i++)
  258. {
  259. const char &ch = str[i];
  260. if((ch & 0x80) == 0)
  261. {
  262. // character is one byte
  263. return i;
  264. }
  265. else if((ch & 0xC0) == 0x80)
  266. {
  267. // character is part of a multi-byte sequence, keep going
  268. continue;
  269. }
  270. else
  271. {
  272. // character is the start of a multi-byte sequence, so stop now
  273. return i;
  274. }
  275. }
  276. return std::string::npos;
  277. }
  278. void CG_NotifyMessage(int32_t isplit, const char *msg, bool is_chat)
  279. {
  280. CG_AddNotify(hud_data[isplit], msg, is_chat);
  281. }
  282. // centerprint stuff
  283. static cl_centerprint_t &CG_QueueCenterPrint(int isplit, bool instant)
  284. {
  285. auto &icl = hud_data[isplit];
  286. // just use first index
  287. if (!icl.center_index.has_value() || instant)
  288. {
  289. icl.center_index = 0;
  290. for (size_t i = 1; i < MAX_CENTER_PRINTS; i++)
  291. icl.centers[i].lines.clear();
  292. return icl.centers[0];
  293. }
  294. // pick the next free index if we can find one
  295. for (size_t i = 1; i < MAX_CENTER_PRINTS; i++)
  296. {
  297. auto &center = icl.centers[(icl.center_index.value() + i) % MAX_CENTER_PRINTS];
  298. if (center.lines.empty())
  299. return center;
  300. }
  301. // none, so update the current one (the new end of buffer)
  302. // and skip ahead
  303. auto &center = icl.centers[icl.center_index.value()];
  304. icl.center_index = (icl.center_index.value() + 1) % MAX_CENTER_PRINTS;
  305. return center;
  306. }
  307. /*
  308. ==============
  309. SCR_CenterPrint
  310. Called for important messages that should stay in the center of the screen
  311. for a few moments
  312. ==============
  313. */
  314. void CG_ParseCenterPrint (const char *str, int isplit, bool instant) // [Sam-KEX] Made 1st param const
  315. {
  316. const char *s;
  317. char line[64];
  318. int i, j, l;
  319. // handle center queueing
  320. cl_centerprint_t &center = CG_QueueCenterPrint(isplit, instant);
  321. center.lines.clear();
  322. // split the string into lines
  323. size_t line_start = 0;
  324. std::string string(str);
  325. center.binds.clear();
  326. // [Paril-KEX] pull out bindings. they'll always be at the start
  327. while (string.compare(0, 6, "%bind:") == 0)
  328. {
  329. size_t end_of_bind = string.find_first_of('%', 1);
  330. if (end_of_bind == std::string::npos)
  331. break;
  332. std::string bind = string.substr(6, end_of_bind - 6);
  333. if (auto purpose_index = bind.find_first_of(':'); purpose_index != std::string::npos)
  334. center.binds.emplace_back(cl_bind_t { bind.substr(0, purpose_index), bind.substr(purpose_index + 1) });
  335. else
  336. center.binds.emplace_back(cl_bind_t { bind });
  337. string = string.substr(end_of_bind + 1);
  338. }
  339. // echo it to the console
  340. cgi.Com_Print("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
  341. s = string.c_str();
  342. do
  343. {
  344. // scan the width of the line
  345. for (l=0 ; l<40 ; l++)
  346. if (s[l] == '\n' || !s[l])
  347. break;
  348. for (i=0 ; i<(40-l)/2 ; i++)
  349. line[i] = ' ';
  350. for (j=0 ; j<l ; j++)
  351. {
  352. line[i++] = s[j];
  353. }
  354. line[i] = '\n';
  355. line[i+1] = 0;
  356. cgi.Com_Print(line);
  357. while (*s && *s != '\n')
  358. s++;
  359. if (!*s)
  360. break;
  361. s++; // skip the \n
  362. } while (1);
  363. cgi.Com_Print("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
  364. CG_ClearNotify (isplit);
  365. for (size_t line_end = 0; ; )
  366. {
  367. line_end = FindEndOfUTF8Codepoint(string, line_end);
  368. if (line_end == std::string::npos)
  369. {
  370. // final line
  371. if (line_start < string.size())
  372. center.lines.emplace_back(string.c_str() + line_start);
  373. break;
  374. }
  375. // char part of current line;
  376. // if newline, end line and cut off
  377. const char &ch = string[line_end];
  378. if (ch == '\n')
  379. {
  380. if (line_end > line_start)
  381. center.lines.emplace_back(string.c_str() + line_start, line_end - line_start);
  382. else
  383. center.lines.emplace_back();
  384. line_start = line_end + 1;
  385. line_end++;
  386. continue;
  387. }
  388. line_end++;
  389. }
  390. if (center.lines.empty())
  391. {
  392. center.finished = true;
  393. return;
  394. }
  395. center.time_tick = cgi.CL_ClientRealTime() + (scr_printspeed->value * 1000);
  396. center.instant = instant;
  397. center.finished = false;
  398. center.current_line = 0;
  399. center.line_count = 0;
  400. }
  401. static void CG_DrawCenterString( const player_state_t *ps, const vrect_t &hud_vrect, const vrect_t &hud_safe, int isplit, int scale, cl_centerprint_t &center)
  402. {
  403. int32_t y = hud_vrect.y * scale;
  404. if (CG_ViewingLayout(ps))
  405. y += hud_safe.y;
  406. else if (center.lines.size() <= 4)
  407. y += (hud_vrect.height * 0.2f) * scale;
  408. else
  409. y += 48 * scale;
  410. int lineHeight = (scr_usekfont->integer ? 10 : 8) * scale;
  411. if (ui_acc_alttypeface->integer) lineHeight *= 1.5f;
  412. // easy!
  413. if (center.instant)
  414. {
  415. for (size_t i = 0; i < center.lines.size(); i++)
  416. {
  417. auto &line = center.lines[i];
  418. cgi.SCR_SetAltTypeface(ui_acc_alttypeface->integer && true);
  419. if (ui_acc_contrast->integer && line.length())
  420. {
  421. vec2_t sz = cgi.SCR_MeasureFontString(line.c_str(), scale);
  422. sz.x += 10; // extra padding for black bars
  423. int barY = ui_acc_alttypeface->integer ? y - 8 : y;
  424. cgi.SCR_DrawColorPic((hud_vrect.x + hud_vrect.width / 2) * scale - (sz.x / 2), barY, sz.x, lineHeight, "_white", rgba_black);
  425. }
  426. CG_DrawHUDString(line.c_str(), (hud_vrect.x + hud_vrect.width/2 + -160) * scale, y, (320 / 2) * 2 * scale, 0, scale);
  427. cgi.SCR_SetAltTypeface(false);
  428. y += lineHeight;
  429. }
  430. for (auto &bind : center.binds)
  431. {
  432. y += lineHeight * 2;
  433. cgi.SCR_DrawBind(isplit, bind.bind.c_str(), bind.purpose.c_str(), (hud_vrect.x + (hud_vrect.width / 2)) * scale, y, scale);
  434. }
  435. if (!center.finished)
  436. {
  437. center.finished = true;
  438. center.time_off = cgi.CL_ClientRealTime() + (scr_centertime->value * 1000);
  439. }
  440. return;
  441. }
  442. // hard and annoying!
  443. // check if it's time to fetch a new char
  444. const uint64_t t = cgi.CL_ClientRealTime();
  445. if (!center.finished)
  446. {
  447. if (center.time_tick < t)
  448. {
  449. center.time_tick = t + (scr_printspeed->value * 1000);
  450. center.line_count = FindEndOfUTF8Codepoint(center.lines[center.current_line], center.line_count + 1);
  451. if (center.line_count == std::string::npos)
  452. {
  453. center.current_line++;
  454. center.line_count = 0;
  455. if (center.current_line == center.lines.size())
  456. {
  457. center.current_line--;
  458. center.finished = true;
  459. center.time_off = t + (scr_centertime->value * 1000);
  460. }
  461. }
  462. }
  463. }
  464. // smallish byte buffer for single line of data...
  465. char buffer[256];
  466. for (size_t i = 0; i < center.lines.size(); i++)
  467. {
  468. cgi.SCR_SetAltTypeface(ui_acc_alttypeface->integer && true);
  469. auto &line = center.lines[i];
  470. buffer[0] = 0;
  471. if (center.finished || i != center.current_line)
  472. Q_strlcpy(buffer, line.c_str(), sizeof(buffer));
  473. else
  474. Q_strlcpy(buffer, line.c_str(), min(center.line_count + 1, sizeof(buffer)));
  475. int blinky_x;
  476. if (ui_acc_contrast->integer && line.length())
  477. {
  478. vec2_t sz = cgi.SCR_MeasureFontString(line.c_str(), scale);
  479. sz.x += 10; // extra padding for black bars
  480. int barY = ui_acc_alttypeface->integer ? y - 8 : y;
  481. cgi.SCR_DrawColorPic((hud_vrect.x + hud_vrect.width / 2) * scale - (sz.x / 2), barY, sz.x, lineHeight, "_white", rgba_black);
  482. }
  483. if (buffer[0])
  484. blinky_x = CG_DrawHUDString(buffer, (hud_vrect.x + hud_vrect.width/2 + -160) * scale, y, (320 / 2) * 2 * scale, 0, scale);
  485. else
  486. blinky_x = (hud_vrect.width / 2) * scale;
  487. cgi.SCR_SetAltTypeface(false);
  488. if (i == center.current_line && !ui_acc_alttypeface->integer)
  489. cgi.SCR_DrawChar(blinky_x, y, scale, 10 + ((cgi.CL_ClientRealTime() >> 8) & 1), true);
  490. y += lineHeight;
  491. if (i == center.current_line)
  492. break;
  493. }
  494. }
  495. static void CG_CheckDrawCenterString( const player_state_t *ps, const vrect_t &hud_vrect, const vrect_t &hud_safe, int isplit, int scale )
  496. {
  497. if (CG_InIntermission(ps))
  498. return;
  499. if (!hud_data[isplit].center_index.has_value())
  500. return;
  501. auto &data = hud_data[isplit];
  502. auto &center = data.centers[data.center_index.value()];
  503. // ran out of center time
  504. if (center.finished && center.time_off < cgi.CL_ClientRealTime())
  505. {
  506. center.lines.clear();
  507. size_t next_index = (data.center_index.value() + 1) % MAX_CENTER_PRINTS;
  508. auto &next_center = data.centers[next_index];
  509. // no more
  510. if (next_center.lines.empty())
  511. {
  512. data.center_index.reset();
  513. return;
  514. }
  515. // buffer rotated; start timer now
  516. data.center_index = next_index;
  517. next_center.current_line = next_center.line_count = 0;
  518. }
  519. if (!data.center_index.has_value())
  520. return;
  521. CG_DrawCenterString( ps, hud_vrect, hud_safe, isplit, scale, data.centers[data.center_index.value()] );
  522. }
  523. /*
  524. ==============
  525. CG_DrawString
  526. ==============
  527. */
  528. static void CG_DrawString (int x, int y, int scale, const char *s, bool alt = false, bool shadow = true)
  529. {
  530. while (*s)
  531. {
  532. cgi.SCR_DrawChar (x, y, scale, *s ^ (alt ? 0x80 : 0), shadow);
  533. x+=8*scale;
  534. s++;
  535. }
  536. }
  537. #include <charconv>
  538. /*
  539. ==============
  540. CG_DrawField
  541. ==============
  542. */
  543. static void CG_DrawField (int x, int y, int color, int width, int value, int scale)
  544. {
  545. char num[16], *ptr;
  546. int l;
  547. int frame;
  548. if (width < 1)
  549. return;
  550. // draw number string
  551. if (width > 5)
  552. width = 5;
  553. auto result = std::to_chars(num, num + sizeof(num) - 1, value);
  554. *(result.ptr) = '\0';
  555. l = (result.ptr - num);
  556. if (l > width)
  557. l = width;
  558. x += (2 + CHAR_WIDTH*(width - l)) * scale;
  559. ptr = num;
  560. while (*ptr && l)
  561. {
  562. if (*ptr == '-')
  563. frame = STAT_MINUS;
  564. else
  565. frame = *ptr -'0';
  566. int w, h;
  567. cgi.Draw_GetPicSize(&w, &h, sb_nums[color][frame]);
  568. cgi.SCR_DrawPic(x, y, w * scale, h * scale, sb_nums[color][frame]);
  569. x += CHAR_WIDTH * scale;
  570. ptr++;
  571. l--;
  572. }
  573. }
  574. // [Paril-KEX]
  575. static void CG_DrawTable(int x, int y, uint32_t width, uint32_t height, int32_t scale)
  576. {
  577. // half left
  578. int32_t width_pixels = width;
  579. x -= width_pixels / 2;
  580. y += CONCHAR_WIDTH * scale;
  581. // use Y as top though
  582. int32_t height_pixels = height;
  583. // draw border
  584. // KEX_FIXME method that requires less chars
  585. cgi.SCR_DrawChar(x - (CONCHAR_WIDTH * scale), y - (CONCHAR_WIDTH * scale), scale, 18, false);
  586. cgi.SCR_DrawChar((x + width_pixels), y - (CONCHAR_WIDTH * scale), scale, 20, false);
  587. cgi.SCR_DrawChar(x - (CONCHAR_WIDTH * scale), y + height_pixels, scale, 24, false);
  588. cgi.SCR_DrawChar((x + width_pixels), y + height_pixels, scale, 26, false);
  589. for (int cx = x; cx < x + width_pixels; cx += CONCHAR_WIDTH * scale)
  590. {
  591. cgi.SCR_DrawChar(cx, y - (CONCHAR_WIDTH * scale), scale, 19, false);
  592. cgi.SCR_DrawChar(cx, y + height_pixels, scale, 25, false);
  593. }
  594. for (int cy = y; cy < y + height_pixels; cy += CONCHAR_WIDTH * scale)
  595. {
  596. cgi.SCR_DrawChar(x - (CONCHAR_WIDTH * scale), cy, scale, 21, false);
  597. cgi.SCR_DrawChar((x + width_pixels), cy, scale, 23, false);
  598. }
  599. cgi.SCR_DrawColorPic(x, y, width_pixels, height_pixels, "_white", { 0, 0, 0, 255 });
  600. // draw in columns
  601. for (int i = 0; i < hud_temp.num_columns; i++)
  602. {
  603. for (int r = 0, ry = y; r < hud_temp.num_rows; r++, ry += (CONCHAR_WIDTH + font_y_offset) * scale)
  604. {
  605. int x_offset = 0;
  606. // center
  607. if (r == 0)
  608. {
  609. x_offset = ((hud_temp.column_widths[i]) / 2) -
  610. ((cgi.SCR_MeasureFontString(hud_temp.table_rows[r].table_cells[i].text, scale).x) / 2);
  611. }
  612. // right align
  613. else if (i != 0)
  614. {
  615. x_offset = (hud_temp.column_widths[i] - cgi.SCR_MeasureFontString(hud_temp.table_rows[r].table_cells[i].text, scale).x);
  616. }
  617. //CG_DrawString(x + x_offset, ry, scale, hud_temp.table_rows[r].table_cells[i].text, r == 0, true);
  618. cgi.SCR_DrawFontString(hud_temp.table_rows[r].table_cells[i].text, x + x_offset, ry - (font_y_offset * scale), scale, r == 0 ? alt_color : rgba_white, true, text_align_t::LEFT);
  619. }
  620. x += (hud_temp.column_widths[i] + cgi.SCR_MeasureFontString(" ", 1).x);
  621. }
  622. }
  623. /*
  624. ================
  625. CG_ExecuteLayoutString
  626. ================
  627. */
  628. static void CG_ExecuteLayoutString (const char *s, vrect_t hud_vrect, vrect_t hud_safe, int32_t scale, int32_t playernum, const player_state_t *ps)
  629. {
  630. int x, y;
  631. int w, h;
  632. int hx, hy;
  633. int value;
  634. const char *token;
  635. int width;
  636. int index;
  637. if (!s[0])
  638. return;
  639. x = hud_vrect.x;
  640. y = hud_vrect.y;
  641. width = 3;
  642. hx = 320 / 2;
  643. hy = 240 / 2;
  644. bool flash_frame = (cgi.CL_ClientTime() % 1000) < 500;
  645. // if non-zero, parse but don't affect state
  646. int32_t if_depth = 0; // current if statement depth
  647. int32_t endif_depth = 0; // at this depth, toggle skip_depth
  648. bool skip_depth = false; // whether we're in a dead stmt or not
  649. while (s)
  650. {
  651. token = COM_Parse (&s);
  652. if (!strcmp(token, "xl"))
  653. {
  654. token = COM_Parse (&s);
  655. if (!skip_depth)
  656. x = ((hud_vrect.x + atoi(token)) * scale) + hud_safe.x;
  657. continue;
  658. }
  659. if (!strcmp(token, "xr"))
  660. {
  661. token = COM_Parse (&s);
  662. if (!skip_depth)
  663. x = ((hud_vrect.x + hud_vrect.width + atoi(token)) * scale) - hud_safe.x;
  664. continue;
  665. }
  666. if (!strcmp(token, "xv"))
  667. {
  668. token = COM_Parse (&s);
  669. if (!skip_depth)
  670. x = (hud_vrect.x + hud_vrect.width/2 + (atoi(token) - hx)) * scale;
  671. continue;
  672. }
  673. if (!strcmp(token, "yt"))
  674. {
  675. token = COM_Parse (&s);
  676. if (!skip_depth)
  677. y = ((hud_vrect.y + atoi(token)) * scale) + hud_safe.y;
  678. continue;
  679. }
  680. if (!strcmp(token, "yb"))
  681. {
  682. token = COM_Parse (&s);
  683. if (!skip_depth)
  684. y = ((hud_vrect.y + hud_vrect.height + atoi(token)) * scale) - hud_safe.y;
  685. continue;
  686. }
  687. if (!strcmp(token, "yv"))
  688. {
  689. token = COM_Parse (&s);
  690. if (!skip_depth)
  691. y = (hud_vrect.y + hud_vrect.height/2 + (atoi(token) - hy)) * scale;
  692. continue;
  693. }
  694. if (!strcmp(token, "pic"))
  695. { // draw a pic from a stat number
  696. token = COM_Parse (&s);
  697. if (!skip_depth)
  698. {
  699. value = ps->stats[atoi(token)];
  700. if (value >= MAX_IMAGES)
  701. cgi.Com_Error("Pic >= MAX_IMAGES");
  702. const char *const pic = cgi.get_configstring(CS_IMAGES + value);
  703. if (pic && *pic)
  704. {
  705. cgi.Draw_GetPicSize (&w, &h, pic);
  706. cgi.SCR_DrawPic (x, y, w * scale, h * scale, pic);
  707. }
  708. }
  709. continue;
  710. }
  711. if (!strcmp(token, "client"))
  712. { // draw a deathmatch client block
  713. token = COM_Parse (&s);
  714. if (!skip_depth)
  715. {
  716. x = (hud_vrect.x + hud_vrect.width/2 + (atoi(token) - hx)) * scale;
  717. x += 8 * scale;
  718. }
  719. token = COM_Parse (&s);
  720. if (!skip_depth)
  721. {
  722. y = (hud_vrect.y + hud_vrect.height/2 + (atoi(token) - hy)) * scale;
  723. y += 7 * scale;
  724. }
  725. token = COM_Parse (&s);
  726. if (!skip_depth)
  727. {
  728. value = atoi(token);
  729. if (value >= MAX_CLIENTS || value < 0)
  730. cgi.Com_Error("client >= MAX_CLIENTS");
  731. }
  732. int score, ping;
  733. token = COM_Parse (&s);
  734. if (!skip_depth)
  735. score = atoi(token);
  736. token = COM_Parse (&s);
  737. if (!skip_depth)
  738. {
  739. ping = atoi(token);
  740. if (!scr_usekfont->integer)
  741. CG_DrawString (x + 32 * scale, y, scale, cgi.CL_GetClientName(value));
  742. else
  743. cgi.SCR_DrawFontString(cgi.CL_GetClientName(value), x + 32 * scale, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT);
  744. if (!scr_usekfont->integer)
  745. CG_DrawString (x + 32 * scale, y + 10 * scale, scale, G_Fmt("{}", score).data(), true);
  746. else
  747. cgi.SCR_DrawFontString(G_Fmt("{}", score).data(), x + 32 * scale, y + (10 - font_y_offset) * scale, scale, rgba_white, true, text_align_t::LEFT);
  748. cgi.SCR_DrawPic(x + 96 * scale, y + 10 * scale, 9 * scale, 9 * scale, "ping");
  749. if (!scr_usekfont->integer)
  750. CG_DrawString (x + 73 * scale + 32 * scale, y + 10 * scale, scale, G_Fmt("{}", ping).data());
  751. else
  752. cgi.SCR_DrawFontString (G_Fmt("{}", ping).data(), x + 107 * scale, y + (10 - font_y_offset) * scale, scale, rgba_white, true, text_align_t::LEFT);
  753. }
  754. continue;
  755. }
  756. if (!strcmp(token, "ctf"))
  757. { // draw a ctf client block
  758. int score, ping;
  759. token = COM_Parse (&s);
  760. if (!skip_depth)
  761. x = (hud_vrect.x + hud_vrect.width/2 - hx + atoi(token)) * scale;
  762. token = COM_Parse (&s);
  763. if (!skip_depth)
  764. y = (hud_vrect.y + hud_vrect.height/2 - hy + atoi(token)) * scale;
  765. token = COM_Parse (&s);
  766. if (!skip_depth)
  767. {
  768. value = atoi(token);
  769. if (value >= MAX_CLIENTS || value < 0)
  770. cgi.Com_Error("client >= MAX_CLIENTS");
  771. }
  772. token = COM_Parse (&s);
  773. if (!skip_depth)
  774. score = atoi(token);
  775. token = COM_Parse (&s);
  776. if (!skip_depth)
  777. {
  778. ping = atoi(token);
  779. if (ping > 999)
  780. ping = 999;
  781. }
  782. token = COM_Parse (&s);
  783. if (!skip_depth)
  784. {
  785. cgi.SCR_DrawFontString (G_Fmt("{}", score).data(), x, y - (font_y_offset * scale), scale, value == playernum ? alt_color : rgba_white, true, text_align_t::LEFT);
  786. x += 3 * 9 * scale;
  787. cgi.SCR_DrawFontString (G_Fmt("{}", ping).data(), x, y - (font_y_offset * scale), scale, value == playernum ? alt_color : rgba_white, true, text_align_t::LEFT);
  788. x += 3 * 9 * scale;
  789. cgi.SCR_DrawFontString (cgi.CL_GetClientName(value), x, y - (font_y_offset * scale), scale, value == playernum ? alt_color : rgba_white, true, text_align_t::LEFT);
  790. if (*token)
  791. {
  792. cgi.Draw_GetPicSize(&w, &h, token);
  793. cgi.SCR_DrawPic(x - ((w + 2) * scale), y, w * scale, h * scale, token);
  794. }
  795. }
  796. continue;
  797. }
  798. if (!strcmp(token, "picn"))
  799. { // draw a pic from a name
  800. token = COM_Parse (&s);
  801. if (!skip_depth)
  802. {
  803. cgi.Draw_GetPicSize(&w, &h, token);
  804. cgi.SCR_DrawPic(x, y, w * scale, h * scale, token);
  805. }
  806. continue;
  807. }
  808. if (!strcmp(token, "num"))
  809. { // draw a number
  810. token = COM_Parse (&s);
  811. if (!skip_depth)
  812. width = atoi(token);
  813. token = COM_Parse (&s);
  814. if (!skip_depth)
  815. {
  816. value = ps->stats[atoi(token)];
  817. CG_DrawField (x, y, 0, width, value, scale);
  818. }
  819. continue;
  820. }
  821. // [Paril-KEX] special handling for the lives number
  822. else if (!strcmp(token, "lives_num"))
  823. {
  824. token = COM_Parse (&s);
  825. if (!skip_depth)
  826. {
  827. value = ps->stats[atoi(token)];
  828. CG_DrawField(x, y, value <= 2 ? flash_frame : 0, 1, max(0, value - 2), scale);
  829. }
  830. }
  831. if (!strcmp(token, "hnum"))
  832. {
  833. // health number
  834. if (!skip_depth)
  835. {
  836. int color;
  837. width = 3;
  838. value = ps->stats[STAT_HEALTH];
  839. if (value > 25)
  840. color = 0; // green
  841. else if (value > 0)
  842. color = flash_frame; // flash
  843. else
  844. color = 1;
  845. if (ps->stats[STAT_FLASHES] & 1)
  846. {
  847. cgi.Draw_GetPicSize(&w, &h, "field_3");
  848. cgi.SCR_DrawPic(x, y, w * scale, h * scale, "field_3");
  849. }
  850. CG_DrawField (x, y, color, width, value, scale);
  851. }
  852. continue;
  853. }
  854. if (!strcmp(token, "anum"))
  855. {
  856. // ammo number
  857. if (!skip_depth)
  858. {
  859. int color;
  860. width = 3;
  861. value = ps->stats[STAT_AMMO];
  862. int32_t min_ammo = cgi.CL_GetWarnAmmoCount(ps->stats[STAT_ACTIVE_WEAPON]);
  863. if (!min_ammo)
  864. min_ammo = 5; // back compat
  865. if (value > min_ammo)
  866. color = 0; // green
  867. else if (value >= 0)
  868. color = flash_frame; // flash
  869. else
  870. continue; // negative number = don't show
  871. if (ps->stats[STAT_FLASHES] & 4)
  872. {
  873. cgi.Draw_GetPicSize(&w, &h, "field_3");
  874. cgi.SCR_DrawPic(x, y, w * scale, h * scale, "field_3");
  875. }
  876. CG_DrawField (x, y, color, width, value, scale);
  877. }
  878. continue;
  879. }
  880. if (!strcmp(token, "rnum"))
  881. {
  882. // armor number
  883. if (!skip_depth)
  884. {
  885. int color;
  886. width = 3;
  887. value = ps->stats[STAT_ARMOR];
  888. if (value < 0)
  889. continue;
  890. color = 0; // green
  891. if (ps->stats[STAT_FLASHES] & 2)
  892. {
  893. cgi.Draw_GetPicSize(&w, &h, "field_3");
  894. cgi.SCR_DrawPic(x, y, w * scale, h * scale, "field_3");
  895. }
  896. CG_DrawField (x, y, color, width, value, scale);
  897. }
  898. continue;
  899. }
  900. if (!strcmp(token, "stat_string"))
  901. {
  902. token = COM_Parse (&s);
  903. if (!skip_depth)
  904. {
  905. index = atoi(token);
  906. if (index < 0 || index >= MAX_STATS)
  907. cgi.Com_Error("Bad stat_string index");
  908. index = ps->stats[index];
  909. if (cgi.CL_ServerProtocol() <= PROTOCOL_VERSION_3XX)
  910. index = CS_REMAP(index).start / CS_MAX_STRING_LENGTH;
  911. if (index < 0 || index >= MAX_CONFIGSTRINGS)
  912. cgi.Com_Error("Bad stat_string index");
  913. if (!scr_usekfont->integer)
  914. CG_DrawString (x, y, scale, cgi.get_configstring(index));
  915. else
  916. cgi.SCR_DrawFontString(cgi.get_configstring(index), x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT);
  917. }
  918. continue;
  919. }
  920. if (!strcmp(token, "cstring"))
  921. {
  922. token = COM_Parse (&s);
  923. if (!skip_depth)
  924. CG_DrawHUDString (token, x, y, hx*2*scale, 0, scale);
  925. continue;
  926. }
  927. if (!strcmp(token, "string"))
  928. {
  929. token = COM_Parse (&s);
  930. if (!skip_depth)
  931. {
  932. if (!scr_usekfont->integer)
  933. CG_DrawString (x, y, scale, token);
  934. else
  935. cgi.SCR_DrawFontString(token, x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT);
  936. }
  937. continue;
  938. }
  939. if (!strcmp(token, "cstring2"))
  940. {
  941. token = COM_Parse (&s);
  942. if (!skip_depth)
  943. CG_DrawHUDString (token, x, y, hx*2*scale, 0x80, scale);
  944. continue;
  945. }
  946. if (!strcmp(token, "string2"))
  947. {
  948. token = COM_Parse (&s);
  949. if (!skip_depth)
  950. {
  951. if (!scr_usekfont->integer)
  952. CG_DrawString (x, y, scale, token, true);
  953. else
  954. cgi.SCR_DrawFontString(token, x, y - (font_y_offset * scale), scale, alt_color, true, text_align_t::LEFT);
  955. }
  956. continue;
  957. }
  958. if (!strcmp(token, "if"))
  959. {
  960. // if stmt
  961. token = COM_Parse (&s);
  962. if_depth++;
  963. // skip to endif
  964. if (!skip_depth && !ps->stats[atoi(token)])
  965. {
  966. skip_depth = true;
  967. endif_depth = if_depth;
  968. }
  969. continue;
  970. }
  971. if (!strcmp(token, "ifgef"))
  972. {
  973. // if stmt
  974. token = COM_Parse (&s);
  975. if_depth++;
  976. // skip to endif
  977. if (!skip_depth && cgi.CL_ServerFrame() < atoi(token))
  978. {
  979. skip_depth = true;
  980. endif_depth = if_depth;
  981. }
  982. continue;
  983. }
  984. if (!strcmp(token, "endif"))
  985. {
  986. if (skip_depth && (if_depth == endif_depth))
  987. skip_depth = false;
  988. if_depth--;
  989. if (if_depth < 0)
  990. cgi.Com_Error("endif without matching if");
  991. continue;
  992. }
  993. // localization stuff
  994. if (!strcmp(token, "loc_stat_string"))
  995. {
  996. token = COM_Parse (&s);
  997. if (!skip_depth)
  998. {
  999. index = atoi(token);
  1000. if (index < 0 || index >= MAX_STATS)
  1001. cgi.Com_Error("Bad stat_string index");
  1002. index = ps->stats[index];
  1003. if (cgi.CL_ServerProtocol() <= PROTOCOL_VERSION_3XX)
  1004. index = CS_REMAP(index).start / CS_MAX_STRING_LENGTH;
  1005. if (index < 0 || index >= MAX_CONFIGSTRINGS)
  1006. cgi.Com_Error("Bad stat_string index");
  1007. if (!scr_usekfont->integer)
  1008. CG_DrawString (x, y, scale, cgi.Localize(cgi.get_configstring(index), nullptr, 0));
  1009. else
  1010. cgi.SCR_DrawFontString(cgi.Localize(cgi.get_configstring(index), nullptr, 0), x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT);
  1011. }
  1012. continue;
  1013. }
  1014. if (!strcmp(token, "loc_stat_rstring"))
  1015. {
  1016. token = COM_Parse (&s);
  1017. if (!skip_depth)
  1018. {
  1019. index = atoi(token);
  1020. if (index < 0 || index >= MAX_STATS)
  1021. cgi.Com_Error("Bad stat_string index");
  1022. index = ps->stats[index];
  1023. if (cgi.CL_ServerProtocol() <= PROTOCOL_VERSION_3XX)
  1024. index = CS_REMAP(index).start / CS_MAX_STRING_LENGTH;
  1025. if (index < 0 || index >= MAX_CONFIGSTRINGS)
  1026. cgi.Com_Error("Bad stat_string index");
  1027. const char *s = cgi.Localize(cgi.get_configstring(index), nullptr, 0);
  1028. if (!scr_usekfont->integer)
  1029. CG_DrawString (x - (strlen(s) * CONCHAR_WIDTH * scale), y, scale, s);
  1030. else
  1031. {
  1032. vec2_t size = cgi.SCR_MeasureFontString(s, scale);
  1033. cgi.SCR_DrawFontString(s, x - size.x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT);
  1034. }
  1035. }
  1036. continue;
  1037. }
  1038. if (!strcmp(token, "loc_stat_cstring"))
  1039. {
  1040. token = COM_Parse (&s);
  1041. if (!skip_depth)
  1042. {
  1043. index = atoi(token);
  1044. if (index < 0 || index >= MAX_STATS)
  1045. cgi.Com_Error("Bad stat_string index");
  1046. index = ps->stats[index];
  1047. if (cgi.CL_ServerProtocol() <= PROTOCOL_VERSION_3XX)
  1048. index = CS_REMAP(index).start / CS_MAX_STRING_LENGTH;
  1049. if (index < 0 || index >= MAX_CONFIGSTRINGS)
  1050. cgi.Com_Error("Bad stat_string index");
  1051. CG_DrawHUDString (cgi.Localize(cgi.get_configstring(index), nullptr, 0), x, y, hx*2*scale, 0, scale);
  1052. }
  1053. continue;
  1054. }
  1055. if (!strcmp(token, "loc_stat_cstring2"))
  1056. {
  1057. token = COM_Parse (&s);
  1058. if (!skip_depth)
  1059. {
  1060. index = atoi(token);
  1061. if (index < 0 || index >= MAX_STATS)
  1062. cgi.Com_Error("Bad stat_string index");
  1063. index = ps->stats[index];
  1064. if (cgi.CL_ServerProtocol() <= PROTOCOL_VERSION_3XX)
  1065. index = CS_REMAP(index).start / CS_MAX_STRING_LENGTH;
  1066. if (index < 0 || index >= MAX_CONFIGSTRINGS)
  1067. cgi.Com_Error("Bad stat_string index");
  1068. CG_DrawHUDString (cgi.Localize(cgi.get_configstring(index), nullptr, 0), x, y, hx*2*scale, 0x80, scale);
  1069. }
  1070. continue;
  1071. }
  1072. static char arg_tokens[MAX_LOCALIZATION_ARGS + 1][MAX_TOKEN_CHARS];
  1073. static const char *arg_buffers[MAX_LOCALIZATION_ARGS];
  1074. if (!strcmp(token, "loc_cstring"))
  1075. {
  1076. int32_t num_args = atoi(COM_Parse (&s));
  1077. if (num_args < 0 || num_args >= MAX_LOCALIZATION_ARGS)
  1078. cgi.Com_Error("Bad loc string");
  1079. // parse base
  1080. token = COM_Parse (&s);
  1081. Q_strlcpy(arg_tokens[0], token, sizeof(arg_tokens[0]));
  1082. // parse args
  1083. for (int32_t i = 0; i < num_args; i++)
  1084. {
  1085. token = COM_Parse (&s);
  1086. Q_strlcpy(arg_tokens[1 + i], token, sizeof(arg_tokens[0]));
  1087. arg_buffers[i] = arg_tokens[1 + i];
  1088. }
  1089. if (!skip_depth)
  1090. CG_DrawHUDString (cgi.Localize(arg_tokens[0], arg_buffers, num_args), x, y, hx*2*scale, 0, scale);
  1091. continue;
  1092. }
  1093. if (!strcmp(token, "loc_string"))
  1094. {
  1095. int32_t num_args = atoi(COM_Parse (&s));
  1096. if (num_args < 0 || num_args >= MAX_LOCALIZATION_ARGS)
  1097. cgi.Com_Error("Bad loc string");
  1098. // parse base
  1099. token = COM_Parse (&s);
  1100. Q_strlcpy(arg_tokens[0], token, sizeof(arg_tokens[0]));
  1101. // parse args
  1102. for (int32_t i = 0; i < num_args; i++)
  1103. {
  1104. token = COM_Parse (&s);
  1105. Q_strlcpy(arg_tokens[1 + i], token, sizeof(arg_tokens[0]));
  1106. arg_buffers[i] = arg_tokens[1 + i];
  1107. }
  1108. if (!skip_depth)
  1109. {
  1110. if (!scr_usekfont->integer)
  1111. CG_DrawString (x, y, scale, cgi.Localize(arg_tokens[0], arg_buffers, num_args));
  1112. else
  1113. cgi.SCR_DrawFontString(cgi.Localize(arg_tokens[0], arg_buffers, num_args), x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT);
  1114. }
  1115. continue;
  1116. }
  1117. if (!strcmp(token, "loc_cstring2"))
  1118. {
  1119. int32_t num_args = atoi(COM_Parse (&s));
  1120. if (num_args < 0 || num_args >= MAX_LOCALIZATION_ARGS)
  1121. cgi.Com_Error("Bad loc string");
  1122. // parse base
  1123. token = COM_Parse (&s);
  1124. Q_strlcpy(arg_tokens[0], token, sizeof(arg_tokens[0]));
  1125. // parse args
  1126. for (int32_t i = 0; i < num_args; i++)
  1127. {
  1128. token = COM_Parse (&s);
  1129. Q_strlcpy(arg_tokens[1 + i], token, sizeof(arg_tokens[0]));
  1130. arg_buffers[i] = arg_tokens[1 + i];
  1131. }
  1132. if (!skip_depth)
  1133. CG_DrawHUDString (cgi.Localize(arg_tokens[0], arg_buffers, num_args), x, y, hx*2*scale, 0x80, scale);
  1134. continue;
  1135. }
  1136. if (!strcmp(token, "loc_string2") || !strcmp(token, "loc_rstring2") ||
  1137. !strcmp(token, "loc_string") || !strcmp(token, "loc_rstring"))
  1138. {
  1139. bool green = token[strlen(token) - 1] == '2';
  1140. bool rightAlign = !Q_strncasecmp(token, "loc_rstring", strlen("loc_rstring"));
  1141. int32_t num_args = atoi(COM_Parse (&s));
  1142. if (num_args < 0 || num_args >= MAX_LOCALIZATION_ARGS)
  1143. cgi.Com_Error("Bad loc string");
  1144. // parse base
  1145. token = COM_Parse (&s);
  1146. Q_strlcpy(arg_tokens[0], token, sizeof(arg_tokens[0]));
  1147. // parse args
  1148. for (int32_t i = 0; i < num_args; i++)
  1149. {
  1150. token = COM_Parse (&s);
  1151. Q_strlcpy(arg_tokens[1 + i], token, sizeof(arg_tokens[0]));
  1152. arg_buffers[i] = arg_tokens[1 + i];
  1153. }
  1154. if (!skip_depth)
  1155. {
  1156. const char *locStr = cgi.Localize(arg_tokens[0], arg_buffers, num_args);
  1157. int xOffs = 0;
  1158. if (rightAlign)
  1159. {
  1160. xOffs = scr_usekfont->integer ? cgi.SCR_MeasureFontString(locStr, scale).x : (strlen(locStr) * CONCHAR_WIDTH * scale);
  1161. }
  1162. if (!scr_usekfont->integer)
  1163. CG_DrawString (x - xOffs, y, scale, locStr, green);
  1164. else
  1165. cgi.SCR_DrawFontString(locStr, x - xOffs, y - (font_y_offset * scale), scale, green ? alt_color : rgba_white, true, text_align_t::LEFT);
  1166. }
  1167. continue;
  1168. }
  1169. // draw time remaining
  1170. if (!strcmp(token, "time_limit"))
  1171. {
  1172. // end frame
  1173. token = COM_Parse (&s);
  1174. if (!skip_depth)
  1175. {
  1176. int32_t end_frame = atoi(token);
  1177. if (end_frame < cgi.CL_ServerFrame())
  1178. continue;
  1179. uint64_t remaining_ms = (end_frame - cgi.CL_ServerFrame()) * cgi.frame_time_ms;
  1180. const bool green = true;
  1181. arg_buffers[0] = G_Fmt("{:02}:{:02}", (remaining_ms / 1000) / 60, (remaining_ms / 1000) % 60).data();
  1182. const char *locStr = cgi.Localize("$g_score_time", arg_buffers, 1);
  1183. int xOffs = scr_usekfont->integer ? cgi.SCR_MeasureFontString(locStr, scale).x : (strlen(locStr) * CONCHAR_WIDTH * scale);
  1184. if (!scr_usekfont->integer)
  1185. CG_DrawString (x - xOffs, y, scale, locStr, green);
  1186. else
  1187. cgi.SCR_DrawFontString(locStr, x - xOffs, y - (font_y_offset * scale), scale, green ? alt_color : rgba_white, true, text_align_t::LEFT);
  1188. }
  1189. }
  1190. // draw client dogtag
  1191. if (!strcmp(token, "dogtag"))
  1192. {
  1193. token = COM_Parse (&s);
  1194. if (!skip_depth)
  1195. {
  1196. value = atoi(token);
  1197. if (value >= MAX_CLIENTS || value < 0)
  1198. cgi.Com_Error("client >= MAX_CLIENTS");
  1199. const std::string_view path = G_Fmt("/tags/{}", cgi.CL_GetClientDogtag(value));
  1200. cgi.SCR_DrawPic(x, y, 198 * scale, 32 * scale, path.data());
  1201. }
  1202. }
  1203. if (!strcmp(token, "start_table"))
  1204. {
  1205. token = COM_Parse (&s);
  1206. value = atoi(token);
  1207. if (!skip_depth)
  1208. {
  1209. if (value >= q_countof(hud_temp.table_rows[0].table_cells))
  1210. cgi.Com_Error("table too big");
  1211. hud_temp.num_columns = value;
  1212. hud_temp.num_rows = 1;
  1213. for (int i = 0; i < value; i++)
  1214. hud_temp.column_widths[i] = 0;
  1215. }
  1216. for (int i = 0; i < value; i++)
  1217. {
  1218. token = COM_Parse (&s);
  1219. if (!skip_depth)
  1220. {
  1221. token = cgi.Localize(token, nullptr, 0);
  1222. Q_strlcpy(hud_temp.table_rows[0].table_cells[i].text, token, sizeof(hud_temp.table_rows[0].table_cells[i].text));
  1223. hud_temp.column_widths[i] = max(hud_temp.column_widths[i], (size_t) cgi.SCR_MeasureFontString(hud_temp.table_rows[0].table_cells[i].text, scale).x);
  1224. }
  1225. }
  1226. }
  1227. if (!strcmp(token, "table_row"))
  1228. {
  1229. token = COM_Parse (&s);
  1230. value = atoi(token);
  1231. if (!skip_depth)
  1232. {
  1233. if (hud_temp.num_rows >= q_countof(hud_temp.table_rows))
  1234. {
  1235. cgi.Com_Error("table too big");
  1236. return;
  1237. }
  1238. }
  1239. auto &row = hud_temp.table_rows[hud_temp.num_rows];
  1240. for (int i = 0; i < value; i++)
  1241. {
  1242. token = COM_Parse (&s);
  1243. if (!skip_depth)
  1244. {
  1245. Q_strlcpy(row.table_cells[i].text, token, sizeof(row.table_cells[i].text));
  1246. hud_temp.column_widths[i] = max(hud_temp.column_widths[i], (size_t) cgi.SCR_MeasureFontString(row.table_cells[i].text, scale).x);
  1247. }
  1248. }
  1249. if (!skip_depth)
  1250. {
  1251. for (int i = value; i < hud_temp.num_columns; i++)
  1252. row.table_cells[i].text[0] = '\0';
  1253. hud_temp.num_rows++;
  1254. }
  1255. }
  1256. if (!strcmp(token, "draw_table"))
  1257. {
  1258. if (!skip_depth)
  1259. {
  1260. // in scaled pixels, incl padding between elements
  1261. uint32_t total_inner_table_width = 0;
  1262. for (int i = 0; i < hud_temp.num_columns; i++)
  1263. {
  1264. if (i != 0)
  1265. total_inner_table_width += cgi.SCR_MeasureFontString(" ", scale).x;
  1266. total_inner_table_width += hud_temp.column_widths[i];
  1267. }
  1268. // in scaled pixels
  1269. uint32_t total_table_height = hud_temp.num_rows * (CONCHAR_WIDTH + font_y_offset) * scale;
  1270. CG_DrawTable(x, y, total_inner_table_width, total_table_height, scale);
  1271. }
  1272. }
  1273. if (!strcmp(token, "stat_pname"))
  1274. {
  1275. token = COM_Parse(&s);
  1276. if (!skip_depth)
  1277. {
  1278. index = atoi(token);
  1279. if (index < 0 || index >= MAX_STATS)
  1280. cgi.Com_Error("Bad stat_string index");
  1281. index = ps->stats[index] - 1;
  1282. if (!scr_usekfont->integer)
  1283. CG_DrawString(x, y, scale, cgi.CL_GetClientName(index));
  1284. else
  1285. cgi.SCR_DrawFontString(cgi.CL_GetClientName(index), x, y - (font_y_offset * scale), scale, rgba_white, true, text_align_t::LEFT);
  1286. }
  1287. continue;
  1288. }
  1289. if (!strcmp(token, "health_bars"))
  1290. {
  1291. if (skip_depth)
  1292. continue;
  1293. const byte *stat = reinterpret_cast<const byte *>(&ps->stats[STAT_HEALTH_BARS]);
  1294. const char *name = cgi.Localize(cgi.get_configstring(CONFIG_HEALTH_BAR_NAME), nullptr, 0);
  1295. CG_DrawHUDString(name, (hud_vrect.x + hud_vrect.width/2 + -160) * scale, y, (320 / 2) * 2 * scale, 0, scale);
  1296. float bar_width = ((hud_vrect.width * scale) - (hud_safe.x * 2)) * 0.50f;
  1297. float bar_height = 4 * scale;
  1298. y += cgi.SCR_FontLineHeight(scale);
  1299. float x = ((hud_vrect.x + (hud_vrect.width * 0.5f)) * scale) - (bar_width * 0.5f);
  1300. // 2 health bars, hardcoded
  1301. for (size_t i = 0; i < 2; i++, stat++)
  1302. {
  1303. if (!(*stat & 0b10000000))
  1304. continue;
  1305. float percent = (*stat & 0b01111111) / 127.f;
  1306. cgi.SCR_DrawColorPic(x, y, bar_width + scale, bar_height + scale, "_white", rgba_black);
  1307. if (percent > 0)
  1308. cgi.SCR_DrawColorPic(x, y, bar_width * percent, bar_height, "_white", rgba_red);
  1309. if (percent < 1)
  1310. cgi.SCR_DrawColorPic(x + (bar_width * percent), y, bar_width * (1.f - percent), bar_height, "_white", { 80, 80, 80, 255 });
  1311. y += bar_height * 3;
  1312. }
  1313. }
  1314. if (!strcmp(token, "story"))
  1315. {
  1316. const char *story_str = cgi.get_configstring(CONFIG_STORY);
  1317. if (!*story_str)
  1318. continue;
  1319. const char *localized = cgi.Localize(story_str, nullptr, 0);
  1320. vec2_t size = cgi.SCR_MeasureFontString(localized, scale);
  1321. float centerx = ((hud_vrect.x + (hud_vrect.width * 0.5f)) * scale);
  1322. float centery = ((hud_vrect.y + (hud_vrect.height * 0.5f)) * scale) - (size.y * 0.5f);
  1323. cgi.SCR_DrawFontString(localized, centerx, centery, scale, rgba_white, true, text_align_t::CENTER);
  1324. }
  1325. }
  1326. if (skip_depth)
  1327. cgi.Com_Error("if with no matching endif");
  1328. }
  1329. static cvar_t *cl_skipHud;
  1330. static cvar_t *cl_paused;
  1331. /*
  1332. ================
  1333. CL_DrawInventory
  1334. ================
  1335. */
  1336. constexpr size_t DISPLAY_ITEMS = 19;
  1337. static void CG_DrawInventory(const player_state_t *ps, const std::array<int16_t, MAX_ITEMS> &inventory, vrect_t hud_vrect, int32_t scale)
  1338. {
  1339. int i;
  1340. int num, selected_num, item;
  1341. int index[MAX_ITEMS];
  1342. int x, y;
  1343. int width, height;
  1344. int selected;
  1345. int top;
  1346. selected = ps->stats[STAT_SELECTED_ITEM];
  1347. num = 0;
  1348. selected_num = 0;
  1349. for (i=0 ; i<MAX_ITEMS ; i++) {
  1350. if ( i == selected ) {
  1351. selected_num = num;
  1352. }
  1353. if ( inventory[i] ) {
  1354. index[num] = i;
  1355. num++;
  1356. }
  1357. }
  1358. // determine scroll point
  1359. top = selected_num - DISPLAY_ITEMS/2;
  1360. if (num - top < DISPLAY_ITEMS)
  1361. top = num - DISPLAY_ITEMS;
  1362. if (top < 0)
  1363. top = 0;
  1364. x = hud_vrect.x * scale;
  1365. y = hud_vrect.y * scale;
  1366. width = hud_vrect.width;
  1367. height = hud_vrect.height;
  1368. x += ((width / 2) - (256 / 2)) * scale;
  1369. y += ((height / 2) - (216 / 2)) * scale;
  1370. int pich, picw;
  1371. cgi.Draw_GetPicSize(&picw, &pich, "inventory");
  1372. cgi.SCR_DrawPic(x, y+8*scale, picw * scale, pich * scale, "inventory");
  1373. y += 27 * scale;
  1374. x += 22 * scale;
  1375. for (i=top ; i<num && i < top+DISPLAY_ITEMS ; i++)
  1376. {
  1377. item = index[i];
  1378. if (item == selected) // draw a blinky cursor by the selected item
  1379. {
  1380. if ( (cgi.CL_ClientRealTime() * 10) & 1)
  1381. cgi.SCR_DrawChar(x-8, y, scale, 15, false);
  1382. }
  1383. if (!scr_usekfont->integer)
  1384. {
  1385. CG_DrawString(x, y, scale,
  1386. G_Fmt("{:3} {}", inventory[item],
  1387. cgi.Localize(cgi.get_configstring(CS_ITEMS + item), nullptr, 0)).data(),
  1388. item == selected, false);
  1389. }
  1390. else
  1391. {
  1392. const char *string = G_Fmt("{}", inventory[item]).data();
  1393. cgi.SCR_DrawFontString(string, x + (216 * scale) - (16 * scale), y - (font_y_offset * scale), scale, (item == selected) ? alt_color : rgba_white, true, text_align_t::RIGHT);
  1394. string = cgi.Localize(cgi.get_configstring(CS_ITEMS + item), nullptr, 0);
  1395. cgi.SCR_DrawFontString(string, x + (16 * scale), y - (font_y_offset * scale), scale, (item == selected) ? alt_color : rgba_white, true, text_align_t::LEFT);
  1396. }
  1397. y += 8 * scale;
  1398. }
  1399. }
  1400. extern uint64_t cgame_init_time;
  1401. void CG_DrawHUD (int32_t isplit, const cg_server_data_t *data, vrect_t hud_vrect, vrect_t hud_safe, int32_t scale, int32_t playernum, const player_state_t *ps)
  1402. {
  1403. if (cgi.CL_InAutoDemoLoop())
  1404. {
  1405. if (cl_paused->integer) return; // demo is paused, menu is open
  1406. uint64_t time = cgi.CL_ClientRealTime() - cgame_init_time;
  1407. if (time < 20000 &&
  1408. (time % 4000) < 2000)
  1409. cgi.SCR_DrawFontString(cgi.Localize("$m_eou_press_button", nullptr, 0), hud_vrect.width * 0.5f * scale, (hud_vrect.height - 64.f) * scale, scale, rgba_green, true, text_align_t::CENTER);
  1410. return;
  1411. }
  1412. // draw HUD
  1413. if (!cl_skipHud->integer && !(ps->stats[STAT_LAYOUTS] & LAYOUTS_HIDE_HUD))
  1414. CG_ExecuteLayoutString(cgi.get_configstring(CS_STATUSBAR), hud_vrect, hud_safe, scale, playernum, ps);
  1415. // draw centerprint string
  1416. CG_CheckDrawCenterString(ps, hud_vrect, hud_safe, isplit, scale);
  1417. // draw notify
  1418. CG_DrawNotify(isplit, hud_vrect, hud_safe, scale);
  1419. // svc_layout still drawn with hud off
  1420. if (ps->stats[STAT_LAYOUTS] & LAYOUTS_LAYOUT)
  1421. CG_ExecuteLayoutString(data->layout, hud_vrect, hud_safe, scale, playernum, ps);
  1422. // inventory too
  1423. if (ps->stats[STAT_LAYOUTS] & LAYOUTS_INVENTORY)
  1424. CG_DrawInventory(ps, data->inventory, hud_vrect, scale);
  1425. }
  1426. /*
  1427. ================
  1428. CG_TouchPics
  1429. ================
  1430. */
  1431. void CG_TouchPics()
  1432. {
  1433. for (auto &nums : sb_nums)
  1434. for (auto &str : nums)
  1435. cgi.Draw_RegisterPic(str);
  1436. cgi.Draw_RegisterPic("inventory");
  1437. font_y_offset = (cgi.SCR_FontLineHeight(1) - CONCHAR_WIDTH) / 2;
  1438. }
  1439. void CG_InitScreen()
  1440. {
  1441. cl_paused = cgi.cvar("paused", "0", CVAR_NOFLAGS);
  1442. cl_skipHud = cgi.cvar("cl_skipHud", "0", CVAR_ARCHIVE);
  1443. scr_usekfont = cgi.cvar("scr_usekfont", "1", CVAR_NOFLAGS);
  1444. scr_centertime = cgi.cvar ("scr_centertime", "5.0", CVAR_ARCHIVE); // [Sam-KEX] Changed from 2.5
  1445. scr_printspeed = cgi.cvar ("scr_printspeed", "0.04", CVAR_NOFLAGS); // [Sam-KEX] Changed from 8
  1446. cl_notifytime = cgi.cvar ("cl_notifytime", "5.0", CVAR_ARCHIVE);
  1447. scr_maxlines = cgi.cvar ("scr_maxlines", "4", CVAR_ARCHIVE);
  1448. ui_acc_contrast = cgi.cvar ("ui_acc_contrast", "0", CVAR_NOFLAGS);
  1449. ui_acc_alttypeface = cgi.cvar("ui_acc_alttypeface", "0", CVAR_NOFLAGS);
  1450. hud_data = {};
  1451. }