ADDED src/Cube.m Index: src/Cube.m ================================================================== --- /dev/null +++ src/Cube.m @@ -0,0 +1,388 @@ +// main.cpp: initialisation & main loop + +#include "cube.h" + +#import "DynamicEntity.h" + +OF_APPLICATION_DELEGATE(Cube) + +VARF(gamespeed, 10, 100, 1000, if (multiplayer()) gamespeed = 100); +VARP(minmillis, 0, 5, 1000); + +@implementation Cube +{ + int _width, _height; +} + ++ (Cube *)sharedInstance +{ + return (Cube *)OFApplication.sharedApplication.delegate; +} + +- (void)applicationDidFinishLaunching:(OFNotification *)notification +{ + @autoreleasepool { + bool dedicated, windowed; + int par = 0, uprate = 0, maxcl = 4; + OFString *__autoreleasing sdesc, *__autoreleasing ip; + OFString *__autoreleasing master, *__autoreleasing passwd; + + processInitQueue(); + +#define log(s) conoutf(@"init: %@", s) + log(@"sdl"); + + const OFOptionsParserOption options[] = { + { 'd', @"dedicated", 0, &dedicated, NULL }, + { 't', @"window", 0, &windowed, NULL }, + { 'w', @"width", 1, NULL, NULL }, + { 'h', @"height", 1, NULL, NULL }, + { 'u', @"upload-rate", 1, NULL, NULL }, + { 'n', @"server-desc", 1, NULL, &sdesc }, + { 'i', @"ip", 1, NULL, &ip }, + { 'm', @"master", 1, NULL, &master }, + { 'p', @"password", 1, NULL, &passwd }, + { 'c', @"max-clients", 1, NULL, NULL }, + { '\0', nil, 0, NULL, NULL } + }; + OFOptionsParser *optionsParser = + [OFOptionsParser parserWithOptions:options]; + OFUnichar option; + while ((option = [optionsParser nextOption]) != '\0') { + switch (option) { + case 'w': + _width = optionsParser.argument.intValue; + break; + case 'h': + _height = optionsParser.argument.intValue; + break; + case 'u': + uprate = optionsParser.argument.intValue; + break; + case 'c': + maxcl = optionsParser.argument.intValue; + break; + case ':': + case '=': + case '?': + conoutf(@"unknown commandline option"); + [OFApplication terminateWithStatus:1]; + } + } + + if (sdesc == nil) + sdesc = @""; + if (ip == nil) + ip = @""; + if (passwd == nil) + passwd = @""; + + _gameDataIRI = + [OFFileManager.defaultManager currentDirectoryIRI]; + _userDataIRI = + [OFFileManager.defaultManager currentDirectoryIRI]; + + [OFFileManager.defaultManager + createDirectoryAtIRI: + [_userDataIRI IRIByAppendingPathComponent:@"demos"] + createParents:true]; + [OFFileManager.defaultManager + createDirectoryAtIRI: + [_userDataIRI IRIByAppendingPathComponent:@"savegames"] + createParents:true]; + + if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | par) < 0) + fatal(@"Unable to initialize SDL"); + + initEntities(); + initPlayers(); + + log(@"net"); + if (enet_initialize() < 0) + fatal(@"Unable to initialise network module"); + + initclient(); + // never returns if dedicated + initserver(dedicated, uprate, sdesc, ip, master, passwd, maxcl); + + log(@"world"); + empty_world(7, true); + + log(@"video: sdl"); + if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) + fatal(@"Unable to initialize SDL Video"); + + if (_width == 0 || _height == 0) { + SDL_DisplayMode mode; + + if (SDL_GetDesktopDisplayMode(0, &mode) == 0) { + _width = mode.w; + _height = mode.h; + } else { + _width = 1920; + _height = 1080; + } + } + + log(@"video: mode"); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + if ((_window = SDL_CreateWindow("cube engine", + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + _width, _height, + SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | + (!windowed ? SDL_WINDOW_FULLSCREEN : 0))) == + NULL || + SDL_GL_CreateContext(_window) == NULL) + fatal(@"Unable to create OpenGL screen"); + + log(@"video: misc"); + SDL_SetWindowGrab(_window, SDL_TRUE); + SDL_SetRelativeMouseMode(SDL_TRUE); + SDL_ShowCursor(0); + + log(@"gl"); + gl_init(_width, _height); + + log(@"basetex"); + int xs, ys; + if (!installtex(2, + [_gameDataIRI + IRIByAppendingPathComponent:@"data/newchars.png"], + &xs, &ys, false) || + !installtex(3, + [_gameDataIRI IRIByAppendingPathComponent: + @"data/martin/base.png"], + &xs, &ys, false) || + !installtex(6, + [_gameDataIRI IRIByAppendingPathComponent: + @"data/martin/ball1.png"], + &xs, &ys, false) || + !installtex(7, + [_gameDataIRI IRIByAppendingPathComponent: + @"data/martin/smoke.png"], + &xs, &ys, false) || + !installtex(8, + [_gameDataIRI IRIByAppendingPathComponent: + @"data/martin/ball2.png"], + &xs, &ys, false) || + !installtex(9, + [_gameDataIRI IRIByAppendingPathComponent: + @"data/martin/ball3.png"], + &xs, &ys, false) || + !installtex(4, + [_gameDataIRI + IRIByAppendingPathComponent:@"data/explosion.jpg"], + &xs, &ys, false) || + !installtex(5, + [_gameDataIRI + IRIByAppendingPathComponent:@"data/items.png"], + &xs, &ys, false) || + !installtex(1, + [_gameDataIRI + IRIByAppendingPathComponent:@"data/crosshair.png"], + &xs, &ys, false)) + fatal(@"could not find core textures (hint: run cube " + @"from the parent of the bin directory)"); + + log(@"sound"); + initsound(); + + log(@"cfg"); + newmenu(@"frags\tpj\tping\tteam\tname"); + newmenu(@"ping\tplr\tserver"); + exec(@"data/keymap.cfg"); + exec(@"data/menus.cfg"); + exec(@"data/prefabs.cfg"); + exec(@"data/sounds.cfg"); + exec(@"servers.cfg"); + if (!execfile([_userDataIRI + IRIByAppendingPathComponent:@"config.cfg"])) + execfile([_gameDataIRI + IRIByAppendingPathComponent:@"data/defaults.cfg"]); + exec(@"autoexec.cfg"); + + log(@"localconnect"); + localconnect(); + // if this map is changed, also change depthcorrect() + changemap(@"metl3"); + + log(@"mainloop"); + } + + OFDate *past = [OFDate date]; + int ignore = 5; + for (;;) { + @autoreleasepool { + [OFRunLoop.mainRunLoop runUntilDate:past]; + + int millis = SDL_GetTicks() * gamespeed / 100; + if (millis - lastmillis > 200) + lastmillis = millis - 200; + else if (millis - lastmillis < 1) + lastmillis = millis - 1; + if (millis - lastmillis < minmillis) + SDL_Delay(minmillis - (millis - lastmillis)); + + cleardlights(); + updateworld(millis); + + if (!demoplayback) + serverslice((int)time(NULL), 0); + + static float fps = 30.0f; + fps = (1000.0f / curtime + fps * 50) / 51; + + computeraytable(player1.o.x, player1.o.y); + readdepth(_width, _height); + SDL_GL_SwapWindow(_window); + extern void updatevol(); + updatevol(); + + // cheap hack to get rid of initial sparklies, even when + // triple buffering etc. + if (_framesInMap++ < 5) { + player1.yaw += 5; + gl_drawframe(_width, _height, fps); + player1.yaw -= 5; + } + + gl_drawframe(_width, _height, fps); + + SDL_Event event; + int lasttype = 0, lastbut = 0; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + [self quit]; + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + if (_repeatsKeys || + event.key.repeat == 0) + keypress(event.key.keysym.sym, + event.key.state == + SDL_PRESSED); + break; + case SDL_TEXTINPUT: + input(@(event.text.text)); + break; + case SDL_MOUSEMOTION: + if (ignore) { + ignore--; + break; + } + mousemove(event.motion.xrel, + event.motion.yrel); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (lasttype == event.type && + lastbut == event.button.button) + // why?? get event twice without + // it + break; + + keypress(-event.button.button, + event.button.state != 0); + lasttype = event.type; + lastbut = event.button.button; + break; + } + } + } + } +} + +- (void)applicationWillTerminate:(OFNotification *)notification +{ + stop(); + disconnect(true, false); + writecfg(); + cleangl(); + cleansound(); + cleanupserver(); + SDL_ShowCursor(1); + SDL_Quit(); +} + +- (void)showMessage:(OFString *)msg +{ +#ifdef _WIN32 + MessageBoxW( + NULL, msg.UTF16String, L"cube fatal error", MB_OK | MB_SYSTEMMODAL); +#else + [OFStdOut writeString:msg]; +#endif +} + +- (void)screenshot +{ + SDL_Surface *image; + SDL_Surface *temp; + + if ((image = SDL_CreateRGBSurface(SDL_SWSURFACE, _width, _height, 24, + 0x0000FF, 0x00FF00, 0xFF0000, 0)) != NULL) { + if ((temp = SDL_CreateRGBSurface(SDL_SWSURFACE, _width, _height, + 24, 0x0000FF, 0x00FF00, 0xFF0000, 0)) != NULL) { + glReadPixels(0, 0, _width, _height, GL_RGB, + GL_UNSIGNED_BYTE, image->pixels); + + for (int idx = 0; idx < _height; idx++) { + char *dest = + (char *)temp->pixels + 3 * _width * idx; + memcpy(dest, + (char *)image->pixels + + 3 * _width * (_height - 1 - idx), + 3 * _width); + endianswap(dest, 3, _width); + } + + OFString *path = [OFString + stringWithFormat:@"screenshots/screenshot_%d.bmp", + lastmillis]; + SDL_SaveBMP(temp, + [_userDataIRI IRIByAppendingPathComponent:path] + .fileSystemRepresentation.UTF8String); + SDL_FreeSurface(temp); + } + + SDL_FreeSurface(image); + } +} + +- (void)quit +{ + writeservercfg(); + [OFApplication terminateWithStatus:0]; +} +@end + +// failure exit +void +fatal(OFConstantString *s, ...) +{ + va_list args; + va_start(args, s); + OFMutableString *msg = [[OFMutableString alloc] initWithFormat:s + arguments:args]; + va_end(args); + + [msg appendFormat:@" (%s)\n", SDL_GetError()]; + + [Cube.sharedInstance showMessage:msg]; + [OFApplication terminateWithStatus:1]; +} + +void +quit() // normal exit +{ + [Cube.sharedInstance quit]; +} +COMMAND(quit, ARG_NONE) + +void +screenshot() +{ + [Cube.sharedInstance screenshot]; +} +COMMAND(screenshot, ARG_NONE) DELETED src/Cube.mm Index: src/Cube.mm ================================================================== --- src/Cube.mm +++ /dev/null @@ -1,388 +0,0 @@ -// main.cpp: initialisation & main loop - -#include "cube.h" - -#import "DynamicEntity.h" - -OF_APPLICATION_DELEGATE(Cube) - -VARF(gamespeed, 10, 100, 1000, if (multiplayer()) gamespeed = 100); -VARP(minmillis, 0, 5, 1000); - -@implementation Cube -{ - int _width, _height; -} - -+ (Cube *)sharedInstance -{ - return (Cube *)OFApplication.sharedApplication.delegate; -} - -- (void)applicationDidFinishLaunching:(OFNotification *)notification -{ - @autoreleasepool { - bool dedicated, windowed; - int par = 0, uprate = 0, maxcl = 4; - OFString *__autoreleasing sdesc, *__autoreleasing ip; - OFString *__autoreleasing master, *__autoreleasing passwd; - - processInitQueue(); - -#define log(s) conoutf(@"init: %@", s) - log(@"sdl"); - - const OFOptionsParserOption options[] = { - { 'd', @"dedicated", 0, &dedicated, NULL }, - { 't', @"window", 0, &windowed, NULL }, - { 'w', @"width", 1, NULL, NULL }, - { 'h', @"height", 1, NULL, NULL }, - { 'u', @"upload-rate", 1, NULL, NULL }, - { 'n', @"server-desc", 1, NULL, &sdesc }, - { 'i', @"ip", 1, NULL, &ip }, - { 'm', @"master", 1, NULL, &master }, - { 'p', @"password", 1, NULL, &passwd }, - { 'c', @"max-clients", 1, NULL, NULL }, - { '\0', nil, 0, NULL, NULL } - }; - OFOptionsParser *optionsParser = - [OFOptionsParser parserWithOptions:options]; - OFUnichar option; - while ((option = [optionsParser nextOption]) != '\0') { - switch (option) { - case 'w': - _width = optionsParser.argument.intValue; - break; - case 'h': - _height = optionsParser.argument.intValue; - break; - case 'u': - uprate = optionsParser.argument.intValue; - break; - case 'c': - maxcl = optionsParser.argument.intValue; - break; - case ':': - case '=': - case '?': - conoutf(@"unknown commandline option"); - [OFApplication terminateWithStatus:1]; - } - } - - if (sdesc == nil) - sdesc = @""; - if (ip == nil) - ip = @""; - if (passwd == nil) - passwd = @""; - - _gameDataIRI = - [OFFileManager.defaultManager currentDirectoryIRI]; - _userDataIRI = - [OFFileManager.defaultManager currentDirectoryIRI]; - - [OFFileManager.defaultManager - createDirectoryAtIRI: - [_userDataIRI IRIByAppendingPathComponent:@"demos"] - createParents:true]; - [OFFileManager.defaultManager - createDirectoryAtIRI: - [_userDataIRI IRIByAppendingPathComponent:@"savegames"] - createParents:true]; - - if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | par) < 0) - fatal(@"Unable to initialize SDL"); - - initEntities(); - initPlayers(); - - log(@"net"); - if (enet_initialize() < 0) - fatal(@"Unable to initialise network module"); - - initclient(); - // never returns if dedicated - initserver(dedicated, uprate, sdesc, ip, master, passwd, maxcl); - - log(@"world"); - empty_world(7, true); - - log(@"video: sdl"); - if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) - fatal(@"Unable to initialize SDL Video"); - - if (_width == 0 || _height == 0) { - SDL_DisplayMode mode; - - if (SDL_GetDesktopDisplayMode(0, &mode) == 0) { - _width = mode.w; - _height = mode.h; - } else { - _width = 1920; - _height = 1080; - } - } - - log(@"video: mode"); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - if ((_window = SDL_CreateWindow("cube engine", - SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - _width, _height, - SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | - (!windowed ? SDL_WINDOW_FULLSCREEN : 0))) == - NULL || - SDL_GL_CreateContext(_window) == NULL) - fatal(@"Unable to create OpenGL screen"); - - log(@"video: misc"); - SDL_SetWindowGrab(_window, SDL_TRUE); - SDL_SetRelativeMouseMode(SDL_TRUE); - SDL_ShowCursor(0); - - log(@"gl"); - gl_init(_width, _height); - - log(@"basetex"); - int xs, ys; - if (!installtex(2, - [_gameDataIRI - IRIByAppendingPathComponent:@"data/newchars.png"], - &xs, &ys, false) || - !installtex(3, - [_gameDataIRI IRIByAppendingPathComponent: - @"data/martin/base.png"], - &xs, &ys, false) || - !installtex(6, - [_gameDataIRI IRIByAppendingPathComponent: - @"data/martin/ball1.png"], - &xs, &ys, false) || - !installtex(7, - [_gameDataIRI IRIByAppendingPathComponent: - @"data/martin/smoke.png"], - &xs, &ys, false) || - !installtex(8, - [_gameDataIRI IRIByAppendingPathComponent: - @"data/martin/ball2.png"], - &xs, &ys, false) || - !installtex(9, - [_gameDataIRI IRIByAppendingPathComponent: - @"data/martin/ball3.png"], - &xs, &ys, false) || - !installtex(4, - [_gameDataIRI - IRIByAppendingPathComponent:@"data/explosion.jpg"], - &xs, &ys, false) || - !installtex(5, - [_gameDataIRI - IRIByAppendingPathComponent:@"data/items.png"], - &xs, &ys, false) || - !installtex(1, - [_gameDataIRI - IRIByAppendingPathComponent:@"data/crosshair.png"], - &xs, &ys, false)) - fatal(@"could not find core textures (hint: run cube " - @"from the parent of the bin directory)"); - - log(@"sound"); - initsound(); - - log(@"cfg"); - newmenu(@"frags\tpj\tping\tteam\tname"); - newmenu(@"ping\tplr\tserver"); - exec(@"data/keymap.cfg"); - exec(@"data/menus.cfg"); - exec(@"data/prefabs.cfg"); - exec(@"data/sounds.cfg"); - exec(@"servers.cfg"); - if (!execfile([_userDataIRI - IRIByAppendingPathComponent:@"config.cfg"])) - execfile([_gameDataIRI - IRIByAppendingPathComponent:@"data/defaults.cfg"]); - exec(@"autoexec.cfg"); - - log(@"localconnect"); - localconnect(); - // if this map is changed, also change depthcorrect() - changemap(@"metl3"); - - log(@"mainloop"); - } - - OFDate *past = [OFDate date]; - int ignore = 5; - for (;;) { - @autoreleasepool { - [OFRunLoop.mainRunLoop runUntilDate:past]; - - int millis = SDL_GetTicks() * gamespeed / 100; - if (millis - lastmillis > 200) - lastmillis = millis - 200; - else if (millis - lastmillis < 1) - lastmillis = millis - 1; - if (millis - lastmillis < minmillis) - SDL_Delay(minmillis - (millis - lastmillis)); - - cleardlights(); - updateworld(millis); - - if (!demoplayback) - serverslice((int)time(NULL), 0); - - static float fps = 30.0f; - fps = (1000.0f / curtime + fps * 50) / 51; - - computeraytable(player1.o.x, player1.o.y); - readdepth(_width, _height); - SDL_GL_SwapWindow(_window); - extern void updatevol(); - updatevol(); - - // cheap hack to get rid of initial sparklies, even when - // triple buffering etc. - if (_framesInMap++ < 5) { - player1.yaw += 5; - gl_drawframe(_width, _height, fps); - player1.yaw -= 5; - } - - gl_drawframe(_width, _height, fps); - - SDL_Event event; - int lasttype = 0, lastbut = 0; - while (SDL_PollEvent(&event)) { - switch (event.type) { - case SDL_QUIT: - [self quit]; - break; - case SDL_KEYDOWN: - case SDL_KEYUP: - if (_repeatsKeys || - event.key.repeat == 0) - keypress(event.key.keysym.sym, - event.key.state == - SDL_PRESSED); - break; - case SDL_TEXTINPUT: - input(@(event.text.text)); - break; - case SDL_MOUSEMOTION: - if (ignore) { - ignore--; - break; - } - mousemove(event.motion.xrel, - event.motion.yrel); - break; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - if (lasttype == event.type && - lastbut == event.button.button) - // why?? get event twice without - // it - break; - - keypress(-event.button.button, - event.button.state != 0); - lasttype = event.type; - lastbut = event.button.button; - break; - } - } - } - } -} - -- (void)applicationWillTerminate:(OFNotification *)notification -{ - stop(); - disconnect(true, false); - writecfg(); - cleangl(); - cleansound(); - cleanupserver(); - SDL_ShowCursor(1); - SDL_Quit(); -} - -- (void)showMessage:(OFString *)msg -{ -#ifdef _WIN32 - MessageBoxW( - NULL, msg.UTF16String, L"cube fatal error", MB_OK | MB_SYSTEMMODAL); -#else - [OFStdOut writeString:msg]; -#endif -} - -- (void)screenshot -{ - SDL_Surface *image; - SDL_Surface *temp; - - if ((image = SDL_CreateRGBSurface(SDL_SWSURFACE, _width, _height, 24, - 0x0000FF, 0x00FF00, 0xFF0000, 0)) != NULL) { - if ((temp = SDL_CreateRGBSurface(SDL_SWSURFACE, _width, _height, - 24, 0x0000FF, 0x00FF00, 0xFF0000, 0)) != NULL) { - glReadPixels(0, 0, _width, _height, GL_RGB, - GL_UNSIGNED_BYTE, image->pixels); - - for (int idx = 0; idx < _height; idx++) { - char *dest = - (char *)temp->pixels + 3 * _width * idx; - memcpy(dest, - (char *)image->pixels + - 3 * _width * (_height - 1 - idx), - 3 * _width); - endianswap(dest, 3, _width); - } - - OFString *path = [OFString - stringWithFormat:@"screenshots/screenshot_%d.bmp", - lastmillis]; - SDL_SaveBMP(temp, - [_userDataIRI IRIByAppendingPathComponent:path] - .fileSystemRepresentation.UTF8String); - SDL_FreeSurface(temp); - } - - SDL_FreeSurface(image); - } -} - -- (void)quit -{ - writeservercfg(); - [OFApplication terminateWithStatus:0]; -} -@end - -// failure exit -void -fatal(OFConstantString *s, ...) -{ - va_list args; - va_start(args, s); - OFMutableString *msg = [[OFMutableString alloc] initWithFormat:s - arguments:args]; - va_end(args); - - [msg appendFormat:@" (%s)\n", SDL_GetError()]; - - [Cube.sharedInstance showMessage:msg]; - [OFApplication terminateWithStatus:1]; -} - -void -quit() // normal exit -{ - [Cube.sharedInstance quit]; -} -COMMAND(quit, ARG_NONE) - -void -screenshot() -{ - [Cube.sharedInstance screenshot]; -} -COMMAND(screenshot, ARG_NONE) ADDED src/commands.m Index: src/commands.m ================================================================== --- /dev/null +++ src/commands.m @@ -0,0 +1,574 @@ +// command.cpp: implements the parsing and execution of a tiny script language +// which is largely backwards compatible with the quake console language. + +#include "cube.h" + +#import "Alias.h" +#import "Command.h" +#import "Identifier.h" +#import "OFString+Cube.h" +#import "Variable.h" + +// contains ALL vars/commands/aliases +static OFMutableDictionary *identifiers; + +static void +cleanup(char **string) +{ + free(*string); +} + +void +alias(OFString *name, OFString *action) +{ + Alias *alias = identifiers[name]; + + if (alias == nil) { + alias = [Alias aliasWithName:name action:action persisted:true]; + + if (identifiers == nil) + identifiers = [[OFMutableDictionary alloc] init]; + + identifiers[name] = alias; + } else { + if ([alias isKindOfClass:Alias.class]) + alias.action = action; + else + conoutf( + @"cannot redefine builtin %@ with an alias", name); + } +} +COMMAND(alias, ARG_2STR) + +int +variable(OFString *name, int min, int cur, int max, int *storage, + void (*function)(), bool persisted) +{ + Variable *variable = [Variable variableWithName:name + min:min + max:max + storage:storage + function:function + persisted:persisted]; + + if (identifiers == nil) + identifiers = [[OFMutableDictionary alloc] init]; + + identifiers[name] = variable; + + return cur; +} + +void +setvar(OFString *name, int i) +{ + *[identifiers[name] storage] = i; +} + +int +getvar(OFString *name) +{ + return *[identifiers[name] storage]; +} + +bool +identexists(OFString *name) +{ + return (identifiers[name] != nil); +} + +OFString * +getalias(OFString *name) +{ + Alias *alias = identifiers[name]; + + if ([alias isKindOfClass:Alias.class]) + return alias.action; + + return nil; +} + +bool +addcommand(OFString *name, void (*function)(), int argumentsTypes) +{ + Command *command = [Command commandWithName:name + function:function + argumentsTypes:argumentsTypes]; + + if (identifiers == nil) + identifiers = [[OFMutableDictionary alloc] init]; + + identifiers[name] = command; + + return false; +} + +// parse any nested set of () or [] +static char * +parseexp(char **p, int right) +{ + int left = *(*p)++; + char *word = *p; + for (int brak = 1; brak;) { + int c = *(*p)++; + if (c == '\r') + *(*p - 1) = ' '; // hack + if (c == left) + brak++; + else if (c == right) + brak--; + else if (!c) { + (*p)--; + conoutf(@"missing \"%c\"", right); + return NULL; + } + } + char *s = strndup(word, *p - word - 1); + if (left == '(') { + OFString *t; + @try { + t = [OFString + stringWithFormat:@"%d", execute(@(s), true)]; + } @finally { + free(s); + } + s = strdup(t.UTF8String); + } + return s; +} + +// parse single argument, including expressions +static char * +parseword(char **p) +{ + (*p) += strspn(*p, " \t\r"); + if ((*p)[0] == '/' && (*p)[1] == '/') + *p += strcspn(*p, "\n\0"); + if (**p == '\"') { + (*p)++; + char *word = *p; + *p += strcspn(*p, "\"\r\n\0"); + char *s = strndup(word, *p - word); + if (**p == '\"') + (*p)++; + return s; + } + if (**p == '(') + return parseexp(p, ')'); + if (**p == '[') + return parseexp(p, ']'); + char *word = *p; + *p += strcspn(*p, "; \t\r\n\0"); + if (*p - word == 0) + return NULL; + return strndup(word, *p - word); +} + +// find value of ident referenced with $ in exp +OFString * +lookup(OFString *n) +{ + __kindof Identifier *identifier = identifiers[[n substringFromIndex:1]]; + + if ([identifier isKindOfClass:Variable.class]) { + return [OFString stringWithFormat:@"%d", *[identifier storage]]; + } else if ([identifier isKindOfClass:Alias.class]) + return [identifier action]; + + conoutf(@"unknown alias lookup: %@", [n substringFromIndex:1]); + return n; +} + +int +executeIdentifier(__kindof Identifier *identifier, + OFArray *arguments, bool isDown) +{ + if (identifier == nil) { + @try { + return [arguments[0] intValueWithBase:0]; + } @catch (OFInvalidFormatException *e) { + conoutf(@"unknown command: %@", arguments[0]); + return 0; + } @catch (OFOutOfRangeException *e) { + conoutf(@"invalid value: %@", arguments[0]); + return 0; + } + } + + if ([identifier isKindOfClass:Command.class]) + // game defined commands use very ad-hoc function signature, + // and just call it + return [identifier callWithArguments:arguments isDown:isDown]; + + if ([identifier isKindOfClass:Variable.class]) { + if (!isDown) + return 0; + + // game defined variables + if (arguments.count < 2 || arguments[1].length == 0) + [identifier printValue]; + else + [identifier + setValue:[arguments[1] cube_intValueWithBase:0]]; + } + + if ([identifier isKindOfClass:Alias.class]) { + // alias, also used as functions and (global) variables + for (int i = 1; i < arguments.count; i++) { + // set any arguments as (global) arg values so + // functions can access them + OFString *t = [OFString stringWithFormat:@"arg%d", i]; + alias(t, arguments[i]); + } + + return execute([identifier action], isDown); + } + + return 0; +} + +// all evaluation happens here, recursively +int +execute(OFString *string, bool isDown) +{ + char *copy __attribute__((__cleanup__(cleanup))) = + strdup(string.UTF8String); + char *p = copy; + const int MAXWORDS = 25; // limit, remove + OFString *w[MAXWORDS]; + int val = 0; + for (bool cont = true; cont;) { + // for each ; seperated statement + int numargs = MAXWORDS; + loopi(MAXWORDS) + { + // collect all argument values + w[i] = @""; + if (i > numargs) + continue; + // parse and evaluate exps + char *s = parseword(&p); + if (!s) { + numargs = i; + s = strdup(""); + } + @try { + if (*s == '$') + // substitute variables + w[i] = lookup(@(s)); + else + w[i] = @(s); + } @finally { + free(s); + } + } + + p += strcspn(p, ";\n\0"); + // more statements if this isn't the end of the string + cont = *p++ != 0; + OFString *c = w[0]; + // strip irc-style command prefix + if ([c hasPrefix:@"/"]) { + c = [c substringFromIndex:1]; + w[0] = c; + } + // empty statement + if (c.length == 0) + continue; + + val = executeIdentifier(identifiers[c], + [OFArray arrayWithObjects:w count:numargs], isDown); + } + + return val; +} + +// tab-completion of all identifiers + +int completesize = 0, completeidx = 0; + +void +resetcomplete() +{ + completesize = 0; +} + +void +complete(OFMutableString *s) +{ + if (![s hasPrefix:@"/"]) + [s insertString:@"/" atIndex:0]; + + if (s.length == 1) + return; + + if (!completesize) { + completesize = s.length - 1; + completeidx = 0; + } + + __block int idx = 0; + [identifiers enumerateKeysAndObjectsUsingBlock:^( + OFString *name, Identifier *identifier, bool *stop) { + if (strncmp(identifier.name.UTF8String, s.UTF8String + 1, + completesize) == 0 && + idx++ == completeidx) + [s replaceCharactersInRange:OFMakeRange(1, s.length - 1) + withString:identifier.name]; + }]; + + completeidx++; + + if (completeidx >= idx) + completeidx = 0; +} + +bool +execfile(OFIRI *cfgfile) +{ + OFString *command; + @try { + command = [OFString stringWithContentsOfIRI:cfgfile]; + } @catch (OFOpenItemFailedException *e) { + return false; + } @catch (OFReadFailedException *e) { + return false; + } + + execute(command, true); + return true; +} + +void +exec(OFString *cfgfile) +{ + if (!execfile([Cube.sharedInstance.userDataIRI + IRIByAppendingPathComponent:cfgfile]) && + !execfile([Cube.sharedInstance.gameDataIRI + IRIByAppendingPathComponent:cfgfile])) + conoutf(@"could not read \"%@\"", cfgfile); +} + +void +writecfg() +{ + OFStream *stream; + @try { + OFIRI *IRI = [Cube.sharedInstance.userDataIRI + IRIByAppendingPathComponent:@"config.cfg"]; + stream = [[OFIRIHandler handlerForIRI:IRI] openItemAtIRI:IRI + mode:@"w"]; + } @catch (id e) { + return; + } + + [stream writeString:@"// automatically written on exit, do not modify\n" + @"// delete this file to have defaults.cfg " + @"overwrite these settings\n" + @"// modify settings in game, or put settings in " + @"autoexec.cfg to override anything\n" + @"\n"]; + writeclientinfo(stream); + [stream writeString:@"\n"]; + + [identifiers enumerateKeysAndObjectsUsingBlock:^( + OFString *name, __kindof Identifier *identifier, bool *stop) { + if (![identifier isKindOfClass:Variable.class] || + ![identifier persisted]) + return; + + [stream writeFormat:@"%@ %d\n", identifier.name, + *[identifier storage]]; + }]; + [stream writeString:@"\n"]; + + writebinds(stream); + [stream writeString:@"\n"]; + + [identifiers enumerateKeysAndObjectsUsingBlock:^( + OFString *name, __kindof Identifier *identifier, bool *stop) { + if (![identifier isKindOfClass:Alias.class] || + [identifier.name hasPrefix:@"nextmap_"]) + return; + + [stream writeFormat:@"alias \"%@\" [%@]\n", identifier.name, + [identifier action]]; + }]; + + [stream close]; +} + +COMMAND(writecfg, ARG_NONE) + +// below the commands that implement a small imperative language. thanks to the +// semantics of () and [] expressions, any control construct can be defined +// trivially. + +void +intset(OFString *name, int v) +{ + alias(name, [OFString stringWithFormat:@"%d", v]); +} + +void +ifthen(OFString *cond, OFString *thenp, OFString *elsep) +{ + execute((![cond hasPrefix:@"0"] ? thenp : elsep), true); +} + +void +loopa(OFString *times, OFString *body) +{ + int t = times.cube_intValue; + + loopi(t) + { + intset(@"i", i); + execute(body, true); + } +} + +void +whilea(OFString *cond, OFString *body) +{ + while (execute(cond, true)) + execute(body, true); +} + +void +onrelease(bool on, OFString *body) +{ + if (!on) + execute(body, true); +} + +void +concat(OFString *s) +{ + alias(@"s", s); +} + +void +concatword(OFString *s) +{ + concat([s stringByReplacingOccurrencesOfString:@" " withString:@""]); +} + +int +listlen(OFString *a_) +{ + const char *a = a_.UTF8String; + + if (!*a) + return 0; + + int n = 0; + while (*a) + if (*a++ == ' ') + n++; + + return n + 1; +} + +void +at(OFString *s_, OFString *pos) +{ + int n = pos.cube_intValue; + char *copy __attribute__((__cleanup__(cleanup))) = + strdup(s_.UTF8String); + char *s = copy; + loopi(n) + { + s += strcspn(s, " \0"); + s += strspn(s, " "); + } + s[strcspn(s, " \0")] = 0; + concat(@(s)); +} + +COMMANDN(loop, loopa, ARG_2STR) +COMMANDN(while, whilea, ARG_2STR) +COMMANDN(if, ifthen, ARG_3STR) +COMMAND(onrelease, ARG_DWN1) +COMMAND(exec, ARG_1STR) +COMMAND(concat, ARG_VARI) +COMMAND(concatword, ARG_VARI) +COMMAND(at, ARG_2STR) +COMMAND(listlen, ARG_1EST) + +int +add(int a, int b) +{ + return a + b; +} +COMMANDN(+, add, ARG_2EXP) + +int +mul(int a, int b) +{ + return a * b; +} +COMMANDN(*, mul, ARG_2EXP) + +int +sub(int a, int b) +{ + return a - b; +} +COMMANDN(-, sub, ARG_2EXP) + +int +divi(int a, int b) +{ + return b ? a / b : 0; +} +COMMANDN(div, divi, ARG_2EXP) + +int +mod(int a, int b) +{ + return b ? a % b : 0; +} +COMMAND(mod, ARG_2EXP) + +int +equal(int a, int b) +{ + return (int)(a == b); +} +COMMANDN(=, equal, ARG_2EXP) + +int +lt(int a, int b) +{ + return (int)(a < b); +} +COMMANDN(<, lt, ARG_2EXP) + +int +gt(int a, int b) +{ + return (int)(a > b); +} +COMMANDN(>, gt, ARG_2EXP) + +int +strcmpa(OFString *a, OFString *b) +{ + return [a isEqual:b]; +} +COMMANDN(strcmp, strcmpa, ARG_2EST) + +int +rndn(int a) +{ + return a > 0 ? rnd(a) : 0; +} +COMMANDN(rnd, rndn, ARG_1EXP) + +int +explastmillis() +{ + return lastmillis; +} +COMMANDN(millis, explastmillis, ARG_1EXP) DELETED src/commands.mm Index: src/commands.mm ================================================================== --- src/commands.mm +++ /dev/null @@ -1,564 +0,0 @@ -// command.cpp: implements the parsing and execution of a tiny script language -// which is largely backwards compatible with the quake console language. - -#include "cube.h" - -#include - -#import "Alias.h" -#import "Command.h" -#import "Identifier.h" -#import "OFString+Cube.h" -#import "Variable.h" - -// contains ALL vars/commands/aliases -static OFMutableDictionary *identifiers; - -void -alias(OFString *name, OFString *action) -{ - Alias *alias = identifiers[name]; - - if (alias == nil) { - alias = [Alias aliasWithName:name action:action persisted:true]; - - if (identifiers == nil) - identifiers = [[OFMutableDictionary alloc] init]; - - identifiers[name] = alias; - } else { - if ([alias isKindOfClass:Alias.class]) - alias.action = action; - else - conoutf( - @"cannot redefine builtin %@ with an alias", name); - } -} -COMMAND(alias, ARG_2STR) - -int -variable(OFString *name, int min, int cur, int max, int *storage, - void (*function)(), bool persisted) -{ - Variable *variable = [Variable variableWithName:name - min:min - max:max - storage:storage - function:function - persisted:persisted]; - - if (identifiers == nil) - identifiers = [[OFMutableDictionary alloc] init]; - - identifiers[name] = variable; - - return cur; -} - -void -setvar(OFString *name, int i) -{ - *[identifiers[name] storage] = i; -} - -int -getvar(OFString *name) -{ - return *[identifiers[name] storage]; -} - -bool -identexists(OFString *name) -{ - return (identifiers[name] != nil); -} - -OFString * -getalias(OFString *name) -{ - Alias *alias = identifiers[name]; - - if ([alias isKindOfClass:Alias.class]) - return alias.action; - - return nil; -} - -bool -addcommand(OFString *name, void (*function)(), int argumentsTypes) -{ - Command *command = [Command commandWithName:name - function:function - argumentsTypes:argumentsTypes]; - - if (identifiers == nil) - identifiers = [[OFMutableDictionary alloc] init]; - - identifiers[name] = command; - - return false; -} - -// parse any nested set of () or [] -char * -parseexp(char *&p, int right) -{ - int left = *p++; - char *word = p; - for (int brak = 1; brak;) { - int c = *p++; - if (c == '\r') - *(p - 1) = ' '; // hack - if (c == left) - brak++; - else if (c == right) - brak--; - else if (!c) { - p--; - conoutf(@"missing \"%c\"", right); - return NULL; - } - } - char *s = strndup(word, p - word - 1); - if (left == '(') { - OFString *t; - @try { - t = [OFString - stringWithFormat:@"%d", execute(@(s), true)]; - } @finally { - free(s); - } - s = strdup(t.UTF8String); - } - return s; -} - -// parse single argument, including expressions -char * -parseword(char *&p) -{ - p += strspn(p, " \t\r"); - if (p[0] == '/' && p[1] == '/') - p += strcspn(p, "\n\0"); - if (*p == '\"') { - p++; - char *word = p; - p += strcspn(p, "\"\r\n\0"); - char *s = strndup(word, p - word); - if (*p == '\"') - p++; - return s; - } - if (*p == '(') - return parseexp(p, ')'); - if (*p == '[') - return parseexp(p, ']'); - char *word = p; - p += strcspn(p, "; \t\r\n\0"); - if (p - word == 0) - return NULL; - return strndup(word, p - word); -} - -// find value of ident referenced with $ in exp -OFString * -lookup(OFString *n) -{ - __kindof Identifier *identifier = identifiers[[n substringFromIndex:1]]; - - if ([identifier isKindOfClass:Variable.class]) { - return [OFString stringWithFormat:@"%d", *[identifier storage]]; - } else if ([identifier isKindOfClass:Alias.class]) - return [identifier action]; - - conoutf(@"unknown alias lookup: %@", [n substringFromIndex:1]); - return n; -} - -int -executeIdentifier(__kindof Identifier *identifier, - OFArray *arguments, bool isDown) -{ - if (identifier == nil) { - @try { - return [arguments[0] intValueWithBase:0]; - } @catch (OFInvalidFormatException *e) { - conoutf(@"unknown command: %@", arguments[0]); - return 0; - } @catch (OFOutOfRangeException *e) { - conoutf(@"invalid value: %@", arguments[0]); - return 0; - } - } - - if ([identifier isKindOfClass:Command.class]) - // game defined commands use very ad-hoc function signature, - // and just call it - return [identifier callWithArguments:arguments isDown:isDown]; - - if ([identifier isKindOfClass:Variable.class]) { - if (!isDown) - return 0; - - // game defined variables - if (arguments.count < 2 || arguments[1].length == 0) - [identifier printValue]; - else - [identifier - setValue:[arguments[1] cube_intValueWithBase:0]]; - } - - if ([identifier isKindOfClass:Alias.class]) { - // alias, also used as functions and (global) variables - for (int i = 1; i < arguments.count; i++) { - // set any arguments as (global) arg values so - // functions can access them - OFString *t = [OFString stringWithFormat:@"arg%d", i]; - alias(t, arguments[i]); - } - - return execute([identifier action], isDown); - } - - return 0; -} - -// all evaluation happens here, recursively -int -execute(OFString *string, bool isDown) -{ - std::unique_ptr copy(strdup(string.UTF8String)); - char *p = copy.get(); - const int MAXWORDS = 25; // limit, remove - OFString *w[MAXWORDS]; - int val = 0; - for (bool cont = true; cont;) { - // for each ; seperated statement - int numargs = MAXWORDS; - loopi(MAXWORDS) - { - // collect all argument values - w[i] = @""; - if (i > numargs) - continue; - // parse and evaluate exps - char *s = parseword(p); - if (!s) { - numargs = i; - s = strdup(""); - } - @try { - if (*s == '$') - // substitute variables - w[i] = lookup(@(s)); - else - w[i] = @(s); - } @finally { - free(s); - } - } - - p += strcspn(p, ";\n\0"); - // more statements if this isn't the end of the string - cont = *p++ != 0; - OFString *c = w[0]; - // strip irc-style command prefix - if ([c hasPrefix:@"/"]) { - c = [c substringFromIndex:1]; - w[0] = c; - } - // empty statement - if (c.length == 0) - continue; - - val = executeIdentifier(identifiers[c], - [OFArray arrayWithObjects:w count:numargs], isDown); - } - - return val; -} - -// tab-completion of all identifiers - -int completesize = 0, completeidx = 0; - -void -resetcomplete() -{ - completesize = 0; -} - -void -complete(OFMutableString *s) -{ - if (![s hasPrefix:@"/"]) - [s insertString:@"/" atIndex:0]; - - if (s.length == 1) - return; - - if (!completesize) { - completesize = s.length - 1; - completeidx = 0; - } - - __block int idx = 0; - [identifiers enumerateKeysAndObjectsUsingBlock:^( - OFString *name, Identifier *identifier, bool *stop) { - if (strncmp(identifier.name.UTF8String, s.UTF8String + 1, - completesize) == 0 && - idx++ == completeidx) - [s replaceCharactersInRange:OFMakeRange(1, s.length - 1) - withString:identifier.name]; - }]; - - completeidx++; - - if (completeidx >= idx) - completeidx = 0; -} - -bool -execfile(OFIRI *cfgfile) -{ - OFString *command; - @try { - command = [OFString stringWithContentsOfIRI:cfgfile]; - } @catch (OFOpenItemFailedException *e) { - return false; - } @catch (OFReadFailedException *e) { - return false; - } - - execute(command, true); - return true; -} - -void -exec(OFString *cfgfile) -{ - if (!execfile([Cube.sharedInstance.userDataIRI - IRIByAppendingPathComponent:cfgfile]) && - !execfile([Cube.sharedInstance.gameDataIRI - IRIByAppendingPathComponent:cfgfile])) - conoutf(@"could not read \"%@\"", cfgfile); -} - -void -writecfg() -{ - OFStream *stream; - @try { - OFIRI *IRI = [Cube.sharedInstance.userDataIRI - IRIByAppendingPathComponent:@"config.cfg"]; - stream = [[OFIRIHandler handlerForIRI:IRI] openItemAtIRI:IRI - mode:@"w"]; - } @catch (id e) { - return; - } - - [stream writeString:@"// automatically written on exit, do not modify\n" - @"// delete this file to have defaults.cfg " - @"overwrite these settings\n" - @"// modify settings in game, or put settings in " - @"autoexec.cfg to override anything\n" - @"\n"]; - writeclientinfo(stream); - [stream writeString:@"\n"]; - - [identifiers enumerateKeysAndObjectsUsingBlock:^( - OFString *name, __kindof Identifier *identifier, bool *stop) { - if (![identifier isKindOfClass:Variable.class] || - ![identifier persisted]) - return; - - [stream writeFormat:@"%@ %d\n", identifier.name, - *[identifier storage]]; - }]; - [stream writeString:@"\n"]; - - writebinds(stream); - [stream writeString:@"\n"]; - - [identifiers enumerateKeysAndObjectsUsingBlock:^( - OFString *name, __kindof Identifier *identifier, bool *stop) { - if (![identifier isKindOfClass:Alias.class] || - [identifier.name hasPrefix:@"nextmap_"]) - return; - - [stream writeFormat:@"alias \"%@\" [%@]\n", identifier.name, - [identifier action]]; - }]; - - [stream close]; -} - -COMMAND(writecfg, ARG_NONE) - -// below the commands that implement a small imperative language. thanks to the -// semantics of () and [] expressions, any control construct can be defined -// trivially. - -void -intset(OFString *name, int v) -{ - alias(name, [OFString stringWithFormat:@"%d", v]); -} - -void -ifthen(OFString *cond, OFString *thenp, OFString *elsep) -{ - execute((![cond hasPrefix:@"0"] ? thenp : elsep), true); -} - -void -loopa(OFString *times, OFString *body) -{ - int t = times.cube_intValue; - - loopi(t) - { - intset(@"i", i); - execute(body, true); - } -} - -void -whilea(OFString *cond, OFString *body) -{ - while (execute(cond, true)) - execute(body, true); -} - -void -onrelease(bool on, OFString *body) -{ - if (!on) - execute(body, true); -} - -void -concat(OFString *s) -{ - alias(@"s", s); -} - -void -concatword(OFString *s) -{ - concat([s stringByReplacingOccurrencesOfString:@" " withString:@""]); -} - -int -listlen(OFString *a_) -{ - const char *a = a_.UTF8String; - - if (!*a) - return 0; - - int n = 0; - while (*a) - if (*a++ == ' ') - n++; - - return n + 1; -} - -void -at(OFString *s_, OFString *pos) -{ - int n = pos.cube_intValue; - std::unique_ptr copy(strdup(s_.UTF8String)); - char *s = copy.get(); - loopi(n) s += strspn(s += strcspn(s, " \0"), " "); - s[strcspn(s, " \0")] = 0; - concat(@(s)); -} - -COMMANDN(loop, loopa, ARG_2STR) -COMMANDN(while, whilea, ARG_2STR) -COMMANDN(if, ifthen, ARG_3STR) -COMMAND(onrelease, ARG_DWN1) -COMMAND(exec, ARG_1STR) -COMMAND(concat, ARG_VARI) -COMMAND(concatword, ARG_VARI) -COMMAND(at, ARG_2STR) -COMMAND(listlen, ARG_1EST) - -int -add(int a, int b) -{ - return a + b; -} -COMMANDN(+, add, ARG_2EXP) - -int -mul(int a, int b) -{ - return a * b; -} -COMMANDN(*, mul, ARG_2EXP) - -int -sub(int a, int b) -{ - return a - b; -} -COMMANDN(-, sub, ARG_2EXP) - -int -divi(int a, int b) -{ - return b ? a / b : 0; -} -COMMANDN(div, divi, ARG_2EXP) - -int -mod(int a, int b) -{ - return b ? a % b : 0; -} -COMMAND(mod, ARG_2EXP) - -int -equal(int a, int b) -{ - return (int)(a == b); -} -COMMANDN(=, equal, ARG_2EXP) - -int -lt(int a, int b) -{ - return (int)(a < b); -} -COMMANDN(<, lt, ARG_2EXP) - -int -gt(int a, int b) -{ - return (int)(a > b); -} -COMMANDN(>, gt, ARG_2EXP) - -int -strcmpa(OFString *a, OFString *b) -{ - return [a isEqual:b]; -} -COMMANDN(strcmp, strcmpa, ARG_2EST) - -int -rndn(int a) -{ - return a > 0 ? rnd(a) : 0; -} -COMMANDN(rnd, rndn, ARG_1EXP) - -int -explastmillis() -{ - return lastmillis; -} -COMMANDN(millis, explastmillis, ARG_1EXP) Index: src/editing.m ================================================================== --- src/editing.m +++ src/editing.m @@ -27,19 +27,19 @@ } int selh = 0; bool selset = false; -#define loopselxy(b) \ - { \ - makeundo(); \ - loop(x, sel->xs) loop(y, sel->ys) \ - { \ +#define loopselxy(b) \ + { \ + makeundo(); \ + loop(x, sel->xs) loop(y, sel->ys) \ + { \ struct sqr *s = S(sel->x + x, sel->y + y); \ - b; \ - } \ - remip(sel, 0); \ + b; \ + } \ + remip(sel, 0); \ } int cx, cy, ch; int curedittex[] = { -1, -1, -1 }; @@ -259,12 +259,12 @@ void makeundo() { if (undos == nil) - undos = - [[OFMutableData alloc] initWithItemSize:sizeof(struct block *)]; + undos = [[OFMutableData alloc] + initWithItemSize:sizeof(struct block *)]; struct block *copy = blockcopy(&sel); [undos addItem:©]; pruneundos(undomegs << 20); } Index: src/entities.m ================================================================== --- src/entities.m +++ src/entities.m @@ -28,12 +28,12 @@ { ents = [[OFMutableArray alloc] init]; } static void -renderent(Entity *e, OFString *mdlname, float z, float yaw, int frame/* = 0*/, - int numf/* = 1*/, int basetime/* = 0*/, float speed/* = 10.0f*/) +renderent(Entity *e, OFString *mdlname, float z, float yaw, int frame /* = 0*/, + int numf /* = 1*/, int basetime /* = 0*/, float speed /* = 10.0f*/) { rendermodel(mdlname, frame, numf, 0, 1.1f, OFMakeVector3D(e.x, z + S(e.x, e.y)->floor, e.y), yaw, 0, false, 1.0f, speed, 0, basetime); } @@ -65,11 +65,11 @@ continue; renderent(e, entmdlnames[e.type - I_SHELLS], (float)(1 + sin(lastmillis / 100.0 + e.x + e.y) / 20), - lastmillis / 10.0f, 0,1,0,10.0f); + lastmillis / 10.0f, 0, 1, 0, 10.0f); } else { switch (e.attr2) { case 1: case 3: continue; @@ -83,11 +83,11 @@ sin(lastmillis / 100.0 + e.x + e.y) / 20), lastmillis / (e.attr2 ? 1.0f : 10.0f), - 0, 1, 0, 10.0f); + 0, 1, 0, 10.0f); break; case 4: renderent(e, @"switch2", 3, (float)e.attr3 * 90, Index: src/meson.build ================================================================== --- src/meson.build +++ src/meson.build @@ -2,11 +2,11 @@ [ 'Alias.m', 'Client.m', 'Command.m', 'ConsoleLine.m', - 'Cube.mm', + 'Cube.m', 'DynamicEntity.m', 'Entity.m', 'Identifier.m', 'KeyMapping.m', 'MD2.m', @@ -23,33 +23,33 @@ 'Variable.m', 'clients.m', 'clientextras.m', 'clientgame.m', 'clients2c.m', - 'commands.mm', + 'commands.m', 'console.m', 'editing.m', 'entities.m', 'init.mm', 'menus.m', 'monster.m', - 'physics.mm', - 'rendercubes.mm', - 'renderextras.mm', + 'physics.m', + 'rendercubes.m', + 'renderextras.m', 'rendergl.mm', 'rendermd2.mm', 'renderparticles.mm', 'rendertext.mm', 'rndmap.mm', 'savegamedemo.mm', - 'server.mm', + 'server.m', 'serverbrowser.mm', - 'serverms.mm', - 'serverutil.mm', - 'sound.mm', - 'tools.mm', - 'weapon.mm', + 'serverms.m', + 'serverutil.m', + 'sound.m', + 'tools.m', + 'weapon.m', 'world.mm', 'worldio.mm', 'worldlight.mm', 'worldocull.mm', 'worldrender.mm', @@ -69,19 +69,19 @@ executable('server', [ 'Client.m', 'ServerEntity.m', - 'server.mm', - 'serverms.mm', - 'serverutil.mm', - 'tools.mm', + 'server.m', + 'serverms.m', + 'serverutil.m', + 'tools.m', ], - objcpp_args: ['-DSTANDALONE'], + objc_args: ['-DSTANDALONE'], dependencies: [ objfw_dep, sdl_dep ], include_directories: [enet_includes], link_args: server_link_args, link_with: [enet], win_subsystem: 'console') ADDED src/physics.m Index: src/physics.m ================================================================== --- /dev/null +++ src/physics.m @@ -0,0 +1,419 @@ +// physics.cpp: no physics books were hurt nor consulted in the construction of +// this code. All physics computations and constants were invented on the fly +// and simply tweaked until they "felt right", and have no basis in reality. +// Collision detection is simplistic but very robust (uses discrete steps at +// fixed fps). + +#include "cube.h" + +#import "DynamicEntity.h" +#import "Entity.h" +#import "MapModelInfo.h" + +// collide with player or monster +static bool +plcollide( + DynamicEntity *d, DynamicEntity *o, float *headspace, float *hi, float *lo) +{ + if (o.state != CS_ALIVE) + return true; + const float r = o.radius + d.radius; + if (fabs(o.o.x - d.o.x) < r && fabs(o.o.y - d.o.y) < r) { + if (d.o.z - d.eyeheight < o.o.z - o.eyeheight) { + if (o.o.z - o.eyeheight < *hi) + *hi = o.o.z - o.eyeheight - 1; + } else if (o.o.z + o.aboveeye > *lo) + *lo = o.o.z + o.aboveeye + 1; + + if (fabs(o.o.z - d.o.z) < o.aboveeye + d.eyeheight) + return false; + if (d.monsterstate) + return false; // hack + *headspace = d.o.z - o.o.z - o.aboveeye - d.eyeheight; + if (*headspace < 0) + *headspace = 10; + } + return true; +} + +// recursively collide with a mipmapped corner cube +static bool +cornertest(int mip, int x, int y, int dx, int dy, int *bx, int *by, int *bs) +{ + struct sqr *w = wmip[mip]; + int sz = ssize >> mip; + bool stest = + SOLID(SWS(w, x + dx, y, sz)) && SOLID(SWS(w, x, y + dy, sz)); + mip++; + x /= 2; + y /= 2; + if (SWS(wmip[mip], x, y, ssize >> mip)->type == CORNER) { + *bx = x << mip; + *by = y << mip; + *bs = 1 << mip; + return cornertest(mip, x, y, dx, dy, bx, by, bs); + } + return stest; +} + +// collide with a mapmodel +static void +mmcollide(DynamicEntity *d, float *hi, float *lo) +{ + for (Entity *e in ents) { + if (e.type != MAPMODEL) + continue; + + MapModelInfo *mmi = getmminfo(e.attr2); + if (mmi == nil || !mmi.h) + continue; + + const float r = mmi.rad + d.radius; + if (fabs(e.x - d.o.x) < r && fabs(e.y - d.o.y) < r) { + float mmz = + (float)(S(e.x, e.y)->floor + mmi.zoff + e.attr3); + + if (d.o.z - d.eyeheight < mmz) { + if (mmz < *hi) + *hi = mmz; + } else if (mmz + mmi.h > *lo) + *lo = mmz + mmi.h; + } + } +} + +// all collision happens here +// spawn is a dirty side effect used in spawning +// drop & rise are supplied by the physics below to indicate gravity/push for +// current mini-timestep + +bool +collide(DynamicEntity *d, bool spawn, float drop, float rise) +{ + // figure out integer cube rectangle this entity covers in map + const float fx1 = d.o.x - d.radius; + const float fy1 = d.o.y - d.radius; + const float fx2 = d.o.x + d.radius; + const float fy2 = d.o.y + d.radius; + const int x1 = fast_f2nat(fx1); + const int y1 = fast_f2nat(fy1); + const int x2 = fast_f2nat(fx2); + const int y2 = fast_f2nat(fy2); + float hi = 127, lo = -128; + // big monsters are afraid of heights, unless angry :) + float minfloor = (d.monsterstate && !spawn && d.health > 100) + ? d.o.z - d.eyeheight - 4.5f + : -1000.0f; + + for (int x = x1; x <= x2; x++) + for (int y = y1; y <= y2; y++) { + // collide with map + if (OUTBORD(x, y)) + return false; + struct sqr *s = S(x, y); + float ceil = s->ceil; + float floor = s->floor; + switch (s->type) { + case SOLID: + return false; + + case CORNER: { + int bx = x, by = y, bs = 1; + if (x == x1 && y == y1 && + cornertest( + 0, x, y, -1, -1, &bx, &by, &bs) && + fx1 - bx + fy1 - by <= bs || + x == x2 && y == y1 && + cornertest( + 0, x, y, 1, -1, &bx, &by, &bs) && + fx2 - bx >= fy1 - by || + x == x1 && y == y2 && + cornertest( + 0, x, y, -1, 1, &bx, &by, &bs) && + fx1 - bx <= fy2 - by || + x == x2 && y == y2 && + cornertest( + 0, x, y, 1, 1, &bx, &by, &bs) && + fx2 - bx + fy2 - by >= bs) + return false; + break; + } + + case FHF: // FIXME: too simplistic collision with + // slopes, makes it feels like tiny stairs + floor -= (s->vdelta + S(x + 1, y)->vdelta + + S(x, y + 1)->vdelta + + S(x + 1, y + 1)->vdelta) / + 16.0f; + break; + + case CHF: + ceil += (s->vdelta + S(x + 1, y)->vdelta + + S(x, y + 1)->vdelta + + S(x + 1, y + 1)->vdelta) / + 16.0f; + } + if (ceil < hi) + hi = ceil; + if (floor > lo) + lo = floor; + if (floor < minfloor) + return false; + } + + if (hi - lo < d.eyeheight + d.aboveeye) + return false; + + float headspace = 10; + for (id player in players) { + if (player == [OFNull null] || player == d) + continue; + if (!plcollide(d, player, &headspace, &hi, &lo)) + return false; + } + if (d != player1) + if (!plcollide(d, player1, &headspace, &hi, &lo)) + return false; + // this loop can be a performance bottleneck with many monster on a slow + // cpu, should replace with a blockmap but seems mostly fast enough + for (DynamicEntity *monster in getmonsters()) + if (!vreject(d.o, monster.o, 7.0f) && d != monster && + !plcollide(d, monster, &headspace, &hi, &lo)) + return false; + headspace -= 0.01f; + + mmcollide(d, &hi, &lo); // collide with map models + + if (spawn) { + // just drop to floor (sideeffect) + d.o = OFMakeVector3D(d.o.x, d.o.y, lo + d.eyeheight); + d.onfloor = true; + } else { + const float space = d.o.z - d.eyeheight - lo; + if (space < 0) { + if (space > -0.01) + // stick on step + d.o = OFMakeVector3D( + d.o.x, d.o.y, lo + d.eyeheight); + else if (space > -1.26f) + // rise thru stair + d.o = + OFMakeVector3D(d.o.x, d.o.y, d.o.z + rise); + else + return false; + } else + // gravity + d.o = OFMakeVector3D(d.o.x, d.o.y, + d.o.z - min(min(drop, space), headspace)); + + const float space2 = hi - (d.o.z + d.aboveeye); + if (space2 < 0) { + if (space2 < -0.1) + return false; // hack alert! + // glue to ceiling + d.o = OFMakeVector3D(d.o.x, d.o.y, hi - d.aboveeye); + // cancel out jumping velocity + d.vel = OFMakeVector3D(d.vel.x, d.vel.y, 0); + } + + d.onfloor = d.o.z - d.eyeheight - lo < 0.001f; + } + return true; +} + +float +rad(float x) +{ + return x * 3.14159f / 180; +} + +VARP(maxroll, 0, 3, 20); + +int physicsfraction = 0, physicsrepeat = 0; +const int MINFRAMETIME = 20; // physics always simulated at 50fps or better + +void +physicsframe() // optimally schedule physics frames inside the graphics frames +{ + if (curtime >= MINFRAMETIME) { + int faketime = curtime + physicsfraction; + physicsrepeat = faketime / MINFRAMETIME; + physicsfraction = faketime - physicsrepeat * MINFRAMETIME; + } else { + physicsrepeat = 1; + } +} + +// main physics routine, moves a player/monster for a curtime step +// moveres indicated the physics precision (which is lower for monsters and +// multiplayer prediction) local is false for multiplayer prediction + +static void +moveplayer4(DynamicEntity *pl, int moveres, bool local, int curtime) +{ + const bool water = hdr.waterlevel > pl.o.z - 0.5f; + const bool floating = (editmode && local) || pl.state == CS_EDITING; + + OFVector3D d; // vector of direction we ideally want to move in + + d.x = (float)(pl.move * cos(rad(pl.yaw - 90))); + d.y = (float)(pl.move * sin(rad(pl.yaw - 90))); + d.z = 0; + + if (floating || water) { + d.x *= (float)cos(rad(pl.pitch)); + d.y *= (float)cos(rad(pl.pitch)); + d.z = (float)(pl.move * sin(rad(pl.pitch))); + } + + d.x += (float)(pl.strafe * cos(rad(pl.yaw - 180))); + d.y += (float)(pl.strafe * sin(rad(pl.yaw - 180))); + + const float speed = curtime / (water ? 2000.0f : 1000.0f) * pl.maxspeed; + const float friction = + water ? 20.0f : (pl.onfloor || floating ? 6.0f : 30.0f); + + const float fpsfric = friction / curtime * 20.0f; + + // slowly apply friction and direction to + // velocity, gives a smooth movement + vmul(pl.vel, fpsfric - 1); + vadd(pl.vel, d); + vdiv(pl.vel, fpsfric); + d = pl.vel; + vmul(d, speed); // d is now frametime based velocity vector + + pl.blocked = false; + pl.moving = true; + + if (floating) { + // just apply velocity + vadd(pl.o, d); + if (pl.jumpnext) { + pl.jumpnext = false; + pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 2); + } + } else { + // apply velocity with collision + if (pl.onfloor || water) { + if (pl.jumpnext) { + pl.jumpnext = false; + // physics impulse upwards + pl.vel = + OFMakeVector3D(pl.vel.x, pl.vel.y, 1.7); + // dampen velocity change even harder, gives + // correct water feel + if (water) + pl.vel = OFMakeVector3D(pl.vel.x / 8, + pl.vel.y / 8, pl.vel.z); + if (local) + playsoundc(S_JUMP); + else if (pl.monsterstate) { + OFVector3D loc = pl.o; + playsound(S_JUMP, &loc); + } + } else if (pl.timeinair > 800) { + // if we land after long time must have been a + // high jump, make thud sound + if (local) + playsoundc(S_LAND); + else if (pl.monsterstate) { + OFVector3D loc = pl.o; + playsound(S_LAND, &loc); + } + } + pl.timeinair = 0; + } else + pl.timeinair += curtime; + + const float gravity = 20; + const float f = 1.0f / moveres; + // incorrect, but works fine + float dropf = ((gravity - 1) + pl.timeinair / 15.0f); + // float slowly down in water + if (water) { + dropf = 5; + pl.timeinair = 0; + } + // at high fps, gravity kicks in too fast + const float drop = dropf * curtime / gravity / 100 / moveres; + // extra smoothness when lifting up stairs + const float rise = speed / moveres / 1.2f; + + loopi(moveres) // discrete steps collision detection & sliding + { + // try move forward + pl.o = OFMakeVector3D(pl.o.x + f * d.x, + pl.o.y + f * d.y, pl.o.z + f * d.z); + if (collide(pl, false, drop, rise)) + continue; + // player stuck, try slide along y axis + pl.blocked = true; + pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z); + if (collide(pl, false, drop, rise)) { + d.x = 0; + continue; + } + // still stuck, try x axis + pl.o = OFMakeVector3D( + pl.o.x + f * d.x, pl.o.y - f * d.y, pl.o.z); + if (collide(pl, false, drop, rise)) { + d.y = 0; + continue; + } + // try just dropping down + pl.moving = false; + pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z); + if (collide(pl, false, drop, rise)) { + d.y = d.x = 0; + continue; + } + pl.o = OFMakeVector3D(pl.o.x, pl.o.y, pl.o.z - f * d.z); + break; + } + } + + // detect wether player is outside map, used for skipping zbuffer clear + // mostly + + if (pl.o.x < 0 || pl.o.x >= ssize || pl.o.y < 0 || pl.o.y > ssize) + pl.outsidemap = true; + else { + struct sqr *s = S((int)pl.o.x, (int)pl.o.y); + pl.outsidemap = SOLID(s) || + pl.o.z < s->floor - (s->type == FHF ? s->vdelta / 4 : 0) || + pl.o.z > s->ceil + (s->type == CHF ? s->vdelta / 4 : 0); + } + + // automatically apply smooth roll when strafing + + if (pl.strafe == 0) + pl.roll = pl.roll / (1 + (float)sqrt((float)curtime) / 25); + else { + pl.roll += pl.strafe * curtime / -30.0f; + if (pl.roll > maxroll) + pl.roll = (float)maxroll; + if (pl.roll < -maxroll) + pl.roll = (float)-maxroll; + } + + // play sounds on water transitions + + if (!pl.inwater && water) { + OFVector3D loc = pl.o; + playsound(S_SPLASH2, &loc); + pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 0); + } else if (pl.inwater && !water) { + OFVector3D loc = pl.o; + playsound(S_SPLASH1, &loc); + } + pl.inwater = water; +} + +void +moveplayer(DynamicEntity *pl, int moveres, bool local) +{ + loopi(physicsrepeat) moveplayer4(pl, moveres, local, + i ? curtime / physicsrepeat + : curtime - curtime / physicsrepeat * (physicsrepeat - 1)); +} DELETED src/physics.mm Index: src/physics.mm ================================================================== --- src/physics.mm +++ /dev/null @@ -1,417 +0,0 @@ -// physics.cpp: no physics books were hurt nor consulted in the construction of -// this code. All physics computations and constants were invented on the fly -// and simply tweaked until they "felt right", and have no basis in reality. -// Collision detection is simplistic but very robust (uses discrete steps at -// fixed fps). - -#include "cube.h" - -#import "DynamicEntity.h" -#import "Entity.h" -#import "MapModelInfo.h" - -// collide with player or monster -bool -plcollide( - DynamicEntity *d, DynamicEntity *o, float &headspace, float &hi, float &lo) -{ - if (o.state != CS_ALIVE) - return true; - const float r = o.radius + d.radius; - if (fabs(o.o.x - d.o.x) < r && fabs(o.o.y - d.o.y) < r) { - if (d.o.z - d.eyeheight < o.o.z - o.eyeheight) { - if (o.o.z - o.eyeheight < hi) - hi = o.o.z - o.eyeheight - 1; - } else if (o.o.z + o.aboveeye > lo) - lo = o.o.z + o.aboveeye + 1; - - if (fabs(o.o.z - d.o.z) < o.aboveeye + d.eyeheight) - return false; - if (d.monsterstate) - return false; // hack - headspace = d.o.z - o.o.z - o.aboveeye - d.eyeheight; - if (headspace < 0) - headspace = 10; - } - return true; -} - -bool -cornertest(int mip, int x, int y, int dx, int dy, int &bx, int &by, - int &bs) // recursively collide with a mipmapped corner cube -{ - sqr *w = wmip[mip]; - int sz = ssize >> mip; - bool stest = - SOLID(SWS(w, x + dx, y, sz)) && SOLID(SWS(w, x, y + dy, sz)); - mip++; - x /= 2; - y /= 2; - if (SWS(wmip[mip], x, y, ssize >> mip)->type == CORNER) { - bx = x << mip; - by = y << mip; - bs = 1 << mip; - return cornertest(mip, x, y, dx, dy, bx, by, bs); - } - return stest; -} - -void -mmcollide(DynamicEntity *d, float &hi, float &lo) // collide with a mapmodel -{ - for (Entity *e in ents) { - if (e.type != MAPMODEL) - continue; - - MapModelInfo *mmi = getmminfo(e.attr2); - if (mmi == nil || !mmi.h) - continue; - - const float r = mmi.rad + d.radius; - if (fabs(e.x - d.o.x) < r && fabs(e.y - d.o.y) < r) { - float mmz = - (float)(S(e.x, e.y)->floor + mmi.zoff + e.attr3); - - if (d.o.z - d.eyeheight < mmz) { - if (mmz < hi) - hi = mmz; - } else if (mmz + mmi.h > lo) - lo = mmz + mmi.h; - } - } -} - -// all collision happens here -// spawn is a dirty side effect used in spawning -// drop & rise are supplied by the physics below to indicate gravity/push for -// current mini-timestep - -bool -collide(DynamicEntity *d, bool spawn, float drop, float rise) -{ - // figure out integer cube rectangle this entity covers in map - const float fx1 = d.o.x - d.radius; - const float fy1 = d.o.y - d.radius; - const float fx2 = d.o.x + d.radius; - const float fy2 = d.o.y + d.radius; - const int x1 = fast_f2nat(fx1); - const int y1 = fast_f2nat(fy1); - const int x2 = fast_f2nat(fx2); - const int y2 = fast_f2nat(fy2); - float hi = 127, lo = -128; - // big monsters are afraid of heights, unless angry :) - float minfloor = (d.monsterstate && !spawn && d.health > 100) - ? d.o.z - d.eyeheight - 4.5f - : -1000.0f; - - for (int x = x1; x <= x2; x++) - for (int y = y1; y <= y2; y++) { - // collide with map - if (OUTBORD(x, y)) - return false; - sqr *s = S(x, y); - float ceil = s->ceil; - float floor = s->floor; - switch (s->type) { - case SOLID: - return false; - - case CORNER: { - int bx = x, by = y, bs = 1; - if (x == x1 && y == y1 && - cornertest( - 0, x, y, -1, -1, bx, by, bs) && - fx1 - bx + fy1 - by <= bs || - x == x2 && y == y1 && - cornertest( - 0, x, y, 1, -1, bx, by, bs) && - fx2 - bx >= fy1 - by || - x == x1 && y == y2 && - cornertest( - 0, x, y, -1, 1, bx, by, bs) && - fx1 - bx <= fy2 - by || - x == x2 && y == y2 && - cornertest(0, x, y, 1, 1, bx, by, bs) && - fx2 - bx + fy2 - by >= bs) - return false; - break; - } - - case FHF: // FIXME: too simplistic collision with - // slopes, makes it feels like tiny stairs - floor -= (s->vdelta + S(x + 1, y)->vdelta + - S(x, y + 1)->vdelta + - S(x + 1, y + 1)->vdelta) / - 16.0f; - break; - - case CHF: - ceil += (s->vdelta + S(x + 1, y)->vdelta + - S(x, y + 1)->vdelta + - S(x + 1, y + 1)->vdelta) / - 16.0f; - } - if (ceil < hi) - hi = ceil; - if (floor > lo) - lo = floor; - if (floor < minfloor) - return false; - } - - if (hi - lo < d.eyeheight + d.aboveeye) - return false; - - float headspace = 10; - for (id player in players) { - if (player == [OFNull null] || player == d) - continue; - if (!plcollide(d, player, headspace, hi, lo)) - return false; - } - if (d != player1) - if (!plcollide(d, player1, headspace, hi, lo)) - return false; - // this loop can be a performance bottleneck with many monster on a slow - // cpu, should replace with a blockmap but seems mostly fast enough - for (DynamicEntity *monster in getmonsters()) - if (!vreject(d.o, monster.o, 7.0f) && d != monster && - !plcollide(d, monster, headspace, hi, lo)) - return false; - headspace -= 0.01f; - - mmcollide(d, hi, lo); // collide with map models - - if (spawn) { - // just drop to floor (sideeffect) - d.o = OFMakeVector3D(d.o.x, d.o.y, lo + d.eyeheight); - d.onfloor = true; - } else { - const float space = d.o.z - d.eyeheight - lo; - if (space < 0) { - if (space > -0.01) - // stick on step - d.o = OFMakeVector3D( - d.o.x, d.o.y, lo + d.eyeheight); - else if (space > -1.26f) - // rise thru stair - d.o = - OFMakeVector3D(d.o.x, d.o.y, d.o.z + rise); - else - return false; - } else - // gravity - d.o = OFMakeVector3D(d.o.x, d.o.y, - d.o.z - min(min(drop, space), headspace)); - - const float space2 = hi - (d.o.z + d.aboveeye); - if (space2 < 0) { - if (space2 < -0.1) - return false; // hack alert! - // glue to ceiling - d.o = OFMakeVector3D(d.o.x, d.o.y, hi - d.aboveeye); - // cancel out jumping velocity - d.vel = OFMakeVector3D(d.vel.x, d.vel.y, 0); - } - - d.onfloor = d.o.z - d.eyeheight - lo < 0.001f; - } - return true; -} - -float -rad(float x) -{ - return x * 3.14159f / 180; -} - -VARP(maxroll, 0, 3, 20); - -int physicsfraction = 0, physicsrepeat = 0; -const int MINFRAMETIME = 20; // physics always simulated at 50fps or better - -void -physicsframe() // optimally schedule physics frames inside the graphics frames -{ - if (curtime >= MINFRAMETIME) { - int faketime = curtime + physicsfraction; - physicsrepeat = faketime / MINFRAMETIME; - physicsfraction = faketime - physicsrepeat * MINFRAMETIME; - } else { - physicsrepeat = 1; - } -} - -// main physics routine, moves a player/monster for a curtime step -// moveres indicated the physics precision (which is lower for monsters and -// multiplayer prediction) local is false for multiplayer prediction - -void -moveplayer(DynamicEntity *pl, int moveres, bool local, int curtime) -{ - const bool water = hdr.waterlevel > pl.o.z - 0.5f; - const bool floating = (editmode && local) || pl.state == CS_EDITING; - - OFVector3D d; // vector of direction we ideally want to move in - - d.x = (float)(pl.move * cos(rad(pl.yaw - 90))); - d.y = (float)(pl.move * sin(rad(pl.yaw - 90))); - d.z = 0; - - if (floating || water) { - d.x *= (float)cos(rad(pl.pitch)); - d.y *= (float)cos(rad(pl.pitch)); - d.z = (float)(pl.move * sin(rad(pl.pitch))); - } - - d.x += (float)(pl.strafe * cos(rad(pl.yaw - 180))); - d.y += (float)(pl.strafe * sin(rad(pl.yaw - 180))); - - const float speed = curtime / (water ? 2000.0f : 1000.0f) * pl.maxspeed; - const float friction = - water ? 20.0f : (pl.onfloor || floating ? 6.0f : 30.0f); - - const float fpsfric = friction / curtime * 20.0f; - - // slowly apply friction and direction to - // velocity, gives a smooth movement - vmul(pl.vel, fpsfric - 1); - vadd(pl.vel, d); - vdiv(pl.vel, fpsfric); - d = pl.vel; - vmul(d, speed); // d is now frametime based velocity vector - - pl.blocked = false; - pl.moving = true; - - if (floating) { - // just apply velocity - vadd(pl.o, d); - if (pl.jumpnext) { - pl.jumpnext = false; - pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 2); - } - } else { - // apply velocity with collision - if (pl.onfloor || water) { - if (pl.jumpnext) { - pl.jumpnext = false; - // physics impulse upwards - pl.vel = - OFMakeVector3D(pl.vel.x, pl.vel.y, 1.7); - // dampen velocity change even harder, gives - // correct water feel - if (water) - pl.vel = OFMakeVector3D(pl.vel.x / 8, - pl.vel.y / 8, pl.vel.z); - if (local) - playsoundc(S_JUMP); - else if (pl.monsterstate) { - OFVector3D loc = pl.o; - playsound(S_JUMP, &loc); - } - } else if (pl.timeinair > 800) { - // if we land after long time must have been a - // high jump, make thud sound - if (local) - playsoundc(S_LAND); - else if (pl.monsterstate) { - OFVector3D loc = pl.o; - playsound(S_LAND, &loc); - } - } - pl.timeinair = 0; - } else - pl.timeinair += curtime; - - const float gravity = 20; - const float f = 1.0f / moveres; - // incorrect, but works fine - float dropf = ((gravity - 1) + pl.timeinair / 15.0f); - // float slowly down in water - if (water) { - dropf = 5; - pl.timeinair = 0; - } - // at high fps, gravity kicks in too fast - const float drop = dropf * curtime / gravity / 100 / moveres; - // extra smoothness when lifting up stairs - const float rise = speed / moveres / 1.2f; - - loopi(moveres) // discrete steps collision detection & sliding - { - // try move forward - pl.o = OFMakeVector3D(pl.o.x + f * d.x, - pl.o.y + f * d.y, pl.o.z + f * d.z); - if (collide(pl, false, drop, rise)) - continue; - // player stuck, try slide along y axis - pl.blocked = true; - pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z); - if (collide(pl, false, drop, rise)) { - d.x = 0; - continue; - } - // still stuck, try x axis - pl.o = OFMakeVector3D( - pl.o.x + f * d.x, pl.o.y - f * d.y, pl.o.z); - if (collide(pl, false, drop, rise)) { - d.y = 0; - continue; - } - // try just dropping down - pl.moving = false; - pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z); - if (collide(pl, false, drop, rise)) { - d.y = d.x = 0; - continue; - } - pl.o = OFMakeVector3D(pl.o.x, pl.o.y, pl.o.z - f * d.z); - break; - } - } - - // detect wether player is outside map, used for skipping zbuffer clear - // mostly - - if (pl.o.x < 0 || pl.o.x >= ssize || pl.o.y < 0 || pl.o.y > ssize) - pl.outsidemap = true; - else { - sqr *s = S((int)pl.o.x, (int)pl.o.y); - pl.outsidemap = SOLID(s) || - pl.o.z < s->floor - (s->type == FHF ? s->vdelta / 4 : 0) || - pl.o.z > s->ceil + (s->type == CHF ? s->vdelta / 4 : 0); - } - - // automatically apply smooth roll when strafing - - if (pl.strafe == 0) - pl.roll = pl.roll / (1 + (float)sqrt((float)curtime) / 25); - else { - pl.roll += pl.strafe * curtime / -30.0f; - if (pl.roll > maxroll) - pl.roll = (float)maxroll; - if (pl.roll < -maxroll) - pl.roll = (float)-maxroll; - } - - // play sounds on water transitions - - if (!pl.inwater && water) { - OFVector3D loc = pl.o; - playsound(S_SPLASH2, &loc); - pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 0); - } else if (pl.inwater && !water) { - OFVector3D loc = pl.o; - playsound(S_SPLASH1, &loc); - } - pl.inwater = water; -} - -void -moveplayer(DynamicEntity *pl, int moveres, bool local) -{ - loopi(physicsrepeat) moveplayer(pl, moveres, local, - i ? curtime / physicsrepeat - : curtime - curtime / physicsrepeat * (physicsrepeat - 1)); -} ADDED src/rendercubes.m Index: src/rendercubes.m ================================================================== --- /dev/null +++ src/rendercubes.m @@ -0,0 +1,430 @@ +// rendercubes.cpp: sits in between worldrender.cpp and rendergl.cpp and fills +// the vertex array for different cube surfaces. + +#include "cube.h" + +static struct vertex *verts = NULL; +int curvert; +static int curmaxverts = 10000; + +void +setarraypointers() +{ + glVertexPointer(3, GL_FLOAT, sizeof(struct vertex), &verts[0].x); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(struct vertex), &verts[0].r); + glTexCoordPointer(2, GL_FLOAT, sizeof(struct vertex), &verts[0].u); +} + +void +reallocv() +{ + verts = + OFResizeMemory(verts, (curmaxverts *= 2), sizeof(struct vertex)); + curmaxverts -= 10; + setarraypointers(); +} + +// generating the actual vertices is done dynamically every frame and sits at +// the leaves of all these functions, and are part of the cpu bottleneck on +// really slow machines, hence the macros. + +#define vertcheck() \ + { \ + if (curvert >= curmaxverts) \ + reallocv(); \ + } + +#define vertf(v1, v2, v3, ls, t1, t2) \ + { \ + struct vertex *v = &verts[curvert++]; \ + v->u = t1; \ + v->v = t2; \ + v->x = v1; \ + v->y = v2; \ + v->z = v3; \ + v->r = ls->r; \ + v->g = ls->g; \ + v->b = ls->b; \ + v->a = 255; \ + } + +#define vert(v1, v2, v3, ls, t1, t2) \ + { \ + vertf((float)(v1), (float)(v2), (float)(v3), ls, t1, t2); \ + } + +int nquads; +const float TEXTURESCALE = 32.0f; +bool floorstrip = false, deltastrip = false; +int oh, oy, ox, ogltex; // the o* vars are used by the stripification +int ol3r, ol3g, ol3b, ol4r, ol4g, ol4b; +int firstindex; +bool showm = false; + +void +showmip() +{ + showm = !showm; +} +COMMAND(showmip, ARG_NONE) + +void +mipstats(int a, int b, int c) +{ + if (showm) + conoutf(@"1x1/2x2/4x4: %d / %d / %d", a, b, c); +} + +#define stripend() \ + { \ + if (floorstrip || deltastrip) { \ + addstrip(ogltex, firstindex, curvert - firstindex); \ + floorstrip = deltastrip = false; \ + } \ + } +void +finishstrips() +{ + stripend(); +} + +static struct sqr sbright, sdark; +VAR(lighterror, 1, 8, 100); + +// floor/ceil quads +void +render_flat(int wtex, int x, int y, int size, int h, struct sqr *l1, + struct sqr *l2, struct sqr *l3, struct sqr *l4, bool isceil) +{ + vertcheck(); + if (showm) { + l3 = l1 = &sbright; + l4 = l2 = &sdark; + } + + int sx, sy; + int gltex = lookuptexture(wtex, &sx, &sy); + float xf = TEXTURESCALE / sx; + float yf = TEXTURESCALE / sy; + float xs = size * xf; + float ys = size * yf; + float xo = xf * x; + float yo = yf * y; + + bool first = !floorstrip || y != oy + size || ogltex != gltex || + h != oh || x != ox; + + if (first) // start strip here + { + stripend(); + firstindex = curvert; + ogltex = gltex; + oh = h; + ox = x; + floorstrip = true; + if (isceil) { + vert(x + size, h, y, l2, xo + xs, yo); + vert(x, h, y, l1, xo, yo); + } else { + vert(x, h, y, l1, xo, yo); + vert(x + size, h, y, l2, xo + xs, yo); + } + ol3r = l1->r; + ol3g = l1->g; + ol3b = l1->b; + ol4r = l2->r; + ol4g = l2->g; + ol4b = l2->b; + } else // continue strip + { + int lighterr = lighterror * 2; + if ((abs(ol3r - l3->r) < lighterr && + abs(ol4r - l4->r) < lighterr // skip vertices if light + // values are close enough + && abs(ol3g - l3->g) < lighterr && + abs(ol4g - l4->g) < lighterr && + abs(ol3b - l3->b) < lighterr && + abs(ol4b - l4->b) < lighterr) || + !wtex) { + curvert -= 2; + nquads--; + } else { + uchar *p3 = (uchar *)(&verts[curvert - 1].r); + ol3r = p3[0]; + ol3g = p3[1]; + ol3b = p3[2]; + uchar *p4 = (uchar *)(&verts[curvert - 2].r); + ol4r = p4[0]; + ol4g = p4[1]; + ol4b = p4[2]; + } + } + + if (isceil) { + vert(x + size, h, y + size, l3, xo + xs, yo + ys); + vert(x, h, y + size, l4, xo, yo + ys); + } else { + vert(x, h, y + size, l4, xo, yo + ys); + vert(x + size, h, y + size, l3, xo + xs, yo + ys); + } + + oy = y; + nquads++; +} + +// floor/ceil quads on a slope +void +render_flatdelta(int wtex, int x, int y, int size, float h1, float h2, float h3, + float h4, struct sqr *l1, struct sqr *l2, struct sqr *l3, struct sqr *l4, + bool isceil) +{ + vertcheck(); + if (showm) { + l3 = l1 = &sbright; + l4 = l2 = &sdark; + } + + int sx, sy; + int gltex = lookuptexture(wtex, &sx, &sy); + float xf = TEXTURESCALE / sx; + float yf = TEXTURESCALE / sy; + float xs = size * xf; + float ys = size * yf; + float xo = xf * x; + float yo = yf * y; + + bool first = + !deltastrip || y != oy + size || ogltex != gltex || x != ox; + + if (first) { + stripend(); + firstindex = curvert; + ogltex = gltex; + ox = x; + deltastrip = true; + if (isceil) { + vertf((float)x + size, h2, (float)y, l2, xo + xs, yo); + vertf((float)x, h1, (float)y, l1, xo, yo); + } else { + vertf((float)x, h1, (float)y, l1, xo, yo); + vertf((float)x + size, h2, (float)y, l2, xo + xs, yo); + } + ol3r = l1->r; + ol3g = l1->g; + ol3b = l1->b; + ol4r = l2->r; + ol4g = l2->g; + ol4b = l2->b; + } + + if (isceil) { + vertf( + (float)x + size, h3, (float)y + size, l3, xo + xs, yo + ys); + vertf((float)x, h4, (float)y + size, l4, xo, yo + ys); + } else { + vertf((float)x, h4, (float)y + size, l4, xo, yo + ys); + vertf( + (float)x + size, h3, (float)y + size, l3, xo + xs, yo + ys); + } + + oy = y; + nquads++; +} + +// floor/ceil tris on a corner cube +void +render_2tris(struct sqr *h, struct sqr *s, int x1, int y1, int x2, int y2, + int x3, int y3, struct sqr *l1, struct sqr *l2, struct sqr *l3) +{ + stripend(); + vertcheck(); + + int sx, sy; + int gltex = lookuptexture(h->ftex, &sx, &sy); + float xf = TEXTURESCALE / sx; + float yf = TEXTURESCALE / sy; + + vertf((float)x1, h->floor, (float)y1, l1, xf * x1, yf * y1); + vertf((float)x2, h->floor, (float)y2, l2, xf * x2, yf * y2); + vertf((float)x3, h->floor, (float)y3, l3, xf * x3, yf * y3); + addstrip(gltex, curvert - 3, 3); + + gltex = lookuptexture(h->ctex, &sx, &sy); + xf = TEXTURESCALE / sx; + yf = TEXTURESCALE / sy; + + vertf((float)x3, h->ceil, (float)y3, l3, xf * x3, yf * y3); + vertf((float)x2, h->ceil, (float)y2, l2, xf * x2, yf * y2); + vertf((float)x1, h->ceil, (float)y1, l1, xf * x1, yf * y1); + addstrip(gltex, curvert - 3, 3); + nquads++; +} + +void +render_tris(int x, int y, int size, bool topleft, struct sqr *h1, + struct sqr *h2, struct sqr *s, struct sqr *t, struct sqr *u, struct sqr *v) +{ + if (topleft) { + if (h1) + render_2tris(h1, s, x + size, y + size, x, y + size, x, + y, u, v, s); + if (h2) + render_2tris(h2, s, x, y, x + size, y, x + size, + y + size, s, t, v); + } else { + if (h1) + render_2tris( + h1, s, x, y, x + size, y, x, y + size, s, t, u); + if (h2) + render_2tris(h2, s, x + size, y, x + size, y + size, x, + y + size, t, u, v); + } +} + +void +render_square(int wtex, float floor1, float floor2, float ceil1, float ceil2, + int x1, int y1, int x2, int y2, int size, struct sqr *l1, struct sqr *l2, + bool flip) // wall quads +{ + stripend(); + vertcheck(); + if (showm) { + l1 = &sbright; + l2 = &sdark; + } + + int sx, sy; + int gltex = lookuptexture(wtex, &sx, &sy); + float xf = TEXTURESCALE / sx; + float yf = TEXTURESCALE / sy; + float xs = size * xf; + float xo = xf * (x1 == x2 ? min(y1, y2) : min(x1, x2)); + + if (!flip) { + vertf((float)x2, ceil2, (float)y2, l2, xo + xs, -yf * ceil2); + vertf((float)x1, ceil1, (float)y1, l1, xo, -yf * ceil1); + vertf((float)x2, floor2, (float)y2, l2, xo + xs, -floor2 * yf); + vertf((float)x1, floor1, (float)y1, l1, xo, -floor1 * yf); + } else { + vertf((float)x1, ceil1, (float)y1, l1, xo, -yf * ceil1); + vertf((float)x2, ceil2, (float)y2, l2, xo + xs, -yf * ceil2); + vertf((float)x1, floor1, (float)y1, l1, xo, -floor1 * yf); + vertf((float)x2, floor2, (float)y2, l2, xo + xs, -floor2 * yf); + } + + nquads++; + addstrip(gltex, curvert - 4, 4); +} + +int wx1, wy1, wx2, wy2; + +VAR(watersubdiv, 1, 4, 64); +VARF(waterlevel, -128, -128, 127, + if (!noteditmode()) hdr.waterlevel = waterlevel); + +static inline void +vertw(int v1, float v2, int v3, struct sqr *c, float t1, float t2, float t) +{ + vertcheck(); + vertf((float)v1, v2 - (float)sin(v1 * v3 * 0.1 + t) * 0.2f, (float)v3, + c, t1, t2); +} + +inline float +dx(float x) +{ + return x + (float)sin(x * 2 + lastmillis / 1000.0f) * 0.04f; +} +inline float +dy(float x) +{ + return x + (float)sin(x * 2 + lastmillis / 900.0f + PI / 5) * 0.05f; +} + +// renders water for bounding rect area that contains water... simple but very +// inefficient + +int +renderwater(float hf) +{ + if (wx1 < 0) + return nquads; + + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_SRC_COLOR); + int sx, sy; + glBindTexture(GL_TEXTURE_2D, lookuptexture(DEFAULT_LIQUID, &sx, &sy)); + + wx1 &= ~(watersubdiv - 1); + wy1 &= ~(watersubdiv - 1); + + float xf = TEXTURESCALE / sx; + float yf = TEXTURESCALE / sy; + float xs = watersubdiv * xf; + float ys = watersubdiv * yf; + float t1 = lastmillis / 300.0f; + float t2 = lastmillis / 4000.0f; + + struct sqr dl; + dl.r = dl.g = dl.b = 255; + + for (int xx = wx1; xx < wx2; xx += watersubdiv) { + for (int yy = wy1; yy < wy2; yy += watersubdiv) { + float xo = xf * (xx + t2); + float yo = yf * (yy + t2); + if (yy == wy1) { + vertw(xx, hf, yy, &dl, dx(xo), dy(yo), t1); + vertw(xx + watersubdiv, hf, yy, &dl, + dx(xo + xs), dy(yo), t1); + } + vertw(xx, hf, yy + watersubdiv, &dl, dx(xo), + dy(yo + ys), t1); + vertw(xx + watersubdiv, hf, yy + watersubdiv, &dl, + dx(xo + xs), dy(yo + ys), t1); + } + int n = (wy2 - wy1 - 1) / watersubdiv; + nquads += n; + n = (n + 2) * 2; + glDrawArrays(GL_TRIANGLE_STRIP, curvert -= n, n); + } + + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + + return nquads; +} + +void +addwaterquad(int x, int y, int size) // update bounding rect that contains water +{ + int x2 = x + size; + int y2 = y + size; + if (wx1 < 0) { + wx1 = x; + wy1 = y; + wx2 = x2; + wy2 = y2; + } else { + if (x < wx1) + wx1 = x; + if (y < wy1) + wy1 = y; + if (x2 > wx2) + wx2 = x2; + if (y2 > wy2) + wy2 = y2; + } +} + +void +resetcubes() +{ + if (!verts) + reallocv(); + floorstrip = deltastrip = false; + wx1 = -1; + nquads = 0; + sbright.r = sbright.g = sbright.b = 255; + sdark.r = sdark.g = sdark.b = 0; +} DELETED src/rendercubes.mm Index: src/rendercubes.mm ================================================================== --- src/rendercubes.mm +++ /dev/null @@ -1,427 +0,0 @@ -// rendercubes.cpp: sits in between worldrender.cpp and rendergl.cpp and fills -// the vertex array for different cube surfaces. - -#include "cube.h" - -vertex *verts = NULL; -int curvert; -int curmaxverts = 10000; - -void -setarraypointers() -{ - glVertexPointer(3, GL_FLOAT, sizeof(vertex), &verts[0].x); - glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertex), &verts[0].r); - glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), &verts[0].u); -} - -void -reallocv() -{ - verts = - (vertex *)OFResizeMemory(verts, (curmaxverts *= 2), sizeof(vertex)); - curmaxverts -= 10; - setarraypointers(); -} - -// generating the actual vertices is done dynamically every frame and sits at -// the leaves of all these functions, and are part of the cpu bottleneck on -// really slow machines, hence the macros. - -#define vertcheck() \ - { \ - if (curvert >= curmaxverts) \ - reallocv(); \ - } - -#define vertf(v1, v2, v3, ls, t1, t2) \ - { \ - vertex &v = verts[curvert++]; \ - v.u = t1; \ - v.v = t2; \ - v.x = v1; \ - v.y = v2; \ - v.z = v3; \ - v.r = ls->r; \ - v.g = ls->g; \ - v.b = ls->b; \ - v.a = 255; \ - } - -#define vert(v1, v2, v3, ls, t1, t2) \ - { \ - vertf((float)(v1), (float)(v2), (float)(v3), ls, t1, t2); \ - } - -int nquads; -const float TEXTURESCALE = 32.0f; -bool floorstrip = false, deltastrip = false; -int oh, oy, ox, ogltex; // the o* vars are used by the stripification -int ol3r, ol3g, ol3b, ol4r, ol4g, ol4b; -int firstindex; -bool showm = false; - -void -showmip() -{ - showm = !showm; -} -COMMAND(showmip, ARG_NONE) - -void -mipstats(int a, int b, int c) -{ - if (showm) - conoutf(@"1x1/2x2/4x4: %d / %d / %d", a, b, c); -} - -#define stripend() \ - { \ - if (floorstrip || deltastrip) { \ - addstrip(ogltex, firstindex, curvert - firstindex); \ - floorstrip = deltastrip = false; \ - } \ - } -void -finishstrips() -{ - stripend(); -} - -sqr sbright, sdark; -VAR(lighterror, 1, 8, 100); - -void -render_flat(int wtex, int x, int y, int size, int h, sqr *l1, sqr *l2, sqr *l3, - sqr *l4, bool isceil) // floor/ceil quads -{ - vertcheck(); - if (showm) { - l3 = l1 = &sbright; - l4 = l2 = &sdark; - } - - int sx, sy; - int gltex = lookuptexture(wtex, &sx, &sy); - float xf = TEXTURESCALE / sx; - float yf = TEXTURESCALE / sy; - float xs = size * xf; - float ys = size * yf; - float xo = xf * x; - float yo = yf * y; - - bool first = !floorstrip || y != oy + size || ogltex != gltex || - h != oh || x != ox; - - if (first) // start strip here - { - stripend(); - firstindex = curvert; - ogltex = gltex; - oh = h; - ox = x; - floorstrip = true; - if (isceil) { - vert(x + size, h, y, l2, xo + xs, yo); - vert(x, h, y, l1, xo, yo); - } else { - vert(x, h, y, l1, xo, yo); - vert(x + size, h, y, l2, xo + xs, yo); - } - ol3r = l1->r; - ol3g = l1->g; - ol3b = l1->b; - ol4r = l2->r; - ol4g = l2->g; - ol4b = l2->b; - } else // continue strip - { - int lighterr = lighterror * 2; - if ((abs(ol3r - l3->r) < lighterr && - abs(ol4r - l4->r) < lighterr // skip vertices if light - // values are close enough - && abs(ol3g - l3->g) < lighterr && - abs(ol4g - l4->g) < lighterr && - abs(ol3b - l3->b) < lighterr && - abs(ol4b - l4->b) < lighterr) || - !wtex) { - curvert -= 2; - nquads--; - } else { - uchar *p3 = (uchar *)(&verts[curvert - 1].r); - ol3r = p3[0]; - ol3g = p3[1]; - ol3b = p3[2]; - uchar *p4 = (uchar *)(&verts[curvert - 2].r); - ol4r = p4[0]; - ol4g = p4[1]; - ol4b = p4[2]; - } - } - - if (isceil) { - vert(x + size, h, y + size, l3, xo + xs, yo + ys); - vert(x, h, y + size, l4, xo, yo + ys); - } else { - vert(x, h, y + size, l4, xo, yo + ys); - vert(x + size, h, y + size, l3, xo + xs, yo + ys); - } - - oy = y; - nquads++; -} - -void -render_flatdelta(int wtex, int x, int y, int size, float h1, float h2, float h3, - float h4, sqr *l1, sqr *l2, sqr *l3, sqr *l4, - bool isceil) // floor/ceil quads on a slope -{ - vertcheck(); - if (showm) { - l3 = l1 = &sbright; - l4 = l2 = &sdark; - } - - int sx, sy; - int gltex = lookuptexture(wtex, &sx, &sy); - float xf = TEXTURESCALE / sx; - float yf = TEXTURESCALE / sy; - float xs = size * xf; - float ys = size * yf; - float xo = xf * x; - float yo = yf * y; - - bool first = - !deltastrip || y != oy + size || ogltex != gltex || x != ox; - - if (first) { - stripend(); - firstindex = curvert; - ogltex = gltex; - ox = x; - deltastrip = true; - if (isceil) { - vertf((float)x + size, h2, (float)y, l2, xo + xs, yo); - vertf((float)x, h1, (float)y, l1, xo, yo); - } else { - vertf((float)x, h1, (float)y, l1, xo, yo); - vertf((float)x + size, h2, (float)y, l2, xo + xs, yo); - } - ol3r = l1->r; - ol3g = l1->g; - ol3b = l1->b; - ol4r = l2->r; - ol4g = l2->g; - ol4b = l2->b; - } - - if (isceil) { - vertf( - (float)x + size, h3, (float)y + size, l3, xo + xs, yo + ys); - vertf((float)x, h4, (float)y + size, l4, xo, yo + ys); - } else { - vertf((float)x, h4, (float)y + size, l4, xo, yo + ys); - vertf( - (float)x + size, h3, (float)y + size, l3, xo + xs, yo + ys); - } - - oy = y; - nquads++; -} - -void -render_2tris(sqr *h, sqr *s, int x1, int y1, int x2, int y2, int x3, int y3, - sqr *l1, sqr *l2, sqr *l3) // floor/ceil tris on a corner cube -{ - stripend(); - vertcheck(); - - int sx, sy; - int gltex = lookuptexture(h->ftex, &sx, &sy); - float xf = TEXTURESCALE / sx; - float yf = TEXTURESCALE / sy; - - vertf((float)x1, h->floor, (float)y1, l1, xf * x1, yf * y1); - vertf((float)x2, h->floor, (float)y2, l2, xf * x2, yf * y2); - vertf((float)x3, h->floor, (float)y3, l3, xf * x3, yf * y3); - addstrip(gltex, curvert - 3, 3); - - gltex = lookuptexture(h->ctex, &sx, &sy); - xf = TEXTURESCALE / sx; - yf = TEXTURESCALE / sy; - - vertf((float)x3, h->ceil, (float)y3, l3, xf * x3, yf * y3); - vertf((float)x2, h->ceil, (float)y2, l2, xf * x2, yf * y2); - vertf((float)x1, h->ceil, (float)y1, l1, xf * x1, yf * y1); - addstrip(gltex, curvert - 3, 3); - nquads++; -} - -void -render_tris(int x, int y, int size, bool topleft, sqr *h1, sqr *h2, sqr *s, - sqr *t, sqr *u, sqr *v) -{ - if (topleft) { - if (h1) - render_2tris(h1, s, x + size, y + size, x, y + size, x, - y, u, v, s); - if (h2) - render_2tris(h2, s, x, y, x + size, y, x + size, - y + size, s, t, v); - } else { - if (h1) - render_2tris( - h1, s, x, y, x + size, y, x, y + size, s, t, u); - if (h2) - render_2tris(h2, s, x + size, y, x + size, y + size, x, - y + size, t, u, v); - } -} - -void -render_square(int wtex, float floor1, float floor2, float ceil1, float ceil2, - int x1, int y1, int x2, int y2, int size, sqr *l1, sqr *l2, - bool flip) // wall quads -{ - stripend(); - vertcheck(); - if (showm) { - l1 = &sbright; - l2 = &sdark; - } - - int sx, sy; - int gltex = lookuptexture(wtex, &sx, &sy); - float xf = TEXTURESCALE / sx; - float yf = TEXTURESCALE / sy; - float xs = size * xf; - float xo = xf * (x1 == x2 ? min(y1, y2) : min(x1, x2)); - - if (!flip) { - vertf((float)x2, ceil2, (float)y2, l2, xo + xs, -yf * ceil2); - vertf((float)x1, ceil1, (float)y1, l1, xo, -yf * ceil1); - vertf((float)x2, floor2, (float)y2, l2, xo + xs, -floor2 * yf); - vertf((float)x1, floor1, (float)y1, l1, xo, -floor1 * yf); - } else { - vertf((float)x1, ceil1, (float)y1, l1, xo, -yf * ceil1); - vertf((float)x2, ceil2, (float)y2, l2, xo + xs, -yf * ceil2); - vertf((float)x1, floor1, (float)y1, l1, xo, -floor1 * yf); - vertf((float)x2, floor2, (float)y2, l2, xo + xs, -floor2 * yf); - } - - nquads++; - addstrip(gltex, curvert - 4, 4); -} - -int wx1, wy1, wx2, wy2; - -VAR(watersubdiv, 1, 4, 64); -VARF(waterlevel, -128, -128, 127, - if (!noteditmode()) hdr.waterlevel = waterlevel); - -inline void -vertw(int v1, float v2, int v3, sqr *c, float t1, float t2, float t) -{ - vertcheck(); - vertf((float)v1, v2 - (float)sin(v1 * v3 * 0.1 + t) * 0.2f, (float)v3, - c, t1, t2); -} - -inline float -dx(float x) -{ - return x + (float)sin(x * 2 + lastmillis / 1000.0f) * 0.04f; -} -inline float -dy(float x) -{ - return x + (float)sin(x * 2 + lastmillis / 900.0f + PI / 5) * 0.05f; -} - -// renders water for bounding rect area that contains water... simple but very -// inefficient - -int -renderwater(float hf) -{ - if (wx1 < 0) - return nquads; - - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_SRC_COLOR); - int sx, sy; - glBindTexture(GL_TEXTURE_2D, lookuptexture(DEFAULT_LIQUID, &sx, &sy)); - - wx1 &= ~(watersubdiv - 1); - wy1 &= ~(watersubdiv - 1); - - float xf = TEXTURESCALE / sx; - float yf = TEXTURESCALE / sy; - float xs = watersubdiv * xf; - float ys = watersubdiv * yf; - float t1 = lastmillis / 300.0f; - float t2 = lastmillis / 4000.0f; - - sqr dl; - dl.r = dl.g = dl.b = 255; - - for (int xx = wx1; xx < wx2; xx += watersubdiv) { - for (int yy = wy1; yy < wy2; yy += watersubdiv) { - float xo = xf * (xx + t2); - float yo = yf * (yy + t2); - if (yy == wy1) { - vertw(xx, hf, yy, &dl, dx(xo), dy(yo), t1); - vertw(xx + watersubdiv, hf, yy, &dl, - dx(xo + xs), dy(yo), t1); - } - vertw(xx, hf, yy + watersubdiv, &dl, dx(xo), - dy(yo + ys), t1); - vertw(xx + watersubdiv, hf, yy + watersubdiv, &dl, - dx(xo + xs), dy(yo + ys), t1); - } - int n = (wy2 - wy1 - 1) / watersubdiv; - nquads += n; - n = (n + 2) * 2; - glDrawArrays(GL_TRIANGLE_STRIP, curvert -= n, n); - } - - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); - - return nquads; -} - -void -addwaterquad(int x, int y, int size) // update bounding rect that contains water -{ - int x2 = x + size; - int y2 = y + size; - if (wx1 < 0) { - wx1 = x; - wy1 = y; - wx2 = x2; - wy2 = y2; - } else { - if (x < wx1) - wx1 = x; - if (y < wy1) - wy1 = y; - if (x2 > wx2) - wx2 = x2; - if (y2 > wy2) - wy2 = y2; - } -} - -void -resetcubes() -{ - if (!verts) - reallocv(); - floorstrip = deltastrip = false; - wx1 = -1; - nquads = 0; - sbright.r = sbright.g = sbright.b = 255; - sdark.r = sdark.g = sdark.b = 0; -} ADDED src/renderextras.m Index: src/renderextras.m ================================================================== --- /dev/null +++ src/renderextras.m @@ -0,0 +1,454 @@ +// renderextras.cpp: misc gl render code and the HUD + +#include "cube.h" + +#import "DynamicEntity.h" +#import "Entity.h" + +void +line(int x1, int y1, float z1, int x2, int y2, float z2) +{ + glBegin(GL_POLYGON); + glVertex3f((float)x1, z1, (float)y1); + glVertex3f((float)x1, z1, y1 + 0.01f); + glVertex3f((float)x2, z2, y2 + 0.01f); + glVertex3f((float)x2, z2, (float)y2); + glEnd(); + xtraverts += 4; +} + +void +linestyle(float width, int r, int g, int b) +{ + glLineWidth(width); + glColor3ub(r, g, b); +} + +void +box(const struct block *b, float z1, float z2, float z3, float z4) +{ + glBegin(GL_POLYGON); + glVertex3f((float)b->x, z1, (float)b->y); + glVertex3f((float)b->x + b->xs, z2, (float)b->y); + glVertex3f((float)b->x + b->xs, z3, (float)b->y + b->ys); + glVertex3f((float)b->x, z4, (float)b->y + b->ys); + glEnd(); + xtraverts += 4; +} + +void +dot(int x, int y, float z) +{ + const float DOF = 0.1f; + glBegin(GL_POLYGON); + glVertex3f(x - DOF, (float)z, y - DOF); + glVertex3f(x + DOF, (float)z, y - DOF); + glVertex3f(x + DOF, (float)z, y + DOF); + glVertex3f(x - DOF, (float)z, y + DOF); + glEnd(); + xtraverts += 4; +} + +void +blendbox(int x1, int y1, int x2, int y2, bool border) +{ + glDepthMask(GL_FALSE); + glDisable(GL_TEXTURE_2D); + glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + glBegin(GL_QUADS); + if (border) + glColor3d(0.5, 0.3, 0.4); + else + glColor3d(1.0, 1.0, 1.0); + glVertex2i(x1, y1); + glVertex2i(x2, y1); + glVertex2i(x2, y2); + glVertex2i(x1, y2); + glEnd(); + glDisable(GL_BLEND); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glBegin(GL_POLYGON); + glColor3d(0.2, 0.7, 0.4); + glVertex2i(x1, y1); + glVertex2i(x2, y1); + glVertex2i(x2, y2); + glVertex2i(x1, y2); + glEnd(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + xtraverts += 8; + glEnable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + glDepthMask(GL_TRUE); +} + +const int MAXSPHERES = 50; +struct sphere { + OFVector3D o; + float size, max; + int type; + struct sphere *next; +}; +static struct sphere spheres[MAXSPHERES], *slist = NULL, *sempty = NULL; +bool sinit = false; + +void +newsphere(const OFVector3D *o, float max, int type) +{ + if (!sinit) { + loopi(MAXSPHERES) + { + spheres[i].next = sempty; + sempty = &spheres[i]; + } + sinit = true; + } + if (sempty) { + struct sphere *p = sempty; + sempty = p->next; + p->o = *o; + p->max = max; + p->size = 1; + p->type = type; + p->next = slist; + slist = p; + } +} + +void +renderspheres(int time) +{ + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glBindTexture(GL_TEXTURE_2D, 4); + + for (struct sphere *p, **pp = &slist; (p = *pp) != NULL;) { + glPushMatrix(); + float size = p->size / p->max; + glColor4f(1.0f, 1.0f, 1.0f, 1.0f - size); + glTranslatef(p->o.x, p->o.z, p->o.y); + glRotatef(lastmillis / 5.0f, 1, 1, 1); + glScalef(p->size, p->size, p->size); + glCallList(1); + glScalef(0.8f, 0.8f, 0.8f); + glCallList(1); + glPopMatrix(); + xtraverts += 12 * 6 * 2; + + if (p->size > p->max) { + *pp = p->next; + p->next = sempty; + sempty = p; + } else { + p->size += time / 100.0f; + pp = &p->next; + } + } + + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); +} + +static OFString *closeent; +OFString *entnames[] = { + @"none?", + @"light", + @"playerstart", + @"shells", + @"bullets", + @"rockets", + @"riflerounds", + @"health", + @"healthboost", + @"greenarmour", + @"yellowarmour", + @"quaddamage", + @"teleport", + @"teledest", + @"mapmodel", + @"monster", + @"trigger", + @"jumppad", + @"?", + @"?", + @"?", + @"?", + @"?", +}; + +// show sparkly thingies for map entities in edit mode +void +renderents() +{ + closeent = @""; + + if (!editmode) + return; + + for (Entity *e in ents) { + if (e.type == NOTUSED) + continue; + + OFVector3D v = OFMakeVector3D(e.x, e.y, e.z); + particle_splash(2, 2, 40, &v); + } + + int e = closestent(); + if (e >= 0) { + Entity *c = ents[e]; + closeent = + [OFString stringWithFormat:@"closest entity = %@ (%d, %d, " + @"%d, %d), selection = (%d, %d)", + entnames[c.type], c.attr1, c.attr2, c.attr3, + c.attr4, getvar(@"selxs"), getvar(@"selys")]; + } +} + +void +loadsky(OFString *basename) +{ + static OFString *lastsky = @""; + + basename = [basename stringByReplacingOccurrencesOfString:@"\\" + withString:@"/"]; + + if ([lastsky isEqual:basename]) + return; + + static const OFString *side[] = { @"ft", @"bk", @"lf", @"rt", @"dn", + @"up" }; + int texnum = 14; + loopi(6) + { + OFString *path = [OFString + stringWithFormat:@"packages/%@_%@.jpg", basename, side[i]]; + + int xs, ys; + if (!installtex(texnum + i, + [Cube.sharedInstance.gameDataIRI + IRIByAppendingPathComponent:path], + &xs, &ys, true)) + conoutf(@"could not load sky textures"); + } + + lastsky = basename; +} +COMMAND(loadsky, ARG_1STR) + +float cursordepth = 0.9f; +GLint viewport[4]; +GLdouble mm[16], pm[16]; +OFVector3D worldpos; + +void +readmatrices() +{ + glGetIntegerv(GL_VIEWPORT, viewport); + glGetDoublev(GL_MODELVIEW_MATRIX, mm); + glGetDoublev(GL_PROJECTION_MATRIX, pm); +} + +// stupid function to cater for stupid ATI linux drivers that return incorrect +// depth values + +float +depthcorrect(float d) +{ + return (d <= 1 / 256.0f) ? d * 256 : d; +} + +// find out the 3d target of the crosshair in the world easily and very +// acurately. sadly many very old cards and drivers appear to fuck up on +// glReadPixels() and give false coordinates, making shooting and such +// impossible. also hits map entities which is unwanted. could be replaced by a +// more acurate version of monster.cpp los() if needed + +void +readdepth(int w, int h) +{ + glReadPixels( + w / 2, h / 2, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &cursordepth); + double worldx = 0, worldy = 0, worldz = 0; + gluUnProject(w / 2, h / 2, depthcorrect(cursordepth), mm, pm, viewport, + &worldx, &worldz, &worldy); + worldpos.x = (float)worldx; + worldpos.y = (float)worldy; + worldpos.z = (float)worldz; + OFVector3D r = OFMakeVector3D(mm[0], mm[4], mm[8]); + OFVector3D u = OFMakeVector3D(mm[1], mm[5], mm[9]); + setorient(&r, &u); +} + +void +drawicon(float tx, float ty, int x, int y) +{ + glBindTexture(GL_TEXTURE_2D, 5); + glBegin(GL_QUADS); + tx /= 192; + ty /= 192; + float o = 1 / 3.0f; + int s = 120; + glTexCoord2f(tx, ty); + glVertex2i(x, y); + glTexCoord2f(tx + o, ty); + glVertex2i(x + s, y); + glTexCoord2f(tx + o, ty + o); + glVertex2i(x + s, y + s); + glTexCoord2f(tx, ty + o); + glVertex2i(x, y + s); + glEnd(); + xtraverts += 4; +} + +void +invertperspective() +{ + // This only generates a valid inverse matrix for matrices generated by + // gluPerspective() + GLdouble inv[16]; + memset(inv, 0, sizeof(inv)); + + inv[0 * 4 + 0] = 1.0 / pm[0 * 4 + 0]; + inv[1 * 4 + 1] = 1.0 / pm[1 * 4 + 1]; + inv[2 * 4 + 3] = 1.0 / pm[3 * 4 + 2]; + inv[3 * 4 + 2] = -1.0; + inv[3 * 4 + 3] = pm[2 * 4 + 2] / pm[3 * 4 + 2]; + + glLoadMatrixd(inv); +} + +VARP(crosshairsize, 0, 15, 50); + +int dblend = 0; +void +damageblend(int n) +{ + dblend += n; +} + +VAR(hidestats, 0, 0, 1); +VARP(crosshairfx, 0, 1, 1); + +void +gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater) +{ + readmatrices(); + if (editmode) { + if (cursordepth == 1.0f) + worldpos = player1.o; + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + cursorupdate(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + + glDisable(GL_DEPTH_TEST); + invertperspective(); + glPushMatrix(); + glOrtho(0, VIRTW, VIRTH, 0, -1, 1); + glEnable(GL_BLEND); + + glDepthMask(GL_FALSE); + + if (dblend || underwater) { + glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + glBegin(GL_QUADS); + if (dblend) + glColor3d(0.0f, 0.9f, 0.9f); + else + glColor3d(0.9f, 0.5f, 0.0f); + glVertex2i(0, 0); + glVertex2i(VIRTW, 0); + glVertex2i(VIRTW, VIRTH); + glVertex2i(0, VIRTH); + glEnd(); + dblend -= curtime / 3; + if (dblend < 0) + dblend = 0; + } + + glEnable(GL_TEXTURE_2D); + + OFString *command = getcurcommand(); + OFString *player = playerincrosshair(); + + if (command) + draw_textf(@"> %@_", 20, 1570, 2, command); + else if (closeent.length > 0) + draw_text(closeent, 20, 1570, 2); + else if (player != nil) + draw_text(player, 20, 1570, 2); + + renderscores(); + if (!rendermenu()) { + glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); + glBindTexture(GL_TEXTURE_2D, 1); + glBegin(GL_QUADS); + glColor3ub(255, 255, 255); + if (crosshairfx) { + if (player1.gunwait) + 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); + 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); + renderconsole(); + + 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); + } + + glPopMatrix(); + + if (player1.state == CS_ALIVE) { + glPushMatrix(); + glOrtho(0, VIRTW / 2, VIRTH / 2, 0, -1, 1); + draw_textf(@"%d", 90, 827, 2, player1.health); + if (player1.armour) + draw_textf(@"%d", 390, 827, 2, player1.armour); + draw_textf(@"%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); + if (player1.armour) + drawicon( + (float)(player1.armourtype * 64), 0, 620, 1650); + int g = player1.gunselect; + int r = 64; + if (g > 2) { + 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); +} DELETED src/renderextras.mm Index: src/renderextras.mm ================================================================== --- src/renderextras.mm +++ /dev/null @@ -1,454 +0,0 @@ -// renderextras.cpp: misc gl render code and the HUD - -#include "cube.h" - -#import "DynamicEntity.h" -#import "Entity.h" - -void -line(int x1, int y1, float z1, int x2, int y2, float z2) -{ - glBegin(GL_POLYGON); - glVertex3f((float)x1, z1, (float)y1); - glVertex3f((float)x1, z1, y1 + 0.01f); - glVertex3f((float)x2, z2, y2 + 0.01f); - glVertex3f((float)x2, z2, (float)y2); - glEnd(); - xtraverts += 4; -} - -void -linestyle(float width, int r, int g, int b) -{ - glLineWidth(width); - glColor3ub(r, g, b); -} - -void -box(const block *b, float z1, float z2, float z3, float z4) -{ - glBegin(GL_POLYGON); - glVertex3f((float)b->x, z1, (float)b->y); - glVertex3f((float)b->x + b->xs, z2, (float)b->y); - glVertex3f((float)b->x + b->xs, z3, (float)b->y + b->ys); - glVertex3f((float)b->x, z4, (float)b->y + b->ys); - glEnd(); - xtraverts += 4; -} - -void -dot(int x, int y, float z) -{ - const float DOF = 0.1f; - glBegin(GL_POLYGON); - glVertex3f(x - DOF, (float)z, y - DOF); - glVertex3f(x + DOF, (float)z, y - DOF); - glVertex3f(x + DOF, (float)z, y + DOF); - glVertex3f(x - DOF, (float)z, y + DOF); - glEnd(); - xtraverts += 4; -} - -void -blendbox(int x1, int y1, int x2, int y2, bool border) -{ - glDepthMask(GL_FALSE); - glDisable(GL_TEXTURE_2D); - glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); - glBegin(GL_QUADS); - if (border) - glColor3d(0.5, 0.3, 0.4); - else - glColor3d(1.0, 1.0, 1.0); - glVertex2i(x1, y1); - glVertex2i(x2, y1); - glVertex2i(x2, y2); - glVertex2i(x1, y2); - glEnd(); - glDisable(GL_BLEND); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - glBegin(GL_POLYGON); - glColor3d(0.2, 0.7, 0.4); - glVertex2i(x1, y1); - glVertex2i(x2, y1); - glVertex2i(x2, y2); - glVertex2i(x1, y2); - glEnd(); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - xtraverts += 8; - glEnable(GL_BLEND); - glEnable(GL_TEXTURE_2D); - glDepthMask(GL_TRUE); -} - -const int MAXSPHERES = 50; -struct sphere { - OFVector3D o; - float size, max; - int type; - sphere *next; -}; -sphere spheres[MAXSPHERES], *slist = NULL, *sempty = NULL; -bool sinit = false; - -void -newsphere(const OFVector3D *o, float max, int type) -{ - if (!sinit) { - loopi(MAXSPHERES) - { - spheres[i].next = sempty; - sempty = &spheres[i]; - } - sinit = true; - } - if (sempty) { - sphere *p = sempty; - sempty = p->next; - p->o = *o; - p->max = max; - p->size = 1; - p->type = type; - p->next = slist; - slist = p; - } -} - -void -renderspheres(int time) -{ - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glBindTexture(GL_TEXTURE_2D, 4); - - for (sphere *p, **pp = &slist; p = *pp;) { - glPushMatrix(); - float size = p->size / p->max; - glColor4f(1.0f, 1.0f, 1.0f, 1.0f - size); - glTranslatef(p->o.x, p->o.z, p->o.y); - glRotatef(lastmillis / 5.0f, 1, 1, 1); - glScalef(p->size, p->size, p->size); - glCallList(1); - glScalef(0.8f, 0.8f, 0.8f); - glCallList(1); - glPopMatrix(); - xtraverts += 12 * 6 * 2; - - if (p->size > p->max) { - *pp = p->next; - p->next = sempty; - sempty = p; - } else { - p->size += time / 100.0f; - pp = &p->next; - } - } - - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); -} - -static OFString *closeent; -OFString *entnames[] = { - @"none?", - @"light", - @"playerstart", - @"shells", - @"bullets", - @"rockets", - @"riflerounds", - @"health", - @"healthboost", - @"greenarmour", - @"yellowarmour", - @"quaddamage", - @"teleport", - @"teledest", - @"mapmodel", - @"monster", - @"trigger", - @"jumppad", - @"?", - @"?", - @"?", - @"?", - @"?", -}; - -// show sparkly thingies for map entities in edit mode -void -renderents() -{ - closeent = @""; - - if (!editmode) - return; - - for (Entity *e in ents) { - if (e.type == NOTUSED) - continue; - - OFVector3D v = OFMakeVector3D(e.x, e.y, e.z); - particle_splash(2, 2, 40, &v); - } - - int e = closestent(); - if (e >= 0) { - Entity *c = ents[e]; - closeent = - [OFString stringWithFormat:@"closest entity = %@ (%d, %d, " - @"%d, %d), selection = (%d, %d)", - entnames[c.type], c.attr1, c.attr2, c.attr3, - c.attr4, getvar(@"selxs"), getvar(@"selys")]; - } -} - -void -loadsky(OFString *basename) -{ - static OFString *lastsky = @""; - - basename = [basename stringByReplacingOccurrencesOfString:@"\\" - withString:@"/"]; - - if ([lastsky isEqual:basename]) - return; - - static const OFString *side[] = { @"ft", @"bk", @"lf", @"rt", @"dn", - @"up" }; - int texnum = 14; - loopi(6) - { - OFString *path = [OFString - stringWithFormat:@"packages/%@_%@.jpg", basename, side[i]]; - - int xs, ys; - if (!installtex(texnum + i, - [Cube.sharedInstance.gameDataIRI - IRIByAppendingPathComponent:path], - &xs, &ys, true)) - conoutf(@"could not load sky textures"); - } - - lastsky = basename; -} -COMMAND(loadsky, ARG_1STR) - -float cursordepth = 0.9f; -GLint viewport[4]; -GLdouble mm[16], pm[16]; -OFVector3D worldpos; - -void -readmatrices() -{ - glGetIntegerv(GL_VIEWPORT, viewport); - glGetDoublev(GL_MODELVIEW_MATRIX, mm); - glGetDoublev(GL_PROJECTION_MATRIX, pm); -} - -// stupid function to cater for stupid ATI linux drivers that return incorrect -// depth values - -float -depthcorrect(float d) -{ - return (d <= 1 / 256.0f) ? d * 256 : d; -} - -// find out the 3d target of the crosshair in the world easily and very -// acurately. sadly many very old cards and drivers appear to fuck up on -// glReadPixels() and give false coordinates, making shooting and such -// impossible. also hits map entities which is unwanted. could be replaced by a -// more acurate version of monster.cpp los() if needed - -void -readdepth(int w, int h) -{ - glReadPixels( - w / 2, h / 2, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &cursordepth); - double worldx = 0, worldy = 0, worldz = 0; - gluUnProject(w / 2, h / 2, depthcorrect(cursordepth), mm, pm, viewport, - &worldx, &worldz, &worldy); - worldpos.x = (float)worldx; - worldpos.y = (float)worldy; - worldpos.z = (float)worldz; - OFVector3D r = OFMakeVector3D(mm[0], mm[4], mm[8]); - OFVector3D u = OFMakeVector3D(mm[1], mm[5], mm[9]); - setorient(&r, &u); -} - -void -drawicon(float tx, float ty, int x, int y) -{ - glBindTexture(GL_TEXTURE_2D, 5); - glBegin(GL_QUADS); - tx /= 192; - ty /= 192; - float o = 1 / 3.0f; - int s = 120; - glTexCoord2f(tx, ty); - glVertex2i(x, y); - glTexCoord2f(tx + o, ty); - glVertex2i(x + s, y); - glTexCoord2f(tx + o, ty + o); - glVertex2i(x + s, y + s); - glTexCoord2f(tx, ty + o); - glVertex2i(x, y + s); - glEnd(); - xtraverts += 4; -} - -void -invertperspective() -{ - // This only generates a valid inverse matrix for matrices generated by - // gluPerspective() - GLdouble inv[16]; - memset(inv, 0, sizeof(inv)); - - inv[0 * 4 + 0] = 1.0 / pm[0 * 4 + 0]; - inv[1 * 4 + 1] = 1.0 / pm[1 * 4 + 1]; - inv[2 * 4 + 3] = 1.0 / pm[3 * 4 + 2]; - inv[3 * 4 + 2] = -1.0; - inv[3 * 4 + 3] = pm[2 * 4 + 2] / pm[3 * 4 + 2]; - - glLoadMatrixd(inv); -} - -VARP(crosshairsize, 0, 15, 50); - -int dblend = 0; -void -damageblend(int n) -{ - dblend += n; -} - -VAR(hidestats, 0, 0, 1); -VARP(crosshairfx, 0, 1, 1); - -void -gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater) -{ - readmatrices(); - if (editmode) { - if (cursordepth == 1.0f) - worldpos = player1.o; - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - cursorupdate(); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } - - glDisable(GL_DEPTH_TEST); - invertperspective(); - glPushMatrix(); - glOrtho(0, VIRTW, VIRTH, 0, -1, 1); - glEnable(GL_BLEND); - - glDepthMask(GL_FALSE); - - if (dblend || underwater) { - glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); - glBegin(GL_QUADS); - if (dblend) - glColor3d(0.0f, 0.9f, 0.9f); - else - glColor3d(0.9f, 0.5f, 0.0f); - glVertex2i(0, 0); - glVertex2i(VIRTW, 0); - glVertex2i(VIRTW, VIRTH); - glVertex2i(0, VIRTH); - glEnd(); - dblend -= curtime / 3; - if (dblend < 0) - dblend = 0; - } - - glEnable(GL_TEXTURE_2D); - - OFString *command = getcurcommand(); - OFString *player = playerincrosshair(); - - if (command) - draw_textf(@"> %@_", 20, 1570, 2, command); - else if (closeent.length > 0) - draw_text(closeent, 20, 1570, 2); - else if (player != nil) - draw_text(player, 20, 1570, 2); - - renderscores(); - if (!rendermenu()) { - glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); - glBindTexture(GL_TEXTURE_2D, 1); - glBegin(GL_QUADS); - glColor3ub(255, 255, 255); - if (crosshairfx) { - if (player1.gunwait) - 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); - 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); - renderconsole(); - - 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); - } - - glPopMatrix(); - - if (player1.state == CS_ALIVE) { - glPushMatrix(); - glOrtho(0, VIRTW / 2, VIRTH / 2, 0, -1, 1); - draw_textf(@"%d", 90, 827, 2, player1.health); - if (player1.armour) - draw_textf(@"%d", 390, 827, 2, player1.armour); - draw_textf(@"%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); - if (player1.armour) - drawicon( - (float)(player1.armourtype * 64), 0, 620, 1650); - int g = player1.gunselect; - int r = 64; - if (g > 2) { - 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); -} ADDED src/server.m Index: src/server.m ================================================================== --- /dev/null +++ src/server.m @@ -0,0 +1,568 @@ +// server.cpp: little more than enhanced multicaster +// runs dedicated or as client coroutine + +#include "cube.h" + +#import "Client.h" +#import "Entity.h" +#import "ServerEntity.h" + +enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; + +static OFMutableArray *clients; + +int maxclients = 8; +static OFString *smapname; + +static OFMutableArray *sents; + +// true when map has changed and waiting for clients to send item +bool notgotitems = true; +int mode = 0; + +// hack: called from savegame code, only works in SP +void +restoreserverstate(OFArray *ents) +{ + [sents enumerateObjectsUsingBlock:^( + ServerEntity *e, size_t i, bool *stop) { + e.spawned = ents[i].spawned; + e.spawnsecs = 0; + }]; +} + +int interm = 0, minremain = 0, mapend = 0; +bool mapreload = false; + +static OFString *serverpassword = @""; + +bool isdedicated; +ENetHost *serverhost = NULL; +int bsend = 0, brec = 0, laststatus = 0, lastsec = 0; + +#define MAXOBUF 100000 + +void process(ENetPacket *packet, int sender); +void multicast(ENetPacket *packet, int sender); +void disconnect_client(int n, OFString *reason); + +static void +send_(int n, ENetPacket *packet) +{ + if (!packet) + return; + + switch (clients[n].type) { + case ST_TCPIP: + enet_peer_send(clients[n].peer, 0, packet); + bsend += packet->dataLength; + break; + case ST_LOCAL: + localservertoclient(packet->data, packet->dataLength); + break; + } +} + +void +send2(bool rel, int cn, int a, int b) +{ + ENetPacket *packet = + enet_packet_create(NULL, 32, rel ? ENET_PACKET_FLAG_RELIABLE : 0); + uchar *start = packet->data; + uchar *p = start + 2; + putint(&p, a); + putint(&p, b); + *(ushort *)start = ENET_HOST_TO_NET_16(p - start); + enet_packet_resize(packet, p - start); + if (cn < 0) + process(packet, -1); + else + send_(cn, packet); + if (packet->referenceCount == 0) + enet_packet_destroy(packet); +} + +void +sendservmsg(OFString *msg) +{ + ENetPacket *packet = enet_packet_create( + NULL, _MAXDEFSTR + 10, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start + 2; + putint(&p, SV_SERVMSG); + sendstring(msg, &p); + *(ushort *)start = ENET_HOST_TO_NET_16(p - start); + enet_packet_resize(packet, p - start); + multicast(packet, -1); + if (packet->referenceCount == 0) + enet_packet_destroy(packet); +} + +void +disconnect_client(int n, OFString *reason) +{ + [OFStdOut writeFormat:@"disconnecting client (%@) [%@]\n", + clients[n].hostname, reason]; + enet_peer_disconnect(clients[n].peer); + clients[n].type = ST_EMPTY; + send2(true, -1, SV_CDIS, n); +} + +void +resetitems() +{ + [sents removeAllObjects]; + notgotitems = true; +} + +// server side item pickup, acknowledge first client that gets it +static void +pickup(uint i, int sec, int sender) +{ + if (i >= (uint)sents.count) + return; + if (sents[i].spawned) { + sents[i].spawned = false; + sents[i].spawnsecs = sec; + send2(true, sender, SV_ITEMACC, i); + } +} + +void +resetvotes() +{ + for (Client *client in clients) + client.mapvote = @""; +} + +bool +vote(OFString *map, int reqmode, int sender) +{ + clients[sender].mapvote = map; + clients[sender].modevote = reqmode; + + int yes = 0, no = 0; + for (Client *client in clients) { + if (client.type != ST_EMPTY) { + if (client.mapvote.length > 0) { + if ([client.mapvote isEqual:map] && + client.modevote == reqmode) + yes++; + else + no++; + } else + no++; + } + } + + if (yes == 1 && no == 0) + return true; // single player + + OFString *msg = [OFString + stringWithFormat:@"%@ suggests %@ on map %@ (set map to vote)", + clients[sender].name, modestr(reqmode), map]; + sendservmsg(msg); + + if (yes / (float)(yes + no) <= 0.5f) + return false; + + sendservmsg(@"vote passed"); + resetvotes(); + return true; +} + +// server side processing of updates: does very little and most state is tracked +// client only could be extended to move more gameplay to server (at expense of +// lag) + +void +process(ENetPacket *packet, int sender) // sender may be -1 +{ + if (ENET_NET_TO_HOST_16(*(ushort *)packet->data) != + packet->dataLength) { + disconnect_client(sender, @"packet length"); + return; + } + + uchar *end = packet->data + packet->dataLength; + uchar *p = packet->data + 2; + char text[MAXTRANS]; + int cn = -1, type; + + while (p < end) { + switch ((type = getint(&p))) { + case SV_TEXT: + sgetstr(); + break; + + case SV_INITC2S: + sgetstr(); + clients[cn].name = @(text); + sgetstr(); + getint(&p); + break; + + case SV_MAPCHANGE: { + sgetstr(); + int reqmode = getint(&p); + if (reqmode < 0) + reqmode = 0; + if (smapname.length > 0 && !mapreload && + !vote(@(text), reqmode, sender)) + return; + mapreload = false; + mode = reqmode; + minremain = mode & 1 ? 15 : 10; + mapend = lastsec + minremain * 60; + interm = 0; + smapname = @(text); + resetitems(); + sender = -1; + break; + } + + case SV_ITEMLIST: { + int n; + while ((n = getint(&p)) != -1) + if (notgotitems) { + while (sents.count <= n) + [sents addObject:[ServerEntity + entity]]; + sents[n].spawned = true; + } + notgotitems = false; + break; + } + + case SV_ITEMPICKUP: { + int n = getint(&p); + pickup(n, getint(&p), sender); + break; + } + + case SV_PING: + send2(false, cn, SV_PONG, getint(&p)); + break; + + case SV_POS: { + cn = getint(&p); + if (cn < 0 || cn >= clients.count || + clients[cn].type == ST_EMPTY) { + disconnect_client(sender, @"client num"); + return; + } + int size = msgsizelookup(type); + assert(size != -1); + loopi(size - 2) getint(&p); + break; + } + + case SV_SENDMAP: { + sgetstr(); + int mapsize = getint(&p); + sendmaps(sender, @(text), mapsize, p); + return; + } + + case SV_RECVMAP: + send_(sender, recvmap(sender)); + return; + + // allows for new features that require no server updates + case SV_EXT: + for (int n = getint(&p); n; n--) + getint(&p); + break; + + default: { + int size = msgsizelookup(type); + if (size == -1) { + disconnect_client(sender, @"tag type"); + return; + } + loopi(size - 1) getint(&p); + } + } + } + + if (p > end) { + disconnect_client(sender, @"end of packet"); + return; + } + + multicast(packet, sender); +} + +void +send_welcome(int n) +{ + ENetPacket *packet = + enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + __block uchar *p = start + 2; + putint(&p, SV_INITS2C); + putint(&p, n); + putint(&p, PROTOCOL_VERSION); + putint(&p, *smapname.UTF8String); + sendstring(serverpassword, &p); + putint(&p, clients.count > maxclients); + if (smapname.length > 0) { + putint(&p, SV_MAPCHANGE); + sendstring(smapname, &p); + putint(&p, mode); + putint(&p, SV_ITEMLIST); + [sents enumerateObjectsUsingBlock:^( + ServerEntity *e, size_t i, bool *stop) { + if (e.spawned) + putint(&p, i); + }]; + putint(&p, -1); + } + *(ushort *)start = ENET_HOST_TO_NET_16(p - start); + enet_packet_resize(packet, p - start); + send_(n, packet); +} + +void +multicast(ENetPacket *packet, int sender) +{ + size_t count = clients.count; + for (size_t i = 0; i < count; i++) + if (i != sender) + send_(i, packet); +} + +void +localclienttoserver(ENetPacket *packet) +{ + process(packet, 0); + + if (packet->referenceCount == 0) + enet_packet_destroy(packet); +} + +Client * +addclient() +{ + for (Client *client in clients) + if (client.type == ST_EMPTY) + return client; + + Client *client = [Client client]; + + if (clients == nil) + clients = [[OFMutableArray alloc] init]; + + [clients addObject:client]; + + return client; +} + +void +checkintermission() +{ + if (!minremain) { + interm = lastsec + 10; + mapend = lastsec + 1000; + } + send2(true, -1, SV_TIMEUP, minremain--); +} + +void +startintermission() +{ + minremain = 0; + checkintermission(); +} + +void +resetserverifempty() +{ + for (Client *client in clients) + if (client.type != ST_EMPTY) + return; + + [clients removeAllObjects]; + smapname = @""; + resetvotes(); + resetitems(); + mode = 0; + mapreload = false; + minremain = 10; + mapend = lastsec + minremain * 60; + interm = 0; +} + +int nonlocalclients = 0; +int lastconnect = 0; + +void +serverslice(int seconds, + unsigned int timeout) // main server update, called from cube main loop in + // sp, or dedicated server loop +{ + // spawn entities when timer reached + [sents enumerateObjectsUsingBlock:^( + ServerEntity *e, size_t i, bool *stop) { + if (e.spawnsecs && (e.spawnsecs -= seconds - lastsec) <= 0) { + e.spawnsecs = 0; + e.spawned = true; + send2(true, -1, SV_ITEMSPAWN, i); + } + }]; + + lastsec = seconds; + + if ((mode > 1 || (mode == 0 && nonlocalclients)) && + seconds > mapend - minremain * 60) + checkintermission(); + if (interm && seconds > interm) { + interm = 0; + [clients enumerateObjectsUsingBlock:^( + Client *client, size_t i, bool *stop) { + if (client.type != ST_EMPTY) { + // ask a client to trigger map reload + send2(true, i, SV_MAPRELOAD, 0); + mapreload = true; + *stop = true; + return; + } + }]; + } + + resetserverifempty(); + + if (!isdedicated) + return; // below is network only + + int numplayers = 0; + for (Client *client in clients) + if (client.type != ST_EMPTY) + numplayers++; + + serverms(mode, numplayers, minremain, smapname, seconds, + clients.count >= maxclients); + + // display bandwidth stats, useful for server ops + if (seconds - laststatus > 60) { + nonlocalclients = 0; + for (Client *client in clients) + if (client.type == ST_TCPIP) + nonlocalclients++; + + laststatus = seconds; + if (nonlocalclients || bsend || brec) + printf("status: %d remote clients, %.1f send, %.1f rec " + "(K/sec)\n", + nonlocalclients, bsend / 60.0f / 1024, + brec / 60.0f / 1024); + bsend = brec = 0; + } + + ENetEvent event; + if (enet_host_service(serverhost, &event, timeout) > 0) { + switch (event.type) { + case ENET_EVENT_TYPE_CONNECT: { + Client *c = addclient(); + c.type = ST_TCPIP; + c.peer = event.peer; + c.peer->data = (void *)(clients.count - 1); + char hn[1024]; + c.hostname = (enet_address_get_host( + &c.peer->address, hn, sizeof(hn)) == 0 + ? @(hn) + : @"localhost"); + [OFStdOut + writeFormat:@"client connected (%@)\n", c.hostname]; + send_welcome(lastconnect = clients.count - 1); + break; + } + case ENET_EVENT_TYPE_RECEIVE: + brec += event.packet->dataLength; + process(event.packet, (intptr_t)event.peer->data); + if (event.packet->referenceCount == 0) + enet_packet_destroy(event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + if ((intptr_t)event.peer->data < 0) + break; + [OFStdOut writeFormat:@"disconnected client (%@)\n", + clients[(size_t)event.peer->data].hostname]; + clients[(size_t)event.peer->data].type = ST_EMPTY; + send2(true, -1, SV_CDIS, (intptr_t)event.peer->data); + event.peer->data = (void *)-1; + break; + } + + if (numplayers > maxclients) + disconnect_client(lastconnect, @"maxclients reached"); + } +#ifndef _WIN32 + fflush(stdout); +#endif +} + +void +cleanupserver() +{ + if (serverhost) + enet_host_destroy(serverhost); +} + +void +localdisconnect() +{ + for (Client *client in clients) + if (client.type == ST_LOCAL) + client.type = ST_EMPTY; +} + +void +localconnect() +{ + Client *c = addclient(); + c.type = ST_LOCAL; + c.hostname = @"local"; + send_welcome(clients.count - 1); +} + +void +initserver(bool dedicated, int uprate, OFString *sdesc, OFString *ip, + OFString *master, OFString *passwd, int maxcl) +{ + serverpassword = passwd; + maxclients = maxcl; + sents = [[OFMutableArray alloc] init]; + servermsinit(master ? master : @"wouter.fov120.com/cube/masterserver/", + sdesc, dedicated); + + if ((isdedicated = dedicated)) { + ENetAddress address = { ENET_HOST_ANY, CUBE_SERVER_PORT }; + if (ip.length > 0 && + enet_address_set_host(&address, ip.UTF8String) < 0) + printf("WARNING: server ip not resolved"); + serverhost = enet_host_create(&address, MAXCLIENTS, 0, uprate); + if (!serverhost) + fatal(@"could not create server host\n"); + loopi(MAXCLIENTS) serverhost->peers[i].data = (void *)-1; + } + + resetserverifempty(); + + // do not return, this becomes main loop + if (isdedicated) { +#ifdef _WIN32 + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); +#endif + printf("dedicated server started, waiting for " + "clients...\nCtrl-C to exit\n\n"); + atexit(cleanupserver); + atexit(enet_deinitialize); + for (;;) + @autoreleasepool { + serverslice( + /*enet_time_get_sec()*/ time(NULL), 5); + } + } +} DELETED src/server.mm Index: src/server.mm ================================================================== --- src/server.mm +++ /dev/null @@ -1,568 +0,0 @@ -// server.cpp: little more than enhanced multicaster -// runs dedicated or as client coroutine - -#include "cube.h" - -#import "Client.h" -#import "Entity.h" -#import "ServerEntity.h" - -enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; - -static OFMutableArray *clients; - -int maxclients = 8; -static OFString *smapname; - -static OFMutableArray *sents; - -// true when map has changed and waiting for clients to send item -bool notgotitems = true; -int mode = 0; - -// hack: called from savegame code, only works in SP -void -restoreserverstate(OFArray *ents) -{ - [sents enumerateObjectsUsingBlock:^( - ServerEntity *e, size_t i, bool *stop) { - e.spawned = ents[i].spawned; - e.spawnsecs = 0; - }]; -} - -int interm = 0, minremain = 0, mapend = 0; -bool mapreload = false; - -static OFString *serverpassword = @""; - -bool isdedicated; -ENetHost *serverhost = NULL; -int bsend = 0, brec = 0, laststatus = 0, lastsec = 0; - -#define MAXOBUF 100000 - -void process(ENetPacket *packet, int sender); -void multicast(ENetPacket *packet, int sender); -void disconnect_client(int n, OFString *reason); - -void -send(int n, ENetPacket *packet) -{ - if (!packet) - return; - - switch (clients[n].type) { - case ST_TCPIP: - enet_peer_send(clients[n].peer, 0, packet); - bsend += packet->dataLength; - break; - case ST_LOCAL: - localservertoclient(packet->data, packet->dataLength); - break; - } -} - -void -send2(bool rel, int cn, int a, int b) -{ - ENetPacket *packet = - enet_packet_create(NULL, 32, rel ? ENET_PACKET_FLAG_RELIABLE : 0); - uchar *start = packet->data; - uchar *p = start + 2; - putint(&p, a); - putint(&p, b); - *(ushort *)start = ENET_HOST_TO_NET_16(p - start); - enet_packet_resize(packet, p - start); - if (cn < 0) - process(packet, -1); - else - send(cn, packet); - if (packet->referenceCount == 0) - enet_packet_destroy(packet); -} - -void -sendservmsg(OFString *msg) -{ - ENetPacket *packet = enet_packet_create( - NULL, _MAXDEFSTR + 10, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start + 2; - putint(&p, SV_SERVMSG); - sendstring(msg, &p); - *(ushort *)start = ENET_HOST_TO_NET_16(p - start); - enet_packet_resize(packet, p - start); - multicast(packet, -1); - if (packet->referenceCount == 0) - enet_packet_destroy(packet); -} - -void -disconnect_client(int n, OFString *reason) -{ - [OFStdOut writeFormat:@"disconnecting client (%@) [%@]\n", - clients[n].hostname, reason]; - enet_peer_disconnect(clients[n].peer); - clients[n].type = ST_EMPTY; - send2(true, -1, SV_CDIS, n); -} - -void -resetitems() -{ - [sents removeAllObjects]; - notgotitems = true; -} - -void -pickup(uint i, int sec, int sender) // server side item pickup, acknowledge - // first client that gets it -{ - if (i >= (uint)sents.count) - return; - if (sents[i].spawned) { - sents[i].spawned = false; - sents[i].spawnsecs = sec; - send2(true, sender, SV_ITEMACC, i); - } -} - -void -resetvotes() -{ - for (Client *client in clients) - client.mapvote = @""; -} - -bool -vote(OFString *map, int reqmode, int sender) -{ - clients[sender].mapvote = map; - clients[sender].modevote = reqmode; - - int yes = 0, no = 0; - for (Client *client in clients) { - if (client.type != ST_EMPTY) { - if (client.mapvote.length > 0) { - if ([client.mapvote isEqual:map] && - client.modevote == reqmode) - yes++; - else - no++; - } else - no++; - } - } - - if (yes == 1 && no == 0) - return true; // single player - - OFString *msg = [OFString - stringWithFormat:@"%@ suggests %@ on map %@ (set map to vote)", - clients[sender].name, modestr(reqmode), map]; - sendservmsg(msg); - - if (yes / (float)(yes + no) <= 0.5f) - return false; - - sendservmsg(@"vote passed"); - resetvotes(); - return true; -} - -// server side processing of updates: does very little and most state is tracked -// client only could be extended to move more gameplay to server (at expense of -// lag) - -void -process(ENetPacket *packet, int sender) // sender may be -1 -{ - if (ENET_NET_TO_HOST_16(*(ushort *)packet->data) != - packet->dataLength) { - disconnect_client(sender, @"packet length"); - return; - } - - uchar *end = packet->data + packet->dataLength; - uchar *p = packet->data + 2; - char text[MAXTRANS]; - int cn = -1, type; - - while (p < end) { - switch ((type = getint(&p))) { - case SV_TEXT: - sgetstr(); - break; - - case SV_INITC2S: - sgetstr(); - clients[cn].name = @(text); - sgetstr(); - getint(&p); - break; - - case SV_MAPCHANGE: { - sgetstr(); - int reqmode = getint(&p); - if (reqmode < 0) - reqmode = 0; - if (smapname.length > 0 && !mapreload && - !vote(@(text), reqmode, sender)) - return; - mapreload = false; - mode = reqmode; - minremain = mode & 1 ? 15 : 10; - mapend = lastsec + minremain * 60; - interm = 0; - smapname = @(text); - resetitems(); - sender = -1; - break; - } - - case SV_ITEMLIST: { - int n; - while ((n = getint(&p)) != -1) - if (notgotitems) { - while (sents.count <= n) - [sents addObject:[ServerEntity - entity]]; - sents[n].spawned = true; - } - notgotitems = false; - break; - } - - case SV_ITEMPICKUP: { - int n = getint(&p); - pickup(n, getint(&p), sender); - break; - } - - case SV_PING: - send2(false, cn, SV_PONG, getint(&p)); - break; - - case SV_POS: { - cn = getint(&p); - if (cn < 0 || cn >= clients.count || - clients[cn].type == ST_EMPTY) { - disconnect_client(sender, @"client num"); - return; - } - int size = msgsizelookup(type); - assert(size != -1); - loopi(size - 2) getint(&p); - break; - } - - case SV_SENDMAP: { - sgetstr(); - int mapsize = getint(&p); - sendmaps(sender, @(text), mapsize, p); - return; - } - - case SV_RECVMAP: - send(sender, recvmap(sender)); - return; - - // allows for new features that require no server updates - case SV_EXT: - for (int n = getint(&p); n; n--) - getint(&p); - break; - - default: { - int size = msgsizelookup(type); - if (size == -1) { - disconnect_client(sender, @"tag type"); - return; - } - loopi(size - 1) getint(&p); - } - } - } - - if (p > end) { - disconnect_client(sender, @"end of packet"); - return; - } - - multicast(packet, sender); -} - -void -send_welcome(int n) -{ - ENetPacket *packet = - enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - __block uchar *p = start + 2; - putint(&p, SV_INITS2C); - putint(&p, n); - putint(&p, PROTOCOL_VERSION); - putint(&p, *smapname.UTF8String); - sendstring(serverpassword, &p); - putint(&p, clients.count > maxclients); - if (smapname.length > 0) { - putint(&p, SV_MAPCHANGE); - sendstring(smapname, &p); - putint(&p, mode); - putint(&p, SV_ITEMLIST); - [sents enumerateObjectsUsingBlock:^( - ServerEntity *e, size_t i, bool *stop) { - if (e.spawned) - putint(&p, i); - }]; - putint(&p, -1); - } - *(ushort *)start = ENET_HOST_TO_NET_16(p - start); - enet_packet_resize(packet, p - start); - send(n, packet); -} - -void -multicast(ENetPacket *packet, int sender) -{ - size_t count = clients.count; - for (size_t i = 0; i < count; i++) - if (i != sender) - send(i, packet); -} - -void -localclienttoserver(ENetPacket *packet) -{ - process(packet, 0); - - if (packet->referenceCount == 0) - enet_packet_destroy(packet); -} - -Client * -addclient() -{ - for (Client *client in clients) - if (client.type == ST_EMPTY) - return client; - - Client *client = [Client client]; - - if (clients == nil) - clients = [[OFMutableArray alloc] init]; - - [clients addObject:client]; - - return client; -} - -void -checkintermission() -{ - if (!minremain) { - interm = lastsec + 10; - mapend = lastsec + 1000; - } - send2(true, -1, SV_TIMEUP, minremain--); -} - -void -startintermission() -{ - minremain = 0; - checkintermission(); -} - -void -resetserverifempty() -{ - for (Client *client in clients) - if (client.type != ST_EMPTY) - return; - - [clients removeAllObjects]; - smapname = @""; - resetvotes(); - resetitems(); - mode = 0; - mapreload = false; - minremain = 10; - mapend = lastsec + minremain * 60; - interm = 0; -} - -int nonlocalclients = 0; -int lastconnect = 0; - -void -serverslice(int seconds, - unsigned int timeout) // main server update, called from cube main loop in - // sp, or dedicated server loop -{ - // spawn entities when timer reached - [sents enumerateObjectsUsingBlock:^( - ServerEntity *e, size_t i, bool *stop) { - if (e.spawnsecs && (e.spawnsecs -= seconds - lastsec) <= 0) { - e.spawnsecs = 0; - e.spawned = true; - send2(true, -1, SV_ITEMSPAWN, i); - } - }]; - - lastsec = seconds; - - if ((mode > 1 || (mode == 0 && nonlocalclients)) && - seconds > mapend - minremain * 60) - checkintermission(); - if (interm && seconds > interm) { - interm = 0; - [clients enumerateObjectsUsingBlock:^( - Client *client, size_t i, bool *stop) { - if (client.type != ST_EMPTY) { - // ask a client to trigger map reload - send2(true, i, SV_MAPRELOAD, 0); - mapreload = true; - *stop = true; - return; - } - }]; - } - - resetserverifempty(); - - if (!isdedicated) - return; // below is network only - - int numplayers = 0; - for (Client *client in clients) - if (client.type != ST_EMPTY) - numplayers++; - - serverms(mode, numplayers, minremain, smapname, seconds, - clients.count >= maxclients); - - // display bandwidth stats, useful for server ops - if (seconds - laststatus > 60) { - nonlocalclients = 0; - for (Client *client in clients) - if (client.type == ST_TCPIP) - nonlocalclients++; - - laststatus = seconds; - if (nonlocalclients || bsend || brec) - printf("status: %d remote clients, %.1f send, %.1f rec " - "(K/sec)\n", - nonlocalclients, bsend / 60.0f / 1024, - brec / 60.0f / 1024); - bsend = brec = 0; - } - - ENetEvent event; - if (enet_host_service(serverhost, &event, timeout) > 0) { - switch (event.type) { - case ENET_EVENT_TYPE_CONNECT: { - Client *c = addclient(); - c.type = ST_TCPIP; - c.peer = event.peer; - c.peer->data = (void *)(clients.count - 1); - char hn[1024]; - c.hostname = (enet_address_get_host( - &c.peer->address, hn, sizeof(hn)) == 0 - ? @(hn) - : @"localhost"); - [OFStdOut - writeFormat:@"client connected (%@)\n", c.hostname]; - send_welcome(lastconnect = clients.count - 1); - break; - } - case ENET_EVENT_TYPE_RECEIVE: - brec += event.packet->dataLength; - process(event.packet, (intptr_t)event.peer->data); - if (event.packet->referenceCount == 0) - enet_packet_destroy(event.packet); - break; - - case ENET_EVENT_TYPE_DISCONNECT: - if ((intptr_t)event.peer->data < 0) - break; - [OFStdOut writeFormat:@"disconnected client (%@)\n", - clients[(size_t)event.peer->data].hostname]; - clients[(size_t)event.peer->data].type = ST_EMPTY; - send2(true, -1, SV_CDIS, (intptr_t)event.peer->data); - event.peer->data = (void *)-1; - break; - } - - if (numplayers > maxclients) - disconnect_client(lastconnect, @"maxclients reached"); - } -#ifndef _WIN32 - fflush(stdout); -#endif -} - -void -cleanupserver() -{ - if (serverhost) - enet_host_destroy(serverhost); -} - -void -localdisconnect() -{ - for (Client *client in clients) - if (client.type == ST_LOCAL) - client.type = ST_EMPTY; -} - -void -localconnect() -{ - Client *c = addclient(); - c.type = ST_LOCAL; - c.hostname = @"local"; - send_welcome(clients.count - 1); -} - -void -initserver(bool dedicated, int uprate, OFString *sdesc, OFString *ip, - OFString *master, OFString *passwd, int maxcl) -{ - serverpassword = passwd; - maxclients = maxcl; - sents = [[OFMutableArray alloc] init]; - servermsinit(master ? master : @"wouter.fov120.com/cube/masterserver/", - sdesc, dedicated); - - if ((isdedicated = dedicated)) { - ENetAddress address = { ENET_HOST_ANY, CUBE_SERVER_PORT }; - if (ip.length > 0 && - enet_address_set_host(&address, ip.UTF8String) < 0) - printf("WARNING: server ip not resolved"); - serverhost = enet_host_create(&address, MAXCLIENTS, 0, uprate); - if (!serverhost) - fatal(@"could not create server host\n"); - loopi(MAXCLIENTS) serverhost->peers[i].data = (void *)-1; - } - - resetserverifempty(); - - // do not return, this becomes main loop - if (isdedicated) { -#ifdef _WIN32 - SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); -#endif - printf("dedicated server started, waiting for " - "clients...\nCtrl-C to exit\n\n"); - atexit(cleanupserver); - atexit(enet_deinitialize); - for (;;) - @autoreleasepool { - serverslice( - /*enet_time_get_sec()*/ time(NULL), 5); - } - } -} ADDED src/serverms.m Index: src/serverms.m ================================================================== --- /dev/null +++ src/serverms.m @@ -0,0 +1,171 @@ +// all server side masterserver and pinging functionality + +#include "cube.h" + +static ENetSocket mssock = ENET_SOCKET_NULL; + +static void +httpgetsend(ENetAddress *ad, OFString *hostname, OFString *req, OFString *ref, + OFString *agent) +{ + if (ad->host == ENET_HOST_ANY) { + [OFStdOut writeFormat:@"looking up %@...\n", hostname]; + enet_address_set_host(ad, hostname.UTF8String); + if (ad->host == ENET_HOST_ANY) + return; + } + if (mssock != ENET_SOCKET_NULL) + enet_socket_destroy(mssock); + mssock = enet_socket_create(ENET_SOCKET_TYPE_STREAM, NULL); + if (mssock == ENET_SOCKET_NULL) { + printf("could not open socket\n"); + return; + } + if (enet_socket_connect(mssock, ad) < 0) { + printf("could not connect\n"); + return; + } + ENetBuffer buf; + OFString *httpget = [OFString stringWithFormat:@"GET %@ HTTP/1.0\n" + @"Host: %@\n" + @"Referer: %@\n" + @"User-Agent: %@\n\n", + req, hostname, ref, agent]; + buf.data = (void *)httpget.UTF8String; + buf.dataLength = httpget.UTF8StringLength; + [OFStdOut writeFormat:@"sending request to %@...\n", hostname]; + enet_socket_send(mssock, NULL, &buf, 1); +} + +static void +httpgetrecieve(ENetBuffer *buf) +{ + if (mssock == ENET_SOCKET_NULL) + return; + enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; + if (enet_socket_wait(mssock, &events, 0) >= 0 && events) { + int len = enet_socket_receive(mssock, NULL, buf, 1); + if (len <= 0) { + enet_socket_destroy(mssock); + mssock = ENET_SOCKET_NULL; + return; + } + buf->data = ((char *)buf->data) + len; + ((char *)buf->data)[0] = 0; + buf->dataLength -= len; + } +} + +static uchar * +stripheader(uchar *b) +{ + char *s = strstr((char *)b, "\n\r\n"); + if (!s) + s = strstr((char *)b, "\n\n"); + return s ? (uchar *)s : b; +} + +static ENetAddress masterserver = { ENET_HOST_ANY, 80 }; +static int updmaster = 0; +static OFString *masterbase; +static OFString *masterpath; +static uchar masterrep[MAXTRANS]; +static ENetBuffer masterb; + +static void +updatemasterserver(int seconds) +{ + // send alive signal to masterserver every hour of uptime + if (seconds > updmaster) { + OFString *path = [OFString + stringWithFormat:@"%@register.do?action=add", masterpath]; + httpgetsend(&masterserver, masterbase, path, @"cubeserver", + @"Cube Server"); + masterrep[0] = 0; + masterb.data = masterrep; + masterb.dataLength = MAXTRANS - 1; + updmaster = seconds + 60 * 60; + } +} + +static void +checkmasterreply() +{ + bool busy = mssock != ENET_SOCKET_NULL; + httpgetrecieve(&masterb); + if (busy && mssock == ENET_SOCKET_NULL) + printf("masterserver reply: %s\n", stripheader(masterrep)); +} + +uchar * +retrieveservers(uchar *buf, int buflen) +{ + OFString *path = + [OFString stringWithFormat:@"%@retrieve.do?item=list", masterpath]; + httpgetsend( + &masterserver, masterbase, path, @"cubeserver", @"Cube Server"); + ENetBuffer eb; + buf[0] = 0; + eb.data = buf; + eb.dataLength = buflen - 1; + while (mssock != ENET_SOCKET_NULL) + httpgetrecieve(&eb); + return stripheader(buf); +} + +static ENetSocket pongsock = ENET_SOCKET_NULL; +static OFString *serverdesc; + +void +serverms(int mode, int numplayers, int minremain, OFString *smapname, + int seconds, bool isfull) +{ + checkmasterreply(); + updatemasterserver(seconds); + + // reply all server info requests + ENetBuffer buf; + ENetAddress addr; + uchar pong[MAXTRANS], *p; + int len; + enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; + buf.data = pong; + while (enet_socket_wait(pongsock, &events, 0) >= 0 && events) { + buf.dataLength = sizeof(pong); + len = enet_socket_receive(pongsock, &addr, &buf, 1); + if (len < 0) + return; + p = &pong[len]; + putint(&p, PROTOCOL_VERSION); + putint(&p, mode); + putint(&p, numplayers); + putint(&p, minremain); + OFString *mname = + [OFString stringWithFormat:@"%@%@", + (isfull ? @"[FULL] " : @""), smapname]; + sendstring(mname, &p); + sendstring(serverdesc, &p); + buf.dataLength = p - pong; + enet_socket_send(pongsock, &addr, &buf, 1); + } +} + +void +servermsinit(OFString *master_, OFString *sdesc, bool listen) +{ + const char *master = master_.UTF8String; + const char *mid = strstr(master, "/"); + if (!mid) + mid = master; + masterpath = @(mid); + masterbase = [OFString stringWithUTF8String:master length:mid - master]; + serverdesc = sdesc; + + if (listen) { + ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; + pongsock = + enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, &address); + if (pongsock == ENET_SOCKET_NULL) + fatal(@"could not create server info socket\n"); + } +} DELETED src/serverms.mm Index: src/serverms.mm ================================================================== --- src/serverms.mm +++ /dev/null @@ -1,171 +0,0 @@ -// all server side masterserver and pinging functionality - -#include "cube.h" - -static ENetSocket mssock = ENET_SOCKET_NULL; - -static void -httpgetsend(ENetAddress &ad, OFString *hostname, OFString *req, OFString *ref, - OFString *agent) -{ - if (ad.host == ENET_HOST_ANY) { - [OFStdOut writeFormat:@"looking up %@...\n", hostname]; - enet_address_set_host(&ad, hostname.UTF8String); - if (ad.host == ENET_HOST_ANY) - return; - } - if (mssock != ENET_SOCKET_NULL) - enet_socket_destroy(mssock); - mssock = enet_socket_create(ENET_SOCKET_TYPE_STREAM, NULL); - if (mssock == ENET_SOCKET_NULL) { - printf("could not open socket\n"); - return; - } - if (enet_socket_connect(mssock, &ad) < 0) { - printf("could not connect\n"); - return; - } - ENetBuffer buf; - OFString *httpget = [OFString stringWithFormat:@"GET %@ HTTP/1.0\n" - @"Host: %@\n" - @"Referer: %@\n" - @"User-Agent: %@\n\n", - req, hostname, ref, agent]; - buf.data = (void *)httpget.UTF8String; - buf.dataLength = httpget.UTF8StringLength; - [OFStdOut writeFormat:@"sending request to %@...\n", hostname]; - enet_socket_send(mssock, NULL, &buf, 1); -} - -static void -httpgetrecieve(ENetBuffer &buf) -{ - if (mssock == ENET_SOCKET_NULL) - return; - enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; - if (enet_socket_wait(mssock, &events, 0) >= 0 && events) { - int len = enet_socket_receive(mssock, NULL, &buf, 1); - if (len <= 0) { - enet_socket_destroy(mssock); - mssock = ENET_SOCKET_NULL; - return; - } - buf.data = ((char *)buf.data) + len; - ((char *)buf.data)[0] = 0; - buf.dataLength -= len; - } -} - -static uchar * -stripheader(uchar *b) -{ - char *s = strstr((char *)b, "\n\r\n"); - if (!s) - s = strstr((char *)b, "\n\n"); - return s ? (uchar *)s : b; -} - -static ENetAddress masterserver = { ENET_HOST_ANY, 80 }; -static int updmaster = 0; -static OFString *masterbase; -static OFString *masterpath; -static uchar masterrep[MAXTRANS]; -static ENetBuffer masterb; - -static void -updatemasterserver(int seconds) -{ - // send alive signal to masterserver every hour of uptime - if (seconds > updmaster) { - OFString *path = [OFString - stringWithFormat:@"%@register.do?action=add", masterpath]; - httpgetsend(masterserver, masterbase, path, @"cubeserver", - @"Cube Server"); - masterrep[0] = 0; - masterb.data = masterrep; - masterb.dataLength = MAXTRANS - 1; - updmaster = seconds + 60 * 60; - } -} - -static void -checkmasterreply() -{ - bool busy = mssock != ENET_SOCKET_NULL; - httpgetrecieve(masterb); - if (busy && mssock == ENET_SOCKET_NULL) - printf("masterserver reply: %s\n", stripheader(masterrep)); -} - -uchar * -retrieveservers(uchar *buf, int buflen) -{ - OFString *path = - [OFString stringWithFormat:@"%@retrieve.do?item=list", masterpath]; - httpgetsend( - masterserver, masterbase, path, @"cubeserver", @"Cube Server"); - ENetBuffer eb; - buf[0] = 0; - eb.data = buf; - eb.dataLength = buflen - 1; - while (mssock != ENET_SOCKET_NULL) - httpgetrecieve(eb); - return stripheader(buf); -} - -static ENetSocket pongsock = ENET_SOCKET_NULL; -static OFString *serverdesc; - -void -serverms(int mode, int numplayers, int minremain, OFString *smapname, - int seconds, bool isfull) -{ - checkmasterreply(); - updatemasterserver(seconds); - - // reply all server info requests - ENetBuffer buf; - ENetAddress addr; - uchar pong[MAXTRANS], *p; - int len; - enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; - buf.data = pong; - while (enet_socket_wait(pongsock, &events, 0) >= 0 && events) { - buf.dataLength = sizeof(pong); - len = enet_socket_receive(pongsock, &addr, &buf, 1); - if (len < 0) - return; - p = &pong[len]; - putint(&p, PROTOCOL_VERSION); - putint(&p, mode); - putint(&p, numplayers); - putint(&p, minremain); - OFString *mname = - [OFString stringWithFormat:@"%@%@", - (isfull ? @"[FULL] " : @""), smapname]; - sendstring(mname, &p); - sendstring(serverdesc, &p); - buf.dataLength = p - pong; - enet_socket_send(pongsock, &addr, &buf, 1); - } -} - -void -servermsinit(OFString *master_, OFString *sdesc, bool listen) -{ - const char *master = master_.UTF8String; - const char *mid = strstr(master, "/"); - if (!mid) - mid = master; - masterpath = @(mid); - masterbase = [OFString stringWithUTF8String:master length:mid - master]; - serverdesc = sdesc; - - if (listen) { - ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; - pongsock = - enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, &address); - if (pongsock == ENET_SOCKET_NULL) - fatal(@"could not create server info socket\n"); - } -} ADDED src/serverutil.m Index: src/serverutil.m ================================================================== --- /dev/null +++ src/serverutil.m @@ -0,0 +1,205 @@ +// misc useful functions used by the server + +#include "cube.h" + +// all network traffic is in 32bit ints, which are then compressed using the +// following simple scheme (assumes that most values are small). + +void +putint(uchar **p, int n) +{ + if (n < 128 && n > -127) { + *(*p)++ = n; + } else if (n < 0x8000 && n >= -0x8000) { + *(*p)++ = 0x80; + *(*p)++ = n; + *(*p)++ = n >> 8; + } else { + *(*p)++ = 0x81; + *(*p)++ = n; + *(*p)++ = n >> 8; + *(*p)++ = n >> 16; + *(*p)++ = n >> 24; + } +} + +int +getint(uchar **p) +{ + int c = *((char *)*p); + (*p)++; + if (c == -128) { + int n = *(*p)++; + n |= *((char *)*p) << 8; + (*p)++; + return n; + } else if (c == -127) { + int n = *(*p)++; + n |= *(*p)++ << 8; + n |= *(*p)++ << 16; + return n | (*(*p)++ << 24); + } else + return c; +} + +void +sendstring(OFString *t_, uchar **p) +{ + const char *t = t_.UTF8String; + + for (size_t i = 0; i < _MAXDEFSTR && *t != '\0'; i++) + putint(p, *t++); + + putint(p, 0); +} + +static const OFString *modenames[] = { + @"SP", + @"DMSP", + @"ffa/default", + @"coopedit", + @"ffa/duel", + @"teamplay", + @"instagib", + @"instagib team", + @"efficiency", + @"efficiency team", + @"insta arena", + @"insta clan arena", + @"tactics arena", + @"tactics clan arena", +}; + +OFString * +modestr(int n) +{ + return (n >= -2 && n < 12) ? modenames[n + 2] : @"unknown"; +} +// size inclusive message token, 0 for variable or not-checked sizes +char msgsizesl[] = { SV_INITS2C, 4, SV_INITC2S, 0, SV_POS, 12, SV_TEXT, 0, + SV_SOUND, 2, SV_CDIS, 2, SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, + SV_EDITD, 6, SV_EDITE, 6, SV_DIED, 2, SV_DAMAGE, 4, SV_SHOT, 8, + SV_FRAGS, 2, SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 3, + SV_DENIED, 2, SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, + SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_ITEMACC, 2, + SV_SENDMAP, 0, SV_RECVMAP, 1, SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_EXT, 0, + -1 }; + +char +msgsizelookup(int msg) +{ + for (char *p = msgsizesl; *p >= 0; p += 2) + if (*p == msg) + return p[1]; + return -1; +} + +// sending of maps between clients + +static OFString *copyname; +int copysize; +uchar *copydata = NULL; + +void +sendmaps(int n, OFString *mapname, int mapsize, uchar *mapdata) +{ + if (mapsize <= 0 || mapsize > 256 * 256) + return; + copyname = mapname; + copysize = mapsize; + if (copydata) + OFFreeMemory(copydata); + copydata = (uchar *)OFAllocMemory(1, mapsize); + memcpy(copydata, mapdata, mapsize); +} + +ENetPacket * +recvmap(int n) +{ + if (!copydata) + return NULL; + ENetPacket *packet = enet_packet_create( + NULL, MAXTRANS + copysize, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start + 2; + putint(&p, SV_RECVMAP); + sendstring(copyname, &p); + putint(&p, copysize); + memcpy(p, copydata, copysize); + p += copysize; + *(ushort *)start = ENET_HOST_TO_NET_16(p - start); + enet_packet_resize(packet, p - start); + return packet; +} + +#ifdef STANDALONE + +void +localservertoclient(uchar *buf, int len) +{ +} + +void +fatal(OFConstantString *s, ...) +{ + cleanupserver(); + + va_list args; + va_start(args, s); + OFString *msg = [[OFString alloc] initWithFormat:s arguments:args]; + va_end(args); + + [OFStdOut writeFormat:@"servererror: %@\n", msg]; + + exit(1); +} + +void * +alloc(int s) +{ + void *b = calloc(1, s); + if (!b) + fatal(@"no memory!"); + return b; +} + +int +main(int argc, char *argv[]) +{ + int uprate = 0, maxcl = 4; + const char *sdesc = "", *ip = "", *master = NULL, *passwd = ""; + + for (int i = 1; i < argc; i++) { + char *a = &argv[i][2]; + if (argv[i][0] == '-') + switch (argv[i][1]) { + case 'u': + uprate = atoi(a); + break; + case 'n': + sdesc = a; + break; + case 'i': + ip = a; + break; + case 'm': + master = a; + break; + case 'p': + passwd = a; + break; + case 'c': + maxcl = atoi(a); + break; + default: + printf("WARNING: unknown commandline option\n"); + } + } + + if (enet_initialize() < 0) + fatal(@"Unable to initialise network module"); + initserver(true, uprate, @(sdesc), @(ip), + (master != NULL ? @(master) : nil), @(passwd), maxcl); + return 0; +} +#endif DELETED src/serverutil.mm Index: src/serverutil.mm ================================================================== --- src/serverutil.mm +++ /dev/null @@ -1,205 +0,0 @@ -// misc useful functions used by the server - -#include "cube.h" - -// all network traffic is in 32bit ints, which are then compressed using the -// following simple scheme (assumes that most values are small). - -void -putint(uchar **p, int n) -{ - if (n < 128 && n > -127) { - *(*p)++ = n; - } else if (n < 0x8000 && n >= -0x8000) { - *(*p)++ = 0x80; - *(*p)++ = n; - *(*p)++ = n >> 8; - } else { - *(*p)++ = 0x81; - *(*p)++ = n; - *(*p)++ = n >> 8; - *(*p)++ = n >> 16; - *(*p)++ = n >> 24; - } -} - -int -getint(uchar **p) -{ - int c = *((char *)*p); - (*p)++; - if (c == -128) { - int n = *(*p)++; - n |= *((char *)*p) << 8; - (*p)++; - return n; - } else if (c == -127) { - int n = *(*p)++; - n |= *(*p)++ << 8; - n |= *(*p)++ << 16; - return n | (*(*p)++ << 24); - } else - return c; -} - -void -sendstring(OFString *t_, uchar **p) -{ - const char *t = t_.UTF8String; - - for (size_t i = 0; i < _MAXDEFSTR && *t != '\0'; i++) - putint(p, *t++); - - putint(p, 0); -} - -static const OFString *modenames[] = { - @"SP", - @"DMSP", - @"ffa/default", - @"coopedit", - @"ffa/duel", - @"teamplay", - @"instagib", - @"instagib team", - @"efficiency", - @"efficiency team", - @"insta arena", - @"insta clan arena", - @"tactics arena", - @"tactics clan arena", -}; - -OFString * -modestr(int n) -{ - return (n >= -2 && n < 12) ? modenames[n + 2] : @"unknown"; -} -// size inclusive message token, 0 for variable or not-checked sizes -char msgsizesl[] = { SV_INITS2C, 4, SV_INITC2S, 0, SV_POS, 12, SV_TEXT, 0, - SV_SOUND, 2, SV_CDIS, 2, SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, - SV_EDITD, 6, SV_EDITE, 6, SV_DIED, 2, SV_DAMAGE, 4, SV_SHOT, 8, - SV_FRAGS, 2, SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 3, - SV_DENIED, 2, SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, - SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_ITEMACC, 2, - SV_SENDMAP, 0, SV_RECVMAP, 1, SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_EXT, 0, - -1 }; - -char -msgsizelookup(int msg) -{ - for (char *p = msgsizesl; *p >= 0; p += 2) - if (*p == msg) - return p[1]; - return -1; -} - -// sending of maps between clients - -static OFString *copyname; -int copysize; -uchar *copydata = NULL; - -void -sendmaps(int n, OFString *mapname, int mapsize, uchar *mapdata) -{ - if (mapsize <= 0 || mapsize > 256 * 256) - return; - copyname = mapname; - copysize = mapsize; - if (copydata) - OFFreeMemory(copydata); - copydata = (uchar *)OFAllocMemory(1, mapsize); - memcpy(copydata, mapdata, mapsize); -} - -ENetPacket * -recvmap(int n) -{ - if (!copydata) - return NULL; - ENetPacket *packet = enet_packet_create( - NULL, MAXTRANS + copysize, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start + 2; - putint(&p, SV_RECVMAP); - sendstring(copyname, &p); - putint(&p, copysize); - memcpy(p, copydata, copysize); - p += copysize; - *(ushort *)start = ENET_HOST_TO_NET_16(p - start); - enet_packet_resize(packet, p - start); - return packet; -} - -#ifdef STANDALONE - -void -localservertoclient(uchar *buf, int len) -{ -} - -void -fatal(OFConstantString *s, ...) -{ - cleanupserver(); - - va_list args; - va_start(args, s); - OFString *msg = [[OFString alloc] initWithFormat:s arguments:args]; - va_end(args); - - [OFStdOut writeFormat:@"servererror: %@\n", msg]; - - exit(1); -} - -void * -alloc(int s) -{ - void *b = calloc(1, s); - if (!b) - fatal(@"no memory!"); - return b; -} - -int -main(int argc, char *argv[]) -{ - int uprate = 0, maxcl = 4; - const char *sdesc = "", *ip = "", *master = NULL, *passwd = ""; - - for (int i = 1; i < argc; i++) { - char *a = &argv[i][2]; - if (argv[i][0] == '-') - switch (argv[i][1]) { - case 'u': - uprate = atoi(a); - break; - case 'n': - sdesc = a; - break; - case 'i': - ip = a; - break; - case 'm': - master = a; - break; - case 'p': - passwd = a; - break; - case 'c': - maxcl = atoi(a); - break; - default: - printf("WARNING: unknown commandline option\n"); - } - } - - if (enet_initialize() < 0) - fatal(@"Unable to initialise network module"); - initserver(true, uprate, @(sdesc), @(ip), - (master != NULL ? @(master) : nil), @(passwd), maxcl); - return 0; -} -#endif ADDED src/sound.m Index: src/sound.m ================================================================== --- /dev/null +++ src/sound.m @@ -0,0 +1,222 @@ +#include "cube.h" + +#import "DynamicEntity.h" + +#include + +VARP(soundvol, 0, 255, 255); +VARP(musicvol, 0, 128, 255); +bool nosound = false; + +#define MAXCHAN 32 +#define SOUNDFREQ 22050 +#define MAXVOL MIX_MAX_VOLUME + +struct soundloc { + OFVector3D loc; + bool inuse; +} soundlocs[MAXCHAN]; + +static Mix_Music *mod = NULL; + +void +stopsound() +{ + if (nosound) + return; + + if (mod != NULL) { + Mix_HaltMusic(); + Mix_FreeMusic(mod); + mod = NULL; + } +} + +VAR(soundbufferlen, 128, 1024, 4096); + +void +initsound() +{ + memset(soundlocs, 0, sizeof(struct soundloc) * MAXCHAN); + if (Mix_OpenAudio(SOUNDFREQ, MIX_DEFAULT_FORMAT, 2, soundbufferlen) < + 0) { + conoutf(@"sound init failed (SDL_mixer): %s", + (size_t)Mix_GetError()); + nosound = true; + } + Mix_AllocateChannels(MAXCHAN); +} + +void +music(OFString *name) +{ + if (nosound) + return; + + stopsound(); + + if (soundvol && musicvol) { + name = [name stringByReplacingOccurrencesOfString:@"\\" + withString:@"/"]; + OFString *path = + [OFString stringWithFormat:@"packages/%@", name]; + OFIRI *IRI = [Cube.sharedInstance.gameDataIRI + IRIByAppendingPathComponent:path]; + + if ((mod = Mix_LoadMUS( + IRI.fileSystemRepresentation.UTF8String)) != NULL) { + Mix_PlayMusic(mod, -1); + Mix_VolumeMusic((musicvol * MAXVOL) / 255); + } + } +} +COMMAND(music, ARG_1STR) + +static OFMutableData *samples; +static OFMutableArray *snames; + +int +registersound(OFString *name) +{ + int i = 0; + for (OFString *iter in snames) { + if ([iter isEqual:name]) + return i; + + i++; + } + + if (snames == nil) + snames = [[OFMutableArray alloc] init]; + if (samples == nil) + samples = [[OFMutableData alloc] + initWithItemSize:sizeof(Mix_Chunk *)]; + + [snames addObject:[name stringByReplacingOccurrencesOfString:@"\\" + withString:@"/"]]; + Mix_Chunk *sample = NULL; + [samples addItem:&sample]; + + return samples.count - 1; +} +COMMAND(registersound, ARG_1EST) + +void +cleansound() +{ + if (nosound) + return; + stopsound(); + Mix_CloseAudio(); +} + +VAR(stereo, 0, 1, 1); + +static void +updatechanvol(int chan, const OFVector3D *loc) +{ + int vol = soundvol, pan = 255 / 2; + if (loc) { + vdist(dist, v, *loc, player1.o); + vol -= (int)(dist * 3 * soundvol / + 255); // simple mono distance attenuation + if (stereo && (v.x != 0 || v.y != 0)) { + // relative angle of sound along X-Y axis + float yaw = + -atan2(v.x, v.y) - player1.yaw * (PI / 180.0f); + // range is from 0 (left) to 255 (right) + pan = (int)(255.9f * (0.5 * sin(yaw) + 0.5f)); + } + } + vol = (vol * MAXVOL) / 255; + Mix_Volume(chan, vol); + Mix_SetPanning(chan, 255 - pan, pan); +} + +static void +newsoundloc(int chan, const OFVector3D *loc) +{ + assert(chan >= 0 && chan < MAXCHAN); + soundlocs[chan].loc = *loc; + soundlocs[chan].inuse = true; +} + +void +updatevol() +{ + if (nosound) + return; + loopi(MAXCHAN) if (soundlocs[i].inuse) + { + if (Mix_Playing(i)) + updatechanvol(i, &soundlocs[i].loc); + else + soundlocs[i].inuse = false; + } +} + +void +playsoundc(int n) +{ + addmsg(0, 2, SV_SOUND, n); + playsound(n, NULL); +} + +int soundsatonce = 0, lastsoundmillis = 0; + +void +playsound(int n, const OFVector3D *loc) +{ + if (nosound) + return; + + if (!soundvol) + return; + + if (lastmillis == lastsoundmillis) + soundsatonce++; + else + soundsatonce = 1; + + lastsoundmillis = lastmillis; + + if (soundsatonce > 5) + // avoid bursts of sounds with heavy packetloss and in sp + return; + + if (n < 0 || n >= samples.count) { + conoutf(@"unregistered sound: %d", n); + return; + } + + Mix_Chunk **sample = (Mix_Chunk **)[samples mutableItemAtIndex:n]; + if (*sample == NULL) { + OFString *path = [OFString + stringWithFormat:@"packages/sounds/%@.wav", snames[n]]; + OFIRI *IRI = [Cube.sharedInstance.gameDataIRI + IRIByAppendingPathComponent:path]; + + *sample = Mix_LoadWAV(IRI.fileSystemRepresentation.UTF8String); + + if (*sample == NULL) { + conoutf(@"failed to load sample: %@", IRI.string); + return; + } + } + + int chan = Mix_PlayChannel(-1, *sample, 0); + if (chan < 0) + return; + + if (loc) + newsoundloc(chan, loc); + + updatechanvol(chan, loc); +} + +void +sound(int n) +{ + playsound(n, NULL); +} +COMMAND(sound, ARG_1INT) DELETED src/sound.mm Index: src/sound.mm ================================================================== --- src/sound.mm +++ /dev/null @@ -1,222 +0,0 @@ -#include "cube.h" - -#import "DynamicEntity.h" - -#include - -VARP(soundvol, 0, 255, 255); -VARP(musicvol, 0, 128, 255); -bool nosound = false; - -#define MAXCHAN 32 -#define SOUNDFREQ 22050 -#define MAXVOL MIX_MAX_VOLUME - -struct soundloc { - OFVector3D loc; - bool inuse; -} soundlocs[MAXCHAN]; - -static Mix_Music *mod = NULL; - -void -stopsound() -{ - if (nosound) - return; - - if (mod != NULL) { - Mix_HaltMusic(); - Mix_FreeMusic(mod); - mod = NULL; - } -} - -VAR(soundbufferlen, 128, 1024, 4096); - -void -initsound() -{ - memset(soundlocs, 0, sizeof(soundloc) * MAXCHAN); - if (Mix_OpenAudio(SOUNDFREQ, MIX_DEFAULT_FORMAT, 2, soundbufferlen) < - 0) { - conoutf(@"sound init failed (SDL_mixer): %s", - (size_t)Mix_GetError()); - nosound = true; - } - Mix_AllocateChannels(MAXCHAN); -} - -void -music(OFString *name) -{ - if (nosound) - return; - - stopsound(); - - if (soundvol && musicvol) { - name = [name stringByReplacingOccurrencesOfString:@"\\" - withString:@"/"]; - OFString *path = - [OFString stringWithFormat:@"packages/%@", name]; - OFIRI *IRI = [Cube.sharedInstance.gameDataIRI - IRIByAppendingPathComponent:path]; - - if ((mod = Mix_LoadMUS( - IRI.fileSystemRepresentation.UTF8String)) != NULL) { - Mix_PlayMusic(mod, -1); - Mix_VolumeMusic((musicvol * MAXVOL) / 255); - } - } -} -COMMAND(music, ARG_1STR) - -static OFMutableData *samples; -static OFMutableArray *snames; - -int -registersound(OFString *name) -{ - int i = 0; - for (OFString *iter in snames) { - if ([iter isEqual:name]) - return i; - - i++; - } - - if (snames == nil) - snames = [[OFMutableArray alloc] init]; - if (samples == nil) - samples = [[OFMutableData alloc] - initWithItemSize:sizeof(Mix_Chunk *)]; - - [snames addObject:[name stringByReplacingOccurrencesOfString:@"\\" - withString:@"/"]]; - Mix_Chunk *sample = NULL; - [samples addItem:&sample]; - - return samples.count - 1; -} -COMMAND(registersound, ARG_1EST) - -void -cleansound() -{ - if (nosound) - return; - stopsound(); - Mix_CloseAudio(); -} - -VAR(stereo, 0, 1, 1); - -static void -updatechanvol(int chan, const OFVector3D *loc) -{ - int vol = soundvol, pan = 255 / 2; - if (loc) { - vdist(dist, v, *loc, player1.o); - vol -= (int)(dist * 3 * soundvol / - 255); // simple mono distance attenuation - if (stereo && (v.x != 0 || v.y != 0)) { - // relative angle of sound along X-Y axis - float yaw = - -atan2(v.x, v.y) - player1.yaw * (PI / 180.0f); - // range is from 0 (left) to 255 (right) - pan = int(255.9f * (0.5 * sin(yaw) + 0.5f)); - } - } - vol = (vol * MAXVOL) / 255; - Mix_Volume(chan, vol); - Mix_SetPanning(chan, 255 - pan, pan); -} - -static void -newsoundloc(int chan, const OFVector3D *loc) -{ - assert(chan >= 0 && chan < MAXCHAN); - soundlocs[chan].loc = *loc; - soundlocs[chan].inuse = true; -} - -void -updatevol() -{ - if (nosound) - return; - loopi(MAXCHAN) if (soundlocs[i].inuse) - { - if (Mix_Playing(i)) - updatechanvol(i, &soundlocs[i].loc); - else - soundlocs[i].inuse = false; - } -} - -void -playsoundc(int n) -{ - addmsg(0, 2, SV_SOUND, n); - playsound(n, NULL); -} - -int soundsatonce = 0, lastsoundmillis = 0; - -void -playsound(int n, const OFVector3D *loc) -{ - if (nosound) - return; - - if (!soundvol) - return; - - if (lastmillis == lastsoundmillis) - soundsatonce++; - else - soundsatonce = 1; - - lastsoundmillis = lastmillis; - - if (soundsatonce > 5) - // avoid bursts of sounds with heavy packetloss and in sp - return; - - if (n < 0 || n >= samples.count) { - conoutf(@"unregistered sound: %d", n); - return; - } - - Mix_Chunk **sample = (Mix_Chunk **)[samples mutableItemAtIndex:n]; - if (*sample == NULL) { - OFString *path = [OFString - stringWithFormat:@"packages/sounds/%@.wav", snames[n]]; - OFIRI *IRI = [Cube.sharedInstance.gameDataIRI - IRIByAppendingPathComponent:path]; - - *sample = Mix_LoadWAV(IRI.fileSystemRepresentation.UTF8String); - - if (*sample == NULL) { - conoutf(@"failed to load sample: %@", IRI.string); - return; - } - } - - int chan = Mix_PlayChannel(-1, *sample, 0); - if (chan < 0) - return; - - if (loc) - newsoundloc(chan, loc); - - updatechanvol(chan, loc); -} - -void -sound(int n) -{ - playsound(n, NULL); -} -COMMAND(sound, ARG_1INT) ADDED src/tools.m Index: src/tools.m ================================================================== --- /dev/null +++ src/tools.m @@ -0,0 +1,20 @@ +// implementation of generic tools + +#include "tools.h" + +///////////////////////// misc tools /////////////////////// + +void +endianswap( + void *memory, int stride, int length) // little indians as storage format +{ + if (*((char *)&stride)) + return; + loop(w, length) loop(i, stride / 2) + { + uchar *p = (uchar *)memory + w * stride; + uchar t = p[i]; + p[i] = p[stride - i - 1]; + p[stride - i - 1] = t; + } +} DELETED src/tools.mm Index: src/tools.mm ================================================================== --- src/tools.mm +++ /dev/null @@ -1,21 +0,0 @@ -// implementation of generic tools - -#include "tools.h" -#include - -///////////////////////// misc tools /////////////////////// - -void -endianswap( - void *memory, int stride, int length) // little indians as storage format -{ - if (*((char *)&stride)) - return; - loop(w, length) loop(i, stride / 2) - { - uchar *p = (uchar *)memory + w * stride; - uchar t = p[i]; - p[i] = p[stride - i - 1]; - p[stride - i - 1] = t; - } -} ADDED src/weapon.m Index: src/weapon.m ================================================================== --- /dev/null +++ src/weapon.m @@ -0,0 +1,441 @@ +// weapon.cpp: all shooting and effects code + +#include "cube.h" + +#import "DynamicEntity.h" +#import "OFString+Cube.h" +#import "Projectile.h" + +static const int MONSTERDAMAGEFACTOR = 4; +static const int SGRAYS = 20; +static const float SGSPREAD = 2; +static OFVector3D sg[SGRAYS]; + +static const struct { + short sound, attackdelay, damage, projspeed, part, kickamount; + OFString *name; +} guns[NUMGUNS] = { + { S_PUNCH1, 250, 50, 0, 0, 1, @"fist" }, + { S_SG, 1400, 10, 0, 0, 20, @"shotgun" }, // *SGRAYS + { S_CG, 100, 30, 0, 0, 7, @"chaingun" }, + { S_RLFIRE, 800, 120, 80, 0, 10, @"rocketlauncher" }, + { S_RIFLE, 1500, 100, 0, 0, 30, @"rifle" }, + { S_FLAUNCH, 200, 20, 50, 4, 1, @"fireball" }, + { S_ICEBALL, 200, 40, 30, 6, 1, @"iceball" }, + { S_SLIMEBALL, 200, 30, 160, 7, 1, @"slimeball" }, + { S_PIGR1, 250, 50, 0, 0, 1, @"bite" }, +}; + +void +selectgun(int a, int b, int c) +{ + if (a < -1 || b < -1 || c < -1 || a >= NUMGUNS || b >= NUMGUNS || + c >= NUMGUNS) + return; + int s = player1.gunselect; + if (a >= 0 && s != a && player1.ammo[a]) + s = a; + else if (b >= 0 && s != b && player1.ammo[b]) + s = b; + else if (c >= 0 && s != c && player1.ammo[c]) + s = c; + else if (s != GUN_RL && player1.ammo[GUN_RL]) + s = GUN_RL; + else if (s != GUN_CG && player1.ammo[GUN_CG]) + s = GUN_CG; + else if (s != GUN_SG && player1.ammo[GUN_SG]) + s = GUN_SG; + else if (s != GUN_RIFLE && player1.ammo[GUN_RIFLE]) + s = GUN_RIFLE; + else + s = GUN_FIST; + if (s != player1.gunselect) + playsoundc(S_WEAPLOAD); + player1.gunselect = s; + // conoutf(@"%@ selected", (int)guns[s].name); +} + +int +reloadtime(int gun) +{ + return guns[gun].attackdelay; +} + +void +weapon(OFString *a1, OFString *a2, OFString *a3) +{ + selectgun((a1.length > 0 ? a1.cube_intValue : -1), + (a2.length > 0 ? a2.cube_intValue : -1), + (a3.length > 0 ? a3.cube_intValue : -1)); +} +COMMAND(weapon, ARG_3STR) + +// create random spread of rays for the shotgun +void +createrays(const OFVector3D *from, const OFVector3D *to) +{ + vdist(dist, dvec, *from, *to); + float f = dist * SGSPREAD / 1000; + loopi(SGRAYS) + { +#define RNDD (rnd(101) - 50) * f + OFVector3D r = OFMakeVector3D(RNDD, RNDD, RNDD); + sg[i] = *to; + vadd(sg[i], r); + } +} + +// if lineseg hits entity bounding box +static bool +intersect(DynamicEntity *d, const OFVector3D *from, const OFVector3D *to) +{ + OFVector3D v = *to, w = d.o; + const OFVector3D *p; + vsub(v, *from); + vsub(w, *from); + float c1 = dotprod(w, v); + + if (c1 <= 0) + p = from; + else { + float c2 = dotprod(v, v); + if (c2 <= c1) + p = to; + else { + float f = c1 / c2; + vmul(v, f); + vadd(v, *from); + p = &v; + } + } + + return (p->x <= d.o.x + d.radius && p->x >= d.o.x - d.radius && + p->y <= d.o.y + d.radius && p->y >= d.o.y - d.radius && + p->z <= d.o.z + d.aboveeye && p->z >= d.o.z - d.eyeheight); +} + +OFString * +playerincrosshair() +{ + if (demoplayback) + return NULL; + + for (id player in players) { + if (player == [OFNull null]) + continue; + + OFVector3D o = player1.o; + if (intersect(player, &o, &worldpos)) + return [player name]; + } + + return nil; +} + +static const size_t MAXPROJ = 100; +static Projectile *projs[MAXPROJ]; + +void +projreset() +{ + for (size_t i = 0; i < MAXPROJ; i++) + projs[i].inuse = false; +} + +void +newprojectile(const OFVector3D *from, const OFVector3D *to, float speed, + bool local, DynamicEntity *owner, int gun) +{ + for (size_t i = 0; i < MAXPROJ; i++) { + Projectile *p = projs[i]; + + if (p == nil) + projs[i] = p = [Projectile projectile]; + + if (p.inuse) + continue; + + p.inuse = true; + p.o = *from; + p.to = *to; + p.speed = speed; + p.local = local; + p.owner = owner; + p.gun = gun; + return; + } +} + +void +hit(int target, int damage, DynamicEntity *d, DynamicEntity *at) +{ + OFVector3D o = d.o; + if (d == player1) + selfdamage(damage, at == player1 ? -1 : -2, at); + else if (d.monsterstate) + monsterpain(d, damage, at); + else { + addmsg(1, 4, SV_DAMAGE, target, damage, d.lifesequence); + playsound(S_PAIN1 + rnd(5), &o); + } + particle_splash(3, damage, 1000, &o); + demodamage(damage, &o); +} + +const float RL_RADIUS = 5; +const float RL_DAMRAD = 7; // hack + +static void +radialeffect( + DynamicEntity *o, const OFVector3D *v, int cn, int qdam, DynamicEntity *at) +{ + if (o.state != CS_ALIVE) + return; + vdist(dist, temp, *v, o.o); + dist -= 2; // account for eye distance imprecision + if (dist < RL_DAMRAD) { + if (dist < 0) + dist = 0; + int damage = (int)(qdam * (1 - (dist / RL_DAMRAD))); + hit(cn, damage, o, at); + vmul(temp, (RL_DAMRAD - dist) * damage / 800); + vadd(o.vel, temp); + } +} + +void +splash(Projectile *p, const OFVector3D *v, const OFVector3D *vold, + int notthisplayer, int notthismonster, int qdam) +{ + particle_splash(0, 50, 300, v); + p.inuse = false; + + if (p.gun != GUN_RL) { + playsound(S_FEXPLODE, v); + // no push? + } else { + playsound(S_RLHIT, v); + newsphere(v, RL_RADIUS, 0); + dodynlight(vold, v, 0, 0, p.owner); + + if (!p.local) + return; + + radialeffect(player1, v, -1, qdam, p.owner); + + [players enumerateObjectsUsingBlock:^( + id player, size_t i, bool *stop) { + if (i == notthisplayer) + return; + + if (player == [OFNull null]) + return; + + radialeffect(player, v, i, qdam, p.owner); + }]; + + [getmonsters() enumerateObjectsUsingBlock:^( + DynamicEntity *monster, size_t i, bool *stop) { + if (i != notthismonster) + radialeffect(monster, v, i, qdam, p.owner); + }]; + } +} + +static inline void +projdamage(DynamicEntity *o, Projectile *p, const OFVector3D *v, int i, int im, + int qdam) +{ + if (o.state != CS_ALIVE) + return; + + OFVector3D po = p.o; + if (intersect(o, &po, v)) { + splash(p, v, &po, i, im, qdam); + hit(i, qdam, o, p.owner); + } +} + +void +moveprojectiles(float time) +{ + for (size_t i = 0; i < MAXPROJ; i++) { + Projectile *p = projs[i]; + + if (!p.inuse) + continue; + + int qdam = guns[p.gun].damage * (p.owner.quadmillis ? 4 : 1); + if (p.owner.monsterstate) + qdam /= MONSTERDAMAGEFACTOR; + vdist(dist, v, p.o, p.to); + float dtime = dist * 1000 / p.speed; + if (time > dtime) + dtime = time; + vmul(v, time / dtime); + vadd(v, p.o); + if (p.local) { + for (id player in players) + if (player != [OFNull null]) + projdamage(player, p, &v, i, -1, qdam); + + if (p.owner != player1) + projdamage(player1, p, &v, -1, -1, qdam); + + for (DynamicEntity *monster in getmonsters()) + if (!vreject(monster.o, v, 10.0f) && + monster != p.owner) + projdamage(monster, p, &v, -1, i, qdam); + } + if (p.inuse) { + OFVector3D po = p.o; + + if (time == dtime) + splash(p, &v, &po, -1, -1, qdam); + else { + if (p.gun == GUN_RL) { + dodynlight(&po, &v, 0, 255, p.owner); + particle_splash(5, 2, 200, &v); + } else { + particle_splash(1, 1, 200, &v); + particle_splash( + guns[p.gun].part, 1, 1, &v); + } + } + } + p.o = v; + } +} + +// create visual effect from a shot +void +shootv(int gun, const OFVector3D *from, const OFVector3D *to, DynamicEntity *d, + bool local) +{ + OFVector3D loc = d.o; + playsound(guns[gun].sound, d == player1 ? NULL : &loc); + int pspeed = 25; + switch (gun) { + case GUN_FIST: + break; + + case GUN_SG: { + loopi(SGRAYS) particle_splash(0, 5, 200, &sg[i]); + break; + } + + case GUN_CG: + particle_splash(0, 100, 250, to); + // particle_trail(1, 10, from, to); + break; + + case GUN_RL: + case GUN_FIREBALL: + case GUN_ICEBALL: + case GUN_SLIMEBALL: + pspeed = guns[gun].projspeed; + if (d.monsterstate) + pspeed /= 2; + newprojectile(from, to, (float)pspeed, local, d, gun); + break; + + case GUN_RIFLE: + particle_splash(0, 50, 200, to); + particle_trail(1, 500, from, to); + break; + } +} + +void +hitpush(int target, int damage, DynamicEntity *d, DynamicEntity *at, + const OFVector3D *from, const OFVector3D *to) +{ + hit(target, damage, d, at); + vdist(dist, v, *from, *to); + vmul(v, damage / dist / 50); + vadd(d.vel, v); +} + +void +raydamage(DynamicEntity *o, const OFVector3D *from, const OFVector3D *to, + DynamicEntity *d, int i) +{ + if (o.state != CS_ALIVE) + return; + int qdam = guns[d.gunselect].damage; + if (d.quadmillis) + qdam *= 4; + if (d.monsterstate) + qdam /= MONSTERDAMAGEFACTOR; + if (d.gunselect == GUN_SG) { + int damage = 0; + loop(r, SGRAYS) if (intersect(o, from, &sg[r])) damage += qdam; + if (damage) + hitpush(i, damage, o, d, from, to); + } else if (intersect(o, from, to)) + hitpush(i, qdam, o, d, from, to); +} + +void +shoot(DynamicEntity *d, const OFVector3D *targ) +{ + int attacktime = lastmillis - d.lastaction; + if (attacktime < d.gunwait) + return; + d.gunwait = 0; + if (!d.attacking) + return; + d.lastaction = lastmillis; + d.lastattackgun = d.gunselect; + if (!d.ammo[d.gunselect]) { + playsoundc(S_NOAMMO); + d.gunwait = 250; + d.lastattackgun = -1; + return; + } + if (d.gunselect) + d.ammo[d.gunselect]--; + OFVector3D from = d.o; + OFVector3D to = *targ; + from.z -= 0.2f; // below eye + + vdist(dist, unitv, from, to); + vdiv(unitv, dist); + OFVector3D kickback = unitv; + vmul(kickback, guns[d.gunselect].kickamount * -0.01f); + vadd(d.vel, kickback); + if (d.pitch < 80.0f) + d.pitch += guns[d.gunselect].kickamount * 0.05f; + + if (d.gunselect == GUN_FIST || d.gunselect == GUN_BITE) { + vmul(unitv, 3); // punch range + to = from; + vadd(to, unitv); + } + if (d.gunselect == GUN_SG) + createrays(&from, &to); + + if (d.quadmillis && attacktime > 200) + playsoundc(S_ITEMPUP); + shootv(d.gunselect, &from, &to, d, true); + if (!d.monsterstate) + addmsg(1, 8, SV_SHOT, d.gunselect, (int)(from.x * DMF), + (int)(from.y * DMF), (int)(from.z * DMF), (int)(to.x * DMF), + (int)(to.y * DMF), (int)(to.z * DMF)); + d.gunwait = guns[d.gunselect].attackdelay; + + if (guns[d.gunselect].projspeed) + return; + + [players enumerateObjectsUsingBlock:^(id player, size_t i, bool *stop) { + if (player != [OFNull null]) + raydamage(player, &from, &to, d, i); + }]; + + for (DynamicEntity *monster in getmonsters()) + if (monster != d) + raydamage(monster, &from, &to, d, -2); + + if (d.monsterstate) + raydamage(player1, &from, &to, d, -1); +} DELETED src/weapon.mm Index: src/weapon.mm ================================================================== --- src/weapon.mm +++ /dev/null @@ -1,441 +0,0 @@ -// weapon.cpp: all shooting and effects code - -#include "cube.h" - -#import "DynamicEntity.h" -#import "OFString+Cube.h" -#import "Projectile.h" - -static const int MONSTERDAMAGEFACTOR = 4; -static const int SGRAYS = 20; -static const float SGSPREAD = 2; -static OFVector3D sg[SGRAYS]; - -static const struct { - short sound, attackdelay, damage, projspeed, part, kickamount; - OFString *name; -} guns[NUMGUNS] = { - { S_PUNCH1, 250, 50, 0, 0, 1, @"fist" }, - { S_SG, 1400, 10, 0, 0, 20, @"shotgun" }, // *SGRAYS - { S_CG, 100, 30, 0, 0, 7, @"chaingun" }, - { S_RLFIRE, 800, 120, 80, 0, 10, @"rocketlauncher" }, - { S_RIFLE, 1500, 100, 0, 0, 30, @"rifle" }, - { S_FLAUNCH, 200, 20, 50, 4, 1, @"fireball" }, - { S_ICEBALL, 200, 40, 30, 6, 1, @"iceball" }, - { S_SLIMEBALL, 200, 30, 160, 7, 1, @"slimeball" }, - { S_PIGR1, 250, 50, 0, 0, 1, @"bite" }, -}; - -void -selectgun(int a, int b, int c) -{ - if (a < -1 || b < -1 || c < -1 || a >= NUMGUNS || b >= NUMGUNS || - c >= NUMGUNS) - return; - int s = player1.gunselect; - if (a >= 0 && s != a && player1.ammo[a]) - s = a; - else if (b >= 0 && s != b && player1.ammo[b]) - s = b; - else if (c >= 0 && s != c && player1.ammo[c]) - s = c; - else if (s != GUN_RL && player1.ammo[GUN_RL]) - s = GUN_RL; - else if (s != GUN_CG && player1.ammo[GUN_CG]) - s = GUN_CG; - else if (s != GUN_SG && player1.ammo[GUN_SG]) - s = GUN_SG; - else if (s != GUN_RIFLE && player1.ammo[GUN_RIFLE]) - s = GUN_RIFLE; - else - s = GUN_FIST; - if (s != player1.gunselect) - playsoundc(S_WEAPLOAD); - player1.gunselect = s; - // conoutf(@"%@ selected", (int)guns[s].name); -} - -int -reloadtime(int gun) -{ - return guns[gun].attackdelay; -} - -void -weapon(OFString *a1, OFString *a2, OFString *a3) -{ - selectgun((a1.length > 0 ? a1.cube_intValue : -1), - (a2.length > 0 ? a2.cube_intValue : -1), - (a3.length > 0 ? a3.cube_intValue : -1)); -} -COMMAND(weapon, ARG_3STR) - -// create random spread of rays for the shotgun -void -createrays(const OFVector3D *from, const OFVector3D *to) -{ - vdist(dist, dvec, *from, *to); - float f = dist * SGSPREAD / 1000; - loopi(SGRAYS) - { -#define RNDD (rnd(101) - 50) * f - OFVector3D r = OFMakeVector3D(RNDD, RNDD, RNDD); - sg[i] = *to; - vadd(sg[i], r); - } -} - -// if lineseg hits entity bounding box -static bool -intersect(DynamicEntity *d, const OFVector3D *from, const OFVector3D *to) -{ - OFVector3D v = *to, w = d.o; - const OFVector3D *p; - vsub(v, *from); - vsub(w, *from); - float c1 = dotprod(w, v); - - if (c1 <= 0) - p = from; - else { - float c2 = dotprod(v, v); - if (c2 <= c1) - p = to; - else { - float f = c1 / c2; - vmul(v, f); - vadd(v, *from); - p = &v; - } - } - - return (p->x <= d.o.x + d.radius && p->x >= d.o.x - d.radius && - p->y <= d.o.y + d.radius && p->y >= d.o.y - d.radius && - p->z <= d.o.z + d.aboveeye && p->z >= d.o.z - d.eyeheight); -} - -OFString * -playerincrosshair() -{ - if (demoplayback) - return NULL; - - for (id player in players) { - if (player == [OFNull null]) - continue; - - OFVector3D o = player1.o; - if (intersect(player, &o, &worldpos)) - return [player name]; - } - - return nil; -} - -static const size_t MAXPROJ = 100; -static Projectile *projs[MAXPROJ]; - -void -projreset() -{ - for (size_t i = 0; i < MAXPROJ; i++) - projs[i].inuse = false; -} - -void -newprojectile(const OFVector3D *from, const OFVector3D *to, float speed, - bool local, DynamicEntity *owner, int gun) -{ - for (size_t i = 0; i < MAXPROJ; i++) { - Projectile *p = projs[i]; - - if (p == nil) - projs[i] = p = [Projectile projectile]; - - if (p.inuse) - continue; - - p.inuse = true; - p.o = *from; - p.to = *to; - p.speed = speed; - p.local = local; - p.owner = owner; - p.gun = gun; - return; - } -} - -void -hit(int target, int damage, DynamicEntity *d, DynamicEntity *at) -{ - OFVector3D o = d.o; - if (d == player1) - selfdamage(damage, at == player1 ? -1 : -2, at); - else if (d.monsterstate) - monsterpain(d, damage, at); - else { - addmsg(1, 4, SV_DAMAGE, target, damage, d.lifesequence); - playsound(S_PAIN1 + rnd(5), &o); - } - particle_splash(3, damage, 1000, &o); - demodamage(damage, &o); -} - -const float RL_RADIUS = 5; -const float RL_DAMRAD = 7; // hack - -static void -radialeffect( - DynamicEntity *o, const OFVector3D *v, int cn, int qdam, DynamicEntity *at) -{ - if (o.state != CS_ALIVE) - return; - vdist(dist, temp, *v, o.o); - dist -= 2; // account for eye distance imprecision - if (dist < RL_DAMRAD) { - if (dist < 0) - dist = 0; - int damage = (int)(qdam * (1 - (dist / RL_DAMRAD))); - hit(cn, damage, o, at); - vmul(temp, (RL_DAMRAD - dist) * damage / 800); - vadd(o.vel, temp); - } -} - -void -splash(Projectile *p, const OFVector3D *v, const OFVector3D *vold, - int notthisplayer, int notthismonster, int qdam) -{ - particle_splash(0, 50, 300, v); - p.inuse = false; - - if (p.gun != GUN_RL) { - playsound(S_FEXPLODE, v); - // no push? - } else { - playsound(S_RLHIT, v); - newsphere(v, RL_RADIUS, 0); - dodynlight(vold, v, 0, 0, p.owner); - - if (!p.local) - return; - - radialeffect(player1, v, -1, qdam, p.owner); - - [players enumerateObjectsUsingBlock:^( - id player, size_t i, bool *stop) { - if (i == notthisplayer) - return; - - if (player == [OFNull null]) - return; - - radialeffect(player, v, i, qdam, p.owner); - }]; - - [getmonsters() enumerateObjectsUsingBlock:^( - DynamicEntity *monster, size_t i, bool *stop) { - if (i != notthismonster) - radialeffect(monster, v, i, qdam, p.owner); - }]; - } -} - -inline void -projdamage(DynamicEntity *o, Projectile *p, const OFVector3D *v, int i, int im, - int qdam) -{ - if (o.state != CS_ALIVE) - return; - - OFVector3D po = p.o; - if (intersect(o, &po, v)) { - splash(p, v, &po, i, im, qdam); - hit(i, qdam, o, p.owner); - } -} - -void -moveprojectiles(float time) -{ - for (size_t i = 0; i < MAXPROJ; i++) { - Projectile *p = projs[i]; - - if (!p.inuse) - continue; - - int qdam = guns[p.gun].damage * (p.owner.quadmillis ? 4 : 1); - if (p.owner.monsterstate) - qdam /= MONSTERDAMAGEFACTOR; - vdist(dist, v, p.o, p.to); - float dtime = dist * 1000 / p.speed; - if (time > dtime) - dtime = time; - vmul(v, time / dtime); - vadd(v, p.o); - if (p.local) { - for (id player in players) - if (player != [OFNull null]) - projdamage(player, p, &v, i, -1, qdam); - - if (p.owner != player1) - projdamage(player1, p, &v, -1, -1, qdam); - - for (DynamicEntity *monster in getmonsters()) - if (!vreject(monster.o, v, 10.0f) && - monster != p.owner) - projdamage(monster, p, &v, -1, i, qdam); - } - if (p.inuse) { - OFVector3D po = p.o; - - if (time == dtime) - splash(p, &v, &po, -1, -1, qdam); - else { - if (p.gun == GUN_RL) { - dodynlight(&po, &v, 0, 255, p.owner); - particle_splash(5, 2, 200, &v); - } else { - particle_splash(1, 1, 200, &v); - particle_splash( - guns[p.gun].part, 1, 1, &v); - } - } - } - p.o = v; - } -} - -// create visual effect from a shot -void -shootv(int gun, const OFVector3D *from, const OFVector3D *to, DynamicEntity *d, - bool local) -{ - OFVector3D loc = d.o; - playsound(guns[gun].sound, d == player1 ? NULL : &loc); - int pspeed = 25; - switch (gun) { - case GUN_FIST: - break; - - case GUN_SG: { - loopi(SGRAYS) particle_splash(0, 5, 200, &sg[i]); - break; - } - - case GUN_CG: - particle_splash(0, 100, 250, to); - // particle_trail(1, 10, from, to); - break; - - case GUN_RL: - case GUN_FIREBALL: - case GUN_ICEBALL: - case GUN_SLIMEBALL: - pspeed = guns[gun].projspeed; - if (d.monsterstate) - pspeed /= 2; - newprojectile(from, to, (float)pspeed, local, d, gun); - break; - - case GUN_RIFLE: - particle_splash(0, 50, 200, to); - particle_trail(1, 500, from, to); - break; - } -} - -void -hitpush(int target, int damage, DynamicEntity *d, DynamicEntity *at, - const OFVector3D *from, const OFVector3D *to) -{ - hit(target, damage, d, at); - vdist(dist, v, *from, *to); - vmul(v, damage / dist / 50); - vadd(d.vel, v); -} - -void -raydamage(DynamicEntity *o, const OFVector3D *from, const OFVector3D *to, - DynamicEntity *d, int i) -{ - if (o.state != CS_ALIVE) - return; - int qdam = guns[d.gunselect].damage; - if (d.quadmillis) - qdam *= 4; - if (d.monsterstate) - qdam /= MONSTERDAMAGEFACTOR; - if (d.gunselect == GUN_SG) { - int damage = 0; - loop(r, SGRAYS) if (intersect(o, from, &sg[r])) damage += qdam; - if (damage) - hitpush(i, damage, o, d, from, to); - } else if (intersect(o, from, to)) - hitpush(i, qdam, o, d, from, to); -} - -void -shoot(DynamicEntity *d, const OFVector3D *targ) -{ - int attacktime = lastmillis - d.lastaction; - if (attacktime < d.gunwait) - return; - d.gunwait = 0; - if (!d.attacking) - return; - d.lastaction = lastmillis; - d.lastattackgun = d.gunselect; - if (!d.ammo[d.gunselect]) { - playsoundc(S_NOAMMO); - d.gunwait = 250; - d.lastattackgun = -1; - return; - } - if (d.gunselect) - d.ammo[d.gunselect]--; - OFVector3D from = d.o; - OFVector3D to = *targ; - from.z -= 0.2f; // below eye - - vdist(dist, unitv, from, to); - vdiv(unitv, dist); - OFVector3D kickback = unitv; - vmul(kickback, guns[d.gunselect].kickamount * -0.01f); - vadd(d.vel, kickback); - if (d.pitch < 80.0f) - d.pitch += guns[d.gunselect].kickamount * 0.05f; - - if (d.gunselect == GUN_FIST || d.gunselect == GUN_BITE) { - vmul(unitv, 3); // punch range - to = from; - vadd(to, unitv); - } - if (d.gunselect == GUN_SG) - createrays(&from, &to); - - if (d.quadmillis && attacktime > 200) - playsoundc(S_ITEMPUP); - shootv(d.gunselect, &from, &to, d, true); - if (!d.monsterstate) - addmsg(1, 8, SV_SHOT, d.gunselect, (int)(from.x * DMF), - (int)(from.y * DMF), (int)(from.z * DMF), (int)(to.x * DMF), - (int)(to.y * DMF), (int)(to.z * DMF)); - d.gunwait = guns[d.gunselect].attackdelay; - - if (guns[d.gunselect].projspeed) - return; - - [players enumerateObjectsUsingBlock:^(id player, size_t i, bool *stop) { - if (player != [OFNull null]) - raydamage(player, &from, &to, d, i); - }]; - - for (DynamicEntity *monster in getmonsters()) - if (monster != d) - raydamage(monster, &from, &to, d, -2); - - if (d.monsterstate) - raydamage(player1, &from, &to, d, -1); -}