From 0bd8f1920f35b361a7cc7878a5a887d360916455 Mon Sep 17 00:00:00 2001 From: Jonathan Schleifer Date: Thu, 6 Mar 2025 00:34:42 +0000 Subject: [PATCH] Clean up menus and text drawing FossilOrigin-Name: f17992369e439fa68605017c6ffb0d11eece9d29a4cdb5768656bd874248d623 --- src/MD2.h | 2 +- src/MD2.mm | 14 ++-- src/Menu.h | 16 ++++ src/Menu.m | 13 +++ src/MenuItem.h | 7 ++ src/MenuItem.m | 31 +++++++ src/clientextras.mm | 20 +++-- src/console.mm | 19 +++-- src/menus.mm | 196 +++++++++++++++++++++---------------------- src/meson.build | 2 + src/protos.h | 11 +-- src/renderextras.mm | 45 +++++----- src/rendertext.mm | 162 ++++++++++++++++++++--------------- src/serverbrowser.mm | 18 ++-- 14 files changed, 331 insertions(+), 225 deletions(-) create mode 100644 src/Menu.h create mode 100644 src/Menu.m create mode 100644 src/MenuItem.h create mode 100644 src/MenuItem.m diff --git a/src/MD2.h b/src/MD2.h index 7ee6ad3..e41aad2 100644 --- a/src/MD2.h +++ b/src/MD2.h @@ -11,7 +11,7 @@ OF_ASSUME_NONNULL_BEGIN @property (nonatomic) bool loaded; - (bool)loadWithIRI:(OFIRI *)IRI; -- (void)renderWithLight:(OFVector3D &)light +- (void)renderWithLight:(OFVector3D)light frame:(int)frame range:(int)range x:(float)x diff --git a/src/MD2.mm b/src/MD2.mm index 669ad09..ccbe274 100644 --- a/src/MD2.mm +++ b/src/MD2.mm @@ -24,6 +24,12 @@ struct md2_frame { md2_vertex vertices[1]; }; +static float +snap(int sn, float f) +{ + return sn ? (float)(((int)(f + sn * 0.5f)) & (~(sn - 1))) : f; +} + @implementation MD2 { int _numGlCommands; int *_glCommands; @@ -103,12 +109,6 @@ struct md2_frame { } } -float -snap(int sn, float f) -{ - return sn ? (float)(((int)(f + sn * 0.5f)) & (~(sn - 1))) : f; -} - - (void)scaleWithFrame:(int)frame scale:(float)scale snap:(int)sn { _mverts[frame] = new OFVector3D[_numVerts]; @@ -125,7 +125,7 @@ snap(int sn, float f) } } -- (void)renderWithLight:(OFVector3D &)light +- (void)renderWithLight:(OFVector3D)light frame:(int)frame range:(int)range x:(float)x diff --git a/src/Menu.h b/src/Menu.h new file mode 100644 index 0000000..c886450 --- /dev/null +++ b/src/Menu.h @@ -0,0 +1,16 @@ +#import + +OF_ASSUME_NONNULL_BEGIN + +@class MenuItem; + +@interface Menu : OFObject +@property (readonly, nonatomic) OFString *name; +@property (readonly) OFMutableArray *items; +@property (nonatomic) int mwidth; +@property (nonatomic) int menusel; + +- (instancetype)initWithName:(OFString *)name; +@end + +OF_ASSUME_NONNULL_END diff --git a/src/Menu.m b/src/Menu.m new file mode 100644 index 0000000..930777d --- /dev/null +++ b/src/Menu.m @@ -0,0 +1,13 @@ +#import "Menu.h" + +@implementation Menu +- (instancetype)initWithName:(OFString *)name +{ + self = [super init]; + + _name = [name copy]; + _items = [[OFMutableArray alloc] init]; + + return self; +} +@end diff --git a/src/MenuItem.h b/src/MenuItem.h new file mode 100644 index 0000000..6b1990f --- /dev/null +++ b/src/MenuItem.h @@ -0,0 +1,7 @@ +#import + +@interface MenuItem : OFObject +@property (readonly, nonatomic) OFString *text, *action; + +- (instancetype)initWithText:(OFString *)text action:(OFString *)action; +@end diff --git a/src/MenuItem.m b/src/MenuItem.m new file mode 100644 index 0000000..99c09b9 --- /dev/null +++ b/src/MenuItem.m @@ -0,0 +1,31 @@ +#import "MenuItem.h" + +@implementation MenuItem +- (instancetype)initWithText:(OFString *)text action:(OFString *)action +{ + self = [super init]; + + _text = [text copy]; + _action = [action copy]; + + return self; +} + +- (OFComparisonResult)compare:(id)otherObject +{ + MenuItem *otherItem; + + if (![otherObject isKindOfClass:[MenuItem class]]) + @throw [OFInvalidArgumentException exception]; + + int x = (int)_text.longLongValue; + int y = (int)otherItem.text.longLongValue; + + if (x > y) + return OFOrderedAscending; + if (x < y) + return OFOrderedDescending; + + return OFOrderedSame; +} +@end diff --git a/src/clientextras.mm b/src/clientextras.mm index e36edd2..e4681c6 100644 --- a/src/clientextras.mm +++ b/src/clientextras.mm @@ -107,8 +107,10 @@ renderscore(dynent *d) sprintf_s(scorelines.add().s)("%d\t%s\t%d\t%s\t%s", d->frags, d->state == CS_LAGGED ? "LAG" : lag, d->ping, d->team, d->state == CS_DEAD ? name : d->name); - menumanual(0, scorelines.length() - 1, scorelines.last().s); -}; + @autoreleasepool { + menumanual(0, scorelines.length() - 1, @(scorelines.last().s)); + } +} const int maxteams = 4; char *teamname[maxteams]; @@ -141,7 +143,7 @@ renderscores() if (!demoplayback) renderscore(player1); loopv(players) if (players[i]) renderscore(players[i]); - sortmenu(0, scorelines.length()); + sortmenu(); if (m_teammode) { teamsused = 0; loopv(players) addteamscore(players[i]); @@ -152,11 +154,13 @@ renderscores() { sprintf_sd(sc)("[ %s: %d ]", teamname[j], teamscore[j]); strcat_s(teamscores, sc); - }; - menumanual(0, scorelines.length(), ""); - menumanual(0, scorelines.length() + 1, teamscores); - }; -}; + } + menumanual(0, scorelines.length(), @""); + @autoreleasepool { + menumanual(0, scorelines.length() + 1, @(teamscores)); + } + } +} // sendmap/getmap commands, should be replaced by more intuitive map downloading diff --git a/src/console.mm b/src/console.mm index fbdd5c9..9b9cd3b 100644 --- a/src/console.mm +++ b/src/console.mm @@ -85,13 +85,15 @@ renderconsole() // render buffer taking into account time & scrolling refs[nd++] = conlines[i].cref; if (nd == ndraw) break; - }; - loopj(nd) - { - draw_text(refs[j], FONTH / 3, - (FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2); - }; -}; + } + @autoreleasepool { + loopj(nd) + { + draw_text(@(refs[j]), FONTH / 3, + (FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2); + } + } +} // keymap is defined externally in keymap.cfg @@ -164,11 +166,12 @@ void history(int n) { static bool rec = false; + if (!rec && n >= 0 && n < vhistory.length()) { rec = true; execute(vhistory[vhistory.length() - n - 1]); rec = false; - }; + } } COMMAND(history, ARG_1INT) diff --git a/src/menus.mm b/src/menus.mm index 9920294..a3af707 100644 --- a/src/menus.mm +++ b/src/menus.mm @@ -2,22 +2,14 @@ #include "cube.h" -struct mitem { - char *text, *action; -}; +#include -struct gmenu { - char *name; - vector items; - int mwidth; - int menusel; -}; +#import "Menu.h" +#import "MenuItem.h" -vector menus; - -int vmenu = -1; - -ivector menustack; +static OFMutableArray *menuStack; +static OFMutableArray *menus; +static int vmenu = -1; void menuset(int menu) @@ -29,114 +21,108 @@ menuset(int menu) } void -showmenu(OFString *name_) +showmenu(OFString *name) { - @autoreleasepool { - const char *name = name_.UTF8String; - loopv(menus) if (i > 1 && strcmp(menus[i].name, name) == 0) - { + int i = 0; + for (Menu *menu in menus) { + if (i > 1 && [menu.name isEqual:name]) { menuset(i); return; } + i++; } } COMMAND(showmenu, ARG_1STR) -int -menucompare(mitem *a, mitem *b) -{ - int x = atoi(a->text); - int y = atoi(b->text); - if (x > y) - return -1; - if (x < y) - return 1; - return 0; -}; - void -sortmenu(int start, int num) +sortmenu() { - qsort(&menus[0].items[start], num, sizeof(mitem), - (int(__cdecl *)(const void *, const void *))menucompare); -}; + [menus[0].items sort]; +} void refreshservers(); bool rendermenu() { - if (vmenu < 0) { - menustack.setsize(0); - return false; - }; - if (vmenu == 1) - refreshservers(); - gmenu &m = menus[vmenu]; - sprintf_sd(title)(vmenu > 1 ? "[ %s menu ]" : "%s", m.name); - int mdisp = m.items.length(); - int w = 0; - loopi(mdisp) - { - int x = text_width(m.items[i].text); - if (x > w) - w = x; - }; - int tw = text_width(title); - if (tw > w) - w = tw; - int step = FONTH / 4 * 5; - int h = (mdisp + 2) * step; - int y = (VIRTH - h) / 2; - int x = (VIRTW - w) / 2; - blendbox(x - FONTH / 2 * 3, y - FONTH, x + w + FONTH / 2 * 3, - y + h + FONTH, true); - draw_text(title, x, y, 2); - y += FONTH * 2; - if (vmenu) { - int bh = y + m.menusel * step; - blendbox( - x - FONTH, bh - 10, x + w + FONTH, bh + FONTH + 10, false); - }; - loopj(mdisp) - { - draw_text(m.items[j].text, x, y, 2); - y += step; - }; - return true; -}; + @autoreleasepool { + if (vmenu < 0) { + [menuStack removeAllObjects]; + return false; + } + + if (vmenu == 1) + refreshservers(); + + Menu *m = menus[vmenu]; + OFString *title; + if (vmenu > 1) + title = + [OFString stringWithFormat:@"[ %@ menu ]", m.name]; + else + title = m.name; + int mdisp = m.items.count; + int w = 0; + loopi(mdisp) + { + int x = text_width(m.items[i].text); + if (x > w) + w = x; + } + int tw = text_width(title); + if (tw > w) + w = tw; + int step = FONTH / 4 * 5; + int h = (mdisp + 2) * step; + int y = (VIRTH - h) / 2; + int x = (VIRTW - w) / 2; + blendbox(x - FONTH / 2 * 3, y - FONTH, x + w + FONTH / 2 * 3, + y + h + FONTH, true); + draw_text(title, x, y, 2); + y += FONTH * 2; + if (vmenu) { + int bh = y + m.menusel * step; + blendbox(x - FONTH, bh - 10, x + w + FONTH, + bh + FONTH + 10, false); + } + loopj(mdisp) + { + draw_text(m.items[j].text, x, y, 2); + y += step; + } + return true; + } +} void newmenu(OFString *name) { - @autoreleasepool { - gmenu &menu = menus.add(); - menu.name = newstring(name.UTF8String); - menu.menusel = 0; - } + if (menus == nil) + menus = [[OFMutableArray alloc] init]; + + [menus addObject:[[Menu alloc] initWithName:name]]; } COMMAND(newmenu, ARG_1STR) void -menumanual(int m, int n, char *text) +menumanual(int m, int n, OFString *text) { - if (!n) - menus[m].items.setsize(0); - mitem &mitem = menus[m].items.add(); - mitem.text = text; - mitem.action = ""; + if (n == 0) + [menus[m].items removeAllObjects]; + + MenuItem *item = [[MenuItem alloc] initWithText:text action:@""]; + [menus[m].items addObject:item]; } void menuitem(OFString *text, OFString *action) { - @autoreleasepool { - gmenu &menu = menus.last(); - mitem &mi = menu.items.add(); - mi.text = newstring(text.UTF8String); - mi.action = - action.length > 0 ? newstring(action.UTF8String) : mi.text; - } + Menu *menu = menus.lastObject; + + MenuItem *item = + [[MenuItem alloc] initWithText:text + action:(action.length > 0 ? action : text)]; + [menu.items addObject:item]; } COMMAND(menuitem, ARG_2STR) @@ -145,18 +131,23 @@ menukey(int code, bool isdown) { if (vmenu <= 0) return false; + int menusel = menus[vmenu].menusel; if (isdown) { if (code == SDLK_ESCAPE) { menuset(-1); - if (!menustack.empty()) - menuset(menustack.pop()); + + if (menuStack.count > 0) { + menuset(menuStack.lastObject.intValue); + [menuStack removeLastObject]; + } + return true; } else if (code == SDLK_UP || code == -4) menusel--; else if (code == SDLK_DOWN || code == -5) menusel++; - int n = menus[vmenu].items.length(); + int n = menus[vmenu].items.count; if (menusel < 0) menusel = n - 1; else if (menusel >= n) @@ -164,16 +155,23 @@ menukey(int code, bool isdown) menus[vmenu].menusel = menusel; } else { if (code == SDLK_RETURN || code == -2) { - char *action = menus[vmenu].items[menusel].action; + OFString *action = menus[vmenu].items[menusel].action; if (vmenu == 1) { @autoreleasepool { connects(@(getservername(menusel))); } } - menustack.add(vmenu); + + if (menuStack == nil) + menuStack = [[OFMutableArray alloc] init]; + + [menuStack addObject:@(vmenu)]; menuset(-1); - execute(action, true); + + std::unique_ptr copy(strdup(action.UTF8String)); + execute(copy.get(), true); } } + return true; -}; +} diff --git a/src/meson.build b/src/meson.build index eb3043d..06f9bb0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -5,6 +5,8 @@ executable('client', 'KeyMapping.m', 'MD2.mm', 'MapModelInfo.m', + 'Menu.m', + 'MenuItem.m', 'client.mm', 'clientextras.mm', 'clientgame.mm', diff --git a/src/protos.h b/src/protos.h index 667f540..51e2e65 100644 --- a/src/protos.h +++ b/src/protos.h @@ -30,8 +30,8 @@ extern void processInitQueue(void); // menus extern bool rendermenu(); extern void menuset(int menu); -extern void menumanual(int m, int n, char *text); -extern void sortmenu(int start, int num); +extern void menumanual(int m, int n, OFString *text); +extern void sortmenu(); extern bool menukey(int code, bool isdown); extern void newmenu(OFString *name); @@ -143,9 +143,10 @@ extern void fatal(OFString *s, OFString *o = @""); extern void *alloc(int s); // rendertext -extern void draw_text(char *str, int left, int top, int gl_num); -extern void draw_textf(char *fstr, int left, int top, int gl_num, ...); -extern int text_width(char *str); +extern void draw_text(OFString *string, int left, int top, int gl_num); +extern void draw_textf( + OFConstantString *format, int left, int top, int gl_num, ...); +extern int text_width(OFString *string); extern void draw_envbox(int t, int fogdist); // editing diff --git a/src/renderextras.mm b/src/renderextras.mm index 978d2d0..49bd52a 100644 --- a/src/renderextras.mm +++ b/src/renderextras.mm @@ -361,18 +361,21 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater) dblend -= curtime / 3; if (dblend < 0) dblend = 0; - }; + } glEnable(GL_TEXTURE_2D); - char *command = getcurcommand(); - char *player = playerincrosshair(); - if (command) - draw_textf("> %s_", 20, 1570, 2, command); - else if (closeent[0]) - draw_text(closeent, 20, 1570, 2); - else if (player) - draw_text(player, 20, 1570, 2); + @autoreleasepool { + char *command = getcurcommand(); + char *player = playerincrosshair(); + + if (command) + draw_textf(@"> %s_", 20, 1570, 2, command); + else if (closeent[0]) + draw_text(@(closeent), 20, 1570, 2); + else if (player) + draw_text(@(player), 20, 1570, 2); + } renderscores(); if (!rendermenu()) { @@ -387,7 +390,7 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater) glColor3ub(255, 0, 0); else if (player1->health <= 50) glColor3ub(255, 128, 0); - }; + } float chsize = (float)crosshairsize; glTexCoord2d(0.0, 0.0); glVertex2f(VIRTW / 2 - chsize, VIRTH / 2 - chsize); @@ -398,7 +401,7 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater) glTexCoord2d(0.0, 1.0); glVertex2f(VIRTW / 2 - chsize, VIRTH / 2 + chsize); glEnd(); - }; + } glPopMatrix(); @@ -410,22 +413,22 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater) glPopMatrix(); glPushMatrix(); glOrtho(0, VIRTW * 3 / 2, VIRTH * 3 / 2, 0, -1, 1); - draw_textf("fps %d", 3200, 2390, 2, curfps); - draw_textf("wqd %d", 3200, 2460, 2, nquads); - draw_textf("wvt %d", 3200, 2530, 2, curvert); - draw_textf("evt %d", 3200, 2600, 2, xtraverts); - }; + draw_textf(@"fps %d", 3200, 2390, 2, curfps); + draw_textf(@"wqd %d", 3200, 2460, 2, nquads); + draw_textf(@"wvt %d", 3200, 2530, 2, curvert); + draw_textf(@"evt %d", 3200, 2600, 2, xtraverts); + } glPopMatrix(); if (player1->state == CS_ALIVE) { glPushMatrix(); glOrtho(0, VIRTW / 2, VIRTH / 2, 0, -1, 1); - draw_textf("%d", 90, 827, 2, player1->health); + draw_textf(@"%d", 90, 827, 2, player1->health); if (player1->armour) - draw_textf("%d", 390, 827, 2, player1->armour); + draw_textf(@"%d", 390, 827, 2, player1->armour); draw_textf( - "%d", 690, 827, 2, player1->ammo[player1->gunselect]); + @"%d", 690, 827, 2, player1->ammo[player1->gunselect]); glPopMatrix(); glPushMatrix(); glOrtho(0, VIRTW, VIRTH, 0, -1, 1); @@ -442,10 +445,10 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater) }; drawicon((float)(g * 64), (float)r, 1220, 1650); glPopMatrix(); - }; + } glDepthMask(GL_TRUE); glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); -}; +} diff --git a/src/rendertext.mm b/src/rendertext.mm index dec1e45..055ac51 100644 --- a/src/rendertext.mm +++ b/src/rendertext.mm @@ -100,90 +100,116 @@ short char_coords[96][4] = { }; int -text_width(char *str) +text_width(OFString *string) { - int x = 0; - for (int i = 0; str[i] != 0; i++) { - int c = str[i]; - if (c == '\t') { - x = (x + PIXELTAB) / PIXELTAB * PIXELTAB; - continue; - }; - if (c == '\f') - continue; - if (c == ' ') { - x += FONTH / 2; - continue; - }; - c -= 33; - if (c < 0 || c >= 95) - continue; - int in_width = char_coords[c][2] - char_coords[c][0]; - x += in_width + 1; + @autoreleasepool { + const char *str = string.UTF8String; + size_t len = string.UTF8StringLength; + + int x = 0; + for (int i = 0; i < len; i++) { + int c = str[i]; + if (c == '\t') { + x = (x + PIXELTAB) / PIXELTAB * PIXELTAB; + continue; + } + + if (c == '\f') + continue; + + if (c == ' ') { + x += FONTH / 2; + continue; + } + + c -= 33; + if (c < 0 || c >= 95) + continue; + + int in_width = char_coords[c][2] - char_coords[c][0]; + x += in_width + 1; + } + + return x; } - return x; } void -draw_textf(char *fstr, int left, int top, int gl_num, ...) +draw_textf(OFConstantString *format, int left, int top, int gl_num, ...) { - sprintf_sdlv(str, gl_num, fstr); - draw_text(str, left, top, gl_num); -}; + @autoreleasepool { + va_list arguments; + va_start(arguments, gl_num); + OFString *str = [[OFString alloc] initWithFormat:format + arguments:arguments]; + va_end(arguments); + draw_text(str, left, top, gl_num); + } +} void -draw_text(char *str, int left, int top, int gl_num) +draw_text(OFString *string, int left, int top, int gl_num) { - glBlendFunc(GL_ONE, GL_ONE); - glBindTexture(GL_TEXTURE_2D, gl_num); - glColor3ub(255, 255, 255); + @autoreleasepool { + glBlendFunc(GL_ONE, GL_ONE); + glBindTexture(GL_TEXTURE_2D, gl_num); + glColor3ub(255, 255, 255); - int x = left; - int y = top; + int x = left; + int y = top; - int i; - float in_left, in_top, in_right, in_bottom; - int in_width, in_height; + int i; + float in_left, in_top, in_right, in_bottom; + int in_width, in_height; - for (i = 0; str[i] != 0; i++) { - int c = str[i]; - if (c == '\t') { - x = (x - left + PIXELTAB) / PIXELTAB * PIXELTAB + left; - continue; - }; - if (c == '\f') { - glColor3ub(64, 255, 128); - continue; - }; - if (c == ' ') { - x += FONTH / 2; - continue; - }; - c -= 33; - if (c < 0 || c >= 95) - continue; + const char *str = string.UTF8String; + size_t len = string.UTF8StringLength; + for (i = 0; i < len; i++) { + int c = str[i]; - in_left = ((float)char_coords[c][0]) / 512.0f; - in_top = ((float)char_coords[c][1] + 2) / 512.0f; - in_right = ((float)char_coords[c][2]) / 512.0f; - in_bottom = ((float)char_coords[c][3] - 2) / 512.0f; + if (c == '\t') { + x = (x - left + PIXELTAB) / PIXELTAB * + PIXELTAB + + left; + continue; + } - in_width = char_coords[c][2] - char_coords[c][0]; - in_height = char_coords[c][3] - char_coords[c][1]; + if (c == '\f') { + glColor3ub(64, 255, 128); + continue; + } - glBegin(GL_QUADS); - glTexCoord2f(in_left, in_top); - glVertex2i(x, y); - glTexCoord2f(in_right, in_top); - glVertex2i(x + in_width, y); - glTexCoord2f(in_right, in_bottom); - glVertex2i(x + in_width, y + in_height); - glTexCoord2f(in_left, in_bottom); - glVertex2i(x, y + in_height); - glEnd(); + if (c == ' ') { + x += FONTH / 2; + continue; + } - xtraverts += 4; - x += in_width + 1; + c -= 33; + if (c < 0 || c >= 95) + continue; + + in_left = ((float)char_coords[c][0]) / 512.0f; + in_top = ((float)char_coords[c][1] + 2) / 512.0f; + in_right = ((float)char_coords[c][2]) / 512.0f; + in_bottom = ((float)char_coords[c][3] - 2) / 512.0f; + + in_width = char_coords[c][2] - char_coords[c][0]; + in_height = char_coords[c][3] - char_coords[c][1]; + + glBegin(GL_QUADS); + glTexCoord2f(in_left, in_top); + glVertex2i(x, y); + glTexCoord2f(in_right, in_top); + glVertex2i(x + in_width, y); + glTexCoord2f(in_right, in_bottom); + glVertex2i(x + in_width, y + in_height); + glTexCoord2f(in_left, in_bottom); + glVertex2i(x, y + in_height); + glEnd(); + + xtraverts += 4; + x += in_width + 1; + } } } diff --git a/src/serverbrowser.mm b/src/serverbrowser.mm index b6fd8a2..ce0e155 100644 --- a/src/serverbrowser.mm +++ b/src/serverbrowser.mm @@ -243,10 +243,10 @@ checkpings() sgetstr(); strcpy_s(si.sdesc, text); break; - }; - }; - }; -}; + } + } + } +} int sicompare(const serverinfo *a, const serverinfo *b) @@ -254,7 +254,7 @@ sicompare(const serverinfo *a, const serverinfo *b) return a->ping > b->ping ? 1 : (a->ping < b->ping ? -1 : strcmp(a->name, b->name)); -}; +} void refreshservers() @@ -285,11 +285,13 @@ refreshservers() si.name); } si.full[50] = 0; // cut off too long server descriptions - menumanual(1, i, si.full); + @autoreleasepool { + menumanual(1, i, @(si.full)); + } if (!--maxmenu) return; - }; -}; + } +} void servermenu()