Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -11,13 +11,20 @@ BUILDSYS_INIT AC_PROG_CC AC_PROG_CPP +AC_PROG_OBJCXX(clang++ g++) +AC_PROG_OBJCXXCPP -AC_PROG_CXX -AC_PROG_CXXCPP +AC_CHECK_TOOL(OBJFW_CONFIG, objfw-config) +AS_IF([test x"$OBJFW_CONFIG" = x""], [ + AC_MSG_ERROR(You need ObjFW and objfw-config installed!) +]) +OBJCXXFLAGS="$OBJCXXFLAGS $($OBJFW_CONFIG --cppflags --objcflags)" +LDFLAGS="$($OBJFW_CONFIG --ldflags --rpath)" +LIBS="$($OBJFW_CONFIG --libs) $LIBS" AC_PATH_TOOL(AR, ar) AC_PATH_TOOL(RANLIB, ranlib) PKG_CHECK_MODULES(SDL, [ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -1,39 +1,39 @@ PROG = client -SRCS = client.cxx \ - clientextras.cxx \ - clientgame.cxx \ - clients2c.cxx \ - command.cxx \ - console.cxx \ - editing.cxx \ - entities.cxx \ - main.cxx \ - menus.cxx \ - monster.cxx \ - physics.cxx \ - rendercubes.cxx \ - renderextras.cxx \ - rendergl.cxx \ - rendermd2.cxx \ - renderparticles.cxx \ - rendertext.cxx \ - rndmap.cxx \ - savegamedemo.cxx \ - server.cxx \ - serverbrowser.cxx \ - serverms.cxx \ - serverutil.cxx \ - sound.cxx \ - tools.cxx \ - weapon.cxx \ - world.cxx \ - worldio.cxx \ - worldlight.cxx \ - worldocull.cxx \ - worldrender.cxx +SRCS = client.mm \ + clientextras.mm \ + clientgame.mm \ + clients2c.mm \ + command.mm \ + console.mm \ + editing.mm \ + entities.mm \ + main.mm \ + menus.mm \ + monster.mm \ + physics.mm \ + rendercubes.mm \ + renderextras.mm \ + rendergl.mm \ + rendermd2.mm \ + renderparticles.mm \ + rendertext.mm \ + rndmap.mm \ + savegamedemo.mm \ + server.mm \ + serverbrowser.mm \ + serverms.mm \ + serverutil.mm \ + sound.mm \ + tools.mm \ + weapon.mm \ + world.mm \ + worldio.mm \ + worldlight.mm \ + worldocull.mm \ + worldrender.mm include ../buildsys.mk include ../extra.mk CPPFLAGS += -I../enet/include \ @@ -45,7 +45,8 @@ LIBS += -L../enet -lenet \ ${SDL_LIBS} \ ${GL_LIBS} \ ${GLU_LIBS} \ ${X11_LIBS} \ - ${ZLIB_LIBS} -LD = ${CXX} + ${ZLIB_LIBS} \ + -lm +LD = ${OBJCXX} DELETED src/client.cxx Index: src/client.cxx ================================================================== --- src/client.cxx +++ /dev/null @@ -1,382 +0,0 @@ -// client.cpp, mostly network related client game code - -#include "cube.h" - -ENetHost *clienthost = NULL; -int connecting = 0; -int connattempts = 0; -int disconnecting = 0; -int clientnum = -1; // our client id in the game -bool c2sinit = false; // whether we need to tell the other clients our stats - -int -getclientnum() -{ - return clientnum; -}; - -bool -multiplayer() -{ - // check not correct on listen server? - if (clienthost) - conoutf("operation not available in multiplayer"); - return clienthost != NULL; -}; - -bool -allowedittoggle() -{ - bool allow = !clienthost || gamemode == 1; - if (!allow) - conoutf("editing in multiplayer requires coopedit mode (1)"); - return allow; -}; - -VARF(rate, 0, 0, 25000, - if (clienthost && (!rate || rate > 1000)) - enet_host_bandwidth_limit(clienthost, rate, rate)); - -void throttle(); - -VARF(throttle_interval, 0, 5, 30, throttle()); -VARF(throttle_accel, 0, 2, 32, throttle()); -VARF(throttle_decel, 0, 2, 32, throttle()); - -void -throttle() -{ - if (!clienthost || connecting) - return; - assert(ENET_PEER_PACKET_THROTTLE_SCALE == 32); - enet_peer_throttle_configure(clienthost->peers, - throttle_interval * 1000, throttle_accel, throttle_decel); -}; - -void -newname(char *name) -{ - c2sinit = false; - strn0cpy(player1->name, name, 16); -}; -void -newteam(char *name) -{ - c2sinit = false; - strn0cpy(player1->team, name, 5); -}; - -COMMANDN(team, newteam, ARG_1STR); -COMMANDN(name, newname, ARG_1STR); - -void -writeclientinfo(FILE *f) -{ - fprintf(f, "name \"%s\"\nteam \"%s\"\n", player1->name, player1->team); -}; - -void -connects(char *servername) -{ - disconnect(1); // reset state - addserver(servername); - - conoutf("attempting to connect to %s", servername); - ENetAddress address = {ENET_HOST_ANY, CUBE_SERVER_PORT}; - if (enet_address_set_host(&address, servername) < 0) { - conoutf("could not resolve server %s", servername); - return; - }; - - clienthost = enet_host_create(NULL, 1, rate, rate); - - if (clienthost) { - enet_host_connect(clienthost, &address, 1); - enet_host_flush(clienthost); - connecting = lastmillis; - connattempts = 0; - } else { - conoutf("could not connect to server"); - disconnect(); - }; -}; - -void -disconnect(int onlyclean, int async) -{ - if (clienthost) { - if (!connecting && !disconnecting) { - enet_peer_disconnect(clienthost->peers); - enet_host_flush(clienthost); - disconnecting = lastmillis; - }; - if (clienthost->peers->state != ENET_PEER_STATE_DISCONNECTED) { - if (async) - return; - enet_peer_reset(clienthost->peers); - }; - enet_host_destroy(clienthost); - }; - - if (clienthost && !connecting) - conoutf("disconnected"); - clienthost = NULL; - connecting = 0; - connattempts = 0; - disconnecting = 0; - clientnum = -1; - c2sinit = false; - player1->lifesequence = 0; - loopv(players) zapdynent(players[i]); - - localdisconnect(); - - if (!onlyclean) { - stop(); - localconnect(); - }; -}; - -void -trydisconnect() -{ - if (!clienthost) { - conoutf("not connected"); - return; - }; - if (connecting) { - conoutf("aborting connection attempt"); - disconnect(); - return; - }; - conoutf("attempting to disconnect..."); - disconnect(0, !disconnecting); -}; - -string ctext; -void -toserver(char *text) -{ - conoutf("%s:\f %s", player1->name, text); - strn0cpy(ctext, text, 80); -}; -void -echo(char *text) -{ - conoutf("%s", text); -}; - -COMMAND(echo, ARG_VARI); -COMMANDN(say, toserver, ARG_VARI); -COMMANDN(connect, connects, ARG_1STR); -COMMANDN(disconnect, trydisconnect, ARG_NONE); - -// collect c2s messages conveniently - -vector messages; - -void -addmsg(int rel, int num, int type, ...) -{ - if (demoplayback) - return; - if (num != msgsizelookup(type)) { - sprintf_sd(s)("inconsistant msg size for %d (%d != %d)", type, - num, msgsizelookup(type)); - fatal(s); - }; - if (messages.length() == 100) { - conoutf("command flood protection (type %d)", type); - return; - }; - ivector &msg = messages.add(); - msg.add(num); - msg.add(rel); - msg.add(type); - va_list marker; - va_start(marker, type); - loopi(num - 1) msg.add(va_arg(marker, int)); - va_end(marker); -}; - -void -server_err() -{ - conoutf("server network error, disconnecting..."); - disconnect(); -}; - -int lastupdate = 0, lastping = 0; -string toservermap; -bool senditemstoserver = - false; // after a map change, since server doesn't have map data - -string clientpassword; -void -password(char *p) -{ - strcpy_s(clientpassword, p); -}; -COMMAND(password, ARG_1STR); - -bool -netmapstart() -{ - senditemstoserver = true; - return clienthost != NULL; -}; - -void -initclientnet() -{ - ctext[0] = 0; - toservermap[0] = 0; - clientpassword[0] = 0; - newname("unnamed"); - newteam("red"); -}; - -void -sendpackettoserv(void *packet) -{ - if (clienthost) { - enet_host_broadcast(clienthost, 0, (ENetPacket *)packet); - enet_host_flush(clienthost); - } else - localclienttoserver((ENetPacket *)packet); -} - -void -c2sinfo(dynent *d) // send update to the server -{ - if (clientnum < 0) - return; // we haven't had a welcome message from the server yet - if (lastmillis - lastupdate < 40) - return; // don't update faster than 25fps - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, 0); - uchar *start = packet->data; - uchar *p = start + 2; - bool serveriteminitdone = false; - if (toservermap[0]) // suggest server to change map - { // do this exclusively as map change may invalidate rest of update - packet->flags = ENET_PACKET_FLAG_RELIABLE; - putint(p, SV_MAPCHANGE); - sendstring(toservermap, p); - toservermap[0] = 0; - putint(p, nextmode); - } else { - putint(p, SV_POS); - putint(p, clientnum); - putint( - p, (int)(d->o.x * DMF)); // quantize coordinates to 1/16th - // of a cube, between 1 and 3 bytes - putint(p, (int)(d->o.y * DMF)); - putint(p, (int)(d->o.z * DMF)); - putint(p, (int)(d->yaw * DAF)); - putint(p, (int)(d->pitch * DAF)); - putint(p, (int)(d->roll * DAF)); - putint( - p, (int)(d->vel.x * - DVF)); // quantize to 1/100, almost always 1 byte - putint(p, (int)(d->vel.y * DVF)); - putint(p, (int)(d->vel.z * DVF)); - // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 - putint(p, (d->strafe & 3) | ((d->move & 3) << 2) | - (((int)d->onfloor) << 4) | - ((editmode ? CS_EDITING : d->state) << 5)); - - if (senditemstoserver) { - packet->flags = ENET_PACKET_FLAG_RELIABLE; - putint(p, SV_ITEMLIST); - if (!m_noitems) - putitems(p); - putint(p, -1); - senditemstoserver = false; - serveriteminitdone = true; - }; - if (ctext[0]) // player chat, not flood protected for now - { - packet->flags = ENET_PACKET_FLAG_RELIABLE; - putint(p, SV_TEXT); - sendstring(ctext, p); - ctext[0] = 0; - }; - if (!c2sinit) // tell other clients who I am - { - packet->flags = ENET_PACKET_FLAG_RELIABLE; - c2sinit = true; - putint(p, SV_INITC2S); - sendstring(player1->name, p); - sendstring(player1->team, p); - putint(p, player1->lifesequence); - }; - loopv(messages) // send messages collected during the previous - // frames - { - ivector &msg = messages[i]; - if (msg[1]) - packet->flags = ENET_PACKET_FLAG_RELIABLE; - loopi(msg[0]) putint(p, msg[i + 2]); - }; - messages.setsize(0); - if (lastmillis - lastping > 250) { - putint(p, SV_PING); - putint(p, lastmillis); - lastping = lastmillis; - }; - }; - *(ushort *)start = ENET_HOST_TO_NET_16(p - start); - enet_packet_resize(packet, p - start); - incomingdemodata(start, p - start, true); - if (clienthost) { - enet_host_broadcast(clienthost, 0, packet); - enet_host_flush(clienthost); - } else - localclienttoserver(packet); - lastupdate = lastmillis; - if (serveriteminitdone) - loadgamerest(); // hack -}; - -void -gets2c() // get updates from the server -{ - ENetEvent event; - if (!clienthost) - return; - if (connecting && lastmillis / 3000 > connecting / 3000) { - conoutf("attempting to connect..."); - connecting = lastmillis; - ++connattempts; - if (connattempts > 3) { - conoutf("could not connect to server"); - disconnect(); - return; - }; - }; - while ( - clienthost != NULL && enet_host_service(clienthost, &event, 0) > 0) - switch (event.type) { - case ENET_EVENT_TYPE_CONNECT: - conoutf("connected to server"); - connecting = 0; - throttle(); - break; - - case ENET_EVENT_TYPE_RECEIVE: - if (disconnecting) - conoutf("attempting to disconnect..."); - else - localservertoclient(event.packet->data, - event.packet->dataLength); - enet_packet_destroy(event.packet); - break; - - case ENET_EVENT_TYPE_DISCONNECT: - if (disconnecting) - disconnect(); - else - server_err(); - return; - } -}; ADDED src/client.mm Index: src/client.mm ================================================================== --- /dev/null +++ src/client.mm @@ -0,0 +1,382 @@ +// client.cpp, mostly network related client game code + +#include "cube.h" + +ENetHost *clienthost = NULL; +int connecting = 0; +int connattempts = 0; +int disconnecting = 0; +int clientnum = -1; // our client id in the game +bool c2sinit = false; // whether we need to tell the other clients our stats + +int +getclientnum() +{ + return clientnum; +}; + +bool +multiplayer() +{ + // check not correct on listen server? + if (clienthost) + conoutf("operation not available in multiplayer"); + return clienthost != NULL; +}; + +bool +allowedittoggle() +{ + bool allow = !clienthost || gamemode == 1; + if (!allow) + conoutf("editing in multiplayer requires coopedit mode (1)"); + return allow; +}; + +VARF(rate, 0, 0, 25000, + if (clienthost && (!rate || rate > 1000)) + enet_host_bandwidth_limit(clienthost, rate, rate)); + +void throttle(); + +VARF(throttle_interval, 0, 5, 30, throttle()); +VARF(throttle_accel, 0, 2, 32, throttle()); +VARF(throttle_decel, 0, 2, 32, throttle()); + +void +throttle() +{ + if (!clienthost || connecting) + return; + assert(ENET_PEER_PACKET_THROTTLE_SCALE == 32); + enet_peer_throttle_configure(clienthost->peers, + throttle_interval * 1000, throttle_accel, throttle_decel); +}; + +void +newname(char *name) +{ + c2sinit = false; + strn0cpy(player1->name, name, 16); +}; +void +newteam(char *name) +{ + c2sinit = false; + strn0cpy(player1->team, name, 5); +}; + +COMMANDN(team, newteam, ARG_1STR); +COMMANDN(name, newname, ARG_1STR); + +void +writeclientinfo(FILE *f) +{ + fprintf(f, "name \"%s\"\nteam \"%s\"\n", player1->name, player1->team); +}; + +void +connects(char *servername) +{ + disconnect(1); // reset state + addserver(servername); + + conoutf("attempting to connect to %s", servername); + ENetAddress address = {ENET_HOST_ANY, CUBE_SERVER_PORT}; + if (enet_address_set_host(&address, servername) < 0) { + conoutf("could not resolve server %s", servername); + return; + }; + + clienthost = enet_host_create(NULL, 1, rate, rate); + + if (clienthost) { + enet_host_connect(clienthost, &address, 1); + enet_host_flush(clienthost); + connecting = lastmillis; + connattempts = 0; + } else { + conoutf("could not connect to server"); + disconnect(); + }; +}; + +void +disconnect(int onlyclean, int async) +{ + if (clienthost) { + if (!connecting && !disconnecting) { + enet_peer_disconnect(clienthost->peers); + enet_host_flush(clienthost); + disconnecting = lastmillis; + }; + if (clienthost->peers->state != ENET_PEER_STATE_DISCONNECTED) { + if (async) + return; + enet_peer_reset(clienthost->peers); + }; + enet_host_destroy(clienthost); + }; + + if (clienthost && !connecting) + conoutf("disconnected"); + clienthost = NULL; + connecting = 0; + connattempts = 0; + disconnecting = 0; + clientnum = -1; + c2sinit = false; + player1->lifesequence = 0; + loopv(players) zapdynent(players[i]); + + localdisconnect(); + + if (!onlyclean) { + stop(); + localconnect(); + }; +}; + +void +trydisconnect() +{ + if (!clienthost) { + conoutf("not connected"); + return; + }; + if (connecting) { + conoutf("aborting connection attempt"); + disconnect(); + return; + }; + conoutf("attempting to disconnect..."); + disconnect(0, !disconnecting); +}; + +string ctext; +void +toserver(char *text) +{ + conoutf("%s:\f %s", player1->name, text); + strn0cpy(ctext, text, 80); +}; +void +echo(char *text) +{ + conoutf("%s", text); +}; + +COMMAND(echo, ARG_VARI); +COMMANDN(say, toserver, ARG_VARI); +COMMANDN(connect, connects, ARG_1STR); +COMMANDN(disconnect, trydisconnect, ARG_NONE); + +// collect c2s messages conveniently + +vector messages; + +void +addmsg(int rel, int num, int type, ...) +{ + if (demoplayback) + return; + if (num != msgsizelookup(type)) { + sprintf_sd(s)("inconsistant msg size for %d (%d != %d)", type, + num, msgsizelookup(type)); + fatal(s); + }; + if (messages.length() == 100) { + conoutf("command flood protection (type %d)", type); + return; + }; + ivector &msg = messages.add(); + msg.add(num); + msg.add(rel); + msg.add(type); + va_list marker; + va_start(marker, type); + loopi(num - 1) msg.add(va_arg(marker, int)); + va_end(marker); +}; + +void +server_err() +{ + conoutf("server network error, disconnecting..."); + disconnect(); +}; + +int lastupdate = 0, lastping = 0; +string toservermap; +bool senditemstoserver = + false; // after a map change, since server doesn't have map data + +string clientpassword; +void +password(char *p) +{ + strcpy_s(clientpassword, p); +}; +COMMAND(password, ARG_1STR); + +bool +netmapstart() +{ + senditemstoserver = true; + return clienthost != NULL; +}; + +void +initclientnet() +{ + ctext[0] = 0; + toservermap[0] = 0; + clientpassword[0] = 0; + newname("unnamed"); + newteam("red"); +}; + +void +sendpackettoserv(void *packet) +{ + if (clienthost) { + enet_host_broadcast(clienthost, 0, (ENetPacket *)packet); + enet_host_flush(clienthost); + } else + localclienttoserver((ENetPacket *)packet); +} + +void +c2sinfo(dynent *d) // send update to the server +{ + if (clientnum < 0) + return; // we haven't had a welcome message from the server yet + if (lastmillis - lastupdate < 40) + return; // don't update faster than 25fps + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, 0); + uchar *start = packet->data; + uchar *p = start + 2; + bool serveriteminitdone = false; + if (toservermap[0]) // suggest server to change map + { // do this exclusively as map change may invalidate rest of update + packet->flags = ENET_PACKET_FLAG_RELIABLE; + putint(p, SV_MAPCHANGE); + sendstring(toservermap, p); + toservermap[0] = 0; + putint(p, nextmode); + } else { + putint(p, SV_POS); + putint(p, clientnum); + putint( + p, (int)(d->o.x * DMF)); // quantize coordinates to 1/16th + // of a cube, between 1 and 3 bytes + putint(p, (int)(d->o.y * DMF)); + putint(p, (int)(d->o.z * DMF)); + putint(p, (int)(d->yaw * DAF)); + putint(p, (int)(d->pitch * DAF)); + putint(p, (int)(d->roll * DAF)); + putint( + p, (int)(d->vel.x * + DVF)); // quantize to 1/100, almost always 1 byte + putint(p, (int)(d->vel.y * DVF)); + putint(p, (int)(d->vel.z * DVF)); + // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 + putint(p, (d->strafe & 3) | ((d->move & 3) << 2) | + (((int)d->onfloor) << 4) | + ((editmode ? CS_EDITING : d->state) << 5)); + + if (senditemstoserver) { + packet->flags = ENET_PACKET_FLAG_RELIABLE; + putint(p, SV_ITEMLIST); + if (!m_noitems) + putitems(p); + putint(p, -1); + senditemstoserver = false; + serveriteminitdone = true; + }; + if (ctext[0]) // player chat, not flood protected for now + { + packet->flags = ENET_PACKET_FLAG_RELIABLE; + putint(p, SV_TEXT); + sendstring(ctext, p); + ctext[0] = 0; + }; + if (!c2sinit) // tell other clients who I am + { + packet->flags = ENET_PACKET_FLAG_RELIABLE; + c2sinit = true; + putint(p, SV_INITC2S); + sendstring(player1->name, p); + sendstring(player1->team, p); + putint(p, player1->lifesequence); + }; + loopv(messages) // send messages collected during the previous + // frames + { + ivector &msg = messages[i]; + if (msg[1]) + packet->flags = ENET_PACKET_FLAG_RELIABLE; + loopi(msg[0]) putint(p, msg[i + 2]); + }; + messages.setsize(0); + if (lastmillis - lastping > 250) { + putint(p, SV_PING); + putint(p, lastmillis); + lastping = lastmillis; + }; + }; + *(ushort *)start = ENET_HOST_TO_NET_16(p - start); + enet_packet_resize(packet, p - start); + incomingdemodata(start, p - start, true); + if (clienthost) { + enet_host_broadcast(clienthost, 0, packet); + enet_host_flush(clienthost); + } else + localclienttoserver(packet); + lastupdate = lastmillis; + if (serveriteminitdone) + loadgamerest(); // hack +}; + +void +gets2c() // get updates from the server +{ + ENetEvent event; + if (!clienthost) + return; + if (connecting && lastmillis / 3000 > connecting / 3000) { + conoutf("attempting to connect..."); + connecting = lastmillis; + ++connattempts; + if (connattempts > 3) { + conoutf("could not connect to server"); + disconnect(); + return; + }; + }; + while ( + clienthost != NULL && enet_host_service(clienthost, &event, 0) > 0) + switch (event.type) { + case ENET_EVENT_TYPE_CONNECT: + conoutf("connected to server"); + connecting = 0; + throttle(); + break; + + case ENET_EVENT_TYPE_RECEIVE: + if (disconnecting) + conoutf("attempting to disconnect..."); + else + localservertoclient(event.packet->data, + event.packet->dataLength); + enet_packet_destroy(event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + if (disconnecting) + disconnect(); + else + server_err(); + return; + } +}; DELETED src/clientextras.cxx Index: src/clientextras.cxx ================================================================== --- src/clientextras.cxx +++ /dev/null @@ -1,214 +0,0 @@ -// clientextras.cpp: stuff that didn't fit in client.cpp or clientgame.cpp :) - -#include "cube.h" - -// render players & monsters -// very messy ad-hoc handling of animation frames, should be made more -// configurable - -// D D D D' D D D D' A A' P P' I I' -// R, R' E L J J' -int frame[] = {178, 184, 190, 137, 183, 189, 197, 164, 46, 51, 54, 32, 0, 0, 40, - 1, 162, 162, 67, 168}; -int range[] = {6, 6, 8, 28, 1, 1, 1, 1, 8, 19, 4, 18, 40, 1, 6, 15, 1, 1, 1, 1}; - -void -renderclient(dynent *d, bool team, char *mdlname, bool hellpig, float scale) -{ - int n = 3; - float speed = 100.0f; - float mz = d->o.z - d->eyeheight + 1.55f * scale; - int basetime = -((intptr_t)d & 0xFFF); - if (d->state == CS_DEAD) { - int r; - if (hellpig) { - n = 2; - r = range[3]; - } else { - n = (intptr_t)d % 3; - r = range[n]; - }; - basetime = d->lastaction; - int t = lastmillis - d->lastaction; - if (t < 0 || t > 20000) - return; - if (t > (r - 1) * 100) { - n += 4; - if (t > (r + 10) * 100) { - t -= (r + 10) * 100; - mz -= t * t / 10000000000.0f * t; - }; - }; - if (mz < -1000) - return; - // mdl = (((int)d>>6)&1)+1; - // mz = d->o.z-d->eyeheight+0.2f; - // scale = 1.2f; - } else if (d->state == CS_EDITING) { - n = 16; - } else if (d->state == CS_LAGGED) { - n = 17; - } else if (d->monsterstate == M_ATTACKING) { - n = 8; - } else if (d->monsterstate == M_PAIN) { - n = 10; - } else if ((!d->move && !d->strafe) || !d->moving) { - n = 12; - } else if (!d->onfloor && d->timeinair > 100) { - n = 18; - } else { - n = 14; - speed = 1200 / d->maxspeed * scale; - if (hellpig) - speed = 300 / d->maxspeed; - }; - if (hellpig) { - n++; - scale *= 32; - mz -= 1.9f; - }; - rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d->o.x, mz, d->o.y, - d->yaw + 90, d->pitch / 2, team, scale, speed, 0, basetime); -}; - -extern int democlientnum; - -void -renderclients() -{ - dynent *d; - loopv(players) if ((d = players[i]) && - (!demoplayback || i != democlientnum)) - renderclient( - d, isteam(player1->team, d->team), "monster/ogro", false, 1.0f); -}; - -// creation of scoreboard pseudo-menu - -bool scoreson = false; - -void -showscores(bool on) -{ - scoreson = on; - menuset(((int)on) - 1); -}; - -struct sline { - string s; -}; -vector scorelines; - -void -renderscore(dynent *d) -{ - sprintf_sd(lag)("%d", d->plag); - sprintf_sd(name)("(%s)", d->name); - sprintf_s(scorelines.add().s)("%d\t%s\t%d\t%s\t%s", d->frags, - d->state == CS_LAGGED ? "LAG" : lag, d->ping, d->team, - d->state == CS_DEAD ? name : d->name); - menumanual(0, scorelines.length() - 1, scorelines.last().s); -}; - -const int maxteams = 4; -char *teamname[maxteams]; -int teamscore[maxteams], teamsused; -string teamscores; -int timeremain = 0; - -void -addteamscore(dynent *d) -{ - if (!d) - return; - loopi(teamsused) if (strcmp(teamname[i], d->team) == 0) - { - teamscore[i] += d->frags; - return; - }; - if (teamsused == maxteams) - return; - teamname[teamsused] = d->team; - teamscore[teamsused++] = d->frags; -}; - -void -renderscores() -{ - if (!scoreson) - return; - scorelines.setsize(0); - if (!demoplayback) - renderscore(player1); - loopv(players) if (players[i]) renderscore(players[i]); - sortmenu(0, scorelines.length()); - if (m_teammode) { - teamsused = 0; - loopv(players) addteamscore(players[i]); - if (!demoplayback) - addteamscore(player1); - teamscores[0] = 0; - loopj(teamsused) - { - sprintf_sd(sc)("[ %s: %d ]", teamname[j], teamscore[j]); - strcat_s(teamscores, sc); - }; - menumanual(0, scorelines.length(), ""); - menumanual(0, scorelines.length() + 1, teamscores); - }; -}; - -// sendmap/getmap commands, should be replaced by more intuitive map downloading - -void -sendmap(char *mapname) -{ - if (*mapname) - save_world(mapname); - changemap(mapname); - mapname = getclientmap(); - int mapsize; - uchar *mapdata = readmap(mapname, &mapsize); - if (!mapdata) - return; - ENetPacket *packet = enet_packet_create( - NULL, MAXTRANS + mapsize, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start + 2; - putint(p, SV_SENDMAP); - sendstring(mapname, p); - putint(p, mapsize); - if (65535 - (p - start) < mapsize) { - conoutf("map %s is too large to send", mapname); - free(mapdata); - enet_packet_destroy(packet); - return; - }; - memcpy(p, mapdata, mapsize); - p += mapsize; - free(mapdata); - *(ushort *)start = ENET_HOST_TO_NET_16(p - start); - enet_packet_resize(packet, p - start); - sendpackettoserv(packet); - conoutf("sending map %s to server...", mapname); - sprintf_sd(msg)( - "[map %s uploaded to server, \"getmap\" to receive it]", mapname); - toserver(msg); -} - -void -getmap() -{ - ENetPacket *packet = - enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start + 2; - putint(p, SV_RECVMAP); - *(ushort *)start = ENET_HOST_TO_NET_16(p - start); - enet_packet_resize(packet, p - start); - sendpackettoserv(packet); - conoutf("requesting map from server..."); -} - -COMMAND(sendmap, ARG_1STR); -COMMAND(getmap, ARG_NONE); ADDED src/clientextras.mm Index: src/clientextras.mm ================================================================== --- /dev/null +++ src/clientextras.mm @@ -0,0 +1,214 @@ +// clientextras.cpp: stuff that didn't fit in client.cpp or clientgame.cpp :) + +#include "cube.h" + +// render players & monsters +// very messy ad-hoc handling of animation frames, should be made more +// configurable + +// D D D D' D D D D' A A' P P' I I' +// R, R' E L J J' +int frame[] = {178, 184, 190, 137, 183, 189, 197, 164, 46, 51, 54, 32, 0, 0, 40, + 1, 162, 162, 67, 168}; +int range[] = {6, 6, 8, 28, 1, 1, 1, 1, 8, 19, 4, 18, 40, 1, 6, 15, 1, 1, 1, 1}; + +void +renderclient(dynent *d, bool team, char *mdlname, bool hellpig, float scale) +{ + int n = 3; + float speed = 100.0f; + float mz = d->o.z - d->eyeheight + 1.55f * scale; + int basetime = -((intptr_t)d & 0xFFF); + if (d->state == CS_DEAD) { + int r; + if (hellpig) { + n = 2; + r = range[3]; + } else { + n = (intptr_t)d % 3; + r = range[n]; + }; + basetime = d->lastaction; + int t = lastmillis - d->lastaction; + if (t < 0 || t > 20000) + return; + if (t > (r - 1) * 100) { + n += 4; + if (t > (r + 10) * 100) { + t -= (r + 10) * 100; + mz -= t * t / 10000000000.0f * t; + }; + }; + if (mz < -1000) + return; + // mdl = (((int)d>>6)&1)+1; + // mz = d->o.z-d->eyeheight+0.2f; + // scale = 1.2f; + } else if (d->state == CS_EDITING) { + n = 16; + } else if (d->state == CS_LAGGED) { + n = 17; + } else if (d->monsterstate == M_ATTACKING) { + n = 8; + } else if (d->monsterstate == M_PAIN) { + n = 10; + } else if ((!d->move && !d->strafe) || !d->moving) { + n = 12; + } else if (!d->onfloor && d->timeinair > 100) { + n = 18; + } else { + n = 14; + speed = 1200 / d->maxspeed * scale; + if (hellpig) + speed = 300 / d->maxspeed; + }; + if (hellpig) { + n++; + scale *= 32; + mz -= 1.9f; + }; + rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d->o.x, mz, d->o.y, + d->yaw + 90, d->pitch / 2, team, scale, speed, 0, basetime); +}; + +extern int democlientnum; + +void +renderclients() +{ + dynent *d; + loopv(players) if ((d = players[i]) && + (!demoplayback || i != democlientnum)) + renderclient( + d, isteam(player1->team, d->team), "monster/ogro", false, 1.0f); +}; + +// creation of scoreboard pseudo-menu + +bool scoreson = false; + +void +showscores(bool on) +{ + scoreson = on; + menuset(((int)on) - 1); +}; + +struct sline { + string s; +}; +vector scorelines; + +void +renderscore(dynent *d) +{ + sprintf_sd(lag)("%d", d->plag); + sprintf_sd(name)("(%s)", d->name); + sprintf_s(scorelines.add().s)("%d\t%s\t%d\t%s\t%s", d->frags, + d->state == CS_LAGGED ? "LAG" : lag, d->ping, d->team, + d->state == CS_DEAD ? name : d->name); + menumanual(0, scorelines.length() - 1, scorelines.last().s); +}; + +const int maxteams = 4; +char *teamname[maxteams]; +int teamscore[maxteams], teamsused; +string teamscores; +int timeremain = 0; + +void +addteamscore(dynent *d) +{ + if (!d) + return; + loopi(teamsused) if (strcmp(teamname[i], d->team) == 0) + { + teamscore[i] += d->frags; + return; + }; + if (teamsused == maxteams) + return; + teamname[teamsused] = d->team; + teamscore[teamsused++] = d->frags; +}; + +void +renderscores() +{ + if (!scoreson) + return; + scorelines.setsize(0); + if (!demoplayback) + renderscore(player1); + loopv(players) if (players[i]) renderscore(players[i]); + sortmenu(0, scorelines.length()); + if (m_teammode) { + teamsused = 0; + loopv(players) addteamscore(players[i]); + if (!demoplayback) + addteamscore(player1); + teamscores[0] = 0; + loopj(teamsused) + { + sprintf_sd(sc)("[ %s: %d ]", teamname[j], teamscore[j]); + strcat_s(teamscores, sc); + }; + menumanual(0, scorelines.length(), ""); + menumanual(0, scorelines.length() + 1, teamscores); + }; +}; + +// sendmap/getmap commands, should be replaced by more intuitive map downloading + +void +sendmap(char *mapname) +{ + if (*mapname) + save_world(mapname); + changemap(mapname); + mapname = getclientmap(); + int mapsize; + uchar *mapdata = readmap(mapname, &mapsize); + if (!mapdata) + return; + ENetPacket *packet = enet_packet_create( + NULL, MAXTRANS + mapsize, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start + 2; + putint(p, SV_SENDMAP); + sendstring(mapname, p); + putint(p, mapsize); + if (65535 - (p - start) < mapsize) { + conoutf("map %s is too large to send", mapname); + free(mapdata); + enet_packet_destroy(packet); + return; + }; + memcpy(p, mapdata, mapsize); + p += mapsize; + free(mapdata); + *(ushort *)start = ENET_HOST_TO_NET_16(p - start); + enet_packet_resize(packet, p - start); + sendpackettoserv(packet); + conoutf("sending map %s to server...", mapname); + sprintf_sd(msg)( + "[map %s uploaded to server, \"getmap\" to receive it]", mapname); + toserver(msg); +} + +void +getmap() +{ + ENetPacket *packet = + enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start + 2; + putint(p, SV_RECVMAP); + *(ushort *)start = ENET_HOST_TO_NET_16(p - start); + enet_packet_resize(packet, p - start); + sendpackettoserv(packet); + conoutf("requesting map from server..."); +} + +COMMAND(sendmap, ARG_1STR); +COMMAND(getmap, ARG_NONE); DELETED src/clientgame.cxx Index: src/clientgame.cxx ================================================================== --- src/clientgame.cxx +++ /dev/null @@ -1,518 +0,0 @@ -// clientgame.cpp: core game related stuff - -#include "cube.h" - -int nextmode = 0; // nextmode becomes gamemode after next map load -VAR(gamemode, 1, 0, 0); - -void -mode(int n) -{ - addmsg(1, 2, SV_GAMEMODE, nextmode = n); -}; -COMMAND(mode, ARG_1INT); - -bool intermission = false; - -dynent *player1 = newdynent(); // our client -dvector players; // other clients - -VARP(sensitivity, 0, 10, 10000); -VARP(sensitivityscale, 1, 1, 10000); -VARP(invmouse, 0, 0, 1); - -int lastmillis = 0; -int curtime = 10; -string clientmap; - -extern int framesinmap; - -char * -getclientmap() -{ - return clientmap; -}; - -void -resetmovement(dynent *d) -{ - d->k_left = false; - d->k_right = false; - d->k_up = false; - d->k_down = false; - d->jumpnext = false; - d->strafe = 0; - d->move = 0; -}; - -void -spawnstate(dynent *d) // reset player state not persistent accross spawns -{ - resetmovement(d); - d->vel.x = d->vel.y = d->vel.z = 0; - d->onfloor = false; - d->timeinair = 0; - d->health = 100; - d->armour = 50; - d->armourtype = A_BLUE; - d->quadmillis = 0; - d->lastattackgun = d->gunselect = GUN_SG; - d->gunwait = 0; - d->attacking = false; - d->lastaction = 0; - loopi(NUMGUNS) d->ammo[i] = 0; - d->ammo[GUN_FIST] = 1; - if (m_noitems) { - d->gunselect = GUN_RIFLE; - d->armour = 0; - if (m_noitemsrail) { - d->health = 1; - d->ammo[GUN_RIFLE] = 100; - } else { - if (gamemode == 12) { - d->gunselect = GUN_FIST; - return; - }; // eihrul's secret "instafist" mode - d->health = 256; - if (m_tarena) { - int gun1 = rnd(4) + 1; - baseammo(d->gunselect = gun1); - for (;;) { - int gun2 = rnd(4) + 1; - if (gun1 != gun2) { - baseammo(gun2); - break; - }; - }; - } else if (m_arena) // insta arena - { - d->ammo[GUN_RIFLE] = 100; - } else // efficiency - { - loopi(4) baseammo(i + 1); - d->gunselect = GUN_CG; - }; - d->ammo[GUN_CG] /= 2; - }; - } else { - d->ammo[GUN_SG] = 5; - }; -}; - -dynent * -newdynent() // create a new blank player or monster -{ - dynent *d = (dynent *)gp()->alloc(sizeof(dynent)); - d->o.x = 0; - d->o.y = 0; - d->o.z = 0; - d->yaw = 270; - d->pitch = 0; - d->roll = 0; - d->maxspeed = 22; - d->outsidemap = false; - d->inwater = false; - d->radius = 1.1f; - d->eyeheight = 3.2f; - d->aboveeye = 0.7f; - d->frags = 0; - d->plag = 0; - d->ping = 0; - d->lastupdate = lastmillis; - d->enemy = NULL; - d->monsterstate = 0; - d->name[0] = d->team[0] = 0; - d->blocked = false; - d->lifesequence = 0; - d->state = CS_ALIVE; - spawnstate(d); - return d; -}; - -void -respawnself() -{ - spawnplayer(player1); - showscores(false); -}; - -void -arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam) -{ - if (d->state != CS_DEAD) { - alive++; - if (lastteam && strcmp(lastteam, d->team)) - oneteam = false; - lastteam = d->team; - } else { - dead++; - }; -}; - -int arenarespawnwait = 0; -int arenadetectwait = 0; - -void -arenarespawn() -{ - if (arenarespawnwait) { - if (arenarespawnwait < lastmillis) { - arenarespawnwait = 0; - conoutf("new round starting... fight!"); - respawnself(); - }; - } else if (arenadetectwait == 0 || arenadetectwait < lastmillis) { - arenadetectwait = 0; - int alive = 0, dead = 0; - char *lastteam = NULL; - bool oneteam = true; - loopv(players) if (players[i]) - arenacount(players[i], alive, dead, lastteam, oneteam); - arenacount(player1, alive, dead, lastteam, oneteam); - if (dead > 0 && (alive <= 1 || (m_teammode && oneteam))) { - conoutf( - "arena round is over! next round in 5 seconds..."); - if (alive) - conoutf( - "team %s is last man standing", lastteam); - else - conoutf("everyone died!"); - arenarespawnwait = lastmillis + 5000; - arenadetectwait = lastmillis + 10000; - player1->roll = 0; - }; - }; -}; - -void -zapdynent(dynent *&d) -{ - if (d) - gp()->dealloc(d, sizeof(dynent)); - d = NULL; -}; - -extern int democlientnum; - -void -otherplayers() -{ - loopv(players) if (players[i]) - { - const int lagtime = lastmillis - players[i]->lastupdate; - if (lagtime > 1000 && players[i]->state == CS_ALIVE) { - players[i]->state = CS_LAGGED; - continue; - }; - if (lagtime && players[i]->state != CS_DEAD && - (!demoplayback || i != democlientnum)) - moveplayer( - players[i], 2, false); // use physics to extrapolate - // player position - }; -}; - -void -respawn() -{ - if (player1->state == CS_DEAD) { - player1->attacking = false; - if (m_arena) { - conoutf("waiting for new round to start..."); - return; - }; - if (m_sp) { - nextmode = gamemode; - changemap(clientmap); - return; - }; // if we die in SP we try the same map again - respawnself(); - }; -}; - -int sleepwait = 0; -string sleepcmd; -void -sleepf(char *msec, char *cmd) -{ - sleepwait = atoi(msec) + lastmillis; - strcpy_s(sleepcmd, cmd); -}; -COMMANDN(sleep, sleepf, ARG_2STR); - -void -updateworld(int millis) // main game update loop -{ - if (lastmillis) { - curtime = millis - lastmillis; - if (sleepwait && lastmillis > sleepwait) { - sleepwait = 0; - execute(sleepcmd); - }; - physicsframe(); - checkquad(curtime); - if (m_arena) - arenarespawn(); - moveprojectiles((float)curtime); - demoplaybackstep(); - if (!demoplayback) { - if (getclientnum() >= 0) - shoot(player1, worldpos); // only shoot when - // connected to server - gets2c(); // do this first, so we have most accurate - // information when our player moves - }; - otherplayers(); - if (!demoplayback) { - monsterthink(); - if (player1->state == CS_DEAD) { - if (lastmillis - player1->lastaction < 2000) { - player1->move = player1->strafe = 0; - moveplayer(player1, 10, false); - } else if (!m_arena && !m_sp && - lastmillis - player1->lastaction > - 10000) - respawn(); - } else if (!intermission) { - moveplayer(player1, 20, true); - checkitems(); - }; - c2sinfo(player1); // do this last, to reduce the - // effective frame lag - }; - }; - lastmillis = millis; -}; - -void -entinmap(dynent * - d) // brute force but effective way to find a free spawn spot in the map -{ - loopi(100) // try max 100 times - { - float dx = (rnd(21) - 10) / 10.0f * i; // increasing distance - float dy = (rnd(21) - 10) / 10.0f * i; - d->o.x += dx; - d->o.y += dy; - if (collide(d, true, 0, 0)) - return; - d->o.x -= dx; - d->o.y -= dy; - }; - conoutf( - "can't find entity spawn spot! (%d, %d)", (int)d->o.x, (int)d->o.y); - // leave ent at original pos, possibly stuck -}; - -int spawncycle = -1; -int fixspawn = 2; - -void -spawnplayer(dynent *d) // place at random spawn. also used by monsters! -{ - int r = fixspawn-- > 0 ? 4 : rnd(10) + 1; - loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle + 1); - if (spawncycle != -1) { - d->o.x = ents[spawncycle].x; - d->o.y = ents[spawncycle].y; - d->o.z = ents[spawncycle].z; - d->yaw = ents[spawncycle].attr1; - d->pitch = 0; - d->roll = 0; - } else { - d->o.x = d->o.y = (float)ssize / 2; - d->o.z = 4; - }; - entinmap(d); - spawnstate(d); - d->state = CS_ALIVE; -}; - -// movement input code - -#define dir(name, v, d, s, os) \ - void name(bool isdown) \ - { \ - player1->s = isdown; \ - player1->v = isdown ? d : (player1->os ? -(d) : 0); \ - player1->lastmove = lastmillis; \ - }; - -dir(backward, move, -1, k_down, k_up); -dir(forward, move, 1, k_up, k_down); -dir(left, strafe, 1, k_left, k_right); -dir(right, strafe, -1, k_right, k_left); - -void -attack(bool on) -{ - if (intermission) - return; - if (editmode) - editdrag(on); - else if (player1->attacking = on) - respawn(); -}; - -void -jumpn(bool on) -{ - if (!intermission && (player1->jumpnext = on)) - respawn(); -}; - -COMMAND(backward, ARG_DOWN); -COMMAND(forward, ARG_DOWN); -COMMAND(left, ARG_DOWN); -COMMAND(right, ARG_DOWN); -COMMANDN(jump, jumpn, ARG_DOWN); -COMMAND(attack, ARG_DOWN); -COMMAND(showscores, ARG_DOWN); - -void -fixplayer1range() -{ - const float MAXPITCH = 90.0f; - if (player1->pitch > MAXPITCH) - player1->pitch = MAXPITCH; - if (player1->pitch < -MAXPITCH) - player1->pitch = -MAXPITCH; - while (player1->yaw < 0.0f) - player1->yaw += 360.0f; - while (player1->yaw >= 360.0f) - player1->yaw -= 360.0f; -}; - -void -mousemove(int dx, int dy) -{ - if (player1->state == CS_DEAD || intermission) - return; - const float SENSF = 33.0f; // try match quake sens - player1->yaw += (dx / SENSF) * (sensitivity / (float)sensitivityscale); - player1->pitch -= (dy / SENSF) * - (sensitivity / (float)sensitivityscale) * - (invmouse ? -1 : 1); - fixplayer1range(); -}; - -// damage arriving from the network, monsters, yourself, all ends up here. - -void -selfdamage(int damage, int actor, dynent *act) -{ - if (player1->state != CS_ALIVE || editmode || intermission) - return; - damageblend(damage); - demoblend(damage); - int ad = damage * (player1->armourtype + 1) * 20 / - 100; // let armour absorb when possible - if (ad > player1->armour) - ad = player1->armour; - player1->armour -= ad; - damage -= ad; - float droll = damage / 0.5f; - player1->roll += - player1->roll > 0 - ? droll - : (player1->roll < 0 - ? -droll - : (rnd(2) ? droll - : -droll)); // give player a kick depending - // on amount of damage - if ((player1->health -= damage) <= 0) { - if (actor == -2) { - conoutf("you got killed by %s!", act->name); - } else if (actor == -1) { - actor = getclientnum(); - conoutf("you suicided!"); - addmsg(1, 2, SV_FRAGS, --player1->frags); - } else { - dynent *a = getclient(actor); - if (a) { - if (isteam(a->team, player1->team)) { - conoutf("you got fragged by a teammate " - "(%s)", - a->name); - } else { - conoutf( - "you got fragged by %s", a->name); - }; - }; - }; - showscores(true); - addmsg(1, 2, SV_DIED, actor); - player1->lifesequence++; - player1->attacking = false; - player1->state = CS_DEAD; - player1->pitch = 0; - player1->roll = 60; - playsound(S_DIE1 + rnd(2)); - spawnstate(player1); - player1->lastaction = lastmillis; - } else { - playsound(S_PAIN6); - }; -}; - -void -timeupdate(int timeremain) -{ - if (!timeremain) { - intermission = true; - player1->attacking = false; - conoutf("intermission:"); - conoutf("game has ended!"); - showscores(true); - } else { - conoutf("time remaining: %d minutes", timeremain); - }; -}; - -dynent * -getclient(int cn) // ensure valid entity -{ - if (cn < 0 || cn >= MAXCLIENTS) { - neterr("clientnum"); - return NULL; - }; - while (cn >= players.length()) - players.add(NULL); - return players[cn] ? players[cn] : (players[cn] = newdynent()); -}; - -void -initclient() -{ - clientmap[0] = 0; - initclientnet(); -}; - -void -startmap(char *name) // called just after a map load -{ - if (netmapstart() && m_sp) { - gamemode = 0; - conoutf("coop sp not supported yet"); - }; - sleepwait = 0; - monsterclear(); - projreset(); - spawncycle = -1; - spawnplayer(player1); - player1->frags = 0; - loopv(players) if (players[i]) players[i]->frags = 0; - resetspawns(); - strcpy_s(clientmap, name); - if (editmode) - toggleedit(); - setvar("gamespeed", 100); - setvar("fog", 180); - setvar("fogcolour", 0x8099B3); - showscores(false); - intermission = false; - framesinmap = 0; - conoutf("game mode is %s", modestr(gamemode)); -}; - -COMMANDN(map, changemap, ARG_1STR); ADDED src/clientgame.mm Index: src/clientgame.mm ================================================================== --- /dev/null +++ src/clientgame.mm @@ -0,0 +1,518 @@ +// clientgame.cpp: core game related stuff + +#include "cube.h" + +int nextmode = 0; // nextmode becomes gamemode after next map load +VAR(gamemode, 1, 0, 0); + +void +mode(int n) +{ + addmsg(1, 2, SV_GAMEMODE, nextmode = n); +}; +COMMAND(mode, ARG_1INT); + +bool intermission = false; + +dynent *player1 = newdynent(); // our client +dvector players; // other clients + +VARP(sensitivity, 0, 10, 10000); +VARP(sensitivityscale, 1, 1, 10000); +VARP(invmouse, 0, 0, 1); + +int lastmillis = 0; +int curtime = 10; +string clientmap; + +extern int framesinmap; + +char * +getclientmap() +{ + return clientmap; +}; + +void +resetmovement(dynent *d) +{ + d->k_left = false; + d->k_right = false; + d->k_up = false; + d->k_down = false; + d->jumpnext = false; + d->strafe = 0; + d->move = 0; +}; + +void +spawnstate(dynent *d) // reset player state not persistent accross spawns +{ + resetmovement(d); + d->vel.x = d->vel.y = d->vel.z = 0; + d->onfloor = false; + d->timeinair = 0; + d->health = 100; + d->armour = 50; + d->armourtype = A_BLUE; + d->quadmillis = 0; + d->lastattackgun = d->gunselect = GUN_SG; + d->gunwait = 0; + d->attacking = false; + d->lastaction = 0; + loopi(NUMGUNS) d->ammo[i] = 0; + d->ammo[GUN_FIST] = 1; + if (m_noitems) { + d->gunselect = GUN_RIFLE; + d->armour = 0; + if (m_noitemsrail) { + d->health = 1; + d->ammo[GUN_RIFLE] = 100; + } else { + if (gamemode == 12) { + d->gunselect = GUN_FIST; + return; + }; // eihrul's secret "instafist" mode + d->health = 256; + if (m_tarena) { + int gun1 = rnd(4) + 1; + baseammo(d->gunselect = gun1); + for (;;) { + int gun2 = rnd(4) + 1; + if (gun1 != gun2) { + baseammo(gun2); + break; + }; + }; + } else if (m_arena) // insta arena + { + d->ammo[GUN_RIFLE] = 100; + } else // efficiency + { + loopi(4) baseammo(i + 1); + d->gunselect = GUN_CG; + }; + d->ammo[GUN_CG] /= 2; + }; + } else { + d->ammo[GUN_SG] = 5; + }; +}; + +dynent * +newdynent() // create a new blank player or monster +{ + dynent *d = (dynent *)gp()->alloc(sizeof(dynent)); + d->o.x = 0; + d->o.y = 0; + d->o.z = 0; + d->yaw = 270; + d->pitch = 0; + d->roll = 0; + d->maxspeed = 22; + d->outsidemap = false; + d->inwater = false; + d->radius = 1.1f; + d->eyeheight = 3.2f; + d->aboveeye = 0.7f; + d->frags = 0; + d->plag = 0; + d->ping = 0; + d->lastupdate = lastmillis; + d->enemy = NULL; + d->monsterstate = 0; + d->name[0] = d->team[0] = 0; + d->blocked = false; + d->lifesequence = 0; + d->state = CS_ALIVE; + spawnstate(d); + return d; +}; + +void +respawnself() +{ + spawnplayer(player1); + showscores(false); +}; + +void +arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam) +{ + if (d->state != CS_DEAD) { + alive++; + if (lastteam && strcmp(lastteam, d->team)) + oneteam = false; + lastteam = d->team; + } else { + dead++; + }; +}; + +int arenarespawnwait = 0; +int arenadetectwait = 0; + +void +arenarespawn() +{ + if (arenarespawnwait) { + if (arenarespawnwait < lastmillis) { + arenarespawnwait = 0; + conoutf("new round starting... fight!"); + respawnself(); + }; + } else if (arenadetectwait == 0 || arenadetectwait < lastmillis) { + arenadetectwait = 0; + int alive = 0, dead = 0; + char *lastteam = NULL; + bool oneteam = true; + loopv(players) if (players[i]) + arenacount(players[i], alive, dead, lastteam, oneteam); + arenacount(player1, alive, dead, lastteam, oneteam); + if (dead > 0 && (alive <= 1 || (m_teammode && oneteam))) { + conoutf( + "arena round is over! next round in 5 seconds..."); + if (alive) + conoutf( + "team %s is last man standing", lastteam); + else + conoutf("everyone died!"); + arenarespawnwait = lastmillis + 5000; + arenadetectwait = lastmillis + 10000; + player1->roll = 0; + }; + }; +}; + +void +zapdynent(dynent *&d) +{ + if (d) + gp()->dealloc(d, sizeof(dynent)); + d = NULL; +}; + +extern int democlientnum; + +void +otherplayers() +{ + loopv(players) if (players[i]) + { + const int lagtime = lastmillis - players[i]->lastupdate; + if (lagtime > 1000 && players[i]->state == CS_ALIVE) { + players[i]->state = CS_LAGGED; + continue; + }; + if (lagtime && players[i]->state != CS_DEAD && + (!demoplayback || i != democlientnum)) + moveplayer( + players[i], 2, false); // use physics to extrapolate + // player position + }; +}; + +void +respawn() +{ + if (player1->state == CS_DEAD) { + player1->attacking = false; + if (m_arena) { + conoutf("waiting for new round to start..."); + return; + }; + if (m_sp) { + nextmode = gamemode; + changemap(clientmap); + return; + }; // if we die in SP we try the same map again + respawnself(); + }; +}; + +int sleepwait = 0; +string sleepcmd; +void +sleepf(char *msec, char *cmd) +{ + sleepwait = atoi(msec) + lastmillis; + strcpy_s(sleepcmd, cmd); +}; +COMMANDN(sleep, sleepf, ARG_2STR); + +void +updateworld(int millis) // main game update loop +{ + if (lastmillis) { + curtime = millis - lastmillis; + if (sleepwait && lastmillis > sleepwait) { + sleepwait = 0; + execute(sleepcmd); + }; + physicsframe(); + checkquad(curtime); + if (m_arena) + arenarespawn(); + moveprojectiles((float)curtime); + demoplaybackstep(); + if (!demoplayback) { + if (getclientnum() >= 0) + shoot(player1, worldpos); // only shoot when + // connected to server + gets2c(); // do this first, so we have most accurate + // information when our player moves + }; + otherplayers(); + if (!demoplayback) { + monsterthink(); + if (player1->state == CS_DEAD) { + if (lastmillis - player1->lastaction < 2000) { + player1->move = player1->strafe = 0; + moveplayer(player1, 10, false); + } else if (!m_arena && !m_sp && + lastmillis - player1->lastaction > + 10000) + respawn(); + } else if (!intermission) { + moveplayer(player1, 20, true); + checkitems(); + }; + c2sinfo(player1); // do this last, to reduce the + // effective frame lag + }; + }; + lastmillis = millis; +}; + +void +entinmap(dynent * + d) // brute force but effective way to find a free spawn spot in the map +{ + loopi(100) // try max 100 times + { + float dx = (rnd(21) - 10) / 10.0f * i; // increasing distance + float dy = (rnd(21) - 10) / 10.0f * i; + d->o.x += dx; + d->o.y += dy; + if (collide(d, true, 0, 0)) + return; + d->o.x -= dx; + d->o.y -= dy; + }; + conoutf( + "can't find entity spawn spot! (%d, %d)", (int)d->o.x, (int)d->o.y); + // leave ent at original pos, possibly stuck +}; + +int spawncycle = -1; +int fixspawn = 2; + +void +spawnplayer(dynent *d) // place at random spawn. also used by monsters! +{ + int r = fixspawn-- > 0 ? 4 : rnd(10) + 1; + loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle + 1); + if (spawncycle != -1) { + d->o.x = ents[spawncycle].x; + d->o.y = ents[spawncycle].y; + d->o.z = ents[spawncycle].z; + d->yaw = ents[spawncycle].attr1; + d->pitch = 0; + d->roll = 0; + } else { + d->o.x = d->o.y = (float)ssize / 2; + d->o.z = 4; + }; + entinmap(d); + spawnstate(d); + d->state = CS_ALIVE; +}; + +// movement input code + +#define dir(name, v, d, s, os) \ + void name(bool isdown) \ + { \ + player1->s = isdown; \ + player1->v = isdown ? d : (player1->os ? -(d) : 0); \ + player1->lastmove = lastmillis; \ + }; + +dir(backward, move, -1, k_down, k_up); +dir(forward, move, 1, k_up, k_down); +dir(left, strafe, 1, k_left, k_right); +dir(right, strafe, -1, k_right, k_left); + +void +attack(bool on) +{ + if (intermission) + return; + if (editmode) + editdrag(on); + else if (player1->attacking = on) + respawn(); +}; + +void +jumpn(bool on) +{ + if (!intermission && (player1->jumpnext = on)) + respawn(); +}; + +COMMAND(backward, ARG_DOWN); +COMMAND(forward, ARG_DOWN); +COMMAND(left, ARG_DOWN); +COMMAND(right, ARG_DOWN); +COMMANDN(jump, jumpn, ARG_DOWN); +COMMAND(attack, ARG_DOWN); +COMMAND(showscores, ARG_DOWN); + +void +fixplayer1range() +{ + const float MAXPITCH = 90.0f; + if (player1->pitch > MAXPITCH) + player1->pitch = MAXPITCH; + if (player1->pitch < -MAXPITCH) + player1->pitch = -MAXPITCH; + while (player1->yaw < 0.0f) + player1->yaw += 360.0f; + while (player1->yaw >= 360.0f) + player1->yaw -= 360.0f; +}; + +void +mousemove(int dx, int dy) +{ + if (player1->state == CS_DEAD || intermission) + return; + const float SENSF = 33.0f; // try match quake sens + player1->yaw += (dx / SENSF) * (sensitivity / (float)sensitivityscale); + player1->pitch -= (dy / SENSF) * + (sensitivity / (float)sensitivityscale) * + (invmouse ? -1 : 1); + fixplayer1range(); +}; + +// damage arriving from the network, monsters, yourself, all ends up here. + +void +selfdamage(int damage, int actor, dynent *act) +{ + if (player1->state != CS_ALIVE || editmode || intermission) + return; + damageblend(damage); + demoblend(damage); + int ad = damage * (player1->armourtype + 1) * 20 / + 100; // let armour absorb when possible + if (ad > player1->armour) + ad = player1->armour; + player1->armour -= ad; + damage -= ad; + float droll = damage / 0.5f; + player1->roll += + player1->roll > 0 + ? droll + : (player1->roll < 0 + ? -droll + : (rnd(2) ? droll + : -droll)); // give player a kick depending + // on amount of damage + if ((player1->health -= damage) <= 0) { + if (actor == -2) { + conoutf("you got killed by %s!", act->name); + } else if (actor == -1) { + actor = getclientnum(); + conoutf("you suicided!"); + addmsg(1, 2, SV_FRAGS, --player1->frags); + } else { + dynent *a = getclient(actor); + if (a) { + if (isteam(a->team, player1->team)) { + conoutf("you got fragged by a teammate " + "(%s)", + a->name); + } else { + conoutf( + "you got fragged by %s", a->name); + }; + }; + }; + showscores(true); + addmsg(1, 2, SV_DIED, actor); + player1->lifesequence++; + player1->attacking = false; + player1->state = CS_DEAD; + player1->pitch = 0; + player1->roll = 60; + playsound(S_DIE1 + rnd(2)); + spawnstate(player1); + player1->lastaction = lastmillis; + } else { + playsound(S_PAIN6); + }; +}; + +void +timeupdate(int timeremain) +{ + if (!timeremain) { + intermission = true; + player1->attacking = false; + conoutf("intermission:"); + conoutf("game has ended!"); + showscores(true); + } else { + conoutf("time remaining: %d minutes", timeremain); + }; +}; + +dynent * +getclient(int cn) // ensure valid entity +{ + if (cn < 0 || cn >= MAXCLIENTS) { + neterr("clientnum"); + return NULL; + }; + while (cn >= players.length()) + players.add(NULL); + return players[cn] ? players[cn] : (players[cn] = newdynent()); +}; + +void +initclient() +{ + clientmap[0] = 0; + initclientnet(); +}; + +void +startmap(char *name) // called just after a map load +{ + if (netmapstart() && m_sp) { + gamemode = 0; + conoutf("coop sp not supported yet"); + }; + sleepwait = 0; + monsterclear(); + projreset(); + spawncycle = -1; + spawnplayer(player1); + player1->frags = 0; + loopv(players) if (players[i]) players[i]->frags = 0; + resetspawns(); + strcpy_s(clientmap, name); + if (editmode) + toggleedit(); + setvar("gamespeed", 100); + setvar("fog", 180); + setvar("fogcolour", 0x8099B3); + showscores(false); + intermission = false; + framesinmap = 0; + conoutf("game mode is %s", modestr(gamemode)); +}; + +COMMANDN(map, changemap, ARG_1STR); DELETED src/clients2c.cxx Index: src/clients2c.cxx ================================================================== --- src/clients2c.cxx +++ /dev/null @@ -1,390 +0,0 @@ -// client processing of the incoming network stream - -#include "cube.h" - -extern int clientnum; -extern bool c2sinit, senditemstoserver; -extern string toservermap; -extern string clientpassword; - -void -neterr(char *s) -{ - conoutf("illegal network message (%s)", s); - disconnect(); -}; - -void -changemapserv(char *name, int mode) // forced map change from the server -{ - gamemode = mode; - load_world(name); -}; - -void -changemap(char *name) // request map change, server may ignore -{ - strcpy_s(toservermap, name); -}; - -// update the position of other clients in the game in our world -// don't care if he's in the scenery or other players, -// just don't overlap with our client - -void -updatepos(dynent *d) -{ - const float r = player1->radius + d->radius; - const float dx = player1->o.x - d->o.x; - const float dy = player1->o.y - d->o.y; - const float dz = player1->o.z - d->o.z; - const float rz = player1->aboveeye + d->eyeheight; - const float fx = (float)fabs(dx), fy = (float)fabs(dy), - fz = (float)fabs(dz); - if (fx < r && fy < r && fz < rz && d->state != CS_DEAD) { - if (fx < fy) - d->o.y += dy < 0 ? r - fy : -(r - fy); // push aside - else - d->o.x += dx < 0 ? r - fx : -(r - fx); - }; - int lagtime = lastmillis - d->lastupdate; - if (lagtime) { - d->plag = (d->plag * 5 + lagtime) / 6; - d->lastupdate = lastmillis; - }; -}; - -void -localservertoclient( - uchar *buf, int len) // processes any updates from the server -{ - if (ENET_NET_TO_HOST_16(*(ushort *)buf) != len) - neterr("packet length"); - incomingdemodata(buf, len); - - uchar *end = buf + len; - uchar *p = buf + 2; - char text[MAXTRANS]; - int cn = -1, type; - dynent *d = NULL; - bool mapchanged = false; - - while (p < end) - switch (type = getint(p)) { - case SV_INITS2C: // welcome messsage from the server - { - cn = getint(p); - int prot = getint(p); - if (prot != PROTOCOL_VERSION) { - conoutf("you are using a different game " - "protocol (you: %d, server: %d)", - PROTOCOL_VERSION, prot); - disconnect(); - return; - }; - toservermap[0] = 0; - clientnum = cn; // we are now fully connected - if (!getint(p)) - strcpy_s(toservermap, - getclientmap()); // we are the first client - // on this server, set map - sgetstr(); - if (text[0] && strcmp(text, clientpassword)) { - conoutf("you need to set the correct password " - "to join this server!"); - disconnect(); - return; - }; - if (getint(p) == 1) { - conoutf("server is FULL, disconnecting.."); - }; - break; - }; - - case SV_POS: // position of another client - { - cn = getint(p); - d = getclient(cn); - if (!d) - return; - d->o.x = getint(p) / DMF; - d->o.y = getint(p) / DMF; - d->o.z = getint(p) / DMF; - d->yaw = getint(p) / DAF; - d->pitch = getint(p) / DAF; - d->roll = getint(p) / DAF; - d->vel.x = getint(p) / DVF; - d->vel.y = getint(p) / DVF; - d->vel.z = getint(p) / DVF; - int f = getint(p); - d->strafe = (f & 3) == 3 ? -1 : f & 3; - f >>= 2; - d->move = (f & 3) == 3 ? -1 : f & 3; - d->onfloor = (f >> 2) & 1; - int state = f >> 3; - if (state == CS_DEAD && d->state != CS_DEAD) - d->lastaction = lastmillis; - d->state = state; - if (!demoplayback) - updatepos(d); - break; - }; - - case SV_SOUND: - playsound(getint(p), &d->o); - break; - - case SV_TEXT: - sgetstr(); - conoutf("%s:\f %s", d->name, text); - break; - - case SV_MAPCHANGE: - sgetstr(); - changemapserv(text, getint(p)); - mapchanged = true; - break; - - case SV_ITEMLIST: { - int n; - if (mapchanged) { - senditemstoserver = false; - resetspawns(); - }; - while ((n = getint(p)) != -1) - if (mapchanged) - setspawn(n, true); - break; - }; - - case SV_MAPRELOAD: // server requests next map - { - getint(p); - sprintf_sd(nextmapalias)("nextmap_%s", getclientmap()); - char *map = - getalias(nextmapalias); // look up map in the cycle - changemap(map ? map : getclientmap()); - break; - }; - - case SV_INITC2S: // another client either connected or changed - // name/team - { - sgetstr(); - if (d->name[0]) // already connected - { - if (strcmp(d->name, text)) - conoutf("%s is now known as %s", - d->name, text); - } else // new client - { - c2sinit = - false; // send new players my info again - conoutf("connected: %s", text); - }; - strcpy_s(d->name, text); - sgetstr(); - strcpy_s(d->team, text); - d->lifesequence = getint(p); - break; - }; - - case SV_CDIS: - cn = getint(p); - if (!(d = getclient(cn))) - break; - conoutf("player %s disconnected", - d->name[0] ? d->name : "[incompatible client]"); - zapdynent(players[cn]); - break; - - case SV_SHOT: { - int gun = getint(p); - vec s, e; - s.x = getint(p) / DMF; - s.y = getint(p) / DMF; - s.z = getint(p) / DMF; - e.x = getint(p) / DMF; - e.y = getint(p) / DMF; - e.z = getint(p) / DMF; - if (gun == GUN_SG) - createrays(s, e); - shootv(gun, s, e, d); - break; - }; - - case SV_DAMAGE: { - int target = getint(p); - int damage = getint(p); - int ls = getint(p); - if (target == clientnum) { - if (ls == player1->lifesequence) - selfdamage(damage, cn, d); - } else - playsound( - S_PAIN1 + rnd(5), &getclient(target)->o); - break; - }; - - case SV_DIED: { - int actor = getint(p); - if (actor == cn) { - conoutf("%s suicided", d->name); - } else if (actor == clientnum) { - int frags; - if (isteam(player1->team, d->team)) { - frags = -1; - conoutf("you fragged a teammate (%s)", - d->name); - } else { - frags = 1; - conoutf("you fragged %s", d->name); - }; - addmsg(1, 2, SV_FRAGS, player1->frags += frags); - } else { - dynent *a = getclient(actor); - if (a) { - if (isteam(a->team, d->name)) { - conoutf("%s fragged his " - "teammate (%s)", - a->name, d->name); - } else { - conoutf("%s fragged %s", - a->name, d->name); - }; - }; - }; - playsound(S_DIE1 + rnd(2), &d->o); - d->lifesequence++; - break; - }; - - case SV_FRAGS: - players[cn]->frags = getint(p); - break; - - case SV_ITEMPICKUP: - setspawn(getint(p), false); - getint(p); - break; - - case SV_ITEMSPAWN: { - uint i = getint(p); - setspawn(i, true); - if (i >= (uint)ents.length()) - break; - vec v = {(float)ents[i].x, (float)ents[i].y, - (float)ents[i].z}; - playsound(S_ITEMSPAWN, &v); - break; - }; - - case SV_ITEMACC: // server acknowledges that I picked up this - // item - realpickup(getint(p), player1); - break; - - case SV_EDITH: // coop editing messages, should be extended to - // include all possible editing ops - case SV_EDITT: - case SV_EDITS: - case SV_EDITD: - case SV_EDITE: { - int x = getint(p); - int y = getint(p); - int xs = getint(p); - int ys = getint(p); - int v = getint(p); - block b = {x, y, xs, ys}; - switch (type) { - case SV_EDITH: - editheightxy(v != 0, getint(p), b); - break; - case SV_EDITT: - edittexxy(v, getint(p), b); - break; - case SV_EDITS: - edittypexy(v, b); - break; - case SV_EDITD: - setvdeltaxy(v, b); - break; - case SV_EDITE: - editequalisexy(v != 0, b); - break; - }; - break; - }; - - case SV_EDITENT: // coop edit of ent - { - uint i = getint(p); - while ((uint)ents.length() <= i) - ents.add().type = NOTUSED; - int to = ents[i].type; - ents[i].type = getint(p); - ents[i].x = getint(p); - ents[i].y = getint(p); - ents[i].z = getint(p); - ents[i].attr1 = getint(p); - ents[i].attr2 = getint(p); - ents[i].attr3 = getint(p); - ents[i].attr4 = getint(p); - ents[i].spawned = false; - if (ents[i].type == LIGHT || to == LIGHT) - calclight(); - break; - }; - - case SV_PING: - getint(p); - break; - - case SV_PONG: - addmsg(0, 2, SV_CLIENTPING, - player1->ping = - (player1->ping * 5 + lastmillis - getint(p)) / - 6); - break; - - case SV_CLIENTPING: - players[cn]->ping = getint(p); - break; - - case SV_GAMEMODE: - nextmode = getint(p); - break; - - case SV_TIMEUP: - timeupdate(getint(p)); - break; - - case SV_RECVMAP: { - sgetstr(); - conoutf("received map \"%s\" from server, reloading..", - text); - int mapsize = getint(p); - writemap(text, mapsize, p); - p += mapsize; - changemapserv(text, gamemode); - break; - }; - - case SV_SERVMSG: - sgetstr(); - conoutf("%s", text); - break; - - case SV_EXT: // so we can messages without breaking previous - // clients/servers, if necessary - { - for (int n = getint(p); n; n--) - getint(p); - break; - }; - - default: - neterr("type"); - return; - }; -}; ADDED src/clients2c.mm Index: src/clients2c.mm ================================================================== --- /dev/null +++ src/clients2c.mm @@ -0,0 +1,390 @@ +// client processing of the incoming network stream + +#include "cube.h" + +extern int clientnum; +extern bool c2sinit, senditemstoserver; +extern string toservermap; +extern string clientpassword; + +void +neterr(char *s) +{ + conoutf("illegal network message (%s)", s); + disconnect(); +}; + +void +changemapserv(char *name, int mode) // forced map change from the server +{ + gamemode = mode; + load_world(name); +}; + +void +changemap(char *name) // request map change, server may ignore +{ + strcpy_s(toservermap, name); +}; + +// update the position of other clients in the game in our world +// don't care if he's in the scenery or other players, +// just don't overlap with our client + +void +updatepos(dynent *d) +{ + const float r = player1->radius + d->radius; + const float dx = player1->o.x - d->o.x; + const float dy = player1->o.y - d->o.y; + const float dz = player1->o.z - d->o.z; + const float rz = player1->aboveeye + d->eyeheight; + const float fx = (float)fabs(dx), fy = (float)fabs(dy), + fz = (float)fabs(dz); + if (fx < r && fy < r && fz < rz && d->state != CS_DEAD) { + if (fx < fy) + d->o.y += dy < 0 ? r - fy : -(r - fy); // push aside + else + d->o.x += dx < 0 ? r - fx : -(r - fx); + }; + int lagtime = lastmillis - d->lastupdate; + if (lagtime) { + d->plag = (d->plag * 5 + lagtime) / 6; + d->lastupdate = lastmillis; + }; +}; + +void +localservertoclient( + uchar *buf, int len) // processes any updates from the server +{ + if (ENET_NET_TO_HOST_16(*(ushort *)buf) != len) + neterr("packet length"); + incomingdemodata(buf, len); + + uchar *end = buf + len; + uchar *p = buf + 2; + char text[MAXTRANS]; + int cn = -1, type; + dynent *d = NULL; + bool mapchanged = false; + + while (p < end) + switch (type = getint(p)) { + case SV_INITS2C: // welcome messsage from the server + { + cn = getint(p); + int prot = getint(p); + if (prot != PROTOCOL_VERSION) { + conoutf("you are using a different game " + "protocol (you: %d, server: %d)", + PROTOCOL_VERSION, prot); + disconnect(); + return; + }; + toservermap[0] = 0; + clientnum = cn; // we are now fully connected + if (!getint(p)) + strcpy_s(toservermap, + getclientmap()); // we are the first client + // on this server, set map + sgetstr(); + if (text[0] && strcmp(text, clientpassword)) { + conoutf("you need to set the correct password " + "to join this server!"); + disconnect(); + return; + }; + if (getint(p) == 1) { + conoutf("server is FULL, disconnecting.."); + }; + break; + }; + + case SV_POS: // position of another client + { + cn = getint(p); + d = getclient(cn); + if (!d) + return; + d->o.x = getint(p) / DMF; + d->o.y = getint(p) / DMF; + d->o.z = getint(p) / DMF; + d->yaw = getint(p) / DAF; + d->pitch = getint(p) / DAF; + d->roll = getint(p) / DAF; + d->vel.x = getint(p) / DVF; + d->vel.y = getint(p) / DVF; + d->vel.z = getint(p) / DVF; + int f = getint(p); + d->strafe = (f & 3) == 3 ? -1 : f & 3; + f >>= 2; + d->move = (f & 3) == 3 ? -1 : f & 3; + d->onfloor = (f >> 2) & 1; + int state = f >> 3; + if (state == CS_DEAD && d->state != CS_DEAD) + d->lastaction = lastmillis; + d->state = state; + if (!demoplayback) + updatepos(d); + break; + }; + + case SV_SOUND: + playsound(getint(p), &d->o); + break; + + case SV_TEXT: + sgetstr(); + conoutf("%s:\f %s", d->name, text); + break; + + case SV_MAPCHANGE: + sgetstr(); + changemapserv(text, getint(p)); + mapchanged = true; + break; + + case SV_ITEMLIST: { + int n; + if (mapchanged) { + senditemstoserver = false; + resetspawns(); + }; + while ((n = getint(p)) != -1) + if (mapchanged) + setspawn(n, true); + break; + }; + + case SV_MAPRELOAD: // server requests next map + { + getint(p); + sprintf_sd(nextmapalias)("nextmap_%s", getclientmap()); + char *map = + getalias(nextmapalias); // look up map in the cycle + changemap(map ? map : getclientmap()); + break; + }; + + case SV_INITC2S: // another client either connected or changed + // name/team + { + sgetstr(); + if (d->name[0]) // already connected + { + if (strcmp(d->name, text)) + conoutf("%s is now known as %s", + d->name, text); + } else // new client + { + c2sinit = + false; // send new players my info again + conoutf("connected: %s", text); + }; + strcpy_s(d->name, text); + sgetstr(); + strcpy_s(d->team, text); + d->lifesequence = getint(p); + break; + }; + + case SV_CDIS: + cn = getint(p); + if (!(d = getclient(cn))) + break; + conoutf("player %s disconnected", + d->name[0] ? d->name : "[incompatible client]"); + zapdynent(players[cn]); + break; + + case SV_SHOT: { + int gun = getint(p); + vec s, e; + s.x = getint(p) / DMF; + s.y = getint(p) / DMF; + s.z = getint(p) / DMF; + e.x = getint(p) / DMF; + e.y = getint(p) / DMF; + e.z = getint(p) / DMF; + if (gun == GUN_SG) + createrays(s, e); + shootv(gun, s, e, d); + break; + }; + + case SV_DAMAGE: { + int target = getint(p); + int damage = getint(p); + int ls = getint(p); + if (target == clientnum) { + if (ls == player1->lifesequence) + selfdamage(damage, cn, d); + } else + playsound( + S_PAIN1 + rnd(5), &getclient(target)->o); + break; + }; + + case SV_DIED: { + int actor = getint(p); + if (actor == cn) { + conoutf("%s suicided", d->name); + } else if (actor == clientnum) { + int frags; + if (isteam(player1->team, d->team)) { + frags = -1; + conoutf("you fragged a teammate (%s)", + d->name); + } else { + frags = 1; + conoutf("you fragged %s", d->name); + }; + addmsg(1, 2, SV_FRAGS, player1->frags += frags); + } else { + dynent *a = getclient(actor); + if (a) { + if (isteam(a->team, d->name)) { + conoutf("%s fragged his " + "teammate (%s)", + a->name, d->name); + } else { + conoutf("%s fragged %s", + a->name, d->name); + }; + }; + }; + playsound(S_DIE1 + rnd(2), &d->o); + d->lifesequence++; + break; + }; + + case SV_FRAGS: + players[cn]->frags = getint(p); + break; + + case SV_ITEMPICKUP: + setspawn(getint(p), false); + getint(p); + break; + + case SV_ITEMSPAWN: { + uint i = getint(p); + setspawn(i, true); + if (i >= (uint)ents.length()) + break; + vec v = {(float)ents[i].x, (float)ents[i].y, + (float)ents[i].z}; + playsound(S_ITEMSPAWN, &v); + break; + }; + + case SV_ITEMACC: // server acknowledges that I picked up this + // item + realpickup(getint(p), player1); + break; + + case SV_EDITH: // coop editing messages, should be extended to + // include all possible editing ops + case SV_EDITT: + case SV_EDITS: + case SV_EDITD: + case SV_EDITE: { + int x = getint(p); + int y = getint(p); + int xs = getint(p); + int ys = getint(p); + int v = getint(p); + block b = {x, y, xs, ys}; + switch (type) { + case SV_EDITH: + editheightxy(v != 0, getint(p), b); + break; + case SV_EDITT: + edittexxy(v, getint(p), b); + break; + case SV_EDITS: + edittypexy(v, b); + break; + case SV_EDITD: + setvdeltaxy(v, b); + break; + case SV_EDITE: + editequalisexy(v != 0, b); + break; + }; + break; + }; + + case SV_EDITENT: // coop edit of ent + { + uint i = getint(p); + while ((uint)ents.length() <= i) + ents.add().type = NOTUSED; + int to = ents[i].type; + ents[i].type = getint(p); + ents[i].x = getint(p); + ents[i].y = getint(p); + ents[i].z = getint(p); + ents[i].attr1 = getint(p); + ents[i].attr2 = getint(p); + ents[i].attr3 = getint(p); + ents[i].attr4 = getint(p); + ents[i].spawned = false; + if (ents[i].type == LIGHT || to == LIGHT) + calclight(); + break; + }; + + case SV_PING: + getint(p); + break; + + case SV_PONG: + addmsg(0, 2, SV_CLIENTPING, + player1->ping = + (player1->ping * 5 + lastmillis - getint(p)) / + 6); + break; + + case SV_CLIENTPING: + players[cn]->ping = getint(p); + break; + + case SV_GAMEMODE: + nextmode = getint(p); + break; + + case SV_TIMEUP: + timeupdate(getint(p)); + break; + + case SV_RECVMAP: { + sgetstr(); + conoutf("received map \"%s\" from server, reloading..", + text); + int mapsize = getint(p); + writemap(text, mapsize, p); + p += mapsize; + changemapserv(text, gamemode); + break; + }; + + case SV_SERVMSG: + sgetstr(); + conoutf("%s", text); + break; + + case SV_EXT: // so we can messages without breaking previous + // clients/servers, if necessary + { + for (int n = getint(p); n; n--) + getint(p); + break; + }; + + default: + neterr("type"); + return; + }; +}; DELETED src/command.cxx Index: src/command.cxx ================================================================== --- src/command.cxx +++ /dev/null @@ -1,627 +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" - -enum { ID_VAR, ID_COMMAND, ID_ALIAS }; - -struct ident { - int type; // one of ID_* above - char *name; - int min, max; // ID_VAR - int *storage; // ID_VAR - void (*fun)(); // ID_VAR, ID_COMMAND - int narg; // ID_VAR, ID_COMMAND - char *action; // ID_ALIAS - bool persist; -}; - -void -itoa(char *s, int i) -{ - sprintf_s(s)("%d", i); -}; -char * -exchangestr(char *o, char *n) -{ - gp()->deallocstr(o); - return newstring(n); -}; - -hashtable *idents = NULL; // contains ALL vars/commands/aliases - -void -alias(char *name, char *action) -{ - ident *b = idents->access(name); - if (!b) { - name = newstring(name); - ident b = { - ID_ALIAS, name, 0, 0, 0, 0, 0, newstring(action), true}; - idents->access(name, &b); - } else { - if (b->type == ID_ALIAS) - b->action = exchangestr(b->action, action); - else - conoutf( - "cannot redefine builtin %s with an alias", name); - }; -}; - -COMMAND(alias, ARG_2STR); - -// variable's and commands are registered through globals, see cube.h - -int -variable(char *name, int min, int cur, int max, int *storage, void (*fun)(), - bool persist) -{ - if (!idents) - idents = new hashtable; - ident v = {ID_VAR, name, min, max, storage, fun, 0, 0, persist}; - idents->access(name, &v); - return cur; -}; - -void -setvar(char *name, int i) -{ - *idents->access(name)->storage = i; -}; -int -getvar(char *name) -{ - return *idents->access(name)->storage; -}; -bool -identexists(char *name) -{ - return idents->access(name) != NULL; -}; - -char * -getalias(char *name) -{ - ident *i = idents->access(name); - return i && i->type == ID_ALIAS ? i->action : NULL; -}; - -bool -addcommand(char *name, void (*fun)(), int narg) -{ - if (!idents) - idents = new hashtable; - ident c = {ID_COMMAND, name, 0, 0, 0, fun, narg, 0, false}; - idents->access(name, &c); - return false; -}; - -char * -parseexp(char *&p, int right) // parse any nested set of () or [] -{ - 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 = newstring(word, p - word - 1); - if (left == '(') { - string t; - itoa(t, - execute( - s)); // evaluate () exps directly, and substitute result - s = exchangestr(s, t); - }; - return s; -}; - -char * -parseword(char *&p) // parse single argument, including expressions -{ - 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 = newstring(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 newstring(word, p - word); -}; - -char * -lookup(char *n) // find value of ident referenced with $ in exp -{ - ident *id = idents->access(n + 1); - if (id) - switch (id->type) { - case ID_VAR: - string t; - itoa(t, *(id->storage)); - return exchangestr(n, t); - case ID_ALIAS: - return exchangestr(n, id->action); - }; - conoutf("unknown alias lookup: %s", n + 1); - return n; -}; - -int -execute(char *p, bool isdown) // all evaluation happens here, recursively -{ - const int MAXWORDS = 25; // limit, remove - char *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; - char *s = parseword(p); // parse and evaluate exps - if (!s) { - numargs = i; - s = ""; - }; - if (*s == '$') - s = lookup(s); // substitute variables - w[i] = s; - }; - - p += strcspn(p, ";\n\0"); - cont = *p++ != - 0; // more statements if this isn't the end of the string - char *c = w[0]; - if (*c == '/') - c++; // strip irc-style command prefix - if (!*c) - continue; // empty statement - - ident *id = idents->access(c); - if (!id) { - val = ATOI(c); - if (!val && *c != '0') - conoutf("unknown command: %s", c); - } else - switch (id->type) { - case ID_COMMAND: // game defined commands - switch (id->narg) // use very ad-hoc function - // signature, and just call it - { - case ARG_1INT: - if (isdown) - ((void(__cdecl *)(int))id->fun)( - ATOI(w[1])); - break; - case ARG_2INT: - if (isdown) - ((void(__cdecl *)( - int, int))id->fun)( - ATOI(w[1]), ATOI(w[2])); - break; - case ARG_3INT: - if (isdown) - ((void(__cdecl *)(int, int, - int))id->fun)(ATOI(w[1]), - ATOI(w[2]), ATOI(w[3])); - break; - case ARG_4INT: - if (isdown) - ((void(__cdecl *)(int, int, int, - int))id->fun)(ATOI(w[1]), - ATOI(w[2]), ATOI(w[3]), - ATOI(w[4])); - break; - case ARG_NONE: - if (isdown) - ((void(__cdecl *)())id->fun)(); - break; - case ARG_1STR: - if (isdown) - ((void(__cdecl *)( - char *))id->fun)(w[1]); - break; - case ARG_2STR: - if (isdown) - ((void(__cdecl *)( - char *, char *))id->fun)( - w[1], w[2]); - break; - case ARG_3STR: - if (isdown) - ((void(__cdecl *)(char *, - char *, char *))id->fun)( - w[1], w[2], w[3]); - break; - case ARG_5STR: - if (isdown) - ((void(__cdecl *)(char *, - char *, char *, char *, - char *))id->fun)(w[1], w[2], - w[3], w[4], w[5]); - break; - case ARG_DOWN: - ((void(__cdecl *)(bool))id->fun)( - isdown); - break; - case ARG_DWN1: - ((void(__cdecl *)(bool, - char *))id->fun)(isdown, w[1]); - break; - case ARG_1EXP: - if (isdown) - val = ((int(__cdecl *)( - int))id->fun)( - execute(w[1])); - break; - case ARG_2EXP: - if (isdown) - val = ((int(__cdecl *)(int, - int))id->fun)(execute(w[1]), - execute(w[2])); - break; - case ARG_1EST: - if (isdown) - val = ((int(__cdecl *)( - char *))id->fun)(w[1]); - break; - case ARG_2EST: - if (isdown) - val = ((int(__cdecl *)( - char *, char *))id->fun)( - w[1], w[2]); - break; - case ARG_VARI: - if (isdown) { - string r; // limit, remove - r[0] = 0; - for (int i = 1; i < numargs; - i++) { - strcat_s(r, - w[i]); // make - // string-list - // out of all - // arguments - if (i == numargs - 1) - break; - strcat_s(r, " "); - }; - ((void(__cdecl *)( - char *))id->fun)(r); - break; - } - }; - break; - - case ID_VAR: // game defined variabled - if (isdown) { - if (!w[1][0]) - conoutf("%s = %d", c, - *id->storage); // var with - // no value - // just - // prints its - // current - // value - else { - if (id->min > id->max) { - conoutf("variable is " - "read-only"); - } else { - int i1 = ATOI(w[1]); - if (i1 < id->min || - i1 > id->max) { - i1 = - i1 < id->min - ? id->min - : id->max; // clamp to valid range - conoutf( - "valid " - "range for " - "%s is " - "%d..%d", - c, id->min, - id->max); - } - *id->storage = i1; - }; - if (id->fun) - ((void(__cdecl *)())id - ->fun)(); // call - // trigger - // function - // if - // available - }; - }; - break; - - case ID_ALIAS: // alias, also used as functions and - // (global) variables - for (int i = 1; i < numargs; i++) { - sprintf_sd(t)("arg%d", - i); // set any arguments as (global) - // arg values so functions can - // access them - alias(t, w[i]); - }; - char *action = newstring( - id->action); // create new string here - // because alias could rebind - // itself - val = execute(action, isdown); - gp()->deallocstr(action); - break; - }; - loopj(numargs) gp()->deallocstr(w[j]); - }; - return val; -}; - -// tab-completion of all idents - -int completesize = 0, completeidx = 0; - -void -resetcomplete() -{ - completesize = 0; -}; - -void -complete(char *s) -{ - if (*s != '/') { - string t; - strcpy_s(t, s); - strcpy_s(s, "/"); - strcat_s(s, t); - }; - if (!s[1]) - return; - if (!completesize) { - completesize = (int)strlen(s) - 1; - completeidx = 0; - }; - int idx = 0; - enumerate( - idents, ident *, id, - if (strncmp(id->name, s + 1, completesize) == 0 && - idx++ == completeidx) { - strcpy_s(s, "/"); - strcat_s(s, id->name); - };); - completeidx++; - if (completeidx >= idx) - completeidx = 0; -}; - -bool -execfile(char *cfgfile) -{ - string s; - strcpy_s(s, cfgfile); - char *buf = loadfile(path(s), NULL); - if (!buf) - return false; - execute(buf); - free(buf); - return true; -}; - -void -exec(char *cfgfile) -{ - if (!execfile(cfgfile)) - conoutf("could not read \"%s\"", cfgfile); -}; - -void -writecfg() -{ - FILE *f = fopen("config.cfg", "w"); - if (!f) - return; - fprintf(f, "// 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(f); - fprintf(f, "\n"); - enumerate( - idents, ident *, id, if (id->type == ID_VAR && id->persist) { - fprintf(f, "%s %d\n", id->name, *id->storage); - };); - fprintf(f, "\n"); - writebinds(f); - fprintf(f, "\n"); - enumerate( - idents, ident *, id, - if (id->type == ID_ALIAS && !strstr(id->name, "nextmap_")) { - fprintf(f, "alias \"%s\" [%s]\n", id->name, id->action); - };); - fclose(f); -}; - -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(char *name, int v) -{ - string b; - itoa(b, v); - alias(name, b); -}; - -void -ifthen(char *cond, char *thenp, char *elsep) -{ - execute(cond[0] != '0' ? thenp : elsep); -}; -void -loopa(char *times, char *body) -{ - int t = atoi(times); - loopi(t) - { - intset("i", i); - execute(body); - }; -}; -void -whilea(char *cond, char *body) -{ - while (execute(cond)) - execute(body); -}; // can't get any simpler than this :) -void -onrelease(bool on, char *body) -{ - if (!on) - execute(body); -}; - -void -concat(char *s) -{ - alias("s", s); -}; - -void -concatword(char *s) -{ - for (char *a = s, *b = s; *a = *b; b++) - if (*a != ' ') - a++; - concat(s); -}; - -int -listlen(char *a) -{ - if (!*a) - return 0; - int n = 0; - while (*a) - if (*a++ == ' ') - n++; - return n + 1; -}; - -void -at(char *s, char *pos) -{ - int n = atoi(pos); - 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(char *a, char *b) -{ - return strcmp(a, b) == 0; -}; -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); ADDED src/command.mm Index: src/command.mm ================================================================== --- /dev/null +++ src/command.mm @@ -0,0 +1,627 @@ +// 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" + +enum { ID_VAR, ID_COMMAND, ID_ALIAS }; + +struct ident { + int type; // one of ID_* above + char *name; + int min, max; // ID_VAR + int *storage; // ID_VAR + void (*fun)(); // ID_VAR, ID_COMMAND + int narg; // ID_VAR, ID_COMMAND + char *action; // ID_ALIAS + bool persist; +}; + +void +itoa(char *s, int i) +{ + sprintf_s(s)("%d", i); +}; +char * +exchangestr(char *o, char *n) +{ + gp()->deallocstr(o); + return newstring(n); +}; + +hashtable *idents = NULL; // contains ALL vars/commands/aliases + +void +alias(char *name, char *action) +{ + ident *b = idents->access(name); + if (!b) { + name = newstring(name); + ident b = { + ID_ALIAS, name, 0, 0, 0, 0, 0, newstring(action), true}; + idents->access(name, &b); + } else { + if (b->type == ID_ALIAS) + b->action = exchangestr(b->action, action); + else + conoutf( + "cannot redefine builtin %s with an alias", name); + }; +}; + +COMMAND(alias, ARG_2STR); + +// variable's and commands are registered through globals, see cube.h + +int +variable(char *name, int min, int cur, int max, int *storage, void (*fun)(), + bool persist) +{ + if (!idents) + idents = new hashtable; + ident v = {ID_VAR, name, min, max, storage, fun, 0, 0, persist}; + idents->access(name, &v); + return cur; +}; + +void +setvar(char *name, int i) +{ + *idents->access(name)->storage = i; +}; +int +getvar(char *name) +{ + return *idents->access(name)->storage; +}; +bool +identexists(char *name) +{ + return idents->access(name) != NULL; +}; + +char * +getalias(char *name) +{ + ident *i = idents->access(name); + return i && i->type == ID_ALIAS ? i->action : NULL; +}; + +bool +addcommand(char *name, void (*fun)(), int narg) +{ + if (!idents) + idents = new hashtable; + ident c = {ID_COMMAND, name, 0, 0, 0, fun, narg, 0, false}; + idents->access(name, &c); + return false; +}; + +char * +parseexp(char *&p, int right) // parse any nested set of () or [] +{ + 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 = newstring(word, p - word - 1); + if (left == '(') { + string t; + itoa(t, + execute( + s)); // evaluate () exps directly, and substitute result + s = exchangestr(s, t); + }; + return s; +}; + +char * +parseword(char *&p) // parse single argument, including expressions +{ + 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 = newstring(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 newstring(word, p - word); +}; + +char * +lookup(char *n) // find value of ident referenced with $ in exp +{ + ident *ID = idents->access(n + 1); + if (ID) + switch (ID->type) { + case ID_VAR: + string t; + itoa(t, *(ID->storage)); + return exchangestr(n, t); + case ID_ALIAS: + return exchangestr(n, ID->action); + }; + conoutf("unknown alias lookup: %s", n + 1); + return n; +}; + +int +execute(char *p, bool isdown) // all evaluation happens here, recursively +{ + const int MAXWORDS = 25; // limit, remove + char *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; + char *s = parseword(p); // parse and evaluate exps + if (!s) { + numargs = i; + s = ""; + }; + if (*s == '$') + s = lookup(s); // substitute variables + w[i] = s; + }; + + p += strcspn(p, ";\n\0"); + cont = *p++ != + 0; // more statements if this isn't the end of the string + char *c = w[0]; + if (*c == '/') + c++; // strip irc-style command prefix + if (!*c) + continue; // empty statement + + ident *ID = idents->access(c); + if (!ID) { + val = ATOI(c); + if (!val && *c != '0') + conoutf("unknown command: %s", c); + } else + switch (ID->type) { + case ID_COMMAND: // game defined commands + switch (ID->narg) // use very ad-hoc function + // signature, and just call it + { + case ARG_1INT: + if (isdown) + ((void(__cdecl *)(int))ID->fun)( + ATOI(w[1])); + break; + case ARG_2INT: + if (isdown) + ((void(__cdecl *)( + int, int))ID->fun)( + ATOI(w[1]), ATOI(w[2])); + break; + case ARG_3INT: + if (isdown) + ((void(__cdecl *)(int, int, + int))ID->fun)(ATOI(w[1]), + ATOI(w[2]), ATOI(w[3])); + break; + case ARG_4INT: + if (isdown) + ((void(__cdecl *)(int, int, int, + int))ID->fun)(ATOI(w[1]), + ATOI(w[2]), ATOI(w[3]), + ATOI(w[4])); + break; + case ARG_NONE: + if (isdown) + ((void(__cdecl *)())ID->fun)(); + break; + case ARG_1STR: + if (isdown) + ((void(__cdecl *)( + char *))ID->fun)(w[1]); + break; + case ARG_2STR: + if (isdown) + ((void(__cdecl *)( + char *, char *))ID->fun)( + w[1], w[2]); + break; + case ARG_3STR: + if (isdown) + ((void(__cdecl *)(char *, + char *, char *))ID->fun)( + w[1], w[2], w[3]); + break; + case ARG_5STR: + if (isdown) + ((void(__cdecl *)(char *, + char *, char *, char *, + char *))ID->fun)(w[1], w[2], + w[3], w[4], w[5]); + break; + case ARG_DOWN: + ((void(__cdecl *)(bool))ID->fun)( + isdown); + break; + case ARG_DWN1: + ((void(__cdecl *)(bool, + char *))ID->fun)(isdown, w[1]); + break; + case ARG_1EXP: + if (isdown) + val = ((int(__cdecl *)( + int))ID->fun)( + execute(w[1])); + break; + case ARG_2EXP: + if (isdown) + val = ((int(__cdecl *)(int, + int))ID->fun)(execute(w[1]), + execute(w[2])); + break; + case ARG_1EST: + if (isdown) + val = ((int(__cdecl *)( + char *))ID->fun)(w[1]); + break; + case ARG_2EST: + if (isdown) + val = ((int(__cdecl *)( + char *, char *))ID->fun)( + w[1], w[2]); + break; + case ARG_VARI: + if (isdown) { + string r; // limit, remove + r[0] = 0; + for (int i = 1; i < numargs; + i++) { + strcat_s(r, + w[i]); // make + // string-list + // out of all + // arguments + if (i == numargs - 1) + break; + strcat_s(r, " "); + }; + ((void(__cdecl *)( + char *))ID->fun)(r); + break; + } + }; + break; + + case ID_VAR: // game defined variabled + if (isdown) { + if (!w[1][0]) + conoutf("%s = %d", c, + *ID->storage); // var with + // no value + // just + // prints its + // current + // value + else { + if (ID->min > ID->max) { + conoutf("variable is " + "read-only"); + } else { + int i1 = ATOI(w[1]); + if (i1 < ID->min || + i1 > ID->max) { + i1 = + i1 < ID->min + ? ID->min + : ID->max; // clamp to valid range + conoutf( + "valid " + "range for " + "%s is " + "%d..%d", + c, ID->min, + ID->max); + } + *ID->storage = i1; + }; + if (ID->fun) + ((void(__cdecl *)())ID + ->fun)(); // call + // trigger + // function + // if + // available + }; + }; + break; + + case ID_ALIAS: // alias, also used as functions and + // (global) variables + for (int i = 1; i < numargs; i++) { + sprintf_sd(t)("arg%d", + i); // set any arguments as (global) + // arg values so functions can + // access them + alias(t, w[i]); + }; + char *action = newstring( + ID->action); // create new string here + // because alias could rebind + // itself + val = execute(action, isdown); + gp()->deallocstr(action); + break; + }; + loopj(numargs) gp()->deallocstr(w[j]); + }; + return val; +}; + +// tab-completion of all idents + +int completesize = 0, completeidx = 0; + +void +resetcomplete() +{ + completesize = 0; +}; + +void +complete(char *s) +{ + if (*s != '/') { + string t; + strcpy_s(t, s); + strcpy_s(s, "/"); + strcat_s(s, t); + }; + if (!s[1]) + return; + if (!completesize) { + completesize = (int)strlen(s) - 1; + completeidx = 0; + }; + int idx = 0; + enumerate( + idents, ident *, id, + if (strncmp(id->name, s + 1, completesize) == 0 && + idx++ == completeidx) { + strcpy_s(s, "/"); + strcat_s(s, id->name); + };); + completeidx++; + if (completeidx >= idx) + completeidx = 0; +}; + +bool +execfile(char *cfgfile) +{ + string s; + strcpy_s(s, cfgfile); + char *buf = loadfile(path(s), NULL); + if (!buf) + return false; + execute(buf); + free(buf); + return true; +}; + +void +exec(char *cfgfile) +{ + if (!execfile(cfgfile)) + conoutf("could not read \"%s\"", cfgfile); +}; + +void +writecfg() +{ + FILE *f = fopen("config.cfg", "w"); + if (!f) + return; + fprintf(f, "// 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(f); + fprintf(f, "\n"); + enumerate( + idents, ident *, ID, if (ID->type == ID_VAR && ID->persist) { + fprintf(f, "%s %d\n", ID->name, *ID->storage); + };); + fprintf(f, "\n"); + writebinds(f); + fprintf(f, "\n"); + enumerate( + idents, ident *, ID, + if (ID->type == ID_ALIAS && !strstr(ID->name, "nextmap_")) { + fprintf(f, "alias \"%s\" [%s]\n", ID->name, ID->action); + };); + fclose(f); +}; + +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(char *name, int v) +{ + string b; + itoa(b, v); + alias(name, b); +}; + +void +ifthen(char *cond, char *thenp, char *elsep) +{ + execute(cond[0] != '0' ? thenp : elsep); +}; +void +loopa(char *times, char *body) +{ + int t = atoi(times); + loopi(t) + { + intset("i", i); + execute(body); + }; +}; +void +whilea(char *cond, char *body) +{ + while (execute(cond)) + execute(body); +}; // can't get any simpler than this :) +void +onrelease(bool on, char *body) +{ + if (!on) + execute(body); +}; + +void +concat(char *s) +{ + alias("s", s); +}; + +void +concatword(char *s) +{ + for (char *a = s, *b = s; *a = *b; b++) + if (*a != ' ') + a++; + concat(s); +}; + +int +listlen(char *a) +{ + if (!*a) + return 0; + int n = 0; + while (*a) + if (*a++ == ' ') + n++; + return n + 1; +}; + +void +at(char *s, char *pos) +{ + int n = atoi(pos); + 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(char *a, char *b) +{ + return strcmp(a, b) == 0; +}; +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/console.cxx Index: src/console.cxx ================================================================== --- src/console.cxx +++ /dev/null @@ -1,303 +0,0 @@ -// console.cpp: the console buffer, its display, and command line control - -#include "cube.h" -#include - -struct cline { - char *cref; - int outtime; -}; -vector conlines; - -const int ndraw = 5; -const int WORDWRAP = 80; -int conskip = 0; - -bool saycommandon = false; -string commandbuf; - -void -setconskip(int n) -{ - conskip += n; - if (conskip < 0) - conskip = 0; -}; - -COMMANDN(conskip, setconskip, ARG_1INT); - -void -conline(const char *sf, bool highlight) // add a line to the console buffer -{ - cline cl; - cl.cref = conlines.length() > 100 - ? conlines.pop().cref - : newstringbuf(""); // constrain the buffer size - cl.outtime = lastmillis; // for how long to keep line on screen - conlines.insert(0, cl); - if (highlight) // show line in a different colour, for chat etc. - { - cl.cref[0] = '\f'; - cl.cref[1] = 0; - strcat_s(cl.cref, sf); - } else { - strcpy_s(cl.cref, sf); - }; - puts(cl.cref); -#ifndef _WIN32 - fflush(stdout); -#endif -}; - -void -conoutf(const char *s, ...) -{ - sprintf_sdv(sf, s); - s = sf; - int n = 0; - while (strlen(s) > WORDWRAP) // cut strings to fit on screen - { - string t; - strn0cpy(t, s, WORDWRAP + 1); - conline(t, n++ != 0); - s += WORDWRAP; - }; - conline(s, n != 0); -}; - -void -renderconsole() // render buffer taking into account time & scrolling -{ - int nd = 0; - char *refs[ndraw]; - loopv(conlines) if (conskip ? i >= conskip - 1 || - i >= conlines.length() - ndraw - : lastmillis - conlines[i].outtime < 20000) - { - refs[nd++] = conlines[i].cref; - if (nd == ndraw) - break; - }; - loopj(nd) - { - draw_text(refs[j], FONTH / 3, - (FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2); - }; -}; - -// keymap is defined externally in keymap.cfg - -struct keym { - int code; - char *name; - char *action; -} keyms[256]; -int numkm = 0; - -void -keymap(char *code, char *key, char *action) -{ - keyms[numkm].code = atoi(code); - keyms[numkm].name = newstring(key); - keyms[numkm++].action = newstringbuf(action); -}; - -COMMAND(keymap, ARG_3STR); - -void -bindkey(char *key, char *action) -{ - for (char *x = key; *x; x++) - *x = toupper(*x); - loopi(numkm) if (strcmp(keyms[i].name, key) == 0) - { - strcpy_s(keyms[i].action, action); - return; - }; - conoutf("unknown key \"%s\"", key); -}; - -COMMANDN(bind, bindkey, ARG_2STR); - -void -saycommand(char *init) // turns input to the command line on or off -{ - SDL_EnableUNICODE(saycommandon = (init != NULL)); - if (!editmode) - keyrepeat(saycommandon); - if (!init) - init = ""; - strcpy_s(commandbuf, init); -}; - -void -mapmsg(char *s) -{ - strn0cpy(hdr.maptitle, s, 128); -}; - -COMMAND(saycommand, ARG_VARI); -COMMAND(mapmsg, ARG_1STR); - -#ifndef _WIN32 -#include -#include -#endif - -void -pasteconsole() -{ -#ifdef _WIN32 - if (!IsClipboardFormatAvailable(CF_TEXT)) - return; - if (!OpenClipboard(NULL)) - return; - char *cb = (char *)GlobalLock(GetClipboardData(CF_TEXT)); - strcat_s(commandbuf, cb); - GlobalUnlock(cb); - CloseClipboard(); -#else - SDL_SysWMinfo wminfo; - SDL_VERSION(&wminfo.version); - wminfo.subsystem = SDL_SYSWM_X11; - if (!SDL_GetWMInfo(&wminfo)) - return; - int cbsize; - char *cb = XFetchBytes(wminfo.info.x11.display, &cbsize); - if (!cb || !cbsize) - return; - int commandlen = strlen(commandbuf); - for (char *cbline = cb, *cbend; - commandlen + 1 < _MAXDEFSTR && cbline < &cb[cbsize]; - cbline = cbend + 1) { - cbend = (char *)memchr(cbline, '\0', &cb[cbsize] - cbline); - if (!cbend) - cbend = &cb[cbsize]; - if (commandlen + cbend - cbline + 1 > _MAXDEFSTR) - cbend = cbline + _MAXDEFSTR - commandlen - 1; - memcpy(&commandbuf[commandlen], cbline, cbend - cbline); - commandlen += cbend - cbline; - commandbuf[commandlen] = '\n'; - if (commandlen + 1 < _MAXDEFSTR && cbend < &cb[cbsize]) - ++commandlen; - commandbuf[commandlen] = '\0'; - }; - XFree(cb); -#endif -}; - -cvector vhistory; -int histpos = 0; - -void -history(int n) -{ - static bool rec = false; - if (!rec && n >= 0 && n < vhistory.length()) { - rec = true; - execute(vhistory[vhistory.length() - n - 1]); - rec = false; - }; -}; - -COMMAND(history, ARG_1INT); - -void -keypress(int code, bool isdown, int cooked) -{ - if (saycommandon) // keystrokes go to commandline - { - if (isdown) { - switch (code) { - case SDLK_RETURN: - break; - - case SDLK_BACKSPACE: - case SDLK_LEFT: { - for (int i = 0; commandbuf[i]; i++) - if (!commandbuf[i + 1]) - commandbuf[i] = 0; - resetcomplete(); - break; - }; - - case SDLK_UP: - if (histpos) - strcpy_s( - commandbuf, vhistory[--histpos]); - break; - - case SDLK_DOWN: - if (histpos < vhistory.length()) - strcpy_s( - commandbuf, vhistory[histpos++]); - break; - - case SDLK_TAB: - complete(commandbuf); - break; - - case SDLK_v: - if (SDL_GetModState() & - (KMOD_LCTRL | KMOD_RCTRL)) { - pasteconsole(); - return; - }; - - default: - resetcomplete(); - if (cooked) { - char add[] = {(char)cooked, 0}; - strcat_s(commandbuf, add); - }; - }; - } else { - if (code == SDLK_RETURN) { - if (commandbuf[0]) { - if (vhistory.empty() || - strcmp( - vhistory.last(), commandbuf)) { - vhistory.add(newstring( - commandbuf)); // cap this? - }; - histpos = vhistory.length(); - if (commandbuf[0] == '/') - execute(commandbuf, true); - else - toserver(commandbuf); - }; - saycommand(NULL); - } else if (code == SDLK_ESCAPE) { - saycommand(NULL); - }; - }; - } else if (!menukey(code, isdown)) // keystrokes go to menu - { - loopi(numkm) if (keyms[i].code == - code) // keystrokes go to game, lookup in - // keymap and execute - { - string temp; - strcpy_s(temp, keyms[i].action); - execute(temp, isdown); - return; - }; - }; -}; - -char * -getcurcommand() -{ - return saycommandon ? commandbuf : NULL; -}; - -void -writebinds(FILE *f) -{ - loopi(numkm) - { - if (*keyms[i].action) - fprintf(f, "bind \"%s\" [%s]\n", keyms[i].name, - keyms[i].action); - }; -}; ADDED src/console.mm Index: src/console.mm ================================================================== --- /dev/null +++ src/console.mm @@ -0,0 +1,303 @@ +// console.cpp: the console buffer, its display, and command line control + +#include "cube.h" +#include + +struct cline { + char *cref; + int outtime; +}; +vector conlines; + +const int ndraw = 5; +const int WORDWRAP = 80; +int conskip = 0; + +bool saycommandon = false; +string commandbuf; + +void +setconskip(int n) +{ + conskip += n; + if (conskip < 0) + conskip = 0; +}; + +COMMANDN(conskip, setconskip, ARG_1INT); + +void +conline(const char *sf, bool highlight) // add a line to the console buffer +{ + cline cl; + cl.cref = conlines.length() > 100 + ? conlines.pop().cref + : newstringbuf(""); // constrain the buffer size + cl.outtime = lastmillis; // for how long to keep line on screen + conlines.insert(0, cl); + if (highlight) // show line in a different colour, for chat etc. + { + cl.cref[0] = '\f'; + cl.cref[1] = 0; + strcat_s(cl.cref, sf); + } else { + strcpy_s(cl.cref, sf); + }; + puts(cl.cref); +#ifndef _WIN32 + fflush(stdout); +#endif +}; + +void +conoutf(const char *s, ...) +{ + sprintf_sdv(sf, s); + s = sf; + int n = 0; + while (strlen(s) > WORDWRAP) // cut strings to fit on screen + { + string t; + strn0cpy(t, s, WORDWRAP + 1); + conline(t, n++ != 0); + s += WORDWRAP; + }; + conline(s, n != 0); +}; + +void +renderconsole() // render buffer taking into account time & scrolling +{ + int nd = 0; + char *refs[ndraw]; + loopv(conlines) if (conskip ? i >= conskip - 1 || + i >= conlines.length() - ndraw + : lastmillis - conlines[i].outtime < 20000) + { + refs[nd++] = conlines[i].cref; + if (nd == ndraw) + break; + }; + loopj(nd) + { + draw_text(refs[j], FONTH / 3, + (FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2); + }; +}; + +// keymap is defined externally in keymap.cfg + +struct keym { + int code; + char *name; + char *action; +} keyms[256]; +int numkm = 0; + +void +keymap(char *code, char *key, char *action) +{ + keyms[numkm].code = atoi(code); + keyms[numkm].name = newstring(key); + keyms[numkm++].action = newstringbuf(action); +}; + +COMMAND(keymap, ARG_3STR); + +void +bindkey(char *key, char *action) +{ + for (char *x = key; *x; x++) + *x = toupper(*x); + loopi(numkm) if (strcmp(keyms[i].name, key) == 0) + { + strcpy_s(keyms[i].action, action); + return; + }; + conoutf("unknown key \"%s\"", key); +}; + +COMMANDN(bind, bindkey, ARG_2STR); + +void +saycommand(char *init) // turns input to the command line on or off +{ + SDL_EnableUNICODE(saycommandon = (init != NULL)); + if (!editmode) + keyrepeat(saycommandon); + if (!init) + init = ""; + strcpy_s(commandbuf, init); +}; + +void +mapmsg(char *s) +{ + strn0cpy(hdr.maptitle, s, 128); +}; + +COMMAND(saycommand, ARG_VARI); +COMMAND(mapmsg, ARG_1STR); + +#ifndef _WIN32 +#include +#include +#endif + +void +pasteconsole() +{ +#ifdef _WIN32 + if (!IsClipboardFormatAvailable(CF_TEXT)) + return; + if (!OpenClipboard(NULL)) + return; + char *cb = (char *)GlobalLock(GetClipboardData(CF_TEXT)); + strcat_s(commandbuf, cb); + GlobalUnlock(cb); + CloseClipboard(); +#else + SDL_SysWMinfo wminfo; + SDL_VERSION(&wminfo.version); + wminfo.subsystem = SDL_SYSWM_X11; + if (!SDL_GetWMInfo(&wminfo)) + return; + int cbsize; + char *cb = XFetchBytes(wminfo.info.x11.display, &cbsize); + if (!cb || !cbsize) + return; + int commandlen = strlen(commandbuf); + for (char *cbline = cb, *cbend; + commandlen + 1 < _MAXDEFSTR && cbline < &cb[cbsize]; + cbline = cbend + 1) { + cbend = (char *)memchr(cbline, '\0', &cb[cbsize] - cbline); + if (!cbend) + cbend = &cb[cbsize]; + if (commandlen + cbend - cbline + 1 > _MAXDEFSTR) + cbend = cbline + _MAXDEFSTR - commandlen - 1; + memcpy(&commandbuf[commandlen], cbline, cbend - cbline); + commandlen += cbend - cbline; + commandbuf[commandlen] = '\n'; + if (commandlen + 1 < _MAXDEFSTR && cbend < &cb[cbsize]) + ++commandlen; + commandbuf[commandlen] = '\0'; + }; + XFree(cb); +#endif +}; + +cvector vhistory; +int histpos = 0; + +void +history(int n) +{ + static bool rec = false; + if (!rec && n >= 0 && n < vhistory.length()) { + rec = true; + execute(vhistory[vhistory.length() - n - 1]); + rec = false; + }; +}; + +COMMAND(history, ARG_1INT); + +void +keypress(int code, bool isdown, int cooked) +{ + if (saycommandon) // keystrokes go to commandline + { + if (isdown) { + switch (code) { + case SDLK_RETURN: + break; + + case SDLK_BACKSPACE: + case SDLK_LEFT: { + for (int i = 0; commandbuf[i]; i++) + if (!commandbuf[i + 1]) + commandbuf[i] = 0; + resetcomplete(); + break; + }; + + case SDLK_UP: + if (histpos) + strcpy_s( + commandbuf, vhistory[--histpos]); + break; + + case SDLK_DOWN: + if (histpos < vhistory.length()) + strcpy_s( + commandbuf, vhistory[histpos++]); + break; + + case SDLK_TAB: + complete(commandbuf); + break; + + case SDLK_v: + if (SDL_GetModState() & + (KMOD_LCTRL | KMOD_RCTRL)) { + pasteconsole(); + return; + }; + + default: + resetcomplete(); + if (cooked) { + char add[] = {(char)cooked, 0}; + strcat_s(commandbuf, add); + }; + }; + } else { + if (code == SDLK_RETURN) { + if (commandbuf[0]) { + if (vhistory.empty() || + strcmp( + vhistory.last(), commandbuf)) { + vhistory.add(newstring( + commandbuf)); // cap this? + }; + histpos = vhistory.length(); + if (commandbuf[0] == '/') + execute(commandbuf, true); + else + toserver(commandbuf); + }; + saycommand(NULL); + } else if (code == SDLK_ESCAPE) { + saycommand(NULL); + }; + }; + } else if (!menukey(code, isdown)) // keystrokes go to menu + { + loopi(numkm) if (keyms[i].code == + code) // keystrokes go to game, lookup in + // keymap and execute + { + string temp; + strcpy_s(temp, keyms[i].action); + execute(temp, isdown); + return; + }; + }; +}; + +char * +getcurcommand() +{ + return saycommandon ? commandbuf : NULL; +}; + +void +writebinds(FILE *f) +{ + loopi(numkm) + { + if (*keyms[i].action) + fprintf(f, "bind \"%s\" [%s]\n", keyms[i].name, + keyms[i].action); + }; +}; DELETED src/editing.cxx Index: src/editing.cxx ================================================================== --- src/editing.cxx +++ /dev/null @@ -1,617 +0,0 @@ -// editing.cpp: most map editing commands go here, entity editing commands are -// in world.cpp - -#include "cube.h" - -bool editmode = false; - -// the current selection, used by almost all editing commands -// invariant: all code assumes that these are kept inside MINBORD distance of -// the edge of the map - -block sel = { - variable("selx", 0, 0, 4096, &sel.x, NULL, false), - variable("sely", 0, 0, 4096, &sel.y, NULL, false), - variable("selxs", 0, 0, 4096, &sel.xs, NULL, false), - variable("selys", 0, 0, 4096, &sel.ys, NULL, false), -}; - -int selh = 0; -bool selset = false; - -#define loopselxy(b) \ - { \ - makeundo(); \ - loop(x, sel.xs) loop(y, sel.ys) \ - { \ - sqr *s = S(sel.x + x, sel.y + y); \ - b; \ - }; \ - remip(sel); \ - } - -int cx, cy, ch; - -int curedittex[] = {-1, -1, -1}; - -bool dragging = false; -int lastx, lasty, lasth; - -int lasttype = 0, lasttex = 0; -sqr rtex; - -VAR(editing, 0, 0, 1); - -void -toggleedit() -{ - if (player1->state == CS_DEAD) - return; // do not allow dead players to edit to avoid state - // confusion - if (!editmode && !allowedittoggle()) - return; // not in most multiplayer modes - if (!(editmode = !editmode)) { - settagareas(); // reset triggers to allow quick playtesting - entinmap(player1); // find spawn closest to current floating pos - } else { - resettagareas(); // clear trigger areas to allow them to be - // edited - player1->health = 100; - if (m_classicsp) - monsterclear(); // all monsters back at their spawns for - // editing - projreset(); - }; - keyrepeat(editmode); - selset = false; - editing = editmode; -}; - -COMMANDN(edittoggle, toggleedit, ARG_NONE); - -void -correctsel() // ensures above invariant -{ - selset = !OUTBORD(sel.x, sel.y); - int bsize = ssize - MINBORD; - if (sel.xs + sel.x > bsize) - sel.xs = bsize - sel.x; - if (sel.ys + sel.y > bsize) - sel.ys = bsize - sel.y; - if (sel.xs <= 0 || sel.ys <= 0) - selset = false; -}; - -bool -noteditmode() -{ - correctsel(); - if (!editmode) - conoutf("this function is only allowed in edit mode"); - return !editmode; -}; - -bool -noselection() -{ - if (!selset) - conoutf("no selection"); - return !selset; -}; - -#define EDITSEL \ - if (noteditmode() || noselection()) \ - return; -#define EDITSELMP \ - if (noteditmode() || noselection() || multiplayer()) \ - return; -#define EDITMP \ - if (noteditmode() || multiplayer()) \ - return; - -void -selectpos(int x, int y, int xs, int ys) -{ - block s = {x, y, xs, ys}; - sel = s; - selh = 0; - correctsel(); -}; - -void -makesel() -{ - block s = {min(lastx, cx), min(lasty, cy), abs(lastx - cx) + 1, - abs(lasty - cy) + 1}; - sel = s; - selh = max(lasth, ch); - correctsel(); - if (selset) - rtex = *S(sel.x, sel.y); -}; - -VAR(flrceil, 0, 0, 2); - -float -sheight( - sqr *s, sqr *t, float z) // finds out z height when cursor points at wall -{ - return !flrceil // z-s->floorceil-z - ? (s->type == FHF ? s->floor - t->vdelta / 4.0f - : (float)s->floor) - : (s->type == CHF ? s->ceil + t->vdelta / 4.0f - : (float)s->ceil); -}; - -void -cursorupdate() // called every frame from hud -{ - flrceil = ((int)(player1->pitch >= 0)) * 2; - - volatile float x = - worldpos.x; // volatile needed to prevent msvc7 optimizer bug? - volatile float y = worldpos.y; - volatile float z = worldpos.z; - - cx = (int)x; - cy = (int)y; - - if (OUTBORD(cx, cy)) - return; - sqr *s = S(cx, cy); - - if (fabs(sheight(s, s, z) - z) > 1) // selected wall - { - x += x > player1->o.x ? 0.5f : -0.5f; // find right wall cube - y += y > player1->o.y ? 0.5f : -0.5f; - - cx = (int)x; - cy = (int)y; - - if (OUTBORD(cx, cy)) - return; - }; - - if (dragging) - makesel(); - - const int GRIDSIZE = 5; - const float GRIDW = 0.5f; - const float GRID8 = 2.0f; - const float GRIDS = 2.0f; - const int GRIDM = 0x7; - - // render editing grid - - for (int ix = cx - GRIDSIZE; ix <= cx + GRIDSIZE; ix++) - for (int iy = cy - GRIDSIZE; iy <= cy + GRIDSIZE; iy++) { - if (OUTBORD(ix, iy)) - continue; - sqr *s = S(ix, iy); - if (SOLID(s)) - continue; - float h1 = sheight(s, s, z); - float h2 = sheight(s, SWS(s, 1, 0, ssize), z); - float h3 = sheight(s, SWS(s, 1, 1, ssize), z); - float h4 = sheight(s, SWS(s, 0, 1, ssize), z); - if (s->tag) - linestyle(GRIDW, 0xFF, 0x40, 0x40); - else if (s->type == FHF || s->type == CHF) - linestyle(GRIDW, 0x80, 0xFF, 0x80); - else - linestyle(GRIDW, 0x80, 0x80, 0x80); - block b = {ix, iy, 1, 1}; - box(b, h1, h2, h3, h4); - linestyle(GRID8, 0x40, 0x40, 0xFF); - if (!(ix & GRIDM)) - line(ix, iy, h1, ix, iy + 1, h4); - if (!(ix + 1 & GRIDM)) - line(ix + 1, iy, h2, ix + 1, iy + 1, h3); - if (!(iy & GRIDM)) - line(ix, iy, h1, ix + 1, iy, h2); - if (!(iy + 1 & GRIDM)) - line(ix, iy + 1, h4, ix + 1, iy + 1, h3); - }; - - if (!SOLID(s)) { - float ih = sheight(s, s, z); - linestyle(GRIDS, 0xFF, 0xFF, 0xFF); - block b = {cx, cy, 1, 1}; - box(b, ih, sheight(s, SWS(s, 1, 0, ssize), z), - sheight(s, SWS(s, 1, 1, ssize), z), - sheight(s, SWS(s, 0, 1, ssize), z)); - linestyle(GRIDS, 0xFF, 0x00, 0x00); - dot(cx, cy, ih); - ch = (int)ih; - }; - - if (selset) { - linestyle(GRIDS, 0xFF, 0x40, 0x40); - box(sel, (float)selh, (float)selh, (float)selh, (float)selh); - }; -}; - -vector undos; // unlimited undo -VARP(undomegs, 0, 1, 10); // bounded by n megs - -void -pruneundos(int maxremain) // bound memory -{ - int t = 0; - loopvrev(undos) - { - t += undos[i]->xs * undos[i]->ys * sizeof(sqr); - if (t > maxremain) - free(undos.remove(i)); - }; -}; - -void -makeundo() -{ - undos.add(blockcopy(sel)); - pruneundos(undomegs << 20); -}; - -void -editundo() -{ - EDITMP; - if (undos.empty()) { - conoutf("nothing more to undo"); - return; - }; - block *p = undos.pop(); - blockpaste(*p); - free(p); -}; - -block *copybuf = NULL; - -void -copy() -{ - EDITSELMP; - if (copybuf) - free(copybuf); - copybuf = blockcopy(sel); -}; - -void -paste() -{ - EDITMP; - if (!copybuf) { - conoutf("nothing to paste"); - return; - }; - sel.xs = copybuf->xs; - sel.ys = copybuf->ys; - correctsel(); - if (!selset || sel.xs != copybuf->xs || sel.ys != copybuf->ys) { - conoutf("incorrect selection"); - return; - }; - makeundo(); - copybuf->x = sel.x; - copybuf->y = sel.y; - blockpaste(*copybuf); -}; - -void -tofronttex() // maintain most recently used of the texture lists when applying - // texture -{ - loopi(3) - { - int c = curedittex[i]; - if (c >= 0) { - uchar *p = hdr.texlists[i]; - int t = p[c]; - for (int a = c - 1; a >= 0; a--) - p[a + 1] = p[a]; - p[0] = t; - curedittex[i] = -1; - }; - }; -}; - -void -editdrag(bool isdown) -{ - if (dragging = isdown) { - lastx = cx; - lasty = cy; - lasth = ch; - selset = false; - tofronttex(); - }; - makesel(); -}; - -// the core editing function. all the *xy functions perform the core operations -// and are also called directly from the network, the function below it is -// strictly triggered locally. They all have very similar structure. - -void -editheightxy(bool isfloor, int amount, block &sel) -{ - loopselxy( - if (isfloor) { - s->floor += amount; - if (s->floor >= s->ceil) - s->floor = s->ceil - 1; - } else { - s->ceil += amount; - if (s->ceil <= s->floor) - s->ceil = s->floor + 1; - }); -}; - -void -editheight(int flr, int amount) -{ - EDITSEL; - bool isfloor = flr == 0; - editheightxy(isfloor, amount, sel); - addmsg(1, 7, SV_EDITH, sel.x, sel.y, sel.xs, sel.ys, isfloor, amount); -}; - -COMMAND(editheight, ARG_2INT); - -void -edittexxy(int type, int t, block &sel) -{ - loopselxy(switch (type) { - case 0: - s->ftex = t; - break; - case 1: - s->wtex = t; - break; - case 2: - s->ctex = t; - break; - case 3: - s->utex = t; - break; - }); -}; - -void -edittex(int type, int dir) -{ - EDITSEL; - if (type < 0 || type > 3) - return; - if (type != lasttype) { - tofronttex(); - lasttype = type; - }; - int atype = type == 3 ? 1 : type; - int i = curedittex[atype]; - i = i < 0 ? 0 : i + dir; - curedittex[atype] = i = min(max(i, 0), 255); - int t = lasttex = hdr.texlists[atype][i]; - edittexxy(type, t, sel); - addmsg(1, 7, SV_EDITT, sel.x, sel.y, sel.xs, sel.ys, type, t); -}; - -void -replace() -{ - EDITSELMP; - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - switch (lasttype) { - case 0: - if (s->ftex == rtex.ftex) - s->ftex = lasttex; - break; - case 1: - if (s->wtex == rtex.wtex) - s->wtex = lasttex; - break; - case 2: - if (s->ctex == rtex.ctex) - s->ctex = lasttex; - break; - case 3: - if (s->utex == rtex.utex) - s->utex = lasttex; - break; - }; - }; - block b = {0, 0, ssize, ssize}; - remip(b); -}; - -void -edittypexy(int type, block &sel) -{ - loopselxy(s->type = type); -}; - -void -edittype(int type) -{ - EDITSEL; - if (type == CORNER && - (sel.xs != sel.ys || sel.xs == 3 || sel.xs > 4 && sel.xs != 8 || - sel.x & ~-sel.xs || sel.y & ~-sel.ys)) { - conoutf("corner selection must be power of 2 aligned"); - return; - }; - edittypexy(type, sel); - addmsg(1, 6, SV_EDITS, sel.x, sel.y, sel.xs, sel.ys, type); -}; - -void -heightfield(int t) -{ - edittype(t == 0 ? FHF : CHF); -}; -void -solid(int t) -{ - edittype(t == 0 ? SPACE : SOLID); -}; -void -corner() -{ - edittype(CORNER); -}; - -COMMAND(heightfield, ARG_1INT); -COMMAND(solid, ARG_1INT); -COMMAND(corner, ARG_NONE); - -void -editequalisexy(bool isfloor, block &sel) -{ - int low = 127, hi = -128; - loopselxy({ - if (s->floor < low) - low = s->floor; - if (s->ceil > hi) - hi = s->ceil; - }); - loopselxy({ - if (isfloor) - s->floor = low; - else - s->ceil = hi; - if (s->floor >= s->ceil) - s->floor = s->ceil - 1; - }); -}; - -void -equalize(int flr) -{ - bool isfloor = flr == 0; - EDITSEL; - editequalisexy(isfloor, sel); - addmsg(1, 6, SV_EDITE, sel.x, sel.y, sel.xs, sel.ys, isfloor); -}; - -COMMAND(equalize, ARG_1INT); - -void -setvdeltaxy(int delta, block &sel) -{ - loopselxy(s->vdelta = max(s->vdelta + delta, 0)); - remipmore(sel); -}; - -void -setvdelta(int delta) -{ - EDITSEL; - setvdeltaxy(delta, sel); - addmsg(1, 6, SV_EDITD, sel.x, sel.y, sel.xs, sel.ys, delta); -}; - -const int MAXARCHVERT = 50; -int archverts[MAXARCHVERT][MAXARCHVERT]; -bool archvinit = false; - -void -archvertex(int span, int vert, int delta) -{ - if (!archvinit) { - archvinit = true; - loop(s, MAXARCHVERT) loop(v, MAXARCHVERT) archverts[s][v] = 0; - }; - if (span >= MAXARCHVERT || vert >= MAXARCHVERT || span < 0 || vert < 0) - return; - archverts[span][vert] = delta; -}; - -void -arch(int sidedelta, int _a) -{ - EDITSELMP; - sel.xs++; - sel.ys++; - if (sel.xs > MAXARCHVERT) - sel.xs = MAXARCHVERT; - if (sel.ys > MAXARCHVERT) - sel.ys = MAXARCHVERT; - loopselxy( - s->vdelta = sel.xs > sel.ys - ? (archverts[sel.xs - 1][x] + - (y == 0 || y == sel.ys - 1 ? sidedelta : 0)) - : (archverts[sel.ys - 1][y] + - (x == 0 || x == sel.xs - 1 ? sidedelta : 0))); - remipmore(sel); -}; - -void -slope(int xd, int yd) -{ - EDITSELMP; - int off = 0; - if (xd < 0) - off -= xd * sel.xs; - if (yd < 0) - off -= yd * sel.ys; - sel.xs++; - sel.ys++; - loopselxy(s->vdelta = xd * x + yd * y + off); - remipmore(sel); -}; - -void -perlin(int scale, int seed, int psize) -{ - EDITSELMP; - sel.xs++; - sel.ys++; - makeundo(); - sel.xs--; - sel.ys--; - perlinarea(sel, scale, seed, psize); - sel.xs++; - sel.ys++; - remipmore(sel); - sel.xs--; - sel.ys--; -}; - -VARF( - fullbright, 0, 0, 1, if (fullbright) { - if (noteditmode()) - return; - loopi(mipsize) world[i].r = world[i].g = world[i].b = 176; - };); - -void -edittag(int tag) -{ - EDITSELMP; - loopselxy(s->tag = tag); -}; - -void -newent(char *what, char *a1, char *a2, char *a3, char *a4) -{ - EDITSEL; - newentity(sel.x, sel.y, (int)player1->o.z, what, ATOI(a1), ATOI(a2), - ATOI(a3), ATOI(a4)); -}; - -COMMANDN(select, selectpos, ARG_4INT); -COMMAND(edittag, ARG_1INT); -COMMAND(replace, ARG_NONE); -COMMAND(archvertex, ARG_3INT); -COMMAND(arch, ARG_2INT); -COMMAND(slope, ARG_2INT); -COMMANDN(vdelta, setvdelta, ARG_1INT); -COMMANDN(undo, editundo, ARG_NONE); -COMMAND(copy, ARG_NONE); -COMMAND(paste, ARG_NONE); -COMMAND(edittex, ARG_2INT); -COMMAND(newent, ARG_5STR); -COMMAND(perlin, ARG_3INT); ADDED src/editing.mm Index: src/editing.mm ================================================================== --- /dev/null +++ src/editing.mm @@ -0,0 +1,617 @@ +// editing.cpp: most map editing commands go here, entity editing commands are +// in world.cpp + +#include "cube.h" + +bool editmode = false; + +// the current selection, used by almost all editing commands +// invariant: all code assumes that these are kept inside MINBORD distance of +// the edge of the map + +block sel = { + variable("selx", 0, 0, 4096, &sel.x, NULL, false), + variable("sely", 0, 0, 4096, &sel.y, NULL, false), + variable("selxs", 0, 0, 4096, &sel.xs, NULL, false), + variable("selys", 0, 0, 4096, &sel.ys, NULL, false), +}; + +int selh = 0; +bool selset = false; + +#define loopselxy(b) \ + { \ + makeundo(); \ + loop(x, sel.xs) loop(y, sel.ys) \ + { \ + sqr *s = S(sel.x + x, sel.y + y); \ + b; \ + }; \ + remip(sel); \ + } + +int cx, cy, ch; + +int curedittex[] = {-1, -1, -1}; + +bool dragging = false; +int lastx, lasty, lasth; + +int lasttype = 0, lasttex = 0; +sqr rtex; + +VAR(editing, 0, 0, 1); + +void +toggleedit() +{ + if (player1->state == CS_DEAD) + return; // do not allow dead players to edit to avoid state + // confusion + if (!editmode && !allowedittoggle()) + return; // not in most multiplayer modes + if (!(editmode = !editmode)) { + settagareas(); // reset triggers to allow quick playtesting + entinmap(player1); // find spawn closest to current floating pos + } else { + resettagareas(); // clear trigger areas to allow them to be + // edited + player1->health = 100; + if (m_classicsp) + monsterclear(); // all monsters back at their spawns for + // editing + projreset(); + }; + keyrepeat(editmode); + selset = false; + editing = editmode; +}; + +COMMANDN(edittoggle, toggleedit, ARG_NONE); + +void +correctsel() // ensures above invariant +{ + selset = !OUTBORD(sel.x, sel.y); + int bsize = ssize - MINBORD; + if (sel.xs + sel.x > bsize) + sel.xs = bsize - sel.x; + if (sel.ys + sel.y > bsize) + sel.ys = bsize - sel.y; + if (sel.xs <= 0 || sel.ys <= 0) + selset = false; +}; + +bool +noteditmode() +{ + correctsel(); + if (!editmode) + conoutf("this function is only allowed in edit mode"); + return !editmode; +}; + +bool +noselection() +{ + if (!selset) + conoutf("no selection"); + return !selset; +}; + +#define EDITSEL \ + if (noteditmode() || noselection()) \ + return; +#define EDITSELMP \ + if (noteditmode() || noselection() || multiplayer()) \ + return; +#define EDITMP \ + if (noteditmode() || multiplayer()) \ + return; + +void +selectpos(int x, int y, int xs, int ys) +{ + block s = {x, y, xs, ys}; + sel = s; + selh = 0; + correctsel(); +}; + +void +makesel() +{ + block s = {min(lastx, cx), min(lasty, cy), abs(lastx - cx) + 1, + abs(lasty - cy) + 1}; + sel = s; + selh = max(lasth, ch); + correctsel(); + if (selset) + rtex = *S(sel.x, sel.y); +}; + +VAR(flrceil, 0, 0, 2); + +float +sheight( + sqr *s, sqr *t, float z) // finds out z height when cursor points at wall +{ + return !flrceil // z-s->floorceil-z + ? (s->type == FHF ? s->floor - t->vdelta / 4.0f + : (float)s->floor) + : (s->type == CHF ? s->ceil + t->vdelta / 4.0f + : (float)s->ceil); +}; + +void +cursorupdate() // called every frame from hud +{ + flrceil = ((int)(player1->pitch >= 0)) * 2; + + volatile float x = + worldpos.x; // volatile needed to prevent msvc7 optimizer bug? + volatile float y = worldpos.y; + volatile float z = worldpos.z; + + cx = (int)x; + cy = (int)y; + + if (OUTBORD(cx, cy)) + return; + sqr *s = S(cx, cy); + + if (fabs(sheight(s, s, z) - z) > 1) // selected wall + { + x += x > player1->o.x ? 0.5f : -0.5f; // find right wall cube + y += y > player1->o.y ? 0.5f : -0.5f; + + cx = (int)x; + cy = (int)y; + + if (OUTBORD(cx, cy)) + return; + }; + + if (dragging) + makesel(); + + const int GRIDSIZE = 5; + const float GRIDW = 0.5f; + const float GRID8 = 2.0f; + const float GRIDS = 2.0f; + const int GRIDM = 0x7; + + // render editing grid + + for (int ix = cx - GRIDSIZE; ix <= cx + GRIDSIZE; ix++) + for (int iy = cy - GRIDSIZE; iy <= cy + GRIDSIZE; iy++) { + if (OUTBORD(ix, iy)) + continue; + sqr *s = S(ix, iy); + if (SOLID(s)) + continue; + float h1 = sheight(s, s, z); + float h2 = sheight(s, SWS(s, 1, 0, ssize), z); + float h3 = sheight(s, SWS(s, 1, 1, ssize), z); + float h4 = sheight(s, SWS(s, 0, 1, ssize), z); + if (s->tag) + linestyle(GRIDW, 0xFF, 0x40, 0x40); + else if (s->type == FHF || s->type == CHF) + linestyle(GRIDW, 0x80, 0xFF, 0x80); + else + linestyle(GRIDW, 0x80, 0x80, 0x80); + block b = {ix, iy, 1, 1}; + box(b, h1, h2, h3, h4); + linestyle(GRID8, 0x40, 0x40, 0xFF); + if (!(ix & GRIDM)) + line(ix, iy, h1, ix, iy + 1, h4); + if (!(ix + 1 & GRIDM)) + line(ix + 1, iy, h2, ix + 1, iy + 1, h3); + if (!(iy & GRIDM)) + line(ix, iy, h1, ix + 1, iy, h2); + if (!(iy + 1 & GRIDM)) + line(ix, iy + 1, h4, ix + 1, iy + 1, h3); + }; + + if (!SOLID(s)) { + float ih = sheight(s, s, z); + linestyle(GRIDS, 0xFF, 0xFF, 0xFF); + block b = {cx, cy, 1, 1}; + box(b, ih, sheight(s, SWS(s, 1, 0, ssize), z), + sheight(s, SWS(s, 1, 1, ssize), z), + sheight(s, SWS(s, 0, 1, ssize), z)); + linestyle(GRIDS, 0xFF, 0x00, 0x00); + dot(cx, cy, ih); + ch = (int)ih; + }; + + if (selset) { + linestyle(GRIDS, 0xFF, 0x40, 0x40); + box(sel, (float)selh, (float)selh, (float)selh, (float)selh); + }; +}; + +vector undos; // unlimited undo +VARP(undomegs, 0, 1, 10); // bounded by n megs + +void +pruneundos(int maxremain) // bound memory +{ + int t = 0; + loopvrev(undos) + { + t += undos[i]->xs * undos[i]->ys * sizeof(sqr); + if (t > maxremain) + free(undos.remove(i)); + }; +}; + +void +makeundo() +{ + undos.add(blockcopy(sel)); + pruneundos(undomegs << 20); +}; + +void +editundo() +{ + EDITMP; + if (undos.empty()) { + conoutf("nothing more to undo"); + return; + }; + block *p = undos.pop(); + blockpaste(*p); + free(p); +}; + +block *copybuf = NULL; + +void +copy() +{ + EDITSELMP; + if (copybuf) + free(copybuf); + copybuf = blockcopy(sel); +}; + +void +paste() +{ + EDITMP; + if (!copybuf) { + conoutf("nothing to paste"); + return; + }; + sel.xs = copybuf->xs; + sel.ys = copybuf->ys; + correctsel(); + if (!selset || sel.xs != copybuf->xs || sel.ys != copybuf->ys) { + conoutf("incorrect selection"); + return; + }; + makeundo(); + copybuf->x = sel.x; + copybuf->y = sel.y; + blockpaste(*copybuf); +}; + +void +tofronttex() // maintain most recently used of the texture lists when applying + // texture +{ + loopi(3) + { + int c = curedittex[i]; + if (c >= 0) { + uchar *p = hdr.texlists[i]; + int t = p[c]; + for (int a = c - 1; a >= 0; a--) + p[a + 1] = p[a]; + p[0] = t; + curedittex[i] = -1; + }; + }; +}; + +void +editdrag(bool isdown) +{ + if (dragging = isdown) { + lastx = cx; + lasty = cy; + lasth = ch; + selset = false; + tofronttex(); + }; + makesel(); +}; + +// the core editing function. all the *xy functions perform the core operations +// and are also called directly from the network, the function below it is +// strictly triggered locally. They all have very similar structure. + +void +editheightxy(bool isfloor, int amount, block &sel) +{ + loopselxy( + if (isfloor) { + s->floor += amount; + if (s->floor >= s->ceil) + s->floor = s->ceil - 1; + } else { + s->ceil += amount; + if (s->ceil <= s->floor) + s->ceil = s->floor + 1; + }); +}; + +void +editheight(int flr, int amount) +{ + EDITSEL; + bool isfloor = flr == 0; + editheightxy(isfloor, amount, sel); + addmsg(1, 7, SV_EDITH, sel.x, sel.y, sel.xs, sel.ys, isfloor, amount); +}; + +COMMAND(editheight, ARG_2INT); + +void +edittexxy(int type, int t, block &sel) +{ + loopselxy(switch (type) { + case 0: + s->ftex = t; + break; + case 1: + s->wtex = t; + break; + case 2: + s->ctex = t; + break; + case 3: + s->utex = t; + break; + }); +}; + +void +edittex(int type, int dir) +{ + EDITSEL; + if (type < 0 || type > 3) + return; + if (type != lasttype) { + tofronttex(); + lasttype = type; + }; + int atype = type == 3 ? 1 : type; + int i = curedittex[atype]; + i = i < 0 ? 0 : i + dir; + curedittex[atype] = i = min(max(i, 0), 255); + int t = lasttex = hdr.texlists[atype][i]; + edittexxy(type, t, sel); + addmsg(1, 7, SV_EDITT, sel.x, sel.y, sel.xs, sel.ys, type, t); +}; + +void +replace() +{ + EDITSELMP; + loop(x, ssize) loop(y, ssize) + { + sqr *s = S(x, y); + switch (lasttype) { + case 0: + if (s->ftex == rtex.ftex) + s->ftex = lasttex; + break; + case 1: + if (s->wtex == rtex.wtex) + s->wtex = lasttex; + break; + case 2: + if (s->ctex == rtex.ctex) + s->ctex = lasttex; + break; + case 3: + if (s->utex == rtex.utex) + s->utex = lasttex; + break; + }; + }; + block b = {0, 0, ssize, ssize}; + remip(b); +}; + +void +edittypexy(int type, block &sel) +{ + loopselxy(s->type = type); +}; + +void +edittype(int type) +{ + EDITSEL; + if (type == CORNER && + (sel.xs != sel.ys || sel.xs == 3 || sel.xs > 4 && sel.xs != 8 || + sel.x & ~-sel.xs || sel.y & ~-sel.ys)) { + conoutf("corner selection must be power of 2 aligned"); + return; + }; + edittypexy(type, sel); + addmsg(1, 6, SV_EDITS, sel.x, sel.y, sel.xs, sel.ys, type); +}; + +void +heightfield(int t) +{ + edittype(t == 0 ? FHF : CHF); +}; +void +solid(int t) +{ + edittype(t == 0 ? SPACE : SOLID); +}; +void +corner() +{ + edittype(CORNER); +}; + +COMMAND(heightfield, ARG_1INT); +COMMAND(solid, ARG_1INT); +COMMAND(corner, ARG_NONE); + +void +editequalisexy(bool isfloor, block &sel) +{ + int low = 127, hi = -128; + loopselxy({ + if (s->floor < low) + low = s->floor; + if (s->ceil > hi) + hi = s->ceil; + }); + loopselxy({ + if (isfloor) + s->floor = low; + else + s->ceil = hi; + if (s->floor >= s->ceil) + s->floor = s->ceil - 1; + }); +}; + +void +equalize(int flr) +{ + bool isfloor = flr == 0; + EDITSEL; + editequalisexy(isfloor, sel); + addmsg(1, 6, SV_EDITE, sel.x, sel.y, sel.xs, sel.ys, isfloor); +}; + +COMMAND(equalize, ARG_1INT); + +void +setvdeltaxy(int delta, block &sel) +{ + loopselxy(s->vdelta = max(s->vdelta + delta, 0)); + remipmore(sel); +}; + +void +setvdelta(int delta) +{ + EDITSEL; + setvdeltaxy(delta, sel); + addmsg(1, 6, SV_EDITD, sel.x, sel.y, sel.xs, sel.ys, delta); +}; + +const int MAXARCHVERT = 50; +int archverts[MAXARCHVERT][MAXARCHVERT]; +bool archvinit = false; + +void +archvertex(int span, int vert, int delta) +{ + if (!archvinit) { + archvinit = true; + loop(s, MAXARCHVERT) loop(v, MAXARCHVERT) archverts[s][v] = 0; + }; + if (span >= MAXARCHVERT || vert >= MAXARCHVERT || span < 0 || vert < 0) + return; + archverts[span][vert] = delta; +}; + +void +arch(int sidedelta, int _a) +{ + EDITSELMP; + sel.xs++; + sel.ys++; + if (sel.xs > MAXARCHVERT) + sel.xs = MAXARCHVERT; + if (sel.ys > MAXARCHVERT) + sel.ys = MAXARCHVERT; + loopselxy( + s->vdelta = sel.xs > sel.ys + ? (archverts[sel.xs - 1][x] + + (y == 0 || y == sel.ys - 1 ? sidedelta : 0)) + : (archverts[sel.ys - 1][y] + + (x == 0 || x == sel.xs - 1 ? sidedelta : 0))); + remipmore(sel); +}; + +void +slope(int xd, int yd) +{ + EDITSELMP; + int off = 0; + if (xd < 0) + off -= xd * sel.xs; + if (yd < 0) + off -= yd * sel.ys; + sel.xs++; + sel.ys++; + loopselxy(s->vdelta = xd * x + yd * y + off); + remipmore(sel); +}; + +void +perlin(int scale, int seed, int psize) +{ + EDITSELMP; + sel.xs++; + sel.ys++; + makeundo(); + sel.xs--; + sel.ys--; + perlinarea(sel, scale, seed, psize); + sel.xs++; + sel.ys++; + remipmore(sel); + sel.xs--; + sel.ys--; +}; + +VARF( + fullbright, 0, 0, 1, if (fullbright) { + if (noteditmode()) + return; + loopi(mipsize) world[i].r = world[i].g = world[i].b = 176; + };); + +void +edittag(int tag) +{ + EDITSELMP; + loopselxy(s->tag = tag); +}; + +void +newent(char *what, char *a1, char *a2, char *a3, char *a4) +{ + EDITSEL; + newentity(sel.x, sel.y, (int)player1->o.z, what, ATOI(a1), ATOI(a2), + ATOI(a3), ATOI(a4)); +}; + +COMMANDN(select, selectpos, ARG_4INT); +COMMAND(edittag, ARG_1INT); +COMMAND(replace, ARG_NONE); +COMMAND(archvertex, ARG_3INT); +COMMAND(arch, ARG_2INT); +COMMAND(slope, ARG_2INT); +COMMANDN(vdelta, setvdelta, ARG_1INT); +COMMANDN(undo, editundo, ARG_NONE); +COMMAND(copy, ARG_NONE); +COMMAND(paste, ARG_NONE); +COMMAND(edittex, ARG_2INT); +COMMAND(newent, ARG_5STR); +COMMAND(perlin, ARG_3INT); DELETED src/entities.cxx Index: src/entities.cxx ================================================================== --- src/entities.cxx +++ /dev/null @@ -1,359 +0,0 @@ -// entities.cpp: map entity related functions (pickup etc.) - -#include "cube.h" - -vector ents; - -char *entmdlnames[] = { - "shells", - "bullets", - "rockets", - "rrounds", - "health", - "boost", - "g_armour", - "y_armour", - "quad", - "teleporter", -}; - -int triggertime = 0; - -void -renderent(entity &e, char *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, e.x, z + S(e.x, e.y)->floor, - e.y, yaw, 0, false, 1.0f, speed, 0, basetime); -}; - -void -renderentities() -{ - if (lastmillis > triggertime + 1000) - triggertime = 0; - loopv(ents) - { - entity &e = ents[i]; - if (e.type == MAPMODEL) { - mapmodelinfo &mmi = getmminfo(e.attr2); - if (!&mmi) - continue; - rendermodel(mmi.name, 0, 1, e.attr4, (float)mmi.rad, - e.x, (float)S(e.x, e.y)->floor + mmi.zoff + e.attr3, - e.y, (float)((e.attr1 + 7) - (e.attr1 + 7) % 15), 0, - false, 1.0f, 10.0f, mmi.snap); - } else { - if (OUTBORD(e.x, e.y)) - continue; - if (e.type != CARROT) { - if (!e.spawned && e.type != TELEPORT) - continue; - if (e.type < I_SHELLS || e.type > TELEPORT) - continue; - renderent(e, entmdlnames[e.type - I_SHELLS], - (float)(1 + sin(lastmillis / 100.0 + e.x + - e.y) / - 20), - lastmillis / 10.0f); - } else - switch (e.attr2) { - case 1: - case 3: - continue; - - case 2: - case 0: - if (!e.spawned) - continue; - renderent(e, "carrot", - (float)(1 + sin(lastmillis / 100.0 + - e.x + e.y) / - 20), - lastmillis / - (e.attr2 ? 1.0f : 10.0f)); - break; - - case 4: - renderent(e, "switch2", 3, - (float)e.attr3 * 90, - (!e.spawned && !triggertime) ? 1 - : 0, - (e.spawned || !triggertime) ? 1 : 2, - triggertime, 1050.0f); - break; - case 5: - renderent(e, "switch1", -0.15f, - (float)e.attr3 * 90, - (!e.spawned && !triggertime) ? 30 - : 0, - (e.spawned || !triggertime) ? 1 - : 30, - triggertime, 35.0f); - break; - }; - }; - }; -}; - -struct itemstat { - int add, max, sound; -} itemstats[] = { - 10, - 50, - S_ITEMAMMO, - 20, - 100, - S_ITEMAMMO, - 5, - 25, - S_ITEMAMMO, - 5, - 25, - S_ITEMAMMO, - 25, - 100, - S_ITEMHEALTH, - 50, - 200, - S_ITEMHEALTH, - 100, - 100, - S_ITEMARMOUR, - 150, - 150, - S_ITEMARMOUR, - 20000, - 30000, - S_ITEMPUP, -}; - -void -baseammo(int gun) -{ - player1->ammo[gun] = itemstats[gun - 1].add * 2; -}; - -// these two functions are called when the server acknowledges that you really -// picked up the item (in multiplayer someone may grab it before you). - -void -radditem(int i, int &v) -{ - itemstat &is = itemstats[ents[i].type - I_SHELLS]; - ents[i].spawned = false; - v += is.add; - if (v > is.max) - v = is.max; - playsoundc(is.sound); -}; - -void -realpickup(int n, dynent *d) -{ - switch (ents[n].type) { - case I_SHELLS: - radditem(n, d->ammo[1]); - break; - case I_BULLETS: - radditem(n, d->ammo[2]); - break; - case I_ROCKETS: - radditem(n, d->ammo[3]); - break; - case I_ROUNDS: - radditem(n, d->ammo[4]); - break; - case I_HEALTH: - radditem(n, d->health); - break; - case I_BOOST: - radditem(n, d->health); - break; - - case I_GREENARMOUR: - radditem(n, d->armour); - d->armourtype = A_GREEN; - break; - - case I_YELLOWARMOUR: - radditem(n, d->armour); - d->armourtype = A_YELLOW; - break; - - case I_QUAD: - radditem(n, d->quadmillis); - conoutf("you got the quad!"); - break; - }; -}; - -// these functions are called when the client touches the item - -void -additem(int i, int &v, int spawnsec) -{ - if (v < itemstats[ents[i].type - I_SHELLS] - .max) // don't pick up if not needed - { - addmsg(1, 3, SV_ITEMPICKUP, i, - m_classicsp ? 100000 - : spawnsec); // first ask the server for an ack - ents[i].spawned = false; // even if someone else gets it first - }; -}; - -void -teleport(int n, dynent *d) // also used by monsters -{ - int e = -1, tag = ents[n].attr1, beenhere = -1; - for (;;) { - e = findentity(TELEDEST, e + 1); - if (e == beenhere || e < 0) { - conoutf("no teleport destination for tag %d", tag); - return; - }; - if (beenhere < 0) - beenhere = e; - if (ents[e].attr2 == tag) { - d->o.x = ents[e].x; - d->o.y = ents[e].y; - d->o.z = ents[e].z; - d->yaw = ents[e].attr1; - d->pitch = 0; - d->vel.x = d->vel.y = d->vel.z = 0; - entinmap(d); - playsoundc(S_TELEPORT); - break; - }; - }; -}; - -void -pickup(int n, dynent *d) -{ - int np = 1; - loopv(players) if (players[i]) np++; - np = np < 3 ? 4 : (np > 4 ? 2 : 3); // spawn times are dependent on - // number of players - int ammo = np * 2; - switch (ents[n].type) { - case I_SHELLS: - additem(n, d->ammo[1], ammo); - break; - case I_BULLETS: - additem(n, d->ammo[2], ammo); - break; - case I_ROCKETS: - additem(n, d->ammo[3], ammo); - break; - case I_ROUNDS: - additem(n, d->ammo[4], ammo); - break; - case I_HEALTH: - additem(n, d->health, np * 5); - break; - case I_BOOST: - additem(n, d->health, 60); - break; - - case I_GREENARMOUR: - // (100h/100g only absorbs 166 damage) - if (d->armourtype == A_YELLOW && d->armour > 66) - break; - additem(n, d->armour, 20); - break; - - case I_YELLOWARMOUR: - additem(n, d->armour, 20); - break; - - case I_QUAD: - additem(n, d->quadmillis, 60); - break; - - case CARROT: - ents[n].spawned = false; - triggertime = lastmillis; - trigger(ents[n].attr1, ents[n].attr2, - false); // needs to go over server for multiplayer - break; - - case TELEPORT: { - static int lastteleport = 0; - if (lastmillis - lastteleport < 500) - break; - lastteleport = lastmillis; - teleport(n, d); - break; - }; - - case JUMPPAD: { - static int lastjumppad = 0; - if (lastmillis - lastjumppad < 300) - break; - lastjumppad = lastmillis; - vec v = {(int)(char)ents[n].attr3 / 10.0f, - (int)(char)ents[n].attr2 / 10.0f, ents[n].attr1 / 10.0f}; - player1->vel.z = 0; - vadd(player1->vel, v); - playsoundc(S_JUMPPAD); - break; - }; - }; -}; - -void -checkitems() -{ - if (editmode) - return; - loopv(ents) - { - entity &e = ents[i]; - if (e.type == NOTUSED) - continue; - if (!ents[i].spawned && e.type != TELEPORT && e.type != JUMPPAD) - continue; - if (OUTBORD(e.x, e.y)) - continue; - vec v = {(float)e.x, (float)e.y, - (float)S(e.x, e.y)->floor + player1->eyeheight}; - vdist(dist, t, player1->o, v); - if (dist < (e.type == TELEPORT ? 4 : 2.5)) - pickup(i, player1); - }; -}; - -void -checkquad(int time) -{ - if (player1->quadmillis && (player1->quadmillis -= time) < 0) { - player1->quadmillis = 0; - playsoundc(S_PUPOUT); - conoutf("quad damage is over"); - }; -}; - -void -putitems(uchar *&p) // puts items in network stream and also spawns them locally -{ - loopv(ents) if ((ents[i].type >= I_SHELLS && ents[i].type <= I_QUAD) || - ents[i].type == CARROT) - { - putint(p, i); - ents[i].spawned = true; - }; -}; - -void -resetspawns() -{ - loopv(ents) ents[i].spawned = false; -}; -void -setspawn(uint i, bool on) -{ - if (i < (uint)ents.length()) - ents[i].spawned = on; -}; ADDED src/entities.mm Index: src/entities.mm ================================================================== --- /dev/null +++ src/entities.mm @@ -0,0 +1,359 @@ +// entities.cpp: map entity related functions (pickup etc.) + +#include "cube.h" + +vector ents; + +char *entmdlnames[] = { + "shells", + "bullets", + "rockets", + "rrounds", + "health", + "boost", + "g_armour", + "y_armour", + "quad", + "teleporter", +}; + +int triggertime = 0; + +void +renderent(entity &e, char *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, e.x, z + S(e.x, e.y)->floor, + e.y, yaw, 0, false, 1.0f, speed, 0, basetime); +}; + +void +renderentities() +{ + if (lastmillis > triggertime + 1000) + triggertime = 0; + loopv(ents) + { + entity &e = ents[i]; + if (e.type == MAPMODEL) { + mapmodelinfo &mmi = getmminfo(e.attr2); + if (!&mmi) + continue; + rendermodel(mmi.name, 0, 1, e.attr4, (float)mmi.rad, + e.x, (float)S(e.x, e.y)->floor + mmi.zoff + e.attr3, + e.y, (float)((e.attr1 + 7) - (e.attr1 + 7) % 15), 0, + false, 1.0f, 10.0f, mmi.snap); + } else { + if (OUTBORD(e.x, e.y)) + continue; + if (e.type != CARROT) { + if (!e.spawned && e.type != TELEPORT) + continue; + if (e.type < I_SHELLS || e.type > TELEPORT) + continue; + renderent(e, entmdlnames[e.type - I_SHELLS], + (float)(1 + sin(lastmillis / 100.0 + e.x + + e.y) / + 20), + lastmillis / 10.0f); + } else + switch (e.attr2) { + case 1: + case 3: + continue; + + case 2: + case 0: + if (!e.spawned) + continue; + renderent(e, "carrot", + (float)(1 + sin(lastmillis / 100.0 + + e.x + e.y) / + 20), + lastmillis / + (e.attr2 ? 1.0f : 10.0f)); + break; + + case 4: + renderent(e, "switch2", 3, + (float)e.attr3 * 90, + (!e.spawned && !triggertime) ? 1 + : 0, + (e.spawned || !triggertime) ? 1 : 2, + triggertime, 1050.0f); + break; + case 5: + renderent(e, "switch1", -0.15f, + (float)e.attr3 * 90, + (!e.spawned && !triggertime) ? 30 + : 0, + (e.spawned || !triggertime) ? 1 + : 30, + triggertime, 35.0f); + break; + }; + }; + }; +}; + +struct itemstat { + int add, max, sound; +} itemstats[] = { + 10, + 50, + S_ITEMAMMO, + 20, + 100, + S_ITEMAMMO, + 5, + 25, + S_ITEMAMMO, + 5, + 25, + S_ITEMAMMO, + 25, + 100, + S_ITEMHEALTH, + 50, + 200, + S_ITEMHEALTH, + 100, + 100, + S_ITEMARMOUR, + 150, + 150, + S_ITEMARMOUR, + 20000, + 30000, + S_ITEMPUP, +}; + +void +baseammo(int gun) +{ + player1->ammo[gun] = itemstats[gun - 1].add * 2; +}; + +// these two functions are called when the server acknowledges that you really +// picked up the item (in multiplayer someone may grab it before you). + +void +radditem(int i, int &v) +{ + itemstat &is = itemstats[ents[i].type - I_SHELLS]; + ents[i].spawned = false; + v += is.add; + if (v > is.max) + v = is.max; + playsoundc(is.sound); +}; + +void +realpickup(int n, dynent *d) +{ + switch (ents[n].type) { + case I_SHELLS: + radditem(n, d->ammo[1]); + break; + case I_BULLETS: + radditem(n, d->ammo[2]); + break; + case I_ROCKETS: + radditem(n, d->ammo[3]); + break; + case I_ROUNDS: + radditem(n, d->ammo[4]); + break; + case I_HEALTH: + radditem(n, d->health); + break; + case I_BOOST: + radditem(n, d->health); + break; + + case I_GREENARMOUR: + radditem(n, d->armour); + d->armourtype = A_GREEN; + break; + + case I_YELLOWARMOUR: + radditem(n, d->armour); + d->armourtype = A_YELLOW; + break; + + case I_QUAD: + radditem(n, d->quadmillis); + conoutf("you got the quad!"); + break; + }; +}; + +// these functions are called when the client touches the item + +void +additem(int i, int &v, int spawnsec) +{ + if (v < itemstats[ents[i].type - I_SHELLS] + .max) // don't pick up if not needed + { + addmsg(1, 3, SV_ITEMPICKUP, i, + m_classicsp ? 100000 + : spawnsec); // first ask the server for an ack + ents[i].spawned = false; // even if someone else gets it first + }; +}; + +void +teleport(int n, dynent *d) // also used by monsters +{ + int e = -1, tag = ents[n].attr1, beenhere = -1; + for (;;) { + e = findentity(TELEDEST, e + 1); + if (e == beenhere || e < 0) { + conoutf("no teleport destination for tag %d", tag); + return; + }; + if (beenhere < 0) + beenhere = e; + if (ents[e].attr2 == tag) { + d->o.x = ents[e].x; + d->o.y = ents[e].y; + d->o.z = ents[e].z; + d->yaw = ents[e].attr1; + d->pitch = 0; + d->vel.x = d->vel.y = d->vel.z = 0; + entinmap(d); + playsoundc(S_TELEPORT); + break; + }; + }; +}; + +void +pickup(int n, dynent *d) +{ + int np = 1; + loopv(players) if (players[i]) np++; + np = np < 3 ? 4 : (np > 4 ? 2 : 3); // spawn times are dependent on + // number of players + int ammo = np * 2; + switch (ents[n].type) { + case I_SHELLS: + additem(n, d->ammo[1], ammo); + break; + case I_BULLETS: + additem(n, d->ammo[2], ammo); + break; + case I_ROCKETS: + additem(n, d->ammo[3], ammo); + break; + case I_ROUNDS: + additem(n, d->ammo[4], ammo); + break; + case I_HEALTH: + additem(n, d->health, np * 5); + break; + case I_BOOST: + additem(n, d->health, 60); + break; + + case I_GREENARMOUR: + // (100h/100g only absorbs 166 damage) + if (d->armourtype == A_YELLOW && d->armour > 66) + break; + additem(n, d->armour, 20); + break; + + case I_YELLOWARMOUR: + additem(n, d->armour, 20); + break; + + case I_QUAD: + additem(n, d->quadmillis, 60); + break; + + case CARROT: + ents[n].spawned = false; + triggertime = lastmillis; + trigger(ents[n].attr1, ents[n].attr2, + false); // needs to go over server for multiplayer + break; + + case TELEPORT: { + static int lastteleport = 0; + if (lastmillis - lastteleport < 500) + break; + lastteleport = lastmillis; + teleport(n, d); + break; + }; + + case JUMPPAD: { + static int lastjumppad = 0; + if (lastmillis - lastjumppad < 300) + break; + lastjumppad = lastmillis; + vec v = {(int)(char)ents[n].attr3 / 10.0f, + (int)(char)ents[n].attr2 / 10.0f, ents[n].attr1 / 10.0f}; + player1->vel.z = 0; + vadd(player1->vel, v); + playsoundc(S_JUMPPAD); + break; + }; + }; +}; + +void +checkitems() +{ + if (editmode) + return; + loopv(ents) + { + entity &e = ents[i]; + if (e.type == NOTUSED) + continue; + if (!ents[i].spawned && e.type != TELEPORT && e.type != JUMPPAD) + continue; + if (OUTBORD(e.x, e.y)) + continue; + vec v = {(float)e.x, (float)e.y, + (float)S(e.x, e.y)->floor + player1->eyeheight}; + vdist(dist, t, player1->o, v); + if (dist < (e.type == TELEPORT ? 4 : 2.5)) + pickup(i, player1); + }; +}; + +void +checkquad(int time) +{ + if (player1->quadmillis && (player1->quadmillis -= time) < 0) { + player1->quadmillis = 0; + playsoundc(S_PUPOUT); + conoutf("quad damage is over"); + }; +}; + +void +putitems(uchar *&p) // puts items in network stream and also spawns them locally +{ + loopv(ents) if ((ents[i].type >= I_SHELLS && ents[i].type <= I_QUAD) || + ents[i].type == CARROT) + { + putint(p, i); + ents[i].spawned = true; + }; +}; + +void +resetspawns() +{ + loopv(ents) ents[i].spawned = false; +}; +void +setspawn(uint i, bool on) +{ + if (i < (uint)ents.length()) + ents[i].spawned = on; +}; DELETED src/main.cxx Index: src/main.cxx ================================================================== --- src/main.cxx +++ /dev/null @@ -1,291 +0,0 @@ -// main.cpp: initialisation & main loop - -#include "cube.h" - -void -cleanup(char *msg) // single program exit point; -{ - stop(); - disconnect(true); - writecfg(); - cleangl(); - cleansound(); - cleanupserver(); - SDL_ShowCursor(1); - if (msg) { -#ifdef _WIN32 - MessageBox( - NULL, msg, "cube fatal error", MB_OK | MB_SYSTEMMODAL); -#else - printf(msg); -#endif - }; - SDL_Quit(); - exit(1); -}; - -void -quit() // normal exit -{ - writeservercfg(); - cleanup(NULL); -}; - -void -fatal(char *s, char *o) // failure exit -{ - sprintf_sd(msg)("%s%s (%s)\n", s, o, SDL_GetError()); - cleanup(msg); -}; - -void * -alloc(int s) // for some big chunks... most other allocs use the memory pool -{ - void *b = calloc(1, s); - if (!b) - fatal("out of memory!"); - return b; -}; - -int scr_w = 640; -int scr_h = 480; - -void -screenshot() -{ - SDL_Surface *image; - SDL_Surface *temp; - int idx; - if (image = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24, - 0x0000FF, 0x00FF00, 0xFF0000, 0)) { - if (temp = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24, - 0x0000FF, 0x00FF00, 0xFF0000, 0)) { - glReadPixels(0, 0, scr_w, scr_h, GL_RGB, - GL_UNSIGNED_BYTE, image->pixels); - for (idx = 0; idx < scr_h; idx++) { - char *dest = - (char *)temp->pixels + 3 * scr_w * idx; - memcpy(dest, - (char *)image->pixels + - 3 * scr_w * (scr_h - 1 - idx), - 3 * scr_w); - endianswap(dest, 3, scr_w); - }; - sprintf_sd(buf)( - "screenshots/screenshot_%d.bmp", lastmillis); - SDL_SaveBMP(temp, path(buf)); - SDL_FreeSurface(temp); - }; - SDL_FreeSurface(image); - }; -}; - -COMMAND(screenshot, ARG_NONE); -COMMAND(quit, ARG_NONE); - -void -keyrepeat(bool on) -{ - SDL_EnableKeyRepeat( - on ? SDL_DEFAULT_REPEAT_DELAY : 0, SDL_DEFAULT_REPEAT_INTERVAL); -}; - -VARF(gamespeed, 10, 100, 1000, if (multiplayer()) gamespeed = 100); -VARP(minmillis, 0, 5, 1000); - -int islittleendian = 1; -int framesinmap = 0; - -int -main(int argc, char **argv) -{ - bool dedicated = false; - int fs = SDL_FULLSCREEN, par = 0, uprate = 0, maxcl = 4; - char *sdesc = "", *ip = "", *master = NULL, *passwd = ""; - islittleendian = *((char *)&islittleendian); - -#define log(s) conoutf("init: %s", s) - log("sdl"); - - for (int i = 1; i < argc; i++) { - char *a = &argv[i][2]; - if (argv[i][0] == '-') - switch (argv[i][1]) { - case 'd': - dedicated = true; - break; - case 't': - fs = 0; - break; - case 'w': - scr_w = atoi(a); - break; - case 'h': - scr_h = atoi(a); - break; - 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: - conoutf("unknown commandline option"); - } - else - conoutf("unknown commandline argument"); - }; - -#ifdef _DEBUG - par = SDL_INIT_NOPARACHUTE; - fs = 0; -#endif - - if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | par) < 0) - fatal("Unable to initialize SDL"); - - log("net"); - if (enet_initialize() < 0) - fatal("Unable to initialise network module"); - - initclient(); - initserver(dedicated, uprate, sdesc, ip, master, passwd, - maxcl); // never returns if dedicated - - log("world"); - empty_world(7, true); - - log("video: sdl"); - if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) - fatal("Unable to initialize SDL Video"); - - log("video: mode"); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - if (SDL_SetVideoMode(scr_w, scr_h, 0, SDL_OPENGL | fs) == NULL) - fatal("Unable to create OpenGL screen"); - - log("video: misc"); - SDL_WM_SetCaption("cube engine", NULL); - SDL_WM_GrabInput(SDL_GRAB_ON); - keyrepeat(false); - SDL_ShowCursor(0); - - log("gl"); - gl_init(scr_w, scr_h); - - log("basetex"); - int xs, ys; - if (!installtex(2, path(newstring("data/newchars.png")), xs, ys) || - !installtex(3, path(newstring("data/martin/base.png")), xs, ys) || - !installtex(6, path(newstring("data/martin/ball1.png")), xs, ys) || - !installtex(7, path(newstring("data/martin/smoke.png")), xs, ys) || - !installtex(8, path(newstring("data/martin/ball2.png")), xs, ys) || - !installtex(9, path(newstring("data/martin/ball3.png")), xs, ys) || - !installtex(4, path(newstring("data/explosion.jpg")), xs, ys) || - !installtex(5, path(newstring("data/items.png")), xs, ys) || - !installtex(1, path(newstring("data/crosshair.png")), xs, ys)) - 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("config.cfg")) - execfile("data/defaults.cfg"); - exec("autoexec.cfg"); - - log("localconnect"); - localconnect(); - changemap( - "metl3"); // if this map is changed, also change depthcorrect() - - log("mainloop"); - int ignore = 5; - for (;;) { - 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(scr_w, scr_h); - SDL_GL_SwapBuffers(); - extern void updatevol(); - updatevol(); - if (framesinmap++ < - 5) // cheap hack to get rid of initial sparklies, even when - // triple buffering etc. - { - player1->yaw += 5; - gl_drawframe(scr_w, scr_h, fps); - player1->yaw -= 5; - }; - gl_drawframe(scr_w, scr_h, fps); - SDL_Event event; - int lasttype = 0, lastbut = 0; - while (SDL_PollEvent(&event)) { - switch (event.type) { - case SDL_QUIT: - quit(); - break; - - case SDL_KEYDOWN: - case SDL_KEYUP: - keypress(event.key.keysym.sym, - event.key.state == SDL_PRESSED, - event.key.keysym.unicode); - 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) - break; // why?? get event twice without - // it - keypress(-event.button.button, - event.button.state != 0, 0); - lasttype = event.type; - lastbut = event.button.button; - break; - }; - }; - }; - quit(); - return 1; -}; ADDED src/main.mm Index: src/main.mm ================================================================== --- /dev/null +++ src/main.mm @@ -0,0 +1,291 @@ +// main.cpp: initialisation & main loop + +#include "cube.h" + +void +cleanup(char *msg) // single program exit point; +{ + stop(); + disconnect(true); + writecfg(); + cleangl(); + cleansound(); + cleanupserver(); + SDL_ShowCursor(1); + if (msg) { +#ifdef _WIN32 + MessageBox( + NULL, msg, "cube fatal error", MB_OK | MB_SYSTEMMODAL); +#else + printf(msg); +#endif + }; + SDL_Quit(); + exit(1); +}; + +void +quit() // normal exit +{ + writeservercfg(); + cleanup(NULL); +}; + +void +fatal(char *s, char *o) // failure exit +{ + sprintf_sd(msg)("%s%s (%s)\n", s, o, SDL_GetError()); + cleanup(msg); +}; + +void * +alloc(int s) // for some big chunks... most other allocs use the memory pool +{ + void *b = calloc(1, s); + if (!b) + fatal("out of memory!"); + return b; +}; + +int scr_w = 640; +int scr_h = 480; + +void +screenshot() +{ + SDL_Surface *image; + SDL_Surface *temp; + int idx; + if (image = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24, + 0x0000FF, 0x00FF00, 0xFF0000, 0)) { + if (temp = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24, + 0x0000FF, 0x00FF00, 0xFF0000, 0)) { + glReadPixels(0, 0, scr_w, scr_h, GL_RGB, + GL_UNSIGNED_BYTE, image->pixels); + for (idx = 0; idx < scr_h; idx++) { + char *dest = + (char *)temp->pixels + 3 * scr_w * idx; + memcpy(dest, + (char *)image->pixels + + 3 * scr_w * (scr_h - 1 - idx), + 3 * scr_w); + endianswap(dest, 3, scr_w); + }; + sprintf_sd(buf)( + "screenshots/screenshot_%d.bmp", lastmillis); + SDL_SaveBMP(temp, path(buf)); + SDL_FreeSurface(temp); + }; + SDL_FreeSurface(image); + }; +}; + +COMMAND(screenshot, ARG_NONE); +COMMAND(quit, ARG_NONE); + +void +keyrepeat(bool on) +{ + SDL_EnableKeyRepeat( + on ? SDL_DEFAULT_REPEAT_DELAY : 0, SDL_DEFAULT_REPEAT_INTERVAL); +}; + +VARF(gamespeed, 10, 100, 1000, if (multiplayer()) gamespeed = 100); +VARP(minmillis, 0, 5, 1000); + +int islittleendian = 1; +int framesinmap = 0; + +int +main(int argc, char **argv) +{ + bool dedicated = false; + int fs = SDL_FULLSCREEN, par = 0, uprate = 0, maxcl = 4; + char *sdesc = "", *ip = "", *master = NULL, *passwd = ""; + islittleendian = *((char *)&islittleendian); + +#define log(s) conoutf("init: %s", s) + log("sdl"); + + for (int i = 1; i < argc; i++) { + char *a = &argv[i][2]; + if (argv[i][0] == '-') + switch (argv[i][1]) { + case 'd': + dedicated = true; + break; + case 't': + fs = 0; + break; + case 'w': + scr_w = atoi(a); + break; + case 'h': + scr_h = atoi(a); + break; + 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: + conoutf("unknown commandline option"); + } + else + conoutf("unknown commandline argument"); + }; + +#ifdef _DEBUG + par = SDL_INIT_NOPARACHUTE; + fs = 0; +#endif + + if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | par) < 0) + fatal("Unable to initialize SDL"); + + log("net"); + if (enet_initialize() < 0) + fatal("Unable to initialise network module"); + + initclient(); + initserver(dedicated, uprate, sdesc, ip, master, passwd, + maxcl); // never returns if dedicated + + log("world"); + empty_world(7, true); + + log("video: sdl"); + if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) + fatal("Unable to initialize SDL Video"); + + log("video: mode"); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + if (SDL_SetVideoMode(scr_w, scr_h, 0, SDL_OPENGL | fs) == NULL) + fatal("Unable to create OpenGL screen"); + + log("video: misc"); + SDL_WM_SetCaption("cube engine", NULL); + SDL_WM_GrabInput(SDL_GRAB_ON); + keyrepeat(false); + SDL_ShowCursor(0); + + log("gl"); + gl_init(scr_w, scr_h); + + log("basetex"); + int xs, ys; + if (!installtex(2, path(newstring("data/newchars.png")), xs, ys) || + !installtex(3, path(newstring("data/martin/base.png")), xs, ys) || + !installtex(6, path(newstring("data/martin/ball1.png")), xs, ys) || + !installtex(7, path(newstring("data/martin/smoke.png")), xs, ys) || + !installtex(8, path(newstring("data/martin/ball2.png")), xs, ys) || + !installtex(9, path(newstring("data/martin/ball3.png")), xs, ys) || + !installtex(4, path(newstring("data/explosion.jpg")), xs, ys) || + !installtex(5, path(newstring("data/items.png")), xs, ys) || + !installtex(1, path(newstring("data/crosshair.png")), xs, ys)) + 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("config.cfg")) + execfile("data/defaults.cfg"); + exec("autoexec.cfg"); + + log("localconnect"); + localconnect(); + changemap( + "metl3"); // if this map is changed, also change depthcorrect() + + log("mainloop"); + int ignore = 5; + for (;;) { + 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(scr_w, scr_h); + SDL_GL_SwapBuffers(); + extern void updatevol(); + updatevol(); + if (framesinmap++ < + 5) // cheap hack to get rid of initial sparklies, even when + // triple buffering etc. + { + player1->yaw += 5; + gl_drawframe(scr_w, scr_h, fps); + player1->yaw -= 5; + }; + gl_drawframe(scr_w, scr_h, fps); + SDL_Event event; + int lasttype = 0, lastbut = 0; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + quit(); + break; + + case SDL_KEYDOWN: + case SDL_KEYUP: + keypress(event.key.keysym.sym, + event.key.state == SDL_PRESSED, + event.key.keysym.unicode); + 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) + break; // why?? get event twice without + // it + keypress(-event.button.button, + event.button.state != 0, 0); + lasttype = event.type; + lastbut = event.button.button; + break; + }; + }; + }; + quit(); + return 1; +}; DELETED src/menus.cxx Index: src/menus.cxx ================================================================== --- src/menus.cxx +++ /dev/null @@ -1,169 +0,0 @@ -// menus.cpp: ingame menu system (also used for scores and serverlist) - -#include "cube.h" - -struct mitem { - char *text, *action; -}; - -struct gmenu { - char *name; - vector items; - int mwidth; - int menusel; -}; - -vector menus; - -int vmenu = -1; - -ivector menustack; - -void -menuset(int menu) -{ - if ((vmenu = menu) >= 1) - resetmovement(player1); - if (vmenu == 1) - menus[1].menusel = 0; -}; - -void -showmenu(char *name) -{ - loopv(menus) if (i > 1 && strcmp(menus[i].name, name) == 0) - { - menuset(i); - return; - }; -}; - -int -menucompare(mitem *a, mitem *b) -{ - int x = atoi(a->text); - int y = atoi(b->text); - if (x > y) - return -1; - if (x < y) - return 1; - return 0; -}; - -void -sortmenu(int start, int num) -{ - qsort(&menus[0].items[start], num, sizeof(mitem), - (int(__cdecl *)(const void *, const void *))menucompare); -}; - -void refreshservers(); - -bool -rendermenu() -{ - if (vmenu < 0) { - menustack.setsize(0); - return false; - }; - if (vmenu == 1) - refreshservers(); - gmenu &m = menus[vmenu]; - sprintf_sd(title)(vmenu > 1 ? "[ %s menu ]" : "%s", m.name); - int mdisp = m.items.length(); - int w = 0; - loopi(mdisp) - { - int x = text_width(m.items[i].text); - if (x > w) - w = x; - }; - int tw = text_width(title); - if (tw > w) - w = tw; - int step = FONTH / 4 * 5; - int h = (mdisp + 2) * step; - int y = (VIRTH - h) / 2; - int x = (VIRTW - w) / 2; - blendbox(x - FONTH / 2 * 3, y - FONTH, x + w + FONTH / 2 * 3, - y + h + FONTH, true); - draw_text(title, x, y, 2); - y += FONTH * 2; - if (vmenu) { - int bh = y + m.menusel * step; - blendbox( - x - FONTH, bh - 10, x + w + FONTH, bh + FONTH + 10, false); - }; - loopj(mdisp) - { - draw_text(m.items[j].text, x, y, 2); - y += step; - }; - return true; -}; - -void -newmenu(char *name) -{ - gmenu &menu = menus.add(); - menu.name = newstring(name); - menu.menusel = 0; -}; - -void -menumanual(int m, int n, char *text) -{ - if (!n) - menus[m].items.setsize(0); - mitem &mitem = menus[m].items.add(); - mitem.text = text; - mitem.action = ""; -} - -void -menuitem(char *text, char *action) -{ - gmenu &menu = menus.last(); - mitem &mi = menu.items.add(); - mi.text = newstring(text); - mi.action = action[0] ? newstring(action) : mi.text; -}; - -COMMAND(menuitem, ARG_2STR); -COMMAND(showmenu, ARG_1STR); -COMMAND(newmenu, ARG_1STR); - -bool -menukey(int code, bool isdown) -{ - if (vmenu <= 0) - return false; - int menusel = menus[vmenu].menusel; - if (isdown) { - if (code == SDLK_ESCAPE) { - menuset(-1); - if (!menustack.empty()) - menuset(menustack.pop()); - return true; - } else if (code == SDLK_UP || code == -4) - menusel--; - else if (code == SDLK_DOWN || code == -5) - menusel++; - int n = menus[vmenu].items.length(); - if (menusel < 0) - menusel = n - 1; - else if (menusel >= n) - menusel = 0; - menus[vmenu].menusel = menusel; - } else { - if (code == SDLK_RETURN || code == -2) { - char *action = menus[vmenu].items[menusel].action; - if (vmenu == 1) - connects(getservername(menusel)); - menustack.add(vmenu); - menuset(-1); - execute(action, true); - }; - }; - return true; -}; ADDED src/menus.mm Index: src/menus.mm ================================================================== --- /dev/null +++ src/menus.mm @@ -0,0 +1,169 @@ +// menus.cpp: ingame menu system (also used for scores and serverlist) + +#include "cube.h" + +struct mitem { + char *text, *action; +}; + +struct gmenu { + char *name; + vector items; + int mwidth; + int menusel; +}; + +vector menus; + +int vmenu = -1; + +ivector menustack; + +void +menuset(int menu) +{ + if ((vmenu = menu) >= 1) + resetmovement(player1); + if (vmenu == 1) + menus[1].menusel = 0; +}; + +void +showmenu(char *name) +{ + loopv(menus) if (i > 1 && strcmp(menus[i].name, name) == 0) + { + menuset(i); + return; + }; +}; + +int +menucompare(mitem *a, mitem *b) +{ + int x = atoi(a->text); + int y = atoi(b->text); + if (x > y) + return -1; + if (x < y) + return 1; + return 0; +}; + +void +sortmenu(int start, int num) +{ + qsort(&menus[0].items[start], num, sizeof(mitem), + (int(__cdecl *)(const void *, const void *))menucompare); +}; + +void refreshservers(); + +bool +rendermenu() +{ + if (vmenu < 0) { + menustack.setsize(0); + return false; + }; + if (vmenu == 1) + refreshservers(); + gmenu &m = menus[vmenu]; + sprintf_sd(title)(vmenu > 1 ? "[ %s menu ]" : "%s", m.name); + int mdisp = m.items.length(); + int w = 0; + loopi(mdisp) + { + int x = text_width(m.items[i].text); + if (x > w) + w = x; + }; + int tw = text_width(title); + if (tw > w) + w = tw; + int step = FONTH / 4 * 5; + int h = (mdisp + 2) * step; + int y = (VIRTH - h) / 2; + int x = (VIRTW - w) / 2; + blendbox(x - FONTH / 2 * 3, y - FONTH, x + w + FONTH / 2 * 3, + y + h + FONTH, true); + draw_text(title, x, y, 2); + y += FONTH * 2; + if (vmenu) { + int bh = y + m.menusel * step; + blendbox( + x - FONTH, bh - 10, x + w + FONTH, bh + FONTH + 10, false); + }; + loopj(mdisp) + { + draw_text(m.items[j].text, x, y, 2); + y += step; + }; + return true; +}; + +void +newmenu(char *name) +{ + gmenu &menu = menus.add(); + menu.name = newstring(name); + menu.menusel = 0; +}; + +void +menumanual(int m, int n, char *text) +{ + if (!n) + menus[m].items.setsize(0); + mitem &mitem = menus[m].items.add(); + mitem.text = text; + mitem.action = ""; +} + +void +menuitem(char *text, char *action) +{ + gmenu &menu = menus.last(); + mitem &mi = menu.items.add(); + mi.text = newstring(text); + mi.action = action[0] ? newstring(action) : mi.text; +}; + +COMMAND(menuitem, ARG_2STR); +COMMAND(showmenu, ARG_1STR); +COMMAND(newmenu, ARG_1STR); + +bool +menukey(int code, bool isdown) +{ + if (vmenu <= 0) + return false; + int menusel = menus[vmenu].menusel; + if (isdown) { + if (code == SDLK_ESCAPE) { + menuset(-1); + if (!menustack.empty()) + menuset(menustack.pop()); + return true; + } else if (code == SDLK_UP || code == -4) + menusel--; + else if (code == SDLK_DOWN || code == -5) + menusel++; + int n = menus[vmenu].items.length(); + if (menusel < 0) + menusel = n - 1; + else if (menusel >= n) + menusel = 0; + menus[vmenu].menusel = menusel; + } else { + if (code == SDLK_RETURN || code == -2) { + char *action = menus[vmenu].items[menusel].action; + if (vmenu == 1) + connects(getservername(menusel)); + menustack.add(vmenu); + menuset(-1); + execute(action, true); + }; + }; + return true; +}; DELETED src/monster.cxx Index: src/monster.cxx ================================================================== --- src/monster.cxx +++ /dev/null @@ -1,409 +0,0 @@ -// monster.cpp: implements AI for single player monsters, currently client only - -#include "cube.h" - -dvector monsters; -int nextmonster, spawnremain, numkilled, monstertotal, mtimestart; - -VARF(skill, 1, 3, 10, conoutf("skill is now %d", skill)); - -dvector & -getmonsters() -{ - return monsters; -}; -void -restoremonsterstate() -{ - loopv(monsters) if (monsters[i]->state == CS_DEAD) numkilled++; -}; // for savegames - -#define TOTMFREQ 13 -#define NUMMONSTERTYPES 8 - -struct monstertype // see docs for how these values modify behaviour -{ - short gun, speed, health, freq, lag, rate, pain, loyalty, mscale, - bscale; - short painsound, diesound; - char *name, *mdlname; -} - -monstertypes[NUMMONSTERTYPES] = { - {GUN_FIREBALL, 15, 100, 3, 0, 100, 800, 1, 10, 10, S_PAINO, S_DIE1, - "an ogre", "monster/ogro"}, - {GUN_CG, 18, 70, 2, 70, 10, 400, 2, 8, 9, S_PAINR, S_DEATHR, "a rhino", - "monster/rhino"}, - {GUN_SG, 14, 120, 1, 100, 300, 400, 4, 14, 14, S_PAINE, S_DEATHE, - "ratamahatta", "monster/rat"}, - {GUN_RIFLE, 15, 200, 1, 80, 300, 300, 4, 18, 18, S_PAINS, S_DEATHS, - "a slith", "monster/slith"}, - {GUN_RL, 13, 500, 1, 0, 100, 200, 6, 24, 24, S_PAINB, S_DEATHB, "bauul", - "monster/bauul"}, - {GUN_BITE, 22, 50, 3, 0, 100, 400, 1, 12, 15, S_PAINP, S_PIGGR2, - "a hellpig", "monster/hellpig"}, - {GUN_ICEBALL, 12, 250, 1, 0, 10, 400, 6, 18, 18, S_PAINH, S_DEATHH, - "a knight", "monster/knight"}, - {GUN_SLIMEBALL, 15, 100, 1, 0, 200, 400, 2, 13, 10, S_PAIND, S_DEATHD, - "a goblin", "monster/goblin"}, -}; - -dynent * -basicmonster(int type, int yaw, int state, int trigger, int move) -{ - if (type >= NUMMONSTERTYPES) { - conoutf("warning: unknown monster in spawn: %d", type); - type = 0; - }; - dynent *m = newdynent(); - monstertype *t = &monstertypes[m->mtype = type]; - m->eyeheight = 2.0f; - m->aboveeye = 1.9f; - m->radius *= t->bscale / 10.0f; - m->eyeheight *= t->bscale / 10.0f; - m->aboveeye *= t->bscale / 10.0f; - m->monsterstate = state; - if (state != M_SLEEP) - spawnplayer(m); - m->trigger = lastmillis + trigger; - m->targetyaw = m->yaw = (float)yaw; - m->move = move; - m->enemy = player1; - m->gunselect = t->gun; - m->maxspeed = (float)t->speed; - m->health = t->health; - m->armour = 0; - loopi(NUMGUNS) m->ammo[i] = 10000; - m->pitch = 0; - m->roll = 0; - m->state = CS_ALIVE; - m->anger = 0; - strcpy_s(m->name, t->name); - monsters.add(m); - return m; -}; - -void -spawnmonster() // spawn a random monster according to freq distribution in DMSP -{ - int n = rnd(TOTMFREQ), type; - for (int i = 0;; i++) - if ((n -= monstertypes[i].freq) < 0) { - type = i; - break; - }; - basicmonster(type, rnd(360), M_SEARCH, 1000, 1); -}; - -void -monsterclear() // called after map start of when toggling edit mode to - // reset/spawn all monsters to initial state -{ - loopv(monsters) gp()->dealloc(monsters[i], sizeof(dynent)); - monsters.setsize(0); - numkilled = 0; - monstertotal = 0; - spawnremain = 0; - if (m_dmsp) { - nextmonster = mtimestart = lastmillis + 10000; - monstertotal = spawnremain = gamemode < 0 ? skill * 10 : 0; - } else if (m_classicsp) { - mtimestart = lastmillis; - loopv(ents) if (ents[i].type == MONSTER) - { - dynent *m = basicmonster( - ents[i].attr2, ents[i].attr1, M_SLEEP, 100, 0); - m->o.x = ents[i].x; - m->o.y = ents[i].y; - m->o.z = ents[i].z; - entinmap(m); - monstertotal++; - }; - }; -}; - -bool -los(float lx, float ly, float lz, float bx, float by, float bz, - vec &v) // height-correct line of sight for monster shooting/seeing -{ - if (OUTBORD((int)lx, (int)ly) || OUTBORD((int)bx, (int)by)) - return false; - float dx = bx - lx; - float dy = by - ly; - int steps = (int)(sqrt(dx * dx + dy * dy) / 0.9); - if (!steps) - return false; - float x = lx; - float y = ly; - int i = 0; - for (;;) { - sqr *s = S(fast_f2nat(x), fast_f2nat(y)); - if (SOLID(s)) - break; - float floor = s->floor; - if (s->type == FHF) - floor -= s->vdelta / 4.0f; - float ceil = s->ceil; - if (s->type == CHF) - ceil += s->vdelta / 4.0f; - float rz = lz - ((lz - bz) * (i / (float)steps)); - if (rz < floor || rz > ceil) - break; - v.x = x; - v.y = y; - v.z = rz; - x += dx / (float)steps; - y += dy / (float)steps; - i++; - }; - return i >= steps; -}; - -bool -enemylos(dynent *m, vec &v) -{ - v = m->o; - return los(m->o.x, m->o.y, m->o.z, m->enemy->o.x, m->enemy->o.y, - m->enemy->o.z, v); -}; - -// monster AI is sequenced using transitions: they are in a particular state -// where they execute a particular behaviour until the trigger time is hit, and -// then they reevaluate their situation based on the current state, the -// environment etc., and transition to the next state. Transition timeframes are -// parametrized by difficulty level (skill), faster transitions means quicker -// decision making means tougher AI. - -void -transition(dynent *m, int state, int moving, int n, - int r) // n = at skill 0, n/2 = at skill 10, r = added random factor -{ - m->monsterstate = state; - m->move = moving; - n = n * 130 / 100; - m->trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1); -}; - -void -normalise(dynent *m, float angle) -{ - while (m->yaw < angle - 180.0f) - m->yaw += 360.0f; - while (m->yaw > angle + 180.0f) - m->yaw -= 360.0f; -}; - -void -monsteraction( - dynent *m) // main AI thinking routine, called every frame for every monster -{ - if (m->enemy->state == CS_DEAD) { - m->enemy = player1; - m->anger = 0; - }; - normalise(m, m->targetyaw); - if (m->targetyaw > m->yaw) // slowly turn monster towards his target - { - m->yaw += curtime * 0.5f; - if (m->targetyaw < m->yaw) - m->yaw = m->targetyaw; - } else { - m->yaw -= curtime * 0.5f; - if (m->targetyaw > m->yaw) - m->yaw = m->targetyaw; - }; - - vdist(disttoenemy, vectoenemy, m->o, m->enemy->o); - m->pitch = atan2(m->enemy->o.z - m->o.z, disttoenemy) * 180 / PI; - - if (m->blocked) // special case: if we run into scenery - { - m->blocked = false; - if (!rnd(20000 / - monstertypes[m->mtype] - .speed)) // try to jump over obstackle (rare) - { - m->jumpnext = true; - } else if (m->trigger < lastmillis && - (m->monsterstate != M_HOME || - !rnd(5))) // search for a way around (common) - { - m->targetyaw += - 180 + rnd(180); // patented "random walk" AI - // pathfinding (tm) ;) - transition(m, M_SEARCH, 1, 400, 1000); - }; - }; - - float enemyyaw = - -(float)atan2(m->enemy->o.x - m->o.x, m->enemy->o.y - m->o.y) / PI * - 180 + - 180; - - switch (m->monsterstate) { - case M_PAIN: - case M_ATTACKING: - case M_SEARCH: - if (m->trigger < lastmillis) - transition(m, M_HOME, 1, 100, 200); - break; - - case M_SLEEP: // state classic sp monster start in, wait for visual - // contact - { - vec target; - if (editmode || !enemylos(m, target)) - return; // skip running physics - normalise(m, enemyyaw); - float angle = (float)fabs(enemyyaw - m->yaw); - if (disttoenemy < 8 // the better the angle to the player, the - // further the monster can see/hear - || (disttoenemy < 16 && angle < 135) || - (disttoenemy < 32 && angle < 90) || - (disttoenemy < 64 && angle < 45) || angle < 10) { - transition(m, M_HOME, 1, 500, 200); - playsound(S_GRUNT1 + rnd(2), &m->o); - }; - break; - }; - - case M_AIMING: // this state is the delay between wanting to shoot and - // actually firing - if (m->trigger < lastmillis) { - m->lastaction = 0; - m->attacking = true; - shoot(m, m->attacktarget); - transition(m, M_ATTACKING, 0, 600, 0); - }; - break; - - case M_HOME: // monster has visual contact, heads straight for player - // and may want to shoot at any time - m->targetyaw = enemyyaw; - if (m->trigger < lastmillis) { - vec target; - if (!enemylos( - m, target)) // no visual contact anymore, let - // monster get as close as possible - // then search for player - { - transition(m, M_HOME, 1, 800, 500); - } else // the closer the monster is the more likely he - // wants to shoot - { - if (!rnd((int)disttoenemy / 3 + 1) && - m->enemy->state == - CS_ALIVE) // get ready to fire - { - m->attacktarget = target; - transition(m, M_AIMING, 0, - monstertypes[m->mtype].lag, 10); - } else // track player some more - { - transition(m, M_HOME, 1, - monstertypes[m->mtype].rate, 0); - }; - }; - }; - break; - }; - - moveplayer(m, 1, false); // use physics to move monster -}; - -void -monsterpain(dynent *m, int damage, dynent *d) -{ - if (d->monsterstate) // a monster hit us - { - if (m != d) // guard for RL guys shooting themselves :) - { - m->anger++; // don't attack straight away, first get - // angry - int anger = - m->mtype == d->mtype ? m->anger / 2 : m->anger; - if (anger >= monstertypes[m->mtype].loyalty) - m->enemy = d; // monster infight if very angry - }; - } else // player hit us - { - m->anger = 0; - m->enemy = d; - }; - transition(m, M_PAIN, 0, monstertypes[m->mtype].pain, - 200); // in this state monster won't attack - if ((m->health -= damage) <= 0) { - m->state = CS_DEAD; - m->lastaction = lastmillis; - numkilled++; - player1->frags = numkilled; - playsound(monstertypes[m->mtype].diesound, &m->o); - int remain = monstertotal - numkilled; - if (remain > 0 && remain <= 5) - conoutf("only %d monster(s) remaining", remain); - } else { - playsound(monstertypes[m->mtype].painsound, &m->o); - }; -}; - -void -endsp(bool allkilled) -{ - conoutf( - allkilled ? "you have cleared the map!" : "you reached the exit!"); - conoutf("score: %d kills in %d seconds", numkilled, - (lastmillis - mtimestart) / 1000); - monstertotal = 0; - startintermission(); -}; - -void -monsterthink() -{ - if (m_dmsp && spawnremain && lastmillis > nextmonster) { - if (spawnremain-- == monstertotal) - conoutf("The invasion has begun!"); - nextmonster = lastmillis + 1000; - spawnmonster(); - }; - - if (monstertotal && !spawnremain && numkilled == monstertotal) - endsp(true); - - loopv(ents) // equivalent of player entity touch, but only teleports are - // used - { - entity &e = ents[i]; - if (e.type != TELEPORT) - continue; - if (OUTBORD(e.x, e.y)) - continue; - vec v = {(float)e.x, (float)e.y, (float)S(e.x, e.y)->floor}; - loopv(monsters) if (monsters[i]->state == CS_DEAD) - { - if (lastmillis - monsters[i]->lastaction < 2000) { - monsters[i]->move = 0; - moveplayer(monsters[i], 1, false); - }; - } - else - { - v.z += monsters[i]->eyeheight; - vdist(dist, t, monsters[i]->o, v); - v.z -= monsters[i]->eyeheight; - if (dist < 4) - teleport((int)(&e - &ents[0]), monsters[i]); - }; - }; - - loopv(monsters) if (monsters[i]->state == CS_ALIVE) - monsteraction(monsters[i]); -}; - -void -monsterrender() -{ - loopv(monsters) renderclient(monsters[i], false, - monstertypes[monsters[i]->mtype].mdlname, monsters[i]->mtype == 5, - monstertypes[monsters[i]->mtype].mscale / 10.0f); -}; ADDED src/monster.mm Index: src/monster.mm ================================================================== --- /dev/null +++ src/monster.mm @@ -0,0 +1,409 @@ +// monster.cpp: implements AI for single player monsters, currently client only + +#include "cube.h" + +dvector monsters; +int nextmonster, spawnremain, numkilled, monstertotal, mtimestart; + +VARF(skill, 1, 3, 10, conoutf("skill is now %d", skill)); + +dvector & +getmonsters() +{ + return monsters; +}; +void +restoremonsterstate() +{ + loopv(monsters) if (monsters[i]->state == CS_DEAD) numkilled++; +}; // for savegames + +#define TOTMFREQ 13 +#define NUMMONSTERTYPES 8 + +struct monstertype // see docs for how these values modify behaviour +{ + short gun, speed, health, freq, lag, rate, pain, loyalty, mscale, + bscale; + short painsound, diesound; + char *name, *mdlname; +} + +monstertypes[NUMMONSTERTYPES] = { + {GUN_FIREBALL, 15, 100, 3, 0, 100, 800, 1, 10, 10, S_PAINO, S_DIE1, + "an ogre", "monster/ogro"}, + {GUN_CG, 18, 70, 2, 70, 10, 400, 2, 8, 9, S_PAINR, S_DEATHR, "a rhino", + "monster/rhino"}, + {GUN_SG, 14, 120, 1, 100, 300, 400, 4, 14, 14, S_PAINE, S_DEATHE, + "ratamahatta", "monster/rat"}, + {GUN_RIFLE, 15, 200, 1, 80, 300, 300, 4, 18, 18, S_PAINS, S_DEATHS, + "a slith", "monster/slith"}, + {GUN_RL, 13, 500, 1, 0, 100, 200, 6, 24, 24, S_PAINB, S_DEATHB, "bauul", + "monster/bauul"}, + {GUN_BITE, 22, 50, 3, 0, 100, 400, 1, 12, 15, S_PAINP, S_PIGGR2, + "a hellpig", "monster/hellpig"}, + {GUN_ICEBALL, 12, 250, 1, 0, 10, 400, 6, 18, 18, S_PAINH, S_DEATHH, + "a knight", "monster/knight"}, + {GUN_SLIMEBALL, 15, 100, 1, 0, 200, 400, 2, 13, 10, S_PAIND, S_DEATHD, + "a goblin", "monster/goblin"}, +}; + +dynent * +basicmonster(int type, int yaw, int state, int trigger, int move) +{ + if (type >= NUMMONSTERTYPES) { + conoutf("warning: unknown monster in spawn: %d", type); + type = 0; + }; + dynent *m = newdynent(); + monstertype *t = &monstertypes[m->mtype = type]; + m->eyeheight = 2.0f; + m->aboveeye = 1.9f; + m->radius *= t->bscale / 10.0f; + m->eyeheight *= t->bscale / 10.0f; + m->aboveeye *= t->bscale / 10.0f; + m->monsterstate = state; + if (state != M_SLEEP) + spawnplayer(m); + m->trigger = lastmillis + trigger; + m->targetyaw = m->yaw = (float)yaw; + m->move = move; + m->enemy = player1; + m->gunselect = t->gun; + m->maxspeed = (float)t->speed; + m->health = t->health; + m->armour = 0; + loopi(NUMGUNS) m->ammo[i] = 10000; + m->pitch = 0; + m->roll = 0; + m->state = CS_ALIVE; + m->anger = 0; + strcpy_s(m->name, t->name); + monsters.add(m); + return m; +}; + +void +spawnmonster() // spawn a random monster according to freq distribution in DMSP +{ + int n = rnd(TOTMFREQ), type; + for (int i = 0;; i++) + if ((n -= monstertypes[i].freq) < 0) { + type = i; + break; + }; + basicmonster(type, rnd(360), M_SEARCH, 1000, 1); +}; + +void +monsterclear() // called after map start of when toggling edit mode to + // reset/spawn all monsters to initial state +{ + loopv(monsters) gp()->dealloc(monsters[i], sizeof(dynent)); + monsters.setsize(0); + numkilled = 0; + monstertotal = 0; + spawnremain = 0; + if (m_dmsp) { + nextmonster = mtimestart = lastmillis + 10000; + monstertotal = spawnremain = gamemode < 0 ? skill * 10 : 0; + } else if (m_classicsp) { + mtimestart = lastmillis; + loopv(ents) if (ents[i].type == MONSTER) + { + dynent *m = basicmonster( + ents[i].attr2, ents[i].attr1, M_SLEEP, 100, 0); + m->o.x = ents[i].x; + m->o.y = ents[i].y; + m->o.z = ents[i].z; + entinmap(m); + monstertotal++; + }; + }; +}; + +bool +los(float lx, float ly, float lz, float bx, float by, float bz, + vec &v) // height-correct line of sight for monster shooting/seeing +{ + if (OUTBORD((int)lx, (int)ly) || OUTBORD((int)bx, (int)by)) + return false; + float dx = bx - lx; + float dy = by - ly; + int steps = (int)(sqrt(dx * dx + dy * dy) / 0.9); + if (!steps) + return false; + float x = lx; + float y = ly; + int i = 0; + for (;;) { + sqr *s = S(fast_f2nat(x), fast_f2nat(y)); + if (SOLID(s)) + break; + float floor = s->floor; + if (s->type == FHF) + floor -= s->vdelta / 4.0f; + float ceil = s->ceil; + if (s->type == CHF) + ceil += s->vdelta / 4.0f; + float rz = lz - ((lz - bz) * (i / (float)steps)); + if (rz < floor || rz > ceil) + break; + v.x = x; + v.y = y; + v.z = rz; + x += dx / (float)steps; + y += dy / (float)steps; + i++; + }; + return i >= steps; +}; + +bool +enemylos(dynent *m, vec &v) +{ + v = m->o; + return los(m->o.x, m->o.y, m->o.z, m->enemy->o.x, m->enemy->o.y, + m->enemy->o.z, v); +}; + +// monster AI is sequenced using transitions: they are in a particular state +// where they execute a particular behaviour until the trigger time is hit, and +// then they reevaluate their situation based on the current state, the +// environment etc., and transition to the next state. Transition timeframes are +// parametrized by difficulty level (skill), faster transitions means quicker +// decision making means tougher AI. + +void +transition(dynent *m, int state, int moving, int n, + int r) // n = at skill 0, n/2 = at skill 10, r = added random factor +{ + m->monsterstate = state; + m->move = moving; + n = n * 130 / 100; + m->trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1); +}; + +void +normalise(dynent *m, float angle) +{ + while (m->yaw < angle - 180.0f) + m->yaw += 360.0f; + while (m->yaw > angle + 180.0f) + m->yaw -= 360.0f; +}; + +void +monsteraction( + dynent *m) // main AI thinking routine, called every frame for every monster +{ + if (m->enemy->state == CS_DEAD) { + m->enemy = player1; + m->anger = 0; + }; + normalise(m, m->targetyaw); + if (m->targetyaw > m->yaw) // slowly turn monster towards his target + { + m->yaw += curtime * 0.5f; + if (m->targetyaw < m->yaw) + m->yaw = m->targetyaw; + } else { + m->yaw -= curtime * 0.5f; + if (m->targetyaw > m->yaw) + m->yaw = m->targetyaw; + }; + + vdist(disttoenemy, vectoenemy, m->o, m->enemy->o); + m->pitch = atan2(m->enemy->o.z - m->o.z, disttoenemy) * 180 / PI; + + if (m->blocked) // special case: if we run into scenery + { + m->blocked = false; + if (!rnd(20000 / + monstertypes[m->mtype] + .speed)) // try to jump over obstackle (rare) + { + m->jumpnext = true; + } else if (m->trigger < lastmillis && + (m->monsterstate != M_HOME || + !rnd(5))) // search for a way around (common) + { + m->targetyaw += + 180 + rnd(180); // patented "random walk" AI + // pathfinding (tm) ;) + transition(m, M_SEARCH, 1, 400, 1000); + }; + }; + + float enemyyaw = + -(float)atan2(m->enemy->o.x - m->o.x, m->enemy->o.y - m->o.y) / PI * + 180 + + 180; + + switch (m->monsterstate) { + case M_PAIN: + case M_ATTACKING: + case M_SEARCH: + if (m->trigger < lastmillis) + transition(m, M_HOME, 1, 100, 200); + break; + + case M_SLEEP: // state classic sp monster start in, wait for visual + // contact + { + vec target; + if (editmode || !enemylos(m, target)) + return; // skip running physics + normalise(m, enemyyaw); + float angle = (float)fabs(enemyyaw - m->yaw); + if (disttoenemy < 8 // the better the angle to the player, the + // further the monster can see/hear + || (disttoenemy < 16 && angle < 135) || + (disttoenemy < 32 && angle < 90) || + (disttoenemy < 64 && angle < 45) || angle < 10) { + transition(m, M_HOME, 1, 500, 200); + playsound(S_GRUNT1 + rnd(2), &m->o); + }; + break; + }; + + case M_AIMING: // this state is the delay between wanting to shoot and + // actually firing + if (m->trigger < lastmillis) { + m->lastaction = 0; + m->attacking = true; + shoot(m, m->attacktarget); + transition(m, M_ATTACKING, 0, 600, 0); + }; + break; + + case M_HOME: // monster has visual contact, heads straight for player + // and may want to shoot at any time + m->targetyaw = enemyyaw; + if (m->trigger < lastmillis) { + vec target; + if (!enemylos( + m, target)) // no visual contact anymore, let + // monster get as close as possible + // then search for player + { + transition(m, M_HOME, 1, 800, 500); + } else // the closer the monster is the more likely he + // wants to shoot + { + if (!rnd((int)disttoenemy / 3 + 1) && + m->enemy->state == + CS_ALIVE) // get ready to fire + { + m->attacktarget = target; + transition(m, M_AIMING, 0, + monstertypes[m->mtype].lag, 10); + } else // track player some more + { + transition(m, M_HOME, 1, + monstertypes[m->mtype].rate, 0); + }; + }; + }; + break; + }; + + moveplayer(m, 1, false); // use physics to move monster +}; + +void +monsterpain(dynent *m, int damage, dynent *d) +{ + if (d->monsterstate) // a monster hit us + { + if (m != d) // guard for RL guys shooting themselves :) + { + m->anger++; // don't attack straight away, first get + // angry + int anger = + m->mtype == d->mtype ? m->anger / 2 : m->anger; + if (anger >= monstertypes[m->mtype].loyalty) + m->enemy = d; // monster infight if very angry + }; + } else // player hit us + { + m->anger = 0; + m->enemy = d; + }; + transition(m, M_PAIN, 0, monstertypes[m->mtype].pain, + 200); // in this state monster won't attack + if ((m->health -= damage) <= 0) { + m->state = CS_DEAD; + m->lastaction = lastmillis; + numkilled++; + player1->frags = numkilled; + playsound(monstertypes[m->mtype].diesound, &m->o); + int remain = monstertotal - numkilled; + if (remain > 0 && remain <= 5) + conoutf("only %d monster(s) remaining", remain); + } else { + playsound(monstertypes[m->mtype].painsound, &m->o); + }; +}; + +void +endsp(bool allkilled) +{ + conoutf( + allkilled ? "you have cleared the map!" : "you reached the exit!"); + conoutf("score: %d kills in %d seconds", numkilled, + (lastmillis - mtimestart) / 1000); + monstertotal = 0; + startintermission(); +}; + +void +monsterthink() +{ + if (m_dmsp && spawnremain && lastmillis > nextmonster) { + if (spawnremain-- == monstertotal) + conoutf("The invasion has begun!"); + nextmonster = lastmillis + 1000; + spawnmonster(); + }; + + if (monstertotal && !spawnremain && numkilled == monstertotal) + endsp(true); + + loopv(ents) // equivalent of player entity touch, but only teleports are + // used + { + entity &e = ents[i]; + if (e.type != TELEPORT) + continue; + if (OUTBORD(e.x, e.y)) + continue; + vec v = {(float)e.x, (float)e.y, (float)S(e.x, e.y)->floor}; + loopv(monsters) if (monsters[i]->state == CS_DEAD) + { + if (lastmillis - monsters[i]->lastaction < 2000) { + monsters[i]->move = 0; + moveplayer(monsters[i], 1, false); + }; + } + else + { + v.z += monsters[i]->eyeheight; + vdist(dist, t, monsters[i]->o, v); + v.z -= monsters[i]->eyeheight; + if (dist < 4) + teleport((int)(&e - &ents[0]), monsters[i]); + }; + }; + + loopv(monsters) if (monsters[i]->state == CS_ALIVE) + monsteraction(monsters[i]); +}; + +void +monsterrender() +{ + loopv(monsters) renderclient(monsters[i], false, + monstertypes[monsters[i]->mtype].mdlname, monsters[i]->mtype == 5, + monstertypes[monsters[i]->mtype].mscale / 10.0f); +}; DELETED src/physics.cxx Index: src/physics.cxx ================================================================== --- src/physics.cxx +++ /dev/null @@ -1,404 +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" - -bool -plcollide(dynent *d, dynent *o, float &headspace, float &hi, - float &lo) // collide with player or monster -{ - 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(dynent *d, float &hi, float &lo) // collide with a mapmodel -{ - loopv(ents) - { - entity &e = ents[i]; - if (e.type != MAPMODEL) - continue; - mapmodelinfo &mmi = getmminfo(e.attr2); - if (!&mmi || !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(dynent *d, bool spawn, float drop, float rise) -{ - const float fx1 = - d->o.x - d->radius; // figure out integer cube rectangle this entity - // covers in map - 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; - float minfloor = (d->monsterstate && !spawn && d->health > 100) - ? d->o.z - d->eyeheight - 4.5f - : -1000.0f; // big monsters are afraid of heights, - // unless angry :) - - 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; - loopv(players) // collide with other players - { - dynent *o = players[i]; - if (!o || o == d) - continue; - if (!plcollide(d, o, headspace, hi, lo)) - return false; - }; - if (d != player1) - if (!plcollide(d, player1, headspace, hi, lo)) - return false; - dvector &v = getmonsters(); - // this loop can be a performance bottleneck with many monster on a slow - // cpu, should replace with a blockmap but seems mostly fast enough - loopv(v) if (!vreject(d->o, v[i]->o, 7.0f) && d != v[i] && - !plcollide(d, v[i], headspace, hi, lo)) return false; - headspace -= 0.01f; - - mmcollide(d, hi, lo); // collide with map models - - if (spawn) { - d->o.z = lo + d->eyeheight; // just drop to floor (sideeffect) - d->onfloor = true; - } else { - const float space = d->o.z - d->eyeheight - lo; - if (space < 0) { - if (space > -0.01) - d->o.z = lo + d->eyeheight; // stick on step - else if (space > -1.26f) - d->o.z += rise; // rise thru stair - else - return false; - } else { - d->o.z -= min(min(drop, space), headspace); // gravity - }; - - const float space2 = hi - (d->o.z + d->aboveeye); - if (space2 < 0) { - if (space2 < -0.1) - return false; // hack alert! - d->o.z = hi - d->aboveeye; // glue to ceiling - d->vel.z = 0; // cancel out jumping velocity - }; - - 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(dynent *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; - - vec 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; - - vmul(pl->vel, fpsfric - 1); // slowly apply friction and direction to - // velocity, gives a smooth movement - 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.z = 2; - } - } else // apply velocity with collision - { - if (pl->onfloor || water) { - if (pl->jumpnext) { - pl->jumpnext = false; - pl->vel.z = 1.7f; // physics impulse upwards - if (water) { - pl->vel.x /= 8; - pl->vel.y /= 8; - }; // dampen velocity change even harder, gives - // correct water feel - if (local) - playsoundc(S_JUMP); - else if (pl->monsterstate) - playsound(S_JUMP, &pl->o); - } 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) - playsound(S_LAND, &pl->o); - }; - pl->timeinair = 0; - } else { - pl->timeinair += curtime; - }; - - const float gravity = 20; - const float f = 1.0f / moveres; - float dropf = - ((gravity - 1) + - pl->timeinair / 15.0f); // incorrect, but works fine - if (water) { - dropf = 5; - pl->timeinair = 0; - }; // float slowly down in water - const float drop = - dropf * curtime / gravity / 100 / - moveres; // at high fps, gravity kicks in too fast - const float rise = - speed / moveres / - 1.2f; // extra smoothness when lifting up stairs - - loopi(moveres) // discrete steps collision detection & sliding - { - // try move forward - 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.x -= f * d.x; - if (collide(pl, false, drop, rise)) { - d.x = 0; - continue; - }; - pl->o.x += f * d.x; - // still stuck, try x axis - pl->o.y -= f * d.y; - if (collide(pl, false, drop, rise)) { - d.y = 0; - continue; - }; - pl->o.y += f * d.y; - // try just dropping down - pl->moving = false; - pl->o.x -= f * d.x; - pl->o.y -= f * d.y; - if (collide(pl, false, drop, rise)) { - d.y = d.x = 0; - continue; - }; - 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) { - playsound(S_SPLASH2, &pl->o); - pl->vel.z = 0; - } else if (pl->inwater && !water) - playsound(S_SPLASH1, &pl->o); - pl->inwater = water; -}; - -void -moveplayer(dynent *pl, int moveres, bool local) -{ - loopi(physicsrepeat) moveplayer(pl, moveres, local, - i ? curtime / physicsrepeat - : curtime - curtime / physicsrepeat * (physicsrepeat - 1)); -}; ADDED src/physics.mm Index: src/physics.mm ================================================================== --- /dev/null +++ src/physics.mm @@ -0,0 +1,404 @@ +// 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" + +bool +plcollide(dynent *d, dynent *o, float &headspace, float &hi, + float &lo) // collide with player or monster +{ + 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(dynent *d, float &hi, float &lo) // collide with a mapmodel +{ + loopv(ents) + { + entity &e = ents[i]; + if (e.type != MAPMODEL) + continue; + mapmodelinfo &mmi = getmminfo(e.attr2); + if (!&mmi || !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(dynent *d, bool spawn, float drop, float rise) +{ + const float fx1 = + d->o.x - d->radius; // figure out integer cube rectangle this entity + // covers in map + 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; + float minfloor = (d->monsterstate && !spawn && d->health > 100) + ? d->o.z - d->eyeheight - 4.5f + : -1000.0f; // big monsters are afraid of heights, + // unless angry :) + + 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; + loopv(players) // collide with other players + { + dynent *o = players[i]; + if (!o || o == d) + continue; + if (!plcollide(d, o, headspace, hi, lo)) + return false; + }; + if (d != player1) + if (!plcollide(d, player1, headspace, hi, lo)) + return false; + dvector &v = getmonsters(); + // this loop can be a performance bottleneck with many monster on a slow + // cpu, should replace with a blockmap but seems mostly fast enough + loopv(v) if (!vreject(d->o, v[i]->o, 7.0f) && d != v[i] && + !plcollide(d, v[i], headspace, hi, lo)) return false; + headspace -= 0.01f; + + mmcollide(d, hi, lo); // collide with map models + + if (spawn) { + d->o.z = lo + d->eyeheight; // just drop to floor (sideeffect) + d->onfloor = true; + } else { + const float space = d->o.z - d->eyeheight - lo; + if (space < 0) { + if (space > -0.01) + d->o.z = lo + d->eyeheight; // stick on step + else if (space > -1.26f) + d->o.z += rise; // rise thru stair + else + return false; + } else { + d->o.z -= min(min(drop, space), headspace); // gravity + }; + + const float space2 = hi - (d->o.z + d->aboveeye); + if (space2 < 0) { + if (space2 < -0.1) + return false; // hack alert! + d->o.z = hi - d->aboveeye; // glue to ceiling + d->vel.z = 0; // cancel out jumping velocity + }; + + 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(dynent *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; + + vec 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; + + vmul(pl->vel, fpsfric - 1); // slowly apply friction and direction to + // velocity, gives a smooth movement + 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.z = 2; + } + } else // apply velocity with collision + { + if (pl->onfloor || water) { + if (pl->jumpnext) { + pl->jumpnext = false; + pl->vel.z = 1.7f; // physics impulse upwards + if (water) { + pl->vel.x /= 8; + pl->vel.y /= 8; + }; // dampen velocity change even harder, gives + // correct water feel + if (local) + playsoundc(S_JUMP); + else if (pl->monsterstate) + playsound(S_JUMP, &pl->o); + } 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) + playsound(S_LAND, &pl->o); + }; + pl->timeinair = 0; + } else { + pl->timeinair += curtime; + }; + + const float gravity = 20; + const float f = 1.0f / moveres; + float dropf = + ((gravity - 1) + + pl->timeinair / 15.0f); // incorrect, but works fine + if (water) { + dropf = 5; + pl->timeinair = 0; + }; // float slowly down in water + const float drop = + dropf * curtime / gravity / 100 / + moveres; // at high fps, gravity kicks in too fast + const float rise = + speed / moveres / + 1.2f; // extra smoothness when lifting up stairs + + loopi(moveres) // discrete steps collision detection & sliding + { + // try move forward + 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.x -= f * d.x; + if (collide(pl, false, drop, rise)) { + d.x = 0; + continue; + }; + pl->o.x += f * d.x; + // still stuck, try x axis + pl->o.y -= f * d.y; + if (collide(pl, false, drop, rise)) { + d.y = 0; + continue; + }; + pl->o.y += f * d.y; + // try just dropping down + pl->moving = false; + pl->o.x -= f * d.x; + pl->o.y -= f * d.y; + if (collide(pl, false, drop, rise)) { + d.y = d.x = 0; + continue; + }; + 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) { + playsound(S_SPLASH2, &pl->o); + pl->vel.z = 0; + } else if (pl->inwater && !water) + playsound(S_SPLASH1, &pl->o); + pl->inwater = water; +}; + +void +moveplayer(dynent *pl, int moveres, bool local) +{ + loopi(physicsrepeat) moveplayer(pl, moveres, local, + i ? curtime / physicsrepeat + : curtime - curtime / physicsrepeat * (physicsrepeat - 1)); +}; DELETED src/rendercubes.cxx Index: src/rendercubes.cxx ================================================================== --- src/rendercubes.cxx +++ /dev/null @@ -1,428 +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 *)realloc(verts, (curmaxverts *= 2) * sizeof(vertex)); - curmaxverts -= 10; - if (!verts) - fatal("no vertex memory!"); - 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; -}; -void -mipstats(int a, int b, int c) -{ - if (showm) - conoutf("1x1/2x2/4x4: %d / %d / %d", a, b, c); -}; - -COMMAND(showmip, ARG_NONE); - -#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/rendercubes.mm Index: src/rendercubes.mm ================================================================== --- /dev/null +++ src/rendercubes.mm @@ -0,0 +1,428 @@ +// 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 *)realloc(verts, (curmaxverts *= 2) * sizeof(vertex)); + curmaxverts -= 10; + if (!verts) + fatal("no vertex memory!"); + 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; +}; +void +mipstats(int a, int b, int c) +{ + if (showm) + conoutf("1x1/2x2/4x4: %d / %d / %d", a, b, c); +}; + +COMMAND(showmip, ARG_NONE); + +#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; +}; DELETED src/renderextras.cxx Index: src/renderextras.cxx ================================================================== --- src/renderextras.cxx +++ /dev/null @@ -1,436 +0,0 @@ -// renderextras.cpp: misc gl render code and the HUD - -#include "cube.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(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 { - vec o; - float size, max; - int type; - sphere *next; -}; -sphere spheres[MAXSPHERES], *slist = NULL, *sempty = NULL; -bool sinit = false; - -void -newsphere(vec &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); -}; - -string closeent; -char *entnames[] = { - "none?", - "light", - "playerstart", - "shells", - "bullets", - "rockets", - "riflerounds", - "health", - "healthboost", - "greenarmour", - "yellowarmour", - "quaddamage", - "teleport", - "teledest", - "mapmodel", - "monster", - "trigger", - "jumppad", - "?", - "?", - "?", - "?", - "?", -}; - -void -renderents() // show sparkly thingies for map entities in edit mode -{ - closeent[0] = 0; - if (!editmode) - return; - loopv(ents) - { - entity &e = ents[i]; - if (e.type == NOTUSED) - continue; - vec v = {(float)e.x, (float)e.y, (float)e.z}; - particle_splash(2, 2, 40, v); - }; - int e = closestent(); - if (e >= 0) { - entity &c = ents[e]; - sprintf_s(closeent)("closest entity = %s (%d, %d, %d, %d), " - "selection = (%d, %d)", - entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, - getvar("selxs"), getvar("selys")); - }; -}; - -void -loadsky(char *basename) -{ - static string lastsky = ""; - if (strcmp(lastsky, basename) == 0) - return; - char *side[] = {"ft", "bk", "lf", "rt", "dn", "up"}; - int texnum = 14; - loopi(6) - { - sprintf_sd(name)("packages/%s_%s.jpg", basename, side[i]); - int xs, ys; - if (!installtex(texnum + i, path(name), xs, ys, true)) - conoutf("could not load sky textures"); - }; - strcpy_s(lastsky, basename); -}; - -COMMAND(loadsky, ARG_1STR); - -float cursordepth = 0.9f; -GLint viewport[4]; -GLdouble mm[16], pm[16]; -vec 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; - vec r = {(float)mm[0], (float)mm[4], (float)mm[8]}; - vec u = {(float)mm[1], (float)mm[5], (float)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); - - char *command = getcurcommand(); - char *player = playerincrosshair(); - if (command) - draw_textf("> %s_", 20, 1570, 2, command); - else if (closeent[0]) - draw_text(closeent, 20, 1570, 2); - else if (player) - draw_text(player, 20, 1570, 2); - - renderscores(); - if (!rendermenu()) { - glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); - glBindTexture(GL_TEXTURE_2D, 1); - 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/renderextras.mm Index: src/renderextras.mm ================================================================== --- /dev/null +++ src/renderextras.mm @@ -0,0 +1,436 @@ +// renderextras.cpp: misc gl render code and the HUD + +#include "cube.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(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 { + vec o; + float size, max; + int type; + sphere *next; +}; +sphere spheres[MAXSPHERES], *slist = NULL, *sempty = NULL; +bool sinit = false; + +void +newsphere(vec &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); +}; + +string closeent; +char *entnames[] = { + "none?", + "light", + "playerstart", + "shells", + "bullets", + "rockets", + "riflerounds", + "health", + "healthboost", + "greenarmour", + "yellowarmour", + "quaddamage", + "teleport", + "teledest", + "mapmodel", + "monster", + "trigger", + "jumppad", + "?", + "?", + "?", + "?", + "?", +}; + +void +renderents() // show sparkly thingies for map entities in edit mode +{ + closeent[0] = 0; + if (!editmode) + return; + loopv(ents) + { + entity &e = ents[i]; + if (e.type == NOTUSED) + continue; + vec v = {(float)e.x, (float)e.y, (float)e.z}; + particle_splash(2, 2, 40, v); + }; + int e = closestent(); + if (e >= 0) { + entity &c = ents[e]; + sprintf_s(closeent)("closest entity = %s (%d, %d, %d, %d), " + "selection = (%d, %d)", + entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, + getvar("selxs"), getvar("selys")); + }; +}; + +void +loadsky(char *basename) +{ + static string lastsky = ""; + if (strcmp(lastsky, basename) == 0) + return; + char *side[] = {"ft", "bk", "lf", "rt", "dn", "up"}; + int texnum = 14; + loopi(6) + { + sprintf_sd(name)("packages/%s_%s.jpg", basename, side[i]); + int xs, ys; + if (!installtex(texnum + i, path(name), xs, ys, true)) + conoutf("could not load sky textures"); + }; + strcpy_s(lastsky, basename); +}; + +COMMAND(loadsky, ARG_1STR); + +float cursordepth = 0.9f; +GLint viewport[4]; +GLdouble mm[16], pm[16]; +vec 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; + vec r = {(float)mm[0], (float)mm[4], (float)mm[8]}; + vec u = {(float)mm[1], (float)mm[5], (float)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); + + char *command = getcurcommand(); + char *player = playerincrosshair(); + if (command) + draw_textf("> %s_", 20, 1570, 2, command); + else if (closeent[0]) + draw_text(closeent, 20, 1570, 2); + else if (player) + draw_text(player, 20, 1570, 2); + + renderscores(); + if (!rendermenu()) { + glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); + glBindTexture(GL_TEXTURE_2D, 1); + 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/rendergl.cxx Index: src/rendergl.cxx ================================================================== --- src/rendergl.cxx +++ /dev/null @@ -1,446 +0,0 @@ -// rendergl.cpp: core opengl rendering stuff - -#include "cube.h" - -#ifdef DARWIN -#define GL_COMBINE_EXT GL_COMBINE_ARB -#define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB -#define GL_SOURCE0_RBG_EXT GL_SOURCE0_RGB_ARB -#define GL_SOURCE1_RBG_EXT GL_SOURCE1_RGB_ARB -#define GL_RGB_SCALE_EXT GL_RGB_SCALE_ARB -#endif - -extern int curvert; - -bool hasoverbright = false; - -void purgetextures(); - -GLUquadricObj *qsphere = NULL; -int glmaxtexsize = 256; - -void -gl_init(int w, int h) -{ - // #define fogvalues 0.5f, 0.6f, 0.7f, 1.0f - - glViewport(0, 0, w, h); - glClearDepth(1.0); - glDepthFunc(GL_LESS); - glEnable(GL_DEPTH_TEST); - glShadeModel(GL_SMOOTH); - - glEnable(GL_FOG); - glFogi(GL_FOG_MODE, GL_LINEAR); - glFogf(GL_FOG_DENSITY, 0.25); - glHint(GL_FOG_HINT, GL_NICEST); - - glEnable(GL_LINE_SMOOTH); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glEnable(GL_POLYGON_OFFSET_LINE); - glPolygonOffset(-3.0, -3.0); - - glCullFace(GL_FRONT); - glEnable(GL_CULL_FACE); - - char *exts = (char *)glGetString(GL_EXTENSIONS); - - if (strstr(exts, "GL_EXT_texture_env_combine")) - hasoverbright = true; - else - conoutf("WARNING: cannot use overbright lighting, using old " - "lighting model!"); - - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glmaxtexsize); - - purgetextures(); - - if (!(qsphere = gluNewQuadric())) - fatal("glu sphere"); - gluQuadricDrawStyle(qsphere, GLU_FILL); - gluQuadricOrientation(qsphere, GLU_INSIDE); - gluQuadricTexture(qsphere, GL_TRUE); - glNewList(1, GL_COMPILE); - gluSphere(qsphere, 1, 12, 6); - glEndList(); -}; - -void -cleangl() -{ - if (qsphere) - gluDeleteQuadric(qsphere); -}; - -bool -installtex(int tnum, char *texname, int &xs, int &ys, bool clamp) -{ - SDL_Surface *s = IMG_Load(texname); - if (!s) { - conoutf("couldn't load texture %s", texname); - return false; - }; - if (s->format->BitsPerPixel != 24) { - conoutf("texture must be 24bpp: %s", texname); - return false; - }; - // loopi(s->w*s->h*3) { uchar *p = (uchar *)s->pixels+i; *p = 255-*p; }; - glBindTexture(GL_TEXTURE_2D, tnum); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, - clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, - clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, - GL_LINEAR_MIPMAP_LINEAR); // NEAREST); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - xs = s->w; - ys = s->h; - while (xs > glmaxtexsize || ys > glmaxtexsize) { - xs /= 2; - ys /= 2; - }; - void *scaledimg = s->pixels; - if (xs != s->w) { - conoutf("warning: quality loss: scaling %s", - texname); // for voodoo cards under linux - scaledimg = alloc(xs * ys * 3); - gluScaleImage(GL_RGB, s->w, s->h, GL_UNSIGNED_BYTE, s->pixels, - xs, ys, GL_UNSIGNED_BYTE, scaledimg); - }; - if (gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, xs, ys, GL_RGB, - GL_UNSIGNED_BYTE, scaledimg)) - fatal("could not build mipmaps"); - if (xs != s->w) - free(scaledimg); - SDL_FreeSurface(s); - return true; -}; - -// management of texture slots -// each texture slot can have multople texture frames, of which currently only -// the first is used additional frames can be used for various shaders - -const int MAXTEX = 1000; -int texx[MAXTEX]; // ( loaded texture ) -> ( name, size ) -int texy[MAXTEX]; -string texname[MAXTEX]; -int curtex = 0; -const int FIRSTTEX = 1000; // opengl id = loaded id + FIRSTTEX -// std 1+, sky 14+, mdls 20+ - -const int MAXFRAMES = 2; // increase to allow more complex shader defs -int mapping[256][MAXFRAMES]; // ( cube texture, frame ) -> ( opengl id, name ) -string mapname[256][MAXFRAMES]; - -void -purgetextures() -{ - loopi(256) loop(j, MAXFRAMES) mapping[i][j] = 0; -}; - -int curtexnum = 0; - -void -texturereset() -{ - curtexnum = 0; -}; - -void -texture(char *aframe, char *name) -{ - int num = curtexnum++, frame = atoi(aframe); - if (num < 0 || num >= 256 || frame < 0 || frame >= MAXFRAMES) - return; - mapping[num][frame] = 1; - char *n = mapname[num][frame]; - strcpy_s(n, name); - path(n); -}; - -COMMAND(texturereset, ARG_NONE); -COMMAND(texture, ARG_2STR); - -int -lookuptexture(int tex, int &xs, int &ys) -{ - int frame = 0; // other frames? - int tid = mapping[tex][frame]; - - if (tid >= FIRSTTEX) { - xs = texx[tid - FIRSTTEX]; - ys = texy[tid - FIRSTTEX]; - return tid; - }; - - xs = ys = 16; - if (!tid) - return 1; // crosshair :) - - loopi(curtex) // lazily happens once per "texture" command, basically - { - if (strcmp(mapname[tex][frame], texname[i]) == 0) { - mapping[tex][frame] = tid = i + FIRSTTEX; - xs = texx[i]; - ys = texy[i]; - return tid; - }; - }; - - if (curtex == MAXTEX) - fatal("loaded too many textures"); - - int tnum = curtex + FIRSTTEX; - strcpy_s(texname[curtex], mapname[tex][frame]); - - sprintf_sd(name)("packages%c%s", PATHDIV, texname[curtex]); - - if (installtex(tnum, name, xs, ys)) { - mapping[tex][frame] = tnum; - texx[curtex] = xs; - texy[curtex] = ys; - curtex++; - return tnum; - } else { - return mapping[tex][frame] = FIRSTTEX; // temp fix - }; -}; - -void -setupworld() -{ - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - setarraypointers(); - - if (hasoverbright) { - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); - glTexEnvi( - GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); - }; -}; - -int skyoglid; - -struct strip { - int tex, start, num; -}; -vector strips; - -void -renderstripssky() -{ - glBindTexture(GL_TEXTURE_2D, skyoglid); - loopv(strips) if (strips[i].tex == skyoglid) - glDrawArrays(GL_TRIANGLE_STRIP, strips[i].start, strips[i].num); -}; - -void -renderstrips() -{ - int lasttex = -1; - loopv(strips) if (strips[i].tex != skyoglid) - { - if (strips[i].tex != lasttex) { - glBindTexture(GL_TEXTURE_2D, strips[i].tex); - lasttex = strips[i].tex; - }; - glDrawArrays(GL_TRIANGLE_STRIP, strips[i].start, strips[i].num); - }; -}; - -void -overbright(float amount) -{ - if (hasoverbright) - glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, amount); -}; - -void -addstrip(int tex, int start, int n) -{ - strip &s = strips.add(); - s.tex = tex; - s.start = start; - s.num = n; -}; - -VARFP(gamma, 30, 100, 300, { - float f = gamma / 100.0f; - if (SDL_SetGamma(f, f, f) == -1) { - conoutf( - "Could not set gamma (card/driver doesn't support it?)"); - conoutf("sdl: %s", SDL_GetError()); - }; -}); - -void -transplayer() -{ - glLoadIdentity(); - - glRotated(player1->roll, 0.0, 0.0, 1.0); - glRotated(player1->pitch, -1.0, 0.0, 0.0); - glRotated(player1->yaw, 0.0, 1.0, 0.0); - - glTranslated(-player1->o.x, - (player1->state == CS_DEAD ? player1->eyeheight - 0.2f : 0) - - player1->o.z, - -player1->o.y); -}; - -VARP(fov, 10, 105, 120); - -int xtraverts; - -VAR(fog, 64, 180, 1024); -VAR(fogcolour, 0, 0x8099B3, 0xFFFFFF); - -VARP(hudgun, 0, 1, 1); - -char *hudgunnames[] = {"hudguns/fist", "hudguns/shotg", "hudguns/chaing", - "hudguns/rocket", "hudguns/rifle"}; - -void -drawhudmodel(int start, int end, float speed, int base) -{ - rendermodel(hudgunnames[player1->gunselect], start, end, 0, 1.0f, - player1->o.x, player1->o.z, player1->o.y, player1->yaw + 90, - player1->pitch, false, 1.0f, speed, 0, base); -}; - -void -drawhudgun(float fovy, float aspect, int farplane) -{ - if (!hudgun /*|| !player1->gunselect*/) - return; - - glEnable(GL_CULL_FACE); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective(fovy, aspect, 0.3f, farplane); - glMatrixMode(GL_MODELVIEW); - - // glClear(GL_DEPTH_BUFFER_BIT); - int rtime = reloadtime(player1->gunselect); - if (player1->lastaction && - player1->lastattackgun == player1->gunselect && - lastmillis - player1->lastaction < rtime) { - drawhudmodel(7, 18, rtime / 18.0f, player1->lastaction); - } else { - drawhudmodel(6, 1, 100, 0); - }; - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective(fovy, aspect, 0.15f, farplane); - glMatrixMode(GL_MODELVIEW); - - glDisable(GL_CULL_FACE); -}; - -void -gl_drawframe(int w, int h, float curfps) -{ - float hf = hdr.waterlevel - 0.3f; - float fovy = (float)fov * h / w; - float aspect = w / (float)h; - bool underwater = player1->o.z < hf; - - glFogi(GL_FOG_START, (fog + 64) / 8); - glFogi(GL_FOG_END, fog); - float fogc[4] = {(fogcolour >> 16) / 256.0f, - ((fogcolour >> 8) & 255) / 256.0f, (fogcolour & 255) / 256.0f, - 1.0f}; - glFogfv(GL_FOG_COLOR, fogc); - glClearColor(fogc[0], fogc[1], fogc[2], 1.0f); - - if (underwater) { - fovy += (float)sin(lastmillis / 1000.0) * 2.0f; - aspect += (float)sin(lastmillis / 1000.0 + PI) * 0.1f; - glFogi(GL_FOG_START, 0); - glFogi(GL_FOG_END, (fog + 96) / 8); - }; - - glClear((player1->outsidemap ? GL_COLOR_BUFFER_BIT : 0) | - GL_DEPTH_BUFFER_BIT); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - int farplane = fog * 5 / 2; - gluPerspective(fovy, aspect, 0.15f, farplane); - glMatrixMode(GL_MODELVIEW); - - transplayer(); - - glEnable(GL_TEXTURE_2D); - - int xs, ys; - skyoglid = lookuptexture(DEFAULT_SKY, xs, ys); - - resetcubes(); - - curvert = 0; - strips.setsize(0); - - render_world(player1->o.x, player1->o.y, player1->o.z, - (int)player1->yaw, (int)player1->pitch, (float)fov, w, h); - finishstrips(); - - setupworld(); - - renderstripssky(); - - glLoadIdentity(); - glRotated(player1->pitch, -1.0, 0.0, 0.0); - glRotated(player1->yaw, 0.0, 1.0, 0.0); - glRotated(90.0, 1.0, 0.0, 0.0); - glColor3f(1.0f, 1.0f, 1.0f); - glDisable(GL_FOG); - glDepthFunc(GL_GREATER); - draw_envbox(14, fog * 4 / 3); - glDepthFunc(GL_LESS); - glEnable(GL_FOG); - - transplayer(); - - overbright(2); - - renderstrips(); - - xtraverts = 0; - - renderclients(); - monsterrender(); - - renderentities(); - - renderspheres(curtime); - renderents(); - - glDisable(GL_CULL_FACE); - - drawhudgun(fovy, aspect, farplane); - - overbright(1); - int nquads = renderwater(hf); - - overbright(2); - render_particles(curtime); - overbright(1); - - glDisable(GL_FOG); - - glDisable(GL_TEXTURE_2D); - - gl_drawhud(w, h, (int)curfps, nquads, curvert, underwater); - - glEnable(GL_CULL_FACE); - glEnable(GL_FOG); -}; ADDED src/rendergl.mm Index: src/rendergl.mm ================================================================== --- /dev/null +++ src/rendergl.mm @@ -0,0 +1,446 @@ +// rendergl.cpp: core opengl rendering stuff + +#include "cube.h" + +#ifdef DARWIN +#define GL_COMBINE_EXT GL_COMBINE_ARB +#define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB +#define GL_SOURCE0_RBG_EXT GL_SOURCE0_RGB_ARB +#define GL_SOURCE1_RBG_EXT GL_SOURCE1_RGB_ARB +#define GL_RGB_SCALE_EXT GL_RGB_SCALE_ARB +#endif + +extern int curvert; + +bool hasoverbright = false; + +void purgetextures(); + +GLUquadricObj *qsphere = NULL; +int glmaxtexsize = 256; + +void +gl_init(int w, int h) +{ + // #define fogvalues 0.5f, 0.6f, 0.7f, 1.0f + + glViewport(0, 0, w, h); + glClearDepth(1.0); + glDepthFunc(GL_LESS); + glEnable(GL_DEPTH_TEST); + glShadeModel(GL_SMOOTH); + + glEnable(GL_FOG); + glFogi(GL_FOG_MODE, GL_LINEAR); + glFogf(GL_FOG_DENSITY, 0.25); + glHint(GL_FOG_HINT, GL_NICEST); + + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glEnable(GL_POLYGON_OFFSET_LINE); + glPolygonOffset(-3.0, -3.0); + + glCullFace(GL_FRONT); + glEnable(GL_CULL_FACE); + + char *exts = (char *)glGetString(GL_EXTENSIONS); + + if (strstr(exts, "GL_EXT_texture_env_combine")) + hasoverbright = true; + else + conoutf("WARNING: cannot use overbright lighting, using old " + "lighting model!"); + + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glmaxtexsize); + + purgetextures(); + + if (!(qsphere = gluNewQuadric())) + fatal("glu sphere"); + gluQuadricDrawStyle(qsphere, GLU_FILL); + gluQuadricOrientation(qsphere, GLU_INSIDE); + gluQuadricTexture(qsphere, GL_TRUE); + glNewList(1, GL_COMPILE); + gluSphere(qsphere, 1, 12, 6); + glEndList(); +}; + +void +cleangl() +{ + if (qsphere) + gluDeleteQuadric(qsphere); +}; + +bool +installtex(int tnum, char *texname, int &xs, int &ys, bool clamp) +{ + SDL_Surface *s = IMG_Load(texname); + if (!s) { + conoutf("couldn't load texture %s", texname); + return false; + }; + if (s->format->BitsPerPixel != 24) { + conoutf("texture must be 24bpp: %s", texname); + return false; + }; + // loopi(s->w*s->h*3) { uchar *p = (uchar *)s->pixels+i; *p = 255-*p; }; + glBindTexture(GL_TEXTURE_2D, tnum); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, + clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, + clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR_MIPMAP_LINEAR); // NEAREST); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + xs = s->w; + ys = s->h; + while (xs > glmaxtexsize || ys > glmaxtexsize) { + xs /= 2; + ys /= 2; + }; + void *scaledimg = s->pixels; + if (xs != s->w) { + conoutf("warning: quality loss: scaling %s", + texname); // for voodoo cards under linux + scaledimg = alloc(xs * ys * 3); + gluScaleImage(GL_RGB, s->w, s->h, GL_UNSIGNED_BYTE, s->pixels, + xs, ys, GL_UNSIGNED_BYTE, scaledimg); + }; + if (gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, xs, ys, GL_RGB, + GL_UNSIGNED_BYTE, scaledimg)) + fatal("could not build mipmaps"); + if (xs != s->w) + free(scaledimg); + SDL_FreeSurface(s); + return true; +}; + +// management of texture slots +// each texture slot can have multople texture frames, of which currently only +// the first is used additional frames can be used for various shaders + +const int MAXTEX = 1000; +int texx[MAXTEX]; // ( loaded texture ) -> ( name, size ) +int texy[MAXTEX]; +string texname[MAXTEX]; +int curtex = 0; +const int FIRSTTEX = 1000; // opengl id = loaded id + FIRSTTEX +// std 1+, sky 14+, mdls 20+ + +const int MAXFRAMES = 2; // increase to allow more complex shader defs +int mapping[256][MAXFRAMES]; // ( cube texture, frame ) -> ( opengl id, name ) +string mapname[256][MAXFRAMES]; + +void +purgetextures() +{ + loopi(256) loop(j, MAXFRAMES) mapping[i][j] = 0; +}; + +int curtexnum = 0; + +void +texturereset() +{ + curtexnum = 0; +}; + +void +texture(char *aframe, char *name) +{ + int num = curtexnum++, frame = atoi(aframe); + if (num < 0 || num >= 256 || frame < 0 || frame >= MAXFRAMES) + return; + mapping[num][frame] = 1; + char *n = mapname[num][frame]; + strcpy_s(n, name); + path(n); +}; + +COMMAND(texturereset, ARG_NONE); +COMMAND(texture, ARG_2STR); + +int +lookuptexture(int tex, int &xs, int &ys) +{ + int frame = 0; // other frames? + int tid = mapping[tex][frame]; + + if (tid >= FIRSTTEX) { + xs = texx[tid - FIRSTTEX]; + ys = texy[tid - FIRSTTEX]; + return tid; + }; + + xs = ys = 16; + if (!tid) + return 1; // crosshair :) + + loopi(curtex) // lazily happens once per "texture" command, basically + { + if (strcmp(mapname[tex][frame], texname[i]) == 0) { + mapping[tex][frame] = tid = i + FIRSTTEX; + xs = texx[i]; + ys = texy[i]; + return tid; + }; + }; + + if (curtex == MAXTEX) + fatal("loaded too many textures"); + + int tnum = curtex + FIRSTTEX; + strcpy_s(texname[curtex], mapname[tex][frame]); + + sprintf_sd(name)("packages%c%s", PATHDIV, texname[curtex]); + + if (installtex(tnum, name, xs, ys)) { + mapping[tex][frame] = tnum; + texx[curtex] = xs; + texy[curtex] = ys; + curtex++; + return tnum; + } else { + return mapping[tex][frame] = FIRSTTEX; // temp fix + }; +}; + +void +setupworld() +{ + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + setarraypointers(); + + if (hasoverbright) { + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); + glTexEnvi( + GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); + }; +}; + +int skyoglid; + +struct strip { + int tex, start, num; +}; +vector strips; + +void +renderstripssky() +{ + glBindTexture(GL_TEXTURE_2D, skyoglid); + loopv(strips) if (strips[i].tex == skyoglid) + glDrawArrays(GL_TRIANGLE_STRIP, strips[i].start, strips[i].num); +}; + +void +renderstrips() +{ + int lasttex = -1; + loopv(strips) if (strips[i].tex != skyoglid) + { + if (strips[i].tex != lasttex) { + glBindTexture(GL_TEXTURE_2D, strips[i].tex); + lasttex = strips[i].tex; + }; + glDrawArrays(GL_TRIANGLE_STRIP, strips[i].start, strips[i].num); + }; +}; + +void +overbright(float amount) +{ + if (hasoverbright) + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, amount); +}; + +void +addstrip(int tex, int start, int n) +{ + strip &s = strips.add(); + s.tex = tex; + s.start = start; + s.num = n; +}; + +VARFP(gamma, 30, 100, 300, { + float f = gamma / 100.0f; + if (SDL_SetGamma(f, f, f) == -1) { + conoutf( + "Could not set gamma (card/driver doesn't support it?)"); + conoutf("sdl: %s", SDL_GetError()); + }; +}); + +void +transplayer() +{ + glLoadIdentity(); + + glRotated(player1->roll, 0.0, 0.0, 1.0); + glRotated(player1->pitch, -1.0, 0.0, 0.0); + glRotated(player1->yaw, 0.0, 1.0, 0.0); + + glTranslated(-player1->o.x, + (player1->state == CS_DEAD ? player1->eyeheight - 0.2f : 0) - + player1->o.z, + -player1->o.y); +}; + +VARP(fov, 10, 105, 120); + +int xtraverts; + +VAR(fog, 64, 180, 1024); +VAR(fogcolour, 0, 0x8099B3, 0xFFFFFF); + +VARP(hudgun, 0, 1, 1); + +char *hudgunnames[] = {"hudguns/fist", "hudguns/shotg", "hudguns/chaing", + "hudguns/rocket", "hudguns/rifle"}; + +void +drawhudmodel(int start, int end, float speed, int base) +{ + rendermodel(hudgunnames[player1->gunselect], start, end, 0, 1.0f, + player1->o.x, player1->o.z, player1->o.y, player1->yaw + 90, + player1->pitch, false, 1.0f, speed, 0, base); +}; + +void +drawhudgun(float fovy, float aspect, int farplane) +{ + if (!hudgun /*|| !player1->gunselect*/) + return; + + glEnable(GL_CULL_FACE); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(fovy, aspect, 0.3f, farplane); + glMatrixMode(GL_MODELVIEW); + + // glClear(GL_DEPTH_BUFFER_BIT); + int rtime = reloadtime(player1->gunselect); + if (player1->lastaction && + player1->lastattackgun == player1->gunselect && + lastmillis - player1->lastaction < rtime) { + drawhudmodel(7, 18, rtime / 18.0f, player1->lastaction); + } else { + drawhudmodel(6, 1, 100, 0); + }; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(fovy, aspect, 0.15f, farplane); + glMatrixMode(GL_MODELVIEW); + + glDisable(GL_CULL_FACE); +}; + +void +gl_drawframe(int w, int h, float curfps) +{ + float hf = hdr.waterlevel - 0.3f; + float fovy = (float)fov * h / w; + float aspect = w / (float)h; + bool underwater = player1->o.z < hf; + + glFogi(GL_FOG_START, (fog + 64) / 8); + glFogi(GL_FOG_END, fog); + float fogc[4] = {(fogcolour >> 16) / 256.0f, + ((fogcolour >> 8) & 255) / 256.0f, (fogcolour & 255) / 256.0f, + 1.0f}; + glFogfv(GL_FOG_COLOR, fogc); + glClearColor(fogc[0], fogc[1], fogc[2], 1.0f); + + if (underwater) { + fovy += (float)sin(lastmillis / 1000.0) * 2.0f; + aspect += (float)sin(lastmillis / 1000.0 + PI) * 0.1f; + glFogi(GL_FOG_START, 0); + glFogi(GL_FOG_END, (fog + 96) / 8); + }; + + glClear((player1->outsidemap ? GL_COLOR_BUFFER_BIT : 0) | + GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + int farplane = fog * 5 / 2; + gluPerspective(fovy, aspect, 0.15f, farplane); + glMatrixMode(GL_MODELVIEW); + + transplayer(); + + glEnable(GL_TEXTURE_2D); + + int xs, ys; + skyoglid = lookuptexture(DEFAULT_SKY, xs, ys); + + resetcubes(); + + curvert = 0; + strips.setsize(0); + + render_world(player1->o.x, player1->o.y, player1->o.z, + (int)player1->yaw, (int)player1->pitch, (float)fov, w, h); + finishstrips(); + + setupworld(); + + renderstripssky(); + + glLoadIdentity(); + glRotated(player1->pitch, -1.0, 0.0, 0.0); + glRotated(player1->yaw, 0.0, 1.0, 0.0); + glRotated(90.0, 1.0, 0.0, 0.0); + glColor3f(1.0f, 1.0f, 1.0f); + glDisable(GL_FOG); + glDepthFunc(GL_GREATER); + draw_envbox(14, fog * 4 / 3); + glDepthFunc(GL_LESS); + glEnable(GL_FOG); + + transplayer(); + + overbright(2); + + renderstrips(); + + xtraverts = 0; + + renderclients(); + monsterrender(); + + renderentities(); + + renderspheres(curtime); + renderents(); + + glDisable(GL_CULL_FACE); + + drawhudgun(fovy, aspect, farplane); + + overbright(1); + int nquads = renderwater(hf); + + overbright(2); + render_particles(curtime); + overbright(1); + + glDisable(GL_FOG); + + glDisable(GL_TEXTURE_2D); + + gl_drawhud(w, h, (int)curfps, nquads, curvert, underwater); + + glEnable(GL_CULL_FACE); + glEnable(GL_FOG); +}; DELETED src/rendermd2.cxx Index: src/rendermd2.cxx ================================================================== --- src/rendermd2.cxx +++ /dev/null @@ -1,303 +0,0 @@ -// rendermd2.cpp: loader code adapted from a nehe tutorial - -#include "cube.h" - -struct md2_header { - int magic; - int version; - int skinWidth, skinHeight; - int frameSize; - int numSkins, numVertices, numTexcoords; - int numTriangles, numGlCommands, numFrames; - int offsetSkins, offsetTexcoords, offsetTriangles; - int offsetFrames, offsetGlCommands, offsetEnd; -}; - -struct md2_vertex { - uchar vertex[3], lightNormalIndex; -}; - -struct md2_frame { - float scale[3]; - float translate[3]; - char name[16]; - md2_vertex vertices[1]; -}; - -struct md2 { - int numGlCommands; - int *glCommands; - int numTriangles; - int frameSize; - int numFrames; - int numVerts; - char *frames; - vec **mverts; - int displaylist; - int displaylistverts; - - mapmodelinfo mmi; - char *loadname; - int mdlnum; - bool loaded; - - bool load(char *filename); - void render(vec &light, int numFrame, int range, float x, float y, - float z, float yaw, float pitch, float scale, float speed, int snap, - int basetime); - void scale(int frame, float scale, int sn); - - md2() - : numGlCommands(0), frameSize(0), numFrames(0), displaylist(0), - loaded(false) {}; - - ~md2() - { - if (glCommands) - delete[] glCommands; - if (frames) - delete[] frames; - } -}; - -bool -md2::load(char *filename) -{ - FILE *file; - md2_header header; - - if ((file = fopen(filename, "rb")) == NULL) - return false; - - fread(&header, sizeof(md2_header), 1, file); - endianswap(&header, sizeof(int), sizeof(md2_header) / sizeof(int)); - - if (header.magic != 844121161 || header.version != 8) - return false; - - frames = new char[header.frameSize * header.numFrames]; - if (frames == NULL) - return false; - - fseek(file, header.offsetFrames, SEEK_SET); - fread(frames, header.frameSize * header.numFrames, 1, file); - - for (int i = 0; i < header.numFrames; ++i) { - endianswap(frames + i * header.frameSize, sizeof(float), 6); - } - - glCommands = new int[header.numGlCommands]; - if (glCommands == NULL) - return false; - - fseek(file, header.offsetGlCommands, SEEK_SET); - fread(glCommands, header.numGlCommands * sizeof(int), 1, file); - - endianswap(glCommands, sizeof(int), header.numGlCommands); - - numFrames = header.numFrames; - numGlCommands = header.numGlCommands; - frameSize = header.frameSize; - numTriangles = header.numTriangles; - numVerts = header.numVertices; - - fclose(file); - - mverts = new vec *[numFrames]; - loopj(numFrames) mverts[j] = NULL; - - return true; -}; - -float -snap(int sn, float f) -{ - return sn ? (float)(((int)(f + sn * 0.5f)) & (~(sn - 1))) : f; -}; - -void -md2::scale(int frame, float scale, int sn) -{ - mverts[frame] = new vec[numVerts]; - md2_frame *cf = (md2_frame *)((char *)frames + frameSize * frame); - float sc = 16.0f / scale; - loop(vi, numVerts) - { - uchar *cv = (uchar *)&cf->vertices[vi].vertex; - vec *v = &(mverts[frame])[vi]; - v->x = (snap(sn, cv[0] * cf->scale[0]) + cf->translate[0]) / sc; - v->y = - -(snap(sn, cv[1] * cf->scale[1]) + cf->translate[1]) / sc; - v->z = (snap(sn, cv[2] * cf->scale[2]) + cf->translate[2]) / sc; - }; -}; - -void -md2::render(vec &light, int frame, int range, float x, float y, float z, - float yaw, float pitch, float sc, float speed, int snap, int basetime) -{ - loopi(range) if (!mverts[frame + i]) scale(frame + i, sc, snap); - - glPushMatrix(); - glTranslatef(x, y, z); - glRotatef(yaw + 180, 0, -1, 0); - glRotatef(pitch, 0, 0, 1); - - glColor3fv((float *)&light); - - if (displaylist && frame == 0 && range == 1) { - glCallList(displaylist); - xtraverts += displaylistverts; - } else { - if (frame == 0 && range == 1) { - static int displaylistn = 10; - glNewList(displaylist = displaylistn++, GL_COMPILE); - displaylistverts = xtraverts; - }; - - int time = lastmillis - basetime; - int fr1 = (int)(time / speed); - float frac1 = (time - fr1 * speed) / speed; - float frac2 = 1 - frac1; - fr1 = fr1 % range + frame; - int fr2 = fr1 + 1; - if (fr2 >= frame + range) - fr2 = frame; - vec *verts1 = mverts[fr1]; - vec *verts2 = mverts[fr2]; - - for (int *command = glCommands; (*command) != 0;) { - int numVertex = *command++; - if (numVertex > 0) { - glBegin(GL_TRIANGLE_STRIP); - } else { - glBegin(GL_TRIANGLE_FAN); - numVertex = -numVertex; - }; - - loopi(numVertex) - { - float tu = *((float *)command++); - float tv = *((float *)command++); - glTexCoord2f(tu, tv); - int vn = *command++; - vec &v1 = verts1[vn]; - vec &v2 = verts2[vn]; -#define ip(c) v1.c *frac2 + v2.c *frac1 - glVertex3f(ip(x), ip(z), ip(y)); - }; - - xtraverts += numVertex; - - glEnd(); - }; - - if (displaylist) { - glEndList(); - displaylistverts = xtraverts - displaylistverts; - }; - }; - - glPopMatrix(); -} - -hashtable *mdllookup = NULL; -vector mapmodels; -const int FIRSTMDL = 20; - -void -delayedload(md2 *m) -{ - if (!m->loaded) { - sprintf_sd(name1)("packages/models/%s/tris.md2", m->loadname); - if (!m->load(path(name1))) - fatal("loadmodel: ", name1); - sprintf_sd(name2)("packages/models/%s/skin.jpg", m->loadname); - int xs, ys; - installtex(FIRSTMDL + m->mdlnum, path(name2), xs, ys); - m->loaded = true; - }; -}; - -int modelnum = 0; - -md2 * -loadmodel(char *name) -{ - if (!mdllookup) - mdllookup = new hashtable; - md2 **mm = mdllookup->access(name); - if (mm) - return *mm; - md2 *m = new md2(); - m->mdlnum = modelnum++; - mapmodelinfo mmi = {2, 2, 0, 0, ""}; - m->mmi = mmi; - m->loadname = newstring(name); - mdllookup->access(m->loadname, &m); - return m; -}; - -void -mapmodel(char *rad, char *h, char *zoff, char *snap, char *name) -{ - md2 *m = loadmodel(name); - mapmodelinfo mmi = { - atoi(rad), atoi(h), atoi(zoff), atoi(snap), m->loadname}; - m->mmi = mmi; - mapmodels.add(m); -}; - -void -mapmodelreset() -{ - mapmodels.setsize(0); -}; - -mapmodelinfo & -getmminfo(int i) -{ - return i < mapmodels.length() ? mapmodels[i]->mmi : *(mapmodelinfo *)0; -}; - -COMMAND(mapmodel, ARG_5STR); -COMMAND(mapmodelreset, ARG_NONE); - -void -rendermodel(char *mdl, int frame, int range, int tex, float rad, float x, - float y, float z, float yaw, float pitch, bool teammate, float scale, - float speed, int snap, int basetime) -{ - md2 *m = loadmodel(mdl); - - if (isoccluded(player1->o.x, player1->o.y, x - rad, z - rad, rad * 2)) - return; - - delayedload(m); - - int xs, ys; - glBindTexture(GL_TEXTURE_2D, - tex ? lookuptexture(tex, xs, ys) : FIRSTMDL + m->mdlnum); - - int ix = (int)x; - int iy = (int)z; - vec light = {1.0f, 1.0f, 1.0f}; - - if (!OUTBORD(ix, iy)) { - sqr *s = S(ix, iy); - float ll = 256.0f; // 0.96f; - float of = 0.0f; // 0.1f; - light.x = s->r / ll + of; - light.y = s->g / ll + of; - light.z = s->b / ll + of; - }; - - if (teammate) { - light.x *= 0.6f; - light.y *= 0.7f; - light.z *= 1.2f; - }; - - m->render(light, frame, range, x, y, z, yaw, pitch, scale, speed, snap, - basetime); -}; ADDED src/rendermd2.mm Index: src/rendermd2.mm ================================================================== --- /dev/null +++ src/rendermd2.mm @@ -0,0 +1,303 @@ +// rendermd2.cpp: loader code adapted from a nehe tutorial + +#include "cube.h" + +struct md2_header { + int magic; + int version; + int skinWidth, skinHeight; + int frameSize; + int numSkins, numVertices, numTexcoords; + int numTriangles, numGlCommands, numFrames; + int offsetSkins, offsetTexcoords, offsetTriangles; + int offsetFrames, offsetGlCommands, offsetEnd; +}; + +struct md2_vertex { + uchar vertex[3], lightNormalIndex; +}; + +struct md2_frame { + float scale[3]; + float translate[3]; + char name[16]; + md2_vertex vertices[1]; +}; + +struct md2 { + int numGlCommands; + int *glCommands; + int numTriangles; + int frameSize; + int numFrames; + int numVerts; + char *frames; + vec **mverts; + int displaylist; + int displaylistverts; + + mapmodelinfo mmi; + char *loadname; + int mdlnum; + bool loaded; + + bool load(char *filename); + void render(vec &light, int numFrame, int range, float x, float y, + float z, float yaw, float pitch, float scale, float speed, int snap, + int basetime); + void scale(int frame, float scale, int sn); + + md2() + : numGlCommands(0), frameSize(0), numFrames(0), displaylist(0), + loaded(false) {}; + + ~md2() + { + if (glCommands) + delete[] glCommands; + if (frames) + delete[] frames; + } +}; + +bool +md2::load(char *filename) +{ + FILE *file; + md2_header header; + + if ((file = fopen(filename, "rb")) == NULL) + return false; + + fread(&header, sizeof(md2_header), 1, file); + endianswap(&header, sizeof(int), sizeof(md2_header) / sizeof(int)); + + if (header.magic != 844121161 || header.version != 8) + return false; + + frames = new char[header.frameSize * header.numFrames]; + if (frames == NULL) + return false; + + fseek(file, header.offsetFrames, SEEK_SET); + fread(frames, header.frameSize * header.numFrames, 1, file); + + for (int i = 0; i < header.numFrames; ++i) { + endianswap(frames + i * header.frameSize, sizeof(float), 6); + } + + glCommands = new int[header.numGlCommands]; + if (glCommands == NULL) + return false; + + fseek(file, header.offsetGlCommands, SEEK_SET); + fread(glCommands, header.numGlCommands * sizeof(int), 1, file); + + endianswap(glCommands, sizeof(int), header.numGlCommands); + + numFrames = header.numFrames; + numGlCommands = header.numGlCommands; + frameSize = header.frameSize; + numTriangles = header.numTriangles; + numVerts = header.numVertices; + + fclose(file); + + mverts = new vec *[numFrames]; + loopj(numFrames) mverts[j] = NULL; + + return true; +}; + +float +snap(int sn, float f) +{ + return sn ? (float)(((int)(f + sn * 0.5f)) & (~(sn - 1))) : f; +}; + +void +md2::scale(int frame, float scale, int sn) +{ + mverts[frame] = new vec[numVerts]; + md2_frame *cf = (md2_frame *)((char *)frames + frameSize * frame); + float sc = 16.0f / scale; + loop(vi, numVerts) + { + uchar *cv = (uchar *)&cf->vertices[vi].vertex; + vec *v = &(mverts[frame])[vi]; + v->x = (snap(sn, cv[0] * cf->scale[0]) + cf->translate[0]) / sc; + v->y = + -(snap(sn, cv[1] * cf->scale[1]) + cf->translate[1]) / sc; + v->z = (snap(sn, cv[2] * cf->scale[2]) + cf->translate[2]) / sc; + }; +}; + +void +md2::render(vec &light, int frame, int range, float x, float y, float z, + float yaw, float pitch, float sc, float speed, int snap, int basetime) +{ + loopi(range) if (!mverts[frame + i]) scale(frame + i, sc, snap); + + glPushMatrix(); + glTranslatef(x, y, z); + glRotatef(yaw + 180, 0, -1, 0); + glRotatef(pitch, 0, 0, 1); + + glColor3fv((float *)&light); + + if (displaylist && frame == 0 && range == 1) { + glCallList(displaylist); + xtraverts += displaylistverts; + } else { + if (frame == 0 && range == 1) { + static int displaylistn = 10; + glNewList(displaylist = displaylistn++, GL_COMPILE); + displaylistverts = xtraverts; + }; + + int time = lastmillis - basetime; + int fr1 = (int)(time / speed); + float frac1 = (time - fr1 * speed) / speed; + float frac2 = 1 - frac1; + fr1 = fr1 % range + frame; + int fr2 = fr1 + 1; + if (fr2 >= frame + range) + fr2 = frame; + vec *verts1 = mverts[fr1]; + vec *verts2 = mverts[fr2]; + + for (int *command = glCommands; (*command) != 0;) { + int numVertex = *command++; + if (numVertex > 0) { + glBegin(GL_TRIANGLE_STRIP); + } else { + glBegin(GL_TRIANGLE_FAN); + numVertex = -numVertex; + }; + + loopi(numVertex) + { + float tu = *((float *)command++); + float tv = *((float *)command++); + glTexCoord2f(tu, tv); + int vn = *command++; + vec &v1 = verts1[vn]; + vec &v2 = verts2[vn]; +#define ip(c) v1.c *frac2 + v2.c *frac1 + glVertex3f(ip(x), ip(z), ip(y)); + }; + + xtraverts += numVertex; + + glEnd(); + }; + + if (displaylist) { + glEndList(); + displaylistverts = xtraverts - displaylistverts; + }; + }; + + glPopMatrix(); +} + +hashtable *mdllookup = NULL; +vector mapmodels; +const int FIRSTMDL = 20; + +void +delayedload(md2 *m) +{ + if (!m->loaded) { + sprintf_sd(name1)("packages/models/%s/tris.md2", m->loadname); + if (!m->load(path(name1))) + fatal("loadmodel: ", name1); + sprintf_sd(name2)("packages/models/%s/skin.jpg", m->loadname); + int xs, ys; + installtex(FIRSTMDL + m->mdlnum, path(name2), xs, ys); + m->loaded = true; + }; +}; + +int modelnum = 0; + +md2 * +loadmodel(char *name) +{ + if (!mdllookup) + mdllookup = new hashtable; + md2 **mm = mdllookup->access(name); + if (mm) + return *mm; + md2 *m = new md2(); + m->mdlnum = modelnum++; + mapmodelinfo mmi = {2, 2, 0, 0, ""}; + m->mmi = mmi; + m->loadname = newstring(name); + mdllookup->access(m->loadname, &m); + return m; +}; + +void +mapmodel(char *rad, char *h, char *zoff, char *snap, char *name) +{ + md2 *m = loadmodel(name); + mapmodelinfo mmi = { + atoi(rad), atoi(h), atoi(zoff), atoi(snap), m->loadname}; + m->mmi = mmi; + mapmodels.add(m); +}; + +void +mapmodelreset() +{ + mapmodels.setsize(0); +}; + +mapmodelinfo & +getmminfo(int i) +{ + return i < mapmodels.length() ? mapmodels[i]->mmi : *(mapmodelinfo *)0; +}; + +COMMAND(mapmodel, ARG_5STR); +COMMAND(mapmodelreset, ARG_NONE); + +void +rendermodel(char *mdl, int frame, int range, int tex, float rad, float x, + float y, float z, float yaw, float pitch, bool teammate, float scale, + float speed, int snap, int basetime) +{ + md2 *m = loadmodel(mdl); + + if (isoccluded(player1->o.x, player1->o.y, x - rad, z - rad, rad * 2)) + return; + + delayedload(m); + + int xs, ys; + glBindTexture(GL_TEXTURE_2D, + tex ? lookuptexture(tex, xs, ys) : FIRSTMDL + m->mdlnum); + + int ix = (int)x; + int iy = (int)z; + vec light = {1.0f, 1.0f, 1.0f}; + + if (!OUTBORD(ix, iy)) { + sqr *s = S(ix, iy); + float ll = 256.0f; // 0.96f; + float of = 0.0f; // 0.1f; + light.x = s->r / ll + of; + light.y = s->g / ll + of; + light.z = s->b / ll + of; + }; + + if (teammate) { + light.x *= 0.6f; + light.y *= 0.7f; + light.z *= 1.2f; + }; + + m->render(light, frame, range, x, y, z, yaw, pitch, scale, speed, snap, + basetime); +}; DELETED src/renderparticles.cxx Index: src/renderparticles.cxx ================================================================== --- src/renderparticles.cxx +++ /dev/null @@ -1,164 +0,0 @@ -// renderparticles.cpp - -#include "cube.h" - -const int MAXPARTICLES = 10500; -const int NUMPARTCUTOFF = 20; -struct particle { - vec o, d; - int fade, type; - int millis; - particle *next; -}; -particle particles[MAXPARTICLES], *parlist = NULL, *parempty = NULL; -bool parinit = false; - -VARP(maxparticles, 100, 2000, MAXPARTICLES - 500); - -void -newparticle(vec &o, vec &d, int fade, int type) -{ - if (!parinit) { - loopi(MAXPARTICLES) - { - particles[i].next = parempty; - parempty = &particles[i]; - }; - parinit = true; - }; - if (parempty) { - particle *p = parempty; - parempty = p->next; - p->o = o; - p->d = d; - p->fade = fade; - p->type = type; - p->millis = lastmillis; - p->next = parlist; - parlist = p; - }; -}; - -VAR(demotracking, 0, 0, 1); -VARP(particlesize, 20, 100, 500); - -vec right, up; - -void -setorient(vec &r, vec &u) -{ - right = r; - up = u; -}; - -void -render_particles(int time) -{ - if (demoplayback && demotracking) { - vec nom = {0, 0, 0}; - newparticle(player1->o, nom, 100000000, 8); - }; - - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); - glDisable(GL_FOG); - - struct parttype { - float r, g, b; - int gr, tex; - float sz; - } parttypes[] = { - {0.7f, 0.6f, 0.3f, 2, 3, 0.06f}, // yellow: sparks - {0.5f, 0.5f, 0.5f, 20, 7, 0.15f}, // grey: small smoke - {0.2f, 0.2f, 1.0f, 20, 3, 0.08f}, // blue: edit mode entities - {1.0f, 0.1f, 0.1f, 1, 7, 0.06f}, // red: blood spats - {1.0f, 0.8f, 0.8f, 20, 6, 1.2f}, // yellow: fireball1 - {0.5f, 0.5f, 0.5f, 20, 7, 0.6f}, // grey: big smoke - {1.0f, 1.0f, 1.0f, 20, 8, 1.2f}, // blue: fireball2 - {1.0f, 1.0f, 1.0f, 20, 9, 1.2f}, // green: fireball3 - {1.0f, 0.1f, 0.1f, 0, 7, 0.2f}, // red: demotrack - }; - - int numrender = 0; - - for (particle *p, **pp = &parlist; p = *pp;) { - parttype *pt = &parttypes[p->type]; - - glBindTexture(GL_TEXTURE_2D, pt->tex); - glBegin(GL_QUADS); - - glColor3d(pt->r, pt->g, pt->b); - float sz = pt->sz * particlesize / 100.0f; - // perf varray? - glTexCoord2f(0.0, 1.0); - glVertex3d(p->o.x + (-right.x + up.x) * sz, - p->o.z + (-right.y + up.y) * sz, - p->o.y + (-right.z + up.z) * sz); - glTexCoord2f(1.0, 1.0); - glVertex3d(p->o.x + (right.x + up.x) * sz, - p->o.z + (right.y + up.y) * sz, - p->o.y + (right.z + up.z) * sz); - glTexCoord2f(1.0, 0.0); - glVertex3d(p->o.x + (right.x - up.x) * sz, - p->o.z + (right.y - up.y) * sz, - p->o.y + (right.z - up.z) * sz); - glTexCoord2f(0.0, 0.0); - glVertex3d(p->o.x + (-right.x - up.x) * sz, - p->o.z + (-right.y - up.y) * sz, - p->o.y + (-right.z - up.z) * sz); - glEnd(); - xtraverts += 4; - - if (numrender++ > maxparticles || (p->fade -= time) < 0) { - *pp = p->next; - p->next = parempty; - parempty = p; - } else { - if (pt->gr) - p->o.z -= ((lastmillis - p->millis) / 3.0f) * - curtime / (pt->gr * 10000); - vec a = p->d; - vmul(a, time); - vdiv(a, 20000.0f); - vadd(p->o, a); - pp = &p->next; - }; - }; - - glEnable(GL_FOG); - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); -}; - -void -particle_splash(int type, int num, int fade, vec &p) -{ - loopi(num) - { - const int radius = type == 5 ? 50 : 150; - int x, y, z; - do { - x = rnd(radius * 2) - radius; - y = rnd(radius * 2) - radius; - z = rnd(radius * 2) - radius; - } while (x * x + y * y + z * z > radius * radius); - vec d = {(float)x, (float)y, (float)z}; - newparticle(p, d, rnd(fade * 3), type); - }; -}; - -void -particle_trail(int type, int fade, vec &s, vec &e) -{ - vdist(d, v, s, e); - vdiv(v, d * 2 + 0.1f); - vec p = s; - loopi((int)d * 2) - { - vadd(p, v); - vec d = { - float(rnd(11) - 5), float(rnd(11) - 5), float(rnd(11) - 5)}; - newparticle(p, d, rnd(fade) + fade, type); - }; -}; ADDED src/renderparticles.mm Index: src/renderparticles.mm ================================================================== --- /dev/null +++ src/renderparticles.mm @@ -0,0 +1,164 @@ +// renderparticles.cpp + +#include "cube.h" + +const int MAXPARTICLES = 10500; +const int NUMPARTCUTOFF = 20; +struct particle { + vec o, d; + int fade, type; + int millis; + particle *next; +}; +particle particles[MAXPARTICLES], *parlist = NULL, *parempty = NULL; +bool parinit = false; + +VARP(maxparticles, 100, 2000, MAXPARTICLES - 500); + +void +newparticle(vec &o, vec &d, int fade, int type) +{ + if (!parinit) { + loopi(MAXPARTICLES) + { + particles[i].next = parempty; + parempty = &particles[i]; + }; + parinit = true; + }; + if (parempty) { + particle *p = parempty; + parempty = p->next; + p->o = o; + p->d = d; + p->fade = fade; + p->type = type; + p->millis = lastmillis; + p->next = parlist; + parlist = p; + }; +}; + +VAR(demotracking, 0, 0, 1); +VARP(particlesize, 20, 100, 500); + +vec right, up; + +void +setorient(vec &r, vec &u) +{ + right = r; + up = u; +}; + +void +render_particles(int time) +{ + if (demoplayback && demotracking) { + vec nom = {0, 0, 0}; + newparticle(player1->o, nom, 100000000, 8); + }; + + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); + glDisable(GL_FOG); + + struct parttype { + float r, g, b; + int gr, tex; + float sz; + } parttypes[] = { + {0.7f, 0.6f, 0.3f, 2, 3, 0.06f}, // yellow: sparks + {0.5f, 0.5f, 0.5f, 20, 7, 0.15f}, // grey: small smoke + {0.2f, 0.2f, 1.0f, 20, 3, 0.08f}, // blue: edit mode entities + {1.0f, 0.1f, 0.1f, 1, 7, 0.06f}, // red: blood spats + {1.0f, 0.8f, 0.8f, 20, 6, 1.2f}, // yellow: fireball1 + {0.5f, 0.5f, 0.5f, 20, 7, 0.6f}, // grey: big smoke + {1.0f, 1.0f, 1.0f, 20, 8, 1.2f}, // blue: fireball2 + {1.0f, 1.0f, 1.0f, 20, 9, 1.2f}, // green: fireball3 + {1.0f, 0.1f, 0.1f, 0, 7, 0.2f}, // red: demotrack + }; + + int numrender = 0; + + for (particle *p, **pp = &parlist; p = *pp;) { + parttype *pt = &parttypes[p->type]; + + glBindTexture(GL_TEXTURE_2D, pt->tex); + glBegin(GL_QUADS); + + glColor3d(pt->r, pt->g, pt->b); + float sz = pt->sz * particlesize / 100.0f; + // perf varray? + glTexCoord2f(0.0, 1.0); + glVertex3d(p->o.x + (-right.x + up.x) * sz, + p->o.z + (-right.y + up.y) * sz, + p->o.y + (-right.z + up.z) * sz); + glTexCoord2f(1.0, 1.0); + glVertex3d(p->o.x + (right.x + up.x) * sz, + p->o.z + (right.y + up.y) * sz, + p->o.y + (right.z + up.z) * sz); + glTexCoord2f(1.0, 0.0); + glVertex3d(p->o.x + (right.x - up.x) * sz, + p->o.z + (right.y - up.y) * sz, + p->o.y + (right.z - up.z) * sz); + glTexCoord2f(0.0, 0.0); + glVertex3d(p->o.x + (-right.x - up.x) * sz, + p->o.z + (-right.y - up.y) * sz, + p->o.y + (-right.z - up.z) * sz); + glEnd(); + xtraverts += 4; + + if (numrender++ > maxparticles || (p->fade -= time) < 0) { + *pp = p->next; + p->next = parempty; + parempty = p; + } else { + if (pt->gr) + p->o.z -= ((lastmillis - p->millis) / 3.0f) * + curtime / (pt->gr * 10000); + vec a = p->d; + vmul(a, time); + vdiv(a, 20000.0f); + vadd(p->o, a); + pp = &p->next; + }; + }; + + glEnable(GL_FOG); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); +}; + +void +particle_splash(int type, int num, int fade, vec &p) +{ + loopi(num) + { + const int radius = type == 5 ? 50 : 150; + int x, y, z; + do { + x = rnd(radius * 2) - radius; + y = rnd(radius * 2) - radius; + z = rnd(radius * 2) - radius; + } while (x * x + y * y + z * z > radius * radius); + vec d = {(float)x, (float)y, (float)z}; + newparticle(p, d, rnd(fade * 3), type); + }; +}; + +void +particle_trail(int type, int fade, vec &s, vec &e) +{ + vdist(d, v, s, e); + vdiv(v, d * 2 + 0.1f); + vec p = s; + loopi((int)d * 2) + { + vadd(p, v); + vec d = { + float(rnd(11) - 5), float(rnd(11) - 5), float(rnd(11) - 5)}; + newparticle(p, d, rnd(fade) + fade, type); + }; +}; DELETED src/rendertext.cxx Index: src/rendertext.cxx ================================================================== --- src/rendertext.cxx +++ /dev/null @@ -1,235 +0,0 @@ -// rendertext.cpp: based on Don's gl_text.cpp - -#include "cube.h" - -short char_coords[96][4] = { - {0, 0, 25, 64}, //! - {25, 0, 54, 64}, //" - {54, 0, 107, 64}, // # - {107, 0, 148, 64}, //$ - {148, 0, 217, 64}, //% - {217, 0, 263, 64}, //& - {263, 0, 280, 64}, //' - {280, 0, 309, 64}, //( - {309, 0, 338, 64}, //) - {338, 0, 379, 64}, //* - {379, 0, 432, 64}, //+ - {432, 0, 455, 64}, //, - {455, 0, 484, 64}, //- - {0, 64, 21, 128}, //. - {23, 64, 52, 128}, /// - {52, 64, 93, 128}, // 0 - {93, 64, 133, 128}, // 1 - {133, 64, 174, 128}, // 2 - {174, 64, 215, 128}, // 3 - {215, 64, 256, 128}, // 4 - {256, 64, 296, 128}, // 5 - {296, 64, 337, 128}, // 6 - {337, 64, 378, 128}, // 7 - {378, 64, 419, 128}, // 8 - {419, 64, 459, 128}, // 9 - {459, 64, 488, 128}, //: - {0, 128, 29, 192}, //; - {29, 128, 81, 192}, //< - {81, 128, 134, 192}, //= - {134, 128, 186, 192}, //> - {186, 128, 221, 192}, //? - {221, 128, 285, 192}, //@ - {285, 128, 329, 192}, // A - {329, 128, 373, 192}, // B - {373, 128, 418, 192}, // C - {418, 128, 467, 192}, // D - {0, 192, 40, 256}, // E - {40, 192, 77, 256}, // F - {77, 192, 127, 256}, // G - {127, 192, 175, 256}, // H - {175, 192, 202, 256}, // I - {202, 192, 231, 256}, // J - {231, 192, 275, 256}, // K - {275, 192, 311, 256}, // L - {311, 192, 365, 256}, // M - {365, 192, 413, 256}, // N - {413, 192, 463, 256}, // O - {1, 256, 38, 320}, // P - {38, 256, 89, 320}, // Q - {89, 256, 133, 320}, // R - {133, 256, 176, 320}, // S - {177, 256, 216, 320}, // T - {217, 256, 263, 320}, // U - {263, 256, 307, 320}, // V - {307, 256, 370, 320}, // W - {370, 256, 414, 320}, // X - {414, 256, 453, 320}, // Y - {453, 256, 497, 320}, // Z - {0, 320, 29, 384}, //[ - {29, 320, 58, 384}, //"\" - {59, 320, 87, 384}, //] - {87, 320, 139, 384}, //^ - {139, 320, 180, 384}, //_ - {180, 320, 221, 384}, //` - {221, 320, 259, 384}, // a - {259, 320, 299, 384}, // b - {299, 320, 332, 384}, // c - {332, 320, 372, 384}, // d - {372, 320, 411, 384}, // e - {411, 320, 433, 384}, // f - {435, 320, 473, 384}, // g - {0, 384, 40, 448}, // h - {40, 384, 56, 448}, // i - {58, 384, 80, 448}, // j - {80, 384, 118, 448}, // k - {118, 384, 135, 448}, // l - {135, 384, 197, 448}, // m - {197, 384, 238, 448}, // n - {238, 384, 277, 448}, // o - {277, 384, 317, 448}, // p - {317, 384, 356, 448}, // q - {357, 384, 384, 448}, // r - {385, 384, 417, 448}, // s - {417, 384, 442, 448}, // t - {443, 384, 483, 448}, // u - {0, 448, 38, 512}, // v - {38, 448, 90, 512}, // w - {90, 448, 128, 512}, // x - {128, 448, 166, 512}, // y - {166, 448, 200, 512}, // z - {200, 448, 241, 512}, //{ - {241, 448, 270, 512}, //| - {270, 448, 310, 512}, //} - {310, 448, 363, 512}, //~ -}; - -int -text_width(char *str) -{ - int x = 0; - for (int i = 0; str[i] != 0; i++) { - int c = str[i]; - if (c == '\t') { - x = (x + PIXELTAB) / PIXELTAB * PIXELTAB; - continue; - }; - if (c == '\f') - continue; - if (c == ' ') { - x += FONTH / 2; - continue; - }; - c -= 33; - if (c < 0 || c >= 95) - continue; - int in_width = char_coords[c][2] - char_coords[c][0]; - x += in_width + 1; - } - return x; -} - -void -draw_textf(char *fstr, int left, int top, int gl_num, ...) -{ - sprintf_sdlv(str, gl_num, fstr); - draw_text(str, left, top, gl_num); -}; - -void -draw_text(char *str, int left, int top, int gl_num) -{ - glBlendFunc(GL_ONE, GL_ONE); - glBindTexture(GL_TEXTURE_2D, gl_num); - glColor3ub(255, 255, 255); - - int x = left; - int y = top; - - int i; - float in_left, in_top, in_right, in_bottom; - int in_width, in_height; - - for (i = 0; str[i] != 0; i++) { - int c = str[i]; - if (c == '\t') { - x = (x - left + PIXELTAB) / PIXELTAB * PIXELTAB + left; - continue; - }; - if (c == '\f') { - glColor3ub(64, 255, 128); - continue; - }; - if (c == ' ') { - x += FONTH / 2; - continue; - }; - c -= 33; - if (c < 0 || c >= 95) - continue; - - in_left = ((float)char_coords[c][0]) / 512.0f; - in_top = ((float)char_coords[c][1] + 2) / 512.0f; - in_right = ((float)char_coords[c][2]) / 512.0f; - in_bottom = ((float)char_coords[c][3] - 2) / 512.0f; - - in_width = char_coords[c][2] - char_coords[c][0]; - in_height = char_coords[c][3] - char_coords[c][1]; - - glBegin(GL_QUADS); - glTexCoord2f(in_left, in_top); - glVertex2i(x, y); - glTexCoord2f(in_right, in_top); - glVertex2i(x + in_width, y); - glTexCoord2f(in_right, in_bottom); - glVertex2i(x + in_width, y + in_height); - glTexCoord2f(in_left, in_bottom); - glVertex2i(x, y + in_height); - glEnd(); - - xtraverts += 4; - x += in_width + 1; - } -} - -// also Don's code, so goes in here too :) - -void -draw_envbox_aux(float s0, float t0, int x0, int y0, int z0, float s1, float t1, - int x1, int y1, int z1, float s2, float t2, int x2, int y2, int z2, - float s3, float t3, int x3, int y3, int z3, int texture) -{ - glBindTexture(GL_TEXTURE_2D, texture); - glBegin(GL_QUADS); - glTexCoord2f(s3, t3); - glVertex3d(x3, y3, z3); - glTexCoord2f(s2, t2); - glVertex3d(x2, y2, z2); - glTexCoord2f(s1, t1); - glVertex3d(x1, y1, z1); - glTexCoord2f(s0, t0); - glVertex3d(x0, y0, z0); - glEnd(); - xtraverts += 4; -} - -void -draw_envbox(int t, int w) -{ - glDepthMask(GL_FALSE); - - draw_envbox_aux(1.0f, 1.0f, -w, -w, w, 0.0f, 1.0f, w, -w, w, 0.0f, 0.0f, - w, -w, -w, 1.0f, 0.0f, -w, -w, -w, t); - - draw_envbox_aux(1.0f, 1.0f, +w, w, w, 0.0f, 1.0f, -w, w, w, 0.0f, 0.0f, - -w, w, -w, 1.0f, 0.0f, +w, w, -w, t + 1); - - draw_envbox_aux(0.0f, 0.0f, -w, -w, -w, 1.0f, 0.0f, -w, w, -w, 1.0f, - 1.0f, -w, w, w, 0.0f, 1.0f, -w, -w, w, t + 2); - - draw_envbox_aux(1.0f, 1.0f, +w, -w, w, 0.0f, 1.0f, +w, w, w, 0.0f, 0.0f, - +w, w, -w, 1.0f, 0.0f, +w, -w, -w, t + 3); - - draw_envbox_aux(0.0f, 1.0f, -w, w, w, 0.0f, 0.0f, +w, w, w, 1.0f, 0.0f, - +w, -w, w, 1.0f, 1.0f, -w, -w, w, t + 4); - - draw_envbox_aux(0.0f, 1.0f, +w, w, -w, 0.0f, 0.0f, -w, w, -w, 1.0f, - 0.0f, -w, -w, -w, 1.0f, 1.0f, +w, -w, -w, t + 5); - - glDepthMask(GL_TRUE); -} ADDED src/rendertext.mm Index: src/rendertext.mm ================================================================== --- /dev/null +++ src/rendertext.mm @@ -0,0 +1,235 @@ +// rendertext.cpp: based on Don's gl_text.cpp + +#include "cube.h" + +short char_coords[96][4] = { + {0, 0, 25, 64}, //! + {25, 0, 54, 64}, //" + {54, 0, 107, 64}, // # + {107, 0, 148, 64}, //$ + {148, 0, 217, 64}, //% + {217, 0, 263, 64}, //& + {263, 0, 280, 64}, //' + {280, 0, 309, 64}, //( + {309, 0, 338, 64}, //) + {338, 0, 379, 64}, //* + {379, 0, 432, 64}, //+ + {432, 0, 455, 64}, //, + {455, 0, 484, 64}, //- + {0, 64, 21, 128}, //. + {23, 64, 52, 128}, /// + {52, 64, 93, 128}, // 0 + {93, 64, 133, 128}, // 1 + {133, 64, 174, 128}, // 2 + {174, 64, 215, 128}, // 3 + {215, 64, 256, 128}, // 4 + {256, 64, 296, 128}, // 5 + {296, 64, 337, 128}, // 6 + {337, 64, 378, 128}, // 7 + {378, 64, 419, 128}, // 8 + {419, 64, 459, 128}, // 9 + {459, 64, 488, 128}, //: + {0, 128, 29, 192}, //; + {29, 128, 81, 192}, //< + {81, 128, 134, 192}, //= + {134, 128, 186, 192}, //> + {186, 128, 221, 192}, //? + {221, 128, 285, 192}, //@ + {285, 128, 329, 192}, // A + {329, 128, 373, 192}, // B + {373, 128, 418, 192}, // C + {418, 128, 467, 192}, // D + {0, 192, 40, 256}, // E + {40, 192, 77, 256}, // F + {77, 192, 127, 256}, // G + {127, 192, 175, 256}, // H + {175, 192, 202, 256}, // I + {202, 192, 231, 256}, // J + {231, 192, 275, 256}, // K + {275, 192, 311, 256}, // L + {311, 192, 365, 256}, // M + {365, 192, 413, 256}, // N + {413, 192, 463, 256}, // O + {1, 256, 38, 320}, // P + {38, 256, 89, 320}, // Q + {89, 256, 133, 320}, // R + {133, 256, 176, 320}, // S + {177, 256, 216, 320}, // T + {217, 256, 263, 320}, // U + {263, 256, 307, 320}, // V + {307, 256, 370, 320}, // W + {370, 256, 414, 320}, // X + {414, 256, 453, 320}, // Y + {453, 256, 497, 320}, // Z + {0, 320, 29, 384}, //[ + {29, 320, 58, 384}, //"\" + {59, 320, 87, 384}, //] + {87, 320, 139, 384}, //^ + {139, 320, 180, 384}, //_ + {180, 320, 221, 384}, //` + {221, 320, 259, 384}, // a + {259, 320, 299, 384}, // b + {299, 320, 332, 384}, // c + {332, 320, 372, 384}, // d + {372, 320, 411, 384}, // e + {411, 320, 433, 384}, // f + {435, 320, 473, 384}, // g + {0, 384, 40, 448}, // h + {40, 384, 56, 448}, // i + {58, 384, 80, 448}, // j + {80, 384, 118, 448}, // k + {118, 384, 135, 448}, // l + {135, 384, 197, 448}, // m + {197, 384, 238, 448}, // n + {238, 384, 277, 448}, // o + {277, 384, 317, 448}, // p + {317, 384, 356, 448}, // q + {357, 384, 384, 448}, // r + {385, 384, 417, 448}, // s + {417, 384, 442, 448}, // t + {443, 384, 483, 448}, // u + {0, 448, 38, 512}, // v + {38, 448, 90, 512}, // w + {90, 448, 128, 512}, // x + {128, 448, 166, 512}, // y + {166, 448, 200, 512}, // z + {200, 448, 241, 512}, //{ + {241, 448, 270, 512}, //| + {270, 448, 310, 512}, //} + {310, 448, 363, 512}, //~ +}; + +int +text_width(char *str) +{ + int x = 0; + for (int i = 0; str[i] != 0; i++) { + int c = str[i]; + if (c == '\t') { + x = (x + PIXELTAB) / PIXELTAB * PIXELTAB; + continue; + }; + if (c == '\f') + continue; + if (c == ' ') { + x += FONTH / 2; + continue; + }; + c -= 33; + if (c < 0 || c >= 95) + continue; + int in_width = char_coords[c][2] - char_coords[c][0]; + x += in_width + 1; + } + return x; +} + +void +draw_textf(char *fstr, int left, int top, int gl_num, ...) +{ + sprintf_sdlv(str, gl_num, fstr); + draw_text(str, left, top, gl_num); +}; + +void +draw_text(char *str, int left, int top, int gl_num) +{ + glBlendFunc(GL_ONE, GL_ONE); + glBindTexture(GL_TEXTURE_2D, gl_num); + glColor3ub(255, 255, 255); + + int x = left; + int y = top; + + int i; + float in_left, in_top, in_right, in_bottom; + int in_width, in_height; + + for (i = 0; str[i] != 0; i++) { + int c = str[i]; + if (c == '\t') { + x = (x - left + PIXELTAB) / PIXELTAB * PIXELTAB + left; + continue; + }; + if (c == '\f') { + glColor3ub(64, 255, 128); + continue; + }; + if (c == ' ') { + x += FONTH / 2; + continue; + }; + c -= 33; + if (c < 0 || c >= 95) + continue; + + in_left = ((float)char_coords[c][0]) / 512.0f; + in_top = ((float)char_coords[c][1] + 2) / 512.0f; + in_right = ((float)char_coords[c][2]) / 512.0f; + in_bottom = ((float)char_coords[c][3] - 2) / 512.0f; + + in_width = char_coords[c][2] - char_coords[c][0]; + in_height = char_coords[c][3] - char_coords[c][1]; + + glBegin(GL_QUADS); + glTexCoord2f(in_left, in_top); + glVertex2i(x, y); + glTexCoord2f(in_right, in_top); + glVertex2i(x + in_width, y); + glTexCoord2f(in_right, in_bottom); + glVertex2i(x + in_width, y + in_height); + glTexCoord2f(in_left, in_bottom); + glVertex2i(x, y + in_height); + glEnd(); + + xtraverts += 4; + x += in_width + 1; + } +} + +// also Don's code, so goes in here too :) + +void +draw_envbox_aux(float s0, float t0, int x0, int y0, int z0, float s1, float t1, + int x1, int y1, int z1, float s2, float t2, int x2, int y2, int z2, + float s3, float t3, int x3, int y3, int z3, int texture) +{ + glBindTexture(GL_TEXTURE_2D, texture); + glBegin(GL_QUADS); + glTexCoord2f(s3, t3); + glVertex3d(x3, y3, z3); + glTexCoord2f(s2, t2); + glVertex3d(x2, y2, z2); + glTexCoord2f(s1, t1); + glVertex3d(x1, y1, z1); + glTexCoord2f(s0, t0); + glVertex3d(x0, y0, z0); + glEnd(); + xtraverts += 4; +} + +void +draw_envbox(int t, int w) +{ + glDepthMask(GL_FALSE); + + draw_envbox_aux(1.0f, 1.0f, -w, -w, w, 0.0f, 1.0f, w, -w, w, 0.0f, 0.0f, + w, -w, -w, 1.0f, 0.0f, -w, -w, -w, t); + + draw_envbox_aux(1.0f, 1.0f, +w, w, w, 0.0f, 1.0f, -w, w, w, 0.0f, 0.0f, + -w, w, -w, 1.0f, 0.0f, +w, w, -w, t + 1); + + draw_envbox_aux(0.0f, 0.0f, -w, -w, -w, 1.0f, 0.0f, -w, w, -w, 1.0f, + 1.0f, -w, w, w, 0.0f, 1.0f, -w, -w, w, t + 2); + + draw_envbox_aux(1.0f, 1.0f, +w, -w, w, 0.0f, 1.0f, +w, w, w, 0.0f, 0.0f, + +w, w, -w, 1.0f, 0.0f, +w, -w, -w, t + 3); + + draw_envbox_aux(0.0f, 1.0f, -w, w, w, 0.0f, 0.0f, +w, w, w, 1.0f, 0.0f, + +w, -w, w, 1.0f, 1.0f, -w, -w, w, t + 4); + + draw_envbox_aux(0.0f, 1.0f, +w, w, -w, 0.0f, 0.0f, -w, w, -w, 1.0f, + 0.0f, -w, -w, -w, 1.0f, 1.0f, +w, -w, -w, t + 5); + + glDepthMask(GL_TRUE); +} DELETED src/rndmap.cxx Index: src/rndmap.cxx ================================================================== --- src/rndmap.cxx +++ /dev/null @@ -1,89 +0,0 @@ -// rndmap.cpp: perlin noise landscape generation and some experimental random -// map stuff, currently not used - -#include "cube.h" - -float -noise(int x, int y, int seed) -{ - int n = x + y * 57; - n = (n << 13) ^ n; - return 1.0f - - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / - 1073741824.0f; -} - -float -smoothednoise(int x, int y, int seed) -{ - float corners = - (noise(x - 1, y - 1, seed) + noise(x + 1, y - 1, seed) + - noise(x - 1, y + 1, seed) + noise(x + 1, y + 1, seed)) / - 16; - float sides = (noise(x - 1, y, seed) + noise(x + 1, y, seed) + - noise(x, y - 1, seed) + noise(x, y + 1, seed)) / - 8; - float center = noise(x, y, seed) / 4; - return corners + sides + center; -} - -float -interpolate(float a, float b, float x) -{ - float ft = x * 3.1415927f; - float f = (1.0f - (float)cos(ft)) * 0.5f; - return a * (1 - f) + b * f; -} - -float -interpolatednoise(float x, float y, int seed) -{ - int ix = (int)x; - float fx = x - ix; - int iy = (int)y; - float fy = y - iy; - float v1 = smoothednoise(ix, iy, seed); - float v2 = smoothednoise(ix + 1, iy, seed); - float v3 = smoothednoise(ix, iy + 1, seed); - float v4 = smoothednoise(ix + 1, iy + 1, seed); - float i1 = interpolate(v1, v2, fx); - float i2 = interpolate(v3, v4, fy); - return interpolate(i1, i2, fy); -} - -float -perlinnoise_2D(float x, float y, int seedstep, float pers) -{ - float total = 0; - int seed = 0; - for (int i = 0; i < 7; i++) { - float frequency = (float)(2 ^ i); - float amplitude = (float)pow(pers, i); - total += interpolatednoise(x * frequency, y * frequency, seed) * - amplitude; - seed += seedstep; - } - return total; -} - -void -perlinarea(block &b, int scale, int seed, int psize) -{ - srand(seed); - seed = rnd(10000); - if (!scale) - scale = 10; - for (int x = b.x; x <= b.x + b.xs; x++) - for (int y = b.y; y <= b.y + b.ys; y++) { - sqr *s = S(x, y); - if (!SOLID(s) && x != b.x + b.xs && y != b.y + b.ys) - s->type = FHF; - s->vdelta = - (int)(perlinnoise_2D(x / ((float)scale) + seed, - y / ((float)scale) + seed, 1000, 0.01f) * - 50 + - 25); - if (s->vdelta > 128) - s->vdelta = 0; - }; -}; ADDED src/rndmap.mm Index: src/rndmap.mm ================================================================== --- /dev/null +++ src/rndmap.mm @@ -0,0 +1,89 @@ +// rndmap.cpp: perlin noise landscape generation and some experimental random +// map stuff, currently not used + +#include "cube.h" + +float +noise(int x, int y, int seed) +{ + int n = x + y * 57; + n = (n << 13) ^ n; + return 1.0f - + ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / + 1073741824.0f; +} + +float +smoothednoise(int x, int y, int seed) +{ + float corners = + (noise(x - 1, y - 1, seed) + noise(x + 1, y - 1, seed) + + noise(x - 1, y + 1, seed) + noise(x + 1, y + 1, seed)) / + 16; + float sides = (noise(x - 1, y, seed) + noise(x + 1, y, seed) + + noise(x, y - 1, seed) + noise(x, y + 1, seed)) / + 8; + float center = noise(x, y, seed) / 4; + return corners + sides + center; +} + +float +interpolate(float a, float b, float x) +{ + float ft = x * 3.1415927f; + float f = (1.0f - (float)cos(ft)) * 0.5f; + return a * (1 - f) + b * f; +} + +float +interpolatednoise(float x, float y, int seed) +{ + int ix = (int)x; + float fx = x - ix; + int iy = (int)y; + float fy = y - iy; + float v1 = smoothednoise(ix, iy, seed); + float v2 = smoothednoise(ix + 1, iy, seed); + float v3 = smoothednoise(ix, iy + 1, seed); + float v4 = smoothednoise(ix + 1, iy + 1, seed); + float i1 = interpolate(v1, v2, fx); + float i2 = interpolate(v3, v4, fy); + return interpolate(i1, i2, fy); +} + +float +perlinnoise_2D(float x, float y, int seedstep, float pers) +{ + float total = 0; + int seed = 0; + for (int i = 0; i < 7; i++) { + float frequency = (float)(2 ^ i); + float amplitude = (float)pow(pers, i); + total += interpolatednoise(x * frequency, y * frequency, seed) * + amplitude; + seed += seedstep; + } + return total; +} + +void +perlinarea(block &b, int scale, int seed, int psize) +{ + srand(seed); + seed = rnd(10000); + if (!scale) + scale = 10; + for (int x = b.x; x <= b.x + b.xs; x++) + for (int y = b.y; y <= b.y + b.ys; y++) { + sqr *s = S(x, y); + if (!SOLID(s) && x != b.x + b.xs && y != b.y + b.ys) + s->type = FHF; + s->vdelta = + (int)(perlinnoise_2D(x / ((float)scale) + seed, + y / ((float)scale) + seed, 1000, 0.01f) * + 50 + + 25); + if (s->vdelta > 128) + s->vdelta = 0; + }; +}; DELETED src/savegamedemo.cxx Index: src/savegamedemo.cxx ================================================================== --- src/savegamedemo.cxx +++ /dev/null @@ -1,494 +0,0 @@ -// loading and saving of savegames & demos, dumps the spawn state of all -// mapents, the full state of all dynents (monsters + player) - -#include "cube.h" - -extern int islittleendian; - -gzFile f = NULL; -bool demorecording = false; -bool demoplayback = false; -bool demoloading = false; -dvector playerhistory; -int democlientnum = 0; - -void startdemo(); - -void -gzput(int i) -{ - gzputc(f, i); -}; -void -gzputi(int i) -{ - gzwrite(f, &i, sizeof(int)); -}; -void -gzputv(vec &v) -{ - gzwrite(f, &v, sizeof(vec)); -}; - -void -gzcheck(int a, int b) -{ - if (a != b) - fatal("savegame file corrupt (short)"); -}; -int -gzget() -{ - char c = gzgetc(f); - return c; -}; -int -gzgeti() -{ - int i; - gzcheck(gzread(f, &i, sizeof(int)), sizeof(int)); - return i; -}; -void -gzgetv(vec &v) -{ - gzcheck(gzread(f, &v, sizeof(vec)), sizeof(vec)); -}; - -void -stop() -{ - if (f) { - if (demorecording) - gzputi(-1); - gzclose(f); - }; - f = NULL; - demorecording = false; - demoplayback = false; - demoloading = false; - loopv(playerhistory) zapdynent(playerhistory[i]); - playerhistory.setsize(0); -}; - -void -stopifrecording() -{ - if (demorecording) - stop(); -}; - -void -savestate(char *fn) -{ - stop(); - f = gzopen(fn, "wb9"); - if (!f) { - conoutf("could not write %s", fn); - return; - }; - gzwrite(f, (void *)"CUBESAVE", 8); - gzputc(f, islittleendian); - gzputi(SAVEGAMEVERSION); - gzputi(sizeof(dynent)); - gzwrite(f, getclientmap(), _MAXDEFSTR); - gzputi(gamemode); - gzputi(ents.length()); - loopv(ents) gzputc(f, ents[i].spawned); - gzwrite(f, player1, sizeof(dynent)); - dvector &monsters = getmonsters(); - gzputi(monsters.length()); - loopv(monsters) gzwrite(f, monsters[i], sizeof(dynent)); - gzputi(players.length()); - loopv(players) - { - gzput(players[i] == NULL); - gzwrite(f, players[i], sizeof(dynent)); - }; -}; - -void -savegame(char *name) -{ - if (!m_classicsp) { - conoutf("can only save classic sp games"); - return; - }; - sprintf_sd(fn)("savegames/%s.csgz", name); - savestate(fn); - stop(); - conoutf("wrote %s", fn); -}; - -void -loadstate(char *fn) -{ - stop(); - if (multiplayer()) - return; - f = gzopen(fn, "rb9"); - if (!f) { - conoutf("could not open %s", fn); - return; - }; - - string buf; - gzread(f, buf, 8); - if (strncmp(buf, "CUBESAVE", 8)) - goto out; - if (gzgetc(f) != islittleendian) - goto out; // not supporting save->load accross incompatible - // architectures simpifies things a LOT - if (gzgeti() != SAVEGAMEVERSION || gzgeti() != sizeof(dynent)) - goto out; - string mapname; - gzread(f, mapname, _MAXDEFSTR); - nextmode = gzgeti(); - changemap(mapname); // continue below once map has been loaded and - // client & server have updated - return; -out: - conoutf("aborting: savegame/demo from a different version of cube or " - "cpu architecture"); - stop(); -}; - -void -loadgame(char *name) -{ - sprintf_sd(fn)("savegames/%s.csgz", name); - loadstate(fn); -}; - -void -loadgameout() -{ - stop(); - conoutf("loadgame incomplete: savegame from a different version of " - "this map"); -}; - -void -loadgamerest() -{ - if (demoplayback || !f) - return; - - if (gzgeti() != ents.length()) - return loadgameout(); - loopv(ents) - { - ents[i].spawned = gzgetc(f) != 0; - if (ents[i].type == CARROT && !ents[i].spawned) - trigger(ents[i].attr1, ents[i].attr2, true); - }; - restoreserverstate(ents); - - gzread(f, player1, sizeof(dynent)); - player1->lastaction = lastmillis; - - int nmonsters = gzgeti(); - dvector &monsters = getmonsters(); - if (nmonsters != monsters.length()) - return loadgameout(); - loopv(monsters) - { - gzread(f, monsters[i], sizeof(dynent)); - monsters[i]->enemy = - player1; // lazy, could save id of enemy instead - monsters[i]->lastaction = monsters[i]->trigger = - lastmillis + - 500; // also lazy, but no real noticable effect on game - if (monsters[i]->state == CS_DEAD) - monsters[i]->lastaction = 0; - }; - restoremonsterstate(); - - int nplayers = gzgeti(); - loopi(nplayers) if (!gzget()) - { - dynent *d = getclient(i); - assert(d); - gzread(f, d, sizeof(dynent)); - }; - - conoutf("savegame restored"); - if (demoloading) - startdemo(); - else - stop(); -}; - -// demo functions - -int starttime = 0; -int playbacktime = 0; -int ddamage, bdamage; -vec dorig; - -void -record(char *name) -{ - if (m_sp) { - conoutf("cannot record singleplayer games"); - return; - }; - int cn = getclientnum(); - if (cn < 0) - return; - sprintf_sd(fn)("demos/%s.cdgz", name); - savestate(fn); - gzputi(cn); - conoutf("started recording demo to %s", fn); - demorecording = true; - starttime = lastmillis; - ddamage = bdamage = 0; -}; - -void -demodamage(int damage, vec &o) -{ - ddamage = damage; - dorig = o; -}; -void -demoblend(int damage) -{ - bdamage = damage; -}; - -void -incomingdemodata(uchar *buf, int len, bool extras) -{ - if (!demorecording) - return; - gzputi(lastmillis - starttime); - gzputi(len); - gzwrite(f, buf, len); - gzput(extras); - if (extras) { - gzput(player1->gunselect); - gzput(player1->lastattackgun); - gzputi(player1->lastaction - starttime); - gzputi(player1->gunwait); - gzputi(player1->health); - gzputi(player1->armour); - gzput(player1->armourtype); - loopi(NUMGUNS) gzput(player1->ammo[i]); - gzput(player1->state); - gzputi(bdamage); - bdamage = 0; - gzputi(ddamage); - if (ddamage) { - gzputv(dorig); - ddamage = 0; - }; - // FIXME: add all other client state which is not send through - // the network - }; -}; - -void -demo(char *name) -{ - sprintf_sd(fn)("demos/%s.cdgz", name); - loadstate(fn); - demoloading = true; -}; - -void -stopreset() -{ - conoutf("demo stopped (%d msec elapsed)", lastmillis - starttime); - stop(); - loopv(players) zapdynent(players[i]); - disconnect(0, 0); -}; - -VAR(demoplaybackspeed, 10, 100, 1000); -int -scaletime(int t) -{ - return (int)(t * (100.0f / demoplaybackspeed)) + starttime; -}; - -void -readdemotime() -{ - if (gzeof(f) || (playbacktime = gzgeti()) == -1) { - stopreset(); - return; - }; - playbacktime = scaletime(playbacktime); -}; - -void -startdemo() -{ - democlientnum = gzgeti(); - demoplayback = true; - starttime = lastmillis; - conoutf("now playing demo"); - dynent *d = getclient(democlientnum); - assert(d); - *d = *player1; - readdemotime(); -}; - -VAR(demodelaymsec, 0, 120, 500); - -void -catmulrom( - vec &z, vec &a, vec &b, vec &c, float s, vec &dest) // spline interpolation -{ - vec t1 = b, t2 = c; - - vsub(t1, z); - vmul(t1, 0.5f) vsub(t2, a); - vmul(t2, 0.5f); - - float s2 = s * s; - float s3 = s * s2; - - dest = a; - vec t = b; - - vmul(dest, 2 * s3 - 3 * s2 + 1); - vmul(t, -2 * s3 + 3 * s2); - vadd(dest, t); - vmul(t1, s3 - 2 * s2 + s); - vadd(dest, t1); - vmul(t2, s3 - s2); - vadd(dest, t2); -}; - -void -fixwrap(dynent *a, dynent *b) -{ - while (b->yaw - a->yaw > 180) - a->yaw += 360; - while (b->yaw - a->yaw < -180) - a->yaw -= 360; -}; - -void -demoplaybackstep() -{ - while (demoplayback && lastmillis >= playbacktime) { - int len = gzgeti(); - if (len < 1 || len > MAXTRANS) { - conoutf( - "error: huge packet during demo play (%d)", len); - stopreset(); - return; - }; - uchar buf[MAXTRANS]; - gzread(f, buf, len); - localservertoclient(buf, len); // update game state - - dynent *target = players[democlientnum]; - assert(target); - - int extras; - if (extras = gzget()) // read additional client side state not - // present in normal network stream - { - target->gunselect = gzget(); - target->lastattackgun = gzget(); - target->lastaction = scaletime(gzgeti()); - target->gunwait = gzgeti(); - target->health = gzgeti(); - target->armour = gzgeti(); - target->armourtype = gzget(); - loopi(NUMGUNS) target->ammo[i] = gzget(); - target->state = gzget(); - target->lastmove = playbacktime; - if (bdamage = gzgeti()) - damageblend(bdamage); - if (ddamage = gzgeti()) { - gzgetv(dorig); - particle_splash(3, ddamage, 1000, dorig); - }; - // FIXME: set more client state here - }; - - // insert latest copy of player into history - if (extras && - (playerhistory.empty() || - playerhistory.last()->lastupdate != playbacktime)) { - dynent *d = newdynent(); - *d = *target; - d->lastupdate = playbacktime; - playerhistory.add(d); - if (playerhistory.length() > 20) { - zapdynent(playerhistory[0]); - playerhistory.remove(0); - }; - }; - - readdemotime(); - }; - - if (demoplayback) { - int itime = lastmillis - demodelaymsec; - loopvrev(playerhistory) if (playerhistory[i]->lastupdate < - itime) // find 2 positions in - // history that surround - // interpolation time point - { - dynent *a = playerhistory[i]; - dynent *b = a; - if (i + 1 < playerhistory.length()) - b = playerhistory[i + 1]; - *player1 = *b; - if (a != b) // interpolate pos & angles - { - dynent *c = b; - if (i + 2 < playerhistory.length()) - c = playerhistory[i + 2]; - dynent *z = a; - if (i - 1 >= 0) - z = playerhistory[i - 1]; - // if(a==z || b==c) printf("* %d\n", - // lastmillis); - float bf = - (itime - a->lastupdate) / - (float)(b->lastupdate - a->lastupdate); - fixwrap(a, player1); - fixwrap(c, player1); - fixwrap(z, player1); - vdist(dist, v, z->o, c->o); - if (dist < 16) // if teleport or spawn, dont't - // interpolate - { - catmulrom(z->o, a->o, b->o, c->o, bf, - player1->o); - catmulrom(*(vec *)&z->yaw, - *(vec *)&a->yaw, *(vec *)&b->yaw, - *(vec *)&c->yaw, bf, - *(vec *)&player1->yaw); - }; - fixplayer1range(); - }; - break; - }; - // if(player1->state!=CS_DEAD) showscores(false); - }; -}; - -void -stopn() -{ - if (demoplayback) - stopreset(); - else - stop(); - conoutf("demo stopped"); -}; - -COMMAND(record, ARG_1STR); -COMMAND(demo, ARG_1STR); -COMMANDN(stop, stopn, ARG_NONE); - -COMMAND(savegame, ARG_1STR); -COMMAND(loadgame, ARG_1STR); ADDED src/savegamedemo.mm Index: src/savegamedemo.mm ================================================================== --- /dev/null +++ src/savegamedemo.mm @@ -0,0 +1,494 @@ +// loading and saving of savegames & demos, dumps the spawn state of all +// mapents, the full state of all dynents (monsters + player) + +#include "cube.h" + +extern int islittleendian; + +gzFile f = NULL; +bool demorecording = false; +bool demoplayback = false; +bool demoloading = false; +dvector playerhistory; +int democlientnum = 0; + +void startdemo(); + +void +gzput(int i) +{ + gzputc(f, i); +}; +void +gzputi(int i) +{ + gzwrite(f, &i, sizeof(int)); +}; +void +gzputv(vec &v) +{ + gzwrite(f, &v, sizeof(vec)); +}; + +void +gzcheck(int a, int b) +{ + if (a != b) + fatal("savegame file corrupt (short)"); +}; +int +gzget() +{ + char c = gzgetc(f); + return c; +}; +int +gzgeti() +{ + int i; + gzcheck(gzread(f, &i, sizeof(int)), sizeof(int)); + return i; +}; +void +gzgetv(vec &v) +{ + gzcheck(gzread(f, &v, sizeof(vec)), sizeof(vec)); +}; + +void +stop() +{ + if (f) { + if (demorecording) + gzputi(-1); + gzclose(f); + }; + f = NULL; + demorecording = false; + demoplayback = false; + demoloading = false; + loopv(playerhistory) zapdynent(playerhistory[i]); + playerhistory.setsize(0); +}; + +void +stopifrecording() +{ + if (demorecording) + stop(); +}; + +void +savestate(char *fn) +{ + stop(); + f = gzopen(fn, "wb9"); + if (!f) { + conoutf("could not write %s", fn); + return; + }; + gzwrite(f, (void *)"CUBESAVE", 8); + gzputc(f, islittleendian); + gzputi(SAVEGAMEVERSION); + gzputi(sizeof(dynent)); + gzwrite(f, getclientmap(), _MAXDEFSTR); + gzputi(gamemode); + gzputi(ents.length()); + loopv(ents) gzputc(f, ents[i].spawned); + gzwrite(f, player1, sizeof(dynent)); + dvector &monsters = getmonsters(); + gzputi(monsters.length()); + loopv(monsters) gzwrite(f, monsters[i], sizeof(dynent)); + gzputi(players.length()); + loopv(players) + { + gzput(players[i] == NULL); + gzwrite(f, players[i], sizeof(dynent)); + }; +}; + +void +savegame(char *name) +{ + if (!m_classicsp) { + conoutf("can only save classic sp games"); + return; + }; + sprintf_sd(fn)("savegames/%s.csgz", name); + savestate(fn); + stop(); + conoutf("wrote %s", fn); +}; + +void +loadstate(char *fn) +{ + stop(); + if (multiplayer()) + return; + f = gzopen(fn, "rb9"); + if (!f) { + conoutf("could not open %s", fn); + return; + }; + + string buf; + gzread(f, buf, 8); + if (strncmp(buf, "CUBESAVE", 8)) + goto out; + if (gzgetc(f) != islittleendian) + goto out; // not supporting save->load accross incompatible + // architectures simpifies things a LOT + if (gzgeti() != SAVEGAMEVERSION || gzgeti() != sizeof(dynent)) + goto out; + string mapname; + gzread(f, mapname, _MAXDEFSTR); + nextmode = gzgeti(); + changemap(mapname); // continue below once map has been loaded and + // client & server have updated + return; +out: + conoutf("aborting: savegame/demo from a different version of cube or " + "cpu architecture"); + stop(); +}; + +void +loadgame(char *name) +{ + sprintf_sd(fn)("savegames/%s.csgz", name); + loadstate(fn); +}; + +void +loadgameout() +{ + stop(); + conoutf("loadgame incomplete: savegame from a different version of " + "this map"); +}; + +void +loadgamerest() +{ + if (demoplayback || !f) + return; + + if (gzgeti() != ents.length()) + return loadgameout(); + loopv(ents) + { + ents[i].spawned = gzgetc(f) != 0; + if (ents[i].type == CARROT && !ents[i].spawned) + trigger(ents[i].attr1, ents[i].attr2, true); + }; + restoreserverstate(ents); + + gzread(f, player1, sizeof(dynent)); + player1->lastaction = lastmillis; + + int nmonsters = gzgeti(); + dvector &monsters = getmonsters(); + if (nmonsters != monsters.length()) + return loadgameout(); + loopv(monsters) + { + gzread(f, monsters[i], sizeof(dynent)); + monsters[i]->enemy = + player1; // lazy, could save id of enemy instead + monsters[i]->lastaction = monsters[i]->trigger = + lastmillis + + 500; // also lazy, but no real noticable effect on game + if (monsters[i]->state == CS_DEAD) + monsters[i]->lastaction = 0; + }; + restoremonsterstate(); + + int nplayers = gzgeti(); + loopi(nplayers) if (!gzget()) + { + dynent *d = getclient(i); + assert(d); + gzread(f, d, sizeof(dynent)); + }; + + conoutf("savegame restored"); + if (demoloading) + startdemo(); + else + stop(); +}; + +// demo functions + +int starttime = 0; +int playbacktime = 0; +int ddamage, bdamage; +vec dorig; + +void +record(char *name) +{ + if (m_sp) { + conoutf("cannot record singleplayer games"); + return; + }; + int cn = getclientnum(); + if (cn < 0) + return; + sprintf_sd(fn)("demos/%s.cdgz", name); + savestate(fn); + gzputi(cn); + conoutf("started recording demo to %s", fn); + demorecording = true; + starttime = lastmillis; + ddamage = bdamage = 0; +}; + +void +demodamage(int damage, vec &o) +{ + ddamage = damage; + dorig = o; +}; +void +demoblend(int damage) +{ + bdamage = damage; +}; + +void +incomingdemodata(uchar *buf, int len, bool extras) +{ + if (!demorecording) + return; + gzputi(lastmillis - starttime); + gzputi(len); + gzwrite(f, buf, len); + gzput(extras); + if (extras) { + gzput(player1->gunselect); + gzput(player1->lastattackgun); + gzputi(player1->lastaction - starttime); + gzputi(player1->gunwait); + gzputi(player1->health); + gzputi(player1->armour); + gzput(player1->armourtype); + loopi(NUMGUNS) gzput(player1->ammo[i]); + gzput(player1->state); + gzputi(bdamage); + bdamage = 0; + gzputi(ddamage); + if (ddamage) { + gzputv(dorig); + ddamage = 0; + }; + // FIXME: add all other client state which is not send through + // the network + }; +}; + +void +demo(char *name) +{ + sprintf_sd(fn)("demos/%s.cdgz", name); + loadstate(fn); + demoloading = true; +}; + +void +stopreset() +{ + conoutf("demo stopped (%d msec elapsed)", lastmillis - starttime); + stop(); + loopv(players) zapdynent(players[i]); + disconnect(0, 0); +}; + +VAR(demoplaybackspeed, 10, 100, 1000); +int +scaletime(int t) +{ + return (int)(t * (100.0f / demoplaybackspeed)) + starttime; +}; + +void +readdemotime() +{ + if (gzeof(f) || (playbacktime = gzgeti()) == -1) { + stopreset(); + return; + }; + playbacktime = scaletime(playbacktime); +}; + +void +startdemo() +{ + democlientnum = gzgeti(); + demoplayback = true; + starttime = lastmillis; + conoutf("now playing demo"); + dynent *d = getclient(democlientnum); + assert(d); + *d = *player1; + readdemotime(); +}; + +VAR(demodelaymsec, 0, 120, 500); + +void +catmulrom( + vec &z, vec &a, vec &b, vec &c, float s, vec &dest) // spline interpolation +{ + vec t1 = b, t2 = c; + + vsub(t1, z); + vmul(t1, 0.5f) vsub(t2, a); + vmul(t2, 0.5f); + + float s2 = s * s; + float s3 = s * s2; + + dest = a; + vec t = b; + + vmul(dest, 2 * s3 - 3 * s2 + 1); + vmul(t, -2 * s3 + 3 * s2); + vadd(dest, t); + vmul(t1, s3 - 2 * s2 + s); + vadd(dest, t1); + vmul(t2, s3 - s2); + vadd(dest, t2); +}; + +void +fixwrap(dynent *a, dynent *b) +{ + while (b->yaw - a->yaw > 180) + a->yaw += 360; + while (b->yaw - a->yaw < -180) + a->yaw -= 360; +}; + +void +demoplaybackstep() +{ + while (demoplayback && lastmillis >= playbacktime) { + int len = gzgeti(); + if (len < 1 || len > MAXTRANS) { + conoutf( + "error: huge packet during demo play (%d)", len); + stopreset(); + return; + }; + uchar buf[MAXTRANS]; + gzread(f, buf, len); + localservertoclient(buf, len); // update game state + + dynent *target = players[democlientnum]; + assert(target); + + int extras; + if (extras = gzget()) // read additional client side state not + // present in normal network stream + { + target->gunselect = gzget(); + target->lastattackgun = gzget(); + target->lastaction = scaletime(gzgeti()); + target->gunwait = gzgeti(); + target->health = gzgeti(); + target->armour = gzgeti(); + target->armourtype = gzget(); + loopi(NUMGUNS) target->ammo[i] = gzget(); + target->state = gzget(); + target->lastmove = playbacktime; + if (bdamage = gzgeti()) + damageblend(bdamage); + if (ddamage = gzgeti()) { + gzgetv(dorig); + particle_splash(3, ddamage, 1000, dorig); + }; + // FIXME: set more client state here + }; + + // insert latest copy of player into history + if (extras && + (playerhistory.empty() || + playerhistory.last()->lastupdate != playbacktime)) { + dynent *d = newdynent(); + *d = *target; + d->lastupdate = playbacktime; + playerhistory.add(d); + if (playerhistory.length() > 20) { + zapdynent(playerhistory[0]); + playerhistory.remove(0); + }; + }; + + readdemotime(); + }; + + if (demoplayback) { + int itime = lastmillis - demodelaymsec; + loopvrev(playerhistory) if (playerhistory[i]->lastupdate < + itime) // find 2 positions in + // history that surround + // interpolation time point + { + dynent *a = playerhistory[i]; + dynent *b = a; + if (i + 1 < playerhistory.length()) + b = playerhistory[i + 1]; + *player1 = *b; + if (a != b) // interpolate pos & angles + { + dynent *c = b; + if (i + 2 < playerhistory.length()) + c = playerhistory[i + 2]; + dynent *z = a; + if (i - 1 >= 0) + z = playerhistory[i - 1]; + // if(a==z || b==c) printf("* %d\n", + // lastmillis); + float bf = + (itime - a->lastupdate) / + (float)(b->lastupdate - a->lastupdate); + fixwrap(a, player1); + fixwrap(c, player1); + fixwrap(z, player1); + vdist(dist, v, z->o, c->o); + if (dist < 16) // if teleport or spawn, dont't + // interpolate + { + catmulrom(z->o, a->o, b->o, c->o, bf, + player1->o); + catmulrom(*(vec *)&z->yaw, + *(vec *)&a->yaw, *(vec *)&b->yaw, + *(vec *)&c->yaw, bf, + *(vec *)&player1->yaw); + }; + fixplayer1range(); + }; + break; + }; + // if(player1->state!=CS_DEAD) showscores(false); + }; +}; + +void +stopn() +{ + if (demoplayback) + stopreset(); + else + stop(); + conoutf("demo stopped"); +}; + +COMMAND(record, ARG_1STR); +COMMAND(demo, ARG_1STR); +COMMANDN(stop, stopn, ARG_NONE); + +COMMAND(savegame, ARG_1STR); +COMMAND(loadgame, ARG_1STR); DELETED src/server.cxx Index: src/server.cxx ================================================================== --- src/server.cxx +++ /dev/null @@ -1,544 +0,0 @@ -// server.cpp: little more than enhanced multicaster -// runs dedicated or as client coroutine - -#include "cube.h" - -enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; - -struct client // server side version of "dynent" type -{ - int type; - ENetPeer *peer; - string hostname; - string mapvote; - string name; - int modevote; -}; - -vector clients; - -int maxclients = 8; -string smapname; - -struct server_entity // server side version of "entity" type -{ - bool spawned; - int spawnsecs; -}; - -vector sents; - -bool notgotitems = - true; // true when map has changed and waiting for clients to send item -int mode = 0; - -void -restoreserverstate( - vector &ents) // hack: called from savegame code, only works in SP -{ - loopv(sents) - { - sents[i].spawned = ents[i].spawned; - sents[i].spawnsecs = 0; - }; -}; - -int interm = 0, minremain = 0, mapend = 0; -bool mapreload = false; - -char *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, char *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(char *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, char *reason) -{ - printf("disconnecting client (%s) [%s]\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.setsize(0); - notgotitems = true; -}; - -void -pickup(uint i, int sec, int sender) // server side item pickup, acknowledge - // first client that gets it -{ - if (i >= (uint)sents.length()) - return; - if (sents[i].spawned) { - sents[i].spawned = false; - sents[i].spawnsecs = sec; - send2(true, sender, SV_ITEMACC, i); - }; -}; - -void -resetvotes() -{ - loopv(clients) clients[i].mapvote[0] = 0; -}; - -bool -vote(char *map, int reqmode, int sender) -{ - strcpy_s(clients[sender].mapvote, map); - clients[sender].modevote = reqmode; - int yes = 0, no = 0; - loopv(clients) if (clients[i].type != ST_EMPTY) - { - if (clients[i].mapvote[0]) { - if (strcmp(clients[i].mapvote, map) == 0 && - clients[i].modevote == reqmode) - yes++; - else - no++; - } else - no++; - }; - if (yes == 1 && no == 0) - return true; // single player - sprintf_sd(msg)("%s suggests %s on map %s (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(); - strcpy_s(clients[cn].name, text); - sgetstr(); - getint(p); - break; - - case SV_MAPCHANGE: { - sgetstr(); - int reqmode = getint(p); - if (reqmode < 0) - reqmode = 0; - if (smapname[0] && !mapreload && - !vote(text, reqmode, sender)) - return; - mapreload = false; - mode = reqmode; - minremain = mode & 1 ? 15 : 10; - mapend = lastsec + minremain * 60; - interm = 0; - strcpy_s(smapname, text); - resetitems(); - sender = -1; - break; - }; - - case SV_ITEMLIST: { - int n; - while ((n = getint(p)) != -1) - if (notgotitems) { - server_entity se = {false, 0}; - while (sents.length() <= n) - sents.add(se); - 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.length() || - 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; - - case SV_EXT: // allows for new features that require no server - // updates - { - 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; - uchar *p = start + 2; - putint(p, SV_INITS2C); - putint(p, n); - putint(p, PROTOCOL_VERSION); - putint(p, smapname[0]); - sendstring(serverpassword, p); - putint(p, clients.length() > maxclients); - if (smapname[0]) { - putint(p, SV_MAPCHANGE); - sendstring(smapname, p); - putint(p, mode); - putint(p, SV_ITEMLIST); - loopv(sents) if (sents[i].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) -{ - loopv(clients) - { - if (i == sender) - continue; - send(i, packet); - }; -}; - -void -localclienttoserver(ENetPacket *packet) -{ - process(packet, 0); - if (!packet->referenceCount) - enet_packet_destroy(packet); -}; - -client & -addclient() -{ - loopv(clients) if (clients[i].type == ST_EMPTY) return clients[i]; - return clients.add(); -}; - -void -checkintermission() -{ - if (!minremain) { - interm = lastsec + 10; - mapend = lastsec + 1000; - }; - send2(true, -1, SV_TIMEUP, minremain--); -}; - -void -startintermission() -{ - minremain = 0; - checkintermission(); -}; - -void -resetserverifempty() -{ - loopv(clients) if (clients[i].type != ST_EMPTY) return; - clients.setsize(0); - smapname[0] = 0; - 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 -{ - loopv(sents) // spawn entities when timer reached - { - if (sents[i].spawnsecs && - (sents[i].spawnsecs -= seconds - lastsec) <= 0) { - sents[i].spawnsecs = 0; - sents[i].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; - loopv(clients) if (clients[i].type != ST_EMPTY) - { - send2(true, i, SV_MAPRELOAD, - 0); // ask a client to trigger map reload - mapreload = true; - break; - }; - }; - - resetserverifempty(); - - if (!isdedicated) - return; // below is network only - - int numplayers = 0; - loopv(clients) if (clients[i].type != ST_EMPTY)++ numplayers; - serverms(mode, numplayers, minremain, smapname, seconds, - clients.length() >= maxclients); - - if (seconds - laststatus > - 60) // display bandwidth stats, useful for server ops - { - nonlocalclients = 0; - loopv(clients) if (clients[i].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 *)(&c - &clients[0]); - char hn[1024]; - strcpy_s( - c.hostname, (enet_address_get_host(&c.peer->address, - hn, sizeof(hn)) == 0) - ? hn - : "localhost"); - printf("client connected (%s)\n", c.hostname); - send_welcome(lastconnect = &c - &clients[0]); - 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; - printf("disconnected client (%s)\n", - clients[(intptr_t)event.peer->data].hostname); - clients[(intptr_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() -{ - loopv(clients) if (clients[i].type == ST_LOCAL) clients[i].type = - ST_EMPTY; -}; - -void -localconnect() -{ - client &c = addclient(); - c.type = ST_LOCAL; - strcpy_s(c.hostname, "local"); - send_welcome(&c - &clients[0]); -}; - -void -initserver(bool dedicated, int uprate, char *sdesc, char *ip, char *master, - char *passwd, int maxcl) -{ - serverpassword = passwd; - maxclients = maxcl; - servermsinit(master ? master : "wouter.fov120.com/cube/masterserver/", - sdesc, dedicated); - - if (isdedicated = dedicated) { - ENetAddress address = {ENET_HOST_ANY, CUBE_SERVER_PORT}; - if (*ip && enet_address_set_host(&address, ip) < 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(); - - if (isdedicated) // do not return, this becomes main loop - { -#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 (;;) - serverslice(/*enet_time_get_sec()*/ time(NULL), 5); - }; -}; ADDED src/server.mm Index: src/server.mm ================================================================== --- /dev/null +++ src/server.mm @@ -0,0 +1,544 @@ +// server.cpp: little more than enhanced multicaster +// runs dedicated or as client coroutine + +#include "cube.h" + +enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; + +struct client // server side version of "dynent" type +{ + int type; + ENetPeer *peer; + string hostname; + string mapvote; + string name; + int modevote; +}; + +vector clients; + +int maxclients = 8; +string smapname; + +struct server_entity // server side version of "entity" type +{ + bool spawned; + int spawnsecs; +}; + +vector sents; + +bool notgotitems = + true; // true when map has changed and waiting for clients to send item +int mode = 0; + +void +restoreserverstate( + vector &ents) // hack: called from savegame code, only works in SP +{ + loopv(sents) + { + sents[i].spawned = ents[i].spawned; + sents[i].spawnsecs = 0; + }; +}; + +int interm = 0, minremain = 0, mapend = 0; +bool mapreload = false; + +char *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, char *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(char *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, char *reason) +{ + printf("disconnecting client (%s) [%s]\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.setsize(0); + notgotitems = true; +}; + +void +pickup(uint i, int sec, int sender) // server side item pickup, acknowledge + // first client that gets it +{ + if (i >= (uint)sents.length()) + return; + if (sents[i].spawned) { + sents[i].spawned = false; + sents[i].spawnsecs = sec; + send2(true, sender, SV_ITEMACC, i); + }; +}; + +void +resetvotes() +{ + loopv(clients) clients[i].mapvote[0] = 0; +}; + +bool +vote(char *map, int reqmode, int sender) +{ + strcpy_s(clients[sender].mapvote, map); + clients[sender].modevote = reqmode; + int yes = 0, no = 0; + loopv(clients) if (clients[i].type != ST_EMPTY) + { + if (clients[i].mapvote[0]) { + if (strcmp(clients[i].mapvote, map) == 0 && + clients[i].modevote == reqmode) + yes++; + else + no++; + } else + no++; + }; + if (yes == 1 && no == 0) + return true; // single player + sprintf_sd(msg)("%s suggests %s on map %s (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(); + strcpy_s(clients[cn].name, text); + sgetstr(); + getint(p); + break; + + case SV_MAPCHANGE: { + sgetstr(); + int reqmode = getint(p); + if (reqmode < 0) + reqmode = 0; + if (smapname[0] && !mapreload && + !vote(text, reqmode, sender)) + return; + mapreload = false; + mode = reqmode; + minremain = mode & 1 ? 15 : 10; + mapend = lastsec + minremain * 60; + interm = 0; + strcpy_s(smapname, text); + resetitems(); + sender = -1; + break; + }; + + case SV_ITEMLIST: { + int n; + while ((n = getint(p)) != -1) + if (notgotitems) { + server_entity se = {false, 0}; + while (sents.length() <= n) + sents.add(se); + 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.length() || + 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; + + case SV_EXT: // allows for new features that require no server + // updates + { + 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; + uchar *p = start + 2; + putint(p, SV_INITS2C); + putint(p, n); + putint(p, PROTOCOL_VERSION); + putint(p, smapname[0]); + sendstring(serverpassword, p); + putint(p, clients.length() > maxclients); + if (smapname[0]) { + putint(p, SV_MAPCHANGE); + sendstring(smapname, p); + putint(p, mode); + putint(p, SV_ITEMLIST); + loopv(sents) if (sents[i].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) +{ + loopv(clients) + { + if (i == sender) + continue; + send(i, packet); + }; +}; + +void +localclienttoserver(ENetPacket *packet) +{ + process(packet, 0); + if (!packet->referenceCount) + enet_packet_destroy(packet); +}; + +client & +addclient() +{ + loopv(clients) if (clients[i].type == ST_EMPTY) return clients[i]; + return clients.add(); +}; + +void +checkintermission() +{ + if (!minremain) { + interm = lastsec + 10; + mapend = lastsec + 1000; + }; + send2(true, -1, SV_TIMEUP, minremain--); +}; + +void +startintermission() +{ + minremain = 0; + checkintermission(); +}; + +void +resetserverifempty() +{ + loopv(clients) if (clients[i].type != ST_EMPTY) return; + clients.setsize(0); + smapname[0] = 0; + 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 +{ + loopv(sents) // spawn entities when timer reached + { + if (sents[i].spawnsecs && + (sents[i].spawnsecs -= seconds - lastsec) <= 0) { + sents[i].spawnsecs = 0; + sents[i].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; + loopv(clients) if (clients[i].type != ST_EMPTY) + { + send2(true, i, SV_MAPRELOAD, + 0); // ask a client to trigger map reload + mapreload = true; + break; + }; + }; + + resetserverifempty(); + + if (!isdedicated) + return; // below is network only + + int numplayers = 0; + loopv(clients) if (clients[i].type != ST_EMPTY)++ numplayers; + serverms(mode, numplayers, minremain, smapname, seconds, + clients.length() >= maxclients); + + if (seconds - laststatus > + 60) // display bandwidth stats, useful for server ops + { + nonlocalclients = 0; + loopv(clients) if (clients[i].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 *)(&c - &clients[0]); + char hn[1024]; + strcpy_s( + c.hostname, (enet_address_get_host(&c.peer->address, + hn, sizeof(hn)) == 0) + ? hn + : "localhost"); + printf("client connected (%s)\n", c.hostname); + send_welcome(lastconnect = &c - &clients[0]); + 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; + printf("disconnected client (%s)\n", + clients[(intptr_t)event.peer->data].hostname); + clients[(intptr_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() +{ + loopv(clients) if (clients[i].type == ST_LOCAL) clients[i].type = + ST_EMPTY; +}; + +void +localconnect() +{ + client &c = addclient(); + c.type = ST_LOCAL; + strcpy_s(c.hostname, "local"); + send_welcome(&c - &clients[0]); +}; + +void +initserver(bool dedicated, int uprate, char *sdesc, char *ip, char *master, + char *passwd, int maxcl) +{ + serverpassword = passwd; + maxclients = maxcl; + servermsinit(master ? master : "wouter.fov120.com/cube/masterserver/", + sdesc, dedicated); + + if (isdedicated = dedicated) { + ENetAddress address = {ENET_HOST_ANY, CUBE_SERVER_PORT}; + if (*ip && enet_address_set_host(&address, ip) < 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(); + + if (isdedicated) // do not return, this becomes main loop + { +#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 (;;) + serverslice(/*enet_time_get_sec()*/ time(NULL), 5); + }; +}; DELETED src/serverbrowser.cxx Index: src/serverbrowser.cxx ================================================================== --- src/serverbrowser.cxx +++ /dev/null @@ -1,330 +0,0 @@ -// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window -// management - -#include "SDL_thread.h" -#include "cube.h" - -struct resolverthread { - SDL_Thread *thread; - char *query; - int starttime; -}; - -struct resolverresult { - char *query; - ENetAddress address; -}; - -vector resolverthreads; -vector resolverqueries; -vector resolverresults; -SDL_mutex *resolvermutex; -SDL_sem *resolversem; -int resolverlimit = 1000; - -int -resolverloop(void *data) -{ - resolverthread *rt = (resolverthread *)data; - for (;;) { - SDL_SemWait(resolversem); - SDL_LockMutex(resolvermutex); - if (resolverqueries.empty()) { - SDL_UnlockMutex(resolvermutex); - continue; - } - rt->query = resolverqueries.pop(); - rt->starttime = lastmillis; - SDL_UnlockMutex(resolvermutex); - ENetAddress address = {ENET_HOST_ANY, CUBE_SERVINFO_PORT}; - enet_address_set_host(&address, rt->query); - SDL_LockMutex(resolvermutex); - resolverresult &rr = resolverresults.add(); - rr.query = rt->query; - rr.address = address; - rt->query = NULL; - rt->starttime = 0; - SDL_UnlockMutex(resolvermutex); - }; - return 0; -}; - -void -resolverinit(int threads, int limit) -{ - resolverlimit = limit; - resolversem = SDL_CreateSemaphore(0); - resolvermutex = SDL_CreateMutex(); - - while (threads > 0) { - resolverthread &rt = resolverthreads.add(); - rt.query = NULL; - rt.starttime = 0; - rt.thread = SDL_CreateThread(resolverloop, &rt); - --threads; - }; -}; - -void -resolverstop(resolverthread &rt, bool restart) -{ - SDL_LockMutex(resolvermutex); - SDL_KillThread(rt.thread); - rt.query = NULL; - rt.starttime = 0; - rt.thread = NULL; - if (restart) - rt.thread = SDL_CreateThread(resolverloop, &rt); - SDL_UnlockMutex(resolvermutex); -}; - -void -resolverclear() -{ - SDL_LockMutex(resolvermutex); - resolverqueries.setsize(0); - resolverresults.setsize(0); - while (SDL_SemTryWait(resolversem) == 0) - ; - loopv(resolverthreads) - { - resolverthread &rt = resolverthreads[i]; - resolverstop(rt, true); - }; - SDL_UnlockMutex(resolvermutex); -}; - -void -resolverquery(char *name) -{ - SDL_LockMutex(resolvermutex); - resolverqueries.add(name); - SDL_SemPost(resolversem); - SDL_UnlockMutex(resolvermutex); -}; - -bool -resolvercheck(char **name, ENetAddress *address) -{ - SDL_LockMutex(resolvermutex); - if (!resolverresults.empty()) { - resolverresult &rr = resolverresults.pop(); - *name = rr.query; - *address = rr.address; - SDL_UnlockMutex(resolvermutex); - return true; - } - loopv(resolverthreads) - { - resolverthread &rt = resolverthreads[i]; - if (rt.query) { - if (lastmillis - rt.starttime > resolverlimit) { - resolverstop(rt, true); - *name = rt.query; - SDL_UnlockMutex(resolvermutex); - return true; - }; - }; - }; - SDL_UnlockMutex(resolvermutex); - return false; -}; - -struct serverinfo { - string name; - string full; - string map; - string sdesc; - int mode, numplayers, ping, protocol, minremain; - ENetAddress address; -}; - -vector servers; -ENetSocket pingsock = ENET_SOCKET_NULL; -int lastinfo = 0; - -char * -getservername(int n) -{ - return servers[n].name; -}; - -void -addserver(char *servername) -{ - loopv(servers) if (strcmp(servers[i].name, servername) == 0) return; - serverinfo &si = servers.insert(0, serverinfo()); - strcpy_s(si.name, servername); - si.full[0] = 0; - si.mode = 0; - si.numplayers = 0; - si.ping = 9999; - si.protocol = 0; - si.minremain = 0; - si.map[0] = 0; - si.sdesc[0] = 0; - si.address.host = ENET_HOST_ANY; - si.address.port = CUBE_SERVINFO_PORT; -}; - -void -pingservers() -{ - ENetBuffer buf; - uchar ping[MAXTRANS]; - uchar *p; - loopv(servers) - { - serverinfo &si = servers[i]; - if (si.address.host == ENET_HOST_ANY) - continue; - p = ping; - putint(p, lastmillis); - buf.data = ping; - buf.dataLength = p - ping; - enet_socket_send(pingsock, &si.address, &buf, 1); - }; - lastinfo = lastmillis; -}; - -void -checkresolver() -{ - char *name = NULL; - ENetAddress addr = {ENET_HOST_ANY, CUBE_SERVINFO_PORT}; - while (resolvercheck(&name, &addr)) { - if (addr.host == ENET_HOST_ANY) - continue; - loopv(servers) - { - serverinfo &si = servers[i]; - if (name == si.name) { - si.address = addr; - addr.host = ENET_HOST_ANY; - break; - } - } - } -} - -void -checkpings() -{ - enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; - ENetBuffer buf; - ENetAddress addr; - uchar ping[MAXTRANS], *p; - char text[MAXTRANS]; - buf.data = ping; - buf.dataLength = sizeof(ping); - while (enet_socket_wait(pingsock, &events, 0) >= 0 && events) { - if (enet_socket_receive(pingsock, &addr, &buf, 1) <= 0) - return; - loopv(servers) - { - serverinfo &si = servers[i]; - if (addr.host == si.address.host) { - p = ping; - si.ping = lastmillis - getint(p); - si.protocol = getint(p); - if (si.protocol != PROTOCOL_VERSION) - si.ping = 9998; - si.mode = getint(p); - si.numplayers = getint(p); - si.minremain = getint(p); - sgetstr(); - strcpy_s(si.map, text); - sgetstr(); - strcpy_s(si.sdesc, text); - break; - }; - }; - }; -}; - -int -sicompare(const serverinfo *a, const serverinfo *b) -{ - return a->ping > b->ping - ? 1 - : (a->ping < b->ping ? -1 : strcmp(a->name, b->name)); -}; - -void -refreshservers() -{ - checkresolver(); - checkpings(); - if (lastmillis - lastinfo >= 5000) - pingservers(); - servers.sort((void *)sicompare); - int maxmenu = 16; - loopv(servers) - { - serverinfo &si = servers[i]; - if (si.address.host != ENET_HOST_ANY && si.ping != 9999) { - if (si.protocol != PROTOCOL_VERSION) - sprintf_s(si.full)( - "%s [different cube protocol]", si.name); - else - sprintf_s(si.full)("%d\t%d\t%s, %s: %s %s", - si.ping, si.numplayers, - si.map[0] ? si.map : "[unknown]", - modestr(si.mode), si.name, si.sdesc); - } else { - sprintf_s(si.full)( - si.address.host != ENET_HOST_ANY - ? "%s [waiting for server response]" - : "%s [unknown host]\t", - si.name); - } - si.full[50] = 0; // cut off too long server descriptions - menumanual(1, i, si.full); - if (!--maxmenu) - return; - }; -}; - -void -servermenu() -{ - if (pingsock == ENET_SOCKET_NULL) { - pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL); - resolverinit(1, 1000); - }; - resolverclear(); - loopv(servers) resolverquery(servers[i].name); - refreshservers(); - menuset(1); -}; - -void -updatefrommaster() -{ - const int MAXUPD = 32000; - uchar buf[MAXUPD]; - uchar *reply = retrieveservers(buf, MAXUPD); - if (!*reply || strstr((char *)reply, "") || - strstr((char *)reply, "")) - conoutf("master server not replying"); - else { - servers.setsize(0); - execute((char *)reply); - }; - servermenu(); -}; - -COMMAND(addserver, ARG_1STR); -COMMAND(servermenu, ARG_NONE); -COMMAND(updatefrommaster, ARG_NONE); - -void -writeservercfg() -{ - FILE *f = fopen("servers.cfg", "w"); - if (!f) - return; - fprintf(f, "// servers connected to are added here automatically\n\n"); - loopvrev(servers) fprintf(f, "addserver %s\n", servers[i].name); - fclose(f); -}; ADDED src/serverbrowser.mm Index: src/serverbrowser.mm ================================================================== --- /dev/null +++ src/serverbrowser.mm @@ -0,0 +1,330 @@ +// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window +// management + +#include "SDL_thread.h" +#include "cube.h" + +struct resolverthread { + SDL_Thread *thread; + char *query; + int starttime; +}; + +struct resolverresult { + char *query; + ENetAddress address; +}; + +vector resolverthreads; +vector resolverqueries; +vector resolverresults; +SDL_mutex *resolvermutex; +SDL_sem *resolversem; +int resolverlimit = 1000; + +int +resolverloop(void *data) +{ + resolverthread *rt = (resolverthread *)data; + for (;;) { + SDL_SemWait(resolversem); + SDL_LockMutex(resolvermutex); + if (resolverqueries.empty()) { + SDL_UnlockMutex(resolvermutex); + continue; + } + rt->query = resolverqueries.pop(); + rt->starttime = lastmillis; + SDL_UnlockMutex(resolvermutex); + ENetAddress address = {ENET_HOST_ANY, CUBE_SERVINFO_PORT}; + enet_address_set_host(&address, rt->query); + SDL_LockMutex(resolvermutex); + resolverresult &rr = resolverresults.add(); + rr.query = rt->query; + rr.address = address; + rt->query = NULL; + rt->starttime = 0; + SDL_UnlockMutex(resolvermutex); + }; + return 0; +}; + +void +resolverinit(int threads, int limit) +{ + resolverlimit = limit; + resolversem = SDL_CreateSemaphore(0); + resolvermutex = SDL_CreateMutex(); + + while (threads > 0) { + resolverthread &rt = resolverthreads.add(); + rt.query = NULL; + rt.starttime = 0; + rt.thread = SDL_CreateThread(resolverloop, &rt); + --threads; + }; +}; + +void +resolverstop(resolverthread &rt, bool restart) +{ + SDL_LockMutex(resolvermutex); + SDL_KillThread(rt.thread); + rt.query = NULL; + rt.starttime = 0; + rt.thread = NULL; + if (restart) + rt.thread = SDL_CreateThread(resolverloop, &rt); + SDL_UnlockMutex(resolvermutex); +}; + +void +resolverclear() +{ + SDL_LockMutex(resolvermutex); + resolverqueries.setsize(0); + resolverresults.setsize(0); + while (SDL_SemTryWait(resolversem) == 0) + ; + loopv(resolverthreads) + { + resolverthread &rt = resolverthreads[i]; + resolverstop(rt, true); + }; + SDL_UnlockMutex(resolvermutex); +}; + +void +resolverquery(char *name) +{ + SDL_LockMutex(resolvermutex); + resolverqueries.add(name); + SDL_SemPost(resolversem); + SDL_UnlockMutex(resolvermutex); +}; + +bool +resolvercheck(char **name, ENetAddress *address) +{ + SDL_LockMutex(resolvermutex); + if (!resolverresults.empty()) { + resolverresult &rr = resolverresults.pop(); + *name = rr.query; + *address = rr.address; + SDL_UnlockMutex(resolvermutex); + return true; + } + loopv(resolverthreads) + { + resolverthread &rt = resolverthreads[i]; + if (rt.query) { + if (lastmillis - rt.starttime > resolverlimit) { + resolverstop(rt, true); + *name = rt.query; + SDL_UnlockMutex(resolvermutex); + return true; + }; + }; + }; + SDL_UnlockMutex(resolvermutex); + return false; +}; + +struct serverinfo { + string name; + string full; + string map; + string sdesc; + int mode, numplayers, ping, protocol, minremain; + ENetAddress address; +}; + +vector servers; +ENetSocket pingsock = ENET_SOCKET_NULL; +int lastinfo = 0; + +char * +getservername(int n) +{ + return servers[n].name; +}; + +void +addserver(char *servername) +{ + loopv(servers) if (strcmp(servers[i].name, servername) == 0) return; + serverinfo &si = servers.insert(0, serverinfo()); + strcpy_s(si.name, servername); + si.full[0] = 0; + si.mode = 0; + si.numplayers = 0; + si.ping = 9999; + si.protocol = 0; + si.minremain = 0; + si.map[0] = 0; + si.sdesc[0] = 0; + si.address.host = ENET_HOST_ANY; + si.address.port = CUBE_SERVINFO_PORT; +}; + +void +pingservers() +{ + ENetBuffer buf; + uchar ping[MAXTRANS]; + uchar *p; + loopv(servers) + { + serverinfo &si = servers[i]; + if (si.address.host == ENET_HOST_ANY) + continue; + p = ping; + putint(p, lastmillis); + buf.data = ping; + buf.dataLength = p - ping; + enet_socket_send(pingsock, &si.address, &buf, 1); + }; + lastinfo = lastmillis; +}; + +void +checkresolver() +{ + char *name = NULL; + ENetAddress addr = {ENET_HOST_ANY, CUBE_SERVINFO_PORT}; + while (resolvercheck(&name, &addr)) { + if (addr.host == ENET_HOST_ANY) + continue; + loopv(servers) + { + serverinfo &si = servers[i]; + if (name == si.name) { + si.address = addr; + addr.host = ENET_HOST_ANY; + break; + } + } + } +} + +void +checkpings() +{ + enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; + ENetBuffer buf; + ENetAddress addr; + uchar ping[MAXTRANS], *p; + char text[MAXTRANS]; + buf.data = ping; + buf.dataLength = sizeof(ping); + while (enet_socket_wait(pingsock, &events, 0) >= 0 && events) { + if (enet_socket_receive(pingsock, &addr, &buf, 1) <= 0) + return; + loopv(servers) + { + serverinfo &si = servers[i]; + if (addr.host == si.address.host) { + p = ping; + si.ping = lastmillis - getint(p); + si.protocol = getint(p); + if (si.protocol != PROTOCOL_VERSION) + si.ping = 9998; + si.mode = getint(p); + si.numplayers = getint(p); + si.minremain = getint(p); + sgetstr(); + strcpy_s(si.map, text); + sgetstr(); + strcpy_s(si.sdesc, text); + break; + }; + }; + }; +}; + +int +sicompare(const serverinfo *a, const serverinfo *b) +{ + return a->ping > b->ping + ? 1 + : (a->ping < b->ping ? -1 : strcmp(a->name, b->name)); +}; + +void +refreshservers() +{ + checkresolver(); + checkpings(); + if (lastmillis - lastinfo >= 5000) + pingservers(); + servers.sort((void *)sicompare); + int maxmenu = 16; + loopv(servers) + { + serverinfo &si = servers[i]; + if (si.address.host != ENET_HOST_ANY && si.ping != 9999) { + if (si.protocol != PROTOCOL_VERSION) + sprintf_s(si.full)( + "%s [different cube protocol]", si.name); + else + sprintf_s(si.full)("%d\t%d\t%s, %s: %s %s", + si.ping, si.numplayers, + si.map[0] ? si.map : "[unknown]", + modestr(si.mode), si.name, si.sdesc); + } else { + sprintf_s(si.full)( + si.address.host != ENET_HOST_ANY + ? "%s [waiting for server response]" + : "%s [unknown host]\t", + si.name); + } + si.full[50] = 0; // cut off too long server descriptions + menumanual(1, i, si.full); + if (!--maxmenu) + return; + }; +}; + +void +servermenu() +{ + if (pingsock == ENET_SOCKET_NULL) { + pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL); + resolverinit(1, 1000); + }; + resolverclear(); + loopv(servers) resolverquery(servers[i].name); + refreshservers(); + menuset(1); +}; + +void +updatefrommaster() +{ + const int MAXUPD = 32000; + uchar buf[MAXUPD]; + uchar *reply = retrieveservers(buf, MAXUPD); + if (!*reply || strstr((char *)reply, "") || + strstr((char *)reply, "")) + conoutf("master server not replying"); + else { + servers.setsize(0); + execute((char *)reply); + }; + servermenu(); +}; + +COMMAND(addserver, ARG_1STR); +COMMAND(servermenu, ARG_NONE); +COMMAND(updatefrommaster, ARG_NONE); + +void +writeservercfg() +{ + FILE *f = fopen("servers.cfg", "w"); + if (!f) + return; + fprintf(f, "// servers connected to are added here automatically\n\n"); + loopvrev(servers) fprintf(f, "addserver %s\n", servers[i].name); + fclose(f); +}; DELETED src/serverms.cxx Index: src/serverms.cxx ================================================================== --- src/serverms.cxx +++ /dev/null @@ -1,166 +0,0 @@ -// all server side masterserver and pinging functionality - -#include "cube.h" - -ENetSocket mssock = ENET_SOCKET_NULL; - -void -httpgetsend(ENetAddress &ad, char *hostname, char *req, char *ref, char *agent) -{ - if (ad.host == ENET_HOST_ANY) { - printf("looking up %s...\n", hostname); - enet_address_set_host(&ad, hostname); - 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; - sprintf_sd(httpget)( - "GET %s HTTP/1.0\nHost: %s\nReferer: %s\nUser-Agent: %s\n\n", req, - hostname, ref, agent); - buf.data = httpget; - buf.dataLength = strlen((char *)buf.data); - printf("sending request to %s...\n", hostname); - enet_socket_send(mssock, NULL, &buf, 1); -}; - -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; - }; -}; - -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; -}; - -ENetAddress masterserver = {ENET_HOST_ANY, 80}; -int updmaster = 0; -string masterbase; -string masterpath; -uchar masterrep[MAXTRANS]; -ENetBuffer masterb; - -void -updatemasterserver(int seconds) -{ - if (seconds > - updmaster) // send alive signal to masterserver every hour of uptime - { - sprintf_sd(path)("%sregister.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; - }; -}; - -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) -{ - sprintf_sd(path)("%sretrieve.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); -}; - -ENetSocket pongsock = ENET_SOCKET_NULL; -string serverdesc; - -void -serverms(int mode, int numplayers, int minremain, char *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); - string mname; - strcpy_s(mname, isfull ? "[FULL] " : ""); - strcat_s(mname, smapname); - sendstring(mname, p); - sendstring(serverdesc, p); - buf.dataLength = p - pong; - enet_socket_send(pongsock, &addr, &buf, 1); - }; -}; - -void -servermsinit(const char *master, char *sdesc, bool listen) -{ - const char *mid = strstr(master, "/"); - if (!mid) - mid = master; - strcpy_s(masterpath, mid); - strn0cpy(masterbase, master, mid - master + 1); - strcpy_s(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/serverms.mm Index: src/serverms.mm ================================================================== --- /dev/null +++ src/serverms.mm @@ -0,0 +1,166 @@ +// all server side masterserver and pinging functionality + +#include "cube.h" + +ENetSocket mssock = ENET_SOCKET_NULL; + +void +httpgetsend(ENetAddress &ad, char *hostname, char *req, char *ref, char *agent) +{ + if (ad.host == ENET_HOST_ANY) { + printf("looking up %s...\n", hostname); + enet_address_set_host(&ad, hostname); + 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; + sprintf_sd(httpget)( + "GET %s HTTP/1.0\nHost: %s\nReferer: %s\nUser-Agent: %s\n\n", req, + hostname, ref, agent); + buf.data = httpget; + buf.dataLength = strlen((char *)buf.data); + printf("sending request to %s...\n", hostname); + enet_socket_send(mssock, NULL, &buf, 1); +}; + +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; + }; +}; + +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; +}; + +ENetAddress masterserver = {ENET_HOST_ANY, 80}; +int updmaster = 0; +string masterbase; +string masterpath; +uchar masterrep[MAXTRANS]; +ENetBuffer masterb; + +void +updatemasterserver(int seconds) +{ + if (seconds > + updmaster) // send alive signal to masterserver every hour of uptime + { + sprintf_sd(path)("%sregister.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; + }; +}; + +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) +{ + sprintf_sd(path)("%sretrieve.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); +}; + +ENetSocket pongsock = ENET_SOCKET_NULL; +string serverdesc; + +void +serverms(int mode, int numplayers, int minremain, char *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); + string mname; + strcpy_s(mname, isfull ? "[FULL] " : ""); + strcat_s(mname, smapname); + sendstring(mname, p); + sendstring(serverdesc, p); + buf.dataLength = p - pong; + enet_socket_send(pongsock, &addr, &buf, 1); + }; +}; + +void +servermsinit(const char *master, char *sdesc, bool listen) +{ + const char *mid = strstr(master, "/"); + if (!mid) + mid = master; + strcpy_s(masterpath, mid); + strn0cpy(masterbase, master, mid - master + 1); + strcpy_s(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/serverutil.cxx Index: src/serverutil.cxx ================================================================== --- src/serverutil.cxx +++ /dev/null @@ -1,190 +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(char *t, uchar *&p) -{ - while (*t) - putint(p, *t++); - putint(p, 0); -}; - -const char *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", -}; - -const char * -modestr(int n) -{ - return (n >= -2 && n < 12) ? modenames[n + 2] : "unknown"; -}; - -char msgsizesl[] = // size inclusive message token, 0 for variable or - // not-checked sizes - {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 - -string copyname; -int copysize; -uchar *copydata = NULL; - -void -sendmaps(int n, string mapname, int mapsize, uchar *mapdata) -{ - if (mapsize <= 0 || mapsize > 256 * 256) - return; - strcpy_s(copyname, mapname); - copysize = mapsize; - if (copydata) - free(copydata); - copydata = (uchar *)alloc(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(char *s, char *o) -{ - cleanupserver(); - printf("servererror: %s\n", s); - 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; - 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, passwd, maxcl); - return 0; -}; -#endif ADDED src/serverutil.mm Index: src/serverutil.mm ================================================================== --- /dev/null +++ src/serverutil.mm @@ -0,0 +1,190 @@ +// 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(char *t, uchar *&p) +{ + while (*t) + putint(p, *t++); + putint(p, 0); +}; + +const char *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", +}; + +const char * +modestr(int n) +{ + return (n >= -2 && n < 12) ? modenames[n + 2] : "unknown"; +}; + +char msgsizesl[] = // size inclusive message token, 0 for variable or + // not-checked sizes + {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 + +string copyname; +int copysize; +uchar *copydata = NULL; + +void +sendmaps(int n, string mapname, int mapsize, uchar *mapdata) +{ + if (mapsize <= 0 || mapsize > 256 * 256) + return; + strcpy_s(copyname, mapname); + copysize = mapsize; + if (copydata) + free(copydata); + copydata = (uchar *)alloc(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(char *s, char *o) +{ + cleanupserver(); + printf("servererror: %s\n", s); + 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; + 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, passwd, maxcl); + return 0; +}; +#endif DELETED src/sound.cxx Index: src/sound.cxx ================================================================== --- src/sound.cxx +++ /dev/null @@ -1,271 +0,0 @@ -// sound.cpp: uses fmod on windows and sdl_mixer on unix (both had problems on -// the other platform) - -#include "cube.h" - -// #ifndef _WIN32 // NOTE: fmod not being supported for the moment as it does -// not allow stereo pan/vol updating during playback -#define USE_MIXER -// #endif - -VARP(soundvol, 0, 255, 255); -VARP(musicvol, 0, 128, 255); -bool nosound = false; - -#define MAXCHAN 32 -#define SOUNDFREQ 22050 - -struct soundloc { - vec loc; - bool inuse; -} soundlocs[MAXCHAN]; - -#ifdef USE_MIXER -#include "SDL_mixer.h" -#define MAXVOL MIX_MAX_VOLUME -Mix_Music *mod = NULL; -void *stream = NULL; -#else -#include "fmod.h" -#define MAXVOL 255 -FMUSIC_MODULE *mod = NULL; -FSOUND_STREAM *stream = NULL; -#endif - -void -stopsound() -{ - if (nosound) - return; - if (mod) { -#ifdef USE_MIXER - Mix_HaltMusic(); - Mix_FreeMusic(mod); -#else - FMUSIC_FreeSong(mod); -#endif - mod = NULL; - }; - if (stream) { -#ifndef USE_MIXER - FSOUND_Stream_Close(stream); -#endif - stream = NULL; - }; -}; - -VAR(soundbufferlen, 128, 1024, 4096); - -void -initsound() -{ - memset(soundlocs, 0, sizeof(soundloc) * MAXCHAN); -#ifdef USE_MIXER - 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); -#else - if (FSOUND_GetVersion() < FMOD_VERSION) - fatal("old FMOD dll"); - if (!FSOUND_Init(SOUNDFREQ, MAXCHAN, FSOUND_INIT_GLOBALFOCUS)) { - conoutf("sound init failed (FMOD): %d", FSOUND_GetError()); - nosound = true; - }; -#endif -}; - -void -music(char *name) -{ - if (nosound) - return; - stopsound(); - if (soundvol && musicvol) { - string sn; - strcpy_s(sn, "packages/"); - strcat_s(sn, name); -#ifdef USE_MIXER - if (mod = Mix_LoadMUS(path(sn))) { - Mix_PlayMusic(mod, -1); - Mix_VolumeMusic((musicvol * MAXVOL) / 255); - }; -#else - if (mod = FMUSIC_LoadSong(path(sn))) { - FMUSIC_PlaySong(mod); - FMUSIC_SetMasterVolume(mod, musicvol); - } else if (stream = FSOUND_Stream_Open( - path(sn), FSOUND_LOOP_NORMAL, 0, 0)) { - int chan = FSOUND_Stream_Play(FSOUND_FREE, stream); - if (chan >= 0) { - FSOUND_SetVolume( - chan, (musicvol * MAXVOL) / 255); - FSOUND_SetPaused(chan, false); - }; - } else { - conoutf("could not play music: %s", sn); - }; -#endif - }; -}; - -COMMAND(music, ARG_1STR); - -#ifdef USE_MIXER -vector samples; -#else -vector samples; -#endif - -cvector snames; - -int -registersound(char *name) -{ - loopv(snames) if (strcmp(snames[i], name) == 0) return i; - snames.add(newstring(name)); - samples.add(NULL); - return samples.length() - 1; -}; - -COMMAND(registersound, ARG_1EST); - -void -cleansound() -{ - if (nosound) - return; - stopsound(); -#ifdef USE_MIXER - Mix_CloseAudio(); -#else - FSOUND_Close(); -#endif -}; - -VAR(stereo, 0, 1, 1); - -void -updatechanvol(int chan, vec *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)) { - float yaw = -atan2(v.x, v.y) - - player1->yaw * - (PI / 180.0f); // relative angle of - // sound along X-Y axis - pan = int( - 255.9f * - (0.5 * sin(yaw) + - 0.5f)); // range is from 0 (left) to 255 (right) - }; - }; - vol = (vol * MAXVOL) / 255; -#ifdef USE_MIXER - Mix_Volume(chan, vol); - Mix_SetPanning(chan, 255 - pan, pan); -#else - FSOUND_SetVolume(chan, vol); - FSOUND_SetPan(chan, pan); -#endif -}; - -void -newsoundloc(int chan, vec *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) - { -#ifdef USE_MIXER - if (Mix_Playing(i)) -#else - if (FSOUND_IsPlaying(i)) -#endif - updatechanvol(i, &soundlocs[i].loc); - else - soundlocs[i].inuse = false; - }; -}; - -void -playsoundc(int n) -{ - addmsg(0, 2, SV_SOUND, n); - playsound(n); -}; - -int soundsatonce = 0, lastsoundmillis = 0; - -void -playsound(int n, vec *loc) -{ - if (nosound) - return; - if (!soundvol) - return; - if (lastmillis == lastsoundmillis) - soundsatonce++; - else - soundsatonce = 1; - lastsoundmillis = lastmillis; - if (soundsatonce > 5) - return; // avoid bursts of sounds with heavy packetloss and in - // sp - if (n < 0 || n >= samples.length()) { - conoutf("unregistered sound: %d", n); - return; - }; - - if (!samples[n]) { - sprintf_sd(buf)("packages/sounds/%s.wav", snames[n]); - -#ifdef USE_MIXER - samples[n] = Mix_LoadWAV(path(buf)); -#else - samples[n] = - FSOUND_Sample_Load(n, path(buf), FSOUND_LOOP_OFF, 0, 0); -#endif - - if (!samples[n]) { - conoutf("failed to load sample: %s", buf); - return; - }; - }; - -#ifdef USE_MIXER - int chan = Mix_PlayChannel(-1, samples[n], 0); -#else - int chan = FSOUND_PlaySoundEx(FSOUND_FREE, samples[n], NULL, true); -#endif - if (chan < 0) - return; - if (loc) - newsoundloc(chan, loc); - updatechanvol(chan, loc); -#ifndef USE_MIXER - FSOUND_SetPaused(chan, false); -#endif -}; - -void -sound(int n) -{ - playsound(n, NULL); -}; -COMMAND(sound, ARG_1INT); ADDED src/sound.mm Index: src/sound.mm ================================================================== --- /dev/null +++ src/sound.mm @@ -0,0 +1,271 @@ +// sound.cpp: uses fmod on windows and sdl_mixer on unix (both had problems on +// the other platform) + +#include "cube.h" + +// #ifndef _WIN32 // NOTE: fmod not being supported for the moment as it does +// not allow stereo pan/vol updating during playback +#define USE_MIXER +// #endif + +VARP(soundvol, 0, 255, 255); +VARP(musicvol, 0, 128, 255); +bool nosound = false; + +#define MAXCHAN 32 +#define SOUNDFREQ 22050 + +struct soundloc { + vec loc; + bool inuse; +} soundlocs[MAXCHAN]; + +#ifdef USE_MIXER +#include "SDL_mixer.h" +#define MAXVOL MIX_MAX_VOLUME +Mix_Music *mod = NULL; +void *stream = NULL; +#else +#include "fmod.h" +#define MAXVOL 255 +FMUSIC_MODULE *mod = NULL; +FSOUND_STREAM *stream = NULL; +#endif + +void +stopsound() +{ + if (nosound) + return; + if (mod) { +#ifdef USE_MIXER + Mix_HaltMusic(); + Mix_FreeMusic(mod); +#else + FMUSIC_FreeSong(mod); +#endif + mod = NULL; + }; + if (stream) { +#ifndef USE_MIXER + FSOUND_Stream_Close(stream); +#endif + stream = NULL; + }; +}; + +VAR(soundbufferlen, 128, 1024, 4096); + +void +initsound() +{ + memset(soundlocs, 0, sizeof(soundloc) * MAXCHAN); +#ifdef USE_MIXER + 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); +#else + if (FSOUND_GetVersion() < FMOD_VERSION) + fatal("old FMOD dll"); + if (!FSOUND_Init(SOUNDFREQ, MAXCHAN, FSOUND_INIT_GLOBALFOCUS)) { + conoutf("sound init failed (FMOD): %d", FSOUND_GetError()); + nosound = true; + }; +#endif +}; + +void +music(char *name) +{ + if (nosound) + return; + stopsound(); + if (soundvol && musicvol) { + string sn; + strcpy_s(sn, "packages/"); + strcat_s(sn, name); +#ifdef USE_MIXER + if (mod = Mix_LoadMUS(path(sn))) { + Mix_PlayMusic(mod, -1); + Mix_VolumeMusic((musicvol * MAXVOL) / 255); + }; +#else + if (mod = FMUSIC_LoadSong(path(sn))) { + FMUSIC_PlaySong(mod); + FMUSIC_SetMasterVolume(mod, musicvol); + } else if (stream = FSOUND_Stream_Open( + path(sn), FSOUND_LOOP_NORMAL, 0, 0)) { + int chan = FSOUND_Stream_Play(FSOUND_FREE, stream); + if (chan >= 0) { + FSOUND_SetVolume( + chan, (musicvol * MAXVOL) / 255); + FSOUND_SetPaused(chan, false); + }; + } else { + conoutf("could not play music: %s", sn); + }; +#endif + }; +}; + +COMMAND(music, ARG_1STR); + +#ifdef USE_MIXER +vector samples; +#else +vector samples; +#endif + +cvector snames; + +int +registersound(char *name) +{ + loopv(snames) if (strcmp(snames[i], name) == 0) return i; + snames.add(newstring(name)); + samples.add(NULL); + return samples.length() - 1; +}; + +COMMAND(registersound, ARG_1EST); + +void +cleansound() +{ + if (nosound) + return; + stopsound(); +#ifdef USE_MIXER + Mix_CloseAudio(); +#else + FSOUND_Close(); +#endif +}; + +VAR(stereo, 0, 1, 1); + +void +updatechanvol(int chan, vec *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)) { + float yaw = -atan2(v.x, v.y) - + player1->yaw * + (PI / 180.0f); // relative angle of + // sound along X-Y axis + pan = int( + 255.9f * + (0.5 * sin(yaw) + + 0.5f)); // range is from 0 (left) to 255 (right) + }; + }; + vol = (vol * MAXVOL) / 255; +#ifdef USE_MIXER + Mix_Volume(chan, vol); + Mix_SetPanning(chan, 255 - pan, pan); +#else + FSOUND_SetVolume(chan, vol); + FSOUND_SetPan(chan, pan); +#endif +}; + +void +newsoundloc(int chan, vec *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) + { +#ifdef USE_MIXER + if (Mix_Playing(i)) +#else + if (FSOUND_IsPlaying(i)) +#endif + updatechanvol(i, &soundlocs[i].loc); + else + soundlocs[i].inuse = false; + }; +}; + +void +playsoundc(int n) +{ + addmsg(0, 2, SV_SOUND, n); + playsound(n); +}; + +int soundsatonce = 0, lastsoundmillis = 0; + +void +playsound(int n, vec *loc) +{ + if (nosound) + return; + if (!soundvol) + return; + if (lastmillis == lastsoundmillis) + soundsatonce++; + else + soundsatonce = 1; + lastsoundmillis = lastmillis; + if (soundsatonce > 5) + return; // avoid bursts of sounds with heavy packetloss and in + // sp + if (n < 0 || n >= samples.length()) { + conoutf("unregistered sound: %d", n); + return; + }; + + if (!samples[n]) { + sprintf_sd(buf)("packages/sounds/%s.wav", snames[n]); + +#ifdef USE_MIXER + samples[n] = Mix_LoadWAV(path(buf)); +#else + samples[n] = + FSOUND_Sample_Load(n, path(buf), FSOUND_LOOP_OFF, 0, 0); +#endif + + if (!samples[n]) { + conoutf("failed to load sample: %s", buf); + return; + }; + }; + +#ifdef USE_MIXER + int chan = Mix_PlayChannel(-1, samples[n], 0); +#else + int chan = FSOUND_PlaySoundEx(FSOUND_FREE, samples[n], NULL, true); +#endif + if (chan < 0) + return; + if (loc) + newsoundloc(chan, loc); + updatechanvol(chan, loc); +#ifndef USE_MIXER + FSOUND_SetPaused(chan, false); +#endif +}; + +void +sound(int n) +{ + playsound(n, NULL); +}; +COMMAND(sound, ARG_1INT); DELETED src/tools.cxx Index: src/tools.cxx ================================================================== --- src/tools.cxx +++ /dev/null @@ -1,148 +0,0 @@ -// implementation of generic tools - -#include "tools.h" -#include - -//////////////////////////// pool /////////////////////////// - -pool::pool() -{ - blocks = 0; - allocnext(POOLSIZE); - for (int i = 0; i < MAXBUCKETS; i++) - reuse[i] = NULL; -}; - -void * -pool::alloc(size_t size) -{ - if (size > MAXREUSESIZE) { - return malloc(size); - } else { - size = bucket(size); - void **r = (void **)reuse[size]; - if (r) { - reuse[size] = *r; - return (void *)r; - } else { - size <<= PTRBITS; - if (left < size) - allocnext(POOLSIZE); - char *r = p; - p += size; - left -= size; - return r; - }; - }; -}; - -void -pool::dealloc(void *p, size_t size) -{ - if (size > MAXREUSESIZE) { - free(p); - } else { - size = bucket(size); - if (size) // only needed for 0-size free, are there any? - { - *((void **)p) = reuse[size]; - reuse[size] = p; - }; - }; -}; - -void * -pool::realloc(void *p, size_t oldsize, size_t newsize) -{ - void *np = alloc(newsize); - if (!oldsize) - return np; - memcpy(np, p, newsize > oldsize ? oldsize : newsize); - dealloc(p, oldsize); - return np; -}; - -void -pool::dealloc_block(void *b) -{ - if (b) { - dealloc_block(*((char **)b)); - free(b); - }; -} - -void -pool::allocnext(size_t allocsize) -{ - char *b = (char *)malloc(allocsize + PTRSIZE); - *((char **)b) = blocks; - blocks = b; - p = b + PTRSIZE; - left = allocsize; -}; - -char * -pool::string(char *s, size_t l) -{ - char *b = (char *)alloc(l + 1); - strncpy(b, s, l); - b[l] = 0; - return b; -}; - -pool * -gp() // useful for global buffers that need to be initialisation order - // independant -{ - static pool *p = NULL; - return p ? p : (p = new pool()); -}; - -///////////////////////// misc tools /////////////////////// - -char * -path(char *s) -{ - for (char *t = s; t = strpbrk(t, "/\\"); *t++ = PATHDIV) - ; - return s; -}; - -char * -loadfile(char *fn, int *size) -{ - FILE *f = fopen(fn, "rb"); - if (!f) - return NULL; - fseek(f, 0, SEEK_END); - int len = ftell(f); - fseek(f, 0, SEEK_SET); - char *buf = (char *)malloc(len + 1); - if (!buf) - return NULL; - buf[len] = 0; - size_t rlen = fread(buf, 1, len, f); - fclose(f); - if (len != rlen || len <= 0) { - free(buf); - return NULL; - }; - if (size != NULL) - *size = len; - return buf; -}; - -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/tools.mm Index: src/tools.mm ================================================================== --- /dev/null +++ src/tools.mm @@ -0,0 +1,148 @@ +// implementation of generic tools + +#include "tools.h" +#include + +//////////////////////////// pool /////////////////////////// + +pool::pool() +{ + blocks = 0; + allocnext(POOLSIZE); + for (int i = 0; i < MAXBUCKETS; i++) + reuse[i] = NULL; +}; + +void * +pool::alloc(size_t size) +{ + if (size > MAXREUSESIZE) { + return malloc(size); + } else { + size = bucket(size); + void **r = (void **)reuse[size]; + if (r) { + reuse[size] = *r; + return (void *)r; + } else { + size <<= PTRBITS; + if (left < size) + allocnext(POOLSIZE); + char *r = p; + p += size; + left -= size; + return r; + }; + }; +}; + +void +pool::dealloc(void *p, size_t size) +{ + if (size > MAXREUSESIZE) { + free(p); + } else { + size = bucket(size); + if (size) // only needed for 0-size free, are there any? + { + *((void **)p) = reuse[size]; + reuse[size] = p; + }; + }; +}; + +void * +pool::realloc(void *p, size_t oldsize, size_t newsize) +{ + void *np = alloc(newsize); + if (!oldsize) + return np; + memcpy(np, p, newsize > oldsize ? oldsize : newsize); + dealloc(p, oldsize); + return np; +}; + +void +pool::dealloc_block(void *b) +{ + if (b) { + dealloc_block(*((char **)b)); + free(b); + }; +} + +void +pool::allocnext(size_t allocsize) +{ + char *b = (char *)malloc(allocsize + PTRSIZE); + *((char **)b) = blocks; + blocks = b; + p = b + PTRSIZE; + left = allocsize; +}; + +char * +pool::string(char *s, size_t l) +{ + char *b = (char *)alloc(l + 1); + strncpy(b, s, l); + b[l] = 0; + return b; +}; + +pool * +gp() // useful for global buffers that need to be initialisation order + // independant +{ + static pool *p = NULL; + return p ? p : (p = new pool()); +}; + +///////////////////////// misc tools /////////////////////// + +char * +path(char *s) +{ + for (char *t = s; t = strpbrk(t, "/\\"); *t++ = PATHDIV) + ; + return s; +}; + +char * +loadfile(char *fn, int *size) +{ + FILE *f = fopen(fn, "rb"); + if (!f) + return NULL; + fseek(f, 0, SEEK_END); + int len = ftell(f); + fseek(f, 0, SEEK_SET); + char *buf = (char *)malloc(len + 1); + if (!buf) + return NULL; + buf[len] = 0; + size_t rlen = fread(buf, 1, len, f); + fclose(f); + if (len != rlen || len <= 0) { + free(buf); + return NULL; + }; + if (size != NULL) + *size = len; + return buf; +}; + +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/weapon.cxx Index: src/weapon.cxx ================================================================== --- src/weapon.cxx +++ /dev/null @@ -1,420 +0,0 @@ -// weapon.cpp: all shooting and effects code - -#include "cube.h" - -struct guninfo { - short sound, attackdelay, damage, projspeed, part, kickamount; - char *name; -}; - -const int MONSTERDAMAGEFACTOR = 4; -const int SGRAYS = 20; -const float SGSPREAD = 2; -vec sg[SGRAYS]; - -guninfo 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("%s selected", (int)guns[s].name); -}; - -int -reloadtime(int gun) -{ - return guns[gun].attackdelay; -}; - -void -weapon(char *a1, char *a2, char *a3) -{ - selectgun(a1[0] ? atoi(a1) : -1, a2[0] ? atoi(a2) : -1, - a3[0] ? atoi(a3) : -1); -}; - -COMMAND(weapon, ARG_3STR); - -void -createrays(vec &from, vec &to) // create random spread of rays for the shotgun -{ - vdist(dist, dvec, from, to); - float f = dist * SGSPREAD / 1000; - loopi(SGRAYS) - { -#define RNDD (rnd(101) - 50) * f - vec r = {RNDD, RNDD, RNDD}; - sg[i] = to; - vadd(sg[i], r); - }; -}; - -bool -intersect(dynent *d, vec &from, vec &to) // if lineseg hits entity bounding box -{ - vec v = to, w = d->o, *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; -}; - -char * -playerincrosshair() -{ - if (demoplayback) - return NULL; - loopv(players) - { - dynent *o = players[i]; - if (!o) - continue; - if (intersect(o, player1->o, worldpos)) - return o->name; - }; - return NULL; -}; - -const int MAXPROJ = 100; -struct projectile { - vec o, to; - float speed; - dynent *owner; - int gun; - bool inuse, local; -}; -projectile projs[MAXPROJ]; - -void -projreset() -{ - loopi(MAXPROJ) projs[i].inuse = false; -}; - -void -newprojectile( - vec &from, vec &to, float speed, bool local, dynent *owner, int gun) -{ - loopi(MAXPROJ) - { - projectile *p = &projs[i]; - 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, dynent *d, dynent *at) -{ - 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), &d->o); - }; - particle_splash(3, damage, 1000, d->o); - demodamage(damage, d->o); -}; - -const float RL_RADIUS = 5; -const float RL_DAMRAD = 7; // hack - -void -radialeffect(dynent *o, vec &v, int cn, int qdam, dynent *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, vec &v, vec &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); - loopv(players) - { - if (i == notthisplayer) - continue; - dynent *o = players[i]; - if (!o) - continue; - radialeffect(o, v, i, qdam, p->owner); - }; - dvector &mv = getmonsters(); - loopv(mv) if (i != notthismonster) - radialeffect(mv[i], v, i, qdam, p->owner); - }; -}; - -inline void -projdamage(dynent *o, projectile *p, vec &v, int i, int im, int qdam) -{ - if (o->state != CS_ALIVE) - return; - if (intersect(o, p->o, v)) { - splash(p, v, p->o, i, im, qdam); - hit(i, qdam, o, p->owner); - }; -}; - -void -moveprojectiles(float time) -{ - loopi(MAXPROJ) - { - 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) - { - loopv(players) - { - dynent *o = players[i]; - if (!o) - continue; - projdamage(o, p, v, i, -1, qdam); - }; - if (p->owner != player1) - projdamage(player1, p, v, -1, -1, qdam); - dvector &mv = getmonsters(); - loopv(mv) if (!vreject(mv[i]->o, v, 10.0f) && - mv[i] != p->owner) - projdamage(mv[i], p, v, -1, i, qdam); - }; - if (p->inuse) { - if (time == dtime) - splash(p, v, p->o, -1, -1, qdam); - else { - if (p->gun == GUN_RL) { - dodynlight(p->o, 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; - }; -}; - -void -shootv(int gun, vec &from, vec &to, dynent *d, - bool local) // create visual effect from a shot -{ - playsound(guns[gun].sound, d == player1 ? NULL : &d->o); - 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, dynent *d, dynent *at, vec &from, vec &to) -{ - hit(target, damage, d, at); - vdist(dist, v, from, to); - vmul(v, damage / dist / 50); - vadd(d->vel, v); -}; - -void -raydamage(dynent *o, vec &from, vec &to, dynent *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(dynent *d, vec &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]--; - vec from = d->o; - vec to = targ; - from.z -= 0.2f; // below eye - - vdist(dist, unitv, from, to); - vdiv(unitv, dist); - vec 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; - - loopv(players) - { - dynent *o = players[i]; - if (!o) - continue; - raydamage(o, from, to, d, i); - }; - - dvector &v = getmonsters(); - loopv(v) if (v[i] != d) raydamage(v[i], from, to, d, -2); - - if (d->monsterstate) - raydamage(player1, from, to, d, -1); -}; ADDED src/weapon.mm Index: src/weapon.mm ================================================================== --- /dev/null +++ src/weapon.mm @@ -0,0 +1,420 @@ +// weapon.cpp: all shooting and effects code + +#include "cube.h" + +struct guninfo { + short sound, attackdelay, damage, projspeed, part, kickamount; + char *name; +}; + +const int MONSTERDAMAGEFACTOR = 4; +const int SGRAYS = 20; +const float SGSPREAD = 2; +vec sg[SGRAYS]; + +guninfo 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("%s selected", (int)guns[s].name); +}; + +int +reloadtime(int gun) +{ + return guns[gun].attackdelay; +}; + +void +weapon(char *a1, char *a2, char *a3) +{ + selectgun(a1[0] ? atoi(a1) : -1, a2[0] ? atoi(a2) : -1, + a3[0] ? atoi(a3) : -1); +}; + +COMMAND(weapon, ARG_3STR); + +void +createrays(vec &from, vec &to) // create random spread of rays for the shotgun +{ + vdist(dist, dvec, from, to); + float f = dist * SGSPREAD / 1000; + loopi(SGRAYS) + { +#define RNDD (rnd(101) - 50) * f + vec r = {RNDD, RNDD, RNDD}; + sg[i] = to; + vadd(sg[i], r); + }; +}; + +bool +intersect(dynent *d, vec &from, vec &to) // if lineseg hits entity bounding box +{ + vec v = to, w = d->o, *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; +}; + +char * +playerincrosshair() +{ + if (demoplayback) + return NULL; + loopv(players) + { + dynent *o = players[i]; + if (!o) + continue; + if (intersect(o, player1->o, worldpos)) + return o->name; + }; + return NULL; +}; + +const int MAXPROJ = 100; +struct projectile { + vec o, to; + float speed; + dynent *owner; + int gun; + bool inuse, local; +}; +projectile projs[MAXPROJ]; + +void +projreset() +{ + loopi(MAXPROJ) projs[i].inuse = false; +}; + +void +newprojectile( + vec &from, vec &to, float speed, bool local, dynent *owner, int gun) +{ + loopi(MAXPROJ) + { + projectile *p = &projs[i]; + 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, dynent *d, dynent *at) +{ + 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), &d->o); + }; + particle_splash(3, damage, 1000, d->o); + demodamage(damage, d->o); +}; + +const float RL_RADIUS = 5; +const float RL_DAMRAD = 7; // hack + +void +radialeffect(dynent *o, vec &v, int cn, int qdam, dynent *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, vec &v, vec &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); + loopv(players) + { + if (i == notthisplayer) + continue; + dynent *o = players[i]; + if (!o) + continue; + radialeffect(o, v, i, qdam, p->owner); + }; + dvector &mv = getmonsters(); + loopv(mv) if (i != notthismonster) + radialeffect(mv[i], v, i, qdam, p->owner); + }; +}; + +inline void +projdamage(dynent *o, projectile *p, vec &v, int i, int im, int qdam) +{ + if (o->state != CS_ALIVE) + return; + if (intersect(o, p->o, v)) { + splash(p, v, p->o, i, im, qdam); + hit(i, qdam, o, p->owner); + }; +}; + +void +moveprojectiles(float time) +{ + loopi(MAXPROJ) + { + 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) + { + loopv(players) + { + dynent *o = players[i]; + if (!o) + continue; + projdamage(o, p, v, i, -1, qdam); + }; + if (p->owner != player1) + projdamage(player1, p, v, -1, -1, qdam); + dvector &mv = getmonsters(); + loopv(mv) if (!vreject(mv[i]->o, v, 10.0f) && + mv[i] != p->owner) + projdamage(mv[i], p, v, -1, i, qdam); + }; + if (p->inuse) { + if (time == dtime) + splash(p, v, p->o, -1, -1, qdam); + else { + if (p->gun == GUN_RL) { + dodynlight(p->o, 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; + }; +}; + +void +shootv(int gun, vec &from, vec &to, dynent *d, + bool local) // create visual effect from a shot +{ + playsound(guns[gun].sound, d == player1 ? NULL : &d->o); + 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, dynent *d, dynent *at, vec &from, vec &to) +{ + hit(target, damage, d, at); + vdist(dist, v, from, to); + vmul(v, damage / dist / 50); + vadd(d->vel, v); +}; + +void +raydamage(dynent *o, vec &from, vec &to, dynent *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(dynent *d, vec &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]--; + vec from = d->o; + vec to = targ; + from.z -= 0.2f; // below eye + + vdist(dist, unitv, from, to); + vdiv(unitv, dist); + vec 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; + + loopv(players) + { + dynent *o = players[i]; + if (!o) + continue; + raydamage(o, from, to, d, i); + }; + + dvector &v = getmonsters(); + loopv(v) if (v[i] != d) raydamage(v[i], from, to, d, -2); + + if (d->monsterstate) + raydamage(player1, from, to, d, -1); +}; DELETED src/world.cxx Index: src/world.cxx ================================================================== --- src/world.cxx +++ /dev/null @@ -1,511 +0,0 @@ -// world.cpp: core map management stuff - -#include "cube.h" - -extern char *entnames[]; // lookup from map entities above to strings - -sqr *world = NULL; -int sfactor, ssize, cubicsize, mipsize; - -header hdr; - -void -settag(int tag, int type) // set all cubes with "tag" to space, if tag is 0 then - // reset ALL tagged cubes according to type -{ - int maxx = 0, maxy = 0, minx = ssize, miny = ssize; - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - if (s->tag) { - if (tag) { - if (tag == s->tag) - s->type = SPACE; - else - continue; - } else { - s->type = type ? SOLID : SPACE; - }; - if (x > maxx) - maxx = x; - if (y > maxy) - maxy = y; - if (x < minx) - minx = x; - if (y < miny) - miny = y; - }; - }; - block b = {minx, miny, maxx - minx + 1, maxy - miny + 1}; - if (maxx) - remip(b); // remip minimal area of changed geometry -}; - -void -resettagareas() -{ - settag(0, 0); -}; // reset for editing or map saving -void -settagareas() -{ - settag(0, 1); - loopv(ents) if (ents[i].type == CARROT) setspawn(i, true); -}; // set for playing - -void -trigger(int tag, int type, bool savegame) -{ - if (!tag) - return; - settag(tag, type); - if (!savegame && type != 3) - playsound(S_RUMBLE); - sprintf_sd(aliasname)("level_trigger_%d", tag); - if (identexists(aliasname)) - execute(aliasname); - if (type == 2) - endsp(false); -}; - -COMMAND(trigger, ARG_2INT); - -// main geometric mipmapping routine, recursively rebuild mipmaps within block -// b. tries to produce cube out of 4 lower level mips as well as possible, sets -// defer to 0 if mipped cube is a perfect mip, i.e. can be rendered at this mip -// level indistinguishable from its constituent cubes (saves considerable -// rendering time if this is possible). - -void -remip(block &b, int level) -{ - if (level >= SMALLEST_FACTOR) - return; - int lighterr = getvar("lighterror") * 3; - sqr *w = wmip[level]; - sqr *v = wmip[level + 1]; - int ws = ssize >> level; - int vs = ssize >> (level + 1); - block s = b; - if (s.x & 1) { - s.x--; - s.xs++; - }; - if (s.y & 1) { - s.y--; - s.ys++; - }; - s.xs = (s.xs + 1) & ~1; - s.ys = (s.ys + 1) & ~1; - for (int x = s.x; x < s.x + s.xs; x += 2) - for (int y = s.y; y < s.y + s.ys; y += 2) { - sqr *o[4]; - o[0] = SWS(w, x, y, ws); // the 4 constituent cubes - o[1] = SWS(w, x + 1, y, ws); - o[2] = SWS(w, x + 1, y + 1, ws); - o[3] = SWS(w, x, y + 1, ws); - sqr *r = SWS(v, x / 2, y / 2, - vs); // the target cube in the higher mip level - *r = *o[0]; - uchar nums[MAXTYPE]; - loopi(MAXTYPE) nums[i] = 0; - loopj(4) nums[o[j]->type]++; - r->type = - SEMISOLID; // cube contains both solid and space, - // treated specially in the renderer - loopk(MAXTYPE) if (nums[k] == 4) r->type = k; - if (!SOLID(r)) { - int floor = 127, ceil = -128, num = 0; - loopi(4) if (!SOLID(o[i])) - { - num++; - int fh = o[i]->floor; - int ch = o[i]->ceil; - if (r->type == SEMISOLID) { - if (o[i]->type == FHF) - fh -= o[i]->vdelta / 4 + - 2; // crap hack, - // needed for - // rendering - // large mips - // next to hfs - if (o[i]->type == CHF) - ch += - o[i]->vdelta / 4 + - 2; // FIXME: needs - // to somehow - // take into - // account middle - // vertices on - // higher mips - }; - if (fh < floor) - floor = - fh; // take lowest floor and - // highest ceil, so we - // never have to see - // missing lower/upper - // from the side - if (ch > ceil) - ceil = ch; - }; - r->floor = floor; - r->ceil = ceil; - }; - if (r->type == CORNER) - goto mip; // special case: don't ever split even - // if textures etc are different - r->defer = 1; - if (SOLID(r)) { - loopi(3) - { - if (o[i]->wtex != o[3]->wtex) - goto c; // on an all solid cube, - // only thing that needs - // to be equal for a - // perfect mip is the - // wall texture - }; - } else { - loopi(3) - { - if (o[i]->type != o[3]->type || - o[i]->floor != o[3]->floor || - o[i]->ceil != o[3]->ceil || - o[i]->ftex != o[3]->ftex || - o[i]->ctex != o[3]->ctex || - abs(o[i + 1]->r - o[0]->r) > - lighterr // perfect mip even if - // light is not exactly - // equal - || abs(o[i + 1]->g - o[0]->g) > - lighterr || - abs(o[i + 1]->b - o[0]->b) > - lighterr || - o[i]->utex != o[3]->utex || - o[i]->wtex != o[3]->wtex) - goto c; - }; - if (r->type == CHF || - r->type == - FHF) // can make a perfect mip out of a - // hf if slopes lie on one line - { - if (o[0]->vdelta - o[1]->vdelta != - o[1]->vdelta - - SWS(w, x + 2, y, ws) - ->vdelta || - o[0]->vdelta - o[2]->vdelta != - o[2]->vdelta - - SWS(w, x + 2, y + 2, ws) - ->vdelta || - o[0]->vdelta - o[3]->vdelta != - o[3]->vdelta - - SWS(w, x, y + 2, ws) - ->vdelta || - o[3]->vdelta - o[2]->vdelta != - o[2]->vdelta - - SWS(w, x + 2, y + 1, ws) - ->vdelta || - o[1]->vdelta - o[2]->vdelta != - o[2]->vdelta - - SWS(w, x + 1, y + 2, ws) - ->vdelta) - goto c; - }; - }; - { - loopi(4) if (o[i]->defer) goto c; - }; // if any of the constituents is not perfect, then - // this one isn't either - mip: - r->defer = 0; - c:; - }; - s.x /= 2; - s.y /= 2; - s.xs /= 2; - s.ys /= 2; - remip(s, level + 1); -}; - -void -remipmore(block &b, int level) -{ - block bb = b; - if (bb.x > 1) - bb.x--; - if (bb.y > 1) - bb.y--; - if (bb.xs < ssize - 3) - bb.xs++; - if (bb.ys < ssize - 3) - bb.ys++; - remip(bb, level); -}; - -int -closestent() // used for delent and edit mode ent display -{ - if (noteditmode()) - return -1; - int best; - float bdist = 99999; - loopv(ents) - { - entity &e = ents[i]; - if (e.type == NOTUSED) - continue; - vec v = {(float)e.x, (float)e.y, (float)e.z}; - vdist(dist, t, player1->o, v); - if (dist < bdist) { - best = i; - bdist = dist; - }; - }; - return bdist == 99999 ? -1 : best; -}; - -void -entproperty(int prop, int amount) -{ - int e = closestent(); - if (e < 0) - return; - switch (prop) { - case 0: - ents[e].attr1 += amount; - break; - case 1: - ents[e].attr2 += amount; - break; - case 2: - ents[e].attr3 += amount; - break; - case 3: - ents[e].attr4 += amount; - break; - }; -}; - -void -delent() -{ - int e = closestent(); - if (e < 0) { - conoutf("no more entities"); - return; - }; - int t = ents[e].type; - conoutf("%s entity deleted", entnames[t]); - ents[e].type = NOTUSED; - addmsg(1, 10, SV_EDITENT, e, NOTUSED, 0, 0, 0, 0, 0, 0, 0); - if (t == LIGHT) - calclight(); -}; - -int -findtype(char *what) -{ - loopi(MAXENTTYPES) if (strcmp(what, entnames[i]) == 0) return i; - conoutf("unknown entity type \"%s\"", what); - return NOTUSED; -} - -entity * -newentity(int x, int y, int z, char *what, int v1, int v2, int v3, int v4) -{ - int type = findtype(what); - persistent_entity e = {(short)x, (short)y, (short)z, (short)v1, - (uchar)type, (uchar)v2, (uchar)v3, (uchar)v4}; - switch (type) { - case LIGHT: - if (v1 > 32) - v1 = 32; - if (!v1) - e.attr1 = 16; - if (!v2 && !v3 && !v4) - e.attr2 = 255; - break; - - case MAPMODEL: - e.attr4 = e.attr3; - e.attr3 = e.attr2; - case MONSTER: - case TELEDEST: - e.attr2 = (uchar)e.attr1; - case PLAYERSTART: - e.attr1 = (int)player1->yaw; - break; - }; - addmsg(1, 10, SV_EDITENT, ents.length(), type, e.x, e.y, e.z, e.attr1, - e.attr2, e.attr3, e.attr4); - ents.add(*((entity *)&e)); // unsafe! - if (type == LIGHT) - calclight(); - return &ents.last(); -}; - -void -clearents(char *name) -{ - int type = findtype(name); - if (noteditmode() || multiplayer()) - return; - loopv(ents) - { - entity &e = ents[i]; - if (e.type == type) - e.type = NOTUSED; - }; - if (type == LIGHT) - calclight(); -}; - -COMMAND(clearents, ARG_1STR); - -void -scalecomp(uchar &c, int intens) -{ - int n = c * intens / 100; - if (n > 255) - n = 255; - c = n; -}; - -void -scalelights(int f, int intens) -{ - loopv(ents) - { - entity &e = ents[i]; - if (e.type != LIGHT) - continue; - e.attr1 = e.attr1 * f / 100; - if (e.attr1 < 2) - e.attr1 = 2; - if (e.attr1 > 32) - e.attr1 = 32; - if (intens) { - scalecomp(e.attr2, intens); - scalecomp(e.attr3, intens); - scalecomp(e.attr4, intens); - }; - }; - calclight(); -}; - -COMMAND(scalelights, ARG_2INT); - -int -findentity(int type, int index) -{ - for (int i = index; i < ents.length(); i++) - if (ents[i].type == type) - return i; - loopj(index) if (ents[j].type == type) return j; - return -1; -}; - -sqr *wmip[LARGEST_FACTOR * 2]; - -void -setupworld(int factor) -{ - ssize = 1 << (sfactor = factor); - cubicsize = ssize * ssize; - mipsize = cubicsize * 134 / 100; - sqr *w = world = (sqr *)alloc(mipsize * sizeof(sqr)); - loopi(LARGEST_FACTOR * 2) - { - wmip[i] = w; - w += cubicsize >> (i * 2); - }; -}; - -void -empty_world( - int factor, bool force) // main empty world creation routine, if passed - // factor -1 will enlarge old world by 1 -{ - if (!force && noteditmode()) - return; - cleardlights(); - pruneundos(); - sqr *oldworld = world; - bool copy = false; - if (oldworld && factor < 0) { - factor = sfactor + 1; - copy = true; - }; - if (factor < SMALLEST_FACTOR) - factor = SMALLEST_FACTOR; - if (factor > LARGEST_FACTOR) - factor = LARGEST_FACTOR; - setupworld(factor); - - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - s->r = s->g = s->b = 150; - s->ftex = DEFAULT_FLOOR; - s->ctex = DEFAULT_CEIL; - s->wtex = s->utex = DEFAULT_WALL; - s->type = SOLID; - s->floor = 0; - s->ceil = 16; - s->vdelta = 0; - s->defer = 0; - }; - - strncpy(hdr.head, "CUBE", 4); - hdr.version = MAPVERSION; - hdr.headersize = sizeof(header); - hdr.sfactor = sfactor; - - if (copy) { - loop(x, ssize / 2) loop(y, ssize / 2) - { - *S(x + ssize / 4, y + ssize / 4) = - *SWS(oldworld, x, y, ssize / 2); - }; - loopv(ents) - { - ents[i].x += ssize / 4; - ents[i].y += ssize / 4; - }; - } else { - strn0cpy(hdr.maptitle, "Untitled Map by Unknown", 128); - hdr.waterlevel = -100000; - loopi(15) hdr.reserved[i] = 0; - loopk(3) loopi(256) hdr.texlists[k][i] = i; - ents.setsize(0); - block b = {8, 8, ssize - 16, ssize - 16}; - edittypexy(SPACE, b); - }; - - calclight(); - startmap("base/unnamed"); - if (oldworld) { - free(oldworld); - toggleedit(); - execute("fullbright 1"); - }; -}; - -void -mapenlarge() -{ - empty_world(-1, false); -}; -void -newmap(int i) -{ - empty_world(i, false); -}; - -COMMAND(mapenlarge, ARG_NONE); -COMMAND(newmap, ARG_1INT); -COMMANDN(recalc, calclight, ARG_NONE); -COMMAND(delent, ARG_NONE); -COMMAND(entproperty, ARG_2INT); ADDED src/world.mm Index: src/world.mm ================================================================== --- /dev/null +++ src/world.mm @@ -0,0 +1,511 @@ +// world.cpp: core map management stuff + +#include "cube.h" + +extern char *entnames[]; // lookup from map entities above to strings + +sqr *world = NULL; +int sfactor, ssize, cubicsize, mipsize; + +header hdr; + +void +settag(int tag, int type) // set all cubes with "tag" to space, if tag is 0 then + // reset ALL tagged cubes according to type +{ + int maxx = 0, maxy = 0, minx = ssize, miny = ssize; + loop(x, ssize) loop(y, ssize) + { + sqr *s = S(x, y); + if (s->tag) { + if (tag) { + if (tag == s->tag) + s->type = SPACE; + else + continue; + } else { + s->type = type ? SOLID : SPACE; + }; + if (x > maxx) + maxx = x; + if (y > maxy) + maxy = y; + if (x < minx) + minx = x; + if (y < miny) + miny = y; + }; + }; + block b = {minx, miny, maxx - minx + 1, maxy - miny + 1}; + if (maxx) + remip(b); // remip minimal area of changed geometry +}; + +void +resettagareas() +{ + settag(0, 0); +}; // reset for editing or map saving +void +settagareas() +{ + settag(0, 1); + loopv(ents) if (ents[i].type == CARROT) setspawn(i, true); +}; // set for playing + +void +trigger(int tag, int type, bool savegame) +{ + if (!tag) + return; + settag(tag, type); + if (!savegame && type != 3) + playsound(S_RUMBLE); + sprintf_sd(aliasname)("level_trigger_%d", tag); + if (identexists(aliasname)) + execute(aliasname); + if (type == 2) + endsp(false); +}; + +COMMAND(trigger, ARG_2INT); + +// main geometric mipmapping routine, recursively rebuild mipmaps within block +// b. tries to produce cube out of 4 lower level mips as well as possible, sets +// defer to 0 if mipped cube is a perfect mip, i.e. can be rendered at this mip +// level indistinguishable from its constituent cubes (saves considerable +// rendering time if this is possible). + +void +remip(block &b, int level) +{ + if (level >= SMALLEST_FACTOR) + return; + int lighterr = getvar("lighterror") * 3; + sqr *w = wmip[level]; + sqr *v = wmip[level + 1]; + int ws = ssize >> level; + int vs = ssize >> (level + 1); + block s = b; + if (s.x & 1) { + s.x--; + s.xs++; + }; + if (s.y & 1) { + s.y--; + s.ys++; + }; + s.xs = (s.xs + 1) & ~1; + s.ys = (s.ys + 1) & ~1; + for (int x = s.x; x < s.x + s.xs; x += 2) + for (int y = s.y; y < s.y + s.ys; y += 2) { + sqr *o[4]; + o[0] = SWS(w, x, y, ws); // the 4 constituent cubes + o[1] = SWS(w, x + 1, y, ws); + o[2] = SWS(w, x + 1, y + 1, ws); + o[3] = SWS(w, x, y + 1, ws); + sqr *r = SWS(v, x / 2, y / 2, + vs); // the target cube in the higher mip level + *r = *o[0]; + uchar nums[MAXTYPE]; + loopi(MAXTYPE) nums[i] = 0; + loopj(4) nums[o[j]->type]++; + r->type = + SEMISOLID; // cube contains both solid and space, + // treated specially in the renderer + loopk(MAXTYPE) if (nums[k] == 4) r->type = k; + if (!SOLID(r)) { + int floor = 127, ceil = -128, num = 0; + loopi(4) if (!SOLID(o[i])) + { + num++; + int fh = o[i]->floor; + int ch = o[i]->ceil; + if (r->type == SEMISOLID) { + if (o[i]->type == FHF) + fh -= o[i]->vdelta / 4 + + 2; // crap hack, + // needed for + // rendering + // large mips + // next to hfs + if (o[i]->type == CHF) + ch += + o[i]->vdelta / 4 + + 2; // FIXME: needs + // to somehow + // take into + // account middle + // vertices on + // higher mips + }; + if (fh < floor) + floor = + fh; // take lowest floor and + // highest ceil, so we + // never have to see + // missing lower/upper + // from the side + if (ch > ceil) + ceil = ch; + }; + r->floor = floor; + r->ceil = ceil; + }; + if (r->type == CORNER) + goto mip; // special case: don't ever split even + // if textures etc are different + r->defer = 1; + if (SOLID(r)) { + loopi(3) + { + if (o[i]->wtex != o[3]->wtex) + goto c; // on an all solid cube, + // only thing that needs + // to be equal for a + // perfect mip is the + // wall texture + }; + } else { + loopi(3) + { + if (o[i]->type != o[3]->type || + o[i]->floor != o[3]->floor || + o[i]->ceil != o[3]->ceil || + o[i]->ftex != o[3]->ftex || + o[i]->ctex != o[3]->ctex || + abs(o[i + 1]->r - o[0]->r) > + lighterr // perfect mip even if + // light is not exactly + // equal + || abs(o[i + 1]->g - o[0]->g) > + lighterr || + abs(o[i + 1]->b - o[0]->b) > + lighterr || + o[i]->utex != o[3]->utex || + o[i]->wtex != o[3]->wtex) + goto c; + }; + if (r->type == CHF || + r->type == + FHF) // can make a perfect mip out of a + // hf if slopes lie on one line + { + if (o[0]->vdelta - o[1]->vdelta != + o[1]->vdelta - + SWS(w, x + 2, y, ws) + ->vdelta || + o[0]->vdelta - o[2]->vdelta != + o[2]->vdelta - + SWS(w, x + 2, y + 2, ws) + ->vdelta || + o[0]->vdelta - o[3]->vdelta != + o[3]->vdelta - + SWS(w, x, y + 2, ws) + ->vdelta || + o[3]->vdelta - o[2]->vdelta != + o[2]->vdelta - + SWS(w, x + 2, y + 1, ws) + ->vdelta || + o[1]->vdelta - o[2]->vdelta != + o[2]->vdelta - + SWS(w, x + 1, y + 2, ws) + ->vdelta) + goto c; + }; + }; + { + loopi(4) if (o[i]->defer) goto c; + }; // if any of the constituents is not perfect, then + // this one isn't either + mip: + r->defer = 0; + c:; + }; + s.x /= 2; + s.y /= 2; + s.xs /= 2; + s.ys /= 2; + remip(s, level + 1); +}; + +void +remipmore(block &b, int level) +{ + block bb = b; + if (bb.x > 1) + bb.x--; + if (bb.y > 1) + bb.y--; + if (bb.xs < ssize - 3) + bb.xs++; + if (bb.ys < ssize - 3) + bb.ys++; + remip(bb, level); +}; + +int +closestent() // used for delent and edit mode ent display +{ + if (noteditmode()) + return -1; + int best; + float bdist = 99999; + loopv(ents) + { + entity &e = ents[i]; + if (e.type == NOTUSED) + continue; + vec v = {(float)e.x, (float)e.y, (float)e.z}; + vdist(dist, t, player1->o, v); + if (dist < bdist) { + best = i; + bdist = dist; + }; + }; + return bdist == 99999 ? -1 : best; +}; + +void +entproperty(int prop, int amount) +{ + int e = closestent(); + if (e < 0) + return; + switch (prop) { + case 0: + ents[e].attr1 += amount; + break; + case 1: + ents[e].attr2 += amount; + break; + case 2: + ents[e].attr3 += amount; + break; + case 3: + ents[e].attr4 += amount; + break; + }; +}; + +void +delent() +{ + int e = closestent(); + if (e < 0) { + conoutf("no more entities"); + return; + }; + int t = ents[e].type; + conoutf("%s entity deleted", entnames[t]); + ents[e].type = NOTUSED; + addmsg(1, 10, SV_EDITENT, e, NOTUSED, 0, 0, 0, 0, 0, 0, 0); + if (t == LIGHT) + calclight(); +}; + +int +findtype(char *what) +{ + loopi(MAXENTTYPES) if (strcmp(what, entnames[i]) == 0) return i; + conoutf("unknown entity type \"%s\"", what); + return NOTUSED; +} + +entity * +newentity(int x, int y, int z, char *what, int v1, int v2, int v3, int v4) +{ + int type = findtype(what); + persistent_entity e = {(short)x, (short)y, (short)z, (short)v1, + (uchar)type, (uchar)v2, (uchar)v3, (uchar)v4}; + switch (type) { + case LIGHT: + if (v1 > 32) + v1 = 32; + if (!v1) + e.attr1 = 16; + if (!v2 && !v3 && !v4) + e.attr2 = 255; + break; + + case MAPMODEL: + e.attr4 = e.attr3; + e.attr3 = e.attr2; + case MONSTER: + case TELEDEST: + e.attr2 = (uchar)e.attr1; + case PLAYERSTART: + e.attr1 = (int)player1->yaw; + break; + }; + addmsg(1, 10, SV_EDITENT, ents.length(), type, e.x, e.y, e.z, e.attr1, + e.attr2, e.attr3, e.attr4); + ents.add(*((entity *)&e)); // unsafe! + if (type == LIGHT) + calclight(); + return &ents.last(); +}; + +void +clearents(char *name) +{ + int type = findtype(name); + if (noteditmode() || multiplayer()) + return; + loopv(ents) + { + entity &e = ents[i]; + if (e.type == type) + e.type = NOTUSED; + }; + if (type == LIGHT) + calclight(); +}; + +COMMAND(clearents, ARG_1STR); + +void +scalecomp(uchar &c, int intens) +{ + int n = c * intens / 100; + if (n > 255) + n = 255; + c = n; +}; + +void +scalelights(int f, int intens) +{ + loopv(ents) + { + entity &e = ents[i]; + if (e.type != LIGHT) + continue; + e.attr1 = e.attr1 * f / 100; + if (e.attr1 < 2) + e.attr1 = 2; + if (e.attr1 > 32) + e.attr1 = 32; + if (intens) { + scalecomp(e.attr2, intens); + scalecomp(e.attr3, intens); + scalecomp(e.attr4, intens); + }; + }; + calclight(); +}; + +COMMAND(scalelights, ARG_2INT); + +int +findentity(int type, int index) +{ + for (int i = index; i < ents.length(); i++) + if (ents[i].type == type) + return i; + loopj(index) if (ents[j].type == type) return j; + return -1; +}; + +sqr *wmip[LARGEST_FACTOR * 2]; + +void +setupworld(int factor) +{ + ssize = 1 << (sfactor = factor); + cubicsize = ssize * ssize; + mipsize = cubicsize * 134 / 100; + sqr *w = world = (sqr *)alloc(mipsize * sizeof(sqr)); + loopi(LARGEST_FACTOR * 2) + { + wmip[i] = w; + w += cubicsize >> (i * 2); + }; +}; + +void +empty_world( + int factor, bool force) // main empty world creation routine, if passed + // factor -1 will enlarge old world by 1 +{ + if (!force && noteditmode()) + return; + cleardlights(); + pruneundos(); + sqr *oldworld = world; + bool copy = false; + if (oldworld && factor < 0) { + factor = sfactor + 1; + copy = true; + }; + if (factor < SMALLEST_FACTOR) + factor = SMALLEST_FACTOR; + if (factor > LARGEST_FACTOR) + factor = LARGEST_FACTOR; + setupworld(factor); + + loop(x, ssize) loop(y, ssize) + { + sqr *s = S(x, y); + s->r = s->g = s->b = 150; + s->ftex = DEFAULT_FLOOR; + s->ctex = DEFAULT_CEIL; + s->wtex = s->utex = DEFAULT_WALL; + s->type = SOLID; + s->floor = 0; + s->ceil = 16; + s->vdelta = 0; + s->defer = 0; + }; + + strncpy(hdr.head, "CUBE", 4); + hdr.version = MAPVERSION; + hdr.headersize = sizeof(header); + hdr.sfactor = sfactor; + + if (copy) { + loop(x, ssize / 2) loop(y, ssize / 2) + { + *S(x + ssize / 4, y + ssize / 4) = + *SWS(oldworld, x, y, ssize / 2); + }; + loopv(ents) + { + ents[i].x += ssize / 4; + ents[i].y += ssize / 4; + }; + } else { + strn0cpy(hdr.maptitle, "Untitled Map by Unknown", 128); + hdr.waterlevel = -100000; + loopi(15) hdr.reserved[i] = 0; + loopk(3) loopi(256) hdr.texlists[k][i] = i; + ents.setsize(0); + block b = {8, 8, ssize - 16, ssize - 16}; + edittypexy(SPACE, b); + }; + + calclight(); + startmap("base/unnamed"); + if (oldworld) { + free(oldworld); + toggleedit(); + execute("fullbright 1"); + }; +}; + +void +mapenlarge() +{ + empty_world(-1, false); +}; +void +newmap(int i) +{ + empty_world(i, false); +}; + +COMMAND(mapenlarge, ARG_NONE); +COMMAND(newmap, ARG_1INT); +COMMANDN(recalc, calclight, ARG_NONE); +COMMAND(delent, ARG_NONE); +COMMAND(entproperty, ARG_2INT); DELETED src/worldio.cxx Index: src/worldio.cxx ================================================================== --- src/worldio.cxx +++ /dev/null @@ -1,376 +0,0 @@ -// worldio.cpp: loading & saving of maps and savegames - -#include "cube.h" - -void -backup(char *name, char *backupname) -{ - remove(backupname); - rename(name, backupname); -}; - -string cgzname, bakname, pcfname, mcfname; - -void -setnames(char *name) -{ - string pakname, mapname; - char *slash = strpbrk(name, "/\\"); - if (slash) { - strn0cpy(pakname, name, slash - name + 1); - strcpy_s(mapname, slash + 1); - } else { - strcpy_s(pakname, "base"); - strcpy_s(mapname, name); - }; - sprintf_s(cgzname)("packages/%s/%s.cgz", pakname, mapname); - sprintf_s(bakname)( - "packages/%s/%s_%d.BAK", pakname, mapname, lastmillis); - sprintf_s(pcfname)("packages/%s/package.cfg", pakname); - sprintf_s(mcfname)("packages/%s/%s.cfg", pakname, mapname); - - path(cgzname); - path(bakname); -}; - -// the optimize routines below are here to reduce the detrimental effects of -// messy mapping by setting certain properties (vdeltas and textures) to -// neighbouring values wherever there is no visible difference. This allows the -// mipmapper to generate more efficient mips. the reason it is done on save is -// to reduce the amount spend in the mipmapper (as that is done in realtime). - -inline bool -nhf(sqr *s) -{ - return s->type != FHF && s->type != CHF; -}; - -void -voptimize() // reset vdeltas on non-hf cubes -{ - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - if (x && y) { - if (nhf(s) && nhf(S(x - 1, y)) && - nhf(S(x - 1, y - 1)) && nhf(S(x, y - 1))) - s->vdelta = 0; - } else - s->vdelta = 0; - }; -}; - -void -topt(sqr *s, bool &wf, bool &uf, int &wt, int &ut) -{ - sqr *o[4]; - o[0] = SWS(s, 0, -1, ssize); - o[1] = SWS(s, 0, 1, ssize); - o[2] = SWS(s, 1, 0, ssize); - o[3] = SWS(s, -1, 0, ssize); - wf = true; - uf = true; - if (SOLID(s)) { - loopi(4) if (!SOLID(o[i])) - { - wf = false; - wt = s->wtex; - ut = s->utex; - return; - }; - } else { - loopi(4) if (!SOLID(o[i])) - { - if (o[i]->floor < s->floor) { - wt = s->wtex; - wf = false; - }; - if (o[i]->ceil > s->ceil) { - ut = s->utex; - uf = false; - }; - }; - }; -}; - -void -toptimize() // FIXME: only does 2x2, make atleast for 4x4 also -{ - bool wf[4], uf[4]; - sqr *s[4]; - for (int x = 2; x < ssize - 4; x += 2) - for (int y = 2; y < ssize - 4; y += 2) { - s[0] = S(x, y); - int wt = s[0]->wtex, ut = s[0]->utex; - topt(s[0], wf[0], uf[0], wt, ut); - topt(s[1] = SWS(s[0], 0, 1, ssize), wf[1], uf[1], wt, - ut); - topt(s[2] = SWS(s[0], 1, 1, ssize), wf[2], uf[2], wt, - ut); - topt(s[3] = SWS(s[0], 1, 0, ssize), wf[3], uf[3], wt, - ut); - loopi(4) - { - if (wf[i]) - s[i]->wtex = wt; - if (uf[i]) - s[i]->utex = ut; - }; - }; -}; - -// these two are used by getmap/sendmap.. transfers compressed maps directly - -void -writemap(char *mname, int msize, uchar *mdata) -{ - setnames(mname); - backup(cgzname, bakname); - FILE *f = fopen(cgzname, "wb"); - if (!f) { - conoutf("could not write map to %s", cgzname); - return; - }; - fwrite(mdata, 1, msize, f); - fclose(f); - conoutf("wrote map %s as file %s", mname, cgzname); -} - -uchar * -readmap(char *mname, int *msize) -{ - setnames(mname); - uchar *mdata = (uchar *)loadfile(cgzname, msize); - if (!mdata) { - conoutf("could not read map %s", cgzname); - return NULL; - }; - return mdata; -} - -// save map as .cgz file. uses 2 layers of compression: first does simple -// run-length encoding and leaves out data for certain kinds of cubes, then zlib -// removes the last bits of redundancy. Both passes contribute greatly to the -// miniscule map sizes. - -void -save_world(char *mname) -{ - resettagareas(); // wouldn't be able to reproduce tagged areas otherwise - voptimize(); - toptimize(); - if (!*mname) - mname = getclientmap(); - setnames(mname); - backup(cgzname, bakname); - gzFile f = gzopen(cgzname, "wb9"); - if (!f) { - conoutf("could not write map to %s", cgzname); - return; - }; - hdr.version = MAPVERSION; - hdr.numents = 0; - loopv(ents) if (ents[i].type != NOTUSED) hdr.numents++; - header tmp = hdr; - endianswap(&tmp.version, sizeof(int), 4); - endianswap(&tmp.waterlevel, sizeof(int), 16); - gzwrite(f, &tmp, sizeof(header)); - loopv(ents) - { - if (ents[i].type != NOTUSED) { - entity tmp = ents[i]; - endianswap(&tmp, sizeof(short), 4); - gzwrite(f, &tmp, sizeof(persistent_entity)); - }; - }; - sqr *t = NULL; - int sc = 0; -#define spurge \ - while (sc) { \ - gzputc(f, 255); \ - if (sc > 255) { \ - gzputc(f, 255); \ - sc -= 255; \ - } else { \ - gzputc(f, sc); \ - sc = 0; \ - } \ - }; - loopk(cubicsize) - { - sqr *s = &world[k]; -#define c(f) (s->f == t->f) - // 4 types of blocks, to compress a bit: - // 255 (2): same as previous block + count - // 254 (3): same as previous, except light // deprecated - // SOLID (5) - // anything else (9) - - if (SOLID(s)) { - if (t && c(type) && c(wtex) && c(vdelta)) { - sc++; - } else { - spurge; - gzputc(f, s->type); - gzputc(f, s->wtex); - gzputc(f, s->vdelta); - }; - } else { - if (t && c(type) && c(floor) && c(ceil) && c(ctex) && - c(ftex) && c(utex) && c(wtex) && c(vdelta) && - c(tag)) { - sc++; - } else { - spurge; - gzputc(f, s->type); - gzputc(f, s->floor); - gzputc(f, s->ceil); - gzputc(f, s->wtex); - gzputc(f, s->ftex); - gzputc(f, s->ctex); - gzputc(f, s->vdelta); - gzputc(f, s->utex); - gzputc(f, s->tag); - }; - }; - t = s; - }; - spurge; - gzclose(f); - conoutf("wrote map file %s", cgzname); - settagareas(); -}; - -void -load_world(char *mname) // still supports all map formats that have existed - // since the earliest cube betas! -{ - stopifrecording(); - cleardlights(); - pruneundos(); - setnames(mname); - gzFile f = gzopen(cgzname, "rb9"); - if (!f) { - conoutf("could not read map %s", cgzname); - return; - }; - gzread(f, &hdr, sizeof(header) - sizeof(int) * 16); - endianswap(&hdr.version, sizeof(int), 4); - if (strncmp(hdr.head, "CUBE", 4) != 0) - fatal("while reading map: header malformatted"); - if (hdr.version > MAPVERSION) - fatal("this map requires a newer version of cube"); - if (sfactor < SMALLEST_FACTOR || sfactor > LARGEST_FACTOR) - fatal("illegal map size"); - if (hdr.version >= 4) { - gzread(f, &hdr.waterlevel, sizeof(int) * 16); - endianswap(&hdr.waterlevel, sizeof(int), 16); - } else { - hdr.waterlevel = -100000; - }; - ents.setsize(0); - loopi(hdr.numents) - { - entity &e = ents.add(); - gzread(f, &e, sizeof(persistent_entity)); - endianswap(&e, sizeof(short), 4); - e.spawned = false; - if (e.type == LIGHT) { - if (!e.attr2) - e.attr2 = 255; // needed for MAPVERSION<=2 - if (e.attr1 > 32) - e.attr1 = 32; // 12_03 and below - }; - }; - free(world); - setupworld(hdr.sfactor); - char texuse[256]; - loopi(256) texuse[i] = 0; - sqr *t = NULL; - loopk(cubicsize) - { - sqr *s = &world[k]; - int type = gzgetc(f); - switch (type) { - case 255: { - int n = gzgetc(f); - for (int i = 0; i < n; i++, k++) - memcpy(&world[k], t, sizeof(sqr)); - k--; - break; - }; - case 254: // only in MAPVERSION<=2 - { - memcpy(s, t, sizeof(sqr)); - s->r = s->g = s->b = gzgetc(f); - gzgetc(f); - break; - }; - case SOLID: { - s->type = SOLID; - s->wtex = gzgetc(f); - s->vdelta = gzgetc(f); - if (hdr.version <= 2) { - gzgetc(f); - gzgetc(f); - }; - s->ftex = DEFAULT_FLOOR; - s->ctex = DEFAULT_CEIL; - s->utex = s->wtex; - s->tag = 0; - s->floor = 0; - s->ceil = 16; - break; - }; - default: { - if (type < 0 || type >= MAXTYPE) { - sprintf_sd(t)("%d @ %d", type, k); - fatal("while reading map: type out of range: ", - t); - }; - s->type = type; - s->floor = gzgetc(f); - s->ceil = gzgetc(f); - if (s->floor >= s->ceil) - s->floor = s->ceil - 1; // for pre 12_13 - s->wtex = gzgetc(f); - s->ftex = gzgetc(f); - s->ctex = gzgetc(f); - if (hdr.version <= 2) { - gzgetc(f); - gzgetc(f); - }; - s->vdelta = gzgetc(f); - s->utex = (hdr.version >= 2) ? gzgetc(f) : s->wtex; - s->tag = (hdr.version >= 5) ? gzgetc(f) : 0; - s->type = type; - }; - }; - s->defer = 0; - t = s; - texuse[s->wtex] = 1; - if (!SOLID(s)) - texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1; - }; - gzclose(f); - calclight(); - settagareas(); - int xs, ys; - loopi(256) if (texuse) lookuptexture(i, xs, ys); - conoutf("read map %s (%d milliseconds)", cgzname, - SDL_GetTicks() - lastmillis); - conoutf("%s", hdr.maptitle); - startmap(mname); - loopl(256) - { - sprintf_sd(aliasname)( - "level_trigger_%d", l); // can this be done smarter? - if (identexists(aliasname)) - alias(aliasname, ""); - }; - execfile("data/default_map_settings.cfg"); - execfile(pcfname); - execfile(mcfname); -}; - -COMMANDN(savemap, save_world, ARG_1STR); ADDED src/worldio.mm Index: src/worldio.mm ================================================================== --- /dev/null +++ src/worldio.mm @@ -0,0 +1,376 @@ +// worldio.cpp: loading & saving of maps and savegames + +#include "cube.h" + +void +backup(char *name, char *backupname) +{ + remove(backupname); + rename(name, backupname); +}; + +string cgzname, bakname, pcfname, mcfname; + +void +setnames(char *name) +{ + string pakname, mapname; + char *slash = strpbrk(name, "/\\"); + if (slash) { + strn0cpy(pakname, name, slash - name + 1); + strcpy_s(mapname, slash + 1); + } else { + strcpy_s(pakname, "base"); + strcpy_s(mapname, name); + }; + sprintf_s(cgzname)("packages/%s/%s.cgz", pakname, mapname); + sprintf_s(bakname)( + "packages/%s/%s_%d.BAK", pakname, mapname, lastmillis); + sprintf_s(pcfname)("packages/%s/package.cfg", pakname); + sprintf_s(mcfname)("packages/%s/%s.cfg", pakname, mapname); + + path(cgzname); + path(bakname); +}; + +// the optimize routines below are here to reduce the detrimental effects of +// messy mapping by setting certain properties (vdeltas and textures) to +// neighbouring values wherever there is no visible difference. This allows the +// mipmapper to generate more efficient mips. the reason it is done on save is +// to reduce the amount spend in the mipmapper (as that is done in realtime). + +inline bool +nhf(sqr *s) +{ + return s->type != FHF && s->type != CHF; +}; + +void +voptimize() // reset vdeltas on non-hf cubes +{ + loop(x, ssize) loop(y, ssize) + { + sqr *s = S(x, y); + if (x && y) { + if (nhf(s) && nhf(S(x - 1, y)) && + nhf(S(x - 1, y - 1)) && nhf(S(x, y - 1))) + s->vdelta = 0; + } else + s->vdelta = 0; + }; +}; + +void +topt(sqr *s, bool &wf, bool &uf, int &wt, int &ut) +{ + sqr *o[4]; + o[0] = SWS(s, 0, -1, ssize); + o[1] = SWS(s, 0, 1, ssize); + o[2] = SWS(s, 1, 0, ssize); + o[3] = SWS(s, -1, 0, ssize); + wf = true; + uf = true; + if (SOLID(s)) { + loopi(4) if (!SOLID(o[i])) + { + wf = false; + wt = s->wtex; + ut = s->utex; + return; + }; + } else { + loopi(4) if (!SOLID(o[i])) + { + if (o[i]->floor < s->floor) { + wt = s->wtex; + wf = false; + }; + if (o[i]->ceil > s->ceil) { + ut = s->utex; + uf = false; + }; + }; + }; +}; + +void +toptimize() // FIXME: only does 2x2, make atleast for 4x4 also +{ + bool wf[4], uf[4]; + sqr *s[4]; + for (int x = 2; x < ssize - 4; x += 2) + for (int y = 2; y < ssize - 4; y += 2) { + s[0] = S(x, y); + int wt = s[0]->wtex, ut = s[0]->utex; + topt(s[0], wf[0], uf[0], wt, ut); + topt(s[1] = SWS(s[0], 0, 1, ssize), wf[1], uf[1], wt, + ut); + topt(s[2] = SWS(s[0], 1, 1, ssize), wf[2], uf[2], wt, + ut); + topt(s[3] = SWS(s[0], 1, 0, ssize), wf[3], uf[3], wt, + ut); + loopi(4) + { + if (wf[i]) + s[i]->wtex = wt; + if (uf[i]) + s[i]->utex = ut; + }; + }; +}; + +// these two are used by getmap/sendmap.. transfers compressed maps directly + +void +writemap(char *mname, int msize, uchar *mdata) +{ + setnames(mname); + backup(cgzname, bakname); + FILE *f = fopen(cgzname, "wb"); + if (!f) { + conoutf("could not write map to %s", cgzname); + return; + }; + fwrite(mdata, 1, msize, f); + fclose(f); + conoutf("wrote map %s as file %s", mname, cgzname); +} + +uchar * +readmap(char *mname, int *msize) +{ + setnames(mname); + uchar *mdata = (uchar *)loadfile(cgzname, msize); + if (!mdata) { + conoutf("could not read map %s", cgzname); + return NULL; + }; + return mdata; +} + +// save map as .cgz file. uses 2 layers of compression: first does simple +// run-length encoding and leaves out data for certain kinds of cubes, then zlib +// removes the last bits of redundancy. Both passes contribute greatly to the +// miniscule map sizes. + +void +save_world(char *mname) +{ + resettagareas(); // wouldn't be able to reproduce tagged areas otherwise + voptimize(); + toptimize(); + if (!*mname) + mname = getclientmap(); + setnames(mname); + backup(cgzname, bakname); + gzFile f = gzopen(cgzname, "wb9"); + if (!f) { + conoutf("could not write map to %s", cgzname); + return; + }; + hdr.version = MAPVERSION; + hdr.numents = 0; + loopv(ents) if (ents[i].type != NOTUSED) hdr.numents++; + header tmp = hdr; + endianswap(&tmp.version, sizeof(int), 4); + endianswap(&tmp.waterlevel, sizeof(int), 16); + gzwrite(f, &tmp, sizeof(header)); + loopv(ents) + { + if (ents[i].type != NOTUSED) { + entity tmp = ents[i]; + endianswap(&tmp, sizeof(short), 4); + gzwrite(f, &tmp, sizeof(persistent_entity)); + }; + }; + sqr *t = NULL; + int sc = 0; +#define spurge \ + while (sc) { \ + gzputc(f, 255); \ + if (sc > 255) { \ + gzputc(f, 255); \ + sc -= 255; \ + } else { \ + gzputc(f, sc); \ + sc = 0; \ + } \ + }; + loopk(cubicsize) + { + sqr *s = &world[k]; +#define c(f) (s->f == t->f) + // 4 types of blocks, to compress a bit: + // 255 (2): same as previous block + count + // 254 (3): same as previous, except light // deprecated + // SOLID (5) + // anything else (9) + + if (SOLID(s)) { + if (t && c(type) && c(wtex) && c(vdelta)) { + sc++; + } else { + spurge; + gzputc(f, s->type); + gzputc(f, s->wtex); + gzputc(f, s->vdelta); + }; + } else { + if (t && c(type) && c(floor) && c(ceil) && c(ctex) && + c(ftex) && c(utex) && c(wtex) && c(vdelta) && + c(tag)) { + sc++; + } else { + spurge; + gzputc(f, s->type); + gzputc(f, s->floor); + gzputc(f, s->ceil); + gzputc(f, s->wtex); + gzputc(f, s->ftex); + gzputc(f, s->ctex); + gzputc(f, s->vdelta); + gzputc(f, s->utex); + gzputc(f, s->tag); + }; + }; + t = s; + }; + spurge; + gzclose(f); + conoutf("wrote map file %s", cgzname); + settagareas(); +}; + +void +load_world(char *mname) // still supports all map formats that have existed + // since the earliest cube betas! +{ + stopifrecording(); + cleardlights(); + pruneundos(); + setnames(mname); + gzFile f = gzopen(cgzname, "rb9"); + if (!f) { + conoutf("could not read map %s", cgzname); + return; + }; + gzread(f, &hdr, sizeof(header) - sizeof(int) * 16); + endianswap(&hdr.version, sizeof(int), 4); + if (strncmp(hdr.head, "CUBE", 4) != 0) + fatal("while reading map: header malformatted"); + if (hdr.version > MAPVERSION) + fatal("this map requires a newer version of cube"); + if (sfactor < SMALLEST_FACTOR || sfactor > LARGEST_FACTOR) + fatal("illegal map size"); + if (hdr.version >= 4) { + gzread(f, &hdr.waterlevel, sizeof(int) * 16); + endianswap(&hdr.waterlevel, sizeof(int), 16); + } else { + hdr.waterlevel = -100000; + }; + ents.setsize(0); + loopi(hdr.numents) + { + entity &e = ents.add(); + gzread(f, &e, sizeof(persistent_entity)); + endianswap(&e, sizeof(short), 4); + e.spawned = false; + if (e.type == LIGHT) { + if (!e.attr2) + e.attr2 = 255; // needed for MAPVERSION<=2 + if (e.attr1 > 32) + e.attr1 = 32; // 12_03 and below + }; + }; + free(world); + setupworld(hdr.sfactor); + char texuse[256]; + loopi(256) texuse[i] = 0; + sqr *t = NULL; + loopk(cubicsize) + { + sqr *s = &world[k]; + int type = gzgetc(f); + switch (type) { + case 255: { + int n = gzgetc(f); + for (int i = 0; i < n; i++, k++) + memcpy(&world[k], t, sizeof(sqr)); + k--; + break; + }; + case 254: // only in MAPVERSION<=2 + { + memcpy(s, t, sizeof(sqr)); + s->r = s->g = s->b = gzgetc(f); + gzgetc(f); + break; + }; + case SOLID: { + s->type = SOLID; + s->wtex = gzgetc(f); + s->vdelta = gzgetc(f); + if (hdr.version <= 2) { + gzgetc(f); + gzgetc(f); + }; + s->ftex = DEFAULT_FLOOR; + s->ctex = DEFAULT_CEIL; + s->utex = s->wtex; + s->tag = 0; + s->floor = 0; + s->ceil = 16; + break; + }; + default: { + if (type < 0 || type >= MAXTYPE) { + sprintf_sd(t)("%d @ %d", type, k); + fatal("while reading map: type out of range: ", + t); + }; + s->type = type; + s->floor = gzgetc(f); + s->ceil = gzgetc(f); + if (s->floor >= s->ceil) + s->floor = s->ceil - 1; // for pre 12_13 + s->wtex = gzgetc(f); + s->ftex = gzgetc(f); + s->ctex = gzgetc(f); + if (hdr.version <= 2) { + gzgetc(f); + gzgetc(f); + }; + s->vdelta = gzgetc(f); + s->utex = (hdr.version >= 2) ? gzgetc(f) : s->wtex; + s->tag = (hdr.version >= 5) ? gzgetc(f) : 0; + s->type = type; + }; + }; + s->defer = 0; + t = s; + texuse[s->wtex] = 1; + if (!SOLID(s)) + texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1; + }; + gzclose(f); + calclight(); + settagareas(); + int xs, ys; + loopi(256) if (texuse) lookuptexture(i, xs, ys); + conoutf("read map %s (%d milliseconds)", cgzname, + SDL_GetTicks() - lastmillis); + conoutf("%s", hdr.maptitle); + startmap(mname); + loopl(256) + { + sprintf_sd(aliasname)( + "level_trigger_%d", l); // can this be done smarter? + if (identexists(aliasname)) + alias(aliasname, ""); + }; + execfile("data/default_map_settings.cfg"); + execfile(pcfname); + execfile(mcfname); +}; + +COMMANDN(savemap, save_world, ARG_1STR); DELETED src/worldlight.cxx Index: src/worldlight.cxx ================================================================== --- src/worldlight.cxx +++ /dev/null @@ -1,257 +0,0 @@ -// worldlight.cpp - -#include "cube.h" - -extern bool hasoverbright; - -VAR(lightscale, 1, 4, 100); - -void -lightray(float bx, float by, - persistent_entity &light) // done in realtime, needs to be fast -{ - float lx = light.x + (rnd(21) - 10) * 0.1f; - float ly = light.y + (rnd(21) - 10) * 0.1f; - float dx = bx - lx; - float dy = by - ly; - float dist = (float)sqrt(dx * dx + dy * dy); - if (dist < 1.0f) - return; - int reach = light.attr1; - int steps = (int)(reach * reach * 1.6f / - dist); // can change this for speedup/quality? - const int PRECBITS = 12; - const float PRECF = 4096.0f; - int x = (int)(lx * PRECF); - int y = (int)(ly * PRECF); - int l = light.attr2 << PRECBITS; - int stepx = (int)(dx / (float)steps * PRECF); - int stepy = (int)(dy / (float)steps * PRECF); - int stepl = - fast_f2nat(l / (float)steps); // incorrect: light will fade quicker - // if near edge of the world - - if (hasoverbright) { - l /= lightscale; - stepl /= lightscale; - - if (light.attr3 || - light.attr4) // coloured light version, special case because - // most lights are white - { - int dimness = rnd( - (255 - - (light.attr2 + light.attr3 + light.attr4) / 3) / - 16 + - 1); - x += stepx * dimness; - y += stepy * dimness; - - if (OUTBORD(x >> PRECBITS, y >> PRECBITS)) - return; - - int g = light.attr3 << PRECBITS; - int stepg = fast_f2nat(g / (float)steps); - int b = light.attr4 << PRECBITS; - int stepb = fast_f2nat(b / (float)steps); - g /= lightscale; - stepg /= lightscale; - b /= lightscale; - stepb /= lightscale; - loopi(steps) - { - sqr *s = S(x >> PRECBITS, y >> PRECBITS); - int tl = (l >> PRECBITS) + s->r; - s->r = tl > 255 ? 255 : tl; - tl = (g >> PRECBITS) + s->g; - s->g = tl > 255 ? 255 : tl; - tl = (b >> PRECBITS) + s->b; - s->b = tl > 255 ? 255 : tl; - if (SOLID(s)) - return; - x += stepx; - y += stepy; - l -= stepl; - g -= stepg; - b -= stepb; - stepl -= 25; - stepg -= 25; - stepb -= 25; - }; - } else // white light, special optimized version - { - int dimness = rnd((255 - light.attr2) / 16 + 1); - x += stepx * dimness; - y += stepy * dimness; - - if (OUTBORD(x >> PRECBITS, y >> PRECBITS)) - return; - - loopi(steps) - { - sqr *s = S(x >> PRECBITS, y >> PRECBITS); - int tl = (l >> PRECBITS) + s->r; - s->r = s->g = s->b = tl > 255 ? 255 : tl; - if (SOLID(s)) - return; - x += stepx; - y += stepy; - l -= stepl; - stepl -= 25; - }; - }; - } else // the old (white) light code, here for the few people with old - // video cards that don't support overbright - { - loopi(steps) - { - sqr *s = S(x >> PRECBITS, y >> PRECBITS); - int light = l >> PRECBITS; - if (light > s->r) - s->r = s->g = s->b = (uchar)light; - if (SOLID(s)) - return; - x += stepx; - y += stepy; - l -= stepl; - }; - }; -}; - -void -calclightsource(persistent_entity &l) -{ - int reach = l.attr1; - int sx = l.x - reach; - int ex = l.x + reach; - int sy = l.y - reach; - int ey = l.y + reach; - - rndreset(); - - const float s = 0.8f; - - for (float sx2 = (float)sx; sx2 <= ex; sx2 += s * 2) { - lightray(sx2, (float)sy, l); - lightray(sx2, (float)ey, l); - }; - for (float sy2 = sy + s; sy2 <= ey - s; sy2 += s * 2) { - lightray((float)sx, sy2, l); - lightray((float)ex, sy2, l); - }; - - rndtime(); -}; - -void -postlightarea(block &a) // median filter, smooths out random noise in light and - // makes it more mipable -{ - loop(x, a.xs) loop(y, a.ys) // assumes area not on edge of world - { - sqr *s = S(x + a.x, y + a.y); -#define median(m) \ - s->m = \ - (s->m * 2 + SW(s, 1, 0)->m * 2 + SW(s, 0, 1)->m * 2 + \ - SW(s, -1, 0)->m * 2 + SW(s, 0, -1)->m * 2 + SW(s, 1, 1)->m + \ - SW(s, 1, -1)->m + SW(s, -1, 1)->m + SW(s, -1, -1)->m) / \ - 14; // median is 4/2/1 instead - median(r); - median(g); - median(b); - }; - - remip(a); -}; - -void -calclight() -{ - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - s->r = s->g = s->b = 10; - }; - - loopv(ents) - { - entity &e = ents[i]; - if (e.type == LIGHT) - calclightsource(e); - }; - - block b = {1, 1, ssize - 2, ssize - 2}; - postlightarea(b); - setvar("fullbright", 0); -}; - -VARP(dynlight, 0, 16, 32); - -vector dlights; - -void -cleardlights() -{ - while (!dlights.empty()) { - block *backup = dlights.pop(); - blockpaste(*backup); - free(backup); - }; -}; - -void -dodynlight(vec &vold, vec &v, int reach, int strength, dynent *owner) -{ - if (!reach) - reach = dynlight; - if (owner->monsterstate) - reach = reach / 2; - if (!reach) - return; - if (v.x < 0 || v.y < 0 || v.x > ssize || v.y > ssize) - return; - - int creach = reach + 16; // dependant on lightray random offsets! - block b = {(int)v.x - creach, (int)v.y - creach, creach * 2 + 1, - creach * 2 + 1}; - - if (b.x < 1) - b.x = 1; - if (b.y < 1) - b.y = 1; - if (b.xs + b.x > ssize - 2) - b.xs = ssize - 2 - b.x; - if (b.ys + b.y > ssize - 2) - b.ys = ssize - 2 - b.y; - - dlights.add(blockcopy(b)); // backup area before rendering in dynlight - - persistent_entity l = {(short)v.x, (short)v.y, (short)v.z, (short)reach, - LIGHT, (uchar)strength, 0, 0}; - calclightsource(l); - postlightarea(b); -}; - -// utility functions also used by editing code - -block * -blockcopy(block &s) -{ - block *b = (block *)alloc(sizeof(block) + s.xs * s.ys * sizeof(sqr)); - *b = s; - sqr *q = (sqr *)(b + 1); - for (int x = s.x; x < s.xs + s.x; x++) - for (int y = s.y; y < s.ys + s.y; y++) - *q++ = *S(x, y); - return b; -}; - -void -blockpaste(block &b) -{ - sqr *q = (sqr *)((&b) + 1); - for (int x = b.x; x < b.xs + b.x; x++) - for (int y = b.y; y < b.ys + b.y; y++) - *S(x, y) = *q++; - remipmore(b); -}; ADDED src/worldlight.mm Index: src/worldlight.mm ================================================================== --- /dev/null +++ src/worldlight.mm @@ -0,0 +1,257 @@ +// worldlight.cpp + +#include "cube.h" + +extern bool hasoverbright; + +VAR(lightscale, 1, 4, 100); + +void +lightray(float bx, float by, + persistent_entity &light) // done in realtime, needs to be fast +{ + float lx = light.x + (rnd(21) - 10) * 0.1f; + float ly = light.y + (rnd(21) - 10) * 0.1f; + float dx = bx - lx; + float dy = by - ly; + float dist = (float)sqrt(dx * dx + dy * dy); + if (dist < 1.0f) + return; + int reach = light.attr1; + int steps = (int)(reach * reach * 1.6f / + dist); // can change this for speedup/quality? + const int PRECBITS = 12; + const float PRECF = 4096.0f; + int x = (int)(lx * PRECF); + int y = (int)(ly * PRECF); + int l = light.attr2 << PRECBITS; + int stepx = (int)(dx / (float)steps * PRECF); + int stepy = (int)(dy / (float)steps * PRECF); + int stepl = + fast_f2nat(l / (float)steps); // incorrect: light will fade quicker + // if near edge of the world + + if (hasoverbright) { + l /= lightscale; + stepl /= lightscale; + + if (light.attr3 || + light.attr4) // coloured light version, special case because + // most lights are white + { + int dimness = rnd( + (255 - + (light.attr2 + light.attr3 + light.attr4) / 3) / + 16 + + 1); + x += stepx * dimness; + y += stepy * dimness; + + if (OUTBORD(x >> PRECBITS, y >> PRECBITS)) + return; + + int g = light.attr3 << PRECBITS; + int stepg = fast_f2nat(g / (float)steps); + int b = light.attr4 << PRECBITS; + int stepb = fast_f2nat(b / (float)steps); + g /= lightscale; + stepg /= lightscale; + b /= lightscale; + stepb /= lightscale; + loopi(steps) + { + sqr *s = S(x >> PRECBITS, y >> PRECBITS); + int tl = (l >> PRECBITS) + s->r; + s->r = tl > 255 ? 255 : tl; + tl = (g >> PRECBITS) + s->g; + s->g = tl > 255 ? 255 : tl; + tl = (b >> PRECBITS) + s->b; + s->b = tl > 255 ? 255 : tl; + if (SOLID(s)) + return; + x += stepx; + y += stepy; + l -= stepl; + g -= stepg; + b -= stepb; + stepl -= 25; + stepg -= 25; + stepb -= 25; + }; + } else // white light, special optimized version + { + int dimness = rnd((255 - light.attr2) / 16 + 1); + x += stepx * dimness; + y += stepy * dimness; + + if (OUTBORD(x >> PRECBITS, y >> PRECBITS)) + return; + + loopi(steps) + { + sqr *s = S(x >> PRECBITS, y >> PRECBITS); + int tl = (l >> PRECBITS) + s->r; + s->r = s->g = s->b = tl > 255 ? 255 : tl; + if (SOLID(s)) + return; + x += stepx; + y += stepy; + l -= stepl; + stepl -= 25; + }; + }; + } else // the old (white) light code, here for the few people with old + // video cards that don't support overbright + { + loopi(steps) + { + sqr *s = S(x >> PRECBITS, y >> PRECBITS); + int light = l >> PRECBITS; + if (light > s->r) + s->r = s->g = s->b = (uchar)light; + if (SOLID(s)) + return; + x += stepx; + y += stepy; + l -= stepl; + }; + }; +}; + +void +calclightsource(persistent_entity &l) +{ + int reach = l.attr1; + int sx = l.x - reach; + int ex = l.x + reach; + int sy = l.y - reach; + int ey = l.y + reach; + + rndreset(); + + const float s = 0.8f; + + for (float sx2 = (float)sx; sx2 <= ex; sx2 += s * 2) { + lightray(sx2, (float)sy, l); + lightray(sx2, (float)ey, l); + }; + for (float sy2 = sy + s; sy2 <= ey - s; sy2 += s * 2) { + lightray((float)sx, sy2, l); + lightray((float)ex, sy2, l); + }; + + rndtime(); +}; + +void +postlightarea(block &a) // median filter, smooths out random noise in light and + // makes it more mipable +{ + loop(x, a.xs) loop(y, a.ys) // assumes area not on edge of world + { + sqr *s = S(x + a.x, y + a.y); +#define median(m) \ + s->m = \ + (s->m * 2 + SW(s, 1, 0)->m * 2 + SW(s, 0, 1)->m * 2 + \ + SW(s, -1, 0)->m * 2 + SW(s, 0, -1)->m * 2 + SW(s, 1, 1)->m + \ + SW(s, 1, -1)->m + SW(s, -1, 1)->m + SW(s, -1, -1)->m) / \ + 14; // median is 4/2/1 instead + median(r); + median(g); + median(b); + }; + + remip(a); +}; + +void +calclight() +{ + loop(x, ssize) loop(y, ssize) + { + sqr *s = S(x, y); + s->r = s->g = s->b = 10; + }; + + loopv(ents) + { + entity &e = ents[i]; + if (e.type == LIGHT) + calclightsource(e); + }; + + block b = {1, 1, ssize - 2, ssize - 2}; + postlightarea(b); + setvar("fullbright", 0); +}; + +VARP(dynlight, 0, 16, 32); + +vector dlights; + +void +cleardlights() +{ + while (!dlights.empty()) { + block *backup = dlights.pop(); + blockpaste(*backup); + free(backup); + }; +}; + +void +dodynlight(vec &vold, vec &v, int reach, int strength, dynent *owner) +{ + if (!reach) + reach = dynlight; + if (owner->monsterstate) + reach = reach / 2; + if (!reach) + return; + if (v.x < 0 || v.y < 0 || v.x > ssize || v.y > ssize) + return; + + int creach = reach + 16; // dependant on lightray random offsets! + block b = {(int)v.x - creach, (int)v.y - creach, creach * 2 + 1, + creach * 2 + 1}; + + if (b.x < 1) + b.x = 1; + if (b.y < 1) + b.y = 1; + if (b.xs + b.x > ssize - 2) + b.xs = ssize - 2 - b.x; + if (b.ys + b.y > ssize - 2) + b.ys = ssize - 2 - b.y; + + dlights.add(blockcopy(b)); // backup area before rendering in dynlight + + persistent_entity l = {(short)v.x, (short)v.y, (short)v.z, (short)reach, + LIGHT, (uchar)strength, 0, 0}; + calclightsource(l); + postlightarea(b); +}; + +// utility functions also used by editing code + +block * +blockcopy(block &s) +{ + block *b = (block *)alloc(sizeof(block) + s.xs * s.ys * sizeof(sqr)); + *b = s; + sqr *q = (sqr *)(b + 1); + for (int x = s.x; x < s.xs + s.x; x++) + for (int y = s.y; y < s.ys + s.y; y++) + *q++ = *S(x, y); + return b; +}; + +void +blockpaste(block &b) +{ + sqr *q = (sqr *)((&b) + 1); + for (int x = b.x; x < b.xs + b.x; x++) + for (int y = b.y; y < b.ys + b.y; y++) + *S(x, y) = *q++; + remipmore(b); +}; DELETED src/worldocull.cxx Index: src/worldocull.cxx ================================================================== --- src/worldocull.cxx +++ /dev/null @@ -1,204 +0,0 @@ -// worldocull.cpp: occlusion map and occlusion test - -#include "cube.h" - -#define NUMRAYS 512 - -float rdist[NUMRAYS]; -bool ocull = true; -float odist = 256; - -void -toggleocull() -{ - ocull = !ocull; -}; - -COMMAND(toggleocull, ARG_NONE); - -// constructs occlusion map: cast rays in all directions on the 2d plane and -// record distance. done exactly once per frame. - -void -computeraytable(float vx, float vy) -{ - if (!ocull) - return; - - odist = getvar("fog") * 1.5f; - - float apitch = (float)fabs(player1->pitch); - float af = getvar("fov") / 2 + apitch / 1.5f + 3; - float byaw = (player1->yaw - 90 + af) / 360 * PI2; - float syaw = (player1->yaw - 90 - af) / 360 * PI2; - - loopi(NUMRAYS) - { - float angle = i * PI2 / NUMRAYS; - if ((apitch > 45 // must be bigger if fov>120 - || (angle < byaw && angle > syaw) || - (angle < byaw - PI2 && angle > syaw - PI2) || - (angle < byaw + PI2 && angle > syaw + PI2)) && - !OUTBORD(vx, vy) && - !SOLID(S(fast_f2nat(vx), - fast_f2nat(vy)))) // try to avoid tracing ray if outside - // of frustrum - { - float ray = i * 8 / (float)NUMRAYS; - float dx, dy; - if (ray > 1 && ray < 3) { - dx = -(ray - 2); - dy = 1; - } else if (ray >= 3 && ray < 5) { - dx = -1; - dy = -(ray - 4); - } else if (ray >= 5 && ray < 7) { - dx = ray - 6; - dy = -1; - } else { - dx = 1; - dy = ray > 4 ? ray - 8 : ray; - }; - float sx = vx; - float sy = vy; - for (;;) { - sx += dx; - sy += dy; - if (SOLID(S(fast_f2nat(sx), - fast_f2nat( - sy)))) // 90% of time spend in this - // function is on this line - { - rdist[i] = (float)(fabs(sx - vx) + - fabs(sy - vy)); - break; - }; - }; - } else { - rdist[i] = 2; - }; - }; -}; - -// test occlusion for a cube... one of the most computationally expensive -// functions in the engine as its done for every cube and entity, but its effect -// is more than worth it! - -inline float -ca(float x, float y) -{ - return x > y ? y / x : 2 - x / y; -}; -inline float -ma(float x, float y) -{ - return x == 0 ? (y > 0 ? 2 : -2) : y / x; -}; - -int -isoccluded(float vx, float vy, float cx, float cy, - float csize) // v = viewer, c = cube to test -{ - if (!ocull) - return 0; - - float - nx = vx, - ny = vy; // n = point on the border of the cube that is closest to v - if (nx < cx) - nx = cx; - else if (nx > cx + csize) - nx = cx + csize; - if (ny < cy) - ny = cy; - else if (ny > cy + csize) - ny = cy + csize; - float xdist = (float)fabs(nx - vx); - float ydist = (float)fabs(ny - vy); - if (xdist > odist || ydist > odist) - return 2; - float dist = xdist + ydist - 1; // 1 needed? - - // ABC - // D E - // FGH - - // - check middle cube? BG - - // find highest and lowest angle in the occlusion map that this cube - // spans, based on its most left and right points on the border from the - // viewer pov... I see no easier way to do this than this silly code - // below - - float h, l; - if (cx <= vx) // ABDFG - { - if (cx + csize < vx) // ADF - { - if (cy <= vy) // AD - { - if (cy + csize < vy) { - h = ca(-(cx - vx), -(cy + csize - vy)) + - 4; - l = ca(-(cx + csize - vx), -(cy - vy)) + - 4; - } // A - else { - h = ma(-(cx + csize - vx), - -(cy + csize - vy)) + - 4; - l = ma(-(cx + csize - vx), -(cy - vy)) + - 4; - } // D - } else { - h = ca(cy + csize - vy, -(cx + csize - vx)) + 2; - l = ca(cy - vy, -(cx - vx)) + 2; - }; // F - } else // BG - { - if (cy <= vy) { - if (cy + csize < vy) { - h = ma(-(cy + csize - vy), cx - vx) + 6; - l = ma(-(cy + csize - vy), - cx + csize - vx) + - 6; - } // B - else - return 0; - } else { - h = ma(cy - vy, -(cx + csize - vx)) + 2; - l = ma(cy - vy, -(cx - vx)) + 2; - }; // G - }; - } else // CEH - { - if (cy <= vy) // CE - { - if (cy + csize < vy) { - h = ca(-(cy - vy), cx - vx) + 6; - l = ca(-(cy + csize - vy), cx + csize - vx) + 6; - } // C - else { - h = ma(cx - vx, cy - vy); - l = ma(cx - vx, cy + csize - vy); - }; // E - } else { - h = ca(cx + csize - vx, cy - vy); - l = ca(cx - vx, cy + csize - vy); - }; // H - }; - int si = fast_f2nat(h * (NUMRAYS / 8)) + - NUMRAYS; // get indexes into occlusion map from angles - int ei = fast_f2nat(l * (NUMRAYS / 8)) + NUMRAYS + 1; - if (ei <= si) - ei += NUMRAYS; - - for (int i = si; i <= ei; i++) { - if (dist < rdist[i & (NUMRAYS - 1)]) - return 0; // if any value in this segment of the - // occlusion map is further away then cube is - // not occluded - }; - - return 1; // cube is entirely occluded -}; ADDED src/worldocull.mm Index: src/worldocull.mm ================================================================== --- /dev/null +++ src/worldocull.mm @@ -0,0 +1,204 @@ +// worldocull.cpp: occlusion map and occlusion test + +#include "cube.h" + +#define NUMRAYS 512 + +float rdist[NUMRAYS]; +bool ocull = true; +float odist = 256; + +void +toggleocull() +{ + ocull = !ocull; +}; + +COMMAND(toggleocull, ARG_NONE); + +// constructs occlusion map: cast rays in all directions on the 2d plane and +// record distance. done exactly once per frame. + +void +computeraytable(float vx, float vy) +{ + if (!ocull) + return; + + odist = getvar("fog") * 1.5f; + + float apitch = (float)fabs(player1->pitch); + float af = getvar("fov") / 2 + apitch / 1.5f + 3; + float byaw = (player1->yaw - 90 + af) / 360 * PI2; + float syaw = (player1->yaw - 90 - af) / 360 * PI2; + + loopi(NUMRAYS) + { + float angle = i * PI2 / NUMRAYS; + if ((apitch > 45 // must be bigger if fov>120 + || (angle < byaw && angle > syaw) || + (angle < byaw - PI2 && angle > syaw - PI2) || + (angle < byaw + PI2 && angle > syaw + PI2)) && + !OUTBORD(vx, vy) && + !SOLID(S(fast_f2nat(vx), + fast_f2nat(vy)))) // try to avoid tracing ray if outside + // of frustrum + { + float ray = i * 8 / (float)NUMRAYS; + float dx, dy; + if (ray > 1 && ray < 3) { + dx = -(ray - 2); + dy = 1; + } else if (ray >= 3 && ray < 5) { + dx = -1; + dy = -(ray - 4); + } else if (ray >= 5 && ray < 7) { + dx = ray - 6; + dy = -1; + } else { + dx = 1; + dy = ray > 4 ? ray - 8 : ray; + }; + float sx = vx; + float sy = vy; + for (;;) { + sx += dx; + sy += dy; + if (SOLID(S(fast_f2nat(sx), + fast_f2nat( + sy)))) // 90% of time spend in this + // function is on this line + { + rdist[i] = (float)(fabs(sx - vx) + + fabs(sy - vy)); + break; + }; + }; + } else { + rdist[i] = 2; + }; + }; +}; + +// test occlusion for a cube... one of the most computationally expensive +// functions in the engine as its done for every cube and entity, but its effect +// is more than worth it! + +inline float +ca(float x, float y) +{ + return x > y ? y / x : 2 - x / y; +}; +inline float +ma(float x, float y) +{ + return x == 0 ? (y > 0 ? 2 : -2) : y / x; +}; + +int +isoccluded(float vx, float vy, float cx, float cy, + float csize) // v = viewer, c = cube to test +{ + if (!ocull) + return 0; + + float + nx = vx, + ny = vy; // n = point on the border of the cube that is closest to v + if (nx < cx) + nx = cx; + else if (nx > cx + csize) + nx = cx + csize; + if (ny < cy) + ny = cy; + else if (ny > cy + csize) + ny = cy + csize; + float xdist = (float)fabs(nx - vx); + float ydist = (float)fabs(ny - vy); + if (xdist > odist || ydist > odist) + return 2; + float dist = xdist + ydist - 1; // 1 needed? + + // ABC + // D E + // FGH + + // - check middle cube? BG + + // find highest and lowest angle in the occlusion map that this cube + // spans, based on its most left and right points on the border from the + // viewer pov... I see no easier way to do this than this silly code + // below + + float h, l; + if (cx <= vx) // ABDFG + { + if (cx + csize < vx) // ADF + { + if (cy <= vy) // AD + { + if (cy + csize < vy) { + h = ca(-(cx - vx), -(cy + csize - vy)) + + 4; + l = ca(-(cx + csize - vx), -(cy - vy)) + + 4; + } // A + else { + h = ma(-(cx + csize - vx), + -(cy + csize - vy)) + + 4; + l = ma(-(cx + csize - vx), -(cy - vy)) + + 4; + } // D + } else { + h = ca(cy + csize - vy, -(cx + csize - vx)) + 2; + l = ca(cy - vy, -(cx - vx)) + 2; + }; // F + } else // BG + { + if (cy <= vy) { + if (cy + csize < vy) { + h = ma(-(cy + csize - vy), cx - vx) + 6; + l = ma(-(cy + csize - vy), + cx + csize - vx) + + 6; + } // B + else + return 0; + } else { + h = ma(cy - vy, -(cx + csize - vx)) + 2; + l = ma(cy - vy, -(cx - vx)) + 2; + }; // G + }; + } else // CEH + { + if (cy <= vy) // CE + { + if (cy + csize < vy) { + h = ca(-(cy - vy), cx - vx) + 6; + l = ca(-(cy + csize - vy), cx + csize - vx) + 6; + } // C + else { + h = ma(cx - vx, cy - vy); + l = ma(cx - vx, cy + csize - vy); + }; // E + } else { + h = ca(cx + csize - vx, cy - vy); + l = ca(cx - vx, cy + csize - vy); + }; // H + }; + int si = fast_f2nat(h * (NUMRAYS / 8)) + + NUMRAYS; // get indexes into occlusion map from angles + int ei = fast_f2nat(l * (NUMRAYS / 8)) + NUMRAYS + 1; + if (ei <= si) + ei += NUMRAYS; + + for (int i = si; i <= ei; i++) { + if (dist < rdist[i & (NUMRAYS - 1)]) + return 0; // if any value in this segment of the + // occlusion map is further away then cube is + // not occluded + }; + + return 1; // cube is entirely occluded +}; DELETED src/worldrender.cxx Index: src/worldrender.cxx ================================================================== --- src/worldrender.cxx +++ /dev/null @@ -1,358 +0,0 @@ -// worldrender.cpp: goes through all cubes in top down quad tree fashion, -// determines what has to be rendered and how (depending on neighbouring cubes), -// then calls functions in rendercubes.cpp - -#include "cube.h" - -void -render_wall(sqr *o, sqr *s, int x1, int y1, int x2, int y2, int mip, sqr *d1, - sqr *d2, bool topleft) -{ - if (SOLID(o) || o->type == SEMISOLID) { - float c1 = s->floor; - float c2 = s->floor; - if (s->type == FHF) { - c1 -= d1->vdelta / 4.0f; - c2 -= d2->vdelta / 4.0f; - }; - float f1 = s->ceil; - float f2 = s->ceil; - if (s->type == CHF) { - f1 += d1->vdelta / 4.0f; - f2 += d2->vdelta / 4.0f; - }; - // if(f1-c1<=0 && f2-c2<=0) return; - render_square(o->wtex, c1, c2, f1, f2, x1 << mip, y1 << mip, - x2 << mip, y2 << mip, 1 << mip, d1, d2, topleft); - return; - }; - { - float f1 = s->floor; - float f2 = s->floor; - float c1 = o->floor; - float c2 = o->floor; - if (o->type == FHF && s->type != FHF) { - c1 -= d1->vdelta / 4.0f; - c2 -= d2->vdelta / 4.0f; - } - if (s->type == FHF && o->type != FHF) { - f1 -= d1->vdelta / 4.0f; - f2 -= d2->vdelta / 4.0f; - } - if (f1 >= c1 && f2 >= c2) - goto skip; - render_square(o->wtex, f1, f2, c1, c2, x1 << mip, y1 << mip, - x2 << mip, y2 << mip, 1 << mip, d1, d2, topleft); - }; -skip: { - float f1 = o->ceil; - float f2 = o->ceil; - float c1 = s->ceil; - float c2 = s->ceil; - if (o->type == CHF && s->type != CHF) { - f1 += d1->vdelta / 4.0f; - f2 += d2->vdelta / 4.0f; - } else if (s->type == CHF && o->type != CHF) { - c1 += d1->vdelta / 4.0f; - c2 += d2->vdelta / 4.0f; - } - if (c1 <= f1 && c2 <= f2) - return; - render_square(o->utex, f1, f2, c1, c2, x1 << mip, y1 << mip, x2 << mip, - y2 << mip, 1 << mip, d1, d2, topleft); -}; -}; - -const int MAX_MIP = 5; // 32x32 unit blocks -const int MIN_LOD = 2; -const int LOW_LOD = 25; -const int MAX_LOD = 1000; - -int lod = 40, lodtop, lodbot, lodleft, lodright; -int min_lod; - -int stats[LARGEST_FACTOR]; - -// detect those cases where a higher mip solid has a visible wall next to lower -// mip cubes (used for wall rendering below) - -bool -issemi(int mip, int x, int y, int x1, int y1, int x2, int y2) -{ - if (!(mip--)) - return true; - sqr *w = wmip[mip]; - int msize = ssize >> mip; - x *= 2; - y *= 2; - switch (SWS(w, x + x1, y + y1, msize)->type) { - case SEMISOLID: - if (issemi(mip, x + x1, y + y1, x1, y1, x2, y2)) - return true; - case CORNER: - case SOLID: - break; - default: - return true; - }; - switch (SWS(w, x + x2, y + y2, msize)->type) { - case SEMISOLID: - if (issemi(mip, x + x2, y + y2, x1, y1, x2, y2)) - return true; - case CORNER: - case SOLID: - break; - default: - return true; - }; - return false; -}; - -bool render_floor, render_ceil; - -// the core recursive function, renders a rect of cubes at a certain mip level -// from a viewer perspective call itself for lower mip levels, on most modern -// machines however this function will use the higher mip levels only for -// perfect mips. - -void -render_seg_new( - float vx, float vy, float vh, int mip, int x, int y, int xs, int ys) -{ - sqr *w = wmip[mip]; - int sz = ssize >> mip; - int vxx = ((int)vx + (1 << mip) / 2) >> mip; - int vyy = ((int)vy + (1 << mip) / 2) >> mip; - int lx = - vxx - lodleft; // these mark the rect inside the current rest that - // we want to render using a lower mip level - int ly = vyy - lodtop; - int rx = vxx + lodright; - int ry = vyy + lodbot; - - float fsize = (float)(1 << mip); - for (int ox = x; ox < xs; ox++) - for (int oy = y; oy < ys; - oy++) // first collect occlusion information for this block - { - SWS(w, ox, oy, sz)->occluded = - isoccluded(player1->o.x, player1->o.y, - (float)(ox << mip), (float)(oy << mip), fsize); - }; - - int pvx = (int)vx >> mip; - int pvy = (int)vy >> mip; - if (pvx >= 0 && pvy >= 0 && pvx < sz && pvy < sz) { - // SWS(w,vxx,vyy,sz)->occluded = 0; - SWS(w, pvx, pvy, sz)->occluded = - 0; // player cell never occluded - }; - -#define df(x) s->floor - (x->vdelta / 4.0f) -#define dc(x) s->ceil + (x->vdelta / 4.0f) - - // loop through the rect 3 times (for floor/ceil/walls seperately, to - // facilitate dynamic stripify) for each we skip occluded cubes - // (occlusion at higher mip levels is a big time saver!). during the - // first loop (ceil) we collect cubes that lie within the lower mip rect - // and are also deferred, and render them recursively. Anything left - // (perfect mips and higher lods) we render here. - -#define LOOPH \ - { \ - for (int xx = x; xx < xs; xx++) \ - for (int yy = y; yy < ys; yy++) { \ - sqr *s = SWS(w, xx, yy, sz); \ - if (s->occluded == 1) \ - continue; \ - if (s->defer && !s->occluded && mip && \ - xx >= lx && xx < rx && yy >= ly && \ - yy < ry) -#define LOOPD \ - sqr *t = SWS(s, 1, 0, sz); \ - sqr *u = SWS(s, 1, 1, sz); \ - sqr *v = SWS(s, 0, 1, sz); - - LOOPH // ceils - { - int start = yy; - sqr *next; - while (yy < ys - 1 && (next = SWS(w, xx, yy + 1, sz))->defer && - !next->occluded) - yy++; // collect 2xN rect of lower mip - render_seg_new(vx, vy, vh, mip - 1, xx * 2, start * 2, - xx * 2 + 2, yy * 2 + 2); - continue; - }; - stats[mip]++; - LOOPD - if ((s->type == SPACE || s->type == FHF) && s->ceil >= vh && - render_ceil) - render_flat(s->ctex, xx << mip, yy << mip, 1 << mip, s->ceil, s, - t, u, v, true); - if (s->type == CHF) // if(s->ceil>=vh) - render_flatdelta(s->ctex, xx << mip, yy << mip, 1 << mip, dc(s), - dc(t), dc(u), dc(v), s, t, u, v, true); -} -} -; - -LOOPH continue; // floors -LOOPD -if ((s->type == SPACE || s->type == CHF) && s->floor <= vh && render_floor) { - render_flat(s->ftex, xx << mip, yy << mip, 1 << mip, s->floor, s, t, u, - v, false); - if (s->floor < hdr.waterlevel && !SOLID(s)) - addwaterquad(xx << mip, yy << mip, 1 << mip); -}; -if (s->type == FHF) { - render_flatdelta(s->ftex, xx << mip, yy << mip, 1 << mip, df(s), df(t), - df(u), df(v), s, t, u, v, false); - if (s->floor - s->vdelta / 4.0f < hdr.waterlevel && !SOLID(s)) - addwaterquad(xx << mip, yy << mip, 1 << mip); -}; -} -} -; - -LOOPH continue; // walls -LOOPD -// w -// zSt -// vu - -sqr *w = SWS(s, 0, -1, sz); -sqr *z = SWS(s, -1, 0, sz); -bool normalwall = true; - -if (s->type == CORNER) { - // cull also - bool topleft = true; - sqr *h1 = NULL; - sqr *h2 = NULL; - if (SOLID(z)) { - if (SOLID(w)) { - render_wall(w, h2 = s, xx + 1, yy, xx, yy + 1, mip, t, - v, false); - topleft = false; - } else if (SOLID(v)) { - render_wall(v, h2 = s, xx, yy, xx + 1, yy + 1, mip, s, - u, false); - }; - } else if (SOLID(t)) { - if (SOLID(w)) { - render_wall(w, h1 = s, xx + 1, yy + 1, xx, yy, mip, u, - s, false); - } else if (SOLID(v)) { - render_wall(v, h1 = s, xx, yy + 1, xx + 1, yy, mip, v, - t, false); - topleft = false; - }; - } else { - normalwall = false; - bool wv = w->ceil - w->floor < v->ceil - v->floor; - if (z->ceil - z->floor < t->ceil - t->floor) { - if (wv) { - render_wall(h1 = s, h2 = v, xx + 1, yy, xx, - yy + 1, mip, t, v, false); - topleft = false; - } else { - render_wall(h1 = s, h2 = w, xx, yy, xx + 1, - yy + 1, mip, s, u, false); - }; - } else { - if (wv) { - render_wall(h2 = s, h1 = v, xx + 1, yy + 1, xx, - yy, mip, u, s, false); - } else { - render_wall(h2 = s, h1 = w, xx, yy + 1, xx + 1, - yy, mip, v, t, false); - topleft = false; - }; - }; - }; - render_tris( - xx << mip, yy << mip, 1 << mip, topleft, h1, h2, s, t, u, v); -} - -if (normalwall) { - bool inner = xx != sz - 1 && yy != sz - 1; - - if (xx >= vxx && xx != 0 && yy != sz - 1 && !SOLID(z) && - (!SOLID(s) || z->type != CORNER) && - (z->type != SEMISOLID || issemi(mip, xx - 1, yy, 1, 0, 1, 1))) - render_wall(s, z, xx, yy, xx, yy + 1, mip, s, v, true); - if (xx <= vxx && inner && !SOLID(t) && - (!SOLID(s) || t->type != CORNER) && - (t->type != SEMISOLID || issemi(mip, xx + 1, yy, 0, 0, 0, 1))) - render_wall(s, t, xx + 1, yy, xx + 1, yy + 1, mip, t, u, false); - if (yy >= vyy && yy != 0 && xx != sz - 1 && !SOLID(w) && - (!SOLID(s) || w->type != CORNER) && - (w->type != SEMISOLID || issemi(mip, xx, yy - 1, 0, 1, 1, 1))) - render_wall(s, w, xx, yy, xx + 1, yy, mip, s, t, false); - if (yy <= vyy && inner && !SOLID(v) && - (!SOLID(s) || v->type != CORNER) && - (v->type != SEMISOLID || issemi(mip, xx, yy + 1, 0, 0, 1, 0))) - render_wall(s, v, xx, yy + 1, xx + 1, yy + 1, mip, v, u, true); -}; -} -} -; -} -; - -void -distlod(int &low, int &high, int angle, float widef) -{ - float f = 90.0f / lod / widef; - low = (int)((90 - angle) / f); - high = (int)(angle / f); - if (low < min_lod) - low = min_lod; - if (high < min_lod) - high = min_lod; -}; - -// does some out of date view frustrum optimisation that doesn't contribute much -// anymore - -void -render_world( - float vx, float vy, float vh, int yaw, int pitch, float fov, int w, int h) -{ - loopi(LARGEST_FACTOR) stats[i] = 0; - min_lod = MIN_LOD + abs(pitch) / 12; - yaw = 360 - yaw; - float widef = fov / 75.0f; - int cdist = abs(yaw % 90 - 45); - if (cdist < 7) // hack to avoid popup at high fovs at 45 yaw - { - min_lod = max(min_lod, - (int)(MIN_LOD + (10 - cdist) / 1.0f * - widef)); // less if lod worked better - widef = 1.0f; - }; - lod = MAX_LOD; - lodtop = lodbot = lodleft = lodright = min_lod; - if (yaw > 45 && yaw <= 135) { - lodleft = lod; - distlod(lodtop, lodbot, yaw - 45, widef); - } else if (yaw > 135 && yaw <= 225) { - lodbot = lod; - distlod(lodleft, lodright, yaw - 135, widef); - } else if (yaw > 225 && yaw <= 315) { - lodright = lod; - distlod(lodbot, lodtop, yaw - 225, widef); - } else { - lodtop = lod; - distlod( - lodright, lodleft, yaw <= 45 ? yaw + 45 : yaw - 315, widef); - }; - float hyfov = fov * h / w / 2; - render_floor = pitch < hyfov; - render_ceil = -pitch < hyfov; - - render_seg_new( - vx, vy, vh, MAX_MIP, 0, 0, ssize >> MAX_MIP, ssize >> MAX_MIP); - mipstats(stats[0], stats[1], stats[2]); -}; ADDED src/worldrender.mm Index: src/worldrender.mm ================================================================== --- /dev/null +++ src/worldrender.mm @@ -0,0 +1,358 @@ +// worldrender.cpp: goes through all cubes in top down quad tree fashion, +// determines what has to be rendered and how (depending on neighbouring cubes), +// then calls functions in rendercubes.cpp + +#include "cube.h" + +void +render_wall(sqr *o, sqr *s, int x1, int y1, int x2, int y2, int mip, sqr *d1, + sqr *d2, bool topleft) +{ + if (SOLID(o) || o->type == SEMISOLID) { + float c1 = s->floor; + float c2 = s->floor; + if (s->type == FHF) { + c1 -= d1->vdelta / 4.0f; + c2 -= d2->vdelta / 4.0f; + }; + float f1 = s->ceil; + float f2 = s->ceil; + if (s->type == CHF) { + f1 += d1->vdelta / 4.0f; + f2 += d2->vdelta / 4.0f; + }; + // if(f1-c1<=0 && f2-c2<=0) return; + render_square(o->wtex, c1, c2, f1, f2, x1 << mip, y1 << mip, + x2 << mip, y2 << mip, 1 << mip, d1, d2, topleft); + return; + }; + { + float f1 = s->floor; + float f2 = s->floor; + float c1 = o->floor; + float c2 = o->floor; + if (o->type == FHF && s->type != FHF) { + c1 -= d1->vdelta / 4.0f; + c2 -= d2->vdelta / 4.0f; + } + if (s->type == FHF && o->type != FHF) { + f1 -= d1->vdelta / 4.0f; + f2 -= d2->vdelta / 4.0f; + } + if (f1 >= c1 && f2 >= c2) + goto skip; + render_square(o->wtex, f1, f2, c1, c2, x1 << mip, y1 << mip, + x2 << mip, y2 << mip, 1 << mip, d1, d2, topleft); + }; +skip: { + float f1 = o->ceil; + float f2 = o->ceil; + float c1 = s->ceil; + float c2 = s->ceil; + if (o->type == CHF && s->type != CHF) { + f1 += d1->vdelta / 4.0f; + f2 += d2->vdelta / 4.0f; + } else if (s->type == CHF && o->type != CHF) { + c1 += d1->vdelta / 4.0f; + c2 += d2->vdelta / 4.0f; + } + if (c1 <= f1 && c2 <= f2) + return; + render_square(o->utex, f1, f2, c1, c2, x1 << mip, y1 << mip, x2 << mip, + y2 << mip, 1 << mip, d1, d2, topleft); +}; +}; + +const int MAX_MIP = 5; // 32x32 unit blocks +const int MIN_LOD = 2; +const int LOW_LOD = 25; +const int MAX_LOD = 1000; + +int lod = 40, lodtop, lodbot, lodleft, lodright; +int min_lod; + +int stats[LARGEST_FACTOR]; + +// detect those cases where a higher mip solid has a visible wall next to lower +// mip cubes (used for wall rendering below) + +bool +issemi(int mip, int x, int y, int x1, int y1, int x2, int y2) +{ + if (!(mip--)) + return true; + sqr *w = wmip[mip]; + int msize = ssize >> mip; + x *= 2; + y *= 2; + switch (SWS(w, x + x1, y + y1, msize)->type) { + case SEMISOLID: + if (issemi(mip, x + x1, y + y1, x1, y1, x2, y2)) + return true; + case CORNER: + case SOLID: + break; + default: + return true; + }; + switch (SWS(w, x + x2, y + y2, msize)->type) { + case SEMISOLID: + if (issemi(mip, x + x2, y + y2, x1, y1, x2, y2)) + return true; + case CORNER: + case SOLID: + break; + default: + return true; + }; + return false; +}; + +bool render_floor, render_ceil; + +// the core recursive function, renders a rect of cubes at a certain mip level +// from a viewer perspective call itself for lower mip levels, on most modern +// machines however this function will use the higher mip levels only for +// perfect mips. + +void +render_seg_new( + float vx, float vy, float vh, int mip, int x, int y, int xs, int ys) +{ + sqr *w = wmip[mip]; + int sz = ssize >> mip; + int vxx = ((int)vx + (1 << mip) / 2) >> mip; + int vyy = ((int)vy + (1 << mip) / 2) >> mip; + int lx = + vxx - lodleft; // these mark the rect inside the current rest that + // we want to render using a lower mip level + int ly = vyy - lodtop; + int rx = vxx + lodright; + int ry = vyy + lodbot; + + float fsize = (float)(1 << mip); + for (int ox = x; ox < xs; ox++) + for (int oy = y; oy < ys; + oy++) // first collect occlusion information for this block + { + SWS(w, ox, oy, sz)->occluded = + isoccluded(player1->o.x, player1->o.y, + (float)(ox << mip), (float)(oy << mip), fsize); + }; + + int pvx = (int)vx >> mip; + int pvy = (int)vy >> mip; + if (pvx >= 0 && pvy >= 0 && pvx < sz && pvy < sz) { + // SWS(w,vxx,vyy,sz)->occluded = 0; + SWS(w, pvx, pvy, sz)->occluded = + 0; // player cell never occluded + }; + +#define df(x) s->floor - (x->vdelta / 4.0f) +#define dc(x) s->ceil + (x->vdelta / 4.0f) + + // loop through the rect 3 times (for floor/ceil/walls seperately, to + // facilitate dynamic stripify) for each we skip occluded cubes + // (occlusion at higher mip levels is a big time saver!). during the + // first loop (ceil) we collect cubes that lie within the lower mip rect + // and are also deferred, and render them recursively. Anything left + // (perfect mips and higher lods) we render here. + +#define LOOPH \ + { \ + for (int xx = x; xx < xs; xx++) \ + for (int yy = y; yy < ys; yy++) { \ + sqr *s = SWS(w, xx, yy, sz); \ + if (s->occluded == 1) \ + continue; \ + if (s->defer && !s->occluded && mip && \ + xx >= lx && xx < rx && yy >= ly && \ + yy < ry) +#define LOOPD \ + sqr *t = SWS(s, 1, 0, sz); \ + sqr *u = SWS(s, 1, 1, sz); \ + sqr *v = SWS(s, 0, 1, sz); + + LOOPH // ceils + { + int start = yy; + sqr *next; + while (yy < ys - 1 && (next = SWS(w, xx, yy + 1, sz))->defer && + !next->occluded) + yy++; // collect 2xN rect of lower mip + render_seg_new(vx, vy, vh, mip - 1, xx * 2, start * 2, + xx * 2 + 2, yy * 2 + 2); + continue; + }; + stats[mip]++; + LOOPD + if ((s->type == SPACE || s->type == FHF) && s->ceil >= vh && + render_ceil) + render_flat(s->ctex, xx << mip, yy << mip, 1 << mip, s->ceil, s, + t, u, v, true); + if (s->type == CHF) // if(s->ceil>=vh) + render_flatdelta(s->ctex, xx << mip, yy << mip, 1 << mip, dc(s), + dc(t), dc(u), dc(v), s, t, u, v, true); +} +} +; + +LOOPH continue; // floors +LOOPD +if ((s->type == SPACE || s->type == CHF) && s->floor <= vh && render_floor) { + render_flat(s->ftex, xx << mip, yy << mip, 1 << mip, s->floor, s, t, u, + v, false); + if (s->floor < hdr.waterlevel && !SOLID(s)) + addwaterquad(xx << mip, yy << mip, 1 << mip); +}; +if (s->type == FHF) { + render_flatdelta(s->ftex, xx << mip, yy << mip, 1 << mip, df(s), df(t), + df(u), df(v), s, t, u, v, false); + if (s->floor - s->vdelta / 4.0f < hdr.waterlevel && !SOLID(s)) + addwaterquad(xx << mip, yy << mip, 1 << mip); +}; +} +} +; + +LOOPH continue; // walls +LOOPD +// w +// zSt +// vu + +sqr *w = SWS(s, 0, -1, sz); +sqr *z = SWS(s, -1, 0, sz); +bool normalwall = true; + +if (s->type == CORNER) { + // cull also + bool topleft = true; + sqr *h1 = NULL; + sqr *h2 = NULL; + if (SOLID(z)) { + if (SOLID(w)) { + render_wall(w, h2 = s, xx + 1, yy, xx, yy + 1, mip, t, + v, false); + topleft = false; + } else if (SOLID(v)) { + render_wall(v, h2 = s, xx, yy, xx + 1, yy + 1, mip, s, + u, false); + }; + } else if (SOLID(t)) { + if (SOLID(w)) { + render_wall(w, h1 = s, xx + 1, yy + 1, xx, yy, mip, u, + s, false); + } else if (SOLID(v)) { + render_wall(v, h1 = s, xx, yy + 1, xx + 1, yy, mip, v, + t, false); + topleft = false; + }; + } else { + normalwall = false; + bool wv = w->ceil - w->floor < v->ceil - v->floor; + if (z->ceil - z->floor < t->ceil - t->floor) { + if (wv) { + render_wall(h1 = s, h2 = v, xx + 1, yy, xx, + yy + 1, mip, t, v, false); + topleft = false; + } else { + render_wall(h1 = s, h2 = w, xx, yy, xx + 1, + yy + 1, mip, s, u, false); + }; + } else { + if (wv) { + render_wall(h2 = s, h1 = v, xx + 1, yy + 1, xx, + yy, mip, u, s, false); + } else { + render_wall(h2 = s, h1 = w, xx, yy + 1, xx + 1, + yy, mip, v, t, false); + topleft = false; + }; + }; + }; + render_tris( + xx << mip, yy << mip, 1 << mip, topleft, h1, h2, s, t, u, v); +} + +if (normalwall) { + bool inner = xx != sz - 1 && yy != sz - 1; + + if (xx >= vxx && xx != 0 && yy != sz - 1 && !SOLID(z) && + (!SOLID(s) || z->type != CORNER) && + (z->type != SEMISOLID || issemi(mip, xx - 1, yy, 1, 0, 1, 1))) + render_wall(s, z, xx, yy, xx, yy + 1, mip, s, v, true); + if (xx <= vxx && inner && !SOLID(t) && + (!SOLID(s) || t->type != CORNER) && + (t->type != SEMISOLID || issemi(mip, xx + 1, yy, 0, 0, 0, 1))) + render_wall(s, t, xx + 1, yy, xx + 1, yy + 1, mip, t, u, false); + if (yy >= vyy && yy != 0 && xx != sz - 1 && !SOLID(w) && + (!SOLID(s) || w->type != CORNER) && + (w->type != SEMISOLID || issemi(mip, xx, yy - 1, 0, 1, 1, 1))) + render_wall(s, w, xx, yy, xx + 1, yy, mip, s, t, false); + if (yy <= vyy && inner && !SOLID(v) && + (!SOLID(s) || v->type != CORNER) && + (v->type != SEMISOLID || issemi(mip, xx, yy + 1, 0, 0, 1, 0))) + render_wall(s, v, xx, yy + 1, xx + 1, yy + 1, mip, v, u, true); +}; +} +} +; +} +; + +void +distlod(int &low, int &high, int angle, float widef) +{ + float f = 90.0f / lod / widef; + low = (int)((90 - angle) / f); + high = (int)(angle / f); + if (low < min_lod) + low = min_lod; + if (high < min_lod) + high = min_lod; +}; + +// does some out of date view frustrum optimisation that doesn't contribute much +// anymore + +void +render_world( + float vx, float vy, float vh, int yaw, int pitch, float fov, int w, int h) +{ + loopi(LARGEST_FACTOR) stats[i] = 0; + min_lod = MIN_LOD + abs(pitch) / 12; + yaw = 360 - yaw; + float widef = fov / 75.0f; + int cdist = abs(yaw % 90 - 45); + if (cdist < 7) // hack to avoid popup at high fovs at 45 yaw + { + min_lod = max(min_lod, + (int)(MIN_LOD + (10 - cdist) / 1.0f * + widef)); // less if lod worked better + widef = 1.0f; + }; + lod = MAX_LOD; + lodtop = lodbot = lodleft = lodright = min_lod; + if (yaw > 45 && yaw <= 135) { + lodleft = lod; + distlod(lodtop, lodbot, yaw - 45, widef); + } else if (yaw > 135 && yaw <= 225) { + lodbot = lod; + distlod(lodleft, lodright, yaw - 135, widef); + } else if (yaw > 225 && yaw <= 315) { + lodright = lod; + distlod(lodbot, lodtop, yaw - 225, widef); + } else { + lodtop = lod; + distlod( + lodright, lodleft, yaw <= 45 ? yaw + 45 : yaw - 315, widef); + }; + float hyfov = fov * h / w / 2; + render_floor = pitch < hyfov; + render_ceil = -pitch < hyfov; + + render_seg_new( + vx, vy, vh, MAX_MIP, 0, 0, ssize >> MAX_MIP, ssize >> MAX_MIP); + mipstats(stats[0], stats[1], stats[2]); +};