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))
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)
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, ipAddressBox, entity, NULL)
32 ATTRIB(NexuizServerList, favoriteButton, entity, NULL)
33 ATTRIB(NexuizServerList, nextRefreshTime, float, 0)
34 METHOD(NexuizServerList, refreshServerList, void(entity, float)) // refresh mode: 0 = just reparametrize, 1 = send new requests, 2 = clear
35 ATTRIB(NexuizServerList, needsRefresh, float, 1)
36 METHOD(NexuizServerList, focusEnter, void(entity))
37 METHOD(NexuizServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
38 ATTRIB(NexuizServerList, sortButton1, entity, NULL)
39 ATTRIB(NexuizServerList, sortButton2, entity, NULL)
40 ATTRIB(NexuizServerList, sortButton3, entity, NULL)
41 ATTRIB(NexuizServerList, sortButton4, entity, NULL)
42 ATTRIB(NexuizServerList, sortButton5, entity, NULL)
43 ATTRIB(NexuizServerList, connectButton, entity, NULL)
44 ATTRIB(NexuizServerList, infoButton, entity, NULL)
45 ATTRIB(NexuizServerList, currentSortOrder, float, 0)
46 ATTRIB(NexuizServerList, currentSortField, float, -1)
47 ATTRIB(NexuizServerList, lastClickedServer, float, -1)
48 ATTRIB(NexuizServerList, lastClickedTime, float, 0)
49 ENDCLASS(NexuizServerList)
50 entity makeNexuizServerList();
51 void ServerList_Connect_Click(entity btn, entity me);
52 void ServerList_ShowEmpty_Click(entity box, entity me);
53 void ServerList_ShowFull_Click(entity box, entity me);
54 void ServerList_Filter_Change(entity box, entity me);
55 void ServerList_Favorite_Click(entity btn, entity me);
56 void ServerList_Info_Click(entity btn, entity me);
60 float SLIST_FIELD_CNAME;
61 float SLIST_FIELD_PING;
62 float SLIST_FIELD_GAME;
63 float SLIST_FIELD_MOD;
64 float SLIST_FIELD_MAP;
65 float SLIST_FIELD_NAME;
66 float SLIST_FIELD_MAXPLAYERS;
67 float SLIST_FIELD_NUMPLAYERS;
68 float SLIST_FIELD_NUMHUMANS;
69 float SLIST_FIELD_NUMBOTS;
70 float SLIST_FIELD_PROTOCOL;
71 float SLIST_FIELD_FREESLOTS;
72 float SLIST_FIELD_PLAYERS;
73 float SLIST_FIELD_QCSTATUS;
74 float SLIST_FIELD_ISFAVORITE;
75 void ServerList_UpdateFieldIDs()
77 SLIST_FIELD_CNAME = gethostcacheindexforkey( "cname" );
78 SLIST_FIELD_PING = gethostcacheindexforkey( "ping" );
79 SLIST_FIELD_GAME = gethostcacheindexforkey( "game" );
80 SLIST_FIELD_MOD = gethostcacheindexforkey( "mod" );
81 SLIST_FIELD_MAP = gethostcacheindexforkey( "map" );
82 SLIST_FIELD_NAME = gethostcacheindexforkey( "name" );
83 SLIST_FIELD_MAXPLAYERS = gethostcacheindexforkey( "maxplayers" );
84 SLIST_FIELD_NUMPLAYERS = gethostcacheindexforkey( "numplayers" );
85 SLIST_FIELD_NUMHUMANS = gethostcacheindexforkey( "numhumans" );
86 SLIST_FIELD_NUMBOTS = gethostcacheindexforkey( "numbots" );
87 SLIST_FIELD_PROTOCOL = gethostcacheindexforkey( "protocol" );
88 SLIST_FIELD_FREESLOTS = gethostcacheindexforkey( "freeslots" );
89 SLIST_FIELD_PLAYERS = gethostcacheindexforkey( "players" );
90 SLIST_FIELD_QCSTATUS = gethostcacheindexforkey( "qcstatus" );
91 SLIST_FIELD_ISFAVORITE = gethostcacheindexforkey( "isfavorite" );
94 float IsFavorite(string srv)
97 srv = netaddress_resolve(srv, 26000);
98 n = tokenize_console(cvar_string("net_slist_favorites"));
99 for(i = 0; i < n; ++i)
100 if(srv == netaddress_resolve(argv(i), 26000))
105 void ToggleFavorite(string srv)
107 string s, s0, s1, s2, srv_resolved;
109 srv_resolved = netaddress_resolve(srv, 26000);
110 s = cvar_string("net_slist_favorites");
111 n = tokenize_console(s);
112 for(i = 0; i < n; ++i)
113 if(srv_resolved == netaddress_resolve(argv(i), 26000))
117 s0 = substring(s, 0, argv_end_index(i - 1));
119 s2 = substring(s, argv_start_index(i + 1), -1);
120 if(s0 != "" && s2 != "")
122 print("s0 = >>", s0, "<<\ns1 = >>", s1, "<<\ns2 = >>", s2, "<<\n");
123 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
130 cvar_set("net_slist_favorites", strcat(s, " ", srv));
135 entity makeNexuizServerList()
138 me = spawnNexuizServerList();
139 me.configureNexuizServerList(me);
142 void configureNexuizServerListNexuizServerList(entity me)
144 me.configureNexuizListBox(me);
146 ServerList_UpdateFieldIDs();
150 void setSelectedNexuizServerList(entity me, float i)
153 save = me.selectedItem;
154 setSelectedListBox(me, i);
156 if(me.selectedItem == save)
161 if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != me.nItems)
162 return; // sorry, it would be wrong
164 if(me.selectedServer)
165 strunzone(me.selectedServer);
166 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
168 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
169 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
171 void refreshServerListNexuizServerList(entity me, float mode)
173 // 0: just reparametrize
174 // 1: also ask for new servers
176 //print("refresh of type ", ftos(mode), "\n");
177 /* if(mode == 2) // borken
180 localcmd("net_slist\n");
181 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
186 string s, typestr, modstr;
189 m = strstrofs(s, ":", 0);
192 typestr = substring(s, 0, m);
193 s = substring(s, m + 1, strlen(s) - m - 1);
194 while(substring(s, 0, 1) == " ")
195 s = substring(s, 1, strlen(s) - 1);
200 modstr = cvar_string("menu_slist_modfilter");
202 m = SLIST_MASK_AND - 1;
203 resethostcachemasks();
204 if(!me.filterShowFull)
205 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL);
206 if(!me.filterShowEmpty)
207 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
209 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
212 if(substring(modstr, 0, 1) == "!")
213 sethostcachemaskstring(++m, SLIST_FIELD_MOD, substring(modstr, 1, strlen(modstr) - 1), SLIST_TEST_NOTEQUAL);
215 sethostcachemaskstring(++m, SLIST_FIELD_MOD, modstr, SLIST_TEST_EQUAL);
217 m = SLIST_MASK_OR - 1;
220 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
221 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
222 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
223 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
225 o = 2; // favorites first
226 if(me.currentSortOrder < 0)
227 o |= 1; // descending
228 sethostcachesort(me.currentSortField, o);
234 void focusEnterNexuizServerList(entity me)
236 if(time < me.nextRefreshTime)
238 //print("sorry, no refresh yet\n");
241 me.nextRefreshTime = time + 10;
242 me.refreshServerList(me, 1);
244 void drawNexuizServerList(entity me)
246 float i, found, owned;
248 if(me.currentSortField == -1)
250 me.setSortOrder(me, SLIST_FIELD_PING, +1);
251 me.refreshServerList(me, 2);
253 else if(me.needsRefresh == 1)
255 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
257 else if(me.needsRefresh == 2)
260 me.refreshServerList(me, 0);
263 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
265 me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
267 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
268 me.infoButton.disabled = ((me.nItems == 0) || !owned);
271 if(me.selectedServer)
273 for(i = 0; i < me.nItems; ++i)
274 if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
276 if(i != me.selectedItem)
278 me.lastClickedServer = -1;
288 if(me.selectedItem >= me.nItems)
289 me.selectedItem = me.nItems - 1;
290 if(me.selectedServer)
291 strunzone(me.selectedServer);
292 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
297 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
298 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
301 if(IsFavorite(me.ipAddressBox.text))
302 me.favoriteButton.setText(me.favoriteButton, "Remove");
304 me.favoriteButton.setText(me.favoriteButton, "Bookmark");
308 void ServerList_PingSort_Click(entity btn, entity me)
310 me.setSortOrder(me, SLIST_FIELD_PING, +1);
312 void ServerList_NameSort_Click(entity btn, entity me)
314 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
316 void ServerList_MapSort_Click(entity btn, entity me)
318 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
320 void ServerList_PlayerSort_Click(entity btn, entity me)
322 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
324 void ServerList_TypeSort_Click(entity btn, entity me)
329 m = strstrofs(s, ":", 0);
332 s = substring(s, 0, m);
333 while(substring(s, m+1, 1) == " ") // skip spaces
339 for(i = 1; ; ++i) // 20 modes ought to be enough for anyone
341 t = GametypeNameFromType(i);
343 if(t == GametypeNameFromType(0)) // it repeats (default case)
346 // choose the first one
350 if(s == GametypeNameFromType(i))
352 // the type was found
353 // choose the next one
354 s = GametypeNameFromType(i + 1);
355 if(s == GametypeNameFromType(0))
363 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
365 me.controlledTextbox.setText(me.controlledTextbox, s);
366 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
367 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
368 //ServerList_Filter_Change(me.controlledTextbox, me);
370 void ServerList_Filter_Change(entity box, entity me)
373 strunzone(me.filterString);
375 me.filterString = strzone(box.text);
377 me.filterString = string_null;
378 me.refreshServerList(me, 0);
380 me.ipAddressBox.setText(me.ipAddressBox, "");
381 me.ipAddressBox.cursorPos = 0;
383 void ServerList_ShowEmpty_Click(entity box, entity me)
385 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
386 me.refreshServerList(me, 0);
388 me.ipAddressBox.setText(me.ipAddressBox, "");
389 me.ipAddressBox.cursorPos = 0;
391 void ServerList_ShowFull_Click(entity box, entity me)
393 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
394 me.refreshServerList(me, 0);
396 me.ipAddressBox.setText(me.ipAddressBox, "");
397 me.ipAddressBox.cursorPos = 0;
399 void setSortOrderNexuizServerList(entity me, float field, float direction)
401 if(me.currentSortField == field)
402 direction = -me.currentSortOrder;
403 me.currentSortOrder = direction;
404 me.currentSortField = field;
405 me.sortButton1.forcePressed = (field == SLIST_FIELD_PING);
406 me.sortButton2.forcePressed = (field == SLIST_FIELD_NAME);
407 me.sortButton3.forcePressed = (field == SLIST_FIELD_MAP);
408 me.sortButton4.forcePressed = 0;
409 me.sortButton5.forcePressed = (field == SLIST_FIELD_NUMHUMANS);
411 if(me.selectedServer)
412 strunzone(me.selectedServer);
413 me.selectedServer = string_null;
414 me.refreshServerList(me, 0);
416 void positionSortButtonNexuizServerList(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
418 vector originInLBSpace, sizeInLBSpace;
419 originInLBSpace = eY * (-me.itemHeight);
420 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
422 vector originInDialogSpace, sizeInDialogSpace;
423 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
424 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
426 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
427 btn.Container_size_x = sizeInDialogSpace_x * theSize;
428 btn.setText(btn, theTitle);
429 btn.onClick = theFunc;
430 btn.onClickEntity = me;
433 void resizeNotifyNexuizServerList(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
435 resizeNotifyNexuizListBox(me, relOrigin, relSize, absOrigin, absSize);
437 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
438 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
439 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
441 me.columnPingOrigin = 0;
442 me.columnPingSize = me.realFontSize_x * 4;
443 me.columnMapSize = me.realFontSize_x * 12;
444 me.columnTypeSize = me.realFontSize_x * 4;
445 me.columnPlayersSize = me.realFontSize_x * 6;
446 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnTypeSize - 4 * me.realFontSize_x;
447 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
448 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
449 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
450 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
452 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, "Ping", ServerList_PingSort_Click);
453 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, "Host name", ServerList_NameSort_Click);
454 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, "Map", ServerList_MapSort_Click);
455 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, "Type", ServerList_TypeSort_Click);
456 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, "Players", ServerList_PlayerSort_Click);
459 f = me.currentSortField;
462 me.currentSortField = -1;
463 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
466 void ServerList_Connect_Click(entity btn, entity me)
468 if(me.ipAddressBox.text == "")
469 localcmd("connect ", me.selectedServer, "\n");
471 localcmd("connect ", me.ipAddressBox.text, "\n");
473 void ServerList_Favorite_Click(entity btn, entity me)
476 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
478 ToggleFavorite(me.ipAddressBox.text);
480 void ServerList_Info_Click(entity btn, entity me)
482 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
483 DialogOpenButton_Click(me, main.serverInfoDialog);
485 void clickListBoxItemNexuizServerList(entity me, float i, vector where)
487 if(i == me.lastClickedServer)
488 if(time < me.lastClickedTime + 0.3)
491 ServerList_Connect_Click(NULL, me);
493 me.lastClickedServer = i;
494 me.lastClickedTime = time;
496 void drawListBoxItemNexuizServerList(entity me, float i, vector absSize, float isSelected)
498 // layout: Ping, Server name, Map name, NP, TP, MP
505 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
507 if(gethostcachenumber(SLIST_FIELD_NUMPLAYERS, i) >= gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i))
508 theAlpha = SKINALPHA_SERVERLIST_FULL;
509 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
510 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
514 p = gethostcachenumber(SLIST_FIELD_PING, i);
517 #define PING_HIGH 500
519 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
520 else if(p < PING_MED)
521 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
522 else if(p < PING_HIGH)
524 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
525 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
530 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
533 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, i))
535 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
536 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
540 draw_Text(me.realUpperMargin * eY + (me.columnPingSize - draw_TextWidth(s, 0) * me.realFontSize_x) * eX, s, me.realFontSize, theColor, theAlpha, 0);
541 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize / me.realFontSize_x, 0);
542 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
543 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), me.columnMapSize / me.realFontSize_x, 0);
544 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);
545 s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
546 p = strstrofs(s, ":", 0);
548 s = substring(s, 0, p);
551 s = draw_TextShortenToWidth(s, me.columnMapSize / me.realFontSize_x, 0);
552 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);
553 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
554 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);
557 float keyDownNexuizServerList(entity me, float scan, float ascii, float shift)
562 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
563 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
567 ServerList_Connect_Click(NULL, me);
570 else if(scan == K_MOUSE2 || scan == K_SPACE)
572 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
573 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
575 else if(scan == K_INS || scan == K_MOUSE3)
579 ToggleFavorite(me.selectedServer);
581 else if(keyDownListBox(me, scan, ascii, shift))
583 else if(!me.controlledTextbox)
586 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);