Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -1,8 +1,9 @@ *.a *.dep *.o +*/.deps aclocal.m4 autom4te.cache buildsys.mk config.cfg config.log ADDED src/.clang-format Index: src/.clang-format ================================================================== --- /dev/null +++ src/.clang-format @@ -0,0 +1,6 @@ +IndentWidth: 8 +TabWidth: 8 +UseTab: ForIndentation +BreakBeforeBraces: Linux +AlwaysBreakAfterReturnType: AllDefinitions +AlignAfterOpenBracket: DontAlign Index: src/client.cxx ================================================================== --- src/client.cxx +++ src/client.cxx @@ -4,137 +4,169 @@ 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)); +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); +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 newname(char *name) { c2sinit = false; strn0cpy(player1->name, name, 16); }; -void newteam(char *name) { c2sinit = false; strn0cpy(player1->team, name, 5); }; +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); +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); }; +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); @@ -141,169 +173,210 @@ // 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 +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() +void +server_err() { - conoutf("server network error, disconnecting..."); - disconnect(); + 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; - } -}; - +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; + } +}; Index: src/clientextras.cxx ================================================================== --- src/clientextras.cxx +++ src/clientextras.cxx @@ -1,157 +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 = -((int)d&0xFFF); - if(d->state==CS_DEAD) - { - int r; - if(hellpig) { n = 2; r = range[3]; } else { n = (int)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); +// 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 = -((int)d & 0xFFF); + if (d->state == CS_DEAD) { + int r; + if (hellpig) { + n = 2; + r = range[3]; + } else { + n = (int)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() +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); + 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) +void +showscores(bool on) { - scoreson = on; - menuset(((int)on)-1); + scoreson = on; + menuset(((int)on) - 1); }; -struct sline { string s; }; +struct sline { + string s; +}; vector scorelines; -void renderscore(dynent *d) +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); + 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); - }; +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..."); +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); - Index: src/clientgame.cxx ================================================================== --- src/clientgame.cxx +++ src/clientgame.cxx @@ -1,19 +1,23 @@ // clientgame.cpp: core game related stuff #include "cube.h" -int nextmode = 0; // nextmode becomes gamemode after next map load +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); }; +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 +dynent *player1 = newdynent(); // our client +dvector players; // other clients VARP(sensitivity, 0, 10, 10000); VARP(sensitivityscale, 1, 1, 10000); VARP(invmouse, 0, 0, 1); @@ -21,435 +25,494 @@ 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; +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; +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() + 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++; - }; +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(arenarespawnwait0 && (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; +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 +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); }; +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) - { +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 + } 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; +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(); +#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(); }; +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); +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)); -}; +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); Index: src/clients2c.cxx ================================================================== --- src/clients2c.cxx +++ src/clients2c.cxx @@ -5,351 +5,385 @@ 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); +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(fxstate!=CS_DEAD) - { - if(fxo.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(po.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 = { ents[i].x, ents[i].y, 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; - }; +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 = {ents[i].x, ents[i].y, 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; + }; }; Index: src/command.cxx ================================================================== --- src/command.cxx +++ src/command.cxx @@ -1,363 +1,627 @@ -// command.cpp: implements the parsing and execution of a tiny script language which -// is largely backwards compatible with the quake console language. +// 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); - }; +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; ifun)(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(i1min || i1>id->max) - { - i1 = i1min ? 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; iaction); // 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; +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); +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 +// 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); +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); +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)(ab); }; 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); - +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); Index: src/console.cxx ================================================================== --- src/console.cxx +++ src/console.cxx @@ -1,254 +1,303 @@ // console.cpp: the console buffer, its display, and command line control #include "cube.h" #include -struct cline { char *cref; int outtime; }; +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) +void +setconskip(int n) { - conskip += n; - if(conskip<0) conskip = 0; + 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); - }; +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); +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); +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); }; +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 +#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= 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(histpos8bit quantity is a little indian - int headersize; // sizeof(header) - int sfactor; // in bits - int numents; - char maptitle[128]; - uchar texlists[3][256]; - int waterlevel; - int reserved[15]; -}; - -#define SWS(w,x,y,s) (&(w)[(y)*(s)+(x)]) -#define SW(w,x,y) SWS(w,x,y,ssize) -#define S(x,y) SW(world,x,y) // convenient lookup of a lowest mip cube -#define SMALLEST_FACTOR 6 // determines number of mips there can be -#define DEFAULT_FACTOR 8 -#define LARGEST_FACTOR 11 // 10 is already insane -#define SOLID(x) ((x)->type==SOLID) -#define MINBORD 2 // 2 cubes from the edge of the world are always solid -#define OUTBORD(x,y) ((x)=ssize-MINBORD || (y)>=ssize-MINBORD) - -struct vec { float x, y, z; }; -struct block { int x, y, xs, ys; }; -struct mapmodelinfo { int rad, h, zoff, snap; char *name; }; - -enum { GUN_FIST = 0, GUN_SG, GUN_CG, GUN_RL, GUN_RIFLE, GUN_FIREBALL, GUN_ICEBALL, GUN_SLIMEBALL, GUN_BITE, NUMGUNS }; - -struct dynent // players & monsters -{ - vec o, vel; // origin, velocity - float yaw, pitch, roll; // used as vec in one place - float maxspeed; // cubes per second, 24 for player - bool outsidemap; // from his eyes - bool inwater; - bool onfloor, jumpnext; - int move, strafe; - bool k_left, k_right, k_up, k_down; // see input code - int timeinair; // used for fake gravity - float radius, eyeheight, aboveeye; // bounding box size - int lastupdate, plag, ping; - int lifesequence; // sequence id for each respawn, used in damage test - int state; // one of CS_* below - int frags; - int health, armour, armourtype, quadmillis; - int gunselect, gunwait; - int lastaction, lastattackgun, lastmove; - bool attacking; - int ammo[NUMGUNS]; - int monsterstate; // one of M_* below, M_NONE means human - int mtype; // see monster.cpp - dynent *enemy; // monster wants to kill this entity - float targetyaw; // monster wants to look in this direction - bool blocked, moving; // used by physics to signal ai - int trigger; // millis at which transition to another monsterstate takes place - vec attacktarget; // delayed attacks - int anger; // how many times already hit by fellow monster - string name, team; -}; - -#define SAVEGAMEVERSION 4 // bump if dynent/netprotocol changes or any other savegame/demo data - -enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off -enum { M_NONE = 0, M_SEARCH, M_HOME, M_ATTACKING, M_PAIN, M_SLEEP, M_AIMING }; // monster states - -#define MAXCLIENTS 256 // in a multiplayer game, can be arbitrarily changed -#define MAXTRANS 5000 // max amount of data to swallow in 1 go -#define CUBE_SERVER_PORT 28765 -#define CUBE_SERVINFO_PORT 28766 -#define PROTOCOL_VERSION 122 // bump when protocol changes - -// network messages codes, c2s, c2c, s2c -enum -{ - SV_INITS2C, SV_INITC2S, SV_POS, SV_TEXT, SV_SOUND, SV_CDIS, - SV_DIED, SV_DAMAGE, SV_SHOT, SV_FRAGS, - SV_TIMEUP, SV_EDITENT, SV_MAPRELOAD, SV_ITEMACC, - SV_MAPCHANGE, SV_ITEMSPAWN, SV_ITEMPICKUP, SV_DENIED, - SV_PING, SV_PONG, SV_CLIENTPING, SV_GAMEMODE, - SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, - SV_SENDMAP, SV_RECVMAP, SV_SERVMSG, SV_ITEMLIST, - SV_EXT, -}; +#include "tools.h" + +enum // block types, order matters! +{ + SOLID = 0, // entirely solid cube [only specifies wtex] + CORNER, // half full corner of a wall + FHF, // floor heightfield using neighbour vdelta values + CHF, // idem ceiling + SPACE, // entirely empty cube + SEMISOLID, // generated by mipmapping + MAXTYPE +}; + +struct sqr { + uchar type; // one of the above + char floor, ceil; // height, in cubes + uchar wtex, ftex, ctex; // wall/floor/ceil texture ids + uchar r, g, b; // light value at upper left vertex + uchar vdelta; // vertex delta, used for heightfield cubes + char defer; // used in mipmapping, when true this cube is not a perfect + // mip + char occluded; // true when occluded + uchar utex; // upper wall tex id + uchar tag; // used by triggers +}; + +enum // hardcoded texture numbers +{ + DEFAULT_SKY = 0, + DEFAULT_LIQUID, + DEFAULT_WALL, + DEFAULT_FLOOR, + DEFAULT_CEIL +}; + +enum // static entity types +{ + NOTUSED = 0, // entity slot not in use in map + LIGHT, // lightsource, attr1 = radius, attr2 = intensity + PLAYERSTART, // attr1 = angle + I_SHELLS, + I_BULLETS, + I_ROCKETS, + I_ROUNDS, + I_HEALTH, + I_BOOST, + I_GREENARMOUR, + I_YELLOWARMOUR, + I_QUAD, + TELEPORT, // attr1 = idx + TELEDEST, // attr1 = angle, attr2 = idx + MAPMODEL, // attr1 = angle, attr2 = idx + MONSTER, // attr1 = angle, attr2 = monstertype + CARROT, // attr1 = tag, attr2 = type + JUMPPAD, // attr1 = zpush, attr2 = ypush, attr3 = xpush + MAXENTTYPES +}; + +struct persistent_entity // map entity +{ + short x, y, z; // cube aligned position + short attr1; + uchar type; // type is one of the above + uchar attr2, attr3, attr4; +}; + +struct entity : public persistent_entity { + bool spawned; // the only dynamic state of a map entity +}; + +#define MAPVERSION 5 // bump if map format changes, see worldio.cpp + +struct header // map file format header +{ + char head[4]; // "CUBE" + int version; // any >8bit quantity is a little indian + int headersize; // sizeof(header) + int sfactor; // in bits + int numents; + char maptitle[128]; + uchar texlists[3][256]; + int waterlevel; + int reserved[15]; +}; + +#define SWS(w, x, y, s) (&(w)[(y) * (s) + (x)]) +#define SW(w, x, y) SWS(w, x, y, ssize) +#define S(x, y) SW(world, x, y) // convenient lookup of a lowest mip cube +#define SMALLEST_FACTOR 6 // determines number of mips there can be +#define DEFAULT_FACTOR 8 +#define LARGEST_FACTOR 11 // 10 is already insane +#define SOLID(x) ((x)->type == SOLID) +#define MINBORD 2 // 2 cubes from the edge of the world are always solid +#define OUTBORD(x, y) \ + ((x) < MINBORD || (y) < MINBORD || (x) >= ssize - MINBORD || \ + (y) >= ssize - MINBORD) + +struct vec { + float x, y, z; +}; +struct block { + int x, y, xs, ys; +}; +struct mapmodelinfo { + int rad, h, zoff, snap; + char *name; +}; + +enum { + GUN_FIST = 0, + GUN_SG, + GUN_CG, + GUN_RL, + GUN_RIFLE, + GUN_FIREBALL, + GUN_ICEBALL, + GUN_SLIMEBALL, + GUN_BITE, + NUMGUNS +}; + +struct dynent // players & monsters +{ + vec o, vel; // origin, velocity + float yaw, pitch, roll; // used as vec in one place + float maxspeed; // cubes per second, 24 for player + bool outsidemap; // from his eyes + bool inwater; + bool onfloor, jumpnext; + int move, strafe; + bool k_left, k_right, k_up, k_down; // see input code + int timeinair; // used for fake gravity + float radius, eyeheight, aboveeye; // bounding box size + int lastupdate, plag, ping; + int lifesequence; // sequence id for each respawn, used in damage test + int state; // one of CS_* below + int frags; + int health, armour, armourtype, quadmillis; + int gunselect, gunwait; + int lastaction, lastattackgun, lastmove; + bool attacking; + int ammo[NUMGUNS]; + int monsterstate; // one of M_* below, M_NONE means human + int mtype; // see monster.cpp + dynent *enemy; // monster wants to kill this entity + float targetyaw; // monster wants to look in this direction + bool blocked, moving; // used by physics to signal ai + int trigger; // millis at which transition to another monsterstate takes + // place + vec attacktarget; // delayed attacks + int anger; // how many times already hit by fellow monster + string name, team; +}; + +#define SAVEGAMEVERSION \ + 4 // bump if dynent/netprotocol changes or any other savegame/demo data + +enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off +enum { + M_NONE = 0, + M_SEARCH, + M_HOME, + M_ATTACKING, + M_PAIN, + M_SLEEP, + M_AIMING +}; // monster states + +#define MAXCLIENTS 256 // in a multiplayer game, can be arbitrarily changed +#define MAXTRANS 5000 // max amount of data to swallow in 1 go +#define CUBE_SERVER_PORT 28765 +#define CUBE_SERVINFO_PORT 28766 +#define PROTOCOL_VERSION 122 // bump when protocol changes + +// network messages codes, c2s, c2c, s2c +enum { + SV_INITS2C, + SV_INITC2S, + SV_POS, + SV_TEXT, + SV_SOUND, + SV_CDIS, + SV_DIED, + SV_DAMAGE, + SV_SHOT, + SV_FRAGS, + SV_TIMEUP, + SV_EDITENT, + SV_MAPRELOAD, + SV_ITEMACC, + SV_MAPCHANGE, + SV_ITEMSPAWN, + SV_ITEMPICKUP, + SV_DENIED, + SV_PING, + SV_PONG, + SV_CLIENTPING, + SV_GAMEMODE, + SV_EDITH, + SV_EDITT, + SV_EDITS, + SV_EDITD, + SV_EDITE, + SV_SENDMAP, + SV_RECVMAP, + SV_SERVMSG, + SV_ITEMLIST, + SV_EXT, +}; enum { CS_ALIVE = 0, CS_DEAD, CS_LAGGED, CS_EDITING }; // hardcoded sounds, defined in sounds.cfg -enum -{ - S_JUMP = 0, S_LAND, S_RIFLE, S_PUNCH1, S_SG, S_CG, - S_RLFIRE, S_RLHIT, S_WEAPLOAD, S_ITEMAMMO, S_ITEMHEALTH, - S_ITEMARMOUR, S_ITEMPUP, S_ITEMSPAWN, S_TELEPORT, S_NOAMMO, S_PUPOUT, - S_PAIN1, S_PAIN2, S_PAIN3, S_PAIN4, S_PAIN5, S_PAIN6, - S_DIE1, S_DIE2, - S_FLAUNCH, S_FEXPLODE, - S_SPLASH1, S_SPLASH2, - S_GRUNT1, S_GRUNT2, S_RUMBLE, - S_PAINO, - S_PAINR, S_DEATHR, - S_PAINE, S_DEATHE, - S_PAINS, S_DEATHS, - S_PAINB, S_DEATHB, - S_PAINP, S_PIGGR2, - S_PAINH, S_DEATHH, - S_PAIND, S_DEATHD, - S_PIGR1, S_ICEBALL, S_SLIMEBALL, - S_JUMPPAD, +enum { + S_JUMP = 0, + S_LAND, + S_RIFLE, + S_PUNCH1, + S_SG, + S_CG, + S_RLFIRE, + S_RLHIT, + S_WEAPLOAD, + S_ITEMAMMO, + S_ITEMHEALTH, + S_ITEMARMOUR, + S_ITEMPUP, + S_ITEMSPAWN, + S_TELEPORT, + S_NOAMMO, + S_PUPOUT, + S_PAIN1, + S_PAIN2, + S_PAIN3, + S_PAIN4, + S_PAIN5, + S_PAIN6, + S_DIE1, + S_DIE2, + S_FLAUNCH, + S_FEXPLODE, + S_SPLASH1, + S_SPLASH2, + S_GRUNT1, + S_GRUNT2, + S_RUMBLE, + S_PAINO, + S_PAINR, + S_DEATHR, + S_PAINE, + S_DEATHE, + S_PAINS, + S_DEATHS, + S_PAINB, + S_DEATHB, + S_PAINP, + S_PIGGR2, + S_PAINH, + S_DEATHH, + S_PAIND, + S_DEATHD, + S_PIGR1, + S_ICEBALL, + S_SLIMEBALL, + S_JUMPPAD, }; // vertex array format -struct vertex { float u, v, x, y, z; uchar r, g, b, a; }; +struct vertex { + float u, v, x, y, z; + uchar r, g, b, a; +}; typedef vector dvector; typedef vector cvector; typedef vector ivector; // globals ooh naughty -extern sqr *world, *wmip[]; // map data, the mips are sequential 2D arrays in memory -extern header hdr; // current map header -extern int sfactor, ssize; // ssize = 2^sfactor -extern int cubicsize, mipsize; // cubicsize = ssize^2 -extern dynent *player1; // special client ent that receives input and acts as camera -extern dvector players; // all the other clients (in multiplayer) +extern sqr *world, + *wmip[]; // map data, the mips are sequential 2D arrays in memory +extern header hdr; // current map header +extern int sfactor, ssize; // ssize = 2^sfactor +extern int cubicsize, mipsize; // cubicsize = ssize^2 +extern dynent + *player1; // special client ent that receives input and acts as camera +extern dvector players; // all the other clients (in multiplayer) extern bool editmode; -extern vector ents; // map entities -extern vec worldpos; // current target of the crosshair in the world -extern int lastmillis; // last time -extern int curtime; // current frame time +extern vector ents; // map entities +extern vec worldpos; // current target of the crosshair in the world +extern int lastmillis; // last time +extern int curtime; // current frame time extern int gamemode, nextmode; extern int xtraverts; extern bool demoplayback; - -#define DMF 16.0f -#define DAF 1.0f +#define DMF 16.0f +#define DAF 1.0f #define DVF 100.0f -#define VIRTW 2400 // virtual screen size for text & HUD +#define VIRTW 2400 // virtual screen size for text & HUD #define VIRTH 1800 #define FONTH 64 -#define PIXELTAB (VIRTW/12) +#define PIXELTAB (VIRTW / 12) -#define PI (3.1415927f) -#define PI2 (2*PI) +#define PI (3.1415927f) +#define PI2 (2 * PI) // simplistic vector ops -#define dotprod(u,v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z) -#define vmul(u,f) { (u).x *= (f); (u).y *= (f); (u).z *= (f); } -#define vdiv(u,f) { (u).x /= (f); (u).y /= (f); (u).z /= (f); } -#define vadd(u,v) { (u).x += (v).x; (u).y += (v).y; (u).z += (v).z; }; -#define vsub(u,v) { (u).x -= (v).x; (u).y -= (v).y; (u).z -= (v).z; }; -#define vdist(d,v,e,s) vec v = s; vsub(v,e); float d = (float)sqrt(dotprod(v,v)); -#define vreject(v,u,max) ((v).x>(u).x+(max) || (v).x<(u).x-(max) || (v).y>(u).y+(max) || (v).y<(u).y-(max)) -#define vlinterp(v,f,u,g) { (v).x = (v).x*f+(u).x*g; (v).y = (v).y*f+(u).y*g; (v).z = (v).z*f+(u).z*g; } - -#define sgetstr() { char *t = text; do { *t = getint(p); } while(*t++); } // used by networking - -#define m_noitems (gamemode>=4) -#define m_noitemsrail (gamemode<=5) -#define m_arena (gamemode>=8) -#define m_tarena (gamemode>=10) -#define m_teammode (gamemode&1 && gamemode>2) -#define m_sp (gamemode<0) -#define m_dmsp (gamemode==-1) -#define m_classicsp (gamemode==-2) -#define isteam(a,b) (m_teammode && strcmp(a, b)==0) - -enum // function signatures for script functions, see command.cpp -{ - ARG_1INT, ARG_2INT, ARG_3INT, ARG_4INT, - ARG_NONE, - ARG_1STR, ARG_2STR, ARG_3STR, ARG_5STR, - ARG_DOWN, ARG_DWN1, - ARG_1EXP, ARG_2EXP, - ARG_1EST, ARG_2EST, - ARG_VARI -}; - -// nasty macros for registering script functions, abuses globals to avoid excessive infrastructure -#define COMMANDN(name, fun, nargs) static bool __dummy_##fun = addcommand(#name, (void (*)())fun, nargs) -#define COMMAND(name, nargs) COMMANDN(name, name, nargs) -#define VARP(name, min, cur, max) int name = variable(#name, min, cur, max, &name, NULL, true) -#define VAR(name, min, cur, max) int name = variable(#name, min, cur, max, &name, NULL, false) -#define VARF(name, min, cur, max, body) void var_##name(); static int name = variable(#name, min, cur, max, &name, var_##name, false); void var_##name() { body; } -#define VARFP(name, min, cur, max, body) void var_##name(); static int name = variable(#name, min, cur, max, &name, var_##name, true); void var_##name() { body; } - -#define ATOI(s) strtol(s, NULL, 0) // supports hexadecimal numbers +#define dotprod(u, v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z) +#define vmul(u, f) \ + { \ + (u).x *= (f); \ + (u).y *= (f); \ + (u).z *= (f); \ + } +#define vdiv(u, f) \ + { \ + (u).x /= (f); \ + (u).y /= (f); \ + (u).z /= (f); \ + } +#define vadd(u, v) \ + { \ + (u).x += (v).x; \ + (u).y += (v).y; \ + (u).z += (v).z; \ + }; +#define vsub(u, v) \ + { \ + (u).x -= (v).x; \ + (u).y -= (v).y; \ + (u).z -= (v).z; \ + }; +#define vdist(d, v, e, s) \ + vec v = s; \ + vsub(v, e); \ + float d = (float)sqrt(dotprod(v, v)); +#define vreject(v, u, max) \ + ((v).x > (u).x + (max) || (v).x < (u).x - (max) || \ + (v).y > (u).y + (max) || (v).y < (u).y - (max)) +#define vlinterp(v, f, u, g) \ + { \ + (v).x = (v).x * f + (u).x * g; \ + (v).y = (v).y * f + (u).y * g; \ + (v).z = (v).z * f + (u).z * g; \ + } + +#define sgetstr() \ + { \ + char *t = text; \ + do { \ + *t = getint(p); \ + } while (*t++); \ + } // used by networking + +#define m_noitems (gamemode >= 4) +#define m_noitemsrail (gamemode <= 5) +#define m_arena (gamemode >= 8) +#define m_tarena (gamemode >= 10) +#define m_teammode (gamemode & 1 && gamemode > 2) +#define m_sp (gamemode < 0) +#define m_dmsp (gamemode == -1) +#define m_classicsp (gamemode == -2) +#define isteam(a, b) (m_teammode && strcmp(a, b) == 0) + +enum // function signatures for script functions, see command.cpp +{ + ARG_1INT, + ARG_2INT, + ARG_3INT, + ARG_4INT, + ARG_NONE, + ARG_1STR, + ARG_2STR, + ARG_3STR, + ARG_5STR, + ARG_DOWN, + ARG_DWN1, + ARG_1EXP, + ARG_2EXP, + ARG_1EST, + ARG_2EST, + ARG_VARI +}; + +// nasty macros for registering script functions, abuses globals to avoid +// excessive infrastructure +#define COMMANDN(name, fun, nargs) \ + static bool __dummy_##fun = addcommand(#name, (void (*)())fun, nargs) +#define COMMAND(name, nargs) COMMANDN(name, name, nargs) +#define VARP(name, min, cur, max) \ + int name = variable(#name, min, cur, max, &name, NULL, true) +#define VAR(name, min, cur, max) \ + int name = variable(#name, min, cur, max, &name, NULL, false) +#define VARF(name, min, cur, max, body) \ + void var_##name(); \ + static int name = \ + variable(#name, min, cur, max, &name, var_##name, false); \ + void var_##name() { body; } +#define VARFP(name, min, cur, max, body) \ + void var_##name(); \ + static int name = \ + variable(#name, min, cur, max, &name, var_##name, true); \ + void var_##name() { body; } + +#define ATOI(s) strtol(s, NULL, 0) // supports hexadecimal numbers #ifdef WIN32 - #define WIN32_LEAN_AND_MEAN - #include "windows.h" - #define _WINDOWS - #define ZLIB_DLL +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#define _WINDOWS +#define ZLIB_DLL #else - #include +#include #endif #include #include -#include #include +#include #include #include #include #include -#include "protos.h" // external function decls - +#include "protos.h" // external function decls Index: src/editing.cxx ================================================================== --- src/editing.cxx +++ src/editing.cxx @@ -1,475 +1,607 @@ -// editing.cpp: most map editing commands go here, entity editing commands are in world.cpp +// editing.cpp: most map editing commands go here, entity editing commands are +// in world.cpp #include "cube.h" -bool editmode = false; +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 +// 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), +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); } +#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 }; +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; +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); +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(); +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; - }); +// 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) +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); + 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); }; +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->floorfloor; - 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 +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) +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); + 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) +void +setvdeltaxy(int delta, block &sel) { - loopselxy(s->vdelta = max(s->vdelta+delta, 0)); - remipmore(sel); + loopselxy(s->vdelta = max(s->vdelta + delta, 0)); + remipmore(sel); }; -void setvdelta(int delta) +void +setvdelta(int delta) { - EDITSEL; - setvdeltaxy(delta, sel); - addmsg(1, 6, SV_EDITD, sel.x, sel.y, sel.xs, sel.ys, 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)); +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); @@ -481,7 +613,5 @@ COMMAND(copy, ARG_NONE); COMMAND(paste, ARG_NONE); COMMAND(edittex, ARG_2INT); COMMAND(newent, ARG_5STR); COMMAND(perlin, ARG_3INT); - - Index: src/entities.cxx ================================================================== --- src/entities.cxx +++ src/entities.cxx @@ -2,241 +2,357 @@ #include "cube.h" vector ents; -char *entmdlnames[] = -{ - "shells", "bullets", "rockets", "rrounds", "health", "boost", - "g_armour", "y_armour", "quad", "teleporter", +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) +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); + 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.typeTELEPORT) 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) - { +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)); + + 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; - - 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; }; + }; + }; + }; +}; + +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; - }; +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(vo.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 = { e.x, e.y, 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; }; +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 = {e.x, e.y, 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; +}; Index: src/main.cxx ================================================================== --- src/main.cxx +++ src/main.cxx @@ -1,237 +1,291 @@ // main.cpp: initialisation & main loop #include "cube.h" -void cleanup(char *msg) // single program exit point; +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; + 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; idxpixels+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); - }; +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) +void +keyrepeat(bool on) { - SDL_EnableKeyRepeat(on ? SDL_DEFAULT_REPEAT_DELAY : 0, - SDL_DEFAULT_REPEAT_INTERVAL); + SDL_EnableKeyRepeat( + on ? SDL_DEFAULT_REPEAT_DELAY : 0, SDL_DEFAULT_REPEAT_INTERVAL); }; -VARF(gamespeed, 10, 100, 1000, if(multiplayer()) gamespeed = 100); +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; i200) lastmillis = millis-200; - else if(millis-lastmillis<1) lastmillis = millis-1; - if(millis-lastmilliso.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. - { +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; -}; - - + }; + 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; +}; Index: src/menus.cxx ================================================================== --- src/menus.cxx +++ src/menus.cxx @@ -1,145 +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; +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= 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; +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; +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; }; Index: src/monster.cxx ================================================================== --- src/monster.cxx +++ src/monster.cxx @@ -5,334 +5,405 @@ 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 +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(rzceil) 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->yawyaw += 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->targetyawyaw) 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->triggermonsterstate!=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->triggeryaw); - 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->triggerlastaction = 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->triggerenemy->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 = { e.x, e.y, S(e.x, e.y)->floor }; - loopv(monsters) if(monsters[i]->state==CS_DEAD) - { - if(lastmillis-monsters[i]->lastaction<2000) - { +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 = {e.x, e.y, 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); + } + 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); }; Index: src/physics.cxx ================================================================== --- src/physics.cxx +++ src/physics.cxx @@ -1,323 +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). +// 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)o.y-d->o.y)o.z-d->eyeheighto.z-o->eyeheight) { if(o->o.z-o->eyeheighto.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)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<radius; - if(fabs(e.x-d->o.x)o.y)floor+mmi.zoff+e.attr3); - if(d->o.z-d->eyeheightlo) lo = mmz+mmi.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(ceillo) lo = floor; - if(flooreyeheight+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; }; +// 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; - }; +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)); -}; - +// 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)); +}; Index: src/protos.h ================================================================== --- src/protos.h +++ src/protos.h @@ -1,9 +1,10 @@ -// protos for ALL external functions in cube... +// protos for ALL external functions in cube... // command -extern int variable(char *name, int min, int cur, int max, int *storage, void (*fun)(), bool persist); +extern int variable(char *name, int min, int cur, int max, int *storage, + void (*fun)(), bool persist); extern void setvar(char *name, int i); extern int getvar(char *name); extern bool identexists(char *name); extern bool addcommand(char *name, void (*fun)(), int narg); extern int execute(char *p, bool down = true); @@ -37,22 +38,29 @@ // rendergl extern void gl_init(int w, int h); extern void cleangl(); extern void gl_drawframe(int w, int h, float curfps); -extern bool installtex(int tnum, char *texname, int &xs, int &ys, bool clamp = false); +extern bool installtex( + int tnum, char *texname, int &xs, int &ys, bool clamp = false); extern void mipstats(int a, int b, int c); extern void vertf(float v1, float v2, float v3, sqr *ls, float t1, float t2); extern void addstrip(int tex, int start, int n); extern int lookuptexture(int tex, int &xs, int &ys); // rendercubes extern void resetcubes(); -extern void render_flat(int tex, int x, int y, int size, int h, sqr *l1, sqr *l2, sqr *l3, sqr *l4, bool isceil); -extern 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); -extern 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 topleft); -extern void render_tris(int x, int y, int size, bool topleft, sqr *h1, sqr *h2, sqr *s, sqr *t, sqr *u, sqr *v); +extern void render_flat(int tex, int x, int y, int size, int h, sqr *l1, + sqr *l2, sqr *l3, sqr *l4, bool isceil); +extern 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); +extern 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 topleft); +extern void render_tris(int x, int y, int size, bool topleft, sqr *h1, sqr *h2, + sqr *s, sqr *t, sqr *u, sqr *v); extern void addwaterquad(int x, int y, int size); extern int renderwater(float hf); extern void finishstrips(); extern void setarraypointers(); @@ -73,11 +81,11 @@ extern int getclientnum(); extern void changemapserv(char *name, int mode); extern void writeclientinfo(FILE *f); // clientgame -extern void mousemove(int dx, int dy); +extern void mousemove(int dx, int dy); extern void updateworld(int millis); extern void startmap(char *name); extern void changemap(char *name); extern void initclient(); extern void spawnplayer(dynent *d); @@ -91,11 +99,12 @@ extern void resetmovement(dynent *d); extern void fixplayer1range(); // clientextras extern void renderclients(); -extern void renderclient(dynent *d, bool team, char *mdlname, bool hellpig, float scale); +extern void renderclient( + dynent *d, bool team, char *mdlname, bool hellpig, float scale); void showscores(bool on); extern void renderscores(); // world extern void setupworld(int factor); @@ -105,21 +114,24 @@ extern int closestent(); extern int findentity(int type, int index = 0); extern void trigger(int tag, int type, bool savegame); extern void resettagareas(); extern void settagareas(); -extern entity *newentity(int x, int y, int z, char *what, int v1, int v2, int v3, int v4); +extern entity *newentity( + int x, int y, int z, char *what, int v1, int v2, int v3, int v4); // worldlight extern void calclight(); -extern void dodynlight(vec &vold, vec &v, int reach, int strength, dynent *owner); +extern void dodynlight( + vec &vold, vec &v, int reach, int strength, dynent *owner); extern void cleardlights(); extern block *blockcopy(block &b); extern void blockpaste(block &b); // worldrender -extern void render_world(float vx, float vy, float vh, int yaw, int pitch, float widef, int w, int h); +extern void render_world(float vx, float vy, float vh, int yaw, int pitch, + float widef, int w, int h); // worldocull extern void computeraytable(float vx, float vy); extern int isoccluded(float vx, float vy, float cx, float cy, float csize); @@ -151,11 +163,12 @@ extern void box(block &b, float z1, float z2, float z3, float z4); extern void dot(int x, int y, float z); extern void linestyle(float width, int r, int g, int b); extern void newsphere(vec &o, float max, int type); extern void renderspheres(int time); -extern void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater); +extern void gl_drawhud( + int w, int h, int curfps, int nquads, int curvert, bool underwater); extern void readdepth(int w, int h); extern void blendbox(int x1, int y1, int x2, int y2, bool border); extern void damageblend(int n); // renderparticles @@ -189,15 +202,18 @@ extern void playsoundc(int n); extern void initsound(); extern void cleansound(); // rendermd2 -extern 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 = 0, int basetime = 0); +extern 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 = 0, int basetime = 0); extern mapmodelinfo &getmminfo(int i); // server -extern void initserver(bool dedicated, int uprate, char *sdesc, char *ip, char *master, char *passwd, int maxcl); +extern void initserver(bool dedicated, int uprate, char *sdesc, char *ip, + char *master, char *passwd, int maxcl); extern void cleanupserver(); extern void localconnect(); extern void localdisconnect(); extern void localclienttoserver(struct _ENetPacket *); extern void serverslice(int seconds, unsigned int timeout); @@ -206,19 +222,21 @@ extern void sendstring(char *t, uchar *&p); extern void startintermission(); extern void restoreserverstate(vector &ents); extern uchar *retrieveservers(uchar *buf, int buflen); extern char msgsizelookup(int msg); -extern void serverms(int mode, int numplayers, int minremain, char *smapname, int seconds, bool isfull); +extern void serverms(int mode, int numplayers, int minremain, char *smapname, + int seconds, bool isfull); extern void servermsinit(const char *master, char *sdesc, bool listen); extern void sendmaps(int n, string mapname, int mapsize, uchar *mapdata); extern ENetPacket *recvmap(int n); // weapon -extern void selectgun(int a = -1, int b = -1, int c =-1); +extern void selectgun(int a = -1, int b = -1, int c = -1); extern void shoot(dynent *d, vec &to); -extern void shootv(int gun, vec &from, vec &to, dynent *d = 0, bool local = false); +extern void shootv( + int gun, vec &from, vec &to, dynent *d = 0, bool local = false); extern void createrays(vec &from, vec &to); extern void moveprojectiles(float time); extern void projreset(); extern char *playerincrosshair(); extern int reloadtime(int gun); @@ -244,6 +262,5 @@ extern void teleport(int n, dynent *d); extern void baseammo(int gun); // rndmap extern void perlinarea(block &b, int scale, int seed, int psize); - Index: src/rendercubes.cxx ================================================================== --- src/rendercubes.cxx +++ src/rendercubes.cxx @@ -1,359 +1,428 @@ -// rendercubes.cpp: sits in between worldrender.cpp and rendergl.cpp and fills the vertex array for different cube surfaces. +// 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); } +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 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); }; +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(); }; +#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)r)g)g)b)b)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); +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; xxwx2) 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; -}; - - +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; +}; Index: src/renderextras.cxx ================================================================== --- src/renderextras.cxx +++ src/renderextras.cxx @@ -1,372 +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); +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; }; +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); +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 = { e.x, e.y, 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); +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 = {e.x, e.y, 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); +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; }; +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); -}; - +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); +}; Index: src/rendergl.cxx ================================================================== --- src/rendergl.cxx +++ src/rendergl.cxx @@ -17,382 +17,430 @@ 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; +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 +// 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]; +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 +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 ) +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() +void +purgetextures() { - loopi(256) loop(j,MAXFRAMES) mapping[i][j] = 0; + 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); +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 +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); +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->lastactionlastaction); - } - 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>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); -}; - +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); +}; Index: src/rendermd2.cxx ================================================================== --- src/rendermd2.cxx +++ src/rendermd2.cxx @@ -1,275 +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); - +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) - { + if (displaylist && frame == 0 && range == 1) { glCallList(displaylist); xtraverts += displaylistverts; - } - else - { - if(frame==0 && range==1) - { + } 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; + + 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;) - { + for (int *command = glCommands; (*command) != 0;) { int numVertex = *command++; - if(numVertex>0) { glBegin(GL_TRIANGLE_STRIP); } - else { glBegin(GL_TRIANGLE_FAN); numVertex = -numVertex; }; + if (numVertex > 0) { + glBegin(GL_TRIANGLE_STRIP); + } else { + glBegin(GL_TRIANGLE_FAN); + numVertex = -numVertex; + }; loopi(numVertex) { - float tu = *((float*)command++); - float tv = *((float*)command++); + 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 +#define ip(c) v1.c *frac2 + v2.c *frac1 glVertex3f(ip(x), ip(z), ip(y)); }; xtraverts += numVertex; glEnd(); }; - - if(displaylist) - { + + if (displaylist) { glEndList(); - displaylistverts = xtraverts-displaylistverts; + displaylistverts = xtraverts - displaylistverts; }; }; - glPopMatrix(); + 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; - }; +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 immi : *(mapmodelinfo *)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); +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); }; Index: src/renderparticles.cxx ================================================================== --- src/renderparticles.cxx +++ src/renderparticles.cxx @@ -2,141 +2,163 @@ #include "cube.h" const int MAXPARTICLES = 10500; const int NUMPARTCUTOFF = 20; -struct particle { vec o, d; int fade, type; int millis; particle *next; }; +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; - }; +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); +VARP(particlesize, 20, 100, 500); vec right, up; -void setorient(vec &r, vec &u) { right = r; up = u; }; +void +setorient(vec &r, vec &u) +{ + right = r; + up = u; +}; -void render_particles(int time) +void +render_particles(int time) { - if(demoplayback && demotracking) - { - vec nom = { 0, 0, 0 }; + 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); - }; -}; - + 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); + }; +}; Index: src/rendertext.cxx ================================================================== --- src/rendertext.cxx +++ src/rendertext.cxx @@ -1,222 +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; - } +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); +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); } Index: src/rndmap.cxx ================================================================== --- src/rndmap.cxx +++ src/rndmap.cxx @@ -1,70 +1,89 @@ -// rndmap.cpp: perlin noise landscape generation and some experimental random map stuff, currently not used +// 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; - }; -}; - - +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; + }; +}; Index: src/savegamedemo.cxx ================================================================== --- src/savegamedemo.cxx +++ src/savegamedemo.cxx @@ -1,6 +1,7 @@ -// loading and saving of savegames & demos, dumps the spawn state of all mapents, the full state of all dynents (monsters + player) +// 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; @@ -11,350 +12,483 @@ 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(); +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; +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); +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); + 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(); +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 +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); + vsub(t1, z); + vmul(t1, 0.5f) vsub(t2, a); + vmul(t2, 0.5f); - float s2 = s*s; - float s3 = s*s2; + 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=0) z = playerhistory[i-1]; - //if(a==z || b==c) printf("* %d\n", lastmillis); - float bf = (itime-a->lastupdate)/(float)(b->lastupdate-a->lastupdate); + 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 + 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); + 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); - }; + break; + }; + // if(player1->state!=CS_DEAD) showscores(false); + }; }; -void stopn() { if(demoplayback) stopreset(); else stop(); conoutf("demo stopped"); }; +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); Index: src/server.cxx ================================================================== --- src/server.cxx +++ src/server.cxx @@ -1,468 +1,544 @@ // server.cpp: little more than enhanced multicaster // runs dedicated or as client coroutine -#include "cube.h" +#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; +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 +struct server_entity // server side version of "entity" type { - bool spawned; - int spawnsecs; + bool spawned; + int spawnsecs; }; vector sents; -bool notgotitems = true; // true when map has changed and waiting for clients to send item +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 +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; - }; + 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; +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=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: +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; + 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 +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, (int)event.peer->data); - if(event.packet->referenceCount==0) enet_packet_destroy(event.packet); - break; - - case ENET_EVENT_TYPE_DISCONNECT: - if((int)event.peer->data<0) break; - printf("disconnected client (%s)\n", clients[(int)event.peer->data].hostname); - clients[(int)event.peer->data].type = ST_EMPTY; - send2(true, -1, SV_CDIS, (int)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); - }; + 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, (int)event.peer->data); + if (event.packet->referenceCount == 0) + enet_packet_destroy(event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + if ((int)event.peer->data < 0) + break; + printf("disconnected client (%s)\n", + clients[(int)event.peer->data].hostname); + clients[(int)event.peer->data].type = ST_EMPTY; + send2(true, -1, SV_CDIS, (int)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); + }; }; Index: src/serverbrowser.cxx ================================================================== --- src/serverbrowser.cxx +++ src/serverbrowser.cxx @@ -1,303 +1,330 @@ -// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window management +// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window +// management -#include "cube.h" #include "SDL_thread.h" +#include "cube.h" -struct resolverthread -{ - SDL_Thread *thread; - char *query; - int starttime; +struct resolverthread { + SDL_Thread *thread; + char *query; + int starttime; }; -struct resolverresult -{ - char *query; - ENetAddress address; +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; +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->pingping ? -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(); +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); -}; - - +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); +}; Index: src/serverms.cxx ================================================================== --- src/serverms.cxx +++ src/serverms.cxx @@ -2,140 +2,165 @@ #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 }; +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) +void +updatemasterserver(int seconds) { - if(seconds>updmaster) // send alive signal to masterserver every hour of uptime - { + 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"); + 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); + 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) +void +serverms(int mode, int numplayers, int minremain, char *smapname, int seconds, + bool isfull) { - checkmasterreply(); - updatemasterserver(seconds); + 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) + 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"); + 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"); }; }; Index: src/serverutil.cxx ================================================================== --- src/serverutil.cxx +++ src/serverutil.cxx @@ -1,123 +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; +// 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; -} - +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=0) { FSOUND_SetVolume(chan, (musicvol*MAXVOL)/255); FSOUND_SetPaused(chan, false); }; - } - else - { - conoutf("could not play music: %s", sn); - }; - #endif - }; +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 @@ -113,114 +120,152 @@ vector samples; #endif cvector snames; -int registersound(char *name) +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; + 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 +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 && chano); + 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); }; +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); Index: src/tools.cxx ================================================================== --- src/tools.cxx +++ src/tools.cxx @@ -5,136 +5,144 @@ //////////////////////////// pool /////////////////////////// pool::pool() { - blocks = 0; - allocnext(POOLSIZE); - for(int i = 0; iMAXREUSESIZE) - { - return malloc(size); - } - else - { - size = bucket(size); - void **r = (void **)reuse[size]; - if(r) - { - reuse[size] = *r; - return (void *)r; - } - else - { - size <<= PTRBITS; - if(leftMAXREUSESIZE) - { - 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()); -}; - + 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; - }; +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; + }; } Index: src/tools.h ================================================================== --- src/tools.h +++ src/tools.h @@ -11,16 +11,16 @@ #ifdef __GNUC__ #undef gamma #endif -#include +#include +#include +#include #include #include -#include -#include -#include +#include #ifdef __GNUC__ #include #else #include #endif @@ -32,248 +32,365 @@ typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; -#define max(a,b) (((a) > (b)) ? (a) : (b)) -#define min(a,b) (((a) < (b)) ? (a) : (b)) -#define rnd(max) (rand()%(max)) +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define rnd(max) (rand() % (max)) #define rndreset() (srand(1)) -#define rndtime() { loopi(lastmillis&0xF) rnd(i+1); } -#define loop(v,m) for(int v = 0; v<(m); v++) -#define loopi(m) loop(i,m) -#define loopj(m) loop(j,m) -#define loopk(m) loop(k,m) -#define loopl(m) loop(l,m) +#define rndtime() \ + { \ + loopi(lastmillis & 0xF) rnd(i + 1); \ + } +#define loop(v, m) for (int v = 0; v < (m); v++) +#define loopi(m) loop(i, m) +#define loopj(m) loop(j, m) +#define loopk(m) loop(k, m) +#define loopl(m) loop(l, m) #ifdef WIN32 -#pragma warning( 3 : 4189 ) -//#pragma comment(linker,"/OPT:NOWIN98") +#pragma warning(3 : 4189) +// #pragma comment(linker,"/OPT:NOWIN98") #define PATHDIV '\\' #else #define __cdecl #define _vsnprintf vsnprintf #define PATHDIV '/' #endif - // easy safe strings #define _MAXDEFSTR 260 -typedef char string[_MAXDEFSTR]; - -inline void strn0cpy(char *d, const char *s, size_t m) { strncpy(d,s,m); d[(m)-1] = 0; }; -inline void strcpy_s(char *d, const char *s) { strn0cpy(d,s,_MAXDEFSTR); }; -inline void strcat_s(char *d, const char *s) { size_t n = strlen(d); strn0cpy(d+n,s,_MAXDEFSTR-n); }; - -inline void formatstring(char *d, const char *fmt, va_list v) -{ - _vsnprintf(d, _MAXDEFSTR, fmt, v); - d[_MAXDEFSTR-1] = 0; -}; - -struct sprintf_s_f -{ - char *d; - sprintf_s_f(char *str): d(str) {}; - void operator()(const char* fmt, ...) - { - va_list v; - va_start(v, fmt); - _vsnprintf(d, _MAXDEFSTR, fmt, v); - va_end(v); - d[_MAXDEFSTR-1] = 0; - }; +typedef char string[_MAXDEFSTR]; + +inline void +strn0cpy(char *d, const char *s, size_t m) +{ + strncpy(d, s, m); + d[(m)-1] = 0; +}; +inline void +strcpy_s(char *d, const char *s) +{ + strn0cpy(d, s, _MAXDEFSTR); +}; +inline void +strcat_s(char *d, const char *s) +{ + size_t n = strlen(d); + strn0cpy(d + n, s, _MAXDEFSTR - n); +}; + +inline void +formatstring(char *d, const char *fmt, va_list v) +{ + _vsnprintf(d, _MAXDEFSTR, fmt, v); + d[_MAXDEFSTR - 1] = 0; +}; + +struct sprintf_s_f { + char *d; + sprintf_s_f(char *str) : d(str) {}; + void + operator()(const char *fmt, ...) + { + va_list v; + va_start(v, fmt); + _vsnprintf(d, _MAXDEFSTR, fmt, v); + va_end(v); + d[_MAXDEFSTR - 1] = 0; + }; }; #define sprintf_s(d) sprintf_s_f((char *)d) -#define sprintf_sd(d) string d; sprintf_s(d) -#define sprintf_sdlv(d,last,fmt) string d; { va_list ap; va_start(ap, last); formatstring(d, fmt, ap); va_end(ap); } -#define sprintf_sdv(d,fmt) sprintf_sdlv(d,fmt,fmt) - +#define sprintf_sd(d) \ + string d; \ + sprintf_s(d) +#define sprintf_sdlv(d, last, fmt) \ + string d; \ + { \ + va_list ap; \ + va_start(ap, last); \ + formatstring(d, fmt, ap); \ + va_end(ap); \ + } +#define sprintf_sdv(d, fmt) sprintf_sdlv(d, fmt, fmt) // fast pentium f2i #ifdef _MSC_VER -inline int fast_f2nat(float a) { // only for positive floats - static const float fhalf = 0.5f; - int retval; - - __asm fld a - __asm fsub fhalf - __asm fistp retval // perf regalloc? - - return retval; +inline int +fast_f2nat(float a) +{ // only for positive floats + static const float fhalf = 0.5f; + int retval; + + __asm fld a __asm fsub fhalf __asm fistp retval // perf regalloc? + + return retval; }; #else -#define fast_f2nat(val) ((int)(val)) +#define fast_f2nat(val) ((int)(val)) #endif - - extern char *path(char *s); extern char *loadfile(char *fn, int *size); extern void endianswap(void *, int, int); // memory pool that uses buckets and linear allocation for small objects -// VERY fast, and reasonably good memory reuse - -struct pool -{ - enum { POOLSIZE = 4096 }; // can be absolutely anything - enum { PTRSIZE = sizeof(char *) }; - enum { MAXBUCKETS = 65 }; // meaning up to size 256 on 32bit pointer systems - enum { MAXREUSESIZE = MAXBUCKETS*PTRSIZE-PTRSIZE }; - inline size_t bucket(size_t s) { return (s+PTRSIZE-1)>>PTRBITS; }; - enum { PTRBITS = PTRSIZE==2 ? 1 : PTRSIZE==4 ? 2 : 3 }; - - char *p; - size_t left; - char *blocks; - void *reuse[MAXBUCKETS]; - - pool(); - ~pool() { dealloc_block(blocks); }; - - void *alloc(size_t size); - void dealloc(void *p, size_t size); - void *realloc(void *p, size_t oldsize, size_t newsize); - - char *string(char *s, size_t l); - char *string(char *s) { return string(s, strlen(s)); }; - void deallocstr(char *s) { dealloc(s, strlen(s)+1); }; - char *stringbuf(char *s) { return string(s, _MAXDEFSTR-1); }; - - void dealloc_block(void *b); - void allocnext(size_t allocsize); -}; - -template struct vector -{ - T *buf; - int alen; - int ulen; - pool *p; - - vector() - { - this->p = gp(); - alen = 8; - buf = (T *)p->alloc(alen*sizeof(T)); - ulen = 0; - }; - - ~vector() { setsize(0); p->dealloc(buf, alen*sizeof(T)); }; - - vector(vector &v); - void operator=(vector &v); - - T &add(const T &x) - { - if(ulen==alen) realloc(); - new (&buf[ulen]) T(x); - return buf[ulen++]; - }; - - T &add() - { - if(ulen==alen) realloc(); - new (&buf[ulen]) T; - return buf[ulen++]; - }; - - T &pop() { return buf[--ulen]; }; - T &last() { return buf[ulen-1]; }; - bool empty() { return ulen==0; }; - - int length() { return ulen; }; - T &operator[](int i) { assert(i>=0 && ii; ulen--) buf[ulen-1].~T(); }; - T *getbuf() { return buf; }; - - void sort(void *cf) { qsort(buf, ulen, sizeof(T), (int (__cdecl *)(const void *,const void *))cf); }; - - void realloc() - { - int olen = alen; - buf = (T *)p->realloc(buf, olen*sizeof(T), (alen *= 2)*sizeof(T)); - }; - - T remove(int i) - { - T e = buf[i]; - for(int p = i+1; pi; p--) buf[p] = buf[p-1]; - buf[i] = e; - return buf[i]; - }; -}; - -#define loopv(v) if(false) {} else for(int i = 0; i<(v).length(); i++) -#define loopvrev(v) if(false) {} else for(int i = (v).length()-1; i>=0; i--) - -template struct hashtable -{ - struct chain { chain *next; char *key; T data; }; - - int size; - int numelems; - chain **table; - pool *parent; - chain *enumc; - - hashtable() - { - this->size = 1<<10; - this->parent = gp(); - numelems = 0; - table = (chain **)parent->alloc(size*sizeof(T)); - for(int i = 0; i &v); - void operator=(hashtable &v); - - T *access(char *key, T *data = NULL) - { - unsigned int h = 5381; - for(int i = 0, k; k = key[i]; i++) h = ((h<<5)+h)^k; // bernstein k=33 xor - h = h&(size-1); // primes not much of an advantage - for(chain *c = table[h]; c; c = c->next) - { - for(char *p1 = key, *p2 = c->key, ch; (ch = *p1++)==*p2++; ) if(!ch) //if(strcmp(key,c->key)==0) - { - T *d = &c->data; - if(data) c->data = *data; - return d; - }; - }; - if(data) - { - chain *c = (chain *)parent->alloc(sizeof(chain)); - c->data = *data; - c->key = key; - c->next = table[h]; - table[h] = c; - numelems++; - }; - return NULL; - }; -}; - - -#define enumerate(ht,t,e,b) loopi(ht->size) for(ht->enumc = ht->table[i]; ht->enumc; ht->enumc = ht->enumc->next) { t e = &ht->enumc->data; b; } - -pool *gp(); -inline char *newstring(char *s) { return gp()->string(s); }; -inline char *newstring(char *s, size_t l) { return gp()->string(s, l); }; -inline char *newstringbuf(char *s) { return gp()->stringbuf(s); }; - -#endif - +// VERY fast, and reasonably good memory reuse + +struct pool { + enum { POOLSIZE = 4096 }; // can be absolutely anything + enum { PTRSIZE = sizeof(char *) }; + enum { + MAXBUCKETS = 65 + }; // meaning up to size 256 on 32bit pointer systems + enum { MAXREUSESIZE = MAXBUCKETS * PTRSIZE - PTRSIZE }; + inline size_t + bucket(size_t s) + { + return (s + PTRSIZE - 1) >> PTRBITS; + }; + enum { PTRBITS = PTRSIZE == 2 ? 1 : PTRSIZE == 4 ? 2 : 3 }; + + char *p; + size_t left; + char *blocks; + void *reuse[MAXBUCKETS]; + + pool(); + ~pool() { dealloc_block(blocks); }; + + void *alloc(size_t size); + void dealloc(void *p, size_t size); + void *realloc(void *p, size_t oldsize, size_t newsize); + + char *string(char *s, size_t l); + char * + string(char *s) + { + return string(s, strlen(s)); + }; + void + deallocstr(char *s) + { + dealloc(s, strlen(s) + 1); + }; + char * + stringbuf(char *s) + { + return string(s, _MAXDEFSTR - 1); + }; + + void dealloc_block(void *b); + void allocnext(size_t allocsize); +}; + +template struct vector { + T *buf; + int alen; + int ulen; + pool *p; + + vector() + { + this->p = gp(); + alen = 8; + buf = (T *)p->alloc(alen * sizeof(T)); + ulen = 0; + }; + + ~vector() + { + setsize(0); + p->dealloc(buf, alen * sizeof(T)); + }; + + vector(vector &v); + void operator=(vector &v); + + T & + add(const T &x) + { + if (ulen == alen) + realloc(); + new (&buf[ulen]) T(x); + return buf[ulen++]; + }; + + T & + add() + { + if (ulen == alen) + realloc(); + new (&buf[ulen]) T; + return buf[ulen++]; + }; + + T & + pop() + { + return buf[--ulen]; + }; + T & + last() + { + return buf[ulen - 1]; + }; + bool + empty() + { + return ulen == 0; + }; + + int + length() + { + return ulen; + }; + T & + operator[](int i) + { + assert(i >= 0 && i < ulen); + return buf[i]; + }; + void + setsize(int i) + { + for (; ulen > i; ulen--) + buf[ulen - 1].~T(); + }; + T * + getbuf() + { + return buf; + }; + + void + sort(void *cf) + { + qsort(buf, ulen, sizeof(T), + (int(__cdecl *)(const void *, const void *))cf); + }; + + void + realloc() + { + int olen = alen; + buf = (T *)p->realloc( + buf, olen * sizeof(T), (alen *= 2) * sizeof(T)); + }; + + T + remove(int i) + { + T e = buf[i]; + for (int p = i + 1; p < ulen; p++) + buf[p - 1] = buf[p]; + ulen--; + return e; + }; + + T & + insert(int i, const T &e) + { + add(T()); + for (int p = ulen - 1; p > i; p--) + buf[p] = buf[p - 1]; + buf[i] = e; + return buf[i]; + }; +}; + +#define loopv(v) \ + if (false) { \ + } else \ + for (int i = 0; i < (v).length(); i++) +#define loopvrev(v) \ + if (false) { \ + } else \ + for (int i = (v).length() - 1; i >= 0; i--) + +template struct hashtable { + struct chain { + chain *next; + char *key; + T data; + }; + + int size; + int numelems; + chain **table; + pool *parent; + chain *enumc; + + hashtable() + { + this->size = 1 << 10; + this->parent = gp(); + numelems = 0; + table = (chain **)parent->alloc(size * sizeof(T)); + for (int i = 0; i < size; i++) + table[i] = NULL; + }; + + hashtable(hashtable &v); + void operator=(hashtable &v); + + T * + access(char *key, T *data = NULL) + { + unsigned int h = 5381; + for (int i = 0, k; k = key[i]; i++) + h = ((h << 5) + h) ^ k; // bernstein k=33 xor + h = h & (size - 1); // primes not much of an advantage + for (chain *c = table[h]; c; c = c->next) { + for (char *p1 = key, *p2 = c->key, ch; + (ch = *p1++) == *p2++;) + if (!ch) // if(strcmp(key,c->key)==0) + { + T *d = &c->data; + if (data) + c->data = *data; + return d; + }; + }; + if (data) { + chain *c = (chain *)parent->alloc(sizeof(chain)); + c->data = *data; + c->key = key; + c->next = table[h]; + table[h] = c; + numelems++; + }; + return NULL; + }; +}; + +#define enumerate(ht, t, e, b) \ + loopi(ht->size) for (ht->enumc = ht->table[i]; ht->enumc; \ + ht->enumc = ht->enumc->next) \ + { \ + t e = &ht->enumc->data; \ + b; \ + } + +pool *gp(); +inline char * +newstring(char *s) +{ + return gp()->string(s); +}; +inline char * +newstring(char *s, size_t l) +{ + return gp()->string(s, l); +}; +inline char * +newstringbuf(char *s) +{ + return gp()->stringbuf(s); +}; + +#endif Index: src/weapon.cxx ================================================================== --- src/weapon.cxx +++ src/weapon.cxx @@ -1,342 +1,420 @@ // weapon.cpp: all shooting and effects code #include "cube.h" -struct guninfo { short sound, attackdelay, damage, projspeed, part, kickamount; char *name; }; +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); +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; +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; }; +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); +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(distvel, 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(attacktimegunwait) 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); -}; - - +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); +}; Index: src/world.cxx ================================================================== --- src/world.cxx +++ src/world.cxx @@ -1,372 +1,510 @@ // world.cpp: core map management stuff #include "cube.h" -extern char *entnames[]; // lookup from map entities above to strings +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(xtag) { + 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 +// 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; xtype]++; - 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(fhceil) 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.xso, v); - if(dist32) 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(); +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 = {e.x, e.y, 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 = {x, y, z, v1, type, v2, v3, 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(); +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>(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(factorLARGEST_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); }; +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); - Index: src/worldio.cxx ================================================================== --- src/worldio.cxx +++ src/worldio.cxx @@ -1,328 +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]->floorfloor) { 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; xwtex, 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(sfactorLARGEST_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; ir = 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); +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); - Index: src/worldlight.cxx ================================================================== --- src/worldlight.cxx +++ src/worldlight.cxx @@ -2,213 +2,256 @@ #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, y>>PRECBITS)) return; - - int g = light.attr3<>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); +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 = { (int)v.x, (int)v.y, (int)v.z, reach, LIGHT, strength, 0, 0 }; - calclightsource(l); - postlightarea(b); +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 = { + (int)v.x, (int)v.y, (int)v.z, reach, LIGHT, 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; xpitch); - 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 - || (anglesyaw) - || (anglesyaw-PI2) - || (anglesyaw+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(nxcx+csize) nx = cx+csize; - if(nycy+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+csizepitch); + 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 +}; Index: src/worldrender.cxx ================================================================== --- src/worldrender.cxx +++ src/worldrender.cxx @@ -1,290 +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 +// 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<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<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<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; +// 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; - int vyy = ((int)vy+(1<>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<occluded = isoccluded(player1->o.x, player1->o.y, (float)(ox<>mip; - int pvy = (int)vy>>mip; - if(pvx>=0 && pvy>=0 && pvxoccluded = 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; xxoccluded==1) continue; \ - if(s->defer && !s->occluded && mip && xx>=lx && xx=ly && yydefer && !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<ceil, s, t, u, v, true); - if(s->type==CHF) //if(s->ceil>=vh) - render_flatdelta(s->ctex, xx<type==SPACE || s->type==CHF) && s->floor<=vh && render_floor) - { - render_flat(s->ftex, xx<floor, s, t, u, v, false); - if(s->floortype==FHF) - { - render_flatdelta(s->ftex, xx<floor-s->vdelta/4.0ftype==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<=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(low45 && 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>MAX_MIP, ssize>>MAX_MIP); - mipstats(stats[0], stats[1], stats[2]); -}; - +// 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]); +};