]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/menu/nexuiz/serverlist.c
use DP_SV_QCSTATUS to show frags to qstat again; make menu aware of player lists...
[divverent/nexuiz.git] / data / qcsrc / menu / nexuiz / serverlist.c
1 #ifdef INTERFACE
2 CLASS(NexuizServerList) EXTENDS(NexuizListBox)
3         METHOD(NexuizServerList, configureNexuizServerList, void(entity))
4         ATTRIB(NexuizServerList, rowsPerItem, float, 1)
5         METHOD(NexuizServerList, draw, void(entity))
6         METHOD(NexuizServerList, drawListBoxItem, void(entity, float, vector, float))
7         METHOD(NexuizServerList, clickListBoxItem, void(entity, float, vector))
8         METHOD(NexuizServerList, resizeNotify, void(entity, vector, vector, vector, vector))
9         METHOD(NexuizServerList, keyDown, float(entity, float, float, float))
10
11         ATTRIB(NexuizServerList, realFontSize, vector, '0 0 0')
12         ATTRIB(NexuizServerList, realUpperMargin, float, 0)
13         ATTRIB(NexuizServerList, columnPingOrigin, float, 0)
14         ATTRIB(NexuizServerList, columnPingSize, float, 0)
15         ATTRIB(NexuizServerList, columnNameOrigin, float, 0)
16         ATTRIB(NexuizServerList, columnNameSize, float, 0)
17         ATTRIB(NexuizServerList, columnMapOrigin, float, 0)
18         ATTRIB(NexuizServerList, columnMapSize, float, 0)
19         ATTRIB(NexuizServerList, columnTypeOrigin, float, 0)
20         ATTRIB(NexuizServerList, columnTypeSize, float, 0)
21         ATTRIB(NexuizServerList, columnPlayersOrigin, float, 0)
22         ATTRIB(NexuizServerList, columnPlayersSize, float, 0)
23
24         ATTRIB(NexuizServerList, selectedServer, string, string_null) // to restore selected server when needed
25         METHOD(NexuizServerList, setSelected, void(entity, float))
26         METHOD(NexuizServerList, setSortOrder, void(entity, float, float))
27         ATTRIB(NexuizServerList, filterShowEmpty, float, 1)
28         ATTRIB(NexuizServerList, filterShowFull, float, 1)
29         ATTRIB(NexuizServerList, filterString, string, string_null)
30         ATTRIB(NexuizServerList, controlledTextbox, entity, NULL)
31         ATTRIB(NexuizServerList, nextRefreshTime, float, 0)
32         METHOD(NexuizServerList, refreshServerList, void(entity, float)) // refresh mode: 0 = just reparametrize, 1 = send new requests, 2 = clear
33         ATTRIB(NexuizServerList, needsRefresh, float, 1)
34         METHOD(NexuizServerList, focusEnter, void(entity))
35         METHOD(NexuizServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
36         ATTRIB(NexuizServerList, sortButton1, entity, NULL)
37         ATTRIB(NexuizServerList, sortButton2, entity, NULL)
38         ATTRIB(NexuizServerList, sortButton3, entity, NULL)
39         ATTRIB(NexuizServerList, sortButton4, entity, NULL)
40         ATTRIB(NexuizServerList, sortButton5, entity, NULL)
41         ATTRIB(NexuizServerList, connectButton, entity, NULL)
42         ATTRIB(NexuizServerList, currentSortOrder, float, 0)
43         ATTRIB(NexuizServerList, currentSortField, float, -1)
44         ATTRIB(NexuizServerList, lastClickedServer, float, -1)
45         ATTRIB(NexuizServerList, lastClickedTime, float, 0)
46 ENDCLASS(NexuizServerList)
47 entity makeNexuizServerList();
48 void ServerList_Connect_Click(entity btn, entity me);
49 void ServerList_ShowEmpty_Click(entity box, entity me);
50 void ServerList_ShowFull_Click(entity box, entity me);
51 void ServerList_Filter_Change(entity box, entity me);
52 #endif
53
54 #ifdef IMPLEMENTATION
55 float SLIST_FIELD_CNAME;
56 float SLIST_FIELD_PING;
57 float SLIST_FIELD_GAME;
58 float SLIST_FIELD_MOD;
59 float SLIST_FIELD_MAP;
60 float SLIST_FIELD_NAME;
61 float SLIST_FIELD_MAXPLAYERS;
62 float SLIST_FIELD_NUMPLAYERS;
63 float SLIST_FIELD_NUMHUMANS;
64 float SLIST_FIELD_NUMBOTS;
65 float SLIST_FIELD_PROTOCOL;
66 float SLIST_FIELD_FREESLOTS;
67 float SLIST_FIELD_PLAYERS;
68 float SLIST_FIELD_QCSTATUS;
69 void ServerList_UpdateFieldIDs()
70 {
71         SLIST_FIELD_CNAME = gethostcacheindexforkey( "cname" );
72         SLIST_FIELD_PING = gethostcacheindexforkey( "ping" );
73         SLIST_FIELD_GAME = gethostcacheindexforkey( "game" );
74         SLIST_FIELD_MOD = gethostcacheindexforkey( "mod" );
75         SLIST_FIELD_MAP = gethostcacheindexforkey( "map" );
76         SLIST_FIELD_NAME = gethostcacheindexforkey( "name" );
77         SLIST_FIELD_MAXPLAYERS = gethostcacheindexforkey( "maxplayers" );
78         SLIST_FIELD_NUMPLAYERS = gethostcacheindexforkey( "numplayers" );
79         SLIST_FIELD_NUMHUMANS = gethostcacheindexforkey( "numhumans" );
80         SLIST_FIELD_NUMBOTS = gethostcacheindexforkey( "numbots" );
81         SLIST_FIELD_PROTOCOL = gethostcacheindexforkey( "protocol" );
82         SLIST_FIELD_FREESLOTS = gethostcacheindexforkey( "freeslots" );
83         SLIST_FIELD_PLAYERS = gethostcacheindexforkey( "players" );
84         SLIST_FIELD_QCSTATUS = gethostcacheindexforkey( "qcstatus" );
85 }
86
87 entity makeNexuizServerList()
88 {
89         entity me;
90         me = spawnNexuizServerList();
91         me.configureNexuizServerList(me);
92         return me;
93 }
94 void configureNexuizServerListNexuizServerList(entity me)
95 {
96         me.configureNexuizListBox(me);
97
98         ServerList_UpdateFieldIDs();
99
100         me.nItems = 0;
101 }
102 void setSelectedNexuizServerList(entity me, float i)
103 {
104         float save;
105         save = me.selectedItem;
106         setSelectedListBox(me, i);
107         /*
108         if(me.selectedItem == save)
109                 return;
110         */
111         if(me.nItems == 0)
112                 return;
113         if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != me.nItems)
114                 return; // sorry, it would be wrong
115         if(me.selectedServer)
116                 strunzone(me.selectedServer);
117         me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
118 }
119 void refreshServerListNexuizServerList(entity me, float mode)
120 {
121         // 0: just reparametrize
122         // 1: also ask for new servers
123         // 2: clear
124         //print("refresh of type ", ftos(mode), "\n");
125         /* if(mode == 2) // borken
126         {
127                 // clear list
128                 localcmd("net_slist\n");
129                 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
130         }
131         else */
132         {
133                 float m;
134                 string s, typestr;
135                 s = me.filterString;
136
137                 m = strstrofs(s, ":", 0);
138                 if(m >= 0)
139                 {
140                         typestr = substring(s, 0, m);
141                         s = substring(s, m + 1, strlen(s) - m - 1);
142                         while(substring(s, 0, 1) == " ")
143                                 s = substring(s, 1, strlen(s) - 1);
144                 }
145                 else
146                         typestr = "";
147
148                 m = SLIST_MASK_AND - 1;
149                 resethostcachemasks();
150                 if(!me.filterShowFull)
151                         sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL);
152                 if(!me.filterShowEmpty)
153                         sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
154                 if(typestr != "")
155                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
156                 m = SLIST_MASK_OR - 1;
157                 if(s != "")
158                 {
159                         sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
160                         sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
161                         sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
162                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
163                 }
164                 sethostcachesort(me.currentSortField, me.currentSortOrder < 0);
165                 resorthostcache();
166                 if(mode >= 1)
167                         refreshhostcache();
168         }
169 }
170 void focusEnterNexuizServerList(entity me)
171 {
172         if(time < me.nextRefreshTime)
173         {
174                 //print("sorry, no refresh yet\n");
175                 return;
176         }
177         me.nextRefreshTime = time + 10;
178         me.refreshServerList(me, 1);
179 }
180 void drawNexuizServerList(entity me)
181 {
182         float i, found;
183
184         if(me.currentSortField == -1)
185         {
186                 me.setSortOrder(me, SLIST_FIELD_PING, +1);
187                 me.refreshServerList(me, 2);
188         }
189         else if(me.needsRefresh == 1)
190         {
191                 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
192         }
193         else if(me.needsRefresh == 2)
194         {
195                 me.needsRefresh = 0;
196                 me.refreshServerList(me, 0);
197         }
198
199         me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
200         me.connectButton.disabled = (me.nItems == 0);
201
202         found = 0;
203         if(me.selectedServer)
204         {
205                 for(i = 0; i < me.nItems; ++i)
206                         if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
207                         {
208                                 if(i != me.selectedItem)
209                                 {
210                                         me.lastClickedServer = -1;
211                                         me.selectedItem = i;
212                                 }
213                                 found = 1;
214                                 break;
215                         }
216         }
217         if(!found)
218                 if(me.nItems > 0)
219                 {
220                         if(me.selectedItem >= me.nItems)
221                                 me.selectedItem = me.nItems - 1;
222                         if(me.selectedServer)
223                                 strunzone(me.selectedServer);
224                         me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
225                 }
226
227         drawListBox(me);
228 }
229 void ServerList_PingSort_Click(entity btn, entity me)
230 {
231         me.setSortOrder(me, SLIST_FIELD_PING, +1);
232 }
233 void ServerList_NameSort_Click(entity btn, entity me)
234 {
235         me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
236 }
237 void ServerList_MapSort_Click(entity btn, entity me)
238 {
239         me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
240 }
241 void ServerList_PlayerSort_Click(entity btn, entity me)
242 {
243         me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
244 }
245 void ServerList_TypeSort_Click(entity btn, entity me)
246 {
247         string s, t;
248         float i, m;
249         s = me.filterString;
250         m = strstrofs(s, ":", 0);
251         if(m >= 0)
252         {
253                 s = substring(s, 0, m);
254                 while(substring(s, m+1, 1) == " ") // skip spaces
255                         ++m;
256         }
257         else
258                 s = "";
259
260         for(i = 1; ; ++i) // 20 modes ought to be enough for anyone
261         {
262                 t = GametypeNameFromType(i);
263                 if(i > 1)
264                         if(t == GametypeNameFromType(0)) // it repeats (default case)
265                         {
266                                 // no type was found
267                                 // choose the first one
268                                 s = t;
269                                 break;
270                         }
271                 if(s == GametypeNameFromType(i))
272                 {
273                         // the type was found
274                         // choose the next one
275                         s = GametypeNameFromType(i + 1);
276                         if(s == GametypeNameFromType(0))
277                                 s = "";
278                         break;
279                 }
280         }
281
282         if(s != "")
283                 s = strcat(s, ":");
284         s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
285
286         me.controlledTextbox.setText(me.controlledTextbox, s);
287         me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
288         me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
289         //ServerList_Filter_Change(me.controlledTextbox, me);
290 }
291 void ServerList_Filter_Change(entity box, entity me)
292 {
293         if(me.filterString)
294                 strunzone(me.filterString);
295         if(box.text != "")
296                 me.filterString = strzone(box.text);
297         else
298                 me.filterString = string_null;
299         me.refreshServerList(me, 0);
300 }
301 void ServerList_ShowEmpty_Click(entity box, entity me)
302 {
303         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
304         me.refreshServerList(me, 0);
305 }
306 void ServerList_ShowFull_Click(entity box, entity me)
307 {
308         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
309         me.refreshServerList(me, 0);
310 }
311 void setSortOrderNexuizServerList(entity me, float field, float direction)
312 {
313         if(me.currentSortField == field)
314                 direction = -me.currentSortOrder;
315         me.currentSortOrder = direction;
316         me.currentSortField = field;
317         me.sortButton1.forcePressed = (field == SLIST_FIELD_PING);
318         me.sortButton2.forcePressed = (field == SLIST_FIELD_NAME);
319         me.sortButton3.forcePressed = (field == SLIST_FIELD_MAP);
320         me.sortButton4.forcePressed = 0;
321         me.sortButton5.forcePressed = (field == SLIST_FIELD_NUMHUMANS);
322         me.selectedItem = 0;
323         if(me.selectedServer)
324                 strunzone(me.selectedServer);
325         me.selectedServer = string_null;
326         me.refreshServerList(me, 0);
327 }
328 void positionSortButtonNexuizServerList(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
329 {
330         vector originInLBSpace, sizeInLBSpace;
331         originInLBSpace = eY * (-me.itemHeight);
332         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
333
334         vector originInDialogSpace, sizeInDialogSpace;
335         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
336         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
337
338         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
339         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
340         btn.setText(btn, theTitle);
341         btn.onClick = theFunc;
342         btn.onClickEntity = me;
343         btn.resized = 1;
344 }
345 void resizeNotifyNexuizServerList(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
346 {
347         resizeNotifyNexuizListBox(me, relOrigin, relSize, absOrigin, absSize);
348
349         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
350         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
351         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
352
353         me.columnPingOrigin = 0;
354         me.columnPingSize = me.realFontSize_x * 4;
355         me.columnMapSize = me.realFontSize_x * 12;
356         me.columnTypeSize = me.realFontSize_x * 4;
357         me.columnPlayersSize = me.realFontSize_x * 6;
358         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnTypeSize - 4 * me.realFontSize_x;
359         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
360         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
361         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
362         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
363
364         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, "Ping", ServerList_PingSort_Click);
365         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, "Host name", ServerList_NameSort_Click);
366         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, "Map", ServerList_MapSort_Click);
367         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, "Type", ServerList_TypeSort_Click);
368         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, "Players", ServerList_PlayerSort_Click);
369
370         float f;
371         f = me.currentSortField;
372         if(f >= 0)
373         {
374                 me.currentSortField = -1;
375                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
376         }
377 }
378 void ServerList_Connect_Click(entity btn, entity me)
379 {
380         if(me.nItems > 0)
381                 localcmd("connect ", me.selectedServer, "\n");
382 }
383 void clickListBoxItemNexuizServerList(entity me, float i, vector where)
384 {
385         if(i == me.lastClickedServer)
386                 if(time < me.lastClickedTime + 0.3)
387                 {
388                         // DOUBLE CLICK!
389                         ServerList_Connect_Click(NULL, me);
390                 }
391         me.lastClickedServer = i;
392         me.lastClickedTime = time;
393 }
394 void drawListBoxItemNexuizServerList(entity me, float i, vector absSize, float isSelected)
395 {
396         // layout: Ping, Server name, Map name, NP, TP, MP
397         string s;
398         float p;
399         vector theColor;
400         float theAlpha;
401
402         if(isSelected)
403                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
404
405         if(gethostcachenumber(SLIST_FIELD_NUMPLAYERS, i) >= gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i))
406                 theAlpha = SKINALPHA_SERVERLIST_FULL;
407         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
408                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
409         else
410                 theAlpha = 1;
411         
412         p = gethostcachenumber(SLIST_FIELD_PING, i);
413 #define PING_LOW 75
414 #define PING_MED 200
415 #define PING_HIGH 500
416         if(p < PING_LOW)
417                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
418         else if(p < PING_MED)
419                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
420         else if(p < PING_HIGH)
421         {
422                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
423                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
424         }
425         else
426         {
427                 theColor = eX;
428                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
429         }
430
431         s = ftos(p);
432         draw_Text(me.realUpperMargin * eY + (me.columnPingSize - draw_TextWidth(s, 0) * me.realFontSize_x) * eX, s, me.realFontSize, theColor, theAlpha, 0);
433         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize / me.realFontSize_x, 0);
434         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
435         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), me.columnMapSize / me.realFontSize_x, 0);
436         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0) * me.realFontSize_x) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
437         s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
438         p = strstrofs(s, ":", 0);
439         if(p >= 0)
440                 s = substring(s, 0, p);
441         else
442                 s = "";
443         s = draw_TextShortenToWidth(s, me.columnMapSize / me.realFontSize_x, 0);
444         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0) * me.realFontSize_x) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
445         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
446         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0) * me.realFontSize_x) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
447 }
448
449 float keyDownNexuizServerList(entity me, float scan, float ascii, float shift)
450 {
451         if(scan == K_ENTER)
452         {
453                 ServerList_Connect_Click(NULL, me);
454                 return 1;
455         }
456         else if(keyDownListBox(me, scan, ascii, shift))
457                 return 1;
458         else if(!me.controlledTextbox)
459                 return 0;
460         else
461                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
462 }
463 #endif