Index: src/MD2.h ================================================================== --- src/MD2.h +++ src/MD2.h @@ -9,11 +9,11 @@ @property (copy, nonatomic) OFString *loadname; @property (nonatomic) int mdlnum; @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 y:(float)y z:(float)z Index: src/MD2.mm ================================================================== --- src/MD2.mm +++ src/MD2.mm @@ -21,10 +21,16 @@ float scale[3]; float translate[3]; char name[16]; 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; int _numTriangles; @@ -101,16 +107,10 @@ return true; } } -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]; md2_frame *cf = (md2_frame *)((char *)_frames + _frameSize * frame); float sc = 16.0f / scale; @@ -123,11 +123,11 @@ -(snap(sn, cv[1] * cf->scale[1]) + cf->translate[1]) / sc; v->z = (snap(sn, cv[2] * cf->scale[2]) + cf->translate[2]) / sc; } } -- (void)renderWithLight:(OFVector3D &)light +- (void)renderWithLight:(OFVector3D)light frame:(int)frame range:(int)range x:(float)x y:(float)y z:(float)z ADDED src/Menu.h Index: src/Menu.h ================================================================== --- /dev/null +++ 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 ADDED src/Menu.m Index: src/Menu.m ================================================================== --- /dev/null +++ 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 ADDED src/MenuItem.h Index: src/MenuItem.h ================================================================== --- /dev/null +++ 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 ADDED src/MenuItem.m Index: src/MenuItem.m ================================================================== --- /dev/null +++ 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 Index: src/clientextras.mm ================================================================== --- src/clientextras.mm +++ src/clientextras.mm @@ -105,12 +105,14 @@ sprintf_sd(lag)("%d", d->plag); sprintf_sd(name)("(%s)", d->name); 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]; int teamscore[maxteams], teamsused; string teamscores; @@ -139,11 +141,11 @@ return; scorelines.setsize(0); 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]); if (!demoplayback) addteamscore(player1); @@ -150,15 +152,17 @@ teamscores[0] = 0; loopj(teamsused) { 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 void sendmap(OFString *mapname) Index: src/console.mm ================================================================== --- src/console.mm +++ src/console.mm @@ -83,17 +83,19 @@ : lastmillis - conlines[i].outtime < 20000) { 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 static OFMutableArray *keyMappings = nil; @@ -162,15 +164,16 @@ 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) void keypress(int code, bool isdown, int cooked) Index: src/menus.mm ================================================================== --- src/menus.mm +++ src/menus.mm @@ -1,25 +1,17 @@ // menus.cpp: ingame menu system (also used for scores and serverlist) #include "cube.h" -struct mitem { - char *text, *action; -}; - -struct gmenu { - char *name; - vector items; - int mwidth; - int menusel; -}; - -vector menus; - -int vmenu = -1; - -ivector menustack; +#include + +#import "Menu.h" +#import "MenuItem.h" + +static OFMutableArray *menuStack; +static OFMutableArray *menus; +static int vmenu = -1; void menuset(int menu) { if ((vmenu = menu) >= 1) @@ -27,153 +19,159 @@ if (vmenu == 1) menus[1].menusel = 0; } 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) bool 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) menusel = 0; 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; -}; +} Index: src/meson.build ================================================================== --- src/meson.build +++ src/meson.build @@ -3,10 +3,12 @@ 'Cube.mm', 'Ident.m', 'KeyMapping.m', 'MD2.mm', 'MapModelInfo.m', + 'Menu.m', + 'MenuItem.m', 'client.mm', 'clientextras.mm', 'clientgame.mm', 'clients2c.mm', 'command.mm', Index: src/protos.h ================================================================== --- src/protos.h +++ src/protos.h @@ -28,12 +28,12 @@ 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); // serverbrowser extern void addserver(OFString *servername); @@ -141,13 +141,14 @@ // main 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 extern void cursorupdate(); extern void toggleedit(); Index: src/renderextras.mm ================================================================== --- src/renderextras.mm +++ src/renderextras.mm @@ -359,22 +359,25 @@ glVertex2i(0, VIRTH); glEnd(); 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()) { glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, 1); @@ -385,11 +388,11 @@ glColor3ub(128, 128, 128); else if (player1->health <= 25) 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); glTexCoord2d(1.0, 0.0); glVertex2f(VIRTW / 2 + chsize, VIRTH / 2 - chsize); @@ -396,11 +399,11 @@ glTexCoord2d(1.0, 1.0); glVertex2f(VIRTW / 2 + chsize, VIRTH / 2 + chsize); glTexCoord2d(0.0, 1.0); glVertex2f(VIRTW / 2 - chsize, VIRTH / 2 + chsize); glEnd(); - }; + } glPopMatrix(); glPushMatrix(); glOrtho(0, VIRTW * 4 / 3, VIRTH * 4 / 3, 0, -1, 1); @@ -408,26 +411,26 @@ if (!hidestats) { 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); glDisable(GL_BLEND); drawicon(128, 128, 20, 1650); @@ -440,12 +443,12 @@ g -= 3; r = 128; }; drawicon((float)(g * 64), (float)r, 1220, 1650); glPopMatrix(); - }; + } glDepthMask(GL_TRUE); glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); -}; +} Index: src/rendertext.mm ================================================================== --- src/rendertext.mm +++ src/rendertext.mm @@ -98,94 +98,120 @@ {270, 448, 310, 512}, //} {310, 448, 363, 512}, //~ }; int -text_width(char *str) -{ - 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; - } - return x; -} - -void -draw_textf(char *fstr, int left, int top, int gl_num, ...) -{ - sprintf_sdlv(str, gl_num, fstr); - draw_text(str, left, top, gl_num); -}; - -void -draw_text(char *str, int left, int top, int gl_num) -{ - glBlendFunc(GL_ONE, GL_ONE); - glBindTexture(GL_TEXTURE_2D, gl_num); - glColor3ub(255, 255, 255); - - int x = left; - int y = top; - - 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; - - 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; +text_width(OFString *string) +{ + @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; + } +} + +void +draw_textf(OFConstantString *format, int left, int top, int 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(OFString *string, int left, int top, int gl_num) +{ + @autoreleasepool { + glBlendFunc(GL_ONE, GL_ONE); + glBindTexture(GL_TEXTURE_2D, gl_num); + glColor3ub(255, 255, 255); + + int x = left; + int y = top; + + int i; + float in_left, in_top, in_right, in_bottom; + int in_width, in_height; + + const char *str = string.UTF8String; + size_t len = string.UTF8StringLength; + for (i = 0; i < len; 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; + + 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; + } } } // also Don's code, so goes in here too :) Index: src/serverbrowser.mm ================================================================== --- src/serverbrowser.mm +++ src/serverbrowser.mm @@ -241,22 +241,22 @@ sgetstr(); strcpy_s(si.map, text); sgetstr(); strcpy_s(si.sdesc, text); break; - }; - }; - }; -}; + } + } + } +} int 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() { checkresolver(); @@ -283,15 +283,17 @@ ? "%s [waiting for server response]" : "%s [unknown host]\t", 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() { if (pingsock == ENET_SOCKET_NULL) {