Import cube_2005_08_29_src_zlib.zip
FossilOrigin-Name: ea7418102a3ee6b415e50bea95694727a4f62ae112b570a07310817614ea3063
This commit is contained in:
parent
88989814f6
commit
79db1ed9fa
144 changed files with 45421 additions and 0 deletions
297
src/CUBE_TODO.txt
Normal file
297
src/CUBE_TODO.txt
Normal file
|
@ -0,0 +1,297 @@
|
|||
- seperate release archives for windows, linux, and macosx (less FAQs) - tar with -keep empty dirs
|
||||
- demo play bugs like sticky scoreboard
|
||||
- look at that immortality bug that sometimes pops up. immortality_bug.cdgz
|
||||
- kick vote
|
||||
- check other demos for common bugs
|
||||
- demo bug: LAG makes other player fly
|
||||
- demo bug: names (who is "you") can sometimes be reversed
|
||||
- ability to set refreshrate? http://twomix.devolution.com/pipermail/sdl/2003-July/055059.html
|
||||
- player should not be stuck moving after respawn (not reset +forward)
|
||||
- change auto wep change?
|
||||
- fps = (1000.0f/(curtime ? curtime : 1)+fps*50)/51 // somehow curtime gets to be 0 even if fps is only 150 on linux
|
||||
- make serverinfo in server port obey -i
|
||||
- make spawns more random
|
||||
- automatic team assignment
|
||||
- configurable serverport
|
||||
- force respawn
|
||||
- delay for player in xhair
|
||||
- demo: if player once in lag mode, stays there
|
||||
- "texture triggers", i.e. triggers that can be set for the player touching a certain floor texture. That way it can support sky, and lava, the effects are customizable, and you can also create full-room triggers for other purposes with them.
|
||||
- -mBLAH sometimes still registers
|
||||
- lighting - store id of strongest light in every tile (or multiple lights) | normals | boundaries | occlusion at steps down <- the hard part, really needs different representation
|
||||
- spectator mode
|
||||
- maprotation.cfg for server
|
||||
- map checksum (detect architectural changes, entity changes(can it be done by just checking size?))
|
||||
- send/getmap: some way to send also the .cfg and neccesary custom stuff
|
||||
- Duel modes (for insta, ffa, tactics, efficiency) maybe with reasonable fraglimit each or only 3-5min rounds (others spectate)
|
||||
- ctf mode (or something else really team-oriented)
|
||||
- accuracy stats ;)
|
||||
- more gore/blood :D
|
||||
- more chars äüö, and ´
|
||||
- SLEEPY: only allow v121 and later
|
||||
- win98 gf2 video init crash?
|
||||
- better fist?
|
||||
- check with vtune for cpu bottlenecks on ksp1
|
||||
- monsters & jumppads
|
||||
- hide monsters until triggered?
|
||||
- trigger when monsters die?
|
||||
- but back the idea of a overwritable config.cfg so settings are easier to change in game?
|
||||
- improve particle engine / make all weapons have more satisfying effects / better sounds overal / lighting on rocket explosion
|
||||
- demos play even from old protocol versions / scoreboards sometimes stick
|
||||
- weapon keeps shooting if you switch to edit/talk while shooting / hide hud weapon in edit mode
|
||||
- load demo when already playing a demo: see yourself
|
||||
- load demo of map editing -> crash?
|
||||
- larger ideas to revamp cube editing/rendering:
|
||||
* can cube internal representation be replaced with typeless representation?
|
||||
2x floorceil, 8xvert offset, and 2x wall offset? (can we do it without floorceil even? byterange?)
|
||||
* new lighting (diff floor ceiling like doom64?) can use lightmap the size of the world
|
||||
to save on polys and make lighting more precise?
|
||||
-> also other ways to reduce on polys by splitting
|
||||
-> can merge polys better
|
||||
most important however would be to allow walls to have different light than floor/ceil depending on light angle
|
||||
and better/sharper shadows
|
||||
* change texture list to be textures used rather than slots to allow >256
|
||||
* proper way of doing multilevel?
|
||||
* a way of rendering that makes static vertex arrays and culling coexist? larger chunks?
|
||||
-> speed is hardly the problem anymore with current cube maps, though may be nice once higher detail geometry editing methods are in
|
||||
* remove LOD completely
|
||||
* ways to reduce sparklies?
|
||||
- render water only where needed... or with some sort of zbuffer trick?
|
||||
* faster cube occlusion culling by checking distance from each cube rather than raytracing... do so using quad tree and in direction of viewer
|
||||
- gluPerspective wrong fov?
|
||||
- fake opengl light for models? (not for bridges)
|
||||
- do something about people having wrong maps etc / map cheating / make auto map download
|
||||
- super simple CTF using pickup - drop off idea pxtl ? no team spawns required!
|
||||
- allow monster spawn on trigger?
|
||||
- VWEP (slith has own weapons), muzzle flash?
|
||||
- About 2 out of 3 times I start Cube (Linux Version), the fps counter displays -214748
|
||||
- make water render not everywhere to reduce sparklies
|
||||
- vertex/fragment programs
|
||||
global: millis, eye, eyeorient
|
||||
vertex: normal, Svec?, Tvec? closest light / light dir vec ?
|
||||
opengl: matrices etc.
|
||||
or instead simple regcombine setup?
|
||||
- shaders stuff
|
||||
- allow texture rotation
|
||||
- glow
|
||||
- under some circumstances, players can not be hurt in multiplayer (wrong lifetime???)
|
||||
(after any spawn, also in ffa, lasts until disconnect)
|
||||
- out of memory bug (just on or after new player join).
|
||||
- mapmodels above eachother gives incorrect physics
|
||||
- fps reporting on demos.../ teleport to spawn?
|
||||
- improve sounds
|
||||
- Ability to get attributes of a single cube at $xsel, $ysel.
|
||||
- Ability to get the current users position (or cursor position, X,Y & height)
|
||||
- Provide Absolute mode in at least functions edittex,editheight,vdelta.
|
||||
- no text when dead? / round over while dead? -> more lag than appearent
|
||||
- can't spawn when 1 is lagging
|
||||
- sendmap hang?
|
||||
- specator mode for arena also
|
||||
- avoid spawn next to eachother : most difficult in arena because spawns all at same time, meaning you can't check other people's position
|
||||
- show last persons health?
|
||||
- bigger monsters can get stuck on top of mapmodels
|
||||
- disable hudgun when dead
|
||||
- make disconnection faster (now can last up to 60 secs)... what features depend on this?
|
||||
- try and debug depth read? GL_DEPTH_SCALE/GL_DEPTH_BIAS
|
||||
- detect if sw driver? glXIsDirect(dpy, ctx) (can't get context from SDL) / glGetString(GL_VENDOR/RENDERER) (many HW drivers use mesa as base)
|
||||
- not reproducable: When you pop out of edit mode where a bridge is right under a ceiling you get stuck.
|
||||
- jumppads don't work for monsters
|
||||
- try to go FMOD-only?
|
||||
- new weapon models?
|
||||
- profiling / replace gluBuild2DMipmaps, its too slow
|
||||
- protocol: send map checksums so checking can happen?
|
||||
|
||||
rendering:
|
||||
- integrate fisheye mod?
|
||||
- fix large overdraw on fog culling?
|
||||
- particles look v bad in fog/water
|
||||
- allow up to fov 135 or so (need to tweak various frustrum culling optis)
|
||||
- experiment with alpha textures & shiny textures for second unit... -> generate normals only for surfs that use it
|
||||
- try out VAR without copying? -> almost no speedup, too cpu bound
|
||||
- use VAR on chunks of the world
|
||||
- speed up md2 render -> put into array too... maybe can render multiple walking monsters from same array? (since same time moment)
|
||||
- texture precache?
|
||||
- try out also up/down occlusion culling?
|
||||
- fix corner textures, and make all 4 walls L-R
|
||||
- render sky polys more efficiently? can't be done with light. need to collect "sky space" and render seperately
|
||||
- reassign mip 1 quads / mip n quads for big open maps
|
||||
- skybox roll not working yet
|
||||
- cull explosions/particles etc outside frustrum
|
||||
- stripify walls?
|
||||
- instead of having defer 0 or 1, have a range that rates its visible disruptiveness
|
||||
|
||||
world structure / editing:
|
||||
- make mip opti tags compliant?
|
||||
- generic push/pull for vdeltas
|
||||
- how to allow editing features as trigger scripts (since they reset triggers/monsters).
|
||||
other triggers... certainly a NULL trigger that affects no tags
|
||||
floor/ceil height, texture change, solid,
|
||||
spawn stuff?
|
||||
waterlevel raise/lower
|
||||
could select tag areas and then do edit callback.. problem is how to generically restore them
|
||||
- need more commands that influence the world in a gameplay way for trigger scripts
|
||||
- ror by models?
|
||||
- hom in custom arch?
|
||||
- vdelta mipping often wrong
|
||||
- improve selecting in void and selecting heightmaps / selecting areas with much height variation
|
||||
- "platform" extra floors or something similarly simple?
|
||||
- generate terrain as edit command (also for 2x2 and 4x4 mips!)
|
||||
- arbitrary shapes a la archvertex
|
||||
- flip/rotate commands
|
||||
- off-vertex feature?
|
||||
- coop copy & paste / other ops
|
||||
- make showmip show acurate values (use real light values)
|
||||
- make mapopt atleast also do 4x4
|
||||
- intermission cam entity?
|
||||
- make "corner" equalize height automatically?
|
||||
- "are you sure" when edits have been made
|
||||
- wr: render too high wall if z is mip mapped (1 unit indent)
|
||||
- wr: walls behind corners
|
||||
- wr: SEMISOLID still causes faces in the floor -> extend issemi
|
||||
- hom: normal walls next to huge mips (on DM2?)
|
||||
- hom: a 2x2 solid next to only a partial HF [workaround: cut up wall]
|
||||
- hom: a 2x2 solid next to concave 2x2 hf [workaround: cut up wall]
|
||||
- hom: cap on a equal height floor/ceil [workaround: make height diff]
|
||||
- allow 2 or more levels?
|
||||
- multiple selections
|
||||
- each client in editmode should be able to see the selection outline of other clients that are in editmode.
|
||||
- expand random map gen ?
|
||||
- texture language: each texture number becomes a little program for
|
||||
texturing. [ warning: must be in both worldrender & mipmapping code
|
||||
for correct results. ] possibilities:
|
||||
* all the shader tricks
|
||||
* random textures: select any from a set
|
||||
* checkerboard tile 2 textures
|
||||
* auto border tiling (when texture touches same height/texture
|
||||
or not): textures specified (any amount):
|
||||
1. texture center
|
||||
2. texture border (replaces 1 when touching changing blocks)
|
||||
3. texture corner (replaces 2 on 2 touching)
|
||||
4. inner corner (replaces 2/3 when indented corner)
|
||||
will work on both floors and walls, and will make nice
|
||||
texturing with rims _extremely_ easy.
|
||||
|
||||
script / console:
|
||||
- make editing-only binds?
|
||||
- delay "music" and "loadsky" commands, such that they do not load multiple times.
|
||||
- make onrelease work everywhere?
|
||||
- beep on "say"
|
||||
- save config.cfg / move autoexec.cfg to last?
|
||||
- international keyboards?
|
||||
|
||||
multiplayer:
|
||||
- insta arena
|
||||
- accumulate accuracy / efficiency / rails in a row / frags in a row stats
|
||||
- arena numplayers>1 & other recent addons / rename "arena" to "last man standing"
|
||||
- allow servers to set port
|
||||
- render CS_LAGGED differently?
|
||||
- spectator mode
|
||||
- make everyone spec for duel/team modes
|
||||
- admin feature
|
||||
- mode 2 duel?
|
||||
- frags should be in c2s welcome packet
|
||||
- simplify tp: only red/blue, otherwise spectate
|
||||
- add time spend on server to scoreboard
|
||||
- specify distance in some way for network ? distance / occlusion / standing still / dead
|
||||
- compress some things about network stream?
|
||||
- all physics pushes don't affect players across the network
|
||||
- servers should send map checksums, small differences in map entities pos could potentially cause crashes, same for out of range ents on clients.
|
||||
- implement http get for masterserver?
|
||||
- vote: either explain it or make it easier.. generally make a multiplayer connect walkthru
|
||||
- rethink map get? make it even easier???
|
||||
- locking of in-progress games
|
||||
- when switching to map that player doesnt have... something better should happen than keep him in old map with floating players
|
||||
- capture the chicken mode? other different DM modes? domination? holy wars?
|
||||
- teamplay scores don't record frags of disconnected players
|
||||
- teamplay binds
|
||||
- ctf mode
|
||||
- character selection... maybe also sounds... relatively easy, maybe share monster/player stuff more
|
||||
- when failed to connect, not clear not connected on map reload?
|
||||
- time remaining not clearly printed...
|
||||
- interpolated demo playback?
|
||||
- dont allow duplicate player names
|
||||
- make carrots work correctly in mp, and various other things in mp
|
||||
- maybe also make deaths instant
|
||||
- console scores
|
||||
- can jump thru players easily when both move.. make sure offset to correct side?
|
||||
- add network interpolation as an option?
|
||||
- different pain sounds for levels of damage applied, or level of health?
|
||||
|
||||
SP/monsters
|
||||
- less <10h -> scared
|
||||
- monster weapons go thru mapmodels.
|
||||
- lava/water hurt
|
||||
- think about save game / copy stats for sp
|
||||
- think about introducing rpg style elements: weapon/stat upgrades, inventory items
|
||||
- wake up others nearby?
|
||||
- secrets
|
||||
- barrels
|
||||
- gibs for health?
|
||||
- other interesting monster behaviour... pacifist until first shot?
|
||||
- make monsters flee
|
||||
- spawn sound?
|
||||
- maybe make 2 versions of each monster to make up for the lack of them (different skins?)
|
||||
- player/monster on top of eachother
|
||||
- coop (not easy, requires lots of sync & bw management).
|
||||
- use tracing code also for player1? too many problems with it...
|
||||
- new trigger types... floor raise/lower, secrets
|
||||
|
||||
gameplay:
|
||||
- sky hurt
|
||||
- splash sound frequently when looking straight up from water
|
||||
- increase amount of blood / better particle lifetimes
|
||||
- think of how make kills etc more satisfying
|
||||
- dynblight when player shoots
|
||||
- make some of the gameplay code in script? problem, for multiplayer this will make cheating easy.
|
||||
- overhead map: show open & closed doors
|
||||
- fun: racing game easter egg
|
||||
- use Mix_SetPosition() for positional sound
|
||||
- anim frames for death
|
||||
- <metlslime> - fix the shotgun behavior :)
|
||||
- auto weapon change?
|
||||
- traceline related:
|
||||
* replace readpixels since its very problematic?
|
||||
-> must solve by software, can't disable depth buffer for models
|
||||
* RL stops behind chasing player
|
||||
* RL path must be cut short on direct hit
|
||||
* guns shots go thru players, and they can miss if model sticks in front
|
||||
of bounding box
|
||||
|
||||
physics
|
||||
- optimise stair physics for 2x1 slopes -> interferes with stairs
|
||||
- speed up monster-monster and monster-mapmodel collision
|
||||
- for 1 cube wide obstackles, push player past it
|
||||
- give player velocity coming out of teleport -> direction he's facing
|
||||
- improve slant sliding
|
||||
- improve heightfield physics?
|
||||
|
||||
light stuffs:
|
||||
- blend out dynlight longer?
|
||||
- sun lighting?
|
||||
- player shadows?
|
||||
- quad damage player dynlight?
|
||||
- switchable lights? lights that oscillate in strength?
|
||||
|
||||
changes for bigger landscapes:
|
||||
- hf: hom next to big mips / water height
|
||||
- make correct collision for slopes
|
||||
- push/pull vertex tools
|
||||
- different scale: *2 in xy, and /2 in z, ratio is then 1:4 (so float hf can be removed)
|
||||
- more tree/plant map models
|
||||
- optimize certain code for 2k^2 maps.. lighting?
|
||||
- cut&paste entities also?
|
||||
- limit occlusion distance?
|
||||
|
||||
non-code:
|
||||
- improve default keybinds for editing -> docs / find better keys for modifiers?
|
||||
- email all model authors
|
||||
- check all dm spawns big enough for monsters
|
||||
- sounds! monster see
|
||||
- more sp maps?
|
||||
- better tree skin / more models?
|
||||
- add metl3?
|
||||
- dgtex?
|
||||
- q3 tree mapmodels?
|
||||
|
||||
|
||||
|
66
src/Makefile
Normal file
66
src/Makefile
Normal file
|
@ -0,0 +1,66 @@
|
|||
CXX=g++
|
||||
CXXOPTFLAGS=-Wall -O3 -fsigned-char -fomit-frame-pointer
|
||||
CXXFLAGS=$(CXXOPTFLAGS) -I../enet/include `sdl-config --cflags`
|
||||
|
||||
CLIENT_LIBS=-L../enet -lenet `sdl-config --libs` -lSDL_image -lSDL_mixer -lz -lGL -lGLU
|
||||
CLIENT_OBJS= \
|
||||
client.o \
|
||||
clientextras.o \
|
||||
clientgame.o \
|
||||
clients2c.o \
|
||||
command.o \
|
||||
console.o \
|
||||
editing.o \
|
||||
entities.o \
|
||||
main.o \
|
||||
menus.o \
|
||||
monster.o \
|
||||
physics.o \
|
||||
rendercubes.o \
|
||||
renderextras.o \
|
||||
rendergl.o \
|
||||
rendermd2.o \
|
||||
renderparticles.o \
|
||||
rendertext.o \
|
||||
rndmap.o \
|
||||
savegamedemo.o \
|
||||
server.o \
|
||||
serverbrowser.o \
|
||||
serverms.o \
|
||||
serverutil.o \
|
||||
sound.o \
|
||||
tools.o \
|
||||
weapon.o \
|
||||
world.o \
|
||||
worldio.o \
|
||||
worldlight.o \
|
||||
worldocull.o \
|
||||
worldrender.o
|
||||
|
||||
SERVER_LIBS=-L../enet -lenet
|
||||
SERVER_OBJS= \
|
||||
standalone.o \
|
||||
serverms.o \
|
||||
server.o \
|
||||
tools.o
|
||||
|
||||
default: all
|
||||
|
||||
all: enet client server
|
||||
|
||||
enet:
|
||||
-make -C ../enet all
|
||||
|
||||
clean:
|
||||
-rm -f $(SERVER_OBJS) $(CLIENT_OBJS) cube_server cube_client
|
||||
-make -C ../enet/ clean
|
||||
|
||||
standalone.o:
|
||||
$(CXX) $(CXXFLAGS) -DSTANDALONE -o standalone.o -c serverutil.cpp
|
||||
|
||||
client: $(CLIENT_OBJS)
|
||||
$(CXX) $(CXXFLAGS) -o cube_client $(CLIENT_OBJS) $(CLIENT_LIBS)
|
||||
|
||||
server: $(SERVER_OBJS)
|
||||
$(CXX) $(CXXFLAGS) -o cube_server $(SERVER_OBJS) $(SERVER_LIBS)
|
||||
|
309
src/client.cpp
Normal file
309
src/client.cpp
Normal file
|
@ -0,0 +1,309 @@
|
|||
// client.cpp, mostly network related client game code
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
ENetHost *clienthost = NULL;
|
||||
int connecting = 0;
|
||||
int connattempts = 0;
|
||||
int disconnecting = 0;
|
||||
int clientnum = -1; // our client id in the game
|
||||
bool c2sinit = false; // whether we need to tell the other clients our stats
|
||||
|
||||
int getclientnum() { return clientnum; };
|
||||
|
||||
bool multiplayer()
|
||||
{
|
||||
// check not correct on listen server?
|
||||
if(clienthost) conoutf("operation not available in multiplayer");
|
||||
return clienthost!=NULL;
|
||||
};
|
||||
|
||||
bool allowedittoggle()
|
||||
{
|
||||
bool allow = !clienthost || gamemode==1;
|
||||
if(!allow) conoutf("editing in multiplayer requires coopedit mode (1)");
|
||||
return allow;
|
||||
};
|
||||
|
||||
VARF(rate, 0, 0, 25000, if(clienthost && (!rate || rate>1000)) enet_host_bandwidth_limit (clienthost, rate, rate));
|
||||
|
||||
void throttle();
|
||||
|
||||
VARF(throttle_interval, 0, 5, 30, throttle());
|
||||
VARF(throttle_accel, 0, 2, 32, throttle());
|
||||
VARF(throttle_decel, 0, 2, 32, throttle());
|
||||
|
||||
void throttle()
|
||||
{
|
||||
if(!clienthost || connecting) return;
|
||||
assert(ENET_PEER_PACKET_THROTTLE_SCALE==32);
|
||||
enet_peer_throttle_configure(clienthost->peers, throttle_interval*1000, throttle_accel, throttle_decel);
|
||||
};
|
||||
|
||||
void newname(char *name) { c2sinit = false; strn0cpy(player1->name, name, 16); };
|
||||
void newteam(char *name) { c2sinit = false; strn0cpy(player1->team, name, 5); };
|
||||
|
||||
COMMANDN(team, newteam, ARG_1STR);
|
||||
COMMANDN(name, newname, ARG_1STR);
|
||||
|
||||
void writeclientinfo(FILE *f)
|
||||
{
|
||||
fprintf(f, "name \"%s\"\nteam \"%s\"\n", player1->name, player1->team);
|
||||
};
|
||||
|
||||
void connects(char *servername)
|
||||
{
|
||||
disconnect(1); // reset state
|
||||
addserver(servername);
|
||||
|
||||
conoutf("attempting to connect to %s", servername);
|
||||
ENetAddress address = { ENET_HOST_ANY, CUBE_SERVER_PORT };
|
||||
if(enet_address_set_host(&address, servername) < 0)
|
||||
{
|
||||
conoutf("could not resolve server %s", servername);
|
||||
return;
|
||||
};
|
||||
|
||||
clienthost = enet_host_create(NULL, 1, rate, rate);
|
||||
|
||||
if(clienthost)
|
||||
{
|
||||
enet_host_connect(clienthost, &address, 1);
|
||||
enet_host_flush(clienthost);
|
||||
connecting = lastmillis;
|
||||
connattempts = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
conoutf("could not connect to server");
|
||||
disconnect();
|
||||
};
|
||||
};
|
||||
|
||||
void disconnect(int onlyclean, int async)
|
||||
{
|
||||
if(clienthost)
|
||||
{
|
||||
if(!connecting && !disconnecting)
|
||||
{
|
||||
enet_peer_disconnect(clienthost->peers);
|
||||
enet_host_flush(clienthost);
|
||||
disconnecting = lastmillis;
|
||||
};
|
||||
if(clienthost->peers->state != ENET_PEER_STATE_DISCONNECTED)
|
||||
{
|
||||
if(async) return;
|
||||
enet_peer_reset(clienthost->peers);
|
||||
};
|
||||
enet_host_destroy(clienthost);
|
||||
};
|
||||
|
||||
if(clienthost && !connecting) conoutf("disconnected");
|
||||
clienthost = NULL;
|
||||
connecting = 0;
|
||||
connattempts = 0;
|
||||
disconnecting = 0;
|
||||
clientnum = -1;
|
||||
c2sinit = false;
|
||||
player1->lifesequence = 0;
|
||||
loopv(players) zapdynent(players[i]);
|
||||
|
||||
localdisconnect();
|
||||
|
||||
if(!onlyclean) { stop(); localconnect(); };
|
||||
};
|
||||
|
||||
void trydisconnect()
|
||||
{
|
||||
if(!clienthost)
|
||||
{
|
||||
conoutf("not connected");
|
||||
return;
|
||||
};
|
||||
if(connecting)
|
||||
{
|
||||
conoutf("aborting connection attempt");
|
||||
disconnect();
|
||||
return;
|
||||
};
|
||||
conoutf("attempting to disconnect...");
|
||||
disconnect(0, !disconnecting);
|
||||
};
|
||||
|
||||
string ctext;
|
||||
void toserver(char *text) { conoutf("%s:\f %s", player1->name, text); strn0cpy(ctext, text, 80); };
|
||||
void echo(char *text) { conoutf("%s", text); };
|
||||
|
||||
COMMAND(echo, ARG_VARI);
|
||||
COMMANDN(say, toserver, ARG_VARI);
|
||||
COMMANDN(connect, connects, ARG_1STR);
|
||||
COMMANDN(disconnect, trydisconnect, ARG_NONE);
|
||||
|
||||
// collect c2s messages conveniently
|
||||
|
||||
vector<ivector> messages;
|
||||
|
||||
void addmsg(int rel, int num, int type, ...)
|
||||
{
|
||||
if(demoplayback) return;
|
||||
if(num!=msgsizelookup(type)) { sprintf_sd(s)("inconsistant msg size for %d (%d != %d)", type, num, msgsizelookup(type)); fatal(s); };
|
||||
if(messages.length()==100) { conoutf("command flood protection (type %d)", type); return; };
|
||||
ivector &msg = messages.add();
|
||||
msg.add(num);
|
||||
msg.add(rel);
|
||||
msg.add(type);
|
||||
va_list marker;
|
||||
va_start(marker, type);
|
||||
loopi(num-1) msg.add(va_arg(marker, int));
|
||||
va_end(marker);
|
||||
};
|
||||
|
||||
void server_err()
|
||||
{
|
||||
conoutf("server network error, disconnecting...");
|
||||
disconnect();
|
||||
};
|
||||
|
||||
int lastupdate = 0, lastping = 0;
|
||||
string toservermap;
|
||||
bool senditemstoserver = false; // after a map change, since server doesn't have map data
|
||||
|
||||
string clientpassword;
|
||||
void password(char *p) { strcpy_s(clientpassword, p); };
|
||||
COMMAND(password, ARG_1STR);
|
||||
|
||||
bool netmapstart() { senditemstoserver = true; return clienthost!=NULL; };
|
||||
|
||||
void initclientnet()
|
||||
{
|
||||
ctext[0] = 0;
|
||||
toservermap[0] = 0;
|
||||
clientpassword[0] = 0;
|
||||
newname("unnamed");
|
||||
newteam("red");
|
||||
};
|
||||
|
||||
void sendpackettoserv(void *packet)
|
||||
{
|
||||
if(clienthost) { enet_host_broadcast(clienthost, 0, (ENetPacket *)packet); enet_host_flush(clienthost); }
|
||||
else localclienttoserver((ENetPacket *)packet);
|
||||
}
|
||||
|
||||
void c2sinfo(dynent *d) // send update to the server
|
||||
{
|
||||
if(clientnum<0) return; // we haven't had a welcome message from the server yet
|
||||
if(lastmillis-lastupdate<40) return; // don't update faster than 25fps
|
||||
ENetPacket *packet = enet_packet_create (NULL, MAXTRANS, 0);
|
||||
uchar *start = packet->data;
|
||||
uchar *p = start+2;
|
||||
bool serveriteminitdone = false;
|
||||
if(toservermap[0]) // suggest server to change map
|
||||
{ // do this exclusively as map change may invalidate rest of update
|
||||
packet->flags = ENET_PACKET_FLAG_RELIABLE;
|
||||
putint(p, SV_MAPCHANGE);
|
||||
sendstring(toservermap, p);
|
||||
toservermap[0] = 0;
|
||||
putint(p, nextmode);
|
||||
}
|
||||
else
|
||||
{
|
||||
putint(p, SV_POS);
|
||||
putint(p, clientnum);
|
||||
putint(p, (int)(d->o.x*DMF)); // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes
|
||||
putint(p, (int)(d->o.y*DMF));
|
||||
putint(p, (int)(d->o.z*DMF));
|
||||
putint(p, (int)(d->yaw*DAF));
|
||||
putint(p, (int)(d->pitch*DAF));
|
||||
putint(p, (int)(d->roll*DAF));
|
||||
putint(p, (int)(d->vel.x*DVF)); // quantize to 1/100, almost always 1 byte
|
||||
putint(p, (int)(d->vel.y*DVF));
|
||||
putint(p, (int)(d->vel.z*DVF));
|
||||
// pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3
|
||||
putint(p, (d->strafe&3) | ((d->move&3)<<2) | (((int)d->onfloor)<<4) | ((editmode ? CS_EDITING : d->state)<<5) );
|
||||
|
||||
if(senditemstoserver)
|
||||
{
|
||||
packet->flags = ENET_PACKET_FLAG_RELIABLE;
|
||||
putint(p, SV_ITEMLIST);
|
||||
if(!m_noitems) putitems(p);
|
||||
putint(p, -1);
|
||||
senditemstoserver = false;
|
||||
serveriteminitdone = true;
|
||||
};
|
||||
if(ctext[0]) // player chat, not flood protected for now
|
||||
{
|
||||
packet->flags = ENET_PACKET_FLAG_RELIABLE;
|
||||
putint(p, SV_TEXT);
|
||||
sendstring(ctext, p);
|
||||
ctext[0] = 0;
|
||||
};
|
||||
if(!c2sinit) // tell other clients who I am
|
||||
{
|
||||
packet->flags = ENET_PACKET_FLAG_RELIABLE;
|
||||
c2sinit = true;
|
||||
putint(p, SV_INITC2S);
|
||||
sendstring(player1->name, p);
|
||||
sendstring(player1->team, p);
|
||||
putint(p, player1->lifesequence);
|
||||
};
|
||||
loopv(messages) // send messages collected during the previous frames
|
||||
{
|
||||
ivector &msg = messages[i];
|
||||
if(msg[1]) packet->flags = ENET_PACKET_FLAG_RELIABLE;
|
||||
loopi(msg[0]) putint(p, msg[i+2]);
|
||||
};
|
||||
messages.setsize(0);
|
||||
if(lastmillis-lastping>250)
|
||||
{
|
||||
putint(p, SV_PING);
|
||||
putint(p, lastmillis);
|
||||
lastping = lastmillis;
|
||||
};
|
||||
};
|
||||
*(ushort *)start = ENET_HOST_TO_NET_16(p-start);
|
||||
enet_packet_resize(packet, p-start);
|
||||
incomingdemodata(start, p-start, true);
|
||||
if(clienthost) { enet_host_broadcast(clienthost, 0, packet); enet_host_flush(clienthost); }
|
||||
else localclienttoserver(packet);
|
||||
lastupdate = lastmillis;
|
||||
if(serveriteminitdone) loadgamerest(); // hack
|
||||
};
|
||||
|
||||
void gets2c() // get updates from the server
|
||||
{
|
||||
ENetEvent event;
|
||||
if(!clienthost) return;
|
||||
if(connecting && lastmillis/3000 > connecting/3000)
|
||||
{
|
||||
conoutf("attempting to connect...");
|
||||
connecting = lastmillis;
|
||||
++connattempts;
|
||||
if(connattempts > 3)
|
||||
{
|
||||
conoutf("could not connect to server");
|
||||
disconnect();
|
||||
return;
|
||||
};
|
||||
};
|
||||
while(clienthost!=NULL && enet_host_service(clienthost, &event, 0)>0)
|
||||
switch(event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
conoutf("connected to server");
|
||||
connecting = 0;
|
||||
throttle();
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
if(disconnecting) conoutf("attempting to disconnect...");
|
||||
else localservertoclient(event.packet->data, event.packet->dataLength);
|
||||
enet_packet_destroy(event.packet);
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
if(disconnecting) disconnect();
|
||||
else server_err();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
157
src/clientextras.cpp
Normal file
157
src/clientextras.cpp
Normal file
|
@ -0,0 +1,157 @@
|
|||
// 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);
|
||||
};
|
||||
|
||||
extern int democlientnum;
|
||||
|
||||
void renderclients()
|
||||
{
|
||||
dynent *d;
|
||||
loopv(players) if((d = players[i]) && (!demoplayback || i!=democlientnum)) renderclient(d, isteam(player1->team, d->team), "monster/ogro", false, 1.0f);
|
||||
};
|
||||
|
||||
// creation of scoreboard pseudo-menu
|
||||
|
||||
bool scoreson = false;
|
||||
|
||||
void showscores(bool on)
|
||||
{
|
||||
scoreson = on;
|
||||
menuset(((int)on)-1);
|
||||
};
|
||||
|
||||
struct sline { string s; };
|
||||
vector<sline> scorelines;
|
||||
|
||||
void renderscore(dynent *d)
|
||||
{
|
||||
sprintf_sd(lag)("%d", d->plag);
|
||||
sprintf_sd(name) ("(%s)", d->name);
|
||||
sprintf_s(scorelines.add().s)("%d\t%s\t%d\t%s\t%s", d->frags, d->state==CS_LAGGED ? "LAG" : lag, d->ping, d->team, d->state==CS_DEAD ? name : d->name);
|
||||
menumanual(0, scorelines.length()-1, scorelines.last().s);
|
||||
};
|
||||
|
||||
const int maxteams = 4;
|
||||
char *teamname[maxteams];
|
||||
int teamscore[maxteams], teamsused;
|
||||
string teamscores;
|
||||
int timeremain = 0;
|
||||
|
||||
void addteamscore(dynent *d)
|
||||
{
|
||||
if(!d) return;
|
||||
loopi(teamsused) if(strcmp(teamname[i], d->team)==0) { teamscore[i] += d->frags; return; };
|
||||
if(teamsused==maxteams) return;
|
||||
teamname[teamsused] = d->team;
|
||||
teamscore[teamsused++] = d->frags;
|
||||
};
|
||||
|
||||
void renderscores()
|
||||
{
|
||||
if(!scoreson) return;
|
||||
scorelines.setsize(0);
|
||||
if(!demoplayback) renderscore(player1);
|
||||
loopv(players) if(players[i]) renderscore(players[i]);
|
||||
sortmenu(0, scorelines.length());
|
||||
if(m_teammode)
|
||||
{
|
||||
teamsused = 0;
|
||||
loopv(players) addteamscore(players[i]);
|
||||
if(!demoplayback) addteamscore(player1);
|
||||
teamscores[0] = 0;
|
||||
loopj(teamsused)
|
||||
{
|
||||
sprintf_sd(sc)("[ %s: %d ]", teamname[j], teamscore[j]);
|
||||
strcat_s(teamscores, sc);
|
||||
};
|
||||
menumanual(0, scorelines.length(), "");
|
||||
menumanual(0, scorelines.length()+1, teamscores);
|
||||
};
|
||||
};
|
||||
|
||||
// sendmap/getmap commands, should be replaced by more intuitive map downloading
|
||||
|
||||
void sendmap(char *mapname)
|
||||
{
|
||||
if(*mapname) save_world(mapname);
|
||||
changemap(mapname);
|
||||
mapname = getclientmap();
|
||||
int mapsize;
|
||||
uchar *mapdata = readmap(mapname, &mapsize);
|
||||
if(!mapdata) return;
|
||||
ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + mapsize, ENET_PACKET_FLAG_RELIABLE);
|
||||
uchar *start = packet->data;
|
||||
uchar *p = start+2;
|
||||
putint(p, SV_SENDMAP);
|
||||
sendstring(mapname, p);
|
||||
putint(p, mapsize);
|
||||
if(65535 - (p - start) < mapsize)
|
||||
{
|
||||
conoutf("map %s is too large to send", mapname);
|
||||
free(mapdata);
|
||||
enet_packet_destroy(packet);
|
||||
return;
|
||||
};
|
||||
memcpy(p, mapdata, mapsize);
|
||||
p += mapsize;
|
||||
free(mapdata);
|
||||
*(ushort *)start = ENET_HOST_TO_NET_16(p-start);
|
||||
enet_packet_resize(packet, p-start);
|
||||
sendpackettoserv(packet);
|
||||
conoutf("sending map %s to server...", mapname);
|
||||
sprintf_sd(msg)("[map %s uploaded to server, \"getmap\" to receive it]", mapname);
|
||||
toserver(msg);
|
||||
}
|
||||
|
||||
void getmap()
|
||||
{
|
||||
ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
|
||||
uchar *start = packet->data;
|
||||
uchar *p = start+2;
|
||||
putint(p, SV_RECVMAP);
|
||||
*(ushort *)start = ENET_HOST_TO_NET_16(p-start);
|
||||
enet_packet_resize(packet, p-start);
|
||||
sendpackettoserv(packet);
|
||||
conoutf("requesting map from server...");
|
||||
}
|
||||
|
||||
COMMAND(sendmap, ARG_1STR);
|
||||
COMMAND(getmap, ARG_NONE);
|
||||
|
455
src/clientgame.cpp
Normal file
455
src/clientgame.cpp
Normal file
|
@ -0,0 +1,455 @@
|
|||
// clientgame.cpp: core game related stuff
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
int nextmode = 0; // nextmode becomes gamemode after next map load
|
||||
VAR(gamemode, 1, 0, 0);
|
||||
|
||||
void mode(int n) { addmsg(1, 2, SV_GAMEMODE, nextmode = n); };
|
||||
COMMAND(mode, ARG_1INT);
|
||||
|
||||
bool intermission = false;
|
||||
|
||||
dynent *player1 = newdynent(); // our client
|
||||
dvector players; // other clients
|
||||
|
||||
VARP(sensitivity, 0, 10, 10000);
|
||||
VARP(sensitivityscale, 1, 1, 10000);
|
||||
VARP(invmouse, 0, 0, 1);
|
||||
|
||||
int lastmillis = 0;
|
||||
int curtime = 10;
|
||||
string clientmap;
|
||||
|
||||
extern int framesinmap;
|
||||
|
||||
char *getclientmap() { return clientmap; };
|
||||
|
||||
void resetmovement(dynent *d)
|
||||
{
|
||||
d->k_left = false;
|
||||
d->k_right = false;
|
||||
d->k_up = false;
|
||||
d->k_down = false;
|
||||
d->jumpnext = false;
|
||||
d->strafe = 0;
|
||||
d->move = 0;
|
||||
};
|
||||
|
||||
void spawnstate(dynent *d) // reset player state not persistent accross spawns
|
||||
{
|
||||
resetmovement(d);
|
||||
d->vel.x = d->vel.y = d->vel.z = 0;
|
||||
d->onfloor = false;
|
||||
d->timeinair = 0;
|
||||
d->health = 100;
|
||||
d->armour = 50;
|
||||
d->armourtype = A_BLUE;
|
||||
d->quadmillis = 0;
|
||||
d->lastattackgun = d->gunselect = GUN_SG;
|
||||
d->gunwait = 0;
|
||||
d->attacking = false;
|
||||
d->lastaction = 0;
|
||||
loopi(NUMGUNS) d->ammo[i] = 0;
|
||||
d->ammo[GUN_FIST] = 1;
|
||||
if(m_noitems)
|
||||
{
|
||||
d->gunselect = GUN_RIFLE;
|
||||
d->armour = 0;
|
||||
if(m_noitemsrail)
|
||||
{
|
||||
d->health = 1;
|
||||
d->ammo[GUN_RIFLE] = 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(gamemode==12) { d->gunselect = GUN_FIST; return; }; // eihrul's secret "instafist" mode
|
||||
d->health = 256;
|
||||
if(m_tarena)
|
||||
{
|
||||
int gun1 = rnd(4)+1;
|
||||
baseammo(d->gunselect = gun1);
|
||||
for(;;)
|
||||
{
|
||||
int gun2 = rnd(4)+1;
|
||||
if(gun1!=gun2) { baseammo(gun2); break; };
|
||||
};
|
||||
}
|
||||
else if(m_arena) // insta arena
|
||||
{
|
||||
d->ammo[GUN_RIFLE] = 100;
|
||||
}
|
||||
else // efficiency
|
||||
{
|
||||
loopi(4) baseammo(i+1);
|
||||
d->gunselect = GUN_CG;
|
||||
};
|
||||
d->ammo[GUN_CG] /= 2;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
d->ammo[GUN_SG] = 5;
|
||||
};
|
||||
};
|
||||
|
||||
dynent *newdynent() // create a new blank player or monster
|
||||
{
|
||||
dynent *d = (dynent *)gp()->alloc(sizeof(dynent));
|
||||
d->o.x = 0;
|
||||
d->o.y = 0;
|
||||
d->o.z = 0;
|
||||
d->yaw = 270;
|
||||
d->pitch = 0;
|
||||
d->roll = 0;
|
||||
d->maxspeed = 22;
|
||||
d->outsidemap = false;
|
||||
d->inwater = false;
|
||||
d->radius = 1.1f;
|
||||
d->eyeheight = 3.2f;
|
||||
d->aboveeye = 0.7f;
|
||||
d->frags = 0;
|
||||
d->plag = 0;
|
||||
d->ping = 0;
|
||||
d->lastupdate = lastmillis;
|
||||
d->enemy = NULL;
|
||||
d->monsterstate = 0;
|
||||
d->name[0] = d->team[0] = 0;
|
||||
d->blocked = false;
|
||||
d->lifesequence = 0;
|
||||
d->state = CS_ALIVE;
|
||||
spawnstate(d);
|
||||
return d;
|
||||
};
|
||||
|
||||
void respawnself()
|
||||
{
|
||||
spawnplayer(player1);
|
||||
showscores(false);
|
||||
};
|
||||
|
||||
void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam)
|
||||
{
|
||||
if(d->state!=CS_DEAD)
|
||||
{
|
||||
alive++;
|
||||
if(lastteam && strcmp(lastteam, d->team)) oneteam = false;
|
||||
lastteam = d->team;
|
||||
}
|
||||
else
|
||||
{
|
||||
dead++;
|
||||
};
|
||||
};
|
||||
|
||||
int arenarespawnwait = 0;
|
||||
int arenadetectwait = 0;
|
||||
|
||||
void arenarespawn()
|
||||
{
|
||||
if(arenarespawnwait)
|
||||
{
|
||||
if(arenarespawnwait<lastmillis)
|
||||
{
|
||||
arenarespawnwait = 0;
|
||||
conoutf("new round starting... fight!");
|
||||
respawnself();
|
||||
};
|
||||
}
|
||||
else if(arenadetectwait==0 || arenadetectwait<lastmillis)
|
||||
{
|
||||
arenadetectwait = 0;
|
||||
int alive = 0, dead = 0;
|
||||
char *lastteam = NULL;
|
||||
bool oneteam = true;
|
||||
loopv(players) if(players[i]) arenacount(players[i], alive, dead, lastteam, oneteam);
|
||||
arenacount(player1, alive, dead, lastteam, oneteam);
|
||||
if(dead>0 && (alive<=1 || (m_teammode && oneteam)))
|
||||
{
|
||||
conoutf("arena round is over! next round in 5 seconds...");
|
||||
if(alive) conoutf("team %s is last man standing", lastteam);
|
||||
else conoutf("everyone died!");
|
||||
arenarespawnwait = lastmillis+5000;
|
||||
arenadetectwait = lastmillis+10000;
|
||||
player1->roll = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
void zapdynent(dynent *&d)
|
||||
{
|
||||
if(d) gp()->dealloc(d, sizeof(dynent));
|
||||
d = NULL;
|
||||
};
|
||||
|
||||
extern int democlientnum;
|
||||
|
||||
void otherplayers()
|
||||
{
|
||||
loopv(players) if(players[i])
|
||||
{
|
||||
const int lagtime = lastmillis-players[i]->lastupdate;
|
||||
if(lagtime>1000 && players[i]->state==CS_ALIVE)
|
||||
{
|
||||
players[i]->state = CS_LAGGED;
|
||||
continue;
|
||||
};
|
||||
if(lagtime && players[i]->state != CS_DEAD && (!demoplayback || i!=democlientnum)) moveplayer(players[i], 2, false); // use physics to extrapolate player position
|
||||
};
|
||||
};
|
||||
|
||||
void respawn()
|
||||
{
|
||||
if(player1->state==CS_DEAD)
|
||||
{
|
||||
player1->attacking = false;
|
||||
if(m_arena) { conoutf("waiting for new round to start..."); return; };
|
||||
if(m_sp) { nextmode = gamemode; changemap(clientmap); return; }; // if we die in SP we try the same map again
|
||||
respawnself();
|
||||
};
|
||||
};
|
||||
|
||||
int sleepwait = 0;
|
||||
string sleepcmd;
|
||||
void sleepf(char *msec, char *cmd) { sleepwait = atoi(msec)+lastmillis; strcpy_s(sleepcmd, cmd); };
|
||||
COMMANDN(sleep, sleepf, ARG_2STR);
|
||||
|
||||
void updateworld(int millis) // main game update loop
|
||||
{
|
||||
if(lastmillis)
|
||||
{
|
||||
curtime = millis - lastmillis;
|
||||
if(sleepwait && lastmillis>sleepwait) { sleepwait = 0; execute(sleepcmd); };
|
||||
physicsframe();
|
||||
checkquad(curtime);
|
||||
if(m_arena) arenarespawn();
|
||||
moveprojectiles((float)curtime);
|
||||
demoplaybackstep();
|
||||
if(!demoplayback)
|
||||
{
|
||||
if(getclientnum()>=0) shoot(player1, worldpos); // only shoot when connected to server
|
||||
gets2c(); // do this first, so we have most accurate information when our player moves
|
||||
};
|
||||
otherplayers();
|
||||
if(!demoplayback)
|
||||
{
|
||||
monsterthink();
|
||||
if(player1->state==CS_DEAD)
|
||||
{
|
||||
if(lastmillis-player1->lastaction<2000)
|
||||
{
|
||||
player1->move = player1->strafe = 0;
|
||||
moveplayer(player1, 10, false);
|
||||
}
|
||||
else if(!m_arena && !m_sp && lastmillis-player1->lastaction>10000) respawn();
|
||||
}
|
||||
else if(!intermission)
|
||||
{
|
||||
moveplayer(player1, 20, true);
|
||||
checkitems();
|
||||
};
|
||||
c2sinfo(player1); // do this last, to reduce the effective frame lag
|
||||
};
|
||||
};
|
||||
lastmillis = millis;
|
||||
};
|
||||
|
||||
void entinmap(dynent *d) // brute force but effective way to find a free spawn spot in the map
|
||||
{
|
||||
loopi(100) // try max 100 times
|
||||
{
|
||||
float dx = (rnd(21)-10)/10.0f*i; // increasing distance
|
||||
float dy = (rnd(21)-10)/10.0f*i;
|
||||
d->o.x += dx;
|
||||
d->o.y += dy;
|
||||
if(collide(d, true, 0, 0)) return;
|
||||
d->o.x -= dx;
|
||||
d->o.y -= dy;
|
||||
};
|
||||
conoutf("can't find entity spawn spot! (%d, %d)", (int)d->o.x, (int)d->o.y);
|
||||
// leave ent at original pos, possibly stuck
|
||||
};
|
||||
|
||||
int spawncycle = -1;
|
||||
int fixspawn = 2;
|
||||
|
||||
void spawnplayer(dynent *d) // place at random spawn. also used by monsters!
|
||||
{
|
||||
int r = fixspawn-->0 ? 4 : rnd(10)+1;
|
||||
loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle+1);
|
||||
if(spawncycle!=-1)
|
||||
{
|
||||
d->o.x = ents[spawncycle].x;
|
||||
d->o.y = ents[spawncycle].y;
|
||||
d->o.z = ents[spawncycle].z;
|
||||
d->yaw = ents[spawncycle].attr1;
|
||||
d->pitch = 0;
|
||||
d->roll = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
d->o.x = d->o.y = (float)ssize/2;
|
||||
d->o.z = 4;
|
||||
};
|
||||
entinmap(d);
|
||||
spawnstate(d);
|
||||
d->state = CS_ALIVE;
|
||||
};
|
||||
|
||||
// movement input code
|
||||
|
||||
#define dir(name,v,d,s,os) void name(bool isdown) { player1->s = isdown; player1->v = isdown ? d : (player1->os ? -(d) : 0); player1->lastmove = lastmillis; };
|
||||
|
||||
dir(backward, move, -1, k_down, k_up);
|
||||
dir(forward, move, 1, k_up, k_down);
|
||||
dir(left, strafe, 1, k_left, k_right);
|
||||
dir(right, strafe, -1, k_right, k_left);
|
||||
|
||||
void attack(bool on)
|
||||
{
|
||||
if(intermission) return;
|
||||
if(editmode) editdrag(on);
|
||||
else if(player1->attacking = on) respawn();
|
||||
};
|
||||
|
||||
void jumpn(bool on) { if(!intermission && (player1->jumpnext = on)) respawn(); };
|
||||
|
||||
COMMAND(backward, ARG_DOWN);
|
||||
COMMAND(forward, ARG_DOWN);
|
||||
COMMAND(left, ARG_DOWN);
|
||||
COMMAND(right, ARG_DOWN);
|
||||
COMMANDN(jump, jumpn, ARG_DOWN);
|
||||
COMMAND(attack, ARG_DOWN);
|
||||
COMMAND(showscores, ARG_DOWN);
|
||||
|
||||
void fixplayer1range()
|
||||
{
|
||||
const float MAXPITCH = 90.0f;
|
||||
if(player1->pitch>MAXPITCH) player1->pitch = MAXPITCH;
|
||||
if(player1->pitch<-MAXPITCH) player1->pitch = -MAXPITCH;
|
||||
while(player1->yaw<0.0f) player1->yaw += 360.0f;
|
||||
while(player1->yaw>=360.0f) player1->yaw -= 360.0f;
|
||||
};
|
||||
|
||||
void mousemove(int dx, int dy)
|
||||
{
|
||||
if(player1->state==CS_DEAD || intermission) return;
|
||||
const float SENSF = 33.0f; // try match quake sens
|
||||
player1->yaw += (dx/SENSF)*(sensitivity/(float)sensitivityscale);
|
||||
player1->pitch -= (dy/SENSF)*(sensitivity/(float)sensitivityscale)*(invmouse ? -1 : 1);
|
||||
fixplayer1range();
|
||||
};
|
||||
|
||||
// damage arriving from the network, monsters, yourself, all ends up here.
|
||||
|
||||
void selfdamage(int damage, int actor, dynent *act)
|
||||
{
|
||||
if(player1->state!=CS_ALIVE || editmode || intermission) return;
|
||||
damageblend(damage);
|
||||
demoblend(damage);
|
||||
int ad = damage*(player1->armourtype+1)*20/100; // let armour absorb when possible
|
||||
if(ad>player1->armour) ad = player1->armour;
|
||||
player1->armour -= ad;
|
||||
damage -= ad;
|
||||
float droll = damage/0.5f;
|
||||
player1->roll += player1->roll>0 ? droll : (player1->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage
|
||||
if((player1->health -= damage)<=0)
|
||||
{
|
||||
if(actor==-2)
|
||||
{
|
||||
conoutf("you got killed by %s!", act->name);
|
||||
}
|
||||
else if(actor==-1)
|
||||
{
|
||||
actor = getclientnum();
|
||||
conoutf("you suicided!");
|
||||
addmsg(1, 2, SV_FRAGS, --player1->frags);
|
||||
}
|
||||
else
|
||||
{
|
||||
dynent *a = getclient(actor);
|
||||
if(a)
|
||||
{
|
||||
if(isteam(a->team, player1->team))
|
||||
{
|
||||
conoutf("you got fragged by a teammate (%s)", a->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
conoutf("you got fragged by %s", a->name);
|
||||
};
|
||||
};
|
||||
};
|
||||
showscores(true);
|
||||
addmsg(1, 2, SV_DIED, actor);
|
||||
player1->lifesequence++;
|
||||
player1->attacking = false;
|
||||
player1->state = CS_DEAD;
|
||||
player1->pitch = 0;
|
||||
player1->roll = 60;
|
||||
playsound(S_DIE1+rnd(2));
|
||||
spawnstate(player1);
|
||||
player1->lastaction = lastmillis;
|
||||
}
|
||||
else
|
||||
{
|
||||
playsound(S_PAIN6);
|
||||
};
|
||||
};
|
||||
|
||||
void timeupdate(int timeremain)
|
||||
{
|
||||
if(!timeremain)
|
||||
{
|
||||
intermission = true;
|
||||
player1->attacking = false;
|
||||
conoutf("intermission:");
|
||||
conoutf("game has ended!");
|
||||
showscores(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
conoutf("time remaining: %d minutes", timeremain);
|
||||
};
|
||||
};
|
||||
|
||||
dynent *getclient(int cn) // ensure valid entity
|
||||
{
|
||||
if(cn<0 || cn>=MAXCLIENTS)
|
||||
{
|
||||
neterr("clientnum");
|
||||
return NULL;
|
||||
};
|
||||
while(cn>=players.length()) players.add(NULL);
|
||||
return players[cn] ? players[cn] : (players[cn] = newdynent());
|
||||
};
|
||||
|
||||
void initclient()
|
||||
{
|
||||
clientmap[0] = 0;
|
||||
initclientnet();
|
||||
};
|
||||
|
||||
void startmap(char *name) // called just after a map load
|
||||
{
|
||||
if(netmapstart() && m_sp) { gamemode = 0; conoutf("coop sp not supported yet"); };
|
||||
sleepwait = 0;
|
||||
monsterclear();
|
||||
projreset();
|
||||
spawncycle = -1;
|
||||
spawnplayer(player1);
|
||||
player1->frags = 0;
|
||||
loopv(players) if(players[i]) players[i]->frags = 0;
|
||||
resetspawns();
|
||||
strcpy_s(clientmap, name);
|
||||
if(editmode) toggleedit();
|
||||
setvar("gamespeed", 100);
|
||||
setvar("fog", 180);
|
||||
setvar("fogcolour", 0x8099B3);
|
||||
showscores(false);
|
||||
intermission = false;
|
||||
framesinmap = 0;
|
||||
conoutf("game mode is %s", modestr(gamemode));
|
||||
};
|
||||
|
||||
COMMANDN(map, changemap, ARG_1STR);
|
355
src/clients2c.cpp
Normal file
355
src/clients2c.cpp
Normal file
|
@ -0,0 +1,355 @@
|
|||
// client processing of the incoming network stream
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
extern int clientnum;
|
||||
extern bool c2sinit, senditemstoserver;
|
||||
extern string toservermap;
|
||||
extern string clientpassword;
|
||||
|
||||
void neterr(char *s)
|
||||
{
|
||||
conoutf("illegal network message (%s)", s);
|
||||
disconnect();
|
||||
};
|
||||
|
||||
void changemapserv(char *name, int mode) // forced map change from the server
|
||||
{
|
||||
gamemode = mode;
|
||||
load_world(name);
|
||||
};
|
||||
|
||||
void changemap(char *name) // request map change, server may ignore
|
||||
{
|
||||
strcpy_s(toservermap, name);
|
||||
};
|
||||
|
||||
// update the position of other clients in the game in our world
|
||||
// don't care if he's in the scenery or other players,
|
||||
// just don't overlap with our client
|
||||
|
||||
void updatepos(dynent *d)
|
||||
{
|
||||
const float r = player1->radius+d->radius;
|
||||
const float dx = player1->o.x-d->o.x;
|
||||
const float dy = player1->o.y-d->o.y;
|
||||
const float dz = player1->o.z-d->o.z;
|
||||
const float rz = player1->aboveeye+d->eyeheight;
|
||||
const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz);
|
||||
if(fx<r && fy<r && fz<rz && d->state!=CS_DEAD)
|
||||
{
|
||||
if(fx<fy) d->o.y += dy<0 ? r-fy : -(r-fy); // push aside
|
||||
else d->o.x += dx<0 ? r-fx : -(r-fx);
|
||||
};
|
||||
int lagtime = lastmillis-d->lastupdate;
|
||||
if(lagtime)
|
||||
{
|
||||
d->plag = (d->plag*5+lagtime)/6;
|
||||
d->lastupdate = lastmillis;
|
||||
};
|
||||
};
|
||||
|
||||
void localservertoclient(uchar *buf, int len) // processes any updates from the server
|
||||
{
|
||||
if(ENET_NET_TO_HOST_16(*(ushort *)buf)!=len) neterr("packet length");
|
||||
incomingdemodata(buf, len);
|
||||
|
||||
uchar *end = buf+len;
|
||||
uchar *p = buf+2;
|
||||
char text[MAXTRANS];
|
||||
int cn = -1, type;
|
||||
dynent *d = NULL;
|
||||
bool mapchanged = false;
|
||||
|
||||
while(p<end) switch(type = getint(p))
|
||||
{
|
||||
case SV_INITS2C: // welcome messsage from the server
|
||||
{
|
||||
cn = getint(p);
|
||||
int prot = getint(p);
|
||||
if(prot!=PROTOCOL_VERSION)
|
||||
{
|
||||
conoutf("you are using a different game protocol (you: %d, server: %d)", PROTOCOL_VERSION, prot);
|
||||
disconnect();
|
||||
return;
|
||||
};
|
||||
toservermap[0] = 0;
|
||||
clientnum = cn; // we are now fully connected
|
||||
if(!getint(p)) strcpy_s(toservermap, getclientmap()); // we are the first client on this server, set map
|
||||
sgetstr();
|
||||
if(text[0] && strcmp(text, clientpassword))
|
||||
{
|
||||
conoutf("you need to set the correct password to join this server!");
|
||||
disconnect();
|
||||
return;
|
||||
};
|
||||
if(getint(p)==1)
|
||||
{
|
||||
conoutf("server is FULL, disconnecting..");
|
||||
};
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_POS: // position of another client
|
||||
{
|
||||
cn = getint(p);
|
||||
d = getclient(cn);
|
||||
if(!d) return;
|
||||
d->o.x = getint(p)/DMF;
|
||||
d->o.y = getint(p)/DMF;
|
||||
d->o.z = getint(p)/DMF;
|
||||
d->yaw = getint(p)/DAF;
|
||||
d->pitch = getint(p)/DAF;
|
||||
d->roll = getint(p)/DAF;
|
||||
d->vel.x = getint(p)/DVF;
|
||||
d->vel.y = getint(p)/DVF;
|
||||
d->vel.z = getint(p)/DVF;
|
||||
int f = getint(p);
|
||||
d->strafe = (f&3)==3 ? -1 : f&3;
|
||||
f >>= 2;
|
||||
d->move = (f&3)==3 ? -1 : f&3;
|
||||
d->onfloor = (f>>2)&1;
|
||||
int state = f>>3;
|
||||
if(state==CS_DEAD && d->state!=CS_DEAD) d->lastaction = lastmillis;
|
||||
d->state = state;
|
||||
if(!demoplayback) updatepos(d);
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_SOUND:
|
||||
playsound(getint(p), &d->o);
|
||||
break;
|
||||
|
||||
case SV_TEXT:
|
||||
sgetstr();
|
||||
conoutf("%s:\f %s", d->name, text);
|
||||
break;
|
||||
|
||||
case SV_MAPCHANGE:
|
||||
sgetstr();
|
||||
changemapserv(text, getint(p));
|
||||
mapchanged = true;
|
||||
break;
|
||||
|
||||
case SV_ITEMLIST:
|
||||
{
|
||||
int n;
|
||||
if(mapchanged) { senditemstoserver = false; resetspawns(); };
|
||||
while((n = getint(p))!=-1) if(mapchanged) setspawn(n, true);
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_MAPRELOAD: // server requests next map
|
||||
{
|
||||
getint(p);
|
||||
sprintf_sd(nextmapalias)("nextmap_%s", getclientmap());
|
||||
char *map = getalias(nextmapalias); // look up map in the cycle
|
||||
changemap(map ? map : getclientmap());
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_INITC2S: // another client either connected or changed name/team
|
||||
{
|
||||
sgetstr();
|
||||
if(d->name[0]) // already connected
|
||||
{
|
||||
if(strcmp(d->name, text))
|
||||
conoutf("%s is now known as %s", d->name, text);
|
||||
}
|
||||
else // new client
|
||||
{
|
||||
c2sinit = false; // send new players my info again
|
||||
conoutf("connected: %s", text);
|
||||
};
|
||||
strcpy_s(d->name, text);
|
||||
sgetstr();
|
||||
strcpy_s(d->team, text);
|
||||
d->lifesequence = getint(p);
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_CDIS:
|
||||
cn = getint(p);
|
||||
if(!(d = getclient(cn))) break;
|
||||
conoutf("player %s disconnected", d->name[0] ? d->name : "[incompatible client]");
|
||||
zapdynent(players[cn]);
|
||||
break;
|
||||
|
||||
case SV_SHOT:
|
||||
{
|
||||
int gun = getint(p);
|
||||
vec s, e;
|
||||
s.x = getint(p)/DMF;
|
||||
s.y = getint(p)/DMF;
|
||||
s.z = getint(p)/DMF;
|
||||
e.x = getint(p)/DMF;
|
||||
e.y = getint(p)/DMF;
|
||||
e.z = getint(p)/DMF;
|
||||
if(gun==GUN_SG) createrays(s, e);
|
||||
shootv(gun, s, e, d);
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_DAMAGE:
|
||||
{
|
||||
int target = getint(p);
|
||||
int damage = getint(p);
|
||||
int ls = getint(p);
|
||||
if(target==clientnum) { if(ls==player1->lifesequence) selfdamage(damage, cn, d); }
|
||||
else playsound(S_PAIN1+rnd(5), &getclient(target)->o);
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_DIED:
|
||||
{
|
||||
int actor = getint(p);
|
||||
if(actor==cn)
|
||||
{
|
||||
conoutf("%s suicided", d->name);
|
||||
}
|
||||
else if(actor==clientnum)
|
||||
{
|
||||
int frags;
|
||||
if(isteam(player1->team, d->team))
|
||||
{
|
||||
frags = -1;
|
||||
conoutf("you fragged a teammate (%s)", d->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
frags = 1;
|
||||
conoutf("you fragged %s", d->name);
|
||||
};
|
||||
addmsg(1, 2, SV_FRAGS, player1->frags += frags);
|
||||
}
|
||||
else
|
||||
{
|
||||
dynent *a = getclient(actor);
|
||||
if(a)
|
||||
{
|
||||
if(isteam(a->team, d->name))
|
||||
{
|
||||
conoutf("%s fragged his teammate (%s)", a->name, d->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
conoutf("%s fragged %s", a->name, d->name);
|
||||
};
|
||||
};
|
||||
};
|
||||
playsound(S_DIE1+rnd(2), &d->o);
|
||||
d->lifesequence++;
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_FRAGS:
|
||||
players[cn]->frags = getint(p);
|
||||
break;
|
||||
|
||||
case SV_ITEMPICKUP:
|
||||
setspawn(getint(p), false);
|
||||
getint(p);
|
||||
break;
|
||||
|
||||
case SV_ITEMSPAWN:
|
||||
{
|
||||
uint i = getint(p);
|
||||
setspawn(i, true);
|
||||
if(i>=(uint)ents.length()) break;
|
||||
vec v = { 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;
|
||||
};
|
||||
};
|
363
src/command.cpp
Normal file
363
src/command.cpp
Normal file
|
@ -0,0 +1,363 @@
|
|||
// 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<ident> *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>;
|
||||
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>;
|
||||
ident c = { ID_COMMAND, name, 0, 0, 0, fun, narg, 0, false };
|
||||
idents->access(name, &c);
|
||||
return false;
|
||||
};
|
||||
|
||||
char *parseexp(char *&p, int right) // parse any nested set of () or []
|
||||
{
|
||||
int left = *p++;
|
||||
char *word = p;
|
||||
for(int brak = 1; brak; )
|
||||
{
|
||||
int c = *p++;
|
||||
if(c=='\r') *(p-1) = ' '; // hack
|
||||
if(c==left) brak++;
|
||||
else if(c==right) brak--;
|
||||
else if(!c) { p--; conoutf("missing \"%c\"", right); return NULL; };
|
||||
};
|
||||
char *s = newstring(word, p-word-1);
|
||||
if(left=='(')
|
||||
{
|
||||
string t;
|
||||
itoa(t, execute(s)); // evaluate () exps directly, and substitute result
|
||||
s = exchangestr(s, t);
|
||||
};
|
||||
return s;
|
||||
};
|
||||
|
||||
char *parseword(char *&p) // parse single argument, including expressions
|
||||
{
|
||||
p += strspn(p, " \t\r");
|
||||
if(p[0]=='/' && p[1]=='/') p += strcspn(p, "\n\0");
|
||||
if(*p=='\"')
|
||||
{
|
||||
p++;
|
||||
char *word = p;
|
||||
p += strcspn(p, "\"\r\n\0");
|
||||
char *s = newstring(word, p-word);
|
||||
if(*p=='\"') p++;
|
||||
return s;
|
||||
};
|
||||
if(*p=='(') return parseexp(p, ')');
|
||||
if(*p=='[') return parseexp(p, ']');
|
||||
char *word = p;
|
||||
p += strcspn(p, "; \t\r\n\0");
|
||||
if(p-word==0) return NULL;
|
||||
return newstring(word, p-word);
|
||||
};
|
||||
|
||||
char *lookup(char *n) // find value of ident referenced with $ in exp
|
||||
{
|
||||
ident *id = idents->access(n+1);
|
||||
if(id) switch(id->type)
|
||||
{
|
||||
case ID_VAR: string t; itoa(t, *(id->storage)); return exchangestr(n, t);
|
||||
case ID_ALIAS: return exchangestr(n, id->action);
|
||||
};
|
||||
conoutf("unknown alias lookup: %s", n+1);
|
||||
return n;
|
||||
};
|
||||
|
||||
int execute(char *p, bool isdown) // all evaluation happens here, recursively
|
||||
{
|
||||
const int MAXWORDS = 25; // limit, remove
|
||||
char *w[MAXWORDS];
|
||||
int val = 0;
|
||||
for(bool cont = true; cont;) // for each ; seperated statement
|
||||
{
|
||||
int numargs = MAXWORDS;
|
||||
loopi(MAXWORDS) // collect all argument values
|
||||
{
|
||||
w[i] = "";
|
||||
if(i>numargs) continue;
|
||||
char *s = parseword(p); // parse and evaluate exps
|
||||
if(!s) { numargs = i; s = ""; };
|
||||
if(*s=='$') s = lookup(s); // substitute variables
|
||||
w[i] = s;
|
||||
};
|
||||
|
||||
p += strcspn(p, ";\n\0");
|
||||
cont = *p++!=0; // more statements if this isn't the end of the string
|
||||
char *c = w[0];
|
||||
if(*c=='/') c++; // strip irc-style command prefix
|
||||
if(!*c) continue; // empty statement
|
||||
|
||||
ident *id = idents->access(c);
|
||||
if(!id)
|
||||
{
|
||||
val = ATOI(c);
|
||||
if(!val && *c!='0') conoutf("unknown command: %s", c);
|
||||
}
|
||||
else switch(id->type)
|
||||
{
|
||||
case ID_COMMAND: // game defined commands
|
||||
switch(id->narg) // use very ad-hoc function signature, and just call it
|
||||
{
|
||||
case ARG_1INT: if(isdown) ((void (__cdecl *)(int))id->fun)(ATOI(w[1])); break;
|
||||
case ARG_2INT: if(isdown) ((void (__cdecl *)(int, int))id->fun)(ATOI(w[1]), ATOI(w[2])); break;
|
||||
case ARG_3INT: if(isdown) ((void (__cdecl *)(int, int, int))id->fun)(ATOI(w[1]), ATOI(w[2]), ATOI(w[3])); break;
|
||||
case ARG_4INT: if(isdown) ((void (__cdecl *)(int, int, int, int))id->fun)(ATOI(w[1]), ATOI(w[2]), ATOI(w[3]), ATOI(w[4])); break;
|
||||
case ARG_NONE: if(isdown) ((void (__cdecl *)())id->fun)(); break;
|
||||
case ARG_1STR: if(isdown) ((void (__cdecl *)(char *))id->fun)(w[1]); break;
|
||||
case ARG_2STR: if(isdown) ((void (__cdecl *)(char *, char *))id->fun)(w[1], w[2]); break;
|
||||
case ARG_3STR: if(isdown) ((void (__cdecl *)(char *, char *, char*))id->fun)(w[1], w[2], w[3]); break;
|
||||
case ARG_5STR: if(isdown) ((void (__cdecl *)(char *, char *, char*, char*, char*))id->fun)(w[1], w[2], w[3], w[4], w[5]); break;
|
||||
case ARG_DOWN: ((void (__cdecl *)(bool))id->fun)(isdown); break;
|
||||
case ARG_DWN1: ((void (__cdecl *)(bool, char *))id->fun)(isdown, w[1]); break;
|
||||
case ARG_1EXP: if(isdown) val = ((int (__cdecl *)(int))id->fun)(execute(w[1])); break;
|
||||
case ARG_2EXP: if(isdown) val = ((int (__cdecl *)(int, int))id->fun)(execute(w[1]), execute(w[2])); break;
|
||||
case ARG_1EST: if(isdown) val = ((int (__cdecl *)(char *))id->fun)(w[1]); break;
|
||||
case ARG_2EST: if(isdown) val = ((int (__cdecl *)(char *, char *))id->fun)(w[1], w[2]); break;
|
||||
case ARG_VARI: if(isdown)
|
||||
{
|
||||
string r; // limit, remove
|
||||
r[0] = 0;
|
||||
for(int i = 1; i<numargs; i++)
|
||||
{
|
||||
strcat_s(r, w[i]); // make string-list out of all arguments
|
||||
if(i==numargs-1) break;
|
||||
strcat_s(r, " ");
|
||||
};
|
||||
((void (__cdecl *)(char *))id->fun)(r);
|
||||
break;
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
case ID_VAR: // game defined variabled
|
||||
if(isdown)
|
||||
{
|
||||
if(!w[1][0]) conoutf("%s = %d", c, *id->storage); // var with no value just prints its current value
|
||||
else
|
||||
{
|
||||
if(id->min>id->max)
|
||||
{
|
||||
conoutf("variable is read-only");
|
||||
}
|
||||
else
|
||||
{
|
||||
int i1 = ATOI(w[1]);
|
||||
if(i1<id->min || i1>id->max)
|
||||
{
|
||||
i1 = i1<id->min ? id->min : id->max; // clamp to valid range
|
||||
conoutf("valid range for %s is %d..%d", c, id->min, id->max);
|
||||
}
|
||||
*id->storage = i1;
|
||||
};
|
||||
if(id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available
|
||||
};
|
||||
};
|
||||
break;
|
||||
|
||||
case ID_ALIAS: // alias, also used as functions and (global) variables
|
||||
for(int i = 1; i<numargs; i++)
|
||||
{
|
||||
sprintf_sd(t)("arg%d", i); // set any arguments as (global) arg values so functions can access them
|
||||
alias(t, w[i]);
|
||||
};
|
||||
char *action = newstring(id->action); // create new string here because alias could rebind itself
|
||||
val = execute(action, isdown);
|
||||
gp()->deallocstr(action);
|
||||
break;
|
||||
};
|
||||
loopj(numargs) gp()->deallocstr(w[j]);
|
||||
};
|
||||
return val;
|
||||
};
|
||||
|
||||
// tab-completion of all idents
|
||||
|
||||
int completesize = 0, completeidx = 0;
|
||||
|
||||
void resetcomplete() { completesize = 0; };
|
||||
|
||||
void complete(char *s)
|
||||
{
|
||||
if(*s!='/')
|
||||
{
|
||||
string t;
|
||||
strcpy_s(t, s);
|
||||
strcpy_s(s, "/");
|
||||
strcat_s(s, t);
|
||||
};
|
||||
if(!s[1]) return;
|
||||
if(!completesize) { completesize = (int)strlen(s)-1; completeidx = 0; };
|
||||
int idx = 0;
|
||||
enumerate(idents, ident *, id,
|
||||
if(strncmp(id->name, s+1, completesize)==0 && idx++==completeidx)
|
||||
{
|
||||
strcpy_s(s, "/");
|
||||
strcat_s(s, id->name);
|
||||
};
|
||||
);
|
||||
completeidx++;
|
||||
if(completeidx>=idx) completeidx = 0;
|
||||
};
|
||||
|
||||
bool execfile(char *cfgfile)
|
||||
{
|
||||
string s;
|
||||
strcpy_s(s, cfgfile);
|
||||
char *buf = loadfile(path(s), NULL);
|
||||
if(!buf) return false;
|
||||
execute(buf);
|
||||
free(buf);
|
||||
return true;
|
||||
};
|
||||
|
||||
void exec(char *cfgfile)
|
||||
{
|
||||
if(!execfile(cfgfile)) conoutf("could not read \"%s\"", cfgfile);
|
||||
};
|
||||
|
||||
void writecfg()
|
||||
{
|
||||
FILE *f = fopen("config.cfg", "w");
|
||||
if(!f) return;
|
||||
fprintf(f, "// automatically written on exit, do not modify\n// delete this file to have defaults.cfg overwrite these settings\n// modify settings in game, or put settings in autoexec.cfg to override anything\n\n");
|
||||
writeclientinfo(f);
|
||||
fprintf(f, "\n");
|
||||
enumerate(idents, ident *, id,
|
||||
if(id->type==ID_VAR && id->persist)
|
||||
{
|
||||
fprintf(f, "%s %d\n", id->name, *id->storage);
|
||||
};
|
||||
);
|
||||
fprintf(f, "\n");
|
||||
writebinds(f);
|
||||
fprintf(f, "\n");
|
||||
enumerate(idents, ident *, id,
|
||||
if(id->type==ID_ALIAS && !strstr(id->name, "nextmap_"))
|
||||
{
|
||||
fprintf(f, "alias \"%s\" [%s]\n", id->name, id->action);
|
||||
};
|
||||
);
|
||||
fclose(f);
|
||||
};
|
||||
|
||||
COMMAND(writecfg, ARG_NONE);
|
||||
|
||||
// below the commands that implement a small imperative language. thanks to the semantics of
|
||||
// () and [] expressions, any control construct can be defined trivially.
|
||||
|
||||
void intset(char *name, int v) { string b; itoa(b, v); alias(name, b); };
|
||||
|
||||
void ifthen(char *cond, char *thenp, char *elsep) { execute(cond[0]!='0' ? thenp : elsep); };
|
||||
void loopa(char *times, char *body) { int t = atoi(times); loopi(t) { intset("i", i); execute(body); }; };
|
||||
void whilea(char *cond, char *body) { while(execute(cond)) execute(body); }; // can't get any simpler than this :)
|
||||
void onrelease(bool on, char *body) { if(!on) execute(body); };
|
||||
|
||||
void concat(char *s) { alias("s", s); };
|
||||
|
||||
void concatword(char *s)
|
||||
{
|
||||
for(char *a = s, *b = s; *a = *b; b++) if(*a!=' ') a++;
|
||||
concat(s);
|
||||
};
|
||||
|
||||
int listlen(char *a)
|
||||
{
|
||||
if(!*a) return 0;
|
||||
int n = 0;
|
||||
while(*a) if(*a++==' ') n++;
|
||||
return n+1;
|
||||
};
|
||||
|
||||
void at(char *s, char *pos)
|
||||
{
|
||||
int n = atoi(pos);
|
||||
loopi(n) s += strspn(s += strcspn(s, " \0"), " ");
|
||||
s[strcspn(s, " \0")] = 0;
|
||||
concat(s);
|
||||
};
|
||||
|
||||
COMMANDN(loop, loopa, ARG_2STR);
|
||||
COMMANDN(while, whilea, ARG_2STR);
|
||||
COMMANDN(if, ifthen, ARG_3STR);
|
||||
COMMAND(onrelease, ARG_DWN1);
|
||||
COMMAND(exec, ARG_1STR);
|
||||
COMMAND(concat, ARG_VARI);
|
||||
COMMAND(concatword, ARG_VARI);
|
||||
COMMAND(at, ARG_2STR);
|
||||
COMMAND(listlen, ARG_1EST);
|
||||
|
||||
int add(int a, int b) { return a+b; }; COMMANDN(+, add, ARG_2EXP);
|
||||
int mul(int a, int b) { return a*b; }; COMMANDN(*, mul, ARG_2EXP);
|
||||
int sub(int a, int b) { return a-b; }; COMMANDN(-, sub, ARG_2EXP);
|
||||
int divi(int a, int b) { return b ? a/b : 0; }; COMMANDN(div, divi, ARG_2EXP);
|
||||
int mod(int a, int b) { return b ? a%b : 0; }; COMMAND(mod, ARG_2EXP);
|
||||
int equal(int a, int b) { return (int)(a==b); }; COMMANDN(=, equal, ARG_2EXP);
|
||||
int lt(int a, int b) { return (int)(a<b); }; COMMANDN(<, lt, ARG_2EXP);
|
||||
int gt(int a, int b) { return (int)(a>b); }; COMMANDN(>, gt, ARG_2EXP);
|
||||
|
||||
int strcmpa(char *a, char *b) { return strcmp(a,b)==0; }; COMMANDN(strcmp, strcmpa, ARG_2EST);
|
||||
|
||||
int rndn(int a) { return a>0 ? rnd(a) : 0; }; COMMANDN(rnd, rndn, ARG_1EXP);
|
||||
|
||||
int explastmillis() { return lastmillis; }; COMMANDN(millis, explastmillis, ARG_1EXP);
|
||||
|
254
src/console.cpp
Normal file
254
src/console.cpp
Normal file
|
@ -0,0 +1,254 @@
|
|||
// console.cpp: the console buffer, its display, and command line control
|
||||
|
||||
#include "cube.h"
|
||||
#include <ctype.h>
|
||||
|
||||
struct cline { char *cref; int outtime; };
|
||||
vector<cline> conlines;
|
||||
|
||||
const int ndraw = 5;
|
||||
const int WORDWRAP = 80;
|
||||
int conskip = 0;
|
||||
|
||||
bool saycommandon = false;
|
||||
string commandbuf;
|
||||
|
||||
void setconskip(int n)
|
||||
{
|
||||
conskip += n;
|
||||
if(conskip<0) conskip = 0;
|
||||
};
|
||||
|
||||
COMMANDN(conskip, setconskip, ARG_1INT);
|
||||
|
||||
void conline(const char *sf, bool highlight) // add a line to the console buffer
|
||||
{
|
||||
cline cl;
|
||||
cl.cref = conlines.length()>100 ? conlines.pop().cref : newstringbuf(""); // constrain the buffer size
|
||||
cl.outtime = lastmillis; // for how long to keep line on screen
|
||||
conlines.insert(0,cl);
|
||||
if(highlight) // show line in a different colour, for chat etc.
|
||||
{
|
||||
cl.cref[0] = '\f';
|
||||
cl.cref[1] = 0;
|
||||
strcat_s(cl.cref, sf);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy_s(cl.cref, sf);
|
||||
};
|
||||
puts(cl.cref);
|
||||
#ifndef WIN32
|
||||
fflush(stdout);
|
||||
#endif
|
||||
};
|
||||
|
||||
void conoutf(const char *s, ...)
|
||||
{
|
||||
sprintf_sdv(sf, s);
|
||||
s = sf;
|
||||
int n = 0;
|
||||
while(strlen(s)>WORDWRAP) // cut strings to fit on screen
|
||||
{
|
||||
string t;
|
||||
strn0cpy(t, s, WORDWRAP+1);
|
||||
conline(t, n++!=0);
|
||||
s += WORDWRAP;
|
||||
};
|
||||
conline(s, n!=0);
|
||||
};
|
||||
|
||||
void renderconsole() // render buffer taking into account time & scrolling
|
||||
{
|
||||
int nd = 0;
|
||||
char *refs[ndraw];
|
||||
loopv(conlines) if(conskip ? i>=conskip-1 || i>=conlines.length()-ndraw : lastmillis-conlines[i].outtime<20000)
|
||||
{
|
||||
refs[nd++] = conlines[i].cref;
|
||||
if(nd==ndraw) break;
|
||||
};
|
||||
loopj(nd)
|
||||
{
|
||||
draw_text(refs[j], FONTH/3, (FONTH/4*5)*(nd-j-1)+FONTH/3, 2);
|
||||
};
|
||||
};
|
||||
|
||||
// keymap is defined externally in keymap.cfg
|
||||
|
||||
struct keym { int code; char *name; char *action; } keyms[256];
|
||||
int numkm = 0;
|
||||
|
||||
void keymap(char *code, char *key, char *action)
|
||||
{
|
||||
keyms[numkm].code = atoi(code);
|
||||
keyms[numkm].name = newstring(key);
|
||||
keyms[numkm++].action = newstringbuf(action);
|
||||
};
|
||||
|
||||
COMMAND(keymap, ARG_3STR);
|
||||
|
||||
void bindkey(char *key, char *action)
|
||||
{
|
||||
for(char *x = key; *x; x++) *x = toupper(*x);
|
||||
loopi(numkm) if(strcmp(keyms[i].name, key)==0)
|
||||
{
|
||||
strcpy_s(keyms[i].action, action);
|
||||
return;
|
||||
};
|
||||
conoutf("unknown key \"%s\"", key);
|
||||
};
|
||||
|
||||
COMMANDN(bind, bindkey, ARG_2STR);
|
||||
|
||||
void saycommand(char *init) // turns input to the command line on or off
|
||||
{
|
||||
SDL_EnableUNICODE(saycommandon = (init!=NULL));
|
||||
if(!editmode) keyrepeat(saycommandon);
|
||||
if(!init) init = "";
|
||||
strcpy_s(commandbuf, init);
|
||||
};
|
||||
|
||||
void mapmsg(char *s) { strn0cpy(hdr.maptitle, s, 128); };
|
||||
|
||||
COMMAND(saycommand, ARG_VARI);
|
||||
COMMAND(mapmsg, ARG_1STR);
|
||||
|
||||
#ifndef WIN32
|
||||
#include <X11/Xlib.h>
|
||||
#include <SDL_syswm.h>
|
||||
#endif
|
||||
|
||||
void pasteconsole()
|
||||
{
|
||||
#ifdef WIN32
|
||||
if(!IsClipboardFormatAvailable(CF_TEXT)) return;
|
||||
if(!OpenClipboard(NULL)) return;
|
||||
char *cb = (char *)GlobalLock(GetClipboardData(CF_TEXT));
|
||||
strcat_s(commandbuf, cb);
|
||||
GlobalUnlock(cb);
|
||||
CloseClipboard();
|
||||
#else
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
wminfo.subsystem = SDL_SYSWM_X11;
|
||||
if(!SDL_GetWMInfo(&wminfo)) return;
|
||||
int cbsize;
|
||||
char *cb = XFetchBytes(wminfo.info.x11.display, &cbsize);
|
||||
if(!cb || !cbsize) return;
|
||||
int commandlen = strlen(commandbuf);
|
||||
for(char *cbline = cb, *cbend; commandlen + 1 < _MAXDEFSTR && cbline < &cb[cbsize]; cbline = cbend + 1)
|
||||
{
|
||||
cbend = (char *)memchr(cbline, '\0', &cb[cbsize] - cbline);
|
||||
if(!cbend) cbend = &cb[cbsize];
|
||||
if(commandlen + cbend - cbline + 1 > _MAXDEFSTR) cbend = cbline + _MAXDEFSTR - commandlen - 1;
|
||||
memcpy(&commandbuf[commandlen], cbline, cbend - cbline);
|
||||
commandlen += cbend - cbline;
|
||||
commandbuf[commandlen] = '\n';
|
||||
if(commandlen + 1 < _MAXDEFSTR && cbend < &cb[cbsize]) ++commandlen;
|
||||
commandbuf[commandlen] = '\0';
|
||||
};
|
||||
XFree(cb);
|
||||
#endif
|
||||
};
|
||||
|
||||
cvector vhistory;
|
||||
int histpos = 0;
|
||||
|
||||
void history(int n)
|
||||
{
|
||||
static bool rec = false;
|
||||
if(!rec && n>=0 && n<vhistory.length())
|
||||
{
|
||||
rec = true;
|
||||
execute(vhistory[vhistory.length()-n-1]);
|
||||
rec = false;
|
||||
};
|
||||
};
|
||||
|
||||
COMMAND(history, ARG_1INT);
|
||||
|
||||
void keypress(int code, bool isdown, int cooked)
|
||||
{
|
||||
if(saycommandon) // keystrokes go to commandline
|
||||
{
|
||||
if(isdown)
|
||||
{
|
||||
switch(code)
|
||||
{
|
||||
case SDLK_RETURN:
|
||||
break;
|
||||
|
||||
case SDLK_BACKSPACE:
|
||||
case SDLK_LEFT:
|
||||
{
|
||||
for(int i = 0; commandbuf[i]; i++) if(!commandbuf[i+1]) commandbuf[i] = 0;
|
||||
resetcomplete();
|
||||
break;
|
||||
};
|
||||
|
||||
case SDLK_UP:
|
||||
if(histpos) strcpy_s(commandbuf, vhistory[--histpos]);
|
||||
break;
|
||||
|
||||
case SDLK_DOWN:
|
||||
if(histpos<vhistory.length()) strcpy_s(commandbuf, vhistory[histpos++]);
|
||||
break;
|
||||
|
||||
case SDLK_TAB:
|
||||
complete(commandbuf);
|
||||
break;
|
||||
|
||||
case SDLK_v:
|
||||
if(SDL_GetModState()&(KMOD_LCTRL|KMOD_RCTRL)) { pasteconsole(); return; };
|
||||
|
||||
default:
|
||||
resetcomplete();
|
||||
if(cooked) { char add[] = { cooked, 0 }; strcat_s(commandbuf, add); };
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
if(code==SDLK_RETURN)
|
||||
{
|
||||
if(commandbuf[0])
|
||||
{
|
||||
if(vhistory.empty() || strcmp(vhistory.last(), commandbuf))
|
||||
{
|
||||
vhistory.add(newstring(commandbuf)); // cap this?
|
||||
};
|
||||
histpos = vhistory.length();
|
||||
if(commandbuf[0]=='/') execute(commandbuf, true);
|
||||
else toserver(commandbuf);
|
||||
};
|
||||
saycommand(NULL);
|
||||
}
|
||||
else if(code==SDLK_ESCAPE)
|
||||
{
|
||||
saycommand(NULL);
|
||||
};
|
||||
};
|
||||
}
|
||||
else if(!menukey(code, isdown)) // keystrokes go to menu
|
||||
{
|
||||
loopi(numkm) if(keyms[i].code==code) // keystrokes go to game, lookup in keymap and execute
|
||||
{
|
||||
string temp;
|
||||
strcpy_s(temp, keyms[i].action);
|
||||
execute(temp, isdown);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
char *getcurcommand()
|
||||
{
|
||||
return saycommandon ? commandbuf : NULL;
|
||||
};
|
||||
|
||||
void writebinds(FILE *f)
|
||||
{
|
||||
loopi(numkm)
|
||||
{
|
||||
if(*keyms[i].action) fprintf(f, "bind \"%s\" [%s]\n", keyms[i].name, keyms[i].action);
|
||||
};
|
||||
};
|
285
src/cube.h
Normal file
285
src/cube.h
Normal file
|
@ -0,0 +1,285 @@
|
|||
// one big bad include file for the whole engine... nasty!
|
||||
|
||||
#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,
|
||||
};
|
||||
|
||||
// vertex array format
|
||||
|
||||
struct vertex { float u, v, x, y, z; uchar r, g, b, a; };
|
||||
|
||||
typedef vector<dynent *> dvector;
|
||||
typedef vector<char *> cvector;
|
||||
typedef vector<int> 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 bool editmode;
|
||||
extern vector<entity> 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 DVF 100.0f
|
||||
|
||||
#define VIRTW 2400 // virtual screen size for text & HUD
|
||||
#define VIRTH 1800
|
||||
#define FONTH 64
|
||||
#define PIXELTAB (VIRTW/12)
|
||||
|
||||
#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
|
||||
|
||||
#ifdef WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include "windows.h"
|
||||
#define _WINDOWS
|
||||
#define ZLIB_DLL
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glu.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_image.h>
|
||||
|
||||
#include <enet/enet.h>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "protos.h" // external function decls
|
||||
|
487
src/editing.cpp
Normal file
487
src/editing.cpp
Normal file
|
@ -0,0 +1,487 @@
|
|||
// editing.cpp: most map editing commands go here, entity editing commands are in world.cpp
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
bool editmode = false;
|
||||
|
||||
// the current selection, used by almost all editing commands
|
||||
// invariant: all code assumes that these are kept inside MINBORD distance of the edge of the map
|
||||
|
||||
block sel =
|
||||
{
|
||||
variable("selx", 0, 0, 4096, &sel.x, NULL, false),
|
||||
variable("sely", 0, 0, 4096, &sel.y, NULL, false),
|
||||
variable("selxs", 0, 0, 4096, &sel.xs, NULL, false),
|
||||
variable("selys", 0, 0, 4096, &sel.ys, NULL, false),
|
||||
};
|
||||
|
||||
int selh = 0;
|
||||
bool selset = false;
|
||||
|
||||
#define loopselxy(b) { makeundo(); loop(x,sel.xs) loop(y,sel.ys) { sqr *s = S(sel.x+x, sel.y+y); b; }; remip(sel); }
|
||||
|
||||
int cx, cy, ch;
|
||||
|
||||
int curedittex[] = { -1, -1, -1 };
|
||||
|
||||
bool dragging = false;
|
||||
int lastx, lasty, lasth;
|
||||
|
||||
int lasttype = 0, lasttex = 0;
|
||||
sqr rtex;
|
||||
|
||||
VAR(editing,0,0,1);
|
||||
|
||||
void toggleedit()
|
||||
{
|
||||
if(player1->state==CS_DEAD) return; // do not allow dead players to edit to avoid state confusion
|
||||
if(!editmode && !allowedittoggle()) return; // not in most multiplayer modes
|
||||
if(!(editmode = !editmode))
|
||||
{
|
||||
settagareas(); // reset triggers to allow quick playtesting
|
||||
entinmap(player1); // find spawn closest to current floating pos
|
||||
}
|
||||
else
|
||||
{
|
||||
resettagareas(); // clear trigger areas to allow them to be edited
|
||||
player1->health = 100;
|
||||
if(m_classicsp) monsterclear(); // all monsters back at their spawns for editing
|
||||
projreset();
|
||||
};
|
||||
keyrepeat(editmode);
|
||||
selset = false;
|
||||
editing = editmode;
|
||||
};
|
||||
|
||||
COMMANDN(edittoggle, toggleedit, ARG_NONE);
|
||||
|
||||
void correctsel() // ensures above invariant
|
||||
{
|
||||
selset = !OUTBORD(sel.x, sel.y);
|
||||
int bsize = ssize-MINBORD;
|
||||
if(sel.xs+sel.x>bsize) sel.xs = bsize-sel.x;
|
||||
if(sel.ys+sel.y>bsize) sel.ys = bsize-sel.y;
|
||||
if(sel.xs<=0 || sel.ys<=0) selset = false;
|
||||
};
|
||||
|
||||
bool noteditmode()
|
||||
{
|
||||
correctsel();
|
||||
if(!editmode) conoutf("this function is only allowed in edit mode");
|
||||
return !editmode;
|
||||
};
|
||||
|
||||
bool noselection()
|
||||
{
|
||||
if(!selset) conoutf("no selection");
|
||||
return !selset;
|
||||
};
|
||||
|
||||
#define EDITSEL if(noteditmode() || noselection()) return;
|
||||
#define EDITSELMP if(noteditmode() || noselection() || multiplayer()) return;
|
||||
#define EDITMP if(noteditmode() || multiplayer()) return;
|
||||
|
||||
void selectpos(int x, int y, int xs, int ys)
|
||||
{
|
||||
block s = { x, y, xs, ys };
|
||||
sel = s;
|
||||
selh = 0;
|
||||
correctsel();
|
||||
};
|
||||
|
||||
void makesel()
|
||||
{
|
||||
block s = { min(lastx,cx), min(lasty,cy), abs(lastx-cx)+1, abs(lasty-cy)+1 };
|
||||
sel = s;
|
||||
selh = max(lasth,ch);
|
||||
correctsel();
|
||||
if(selset) rtex = *S(sel.x, sel.y);
|
||||
};
|
||||
|
||||
VAR(flrceil,0,0,2);
|
||||
|
||||
float sheight(sqr *s, sqr *t, float z) // finds out z height when cursor points at wall
|
||||
{
|
||||
return !flrceil //z-s->floor<s->ceil-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<block *> undos; // unlimited undo
|
||||
VARP(undomegs, 0, 1, 10); // bounded by n megs
|
||||
|
||||
void pruneundos(int maxremain) // bound memory
|
||||
{
|
||||
int t = 0;
|
||||
loopvrev(undos)
|
||||
{
|
||||
t += undos[i]->xs*undos[i]->ys*sizeof(sqr);
|
||||
if(t>maxremain) free(undos.remove(i));
|
||||
};
|
||||
};
|
||||
|
||||
void makeundo()
|
||||
{
|
||||
undos.add(blockcopy(sel));
|
||||
pruneundos(undomegs<<20);
|
||||
};
|
||||
|
||||
void editundo()
|
||||
{
|
||||
EDITMP;
|
||||
if(undos.empty()) { conoutf("nothing more to undo"); return; };
|
||||
block *p = undos.pop();
|
||||
blockpaste(*p);
|
||||
free(p);
|
||||
};
|
||||
|
||||
block *copybuf = NULL;
|
||||
|
||||
void copy()
|
||||
{
|
||||
EDITSELMP;
|
||||
if(copybuf) free(copybuf);
|
||||
copybuf = blockcopy(sel);
|
||||
};
|
||||
|
||||
void paste()
|
||||
{
|
||||
EDITMP;
|
||||
if(!copybuf) { conoutf("nothing to paste"); return; };
|
||||
sel.xs = copybuf->xs;
|
||||
sel.ys = copybuf->ys;
|
||||
correctsel();
|
||||
if(!selset || sel.xs!=copybuf->xs || sel.ys!=copybuf->ys) { conoutf("incorrect selection"); return; };
|
||||
makeundo();
|
||||
copybuf->x = sel.x;
|
||||
copybuf->y = sel.y;
|
||||
blockpaste(*copybuf);
|
||||
};
|
||||
|
||||
void tofronttex() // maintain most recently used of the texture lists when applying texture
|
||||
{
|
||||
loopi(3)
|
||||
{
|
||||
int c = curedittex[i];
|
||||
if(c>=0)
|
||||
{
|
||||
uchar *p = hdr.texlists[i];
|
||||
int t = p[c];
|
||||
for(int a = c-1; a>=0; a--) p[a+1] = p[a];
|
||||
p[0] = t;
|
||||
curedittex[i] = -1;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
void editdrag(bool isdown)
|
||||
{
|
||||
if(dragging = isdown)
|
||||
{
|
||||
lastx = cx;
|
||||
lasty = cy;
|
||||
lasth = ch;
|
||||
selset = false;
|
||||
tofronttex();
|
||||
};
|
||||
makesel();
|
||||
};
|
||||
|
||||
// the core editing function. all the *xy functions perform the core operations
|
||||
// and are also called directly from the network, the function below it is strictly
|
||||
// triggered locally. They all have very similar structure.
|
||||
|
||||
void editheightxy(bool isfloor, int amount, block &sel)
|
||||
{
|
||||
loopselxy(if(isfloor)
|
||||
{
|
||||
s->floor += amount;
|
||||
if(s->floor>=s->ceil) s->floor = s->ceil-1;
|
||||
}
|
||||
else
|
||||
{
|
||||
s->ceil += amount;
|
||||
if(s->ceil<=s->floor) s->ceil = s->floor+1;
|
||||
});
|
||||
};
|
||||
|
||||
void editheight(int flr, int amount)
|
||||
{
|
||||
EDITSEL;
|
||||
bool isfloor = flr==0;
|
||||
editheightxy(isfloor, amount, sel);
|
||||
addmsg(1, 7, SV_EDITH, sel.x, sel.y, sel.xs, sel.ys, isfloor, amount);
|
||||
};
|
||||
|
||||
COMMAND(editheight, ARG_2INT);
|
||||
|
||||
void edittexxy(int type, int t, block &sel)
|
||||
{
|
||||
loopselxy(switch(type)
|
||||
{
|
||||
case 0: s->ftex = t; break;
|
||||
case 1: s->wtex = t; break;
|
||||
case 2: s->ctex = t; break;
|
||||
case 3: s->utex = t; break;
|
||||
});
|
||||
};
|
||||
|
||||
void edittex(int type, int dir)
|
||||
{
|
||||
EDITSEL;
|
||||
if(type<0 || type>3) return;
|
||||
if(type!=lasttype) { tofronttex(); lasttype = type; };
|
||||
int atype = type==3 ? 1 : type;
|
||||
int i = curedittex[atype];
|
||||
i = i<0 ? 0 : i+dir;
|
||||
curedittex[atype] = i = min(max(i, 0), 255);
|
||||
int t = lasttex = hdr.texlists[atype][i];
|
||||
edittexxy(type, t, sel);
|
||||
addmsg(1, 7, SV_EDITT, sel.x, sel.y, sel.xs, sel.ys, type, t);
|
||||
};
|
||||
|
||||
void replace()
|
||||
{
|
||||
EDITSELMP;
|
||||
loop(x,ssize) loop(y,ssize)
|
||||
{
|
||||
sqr *s = S(x, y);
|
||||
switch(lasttype)
|
||||
{
|
||||
case 0: if(s->ftex == rtex.ftex) s->ftex = lasttex; break;
|
||||
case 1: if(s->wtex == rtex.wtex) s->wtex = lasttex; break;
|
||||
case 2: if(s->ctex == rtex.ctex) s->ctex = lasttex; break;
|
||||
case 3: if(s->utex == rtex.utex) s->utex = lasttex; break;
|
||||
};
|
||||
};
|
||||
block b = { 0, 0, ssize, ssize };
|
||||
remip(b);
|
||||
};
|
||||
|
||||
void edittypexy(int type, block &sel)
|
||||
{
|
||||
loopselxy(s->type = type);
|
||||
};
|
||||
|
||||
void edittype(int type)
|
||||
{
|
||||
EDITSEL;
|
||||
if(type==CORNER && (sel.xs!=sel.ys || sel.xs==3 || sel.xs>4 && sel.xs!=8
|
||||
|| sel.x&~-sel.xs || sel.y&~-sel.ys))
|
||||
{ conoutf("corner selection must be power of 2 aligned"); return; };
|
||||
edittypexy(type, sel);
|
||||
addmsg(1, 6, SV_EDITS, sel.x, sel.y, sel.xs, sel.ys, type);
|
||||
};
|
||||
|
||||
void heightfield(int t) { edittype(t==0 ? FHF : CHF); };
|
||||
void solid(int t) { edittype(t==0 ? SPACE : SOLID); };
|
||||
void corner() { edittype(CORNER); };
|
||||
|
||||
COMMAND(heightfield, ARG_1INT);
|
||||
COMMAND(solid, ARG_1INT);
|
||||
COMMAND(corner, ARG_NONE);
|
||||
|
||||
void editequalisexy(bool isfloor, block &sel)
|
||||
{
|
||||
int low = 127, hi = -128;
|
||||
loopselxy(
|
||||
{
|
||||
if(s->floor<low) low = s->floor;
|
||||
if(s->ceil>hi) hi = s->ceil;
|
||||
});
|
||||
loopselxy(
|
||||
{
|
||||
if(isfloor) s->floor = low; else s->ceil = hi;
|
||||
if(s->floor>=s->ceil) s->floor = s->ceil-1;
|
||||
});
|
||||
};
|
||||
|
||||
void equalize(int flr)
|
||||
{
|
||||
bool isfloor = flr==0;
|
||||
EDITSEL;
|
||||
editequalisexy(isfloor, sel);
|
||||
addmsg(1, 6, SV_EDITE, sel.x, sel.y, sel.xs, sel.ys, isfloor);
|
||||
};
|
||||
|
||||
COMMAND(equalize, ARG_1INT);
|
||||
|
||||
void setvdeltaxy(int delta, block &sel)
|
||||
{
|
||||
loopselxy(s->vdelta = max(s->vdelta+delta, 0));
|
||||
remipmore(sel);
|
||||
};
|
||||
|
||||
void setvdelta(int delta)
|
||||
{
|
||||
EDITSEL;
|
||||
setvdeltaxy(delta, sel);
|
||||
addmsg(1, 6, SV_EDITD, sel.x, sel.y, sel.xs, sel.ys, delta);
|
||||
};
|
||||
|
||||
const int MAXARCHVERT = 50;
|
||||
int archverts[MAXARCHVERT][MAXARCHVERT];
|
||||
bool archvinit = false;
|
||||
|
||||
void archvertex(int span, int vert, int delta)
|
||||
{
|
||||
if(!archvinit)
|
||||
{
|
||||
archvinit = true;
|
||||
loop(s,MAXARCHVERT) loop(v,MAXARCHVERT) archverts[s][v] = 0;
|
||||
};
|
||||
if(span>=MAXARCHVERT || vert>=MAXARCHVERT || span<0 || vert<0) return;
|
||||
archverts[span][vert] = delta;
|
||||
};
|
||||
|
||||
void arch(int sidedelta, int _a)
|
||||
{
|
||||
EDITSELMP;
|
||||
sel.xs++;
|
||||
sel.ys++;
|
||||
if(sel.xs>MAXARCHVERT) sel.xs = MAXARCHVERT;
|
||||
if(sel.ys>MAXARCHVERT) sel.ys = MAXARCHVERT;
|
||||
loopselxy(s->vdelta =
|
||||
sel.xs>sel.ys
|
||||
? (archverts[sel.xs-1][x] + (y==0 || y==sel.ys-1 ? sidedelta : 0))
|
||||
: (archverts[sel.ys-1][y] + (x==0 || x==sel.xs-1 ? sidedelta : 0)));
|
||||
remipmore(sel);
|
||||
};
|
||||
|
||||
void slope(int xd, int yd)
|
||||
{
|
||||
EDITSELMP;
|
||||
int off = 0;
|
||||
if(xd<0) off -= xd*sel.xs;
|
||||
if(yd<0) off -= yd*sel.ys;
|
||||
sel.xs++;
|
||||
sel.ys++;
|
||||
loopselxy(s->vdelta = xd*x+yd*y+off);
|
||||
remipmore(sel);
|
||||
};
|
||||
|
||||
void perlin(int scale, int seed, int psize)
|
||||
{
|
||||
EDITSELMP;
|
||||
sel.xs++;
|
||||
sel.ys++;
|
||||
makeundo();
|
||||
sel.xs--;
|
||||
sel.ys--;
|
||||
perlinarea(sel, scale, seed, psize);
|
||||
sel.xs++;
|
||||
sel.ys++;
|
||||
remipmore(sel);
|
||||
sel.xs--;
|
||||
sel.ys--;
|
||||
};
|
||||
|
||||
VARF(fullbright, 0, 0, 1,
|
||||
if(fullbright)
|
||||
{
|
||||
if(noteditmode()) return;
|
||||
loopi(mipsize) world[i].r = world[i].g = world[i].b = 176;
|
||||
};
|
||||
);
|
||||
|
||||
void edittag(int tag)
|
||||
{
|
||||
EDITSELMP;
|
||||
loopselxy(s->tag = tag);
|
||||
};
|
||||
|
||||
void newent(char *what, char *a1, char *a2, char *a3, char *a4)
|
||||
{
|
||||
EDITSEL;
|
||||
newentity(sel.x, sel.y, (int)player1->o.z, what, ATOI(a1), ATOI(a2), ATOI(a3), ATOI(a4));
|
||||
};
|
||||
|
||||
COMMANDN(select, selectpos, ARG_4INT);
|
||||
COMMAND(edittag, ARG_1INT);
|
||||
COMMAND(replace, ARG_NONE);
|
||||
COMMAND(archvertex, ARG_3INT);
|
||||
COMMAND(arch, ARG_2INT);
|
||||
COMMAND(slope, ARG_2INT);
|
||||
COMMANDN(vdelta, setvdelta, ARG_1INT);
|
||||
COMMANDN(undo, editundo, ARG_NONE);
|
||||
COMMAND(copy, ARG_NONE);
|
||||
COMMAND(paste, ARG_NONE);
|
||||
COMMAND(edittex, ARG_2INT);
|
||||
COMMAND(newent, ARG_5STR);
|
||||
COMMAND(perlin, ARG_3INT);
|
||||
|
||||
|
242
src/entities.cpp
Normal file
242
src/entities.cpp
Normal file
|
@ -0,0 +1,242 @@
|
|||
// entities.cpp: map entity related functions (pickup etc.)
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
vector<entity> ents;
|
||||
|
||||
char *entmdlnames[] =
|
||||
{
|
||||
"shells", "bullets", "rockets", "rrounds", "health", "boost",
|
||||
"g_armour", "y_armour", "quad", "teleporter",
|
||||
};
|
||||
|
||||
int triggertime = 0;
|
||||
|
||||
void renderent(entity &e, char *mdlname, float z, float yaw, int frame = 0, int numf = 1, int basetime = 0, float speed = 10.0f)
|
||||
{
|
||||
rendermodel(mdlname, frame, numf, 0, 1.1f, e.x, z+S(e.x, e.y)->floor, e.y, yaw, 0, false, 1.0f, speed, 0, basetime);
|
||||
};
|
||||
|
||||
void renderentities()
|
||||
{
|
||||
if(lastmillis>triggertime+1000) triggertime = 0;
|
||||
loopv(ents)
|
||||
{
|
||||
entity &e = ents[i];
|
||||
if(e.type==MAPMODEL)
|
||||
{
|
||||
mapmodelinfo &mmi = getmminfo(e.attr2);
|
||||
if(!&mmi) continue;
|
||||
rendermodel(mmi.name, 0, 1, e.attr4, (float)mmi.rad, e.x, (float)S(e.x, e.y)->floor+mmi.zoff+e.attr3, e.y, (float)((e.attr1+7)-(e.attr1+7)%15), 0, false, 1.0f, 10.0f, mmi.snap);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(OUTBORD(e.x, e.y)) continue;
|
||||
if(e.type!=CARROT)
|
||||
{
|
||||
if(!e.spawned && e.type!=TELEPORT) continue;
|
||||
if(e.type<I_SHELLS || e.type>TELEPORT) continue;
|
||||
renderent(e, entmdlnames[e.type-I_SHELLS], (float)(1+sin(lastmillis/100.0+e.x+e.y)/20), lastmillis/10.0f);
|
||||
}
|
||||
else switch(e.attr2)
|
||||
{
|
||||
case 1:
|
||||
case 3:
|
||||
continue;
|
||||
|
||||
case 2:
|
||||
case 0:
|
||||
if(!e.spawned) continue;
|
||||
renderent(e, "carrot", (float)(1+sin(lastmillis/100.0+e.x+e.y)/20), lastmillis/(e.attr2 ? 1.0f : 10.0f));
|
||||
break;
|
||||
|
||||
case 4: renderent(e, "switch2", 3, (float)e.attr3*90, (!e.spawned && !triggertime) ? 1 : 0, (e.spawned || !triggertime) ? 1 : 2, triggertime, 1050.0f); break;
|
||||
case 5: renderent(e, "switch1", -0.15f, (float)e.attr3*90, (!e.spawned && !triggertime) ? 30 : 0, (e.spawned || !triggertime) ? 1 : 30, triggertime, 35.0f); break;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
struct itemstat { int add, max, sound; } itemstats[] =
|
||||
{
|
||||
10, 50, S_ITEMAMMO,
|
||||
20, 100, S_ITEMAMMO,
|
||||
5, 25, S_ITEMAMMO,
|
||||
5, 25, S_ITEMAMMO,
|
||||
25, 100, S_ITEMHEALTH,
|
||||
50, 200, S_ITEMHEALTH,
|
||||
100, 100, S_ITEMARMOUR,
|
||||
150, 150, S_ITEMARMOUR,
|
||||
20000, 30000, S_ITEMPUP,
|
||||
};
|
||||
|
||||
void baseammo(int gun) { player1->ammo[gun] = itemstats[gun-1].add*2; };
|
||||
|
||||
// these two functions are called when the server acknowledges that you really
|
||||
// picked up the item (in multiplayer someone may grab it before you).
|
||||
|
||||
void radditem(int i, int &v)
|
||||
{
|
||||
itemstat &is = itemstats[ents[i].type-I_SHELLS];
|
||||
ents[i].spawned = false;
|
||||
v += is.add;
|
||||
if(v>is.max) v = is.max;
|
||||
playsoundc(is.sound);
|
||||
};
|
||||
|
||||
void realpickup(int n, dynent *d)
|
||||
{
|
||||
switch(ents[n].type)
|
||||
{
|
||||
case I_SHELLS: radditem(n, d->ammo[1]); break;
|
||||
case I_BULLETS: radditem(n, d->ammo[2]); break;
|
||||
case I_ROCKETS: radditem(n, d->ammo[3]); break;
|
||||
case I_ROUNDS: radditem(n, d->ammo[4]); break;
|
||||
case I_HEALTH: radditem(n, d->health); break;
|
||||
case I_BOOST: radditem(n, d->health); break;
|
||||
|
||||
case I_GREENARMOUR:
|
||||
radditem(n, d->armour);
|
||||
d->armourtype = A_GREEN;
|
||||
break;
|
||||
|
||||
case I_YELLOWARMOUR:
|
||||
radditem(n, d->armour);
|
||||
d->armourtype = A_YELLOW;
|
||||
break;
|
||||
|
||||
case I_QUAD:
|
||||
radditem(n, d->quadmillis);
|
||||
conoutf("you got the quad!");
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
// these functions are called when the client touches the item
|
||||
|
||||
void additem(int i, int &v, int spawnsec)
|
||||
{
|
||||
if(v<itemstats[ents[i].type-I_SHELLS].max) // don't pick up if not needed
|
||||
{
|
||||
addmsg(1, 3, SV_ITEMPICKUP, i, m_classicsp ? 100000 : spawnsec); // first ask the server for an ack
|
||||
ents[i].spawned = false; // even if someone else gets it first
|
||||
};
|
||||
};
|
||||
|
||||
void teleport(int n, dynent *d) // also used by monsters
|
||||
{
|
||||
int e = -1, tag = ents[n].attr1, beenhere = -1;
|
||||
for(;;)
|
||||
{
|
||||
e = findentity(TELEDEST, e+1);
|
||||
if(e==beenhere || e<0) { conoutf("no teleport destination for tag %d", tag); return; };
|
||||
if(beenhere<0) beenhere = e;
|
||||
if(ents[e].attr2==tag)
|
||||
{
|
||||
d->o.x = ents[e].x;
|
||||
d->o.y = ents[e].y;
|
||||
d->o.z = ents[e].z;
|
||||
d->yaw = ents[e].attr1;
|
||||
d->pitch = 0;
|
||||
d->vel.x = d->vel.y = d->vel.z = 0;
|
||||
entinmap(d);
|
||||
playsoundc(S_TELEPORT);
|
||||
break;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
void pickup(int n, dynent *d)
|
||||
{
|
||||
int np = 1;
|
||||
loopv(players) if(players[i]) np++;
|
||||
np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players
|
||||
int ammo = np*2;
|
||||
switch(ents[n].type)
|
||||
{
|
||||
case I_SHELLS: additem(n, d->ammo[1], ammo); break;
|
||||
case I_BULLETS: additem(n, d->ammo[2], ammo); break;
|
||||
case I_ROCKETS: additem(n, d->ammo[3], ammo); break;
|
||||
case I_ROUNDS: additem(n, d->ammo[4], ammo); break;
|
||||
case I_HEALTH: additem(n, d->health, np*5); break;
|
||||
case I_BOOST: additem(n, d->health, 60); break;
|
||||
|
||||
case I_GREENARMOUR:
|
||||
// (100h/100g only absorbs 166 damage)
|
||||
if(d->armourtype==A_YELLOW && d->armour>66) break;
|
||||
additem(n, d->armour, 20);
|
||||
break;
|
||||
|
||||
case I_YELLOWARMOUR:
|
||||
additem(n, d->armour, 20);
|
||||
break;
|
||||
|
||||
case I_QUAD:
|
||||
additem(n, d->quadmillis, 60);
|
||||
break;
|
||||
|
||||
case CARROT:
|
||||
ents[n].spawned = false;
|
||||
triggertime = lastmillis;
|
||||
trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer
|
||||
break;
|
||||
|
||||
case TELEPORT:
|
||||
{
|
||||
static int lastteleport = 0;
|
||||
if(lastmillis-lastteleport<500) break;
|
||||
lastteleport = lastmillis;
|
||||
teleport(n, d);
|
||||
break;
|
||||
};
|
||||
|
||||
case JUMPPAD:
|
||||
{
|
||||
static int lastjumppad = 0;
|
||||
if(lastmillis-lastjumppad<300) break;
|
||||
lastjumppad = lastmillis;
|
||||
vec v = { (int)(char)ents[n].attr3/10.0f, (int)(char)ents[n].attr2/10.0f, ents[n].attr1/10.0f };
|
||||
player1->vel.z = 0;
|
||||
vadd(player1->vel, v);
|
||||
playsoundc(S_JUMPPAD);
|
||||
break;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
void checkitems()
|
||||
{
|
||||
if(editmode) return;
|
||||
loopv(ents)
|
||||
{
|
||||
entity &e = ents[i];
|
||||
if(e.type==NOTUSED) continue;
|
||||
if(!ents[i].spawned && e.type!=TELEPORT && e.type!=JUMPPAD) continue;
|
||||
if(OUTBORD(e.x, e.y)) continue;
|
||||
vec v = { 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; };
|
237
src/main.cpp
Normal file
237
src/main.cpp
Normal file
|
@ -0,0 +1,237 @@
|
|||
// main.cpp: initialisation & main loop
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
void cleanup(char *msg) // single program exit point;
|
||||
{
|
||||
stop();
|
||||
disconnect(true);
|
||||
writecfg();
|
||||
cleangl();
|
||||
cleansound();
|
||||
cleanupserver();
|
||||
SDL_ShowCursor(1);
|
||||
if(msg)
|
||||
{
|
||||
#ifdef WIN32
|
||||
MessageBox(NULL, msg, "cube fatal error", MB_OK|MB_SYSTEMMODAL);
|
||||
#else
|
||||
printf(msg);
|
||||
#endif
|
||||
};
|
||||
SDL_Quit();
|
||||
exit(1);
|
||||
};
|
||||
|
||||
void quit() // normal exit
|
||||
{
|
||||
writeservercfg();
|
||||
cleanup(NULL);
|
||||
};
|
||||
|
||||
void fatal(char *s, char *o) // failure exit
|
||||
{
|
||||
sprintf_sd(msg)("%s%s (%s)\n", s, o, SDL_GetError());
|
||||
cleanup(msg);
|
||||
};
|
||||
|
||||
void *alloc(int s) // for some big chunks... most other allocs use the memory pool
|
||||
{
|
||||
void *b = calloc(1,s);
|
||||
if(!b) fatal("out of memory!");
|
||||
return b;
|
||||
};
|
||||
|
||||
int scr_w = 640;
|
||||
int scr_h = 480;
|
||||
|
||||
void screenshot()
|
||||
{
|
||||
SDL_Surface *image;
|
||||
SDL_Surface *temp;
|
||||
int idx;
|
||||
if(image = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24, 0x0000FF, 0x00FF00, 0xFF0000, 0))
|
||||
{
|
||||
if(temp = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24, 0x0000FF, 0x00FF00, 0xFF0000, 0))
|
||||
{
|
||||
glReadPixels(0, 0, scr_w, scr_h, GL_RGB, GL_UNSIGNED_BYTE, image->pixels);
|
||||
for (idx = 0; idx<scr_h; idx++)
|
||||
{
|
||||
char *dest = (char *)temp->pixels+3*scr_w*idx;
|
||||
memcpy(dest, (char *)image->pixels+3*scr_w*(scr_h-1-idx), 3*scr_w);
|
||||
endianswap(dest, 3, scr_w);
|
||||
};
|
||||
sprintf_sd(buf)("screenshots/screenshot_%d.bmp", lastmillis);
|
||||
SDL_SaveBMP(temp, path(buf));
|
||||
SDL_FreeSurface(temp);
|
||||
};
|
||||
SDL_FreeSurface(image);
|
||||
};
|
||||
};
|
||||
|
||||
COMMAND(screenshot, ARG_NONE);
|
||||
COMMAND(quit, ARG_NONE);
|
||||
|
||||
void keyrepeat(bool on)
|
||||
{
|
||||
SDL_EnableKeyRepeat(on ? SDL_DEFAULT_REPEAT_DELAY : 0,
|
||||
SDL_DEFAULT_REPEAT_INTERVAL);
|
||||
};
|
||||
|
||||
VARF(gamespeed, 10, 100, 1000, if(multiplayer()) gamespeed = 100);
|
||||
VARP(minmillis, 0, 5, 1000);
|
||||
|
||||
int islittleendian = 1;
|
||||
int framesinmap = 0;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool dedicated = false;
|
||||
int fs = SDL_FULLSCREEN, par = 0, uprate = 0, maxcl = 4;
|
||||
char *sdesc = "", *ip = "", *master = NULL, *passwd = "";
|
||||
islittleendian = *((char *)&islittleendian);
|
||||
|
||||
#define log(s) conoutf("init: %s", s)
|
||||
log("sdl");
|
||||
|
||||
for(int i = 1; i<argc; i++)
|
||||
{
|
||||
char *a = &argv[i][2];
|
||||
if(argv[i][0]=='-') switch(argv[i][1])
|
||||
{
|
||||
case 'd': dedicated = true; break;
|
||||
case 't': fs = 0; break;
|
||||
case 'w': scr_w = atoi(a); break;
|
||||
case 'h': scr_h = atoi(a); break;
|
||||
case 'u': uprate = atoi(a); break;
|
||||
case 'n': sdesc = a; break;
|
||||
case 'i': ip = a; break;
|
||||
case 'm': master = a; break;
|
||||
case 'p': passwd = a; break;
|
||||
case 'c': maxcl = atoi(a); break;
|
||||
default: conoutf("unknown commandline option");
|
||||
}
|
||||
else conoutf("unknown commandline argument");
|
||||
};
|
||||
|
||||
#ifdef _DEBUG
|
||||
par = SDL_INIT_NOPARACHUTE;
|
||||
fs = 0;
|
||||
#endif
|
||||
|
||||
if(SDL_Init(SDL_INIT_TIMER|SDL_INIT_VIDEO|par)<0) fatal("Unable to initialize SDL");
|
||||
|
||||
log("net");
|
||||
if(enet_initialize()<0) fatal("Unable to initialise network module");
|
||||
|
||||
initclient();
|
||||
initserver(dedicated, uprate, sdesc, ip, master, passwd, maxcl); // never returns if dedicated
|
||||
|
||||
log("world");
|
||||
empty_world(7, true);
|
||||
|
||||
log("video: sdl");
|
||||
if(SDL_InitSubSystem(SDL_INIT_VIDEO)<0) fatal("Unable to initialize SDL Video");
|
||||
|
||||
log("video: mode");
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
if(SDL_SetVideoMode(scr_w, scr_h, 0, SDL_OPENGL|fs)==NULL) fatal("Unable to create OpenGL screen");
|
||||
|
||||
log("video: misc");
|
||||
SDL_WM_SetCaption("cube engine", NULL);
|
||||
SDL_WM_GrabInput(SDL_GRAB_ON);
|
||||
keyrepeat(false);
|
||||
SDL_ShowCursor(0);
|
||||
|
||||
log("gl");
|
||||
gl_init(scr_w, scr_h);
|
||||
|
||||
log("basetex");
|
||||
int xs, ys;
|
||||
if(!installtex(2, path(newstring("data/newchars.png")), xs, ys) ||
|
||||
!installtex(3, path(newstring("data/martin/base.png")), xs, ys) ||
|
||||
!installtex(6, path(newstring("data/martin/ball1.png")), xs, ys) ||
|
||||
!installtex(7, path(newstring("data/martin/smoke.png")), xs, ys) ||
|
||||
!installtex(8, path(newstring("data/martin/ball2.png")), xs, ys) ||
|
||||
!installtex(9, path(newstring("data/martin/ball3.png")), xs, ys) ||
|
||||
!installtex(4, path(newstring("data/explosion.jpg")), xs, ys) ||
|
||||
!installtex(5, path(newstring("data/items.png")), xs, ys) ||
|
||||
!installtex(1, path(newstring("data/crosshair.png")), xs, ys)) fatal("could not find core textures (hint: run cube from the parent of the bin directory)");
|
||||
|
||||
log("sound");
|
||||
initsound();
|
||||
|
||||
log("cfg");
|
||||
newmenu("frags\tpj\tping\tteam\tname");
|
||||
newmenu("ping\tplr\tserver");
|
||||
exec("data/keymap.cfg");
|
||||
exec("data/menus.cfg");
|
||||
exec("data/prefabs.cfg");
|
||||
exec("data/sounds.cfg");
|
||||
exec("servers.cfg");
|
||||
if(!execfile("config.cfg")) execfile("data/defaults.cfg");
|
||||
exec("autoexec.cfg");
|
||||
|
||||
log("localconnect");
|
||||
localconnect();
|
||||
changemap("metl3"); // if this map is changed, also change depthcorrect()
|
||||
|
||||
log("mainloop");
|
||||
int ignore = 5;
|
||||
for(;;)
|
||||
{
|
||||
int millis = SDL_GetTicks()*gamespeed/100;
|
||||
if(millis-lastmillis>200) lastmillis = millis-200;
|
||||
else if(millis-lastmillis<1) lastmillis = millis-1;
|
||||
if(millis-lastmillis<minmillis) SDL_Delay(minmillis-(millis-lastmillis));
|
||||
cleardlights();
|
||||
updateworld(millis);
|
||||
if(!demoplayback) serverslice((int)time(NULL), 0);
|
||||
static float fps = 30.0f;
|
||||
fps = (1000.0f/curtime+fps*50)/51;
|
||||
computeraytable(player1->o.x, player1->o.y);
|
||||
readdepth(scr_w, scr_h);
|
||||
SDL_GL_SwapBuffers();
|
||||
extern void updatevol(); updatevol();
|
||||
if(framesinmap++<5) // cheap hack to get rid of initial sparklies, even when triple buffering etc.
|
||||
{
|
||||
player1->yaw += 5;
|
||||
gl_drawframe(scr_w, scr_h, fps);
|
||||
player1->yaw -= 5;
|
||||
};
|
||||
gl_drawframe(scr_w, scr_h, fps);
|
||||
SDL_Event event;
|
||||
int lasttype = 0, lastbut = 0;
|
||||
while(SDL_PollEvent(&event))
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
quit();
|
||||
break;
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
keypress(event.key.keysym.sym, event.key.state==SDL_PRESSED, event.key.keysym.unicode);
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
if(ignore) { ignore--; break; };
|
||||
mousemove(event.motion.xrel, event.motion.yrel);
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
if(lasttype==event.type && lastbut==event.button.button) break; // why?? get event twice without it
|
||||
keypress(-event.button.button, event.button.state!=0, 0);
|
||||
lasttype = event.type;
|
||||
lastbut = event.button.button;
|
||||
break;
|
||||
};
|
||||
};
|
||||
};
|
||||
quit();
|
||||
return 1;
|
||||
};
|
||||
|
||||
|
145
src/menus.cpp
Normal file
145
src/menus.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
// menus.cpp: ingame menu system (also used for scores and serverlist)
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
struct mitem { char *text, *action; };
|
||||
|
||||
struct gmenu
|
||||
{
|
||||
char *name;
|
||||
vector<mitem> items;
|
||||
int mwidth;
|
||||
int menusel;
|
||||
};
|
||||
|
||||
vector<gmenu> menus;
|
||||
|
||||
int vmenu = -1;
|
||||
|
||||
ivector menustack;
|
||||
|
||||
void menuset(int menu)
|
||||
{
|
||||
if((vmenu = menu)>=1) resetmovement(player1);
|
||||
if(vmenu==1) menus[1].menusel = 0;
|
||||
};
|
||||
|
||||
void showmenu(char *name)
|
||||
{
|
||||
loopv(menus) if(i>1 && strcmp(menus[i].name, name)==0)
|
||||
{
|
||||
menuset(i);
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
int menucompare(mitem *a, mitem *b)
|
||||
{
|
||||
int x = atoi(a->text);
|
||||
int y = atoi(b->text);
|
||||
if(x>y) return -1;
|
||||
if(x<y) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
void sortmenu(int start, int num)
|
||||
{
|
||||
qsort(&menus[0].items[start], num, sizeof(mitem), (int (__cdecl *)(const void *,const void *))menucompare);
|
||||
};
|
||||
|
||||
void refreshservers();
|
||||
|
||||
bool rendermenu()
|
||||
{
|
||||
if(vmenu<0) { menustack.setsize(0); return false; };
|
||||
if(vmenu==1) refreshservers();
|
||||
gmenu &m = menus[vmenu];
|
||||
sprintf_sd(title)(vmenu>1 ? "[ %s menu ]" : "%s", m.name);
|
||||
int mdisp = m.items.length();
|
||||
int w = 0;
|
||||
loopi(mdisp)
|
||||
{
|
||||
int x = text_width(m.items[i].text);
|
||||
if(x>w) w = x;
|
||||
};
|
||||
int tw = text_width(title);
|
||||
if(tw>w) w = tw;
|
||||
int step = FONTH/4*5;
|
||||
int h = (mdisp+2)*step;
|
||||
int y = (VIRTH-h)/2;
|
||||
int x = (VIRTW-w)/2;
|
||||
blendbox(x-FONTH/2*3, y-FONTH, x+w+FONTH/2*3, y+h+FONTH, true);
|
||||
draw_text(title, x, y,2);
|
||||
y += FONTH*2;
|
||||
if(vmenu)
|
||||
{
|
||||
int bh = y+m.menusel*step;
|
||||
blendbox(x-FONTH, bh-10, x+w+FONTH, bh+FONTH+10, false);
|
||||
};
|
||||
loopj(mdisp)
|
||||
{
|
||||
draw_text(m.items[j].text, x, y, 2);
|
||||
y += step;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
void newmenu(char *name)
|
||||
{
|
||||
gmenu &menu = menus.add();
|
||||
menu.name = newstring(name);
|
||||
menu.menusel = 0;
|
||||
};
|
||||
|
||||
void menumanual(int m, int n, char *text)
|
||||
{
|
||||
if(!n) menus[m].items.setsize(0);
|
||||
mitem &mitem = menus[m].items.add();
|
||||
mitem.text = text;
|
||||
mitem.action = "";
|
||||
}
|
||||
|
||||
void menuitem(char *text, char *action)
|
||||
{
|
||||
gmenu &menu = menus.last();
|
||||
mitem &mi = menu.items.add();
|
||||
mi.text = newstring(text);
|
||||
mi.action = action[0] ? newstring(action) : mi.text;
|
||||
};
|
||||
|
||||
COMMAND(menuitem, ARG_2STR);
|
||||
COMMAND(showmenu, ARG_1STR);
|
||||
COMMAND(newmenu, ARG_1STR);
|
||||
|
||||
bool menukey(int code, bool isdown)
|
||||
{
|
||||
if(vmenu<=0) return false;
|
||||
int menusel = menus[vmenu].menusel;
|
||||
if(isdown)
|
||||
{
|
||||
if(code==SDLK_ESCAPE)
|
||||
{
|
||||
menuset(-1);
|
||||
if(!menustack.empty()) menuset(menustack.pop());
|
||||
return true;
|
||||
}
|
||||
else if(code==SDLK_UP || code==-4) menusel--;
|
||||
else if(code==SDLK_DOWN || code==-5) menusel++;
|
||||
int n = menus[vmenu].items.length();
|
||||
if(menusel<0) menusel = n-1;
|
||||
else if(menusel>=n) menusel = 0;
|
||||
menus[vmenu].menusel = menusel;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(code==SDLK_RETURN || code==-2)
|
||||
{
|
||||
char *action = menus[vmenu].items[menusel].action;
|
||||
if(vmenu==1) connects(getservername(menusel));
|
||||
menustack.add(vmenu);
|
||||
menuset(-1);
|
||||
execute(action, true);
|
||||
};
|
||||
};
|
||||
return true;
|
||||
};
|
338
src/monster.cpp
Normal file
338
src/monster.cpp
Normal file
|
@ -0,0 +1,338 @@
|
|||
// monster.cpp: implements AI for single player monsters, currently client only
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
dvector monsters;
|
||||
int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;
|
||||
|
||||
VARF(skill, 1, 3, 10, conoutf("skill is now %d", skill));
|
||||
|
||||
dvector &getmonsters() { return monsters; };
|
||||
void restoremonsterstate() { loopv(monsters) if(monsters[i]->state==CS_DEAD) numkilled++; }; // for savegames
|
||||
|
||||
#define TOTMFREQ 13
|
||||
#define NUMMONSTERTYPES 8
|
||||
|
||||
struct monstertype // see docs for how these values modify behaviour
|
||||
{
|
||||
short gun, speed, health, freq, lag, rate, pain, loyalty, mscale, bscale;
|
||||
short painsound, diesound;
|
||||
char *name, *mdlname;
|
||||
}
|
||||
|
||||
monstertypes[NUMMONSTERTYPES] =
|
||||
{
|
||||
{ GUN_FIREBALL, 15, 100, 3, 0, 100, 800, 1, 10, 10, S_PAINO, S_DIE1, "an ogre", "monster/ogro" },
|
||||
{ GUN_CG, 18, 70, 2, 70, 10, 400, 2, 8, 9, S_PAINR, S_DEATHR, "a rhino", "monster/rhino" },
|
||||
{ GUN_SG, 14, 120, 1, 100, 300, 400, 4, 14, 14, S_PAINE, S_DEATHE, "ratamahatta", "monster/rat" },
|
||||
{ GUN_RIFLE, 15, 200, 1, 80, 300, 300, 4, 18, 18, S_PAINS, S_DEATHS, "a slith", "monster/slith" },
|
||||
{ GUN_RL, 13, 500, 1, 0, 100, 200, 6, 24, 24, S_PAINB, S_DEATHB, "bauul", "monster/bauul" },
|
||||
{ GUN_BITE, 22, 50, 3, 0, 100, 400, 1, 12, 15, S_PAINP, S_PIGGR2, "a hellpig", "monster/hellpig" },
|
||||
{ GUN_ICEBALL, 12, 250, 1, 0, 10, 400, 6, 18, 18, S_PAINH, S_DEATHH, "a knight", "monster/knight" },
|
||||
{ GUN_SLIMEBALL, 15, 100, 1, 0, 200, 400, 2, 13, 10, S_PAIND, S_DEATHD, "a goblin", "monster/goblin" },
|
||||
};
|
||||
|
||||
dynent *basicmonster(int type, int yaw, int state, int trigger, int move)
|
||||
{
|
||||
if(type>=NUMMONSTERTYPES)
|
||||
{
|
||||
conoutf("warning: unknown monster in spawn: %d", type);
|
||||
type = 0;
|
||||
};
|
||||
dynent *m = newdynent();
|
||||
monstertype *t = &monstertypes[m->mtype = type];
|
||||
m->eyeheight = 2.0f;
|
||||
m->aboveeye = 1.9f;
|
||||
m->radius *= t->bscale/10.0f;
|
||||
m->eyeheight *= t->bscale/10.0f;
|
||||
m->aboveeye *= t->bscale/10.0f;
|
||||
m->monsterstate = state;
|
||||
if(state!=M_SLEEP) spawnplayer(m);
|
||||
m->trigger = lastmillis+trigger;
|
||||
m->targetyaw = m->yaw = (float)yaw;
|
||||
m->move = move;
|
||||
m->enemy = player1;
|
||||
m->gunselect = t->gun;
|
||||
m->maxspeed = (float)t->speed;
|
||||
m->health = t->health;
|
||||
m->armour = 0;
|
||||
loopi(NUMGUNS) m->ammo[i] = 10000;
|
||||
m->pitch = 0;
|
||||
m->roll = 0;
|
||||
m->state = CS_ALIVE;
|
||||
m->anger = 0;
|
||||
strcpy_s(m->name, t->name);
|
||||
monsters.add(m);
|
||||
return m;
|
||||
};
|
||||
|
||||
void spawnmonster() // spawn a random monster according to freq distribution in DMSP
|
||||
{
|
||||
int n = rnd(TOTMFREQ), type;
|
||||
for(int i = 0; ; i++) if((n -= monstertypes[i].freq)<0) { type = i; break; };
|
||||
basicmonster(type, rnd(360), M_SEARCH, 1000, 1);
|
||||
};
|
||||
|
||||
void monsterclear() // called after map start of when toggling edit mode to reset/spawn all monsters to initial state
|
||||
{
|
||||
loopv(monsters) gp()->dealloc(monsters[i], sizeof(dynent));
|
||||
monsters.setsize(0);
|
||||
numkilled = 0;
|
||||
monstertotal = 0;
|
||||
spawnremain = 0;
|
||||
if(m_dmsp)
|
||||
{
|
||||
nextmonster = mtimestart = lastmillis+10000;
|
||||
monstertotal = spawnremain = gamemode<0 ? skill*10 : 0;
|
||||
}
|
||||
else if(m_classicsp)
|
||||
{
|
||||
mtimestart = lastmillis;
|
||||
loopv(ents) if(ents[i].type==MONSTER)
|
||||
{
|
||||
dynent *m = basicmonster(ents[i].attr2, ents[i].attr1, M_SLEEP, 100, 0);
|
||||
m->o.x = ents[i].x;
|
||||
m->o.y = ents[i].y;
|
||||
m->o.z = ents[i].z;
|
||||
entinmap(m);
|
||||
monstertotal++;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
bool los(float lx, float ly, float lz, float bx, float by, float bz, vec &v) // height-correct line of sight for monster shooting/seeing
|
||||
{
|
||||
if(OUTBORD((int)lx, (int)ly) || OUTBORD((int)bx, (int)by)) return false;
|
||||
float dx = bx-lx;
|
||||
float dy = by-ly;
|
||||
int steps = (int)(sqrt(dx*dx+dy*dy)/0.9);
|
||||
if(!steps) return false;
|
||||
float x = lx;
|
||||
float y = ly;
|
||||
int i = 0;
|
||||
for(;;)
|
||||
{
|
||||
sqr *s = S(fast_f2nat(x), fast_f2nat(y));
|
||||
if(SOLID(s)) break;
|
||||
float floor = s->floor;
|
||||
if(s->type==FHF) floor -= s->vdelta/4.0f;
|
||||
float ceil = s->ceil;
|
||||
if(s->type==CHF) ceil += s->vdelta/4.0f;
|
||||
float rz = lz-((lz-bz)*(i/(float)steps));
|
||||
if(rz<floor || rz>ceil) break;
|
||||
v.x = x;
|
||||
v.y = y;
|
||||
v.z = rz;
|
||||
x += dx/(float)steps;
|
||||
y += dy/(float)steps;
|
||||
i++;
|
||||
};
|
||||
return i>=steps;
|
||||
};
|
||||
|
||||
bool enemylos(dynent *m, vec &v)
|
||||
{
|
||||
v = m->o;
|
||||
return los(m->o.x, m->o.y, m->o.z, m->enemy->o.x, m->enemy->o.y, m->enemy->o.z, v);
|
||||
};
|
||||
|
||||
// monster AI is sequenced using transitions: they are in a particular state where
|
||||
// they execute a particular behaviour until the trigger time is hit, and then they
|
||||
// reevaluate their situation based on the current state, the environment etc., and
|
||||
// transition to the next state. Transition timeframes are parametrized by difficulty
|
||||
// level (skill), faster transitions means quicker decision making means tougher AI.
|
||||
|
||||
void transition(dynent *m, int state, int moving, int n, int r) // n = at skill 0, n/2 = at skill 10, r = added random factor
|
||||
{
|
||||
m->monsterstate = state;
|
||||
m->move = moving;
|
||||
n = n*130/100;
|
||||
m->trigger = lastmillis+n-skill*(n/16)+rnd(r+1);
|
||||
};
|
||||
|
||||
void normalise(dynent *m, float angle)
|
||||
{
|
||||
while(m->yaw<angle-180.0f) m->yaw += 360.0f;
|
||||
while(m->yaw>angle+180.0f) m->yaw -= 360.0f;
|
||||
};
|
||||
|
||||
void monsteraction(dynent *m) // main AI thinking routine, called every frame for every monster
|
||||
{
|
||||
if(m->enemy->state==CS_DEAD) { m->enemy = player1; m->anger = 0; };
|
||||
normalise(m, m->targetyaw);
|
||||
if(m->targetyaw>m->yaw) // slowly turn monster towards his target
|
||||
{
|
||||
m->yaw += curtime*0.5f;
|
||||
if(m->targetyaw<m->yaw) m->yaw = m->targetyaw;
|
||||
}
|
||||
else
|
||||
{
|
||||
m->yaw -= curtime*0.5f;
|
||||
if(m->targetyaw>m->yaw) m->yaw = m->targetyaw;
|
||||
};
|
||||
|
||||
vdist(disttoenemy, vectoenemy, m->o, m->enemy->o);
|
||||
m->pitch = atan2(m->enemy->o.z-m->o.z, disttoenemy)*180/PI;
|
||||
|
||||
if(m->blocked) // special case: if we run into scenery
|
||||
{
|
||||
m->blocked = false;
|
||||
if(!rnd(20000/monstertypes[m->mtype].speed)) // try to jump over obstackle (rare)
|
||||
{
|
||||
m->jumpnext = true;
|
||||
}
|
||||
else if(m->trigger<lastmillis && (m->monsterstate!=M_HOME || !rnd(5))) // search for a way around (common)
|
||||
{
|
||||
m->targetyaw += 180+rnd(180); // patented "random walk" AI pathfinding (tm) ;)
|
||||
transition(m, M_SEARCH, 1, 400, 1000);
|
||||
};
|
||||
};
|
||||
|
||||
float enemyyaw = -(float)atan2(m->enemy->o.x - m->o.x, m->enemy->o.y - m->o.y)/PI*180+180;
|
||||
|
||||
switch(m->monsterstate)
|
||||
{
|
||||
case M_PAIN:
|
||||
case M_ATTACKING:
|
||||
case M_SEARCH:
|
||||
if(m->trigger<lastmillis) transition(m, M_HOME, 1, 100, 200);
|
||||
break;
|
||||
|
||||
case M_SLEEP: // state classic sp monster start in, wait for visual contact
|
||||
{
|
||||
vec target;
|
||||
if(editmode || !enemylos(m, target)) return; // skip running physics
|
||||
normalise(m, enemyyaw);
|
||||
float angle = (float)fabs(enemyyaw-m->yaw);
|
||||
if(disttoenemy<8 // the better the angle to the player, the further the monster can see/hear
|
||||
||(disttoenemy<16 && angle<135)
|
||||
||(disttoenemy<32 && angle<90)
|
||||
||(disttoenemy<64 && angle<45)
|
||||
|| angle<10)
|
||||
{
|
||||
transition(m, M_HOME, 1, 500, 200);
|
||||
playsound(S_GRUNT1+rnd(2), &m->o);
|
||||
};
|
||||
break;
|
||||
};
|
||||
|
||||
case M_AIMING: // this state is the delay between wanting to shoot and actually firing
|
||||
if(m->trigger<lastmillis)
|
||||
{
|
||||
m->lastaction = 0;
|
||||
m->attacking = true;
|
||||
shoot(m, m->attacktarget);
|
||||
transition(m, M_ATTACKING, 0, 600, 0);
|
||||
};
|
||||
break;
|
||||
|
||||
case M_HOME: // monster has visual contact, heads straight for player and may want to shoot at any time
|
||||
m->targetyaw = enemyyaw;
|
||||
if(m->trigger<lastmillis)
|
||||
{
|
||||
vec target;
|
||||
if(!enemylos(m, target)) // no visual contact anymore, let monster get as close as possible then search for player
|
||||
{
|
||||
transition(m, M_HOME, 1, 800, 500);
|
||||
}
|
||||
else // the closer the monster is the more likely he wants to shoot
|
||||
{
|
||||
if(!rnd((int)disttoenemy/3+1) && m->enemy->state==CS_ALIVE) // get ready to fire
|
||||
{
|
||||
m->attacktarget = target;
|
||||
transition(m, M_AIMING, 0, monstertypes[m->mtype].lag, 10);
|
||||
}
|
||||
else // track player some more
|
||||
{
|
||||
transition(m, M_HOME, 1, monstertypes[m->mtype].rate, 0);
|
||||
};
|
||||
};
|
||||
};
|
||||
break;
|
||||
};
|
||||
|
||||
moveplayer(m, 1, false); // use physics to move monster
|
||||
};
|
||||
|
||||
void monsterpain(dynent *m, int damage, dynent *d)
|
||||
{
|
||||
if(d->monsterstate) // a monster hit us
|
||||
{
|
||||
if(m!=d) // guard for RL guys shooting themselves :)
|
||||
{
|
||||
m->anger++; // don't attack straight away, first get angry
|
||||
int anger = m->mtype==d->mtype ? m->anger/2 : m->anger;
|
||||
if(anger>=monstertypes[m->mtype].loyalty) m->enemy = d; // monster infight if very angry
|
||||
};
|
||||
}
|
||||
else // player hit us
|
||||
{
|
||||
m->anger = 0;
|
||||
m->enemy = d;
|
||||
};
|
||||
transition(m, M_PAIN, 0, monstertypes[m->mtype].pain,200); // in this state monster won't attack
|
||||
if((m->health -= damage)<=0)
|
||||
{
|
||||
m->state = CS_DEAD;
|
||||
m->lastaction = lastmillis;
|
||||
numkilled++;
|
||||
player1->frags = numkilled;
|
||||
playsound(monstertypes[m->mtype].diesound, &m->o);
|
||||
int remain = monstertotal-numkilled;
|
||||
if(remain>0 && remain<=5) conoutf("only %d monster(s) remaining", remain);
|
||||
}
|
||||
else
|
||||
{
|
||||
playsound(monstertypes[m->mtype].painsound, &m->o);
|
||||
};
|
||||
};
|
||||
|
||||
void endsp(bool allkilled)
|
||||
{
|
||||
conoutf(allkilled ? "you have cleared the map!" : "you reached the exit!");
|
||||
conoutf("score: %d kills in %d seconds", numkilled, (lastmillis-mtimestart)/1000);
|
||||
monstertotal = 0;
|
||||
startintermission();
|
||||
};
|
||||
|
||||
void monsterthink()
|
||||
{
|
||||
if(m_dmsp && spawnremain && lastmillis>nextmonster)
|
||||
{
|
||||
if(spawnremain--==monstertotal) conoutf("The invasion has begun!");
|
||||
nextmonster = lastmillis+1000;
|
||||
spawnmonster();
|
||||
};
|
||||
|
||||
if(monstertotal && !spawnremain && numkilled==monstertotal) endsp(true);
|
||||
|
||||
loopv(ents) // equivalent of player entity touch, but only teleports are used
|
||||
{
|
||||
entity &e = ents[i];
|
||||
if(e.type!=TELEPORT) continue;
|
||||
if(OUTBORD(e.x, e.y)) continue;
|
||||
vec v = { 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);
|
||||
};
|
1
src/pch.cpp
Normal file
1
src/pch.cpp
Normal file
|
@ -0,0 +1 @@
|
|||
#include "cube.h"
|
323
src/physics.cpp
Normal file
323
src/physics.cpp
Normal file
|
@ -0,0 +1,323 @@
|
|||
// physics.cpp: no physics books were hurt nor consulted in the construction of this code.
|
||||
// All physics computations and constants were invented on the fly and simply tweaked until
|
||||
// they "felt right", and have no basis in reality. Collision detection is simplistic but
|
||||
// very robust (uses discrete steps at fixed fps).
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
bool plcollide(dynent *d, dynent *o, float &headspace, float &hi, float &lo) // collide with player or monster
|
||||
{
|
||||
if(o->state!=CS_ALIVE) return true;
|
||||
const float r = o->radius+d->radius;
|
||||
if(fabs(o->o.x-d->o.x)<r && fabs(o->o.y-d->o.y)<r)
|
||||
{
|
||||
if(d->o.z-d->eyeheight<o->o.z-o->eyeheight) { if(o->o.z-o->eyeheight<hi) hi = o->o.z-o->eyeheight-1; }
|
||||
else if(o->o.z+o->aboveeye>lo) lo = o->o.z+o->aboveeye+1;
|
||||
|
||||
if(fabs(o->o.z-d->o.z)<o->aboveeye+d->eyeheight) return false;
|
||||
if(d->monsterstate) return false; // hack
|
||||
headspace = d->o.z-o->o.z-o->aboveeye-d->eyeheight;
|
||||
if(headspace<0) headspace = 10;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
bool cornertest(int mip, int x, int y, int dx, int dy, int &bx, int &by, int &bs) // recursively collide with a mipmapped corner cube
|
||||
{
|
||||
sqr *w = wmip[mip];
|
||||
int sz = ssize>>mip;
|
||||
bool stest = SOLID(SWS(w, x+dx, y, sz)) && SOLID(SWS(w, x, y+dy, sz));
|
||||
mip++;
|
||||
x /= 2;
|
||||
y /= 2;
|
||||
if(SWS(wmip[mip], x, y, ssize>>mip)->type==CORNER)
|
||||
{
|
||||
bx = x<<mip;
|
||||
by = y<<mip;
|
||||
bs = 1<<mip;
|
||||
return cornertest(mip, x, y, dx, dy, bx, by, bs);
|
||||
};
|
||||
return stest;
|
||||
};
|
||||
|
||||
void mmcollide(dynent *d, float &hi, float &lo) // collide with a mapmodel
|
||||
{
|
||||
loopv(ents)
|
||||
{
|
||||
entity &e = ents[i];
|
||||
if(e.type!=MAPMODEL) continue;
|
||||
mapmodelinfo &mmi = getmminfo(e.attr2);
|
||||
if(!&mmi || !mmi.h) continue;
|
||||
const float r = mmi.rad+d->radius;
|
||||
if(fabs(e.x-d->o.x)<r && fabs(e.y-d->o.y)<r)
|
||||
{
|
||||
float mmz = (float)(S(e.x, e.y)->floor+mmi.zoff+e.attr3);
|
||||
if(d->o.z-d->eyeheight<mmz) { if(mmz<hi) hi = mmz; }
|
||||
else if(mmz+mmi.h>lo) lo = mmz+mmi.h;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// all collision happens here
|
||||
// spawn is a dirty side effect used in spawning
|
||||
// drop & rise are supplied by the physics below to indicate gravity/push for current mini-timestep
|
||||
|
||||
bool collide(dynent *d, bool spawn, float drop, float rise)
|
||||
{
|
||||
const float fx1 = d->o.x-d->radius; // figure out integer cube rectangle this entity covers in map
|
||||
const float fy1 = d->o.y-d->radius;
|
||||
const float fx2 = d->o.x+d->radius;
|
||||
const float fy2 = d->o.y+d->radius;
|
||||
const int x1 = fast_f2nat(fx1);
|
||||
const int y1 = fast_f2nat(fy1);
|
||||
const int x2 = fast_f2nat(fx2);
|
||||
const int y2 = fast_f2nat(fy2);
|
||||
float hi = 127, lo = -128;
|
||||
float minfloor = (d->monsterstate && !spawn && d->health>100) ? d->o.z-d->eyeheight-4.5f : -1000.0f; // big monsters are afraid of heights, unless angry :)
|
||||
|
||||
for(int x = x1; x<=x2; x++) for(int y = y1; y<=y2; y++) // collide with map
|
||||
{
|
||||
if(OUTBORD(x,y)) return false;
|
||||
sqr *s = S(x,y);
|
||||
float ceil = s->ceil;
|
||||
float floor = s->floor;
|
||||
switch(s->type)
|
||||
{
|
||||
case SOLID:
|
||||
return false;
|
||||
|
||||
case CORNER:
|
||||
{
|
||||
int bx = x, by = y, bs = 1;
|
||||
if(x==x1 && y==y1 && cornertest(0, x, y, -1, -1, bx, by, bs) && fx1-bx+fy1-by<=bs
|
||||
|| x==x2 && y==y1 && cornertest(0, x, y, 1, -1, bx, by, bs) && fx2-bx>=fy1-by
|
||||
|| x==x1 && y==y2 && cornertest(0, x, y, -1, 1, bx, by, bs) && fx1-bx<=fy2-by
|
||||
|| x==x2 && y==y2 && cornertest(0, x, y, 1, 1, bx, by, bs) && fx2-bx+fy2-by>=bs)
|
||||
return false;
|
||||
break;
|
||||
};
|
||||
|
||||
case FHF: // FIXME: too simplistic collision with slopes, makes it feels like tiny stairs
|
||||
floor -= (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f;
|
||||
break;
|
||||
|
||||
case CHF:
|
||||
ceil += (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f;
|
||||
|
||||
};
|
||||
if(ceil<hi) hi = ceil;
|
||||
if(floor>lo) lo = floor;
|
||||
if(floor<minfloor) return false;
|
||||
};
|
||||
|
||||
if(hi-lo < d->eyeheight+d->aboveeye) return false;
|
||||
|
||||
float headspace = 10;
|
||||
loopv(players) // collide with other players
|
||||
{
|
||||
dynent *o = players[i];
|
||||
if(!o || o==d) continue;
|
||||
if(!plcollide(d, o, headspace, hi, lo)) return false;
|
||||
};
|
||||
if(d!=player1) if(!plcollide(d, player1, headspace, hi, lo)) return false;
|
||||
dvector &v = getmonsters();
|
||||
// this loop can be a performance bottleneck with many monster on a slow cpu,
|
||||
// should replace with a blockmap but seems mostly fast enough
|
||||
loopv(v) if(!vreject(d->o, v[i]->o, 7.0f) && d!=v[i] && !plcollide(d, v[i], headspace, hi, lo)) return false;
|
||||
headspace -= 0.01f;
|
||||
|
||||
mmcollide(d, hi, lo); // collide with map models
|
||||
|
||||
if(spawn)
|
||||
{
|
||||
d->o.z = lo+d->eyeheight; // just drop to floor (sideeffect)
|
||||
d->onfloor = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float space = d->o.z-d->eyeheight-lo;
|
||||
if(space<0)
|
||||
{
|
||||
if(space>-0.01) d->o.z = lo+d->eyeheight; // stick on step
|
||||
else if(space>-1.26f) d->o.z += rise; // rise thru stair
|
||||
else return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
d->o.z -= min(min(drop, space), headspace); // gravity
|
||||
};
|
||||
|
||||
const float space2 = hi-(d->o.z+d->aboveeye);
|
||||
if(space2<0)
|
||||
{
|
||||
if(space2<-0.1) return false; // hack alert!
|
||||
d->o.z = hi-d->aboveeye; // glue to ceiling
|
||||
d->vel.z = 0; // cancel out jumping velocity
|
||||
};
|
||||
|
||||
d->onfloor = d->o.z-d->eyeheight-lo<0.001f;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
float rad(float x) { return x*3.14159f/180; };
|
||||
|
||||
VARP(maxroll, 0, 3, 20);
|
||||
|
||||
int physicsfraction = 0, physicsrepeat = 0;
|
||||
const int MINFRAMETIME = 20; // physics always simulated at 50fps or better
|
||||
|
||||
void physicsframe() // optimally schedule physics frames inside the graphics frames
|
||||
{
|
||||
if(curtime>=MINFRAMETIME)
|
||||
{
|
||||
int faketime = curtime+physicsfraction;
|
||||
physicsrepeat = faketime/MINFRAMETIME;
|
||||
physicsfraction = faketime-physicsrepeat*MINFRAMETIME;
|
||||
}
|
||||
else
|
||||
{
|
||||
physicsrepeat = 1;
|
||||
};
|
||||
};
|
||||
|
||||
// main physics routine, moves a player/monster for a curtime step
|
||||
// moveres indicated the physics precision (which is lower for monsters and multiplayer prediction)
|
||||
// local is false for multiplayer prediction
|
||||
|
||||
void moveplayer(dynent *pl, int moveres, bool local, int curtime)
|
||||
{
|
||||
const bool water = hdr.waterlevel>pl->o.z-0.5f;
|
||||
const bool floating = (editmode && local) || pl->state==CS_EDITING;
|
||||
|
||||
vec d; // vector of direction we ideally want to move in
|
||||
|
||||
d.x = (float)(pl->move*cos(rad(pl->yaw-90)));
|
||||
d.y = (float)(pl->move*sin(rad(pl->yaw-90)));
|
||||
d.z = 0;
|
||||
|
||||
if(floating || water)
|
||||
{
|
||||
d.x *= (float)cos(rad(pl->pitch));
|
||||
d.y *= (float)cos(rad(pl->pitch));
|
||||
d.z = (float)(pl->move*sin(rad(pl->pitch)));
|
||||
};
|
||||
|
||||
d.x += (float)(pl->strafe*cos(rad(pl->yaw-180)));
|
||||
d.y += (float)(pl->strafe*sin(rad(pl->yaw-180)));
|
||||
|
||||
const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed;
|
||||
const float friction = water ? 20.0f : (pl->onfloor || floating ? 6.0f : 30.0f);
|
||||
|
||||
const float fpsfric = friction/curtime*20.0f;
|
||||
|
||||
vmul(pl->vel, fpsfric-1); // slowly apply friction and direction to velocity, gives a smooth movement
|
||||
vadd(pl->vel, d);
|
||||
vdiv(pl->vel, fpsfric);
|
||||
d = pl->vel;
|
||||
vmul(d, speed); // d is now frametime based velocity vector
|
||||
|
||||
pl->blocked = false;
|
||||
pl->moving = true;
|
||||
|
||||
if(floating) // just apply velocity
|
||||
{
|
||||
vadd(pl->o, d);
|
||||
if(pl->jumpnext) { pl->jumpnext = false; pl->vel.z = 2; }
|
||||
}
|
||||
else // apply velocity with collision
|
||||
{
|
||||
if(pl->onfloor || water)
|
||||
{
|
||||
if(pl->jumpnext)
|
||||
{
|
||||
pl->jumpnext = false;
|
||||
pl->vel.z = 1.7f; // physics impulse upwards
|
||||
if(water) { pl->vel.x /= 8; pl->vel.y /= 8; }; // dampen velocity change even harder, gives correct water feel
|
||||
if(local) playsoundc(S_JUMP);
|
||||
else if(pl->monsterstate) playsound(S_JUMP, &pl->o);
|
||||
}
|
||||
else if(pl->timeinair>800) // if we land after long time must have been a high jump, make thud sound
|
||||
{
|
||||
if(local) playsoundc(S_LAND);
|
||||
else if(pl->monsterstate) playsound(S_LAND, &pl->o);
|
||||
};
|
||||
pl->timeinair = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
pl->timeinair += curtime;
|
||||
};
|
||||
|
||||
const float gravity = 20;
|
||||
const float f = 1.0f/moveres;
|
||||
float dropf = ((gravity-1)+pl->timeinair/15.0f); // incorrect, but works fine
|
||||
if(water) { dropf = 5; pl->timeinair = 0; }; // float slowly down in water
|
||||
const float drop = dropf*curtime/gravity/100/moveres; // at high fps, gravity kicks in too fast
|
||||
const float rise = speed/moveres/1.2f; // extra smoothness when lifting up stairs
|
||||
|
||||
loopi(moveres) // discrete steps collision detection & sliding
|
||||
{
|
||||
// try move forward
|
||||
pl->o.x += f*d.x;
|
||||
pl->o.y += f*d.y;
|
||||
pl->o.z += f*d.z;
|
||||
if(collide(pl, false, drop, rise)) continue;
|
||||
// player stuck, try slide along y axis
|
||||
pl->blocked = true;
|
||||
pl->o.x -= f*d.x;
|
||||
if(collide(pl, false, drop, rise)) { d.x = 0; continue; };
|
||||
pl->o.x += f*d.x;
|
||||
// still stuck, try x axis
|
||||
pl->o.y -= f*d.y;
|
||||
if(collide(pl, false, drop, rise)) { d.y = 0; continue; };
|
||||
pl->o.y += f*d.y;
|
||||
// try just dropping down
|
||||
pl->moving = false;
|
||||
pl->o.x -= f*d.x;
|
||||
pl->o.y -= f*d.y;
|
||||
if(collide(pl, false, drop, rise)) { d.y = d.x = 0; continue; };
|
||||
pl->o.z -= f*d.z;
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
// detect wether player is outside map, used for skipping zbuffer clear mostly
|
||||
|
||||
if(pl->o.x < 0 || pl->o.x >= ssize || pl->o.y <0 || pl->o.y > ssize)
|
||||
{
|
||||
pl->outsidemap = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
sqr *s = S((int)pl->o.x, (int)pl->o.y);
|
||||
pl->outsidemap = SOLID(s)
|
||||
|| pl->o.z < s->floor - (s->type==FHF ? s->vdelta/4 : 0)
|
||||
|| pl->o.z > s->ceil + (s->type==CHF ? s->vdelta/4 : 0);
|
||||
};
|
||||
|
||||
// automatically apply smooth roll when strafing
|
||||
|
||||
if(pl->strafe==0)
|
||||
{
|
||||
pl->roll = pl->roll/(1+(float)sqrt((float)curtime)/25);
|
||||
}
|
||||
else
|
||||
{
|
||||
pl->roll += pl->strafe*curtime/-30.0f;
|
||||
if(pl->roll>maxroll) pl->roll = (float)maxroll;
|
||||
if(pl->roll<-maxroll) pl->roll = (float)-maxroll;
|
||||
};
|
||||
|
||||
// play sounds on water transitions
|
||||
|
||||
if(!pl->inwater && water) { playsound(S_SPLASH2, &pl->o); pl->vel.z = 0; }
|
||||
else if(pl->inwater && !water) playsound(S_SPLASH1, &pl->o);
|
||||
pl->inwater = water;
|
||||
};
|
||||
|
||||
void moveplayer(dynent *pl, int moveres, bool local)
|
||||
{
|
||||
loopi(physicsrepeat) moveplayer(pl, moveres, local, i ? curtime/physicsrepeat : curtime-curtime/physicsrepeat*(physicsrepeat-1));
|
||||
};
|
||||
|
249
src/protos.h
Normal file
249
src/protos.h
Normal file
|
@ -0,0 +1,249 @@
|
|||
// 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 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);
|
||||
extern void exec(char *cfgfile);
|
||||
extern bool execfile(char *cfgfile);
|
||||
extern void resetcomplete();
|
||||
extern void complete(char *s);
|
||||
extern void alias(char *name, char *action);
|
||||
extern char *getalias(char *name);
|
||||
extern void writecfg();
|
||||
|
||||
// console
|
||||
extern void keypress(int code, bool isdown, int cooked);
|
||||
extern void renderconsole();
|
||||
extern void conoutf(const char *s, ...);
|
||||
extern char *getcurcommand();
|
||||
extern void writebinds(FILE *f);
|
||||
|
||||
// menus
|
||||
extern bool rendermenu();
|
||||
extern void menuset(int menu);
|
||||
extern void menumanual(int m, int n, char *text);
|
||||
extern void sortmenu(int start, int num);
|
||||
extern bool menukey(int code, bool isdown);
|
||||
extern void newmenu(char *name);
|
||||
|
||||
// serverbrowser
|
||||
extern void addserver(char *servername);
|
||||
extern char *getservername(int n);
|
||||
extern void writeservercfg();
|
||||
|
||||
// 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 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 addwaterquad(int x, int y, int size);
|
||||
extern int renderwater(float hf);
|
||||
extern void finishstrips();
|
||||
extern void setarraypointers();
|
||||
|
||||
// client
|
||||
extern void localservertoclient(uchar *buf, int len);
|
||||
extern void connects(char *servername);
|
||||
extern void disconnect(int onlyclean = 0, int async = 0);
|
||||
extern void toserver(char *text);
|
||||
extern void addmsg(int rel, int num, int type, ...);
|
||||
extern bool multiplayer();
|
||||
extern bool allowedittoggle();
|
||||
extern void sendpackettoserv(void *packet);
|
||||
extern void gets2c();
|
||||
extern void c2sinfo(dynent *d);
|
||||
extern void neterr(char *s);
|
||||
extern void initclientnet();
|
||||
extern bool netmapstart();
|
||||
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 updateworld(int millis);
|
||||
extern void startmap(char *name);
|
||||
extern void changemap(char *name);
|
||||
extern void initclient();
|
||||
extern void spawnplayer(dynent *d);
|
||||
extern void selfdamage(int damage, int actor, dynent *act);
|
||||
extern dynent *newdynent();
|
||||
extern char *getclientmap();
|
||||
extern const char *modestr(int n);
|
||||
extern void zapdynent(dynent *&d);
|
||||
extern dynent *getclient(int cn);
|
||||
extern void timeupdate(int timeremain);
|
||||
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);
|
||||
void showscores(bool on);
|
||||
extern void renderscores();
|
||||
|
||||
// world
|
||||
extern void setupworld(int factor);
|
||||
extern void empty_world(int factor, bool force);
|
||||
extern void remip(block &b, int level = 0);
|
||||
extern void remipmore(block &b, int level = 0);
|
||||
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);
|
||||
|
||||
// worldlight
|
||||
extern void calclight();
|
||||
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);
|
||||
|
||||
// worldocull
|
||||
extern void computeraytable(float vx, float vy);
|
||||
extern int isoccluded(float vx, float vy, float cx, float cy, float csize);
|
||||
|
||||
// main
|
||||
extern void fatal(char *s, char *o = "");
|
||||
extern void *alloc(int s);
|
||||
extern void keyrepeat(bool on);
|
||||
|
||||
// rendertext
|
||||
extern void draw_text(char *str, int left, int top, int gl_num);
|
||||
extern void draw_textf(char *fstr, int left, int top, int gl_num, ...);
|
||||
extern int text_width(char *str);
|
||||
extern void draw_envbox(int t, int fogdist);
|
||||
|
||||
// editing
|
||||
extern void cursorupdate();
|
||||
extern void toggleedit();
|
||||
extern void editdrag(bool isdown);
|
||||
extern void setvdeltaxy(int delta, block &sel);
|
||||
extern void editequalisexy(bool isfloor, block &sel);
|
||||
extern void edittypexy(int type, block &sel);
|
||||
extern void edittexxy(int type, int t, block &sel);
|
||||
extern void editheightxy(bool isfloor, int amount, block &sel);
|
||||
extern bool noteditmode();
|
||||
extern void pruneundos(int maxremain = 0);
|
||||
|
||||
// renderextras
|
||||
extern void line(int x1, int y1, float z1, int x2, int y2, float z2);
|
||||
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 readdepth(int w, int h);
|
||||
extern void blendbox(int x1, int y1, int x2, int y2, bool border);
|
||||
extern void damageblend(int n);
|
||||
|
||||
// renderparticles
|
||||
extern void setorient(vec &r, vec &u);
|
||||
extern void particle_splash(int type, int num, int fade, vec &p);
|
||||
extern void particle_trail(int type, int fade, vec &from, vec &to);
|
||||
extern void render_particles(int time);
|
||||
|
||||
// worldio
|
||||
extern void save_world(char *fname);
|
||||
extern void load_world(char *mname);
|
||||
extern void writemap(char *mname, int msize, uchar *mdata);
|
||||
extern uchar *readmap(char *mname, int *msize);
|
||||
extern void loadgamerest();
|
||||
extern void incomingdemodata(uchar *buf, int len, bool extras = false);
|
||||
extern void demoplaybackstep();
|
||||
extern void stop();
|
||||
extern void stopifrecording();
|
||||
extern void demodamage(int damage, vec &o);
|
||||
extern void demoblend(int damage);
|
||||
|
||||
// physics
|
||||
extern void moveplayer(dynent *pl, int moveres, bool local);
|
||||
extern bool collide(dynent *d, bool spawn, float drop, float rise);
|
||||
extern void entinmap(dynent *d);
|
||||
extern void setentphysics(int mml, int mmr);
|
||||
extern void physicsframe();
|
||||
|
||||
// sound
|
||||
extern void playsound(int n, vec *loc = 0);
|
||||
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 mapmodelinfo &getmminfo(int i);
|
||||
|
||||
// server
|
||||
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);
|
||||
extern void putint(uchar *&p, int n);
|
||||
extern int getint(uchar *&p);
|
||||
extern void sendstring(char *t, uchar *&p);
|
||||
extern void startintermission();
|
||||
extern void restoreserverstate(vector<entity> &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 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 shoot(dynent *d, vec &to);
|
||||
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);
|
||||
|
||||
// monster
|
||||
extern void monsterclear();
|
||||
extern void restoremonsterstate();
|
||||
extern void monsterthink();
|
||||
extern void monsterrender();
|
||||
extern dvector &getmonsters();
|
||||
extern void monsterpain(dynent *m, int damage, dynent *d);
|
||||
extern void endsp(bool allkilled);
|
||||
|
||||
// entities
|
||||
extern void renderents();
|
||||
extern void putitems(uchar *&p);
|
||||
extern void checkquad(int time);
|
||||
extern void checkitems();
|
||||
extern void realpickup(int n, dynent *d);
|
||||
extern void renderentities();
|
||||
extern void resetspawns();
|
||||
extern void setspawn(uint i, bool on);
|
||||
extern void teleport(int n, dynent *d);
|
||||
extern void baseammo(int gun);
|
||||
|
||||
// rndmap
|
||||
extern void perlinarea(block &b, int scale, int seed, int psize);
|
||||
|
359
src/rendercubes.cpp
Normal file
359
src/rendercubes.cpp
Normal file
|
@ -0,0 +1,359 @@
|
|||
// rendercubes.cpp: sits in between worldrender.cpp and rendergl.cpp and fills the vertex array for different cube surfaces.
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
vertex *verts = NULL;
|
||||
int curvert;
|
||||
int curmaxverts = 10000;
|
||||
|
||||
void setarraypointers()
|
||||
{
|
||||
glVertexPointer(3, GL_FLOAT, sizeof(vertex), &verts[0].x);
|
||||
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertex), &verts[0].r);
|
||||
glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), &verts[0].u);
|
||||
};
|
||||
|
||||
void reallocv()
|
||||
{
|
||||
verts = (vertex *)realloc(verts, (curmaxverts *= 2)*sizeof(vertex));
|
||||
curmaxverts -= 10;
|
||||
if(!verts) fatal("no vertex memory!");
|
||||
setarraypointers();
|
||||
};
|
||||
|
||||
// generating the actual vertices is done dynamically every frame and sits at the
|
||||
// leaves of all these functions, and are part of the cpu bottleneck on really slow
|
||||
// machines, hence the macros.
|
||||
|
||||
#define vertcheck() { if(curvert>=curmaxverts) reallocv(); }
|
||||
|
||||
#define vertf(v1, v2, v3, ls, t1, t2) { vertex &v = verts[curvert++]; \
|
||||
v.u = t1; v.v = t2; \
|
||||
v.x = v1; v.y = v2; v.z = v3; \
|
||||
v.r = ls->r; v.g = ls->g; v.b = ls->b; v.a = 255; };
|
||||
|
||||
#define vert(v1, v2, v3, ls, t1, t2) { vertf((float)(v1), (float)(v2), (float)(v3), ls, t1, t2); }
|
||||
|
||||
int nquads;
|
||||
const float TEXTURESCALE = 32.0f;
|
||||
bool floorstrip = false, deltastrip = false;
|
||||
int oh, oy, ox, ogltex; // the o* vars are used by the stripification
|
||||
int ol3r, ol3g, ol3b, ol4r, ol4g, ol4b;
|
||||
int firstindex;
|
||||
bool showm = false;
|
||||
|
||||
void showmip() { showm = !showm; };
|
||||
void mipstats(int a, int b, int c) { if(showm) conoutf("1x1/2x2/4x4: %d / %d / %d", a, b, c); };
|
||||
|
||||
COMMAND(showmip, ARG_NONE);
|
||||
|
||||
#define stripend() { if(floorstrip || deltastrip) { addstrip(ogltex, firstindex, curvert-firstindex); floorstrip = deltastrip = false; }; };
|
||||
void finishstrips() { stripend(); };
|
||||
|
||||
sqr sbright, sdark;
|
||||
VAR(lighterror,1,8,100);
|
||||
|
||||
void render_flat(int wtex, int x, int y, int size, int h, sqr *l1, sqr *l2, sqr *l3, sqr *l4, bool isceil) // floor/ceil quads
|
||||
{
|
||||
vertcheck();
|
||||
if(showm) { l3 = l1 = &sbright; l4 = l2 = &sdark; };
|
||||
|
||||
int sx, sy;
|
||||
int gltex = lookuptexture(wtex, sx, sy);
|
||||
float xf = TEXTURESCALE/sx;
|
||||
float yf = TEXTURESCALE/sy;
|
||||
float xs = size*xf;
|
||||
float ys = size*yf;
|
||||
float xo = xf*x;
|
||||
float yo = yf*y;
|
||||
|
||||
bool first = !floorstrip || y!=oy+size || ogltex!=gltex || h!=oh || x!=ox;
|
||||
|
||||
if(first) // start strip here
|
||||
{
|
||||
stripend();
|
||||
firstindex = curvert;
|
||||
ogltex = gltex;
|
||||
oh = h;
|
||||
ox = x;
|
||||
floorstrip = true;
|
||||
if(isceil)
|
||||
{
|
||||
vert(x+size, h, y, l2, xo+xs, yo);
|
||||
vert(x, h, y, l1, xo, yo);
|
||||
}
|
||||
else
|
||||
{
|
||||
vert(x, h, y, l1, xo, yo);
|
||||
vert(x+size, h, y, l2, xo+xs, yo);
|
||||
};
|
||||
ol3r = l1->r;
|
||||
ol3g = l1->g;
|
||||
ol3b = l1->b;
|
||||
ol4r = l2->r;
|
||||
ol4g = l2->g;
|
||||
ol4b = l2->b;
|
||||
}
|
||||
else // continue strip
|
||||
{
|
||||
int lighterr = lighterror*2;
|
||||
if((abs(ol3r-l3->r)<lighterr && abs(ol4r-l4->r)<lighterr // skip vertices if light values are close enough
|
||||
&& abs(ol3g-l3->g)<lighterr && abs(ol4g-l4->g)<lighterr
|
||||
&& abs(ol3b-l3->b)<lighterr && abs(ol4b-l4->b)<lighterr) || !wtex)
|
||||
{
|
||||
curvert -= 2;
|
||||
nquads--;
|
||||
}
|
||||
else
|
||||
{
|
||||
uchar *p3 = (uchar *)(&verts[curvert-1].r);
|
||||
ol3r = p3[0];
|
||||
ol3g = p3[1];
|
||||
ol3b = p3[2];
|
||||
uchar *p4 = (uchar *)(&verts[curvert-2].r);
|
||||
ol4r = p4[0];
|
||||
ol4g = p4[1];
|
||||
ol4b = p4[2];
|
||||
};
|
||||
};
|
||||
|
||||
if(isceil)
|
||||
{
|
||||
vert(x+size, h, y+size, l3, xo+xs, yo+ys);
|
||||
vert(x, h, y+size, l4, xo, yo+ys);
|
||||
}
|
||||
else
|
||||
{
|
||||
vert(x, h, y+size, l4, xo, yo+ys);
|
||||
vert(x+size, h, y+size, l3, xo+xs, yo+ys);
|
||||
};
|
||||
|
||||
oy = y;
|
||||
nquads++;
|
||||
};
|
||||
|
||||
void render_flatdelta(int wtex, int x, int y, int size, float h1, float h2, float h3, float h4, sqr *l1, sqr *l2, sqr *l3, sqr *l4, bool isceil) // floor/ceil quads on a slope
|
||||
{
|
||||
vertcheck();
|
||||
if(showm) { l3 = l1 = &sbright; l4 = l2 = &sdark; };
|
||||
|
||||
int sx, sy;
|
||||
int gltex = lookuptexture(wtex, sx, sy);
|
||||
float xf = TEXTURESCALE/sx;
|
||||
float yf = TEXTURESCALE/sy;
|
||||
float xs = size*xf;
|
||||
float ys = size*yf;
|
||||
float xo = xf*x;
|
||||
float yo = yf*y;
|
||||
|
||||
bool first = !deltastrip || y!=oy+size || ogltex!=gltex || x!=ox;
|
||||
|
||||
if(first)
|
||||
{
|
||||
stripend();
|
||||
firstindex = curvert;
|
||||
ogltex = gltex;
|
||||
ox = x;
|
||||
deltastrip = true;
|
||||
if(isceil)
|
||||
{
|
||||
vertf((float)x+size, h2, (float)y, l2, xo+xs, yo);
|
||||
vertf((float)x, h1, (float)y, l1, xo, yo);
|
||||
}
|
||||
else
|
||||
{
|
||||
vertf((float)x, h1, (float)y, l1, xo, yo);
|
||||
vertf((float)x+size, h2, (float)y, l2, xo+xs, yo);
|
||||
};
|
||||
ol3r = l1->r;
|
||||
ol3g = l1->g;
|
||||
ol3b = l1->b;
|
||||
ol4r = l2->r;
|
||||
ol4g = l2->g;
|
||||
ol4b = l2->b;
|
||||
};
|
||||
|
||||
if(isceil)
|
||||
{
|
||||
vertf((float)x+size, h3, (float)y+size, l3, xo+xs, yo+ys);
|
||||
vertf((float)x, h4, (float)y+size, l4, xo, yo+ys);
|
||||
}
|
||||
else
|
||||
{
|
||||
vertf((float)x, h4, (float)y+size, l4, xo, yo+ys);
|
||||
vertf((float)x+size, h3, (float)y+size, l3, xo+xs, yo+ys);
|
||||
};
|
||||
|
||||
oy = y;
|
||||
nquads++;
|
||||
};
|
||||
|
||||
void render_2tris(sqr *h, sqr *s, int x1, int y1, int x2, int y2, int x3, int y3, sqr *l1, sqr *l2, sqr *l3) // floor/ceil tris on a corner cube
|
||||
{
|
||||
stripend();
|
||||
vertcheck();
|
||||
|
||||
int sx, sy;
|
||||
int gltex = lookuptexture(h->ftex, sx, sy);
|
||||
float xf = TEXTURESCALE/sx;
|
||||
float yf = TEXTURESCALE/sy;
|
||||
|
||||
vertf((float)x1, h->floor, (float)y1, l1, xf*x1, yf*y1);
|
||||
vertf((float)x2, h->floor, (float)y2, l2, xf*x2, yf*y2);
|
||||
vertf((float)x3, h->floor, (float)y3, l3, xf*x3, yf*y3);
|
||||
addstrip(gltex, curvert-3, 3);
|
||||
|
||||
gltex = lookuptexture(h->ctex, sx, sy);
|
||||
xf = TEXTURESCALE/sx;
|
||||
yf = TEXTURESCALE/sy;
|
||||
|
||||
vertf((float)x3, h->ceil, (float)y3, l3, xf*x3, yf*y3);
|
||||
vertf((float)x2, h->ceil, (float)y2, l2, xf*x2, yf*y2);
|
||||
vertf((float)x1, h->ceil, (float)y1, l1, xf*x1, yf*y1);
|
||||
addstrip(gltex, curvert-3, 3);
|
||||
nquads++;
|
||||
};
|
||||
|
||||
void render_tris(int x, int y, int size, bool topleft,
|
||||
sqr *h1, sqr *h2, sqr *s, sqr *t, sqr *u, sqr *v)
|
||||
{
|
||||
if(topleft)
|
||||
{
|
||||
if(h1) render_2tris(h1, s, x+size, y+size, x, y+size, x, y, u, v, s);
|
||||
if(h2) render_2tris(h2, s, x, y, x+size, y, x+size, y+size, s, t, v);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(h1) render_2tris(h1, s, x, y, x+size, y, x, y+size, s, t, u);
|
||||
if(h2) render_2tris(h2, s, x+size, y, x+size, y+size, x, y+size, t, u, v);
|
||||
};
|
||||
};
|
||||
|
||||
void render_square(int wtex, float floor1, float floor2, float ceil1, float ceil2, int x1, int y1, int x2, int y2, int size, sqr *l1, sqr *l2, bool flip) // wall quads
|
||||
{
|
||||
stripend();
|
||||
vertcheck();
|
||||
if(showm) { l1 = &sbright; l2 = &sdark; };
|
||||
|
||||
int sx, sy;
|
||||
int gltex = lookuptexture(wtex, sx, sy);
|
||||
float xf = TEXTURESCALE/sx;
|
||||
float yf = TEXTURESCALE/sy;
|
||||
float xs = size*xf;
|
||||
float xo = xf*(x1==x2 ? min(y1,y2) : min(x1,x2));
|
||||
|
||||
if(!flip)
|
||||
{
|
||||
vertf((float)x2, ceil2, (float)y2, l2, xo+xs, -yf*ceil2);
|
||||
vertf((float)x1, ceil1, (float)y1, l1, xo, -yf*ceil1);
|
||||
vertf((float)x2, floor2, (float)y2, l2, xo+xs, -floor2*yf);
|
||||
vertf((float)x1, floor1, (float)y1, l1, xo, -floor1*yf);
|
||||
}
|
||||
else
|
||||
{
|
||||
vertf((float)x1, ceil1, (float)y1, l1, xo, -yf*ceil1);
|
||||
vertf((float)x2, ceil2, (float)y2, l2, xo+xs, -yf*ceil2);
|
||||
vertf((float)x1, floor1, (float)y1, l1, xo, -floor1*yf);
|
||||
vertf((float)x2, floor2, (float)y2, l2, xo+xs, -floor2*yf);
|
||||
};
|
||||
|
||||
nquads++;
|
||||
addstrip(gltex, curvert-4, 4);
|
||||
};
|
||||
|
||||
int wx1, wy1, wx2, wy2;
|
||||
|
||||
VAR(watersubdiv, 1, 4, 64);
|
||||
VARF(waterlevel, -128, -128, 127, if(!noteditmode()) hdr.waterlevel = waterlevel);
|
||||
|
||||
inline void vertw(int v1, float v2, int v3, sqr *c, float t1, float t2, float t)
|
||||
{
|
||||
vertcheck();
|
||||
vertf((float)v1, v2-(float)sin(v1*v3*0.1+t)*0.2f, (float)v3, c, t1, t2);
|
||||
};
|
||||
|
||||
inline float dx(float x) { return x+(float)sin(x*2+lastmillis/1000.0f)*0.04f; };
|
||||
inline float dy(float x) { return x+(float)sin(x*2+lastmillis/900.0f+PI/5)*0.05f; };
|
||||
|
||||
// renders water for bounding rect area that contains water... simple but very inefficient
|
||||
|
||||
int renderwater(float hf)
|
||||
{
|
||||
if(wx1<0) return nquads;
|
||||
|
||||
glDepthMask(GL_FALSE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_ONE, GL_SRC_COLOR);
|
||||
int sx, sy;
|
||||
glBindTexture(GL_TEXTURE_2D, lookuptexture(DEFAULT_LIQUID, sx, sy));
|
||||
|
||||
wx1 &= ~(watersubdiv-1);
|
||||
wy1 &= ~(watersubdiv-1);
|
||||
|
||||
float xf = TEXTURESCALE/sx;
|
||||
float yf = TEXTURESCALE/sy;
|
||||
float xs = watersubdiv*xf;
|
||||
float ys = watersubdiv*yf;
|
||||
float t1 = lastmillis/300.0f;
|
||||
float t2 = lastmillis/4000.0f;
|
||||
|
||||
sqr dl;
|
||||
dl.r = dl.g = dl.b = 255;
|
||||
|
||||
for(int xx = wx1; xx<wx2; xx += watersubdiv)
|
||||
{
|
||||
for(int yy = wy1; yy<wy2; yy += watersubdiv)
|
||||
{
|
||||
float xo = xf*(xx+t2);
|
||||
float yo = yf*(yy+t2);
|
||||
if(yy==wy1)
|
||||
{
|
||||
vertw(xx, hf, yy, &dl, dx(xo), dy(yo), t1);
|
||||
vertw(xx+watersubdiv, hf, yy, &dl, dx(xo+xs), dy(yo), t1);
|
||||
};
|
||||
vertw(xx, hf, yy+watersubdiv, &dl, dx(xo), dy(yo+ys), t1);
|
||||
vertw(xx+watersubdiv, hf, yy+watersubdiv, &dl, dx(xo+xs), dy(yo+ys), t1);
|
||||
};
|
||||
int n = (wy2-wy1-1)/watersubdiv;
|
||||
nquads += n;
|
||||
n = (n+2)*2;
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, curvert -= n, n);
|
||||
};
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glDepthMask(GL_TRUE);
|
||||
|
||||
return nquads;
|
||||
};
|
||||
|
||||
void addwaterquad(int x, int y, int size) // update bounding rect that contains water
|
||||
{
|
||||
int x2 = x+size;
|
||||
int y2 = y+size;
|
||||
if(wx1<0)
|
||||
{
|
||||
wx1 = x;
|
||||
wy1 = y;
|
||||
wx2 = x2;
|
||||
wy2 = y2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(x<wx1) wx1 = x;
|
||||
if(y<wy1) wy1 = y;
|
||||
if(x2>wx2) wx2 = x2;
|
||||
if(y2>wy2) wy2 = y2;
|
||||
};
|
||||
};
|
||||
|
||||
void resetcubes()
|
||||
{
|
||||
if(!verts) reallocv();
|
||||
floorstrip = deltastrip = false;
|
||||
wx1 = -1;
|
||||
nquads = 0;
|
||||
sbright.r = sbright.g = sbright.b = 255;
|
||||
sdark.r = sdark.g = sdark.b = 0;
|
||||
};
|
||||
|
||||
|
372
src/renderextras.cpp
Normal file
372
src/renderextras.cpp
Normal file
|
@ -0,0 +1,372 @@
|
|||
// renderextras.cpp: misc gl render code and the HUD
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
void line(int x1, int y1, float z1, int x2, int y2, float z2)
|
||||
{
|
||||
glBegin(GL_POLYGON);
|
||||
glVertex3f((float)x1, z1, (float)y1);
|
||||
glVertex3f((float)x1, z1, y1+0.01f);
|
||||
glVertex3f((float)x2, z2, y2+0.01f);
|
||||
glVertex3f((float)x2, z2, (float)y2);
|
||||
glEnd();
|
||||
xtraverts += 4;
|
||||
};
|
||||
|
||||
void linestyle(float width, int r, int g, int b)
|
||||
{
|
||||
glLineWidth(width);
|
||||
glColor3ub(r,g,b);
|
||||
};
|
||||
|
||||
void box(block &b, float z1, float z2, float z3, float z4)
|
||||
{
|
||||
glBegin(GL_POLYGON);
|
||||
glVertex3f((float)b.x, z1, (float)b.y);
|
||||
glVertex3f((float)b.x+b.xs, z2, (float)b.y);
|
||||
glVertex3f((float)b.x+b.xs, z3, (float)b.y+b.ys);
|
||||
glVertex3f((float)b.x, z4, (float)b.y+b.ys);
|
||||
glEnd();
|
||||
xtraverts += 4;
|
||||
};
|
||||
|
||||
void dot(int x, int y, float z)
|
||||
{
|
||||
const float DOF = 0.1f;
|
||||
glBegin(GL_POLYGON);
|
||||
glVertex3f(x-DOF, (float)z, y-DOF);
|
||||
glVertex3f(x+DOF, (float)z, y-DOF);
|
||||
glVertex3f(x+DOF, (float)z, y+DOF);
|
||||
glVertex3f(x-DOF, (float)z, y+DOF);
|
||||
glEnd();
|
||||
xtraverts += 4;
|
||||
};
|
||||
|
||||
void blendbox(int x1, int y1, int x2, int y2, bool border)
|
||||
{
|
||||
glDepthMask(GL_FALSE);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
|
||||
glBegin(GL_QUADS);
|
||||
if(border) glColor3d(0.5, 0.3, 0.4);
|
||||
else glColor3d(1.0, 1.0, 1.0);
|
||||
glVertex2i(x1, y1);
|
||||
glVertex2i(x2, y1);
|
||||
glVertex2i(x2, y2);
|
||||
glVertex2i(x1, y2);
|
||||
glEnd();
|
||||
glDisable(GL_BLEND);
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
||||
glBegin(GL_POLYGON);
|
||||
glColor3d(0.2, 0.7, 0.4);
|
||||
glVertex2i(x1, y1);
|
||||
glVertex2i(x2, y1);
|
||||
glVertex2i(x2, y2);
|
||||
glVertex2i(x1, y2);
|
||||
glEnd();
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
xtraverts += 8;
|
||||
glEnable(GL_BLEND);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glDepthMask(GL_TRUE);
|
||||
};
|
||||
|
||||
const int MAXSPHERES = 50;
|
||||
struct sphere { vec o; float size, max; int type; sphere *next; };
|
||||
sphere spheres[MAXSPHERES], *slist = NULL, *sempty = NULL;
|
||||
bool sinit = false;
|
||||
|
||||
void newsphere(vec &o, float max, int type)
|
||||
{
|
||||
if(!sinit)
|
||||
{
|
||||
loopi(MAXSPHERES)
|
||||
{
|
||||
spheres[i].next = sempty;
|
||||
sempty = &spheres[i];
|
||||
};
|
||||
sinit = true;
|
||||
};
|
||||
if(sempty)
|
||||
{
|
||||
sphere *p = sempty;
|
||||
sempty = p->next;
|
||||
p->o = o;
|
||||
p->max = max;
|
||||
p->size = 1;
|
||||
p->type = type;
|
||||
p->next = slist;
|
||||
slist = p;
|
||||
};
|
||||
};
|
||||
|
||||
void renderspheres(int time)
|
||||
{
|
||||
glDepthMask(GL_FALSE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
||||
glBindTexture(GL_TEXTURE_2D, 4);
|
||||
|
||||
for(sphere *p, **pp = &slist; p = *pp;)
|
||||
{
|
||||
glPushMatrix();
|
||||
float size = p->size/p->max;
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f-size);
|
||||
glTranslatef(p->o.x, p->o.z, p->o.y);
|
||||
glRotatef(lastmillis/5.0f, 1, 1, 1);
|
||||
glScalef(p->size, p->size, p->size);
|
||||
glCallList(1);
|
||||
glScalef(0.8f, 0.8f, 0.8f);
|
||||
glCallList(1);
|
||||
glPopMatrix();
|
||||
xtraverts += 12*6*2;
|
||||
|
||||
if(p->size>p->max)
|
||||
{
|
||||
*pp = p->next;
|
||||
p->next = sempty;
|
||||
sempty = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
p->size += time/100.0f;
|
||||
pp = &p->next;
|
||||
};
|
||||
};
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glDepthMask(GL_TRUE);
|
||||
};
|
||||
|
||||
string closeent;
|
||||
char *entnames[] =
|
||||
{
|
||||
"none?", "light", "playerstart",
|
||||
"shells", "bullets", "rockets", "riflerounds",
|
||||
"health", "healthboost", "greenarmour", "yellowarmour", "quaddamage",
|
||||
"teleport", "teledest",
|
||||
"mapmodel", "monster", "trigger", "jumppad",
|
||||
"?", "?", "?", "?", "?",
|
||||
};
|
||||
|
||||
void renderents() // show sparkly thingies for map entities in edit mode
|
||||
{
|
||||
closeent[0] = 0;
|
||||
if(!editmode) return;
|
||||
loopv(ents)
|
||||
{
|
||||
entity &e = ents[i];
|
||||
if(e.type==NOTUSED) continue;
|
||||
vec v = { 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);
|
||||
};
|
||||
|
||||
VARP(crosshairsize, 0, 15, 50);
|
||||
|
||||
int dblend = 0;
|
||||
void damageblend(int n) { dblend += n; };
|
||||
|
||||
VAR(hidestats, 0, 0, 1);
|
||||
VARP(crosshairfx, 0, 1, 1);
|
||||
|
||||
void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater)
|
||||
{
|
||||
readmatrices();
|
||||
if(editmode)
|
||||
{
|
||||
if(cursordepth==1.0f) worldpos = player1->o;
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
||||
cursorupdate();
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
};
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
invertperspective();
|
||||
glPushMatrix();
|
||||
glOrtho(0, VIRTW, VIRTH, 0, -1, 1);
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
glDepthMask(GL_FALSE);
|
||||
|
||||
if(dblend || underwater)
|
||||
{
|
||||
glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
|
||||
glBegin(GL_QUADS);
|
||||
if(dblend) glColor3d(0.0f, 0.9f, 0.9f);
|
||||
else glColor3d(0.9f, 0.5f, 0.0f);
|
||||
glVertex2i(0, 0);
|
||||
glVertex2i(VIRTW, 0);
|
||||
glVertex2i(VIRTW, VIRTH);
|
||||
glVertex2i(0, VIRTH);
|
||||
glEnd();
|
||||
dblend -= curtime/3;
|
||||
if(dblend<0) dblend = 0;
|
||||
};
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
char *command = getcurcommand();
|
||||
char *player = playerincrosshair();
|
||||
if(command) draw_textf("> %s_", 20, 1570, 2, command);
|
||||
else if(closeent[0]) draw_text(closeent, 20, 1570, 2);
|
||||
else if(player) draw_text(player, 20, 1570, 2);
|
||||
|
||||
renderscores();
|
||||
if(!rendermenu())
|
||||
{
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA);
|
||||
glBindTexture(GL_TEXTURE_2D, 1);
|
||||
glBegin(GL_QUADS);
|
||||
glColor3ub(255,255,255);
|
||||
if(crosshairfx)
|
||||
{
|
||||
if(player1->gunwait) glColor3ub(128,128,128);
|
||||
else if(player1->health<=25) glColor3ub(255,0,0);
|
||||
else if(player1->health<=50) glColor3ub(255,128,0);
|
||||
};
|
||||
float chsize = (float)crosshairsize;
|
||||
glTexCoord2d(0.0, 0.0); glVertex2f(VIRTW/2 - chsize, VIRTH/2 - chsize);
|
||||
glTexCoord2d(1.0, 0.0); glVertex2f(VIRTW/2 + chsize, VIRTH/2 - chsize);
|
||||
glTexCoord2d(1.0, 1.0); glVertex2f(VIRTW/2 + chsize, VIRTH/2 + chsize);
|
||||
glTexCoord2d(0.0, 1.0); glVertex2f(VIRTW/2 - chsize, VIRTH/2 + chsize);
|
||||
glEnd();
|
||||
};
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
glPushMatrix();
|
||||
glOrtho(0, VIRTW*4/3, VIRTH*4/3, 0, -1, 1);
|
||||
renderconsole();
|
||||
|
||||
if(!hidestats)
|
||||
{
|
||||
glPopMatrix();
|
||||
glPushMatrix();
|
||||
glOrtho(0, VIRTW*3/2, VIRTH*3/2, 0, -1, 1);
|
||||
draw_textf("fps %d", 3200, 2390, 2, curfps);
|
||||
draw_textf("wqd %d", 3200, 2460, 2, nquads);
|
||||
draw_textf("wvt %d", 3200, 2530, 2, curvert);
|
||||
draw_textf("evt %d", 3200, 2600, 2, xtraverts);
|
||||
};
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
if(player1->state==CS_ALIVE)
|
||||
{
|
||||
glPushMatrix();
|
||||
glOrtho(0, VIRTW/2, VIRTH/2, 0, -1, 1);
|
||||
draw_textf("%d", 90, 827, 2, player1->health);
|
||||
if(player1->armour) draw_textf("%d", 390, 827, 2, player1->armour);
|
||||
draw_textf("%d", 690, 827, 2, player1->ammo[player1->gunselect]);
|
||||
glPopMatrix();
|
||||
glPushMatrix();
|
||||
glOrtho(0, VIRTW, VIRTH, 0, -1, 1);
|
||||
glDisable(GL_BLEND);
|
||||
drawicon(128, 128, 20, 1650);
|
||||
if(player1->armour) drawicon((float)(player1->armourtype*64), 0, 620, 1650);
|
||||
int g = player1->gunselect;
|
||||
int r = 64;
|
||||
if(g>2) { g -= 3; r = 128; };
|
||||
drawicon((float)(g*64), (float)r, 1220, 1650);
|
||||
glPopMatrix();
|
||||
};
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
};
|
||||
|
398
src/rendergl.cpp
Normal file
398
src/rendergl.cpp
Normal file
|
@ -0,0 +1,398 @@
|
|||
// rendergl.cpp: core opengl rendering stuff
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
#ifdef DARWIN
|
||||
#define GL_COMBINE_EXT GL_COMBINE_ARB
|
||||
#define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB
|
||||
#define GL_SOURCE0_RBG_EXT GL_SOURCE0_RGB_ARB
|
||||
#define GL_SOURCE1_RBG_EXT GL_SOURCE1_RGB_ARB
|
||||
#define GL_RGB_SCALE_EXT GL_RGB_SCALE_ARB
|
||||
#endif
|
||||
|
||||
extern int curvert;
|
||||
|
||||
bool hasoverbright = false;
|
||||
|
||||
void purgetextures();
|
||||
|
||||
GLUquadricObj *qsphere = NULL;
|
||||
int glmaxtexsize = 256;
|
||||
|
||||
void gl_init(int w, int h)
|
||||
{
|
||||
//#define fogvalues 0.5f, 0.6f, 0.7f, 1.0f
|
||||
|
||||
glViewport(0, 0, w, h);
|
||||
glClearDepth(1.0);
|
||||
glDepthFunc(GL_LESS);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glShadeModel(GL_SMOOTH);
|
||||
|
||||
|
||||
glEnable(GL_FOG);
|
||||
glFogi(GL_FOG_MODE, GL_LINEAR);
|
||||
glFogf(GL_FOG_DENSITY, 0.25);
|
||||
glHint(GL_FOG_HINT, GL_NICEST);
|
||||
|
||||
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
||||
glEnable(GL_POLYGON_OFFSET_LINE);
|
||||
glPolygonOffset(-3.0, -3.0);
|
||||
|
||||
glCullFace(GL_FRONT);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
char *exts = (char *)glGetString(GL_EXTENSIONS);
|
||||
|
||||
if(strstr(exts, "GL_EXT_texture_env_combine")) hasoverbright = true;
|
||||
else conoutf("WARNING: cannot use overbright lighting, using old lighting model!");
|
||||
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glmaxtexsize);
|
||||
|
||||
purgetextures();
|
||||
|
||||
if(!(qsphere = gluNewQuadric())) fatal("glu sphere");
|
||||
gluQuadricDrawStyle(qsphere, GLU_FILL);
|
||||
gluQuadricOrientation(qsphere, GLU_INSIDE);
|
||||
gluQuadricTexture(qsphere, GL_TRUE);
|
||||
glNewList(1, GL_COMPILE);
|
||||
gluSphere(qsphere, 1, 12, 6);
|
||||
glEndList();
|
||||
};
|
||||
|
||||
void cleangl()
|
||||
{
|
||||
if(qsphere) gluDeleteQuadric(qsphere);
|
||||
};
|
||||
|
||||
bool installtex(int tnum, char *texname, int &xs, int &ys, bool clamp)
|
||||
{
|
||||
SDL_Surface *s = IMG_Load(texname);
|
||||
if(!s) { conoutf("couldn't load texture %s", texname); return false; };
|
||||
if(s->format->BitsPerPixel!=24) { conoutf("texture must be 24bpp: %s", texname); return false; };
|
||||
// loopi(s->w*s->h*3) { uchar *p = (uchar *)s->pixels+i; *p = 255-*p; };
|
||||
glBindTexture(GL_TEXTURE_2D, tnum);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); //NEAREST);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
||||
xs = s->w;
|
||||
ys = s->h;
|
||||
while(xs>glmaxtexsize || ys>glmaxtexsize) { xs /= 2; ys /= 2; };
|
||||
void *scaledimg = s->pixels;
|
||||
if(xs!=s->w)
|
||||
{
|
||||
conoutf("warning: quality loss: scaling %s", texname); // for voodoo cards under linux
|
||||
scaledimg = alloc(xs*ys*3);
|
||||
gluScaleImage(GL_RGB, s->w, s->h, GL_UNSIGNED_BYTE, s->pixels, xs, ys, GL_UNSIGNED_BYTE, scaledimg);
|
||||
};
|
||||
if(gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, xs, ys, GL_RGB, GL_UNSIGNED_BYTE, scaledimg)) fatal("could not build mipmaps");
|
||||
if(xs!=s->w) free(scaledimg);
|
||||
SDL_FreeSurface(s);
|
||||
return true;
|
||||
};
|
||||
|
||||
// management of texture slots
|
||||
// each texture slot can have multople texture frames, of which currently only the first is used
|
||||
// additional frames can be used for various shaders
|
||||
|
||||
const int MAXTEX = 1000;
|
||||
int texx[MAXTEX]; // ( loaded texture ) -> ( name, size )
|
||||
int texy[MAXTEX];
|
||||
string texname[MAXTEX];
|
||||
int curtex = 0;
|
||||
const int FIRSTTEX = 1000; // opengl id = loaded id + FIRSTTEX
|
||||
// std 1+, sky 14+, mdls 20+
|
||||
|
||||
const int MAXFRAMES = 2; // increase to allow more complex shader defs
|
||||
int mapping[256][MAXFRAMES]; // ( cube texture, frame ) -> ( opengl id, name )
|
||||
string mapname[256][MAXFRAMES];
|
||||
|
||||
void purgetextures()
|
||||
{
|
||||
loopi(256) loop(j,MAXFRAMES) mapping[i][j] = 0;
|
||||
};
|
||||
|
||||
int curtexnum = 0;
|
||||
|
||||
void texturereset() { curtexnum = 0; };
|
||||
|
||||
void texture(char *aframe, char *name)
|
||||
{
|
||||
int num = curtexnum++, frame = atoi(aframe);
|
||||
if(num<0 || num>=256 || frame<0 || frame>=MAXFRAMES) return;
|
||||
mapping[num][frame] = 1;
|
||||
char *n = mapname[num][frame];
|
||||
strcpy_s(n, name);
|
||||
path(n);
|
||||
};
|
||||
|
||||
COMMAND(texturereset, ARG_NONE);
|
||||
COMMAND(texture, ARG_2STR);
|
||||
|
||||
int lookuptexture(int tex, int &xs, int &ys)
|
||||
{
|
||||
int frame = 0; // other frames?
|
||||
int tid = mapping[tex][frame];
|
||||
|
||||
if(tid>=FIRSTTEX)
|
||||
{
|
||||
xs = texx[tid-FIRSTTEX];
|
||||
ys = texy[tid-FIRSTTEX];
|
||||
return tid;
|
||||
};
|
||||
|
||||
xs = ys = 16;
|
||||
if(!tid) return 1; // crosshair :)
|
||||
|
||||
loopi(curtex) // lazily happens once per "texture" command, basically
|
||||
{
|
||||
if(strcmp(mapname[tex][frame], texname[i])==0)
|
||||
{
|
||||
mapping[tex][frame] = tid = i+FIRSTTEX;
|
||||
xs = texx[i];
|
||||
ys = texy[i];
|
||||
return tid;
|
||||
};
|
||||
};
|
||||
|
||||
if(curtex==MAXTEX) fatal("loaded too many textures");
|
||||
|
||||
int tnum = curtex+FIRSTTEX;
|
||||
strcpy_s(texname[curtex], mapname[tex][frame]);
|
||||
|
||||
sprintf_sd(name)("packages%c%s", PATHDIV, texname[curtex]);
|
||||
|
||||
if(installtex(tnum, name, xs, ys))
|
||||
{
|
||||
mapping[tex][frame] = tnum;
|
||||
texx[curtex] = xs;
|
||||
texy[curtex] = ys;
|
||||
curtex++;
|
||||
return tnum;
|
||||
}
|
||||
else
|
||||
{
|
||||
return mapping[tex][frame] = FIRSTTEX; // temp fix
|
||||
};
|
||||
};
|
||||
|
||||
void setupworld()
|
||||
{
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
setarraypointers();
|
||||
|
||||
if(hasoverbright)
|
||||
{
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE);
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT);
|
||||
};
|
||||
};
|
||||
|
||||
int skyoglid;
|
||||
|
||||
struct strip { int tex, start, num; };
|
||||
vector<strip> strips;
|
||||
|
||||
void renderstripssky()
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, skyoglid);
|
||||
loopv(strips) if(strips[i].tex==skyoglid) glDrawArrays(GL_TRIANGLE_STRIP, strips[i].start, strips[i].num);
|
||||
};
|
||||
|
||||
void renderstrips()
|
||||
{
|
||||
int lasttex = -1;
|
||||
loopv(strips) if(strips[i].tex!=skyoglid)
|
||||
{
|
||||
if(strips[i].tex!=lasttex)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, strips[i].tex);
|
||||
lasttex = strips[i].tex;
|
||||
};
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, strips[i].start, strips[i].num);
|
||||
};
|
||||
};
|
||||
|
||||
void overbright(float amount) { if(hasoverbright) glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, amount ); };
|
||||
|
||||
void addstrip(int tex, int start, int n)
|
||||
{
|
||||
strip &s = strips.add();
|
||||
s.tex = tex;
|
||||
s.start = start;
|
||||
s.num = n;
|
||||
};
|
||||
|
||||
VARFP(gamma, 30, 100, 300,
|
||||
{
|
||||
float f = gamma/100.0f;
|
||||
if(SDL_SetGamma(f,f,f)==-1)
|
||||
{
|
||||
conoutf("Could not set gamma (card/driver doesn't support it?)");
|
||||
conoutf("sdl: %s", SDL_GetError());
|
||||
};
|
||||
});
|
||||
|
||||
void transplayer()
|
||||
{
|
||||
glLoadIdentity();
|
||||
|
||||
glRotated(player1->roll,0.0,0.0,1.0);
|
||||
glRotated(player1->pitch,-1.0,0.0,0.0);
|
||||
glRotated(player1->yaw,0.0,1.0,0.0);
|
||||
|
||||
glTranslated(-player1->o.x, (player1->state==CS_DEAD ? player1->eyeheight-0.2f : 0)-player1->o.z, -player1->o.y);
|
||||
};
|
||||
|
||||
VARP(fov, 10, 105, 120);
|
||||
|
||||
int xtraverts;
|
||||
|
||||
VAR(fog, 64, 180, 1024);
|
||||
VAR(fogcolour, 0, 0x8099B3, 0xFFFFFF);
|
||||
|
||||
VARP(hudgun,0,1,1);
|
||||
|
||||
char *hudgunnames[] = { "hudguns/fist", "hudguns/shotg", "hudguns/chaing", "hudguns/rocket", "hudguns/rifle" };
|
||||
|
||||
void drawhudmodel(int start, int end, float speed, int base)
|
||||
{
|
||||
rendermodel(hudgunnames[player1->gunselect], start, end, 0, 1.0f, player1->o.x, player1->o.z, player1->o.y, player1->yaw+90, player1->pitch, false, 1.0f, speed, 0, base);
|
||||
};
|
||||
|
||||
void drawhudgun(float fovy, float aspect, int farplane)
|
||||
{
|
||||
if(!hudgun /*|| !player1->gunselect*/) return;
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluPerspective(fovy, aspect, 0.3f, farplane);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
|
||||
//glClear(GL_DEPTH_BUFFER_BIT);
|
||||
int rtime = reloadtime(player1->gunselect);
|
||||
if(player1->lastaction && player1->lastattackgun==player1->gunselect && lastmillis-player1->lastaction<rtime)
|
||||
{
|
||||
drawhudmodel(7, 18, rtime/18.0f, player1->lastaction);
|
||||
}
|
||||
else
|
||||
{
|
||||
drawhudmodel(6, 1, 100, 0);
|
||||
};
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluPerspective(fovy, aspect, 0.15f, farplane);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
|
||||
glDisable(GL_CULL_FACE);
|
||||
};
|
||||
|
||||
void gl_drawframe(int w, int h, float curfps)
|
||||
{
|
||||
float hf = hdr.waterlevel-0.3f;
|
||||
float fovy = (float)fov*h/w;
|
||||
float aspect = w/(float)h;
|
||||
bool underwater = player1->o.z<hf;
|
||||
|
||||
glFogi(GL_FOG_START, (fog+64)/8);
|
||||
glFogi(GL_FOG_END, fog);
|
||||
float fogc[4] = { (fogcolour>>16)/256.0f, ((fogcolour>>8)&255)/256.0f, (fogcolour&255)/256.0f, 1.0f };
|
||||
glFogfv(GL_FOG_COLOR, fogc);
|
||||
glClearColor(fogc[0], fogc[1], fogc[2], 1.0f);
|
||||
|
||||
if(underwater)
|
||||
{
|
||||
fovy += (float)sin(lastmillis/1000.0)*2.0f;
|
||||
aspect += (float)sin(lastmillis/1000.0+PI)*0.1f;
|
||||
glFogi(GL_FOG_START, 0);
|
||||
glFogi(GL_FOG_END, (fog+96)/8);
|
||||
};
|
||||
|
||||
glClear((player1->outsidemap ? GL_COLOR_BUFFER_BIT : 0) | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
int farplane = fog*5/2;
|
||||
gluPerspective(fovy, aspect, 0.15f, farplane);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
|
||||
transplayer();
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
int xs, ys;
|
||||
skyoglid = lookuptexture(DEFAULT_SKY, xs, ys);
|
||||
|
||||
resetcubes();
|
||||
|
||||
curvert = 0;
|
||||
strips.setsize(0);
|
||||
|
||||
render_world(player1->o.x, player1->o.y, player1->o.z,
|
||||
(int)player1->yaw, (int)player1->pitch, (float)fov, w, h);
|
||||
finishstrips();
|
||||
|
||||
setupworld();
|
||||
|
||||
renderstripssky();
|
||||
|
||||
glLoadIdentity();
|
||||
glRotated(player1->pitch, -1.0, 0.0, 0.0);
|
||||
glRotated(player1->yaw, 0.0, 1.0, 0.0);
|
||||
glRotated(90.0, 1.0, 0.0, 0.0);
|
||||
glColor3f(1.0f, 1.0f, 1.0f);
|
||||
glDisable(GL_FOG);
|
||||
glDepthFunc(GL_GREATER);
|
||||
draw_envbox(14, fog*4/3);
|
||||
glDepthFunc(GL_LESS);
|
||||
glEnable(GL_FOG);
|
||||
|
||||
transplayer();
|
||||
|
||||
overbright(2);
|
||||
|
||||
renderstrips();
|
||||
|
||||
xtraverts = 0;
|
||||
|
||||
renderclients();
|
||||
monsterrender();
|
||||
|
||||
renderentities();
|
||||
|
||||
renderspheres(curtime);
|
||||
renderents();
|
||||
|
||||
glDisable(GL_CULL_FACE);
|
||||
|
||||
drawhudgun(fovy, aspect, farplane);
|
||||
|
||||
overbright(1);
|
||||
int nquads = renderwater(hf);
|
||||
|
||||
overbright(2);
|
||||
render_particles(curtime);
|
||||
overbright(1);
|
||||
|
||||
glDisable(GL_FOG);
|
||||
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
|
||||
gl_drawhud(w, h, (int)curfps, nquads, curvert, underwater);
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glEnable(GL_FOG);
|
||||
};
|
||||
|
275
src/rendermd2.cpp
Normal file
275
src/rendermd2.cpp
Normal file
|
@ -0,0 +1,275 @@
|
|||
// rendermd2.cpp: loader code adapted from a nehe tutorial
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
struct md2_header
|
||||
{
|
||||
int magic;
|
||||
int version;
|
||||
int skinWidth, skinHeight;
|
||||
int frameSize;
|
||||
int numSkins, numVertices, numTexcoords;
|
||||
int numTriangles, numGlCommands, numFrames;
|
||||
int offsetSkins, offsetTexcoords, offsetTriangles;
|
||||
int offsetFrames, offsetGlCommands, offsetEnd;
|
||||
};
|
||||
|
||||
struct md2_vertex
|
||||
{
|
||||
uchar vertex[3], lightNormalIndex;
|
||||
};
|
||||
|
||||
struct md2_frame
|
||||
{
|
||||
float scale[3];
|
||||
float translate[3];
|
||||
char name[16];
|
||||
md2_vertex vertices[1];
|
||||
};
|
||||
|
||||
struct md2
|
||||
{
|
||||
int numGlCommands;
|
||||
int* glCommands;
|
||||
int numTriangles;
|
||||
int frameSize;
|
||||
int numFrames;
|
||||
int numVerts;
|
||||
char* frames;
|
||||
vec **mverts;
|
||||
int displaylist;
|
||||
int displaylistverts;
|
||||
|
||||
mapmodelinfo mmi;
|
||||
char *loadname;
|
||||
int mdlnum;
|
||||
bool loaded;
|
||||
|
||||
bool load(char* filename);
|
||||
void render(vec &light, int numFrame, int range, float x, float y, float z, float yaw, float pitch, float scale, float speed, int snap, int basetime);
|
||||
void scale(int frame, float scale, int sn);
|
||||
|
||||
md2() : numGlCommands(0), frameSize(0), numFrames(0), displaylist(0), loaded(false) {};
|
||||
|
||||
~md2()
|
||||
{
|
||||
if(glCommands)
|
||||
delete [] glCommands;
|
||||
if(frames)
|
||||
delete [] frames;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
bool md2::load(char* filename)
|
||||
{
|
||||
FILE* file;
|
||||
md2_header header;
|
||||
|
||||
if((file= fopen(filename, "rb"))==NULL) return false;
|
||||
|
||||
fread(&header, sizeof(md2_header), 1, file);
|
||||
endianswap(&header, sizeof(int), sizeof(md2_header)/sizeof(int));
|
||||
|
||||
if(header.magic!= 844121161 || header.version!=8) return false;
|
||||
|
||||
frames = new char[header.frameSize*header.numFrames];
|
||||
if(frames==NULL) return false;
|
||||
|
||||
fseek(file, header.offsetFrames, SEEK_SET);
|
||||
fread(frames, header.frameSize*header.numFrames, 1, file);
|
||||
|
||||
for(int i = 0; i < header.numFrames; ++i)
|
||||
{
|
||||
endianswap(frames + i * header.frameSize, sizeof(float), 6);
|
||||
}
|
||||
|
||||
glCommands = new int[header.numGlCommands];
|
||||
if(glCommands==NULL) return false;
|
||||
|
||||
fseek(file, header.offsetGlCommands, SEEK_SET);
|
||||
fread(glCommands, header.numGlCommands*sizeof(int), 1, file);
|
||||
|
||||
endianswap(glCommands, sizeof(int), header.numGlCommands);
|
||||
|
||||
numFrames = header.numFrames;
|
||||
numGlCommands= header.numGlCommands;
|
||||
frameSize = header.frameSize;
|
||||
numTriangles = header.numTriangles;
|
||||
numVerts = header.numVertices;
|
||||
|
||||
fclose(file);
|
||||
|
||||
mverts = new vec*[numFrames];
|
||||
loopj(numFrames) mverts[j] = NULL;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
float snap(int sn, float f) { return sn ? (float)(((int)(f+sn*0.5f))&(~(sn-1))) : f; };
|
||||
|
||||
void md2::scale(int frame, float scale, int sn)
|
||||
{
|
||||
mverts[frame] = new vec[numVerts];
|
||||
md2_frame *cf = (md2_frame *) ((char*)frames+frameSize*frame);
|
||||
float sc = 16.0f/scale;
|
||||
loop(vi, numVerts)
|
||||
{
|
||||
uchar *cv = (uchar *)&cf->vertices[vi].vertex;
|
||||
vec *v = &(mverts[frame])[vi];
|
||||
v->x = (snap(sn, cv[0]*cf->scale[0])+cf->translate[0])/sc;
|
||||
v->y = -(snap(sn, cv[1]*cf->scale[1])+cf->translate[1])/sc;
|
||||
v->z = (snap(sn, cv[2]*cf->scale[2])+cf->translate[2])/sc;
|
||||
};
|
||||
};
|
||||
|
||||
void md2::render(vec &light, int frame, int range, float x, float y, float z, float yaw, float pitch, float sc, float speed, int snap, int basetime)
|
||||
{
|
||||
loopi(range) if(!mverts[frame+i]) scale(frame+i, sc, snap);
|
||||
|
||||
glPushMatrix ();
|
||||
glTranslatef(x, y, z);
|
||||
glRotatef(yaw+180, 0, -1, 0);
|
||||
glRotatef(pitch, 0, 0, 1);
|
||||
|
||||
glColor3fv((float *)&light);
|
||||
|
||||
if(displaylist && frame==0 && range==1)
|
||||
{
|
||||
glCallList(displaylist);
|
||||
xtraverts += displaylistverts;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(frame==0 && range==1)
|
||||
{
|
||||
static int displaylistn = 10;
|
||||
glNewList(displaylist = displaylistn++, GL_COMPILE);
|
||||
displaylistverts = xtraverts;
|
||||
};
|
||||
|
||||
int time = lastmillis-basetime;
|
||||
int fr1 = (int)(time/speed);
|
||||
float frac1 = (time-fr1*speed)/speed;
|
||||
float frac2 = 1-frac1;
|
||||
fr1 = fr1%range+frame;
|
||||
int fr2 = fr1+1;
|
||||
if(fr2>=frame+range) fr2 = frame;
|
||||
vec *verts1 = mverts[fr1];
|
||||
vec *verts2 = mverts[fr2];
|
||||
|
||||
for(int *command = glCommands; (*command)!=0;)
|
||||
{
|
||||
int numVertex = *command++;
|
||||
if(numVertex>0) { glBegin(GL_TRIANGLE_STRIP); }
|
||||
else { glBegin(GL_TRIANGLE_FAN); numVertex = -numVertex; };
|
||||
|
||||
loopi(numVertex)
|
||||
{
|
||||
float tu = *((float*)command++);
|
||||
float tv = *((float*)command++);
|
||||
glTexCoord2f(tu, tv);
|
||||
int vn = *command++;
|
||||
vec &v1 = verts1[vn];
|
||||
vec &v2 = verts2[vn];
|
||||
#define ip(c) v1.c*frac2+v2.c*frac1
|
||||
glVertex3f(ip(x), ip(z), ip(y));
|
||||
};
|
||||
|
||||
xtraverts += numVertex;
|
||||
|
||||
glEnd();
|
||||
};
|
||||
|
||||
if(displaylist)
|
||||
{
|
||||
glEndList();
|
||||
displaylistverts = xtraverts-displaylistverts;
|
||||
};
|
||||
};
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
hashtable<md2 *> *mdllookup = NULL;
|
||||
vector<md2 *> mapmodels;
|
||||
const int FIRSTMDL = 20;
|
||||
|
||||
void delayedload(md2 *m)
|
||||
{
|
||||
if(!m->loaded)
|
||||
{
|
||||
sprintf_sd(name1)("packages/models/%s/tris.md2", m->loadname);
|
||||
if(!m->load(path(name1))) fatal("loadmodel: ", name1);
|
||||
sprintf_sd(name2)("packages/models/%s/skin.jpg", m->loadname);
|
||||
int xs, ys;
|
||||
installtex(FIRSTMDL+m->mdlnum, path(name2), xs, ys);
|
||||
m->loaded = true;
|
||||
};
|
||||
};
|
||||
|
||||
int modelnum = 0;
|
||||
|
||||
md2 *loadmodel(char *name)
|
||||
{
|
||||
if(!mdllookup) mdllookup = new hashtable<md2 *>;
|
||||
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);
|
||||
};
|
142
src/renderparticles.cpp
Normal file
142
src/renderparticles.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
// renderparticles.cpp
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
const int MAXPARTICLES = 10500;
|
||||
const int NUMPARTCUTOFF = 20;
|
||||
struct particle { vec o, d; int fade, type; int millis; particle *next; };
|
||||
particle particles[MAXPARTICLES], *parlist = NULL, *parempty = NULL;
|
||||
bool parinit = false;
|
||||
|
||||
VARP(maxparticles, 100, 2000, MAXPARTICLES-500);
|
||||
|
||||
void newparticle(vec &o, vec &d, int fade, int type)
|
||||
{
|
||||
if(!parinit)
|
||||
{
|
||||
loopi(MAXPARTICLES)
|
||||
{
|
||||
particles[i].next = parempty;
|
||||
parempty = &particles[i];
|
||||
};
|
||||
parinit = true;
|
||||
};
|
||||
if(parempty)
|
||||
{
|
||||
particle *p = parempty;
|
||||
parempty = p->next;
|
||||
p->o = o;
|
||||
p->d = d;
|
||||
p->fade = fade;
|
||||
p->type = type;
|
||||
p->millis = lastmillis;
|
||||
p->next = parlist;
|
||||
parlist = p;
|
||||
};
|
||||
};
|
||||
|
||||
VAR(demotracking, 0, 0, 1);
|
||||
VARP(particlesize, 20, 100, 500);
|
||||
|
||||
vec right, up;
|
||||
|
||||
void setorient(vec &r, vec &u) { right = r; up = u; };
|
||||
|
||||
void render_particles(int time)
|
||||
{
|
||||
if(demoplayback && demotracking)
|
||||
{
|
||||
vec nom = { 0, 0, 0 };
|
||||
newparticle(player1->o, nom, 100000000, 8);
|
||||
};
|
||||
|
||||
glDepthMask(GL_FALSE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA);
|
||||
glDisable(GL_FOG);
|
||||
|
||||
struct parttype { float r, g, b; int gr, tex; float sz; } parttypes[] =
|
||||
{
|
||||
{ 0.7f, 0.6f, 0.3f, 2, 3, 0.06f }, // yellow: sparks
|
||||
{ 0.5f, 0.5f, 0.5f, 20, 7, 0.15f }, // grey: small smoke
|
||||
{ 0.2f, 0.2f, 1.0f, 20, 3, 0.08f }, // blue: edit mode entities
|
||||
{ 1.0f, 0.1f, 0.1f, 1, 7, 0.06f }, // red: blood spats
|
||||
{ 1.0f, 0.8f, 0.8f, 20, 6, 1.2f }, // yellow: fireball1
|
||||
{ 0.5f, 0.5f, 0.5f, 20, 7, 0.6f }, // grey: big smoke
|
||||
{ 1.0f, 1.0f, 1.0f, 20, 8, 1.2f }, // blue: fireball2
|
||||
{ 1.0f, 1.0f, 1.0f, 20, 9, 1.2f }, // green: fireball3
|
||||
{ 1.0f, 0.1f, 0.1f, 0, 7, 0.2f }, // red: demotrack
|
||||
};
|
||||
|
||||
int numrender = 0;
|
||||
|
||||
for(particle *p, **pp = &parlist; p = *pp;)
|
||||
{
|
||||
parttype *pt = &parttypes[p->type];
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, pt->tex);
|
||||
glBegin(GL_QUADS);
|
||||
|
||||
glColor3d(pt->r, pt->g, pt->b);
|
||||
float sz = pt->sz*particlesize/100.0f;
|
||||
// perf varray?
|
||||
glTexCoord2f(0.0, 1.0); glVertex3d(p->o.x+(-right.x+up.x)*sz, p->o.z+(-right.y+up.y)*sz, p->o.y+(-right.z+up.z)*sz);
|
||||
glTexCoord2f(1.0, 1.0); glVertex3d(p->o.x+( right.x+up.x)*sz, p->o.z+( right.y+up.y)*sz, p->o.y+( right.z+up.z)*sz);
|
||||
glTexCoord2f(1.0, 0.0); glVertex3d(p->o.x+( right.x-up.x)*sz, p->o.z+( right.y-up.y)*sz, p->o.y+( right.z-up.z)*sz);
|
||||
glTexCoord2f(0.0, 0.0); glVertex3d(p->o.x+(-right.x-up.x)*sz, p->o.z+(-right.y-up.y)*sz, p->o.y+(-right.z-up.z)*sz);
|
||||
glEnd();
|
||||
xtraverts += 4;
|
||||
|
||||
if(numrender++>maxparticles || (p->fade -= time)<0)
|
||||
{
|
||||
*pp = p->next;
|
||||
p->next = parempty;
|
||||
parempty = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(pt->gr) p->o.z -= ((lastmillis-p->millis)/3.0f)*curtime/(pt->gr*10000);
|
||||
vec a = p->d;
|
||||
vmul(a,time);
|
||||
vdiv(a,20000.0f);
|
||||
vadd(p->o, a);
|
||||
pp = &p->next;
|
||||
};
|
||||
};
|
||||
|
||||
glEnable(GL_FOG);
|
||||
glDisable(GL_BLEND);
|
||||
glDepthMask(GL_TRUE);
|
||||
};
|
||||
|
||||
void particle_splash(int type, int num, int fade, vec &p)
|
||||
{
|
||||
loopi(num)
|
||||
{
|
||||
const int radius = type==5 ? 50 : 150;
|
||||
int x, y, z;
|
||||
do
|
||||
{
|
||||
x = rnd(radius*2)-radius;
|
||||
y = rnd(radius*2)-radius;
|
||||
z = rnd(radius*2)-radius;
|
||||
}
|
||||
while(x*x+y*y+z*z>radius*radius);
|
||||
vec d = { (float)x, (float)y, (float)z };
|
||||
newparticle(p, d, rnd(fade*3), type);
|
||||
};
|
||||
};
|
||||
|
||||
void particle_trail(int type, int fade, vec &s, vec &e)
|
||||
{
|
||||
vdist(d, v, s, e);
|
||||
vdiv(v, d*2+0.1f);
|
||||
vec p = s;
|
||||
loopi((int)d*2)
|
||||
{
|
||||
vadd(p, v);
|
||||
vec d = { float(rnd(11)-5), float(rnd(11)-5), float(rnd(11)-5) };
|
||||
newparticle(p, d, rnd(fade)+fade, type);
|
||||
};
|
||||
};
|
||||
|
222
src/rendertext.cpp
Normal file
222
src/rendertext.cpp
Normal file
|
@ -0,0 +1,222 @@
|
|||
// rendertext.cpp: based on Don's gl_text.cpp
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
short char_coords[96][4] =
|
||||
{
|
||||
{0,0,25,64}, //!
|
||||
{25,0,54,64}, //"
|
||||
{54,0,107,64}, //#
|
||||
{107,0,148,64}, //$
|
||||
{148,0,217,64}, //%
|
||||
{217,0,263,64}, //&
|
||||
{263,0,280,64}, //'
|
||||
{280,0,309,64}, //(
|
||||
{309,0,338,64}, //)
|
||||
{338,0,379,64}, //*
|
||||
{379,0,432,64}, //+
|
||||
{432,0,455,64}, //,
|
||||
{455,0,484,64}, //-
|
||||
{0,64,21,128}, //.
|
||||
{23,64,52,128}, ///
|
||||
{52,64,93,128}, //0
|
||||
{93,64,133,128}, //1
|
||||
{133,64,174,128}, //2
|
||||
{174,64,215,128}, //3
|
||||
{215,64,256,128}, //4
|
||||
{256,64,296,128}, //5
|
||||
{296,64,337,128}, //6
|
||||
{337,64,378,128}, //7
|
||||
{378,64,419,128}, //8
|
||||
{419,64,459,128}, //9
|
||||
{459,64,488,128}, //:
|
||||
{0,128,29,192}, //;
|
||||
{29,128,81,192}, //<
|
||||
{81,128,134,192}, //=
|
||||
{134,128,186,192}, //>
|
||||
{186,128,221,192}, //?
|
||||
{221,128,285,192}, //@
|
||||
{285,128,329,192}, //A
|
||||
{329,128,373,192}, //B
|
||||
{373,128,418,192}, //C
|
||||
{418,128,467,192}, //D
|
||||
{0,192,40,256}, //E
|
||||
{40,192,77,256}, //F
|
||||
{77,192,127,256}, //G
|
||||
{127,192,175,256}, //H
|
||||
{175,192,202,256}, //I
|
||||
{202,192,231,256}, //J
|
||||
{231,192,275,256}, //K
|
||||
{275,192,311,256}, //L
|
||||
{311,192,365,256}, //M
|
||||
{365,192,413,256}, //N
|
||||
{413,192,463,256}, //O
|
||||
{1,256,38,320}, //P
|
||||
{38,256,89,320}, //Q
|
||||
{89,256,133,320}, //R
|
||||
{133,256,176,320}, //S
|
||||
{177,256,216,320}, //T
|
||||
{217,256,263,320}, //U
|
||||
{263,256,307,320}, //V
|
||||
{307,256,370,320}, //W
|
||||
{370,256,414,320}, //X
|
||||
{414,256,453,320}, //Y
|
||||
{453,256,497,320}, //Z
|
||||
{0,320,29,384}, //[
|
||||
{29,320,58,384}, //"\"
|
||||
{59,320,87,384}, //]
|
||||
{87,320,139,384}, //^
|
||||
{139,320,180,384}, //_
|
||||
{180,320,221,384}, //`
|
||||
{221,320,259,384}, //a
|
||||
{259,320,299,384}, //b
|
||||
{299,320,332,384}, //c
|
||||
{332,320,372,384}, //d
|
||||
{372,320,411,384}, //e
|
||||
{411,320,433,384}, //f
|
||||
{435,320,473,384}, //g
|
||||
{0,384,40,448}, //h
|
||||
{40,384,56,448}, //i
|
||||
{58,384,80,448}, //j
|
||||
{80,384,118,448}, //k
|
||||
{118,384,135,448}, //l
|
||||
{135,384,197,448}, //m
|
||||
{197,384,238,448}, //n
|
||||
{238,384,277,448}, //o
|
||||
{277,384,317,448}, //p
|
||||
{317,384,356,448}, //q
|
||||
{357,384,384,448}, //r
|
||||
{385,384,417,448}, //s
|
||||
{417,384,442,448}, //t
|
||||
{443,384,483,448}, //u
|
||||
{0,448,38,512}, //v
|
||||
{38,448,90,512}, //w
|
||||
{90,448,128,512}, //x
|
||||
{128,448,166,512}, //y
|
||||
{166,448,200,512}, //z
|
||||
{200,448,241,512}, //{
|
||||
{241,448,270,512}, //|
|
||||
{270,448,310,512}, //}
|
||||
{310,448,363,512}, //~
|
||||
};
|
||||
|
||||
int text_width(char *str)
|
||||
{
|
||||
int x = 0;
|
||||
for (int i = 0; str[i] != 0; i++)
|
||||
{
|
||||
int c = str[i];
|
||||
if(c=='\t') { x = (x+PIXELTAB)/PIXELTAB*PIXELTAB; continue; };
|
||||
if(c=='\f') continue;
|
||||
if(c==' ') { x += FONTH/2; continue; };
|
||||
c -= 33;
|
||||
if(c<0 || c>=95) continue;
|
||||
int in_width = char_coords[c][2] - char_coords[c][0];
|
||||
x += in_width + 1;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
void draw_textf(char *fstr, int left, int top, int gl_num, ...)
|
||||
{
|
||||
sprintf_sdlv(str, gl_num, fstr);
|
||||
draw_text(str, left, top, gl_num);
|
||||
};
|
||||
|
||||
void draw_text(char *str, int left, int top, int gl_num)
|
||||
{
|
||||
glBlendFunc(GL_ONE, GL_ONE);
|
||||
glBindTexture(GL_TEXTURE_2D, gl_num);
|
||||
glColor3ub(255,255,255);
|
||||
|
||||
int x = left;
|
||||
int y = top;
|
||||
|
||||
int i;
|
||||
float in_left, in_top, in_right, in_bottom;
|
||||
int in_width, in_height;
|
||||
|
||||
for (i = 0; str[i] != 0; i++)
|
||||
{
|
||||
int c = str[i];
|
||||
if(c=='\t') { x = (x-left+PIXELTAB)/PIXELTAB*PIXELTAB+left; continue; };
|
||||
if(c=='\f') { glColor3ub(64,255,128); continue; };
|
||||
if(c==' ') { x += FONTH/2; continue; };
|
||||
c -= 33;
|
||||
if(c<0 || c>=95) continue;
|
||||
|
||||
in_left = ((float) char_coords[c][0]) / 512.0f;
|
||||
in_top = ((float) char_coords[c][1]+2) / 512.0f;
|
||||
in_right = ((float) char_coords[c][2]) / 512.0f;
|
||||
in_bottom = ((float) char_coords[c][3]-2) / 512.0f;
|
||||
|
||||
in_width = char_coords[c][2] - char_coords[c][0];
|
||||
in_height = char_coords[c][3] - char_coords[c][1];
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(in_left, in_top ); glVertex2i(x, y);
|
||||
glTexCoord2f(in_right, in_top ); glVertex2i(x + in_width, y);
|
||||
glTexCoord2f(in_right, in_bottom); glVertex2i(x + in_width, y + in_height);
|
||||
glTexCoord2f(in_left, in_bottom); glVertex2i(x, y + in_height);
|
||||
glEnd();
|
||||
|
||||
xtraverts += 4;
|
||||
x += in_width + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// also Don's code, so goes in here too :)
|
||||
|
||||
void draw_envbox_aux(float s0, float t0, int x0, int y0, int z0,
|
||||
float s1, float t1, int x1, int y1, int z1,
|
||||
float s2, float t2, int x2, int y2, int z2,
|
||||
float s3, float t3, int x3, int y3, int z3,
|
||||
int texture)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(s3, t3); glVertex3d(x3, y3, z3);
|
||||
glTexCoord2f(s2, t2); glVertex3d(x2, y2, z2);
|
||||
glTexCoord2f(s1, t1); glVertex3d(x1, y1, z1);
|
||||
glTexCoord2f(s0, t0); glVertex3d(x0, y0, z0);
|
||||
glEnd();
|
||||
xtraverts += 4;
|
||||
}
|
||||
|
||||
void draw_envbox(int t, int w)
|
||||
{
|
||||
glDepthMask(GL_FALSE);
|
||||
|
||||
draw_envbox_aux(1.0f, 1.0f, -w, -w, w,
|
||||
0.0f, 1.0f, w, -w, w,
|
||||
0.0f, 0.0f, w, -w, -w,
|
||||
1.0f, 0.0f, -w, -w, -w, t);
|
||||
|
||||
draw_envbox_aux(1.0f, 1.0f, +w, w, w,
|
||||
0.0f, 1.0f, -w, w, w,
|
||||
0.0f, 0.0f, -w, w, -w,
|
||||
1.0f, 0.0f, +w, w, -w, t+1);
|
||||
|
||||
draw_envbox_aux(0.0f, 0.0f, -w, -w, -w,
|
||||
1.0f, 0.0f, -w, w, -w,
|
||||
1.0f, 1.0f, -w, w, w,
|
||||
0.0f, 1.0f, -w, -w, w, t+2);
|
||||
|
||||
draw_envbox_aux(1.0f, 1.0f, +w, -w, w,
|
||||
0.0f, 1.0f, +w, w, w,
|
||||
0.0f, 0.0f, +w, w, -w,
|
||||
1.0f, 0.0f, +w, -w, -w, t+3);
|
||||
|
||||
draw_envbox_aux(0.0f, 1.0f, -w, w, w,
|
||||
0.0f, 0.0f, +w, w, w,
|
||||
1.0f, 0.0f, +w, -w, w,
|
||||
1.0f, 1.0f, -w, -w, w, t+4);
|
||||
|
||||
draw_envbox_aux(0.0f, 1.0f, +w, w, -w,
|
||||
0.0f, 0.0f, -w, w, -w,
|
||||
1.0f, 0.0f, -w, -w, -w,
|
||||
1.0f, 1.0f, +w, -w, -w, t+5);
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
70
src/rndmap.cpp
Normal file
70
src/rndmap.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
// 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;
|
||||
};
|
||||
};
|
||||
|
||||
|
360
src/savegamedemo.cpp
Normal file
360
src/savegamedemo.cpp
Normal file
|
@ -0,0 +1,360 @@
|
|||
// loading and saving of savegames & demos, dumps the spawn state of all mapents, the full state of all dynents (monsters + player)
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
extern int islittleendian;
|
||||
|
||||
gzFile f = NULL;
|
||||
bool demorecording = false;
|
||||
bool demoplayback = false;
|
||||
bool demoloading = false;
|
||||
dvector playerhistory;
|
||||
int democlientnum = 0;
|
||||
|
||||
void startdemo();
|
||||
|
||||
void gzput(int i) { gzputc(f, i); };
|
||||
void gzputi(int i) { gzwrite(f, &i, sizeof(int)); };
|
||||
void gzputv(vec &v) { gzwrite(f, &v, sizeof(vec)); };
|
||||
|
||||
void gzcheck(int a, int b) { if(a!=b) fatal("savegame file corrupt (short)"); };
|
||||
int gzget() { char c = gzgetc(f); return c; };
|
||||
int gzgeti() { int i; gzcheck(gzread(f, &i, sizeof(int)), sizeof(int)); return i; };
|
||||
void gzgetv(vec &v) { gzcheck(gzread(f, &v, sizeof(vec)), sizeof(vec)); };
|
||||
|
||||
void stop()
|
||||
{
|
||||
if(f)
|
||||
{
|
||||
if(demorecording) gzputi(-1);
|
||||
gzclose(f);
|
||||
};
|
||||
f = NULL;
|
||||
demorecording = false;
|
||||
demoplayback = false;
|
||||
demoloading = false;
|
||||
loopv(playerhistory) zapdynent(playerhistory[i]);
|
||||
playerhistory.setsize(0);
|
||||
};
|
||||
|
||||
void stopifrecording() { if(demorecording) stop(); };
|
||||
|
||||
void savestate(char *fn)
|
||||
{
|
||||
stop();
|
||||
f = gzopen(fn, "wb9");
|
||||
if(!f) { conoutf("could not write %s", fn); return; };
|
||||
gzwrite(f, (void *)"CUBESAVE", 8);
|
||||
gzputc(f, islittleendian);
|
||||
gzputi(SAVEGAMEVERSION);
|
||||
gzputi(sizeof(dynent));
|
||||
gzwrite(f, getclientmap(), _MAXDEFSTR);
|
||||
gzputi(gamemode);
|
||||
gzputi(ents.length());
|
||||
loopv(ents) gzputc(f, ents[i].spawned);
|
||||
gzwrite(f, player1, sizeof(dynent));
|
||||
dvector &monsters = getmonsters();
|
||||
gzputi(monsters.length());
|
||||
loopv(monsters) gzwrite(f, monsters[i], sizeof(dynent));
|
||||
gzputi(players.length());
|
||||
loopv(players)
|
||||
{
|
||||
gzput(players[i]==NULL);
|
||||
gzwrite(f, players[i], sizeof(dynent));
|
||||
};
|
||||
};
|
||||
|
||||
void savegame(char *name)
|
||||
{
|
||||
if(!m_classicsp) { conoutf("can only save classic sp games"); return; };
|
||||
sprintf_sd(fn)("savegames/%s.csgz", name);
|
||||
savestate(fn);
|
||||
stop();
|
||||
conoutf("wrote %s", fn);
|
||||
};
|
||||
|
||||
void loadstate(char *fn)
|
||||
{
|
||||
stop();
|
||||
if(multiplayer()) return;
|
||||
f = gzopen(fn, "rb9");
|
||||
if(!f) { conoutf("could not open %s", fn); return; };
|
||||
|
||||
string buf;
|
||||
gzread(f, buf, 8);
|
||||
if(strncmp(buf, "CUBESAVE", 8)) goto out;
|
||||
if(gzgetc(f)!=islittleendian) goto out; // not supporting save->load accross incompatible architectures simpifies things a LOT
|
||||
if(gzgeti()!=SAVEGAMEVERSION || gzgeti()!=sizeof(dynent)) goto out;
|
||||
string mapname;
|
||||
gzread(f, mapname, _MAXDEFSTR);
|
||||
nextmode = gzgeti();
|
||||
changemap(mapname); // continue below once map has been loaded and client & server have updated
|
||||
return;
|
||||
out:
|
||||
conoutf("aborting: savegame/demo from a different version of cube or cpu architecture");
|
||||
stop();
|
||||
};
|
||||
|
||||
void loadgame(char *name)
|
||||
{
|
||||
sprintf_sd(fn)("savegames/%s.csgz", name);
|
||||
loadstate(fn);
|
||||
};
|
||||
|
||||
void loadgameout()
|
||||
{
|
||||
stop();
|
||||
conoutf("loadgame incomplete: savegame from a different version of this map");
|
||||
};
|
||||
|
||||
void loadgamerest()
|
||||
{
|
||||
if(demoplayback || !f) return;
|
||||
|
||||
if(gzgeti()!=ents.length()) return loadgameout();
|
||||
loopv(ents)
|
||||
{
|
||||
ents[i].spawned = gzgetc(f)!=0;
|
||||
if(ents[i].type==CARROT && !ents[i].spawned) trigger(ents[i].attr1, ents[i].attr2, true);
|
||||
};
|
||||
restoreserverstate(ents);
|
||||
|
||||
gzread(f, player1, sizeof(dynent));
|
||||
player1->lastaction = lastmillis;
|
||||
|
||||
int nmonsters = gzgeti();
|
||||
dvector &monsters = getmonsters();
|
||||
if(nmonsters!=monsters.length()) return loadgameout();
|
||||
loopv(monsters)
|
||||
{
|
||||
gzread(f, monsters[i], sizeof(dynent));
|
||||
monsters[i]->enemy = player1; // lazy, could save id of enemy instead
|
||||
monsters[i]->lastaction = monsters[i]->trigger = lastmillis+500; // also lazy, but no real noticable effect on game
|
||||
if(monsters[i]->state==CS_DEAD) monsters[i]->lastaction = 0;
|
||||
};
|
||||
restoremonsterstate();
|
||||
|
||||
int nplayers = gzgeti();
|
||||
loopi(nplayers) if(!gzget())
|
||||
{
|
||||
dynent *d = getclient(i);
|
||||
assert(d);
|
||||
gzread(f, d, sizeof(dynent));
|
||||
};
|
||||
|
||||
conoutf("savegame restored");
|
||||
if(demoloading) startdemo(); else stop();
|
||||
};
|
||||
|
||||
// demo functions
|
||||
|
||||
int starttime = 0;
|
||||
int playbacktime = 0;
|
||||
int ddamage, bdamage;
|
||||
vec dorig;
|
||||
|
||||
void record(char *name)
|
||||
{
|
||||
if(m_sp) { conoutf("cannot record singleplayer games"); return; };
|
||||
int cn = getclientnum();
|
||||
if(cn<0) return;
|
||||
sprintf_sd(fn)("demos/%s.cdgz", name);
|
||||
savestate(fn);
|
||||
gzputi(cn);
|
||||
conoutf("started recording demo to %s", fn);
|
||||
demorecording = true;
|
||||
starttime = lastmillis;
|
||||
ddamage = bdamage = 0;
|
||||
};
|
||||
|
||||
void demodamage(int damage, vec &o) { ddamage = damage; dorig = o; };
|
||||
void demoblend(int damage) { bdamage = damage; };
|
||||
|
||||
void incomingdemodata(uchar *buf, int len, bool extras)
|
||||
{
|
||||
if(!demorecording) return;
|
||||
gzputi(lastmillis-starttime);
|
||||
gzputi(len);
|
||||
gzwrite(f, buf, len);
|
||||
gzput(extras);
|
||||
if(extras)
|
||||
{
|
||||
gzput(player1->gunselect);
|
||||
gzput(player1->lastattackgun);
|
||||
gzputi(player1->lastaction-starttime);
|
||||
gzputi(player1->gunwait);
|
||||
gzputi(player1->health);
|
||||
gzputi(player1->armour);
|
||||
gzput(player1->armourtype);
|
||||
loopi(NUMGUNS) gzput(player1->ammo[i]);
|
||||
gzput(player1->state);
|
||||
gzputi(bdamage);
|
||||
bdamage = 0;
|
||||
gzputi(ddamage);
|
||||
if(ddamage) { gzputv(dorig); ddamage = 0; };
|
||||
// FIXME: add all other client state which is not send through the network
|
||||
};
|
||||
};
|
||||
|
||||
void demo(char *name)
|
||||
{
|
||||
sprintf_sd(fn)("demos/%s.cdgz", name);
|
||||
loadstate(fn);
|
||||
demoloading = true;
|
||||
};
|
||||
|
||||
void stopreset()
|
||||
{
|
||||
conoutf("demo stopped (%d msec elapsed)", lastmillis-starttime);
|
||||
stop();
|
||||
loopv(players) zapdynent(players[i]);
|
||||
disconnect(0, 0);
|
||||
};
|
||||
|
||||
VAR(demoplaybackspeed, 10, 100, 1000);
|
||||
int scaletime(int t) { return (int)(t*(100.0f/demoplaybackspeed))+starttime; };
|
||||
|
||||
void readdemotime()
|
||||
{
|
||||
if(gzeof(f) || (playbacktime = gzgeti())==-1)
|
||||
{
|
||||
stopreset();
|
||||
return;
|
||||
};
|
||||
playbacktime = scaletime(playbacktime);
|
||||
};
|
||||
|
||||
void startdemo()
|
||||
{
|
||||
democlientnum = gzgeti();
|
||||
demoplayback = true;
|
||||
starttime = lastmillis;
|
||||
conoutf("now playing demo");
|
||||
dynent *d = getclient(democlientnum);
|
||||
assert(d);
|
||||
*d = *player1;
|
||||
readdemotime();
|
||||
};
|
||||
|
||||
VAR(demodelaymsec, 0, 120, 500);
|
||||
|
||||
void catmulrom(vec &z, vec &a, vec &b, vec &c, float s, vec &dest) // spline interpolation
|
||||
{
|
||||
vec t1 = b, t2 = c;
|
||||
|
||||
vsub(t1, z); vmul(t1, 0.5f)
|
||||
vsub(t2, a); vmul(t2, 0.5f);
|
||||
|
||||
float s2 = s*s;
|
||||
float s3 = s*s2;
|
||||
|
||||
dest = a;
|
||||
vec t = b;
|
||||
|
||||
vmul(dest, 2*s3 - 3*s2 + 1);
|
||||
vmul(t, -2*s3 + 3*s2); vadd(dest, t);
|
||||
vmul(t1, s3 - 2*s2 + s); vadd(dest, t1);
|
||||
vmul(t2, s3 - s2); vadd(dest, t2);
|
||||
};
|
||||
|
||||
void fixwrap(dynent *a, dynent *b)
|
||||
{
|
||||
while(b->yaw-a->yaw>180) a->yaw += 360;
|
||||
while(b->yaw-a->yaw<-180) a->yaw -= 360;
|
||||
};
|
||||
|
||||
void demoplaybackstep()
|
||||
{
|
||||
while(demoplayback && lastmillis>=playbacktime)
|
||||
{
|
||||
int len = gzgeti();
|
||||
if(len<1 || len>MAXTRANS)
|
||||
{
|
||||
conoutf("error: huge packet during demo play (%d)", len);
|
||||
stopreset();
|
||||
return;
|
||||
};
|
||||
uchar buf[MAXTRANS];
|
||||
gzread(f, buf, len);
|
||||
localservertoclient(buf, len); // update game state
|
||||
|
||||
dynent *target = players[democlientnum];
|
||||
assert(target);
|
||||
|
||||
int extras;
|
||||
if(extras = gzget()) // read additional client side state not present in normal network stream
|
||||
{
|
||||
target->gunselect = gzget();
|
||||
target->lastattackgun = gzget();
|
||||
target->lastaction = scaletime(gzgeti());
|
||||
target->gunwait = gzgeti();
|
||||
target->health = gzgeti();
|
||||
target->armour = gzgeti();
|
||||
target->armourtype = gzget();
|
||||
loopi(NUMGUNS) target->ammo[i] = gzget();
|
||||
target->state = gzget();
|
||||
target->lastmove = playbacktime;
|
||||
if(bdamage = gzgeti()) damageblend(bdamage);
|
||||
if(ddamage = gzgeti()) { gzgetv(dorig); particle_splash(3, ddamage, 1000, dorig); };
|
||||
// FIXME: set more client state here
|
||||
};
|
||||
|
||||
// insert latest copy of player into history
|
||||
if(extras && (playerhistory.empty() || playerhistory.last()->lastupdate!=playbacktime))
|
||||
{
|
||||
dynent *d = newdynent();
|
||||
*d = *target;
|
||||
d->lastupdate = playbacktime;
|
||||
playerhistory.add(d);
|
||||
if(playerhistory.length()>20)
|
||||
{
|
||||
zapdynent(playerhistory[0]);
|
||||
playerhistory.remove(0);
|
||||
};
|
||||
};
|
||||
|
||||
readdemotime();
|
||||
};
|
||||
|
||||
if(demoplayback)
|
||||
{
|
||||
int itime = lastmillis-demodelaymsec;
|
||||
loopvrev(playerhistory) if(playerhistory[i]->lastupdate<itime) // find 2 positions in history that surround interpolation time point
|
||||
{
|
||||
dynent *a = playerhistory[i];
|
||||
dynent *b = a;
|
||||
if(i+1<playerhistory.length()) b = playerhistory[i+1];
|
||||
*player1 = *b;
|
||||
if(a!=b) // interpolate pos & angles
|
||||
{
|
||||
dynent *c = b;
|
||||
if(i+2<playerhistory.length()) c = playerhistory[i+2];
|
||||
dynent *z = a;
|
||||
if(i-1>=0) z = playerhistory[i-1];
|
||||
//if(a==z || b==c) printf("* %d\n", lastmillis);
|
||||
float bf = (itime-a->lastupdate)/(float)(b->lastupdate-a->lastupdate);
|
||||
fixwrap(a, player1);
|
||||
fixwrap(c, player1);
|
||||
fixwrap(z, player1);
|
||||
vdist(dist, v, z->o, c->o);
|
||||
if(dist<16) // if teleport or spawn, dont't interpolate
|
||||
{
|
||||
catmulrom(z->o, a->o, b->o, c->o, bf, player1->o);
|
||||
catmulrom(*(vec *)&z->yaw, *(vec *)&a->yaw, *(vec *)&b->yaw, *(vec *)&c->yaw, bf, *(vec *)&player1->yaw);
|
||||
};
|
||||
fixplayer1range();
|
||||
};
|
||||
break;
|
||||
};
|
||||
//if(player1->state!=CS_DEAD) showscores(false);
|
||||
};
|
||||
};
|
||||
|
||||
void stopn() { if(demoplayback) stopreset(); else stop(); conoutf("demo stopped"); };
|
||||
|
||||
COMMAND(record, ARG_1STR);
|
||||
COMMAND(demo, ARG_1STR);
|
||||
COMMANDN(stop, stopn, ARG_NONE);
|
||||
|
||||
COMMAND(savegame, ARG_1STR);
|
||||
COMMAND(loadgame, ARG_1STR);
|
468
src/server.cpp
Normal file
468
src/server.cpp
Normal file
|
@ -0,0 +1,468 @@
|
|||
// server.cpp: little more than enhanced multicaster
|
||||
// runs dedicated or as client coroutine
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
enum { ST_EMPTY, ST_LOCAL, ST_TCPIP };
|
||||
|
||||
struct client // server side version of "dynent" type
|
||||
{
|
||||
int type;
|
||||
ENetPeer *peer;
|
||||
string hostname;
|
||||
string mapvote;
|
||||
string name;
|
||||
int modevote;
|
||||
};
|
||||
|
||||
vector<client> clients;
|
||||
|
||||
int maxclients = 8;
|
||||
string smapname;
|
||||
|
||||
struct server_entity // server side version of "entity" type
|
||||
{
|
||||
bool spawned;
|
||||
int spawnsecs;
|
||||
};
|
||||
|
||||
vector<server_entity> sents;
|
||||
|
||||
bool notgotitems = true; // true when map has changed and waiting for clients to send item
|
||||
int mode = 0;
|
||||
|
||||
void restoreserverstate(vector<entity> &ents) // hack: called from savegame code, only works in SP
|
||||
{
|
||||
loopv(sents)
|
||||
{
|
||||
sents[i].spawned = ents[i].spawned;
|
||||
sents[i].spawnsecs = 0;
|
||||
};
|
||||
};
|
||||
|
||||
int interm = 0, minremain = 0, mapend = 0;
|
||||
bool mapreload = false;
|
||||
|
||||
char *serverpassword = "";
|
||||
|
||||
bool isdedicated;
|
||||
ENetHost * serverhost = NULL;
|
||||
int bsend = 0, brec = 0, laststatus = 0, lastsec = 0;
|
||||
|
||||
#define MAXOBUF 100000
|
||||
|
||||
void process(ENetPacket *packet, int sender);
|
||||
void multicast(ENetPacket *packet, int sender);
|
||||
void disconnect_client(int n, char *reason);
|
||||
|
||||
void send(int n, ENetPacket *packet)
|
||||
{
|
||||
if(!packet) return;
|
||||
switch(clients[n].type)
|
||||
{
|
||||
case ST_TCPIP:
|
||||
{
|
||||
enet_peer_send(clients[n].peer, 0, packet);
|
||||
bsend += packet->dataLength;
|
||||
break;
|
||||
};
|
||||
|
||||
case ST_LOCAL:
|
||||
localservertoclient(packet->data, packet->dataLength);
|
||||
break;
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
void send2(bool rel, int cn, int a, int b)
|
||||
{
|
||||
ENetPacket *packet = enet_packet_create(NULL, 32, rel ? ENET_PACKET_FLAG_RELIABLE : 0);
|
||||
uchar *start = packet->data;
|
||||
uchar *p = start+2;
|
||||
putint(p, a);
|
||||
putint(p, b);
|
||||
*(ushort *)start = ENET_HOST_TO_NET_16(p-start);
|
||||
enet_packet_resize(packet, p-start);
|
||||
if(cn<0) process(packet, -1);
|
||||
else send(cn, packet);
|
||||
if(packet->referenceCount==0) enet_packet_destroy(packet);
|
||||
};
|
||||
|
||||
void sendservmsg(char *msg)
|
||||
{
|
||||
ENetPacket *packet = enet_packet_create(NULL, _MAXDEFSTR+10, ENET_PACKET_FLAG_RELIABLE);
|
||||
uchar *start = packet->data;
|
||||
uchar *p = start+2;
|
||||
putint(p, SV_SERVMSG);
|
||||
sendstring(msg, p);
|
||||
*(ushort *)start = ENET_HOST_TO_NET_16(p-start);
|
||||
enet_packet_resize(packet, p-start);
|
||||
multicast(packet, -1);
|
||||
if(packet->referenceCount==0) enet_packet_destroy(packet);
|
||||
};
|
||||
|
||||
void disconnect_client(int n, char *reason)
|
||||
{
|
||||
printf("disconnecting client (%s) [%s]\n", clients[n].hostname, reason);
|
||||
enet_peer_disconnect(clients[n].peer);
|
||||
clients[n].type = ST_EMPTY;
|
||||
send2(true, -1, SV_CDIS, n);
|
||||
};
|
||||
|
||||
void resetitems() { sents.setsize(0); notgotitems = true; };
|
||||
|
||||
void pickup(uint i, int sec, int sender) // server side item pickup, acknowledge first client that gets it
|
||||
{
|
||||
if(i>=(uint)sents.length()) return;
|
||||
if(sents[i].spawned)
|
||||
{
|
||||
sents[i].spawned = false;
|
||||
sents[i].spawnsecs = sec;
|
||||
send2(true, sender, SV_ITEMACC, i);
|
||||
};
|
||||
};
|
||||
|
||||
void resetvotes()
|
||||
{
|
||||
loopv(clients) clients[i].mapvote[0] = 0;
|
||||
};
|
||||
|
||||
bool vote(char *map, int reqmode, int sender)
|
||||
{
|
||||
strcpy_s(clients[sender].mapvote, map);
|
||||
clients[sender].modevote = reqmode;
|
||||
int yes = 0, no = 0;
|
||||
loopv(clients) if(clients[i].type!=ST_EMPTY)
|
||||
{
|
||||
if(clients[i].mapvote[0]) { if(strcmp(clients[i].mapvote, map)==0 && clients[i].modevote==reqmode) yes++; else no++; }
|
||||
else no++;
|
||||
};
|
||||
if(yes==1 && no==0) return true; // single player
|
||||
sprintf_sd(msg)("%s suggests %s on map %s (set map to vote)", clients[sender].name, modestr(reqmode), map);
|
||||
sendservmsg(msg);
|
||||
if(yes/(float)(yes+no) <= 0.5f) return false;
|
||||
sendservmsg("vote passed");
|
||||
resetvotes();
|
||||
return true;
|
||||
};
|
||||
|
||||
// server side processing of updates: does very little and most state is tracked client only
|
||||
// could be extended to move more gameplay to server (at expense of lag)
|
||||
|
||||
void process(ENetPacket * packet, int sender) // sender may be -1
|
||||
{
|
||||
if(ENET_NET_TO_HOST_16(*(ushort *)packet->data)!=packet->dataLength)
|
||||
{
|
||||
disconnect_client(sender, "packet length");
|
||||
return;
|
||||
};
|
||||
|
||||
uchar *end = packet->data+packet->dataLength;
|
||||
uchar *p = packet->data+2;
|
||||
char text[MAXTRANS];
|
||||
int cn = -1, type;
|
||||
|
||||
while(p<end) switch(type = getint(p))
|
||||
{
|
||||
case SV_TEXT:
|
||||
sgetstr();
|
||||
break;
|
||||
|
||||
case SV_INITC2S:
|
||||
sgetstr();
|
||||
strcpy_s(clients[cn].name, text);
|
||||
sgetstr();
|
||||
getint(p);
|
||||
break;
|
||||
|
||||
case SV_MAPCHANGE:
|
||||
{
|
||||
sgetstr();
|
||||
int reqmode = getint(p);
|
||||
if(reqmode<0) reqmode = 0;
|
||||
if(smapname[0] && !mapreload && !vote(text, reqmode, sender)) return;
|
||||
mapreload = false;
|
||||
mode = reqmode;
|
||||
minremain = mode&1 ? 15 : 10;
|
||||
mapend = lastsec+minremain*60;
|
||||
interm = 0;
|
||||
strcpy_s(smapname, text);
|
||||
resetitems();
|
||||
sender = -1;
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_ITEMLIST:
|
||||
{
|
||||
int n;
|
||||
while((n = getint(p))!=-1) if(notgotitems)
|
||||
{
|
||||
server_entity se = { false, 0 };
|
||||
while(sents.length()<=n) sents.add(se);
|
||||
sents[n].spawned = true;
|
||||
};
|
||||
notgotitems = false;
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_ITEMPICKUP:
|
||||
{
|
||||
int n = getint(p);
|
||||
pickup(n, getint(p), sender);
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_PING:
|
||||
send2(false, cn, SV_PONG, getint(p));
|
||||
break;
|
||||
|
||||
case SV_POS:
|
||||
{
|
||||
cn = getint(p);
|
||||
if(cn<0 || cn>=clients.length() || clients[cn].type==ST_EMPTY)
|
||||
{
|
||||
disconnect_client(sender, "client num");
|
||||
return;
|
||||
};
|
||||
int size = msgsizelookup(type);
|
||||
assert(size!=-1);
|
||||
loopi(size-2) getint(p);
|
||||
break;
|
||||
};
|
||||
|
||||
case SV_SENDMAP:
|
||||
{
|
||||
sgetstr();
|
||||
int mapsize = getint(p);
|
||||
sendmaps(sender, text, mapsize, p);
|
||||
return;
|
||||
}
|
||||
|
||||
case SV_RECVMAP:
|
||||
send(sender, recvmap(sender));
|
||||
return;
|
||||
|
||||
case SV_EXT: // allows for new features that require no server updates
|
||||
{
|
||||
for(int n = getint(p); n; n--) getint(p);
|
||||
break;
|
||||
};
|
||||
|
||||
default:
|
||||
{
|
||||
int size = msgsizelookup(type);
|
||||
if(size==-1) { disconnect_client(sender, "tag type"); return; };
|
||||
loopi(size-1) getint(p);
|
||||
};
|
||||
};
|
||||
|
||||
if(p>end) { disconnect_client(sender, "end of packet"); return; };
|
||||
multicast(packet, sender);
|
||||
};
|
||||
|
||||
void send_welcome(int n)
|
||||
{
|
||||
ENetPacket * packet = enet_packet_create (NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
|
||||
uchar *start = packet->data;
|
||||
uchar *p = start+2;
|
||||
putint(p, SV_INITS2C);
|
||||
putint(p, n);
|
||||
putint(p, PROTOCOL_VERSION);
|
||||
putint(p, smapname[0]);
|
||||
sendstring(serverpassword, p);
|
||||
putint(p, clients.length()>maxclients);
|
||||
if(smapname[0])
|
||||
{
|
||||
putint(p, SV_MAPCHANGE);
|
||||
sendstring(smapname, p);
|
||||
putint(p, mode);
|
||||
putint(p, SV_ITEMLIST);
|
||||
loopv(sents) if(sents[i].spawned) putint(p, i);
|
||||
putint(p, -1);
|
||||
};
|
||||
*(ushort *)start = ENET_HOST_TO_NET_16(p-start);
|
||||
enet_packet_resize(packet, p-start);
|
||||
send(n, packet);
|
||||
};
|
||||
|
||||
void multicast(ENetPacket *packet, int sender)
|
||||
{
|
||||
loopv(clients)
|
||||
{
|
||||
if(i==sender) continue;
|
||||
send(i, packet);
|
||||
};
|
||||
};
|
||||
|
||||
void localclienttoserver(ENetPacket *packet)
|
||||
{
|
||||
process(packet, 0);
|
||||
if(!packet->referenceCount) enet_packet_destroy (packet);
|
||||
};
|
||||
|
||||
client &addclient()
|
||||
{
|
||||
loopv(clients) if(clients[i].type==ST_EMPTY) return clients[i];
|
||||
return clients.add();
|
||||
};
|
||||
|
||||
void checkintermission()
|
||||
{
|
||||
if(!minremain)
|
||||
{
|
||||
interm = lastsec+10;
|
||||
mapend = lastsec+1000;
|
||||
};
|
||||
send2(true, -1, SV_TIMEUP, minremain--);
|
||||
};
|
||||
|
||||
void startintermission() { minremain = 0; checkintermission(); };
|
||||
|
||||
void resetserverifempty()
|
||||
{
|
||||
loopv(clients) if(clients[i].type!=ST_EMPTY) return;
|
||||
clients.setsize(0);
|
||||
smapname[0] = 0;
|
||||
resetvotes();
|
||||
resetitems();
|
||||
mode = 0;
|
||||
mapreload = false;
|
||||
minremain = 10;
|
||||
mapend = lastsec+minremain*60;
|
||||
interm = 0;
|
||||
};
|
||||
|
||||
int nonlocalclients = 0;
|
||||
int lastconnect = 0;
|
||||
|
||||
void serverslice(int seconds, unsigned int timeout) // main server update, called from cube main loop in sp, or dedicated server loop
|
||||
{
|
||||
loopv(sents) // spawn entities when timer reached
|
||||
{
|
||||
if(sents[i].spawnsecs && (sents[i].spawnsecs -= seconds-lastsec)<=0)
|
||||
{
|
||||
sents[i].spawnsecs = 0;
|
||||
sents[i].spawned = true;
|
||||
send2(true, -1, SV_ITEMSPAWN, i);
|
||||
};
|
||||
};
|
||||
|
||||
lastsec = seconds;
|
||||
|
||||
if((mode>1 || (mode==0 && nonlocalclients)) && seconds>mapend-minremain*60) checkintermission();
|
||||
if(interm && seconds>interm)
|
||||
{
|
||||
interm = 0;
|
||||
loopv(clients) if(clients[i].type!=ST_EMPTY)
|
||||
{
|
||||
send2(true, i, SV_MAPRELOAD, 0); // ask a client to trigger map reload
|
||||
mapreload = true;
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
resetserverifempty();
|
||||
|
||||
if(!isdedicated) return; // below is network only
|
||||
|
||||
int numplayers = 0;
|
||||
loopv(clients) if(clients[i].type!=ST_EMPTY) ++numplayers;
|
||||
serverms(mode, numplayers, minremain, smapname, seconds, clients.length()>=maxclients);
|
||||
|
||||
if(seconds-laststatus>60) // display bandwidth stats, useful for server ops
|
||||
{
|
||||
nonlocalclients = 0;
|
||||
loopv(clients) if(clients[i].type==ST_TCPIP) nonlocalclients++;
|
||||
laststatus = seconds;
|
||||
if(nonlocalclients || bsend || brec) printf("status: %d remote clients, %.1f send, %.1f rec (K/sec)\n", nonlocalclients, bsend/60.0f/1024, brec/60.0f/1024);
|
||||
bsend = brec = 0;
|
||||
};
|
||||
|
||||
ENetEvent event;
|
||||
if(enet_host_service(serverhost, &event, timeout) > 0)
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
{
|
||||
client &c = addclient();
|
||||
c.type = ST_TCPIP;
|
||||
c.peer = event.peer;
|
||||
c.peer->data = (void *)(&c-&clients[0]);
|
||||
char hn[1024];
|
||||
strcpy_s(c.hostname, (enet_address_get_host(&c.peer->address, hn, sizeof(hn))==0) ? hn : "localhost");
|
||||
printf("client connected (%s)\n", c.hostname);
|
||||
send_welcome(lastconnect = &c-&clients[0]);
|
||||
break;
|
||||
}
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
brec += event.packet->dataLength;
|
||||
process(event.packet, (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);
|
||||
};
|
||||
};
|
303
src/serverbrowser.cpp
Normal file
303
src/serverbrowser.cpp
Normal file
|
@ -0,0 +1,303 @@
|
|||
// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window management
|
||||
|
||||
#include "cube.h"
|
||||
#include "SDL_thread.h"
|
||||
|
||||
struct resolverthread
|
||||
{
|
||||
SDL_Thread *thread;
|
||||
char *query;
|
||||
int starttime;
|
||||
};
|
||||
|
||||
struct resolverresult
|
||||
{
|
||||
char *query;
|
||||
ENetAddress address;
|
||||
};
|
||||
|
||||
vector<resolverthread> resolverthreads;
|
||||
vector<char *> resolverqueries;
|
||||
vector<resolverresult> resolverresults;
|
||||
SDL_mutex *resolvermutex;
|
||||
SDL_sem *resolversem;
|
||||
int resolverlimit = 1000;
|
||||
|
||||
int resolverloop(void * data)
|
||||
{
|
||||
resolverthread *rt = (resolverthread *)data;
|
||||
for(;;)
|
||||
{
|
||||
SDL_SemWait(resolversem);
|
||||
SDL_LockMutex(resolvermutex);
|
||||
if(resolverqueries.empty())
|
||||
{
|
||||
SDL_UnlockMutex(resolvermutex);
|
||||
continue;
|
||||
}
|
||||
rt->query = resolverqueries.pop();
|
||||
rt->starttime = lastmillis;
|
||||
SDL_UnlockMutex(resolvermutex);
|
||||
ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT };
|
||||
enet_address_set_host(&address, rt->query);
|
||||
SDL_LockMutex(resolvermutex);
|
||||
resolverresult &rr = resolverresults.add();
|
||||
rr.query = rt->query;
|
||||
rr.address = address;
|
||||
rt->query = NULL;
|
||||
rt->starttime = 0;
|
||||
SDL_UnlockMutex(resolvermutex);
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
void resolverinit(int threads, int limit)
|
||||
{
|
||||
resolverlimit = limit;
|
||||
resolversem = SDL_CreateSemaphore(0);
|
||||
resolvermutex = SDL_CreateMutex();
|
||||
|
||||
while(threads > 0)
|
||||
{
|
||||
resolverthread &rt = resolverthreads.add();
|
||||
rt.query = NULL;
|
||||
rt.starttime = 0;
|
||||
rt.thread = SDL_CreateThread(resolverloop, &rt);
|
||||
--threads;
|
||||
};
|
||||
};
|
||||
|
||||
void resolverstop(resolverthread &rt, bool restart)
|
||||
{
|
||||
SDL_LockMutex(resolvermutex);
|
||||
SDL_KillThread(rt.thread);
|
||||
rt.query = NULL;
|
||||
rt.starttime = 0;
|
||||
rt.thread = NULL;
|
||||
if(restart) rt.thread = SDL_CreateThread(resolverloop, &rt);
|
||||
SDL_UnlockMutex(resolvermutex);
|
||||
};
|
||||
|
||||
void resolverclear()
|
||||
{
|
||||
SDL_LockMutex(resolvermutex);
|
||||
resolverqueries.setsize(0);
|
||||
resolverresults.setsize(0);
|
||||
while (SDL_SemTryWait(resolversem) == 0);
|
||||
loopv(resolverthreads)
|
||||
{
|
||||
resolverthread &rt = resolverthreads[i];
|
||||
resolverstop(rt, true);
|
||||
};
|
||||
SDL_UnlockMutex(resolvermutex);
|
||||
};
|
||||
|
||||
void resolverquery(char *name)
|
||||
{
|
||||
SDL_LockMutex(resolvermutex);
|
||||
resolverqueries.add(name);
|
||||
SDL_SemPost(resolversem);
|
||||
SDL_UnlockMutex(resolvermutex);
|
||||
};
|
||||
|
||||
bool resolvercheck(char **name, ENetAddress *address)
|
||||
{
|
||||
SDL_LockMutex(resolvermutex);
|
||||
if(!resolverresults.empty())
|
||||
{
|
||||
resolverresult &rr = resolverresults.pop();
|
||||
*name = rr.query;
|
||||
*address = rr.address;
|
||||
SDL_UnlockMutex(resolvermutex);
|
||||
return true;
|
||||
}
|
||||
loopv(resolverthreads)
|
||||
{
|
||||
resolverthread &rt = resolverthreads[i];
|
||||
if(rt.query)
|
||||
{
|
||||
if(lastmillis - rt.starttime > resolverlimit)
|
||||
{
|
||||
resolverstop(rt, true);
|
||||
*name = rt.query;
|
||||
SDL_UnlockMutex(resolvermutex);
|
||||
return true;
|
||||
};
|
||||
};
|
||||
};
|
||||
SDL_UnlockMutex(resolvermutex);
|
||||
return false;
|
||||
};
|
||||
|
||||
struct serverinfo
|
||||
{
|
||||
string name;
|
||||
string full;
|
||||
string map;
|
||||
string sdesc;
|
||||
int mode, numplayers, ping, protocol, minremain;
|
||||
ENetAddress address;
|
||||
};
|
||||
|
||||
vector<serverinfo> servers;
|
||||
ENetSocket pingsock = ENET_SOCKET_NULL;
|
||||
int lastinfo = 0;
|
||||
|
||||
char *getservername(int n) { return servers[n].name; };
|
||||
|
||||
void addserver(char *servername)
|
||||
{
|
||||
loopv(servers) if(strcmp(servers[i].name, servername)==0) return;
|
||||
serverinfo &si = servers.insert(0, serverinfo());
|
||||
strcpy_s(si.name, servername);
|
||||
si.full[0] = 0;
|
||||
si.mode = 0;
|
||||
si.numplayers = 0;
|
||||
si.ping = 9999;
|
||||
si.protocol = 0;
|
||||
si.minremain = 0;
|
||||
si.map[0] = 0;
|
||||
si.sdesc[0] = 0;
|
||||
si.address.host = ENET_HOST_ANY;
|
||||
si.address.port = CUBE_SERVINFO_PORT;
|
||||
};
|
||||
|
||||
void pingservers()
|
||||
{
|
||||
ENetBuffer buf;
|
||||
uchar ping[MAXTRANS];
|
||||
uchar *p;
|
||||
loopv(servers)
|
||||
{
|
||||
serverinfo &si = servers[i];
|
||||
if(si.address.host == ENET_HOST_ANY) continue;
|
||||
p = ping;
|
||||
putint(p, lastmillis);
|
||||
buf.data = ping;
|
||||
buf.dataLength = p - ping;
|
||||
enet_socket_send(pingsock, &si.address, &buf, 1);
|
||||
};
|
||||
lastinfo = lastmillis;
|
||||
};
|
||||
|
||||
void checkresolver()
|
||||
{
|
||||
char *name = NULL;
|
||||
ENetAddress addr = { ENET_HOST_ANY, CUBE_SERVINFO_PORT };
|
||||
while(resolvercheck(&name, &addr))
|
||||
{
|
||||
if(addr.host == ENET_HOST_ANY) continue;
|
||||
loopv(servers)
|
||||
{
|
||||
serverinfo &si = servers[i];
|
||||
if(name == si.name)
|
||||
{
|
||||
si.address = addr;
|
||||
addr.host = ENET_HOST_ANY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkpings()
|
||||
{
|
||||
enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE;
|
||||
ENetBuffer buf;
|
||||
ENetAddress addr;
|
||||
uchar ping[MAXTRANS], *p;
|
||||
char text[MAXTRANS];
|
||||
buf.data = ping;
|
||||
buf.dataLength = sizeof(ping);
|
||||
while(enet_socket_wait(pingsock, &events, 0) >= 0 && events)
|
||||
{
|
||||
if(enet_socket_receive(pingsock, &addr, &buf, 1) <= 0) return;
|
||||
loopv(servers)
|
||||
{
|
||||
serverinfo &si = servers[i];
|
||||
if(addr.host == si.address.host)
|
||||
{
|
||||
p = ping;
|
||||
si.ping = lastmillis - getint(p);
|
||||
si.protocol = getint(p);
|
||||
if(si.protocol!=PROTOCOL_VERSION) si.ping = 9998;
|
||||
si.mode = getint(p);
|
||||
si.numplayers = getint(p);
|
||||
si.minremain = getint(p);
|
||||
sgetstr();
|
||||
strcpy_s(si.map, text);
|
||||
sgetstr();
|
||||
strcpy_s(si.sdesc, text);
|
||||
break;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
int sicompare(const serverinfo *a, const serverinfo *b)
|
||||
{
|
||||
return a->ping>b->ping ? 1 : (a->ping<b->ping ? -1 : strcmp(a->name, b->name));
|
||||
};
|
||||
|
||||
void refreshservers()
|
||||
{
|
||||
checkresolver();
|
||||
checkpings();
|
||||
if(lastmillis - lastinfo >= 5000) pingservers();
|
||||
servers.sort((void *)sicompare);
|
||||
int maxmenu = 16;
|
||||
loopv(servers)
|
||||
{
|
||||
serverinfo &si = servers[i];
|
||||
if(si.address.host != ENET_HOST_ANY && si.ping != 9999)
|
||||
{
|
||||
if(si.protocol!=PROTOCOL_VERSION) sprintf_s(si.full)("%s [different cube protocol]", si.name);
|
||||
else sprintf_s(si.full)("%d\t%d\t%s, %s: %s %s", si.ping, si.numplayers, si.map[0] ? si.map : "[unknown]", modestr(si.mode), si.name, si.sdesc);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf_s(si.full)(si.address.host != ENET_HOST_ANY ? "%s [waiting for server response]" : "%s [unknown host]\t", si.name);
|
||||
}
|
||||
si.full[50] = 0; // cut off too long server descriptions
|
||||
menumanual(1, i, si.full);
|
||||
if(!--maxmenu) return;
|
||||
};
|
||||
};
|
||||
|
||||
void servermenu()
|
||||
{
|
||||
if(pingsock == ENET_SOCKET_NULL)
|
||||
{
|
||||
pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL);
|
||||
resolverinit(1, 1000);
|
||||
};
|
||||
resolverclear();
|
||||
loopv(servers) resolverquery(servers[i].name);
|
||||
refreshservers();
|
||||
menuset(1);
|
||||
};
|
||||
|
||||
void updatefrommaster()
|
||||
{
|
||||
const int MAXUPD = 32000;
|
||||
uchar buf[MAXUPD];
|
||||
uchar *reply = retrieveservers(buf, MAXUPD);
|
||||
if(!*reply || strstr((char *)reply, "<html>") || strstr((char *)reply, "<HTML>")) 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);
|
||||
};
|
||||
|
||||
|
141
src/serverms.cpp
Normal file
141
src/serverms.cpp
Normal file
|
@ -0,0 +1,141 @@
|
|||
// all server side masterserver and pinging functionality
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
ENetSocket mssock = ENET_SOCKET_NULL;
|
||||
|
||||
void httpgetsend(ENetAddress &ad, char *hostname, char *req, char *ref, char *agent)
|
||||
{
|
||||
if(ad.host==ENET_HOST_ANY)
|
||||
{
|
||||
printf("looking up %s...\n", hostname);
|
||||
enet_address_set_host(&ad, hostname);
|
||||
if(ad.host==ENET_HOST_ANY) return;
|
||||
};
|
||||
if(mssock!=ENET_SOCKET_NULL) enet_socket_destroy(mssock);
|
||||
mssock = enet_socket_create(ENET_SOCKET_TYPE_STREAM, NULL);
|
||||
if(mssock==ENET_SOCKET_NULL) { printf("could not open socket\n"); return; };
|
||||
if(enet_socket_connect(mssock, &ad)<0) { printf("could not connect\n"); return; };
|
||||
ENetBuffer buf;
|
||||
sprintf_sd(httpget)("GET %s HTTP/1.0\nHost: %s\nReferer: %s\nUser-Agent: %s\n\n", req, hostname, ref, agent);
|
||||
buf.data = httpget;
|
||||
buf.dataLength = strlen((char *)buf.data);
|
||||
printf("sending request to %s...\n", hostname);
|
||||
enet_socket_send(mssock, NULL, &buf, 1);
|
||||
};
|
||||
|
||||
void httpgetrecieve(ENetBuffer &buf)
|
||||
{
|
||||
if(mssock==ENET_SOCKET_NULL) return;
|
||||
enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE;
|
||||
if(enet_socket_wait(mssock, &events, 0) >= 0 && events)
|
||||
{
|
||||
int len = enet_socket_receive(mssock, NULL, &buf, 1);
|
||||
if(len<=0)
|
||||
{
|
||||
enet_socket_destroy(mssock);
|
||||
mssock = ENET_SOCKET_NULL;
|
||||
return;
|
||||
};
|
||||
buf.data = ((char *)buf.data)+len;
|
||||
((char*)buf.data)[0] = 0;
|
||||
buf.dataLength -= len;
|
||||
};
|
||||
};
|
||||
|
||||
uchar *stripheader(uchar *b)
|
||||
{
|
||||
char *s = strstr((char *)b, "\n\r\n");
|
||||
if(!s) s = strstr((char *)b, "\n\n");
|
||||
return s ? (uchar *)s : b;
|
||||
};
|
||||
|
||||
ENetAddress masterserver = { ENET_HOST_ANY, 80 };
|
||||
int updmaster = 0;
|
||||
string masterbase;
|
||||
string masterpath;
|
||||
uchar masterrep[MAXTRANS];
|
||||
ENetBuffer masterb;
|
||||
|
||||
void updatemasterserver(int seconds)
|
||||
{
|
||||
if(seconds>updmaster) // send alive signal to masterserver every hour of uptime
|
||||
{
|
||||
sprintf_sd(path)("%sregister.do?action=add", masterpath);
|
||||
httpgetsend(masterserver, masterbase, path, "cubeserver", "Cube Server");
|
||||
masterrep[0] = 0;
|
||||
masterb.data = masterrep;
|
||||
masterb.dataLength = MAXTRANS-1;
|
||||
updmaster = seconds+60*60;
|
||||
};
|
||||
};
|
||||
|
||||
void checkmasterreply()
|
||||
{
|
||||
bool busy = mssock!=ENET_SOCKET_NULL;
|
||||
httpgetrecieve(masterb);
|
||||
if(busy && mssock==ENET_SOCKET_NULL) printf("masterserver reply: %s\n", stripheader(masterrep));
|
||||
};
|
||||
|
||||
uchar *retrieveservers(uchar *buf, int buflen)
|
||||
{
|
||||
sprintf_sd(path)("%sretrieve.do?item=list", masterpath);
|
||||
httpgetsend(masterserver, masterbase, path, "cubeserver", "Cube Server");
|
||||
ENetBuffer eb;
|
||||
buf[0] = 0;
|
||||
eb.data = buf;
|
||||
eb.dataLength = buflen-1;
|
||||
while(mssock!=ENET_SOCKET_NULL) httpgetrecieve(eb);
|
||||
return stripheader(buf);
|
||||
};
|
||||
|
||||
ENetSocket pongsock = ENET_SOCKET_NULL;
|
||||
string serverdesc;
|
||||
|
||||
void serverms(int mode, int numplayers, int minremain, char *smapname, int seconds, bool isfull)
|
||||
{
|
||||
checkmasterreply();
|
||||
updatemasterserver(seconds);
|
||||
|
||||
// reply all server info requests
|
||||
ENetBuffer buf;
|
||||
ENetAddress addr;
|
||||
uchar pong[MAXTRANS], *p;
|
||||
int len;
|
||||
enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE;
|
||||
buf.data = pong;
|
||||
while(enet_socket_wait(pongsock, &events, 0) >= 0 && events)
|
||||
{
|
||||
buf.dataLength = sizeof(pong);
|
||||
len = enet_socket_receive(pongsock, &addr, &buf, 1);
|
||||
if(len < 0) return;
|
||||
p = &pong[len];
|
||||
putint(p, PROTOCOL_VERSION);
|
||||
putint(p, mode);
|
||||
putint(p, numplayers);
|
||||
putint(p, minremain);
|
||||
string mname;
|
||||
strcpy_s(mname, isfull ? "[FULL] " : "");
|
||||
strcat_s(mname, smapname);
|
||||
sendstring(mname, p);
|
||||
sendstring(serverdesc, p);
|
||||
buf.dataLength = p - pong;
|
||||
enet_socket_send(pongsock, &addr, &buf, 1);
|
||||
};
|
||||
};
|
||||
|
||||
void servermsinit(const char *master, char *sdesc, bool listen)
|
||||
{
|
||||
const char *mid = strstr(master, "/");
|
||||
if(!mid) mid = master;
|
||||
strcpy_s(masterpath, mid);
|
||||
strn0cpy(masterbase, master, mid-master+1);
|
||||
strcpy_s(serverdesc, sdesc);
|
||||
|
||||
if(listen)
|
||||
{
|
||||
ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT };
|
||||
pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, &address);
|
||||
if(pongsock == ENET_SOCKET_NULL) fatal("could not create server info socket\n");
|
||||
};
|
||||
};
|
123
src/serverutil.cpp
Normal file
123
src/serverutil.cpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
// misc useful functions used by the server
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
// all network traffic is in 32bit ints, which are then compressed using the following simple scheme (assumes that most values are small).
|
||||
|
||||
void putint(uchar *&p, int n)
|
||||
{
|
||||
if(n<128 && n>-127) { *p++ = n; }
|
||||
else if(n<0x8000 && n>=-0x8000) { *p++ = 0x80; *p++ = n; *p++ = n>>8; }
|
||||
else { *p++ = 0x81; *p++ = n; *p++ = n>>8; *p++ = n>>16; *p++ = n>>24; };
|
||||
};
|
||||
|
||||
int getint(uchar *&p)
|
||||
{
|
||||
int c = *((char *)p);
|
||||
p++;
|
||||
if(c==-128) { int n = *p++; n |= *((char *)p)<<8; p++; return n;}
|
||||
else if(c==-127) { int n = *p++; n |= *p++<<8; n |= *p++<<16; return n|(*p++<<24); }
|
||||
else return c;
|
||||
};
|
||||
|
||||
void sendstring(char *t, uchar *&p)
|
||||
{
|
||||
while(*t) putint(p, *t++);
|
||||
putint(p, 0);
|
||||
};
|
||||
|
||||
const char *modenames[] =
|
||||
{
|
||||
"SP", "DMSP", "ffa/default", "coopedit", "ffa/duel", "teamplay",
|
||||
"instagib", "instagib team", "efficiency", "efficiency team",
|
||||
"insta arena", "insta clan arena", "tactics arena", "tactics clan arena",
|
||||
};
|
||||
|
||||
const char *modestr(int n) { return (n>=-2 && n<12) ? modenames[n+2] : "unknown"; };
|
||||
|
||||
char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes
|
||||
{
|
||||
SV_INITS2C, 4, SV_INITC2S, 0, SV_POS, 12, SV_TEXT, 0, SV_SOUND, 2, SV_CDIS, 2,
|
||||
SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, SV_EDITD, 6, SV_EDITE, 6,
|
||||
SV_DIED, 2, SV_DAMAGE, 4, SV_SHOT, 8, SV_FRAGS, 2,
|
||||
SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 3, SV_DENIED, 2,
|
||||
SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2,
|
||||
SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_ITEMACC, 2,
|
||||
SV_SENDMAP, 0, SV_RECVMAP, 1, SV_SERVMSG, 0, SV_ITEMLIST, 0,
|
||||
SV_EXT, 0,
|
||||
-1
|
||||
};
|
||||
|
||||
char msgsizelookup(int msg)
|
||||
{
|
||||
for(char *p = msgsizesl; *p>=0; p += 2) if(*p==msg) return p[1];
|
||||
return -1;
|
||||
};
|
||||
|
||||
// sending of maps between clients
|
||||
|
||||
string copyname;
|
||||
int copysize;
|
||||
uchar *copydata = NULL;
|
||||
|
||||
void sendmaps(int n, string mapname, int mapsize, uchar *mapdata)
|
||||
{
|
||||
if(mapsize <= 0 || mapsize > 256*256) return;
|
||||
strcpy_s(copyname, mapname);
|
||||
copysize = mapsize;
|
||||
if(copydata) free(copydata);
|
||||
copydata = (uchar *)alloc(mapsize);
|
||||
memcpy(copydata, mapdata, mapsize);
|
||||
}
|
||||
|
||||
ENetPacket *recvmap(int n)
|
||||
{
|
||||
if(!copydata) return NULL;
|
||||
ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + copysize, ENET_PACKET_FLAG_RELIABLE);
|
||||
uchar *start = packet->data;
|
||||
uchar *p = start+2;
|
||||
putint(p, SV_RECVMAP);
|
||||
sendstring(copyname, p);
|
||||
putint(p, copysize);
|
||||
memcpy(p, copydata, copysize);
|
||||
p += copysize;
|
||||
*(ushort *)start = ENET_HOST_TO_NET_16(p-start);
|
||||
enet_packet_resize(packet, p-start);
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
||||
#ifdef STANDALONE
|
||||
|
||||
void localservertoclient(uchar *buf, int len) {};
|
||||
void fatal(char *s, char *o) { cleanupserver(); printf("servererror: %s\n", s); exit(1); };
|
||||
void *alloc(int s) { void *b = calloc(1,s); if(!b) fatal("no memory!"); return b; };
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int uprate = 0, maxcl = 4;
|
||||
char *sdesc = "", *ip = "", *master = NULL, *passwd = "";
|
||||
|
||||
for(int i = 1; i<argc; i++)
|
||||
{
|
||||
char *a = &argv[i][2];
|
||||
if(argv[i][0]=='-') switch(argv[i][1])
|
||||
{
|
||||
case 'u': uprate = atoi(a); break;
|
||||
case 'n': sdesc = a; break;
|
||||
case 'i': ip = a; break;
|
||||
case 'm': master = a; break;
|
||||
case 'p': passwd = a; break;
|
||||
case 'c': maxcl = atoi(a); break;
|
||||
default: printf("WARNING: unknown commandline option\n");
|
||||
};
|
||||
};
|
||||
|
||||
if(enet_initialize()<0) fatal("Unable to initialise network module");
|
||||
initserver(true, uprate, sdesc, ip, master, passwd, maxcl);
|
||||
return 0;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
|
226
src/sound.cpp
Normal file
226
src/sound.cpp
Normal file
|
@ -0,0 +1,226 @@
|
|||
// sound.cpp: uses fmod on windows and sdl_mixer on unix (both had problems on the other platform)
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
//#ifndef WIN32 // NOTE: fmod not being supported for the moment as it does not allow stereo pan/vol updating during playback
|
||||
#define USE_MIXER
|
||||
//#endif
|
||||
|
||||
VARP(soundvol, 0, 255, 255);
|
||||
VARP(musicvol, 0, 128, 255);
|
||||
bool nosound = false;
|
||||
|
||||
#define MAXCHAN 32
|
||||
#define SOUNDFREQ 22050
|
||||
|
||||
struct soundloc { vec loc; bool inuse; } soundlocs[MAXCHAN];
|
||||
|
||||
#ifdef USE_MIXER
|
||||
#include "SDL_mixer.h"
|
||||
#define MAXVOL MIX_MAX_VOLUME
|
||||
Mix_Music *mod = NULL;
|
||||
void *stream = NULL;
|
||||
#else
|
||||
#include "fmod.h"
|
||||
#define MAXVOL 255
|
||||
FMUSIC_MODULE *mod = NULL;
|
||||
FSOUND_STREAM *stream = NULL;
|
||||
#endif
|
||||
|
||||
void stopsound()
|
||||
{
|
||||
if(nosound) return;
|
||||
if(mod)
|
||||
{
|
||||
#ifdef USE_MIXER
|
||||
Mix_HaltMusic();
|
||||
Mix_FreeMusic(mod);
|
||||
#else
|
||||
FMUSIC_FreeSong(mod);
|
||||
#endif
|
||||
mod = NULL;
|
||||
};
|
||||
if(stream)
|
||||
{
|
||||
#ifndef USE_MIXER
|
||||
FSOUND_Stream_Close(stream);
|
||||
#endif
|
||||
stream = NULL;
|
||||
};
|
||||
};
|
||||
|
||||
VAR(soundbufferlen, 128, 1024, 4096);
|
||||
|
||||
void initsound()
|
||||
{
|
||||
memset(soundlocs, 0, sizeof(soundloc)*MAXCHAN);
|
||||
#ifdef USE_MIXER
|
||||
if(Mix_OpenAudio(SOUNDFREQ, MIX_DEFAULT_FORMAT, 2, soundbufferlen)<0)
|
||||
{
|
||||
conoutf("sound init failed (SDL_mixer): %s", (size_t)Mix_GetError());
|
||||
nosound = true;
|
||||
};
|
||||
Mix_AllocateChannels(MAXCHAN);
|
||||
#else
|
||||
if(FSOUND_GetVersion()<FMOD_VERSION) fatal("old FMOD dll");
|
||||
if(!FSOUND_Init(SOUNDFREQ, MAXCHAN, FSOUND_INIT_GLOBALFOCUS))
|
||||
{
|
||||
conoutf("sound init failed (FMOD): %d", FSOUND_GetError());
|
||||
nosound = true;
|
||||
};
|
||||
#endif
|
||||
};
|
||||
|
||||
void music(char *name)
|
||||
{
|
||||
if(nosound) return;
|
||||
stopsound();
|
||||
if(soundvol && musicvol)
|
||||
{
|
||||
string sn;
|
||||
strcpy_s(sn, "packages/");
|
||||
strcat_s(sn, name);
|
||||
#ifdef USE_MIXER
|
||||
if(mod = Mix_LoadMUS(path(sn)))
|
||||
{
|
||||
Mix_PlayMusic(mod, -1);
|
||||
Mix_VolumeMusic((musicvol*MAXVOL)/255);
|
||||
};
|
||||
#else
|
||||
if(mod = FMUSIC_LoadSong(path(sn)))
|
||||
{
|
||||
FMUSIC_PlaySong(mod);
|
||||
FMUSIC_SetMasterVolume(mod, musicvol);
|
||||
}
|
||||
else if(stream = FSOUND_Stream_Open(path(sn), FSOUND_LOOP_NORMAL, 0, 0))
|
||||
{
|
||||
int chan = FSOUND_Stream_Play(FSOUND_FREE, stream);
|
||||
if(chan>=0) { FSOUND_SetVolume(chan, (musicvol*MAXVOL)/255); FSOUND_SetPaused(chan, false); };
|
||||
}
|
||||
else
|
||||
{
|
||||
conoutf("could not play music: %s", sn);
|
||||
};
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
COMMAND(music, ARG_1STR);
|
||||
|
||||
#ifdef USE_MIXER
|
||||
vector<Mix_Chunk *> samples;
|
||||
#else
|
||||
vector<FSOUND_SAMPLE *> samples;
|
||||
#endif
|
||||
|
||||
cvector snames;
|
||||
|
||||
int registersound(char *name)
|
||||
{
|
||||
loopv(snames) if(strcmp(snames[i], name)==0) return i;
|
||||
snames.add(newstring(name));
|
||||
samples.add(NULL);
|
||||
return samples.length()-1;
|
||||
};
|
||||
|
||||
COMMAND(registersound, ARG_1EST);
|
||||
|
||||
void cleansound()
|
||||
{
|
||||
if(nosound) return;
|
||||
stopsound();
|
||||
#ifdef USE_MIXER
|
||||
Mix_CloseAudio();
|
||||
#else
|
||||
FSOUND_Close();
|
||||
#endif
|
||||
};
|
||||
|
||||
VAR(stereo, 0, 1, 1);
|
||||
|
||||
void updatechanvol(int chan, vec *loc)
|
||||
{
|
||||
int vol = soundvol, pan = 255/2;
|
||||
if(loc)
|
||||
{
|
||||
vdist(dist, v, *loc, player1->o);
|
||||
vol -= (int)(dist*3*soundvol/255); // simple mono distance attenuation
|
||||
if(stereo && (v.x != 0 || v.y != 0))
|
||||
{
|
||||
float yaw = -atan2(v.x, v.y) - player1->yaw*(PI / 180.0f); // relative angle of sound along X-Y axis
|
||||
pan = int(255.9f*(0.5*sin(yaw)+0.5f)); // range is from 0 (left) to 255 (right)
|
||||
};
|
||||
};
|
||||
vol = (vol*MAXVOL)/255;
|
||||
#ifdef USE_MIXER
|
||||
Mix_Volume(chan, vol);
|
||||
Mix_SetPanning(chan, 255-pan, pan);
|
||||
#else
|
||||
FSOUND_SetVolume(chan, vol);
|
||||
FSOUND_SetPan(chan, pan);
|
||||
#endif
|
||||
};
|
||||
|
||||
void newsoundloc(int chan, vec *loc)
|
||||
{
|
||||
assert(chan>=0 && chan<MAXCHAN);
|
||||
soundlocs[chan].loc = *loc;
|
||||
soundlocs[chan].inuse = true;
|
||||
};
|
||||
|
||||
void updatevol()
|
||||
{
|
||||
if(nosound) return;
|
||||
loopi(MAXCHAN) if(soundlocs[i].inuse)
|
||||
{
|
||||
#ifdef USE_MIXER
|
||||
if(Mix_Playing(i))
|
||||
#else
|
||||
if(FSOUND_IsPlaying(i))
|
||||
#endif
|
||||
updatechanvol(i, &soundlocs[i].loc);
|
||||
else soundlocs[i].inuse = false;
|
||||
};
|
||||
};
|
||||
|
||||
void playsoundc(int n) { addmsg(0, 2, SV_SOUND, n); playsound(n); };
|
||||
|
||||
int soundsatonce = 0, lastsoundmillis = 0;
|
||||
|
||||
void playsound(int n, vec *loc)
|
||||
{
|
||||
if(nosound) return;
|
||||
if(!soundvol) return;
|
||||
if(lastmillis==lastsoundmillis) soundsatonce++; else soundsatonce = 1;
|
||||
lastsoundmillis = lastmillis;
|
||||
if(soundsatonce>5) return; // avoid bursts of sounds with heavy packetloss and in sp
|
||||
if(n<0 || n>=samples.length()) { conoutf("unregistered sound: %d", n); return; };
|
||||
|
||||
if(!samples[n])
|
||||
{
|
||||
sprintf_sd(buf)("packages/sounds/%s.wav", snames[n]);
|
||||
|
||||
#ifdef USE_MIXER
|
||||
samples[n] = Mix_LoadWAV(path(buf));
|
||||
#else
|
||||
samples[n] = FSOUND_Sample_Load(n, path(buf), FSOUND_LOOP_OFF, 0, 0);
|
||||
#endif
|
||||
|
||||
if(!samples[n]) { conoutf("failed to load sample: %s", buf); return; };
|
||||
};
|
||||
|
||||
#ifdef USE_MIXER
|
||||
int chan = Mix_PlayChannel(-1, samples[n], 0);
|
||||
#else
|
||||
int chan = FSOUND_PlaySoundEx(FSOUND_FREE, samples[n], NULL, true);
|
||||
#endif
|
||||
if(chan<0) return;
|
||||
if(loc) newsoundloc(chan, loc);
|
||||
updatechanvol(chan, loc);
|
||||
#ifndef USE_MIXER
|
||||
FSOUND_SetPaused(chan, false);
|
||||
#endif
|
||||
};
|
||||
|
||||
void sound(int n) { playsound(n, NULL); };
|
||||
COMMAND(sound, ARG_1INT);
|
140
src/tools.cpp
Normal file
140
src/tools.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
// implementation of generic tools
|
||||
|
||||
#include "tools.h"
|
||||
#include <new>
|
||||
|
||||
//////////////////////////// pool ///////////////////////////
|
||||
|
||||
pool::pool()
|
||||
{
|
||||
blocks = 0;
|
||||
allocnext(POOLSIZE);
|
||||
for(int i = 0; i<MAXBUCKETS; i++) reuse[i] = NULL;
|
||||
};
|
||||
|
||||
void *pool::alloc(size_t size)
|
||||
{
|
||||
if(size>MAXREUSESIZE)
|
||||
{
|
||||
return malloc(size);
|
||||
}
|
||||
else
|
||||
{
|
||||
size = bucket(size);
|
||||
void **r = (void **)reuse[size];
|
||||
if(r)
|
||||
{
|
||||
reuse[size] = *r;
|
||||
return (void *)r;
|
||||
}
|
||||
else
|
||||
{
|
||||
size <<= PTRBITS;
|
||||
if(left<size) allocnext(POOLSIZE);
|
||||
char *r = p;
|
||||
p += size;
|
||||
left -= size;
|
||||
return r;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
void pool::dealloc(void *p, size_t size)
|
||||
{
|
||||
if(size>MAXREUSESIZE)
|
||||
{
|
||||
free(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
size = bucket(size);
|
||||
if(size) // only needed for 0-size free, are there any?
|
||||
{
|
||||
*((void **)p) = reuse[size];
|
||||
reuse[size] = p;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
void *pool::realloc(void *p, size_t oldsize, size_t newsize)
|
||||
{
|
||||
void *np = alloc(newsize);
|
||||
if(!oldsize) return np;
|
||||
memcpy(np, p, newsize>oldsize ? oldsize : newsize);
|
||||
dealloc(p, oldsize);
|
||||
return np;
|
||||
};
|
||||
|
||||
void pool::dealloc_block(void *b)
|
||||
{
|
||||
if(b)
|
||||
{
|
||||
dealloc_block(*((char **)b));
|
||||
free(b);
|
||||
};
|
||||
}
|
||||
|
||||
void pool::allocnext(size_t allocsize)
|
||||
{
|
||||
char *b = (char *)malloc(allocsize+PTRSIZE);
|
||||
*((char **)b) = blocks;
|
||||
blocks = b;
|
||||
p = b+PTRSIZE;
|
||||
left = allocsize;
|
||||
};
|
||||
|
||||
char *pool::string(char *s, size_t l)
|
||||
{
|
||||
char *b = (char *)alloc(l+1);
|
||||
strncpy(b,s,l);
|
||||
b[l] = 0;
|
||||
return b;
|
||||
};
|
||||
|
||||
pool *gp() // useful for global buffers that need to be initialisation order independant
|
||||
{
|
||||
static pool *p = NULL;
|
||||
return p ? p : (p = new pool());
|
||||
};
|
||||
|
||||
|
||||
///////////////////////// misc tools ///////////////////////
|
||||
|
||||
char *path(char *s)
|
||||
{
|
||||
for(char *t = s; t = strpbrk(t, "/\\"); *t++ = PATHDIV);
|
||||
return s;
|
||||
};
|
||||
|
||||
char *loadfile(char *fn, int *size)
|
||||
{
|
||||
FILE *f = fopen(fn, "rb");
|
||||
if(!f) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
int len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
char *buf = (char *)malloc(len+1);
|
||||
if(!buf) return NULL;
|
||||
buf[len] = 0;
|
||||
size_t rlen = fread(buf, 1, len, f);
|
||||
fclose(f);
|
||||
if(len!=rlen || len<=0)
|
||||
{
|
||||
free(buf);
|
||||
return NULL;
|
||||
};
|
||||
if(size!=NULL) *size = len;
|
||||
return buf;
|
||||
};
|
||||
|
||||
void endianswap(void *memory, int stride, int length) // little indians as storage format
|
||||
{
|
||||
if(*((char *)&stride)) return;
|
||||
loop(w, length) loop(i, stride/2)
|
||||
{
|
||||
uchar *p = (uchar *)memory+w*stride;
|
||||
uchar t = p[i];
|
||||
p[i] = p[stride-i-1];
|
||||
p[stride-i-1] = t;
|
||||
};
|
||||
}
|
279
src/tools.h
Normal file
279
src/tools.h
Normal file
|
@ -0,0 +1,279 @@
|
|||
// generic useful stuff for any C++ program
|
||||
|
||||
#ifndef _TOOLS_H
|
||||
#define _TOOLS_H
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define gamma __gamma
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#undef gamma
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
#ifdef __GNUC__
|
||||
#include <new>
|
||||
#else
|
||||
#include <new.h>
|
||||
#endif
|
||||
|
||||
#ifdef NULL
|
||||
#undef NULL
|
||||
#endif
|
||||
#define NULL 0
|
||||
|
||||
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 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)
|
||||
|
||||
#ifdef WIN32
|
||||
#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;
|
||||
};
|
||||
};
|
||||
|
||||
#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)
|
||||
|
||||
|
||||
// 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;
|
||||
};
|
||||
#else
|
||||
#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 <class T> 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<T> &v);
|
||||
void operator=(vector<T> &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 <class T> 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<T> &v);
|
||||
void operator=(hashtable<T> &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
|
||||
|
342
src/weapon.cpp
Normal file
342
src/weapon.cpp
Normal file
|
@ -0,0 +1,342 @@
|
|||
// weapon.cpp: all shooting and effects code
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
struct guninfo { short sound, attackdelay, damage, projspeed, part, kickamount; char *name; };
|
||||
|
||||
const int MONSTERDAMAGEFACTOR = 4;
|
||||
const int SGRAYS = 20;
|
||||
const float SGSPREAD = 2;
|
||||
vec sg[SGRAYS];
|
||||
|
||||
guninfo guns[NUMGUNS] =
|
||||
{
|
||||
{ S_PUNCH1, 250, 50, 0, 0, 1, "fist" },
|
||||
{ S_SG, 1400, 10, 0, 0, 20, "shotgun" }, // *SGRAYS
|
||||
{ S_CG, 100, 30, 0, 0, 7, "chaingun" },
|
||||
{ S_RLFIRE, 800, 120, 80, 0, 10, "rocketlauncher" },
|
||||
{ S_RIFLE, 1500, 100, 0, 0, 30, "rifle" },
|
||||
{ S_FLAUNCH, 200, 20, 50, 4, 1, "fireball" },
|
||||
{ S_ICEBALL, 200, 40, 30, 6, 1, "iceball" },
|
||||
{ S_SLIMEBALL, 200, 30, 160, 7, 1, "slimeball" },
|
||||
{ S_PIGR1, 250, 50, 0, 0, 1, "bite" },
|
||||
};
|
||||
|
||||
void selectgun(int a, int b, int c)
|
||||
{
|
||||
if(a<-1 || b<-1 || c<-1 || a>=NUMGUNS || b>=NUMGUNS || c>=NUMGUNS) return;
|
||||
int s = player1->gunselect;
|
||||
if(a>=0 && s!=a && player1->ammo[a]) s = a;
|
||||
else if(b>=0 && s!=b && player1->ammo[b]) s = b;
|
||||
else if(c>=0 && s!=c && player1->ammo[c]) s = c;
|
||||
else if(s!=GUN_RL && player1->ammo[GUN_RL]) s = GUN_RL;
|
||||
else if(s!=GUN_CG && player1->ammo[GUN_CG]) s = GUN_CG;
|
||||
else if(s!=GUN_SG && player1->ammo[GUN_SG]) s = GUN_SG;
|
||||
else if(s!=GUN_RIFLE && player1->ammo[GUN_RIFLE]) s = GUN_RIFLE;
|
||||
else s = GUN_FIST;
|
||||
if(s!=player1->gunselect) playsoundc(S_WEAPLOAD);
|
||||
player1->gunselect = s;
|
||||
//conoutf("%s selected", (int)guns[s].name);
|
||||
};
|
||||
|
||||
int reloadtime(int gun) { return guns[gun].attackdelay; };
|
||||
|
||||
void weapon(char *a1, char *a2, char *a3)
|
||||
{
|
||||
selectgun(a1[0] ? atoi(a1) : -1,
|
||||
a2[0] ? atoi(a2) : -1,
|
||||
a3[0] ? atoi(a3) : -1);
|
||||
};
|
||||
|
||||
COMMAND(weapon, ARG_3STR);
|
||||
|
||||
void createrays(vec &from, vec &to) // create random spread of rays for the shotgun
|
||||
{
|
||||
vdist(dist, dvec, from, to);
|
||||
float f = dist*SGSPREAD/1000;
|
||||
loopi(SGRAYS)
|
||||
{
|
||||
#define RNDD (rnd(101)-50)*f
|
||||
vec r = { RNDD, RNDD, RNDD };
|
||||
sg[i] = to;
|
||||
vadd(sg[i], r);
|
||||
};
|
||||
};
|
||||
|
||||
bool intersect(dynent *d, vec &from, vec &to) // if lineseg hits entity bounding box
|
||||
{
|
||||
vec v = to, w = d->o, *p;
|
||||
vsub(v, from);
|
||||
vsub(w, from);
|
||||
float c1 = dotprod(w, v);
|
||||
|
||||
if(c1<=0) p = &from;
|
||||
else
|
||||
{
|
||||
float c2 = dotprod(v, v);
|
||||
if(c2<=c1) p = &to;
|
||||
else
|
||||
{
|
||||
float f = c1/c2;
|
||||
vmul(v, f);
|
||||
vadd(v, from);
|
||||
p = &v;
|
||||
};
|
||||
};
|
||||
|
||||
return p->x <= d->o.x+d->radius
|
||||
&& p->x >= d->o.x-d->radius
|
||||
&& p->y <= d->o.y+d->radius
|
||||
&& p->y >= d->o.y-d->radius
|
||||
&& p->z <= d->o.z+d->aboveeye
|
||||
&& p->z >= d->o.z-d->eyeheight;
|
||||
};
|
||||
|
||||
char *playerincrosshair()
|
||||
{
|
||||
if(demoplayback) return NULL;
|
||||
loopv(players)
|
||||
{
|
||||
dynent *o = players[i];
|
||||
if(!o) continue;
|
||||
if(intersect(o, player1->o, worldpos)) return o->name;
|
||||
};
|
||||
return NULL;
|
||||
};
|
||||
|
||||
const int MAXPROJ = 100;
|
||||
struct projectile { vec o, to; float speed; dynent *owner; int gun; bool inuse, local; };
|
||||
projectile projs[MAXPROJ];
|
||||
|
||||
void projreset() { loopi(MAXPROJ) projs[i].inuse = false; };
|
||||
|
||||
void newprojectile(vec &from, vec &to, float speed, bool local, dynent *owner, int gun)
|
||||
{
|
||||
loopi(MAXPROJ)
|
||||
{
|
||||
projectile *p = &projs[i];
|
||||
if(p->inuse) continue;
|
||||
p->inuse = true;
|
||||
p->o = from;
|
||||
p->to = to;
|
||||
p->speed = speed;
|
||||
p->local = local;
|
||||
p->owner = owner;
|
||||
p->gun = gun;
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
void hit(int target, int damage, dynent *d, dynent *at)
|
||||
{
|
||||
if(d==player1) selfdamage(damage, at==player1 ? -1 : -2, at);
|
||||
else if(d->monsterstate) monsterpain(d, damage, at);
|
||||
else { addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence); playsound(S_PAIN1+rnd(5), &d->o); };
|
||||
particle_splash(3, damage, 1000, d->o);
|
||||
demodamage(damage, d->o);
|
||||
};
|
||||
|
||||
const float RL_RADIUS = 5;
|
||||
const float RL_DAMRAD = 7; // hack
|
||||
|
||||
void radialeffect(dynent *o, vec &v, int cn, int qdam, dynent *at)
|
||||
{
|
||||
if(o->state!=CS_ALIVE) return;
|
||||
vdist(dist, temp, v, o->o);
|
||||
dist -= 2; // account for eye distance imprecision
|
||||
if(dist<RL_DAMRAD)
|
||||
{
|
||||
if(dist<0) dist = 0;
|
||||
int damage = (int)(qdam*(1-(dist/RL_DAMRAD)));
|
||||
hit(cn, damage, o, at);
|
||||
vmul(temp, (RL_DAMRAD-dist)*damage/800);
|
||||
vadd(o->vel, temp);
|
||||
};
|
||||
};
|
||||
|
||||
void splash(projectile *p, vec &v, vec &vold, int notthisplayer, int notthismonster, int qdam)
|
||||
{
|
||||
particle_splash(0, 50, 300, v);
|
||||
p->inuse = false;
|
||||
if(p->gun!=GUN_RL)
|
||||
{
|
||||
playsound(S_FEXPLODE, &v);
|
||||
// no push?
|
||||
}
|
||||
else
|
||||
{
|
||||
playsound(S_RLHIT, &v);
|
||||
newsphere(v, RL_RADIUS, 0);
|
||||
dodynlight(vold, v, 0, 0, p->owner);
|
||||
if(!p->local) return;
|
||||
radialeffect(player1, v, -1, qdam, p->owner);
|
||||
loopv(players)
|
||||
{
|
||||
if(i==notthisplayer) continue;
|
||||
dynent *o = players[i];
|
||||
if(!o) continue;
|
||||
radialeffect(o, v, i, qdam, p->owner);
|
||||
};
|
||||
dvector &mv = getmonsters();
|
||||
loopv(mv) if(i!=notthismonster) radialeffect(mv[i], v, i, qdam, p->owner);
|
||||
};
|
||||
};
|
||||
|
||||
inline void projdamage(dynent *o, projectile *p, vec &v, int i, int im, int qdam)
|
||||
{
|
||||
if(o->state!=CS_ALIVE) return;
|
||||
if(intersect(o, p->o, v))
|
||||
{
|
||||
splash(p, v, p->o, i, im, qdam);
|
||||
hit(i, qdam, o, p->owner);
|
||||
};
|
||||
};
|
||||
|
||||
void moveprojectiles(float time)
|
||||
{
|
||||
loopi(MAXPROJ)
|
||||
{
|
||||
projectile *p = &projs[i];
|
||||
if(!p->inuse) continue;
|
||||
int qdam = guns[p->gun].damage*(p->owner->quadmillis ? 4 : 1);
|
||||
if(p->owner->monsterstate) qdam /= MONSTERDAMAGEFACTOR;
|
||||
vdist(dist, v, p->o, p->to);
|
||||
float dtime = dist*1000/p->speed;
|
||||
if(time>dtime) dtime = time;
|
||||
vmul(v, time/dtime);
|
||||
vadd(v, p->o)
|
||||
if(p->local)
|
||||
{
|
||||
loopv(players)
|
||||
{
|
||||
dynent *o = players[i];
|
||||
if(!o) continue;
|
||||
projdamage(o, p, v, i, -1, qdam);
|
||||
};
|
||||
if(p->owner!=player1) projdamage(player1, p, v, -1, -1, qdam);
|
||||
dvector &mv = getmonsters();
|
||||
loopv(mv) if(!vreject(mv[i]->o, v, 10.0f) && mv[i]!=p->owner) projdamage(mv[i], p, v, -1, i, qdam);
|
||||
};
|
||||
if(p->inuse)
|
||||
{
|
||||
if(time==dtime) splash(p, v, p->o, -1, -1, qdam);
|
||||
else
|
||||
{
|
||||
if(p->gun==GUN_RL) { dodynlight(p->o, v, 0, 255, p->owner); particle_splash(5, 2, 200, v); }
|
||||
else { particle_splash(1, 1, 200, v); particle_splash(guns[p->gun].part, 1, 1, v); };
|
||||
};
|
||||
};
|
||||
p->o = v;
|
||||
};
|
||||
};
|
||||
|
||||
void shootv(int gun, vec &from, vec &to, dynent *d, bool local) // create visual effect from a shot
|
||||
{
|
||||
playsound(guns[gun].sound, d==player1 ? NULL : &d->o);
|
||||
int pspeed = 25;
|
||||
switch(gun)
|
||||
{
|
||||
case GUN_FIST:
|
||||
break;
|
||||
|
||||
case GUN_SG:
|
||||
{
|
||||
loopi(SGRAYS) particle_splash(0, 5, 200, sg[i]);
|
||||
break;
|
||||
};
|
||||
|
||||
case GUN_CG:
|
||||
particle_splash(0, 100, 250, to);
|
||||
//particle_trail(1, 10, from, to);
|
||||
break;
|
||||
|
||||
case GUN_RL:
|
||||
case GUN_FIREBALL:
|
||||
case GUN_ICEBALL:
|
||||
case GUN_SLIMEBALL:
|
||||
pspeed = guns[gun].projspeed;
|
||||
if(d->monsterstate) pspeed /= 2;
|
||||
newprojectile(from, to, (float)pspeed, local, d, gun);
|
||||
break;
|
||||
|
||||
case GUN_RIFLE:
|
||||
particle_splash(0, 50, 200, to);
|
||||
particle_trail(1, 500, from, to);
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
void hitpush(int target, int damage, dynent *d, dynent *at, vec &from, vec &to)
|
||||
{
|
||||
hit(target, damage, d, at);
|
||||
vdist(dist, v, from, to);
|
||||
vmul(v, damage/dist/50);
|
||||
vadd(d->vel, v);
|
||||
};
|
||||
|
||||
void raydamage(dynent *o, vec &from, vec &to, dynent *d, int i)
|
||||
{
|
||||
if(o->state!=CS_ALIVE) return;
|
||||
int qdam = guns[d->gunselect].damage;
|
||||
if(d->quadmillis) qdam *= 4;
|
||||
if(d->monsterstate) qdam /= MONSTERDAMAGEFACTOR;
|
||||
if(d->gunselect==GUN_SG)
|
||||
{
|
||||
int damage = 0;
|
||||
loop(r, SGRAYS) if(intersect(o, from, sg[r])) damage += qdam;
|
||||
if(damage) hitpush(i, damage, o, d, from, to);
|
||||
}
|
||||
else if(intersect(o, from, to)) hitpush(i, qdam, o, d, from, to);
|
||||
};
|
||||
|
||||
void shoot(dynent *d, vec &targ)
|
||||
{
|
||||
int attacktime = lastmillis-d->lastaction;
|
||||
if(attacktime<d->gunwait) return;
|
||||
d->gunwait = 0;
|
||||
if(!d->attacking) return;
|
||||
d->lastaction = lastmillis;
|
||||
d->lastattackgun = d->gunselect;
|
||||
if(!d->ammo[d->gunselect]) { playsoundc(S_NOAMMO); d->gunwait = 250; d->lastattackgun = -1; return; };
|
||||
if(d->gunselect) d->ammo[d->gunselect]--;
|
||||
vec from = d->o;
|
||||
vec to = targ;
|
||||
from.z -= 0.2f; // below eye
|
||||
|
||||
vdist(dist, unitv, from, to);
|
||||
vdiv(unitv, dist);
|
||||
vec kickback = unitv;
|
||||
vmul(kickback, guns[d->gunselect].kickamount*-0.01f);
|
||||
vadd(d->vel, kickback);
|
||||
if(d->pitch<80.0f) d->pitch += guns[d->gunselect].kickamount*0.05f;
|
||||
|
||||
|
||||
if(d->gunselect==GUN_FIST || d->gunselect==GUN_BITE)
|
||||
{
|
||||
vmul(unitv, 3); // punch range
|
||||
to = from;
|
||||
vadd(to, unitv);
|
||||
};
|
||||
if(d->gunselect==GUN_SG) createrays(from, to);
|
||||
|
||||
if(d->quadmillis && attacktime>200) playsoundc(S_ITEMPUP);
|
||||
shootv(d->gunselect, from, to, d, true);
|
||||
if(!d->monsterstate) addmsg(1, 8, SV_SHOT, d->gunselect, (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF));
|
||||
d->gunwait = guns[d->gunselect].attackdelay;
|
||||
|
||||
if(guns[d->gunselect].projspeed) return;
|
||||
|
||||
loopv(players)
|
||||
{
|
||||
dynent *o = players[i];
|
||||
if(!o) continue;
|
||||
raydamage(o, from, to, d, i);
|
||||
};
|
||||
|
||||
dvector &v = getmonsters();
|
||||
loopv(v) if(v[i]!=d) raydamage(v[i], from, to, d, -2);
|
||||
|
||||
if(d->monsterstate) raydamage(player1, from, to, d, -1);
|
||||
};
|
||||
|
||||
|
372
src/world.cpp
Normal file
372
src/world.cpp
Normal file
|
@ -0,0 +1,372 @@
|
|||
// world.cpp: core map management stuff
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
extern char *entnames[]; // lookup from map entities above to strings
|
||||
|
||||
sqr *world = NULL;
|
||||
int sfactor, ssize, cubicsize, mipsize;
|
||||
|
||||
header hdr;
|
||||
|
||||
void settag(int tag, int type) // set all cubes with "tag" to space, if tag is 0 then reset ALL tagged cubes according to type
|
||||
{
|
||||
int maxx = 0, maxy = 0, minx = ssize, miny = ssize;
|
||||
loop(x, ssize) loop(y, ssize)
|
||||
{
|
||||
sqr *s = S(x, y);
|
||||
if(s->tag)
|
||||
{
|
||||
if(tag)
|
||||
{
|
||||
if(tag==s->tag) s->type = SPACE;
|
||||
else continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
s->type = type ? SOLID : SPACE;
|
||||
};
|
||||
if(x>maxx) maxx = x;
|
||||
if(y>maxy) maxy = y;
|
||||
if(x<minx) minx = x;
|
||||
if(y<miny) miny = y;
|
||||
};
|
||||
};
|
||||
block b = { minx, miny, maxx-minx+1, maxy-miny+1 };
|
||||
if(maxx) remip(b); // remip minimal area of changed geometry
|
||||
};
|
||||
|
||||
void resettagareas() { settag(0, 0); }; // reset for editing or map saving
|
||||
void settagareas() { settag(0, 1); loopv(ents) if(ents[i].type==CARROT) setspawn(i, true); }; // set for playing
|
||||
|
||||
void trigger(int tag, int type, bool savegame)
|
||||
{
|
||||
if(!tag) return;
|
||||
settag(tag, type);
|
||||
if(!savegame && type!=3) playsound(S_RUMBLE);
|
||||
sprintf_sd(aliasname)("level_trigger_%d", tag);
|
||||
if(identexists(aliasname)) execute(aliasname);
|
||||
if(type==2) endsp(false);
|
||||
};
|
||||
|
||||
COMMAND(trigger, ARG_2INT);
|
||||
|
||||
// main geometric mipmapping routine, recursively rebuild mipmaps within block b.
|
||||
// tries to produce cube out of 4 lower level mips as well as possible,
|
||||
// sets defer to 0 if mipped cube is a perfect mip, i.e. can be rendered at this
|
||||
// mip level indistinguishable from its constituent cubes (saves considerable
|
||||
// rendering time if this is possible).
|
||||
|
||||
void remip(block &b, int level)
|
||||
{
|
||||
if(level>=SMALLEST_FACTOR) return;
|
||||
int lighterr = getvar("lighterror")*3;
|
||||
sqr *w = wmip[level];
|
||||
sqr *v = wmip[level+1];
|
||||
int ws = ssize>>level;
|
||||
int vs = ssize>>(level+1);
|
||||
block s = b;
|
||||
if(s.x&1) { s.x--; s.xs++; };
|
||||
if(s.y&1) { s.y--; s.ys++; };
|
||||
s.xs = (s.xs+1)&~1;
|
||||
s.ys = (s.ys+1)&~1;
|
||||
for(int x = s.x; x<s.x+s.xs; x+=2) for(int y = s.y; y<s.y+s.ys; y+=2)
|
||||
{
|
||||
sqr *o[4];
|
||||
o[0] = SWS(w,x,y,ws); // the 4 constituent cubes
|
||||
o[1] = SWS(w,x+1,y,ws);
|
||||
o[2] = SWS(w,x+1,y+1,ws);
|
||||
o[3] = SWS(w,x,y+1,ws);
|
||||
sqr *r = SWS(v,x/2,y/2,vs); // the target cube in the higher mip level
|
||||
*r = *o[0];
|
||||
uchar nums[MAXTYPE];
|
||||
loopi(MAXTYPE) nums[i] = 0;
|
||||
loopj(4) nums[o[j]->type]++;
|
||||
r->type = SEMISOLID; // cube contains both solid and space, treated specially in the renderer
|
||||
loopk(MAXTYPE) if(nums[k]==4) r->type = k;
|
||||
if(!SOLID(r))
|
||||
{
|
||||
int floor = 127, ceil = -128, num = 0;
|
||||
loopi(4) if(!SOLID(o[i]))
|
||||
{
|
||||
num++;
|
||||
int fh = o[i]->floor;
|
||||
int ch = o[i]->ceil;
|
||||
if(r->type==SEMISOLID)
|
||||
{
|
||||
if(o[i]->type==FHF) fh -= o[i]->vdelta/4+2; // crap hack, needed for rendering large mips next to hfs
|
||||
if(o[i]->type==CHF) ch += o[i]->vdelta/4+2; // FIXME: needs to somehow take into account middle vertices on higher mips
|
||||
};
|
||||
if(fh<floor) floor = fh; // take lowest floor and highest ceil, so we never have to see missing lower/upper from the side
|
||||
if(ch>ceil) ceil = ch;
|
||||
};
|
||||
r->floor = floor;
|
||||
r->ceil = ceil;
|
||||
};
|
||||
if(r->type==CORNER) goto mip; // special case: don't ever split even if textures etc are different
|
||||
r->defer = 1;
|
||||
if(SOLID(r))
|
||||
{
|
||||
loopi(3)
|
||||
{
|
||||
if(o[i]->wtex!=o[3]->wtex) goto c; // on an all solid cube, only thing that needs to be equal for a perfect mip is the wall texture
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
loopi(3)
|
||||
{
|
||||
if(o[i]->type!=o[3]->type
|
||||
|| o[i]->floor!=o[3]->floor
|
||||
|| o[i]->ceil!=o[3]->ceil
|
||||
|| o[i]->ftex!=o[3]->ftex
|
||||
|| o[i]->ctex!=o[3]->ctex
|
||||
|| abs(o[i+1]->r-o[0]->r)>lighterr // perfect mip even if light is not exactly equal
|
||||
|| abs(o[i+1]->g-o[0]->g)>lighterr
|
||||
|| abs(o[i+1]->b-o[0]->b)>lighterr
|
||||
|| o[i]->utex!=o[3]->utex
|
||||
|| o[i]->wtex!=o[3]->wtex) goto c;
|
||||
};
|
||||
if(r->type==CHF || r->type==FHF) // can make a perfect mip out of a hf if slopes lie on one line
|
||||
{
|
||||
if(o[0]->vdelta-o[1]->vdelta != o[1]->vdelta-SWS(w,x+2,y,ws)->vdelta
|
||||
|| o[0]->vdelta-o[2]->vdelta != o[2]->vdelta-SWS(w,x+2,y+2,ws)->vdelta
|
||||
|| o[0]->vdelta-o[3]->vdelta != o[3]->vdelta-SWS(w,x,y+2,ws)->vdelta
|
||||
|| o[3]->vdelta-o[2]->vdelta != o[2]->vdelta-SWS(w,x+2,y+1,ws)->vdelta
|
||||
|| o[1]->vdelta-o[2]->vdelta != o[2]->vdelta-SWS(w,x+1,y+2,ws)->vdelta) goto c;
|
||||
};
|
||||
};
|
||||
{ loopi(4) if(o[i]->defer) goto c; }; // if any of the constituents is not perfect, then this one isn't either
|
||||
mip:
|
||||
r->defer = 0;
|
||||
c:;
|
||||
};
|
||||
s.x /= 2;
|
||||
s.y /= 2;
|
||||
s.xs /= 2;
|
||||
s.ys /= 2;
|
||||
remip(s, level+1);
|
||||
};
|
||||
|
||||
void remipmore(block &b, int level)
|
||||
{
|
||||
block bb = b;
|
||||
if(bb.x>1) bb.x--;
|
||||
if(bb.y>1) bb.y--;
|
||||
if(bb.xs<ssize-3) bb.xs++;
|
||||
if(bb.ys<ssize-3) bb.ys++;
|
||||
remip(bb, level);
|
||||
};
|
||||
|
||||
int closestent() // used for delent and edit mode ent display
|
||||
{
|
||||
if(noteditmode()) return -1;
|
||||
int best;
|
||||
float bdist = 99999;
|
||||
loopv(ents)
|
||||
{
|
||||
entity &e = ents[i];
|
||||
if(e.type==NOTUSED) continue;
|
||||
vec v = { 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();
|
||||
};
|
||||
|
||||
COMMAND(scalelights, ARG_2INT);
|
||||
|
||||
int findentity(int type, int index)
|
||||
{
|
||||
for(int i = index; i<ents.length(); i++) if(ents[i].type==type) return i;
|
||||
loopj(index) if(ents[j].type==type) return j;
|
||||
return -1;
|
||||
};
|
||||
|
||||
sqr *wmip[LARGEST_FACTOR*2];
|
||||
|
||||
void setupworld(int factor)
|
||||
{
|
||||
ssize = 1<<(sfactor = factor);
|
||||
cubicsize = ssize*ssize;
|
||||
mipsize = cubicsize*134/100;
|
||||
sqr *w = world = (sqr *)alloc(mipsize*sizeof(sqr));
|
||||
loopi(LARGEST_FACTOR*2) { wmip[i] = w; w += cubicsize>>(i*2); };
|
||||
};
|
||||
|
||||
void empty_world(int factor, bool force) // main empty world creation routine, if passed factor -1 will enlarge old world by 1
|
||||
{
|
||||
if(!force && noteditmode()) return;
|
||||
cleardlights();
|
||||
pruneundos();
|
||||
sqr *oldworld = world;
|
||||
bool copy = false;
|
||||
if(oldworld && factor<0) { factor = sfactor+1; copy = true; };
|
||||
if(factor<SMALLEST_FACTOR) factor = SMALLEST_FACTOR;
|
||||
if(factor>LARGEST_FACTOR) factor = LARGEST_FACTOR;
|
||||
setupworld(factor);
|
||||
|
||||
loop(x,ssize) loop(y,ssize)
|
||||
{
|
||||
sqr *s = S(x,y);
|
||||
s->r = s->g = s->b = 150;
|
||||
s->ftex = DEFAULT_FLOOR;
|
||||
s->ctex = DEFAULT_CEIL;
|
||||
s->wtex = s->utex = DEFAULT_WALL;
|
||||
s->type = SOLID;
|
||||
s->floor = 0;
|
||||
s->ceil = 16;
|
||||
s->vdelta = 0;
|
||||
s->defer = 0;
|
||||
};
|
||||
|
||||
strncpy(hdr.head, "CUBE", 4);
|
||||
hdr.version = MAPVERSION;
|
||||
hdr.headersize = sizeof(header);
|
||||
hdr.sfactor = sfactor;
|
||||
|
||||
if(copy)
|
||||
{
|
||||
loop(x,ssize/2) loop(y,ssize/2)
|
||||
{
|
||||
*S(x+ssize/4, y+ssize/4) = *SWS(oldworld, x, y, ssize/2);
|
||||
};
|
||||
loopv(ents)
|
||||
{
|
||||
ents[i].x += ssize/4;
|
||||
ents[i].y += ssize/4;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
strn0cpy(hdr.maptitle, "Untitled Map by Unknown", 128);
|
||||
hdr.waterlevel = -100000;
|
||||
loopi(15) hdr.reserved[i] = 0;
|
||||
loopk(3) loopi(256) hdr.texlists[k][i] = i;
|
||||
ents.setsize(0);
|
||||
block b = { 8, 8, ssize-16, ssize-16 };
|
||||
edittypexy(SPACE, b);
|
||||
};
|
||||
|
||||
calclight();
|
||||
startmap("base/unnamed");
|
||||
if(oldworld)
|
||||
{
|
||||
free(oldworld);
|
||||
toggleedit();
|
||||
execute("fullbright 1");
|
||||
};
|
||||
};
|
||||
|
||||
void mapenlarge() { empty_world(-1, false); };
|
||||
void newmap(int i) { empty_world(i, false); };
|
||||
|
||||
COMMAND(mapenlarge, ARG_NONE);
|
||||
COMMAND(newmap, ARG_1INT);
|
||||
COMMANDN(recalc, calclight, ARG_NONE);
|
||||
COMMAND(delent, ARG_NONE);
|
||||
COMMAND(entproperty, ARG_2INT);
|
||||
|
328
src/worldio.cpp
Normal file
328
src/worldio.cpp
Normal file
|
@ -0,0 +1,328 @@
|
|||
// worldio.cpp: loading & saving of maps and savegames
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
void backup(char *name, char *backupname)
|
||||
{
|
||||
remove(backupname);
|
||||
rename(name, backupname);
|
||||
};
|
||||
|
||||
string cgzname, bakname, pcfname, mcfname;
|
||||
|
||||
void setnames(char *name)
|
||||
{
|
||||
string pakname, mapname;
|
||||
char *slash = strpbrk(name, "/\\");
|
||||
if(slash)
|
||||
{
|
||||
strn0cpy(pakname, name, slash-name+1);
|
||||
strcpy_s(mapname, slash+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy_s(pakname, "base");
|
||||
strcpy_s(mapname, name);
|
||||
};
|
||||
sprintf_s(cgzname)("packages/%s/%s.cgz", pakname, mapname);
|
||||
sprintf_s(bakname)("packages/%s/%s_%d.BAK", pakname, mapname, lastmillis);
|
||||
sprintf_s(pcfname)("packages/%s/package.cfg", pakname);
|
||||
sprintf_s(mcfname)("packages/%s/%s.cfg", pakname, mapname);
|
||||
|
||||
path(cgzname);
|
||||
path(bakname);
|
||||
};
|
||||
|
||||
// the optimize routines below are here to reduce the detrimental effects of messy mapping by
|
||||
// setting certain properties (vdeltas and textures) to neighbouring values wherever there is no
|
||||
// visible difference. This allows the mipmapper to generate more efficient mips.
|
||||
// the reason it is done on save is to reduce the amount spend in the mipmapper (as that is done
|
||||
// in realtime).
|
||||
|
||||
inline bool nhf(sqr *s) { return s->type!=FHF && s->type!=CHF; };
|
||||
|
||||
void voptimize() // reset vdeltas on non-hf cubes
|
||||
{
|
||||
loop(x, ssize) loop(y, ssize)
|
||||
{
|
||||
sqr *s = S(x, y);
|
||||
if(x && y) { if(nhf(s) && nhf(S(x-1, y)) && nhf(S(x-1, y-1)) && nhf(S(x, y-1))) s->vdelta = 0; }
|
||||
else s->vdelta = 0;
|
||||
};
|
||||
};
|
||||
|
||||
void topt(sqr *s, bool &wf, bool &uf, int &wt, int &ut)
|
||||
{
|
||||
sqr *o[4];
|
||||
o[0] = SWS(s,0,-1,ssize);
|
||||
o[1] = SWS(s,0,1,ssize);
|
||||
o[2] = SWS(s,1,0,ssize);
|
||||
o[3] = SWS(s,-1,0,ssize);
|
||||
wf = true;
|
||||
uf = true;
|
||||
if(SOLID(s))
|
||||
{
|
||||
loopi(4) if(!SOLID(o[i]))
|
||||
{
|
||||
wf = false;
|
||||
wt = s->wtex;
|
||||
ut = s->utex;
|
||||
return;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
loopi(4) if(!SOLID(o[i]))
|
||||
{
|
||||
if(o[i]->floor<s->floor) { wt = s->wtex; wf = false; };
|
||||
if(o[i]->ceil>s->ceil) { ut = s->utex; uf = false; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
void toptimize() // FIXME: only does 2x2, make atleast for 4x4 also
|
||||
{
|
||||
bool wf[4], uf[4];
|
||||
sqr *s[4];
|
||||
for(int x = 2; x<ssize-4; x += 2) for(int y = 2; y<ssize-4; y += 2)
|
||||
{
|
||||
s[0] = S(x,y);
|
||||
int wt = s[0]->wtex, ut = s[0]->utex;
|
||||
topt(s[0], wf[0], uf[0], wt, ut);
|
||||
topt(s[1] = SWS(s[0],0,1,ssize), wf[1], uf[1], wt, ut);
|
||||
topt(s[2] = SWS(s[0],1,1,ssize), wf[2], uf[2], wt, ut);
|
||||
topt(s[3] = SWS(s[0],1,0,ssize), wf[3], uf[3], wt, ut);
|
||||
loopi(4)
|
||||
{
|
||||
if(wf[i]) s[i]->wtex = wt;
|
||||
if(uf[i]) s[i]->utex = ut;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// these two are used by getmap/sendmap.. transfers compressed maps directly
|
||||
|
||||
void writemap(char *mname, int msize, uchar *mdata)
|
||||
{
|
||||
setnames(mname);
|
||||
backup(cgzname, bakname);
|
||||
FILE *f = fopen(cgzname, "wb");
|
||||
if(!f) { conoutf("could not write map to %s", cgzname); return; };
|
||||
fwrite(mdata, 1, msize, f);
|
||||
fclose(f);
|
||||
conoutf("wrote map %s as file %s", mname, cgzname);
|
||||
}
|
||||
|
||||
uchar *readmap(char *mname, int *msize)
|
||||
{
|
||||
setnames(mname);
|
||||
uchar *mdata = (uchar *)loadfile(cgzname, msize);
|
||||
if(!mdata) { conoutf("could not read map %s", cgzname); return NULL; };
|
||||
return mdata;
|
||||
}
|
||||
|
||||
// save map as .cgz file. uses 2 layers of compression: first does simple run-length
|
||||
// encoding and leaves out data for certain kinds of cubes, then zlib removes the
|
||||
// last bits of redundancy. Both passes contribute greatly to the miniscule map sizes.
|
||||
|
||||
void save_world(char *mname)
|
||||
{
|
||||
resettagareas(); // wouldn't be able to reproduce tagged areas otherwise
|
||||
voptimize();
|
||||
toptimize();
|
||||
if(!*mname) mname = getclientmap();
|
||||
setnames(mname);
|
||||
backup(cgzname, bakname);
|
||||
gzFile f = gzopen(cgzname, "wb9");
|
||||
if(!f) { conoutf("could not write map to %s", cgzname); return; };
|
||||
hdr.version = MAPVERSION;
|
||||
hdr.numents = 0;
|
||||
loopv(ents) if(ents[i].type!=NOTUSED) hdr.numents++;
|
||||
header tmp = hdr;
|
||||
endianswap(&tmp.version, sizeof(int), 4);
|
||||
endianswap(&tmp.waterlevel, sizeof(int), 16);
|
||||
gzwrite(f, &tmp, sizeof(header));
|
||||
loopv(ents)
|
||||
{
|
||||
if(ents[i].type!=NOTUSED)
|
||||
{
|
||||
entity tmp = ents[i];
|
||||
endianswap(&tmp, sizeof(short), 4);
|
||||
gzwrite(f, &tmp, sizeof(persistent_entity));
|
||||
};
|
||||
};
|
||||
sqr *t = NULL;
|
||||
int sc = 0;
|
||||
#define spurge while(sc) { gzputc(f, 255); if(sc>255) { gzputc(f, 255); sc -= 255; } else { gzputc(f, sc); sc = 0; } };
|
||||
loopk(cubicsize)
|
||||
{
|
||||
sqr *s = &world[k];
|
||||
#define c(f) (s->f==t->f)
|
||||
// 4 types of blocks, to compress a bit:
|
||||
// 255 (2): same as previous block + count
|
||||
// 254 (3): same as previous, except light // deprecated
|
||||
// SOLID (5)
|
||||
// anything else (9)
|
||||
|
||||
if(SOLID(s))
|
||||
{
|
||||
if(t && c(type) && c(wtex) && c(vdelta))
|
||||
{
|
||||
sc++;
|
||||
}
|
||||
else
|
||||
{
|
||||
spurge;
|
||||
gzputc(f, s->type);
|
||||
gzputc(f, s->wtex);
|
||||
gzputc(f, s->vdelta);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
if(t && c(type) && c(floor) && c(ceil) && c(ctex) && c(ftex) && c(utex) && c(wtex) && c(vdelta) && c(tag))
|
||||
{
|
||||
sc++;
|
||||
}
|
||||
else
|
||||
{
|
||||
spurge;
|
||||
gzputc(f, s->type);
|
||||
gzputc(f, s->floor);
|
||||
gzputc(f, s->ceil);
|
||||
gzputc(f, s->wtex);
|
||||
gzputc(f, s->ftex);
|
||||
gzputc(f, s->ctex);
|
||||
gzputc(f, s->vdelta);
|
||||
gzputc(f, s->utex);
|
||||
gzputc(f, s->tag);
|
||||
};
|
||||
};
|
||||
t = s;
|
||||
};
|
||||
spurge;
|
||||
gzclose(f);
|
||||
conoutf("wrote map file %s", cgzname);
|
||||
settagareas();
|
||||
};
|
||||
|
||||
void load_world(char *mname) // still supports all map formats that have existed since the earliest cube betas!
|
||||
{
|
||||
stopifrecording();
|
||||
cleardlights();
|
||||
pruneundos();
|
||||
setnames(mname);
|
||||
gzFile f = gzopen(cgzname, "rb9");
|
||||
if(!f) { conoutf("could not read map %s", cgzname); return; };
|
||||
gzread(f, &hdr, sizeof(header)-sizeof(int)*16);
|
||||
endianswap(&hdr.version, sizeof(int), 4);
|
||||
if(strncmp(hdr.head, "CUBE", 4)!=0) fatal("while reading map: header malformatted");
|
||||
if(hdr.version>MAPVERSION) fatal("this map requires a newer version of cube");
|
||||
if(sfactor<SMALLEST_FACTOR || sfactor>LARGEST_FACTOR) fatal("illegal map size");
|
||||
if(hdr.version>=4)
|
||||
{
|
||||
gzread(f, &hdr.waterlevel, sizeof(int)*16);
|
||||
endianswap(&hdr.waterlevel, sizeof(int), 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
hdr.waterlevel = -100000;
|
||||
};
|
||||
ents.setsize(0);
|
||||
loopi(hdr.numents)
|
||||
{
|
||||
entity &e = ents.add();
|
||||
gzread(f, &e, sizeof(persistent_entity));
|
||||
endianswap(&e, sizeof(short), 4);
|
||||
e.spawned = false;
|
||||
if(e.type==LIGHT)
|
||||
{
|
||||
if(!e.attr2) e.attr2 = 255; // needed for MAPVERSION<=2
|
||||
if(e.attr1>32) e.attr1 = 32; // 12_03 and below
|
||||
};
|
||||
};
|
||||
free(world);
|
||||
setupworld(hdr.sfactor);
|
||||
char texuse[256];
|
||||
loopi(256) texuse[i] = 0;
|
||||
sqr *t = NULL;
|
||||
loopk(cubicsize)
|
||||
{
|
||||
sqr *s = &world[k];
|
||||
int type = gzgetc(f);
|
||||
switch(type)
|
||||
{
|
||||
case 255:
|
||||
{
|
||||
int n = gzgetc(f);
|
||||
for(int i = 0; i<n; i++, k++) memcpy(&world[k], t, sizeof(sqr));
|
||||
k--;
|
||||
break;
|
||||
};
|
||||
case 254: // only in MAPVERSION<=2
|
||||
{
|
||||
memcpy(s, t, sizeof(sqr));
|
||||
s->r = s->g = s->b = gzgetc(f);
|
||||
gzgetc(f);
|
||||
break;
|
||||
};
|
||||
case SOLID:
|
||||
{
|
||||
s->type = SOLID;
|
||||
s->wtex = gzgetc(f);
|
||||
s->vdelta = gzgetc(f);
|
||||
if(hdr.version<=2) { gzgetc(f); gzgetc(f); };
|
||||
s->ftex = DEFAULT_FLOOR;
|
||||
s->ctex = DEFAULT_CEIL;
|
||||
s->utex = s->wtex;
|
||||
s->tag = 0;
|
||||
s->floor = 0;
|
||||
s->ceil = 16;
|
||||
break;
|
||||
};
|
||||
default:
|
||||
{
|
||||
if(type<0 || type>=MAXTYPE)
|
||||
{
|
||||
sprintf_sd(t)("%d @ %d", type, k);
|
||||
fatal("while reading map: type out of range: ", t);
|
||||
};
|
||||
s->type = type;
|
||||
s->floor = gzgetc(f);
|
||||
s->ceil = gzgetc(f);
|
||||
if(s->floor>=s->ceil) s->floor = s->ceil-1; // for pre 12_13
|
||||
s->wtex = gzgetc(f);
|
||||
s->ftex = gzgetc(f);
|
||||
s->ctex = gzgetc(f);
|
||||
if(hdr.version<=2) { gzgetc(f); gzgetc(f); };
|
||||
s->vdelta = gzgetc(f);
|
||||
s->utex = (hdr.version>=2) ? gzgetc(f) : s->wtex;
|
||||
s->tag = (hdr.version>=5) ? gzgetc(f) : 0;
|
||||
s->type = type;
|
||||
};
|
||||
};
|
||||
s->defer = 0;
|
||||
t = s;
|
||||
texuse[s->wtex] = 1;
|
||||
if(!SOLID(s)) texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1;
|
||||
};
|
||||
gzclose(f);
|
||||
calclight();
|
||||
settagareas();
|
||||
int xs, ys;
|
||||
loopi(256) if(texuse) lookuptexture(i, xs, ys);
|
||||
conoutf("read map %s (%d milliseconds)", cgzname, SDL_GetTicks()-lastmillis);
|
||||
conoutf("%s", hdr.maptitle);
|
||||
startmap(mname);
|
||||
loopl(256)
|
||||
{
|
||||
sprintf_sd(aliasname)("level_trigger_%d", l); // can this be done smarter?
|
||||
if(identexists(aliasname)) alias(aliasname, "");
|
||||
};
|
||||
execfile("data/default_map_settings.cfg");
|
||||
execfile(pcfname);
|
||||
execfile(mcfname);
|
||||
};
|
||||
|
||||
COMMANDN(savemap, save_world, ARG_1STR);
|
||||
|
214
src/worldlight.cpp
Normal file
214
src/worldlight.cpp
Normal file
|
@ -0,0 +1,214 @@
|
|||
// worldlight.cpp
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
extern bool hasoverbright;
|
||||
|
||||
VAR(lightscale,1,4,100);
|
||||
|
||||
void lightray(float bx, float by, persistent_entity &light) // done in realtime, needs to be fast
|
||||
{
|
||||
float lx = light.x+(rnd(21)-10)*0.1f;
|
||||
float ly = light.y+(rnd(21)-10)*0.1f;
|
||||
float dx = bx-lx;
|
||||
float dy = by-ly;
|
||||
float dist = (float)sqrt(dx*dx+dy*dy);
|
||||
if(dist<1.0f) return;
|
||||
int reach = light.attr1;
|
||||
int steps = (int)(reach*reach*1.6f/dist); // can change this for speedup/quality?
|
||||
const int PRECBITS = 12;
|
||||
const float PRECF = 4096.0f;
|
||||
int x = (int)(lx*PRECF);
|
||||
int y = (int)(ly*PRECF);
|
||||
int l = light.attr2<<PRECBITS;
|
||||
int stepx = (int)(dx/(float)steps*PRECF);
|
||||
int stepy = (int)(dy/(float)steps*PRECF);
|
||||
int stepl = fast_f2nat(l/(float)steps); // incorrect: light will fade quicker if near edge of the world
|
||||
|
||||
if(hasoverbright)
|
||||
{
|
||||
l /= lightscale;
|
||||
stepl /= lightscale;
|
||||
|
||||
if(light.attr3 || light.attr4) // coloured light version, special case because most lights are white
|
||||
{
|
||||
int dimness = rnd((255-(light.attr2+light.attr3+light.attr4)/3)/16+1);
|
||||
x += stepx*dimness;
|
||||
y += stepy*dimness;
|
||||
|
||||
if(OUTBORD(x>>PRECBITS, y>>PRECBITS)) return;
|
||||
|
||||
int g = light.attr3<<PRECBITS;
|
||||
int stepg = fast_f2nat(g/(float)steps);
|
||||
int b = light.attr4<<PRECBITS;
|
||||
int stepb = fast_f2nat(b/(float)steps);
|
||||
g /= lightscale;
|
||||
stepg /= lightscale;
|
||||
b /= lightscale;
|
||||
stepb /= lightscale;
|
||||
loopi(steps)
|
||||
{
|
||||
sqr *s = S(x>>PRECBITS, y>>PRECBITS);
|
||||
int tl = (l>>PRECBITS)+s->r;
|
||||
s->r = tl>255 ? 255 : tl;
|
||||
tl = (g>>PRECBITS)+s->g;
|
||||
s->g = tl>255 ? 255 : tl;
|
||||
tl = (b>>PRECBITS)+s->b;
|
||||
s->b = tl>255 ? 255 : tl;
|
||||
if(SOLID(s)) return;
|
||||
x += stepx;
|
||||
y += stepy;
|
||||
l -= stepl;
|
||||
g -= stepg;
|
||||
b -= stepb;
|
||||
stepl -= 25;
|
||||
stepg -= 25;
|
||||
stepb -= 25;
|
||||
};
|
||||
}
|
||||
else // white light, special optimized version
|
||||
{
|
||||
int dimness = rnd((255-light.attr2)/16+1);
|
||||
x += stepx*dimness;
|
||||
y += stepy*dimness;
|
||||
|
||||
if(OUTBORD(x>>PRECBITS, y>>PRECBITS)) return;
|
||||
|
||||
loopi(steps)
|
||||
{
|
||||
sqr *s = S(x>>PRECBITS, y>>PRECBITS);
|
||||
int tl = (l>>PRECBITS)+s->r;
|
||||
s->r = s->g = s->b = tl>255 ? 255 : tl;
|
||||
if(SOLID(s)) return;
|
||||
x += stepx;
|
||||
y += stepy;
|
||||
l -= stepl;
|
||||
stepl -= 25;
|
||||
};
|
||||
};
|
||||
}
|
||||
else // the old (white) light code, here for the few people with old video cards that don't support overbright
|
||||
{
|
||||
loopi(steps)
|
||||
{
|
||||
sqr *s = S(x>>PRECBITS, y>>PRECBITS);
|
||||
int light = l>>PRECBITS;
|
||||
if(light>s->r) s->r = s->g = s->b = (uchar)light;
|
||||
if(SOLID(s)) return;
|
||||
x += stepx;
|
||||
y += stepy;
|
||||
l -= stepl;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
void calclightsource(persistent_entity &l)
|
||||
{
|
||||
int reach = l.attr1;
|
||||
int sx = l.x-reach;
|
||||
int ex = l.x+reach;
|
||||
int sy = l.y-reach;
|
||||
int ey = l.y+reach;
|
||||
|
||||
rndreset();
|
||||
|
||||
const float s = 0.8f;
|
||||
|
||||
for(float sx2 = (float)sx; sx2<=ex; sx2+=s*2) { lightray(sx2, (float)sy, l); lightray(sx2, (float)ey, l); };
|
||||
for(float sy2 = sy+s; sy2<=ey-s; sy2+=s*2) { lightray((float)sx, sy2, l); lightray((float)ex, sy2, l); };
|
||||
|
||||
rndtime();
|
||||
};
|
||||
|
||||
void postlightarea(block &a) // median filter, smooths out random noise in light and makes it more mipable
|
||||
{
|
||||
loop(x,a.xs) loop(y,a.ys) // assumes area not on edge of world
|
||||
{
|
||||
sqr *s = S(x+a.x,y+a.y);
|
||||
#define median(m) s->m = (s->m*2 + SW(s,1,0)->m*2 + SW(s,0,1)->m*2 \
|
||||
+ SW(s,-1,0)->m*2 + SW(s,0,-1)->m*2 \
|
||||
+ SW(s,1,1)->m + SW(s,1,-1)->m \
|
||||
+ SW(s,-1,1)->m + SW(s,-1,-1)->m)/14; // median is 4/2/1 instead
|
||||
median(r);
|
||||
median(g);
|
||||
median(b);
|
||||
};
|
||||
|
||||
remip(a);
|
||||
};
|
||||
|
||||
void calclight()
|
||||
{
|
||||
loop(x,ssize) loop(y,ssize)
|
||||
{
|
||||
sqr *s = S(x,y);
|
||||
s->r = s->g = s->b = 10;
|
||||
};
|
||||
|
||||
loopv(ents)
|
||||
{
|
||||
entity &e = ents[i];
|
||||
if(e.type==LIGHT) calclightsource(e);
|
||||
};
|
||||
|
||||
block b = { 1, 1, ssize-2, ssize-2 };
|
||||
postlightarea(b);
|
||||
setvar("fullbright", 0);
|
||||
};
|
||||
|
||||
VARP(dynlight, 0, 16, 32);
|
||||
|
||||
vector<block *> 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);
|
||||
};
|
||||
|
||||
// utility functions also used by editing code
|
||||
|
||||
block *blockcopy(block &s)
|
||||
{
|
||||
block *b = (block *)alloc(sizeof(block)+s.xs*s.ys*sizeof(sqr));
|
||||
*b = s;
|
||||
sqr *q = (sqr *)(b+1);
|
||||
for(int x = s.x; x<s.xs+s.x; x++) for(int y = s.y; y<s.ys+s.y; y++) *q++ = *S(x,y);
|
||||
return b;
|
||||
};
|
||||
|
||||
void blockpaste(block &b)
|
||||
{
|
||||
sqr *q = (sqr *)((&b)+1);
|
||||
for(int x = b.x; x<b.xs+b.x; x++) for(int y = b.y; y<b.ys+b.y; y++) *S(x,y) = *q++;
|
||||
remipmore(b);
|
||||
};
|
||||
|
||||
|
136
src/worldocull.cpp
Normal file
136
src/worldocull.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
// worldocull.cpp: occlusion map and occlusion test
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
#define NUMRAYS 512
|
||||
|
||||
float rdist[NUMRAYS];
|
||||
bool ocull = true;
|
||||
float odist = 256;
|
||||
|
||||
void toggleocull() { ocull = !ocull; };
|
||||
|
||||
COMMAND(toggleocull, ARG_NONE);
|
||||
|
||||
// constructs occlusion map: cast rays in all directions on the 2d plane and record distance.
|
||||
// done exactly once per frame.
|
||||
|
||||
void computeraytable(float vx, float vy)
|
||||
{
|
||||
if(!ocull) return;
|
||||
|
||||
odist = getvar("fog")*1.5f;
|
||||
|
||||
float apitch = (float)fabs(player1->pitch);
|
||||
float af = getvar("fov")/2+apitch/1.5f+3;
|
||||
float byaw = (player1->yaw-90+af)/360*PI2;
|
||||
float syaw = (player1->yaw-90-af)/360*PI2;
|
||||
|
||||
loopi(NUMRAYS)
|
||||
{
|
||||
float angle = i*PI2/NUMRAYS;
|
||||
if((apitch>45 // must be bigger if fov>120
|
||||
|| (angle<byaw && angle>syaw)
|
||||
|| (angle<byaw-PI2 && angle>syaw-PI2)
|
||||
|| (angle<byaw+PI2 && angle>syaw+PI2))
|
||||
&& !OUTBORD(vx, vy)
|
||||
&& !SOLID(S(fast_f2nat(vx), fast_f2nat(vy)))) // try to avoid tracing ray if outside of frustrum
|
||||
{
|
||||
float ray = i*8/(float)NUMRAYS;
|
||||
float dx, dy;
|
||||
if(ray>1 && ray<3) { dx = -(ray-2); dy = 1; }
|
||||
else if(ray>=3 && ray<5) { dx = -1; dy = -(ray-4); }
|
||||
else if(ray>=5 && ray<7) { dx = ray-6; dy = -1; }
|
||||
else { dx = 1; dy = ray>4 ? ray-8 : ray; };
|
||||
float sx = vx;
|
||||
float sy = vy;
|
||||
for(;;)
|
||||
{
|
||||
sx += dx;
|
||||
sy += dy;
|
||||
if(SOLID(S(fast_f2nat(sx), fast_f2nat(sy)))) // 90% of time spend in this function is on this line
|
||||
{
|
||||
rdist[i] = (float)(fabs(sx-vx)+fabs(sy-vy));
|
||||
break;
|
||||
};
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
rdist[i] = 2;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// test occlusion for a cube... one of the most computationally expensive functions in the engine
|
||||
// as its done for every cube and entity, but its effect is more than worth it!
|
||||
|
||||
inline float ca(float x, float y) { return x>y ? y/x : 2-x/y; };
|
||||
inline float ma(float x, float y) { return x==0 ? (y>0 ? 2 : -2) : y/x; };
|
||||
|
||||
int isoccluded(float vx, float vy, float cx, float cy, float csize) // v = viewer, c = cube to test
|
||||
{
|
||||
if(!ocull) return 0;
|
||||
|
||||
float nx = vx, ny = vy; // n = point on the border of the cube that is closest to v
|
||||
if(nx<cx) nx = cx;
|
||||
else if(nx>cx+csize) nx = cx+csize;
|
||||
if(ny<cy) ny = cy;
|
||||
else if(ny>cy+csize) ny = cy+csize;
|
||||
float xdist = (float)fabs(nx-vx);
|
||||
float ydist = (float)fabs(ny-vy);
|
||||
if(xdist>odist || ydist>odist) return 2;
|
||||
float dist = xdist+ydist-1; // 1 needed?
|
||||
|
||||
// ABC
|
||||
// D E
|
||||
// FGH
|
||||
|
||||
// - check middle cube? BG
|
||||
|
||||
// find highest and lowest angle in the occlusion map that this cube spans, based on its most left and right
|
||||
// points on the border from the viewer pov... I see no easier way to do this than this silly code below
|
||||
|
||||
float h, l;
|
||||
if(cx<=vx) // ABDFG
|
||||
{
|
||||
if(cx+csize<vx) // ADF
|
||||
{
|
||||
if(cy<=vy) // AD
|
||||
{
|
||||
if(cy+csize<vy) { h = ca(-(cx-vx), -(cy+csize-vy))+4; l = ca(-(cx+csize-vx), -(cy-vy))+4; } // A
|
||||
else { h = ma(-(cx+csize-vx), -(cy+csize-vy))+4; l = ma(-(cx+csize-vx), -(cy-vy))+4; } // D
|
||||
}
|
||||
else { h = ca(cy+csize-vy, -(cx+csize-vx))+2; l = ca(cy-vy, -(cx-vx))+2; }; // F
|
||||
}
|
||||
else // BG
|
||||
{
|
||||
if(cy<=vy)
|
||||
{
|
||||
if(cy+csize<vy) { h = ma(-(cy+csize-vy), cx-vx)+6; l = ma(-(cy+csize-vy), cx+csize-vx)+6; } // B
|
||||
else return 0;
|
||||
}
|
||||
else { h = ma(cy-vy, -(cx+csize-vx))+2; l = ma(cy-vy, -(cx-vx))+2; }; // G
|
||||
};
|
||||
}
|
||||
else // CEH
|
||||
{
|
||||
if(cy<=vy) // CE
|
||||
{
|
||||
if(cy+csize<vy) { h = ca(-(cy-vy), cx-vx)+6; l = ca(-(cy+csize-vy), cx+csize-vx)+6; } // C
|
||||
else { h = ma(cx-vx, cy-vy); l = ma(cx-vx, cy+csize-vy); }; // E
|
||||
}
|
||||
else { h = ca(cx+csize-vx, cy-vy); l = ca(cx-vx, cy+csize-vy); }; // H
|
||||
};
|
||||
int si = fast_f2nat(h*(NUMRAYS/8))+NUMRAYS; // get indexes into occlusion map from angles
|
||||
int ei = fast_f2nat(l*(NUMRAYS/8))+NUMRAYS+1;
|
||||
if(ei<=si) ei += NUMRAYS;
|
||||
|
||||
for(int i = si; i<=ei; i++)
|
||||
{
|
||||
if(dist<rdist[i&(NUMRAYS-1)]) return 0; // if any value in this segment of the occlusion map is further away then cube is not occluded
|
||||
};
|
||||
|
||||
return 1; // cube is entirely occluded
|
||||
};
|
||||
|
290
src/worldrender.cpp
Normal file
290
src/worldrender.cpp
Normal file
|
@ -0,0 +1,290 @@
|
|||
// worldrender.cpp: goes through all cubes in top down quad tree fashion, determines what has to
|
||||
// be rendered and how (depending on neighbouring cubes), then calls functions in rendercubes.cpp
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
void render_wall(sqr *o, sqr *s, int x1, int y1, int x2, int y2, int mip, sqr *d1, sqr *d2, bool topleft)
|
||||
{
|
||||
if(SOLID(o) || o->type==SEMISOLID)
|
||||
{
|
||||
float c1 = s->floor;
|
||||
float c2 = s->floor;
|
||||
if(s->type==FHF) { c1 -= d1->vdelta/4.0f; c2 -= d2->vdelta/4.0f; };
|
||||
float f1 = s->ceil;
|
||||
float f2 = s->ceil;
|
||||
if(s->type==CHF) { f1 += d1->vdelta/4.0f; f2 += d2->vdelta/4.0f; };
|
||||
//if(f1-c1<=0 && f2-c2<=0) return;
|
||||
render_square(o->wtex, c1, c2, f1, f2, x1<<mip, y1<<mip, x2<<mip, y2<<mip, 1<<mip, d1, d2, topleft);
|
||||
return;
|
||||
};
|
||||
{
|
||||
float f1 = s->floor;
|
||||
float f2 = s->floor;
|
||||
float c1 = o->floor;
|
||||
float c2 = o->floor;
|
||||
if(o->type==FHF && s->type!=FHF)
|
||||
{
|
||||
c1 -= d1->vdelta/4.0f;
|
||||
c2 -= d2->vdelta/4.0f;
|
||||
}
|
||||
if(s->type==FHF && o->type!=FHF)
|
||||
{
|
||||
f1 -= d1->vdelta/4.0f;
|
||||
f2 -= d2->vdelta/4.0f;
|
||||
}
|
||||
if(f1>=c1 && f2>=c2) goto skip;
|
||||
render_square(o->wtex, f1, f2, c1, c2, x1<<mip, y1<<mip, x2<<mip, y2<<mip, 1<<mip, d1, d2, topleft);
|
||||
};
|
||||
skip:
|
||||
{
|
||||
float f1 = o->ceil;
|
||||
float f2 = o->ceil;
|
||||
float c1 = s->ceil;
|
||||
float c2 = s->ceil;
|
||||
if(o->type==CHF && s->type!=CHF)
|
||||
{
|
||||
f1 += d1->vdelta/4.0f;
|
||||
f2 += d2->vdelta/4.0f;
|
||||
}
|
||||
else if(s->type==CHF && o->type!=CHF)
|
||||
{
|
||||
c1 += d1->vdelta/4.0f;
|
||||
c2 += d2->vdelta/4.0f;
|
||||
}
|
||||
if(c1<=f1 && c2<=f2) return;
|
||||
render_square(o->utex, f1, f2, c1, c2, x1<<mip, y1<<mip, x2<<mip, y2<<mip, 1<<mip, d1, d2, topleft);
|
||||
};
|
||||
};
|
||||
|
||||
const int MAX_MIP = 5; // 32x32 unit blocks
|
||||
const int MIN_LOD = 2;
|
||||
const int LOW_LOD = 25;
|
||||
const int MAX_LOD = 1000;
|
||||
|
||||
int lod = 40, lodtop, lodbot, lodleft, lodright;
|
||||
int min_lod;
|
||||
|
||||
int stats[LARGEST_FACTOR];
|
||||
|
||||
// detect those cases where a higher mip solid has a visible wall next to lower mip cubes
|
||||
// (used for wall rendering below)
|
||||
|
||||
bool issemi(int mip, int x, int y, int x1, int y1, int x2, int y2)
|
||||
{
|
||||
if(!(mip--)) return true;
|
||||
sqr *w = wmip[mip];
|
||||
int msize = ssize>>mip;
|
||||
x *= 2;
|
||||
y *= 2;
|
||||
switch(SWS(w, x+x1, y+y1, msize)->type)
|
||||
{
|
||||
case SEMISOLID: if(issemi(mip, x+x1, y+y1, x1, y1, x2, y2)) return true;
|
||||
case CORNER:
|
||||
case SOLID: break;
|
||||
default: return true;
|
||||
};
|
||||
switch(SWS(w, x+x2, y+y2, msize)->type)
|
||||
{
|
||||
case SEMISOLID: if(issemi(mip, x+x2, y+y2, x1, y1, x2, y2)) return true;
|
||||
case CORNER:
|
||||
case SOLID: break;
|
||||
default: return true;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
bool render_floor, render_ceil;
|
||||
|
||||
// the core recursive function, renders a rect of cubes at a certain mip level from a viewer perspective
|
||||
// call itself for lower mip levels, on most modern machines however this function will use the higher
|
||||
// mip levels only for perfect mips.
|
||||
|
||||
void render_seg_new(float vx, float vy, float vh, int mip, int x, int y, int xs, int ys)
|
||||
{
|
||||
sqr *w = wmip[mip];
|
||||
int sz = ssize>>mip;
|
||||
int vxx = ((int)vx+(1<<mip)/2)>>mip;
|
||||
int vyy = ((int)vy+(1<<mip)/2)>>mip;
|
||||
int lx = vxx-lodleft; // these mark the rect inside the current rest that we want to render using a lower mip level
|
||||
int ly = vyy-lodtop;
|
||||
int rx = vxx+lodright;
|
||||
int ry = vyy+lodbot;
|
||||
|
||||
float fsize = (float)(1<<mip);
|
||||
for(int ox = x; ox<xs; ox++) for(int oy = y; oy<ys; oy++) // first collect occlusion information for this block
|
||||
{
|
||||
SWS(w,ox,oy,sz)->occluded = isoccluded(player1->o.x, player1->o.y, (float)(ox<<mip), (float)(oy<<mip), fsize);
|
||||
};
|
||||
|
||||
int pvx = (int)vx>>mip;
|
||||
int pvy = (int)vy>>mip;
|
||||
if(pvx>=0 && pvy>=0 && pvx<sz && pvy<sz)
|
||||
{
|
||||
//SWS(w,vxx,vyy,sz)->occluded = 0;
|
||||
SWS(w, pvx, pvy, sz)->occluded = 0; // player cell never occluded
|
||||
};
|
||||
|
||||
#define df(x) s->floor-(x->vdelta/4.0f)
|
||||
#define dc(x) s->ceil+(x->vdelta/4.0f)
|
||||
|
||||
// loop through the rect 3 times (for floor/ceil/walls seperately, to facilitate dynamic stripify)
|
||||
// for each we skip occluded cubes (occlusion at higher mip levels is a big time saver!).
|
||||
// during the first loop (ceil) we collect cubes that lie within the lower mip rect and are
|
||||
// also deferred, and render them recursively. Anything left (perfect mips and higher lods) we
|
||||
// render here.
|
||||
|
||||
#define LOOPH {for(int xx = x; xx<xs; xx++) for(int yy = y; yy<ys; yy++) { \
|
||||
sqr *s = SWS(w,xx,yy,sz); if(s->occluded==1) continue; \
|
||||
if(s->defer && !s->occluded && mip && xx>=lx && xx<rx && yy>=ly && yy<ry)
|
||||
#define LOOPD sqr *t = SWS(s,1,0,sz); \
|
||||
sqr *u = SWS(s,1,1,sz); \
|
||||
sqr *v = SWS(s,0,1,sz); \
|
||||
|
||||
LOOPH // ceils
|
||||
{
|
||||
int start = yy;
|
||||
sqr *next;
|
||||
while(yy<ys-1 && (next = SWS(w,xx,yy+1,sz))->defer && !next->occluded) yy++; // collect 2xN rect of lower mip
|
||||
render_seg_new(vx, vy, vh, mip-1, xx*2, start*2, xx*2+2, yy*2+2);
|
||||
continue;
|
||||
};
|
||||
stats[mip]++;
|
||||
LOOPD
|
||||
if((s->type==SPACE || s->type==FHF) && s->ceil>=vh && render_ceil)
|
||||
render_flat(s->ctex, xx<<mip, yy<<mip, 1<<mip, s->ceil, s, t, u, v, true);
|
||||
if(s->type==CHF) //if(s->ceil>=vh)
|
||||
render_flatdelta(s->ctex, xx<<mip, yy<<mip, 1<<mip, dc(s), dc(t), dc(u), dc(v), s, t, u, v, true);
|
||||
}};
|
||||
|
||||
LOOPH continue; // floors
|
||||
LOOPD
|
||||
if((s->type==SPACE || s->type==CHF) && s->floor<=vh && render_floor)
|
||||
{
|
||||
render_flat(s->ftex, xx<<mip, yy<<mip, 1<<mip, s->floor, s, t, u, v, false);
|
||||
if(s->floor<hdr.waterlevel && !SOLID(s)) addwaterquad(xx<<mip, yy<<mip, 1<<mip);
|
||||
};
|
||||
if(s->type==FHF)
|
||||
{
|
||||
render_flatdelta(s->ftex, xx<<mip, yy<<mip, 1<<mip, df(s), df(t), df(u), df(v), s, t, u, v, false);
|
||||
if(s->floor-s->vdelta/4.0f<hdr.waterlevel && !SOLID(s)) addwaterquad(xx<<mip, yy<<mip, 1<<mip);
|
||||
};
|
||||
}};
|
||||
|
||||
LOOPH continue; // walls
|
||||
LOOPD
|
||||
// w
|
||||
// zSt
|
||||
// vu
|
||||
|
||||
sqr *w = SWS(s,0,-1,sz);
|
||||
sqr *z = SWS(s,-1,0,sz);
|
||||
bool normalwall = true;
|
||||
|
||||
if(s->type==CORNER)
|
||||
{
|
||||
// cull also
|
||||
bool topleft = true;
|
||||
sqr *h1 = NULL;
|
||||
sqr *h2 = NULL;
|
||||
if(SOLID(z))
|
||||
{
|
||||
if(SOLID(w)) { render_wall(w, h2 = s, xx+1, yy, xx, yy+1, mip, t, v, false); topleft = false; }
|
||||
else if(SOLID(v)) { render_wall(v, h2 = s, xx, yy, xx+1, yy+1, mip, s, u, false); };
|
||||
}
|
||||
else if(SOLID(t))
|
||||
{
|
||||
if(SOLID(w)) { render_wall(w, h1 = s, xx+1, yy+1, xx, yy, mip, u, s, false); }
|
||||
else if(SOLID(v)) { render_wall(v, h1 = s, xx, yy+1, xx+1, yy, mip, v, t, false); topleft = false; };
|
||||
}
|
||||
else
|
||||
{
|
||||
normalwall = false;
|
||||
bool wv = w->ceil-w->floor < v->ceil-v->floor;
|
||||
if(z->ceil-z->floor < t->ceil-t->floor)
|
||||
{
|
||||
if(wv) { render_wall(h1 = s, h2 = v, xx+1, yy, xx, yy+1, mip, t, v, false); topleft = false; }
|
||||
else { render_wall(h1 = s, h2 = w, xx, yy, xx+1, yy+1, mip, s, u, false); };
|
||||
}
|
||||
else
|
||||
{
|
||||
if(wv) { render_wall(h2 = s, h1 = v, xx+1, yy+1, xx, yy, mip, u, s, false); }
|
||||
else { render_wall(h2 = s, h1 = w, xx, yy+1, xx+1, yy, mip, v, t, false); topleft = false; };
|
||||
};
|
||||
};
|
||||
render_tris(xx<<mip, yy<<mip, 1<<mip, topleft, h1, h2, s, t, u, v);
|
||||
}
|
||||
|
||||
if(normalwall)
|
||||
{
|
||||
bool inner = xx!=sz-1 && yy!=sz-1;
|
||||
|
||||
if(xx>=vxx && xx!=0 && yy!=sz-1 && !SOLID(z) && (!SOLID(s) || z->type!=CORNER)
|
||||
&& (z->type!=SEMISOLID || issemi(mip, xx-1, yy, 1, 0, 1, 1)))
|
||||
render_wall(s, z, xx, yy, xx, yy+1, mip, s, v, true);
|
||||
if(xx<=vxx && inner && !SOLID(t) && (!SOLID(s) || t->type!=CORNER)
|
||||
&& (t->type!=SEMISOLID || issemi(mip, xx+1, yy, 0, 0, 0, 1)))
|
||||
render_wall(s, t, xx+1, yy, xx+1, yy+1, mip, t, u, false);
|
||||
if(yy>=vyy && yy!=0 && xx!=sz-1 && !SOLID(w) && (!SOLID(s) || w->type!=CORNER)
|
||||
&& (w->type!=SEMISOLID || issemi(mip, xx, yy-1, 0, 1, 1, 1)))
|
||||
render_wall(s, w, xx, yy, xx+1, yy, mip, s, t, false);
|
||||
if(yy<=vyy && inner && !SOLID(v) && (!SOLID(s) || v->type!=CORNER)
|
||||
&& (v->type!=SEMISOLID || issemi(mip, xx, yy+1, 0, 0, 1, 0)))
|
||||
render_wall(s, v, xx, yy+1, xx+1, yy+1, mip, v, u, true);
|
||||
};
|
||||
}};
|
||||
|
||||
};
|
||||
|
||||
void distlod(int &low, int &high, int angle, float widef)
|
||||
{
|
||||
float f = 90.0f/lod/widef;
|
||||
low = (int)((90-angle)/f);
|
||||
high = (int)(angle/f);
|
||||
if(low<min_lod) low = min_lod;
|
||||
if(high<min_lod) high = min_lod;
|
||||
};
|
||||
|
||||
// does some out of date view frustrum optimisation that doesn't contribute much anymore
|
||||
|
||||
void render_world(float vx, float vy, float vh, int yaw, int pitch, float fov, int w, int h)
|
||||
{
|
||||
loopi(LARGEST_FACTOR) stats[i] = 0;
|
||||
min_lod = MIN_LOD+abs(pitch)/12;
|
||||
yaw = 360-yaw;
|
||||
float widef = fov/75.0f;
|
||||
int cdist = abs(yaw%90-45);
|
||||
if(cdist<7) // hack to avoid popup at high fovs at 45 yaw
|
||||
{
|
||||
min_lod = max(min_lod, (int)(MIN_LOD+(10-cdist)/1.0f*widef)); // less if lod worked better
|
||||
widef = 1.0f;
|
||||
};
|
||||
lod = MAX_LOD;
|
||||
lodtop = lodbot = lodleft = lodright = min_lod;
|
||||
if(yaw>45 && yaw<=135)
|
||||
{
|
||||
lodleft = lod;
|
||||
distlod(lodtop, lodbot, yaw-45, widef);
|
||||
}
|
||||
else if(yaw>135 && yaw<=225)
|
||||
{
|
||||
lodbot = lod;
|
||||
distlod(lodleft, lodright, yaw-135, widef);
|
||||
}
|
||||
else if(yaw>225 && yaw<=315)
|
||||
{
|
||||
lodright = lod;
|
||||
distlod(lodbot, lodtop, yaw-225, widef);
|
||||
}
|
||||
else
|
||||
{
|
||||
lodtop = lod;
|
||||
distlod(lodright, lodleft, yaw<=45 ? yaw+45 : yaw-315, widef);
|
||||
};
|
||||
float hyfov = fov*h/w/2;
|
||||
render_floor = pitch<hyfov;
|
||||
render_ceil = -pitch<hyfov;
|
||||
|
||||
render_seg_new(vx, vy, vh, MAX_MIP, 0, 0, ssize>>MAX_MIP, ssize>>MAX_MIP);
|
||||
mipstats(stats[0], stats[1], stats[2]);
|
||||
};
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue